mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-22 08:56:35 +02:00
Compare commits
No commits in common. "openssl" and "v0.3.0-1stbinrelease" have entirely different histories.
openssl
...
v0.3.0-1st
446 changed files with 39310 additions and 76892 deletions
|
@ -1,2 +0,0 @@
|
|||
((c++-mode . ((indent-tabs-mode . t)))
|
||||
(c-mode . ((mode . c++))))
|
|
@ -1,39 +0,0 @@
|
|||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# Unix style files
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[Makefile,Makefile.*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.cmd]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = crlf
|
||||
|
||||
[*.{h,cpp}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.rc]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{md,markdown}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.patch]
|
||||
trim_trailing_whitespace = false
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
/build/build_mingw.cmd eol=crlf
|
61
.github/workflows/build-deb.yml
vendored
61
.github/workflows/build-deb.yml
vendored
|
@ -1,61 +0,0 @@
|
|||
name: Build Debian packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build-deb.yml
|
||||
- contrib/**
|
||||
- daemon/**
|
||||
- debian/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Makefile
|
||||
- Makefile.linux
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.dist }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dist: ['buster', 'bullseye', 'bookworm']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit Hash
|
||||
id: commit
|
||||
uses: prompt/actions-commit-hash@v3.0.0
|
||||
|
||||
- name: Build package
|
||||
uses: jtdor/build-deb-action@v1
|
||||
with:
|
||||
docker-image: debian:${{ matrix.dist }}-slim
|
||||
buildpackage-opts: --build=binary --no-sign
|
||||
before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build"
|
||||
extra-build-deps: devscripts git
|
||||
|
||||
- name: Upload package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd_${{ matrix.dist }}
|
||||
path: debian/artifacts/i2pd_*.deb
|
||||
|
||||
- name: Upload debugging symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-dbgsym_${{ matrix.dist }}
|
||||
path: debian/artifacts/i2pd-dbgsym_*.deb
|
50
.github/workflows/build-freebsd.yml
vendored
50
.github/workflows/build-freebsd.yml
vendored
|
@ -1,50 +0,0 @@
|
|||
name: Build on FreeBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build-freebsd.yml
|
||||
- build/CMakeLists.txt
|
||||
- build/cmake_modules/**
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Makefile
|
||||
- Makefile.bsd
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: with UPnP
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test in FreeBSD
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
mem: 2048
|
||||
sync: rsync
|
||||
copyback: true
|
||||
prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc
|
||||
run: |
|
||||
cd build
|
||||
cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release .
|
||||
gmake -j2
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-freebsd
|
||||
path: build/i2pd
|
45
.github/workflows/build-osx.yml
vendored
45
.github/workflows/build-osx.yml
vendored
|
@ -1,45 +0,0 @@
|
|||
name: Build on OSX
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build-osx.yml
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Makefile
|
||||
- Makefile.homebrew
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: With USE_UPNP=${{ matrix.with_upnp }}
|
||||
runs-on: macOS-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
with_upnp: ['yes', 'no']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install required formulae
|
||||
run: |
|
||||
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
|
||||
brew update
|
||||
brew install boost miniupnpc openssl@1.1
|
||||
|
||||
- name: List installed formulae
|
||||
run: brew list
|
||||
|
||||
- name: Build application
|
||||
run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3
|
|
@ -1,80 +0,0 @@
|
|||
name: Build on Windows with MSVC
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build-windows-msvc.yml
|
||||
- build/CMakeLists.txt
|
||||
- build/cmake_modules/**
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Win32/**
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
boost_path: ${{ github.workspace }}\boost_1_83_0
|
||||
openssl_path: ${{ github.workspace }}\openssl_3_2_1
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build and install zlib
|
||||
run: |
|
||||
powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)"
|
||||
powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings
|
||||
set BUILD_TYPE=Debug
|
||||
./install_zlib.bat
|
||||
set BUILD_TYPE=Release
|
||||
./install_zlib.bat
|
||||
del install_zlib.bat
|
||||
|
||||
- name: Install Boost
|
||||
run: |
|
||||
powershell -Command "(Start-BitsTransfer -Source https://sourceforge.net/projects/boost/files/boost-binaries/1.83.0/boost_1_83_0-msvc-14.3-64.exe/download -Destination boost_1_83_0-msvc-14.3-64.exe)"
|
||||
./boost_1_83_0-msvc-14.3-64.exe /DIR="${{env.boost_path}}" /VERYSILENT /SUPPRESSMSGBOXES /SP-
|
||||
|
||||
- name: Install OpenSSL
|
||||
run: |
|
||||
powershell -Command "(Start-BitsTransfer -Source https://slproweb.com/download/Win64OpenSSL-3_2_1.exe -Destination Win64OpenSSL-3_2_1.exe)"
|
||||
./Win64OpenSSL-3_2_1.exe /DIR="${{env.openssl_path}}" /TASKS="copytobin" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
|
||||
|
||||
- name: Make copy of the OpenSSL libraries for CMake
|
||||
run: |
|
||||
dir ${{ github.workspace }}
|
||||
dir ${{env.openssl_path}}\lib\VC
|
||||
dir ${{env.openssl_path}}\lib\VC\x64\
|
||||
dir ${{env.openssl_path}}\lib\VC\x64\MTd\
|
||||
xcopy /s /y "${{env.openssl_path}}\lib\VC\x64\MTd" "${{env.openssl_path}}\lib"
|
||||
|
||||
- name: Configure
|
||||
working-directory: build
|
||||
run: cmake -DBoost_ROOT="${{env.boost_path}}" -DOPENSSL_ROOT_DIR="${{env.openssl_path}}" -DWITH_STATIC=ON .
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
run: cmake --build . --config Debug -- -m
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-msvc
|
||||
path: build/Debug/i2pd.*
|
||||
|
250
.github/workflows/build-windows.yml
vendored
250
.github/workflows/build-windows.yml
vendored
|
@ -1,250 +0,0 @@
|
|||
name: Build on Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build-windows.yml
|
||||
- build/CMakeLists.txt
|
||||
- build/cmake_modules/**
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Win32/**
|
||||
- Makefile
|
||||
- Makefile.mingw
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.arch }}
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: [
|
||||
{ msystem: UCRT64, arch: ucrt-x86_64, arch_short: x64-ucrt, compiler: gcc },
|
||||
{ msystem: CLANG64, arch: clang-x86_64, arch_short: x64-clang, compiler: clang },
|
||||
{ msystem: MINGW64, arch: x86_64, arch_short: x64, compiler: gcc },
|
||||
{ msystem: MINGW32, arch: i686, arch_short: x86, compiler: gcc }
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
install: base-devel git mingw-w64-${{ matrix.arch }}-${{ matrix.compiler }} mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc
|
||||
update: true
|
||||
|
||||
- name: Install additional clang packages
|
||||
if: ${{ matrix.msystem == 'CLANG64' }}
|
||||
run: pacman --noconfirm -S mingw-w64-${{ matrix.arch }}-gcc-compat
|
||||
|
||||
- name: Build application
|
||||
run: |
|
||||
mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon
|
||||
make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes -j3
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-${{ matrix.arch_short }}.exe
|
||||
path: i2pd.exe
|
||||
|
||||
build-cmake:
|
||||
name: CMake ${{ matrix.arch }}
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: [
|
||||
{ msystem: UCRT64, arch: ucrt-x86_64, arch_short: x64-ucrt, compiler: gcc },
|
||||
{ msystem: CLANG64, arch: clang-x86_64, arch_short: x64-clang, compiler: clang },
|
||||
{ msystem: MINGW64, arch: x86_64, arch_short: x64, compiler: gcc },
|
||||
{ msystem: MINGW32, arch: i686, arch_short: x86, compiler: gcc }
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
install: base-devel git mingw-w64-${{ matrix.arch }}-cmake mingw-w64-${{ matrix.arch }}-ninja mingw-w64-${{ matrix.arch }}-${{ matrix.compiler }} mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc
|
||||
update: true
|
||||
|
||||
- name: Build application
|
||||
run: |
|
||||
cd build
|
||||
cmake -DWITH_GIT_VERSION=ON -DWITH_STATIC=ON -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release .
|
||||
cmake --build . -- -j3
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-cmake-${{ matrix.arch_short }}.exe
|
||||
path: build/i2pd.exe
|
||||
|
||||
build-xp:
|
||||
name: XP
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW32
|
||||
install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-miniupnpc
|
||||
cache: true
|
||||
update: true
|
||||
|
||||
- name: Clone MinGW packages repository and revert boost to 1.85.0
|
||||
run: |
|
||||
git clone https://github.com/msys2/MINGW-packages
|
||||
cd MINGW-packages
|
||||
git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost
|
||||
cd mingw-w64-boost
|
||||
sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD
|
||||
|
||||
# headers
|
||||
- name: Get headers package version
|
||||
id: version-headers
|
||||
run: |
|
||||
echo "version=$(pacman -Si mingw-w64-i686-headers-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
|
||||
- name: Cache headers package
|
||||
uses: actions/cache@v4
|
||||
id: cache-headers
|
||||
with:
|
||||
path: MINGW-packages/mingw-w64-headers-git/*.zst
|
||||
key: winxp-headers-${{ steps.version-headers.outputs.version }}
|
||||
- name: Build WinXP-capable headers package
|
||||
if: steps.cache-headers.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd MINGW-packages/mingw-w64-headers-git
|
||||
sed -i 's/0x601/0x501/' PKGBUILD
|
||||
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
|
||||
- name: Install headers package
|
||||
run: pacman --noconfirm -U MINGW-packages/mingw-w64-headers-git/mingw-w64-i686-*-any.pkg.tar.zst
|
||||
|
||||
# CRT
|
||||
- name: Get crt package version
|
||||
id: version-crt
|
||||
run: |
|
||||
echo "version=$(pacman -Si mingw-w64-i686-crt-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
|
||||
- name: Cache crt package
|
||||
uses: actions/cache@v4
|
||||
id: cache-crt
|
||||
with:
|
||||
path: MINGW-packages/mingw-w64-crt-git/*.zst
|
||||
key: winxp-crt-${{ steps.version-crt.outputs.version }}
|
||||
- name: Build WinXP-capable crt package
|
||||
if: steps.cache-crt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd MINGW-packages/mingw-w64-crt-git
|
||||
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
|
||||
- name: Install crt package
|
||||
run: pacman --noconfirm -U MINGW-packages/mingw-w64-crt-git/mingw-w64-i686-*-any.pkg.tar.zst
|
||||
|
||||
# winpthreads
|
||||
- name: Get winpthreads package version
|
||||
id: version-winpthreads
|
||||
run: |
|
||||
echo "version=$(pacman -Si mingw-w64-i686-winpthreads-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
|
||||
- name: Cache winpthreads package
|
||||
uses: actions/cache@v4
|
||||
id: cache-winpthreads
|
||||
with:
|
||||
path: MINGW-packages/mingw-w64-winpthreads-git/*.zst
|
||||
key: winxp-winpthreads-${{ steps.version-winpthreads.outputs.version }}
|
||||
- name: Build WinXP-capable winpthreads package
|
||||
if: steps.cache-winpthreads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd MINGW-packages/mingw-w64-winpthreads-git
|
||||
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
|
||||
- name: Install winpthreads package
|
||||
run: pacman --noconfirm -U MINGW-packages/mingw-w64-winpthreads-git/mingw-w64-i686-*-any.pkg.tar.zst
|
||||
|
||||
# OpenSSL
|
||||
- name: Get openssl package version
|
||||
id: version-openssl
|
||||
run: |
|
||||
echo "version=$(pacman -Si mingw-w64-i686-openssl | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
|
||||
- name: Cache openssl package
|
||||
uses: actions/cache@v4
|
||||
id: cache-openssl
|
||||
with:
|
||||
path: MINGW-packages/mingw-w64-openssl/*.zst
|
||||
key: winxp-openssl-${{ steps.version-openssl.outputs.version }}
|
||||
- name: Build WinXP-capable openssl package
|
||||
if: steps.cache-openssl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd MINGW-packages/mingw-w64-openssl
|
||||
gpg --recv-keys D894E2CE8B3D79F5
|
||||
gpg --recv-keys 216094DFD0CB81EF
|
||||
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
|
||||
- name: Install openssl package
|
||||
run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst
|
||||
|
||||
# Boost
|
||||
#- name: Get boost package version
|
||||
# id: version-boost
|
||||
# run: |
|
||||
# echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
|
||||
- name: Cache boost package
|
||||
uses: actions/cache@v4
|
||||
id: cache-boost
|
||||
with:
|
||||
path: MINGW-packages/mingw-w64-boost/*.zst
|
||||
key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }}
|
||||
# Rebuild package if packages above has changed
|
||||
- name: Build WinXP-capable boost package
|
||||
if: steps.cache-boost.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd MINGW-packages/mingw-w64-boost
|
||||
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
|
||||
- name: Remove boost packages
|
||||
run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs
|
||||
- name: Install boost package
|
||||
run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst
|
||||
|
||||
# Building i2pd
|
||||
- name: Build application
|
||||
run: |
|
||||
mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon
|
||||
make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes USE_WINXP_FLAGS=yes -j3
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i2pd-xp.exe
|
||||
path: i2pd.exe
|
67
.github/workflows/build.yml
vendored
67
.github/workflows/build.yml
vendored
|
@ -1,67 +0,0 @@
|
|||
name: Build on Ubuntu
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- .github/workflows/build.yml
|
||||
- build/CMakeLists.txt
|
||||
- build/cmake_modules/**
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Makefile
|
||||
- Makefile.linux
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-make:
|
||||
name: Make with USE_UPNP=${{ matrix.with_upnp }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
with_upnp: ['yes', 'no']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: install packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
|
||||
|
||||
- name: build application
|
||||
run: make USE_UPNP=${{ matrix.with_upnp }} -j3
|
||||
|
||||
build-cmake:
|
||||
name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
with_upnp: ['ON', 'OFF']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: install packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
|
||||
|
||||
- name: build application
|
||||
run: |
|
||||
cd build
|
||||
cmake -DWITH_UPNP=${{ matrix.with_upnp }} .
|
||||
make -j3
|
140
.github/workflows/docker.yml
vendored
140
.github/workflows/docker.yml
vendored
|
@ -1,140 +0,0 @@
|
|||
name: Build containers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- openssl
|
||||
- docker
|
||||
paths:
|
||||
- .github/workflows/docker.yml
|
||||
- contrib/docker/**
|
||||
- contrib/certificates/**
|
||||
- daemon/**
|
||||
- i18n/**
|
||||
- libi2pd/**
|
||||
- libi2pd_client/**
|
||||
- Makefile
|
||||
- Makefile.linux
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Building container for ${{ matrix.platform }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include: [
|
||||
{ platform: 'linux/amd64', archname: 'amd64' },
|
||||
{ platform: 'linux/386', archname: 'i386' },
|
||||
{ platform: 'linux/arm64', archname: 'arm64' },
|
||||
{ platform: 'linux/arm/v7', archname: 'armv7' },
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build container for ${{ matrix.archname }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./contrib/docker
|
||||
file: ./contrib/docker/Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: true
|
||||
tags: |
|
||||
purplei2p/i2pd:latest-${{ matrix.archname }}
|
||||
ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }}
|
||||
provenance: false
|
||||
|
||||
push:
|
||||
name: Pushing merged manifest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push latest manifest image to Docker Hub
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
inputs: purplei2p/i2pd:latest
|
||||
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
|
||||
push: true
|
||||
|
||||
- name: Create and push latest manifest image to GHCR
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
inputs: ghcr.io/purplei2p/i2pd:latest
|
||||
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
|
||||
push: true
|
||||
|
||||
- name: Store release version to env
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
|
||||
- name: Create and push release manifest to Docker Hub
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
|
||||
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
|
||||
push: true
|
||||
|
||||
- name: Create and push release manifest to GHCR
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
|
||||
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
|
||||
push: true
|
52
.gitignore
vendored
52
.gitignore
vendored
|
@ -1,27 +1,15 @@
|
|||
# i2pd
|
||||
*.o
|
||||
obj/*.o
|
||||
router.info
|
||||
router.keys
|
||||
i2p
|
||||
netDb
|
||||
/i2pd
|
||||
/libi2pd.a
|
||||
/libi2pdclient.a
|
||||
/libi2pdlang.a
|
||||
/libi2pd.so
|
||||
/libi2pdclient.so
|
||||
/libi2pdlang.so
|
||||
/libi2pd.dll
|
||||
/libi2pdclient.dll
|
||||
/libi2pdlang.dll
|
||||
*.exe
|
||||
|
||||
|
||||
# Autotools
|
||||
autom4te.cache
|
||||
.deps
|
||||
stamp-h1
|
||||
#Makefile
|
||||
Makefile
|
||||
config.h
|
||||
config.h.in~
|
||||
config.log
|
||||
|
@ -241,39 +229,3 @@ pip-log.txt
|
|||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# Sphinx
|
||||
docs/_build
|
||||
/androidIdea/
|
||||
|
||||
# Doxygen
|
||||
docs/generated
|
||||
|
||||
# emacs files
|
||||
*~
|
||||
*\#*
|
||||
|
||||
# gdb files
|
||||
.gdb_history
|
||||
|
||||
# cmake makefile
|
||||
build/Makefile
|
||||
|
||||
# debian stuff
|
||||
debian/i2pd.1.gz
|
||||
.pc/
|
||||
|
||||
# qt
|
||||
|
||||
qt/i2pd_qt/*.autosave
|
||||
qt/i2pd_qt/*.ui.bk*
|
||||
qt/i2pd_qt/*.ui_*
|
||||
|
||||
#unknown android stuff
|
||||
android/libs/
|
||||
|
||||
#various logs
|
||||
*LOGS/
|
||||
|
||||
qt/build-*.sh*
|
||||
|
||||
|
|
133
AddressBook.cpp
Normal file
133
AddressBook.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#include <string.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "base64.h"
|
||||
#include "util.h"
|
||||
#include "Identity.h"
|
||||
#include "Log.h"
|
||||
#include "AddressBook.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
|
||||
AddressBook::AddressBook (): m_IsLoaded (false), m_IsDowloading (false)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address)
|
||||
{
|
||||
if (!m_IsLoaded)
|
||||
LoadHosts ();
|
||||
if (m_IsLoaded)
|
||||
{
|
||||
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)
|
||||
{
|
||||
i2p::data::IdentityEx ident;
|
||||
ident.FromBase64 (base64);
|
||||
m_Addresses[address] = ident.GetIdentHash ();
|
||||
LogPrint (address,"->",ident.GetIdentHash ().ToBase32 (), ".b32.i2p added");
|
||||
}
|
||||
|
||||
void AddressBook::LoadHostsFromI2P ()
|
||||
{
|
||||
std::string content;
|
||||
int http_code = i2p::util::http::httpRequestViaI2pProxy("http://udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p/hosts.txt", content);
|
||||
if (http_code == 200)
|
||||
{
|
||||
std::ofstream f_save(i2p::util::filesystem::GetFullPath("hosts.txt").c_str(), std::ofstream::out);
|
||||
if (f_save.is_open())
|
||||
{
|
||||
f_save << content;
|
||||
f_save.close();
|
||||
}
|
||||
else
|
||||
LogPrint("Can't write hosts.txt");
|
||||
m_IsLoaded = false;
|
||||
}
|
||||
else
|
||||
LogPrint ("Failed to download hosts.txt");
|
||||
m_IsDowloading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void AddressBook::LoadHosts ()
|
||||
{
|
||||
std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ofstream::in); // in text mode
|
||||
if (!f.is_open ())
|
||||
{
|
||||
LogPrint ("hosts.txt not found. Try to load...");
|
||||
if (!m_IsDowloading)
|
||||
{
|
||||
m_IsDowloading = true;
|
||||
std::thread load_hosts(&AddressBook::LoadHostsFromI2P, this);
|
||||
load_hosts.detach();
|
||||
}
|
||||
return;
|
||||
}
|
||||
int numAddresses = 0;
|
||||
|
||||
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);
|
||||
|
||||
i2p::data::IdentityEx ident;
|
||||
ident.FromBase64(addr);
|
||||
m_Addresses[name] = ident.GetIdentHash ();
|
||||
numAddresses++;
|
||||
}
|
||||
}
|
||||
LogPrint (numAddresses, " addresses loaded");
|
||||
m_IsLoaded = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
38
AddressBook.h
Normal file
38
AddressBook.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef ADDRESS_BOOK_H__
|
||||
#define ADDRESS_BOOK_H__
|
||||
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "base64.h"
|
||||
#include "util.h"
|
||||
#include "Identity.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
class AddressBook
|
||||
{
|
||||
public:
|
||||
|
||||
AddressBook ();
|
||||
bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident);
|
||||
const i2p::data::IdentHash * FindAddress (const std::string& address);
|
||||
void InsertAddress (const std::string& address, const std::string& base64); // for jump service
|
||||
|
||||
private:
|
||||
|
||||
void LoadHosts ();
|
||||
void LoadHostsFromI2P ();
|
||||
|
||||
std::map<std::string, i2p::data::IdentHash> m_Addresses;
|
||||
bool m_IsLoaded, m_IsDowloading;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
199
ClientContext.cpp
Normal file
199
ClientContext.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
#include "util.h"
|
||||
#include "Log.h"
|
||||
#include "ClientContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
ClientContext context;
|
||||
|
||||
ClientContext::ClientContext (): m_SharedLocalDestination (nullptr),
|
||||
m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_IrcTunnel (nullptr),
|
||||
m_ServerTunnel (nullptr), m_SamBridge (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ClientContext::~ClientContext ()
|
||||
{
|
||||
delete m_HttpProxy;
|
||||
delete m_SocksProxy;
|
||||
delete m_IrcTunnel;
|
||||
delete m_ServerTunnel;
|
||||
delete m_SamBridge;
|
||||
}
|
||||
|
||||
void ClientContext::Start ()
|
||||
{
|
||||
if (!m_SharedLocalDestination)
|
||||
{
|
||||
m_SharedLocalDestination = new ClientDestination (false, i2p::data::SIGNING_KEY_TYPE_DSA_SHA1); // non-public, DSA
|
||||
m_Destinations[m_SharedLocalDestination->GetIdentity ().GetIdentHash ()] = m_SharedLocalDestination;
|
||||
m_SharedLocalDestination->Start ();
|
||||
}
|
||||
|
||||
m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyport", 4446));
|
||||
m_HttpProxy->Start();
|
||||
LogPrint("HTTP Proxy started");
|
||||
m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyport", 4447));
|
||||
m_SocksProxy->Start();
|
||||
LogPrint("SOCKS Proxy Started");
|
||||
std::string ircDestination = i2p::util::config::GetArg("-ircdest", "");
|
||||
if (ircDestination.length () > 0) // ircdest is presented
|
||||
{
|
||||
ClientDestination * localDestination = nullptr;
|
||||
std::string ircKeys = i2p::util::config::GetArg("-irckeys", "");
|
||||
if (ircKeys.length () > 0)
|
||||
localDestination = i2p::client::context.LoadLocalDestination (ircKeys, false);
|
||||
m_IrcTunnel = new I2PClientTunnel (m_SocksProxy->GetService (), ircDestination,
|
||||
i2p::util::config::GetArg("-ircport", 6668), localDestination);
|
||||
m_IrcTunnel->Start ();
|
||||
LogPrint("IRC tunnel started");
|
||||
}
|
||||
std::string eepKeys = i2p::util::config::GetArg("-eepkeys", "");
|
||||
if (eepKeys.length () > 0) // eepkeys file is presented
|
||||
{
|
||||
auto localDestination = i2p::client::context.LoadLocalDestination (eepKeys, true);
|
||||
m_ServerTunnel = new I2PServerTunnel (m_SocksProxy->GetService (),
|
||||
i2p::util::config::GetArg("-eephost", "127.0.0.1"), i2p::util::config::GetArg("-eepport", 80),
|
||||
localDestination);
|
||||
m_ServerTunnel->Start ();
|
||||
LogPrint("Server tunnel started");
|
||||
}
|
||||
int samPort = i2p::util::config::GetArg("-samport", 0);
|
||||
if (samPort)
|
||||
{
|
||||
m_SamBridge = new SAMBridge (samPort);
|
||||
m_SamBridge->Start ();
|
||||
LogPrint("SAM bridge started");
|
||||
}
|
||||
}
|
||||
|
||||
void ClientContext::Stop ()
|
||||
{
|
||||
m_HttpProxy->Stop();
|
||||
delete m_HttpProxy;
|
||||
m_HttpProxy = nullptr;
|
||||
LogPrint("HTTP Proxy stoped");
|
||||
m_SocksProxy->Stop();
|
||||
delete m_SocksProxy;
|
||||
m_SocksProxy = nullptr;
|
||||
LogPrint("SOCKS Proxy stoped");
|
||||
if (m_IrcTunnel)
|
||||
{
|
||||
m_IrcTunnel->Stop ();
|
||||
delete m_IrcTunnel;
|
||||
m_IrcTunnel = nullptr;
|
||||
LogPrint("IRC tunnel stoped");
|
||||
}
|
||||
if (m_ServerTunnel)
|
||||
{
|
||||
m_ServerTunnel->Stop ();
|
||||
delete m_ServerTunnel;
|
||||
m_ServerTunnel = nullptr;
|
||||
LogPrint("Server tunnel stoped");
|
||||
}
|
||||
if (m_SamBridge)
|
||||
{
|
||||
m_SamBridge->Stop ();
|
||||
delete m_SamBridge;
|
||||
m_SamBridge = nullptr;
|
||||
LogPrint("SAM brdige stoped");
|
||||
}
|
||||
|
||||
for (auto it: m_Destinations)
|
||||
{
|
||||
it.second->Stop ();
|
||||
delete it.second;
|
||||
}
|
||||
m_Destinations.clear ();
|
||||
m_SharedLocalDestination = 0; // deleted through m_Destination
|
||||
}
|
||||
|
||||
void ClientContext::LoadLocalDestinations ()
|
||||
{
|
||||
int numDestinations = 0;
|
||||
boost::filesystem::path p (i2p::util::filesystem::GetDataDir());
|
||||
boost::filesystem::directory_iterator end;
|
||||
for (boost::filesystem::directory_iterator it (p); it != end; ++it)
|
||||
{
|
||||
if (boost::filesystem::is_regular_file (*it) && it->path ().extension () == ".dat")
|
||||
{
|
||||
auto fullPath =
|
||||
#if BOOST_VERSION > 10500
|
||||
it->path().string();
|
||||
#else
|
||||
it->path();
|
||||
#endif
|
||||
auto localDestination = new ClientDestination (fullPath, true);
|
||||
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||
numDestinations++;
|
||||
}
|
||||
}
|
||||
if (numDestinations > 0)
|
||||
LogPrint (numDestinations, " local destinations loaded");
|
||||
}
|
||||
|
||||
ClientDestination * ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic)
|
||||
{
|
||||
auto localDestination = new ClientDestination (i2p::util::filesystem::GetFullPath (filename), isPublic);
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
ClientDestination * ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType)
|
||||
{
|
||||
auto localDestination = new ClientDestination (isPublic, sigType);
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
void ClientContext::DeleteLocalDestination (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 ();
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
ClientDestination * ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic)
|
||||
{
|
||||
auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ());
|
||||
if (it != m_Destinations.end ())
|
||||
{
|
||||
LogPrint ("Local destination ", keys.GetPublic ().GetIdentHash ().ToBase32 (), ".b32.i2p exists");
|
||||
if (!it->second->IsRunning ())
|
||||
{
|
||||
it->second->Start ();
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
auto localDestination = new ClientDestination (keys, isPublic);
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations[keys.GetPublic ().GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
62
ClientContext.h
Normal file
62
ClientContext.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef CLIENT_CONTEXT_H__
|
||||
#define CLIENT_CONTEXT_H__
|
||||
|
||||
#include <mutex>
|
||||
#include "Destination.h"
|
||||
#include "HTTPProxy.h"
|
||||
#include "SOCKS.h"
|
||||
#include "I2PTunnel.h"
|
||||
#include "SAM.h"
|
||||
#include "AddressBook.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
class ClientContext
|
||||
{
|
||||
public:
|
||||
|
||||
ClientContext ();
|
||||
~ClientContext ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
ClientDestination * GetSharedLocalDestination () const { return m_SharedLocalDestination; };
|
||||
ClientDestination * CreateNewLocalDestination (bool isPublic = true, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1); // transient
|
||||
ClientDestination * CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true);
|
||||
void DeleteLocalDestination (ClientDestination * destination);
|
||||
ClientDestination * FindLocalDestination (const i2p::data::IdentHash& destination) const;
|
||||
ClientDestination * LoadLocalDestination (const std::string& filename, bool isPublic);
|
||||
|
||||
AddressBook& GetAddressBook () { return m_AddressBook; };
|
||||
|
||||
private:
|
||||
|
||||
void LoadLocalDestinations ();
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_DestinationsMutex;
|
||||
std::map<i2p::data::IdentHash, ClientDestination *> m_Destinations;
|
||||
ClientDestination * m_SharedLocalDestination;
|
||||
|
||||
AddressBook m_AddressBook;
|
||||
|
||||
i2p::proxy::HTTPProxy * m_HttpProxy;
|
||||
i2p::proxy::SOCKSProxy * m_SocksProxy;
|
||||
I2PClientTunnel * m_IrcTunnel;
|
||||
I2PServerTunnel * m_ServerTunnel;
|
||||
SAMBridge * m_SamBridge;
|
||||
|
||||
public:
|
||||
// for HTTP
|
||||
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
|
||||
};
|
||||
|
||||
extern ClientContext context;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
73
CryptoConst.cpp
Normal file
73
CryptoConst.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include <inttypes.h>
|
||||
#include "CryptoConst.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 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 CryptoConstants& GetCryptoConstants ()
|
||||
{
|
||||
static CryptoConstants cryptoConstants =
|
||||
{
|
||||
{elgp_, 256}, // elgp
|
||||
{2}, // elgg
|
||||
{dsap_, 128}, // dsap
|
||||
{dsaq_, 20}, // dsaq
|
||||
{dsag_, 128} // dsag
|
||||
};
|
||||
return cryptoConstants;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
35
CryptoConst.h
Normal file
35
CryptoConst.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef CRYPTO_CONST_H__
|
||||
#define CRYPTO_CONST_H__
|
||||
|
||||
#include <cryptopp/integer.h>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
struct CryptoConstants
|
||||
{
|
||||
// DH/ElGamal
|
||||
const CryptoPP::Integer elgp;
|
||||
const CryptoPP::Integer elgg;
|
||||
|
||||
// DSA
|
||||
const CryptoPP::Integer dsap;
|
||||
const CryptoPP::Integer dsaq;
|
||||
const CryptoPP::Integer dsag;
|
||||
};
|
||||
|
||||
const CryptoConstants& GetCryptoConstants ();
|
||||
|
||||
// DH/ElGamal
|
||||
#define elgp GetCryptoConstants ().elgp
|
||||
#define elgg GetCryptoConstants ().elgg
|
||||
|
||||
// DSA
|
||||
#define dsap GetCryptoConstants ().dsap
|
||||
#define dsaq GetCryptoConstants ().dsaq
|
||||
#define dsag GetCryptoConstants ().dsag
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
138
Daemon.cpp
Normal file
138
Daemon.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include <thread>
|
||||
|
||||
#include "Daemon.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "base64.h"
|
||||
#include "version.h"
|
||||
#include "Transports.h"
|
||||
#include "NTCPSession.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Tunnel.h"
|
||||
#include "NetDb.h"
|
||||
#include "Garlic.h"
|
||||
#include "util.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "HTTPServer.h"
|
||||
#include "ClientContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
class Daemon_Singleton::Daemon_Singleton_Private
|
||||
{
|
||||
public:
|
||||
Daemon_Singleton_Private() : httpServer(nullptr)
|
||||
{};
|
||||
~Daemon_Singleton_Private()
|
||||
{
|
||||
delete httpServer;
|
||||
};
|
||||
|
||||
i2p::util::HTTPServer *httpServer;
|
||||
};
|
||||
|
||||
Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {};
|
||||
Daemon_Singleton::~Daemon_Singleton() {
|
||||
delete &d;
|
||||
};
|
||||
|
||||
bool Daemon_Singleton::IsService () const
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return i2p::util::config::GetArg("-service", 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::init(int argc, char* argv[])
|
||||
{
|
||||
i2p::util::config::OptionParser(argc, argv);
|
||||
i2p::context.Init ();
|
||||
|
||||
LogPrint("\n\n\n\ni2pd starting\n");
|
||||
LogPrint("Version ", VERSION);
|
||||
LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string());
|
||||
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
|
||||
|
||||
isDaemon = i2p::util::config::GetArg("-daemon", 0);
|
||||
isLogging = i2p::util::config::GetArg("-log", 1);
|
||||
|
||||
int port = i2p::util::config::GetArg("-port", 0);
|
||||
if (port)
|
||||
i2p::context.UpdatePort (port);
|
||||
const char * host = i2p::util::config::GetCharArg("-host", "");
|
||||
if (host && host[0])
|
||||
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));
|
||||
|
||||
if (i2p::util::config::GetArg("-unreachable", 0))
|
||||
i2p::context.SetUnreachable ();
|
||||
|
||||
i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0));
|
||||
|
||||
LogPrint("CMD parameters:");
|
||||
for (int i = 0; i < argc; ++i)
|
||||
LogPrint(i, " ", argv[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::start()
|
||||
{
|
||||
// initialize log
|
||||
if (isLogging)
|
||||
{
|
||||
if (isDaemon)
|
||||
{
|
||||
std::string logfile_path = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string();
|
||||
#ifndef _WIN32
|
||||
logfile_path.append("/i2pd.log");
|
||||
#else
|
||||
logfile_path.append("\\i2pd.log");
|
||||
#endif
|
||||
StartLog (logfile_path);
|
||||
}
|
||||
else
|
||||
StartLog (""); // write to stdout
|
||||
}
|
||||
|
||||
d.httpServer = new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpport", 7070));
|
||||
d.httpServer->Start();
|
||||
LogPrint("HTTP Server started");
|
||||
i2p::data::netdb.Start();
|
||||
LogPrint("NetDB started");
|
||||
i2p::transport::transports.Start();
|
||||
LogPrint("Transports started");
|
||||
i2p::tunnel::tunnels.Start();
|
||||
LogPrint("Tunnels started");
|
||||
i2p::client::context.Start ();
|
||||
LogPrint("Client started");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::stop()
|
||||
{
|
||||
LogPrint("Shutdown started.");
|
||||
i2p::client::context.Stop();
|
||||
LogPrint("Client stoped");
|
||||
i2p::tunnel::tunnels.Stop();
|
||||
LogPrint("Tunnels stoped");
|
||||
i2p::transport::transports.Stop();
|
||||
LogPrint("Transports stoped");
|
||||
i2p::data::netdb.Stop();
|
||||
LogPrint("NetDB stoped");
|
||||
d.httpServer->Stop();
|
||||
LogPrint("HTTP Server stoped");
|
||||
StopLog ();
|
||||
|
||||
delete d.httpServer; d.httpServer = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
71
Daemon.h
Normal file
71
Daemon.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Daemon i2p::util::DaemonWin32::Instance()
|
||||
#else
|
||||
#define Daemon i2p::util::DaemonLinux::Instance()
|
||||
#endif
|
||||
|
||||
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();
|
||||
|
||||
int isLogging;
|
||||
int isDaemon;
|
||||
|
||||
int running;
|
||||
|
||||
protected:
|
||||
Daemon_Singleton();
|
||||
virtual ~Daemon_Singleton();
|
||||
|
||||
bool IsService () const;
|
||||
|
||||
// d-pointer for httpServer, httpProxy, etc.
|
||||
class Daemon_Singleton_Private;
|
||||
Daemon_Singleton_Private &d;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
class DaemonWin32 : public Daemon_Singleton
|
||||
{
|
||||
public:
|
||||
static DaemonWin32& Instance()
|
||||
{
|
||||
static DaemonWin32 instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
virtual bool init(int argc, char* argv[]);
|
||||
virtual bool start();
|
||||
virtual bool stop();
|
||||
};
|
||||
#else
|
||||
class DaemonLinux : public Daemon_Singleton
|
||||
{
|
||||
public:
|
||||
static DaemonLinux& Instance()
|
||||
{
|
||||
static DaemonLinux instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
virtual bool start();
|
||||
virtual bool stop();
|
||||
private:
|
||||
std::string pidfile;
|
||||
int pidFilehandle;
|
||||
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
118
DaemonLinux.cpp
Normal file
118
DaemonLinux.cpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#include "Daemon.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
void handle_signal(int sig)
|
||||
{
|
||||
switch (sig)
|
||||
{
|
||||
case SIGHUP:
|
||||
if (i2p::util::config::GetArg("daemon", 0) == 1)
|
||||
{
|
||||
static bool first=true;
|
||||
if (first)
|
||||
{
|
||||
first=false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
LogPrint("Reloading config.");
|
||||
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
|
||||
break;
|
||||
case SIGABRT:
|
||||
case SIGTERM:
|
||||
case SIGINT:
|
||||
Daemon.running = 0; // Exit loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
bool DaemonLinux::start()
|
||||
{
|
||||
if (isDaemon == 1)
|
||||
{
|
||||
pid_t pid;
|
||||
pid = fork();
|
||||
if (pid > 0) // parent
|
||||
::exit (EXIT_SUCCESS);
|
||||
|
||||
if (pid < 0) // error
|
||||
return false;
|
||||
|
||||
// child
|
||||
umask(0);
|
||||
int sid = setsid();
|
||||
if (sid < 0)
|
||||
{
|
||||
LogPrint("Error, could not create process group.");
|
||||
return false;
|
||||
}
|
||||
chdir(i2p::util::filesystem::GetDataDir().string().c_str());
|
||||
|
||||
// close stdin/stdout/stderr descriptors
|
||||
::close (0);
|
||||
::open ("/dev/null", O_RDWR);
|
||||
::close (1);
|
||||
::open ("/dev/null", O_RDWR);
|
||||
::close (2);
|
||||
::open ("/dev/null", O_RDWR);
|
||||
}
|
||||
|
||||
// Pidfile
|
||||
pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string();
|
||||
pidfile.append("/i2pd.pid");
|
||||
pidFilehandle = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600);
|
||||
if (pidFilehandle == -1)
|
||||
{
|
||||
LogPrint("Error, could not create pid file (", pidfile, ")\nIs an instance already running?");
|
||||
return false;
|
||||
}
|
||||
if (lockf(pidFilehandle, F_TLOCK, 0) == -1)
|
||||
{
|
||||
LogPrint("Error, could not lock pid file (", pidfile, ")\nIs an instance already running?");
|
||||
return false;
|
||||
}
|
||||
char pid[10];
|
||||
sprintf(pid, "%d\n", getpid());
|
||||
write(pidFilehandle, pid, strlen(pid));
|
||||
|
||||
// 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()
|
||||
{
|
||||
close(pidFilehandle);
|
||||
unlink(pidfile.c_str());
|
||||
|
||||
return Daemon_Singleton::stop();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
83
DaemonWin32.cpp
Normal file
83
DaemonWin32.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "Daemon.h"
|
||||
#include "util.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "./Win32/Win32Service.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
bool DaemonWin32::init(int argc, char* argv[])
|
||||
{
|
||||
setlocale(LC_CTYPE, "");
|
||||
SetConsoleCP(1251);
|
||||
SetConsoleOutputCP(1251);
|
||||
setlocale(LC_ALL, "Russian");
|
||||
|
||||
if (!Daemon_Singleton::init(argc, argv)) return false;
|
||||
if (I2PService::isService())
|
||||
isDaemon = 1;
|
||||
else
|
||||
isDaemon = 0;
|
||||
|
||||
std::string serviceControl = i2p::util::config::GetArg("-service", "none");
|
||||
if (serviceControl == "install")
|
||||
{
|
||||
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
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
else if (serviceControl == "remove")
|
||||
{
|
||||
UninstallService(SERVICE_NAME);
|
||||
exit(0);
|
||||
}
|
||||
else if (serviceControl != "none")
|
||||
{
|
||||
printf(" --service=install to install the service.\n");
|
||||
printf(" --service=remove to remove the service.\n");
|
||||
}
|
||||
|
||||
if (isDaemon == 1)
|
||||
{
|
||||
LogPrint("Service session");
|
||||
I2PService service(SERVICE_NAME);
|
||||
if (!I2PService::Run(service))
|
||||
{
|
||||
LogPrint("Service failed to run w/err 0x%08lx\n", GetLastError());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else
|
||||
LogPrint("User session");
|
||||
|
||||
return true;
|
||||
}
|
||||
bool DaemonWin32::start()
|
||||
{
|
||||
setlocale(LC_CTYPE, "");
|
||||
SetConsoleCP(1251);
|
||||
SetConsoleOutputCP(1251);
|
||||
setlocale(LC_ALL, "Russian");
|
||||
|
||||
return Daemon_Singleton::start();
|
||||
}
|
||||
|
||||
bool DaemonWin32::stop()
|
||||
{
|
||||
return Daemon_Singleton::stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
135
Datagram.cpp
Normal file
135
Datagram.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <cryptopp/sha.h>
|
||||
#include <cryptopp/gzip.h>
|
||||
#include "Log.h"
|
||||
#include "TunnelBase.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Destination.h"
|
||||
#include "Datagram.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace datagram
|
||||
{
|
||||
DatagramDestination::DatagramDestination (i2p::client::ClientDestination& owner):
|
||||
m_Owner (owner), m_Receiver (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::LeaseSet& remote)
|
||||
{
|
||||
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];
|
||||
CryptoPP::SHA256().CalculateDigest (hash, buf1, len);
|
||||
m_Owner.Sign (hash, 32, signature);
|
||||
}
|
||||
else
|
||||
m_Owner.Sign (buf1, len, signature);
|
||||
|
||||
auto service = m_Owner.GetService ();
|
||||
if (service)
|
||||
service->post (boost::bind (&DatagramDestination::SendMsg, this,
|
||||
CreateDataMessage (buf, len + headerLen), remote));
|
||||
else
|
||||
LogPrint (eLogWarning, "Failed to send datagram. Destination is not running");
|
||||
}
|
||||
|
||||
void DatagramDestination::SendMsg (I2NPMessage * msg, const i2p::data::LeaseSet& remote)
|
||||
{
|
||||
auto leases = remote.GetNonExpiredLeases ();
|
||||
if (!leases.empty ())
|
||||
{
|
||||
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||
uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1);
|
||||
auto garlic = m_Owner.WrapMessage (remote, msg, true);
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeTunnel,
|
||||
leases[i].tunnelGateway, leases[i].tunnelID,
|
||||
garlic
|
||||
});
|
||||
m_Owner.SendTunnelDataMsgs (msgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Failed to send datagram. All leases expired");
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramDestination::HandleDatagram (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];
|
||||
CryptoPP::SHA256().CalculateDigest (hash, buf + headerLen, len - headerLen);
|
||||
verified = identity.Verify (hash, 32, signature);
|
||||
}
|
||||
else
|
||||
verified = identity.Verify (buf + headerLen, len - headerLen, signature);
|
||||
|
||||
if (verified)
|
||||
{
|
||||
if (m_Receiver != nullptr)
|
||||
m_Receiver (identity, buf + headerLen, len -headerLen);
|
||||
else
|
||||
LogPrint (eLogWarning, "Receiver for datagram is not set");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Datagram signature verification failed");
|
||||
}
|
||||
|
||||
void DatagramDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len)
|
||||
{
|
||||
// unzip it
|
||||
CryptoPP::Gunzip decompressor;
|
||||
decompressor.Put (buf, len);
|
||||
decompressor.MessageEnd();
|
||||
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
|
||||
auto uncompressedLen = decompressor.MaxRetrievable ();
|
||||
if (uncompressedLen <= MAX_DATAGRAM_SIZE)
|
||||
{
|
||||
decompressor.Get (uncompressed, uncompressedLen);
|
||||
HandleDatagram (uncompressed, uncompressedLen);
|
||||
}
|
||||
else
|
||||
LogPrint ("Received datagram size ", uncompressedLen, " exceeds max size");
|
||||
|
||||
}
|
||||
|
||||
I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage ();
|
||||
CryptoPP::Gzip compressor; // default level
|
||||
compressor.Put (payload, len);
|
||||
compressor.MessageEnd();
|
||||
int size = compressor.MaxRetrievable ();
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
*(uint32_t *)buf = htobe32 (size); // length
|
||||
buf += 4;
|
||||
compressor.Get (buf, size);
|
||||
memset (buf + 4, 0, 4); // source and destination are zeroes
|
||||
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
|
||||
msg->len += size + 4;
|
||||
FillI2NPMessageHeader (msg, eI2NPData);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
49
Datagram.h
Normal file
49
Datagram.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef DATAGRAM_H__
|
||||
#define DATAGRAM_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <functional>
|
||||
#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& ident, const uint8_t *, size_t)> Receiver;
|
||||
|
||||
public:
|
||||
|
||||
DatagramDestination (i2p::client::ClientDestination& owner);
|
||||
~DatagramDestination () {};
|
||||
|
||||
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::LeaseSet& remote);
|
||||
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
|
||||
|
||||
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
|
||||
void ResetReceiver () { m_Receiver = nullptr; };
|
||||
|
||||
private:
|
||||
|
||||
I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len);
|
||||
void SendMsg (I2NPMessage * msg, const i2p::data::LeaseSet& remote);
|
||||
void HandleDatagram (const uint8_t * buf, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
i2p::client::ClientDestination& m_Owner;
|
||||
Receiver m_Receiver;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
310
Destination.cpp
Normal file
310
Destination.cpp
Normal file
|
@ -0,0 +1,310 @@
|
|||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <cryptopp/dh.h>
|
||||
#include "Log.h"
|
||||
#include "util.h"
|
||||
#include "NetDb.h"
|
||||
#include "Destination.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
ClientDestination::ClientDestination (bool isPublic, i2p::data::SigningKeyType sigType):
|
||||
m_IsRunning (false), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr),
|
||||
m_CurrentOutboundTunnel (nullptr), m_LeaseSet (nullptr), m_IsPublic (isPublic),
|
||||
m_DatagramDestination (nullptr)
|
||||
{
|
||||
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (*this, 3); // 3-hops tunnel
|
||||
if (m_IsPublic)
|
||||
LogPrint ("Local address ", GetIdentHash ().ToBase32 (), ".b32.i2p created");
|
||||
m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO:
|
||||
}
|
||||
|
||||
ClientDestination::ClientDestination (const std::string& fullPath, bool isPublic):
|
||||
m_IsRunning (false), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr),
|
||||
m_CurrentOutboundTunnel (nullptr), m_LeaseSet (nullptr), m_IsPublic (isPublic),
|
||||
m_DatagramDestination (nullptr)
|
||||
{
|
||||
std::ifstream s(fullPath.c_str (), 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);
|
||||
m_Keys.FromBuffer (buf, len);
|
||||
delete[] buf;
|
||||
LogPrint ("Local address ", GetIdentHash ().ToBase32 (), ".b32.i2p loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Can't open file ", fullPath, " Creating new one");
|
||||
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1);
|
||||
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
|
||||
size_t len = m_Keys.GetFullLen ();
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
len = m_Keys.ToBuffer (buf, len);
|
||||
f.write ((char *)buf, len);
|
||||
delete[] buf;
|
||||
|
||||
LogPrint ("New private keys file ", fullPath, " for ", m_Keys.GetPublic ().GetIdentHash ().ToBase32 (), ".b32.i2p created");
|
||||
}
|
||||
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (*this, 3); // 3-hops tunnel
|
||||
m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO:
|
||||
}
|
||||
|
||||
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic):
|
||||
m_IsRunning (false), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr),
|
||||
m_Keys (keys), m_CurrentOutboundTunnel (nullptr), m_LeaseSet (nullptr), m_IsPublic (isPublic),
|
||||
m_DatagramDestination (nullptr)
|
||||
{
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (*this, 3); // 3-hops tunnel
|
||||
if (m_IsPublic)
|
||||
LogPrint ("Local address ", GetIdentHash ().ToBase32 (), ".b32.i2p created");
|
||||
m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO:
|
||||
}
|
||||
|
||||
ClientDestination::~ClientDestination ()
|
||||
{
|
||||
Stop ();
|
||||
for (auto it: m_RemoteLeaseSets)
|
||||
delete it.second;
|
||||
if (m_Pool)
|
||||
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
|
||||
delete m_LeaseSet;
|
||||
delete m_Work;
|
||||
delete m_Service;
|
||||
delete m_StreamingDestination;
|
||||
delete m_DatagramDestination;
|
||||
}
|
||||
|
||||
void ClientDestination::Run ()
|
||||
{
|
||||
if (m_Service)
|
||||
m_Service->run ();
|
||||
}
|
||||
|
||||
void ClientDestination::Start ()
|
||||
{
|
||||
m_Service = new boost::asio::io_service;
|
||||
m_Work = new boost::asio::io_service::work (*m_Service);
|
||||
m_Pool->SetActive (true);
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&ClientDestination::Run, this));
|
||||
m_StreamingDestination->Start ();
|
||||
}
|
||||
|
||||
void ClientDestination::Stop ()
|
||||
{
|
||||
m_StreamingDestination->Stop ();
|
||||
if (m_DatagramDestination)
|
||||
{
|
||||
auto d = m_DatagramDestination;
|
||||
m_DatagramDestination = nullptr;
|
||||
delete d;
|
||||
}
|
||||
if (m_Pool)
|
||||
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
|
||||
m_IsRunning = false;
|
||||
if (m_Service)
|
||||
m_Service->stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
delete m_Work; m_Work = nullptr;
|
||||
delete m_Service; m_Service = nullptr;
|
||||
}
|
||||
|
||||
const i2p::data::LeaseSet * ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
|
||||
{
|
||||
auto it = m_RemoteLeaseSets.find (ident);
|
||||
if (it != m_RemoteLeaseSets.end ())
|
||||
{
|
||||
if (it->second->HasNonExpiredLeases ())
|
||||
return it->second;
|
||||
else
|
||||
{
|
||||
LogPrint ("All leases of remote LeaseSet expired. Request it");
|
||||
i2p::data::netdb.RequestDestination (ident, true, m_Pool);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ls = i2p::data::netdb.FindLeaseSet (ident);
|
||||
if (ls)
|
||||
{
|
||||
ls = new i2p::data::LeaseSet (*ls);
|
||||
m_RemoteLeaseSets[ident] = ls;
|
||||
return ls;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const i2p::data::LeaseSet * ClientDestination::GetLeaseSet ()
|
||||
{
|
||||
if (!m_Pool) return nullptr;
|
||||
if (!m_LeaseSet)
|
||||
UpdateLeaseSet ();
|
||||
return m_LeaseSet;
|
||||
}
|
||||
|
||||
void ClientDestination::UpdateLeaseSet ()
|
||||
{
|
||||
auto newLeaseSet = new i2p::data::LeaseSet (*m_Pool);
|
||||
if (!m_LeaseSet)
|
||||
m_LeaseSet = newLeaseSet;
|
||||
else
|
||||
{
|
||||
// TODO: implement it better
|
||||
*m_LeaseSet = *newLeaseSet;
|
||||
delete newLeaseSet;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientDestination::SendTunnelDataMsgs (const std::vector<i2p::tunnel::TunnelMessageBlock>& msgs)
|
||||
{
|
||||
m_CurrentOutboundTunnel = m_Pool->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
|
||||
if (m_CurrentOutboundTunnel)
|
||||
m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs);
|
||||
else
|
||||
{
|
||||
LogPrint ("No outbound tunnels in the pool");
|
||||
for (auto it: msgs)
|
||||
DeleteI2NPMessage (it.data);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientDestination::ProcessGarlicMessage (I2NPMessage * msg)
|
||||
{
|
||||
m_Service->post (boost::bind (&ClientDestination::HandleGarlicMessage, this, msg));
|
||||
}
|
||||
|
||||
void ClientDestination::ProcessDeliveryStatusMessage (I2NPMessage * msg)
|
||||
{
|
||||
m_Service->post (boost::bind (&ClientDestination::HandleDeliveryStatusMessage, this, msg));
|
||||
}
|
||||
|
||||
void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
I2NPHeader * header = (I2NPHeader *)buf;
|
||||
switch (header->typeID)
|
||||
{
|
||||
case eI2NPData:
|
||||
HandleDataMessage (buf + sizeof (I2NPHeader), be16toh (header->size));
|
||||
break;
|
||||
case eI2NPDatabaseStore:
|
||||
HandleDatabaseStoreMessage (buf + sizeof (I2NPHeader), be16toh (header->size));
|
||||
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); // TODO: remove
|
||||
break;
|
||||
default:
|
||||
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||
}
|
||||
}
|
||||
|
||||
void ClientDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
|
||||
{
|
||||
I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)buf;
|
||||
size_t offset = sizeof (I2NPDatabaseStoreMsg);
|
||||
if (msg->replyToken) // TODO:
|
||||
offset += 36;
|
||||
if (msg->type == 1) // LeaseSet
|
||||
{
|
||||
LogPrint ("Remote LeaseSet");
|
||||
auto it = m_RemoteLeaseSets.find (msg->key);
|
||||
if (it != m_RemoteLeaseSets.end ())
|
||||
{
|
||||
it->second->Update (buf + offset, len - offset);
|
||||
LogPrint ("Remote LeaseSet updated");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("New remote LeaseSet added");
|
||||
m_RemoteLeaseSets[msg->key] = new i2p::data::LeaseSet (buf + offset, len - offset);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint ("Unexpected client's DatabaseStore type ", msg->type, ". Dropped");
|
||||
}
|
||||
|
||||
void ClientDestination::SetLeaseSetUpdated ()
|
||||
{
|
||||
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
|
||||
UpdateLeaseSet ();
|
||||
if (m_IsPublic)
|
||||
i2p::data::netdb.PublishLeaseSet (m_LeaseSet, m_Pool);
|
||||
}
|
||||
|
||||
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
|
||||
{
|
||||
uint32_t length = be32toh (*(uint32_t *)buf);
|
||||
buf += 4;
|
||||
// we assume I2CP payload
|
||||
switch (buf[9])
|
||||
{
|
||||
case PROTOCOL_TYPE_STREAMING:
|
||||
// streaming protocol
|
||||
if (m_StreamingDestination)
|
||||
m_StreamingDestination->HandleDataMessagePayload (buf, length);
|
||||
else
|
||||
LogPrint ("Missing streaming destination");
|
||||
break;
|
||||
case PROTOCOL_TYPE_DATAGRAM:
|
||||
// datagram protocol
|
||||
if (m_DatagramDestination)
|
||||
m_DatagramDestination->HandleDataMessagePayload (buf, length);
|
||||
else
|
||||
LogPrint ("Missing streaming destination");
|
||||
break;
|
||||
default:
|
||||
LogPrint ("Data: unexpected protocol ", buf[9]);
|
||||
}
|
||||
}
|
||||
|
||||
i2p::stream::Stream * ClientDestination::CreateStream (const i2p::data::LeaseSet& remote, int port)
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination ()
|
||||
{
|
||||
if (!m_DatagramDestination)
|
||||
m_DatagramDestination = new i2p::datagram::DatagramDestination (*this);
|
||||
return m_DatagramDestination;
|
||||
}
|
||||
}
|
||||
}
|
102
Destination.h
Normal file
102
Destination.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
#ifndef DESTINATION_H__
|
||||
#define DESTINATION_H__
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "Identity.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "CryptoConst.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Garlic.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;
|
||||
|
||||
class ClientDestination: public i2p::garlic::GarlicDestination
|
||||
{
|
||||
public:
|
||||
|
||||
ClientDestination (bool isPublic, i2p::data::SigningKeyType sigType);
|
||||
ClientDestination (const std::string& fullPath, bool isPublic);
|
||||
ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic);
|
||||
~ClientDestination ();
|
||||
|
||||
virtual void Start ();
|
||||
virtual void Stop ();
|
||||
bool IsRunning () const { return m_IsRunning; };
|
||||
boost::asio::io_service * GetService () { return m_Service; };
|
||||
i2p::tunnel::TunnelPool * GetTunnelPool () { return m_Pool; };
|
||||
bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases (); };
|
||||
|
||||
void ResetCurrentOutboundTunnel () { m_CurrentOutboundTunnel = nullptr; };
|
||||
const i2p::data::LeaseSet * FindLeaseSet (const i2p::data::IdentHash& ident);
|
||||
void SendTunnelDataMsgs (const std::vector<i2p::tunnel::TunnelMessageBlock>& msgs);
|
||||
|
||||
// streaming
|
||||
i2p::stream::StreamingDestination * GetStreamingDestination () const { return m_StreamingDestination; };
|
||||
i2p::stream::Stream * CreateStream (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 i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
|
||||
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; };
|
||||
|
||||
// implements GarlicDestination
|
||||
const i2p::data::LeaseSet * GetLeaseSet ();
|
||||
void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from);
|
||||
|
||||
// override GarlicDestination
|
||||
void ProcessGarlicMessage (I2NPMessage * msg);
|
||||
void ProcessDeliveryStatusMessage (I2NPMessage * msg);
|
||||
void SetLeaseSetUpdated ();
|
||||
|
||||
// I2CP
|
||||
void HandleDataMessage (const uint8_t * buf, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void UpdateLeaseSet ();
|
||||
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service * m_Service;
|
||||
boost::asio::io_service::work * m_Work;
|
||||
i2p::data::PrivateKeys m_Keys;
|
||||
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
|
||||
std::map<i2p::data::IdentHash, i2p::data::LeaseSet *> m_RemoteLeaseSets;
|
||||
|
||||
i2p::tunnel::TunnelPool * m_Pool;
|
||||
i2p::tunnel::OutboundTunnel * m_CurrentOutboundTunnel;
|
||||
i2p::data::LeaseSet * m_LeaseSet;
|
||||
bool m_IsPublic;
|
||||
|
||||
i2p::stream::StreamingDestination * m_StreamingDestination;
|
||||
i2p::datagram::DatagramDestination * m_DatagramDestination;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
77
ElGamal.h
Normal file
77
ElGamal.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
#ifndef EL_GAMAL_H__
|
||||
#define EL_GAMAL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <cryptopp/integer.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#include "CryptoConst.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
|
||||
class ElGamalEncryption
|
||||
{
|
||||
public:
|
||||
|
||||
ElGamalEncryption (const uint8_t * key):
|
||||
y (key, 256), k (rnd, CryptoPP::Integer::One(), elgp-1),
|
||||
a (a_exp_b_mod_c (elgg, k, elgp)), b1 (a_exp_b_mod_c (y, k, elgp))
|
||||
{
|
||||
}
|
||||
|
||||
void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false)
|
||||
{
|
||||
// calculate b = b1*m mod p
|
||||
uint8_t m[255];
|
||||
m[0] = 0xFF;
|
||||
memcpy (m+33, data, len);
|
||||
CryptoPP::SHA256().CalculateDigest(m+1, m+33, 222);
|
||||
CryptoPP::Integer b (a_times_b_mod_c (b1, CryptoPP::Integer (m, 255), elgp));
|
||||
|
||||
// copy a and b
|
||||
if (zeroPadding)
|
||||
{
|
||||
encrypted[0] = 0;
|
||||
a.Encode (encrypted + 1, 256);
|
||||
encrypted[257] = 0;
|
||||
b.Encode (encrypted + 258, 256);
|
||||
}
|
||||
else
|
||||
{
|
||||
a.Encode (encrypted, 256);
|
||||
b.Encode (encrypted + 256, 256);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
CryptoPP::AutoSeededRandomPool rnd;
|
||||
CryptoPP::Integer y, k, a, b1;
|
||||
bool m_ZeroPadding;
|
||||
};
|
||||
|
||||
inline bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted,
|
||||
uint8_t * data, bool zeroPadding = false)
|
||||
{
|
||||
CryptoPP::Integer x(key, 256), a(zeroPadding? encrypted +1 : encrypted, 256),
|
||||
b(zeroPadding? encrypted + 258 :encrypted + 256, 256);
|
||||
uint8_t m[255], hash[32];
|
||||
a_times_b_mod_c (b, a_exp_b_mod_c (a, elgp - x - 1, elgp), elgp).Encode (m, 255);
|
||||
CryptoPP::SHA256().CalculateDigest(hash, m+33, 222);
|
||||
for (int i = 0; i < 32; i++)
|
||||
if (hash[i] != m[i+1])
|
||||
{
|
||||
LogPrint ("ElGamal decrypt hash doesn't match");
|
||||
return false;
|
||||
}
|
||||
memcpy (data, m + 33, 222);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
550
Garlic.cpp
Normal file
550
Garlic.cpp
Normal file
|
@ -0,0 +1,550 @@
|
|||
#include <inttypes.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "RouterContext.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Destination.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace garlic
|
||||
{
|
||||
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
|
||||
const i2p::data::RoutingDestination * destination, int numTags):
|
||||
m_Owner (owner), m_Destination (destination), m_NumTags (numTags),
|
||||
m_LeaseSetUpdated (numTags > 0)
|
||||
{
|
||||
// create new session tags and session key
|
||||
m_Rnd.GenerateBlock (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_LeaseSetUpdated (false)
|
||||
{
|
||||
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 ();
|
||||
}
|
||||
|
||||
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
|
||||
{
|
||||
auto tags = new UnconfirmedTags (m_NumTags);
|
||||
tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (int i = 0; i < m_NumTags; i++)
|
||||
{
|
||||
m_Rnd.GenerateBlock (tags->sessionTags[i], 32);
|
||||
tags->sessionTags[i].creationTime = tags->tagsCreationTime;
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
void GarlicRoutingSession::TagsConfirmed (uint32_t msgID)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
auto it = m_UnconfirmedTagsMsgs.find (msgID);
|
||||
if (it != m_UnconfirmedTagsMsgs.end ())
|
||||
{
|
||||
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;
|
||||
}
|
||||
// delete expired unconfirmed tags
|
||||
for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();)
|
||||
{
|
||||
if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
delete it->second;
|
||||
it = m_UnconfirmedTagsMsgs.erase (it);
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessage * GarlicRoutingSession::WrapSingleMessage (I2NPMessage * msg)
|
||||
{
|
||||
I2NPMessage * m = NewI2NPMessage ();
|
||||
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 ("No garlic tags available. Use ElGamal");
|
||||
if (!m_Destination)
|
||||
{
|
||||
LogPrint ("Can't use ElGamal for unknown destination");
|
||||
return nullptr;
|
||||
}
|
||||
// create ElGamal block
|
||||
ElGamalBlock elGamal;
|
||||
memcpy (elGamal.sessionKey, m_SessionKey, 32);
|
||||
m_Rnd.GenerateBlock (elGamal.preIV, 32); // Pre-IV
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32);
|
||||
m_Destination->GetElGamalEncryption ()->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
|
||||
CryptoPP::SHA256().CalculateDigest(iv, tag, 32);
|
||||
m_Encryption.SetIV (iv);
|
||||
buf += 32;
|
||||
len += 32;
|
||||
}
|
||||
// AES block
|
||||
len += CreateAESBlock (buf, msg);
|
||||
*(uint32_t *)(m->GetPayload ()) = htobe32 (len);
|
||||
m->len += len + 4;
|
||||
FillI2NPMessageHeader (m, eI2NPGarlic);
|
||||
if (msg)
|
||||
DeleteI2NPMessage (msg);
|
||||
return m;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, const I2NPMessage * msg)
|
||||
{
|
||||
size_t blockSize = 0;
|
||||
bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags/2);
|
||||
UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr;
|
||||
*(uint16_t *)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);
|
||||
*payloadSize = htobe32 (len);
|
||||
CryptoPP::SHA256().CalculateDigest(payloadHash, buf + blockSize, len);
|
||||
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, const I2NPMessage * msg, UnconfirmedTags * newTags)
|
||||
{
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec
|
||||
uint32_t msgID = m_Rnd.GenerateWord32 ();
|
||||
size_t size = 0;
|
||||
uint8_t * numCloves = payload + size;
|
||||
*numCloves = 0;
|
||||
size++;
|
||||
|
||||
if (m_Owner)
|
||||
{
|
||||
if (newTags) // new session
|
||||
{
|
||||
// clove is DeliveryStatus
|
||||
size += CreateDeliveryStatusClove (payload + size, msgID);
|
||||
if (size > 0) // successive?
|
||||
{
|
||||
(*numCloves)++;
|
||||
m_UnconfirmedTagsMsgs[msgID] = newTags;
|
||||
m_Owner->DeliveryStatusSent (this, msgID);
|
||||
}
|
||||
else
|
||||
LogPrint ("DeliveryStatus clove was not created");
|
||||
}
|
||||
if (m_LeaseSetUpdated)
|
||||
{
|
||||
m_LeaseSetUpdated = false;
|
||||
// clove if our leaseSet must be attached
|
||||
auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ());
|
||||
size += CreateGarlicClove (payload + size, leaseSet, false);
|
||||
DeleteI2NPMessage (leaseSet);
|
||||
(*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;
|
||||
*(uint32_t *)(payload + size) = htobe32 (msgID); // MessageID
|
||||
size += 4;
|
||||
*(uint64_t *)(payload + size) = htobe64 (ts); // Expiration of message
|
||||
size += 8;
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, const I2NPMessage * msg, bool isDestination)
|
||||
{
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 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 *)(buf + size) = htobe32 (m_Rnd.GenerateWord32 ()); // CloveID
|
||||
size += 4;
|
||||
*(uint64_t *)(buf + size) = htobe64 (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 leases = m_Owner->GetLeaseSet ()->GetNonExpiredLeases ();
|
||||
if (!leases.empty ())
|
||||
{
|
||||
buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel
|
||||
size++;
|
||||
uint32_t i = m_Rnd.GenerateWord32 (0, leases.size () - 1);
|
||||
// hash and tunnelID sequence is reversed for Garlic
|
||||
memcpy (buf + size, leases[i].tunnelGateway, 32); // To Hash
|
||||
size += 32;
|
||||
*(uint32_t *)(buf + size) = htobe32 (leases[i].tunnelID); // tunnelID
|
||||
size += 4;
|
||||
// create msg
|
||||
I2NPMessage * msg = CreateDeliveryStatusMsg (msgID);
|
||||
if (m_Owner)
|
||||
{
|
||||
//encrypt
|
||||
uint8_t key[32], tag[32];
|
||||
m_Rnd.GenerateBlock (key, 32); // random session key
|
||||
m_Rnd.GenerateBlock (tag, 32); // random session tag
|
||||
m_Owner->AddSessionKey (key, tag);
|
||||
GarlicRoutingSession garlic (key, tag);
|
||||
msg = garlic.WrapSingleMessage (msg);
|
||||
}
|
||||
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
|
||||
size += msg->GetLength ();
|
||||
DeleteI2NPMessage (msg);
|
||||
// fill clove
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec
|
||||
*(uint32_t *)(buf + size) = htobe32 (m_Rnd.GenerateWord32 ()); // CloveID
|
||||
size += 4;
|
||||
*(uint64_t *)(buf + size) = htobe64 (ts); // Expiration of clove
|
||||
size += 8;
|
||||
memset (buf + size, 0, 3); // certificate of clove
|
||||
size += 3;
|
||||
}
|
||||
else
|
||||
LogPrint ("All tunnels of local LeaseSet expired");
|
||||
}
|
||||
else
|
||||
LogPrint ("Missing local LeaseSet");
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
GarlicDestination::~GarlicDestination ()
|
||||
{
|
||||
for (auto it: m_Sessions)
|
||||
delete it.second;
|
||||
m_Sessions.clear ();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleGarlicMessage (I2NPMessage * msg)
|
||||
{
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
uint32_t length = be32toh (*(uint32_t *)buf);
|
||||
buf += 4; // length
|
||||
auto it = m_Tags.find (SessionTag(buf));
|
||||
if (it != m_Tags.end ())
|
||||
{
|
||||
// tag found. Use AES
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
CryptoPP::SHA256().CalculateDigest(iv, buf, 32);
|
||||
it->second->SetIV (iv);
|
||||
it->second->Decrypt (buf + 32, length - 32, buf + 32);
|
||||
HandleAESBlock (buf + 32, length - 32, it->second, msg->from);
|
||||
m_Tags.erase (it); // tag might be used only once
|
||||
}
|
||||
else
|
||||
{
|
||||
// tag not found. Use ElGamal
|
||||
ElGamalBlock elGamal;
|
||||
if (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
|
||||
CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32);
|
||||
decryption->SetIV (iv);
|
||||
decryption->Decrypt(buf + 514, length - 514, buf + 514);
|
||||
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
|
||||
}
|
||||
else
|
||||
LogPrint ("Failed to decrypt garlic");
|
||||
}
|
||||
DeleteI2NPMessage (msg);
|
||||
|
||||
// cleanup expired tags
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts > m_LastTagsCleanupTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
if (m_LastTagsCleanupTime)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
LogPrint (numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ());
|
||||
}
|
||||
m_LastTagsCleanupTime = ts;
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
|
||||
i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
uint16_t tagCount = be16toh (*(uint16_t *)buf);
|
||||
buf += 2;
|
||||
if (tagCount > 0)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (int i = 0; i < tagCount; i++)
|
||||
m_Tags[SessionTag(buf + i*32, ts)] = decryption;
|
||||
}
|
||||
buf += tagCount*32;
|
||||
uint32_t payloadSize = be32toh (*(uint32_t *)buf);
|
||||
if (payloadSize > len)
|
||||
{
|
||||
LogPrint ("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 hash[32];
|
||||
CryptoPP::SHA256().CalculateDigest(hash, buf, payloadSize);
|
||||
if (memcmp (hash, payloadHash, 32)) // payload hash doesn't match
|
||||
{
|
||||
LogPrint ("Wrong payload hash");
|
||||
return;
|
||||
}
|
||||
HandleGarlicPayload (buf, payloadSize, from);
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
int numCloves = buf[0];
|
||||
LogPrint (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 ("Clove encrypted");
|
||||
buf += 32;
|
||||
}
|
||||
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
|
||||
switch (deliveryType)
|
||||
{
|
||||
case eGarlicDeliveryTypeLocal:
|
||||
LogPrint ("Garlic type local");
|
||||
HandleI2NPMessage (buf, len, from);
|
||||
break;
|
||||
case eGarlicDeliveryTypeDestination:
|
||||
LogPrint ("Garlic type destination");
|
||||
buf += 32; // destination. check it later or for multiple destinations
|
||||
HandleI2NPMessage (buf, len, from);
|
||||
break;
|
||||
case eGarlicDeliveryTypeTunnel:
|
||||
{
|
||||
LogPrint ("Garlic type tunnel");
|
||||
// gwHash and gwTunnel sequence is reverted
|
||||
uint8_t * gwHash = buf;
|
||||
buf += 32;
|
||||
uint32_t gwTunnel = be32toh (*(uint32_t *)buf);
|
||||
buf += 4;
|
||||
i2p::tunnel::OutboundTunnel * tunnel = nullptr;
|
||||
if (from && from->GetTunnelPool ())
|
||||
tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel ();
|
||||
if (tunnel) // we have send it through an outbound tunnel
|
||||
{
|
||||
I2NPMessage * msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from);
|
||||
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
|
||||
}
|
||||
else
|
||||
LogPrint ("No outbound tunnels available for garlic clove");
|
||||
break;
|
||||
}
|
||||
case eGarlicDeliveryTypeRouter:
|
||||
LogPrint ("Garlic type router not supported");
|
||||
buf += 32;
|
||||
break;
|
||||
default:
|
||||
LogPrint ("Unknow garlic delivery type ", (int)deliveryType);
|
||||
}
|
||||
buf += GetI2NPMessageLength (buf); // I2NP
|
||||
buf += 4; // CloveID
|
||||
buf += 8; // Date
|
||||
buf += 3; // Certificate
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessage * GarlicDestination::WrapMessage (const i2p::data::RoutingDestination& destination,
|
||||
I2NPMessage * msg, bool attachLeaseSet)
|
||||
{
|
||||
if (attachLeaseSet) // we should maintain this session
|
||||
{
|
||||
auto session = GetRoutingSession (destination, 32); // 32 tags by default
|
||||
return session->WrapSingleMessage (msg);
|
||||
}
|
||||
else // one time session
|
||||
{
|
||||
GarlicRoutingSession session (this, &destination, 0); // don't use tag if no LeaseSet
|
||||
return session.WrapSingleMessage (msg);
|
||||
}
|
||||
}
|
||||
|
||||
GarlicRoutingSession * GarlicDestination::GetRoutingSession (
|
||||
const i2p::data::RoutingDestination& destination, int numTags)
|
||||
{
|
||||
auto it = m_Sessions.find (destination.GetIdentHash ());
|
||||
GarlicRoutingSession * session = nullptr;
|
||||
if (it != m_Sessions.end ())
|
||||
session = it->second;
|
||||
if (!session)
|
||||
{
|
||||
session = new GarlicRoutingSession (this, &destination, numTags);
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
m_Sessions[destination.GetIdentHash ()] = session;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
void GarlicDestination::DeliveryStatusSent (GarlicRoutingSession * session, uint32_t msgID)
|
||||
{
|
||||
m_CreatedSessions[msgID] = session;
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleDeliveryStatusMessage (I2NPMessage * msg)
|
||||
{
|
||||
I2NPDeliveryStatusMsg * deliveryStatus = (I2NPDeliveryStatusMsg *)msg->GetPayload ();
|
||||
uint32_t msgID = be32toh (deliveryStatus->msgID);
|
||||
{
|
||||
auto it = m_CreatedSessions.find (msgID);
|
||||
if (it != m_CreatedSessions.end ())
|
||||
{
|
||||
it->second->TagsConfirmed (msgID);
|
||||
m_CreatedSessions.erase (it);
|
||||
LogPrint ("Garlic message ", msgID, " acknowledged");
|
||||
}
|
||||
}
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
|
||||
void GarlicDestination::SetLeaseSetUpdated ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
for (auto it: m_Sessions)
|
||||
it.second->SetLeaseSetUpdated ();
|
||||
}
|
||||
|
||||
void GarlicDestination::ProcessGarlicMessage (I2NPMessage * msg)
|
||||
{
|
||||
HandleGarlicMessage (msg);
|
||||
}
|
||||
|
||||
void GarlicDestination::ProcessDeliveryStatusMessage (I2NPMessage * msg)
|
||||
{
|
||||
HandleDeliveryStatusMessage (msg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
147
Garlic.h
Normal file
147
Garlic.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
#ifndef GARLIC_H__
|
||||
#define GARLIC_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include "aes.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Queue.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace garlic
|
||||
{
|
||||
|
||||
enum GarlicDeliveryType
|
||||
{
|
||||
eGarlicDeliveryTypeLocal = 0,
|
||||
eGarlicDeliveryTypeDestination = 1,
|
||||
eGarlicDeliveryTypeRouter = 2,
|
||||
eGarlicDeliveryTypeTunnel = 3
|
||||
};
|
||||
|
||||
#pragma pack(1)
|
||||
struct ElGamalBlock
|
||||
{
|
||||
uint8_t sessionKey[32];
|
||||
uint8_t preIV[32];
|
||||
uint8_t padding[158];
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
|
||||
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
class GarlicDestination;
|
||||
class GarlicRoutingSession
|
||||
{
|
||||
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, const i2p::data::RoutingDestination * destination, int numTags);
|
||||
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
|
||||
~GarlicRoutingSession ();
|
||||
I2NPMessage * WrapSingleMessage (I2NPMessage * msg);
|
||||
void TagsConfirmed (uint32_t msgID);
|
||||
|
||||
void SetLeaseSetUpdated () { m_LeaseSetUpdated = true; };
|
||||
|
||||
private:
|
||||
|
||||
size_t CreateAESBlock (uint8_t * buf, const I2NPMessage * msg);
|
||||
size_t CreateGarlicPayload (uint8_t * payload, const I2NPMessage * msg, UnconfirmedTags * newTags);
|
||||
size_t CreateGarlicClove (uint8_t * buf, const I2NPMessage * msg, bool isDestination);
|
||||
size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID);
|
||||
|
||||
UnconfirmedTags * GenerateSessionTags ();
|
||||
|
||||
private:
|
||||
|
||||
GarlicDestination * m_Owner;
|
||||
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;
|
||||
bool m_LeaseSetUpdated;
|
||||
|
||||
i2p::crypto::CBCEncryption m_Encryption;
|
||||
CryptoPP::AutoSeededRandomPool m_Rnd;
|
||||
};
|
||||
|
||||
class GarlicDestination: public i2p::data::LocalDestination
|
||||
{
|
||||
public:
|
||||
|
||||
GarlicDestination (): m_LastTagsCleanupTime (0) {};
|
||||
~GarlicDestination ();
|
||||
|
||||
GarlicRoutingSession * GetRoutingSession (const i2p::data::RoutingDestination& destination, int numTags);
|
||||
I2NPMessage * WrapMessage (const i2p::data::RoutingDestination& destination,
|
||||
I2NPMessage * msg, bool attachLeaseSet = false);
|
||||
|
||||
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
|
||||
void DeliveryStatusSent (GarlicRoutingSession * session, uint32_t msgID);
|
||||
|
||||
virtual void ProcessGarlicMessage (I2NPMessage * msg);
|
||||
virtual void ProcessDeliveryStatusMessage (I2NPMessage * msg);
|
||||
virtual void SetLeaseSetUpdated ();
|
||||
|
||||
virtual const i2p::data::LeaseSet * GetLeaseSet () = 0; // TODO
|
||||
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
void HandleGarlicMessage (I2NPMessage * msg);
|
||||
void HandleDeliveryStatusMessage (I2NPMessage * msg);
|
||||
|
||||
private:
|
||||
|
||||
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
|
||||
i2p::tunnel::InboundTunnel * from);
|
||||
void HandleGarlicPayload (uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from);
|
||||
|
||||
private:
|
||||
|
||||
// outgoing sessions
|
||||
std::mutex m_SessionsMutex;
|
||||
std::map<i2p::data::IdentHash, GarlicRoutingSession *> m_Sessions;
|
||||
// incoming
|
||||
std::map<SessionTag, std::shared_ptr<i2p::crypto::CBCDecryption>> m_Tags;
|
||||
uint32_t m_LastTagsCleanupTime;
|
||||
// DeliveryStatus
|
||||
std::map<uint32_t, GarlicRoutingSession *> m_CreatedSessions; // msgID -> session
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
87
HTTPProxy.cpp
Normal file
87
HTTPProxy.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include "ClientContext.h"
|
||||
#include "HTTPProxy.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace proxy
|
||||
{
|
||||
void HTTPProxyConnection::parseHeaders(const std::string& h, std::vector<header>& hm) {
|
||||
std::string str (h);
|
||||
std::string::size_type idx;
|
||||
std::string t;
|
||||
int i = 0;
|
||||
while( (idx=str.find ("\r\n")) != std::string::npos) {
|
||||
t=str.substr (0,idx);
|
||||
str.erase (0,idx+2);
|
||||
if (t == "")
|
||||
break;
|
||||
idx=t.find(": ");
|
||||
if (idx == std::string::npos)
|
||||
{
|
||||
std::cout << "Bad header line: " << t << std::endl;
|
||||
break;
|
||||
}
|
||||
LogPrint ("Name: ", t.substr (0,idx), " Value: ", t.substr (idx+2));
|
||||
hm[i].name = t.substr (0,idx);
|
||||
hm[i].value = t.substr (idx+2);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPProxyConnection::ExtractRequest(request& r)
|
||||
{
|
||||
std::string requestString = m_Buffer;
|
||||
int idx=requestString.find(" ");
|
||||
std::string method = requestString.substr(0,idx);
|
||||
requestString = requestString.substr(idx+1);
|
||||
idx=requestString.find(" ");
|
||||
std::string requestUrl = requestString.substr(0,idx);
|
||||
LogPrint("method is: ", method, "\nRequest is: ", requestUrl);
|
||||
std::string server="";
|
||||
std::string port="80";
|
||||
boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)");
|
||||
boost::smatch m;
|
||||
std::string path;
|
||||
if(boost::regex_search(requestUrl, m, rHTTP, boost::match_extra)) {
|
||||
server=m[1].str();
|
||||
if(m[2].str() != "") {
|
||||
port=m[3].str();
|
||||
}
|
||||
path=m[4].str();
|
||||
}
|
||||
LogPrint("server is: ",server, " port is: ", port, "\n path is: ",path);
|
||||
r.uri = path;
|
||||
r.method = method;
|
||||
r.host = server;
|
||||
r.port = boost::lexical_cast<int>(port);
|
||||
}
|
||||
|
||||
|
||||
void HTTPProxyConnection::RunRequest()
|
||||
{
|
||||
request r;
|
||||
ExtractRequest(r);
|
||||
parseHeaders(m_Buffer, r.headers);
|
||||
size_t addressHelperPos = r.uri.find ("i2paddresshelper");
|
||||
if (addressHelperPos != std::string::npos)
|
||||
{
|
||||
// jump service
|
||||
size_t addressPos = r.uri.find ("=", addressHelperPos);
|
||||
if (addressPos != std::string::npos)
|
||||
{
|
||||
LogPrint ("Jump service for ", r.host, " found. Inserting to address book");
|
||||
auto base64 = r.uri.substr (addressPos + 1);
|
||||
i2p::client::context.GetAddressBook ().InsertAddress (r.host, base64);
|
||||
}
|
||||
}
|
||||
|
||||
LogPrint("Requesting ", r.host, ":", r.port, " with path ", r.uri, " and method ", r.method);
|
||||
SendToAddress (r.host, r.port, m_Buffer, m_BufferLen);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
42
HTTPProxy.h
Normal file
42
HTTPProxy.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef HTTP_PROXY_H__
|
||||
#define HTTP_PROXY_H__
|
||||
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/array.hpp>
|
||||
|
||||
#include "HTTPServer.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace proxy
|
||||
{
|
||||
class HTTPProxyConnection : public i2p::util::HTTPConnection
|
||||
{
|
||||
public:
|
||||
HTTPProxyConnection (boost::asio::ip::tcp::socket * socket): HTTPConnection(socket) { };
|
||||
|
||||
protected:
|
||||
void RunRequest();
|
||||
void parseHeaders(const std::string& h, std::vector<header>& hm);
|
||||
void ExtractRequest(request& r);
|
||||
};
|
||||
|
||||
class HTTPProxy : public i2p::util::HTTPServer
|
||||
{
|
||||
public:
|
||||
HTTPProxy (int port): HTTPServer(port) {};
|
||||
|
||||
private:
|
||||
void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket)
|
||||
{
|
||||
new HTTPProxyConnection(m_NewSocket);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
996
HTTPServer.cpp
Normal file
996
HTTPServer.cpp
Normal file
|
@ -0,0 +1,996 @@
|
|||
#include <boost/bind.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "base64.h"
|
||||
#include "Log.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TransitTunnel.h"
|
||||
#include "Transports.h"
|
||||
#include "NetDb.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "RouterContext.h"
|
||||
#include "ClientContext.h"
|
||||
#include "HTTPServer.h"
|
||||
|
||||
// For image and info
|
||||
#include "version.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
const std::string HTTPConnection::itoopieImage =
|
||||
"<img alt=\"ICToopie Icon\" src=\"data:image/png;base64,"
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXM"
|
||||
"AAA3XAAAN1wFCKJt4AAAAB3RJTUUH3ggRChYFXVBoSgAAIABJREFUeNrtnXl8VOX1/9/PvXcmewiQBB"
|
||||
"J2CKsKihQXkCJuiD8VKyptXejXaikWbe1C1dqi0lpr7UvrgihV64ZCXaqCUBEUQVBAAZUl7EtYEkLIP"
|
||||
"pmZ+zy/P+6dySx3JgESkpAcX/MiznLvc8/5POc55zznnAfaqFWTaIXPnAt0AzoBqYAB1AAVwAFgF3Ck"
|
||||
"DQCnBvUHxgEXAMOBLsfw22+BT4ElwGKgrE1ftAwaBswEygFlv+QJvALX2AH8BchrY3HzpF8A+xtA4PU"
|
||||
"BwxZgUhvLmwf9AfA1suBjgaEK+GWbDdA0dAswC0iwhVEvSk5A9smFThmIjFSUroPHC0cr0HYexNxTiH"
|
||||
"aMfBFAiT2e99sA0PiUBXwMnFEfwZ/ZB3n1eTDmTDh3IMKdgoYZoi8CXBCABhioA/uRn3+H+OgreGcFq"
|
||||
"vAoWj15udQ2Oj1tAGgcmgS8WJfgczsif3sd3D4OkZyCZnqPQUWEkKaBlgDbd2PO+gDx5H/B462TZwq4"
|
||||
"zPYc2gDQgPQmcH084Z/eE/nkHYjRw9H8VQ17c02A5ka99j/kb59DHDgSl3cC+BswrQ0AJ04GsB4YFEv"
|
||||
"47VJQr/8eNW4kuv8kKF8jEfXSfOSUf6JVe+PydhEwtg0Ax0/Jtv+dHesLU65EPn0Xmt/XJM+ibn0M8+"
|
||||
"XF6HH4+xVwdhsAjp0Sgb1AB6dxCoH67B+oEaeh+80mVE8GLP0a8+LfI6R05KcA1gFntQHg2GgX0N3pg"
|
||||
"87tkd/NRktPbj7jr/SghkxG7j7k6DEI23O5uLkxWWumwl8WS/i9OmPueQ3RnIQPkJKI2PUq+jkDgs5l"
|
||||
"pGdwEfDPNgDUTQ9hbd5EUfds5PZ/owvRPIHr98Oqp9EvHBITBFOBa9qWgNg0FFjrZO1npKIOvgm61my"
|
||||
"1Vq1d4IbhP0euzo9pE3TAih62ASCCioH2TrNn72vQuUPzF34QBDoqdyLywBHHMa+zwd62BITQX+yZEU"
|
||||
"X/uR+V04TCN9ygFOaafNTbyzHnLsNc9g3S60ca7hjLgYlY9yxajNjFWcBNbRqgltKBUidmTRiFnPcnd"
|
||||
"L9DwEUI0JNgz17k5xuRBYfRvX7I6YD8/mBEr+5o/uoTEHwC6vn3UE+9h9qwwxmAw/oh/3or4qJhaE5j"
|
||||
"fGcF5vUzHH/rtV3dNgBgxfdvcfL1a+YjhIgep6Ej/zYX+eg8tMOlzs/RLQv52M/gujHo/pr6D0bXYG0"
|
||||
"+5iX3II5W1I9Hlw/HnP8Qhimjtce432N+uDoKBAJ4AJje2gHQDjjqNPtn34265ZJwxmkarMnHvOi3iA"
|
||||
"pP/cY/5izkx4/UL2CkaTBvGf6Jfw6L7gXus/aCCy4YcujQoZL8/HzdXrKC4x7UHfXdbLTI+1TXINPHO"
|
||||
"/JbNLUMmoMNMN1J+DkdkLdeGc4cXYO3l+M/ZypaiPAFsHvMmDFFl1122ZoxY8Zsyc7OLgxl7JKv0YZM"
|
||||
"RhquugezJh8zQvjmpEmT9hUWFuYrpc5etmyZsWXLliylVOLs2bPXCyFKA/fauAcxfjr+SLsgORHtjz+"
|
||||
"OuYl1F62c/Dhk3My5F7/vQ1Toa8XjmIHPhRAK2L1w4cIDSimPiqCCgoJdI0aM2EtIptAtl+BTH4VfM/"
|
||||
"SlPkalJ9feIyEhQa5fv36Nik/Fffv2LbHHIwH5v4ejx24uQkLttUNe+1uz8K/CIZUrIxVTLUWGMXAhM"
|
||||
"tFdK/y8vLzNSimzDuGo++67b37oPdY8HS2cwOuZqWECqtm0adNaVT86AhQEftuvK361NAIAC1G/uc4R"
|
||||
"AAo4s7UuAT9xUv+/uQ5l1tSqcE3A/f9GeWwru127dnu2bt3auz7jnzFjxriJEyeuEkIIgDufRjm5boY"
|
||||
"bZn4QHIuYPn367gEDBtTXV2+/atWqI4GlIH8f2uYdhFkCUsG06x1/q2jCBNOmNgKVEwDK/otKctcK10"
|
||||
"hEuS5G+U3LaNq5c2dhz549s4/hPj4hxFEgE6BoHmSkhj+7pmHqlwXvWaaUcmFtR9ebMjMzNxcXF/cHm"
|
||||
"DEJNe2GcIAabjhnCuaXW6KAexCrYKVVaQDH2TW8PzItNXxcK9cjbeGTnZ295xiFD+CaMmWKPwD4uZ9G"
|
||||
"g+7bnbX3vP766w8fq/ABpk2bFrTqV26ytorDjB0v3Oi8H5hje0OtCgCOrJh4ocWoUFqxsXac11xzzXG"
|
||||
"Nefz48cGrLvsWZUSkcBwuq00RHTNmzHFlGFx55ZU5gb93HUQ6cffakTG17oWtDQDnO6n/K8+JUs1s3x"
|
||||
"9cT8WgQYNkHdfdiVUVFEaDBw/2Bf7eVgCROTyGXntfl8t1XBmFOTk5e4O+vxflJOrcXLTUxKjdQgWc0"
|
||||
"9oAcKZT5C+vdzjbBODzhwfqnC722Wef7cnMzNwthOglhEjMzMxct2HDhj1BARtG8CpHK6OF0yWz9u/8"
|
||||
"/PxOAEoppJSlU6ZM2dipU6cCIcSXEyZM2KaUKncaQ3l5eXrQHkhHd/T8vTDydEctcEZrA0CPyDfOykP"
|
||||
"hD2eOlJCdEXxPff7551FFmgsWLDg4atSorsXFxd3t2WQUFxcPGTJkSJeFCxceBti2bVtwoyk1CREpnD"
|
||||
"7dEQGj9IknnvABFBcXl+u6rs+cOXNQYWFhLvC9t956K0/TtIMQvee/fPny4FUHdEcqf/RDmyYM6VN/m"
|
||||
"+hUBUCa05uDutuhkgjdOLRvSFRvyZLIHcODV1xxRaxqHu3yyy/XgKqXXnopKI7enR3EZyLGnGnBwuPx"
|
||||
"dP/666935+Xl7QNSIpYqJYToO3Xq1PWRN3vooYeqA98dOwzNdFislILeOTENwVYDAEeXp1uWNUOi7IJ"
|
||||
"za4VbVFTUafXq1RtCZr+POFnDQIfbb7/962effbZdQDgjT7eyd8IsdB9MqQ09q6FDh3rKysoGOvquSq"
|
||||
"mnnnoqzGpftGjRVxs3buwf+MrE0bFd7JwOxLJjcloLABz3/TukoTktmwkuxPgRwVmohg8fHtQg+/btK"
|
||||
"60r1vD888+PCHXrbr7YWTjXjkHLzggKp59SKl5BUW9gD8CKFSu2jh07tm8AYPdMRCkVGwDtU2Omkbca"
|
||||
"ACThLGhHhvtNeGZqqLEoemVnZx+srKwsGjhwYHo9A04A/L9zUZkZzs/t98D8GfUPjuXn538+ZsyYb0e"
|
||||
"OHNkXq9sInTKQf/kpuowDHU3EvEdGawGA476cz4zN/OwMtNl3WxaCUkoVFRV1Sk1NTZg5c+aeY4k8vv"
|
||||
"w7hN8f+wvD+qH9YzL1iQPI/v37T1y6dOnpAYClJKK+eQ7N74v/Q1PGXAJcrQUAjiyqjJO9oxTcOg7jr"
|
||||
"7eGCSdtzpw5I6ln7eeqf0JaUvwZ7jfhVxMwnrmTuuINQa8By1CVB96AjLS6NUhI0CkKG60FAJVOb+4p"
|
||||
"wtTjjMjvg2k3YCx6GJmUEK3eY1G3LGT+i6hhfev3vH4f/OwK9J2voEYPiS+UIX2Q707HXDsLPSkBrT7"
|
||||
"rx/7imOOoONmCMJoIAMWOAChEF5qThx0+Q8eciV71PuqRNzGffg+xtyiaoalJyAuHwE8vR1w1yioaPZ"
|
||||
"YScSmhayba0sfQjpYhF3yJ2rwXUVqJmdkO47QeyEuGItLSrHzF+qacCQFbC1Ax3NZDJ1sQTbUbmGxrg"
|
||||
"TCZdEzHPPweRn0TOYUAPQHwYe4uRPj8kJwAudmAjoYv2t07YYYJazk67hnngot+g1yyzjE9zDjZy0BT"
|
||||
"bgc7bgXXLEBqIqab1OLJSIbkSzCrvVFayw+4W4sNAFbxZxR9/DWnNB04gHQQPlhl5LQmAKx3evO9ldY"
|
||||
"O4KlK76+KaYqsbG0AWO20BL35CWiJp6bwDRe8sTTmUvxxawOAIytKKtBWf4N5KgLA40EuXR+T5/NbGw"
|
||||
"A+j/XB0/+1agBONZr5flxtqFobAMBqohRF//4IzedvGoY0mvpPRP15Tkz1/3JTjaupAfCvWK7oA68it"
|
||||
"VOol/m8j5HFZTHd7tlNNa7mwOJYcT9VMx+haS2/pb2RiOr8A9ShEsdnWYjVXbRVagCAR2IAUdz+BKbR"
|
||||
"wkNCQsATc5ExhC+AGU06vmbAowSs3rqOa/6GWaiB3WmxJmGlB5lxTUxeb8U61ILWrAFqgEdjgfHSe1C"
|
||||
"Gq2UK30hAjbsvpvAF8KumHmNzmVnTsGLhUXTwCNqND+NvaSDQNXj4VczPN8bUspuABU0+zmbEs93EaK"
|
||||
"H2zU60HlmYZ+WhqRbiHK74DnnTIzEnmMCqjDrU1ONsbhb2GuLkxy97DHX+ac0fBNv2Yw68NW73D59t+"
|
||||
"zQ5NTfjamw8UI76NWLtVqRoxo7hzoP4T7utztYvbqyDrZp+qWpm/KvCSrUeH+sLsz9EDO+PHNANTTYj"
|
||||
"TaAJWL8D84zb0eKlhIfQ97CaSnzVBoBwWgecS5zj2V5fitAE8sJhCGk2/TJmuOHVxcjL7zvm84ausgG"
|
||||
"/rs0GAObOhQ8+QLz8Msp2D+Pa/qMGIz/8M8JtNGETSRfqhzMw3/jkuCeTAO4B/tpmBAJCMFIpXsc63r"
|
||||
"VOJa8J1CvTUD+67OScFhI665evx3/FH9DKqsL4qM7nbDqSIQ9QqK3hm/rwWQBPY5192GoB4BaCuUpxN"
|
||||
"cexNTq0L2r5P8DVyNrAcMGuA6jJT6AWrQnn37WMlT/kKg2UkCh0NHR01vKt+ojP1CrW1XXO0HvA1a0R"
|
||||
"AFcC79ZzPMECzsgPrj4P+e4DDX+CSKAl7RfrMR94BSK7fmbTUT3Ar0QmGULGwK6Ojh+/eoV31XyWiDj"
|
||||
"PtpwY7fJPVQC8BfxACOKWYuaQLccx2ncOZ/o6kam2sUu7h0dTvCFFRmf0Qm6Y7dxXONCvxzTrl9ZtGJ"
|
||||
"anvnkr5pyl8NwCKyoZ7beOkrfzQ91H/fLPNTQKOCin8VdR41wgJbDyA88/1QEwGPiEOgoiu5Erf8r1n"
|
||||
"rMY5K+mJmy8bzI/4W0WBlOp774W+eht4YWZhhtmvYf8cDVKSkSfXNSg7ojeOaiMVLT0ZJQmrPMAj1bC"
|
||||
"7kPIrQVoq7cgF64BUzovKSkkq3uYrAaSp/uPI4Otkmp1O/fidwaOAOZhHZN3SgLgfuDBgBp3KrZIJkl"
|
||||
"N4UbPBXzP54kQfIDms9T9Mm8HI2oFc1DZIZW/moCH30D+4aWGe84cstRVXMJYRmlefCd0rU1sM6fzRL"
|
||||
"xw8R3AM41q05xkwacDn2L1BwqKPEL4YjyXem7mB14fPmIJX0Own0NB5o0dhszNQg+tzFWg/vDSiQ+6P"
|
||||
"e3UBQzjIkbQk66ahxpOVPgAQxio96OXmc9OJxAo2zN4HauZdosHwDXA20RUBIXO/q50lvcztaoD7ZSv"
|
||||
"DgYnkKDW8m1w/HeOR0SWZb++JLwGbzTnmns5oO2hAB9+R2AlkyS70ln0opsaSB8xmAGiI+21GrwoFB5"
|
||||
"qGowhXnxcw2XiEZ6N9RUFPAXc2JIB4Lbdm8siLfcQ4Ysfc7XnOsZ5a/Ai6+EF7qZAL6E0cCKHuvz88A"
|
||||
"JNw4B5n9UCII8e8lf8n2EiMdCRSFVOpfTiFQJBAm6VTpoukbqJiR8TZY+jIYUeSd9jcF3L049bMgBGA"
|
||||
"EvsiJ5ygncG6eoh7q7sRKaswVtvS/o9/ucOXHPCBSj8EZE4F+r9lbWz/xauFQFB2tpFuHHp7pBgYxXV"
|
||||
"nGwy0EV72vlLKNXrMJg3NMb9tUYE1hu2T+uKYeKIUWqY/wUeqcimo1THEPvREHzE58HrTr4SEen7L15"
|
||||
"VO/s7k6UGM6BZppVJJNl0rCuMvKElaYAJwNxYwZoA/VbdVnkeQ81o/1nV6Zx8wJKg8NOTURcNR4SWlB"
|
||||
"s6vLAo1Pi4tFHV+ImQAlzxxfBhS/IC/g3cHE/wncmSM/h1VRop6niEn0Sieo/FQd//l9egTE+EJtNRc"
|
||||
"2oLz9TFjBD+ZlptJoA4QSQBvNqY929ItTizLuFfxAjfs8yoSCNF1RWW0NAQCAo4qCXgVoHzIrexWy/m"
|
||||
"aFBl3j0hOkPovyHG32jORaKaLOCVSALVeKQ7Rum/hkYhxfH6Ec1pCRqgHzA5nvCvZaz3x4yvqcErnFW"
|
||||
"hItA9TUPjOV5P/IgVLstZEGoU3/MNYZD5DouCxt+lZyPbpYX7/oYBL1rHs+gAlzASWWe/p8aY2YJt7J"
|
||||
"YzeFJU4RG96Sb/zr1a5GzX0JTtzcRS/6olAOD78f1AF5OY4KmiWsRaCQPCr6BK/IoHU8qoDNn0UXzKl"
|
||||
"65P+TLMoPzNhGjfH5D/XWmpiySS1Bn016rxnHQAHKRI3sujwefdwV7xPkvkWEaFCXtP7CODBPBcY4+z"
|
||||
"oZaA5+NFq3T0uDo4FOJT+VOo8IO92CLzANuloi45L9pgeGtZ7VoymnOaxPhLJIFHmBX1/qesUu4Ip2g"
|
||||
"jW+PN8HdbCgAgTkJnNR7xBesNZ+FLBAINwYv8J6EKjwgLFMW42S+uQpkR5wYaBrywqPYnFzAM1QRFxl"
|
||||
"vZJQs4GMWLQooJPftaR+drNsYa4OsnY6wNCYAvgHtjgeBv4tmk6Li+InASvBu3WslaV9jMV+ERw9DWM"
|
||||
"VOvRkQaf6YfteDL4DOp0+jXJMbfmhhueyQYXRis5CvRVOq/MQJBD2PFrsMPfRDgVT5xFw+mxArzSqRI"
|
||||
"I1XhgCClrGtI25Yb0A3ZKSt67M8tqLX2hjMkZry/MUlHZyf7HD9zYYQ9/Vd8J2NMGA/WplmLA4C1jMP"
|
||||
"fIx9MAUcpE1P5U6qJiSL02RVevNzFT6rDIgKiFkChdONF0Y0ZjUR44t3ae57DmcJsAt9fR6OcCkfg+U"
|
||||
"JOw9DR+JgVsS7zwskab2OFR39rxwQEhG/3HqZETOa+1AqqRKTW60GuvIfJ1YrwXUKlwq8xfkT0rFm3G"
|
||||
"XPL3tr3z2+CAzgkUr3CO3IHex0/r6Raq8KjAEykWs6aWNb/yy0dAACvAGdBtBleQZW4nftSN7FN1yNS"
|
||||
"6Rdbvn/Y+h+6lAC8+jGyqgYZ6B1gGPDQa7UXGckw5cI4qeq/iCPyRu7mbRaJeJ7HS8yTblx8yCexwp5"
|
||||
"+2546aZHIBiUFbGCwGMIGFfSKrAcaDCgNEbrdKy5hpHcyP/J48XMXD6QWUiycMoSc3ptwAfLBW6wzhT"
|
||||
"In1D7L37mHbuSeTACom7hbefE5tX+NMnrGcaFawRpKKXca4zzghhYLgOD6Hf32UwLuUIE0sJDvJuKmM"
|
||||
"1nmLgr0+gg/8v9Tk5CV1bWnjbzPbGIHnRo+4vcOi8w5vB+qTcsmZVDR1UXKp5Uc+ayKHKxDMlQ95HEX"
|
||||
"8M8WuQTMJe52zi90xA9DPw58twYvuynQNa3W4g8FqF1rJ2JpglDhA5RSftKcfxcGK1gbVhiyrS/mUzl"
|
||||
"0mZZJxv960rtyIPLGduyq54Q7cjKXrgYFwAgeZ26Mh7yXnoYf9YaAoQJEQPjBYI/t5gUEnKzhfzKHzS"
|
||||
"t7oeZ2Y98vO7K/h5viyMJLJx37AUuUOEn5rjp6WDh3eBKHurnoEBiTX4GElOe70PPlLmyvBwgOt0gAf"
|
||||
"AK8wi/FDaDmhrw/i1xm00esQ8kXEDxiFUL2Ddh0gRkf+i8gHu7EnkkZDDg9Ee3yVLo+lE3u9jwyN+Wx"
|
||||
"9/I0CoK/dxjLG7wvKqk6KVogAmji0lQSvA539iuY0I4+d3TgmzpAcLBFAmA01llw07GS2QOa4Gfs51v"
|
||||
"2iwXsls+QIbrSTaym1zYXYriyNUGE8EFAoog+W7BaQVcX3d7uRtdNeRR1dVEYg5ni1/xZSRq/lYSIsK"
|
||||
"U6GbHz2kwFT+YwECiLc8k9LQ4AS4EPQNwMarptC1xvT843gMeplgB3YfIj9sov0LTpZH/lFlo7oCBU+"
|
||||
"EKgBKhfH8SbJJz3cf0WELJ29aP9be2d1eoRSsXPuFcVU6Ias9XgTvbJiLHFTe8yFUaqFiNQ0FJtgPsB"
|
||||
"RY9gHlhoOcvEoFrOEjdRpv5Cd93Axz5d4+IJsqJHD/KASiHANgeEUlCp6DpsJ4UaURGjIFVJ3E/m0Gd"
|
||||
"GNt85gaCMCjGFP/Im800dXWkNpPAEgkQS1Lfkq9/zSJgDtNWLHg9ufiitkPSOiaeTTKIhZr+HjqKAYv"
|
||||
"XTGN+5kgzxfxxVW+ijJZPAdo6I6jFKZp93iKLDaLNmcbEQLITa+kBbKwig9I4O+G/MgGGJVBjCPnNYw"
|
||||
"EEfe5ZXoS2qQH+9FFUl4x68qC5mBOczlNPoRwJuzY9JfcPFOjoJuNjJPrmElfyPzwKuZlixaprGgbKB"
|
||||
"5FZE6C6XgKMmBefuIHGXz/ngTKz0r5tbFAAA3gHtGpCRLuB0+/U4XfTVpMvz2MFWMrTNJJs3vbJTlJa"
|
||||
"h3XGHJQEhKFSKzIALGOYOKstWsOko1rk6qdQ2WjrmtT6T9rIX3UQvutGJTNWJTC2NFBJJUAKBDz8VVI"
|
||||
"rDlMj9HBJb2ckGtigPNYHQZTndkPTAoJCj5NMl4Nnel8XWGdlk+hUFm2vouaSSqldL8a6uJjcOz4WtP"
|
||||
"OfRUmgW8G8QHzJAADzChVHfeYw8A+AfZGiv0V+MI1sD+N3vLH1805AgQ2YLgRTWul/7r9VLuKlfgWqm"
|
||||
"EvpRwpWUcCc1/ALFFBQ/Zq/9eeT3Q1/1ucdJpxNKCfsZMJfB2uVsMDeBWMnSsIe4mk5iMO3Mn5OijaC"
|
||||
"repAj2gIKzUsvRf/7v5A/vxS9x3pLA2ga+UohlLKqdYMbQfFiqvG0mosictERwC4U0LGelxAYlNIZHT"
|
||||
"DRqKELKXTFSy7J+ElAEd7WsiNdSeMA5XQ+Xo1kz6eTTie0BCwgV4xjv3qZwdzMhmBk7zqgEz3FU+xSk"
|
||||
"8gWP6VQ/RGrRChAd16A/s/PLOHfMQV95rPcISVPaAIlVVDgIiLCHP85UijhdLycQRIppAeXdwMvGyhm"
|
||||
"KZmouKAXdOMw15KGP6SPX31ySqup4UU7sh0+VlHP8adgdUlrORpgHPvVJ8BoOwNGBE3Z03Czhz/QWXx"
|
||||
"qFWKJj6nNzX7sJsQXr1hsnTYNo8SDlJJUzT40Mij8qzmAi1QOotjHUUpIohQFpNm3KyWLJLpSzun4aU"
|
||||
"+P4MwMTRb14mYAOfSljH/hxU/HGI8kGUcy3uNo4phEAj+nmq8o5BAmAkEqCWThZxUGVTH7IAis+r+qF"
|
||||
"qcBAjQfxBUhCJ8IooLBKoES8RZ7w5B/xyC0nhmoHpeiCtpBUhJi8mSUYTBL+cVtZuhEuRZBp5CRavYr"
|
||||
"dE5Jju2oRZMynicZ6eCvp1PCJDpwoodNaiGawwCeZDvK0fUTWI2yf9dUdtwJO8ZzgSsi1NsboJLYpv0"
|
||||
"nQvgPno22dyOqqBi1Efjr47D4BWsM0i8GmPG0pLIF7QO89svHsZ+zqZPO2BgRxA54G6SEQIYsG5Y6i3"
|
||||
"XE/RtNKfwGAYBTD5Nr6KLNo0q+ZP//tN7wu3SE2o4amoc6+n2YPh2uGop+9W0BnqlBUbPDy+5Geeq+5"
|
||||
"JLqcH5xSj3X+2PncCz137WpPbkGzwi6jjOEQZW6DvgJML0DHDyI0HOgSqCOjIO1WxFTf4Lr7AtRN90W"
|
||||
"nMOZUVngnkaK4fqAc0iI0AKCdNo3+L0q2E3shpcjTzkAzOMbBTkqGM0YiOjTGfHwFtTi3jBnPaJfGVp"
|
||||
"7N77Jd1rzzdDEwGCMNSzWGzNiduLUz8Ho6tgIVSRVIaDSHTeKup5SALBAsLE2GrgC9ccdlqAPZSB67E"
|
||||
"XMWYt5ur3lcUMvhKlUXiD6F7bqF1HdaPs4brIhYonJaoQOEV5Sgi5gF6yMuHA6+5QDQPDJIh6tfwGs2"
|
||||
"YGcPhqu3w6fPoo41AuhFJmOFziA0WjtrCXQJWLvwN0oRYQq5C+N9ChLt+8pC4C1ayE3t/b/P95sPfz0"
|
||||
"T+BWgbjvPUR5KZLo42Ks0Gg57fFQ0iiDU4BOedh7+2PGB04k0lITtDUUGon4IxzZLqcsAAD2xyh+XeN"
|
||||
"DLP8MuXYtAEVhnnqot7++Eas7wqOCimWUNnjLjEi7xkVCRFQw7ZQGQCxav8FeC28HYEuYpx66ibKaZF"
|
||||
"z17B51rCGw0ohedKV0Ib+Bc/IOBw1LgUGNXa4sGjoY1+IAEGIkQWgihAjODs1eDJJZFzeF6vhIx0MZq"
|
||||
"VE6YSGJeBvIGHRhssIOBen4cJFIDUaEBiht3QB4KfjXUlsEwlacHpKosVVzCnoDLwV7KMHauCECfCm8"
|
||||
"SPkJc0YDlnGASjIAQXYwLhCph3a0bgDU0pwwdahIJBMdDRNFEkspaDBlqQFrHXoXdgFSUZhk8zrF6Mf"
|
||||
"ZD1YDNnOIr+kKKFLxkYKLcnwOu5Gr2wBg0b+i1PFhBN0QgORbulLaQD1ziznM7qDraYbxIweNZHwcoS"
|
||||
"MfUnbMRqEBrGIbi+kEKNz46GTnJRwOb5Nr0xdtAKh1/cJBUI2BH0V7u5Z8Dj70E8ycEVQx116HXUhyQ"
|
||||
"7Zt/HiQQC4GBpJtdGQ1+49B81TxNkWsIc/WYT664wI0SvDhj2oV9kJTM725nRmUjXWapgpzC/uisxMT"
|
||||
"PwbZ7OaH9Dgu5awo5jUSKSMZ8NMHHZBstwHREUmGHXoyMdll8+cHFNOZrLjTaC+FfEA6pp0QkoGfLFx"
|
||||
"IwIdkDypiwgmgE1DYlAxvbsfGVWIdFnVWGHtr8JGDzlEklbSngqP0JbHeO3cGUEARr5OMh2QAeqAF/y"
|
||||
"ulxj7ixyTN5omGhgs/lRhsQqMPB0iinQMHJYso5nOysGoC/HRB0Q6XvYUt7YBzpPDvp5G7gLZEDRAAZ"
|
||||
"U0UwzrjRaFxyF6VsyjiCjTS6Ri2/05YGOko24EVlFFK96Bm6YYXt531I4B9gMcWVx4ayr63AA7hpxwd"
|
||||
"8HIhRxlMeyRuNLx8w2E+IR1JKtauv4+sEDXvR7Eb6SD8X2CdBUAbAJzpOmqLjWupD4rDVFMa3GARJLC"
|
||||
"fXAyS8JBCd2oopgwfJeiU0t6e/9Z33fjJBfQQ004g2YZJID0uG5O0kM814ACSimCF8mEySeEwEiuDAF"
|
||||
"z46IwgwW4CJIBKajgQteYLrJPS/9ZcGN2MT+HlQ6wzBmopGS9dSKAUH4WIei5hVgQuE500jChNcRBJO"
|
||||
"aEF6X76YKAIL1IvwUsxRths1jDJQpJur/UBQB3G5Kij/yBsO6eouTDZaMYAqHJ4x025zfAUFEe/Nz35"
|
||||
"AAABiUlEQVTwUoHAjJppVk5vMpJ0dNwkhC0TGlCJj8OANyIeoDA4iEnnkJZe1sEGbtojqcCHHz8JGCT"
|
||||
"jQqIH+13VYHIAiT8uX4cAi9s0QHxKBKqDccGIM4VIwkMSbhLwY+BGpxrwIzAwcKHZwgv9XQ1evAiq0C"
|
||||
"hH2QEZFZMvafjojIGsg0cC6+yXIkyqo1LCnWgHcc5Fbn0AOA34zjEqeEM9x69C/lVYuwuh28surGNr6"
|
||||
"pOfH6kffWQCabijMv1N/FQgKMVPTdQOX11jfgbrRLBWTgMdATia+pVSncyyMB8JmCQiSUQFtdOJXfMn"
|
||||
"bRrAmcqD1vWpTQLoBexqykE0t3N0noCoLdpTlRQnsSFkS9AABlbCtqL1kKDVJ4TU0sWtzAISWAdptmk"
|
||||
"Am9phNX9QTcwD1cg8K8HqBLYO+FEbAMIpF3gc+AGNv1G1GPgSqzYgkKeTBmTar2ygg22TGHZgqgBYb/"
|
||||
"+mHGvzKrRS0R/yqsZq++6BRshpPMUDQcfzHFrIsqZHhWqasAtHc6b/D3cbSAuGcmWdAAAAAElFTkSuQmCC\" />";
|
||||
|
||||
const std::string HTTPConnection::itoopieFavicon =
|
||||
"data:image/vnd.microsoft.icon;base64,"
|
||||
"AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAABsAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgQAAAIMAAACEAAAAg"
|
||||
"wAAAIAAAACCAAAASQAAACUAAAAmAAAAOwAAAFIAAABgAAAAVAAAACcAAAAVAAAADQAAAAcAAAADAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAAAA/wAAAP4AAAD+AAAA/gAAAP4AAAD+A"
|
||||
"AAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/"
|
||||
"AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAP8AA"
|
||||
"AD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4A"
|
||||
"AAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/wAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAKQAAAAAAAAAAAAAAAADbAAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/gAA"
|
||||
"AP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAIA/gAAAPwAAAD/AAAA/gAAAP4AAAD+AAAA/gA"
|
||||
"AAP4AAAD+AAAA/wAAAFkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAAAAAAAHAAAAP8AAAD+AAAA/gAAAPwACwD9ABEA"
|
||||
"/QASAPgAFgD4ABUA+AATAP0ADQD8AAIA/AAAAP8AAAD+AAAA/gAAAP8ADQD7ACMA/QAlAP8AJwD/ACI"
|
||||
"A/QATAPwAAAD8AAAA/gAAAP4AAAD+AAAA/gAAAKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe"
|
||||
"AAAA/gAAAPwAJAD+AAwA+wAAAP8AAAD/AAAA/wAEAPwAEgD8ACQA/gAmAP8AHAD8AAEA/gAAAP4ACwD"
|
||||
"8ACUA/wAkAP8AFwD8AAkA/AAQAP0AIwD+ACQA/wANAPsAAAD+AAAA/gAAAP8AAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAEAAABNAAAA/wAAAP8AGAD8AAAA/QAYAPMARQDyAFEA9wBOAPIAPAD0ABEA8QAAAP8"
|
||||
"AGgD7ACMA/wAVAP0AAAD9ACMA/QAkAP8ADwD9AAgA+ABBAPUALADyAAAA/AAUAPwAJgD/ABgA/AAAAP"
|
||||
"0AAAD/AAAAdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAP4ADQD8AAgA/wAqAPAAcAD/AGoA/wB"
|
||||
"qAP8AagD/AGoA/wBvAP8ATwDvAAAA/QAbAP0AEwD8AAYA/QAkAP8AGAD8AAsA9wBuAPgAagD/AGsA/w"
|
||||
"BjAPkADAD3AAQA/QAiAP4AGgD9AAAA/gAAAP8AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZgAAAP8AAAD+ABY"
|
||||
"A+wAQAPQAcQD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBTAPEAAAD/AAcA/AAMAPwAIQD9AA"
|
||||
"AA+gBoAPgAagD/AGoA/wBqAP8AagD/AHAA/gAuAO0AAAD/AB8A/QAPAPwAAAD+AAAA+AAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAANIAAAD+AAwA+QAAAP0AZQD1AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8Abg"
|
||||
"D/ABcA8QAAAP4ACAD9AAAA+QBeAPkAagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AbQD/ADkA7wAAAP8AJ"
|
||||
"AD9AAAA/gAAAP8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAALi8AAAAAAAAAAAAAAAD/AAAA/QAAAP8AKwD1AGsA/wBqAP8AagD/AGoA/wBqAP"
|
||||
"8AagD/AGoA/wBqAP8AagD/AGoA/wA9APUAAAD/AAAA/wBKAPMAagD/AGoA/wBqAP8AagD/AGoA/wBqA"
|
||||
"P8AagD/AGoA/wBxAP8ADgD1ABMA/AAHAPsAAAD/AAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzPAIAAAAgAAAA/wAAAPwAAAD/AGQA9A"
|
||||
"BqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8ASQD1AAAA/wASAPMAcAD/AGoA/"
|
||||
"wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AawD/ACsA+QAOAP4ADQD8AAAA/wAAAEUAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7gAAAAAAAADgwAQAYLwAA//8AAA"
|
||||
"ICIwAAAP8AAAD+AAYA8wBwAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/A"
|
||||
"EIA9QAAAP8AMADvAGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGsA/wApAP0AEQD8"
|
||||
"AAIA/QAAAP8AAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAVgEAAA"
|
||||
"AAACJ5AQA2ygAAND8BAHV+AgAAAAAAAAH/AAAA/gAcAPQAbAD/AGoA/wBqAP8AagD/AGoA/wBqAP8Aa"
|
||||
"gD/AGoA/wBqAP8AagD/AGsA/wApAPYAAAD+AB4A+gBsAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8A"
|
||||
"agD/AGoA/wBrAP8ALADxAAAA/AAAAP4AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAABYAhQAfAE8ABgBWAQAAAAAAIoABAAAAAAA5UgEAAAAAAAAA+AAAAP4AJQD2AGsA/wBqA"
|
||||
"P8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBsAPgAAAD6AAAA/gANAPAAbgD/AGoA/wBq"
|
||||
"AP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/ADsA+QAAAP8AAAD+AAAA4AAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtQAAAAAAAABYABgAZgEGAIMBAAAAAAA+fgEAAAAAAAAAA"
|
||||
"AACA8wAAAH+ACkA9wBgAPgARQDwADoA9gA5APYASgD0AHAA9wBrAP8AagD/AGoA/wBxAP0AFQD0AAAA"
|
||||
"/gAAAP4AAAD5AHMA/QBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBvAP0ATgDxAEwA9AA1AOkAAAD/AAA"
|
||||
"A/gAAAP8AAAB3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAvAAzAKcBAADXAAAAAAAcAFYBF"
|
||||
"gB4ASAAZwEAXT8BADpdAgAAAAAAAQG+AAEB/gAAAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAPsAJAD1"
|
||||
"AEQA9ABEAPMACQD3AAAA/wAAAP4AAAD+AAAA/wAiAPMASAD4AE8A9gBuAPgAbQD/AGoA/wBvAP8AFQD"
|
||||
"yAAAA/wAAAP8AAAD/AAAA/gAAAP4AAAD+AAAA/wAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAABgCuAQAAAAAAAAAAAAAAACEAkQIWAJMBGQBXAQCWlAIAAAAAAAAA7gABAf4AAQD+AAAA/wAAAP8A"
|
||||
"AAD0AAAA/gAAAP8AAAD/AAAA/gAAAP8AAAD/AAAA/gAAAP4IAAn9JwAs/TsARP5CAEz+PQBG/ioAMP4"
|
||||
"EAAX6ABkA9gBAAPUAUwDrAAAA/wADEfUAFXztAA5U9wAAAPcAAAD/AAAA/gAAAP4AAACYAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAABcAugAkALIAAAAAAAAAAAAJABQAHwCaAR8AhwMNKUwCAAMDRgAB"
|
||||
"Af8AAAD+AAAA+QAbn/MAK/zwACz//wAs//cAIMDkAAAA+QAAAP4AAAD+CAAK/V0Aav6TAKn+nQC0/5c"
|
||||
"Arf+VAKv/lACq/5QAq/+WAK3/nwC3/4IAlv5FAE/+AAAA/wAAAP8AHKnzACnx/wAq9v8ALP/5AAo+9A"
|
||||
"AAAP4AAAD+AAAA7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtAQAAAAAAAKoACABt"
|
||||
"AQwAYwAAAAAAAQAERQAAAP8AAAD+AAAA/wAfuPEAKfL/ACnz/wAq+v8AIsrwAAEH9gAAAP4AAAD+JAA"
|
||||
"p/ZoAsf+TAKj/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/lACq/0sAVv4AAAD/AC"
|
||||
"PQ8gAp8/8AKfP/ACny/wAYke4AAAD/AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAgAAAAK0AAAAAAGsA/wAAAAAAAAACSgAAAf8AAQH+AAAA/gAAAP8AGZHnACfm+AAdqvIACjz"
|
||||
"tAAAA/wAAAP4AAAD+CgAL/ZwAtP+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAK"
|
||||
"n/kwCp/5IAqP9OAFn+AAAA/wAetPMAKfP/ACny/wAq+PcAAQfyAAAA/gAAAP4AAAD/AAAAEgAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKkAAAAAAAUAFwAAAAAAAAABeQAAAP8AAAH+AAAA/wMBBf0"
|
||||
"AAAH+AAAA/wAAAP8AAAD/AAEB/gAAAP4AAAD9AAAB/k4AWv6SAKj/kwCp/5MAqf+TAKn/kwCp/5MAqf"
|
||||
"+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/ZwB2/gAAAP4ACC32ACHC9AAZk/AAAQX3AAAA/gAAA"
|
||||
"P4AAAD+AAAA/wAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUAYQEAAAAAAAAAwAA"
|
||||
"AAP8BAAH+EwAV/YkAnfJ6AIzzBQAH/QAAAP4AAAD+AAEB/gARF/0Aa5P9AMb//gACAv4oAC7+lwCu/5"
|
||||
"MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5oAsv8YABv9AAAA/"
|
||||
"gAAAP8AAAD/AAAA/ksAVv0SABX9AAAA/gAAAP8AAABmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAEAAAATAAAA6AAAAP0AAAD+IgAo+5oAsfCSAKnxlQCr8RYAGvwAAQH+AAAA/gAAAP4AQFf9AL"
|
||||
"r//wC3/P8AXn79AAAA/o8ApP6TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/k"
|
||||
"wCp/5MAqf+TAKn/mgCx/0QATv4AAAD+AAAA/gAAAP2gALj/WQBn/gAAAP4AAAD+AAAAtwAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAA9gAAAP0AAAD+HwAk+5wAs/CSAKnxkwCp8VEAXfcAAA"
|
||||
"D+AAAB/gAAAP4AAQH+AAAA/QDD//8At/v/ALDz/gAAAP5aAGf+kwCo/5MAqf+TAKn/kwCp/5MAqf+TA"
|
||||
"Kn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf9sAHz+AAAA/gAAAP4AAAD9nwC3/5YArf8A"
|
||||
"AAD+AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAAP0AAAD+AAAA/pgAr/"
|
||||
"CTAKnxkwCp8ZIAqPGHAJvyAAAA/wAAAP4AAAD+AAEB/gAAAP4Ae6X9ALb7/wC+//8AJjX9KgAw/ZcAr"
|
||||
"f+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/mQCv/wAA"
|
||||
"AP4AAAD+AAAA/Z8Atv+YAK//HwAk/QAAAP4AAAD/AAAAZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHA"
|
||||
"AAAP8AAAD+AAAA/gAAAP+RAKbxkgCo8ZMAqfGTAKnxkgCo8XYAiPQEAAX+AAAA/yUAK/sBAAH+ACg3/"
|
||||
"QC+//8Atvr/AG6W/QAAAP6YAK//kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp"
|
||||
"/5MAqf+TAKn/kwCp/5MAqf9oAHf+AAAA/kAASf6UAKv/kwCp/0cAUv4AAAD+AAAA/gAAAMkAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAACRAAAA/wAAAP4AAAD+BQAG/nsAjfOUAKrxkwCp8ZMAqfGTAKjxn"
|
||||
"QC08JcArvCYAK/xSQBU9wAAAP4Aqen+ALf7/wC3+v4AAAD+WwBo/pMAqf+TAKn/kwCp/5MAqf+TAKn/"
|
||||
"kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5wAs/6ZAK//kwCp/5MAqf9bAGj"
|
||||
"+AAAA/gAAAP4AAAD/AAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE0AAAD7AAAA/QAAAP4AA"
|
||||
"AD/TABY+J0AtPCTAKnxkwCp8ZMAqfGTAKnxkwCp8Y4ApPEAAAD/AFRx/QC4/P8Avf//AC0+/RQAGP2c"
|
||||
"ALP/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+"
|
||||
"TAKn/kwCp/5MAqf+TAKn/YQBv/wAAAP4AAAD+AAAA/wAAAG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAABAAAAAAAAALgAAAD/AAAA/gABAP4bAB78jwCk8pMAqfGTAKnxkwCp8ZMAqfGXAK3xKwAy+wAC"
|
||||
"BP0Axv//ALb6/wBvlf0AAAD+dwCJ/p8Atv+dALT+lgCt/p4Atv6eALb/lwCu/5MAqf+TAKn/kwCp/5M"
|
||||
"Aqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCo/1IAXv0AAAD+AAAA/gAAAP4AAACRAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATgAAAP8AAAD9AAAB/gAAAP5ZAGf2ngC1"
|
||||
"8JMAqfGTAKnxkwCp8WAAb/UAAAD+AJLH/gC3+/8As/X+AAAA/gsADP0AAAD9AAAA/gAAAP4AAAD+AQA"
|
||||
"C/SMAKP1NAFj+gQCU/pwAs/+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5sAsv8cACH9AA"
|
||||
"AA/gAAAP4AAAD/AAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO"
|
||||
"AAAA1QAAAP8AAAD+AAAA/hMAFfxzAIT0nQC08JMAqfF5AIrzAAAA/gA6T/0Au///ALn//wBkh/4AERj"
|
||||
"9ACs7/QBFX/0AT2z9AElk/gAxQ/0AAgT9AAAA/gAAAP4UABf9bwCA/pcArv+TAKn/kwCp/5MAqf+TAK"
|
||||
"n/kwCp/5MAqv9rAHr+AAAA/gAAAP4AAAD/AAAAhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAA/wAAAP4AAAD+AAAA/hQAF/xJAFP4GwAf/AAAAP4"
|
||||
"ABgn9AMP//wC3+/8Auf7/AML//wC8//8Auf7/ALj9/wC5/v8AvP//AMb//wCj4P0AWnj9AAAA/QAAAP"
|
||||
"42AD/9mgCx/5MAqP+TAKn/lACq/54Atv9YAGX+AAAA/gAAAP4AAAD/AAAArAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAh4AAADVAAAA/gA"
|
||||
"AAP4AAAD+AAAA/gAAAP4AEhr9AJ3V/gC3+v8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/w"
|
||||
"C3+/8At/v/ALf8/wDB//8AYYT9AAAA/ggACv1cAGn+bAB9/UEAS/4CAAL+AAAA/gAAAP4AAAD/AAAAl"
|
||||
"gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAABAAEBFAAAAP8AAAD+AAAA/gAAAP4AUGz9AML//wC2+/8At/v/ALf7/wC3+/8At/v/AL"
|
||||
"f7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf8/wCq5v4AERj+AAEA/gAAAP4AAAD+A"
|
||||
"AAA/gAAAP4AAAD/AAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgAAAD+AAAA/gAAAP4Aeqf9ALz//wC3+/8At/"
|
||||
"v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At"
|
||||
"/r/ALz//gAcJvwAAQH+AAAA/gAAAP4AAAD/AAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI0AAAD/AAAA/gAAAP"
|
||||
"4Ac579ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+"
|
||||
"/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Avf/+ABcg/QAAAf4AAAD+AAAA/wAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAABIAAAD/AAAA/gAAAP4AP1X9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/"
|
||||
"wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wCy8v4AAAD+AAAA"
|
||||
"/gAAAP8AAACKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAA/QAAAP4ABAb9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/A"
|
||||
"Lf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/"
|
||||
"ALf7/wC3+/8Atvr/AHmm/QAAAP4AAAD+AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA/wAAAP4AAAD+AHWh/QC2+v8At"
|
||||
"/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8A"
|
||||
"t/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDD//8AGSP9AAAA/gAAAP8AAABuAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQAAA"
|
||||
"P8AAAD+AAUH/QDE//8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3"
|
||||
"+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atvv/AH+s/gA"
|
||||
"AAP4AAAD+AAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAALMAAAD+AAAA/gBZd/0At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7"
|
||||
"/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf"
|
||||
"7/wC3+/8At/v/ALf7/wDF//8AAAD9AAAA/gAAAP8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA/gAAAP4Al9D9ALf7/wC3+/8At/v/"
|
||||
"ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v"
|
||||
"/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Auf7/AERe/QAAAP4AAAD/AAAAUQAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP4A"
|
||||
"AAD9AMb//gC3+/8At/v/ALf7/wC3+/8Auf7/AMb//gC2+f4AwP/+AMT//wC4/f8At/v/ALf7/wC3+/8"
|
||||
"At/v/ALf7/wC3+/8At/v/ALf7/wC8//8Avf//ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALb7/wBvlf"
|
||||
"4AAAD+AAAA/gAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAACgAAAP8AAAD+ABgg/QDA//8At/v/ALf7/wC2+v8AuPz+AEVe/QAAAP0AAAD+AAAA/gA"
|
||||
"EBv0AU2/9ALz//wC3+/8At/v/ALf7/wC3+/8Atvr/AL7//wBmi/0ALj/9ACQx/QBJZP0Ai739AMD//w"
|
||||
"C3+/8At/v/ALf7/wC3+/8Aksj+AAAA/gAAAP4AAACZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAD/AAAA/gAtPf4AvP//ALf7/wC3+/8As/T+AAo"
|
||||
"O/QAAAP0SEhLtAAAA/gAAAP4AAAD+AAAA/gAWH/0Av///ALf7/wC3+/8At/v/ALHx/gANEv0AAAD+AA"
|
||||
"AA/gAAAP4AAAD/AAAA/wAuP/0Avv//ALf7/wC3+/8At/v/AKjm/QAAAP4AAAD+AAAApwAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAA/wAAAP4AL0H"
|
||||
"+ALz//wC3+/8AvP//ADJE/QAAAPfc3Nz1TExM6wAAAP4AAAD+AAAA/gAAAP8AAAD+AGeN/gC3+/8At/"
|
||||
"v/AML//wAeKf0AAAH/AAAA/gAAAP4AAAD+cXFx3YSEhOoAAAD9AEVe/QC6//8At/v/ALf7/wCs7P8AA"
|
||||
"AD+AAAA/gAAAKEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAP8AAAD+ACAt/QC+//8At/v/ALP1/gAAAP95eXns/////2FhYeoBAQH/AAAA/gEBAf"
|
||||
"9eXl7lDQ0N9gAgLP0Av///ALf7/wCNwf4AAAD/NDQ03AAAAP8AAAD+AAAA/6Ojo+b/////eXl55AAAA"
|
||||
"P8AtPf+ALf7/wC3+/8AoNv9AAAA/gAAAP4AAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/gAAAP0Ax///ALf7/wCIu/0AAAD/ysrK6/"
|
||||
"/////8/Pz6Hx8f6wAAAP8kJCTm/f39/WVlZe4ABAX8AMT//wC3/P8AV3X9ERER7/////9MTEzrAAAA6"
|
||||
"ElJSeb///////////X19e8AAAD/AHuo/QC3+/8At/v/AIW2/gAAAP4AAAD/AAAAZAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0wAAAP4AAAD+AK"
|
||||
"fl/gC3+/8AjcH+AAAA/83Nze/////////////////////w//////////9gYGDsAA8V/QDC//8Auv//A"
|
||||
"EJa/UFBQeb////////////////////////////////////vAAAA/wBvlfwAt/v/ALf7/wBdff0AAAD+"
|
||||
"AAAA/wAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAI4AAAD+AAAA/gBnjP0At/v/ALz//gAAAP+CgoLx/////////////////////////////"
|
||||
"///CAkJ8QBCXP4Auf7/ALj8/wBScf0DAwPw////////////////////////////////tra26AAAAP8A"
|
||||
"hrf+ALf7/wDA//8AHCf9AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAA/wAAAP4AEBb9AMT//wC6//8AQVn+AAAA9+Pj4"
|
||||
"/L/////////////////////cnJy1gAAAP8AlMr9ALf7/wC3+/8AhLT+AAAA/5mZmeL/////////////"
|
||||
"/////////////R0dHesAAAD9AL///gC3+/8ApuX+AAAA/gAAAP4AAAC1AAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMAAAD+AAAA/"
|
||||
"gCGtv0Atvr/ALn+/wARF/0AAAD3cHBw7bGxseqioqLuOjo67QAAAP8ARFz9ALz//wC3+/8At/v/AML/"
|
||||
"/wAbJf0AAAD/jo6O5v////b//////f397zw8PPEAAAD/AGCC/QC4/P8Auv//AEJb/QAAAP4AAAD/AAA"
|
||||
"AUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAABpAAAA/wAAAP4AERf9AMP//wC3+/8Auv/+AEVf/AAAAP8AAAD/AAAA/wAAAP0AWnn9"
|
||||
"AMH//wC3+/8At/v/ALf7/wC3+/8AtPf+ABsm/QAAAP8AAADwDg4O+QAAAPwAAAD+AD1T/QDB//8At/v"
|
||||
"/AKLd/gAAAP4AAAD+AAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPIAAAD+AAAA/gBfgP0Auf7/ALf7/wC5/v8A"
|
||||
"wv//AJnS/gCWzv4Awf//ALj9/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDC//8Ahbb+AFBt/QA3TP0"
|
||||
"ATGn9AI/D/gC+//8At/v/AMD//wATGv0AAAD+AAAA/wAAAE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA9AAAA/wAA"
|
||||
"AP4AAAD+AJPJ/QC2+v8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC"
|
||||
"3+/8At/v/ALf7/wC4/f8Au///ALj9/wC3+/8At/v/AML//wA0SP0AAAD+AAAA/QAAAMMAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAJYAAAD/AAAA/gAAAP4Andj9ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf"
|
||||
"7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/AML//wA+Vf0AAAD+AA"
|
||||
"AA/gAAAPYAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwwAAAP4AAAD+AAAA/gCGt/0AvP/"
|
||||
"/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atv"
|
||||
"r/ALr//gAoNv0AAAD+AAAA/gAAAP8AAAAeAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAC9AAAA/wAAAP4AAAD+AEFY/QC6/f4At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/"
|
||||
"8At/v/ALf7/wC2+v8Aw///AICv/gAEBv0AAAH+AAAA/QAAAP8AAAAiAAAAAQAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAAD/AAAA/gAAAP4AAAD9AFl3/QCt7/4AwP//ALj9/w"
|
||||
"C2+/8At/v/ALf7/wC3+/8At/v/ALz//wDB//4AeKP+ABki/QAAAP4AAAD+AAAA/wAAAOEAAAASAAAAA"
|
||||
"QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAOUAAAD/AA"
|
||||
"AA/gAAAP4AAAD+ABcg/QBQbv0AbJD9AH2s/gCBsP0Ac5v+AFt6/QAtPv0AAAD9AAAA/gAAAP4AAAD+A"
|
||||
"AAA/wAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAWgAAAOIAAAD/AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AA"
|
||||
"AD+AAAA/gAAAP8AAAD/AAAAmQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAIgAAADRAAAA/wAAA"
|
||||
"P8AAAD/AAAA/wAAAP8AAAD/AAAA5QAAAJoAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAIAAAACQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///gAf///////4AAAAD/////wAAAAP////"
|
||||
"/AAAAB/////+AAAAH/////4AAAA//////gAAAD/////8AAAAH/////wAAAAP////+AAAAA/////4AAA"
|
||||
"AD/////gAAAAP////+AAAAA/////4AAAAD/////gAAAAP////+AAAAA/////4AAAAB/////gAAAAD//"
|
||||
"//+AAAAAP////wAAAAA////+AAAAAD////wAAAAAP///8AAAAAA////gAAAAAB///8AAAAAAH///gAA"
|
||||
"AAAAf//+AAAAAAA///4AAAAAAD///4AAAAAAP///wAAAAAAf///wAAAAAD////gAAAAAP////gAAAAB"
|
||||
"/////AAAAAP////+AAAAD/////wAAAAf////+AAAAB/////4AAAAD/////AAAAAP////8AAAAA/////"
|
||||
"wAAAAB////+AAAAAH////4AAAAAf////gAAAAA////+AAAAAD////4AAAAAP////gAAAAA////+AAAA"
|
||||
"AD////4AAAAAf////gAAAAB////+AAAAAH////8AAAAAf////wAAAAD/////gAAAAP////+AAAAB///"
|
||||
"//8AAAAH/////wAAAA//////gAAAH//////AAAA//////+AAAH//////+AAA///////+AAP///////+"
|
||||
"AH//////////////8=";
|
||||
|
||||
const char HTTP_COMMAND_TUNNELS[] = "tunnels";
|
||||
const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels";
|
||||
const char HTTP_COMMAND_TRANSPORTS[] = "transports";
|
||||
const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels";
|
||||
const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels";
|
||||
const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations";
|
||||
const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination";
|
||||
const char HTTP_PARAM_BASE32_ADDRESS[] = "b32";
|
||||
|
||||
namespace misc_strings
|
||||
{
|
||||
|
||||
const char name_value_separator[] = { ':', ' ' };
|
||||
const char crlf[] = { '\r', '\n' };
|
||||
|
||||
} // namespace misc_strings
|
||||
|
||||
std::vector<boost::asio::const_buffer> HTTPConnection::reply::to_buffers(int status)
|
||||
{
|
||||
std::vector<boost::asio::const_buffer> buffers;
|
||||
if (headers.size () > 0)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case 105: buffers.push_back(boost::asio::buffer("HTTP/1.0 105 Name Not Resolved\r\n")); break;
|
||||
case 200: buffers.push_back(boost::asio::buffer("HTTP/1.0 200 OK\r\n")); break;
|
||||
case 400: buffers.push_back(boost::asio::buffer("HTTP/1.0 400 Bad Request\r\n")); break;
|
||||
case 404: buffers.push_back(boost::asio::buffer("HTTP/1.0 404 Not Found\r\n")); break;
|
||||
case 408: buffers.push_back(boost::asio::buffer("HTTP/1.0 408 Request Timeout\r\n")); break;
|
||||
case 500: buffers.push_back(boost::asio::buffer("HTTP/1.0 500 Internal Server Error\r\n")); break;
|
||||
case 502: buffers.push_back(boost::asio::buffer("HTTP/1.0 502 Bad Gateway\r\n")); break;
|
||||
case 503: buffers.push_back(boost::asio::buffer("HTTP/1.0 503 Not Implemented\r\n")); break;
|
||||
case 504: buffers.push_back(boost::asio::buffer("HTTP/1.0 504 Gateway Timeout\r\n")); break;
|
||||
default:
|
||||
buffers.push_back(boost::asio::buffer("HTTP/1.0 200 OK\r\n"));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < headers.size(); ++i)
|
||||
{
|
||||
header& h = headers[i];
|
||||
buffers.push_back(boost::asio::buffer(h.name));
|
||||
buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
|
||||
buffers.push_back(boost::asio::buffer(h.value));
|
||||
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
|
||||
}
|
||||
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
|
||||
}
|
||||
buffers.push_back(boost::asio::buffer(content));
|
||||
return buffers;
|
||||
}
|
||||
|
||||
void HTTPConnection::Terminate ()
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
i2p::stream::DeleteStream (m_Stream);
|
||||
m_Stream = nullptr;
|
||||
}
|
||||
m_Socket->close ();
|
||||
//delete this;
|
||||
}
|
||||
|
||||
void HTTPConnection::Receive ()
|
||||
{
|
||||
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
|
||||
boost::bind(&HTTPConnection::HandleReceive, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
if (!m_Stream) // new request
|
||||
{
|
||||
m_Buffer[bytes_transferred] = 0;
|
||||
m_BufferLen = bytes_transferred;
|
||||
RunRequest();
|
||||
}
|
||||
else // follow-on
|
||||
m_Stream->Send ((uint8_t *)m_Buffer, bytes_transferred);
|
||||
Receive ();
|
||||
}
|
||||
else if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
|
||||
void HTTPConnection::RunRequest ()
|
||||
{
|
||||
auto address = ExtractAddress ();
|
||||
if (address.length () > 1 && address[1] != '?') // not just '/' or '/?'
|
||||
{
|
||||
std::string uri ("/"), b32;
|
||||
size_t pos = address.find ('/', 1);
|
||||
if (pos == std::string::npos)
|
||||
b32 = address.substr (1); // excluding leading '/' to end of line
|
||||
else
|
||||
{
|
||||
b32 = address.substr (1, pos - 1); // excluding leading '/' to next '/'
|
||||
uri = address.substr (pos); // rest of line
|
||||
}
|
||||
|
||||
HandleDestinationRequest (b32, uri);
|
||||
}
|
||||
else
|
||||
HandleRequest (address);
|
||||
}
|
||||
|
||||
std::string HTTPConnection::ExtractAddress ()
|
||||
{
|
||||
char * get = strstr (m_Buffer, "GET");
|
||||
if (get)
|
||||
{
|
||||
char * http = strstr (get, "HTTP");
|
||||
if (http)
|
||||
return std::string (get + 4, http - get - 5);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void HTTPConnection::ExtractParams (const std::string& str, std::map<std::string, std::string>& params)
|
||||
{
|
||||
if (str[0] != '&') return;
|
||||
size_t pos = 1, end;
|
||||
do
|
||||
{
|
||||
end = str.find ('&', pos);
|
||||
std::string param = str.substr (pos, end - pos);
|
||||
LogPrint (param);
|
||||
size_t e = param.find ('=');
|
||||
if (e != std::string::npos)
|
||||
params[param.substr(0, e)] = param.substr(e+1);
|
||||
pos = end + 1;
|
||||
}
|
||||
while (end != std::string::npos);
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleWrite (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode || (m_Stream && !m_Stream->IsOpen ()))
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else // data keeps coming
|
||||
AsyncStreamReceive ();
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleRequest (const std::string& address)
|
||||
{
|
||||
std::stringstream s;
|
||||
// Html5 head start
|
||||
s << "<!DOCTYPE html>\n<html lang=\"en\">"; // TODO: Add support for locale.
|
||||
s << "<head><meta charset=\"utf-8\" />"; // TODO: Find something to parse html/template system. This is horrible.
|
||||
s << "<link rel='shortcut icon' href='";
|
||||
s << itoopieFavicon;
|
||||
s << "' /><title>Purple I2P " << VERSION " Webconsole</title></head>";
|
||||
// Head end
|
||||
if (address.length () > 1)
|
||||
HandleCommand (address.substr (2), s);
|
||||
else
|
||||
FillContent (s);
|
||||
s << "</html>";
|
||||
SendReply (s.str ());
|
||||
}
|
||||
|
||||
void HTTPConnection::FillContent (std::stringstream& s)
|
||||
{
|
||||
s << "<h2>Welcome to the Webconsole!</h2><br><br>";
|
||||
s << "<b>Data path:</b> " << i2p::util::filesystem::GetDataDir().string() << "<br>" << "<br>";
|
||||
s << "<b>Our external address:</b>" << "<br>";
|
||||
for (auto& address : i2p::context.GetRouterInfo().GetAddresses())
|
||||
{
|
||||
switch (address.transportStyle)
|
||||
{
|
||||
case i2p::data::RouterInfo::eTransportNTCP:
|
||||
if (address.host.is_v6 ())
|
||||
s << "NTCP6 ";
|
||||
else
|
||||
s << "NTCP ";
|
||||
break;
|
||||
case i2p::data::RouterInfo::eTransportSSU:
|
||||
if (address.host.is_v6 ())
|
||||
s << "SSU6 ";
|
||||
else
|
||||
s << "SSU ";
|
||||
break;
|
||||
default:
|
||||
s << "Unknown ";
|
||||
}
|
||||
s << address.host.to_string() << ":" << address.port << "<br>";
|
||||
}
|
||||
s << "<br><b>Routers:</b> <i>" << i2p::data::netdb.GetNumRouters () << "</i> ";
|
||||
s << "<b>Floodfills:</b> <i>" << i2p::data::netdb.GetNumFloodfills () << "</i> ";
|
||||
s << "<b>LeaseSets:</b> <i>" << i2p::data::netdb.GetNumLeaseSets () << "</i><br>";
|
||||
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_LOCAL_DESTINATIONS << ">Local destinations</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TUNNELS << ">Tunnels</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSIT_TUNNELS << ">Transit tunnels</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSPORTS << ">Transports</a></b><br>";
|
||||
|
||||
if (i2p::context.AcceptsTunnels ())
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << ">Stop accepting tunnels</a></b><br>";
|
||||
else
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << ">Start accepting tunnels</a></b><br>";
|
||||
|
||||
s << "<p><a href=\"zmw2cyw2vj7f6obx3msmdvdepdhnw2ctc4okza2zjxlukkdfckhq.b32.i2p\">Flibusta</a></p>";
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s)
|
||||
{
|
||||
size_t paramsPos = command.find('&');
|
||||
std::string cmd = command.substr (0, paramsPos);
|
||||
if (cmd == HTTP_COMMAND_TRANSPORTS)
|
||||
ShowTransports (s);
|
||||
else if (cmd == HTTP_COMMAND_TUNNELS)
|
||||
ShowTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS)
|
||||
ShowTransitTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
|
||||
StartAcceptingTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
|
||||
StopAcceptingTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS)
|
||||
ShowLocalDestinations (s);
|
||||
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION)
|
||||
{
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (command.substr (paramsPos), params);
|
||||
auto b32 = params[HTTP_PARAM_BASE32_ADDRESS];
|
||||
ShowLocalDestination (b32, s);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTransports (std::stringstream& s)
|
||||
{
|
||||
s << "NTCP<br>";
|
||||
for (auto it: i2p::transport::transports.GetNTCPSessions ())
|
||||
{
|
||||
if (it.second && it.second->IsEstablished ())
|
||||
{
|
||||
// incoming connection doesn't have remote RI
|
||||
bool outgoing = it.second->GetRemoteRouter ();
|
||||
if (outgoing) s << "-->";
|
||||
s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": "
|
||||
<< it.second->GetSocket ().remote_endpoint().address ().to_string ();
|
||||
if (!outgoing) s << "-->";
|
||||
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||
s << "<br>";
|
||||
}
|
||||
s << std::endl;
|
||||
}
|
||||
auto ssuServer = i2p::transport::transports.GetSSUServer ();
|
||||
if (ssuServer)
|
||||
{
|
||||
s << "<br>SSU<br>";
|
||||
for (auto it: ssuServer->GetSessions ())
|
||||
{
|
||||
// incoming connections don't have remote router
|
||||
bool outgoing = it.second->GetRemoteRouter ();
|
||||
auto endpoint = it.second->GetRemoteEndpoint ();
|
||||
if (outgoing) s << "-->";
|
||||
s << endpoint.address ().to_string () << ":" << endpoint.port ();
|
||||
if (!outgoing) s << "-->";
|
||||
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||
s << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTunnels (std::stringstream& s)
|
||||
{
|
||||
for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->Print (s);
|
||||
if (it->GetTunnelPool () && !it->GetTunnelPool ()->IsExploratory ())
|
||||
s << " " << "Pool";
|
||||
auto state = it->GetState ();
|
||||
if (state == i2p::tunnel::eTunnelStateFailed)
|
||||
s << " " << "Failed";
|
||||
else if (state == i2p::tunnel::eTunnelStateExpiring)
|
||||
s << " " << "Exp";
|
||||
s << " " << (int)it->GetNumSentBytes () << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
|
||||
for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ())
|
||||
{
|
||||
it.second->GetTunnelConfig ()->Print (s);
|
||||
if (it.second->GetTunnelPool () && !it.second->GetTunnelPool ()->IsExploratory ())
|
||||
s << " " << "Pool";
|
||||
auto state = it.second->GetState ();
|
||||
if (state == i2p::tunnel::eTunnelStateFailed)
|
||||
s << " " << "Failed";
|
||||
else if (state == i2p::tunnel::eTunnelStateExpiring)
|
||||
s << " " << "Exp";
|
||||
s << " " << (int)it.second->GetNumReceivedBytes () << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTransitTunnels (std::stringstream& s)
|
||||
{
|
||||
for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
|
||||
{
|
||||
if (dynamic_cast<i2p::tunnel::TransitTunnelGateway *>(it.second))
|
||||
s << it.second->GetTunnelID () << "-->";
|
||||
else if (dynamic_cast<i2p::tunnel::TransitTunnelEndpoint *>(it.second))
|
||||
s << "-->" << it.second->GetTunnelID ();
|
||||
else
|
||||
s << "-->" << it.second->GetTunnelID () << "-->";
|
||||
s << " " << it.second->GetNumTransmittedBytes () << "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowLocalDestinations (std::stringstream& s)
|
||||
{
|
||||
for (auto& it: i2p::client::context.GetDestinations ())
|
||||
{
|
||||
std::string b32 = it.first.ToBase32 ();
|
||||
s << "<a href=/?" << HTTP_COMMAND_LOCAL_DESTINATION;
|
||||
s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << b32 << ">";
|
||||
s << b32 << ".b32.i2p</a><br>" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s)
|
||||
{
|
||||
i2p::data::IdentHash ident;
|
||||
i2p::data::Base32ToByteStream (b32.c_str (), b32.length (), ident, 32);
|
||||
auto dest = i2p::client::context.FindLocalDestination (ident);
|
||||
if (dest)
|
||||
{
|
||||
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>";
|
||||
auto pool = dest->GetTunnelPool ();
|
||||
if (pool)
|
||||
{
|
||||
s << "<b>Tunnels:</b><br>";
|
||||
for (auto it: pool->GetOutboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->Print (s);
|
||||
s << "<br>" << std::endl;
|
||||
}
|
||||
for (auto it: pool->GetInboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->Print (s);
|
||||
s << "<br>" << std::endl;
|
||||
}
|
||||
}
|
||||
s << "<br><b>Streams:</b><br>";
|
||||
for (auto it: dest->GetStreamingDestination ()->GetStreams ())
|
||||
{
|
||||
s << it.first << "->" << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase32 () << ".b32.i2p ";
|
||||
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||
s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]";
|
||||
s << "<br>"<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::StartAcceptingTunnels (std::stringstream& s)
|
||||
{
|
||||
i2p::context.SetAcceptsTunnels (true);
|
||||
s << "Accepting tunnels started" << std::endl;
|
||||
}
|
||||
|
||||
void HTTPConnection::StopAcceptingTunnels (std::stringstream& s)
|
||||
{
|
||||
i2p::context.SetAcceptsTunnels (false);
|
||||
s << "Accepting tunnels stopped" << std::endl;
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri)
|
||||
{
|
||||
std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n";
|
||||
LogPrint("HTTP Client Request: ", request);
|
||||
SendToAddress (address, 80, request.c_str (), request.size ());
|
||||
}
|
||||
|
||||
void HTTPConnection::SendToAddress (const std::string& address, int port, const char * buf, size_t len)
|
||||
{
|
||||
i2p::data::IdentHash destination;
|
||||
if (!i2p::client::context.GetAddressBook ().GetIdentHash (address, destination))
|
||||
{
|
||||
LogPrint ("Unknown address ", address);
|
||||
SendReply ("<html>" + itoopieImage + "<br>Unknown address " + address + "</html>", 404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination);
|
||||
if (leaseSet && leaseSet->HasNonExpiredLeases ())
|
||||
SendToDestination (leaseSet, port, buf, len);
|
||||
else
|
||||
{
|
||||
i2p::data::netdb.RequestDestination (destination, true, i2p::client::context.GetSharedLocalDestination ()->GetTunnelPool ());
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(HTTP_DESTINATION_REQUEST_TIMEOUT));
|
||||
m_Timer.async_wait (boost::bind (&HTTPConnection::HandleDestinationRequestTimeout,
|
||||
this, boost::asio::placeholders::error, destination, port, buf, len));
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleDestinationRequestTimeout (const boost::system::error_code& ecode,
|
||||
i2p::data::IdentHash destination, int port, const char * buf, size_t len)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination);
|
||||
if (leaseSet && leaseSet->HasNonExpiredLeases ())
|
||||
SendToDestination (leaseSet, port, buf, len);
|
||||
else
|
||||
// still no LeaseSet
|
||||
SendReply (leaseSet ? "<html>" + itoopieImage + "<br>Leases expired</html>" : "<html>" + itoopieImage + "LeaseSet not found</html>", 504);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::SendToDestination (const i2p::data::LeaseSet * remote, int port, const char * buf, size_t len)
|
||||
{
|
||||
if (!m_Stream)
|
||||
m_Stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (*remote, port);
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Send ((uint8_t *)buf, len);
|
||||
AsyncStreamReceive ();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::AsyncStreamReceive ()
|
||||
{
|
||||
if (m_Stream)
|
||||
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, 8192),
|
||||
boost::bind (&HTTPConnection::HandleStreamReceive, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred),
|
||||
45); // 45 seconds timeout
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
boost::asio::async_write (*m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred),
|
||||
boost::bind (&HTTPConnection::HandleWrite, this, boost::asio::placeholders::error));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ecode == boost::asio::error::timed_out)
|
||||
SendReply ("<html>" + itoopieImage + "<br>Not responding</html>", 504);
|
||||
else if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::SendReply (const std::string& content, int status)
|
||||
{
|
||||
m_Reply.content = content;
|
||||
m_Reply.headers.resize(2);
|
||||
m_Reply.headers[0].name = "Content-Length";
|
||||
m_Reply.headers[0].value = boost::lexical_cast<std::string>(m_Reply.content.size());
|
||||
m_Reply.headers[1].name = "Content-Type";
|
||||
m_Reply.headers[1].value = "text/html";
|
||||
|
||||
boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status),
|
||||
boost::bind (&HTTPConnection::HandleWriteReply, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
HTTPServer::HTTPServer (int port):
|
||||
m_Thread (nullptr), m_Work (m_Service),
|
||||
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)),
|
||||
m_NewSocket (nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HTTPServer::~HTTPServer ()
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void HTTPServer::Start ()
|
||||
{
|
||||
m_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 ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::Run ()
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
|
||||
void HTTPServer::Accept ()
|
||||
{
|
||||
m_NewSocket = new boost::asio::ip::tcp::socket (m_Service);
|
||||
m_Acceptor.async_accept (*m_NewSocket, boost::bind (&HTTPServer::HandleAccept, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void HTTPServer::HandleAccept(const boost::system::error_code& ecode)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
CreateConnection(m_NewSocket); // new HTTPConnection(m_NewSocket);
|
||||
Accept ();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket)
|
||||
{
|
||||
new HTTPConnection (m_NewSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
135
HTTPServer.h
Normal file
135
HTTPServer.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
#ifndef HTTP_SERVER_H__
|
||||
#define HTTP_SERVER_H__
|
||||
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/array.hpp>
|
||||
#include "LeaseSet.h"
|
||||
#include "Streaming.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
|
||||
const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
|
||||
class HTTPConnection
|
||||
{
|
||||
protected:
|
||||
|
||||
struct header
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct request
|
||||
{
|
||||
std::string method;
|
||||
std::string uri;
|
||||
std::string host;
|
||||
int port;
|
||||
int http_version_major;
|
||||
int http_version_minor;
|
||||
std::vector<header> headers;
|
||||
};
|
||||
|
||||
struct reply
|
||||
{
|
||||
std::vector<header> headers;
|
||||
std::string content;
|
||||
|
||||
std::vector<boost::asio::const_buffer> to_buffers (int status);
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
HTTPConnection (boost::asio::ip::tcp::socket * socket):
|
||||
m_Socket (socket), m_Timer (socket->get_io_service ()),
|
||||
m_Stream (nullptr), m_BufferLen (0) { Receive (); };
|
||||
virtual ~HTTPConnection() { delete m_Socket; }
|
||||
|
||||
private:
|
||||
|
||||
void Terminate ();
|
||||
void Receive ();
|
||||
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void AsyncStreamReceive ();
|
||||
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandleWriteReply(const boost::system::error_code& ecode);
|
||||
void HandleWrite (const boost::system::error_code& ecode);
|
||||
void SendReply (const std::string& content, int status = 200);
|
||||
|
||||
void HandleRequest (const std::string& address);
|
||||
void HandleCommand (const std::string& command, std::stringstream& s);
|
||||
void ShowTransports (std::stringstream& s);
|
||||
void ShowTunnels (std::stringstream& s);
|
||||
void ShowTransitTunnels (std::stringstream& s);
|
||||
void ShowLocalDestinations (std::stringstream& s);
|
||||
void ShowLocalDestination (const std::string& b32, std::stringstream& s);
|
||||
void StartAcceptingTunnels (std::stringstream& s);
|
||||
void StopAcceptingTunnels (std::stringstream& s);
|
||||
void FillContent (std::stringstream& s);
|
||||
std::string ExtractAddress ();
|
||||
void ExtractParams (const std::string& str, std::map<std::string, std::string>& params);
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
boost::asio::ip::tcp::socket * m_Socket;
|
||||
boost::asio::deadline_timer m_Timer;
|
||||
i2p::stream::Stream * m_Stream;
|
||||
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
|
||||
size_t m_BufferLen;
|
||||
request m_Request;
|
||||
reply m_Reply;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void RunRequest ();
|
||||
void HandleDestinationRequest(const std::string& address, const std::string& uri);
|
||||
void SendToAddress (const std::string& address, int port, const char * buf, size_t len);
|
||||
void HandleDestinationRequestTimeout (const boost::system::error_code& ecode,
|
||||
i2p::data::IdentHash destination, int port, const char * buf, size_t len);
|
||||
void SendToDestination (const i2p::data::LeaseSet * remote, int port, const char * buf, size_t len);
|
||||
|
||||
public:
|
||||
|
||||
static const std::string itoopieImage;
|
||||
static const std::string itoopieFavicon;
|
||||
};
|
||||
|
||||
class HTTPServer
|
||||
{
|
||||
public:
|
||||
|
||||
HTTPServer (int port);
|
||||
virtual ~HTTPServer ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void Accept ();
|
||||
void HandleAccept(const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::io_service::work m_Work;
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
boost::asio::ip::tcp::socket * m_NewSocket;
|
||||
|
||||
protected:
|
||||
virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
589
I2NPProtocol.cpp
Normal file
589
I2NPProtocol.cpp
Normal file
|
@ -0,0 +1,589 @@
|
|||
#include <string.h>
|
||||
#include <atomic>
|
||||
#include "I2PEndian.h"
|
||||
#include <cryptopp/sha.h>
|
||||
#include <cryptopp/gzip.h>
|
||||
#include "ElGamal.h"
|
||||
#include "Timestamp.h"
|
||||
#include "RouterContext.h"
|
||||
#include "NetDb.h"
|
||||
#include "Tunnel.h"
|
||||
#include "base64.h"
|
||||
#include "Transports.h"
|
||||
#include "Garlic.h"
|
||||
#include "I2NPProtocol.h"
|
||||
|
||||
using namespace i2p::transport;
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
I2NPMessage * NewI2NPMessage ()
|
||||
{
|
||||
return new I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE>();
|
||||
}
|
||||
|
||||
I2NPMessage * NewI2NPShortMessage ()
|
||||
{
|
||||
return new I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE>();
|
||||
}
|
||||
|
||||
I2NPMessage * NewI2NPMessage (size_t len)
|
||||
{
|
||||
return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage ();
|
||||
}
|
||||
|
||||
void DeleteI2NPMessage (I2NPMessage * msg)
|
||||
{
|
||||
delete msg;
|
||||
}
|
||||
|
||||
static std::atomic<uint32_t> I2NPmsgID(0); // TODO: create class
|
||||
void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID)
|
||||
{
|
||||
I2NPHeader * header = msg->GetHeader ();
|
||||
header->typeID = msgType;
|
||||
if (replyMsgID) // for tunnel creation
|
||||
header->msgID = htobe32 (replyMsgID);
|
||||
else
|
||||
{
|
||||
header->msgID = htobe32 (I2NPmsgID);
|
||||
I2NPmsgID++;
|
||||
}
|
||||
header->expiration = htobe64 (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number
|
||||
int len = msg->GetLength () - sizeof (I2NPHeader);
|
||||
header->size = htobe16 (len);
|
||||
uint8_t hash[32];
|
||||
CryptoPP::SHA256().CalculateDigest(hash, msg->GetPayload (), len);
|
||||
header->chks = hash[0];
|
||||
}
|
||||
|
||||
void RenewI2NPMessageHeader (I2NPMessage * msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
I2NPHeader * header = msg->GetHeader ();
|
||||
header->msgID = htobe32 (I2NPmsgID);
|
||||
I2NPmsgID++;
|
||||
header->expiration = htobe64 (i2p::util::GetMillisecondsSinceEpoch () + 5000);
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage (len);
|
||||
memcpy (msg->GetPayload (), buf, len);
|
||||
msg->len += len;
|
||||
FillI2NPMessageHeader (msg, msgType, replyMsgID);
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage ();
|
||||
memcpy (msg->GetBuffer (), buf, len);
|
||||
msg->len = msg->offset + len;
|
||||
msg->from = from;
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID)
|
||||
{
|
||||
I2NPDeliveryStatusMsg msg;
|
||||
if (msgID)
|
||||
{
|
||||
msg.msgID = htobe32 (msgID);
|
||||
msg.timestamp = htobe64 (i2p::util::GetMillisecondsSinceEpoch ());
|
||||
}
|
||||
else // for SSU establishment
|
||||
{
|
||||
msg.msgID = htobe32 (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ());
|
||||
msg.timestamp = htobe64 (2); // netID = 2
|
||||
}
|
||||
return CreateI2NPMessage (eI2NPDeliveryStatus, (uint8_t *)&msg, sizeof (msg));
|
||||
}
|
||||
|
||||
I2NPMessage * CreateDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers,
|
||||
bool encryption, i2p::tunnel::TunnelPool * pool)
|
||||
{
|
||||
I2NPMessage * m = NewI2NPMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
memcpy (buf, key, 32); // key
|
||||
buf += 32;
|
||||
memcpy (buf, from, 32); // from
|
||||
buf += 32;
|
||||
if (replyTunnelID)
|
||||
{
|
||||
*buf = encryption ? 0x03: 0x01; // set delivery flag
|
||||
*(uint32_t *)(buf+1) = htobe32 (replyTunnelID);
|
||||
buf += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
encryption = false; // encryption can we set for tunnels only
|
||||
*buf = 0; // flag
|
||||
buf++;
|
||||
}
|
||||
|
||||
if (exploratory)
|
||||
{
|
||||
*(uint16_t *)buf = htobe16 (1); // one exlude record
|
||||
buf += 2;
|
||||
// reply with non-floodfill routers only
|
||||
memset (buf, 0, 32);
|
||||
buf += 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (excludedPeers)
|
||||
{
|
||||
int cnt = excludedPeers->size ();
|
||||
*(uint16_t *)buf = htobe16 (cnt);
|
||||
buf += 2;
|
||||
for (auto& it: *excludedPeers)
|
||||
{
|
||||
memcpy (buf, it, 32);
|
||||
buf += 32;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// nothing to exclude
|
||||
*(uint16_t *)buf = htobe16 (0);
|
||||
buf += 2;
|
||||
}
|
||||
}
|
||||
if (encryption)
|
||||
{
|
||||
// session key and tag for reply
|
||||
auto& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
rnd.GenerateBlock (buf, 32); // key
|
||||
buf[32] = 1; // 1 tag
|
||||
rnd.GenerateBlock (buf + 33, 32); // tag
|
||||
if (pool)
|
||||
pool->GetGarlicDestination ().AddSessionKey (buf, buf + 33); // introduce new key-tag to garlic engine
|
||||
else
|
||||
LogPrint ("Destination for encrypteed reply not specified");
|
||||
buf += 65;
|
||||
}
|
||||
m->len += (buf - m->GetPayload ());
|
||||
FillI2NPMessageHeader (m, eI2NPDatabaseLookup);
|
||||
return m;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident,
|
||||
const i2p::data::RouterInfo * floodfill)
|
||||
{
|
||||
I2NPMessage * m = NewI2NPShortMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
size_t len = 0;
|
||||
memcpy (buf, ident, 32);
|
||||
len += 32;
|
||||
buf[len] = floodfill ? 1 : 0; // 1 router for now
|
||||
len++;
|
||||
if (floodfill)
|
||||
{
|
||||
memcpy (buf + len, floodfill->GetIdentHash (), 32);
|
||||
len += 32;
|
||||
}
|
||||
memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32);
|
||||
len += 32;
|
||||
m->len += len;
|
||||
FillI2NPMessageHeader (m, eI2NPDatabaseSearchReply);
|
||||
return m;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router)
|
||||
{
|
||||
if (!router) // we send own RouterInfo
|
||||
router = &context.GetRouterInfo ();
|
||||
|
||||
I2NPMessage * m = NewI2NPShortMessage ();
|
||||
I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)m->GetPayload ();
|
||||
|
||||
memcpy (msg->key, router->GetIdentHash (), 32);
|
||||
msg->type = 0;
|
||||
msg->replyToken = 0;
|
||||
|
||||
CryptoPP::Gzip compressor;
|
||||
compressor.Put (router->GetBuffer (), router->GetBufferLen ());
|
||||
compressor.MessageEnd();
|
||||
auto size = compressor.MaxRetrievable ();
|
||||
uint8_t * buf = m->GetPayload () + sizeof (I2NPDatabaseStoreMsg);
|
||||
*(uint16_t *)buf = htobe16 (size); // size
|
||||
buf += 2;
|
||||
// TODO: check if size doesn't exceed buffer
|
||||
compressor.Get (buf, size);
|
||||
m->len += sizeof (I2NPDatabaseStoreMsg) + 2 + size; // payload size
|
||||
FillI2NPMessageHeader (m, eI2NPDatabaseStore);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken)
|
||||
{
|
||||
if (!leaseSet) return nullptr;
|
||||
I2NPMessage * m = NewI2NPShortMessage ();
|
||||
uint8_t * payload = m->GetPayload ();
|
||||
I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)payload;
|
||||
memcpy (msg->key, leaseSet->GetIdentHash (), 32);
|
||||
msg->type = 1; // LeaseSet
|
||||
msg->replyToken = htobe32 (replyToken);
|
||||
size_t size = sizeof (I2NPDatabaseStoreMsg);
|
||||
if (replyToken)
|
||||
{
|
||||
auto leases = leaseSet->GetNonExpiredLeases ();
|
||||
if (leases.size () > 0)
|
||||
{
|
||||
*(uint32_t *)(payload + size) = htobe32 (leases[0].tunnelID);
|
||||
size += 4; // reply tunnelID
|
||||
memcpy (payload + size, leases[0].tunnelGateway, 32);
|
||||
size += 32; // reply tunnel gateway
|
||||
}
|
||||
else
|
||||
msg->replyToken = 0;
|
||||
}
|
||||
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
|
||||
size += leaseSet->GetBufferLen ();
|
||||
m->len += size;
|
||||
FillI2NPMessageHeader (m, eI2NPDatabaseStore);
|
||||
return m;
|
||||
}
|
||||
|
||||
I2NPBuildRequestRecordClearText CreateBuildRequestRecord (
|
||||
const uint8_t * ourIdent, uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||
const uint8_t * replyKey, const uint8_t * replyIV, uint32_t nextMessageID,
|
||||
bool isGateway, bool isEndpoint)
|
||||
{
|
||||
I2NPBuildRequestRecordClearText clearText;
|
||||
clearText.receiveTunnel = htobe32 (receiveTunnelID);
|
||||
clearText.nextTunnel = htobe32(nextTunnelID);
|
||||
memcpy (clearText.layerKey, layerKey, 32);
|
||||
memcpy (clearText.ivKey, ivKey, 32);
|
||||
memcpy (clearText.replyKey, replyKey, 32);
|
||||
memcpy (clearText.replyIV, replyIV, 16);
|
||||
clearText.flag = 0;
|
||||
if (isGateway) clearText.flag |= 0x80;
|
||||
if (isEndpoint) clearText.flag |= 0x40;
|
||||
memcpy (clearText.ourIdent, ourIdent, 32);
|
||||
memcpy (clearText.nextIdent, nextIdent, 32);
|
||||
clearText.requestTime = htobe32 (i2p::util::GetHoursSinceEpoch ());
|
||||
clearText.nextMessageID = htobe32(nextMessageID);
|
||||
return clearText;
|
||||
}
|
||||
|
||||
void EncryptBuildRequestRecord (const i2p::data::RouterInfo& router,
|
||||
const I2NPBuildRequestRecordClearText& clearText,
|
||||
I2NPBuildRequestRecordElGamalEncrypted& record)
|
||||
{
|
||||
router.GetElGamalEncryption ()->Encrypt ((uint8_t *)&clearText, sizeof(clearText), record.encrypted);
|
||||
memcpy (record.toPeer, (const uint8_t *)router.GetIdentHash (), 16);
|
||||
}
|
||||
|
||||
bool HandleBuildRequestRecords (int num, I2NPBuildRequestRecordElGamalEncrypted * records, I2NPBuildRequestRecordClearText& clearText)
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
if (!memcmp (records[i].toPeer, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
|
||||
{
|
||||
LogPrint ("Record ",i," is ours");
|
||||
|
||||
i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), records[i].encrypted, (uint8_t *)&clearText);
|
||||
// replace record to reply
|
||||
I2NPBuildResponseRecord * reply = (I2NPBuildResponseRecord *)(records + i);
|
||||
if (i2p::context.AcceptsTunnels ())
|
||||
{
|
||||
i2p::tunnel::TransitTunnel * transitTunnel =
|
||||
i2p::tunnel::CreateTransitTunnel (
|
||||
be32toh (clearText.receiveTunnel),
|
||||
clearText.nextIdent, be32toh (clearText.nextTunnel),
|
||||
clearText.layerKey, clearText.ivKey,
|
||||
clearText.flag & 0x80, clearText.flag & 0x40);
|
||||
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
|
||||
reply->ret = 0;
|
||||
}
|
||||
else
|
||||
reply->ret = 30; // always reject with bandwidth reason (30)
|
||||
|
||||
//TODO: fill filler
|
||||
CryptoPP::SHA256().CalculateDigest(reply->hash, reply->padding, sizeof (reply->padding) + 1); // + 1 byte of ret
|
||||
// encrypt reply
|
||||
i2p::crypto::CBCEncryption encryption;
|
||||
for (int j = 0; j < num; j++)
|
||||
{
|
||||
encryption.SetKey (clearText.replyKey);
|
||||
encryption.SetIV (clearText.replyIV);
|
||||
encryption.Encrypt((uint8_t *)(records + j), sizeof (records[j]), (uint8_t *)(records + j));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||
{
|
||||
int num = buf[0];
|
||||
LogPrint ("VariableTunnelBuild ", num, " records");
|
||||
|
||||
i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID);
|
||||
if (tunnel)
|
||||
{
|
||||
// endpoint of inbound tunnel
|
||||
LogPrint ("VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
|
||||
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||
{
|
||||
LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||
i2p::tunnel::tunnels.AddInboundTunnel (static_cast<i2p::tunnel::InboundTunnel *>(tunnel));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
I2NPBuildRequestRecordElGamalEncrypted * records = (I2NPBuildRequestRecordElGamalEncrypted *)(buf+1);
|
||||
I2NPBuildRequestRecordClearText clearText;
|
||||
if (HandleBuildRequestRecords (num, records, clearText))
|
||||
{
|
||||
if (clearText.flag & 0x40) // we are endpoint of outboud tunnel
|
||||
{
|
||||
// so we send it to reply tunnel
|
||||
transports.SendMessage (clearText.nextIdent,
|
||||
CreateTunnelGatewayMsg (be32toh (clearText.nextTunnel),
|
||||
eI2NPVariableTunnelBuildReply, buf, len,
|
||||
be32toh (clearText.nextMessageID)));
|
||||
}
|
||||
else
|
||||
transports.SendMessage (clearText.nextIdent,
|
||||
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, be32toh (clearText.nextMessageID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
|
||||
{
|
||||
I2NPBuildRequestRecordClearText clearText;
|
||||
if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, (I2NPBuildRequestRecordElGamalEncrypted *)buf, clearText))
|
||||
{
|
||||
if (clearText.flag & 0x40) // we are endpoint of outbound tunnel
|
||||
{
|
||||
// so we send it to reply tunnel
|
||||
transports.SendMessage (clearText.nextIdent,
|
||||
CreateTunnelGatewayMsg (be32toh (clearText.nextTunnel),
|
||||
eI2NPTunnelBuildReply, buf, len,
|
||||
be32toh (clearText.nextMessageID)));
|
||||
}
|
||||
else
|
||||
transports.SendMessage (clearText.nextIdent,
|
||||
CreateI2NPMessage (eI2NPTunnelBuild, buf, len, be32toh (clearText.nextMessageID)));
|
||||
}
|
||||
}
|
||||
|
||||
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||
{
|
||||
LogPrint ("VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID);
|
||||
i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID);
|
||||
if (tunnel)
|
||||
{
|
||||
// reply for outbound tunnel
|
||||
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||
{
|
||||
LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||
i2p::tunnel::tunnels.AddOutboundTunnel (static_cast<i2p::tunnel::OutboundTunnel *>(tunnel));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint ("Pending tunnel for message ", replyMsgID, " not found");
|
||||
}
|
||||
|
||||
|
||||
I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage ();
|
||||
memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE);
|
||||
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
|
||||
FillI2NPMessageHeader (msg, eI2NPTunnelData);
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage ();
|
||||
memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
|
||||
*(uint32_t *)(msg->GetPayload ()) = htobe32 (tunnelID);
|
||||
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
|
||||
FillI2NPMessageHeader (msg, eI2NPTunnelData);
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage (len);
|
||||
TunnelGatewayHeader * header = (TunnelGatewayHeader *)msg->GetPayload ();
|
||||
header->tunnelID = htobe32 (tunnelID);
|
||||
header->length = htobe16 (len);
|
||||
memcpy (msg->GetPayload () + sizeof (TunnelGatewayHeader), buf, len);
|
||||
msg->len += sizeof (TunnelGatewayHeader) + len;
|
||||
FillI2NPMessageHeader (msg, eI2NPTunnelGateway);
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessage * msg)
|
||||
{
|
||||
if (msg->offset >= sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader))
|
||||
{
|
||||
// message is capable to be used without copying
|
||||
TunnelGatewayHeader * header = (TunnelGatewayHeader *)(msg->GetBuffer () - sizeof (TunnelGatewayHeader));
|
||||
header->tunnelID = htobe32 (tunnelID);
|
||||
int len = msg->GetLength ();
|
||||
header->length = htobe16 (len);
|
||||
msg->offset -= (sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader));
|
||||
msg->len = msg->offset + sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader) +len;
|
||||
FillI2NPMessageHeader (msg, eI2NPTunnelGateway);
|
||||
return msg;
|
||||
}
|
||||
else
|
||||
{
|
||||
I2NPMessage * msg1 = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
|
||||
DeleteI2NPMessage (msg);
|
||||
return msg1;
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||
const uint8_t * buf, size_t len, uint32_t replyMsgID)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPMessage (len);
|
||||
size_t gatewayMsgOffset = sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader);
|
||||
msg->offset += gatewayMsgOffset;
|
||||
msg->len += gatewayMsgOffset;
|
||||
memcpy (msg->GetPayload (), buf, len);
|
||||
msg->len += len;
|
||||
FillI2NPMessageHeader (msg, msgType, replyMsgID); // create content message
|
||||
len = msg->GetLength ();
|
||||
msg->offset -= gatewayMsgOffset;
|
||||
TunnelGatewayHeader * header = (TunnelGatewayHeader *)msg->GetPayload ();
|
||||
header->tunnelID = htobe32 (tunnelID);
|
||||
header->length = htobe16 (len);
|
||||
FillI2NPMessageHeader (msg, eI2NPTunnelGateway); // gateway message
|
||||
return msg;
|
||||
}
|
||||
|
||||
void HandleTunnelGatewayMsg (I2NPMessage * msg)
|
||||
{
|
||||
TunnelGatewayHeader * header = (TunnelGatewayHeader *)msg->GetPayload ();
|
||||
uint32_t tunnelID = be32toh(header->tunnelID);
|
||||
uint16_t len = be16toh(header->length);
|
||||
// we make payload as new I2NP message to send
|
||||
msg->offset += sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader);
|
||||
msg->len = msg->offset + len;
|
||||
LogPrint ("TunnelGateway of ", (int)len, " bytes for tunnel ", (unsigned int)tunnelID, ". Msg type ", (int)msg->GetHeader()->typeID);
|
||||
if (msg->GetHeader()->typeID == eI2NPDatabaseStore ||
|
||||
msg->GetHeader()->typeID == eI2NPDatabaseSearchReply)
|
||||
{
|
||||
// transit DatabaseStore my contain new/updated RI
|
||||
// or DatabaseSearchReply with new routers
|
||||
auto ds = NewI2NPMessage ();
|
||||
*ds = *msg;
|
||||
i2p::data::netdb.PostI2NPMsg (ds);
|
||||
}
|
||||
i2p::tunnel::TransitTunnel * tunnel = i2p::tunnel::tunnels.GetTransitTunnel (tunnelID);
|
||||
if (tunnel)
|
||||
tunnel->SendTunnelDataMsg (msg);
|
||||
else
|
||||
{
|
||||
LogPrint ("Tunnel ", (unsigned int)tunnelID, " not found");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetI2NPMessageLength (const uint8_t * msg)
|
||||
{
|
||||
I2NPHeader * header = (I2NPHeader *)msg;
|
||||
return be16toh (header->size) + sizeof (I2NPHeader);
|
||||
}
|
||||
|
||||
void HandleI2NPMessage (uint8_t * msg, size_t len)
|
||||
{
|
||||
I2NPHeader * header = (I2NPHeader *)msg;
|
||||
uint32_t msgID = be32toh (header->msgID);
|
||||
LogPrint ("I2NP msg received len=", len,", type=", (int)header->typeID, ", msgID=", (unsigned int)msgID);
|
||||
|
||||
uint8_t * buf = msg + sizeof (I2NPHeader);
|
||||
int size = be16toh (header->size);
|
||||
switch (header->typeID)
|
||||
{
|
||||
case eI2NPVariableTunnelBuild:
|
||||
LogPrint ("VariableTunnelBuild");
|
||||
HandleVariableTunnelBuildMsg (msgID, buf, size);
|
||||
break;
|
||||
case eI2NPVariableTunnelBuildReply:
|
||||
LogPrint ("VariableTunnelBuildReply");
|
||||
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
|
||||
break;
|
||||
case eI2NPTunnelBuild:
|
||||
LogPrint ("TunnelBuild");
|
||||
HandleTunnelBuildMsg (buf, size);
|
||||
break;
|
||||
case eI2NPTunnelBuildReply:
|
||||
LogPrint ("TunnelBuildReply");
|
||||
// TODO:
|
||||
break;
|
||||
default:
|
||||
LogPrint ("Unexpected message ", (int)header->typeID);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleI2NPMessage (I2NPMessage * msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
switch (msg->GetHeader ()->typeID)
|
||||
{
|
||||
case eI2NPTunnelData:
|
||||
LogPrint ("TunnelData");
|
||||
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||
break;
|
||||
case eI2NPTunnelGateway:
|
||||
LogPrint ("TunnelGateway");
|
||||
HandleTunnelGatewayMsg (msg);
|
||||
break;
|
||||
case eI2NPGarlic:
|
||||
LogPrint ("Garlic");
|
||||
if (msg->from && msg->from->GetTunnelPool ())
|
||||
msg->from->GetTunnelPool ()->GetGarlicDestination ().ProcessGarlicMessage (msg);
|
||||
else
|
||||
i2p::context.ProcessGarlicMessage (msg);
|
||||
break;
|
||||
case eI2NPDatabaseStore:
|
||||
case eI2NPDatabaseSearchReply:
|
||||
case eI2NPDatabaseLookup:
|
||||
// forward to netDb
|
||||
i2p::data::netdb.PostI2NPMsg (msg);
|
||||
break;
|
||||
case eI2NPDeliveryStatus:
|
||||
LogPrint ("DeliveryStatus");
|
||||
if (msg->from && msg->from->GetTunnelPool ())
|
||||
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
|
||||
else
|
||||
i2p::context.ProcessDeliveryStatusMessage (msg);
|
||||
break;
|
||||
default:
|
||||
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
208
I2NPProtocol.h
Normal file
208
I2NPProtocol.h
Normal file
|
@ -0,0 +1,208 @@
|
|||
#ifndef I2NP_PROTOCOL_H__
|
||||
#define I2NP_PROTOCOL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "LeaseSet.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
#pragma pack (1)
|
||||
|
||||
struct I2NPHeader
|
||||
{
|
||||
uint8_t typeID;
|
||||
uint32_t msgID;
|
||||
uint64_t expiration;
|
||||
uint16_t size;
|
||||
uint8_t chks;
|
||||
};
|
||||
|
||||
struct I2NPHeaderShort
|
||||
{
|
||||
uint8_t typeID;
|
||||
uint32_t shortExpiration;
|
||||
};
|
||||
|
||||
struct I2NPDatabaseStoreMsg
|
||||
{
|
||||
uint8_t key[32];
|
||||
uint8_t type;
|
||||
uint32_t replyToken;
|
||||
};
|
||||
|
||||
struct I2NPDeliveryStatusMsg
|
||||
{
|
||||
uint32_t msgID;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct I2NPBuildRequestRecordClearText
|
||||
{
|
||||
uint32_t receiveTunnel;
|
||||
uint8_t ourIdent[32];
|
||||
uint32_t nextTunnel;
|
||||
uint8_t nextIdent[32];
|
||||
uint8_t layerKey[32];
|
||||
uint8_t ivKey[32];
|
||||
uint8_t replyKey[32];
|
||||
uint8_t replyIV[16];
|
||||
uint8_t flag;
|
||||
uint32_t requestTime;
|
||||
uint32_t nextMessageID;
|
||||
uint8_t filler[29];
|
||||
};
|
||||
|
||||
struct I2NPBuildResponseRecord
|
||||
{
|
||||
uint8_t hash[32];
|
||||
uint8_t padding[495];
|
||||
uint8_t ret;
|
||||
};
|
||||
|
||||
struct I2NPBuildRequestRecordElGamalEncrypted
|
||||
{
|
||||
uint8_t toPeer[16];
|
||||
uint8_t encrypted[512];
|
||||
};
|
||||
|
||||
struct TunnelGatewayHeader
|
||||
{
|
||||
uint32_t tunnelID;
|
||||
uint16_t length;
|
||||
};
|
||||
|
||||
|
||||
#pragma pack ()
|
||||
|
||||
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;
|
||||
|
||||
namespace tunnel
|
||||
{
|
||||
class InboundTunnel;
|
||||
class TunnelPool;
|
||||
}
|
||||
|
||||
const size_t I2NP_MAX_MESSAGE_SIZE = 32768;
|
||||
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 2400;
|
||||
struct I2NPMessage
|
||||
{
|
||||
uint8_t * buf;
|
||||
size_t len, offset, maxLen;
|
||||
i2p::tunnel::InboundTunnel * from;
|
||||
|
||||
I2NPMessage (): buf (nullptr),len (sizeof (I2NPHeader) + 2),
|
||||
offset(2), maxLen (0), from (nullptr) {};
|
||||
// reserve 2 bytes for NTCP header
|
||||
I2NPHeader * GetHeader () { return (I2NPHeader *)GetBuffer (); };
|
||||
uint8_t * GetPayload () { return GetBuffer () + sizeof(I2NPHeader); };
|
||||
uint8_t * GetBuffer () { return buf + offset; };
|
||||
const uint8_t * GetBuffer () const { return buf + offset; };
|
||||
size_t GetLength () const { return len - offset; };
|
||||
|
||||
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 + sizeof(I2NPHeader) - sizeof(I2NPHeaderShort); };
|
||||
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
|
||||
{
|
||||
I2NPHeaderShort ssu = *(I2NPHeaderShort *)GetSSUHeader ();
|
||||
I2NPHeader * header = GetHeader ();
|
||||
header->typeID = ssu.typeID;
|
||||
header->msgID = htobe32 (msgID);
|
||||
header->expiration = htobe64 (be32toh (ssu.shortExpiration)*1000LL);
|
||||
header->size = htobe16 (len - offset - sizeof (I2NPHeader));
|
||||
header->chks = 0;
|
||||
}
|
||||
uint32_t ToSSU () // return msgID
|
||||
{
|
||||
I2NPHeader header = *GetHeader ();
|
||||
I2NPHeaderShort * ssu = (I2NPHeaderShort *)GetSSUHeader ();
|
||||
ssu->typeID = header.typeID;
|
||||
ssu->shortExpiration = htobe32 (be64toh (header.expiration)/1000LL);
|
||||
len = offset + sizeof (I2NPHeaderShort) + be16toh (header.size);
|
||||
return be32toh (header.msgID);
|
||||
}
|
||||
};
|
||||
|
||||
template<int sz>
|
||||
struct I2NPMessageBuffer: public I2NPMessage
|
||||
{
|
||||
I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
|
||||
uint8_t m_Buffer[sz];
|
||||
};
|
||||
|
||||
I2NPMessage * NewI2NPMessage ();
|
||||
I2NPMessage * NewI2NPShortMessage ();
|
||||
I2NPMessage * NewI2NPMessage (size_t len);
|
||||
void DeleteI2NPMessage (I2NPMessage * msg);
|
||||
void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0);
|
||||
void RenewI2NPMessageHeader (I2NPMessage * msg);
|
||||
I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0);
|
||||
I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from = nullptr);
|
||||
|
||||
I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID);
|
||||
I2NPMessage * CreateDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||
uint32_t replyTunnelID, bool exploratory = false,
|
||||
std::set<i2p::data::IdentHash> * excludedPeers = nullptr, bool encryption = false,
|
||||
i2p::tunnel::TunnelPool * pool = nullptr);
|
||||
I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, const i2p::data::RouterInfo * floodfill);
|
||||
|
||||
I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr);
|
||||
I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken = 0);
|
||||
|
||||
I2NPBuildRequestRecordClearText CreateBuildRequestRecord (
|
||||
const uint8_t * ourIdent, uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||
const uint8_t * replyKey, const uint8_t * replyIV, uint32_t nextMessageID,
|
||||
bool isGateway, bool isEndpoint);
|
||||
void EncryptBuildRequestRecord (const i2p::data::RouterInfo& router,
|
||||
const I2NPBuildRequestRecordClearText& clearText,
|
||||
I2NPBuildRequestRecordElGamalEncrypted& record);
|
||||
|
||||
bool HandleBuildRequestRecords (int num, I2NPBuildRequestRecordElGamalEncrypted * records, I2NPBuildRequestRecordClearText& 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);
|
||||
|
||||
I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf);
|
||||
I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
|
||||
|
||||
void HandleTunnelGatewayMsg (I2NPMessage * msg);
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
|
||||
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessage * msg);
|
||||
|
||||
size_t GetI2NPMessageLength (const uint8_t * msg);
|
||||
void HandleI2NPMessage (uint8_t * msg, size_t len);
|
||||
void HandleI2NPMessage (I2NPMessage * msg);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,11 +1,3 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2022, 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 "I2PEndian.h"
|
||||
|
||||
// http://habrahabr.ru/post/121811/
|
||||
|
@ -13,7 +5,6 @@
|
|||
|
||||
#include "LittleBigEndian.h"
|
||||
|
||||
#ifdef NEEDS_LOCAL_ENDIAN
|
||||
uint16_t htobe16(uint16_t int16)
|
||||
{
|
||||
BigEndian<uint16_t> u16(int16);
|
||||
|
@ -49,4 +40,42 @@ 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;
|
||||
}
|
||||
*/
|
48
I2PEndian.h
Normal file
48
I2PEndian.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef I2PENDIAN_H__
|
||||
#define I2PENDIAN_H__
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD_kernel__)
|
||||
#include <endian.h>
|
||||
#elif __FreeBSD__
|
||||
#include <sys/endian.h>
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
#define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
#define htole16(x) OSSwapHostToLittleInt16(x)
|
||||
#define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
#define le16toh(x) OSSwapLittleToHostInt16(x)
|
||||
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define htole32(x) OSSwapHostToLittleInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define htole64(x) OSSwapHostToLittleInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
#define le64toh(x) OSSwapLittleToHostInt64(x)
|
||||
|
||||
#else
|
||||
#include <cstdint>
|
||||
uint16_t htobe16(uint16_t int16);
|
||||
uint32_t htobe32(uint32_t int32);
|
||||
uint64_t htobe64(uint64_t int64);
|
||||
|
||||
uint16_t be16toh(uint16_t big16);
|
||||
uint32_t be32toh(uint32_t big32);
|
||||
uint64_t be64toh(uint64_t big64);
|
||||
|
||||
// assume LittleEndine
|
||||
#define htole16
|
||||
#define htole32
|
||||
#define htole64
|
||||
#define le16toh
|
||||
#define le32toh
|
||||
#define le64toh
|
||||
|
||||
#endif
|
||||
|
||||
#endif // I2PENDIAN_H__
|
||||
|
284
I2PTunnel.cpp
Normal file
284
I2PTunnel.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
#include <boost/bind.hpp>
|
||||
#include "base64.h"
|
||||
#include "Log.h"
|
||||
#include "NetDb.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include "I2PTunnel.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
I2PTunnelConnection::I2PTunnelConnection (I2PTunnel * owner,
|
||||
boost::asio::ip::tcp::socket * socket, const i2p::data::LeaseSet * leaseSet):
|
||||
m_Socket (socket), m_Owner (owner)
|
||||
{
|
||||
m_Stream = m_Owner->GetLocalDestination ()->CreateStream (*leaseSet);
|
||||
m_Stream->Send (m_Buffer, 0); // connect
|
||||
StreamReceive ();
|
||||
Receive ();
|
||||
}
|
||||
|
||||
I2PTunnelConnection::I2PTunnelConnection (I2PTunnel * owner, i2p::stream::Stream * stream,
|
||||
boost::asio::ip::tcp::socket * socket, const boost::asio::ip::tcp::endpoint& target):
|
||||
m_Socket (socket), m_Stream (stream), m_Owner (owner)
|
||||
{
|
||||
if (m_Socket)
|
||||
m_Socket->async_connect (target, boost::bind (&I2PTunnelConnection::HandleConnect,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
I2PTunnelConnection::~I2PTunnelConnection ()
|
||||
{
|
||||
delete m_Socket;
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::Terminate ()
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
i2p::stream::DeleteStream (m_Stream);
|
||||
m_Stream = nullptr;
|
||||
}
|
||||
m_Socket->close ();
|
||||
if (m_Owner)
|
||||
m_Owner->RemoveConnection (this);
|
||||
//delete this;
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::Receive ()
|
||||
{
|
||||
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
|
||||
boost::bind(&I2PTunnelConnection::HandleReceived, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Stream)
|
||||
m_Stream->Send (m_Buffer, bytes_transferred);
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel write error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
StreamReceive ();
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::StreamReceive ()
|
||||
{
|
||||
if (m_Stream)
|
||||
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
|
||||
boost::bind (&I2PTunnelConnection::HandleStreamReceive, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred),
|
||||
I2P_TUNNEL_CONNECTION_MAX_IDLE);
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel 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),
|
||||
boost::bind (&I2PTunnelConnection::HandleWrite, this, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel connect error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
if (m_Stream) m_Stream->Close ();
|
||||
i2p::stream::DeleteStream (m_Stream);
|
||||
m_Stream = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("I2PTunnel connected");
|
||||
StreamReceive ();
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
void I2PTunnel::AddConnection (I2PTunnelConnection * conn)
|
||||
{
|
||||
m_Connections.insert (conn);
|
||||
}
|
||||
|
||||
void I2PTunnel::RemoveConnection (I2PTunnelConnection * conn)
|
||||
{
|
||||
m_Connections.erase (conn);
|
||||
}
|
||||
|
||||
void I2PTunnel::ClearConnections ()
|
||||
{
|
||||
for (auto it: m_Connections)
|
||||
delete it;
|
||||
m_Connections.clear ();
|
||||
}
|
||||
|
||||
I2PClientTunnel::I2PClientTunnel (boost::asio::io_service& service, const std::string& destination,
|
||||
int port, ClientDestination * localDestination):
|
||||
I2PTunnel (service, localDestination ? localDestination :
|
||||
i2p::client::context.CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256)),
|
||||
m_Acceptor (service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)),
|
||||
m_Timer (service), m_Destination (destination), m_DestinationIdentHash (nullptr),
|
||||
m_RemoteLeaseSet (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
I2PClientTunnel::~I2PClientTunnel ()
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void I2PClientTunnel::Start ()
|
||||
{
|
||||
i2p::data::IdentHash identHash;
|
||||
if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash))
|
||||
m_DestinationIdentHash = new i2p::data::IdentHash (identHash);
|
||||
if (!m_DestinationIdentHash)
|
||||
LogPrint ("I2PTunnel unknown destination ", m_Destination);
|
||||
m_Acceptor.listen ();
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void I2PClientTunnel::Stop ()
|
||||
{
|
||||
m_Acceptor.close();
|
||||
m_Timer.cancel ();
|
||||
ClearConnections ();
|
||||
m_DestinationIdentHash = nullptr;
|
||||
}
|
||||
|
||||
void I2PClientTunnel::Accept ()
|
||||
{
|
||||
auto newSocket = new boost::asio::ip::tcp::socket (GetService ());
|
||||
m_Acceptor.async_accept (*newSocket, boost::bind (&I2PClientTunnel::HandleAccept, this,
|
||||
boost::asio::placeholders::error, newSocket));
|
||||
}
|
||||
|
||||
void I2PClientTunnel::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
if (!m_DestinationIdentHash)
|
||||
{
|
||||
i2p::data::IdentHash identHash;
|
||||
if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash))
|
||||
m_DestinationIdentHash = new i2p::data::IdentHash (identHash);
|
||||
}
|
||||
if (m_DestinationIdentHash)
|
||||
{
|
||||
// try to get a LeaseSet
|
||||
m_RemoteLeaseSet = GetLocalDestination ()->FindLeaseSet (*m_DestinationIdentHash);
|
||||
if (m_RemoteLeaseSet && m_RemoteLeaseSet->HasNonExpiredLeases ())
|
||||
CreateConnection (socket);
|
||||
else
|
||||
{
|
||||
i2p::data::netdb.RequestDestination (*m_DestinationIdentHash, true, GetLocalDestination ()->GetTunnelPool ());
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds (I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT));
|
||||
m_Timer.async_wait (boost::bind (&I2PClientTunnel::HandleDestinationRequestTimer,
|
||||
this, boost::asio::placeholders::error, socket));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Remote destination ", m_Destination, " not found");
|
||||
delete socket;
|
||||
}
|
||||
|
||||
Accept ();
|
||||
}
|
||||
else
|
||||
delete socket;
|
||||
}
|
||||
|
||||
void I2PClientTunnel::HandleDestinationRequestTimer (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
if (m_DestinationIdentHash)
|
||||
{
|
||||
m_RemoteLeaseSet = GetLocalDestination ()->FindLeaseSet (*m_DestinationIdentHash);
|
||||
CreateConnection (socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
delete socket;
|
||||
}
|
||||
|
||||
void I2PClientTunnel::CreateConnection (boost::asio::ip::tcp::socket * socket)
|
||||
{
|
||||
if (m_RemoteLeaseSet) // leaseSet found
|
||||
{
|
||||
LogPrint ("New I2PTunnel connection");
|
||||
auto connection = new I2PTunnelConnection (this, socket, m_RemoteLeaseSet);
|
||||
AddConnection (connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("LeaseSet for I2PTunnel destination not found");
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
|
||||
I2PServerTunnel::I2PServerTunnel (boost::asio::io_service& service, const std::string& address, int port,
|
||||
ClientDestination * localDestination): I2PTunnel (service, localDestination),
|
||||
m_Endpoint (boost::asio::ip::address::from_string (address), port)
|
||||
{
|
||||
}
|
||||
|
||||
void I2PServerTunnel::Start ()
|
||||
{
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void I2PServerTunnel::Stop ()
|
||||
{
|
||||
ClearConnections ();
|
||||
}
|
||||
|
||||
void I2PServerTunnel::Accept ()
|
||||
{
|
||||
auto localDestination = GetLocalDestination ();
|
||||
if (localDestination)
|
||||
localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
|
||||
else
|
||||
LogPrint ("Local destination not set for server tunnel");
|
||||
}
|
||||
|
||||
void I2PServerTunnel::HandleAccept (i2p::stream::Stream * stream)
|
||||
{
|
||||
if (stream)
|
||||
new I2PTunnelConnection (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
123
I2PTunnel.h
Normal file
123
I2PTunnel.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
#ifndef I2PTUNNEL_H__
|
||||
#define I2PTUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Identity.h"
|
||||
#include "Destination.h"
|
||||
#include "Streaming.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
|
||||
|
||||
class I2PTunnel;
|
||||
class I2PTunnelConnection
|
||||
{
|
||||
public:
|
||||
|
||||
I2PTunnelConnection (I2PTunnel * owner, boost::asio::ip::tcp::socket * socket,
|
||||
const i2p::data::LeaseSet * leaseSet);
|
||||
I2PTunnelConnection (I2PTunnel * owner, i2p::stream::Stream * stream, boost::asio::ip::tcp::socket * socket,
|
||||
const boost::asio::ip::tcp::endpoint& target);
|
||||
~I2PTunnelConnection ();
|
||||
|
||||
private:
|
||||
|
||||
void Terminate ();
|
||||
|
||||
void Receive ();
|
||||
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
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];
|
||||
boost::asio::ip::tcp::socket * m_Socket;
|
||||
i2p::stream::Stream * m_Stream;
|
||||
I2PTunnel * m_Owner;
|
||||
};
|
||||
|
||||
class I2PTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
I2PTunnel (boost::asio::io_service& service, ClientDestination * localDestination):
|
||||
m_Service (service), m_LocalDestination (localDestination) {};
|
||||
virtual ~I2PTunnel () { ClearConnections (); };
|
||||
|
||||
void AddConnection (I2PTunnelConnection * conn);
|
||||
void RemoveConnection (I2PTunnelConnection * conn);
|
||||
void ClearConnections ();
|
||||
ClientDestination * GetLocalDestination () { return m_LocalDestination; };
|
||||
void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; };
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::io_service& m_Service;
|
||||
ClientDestination * m_LocalDestination;
|
||||
std::set<I2PTunnelConnection *> m_Connections;
|
||||
};
|
||||
|
||||
class I2PClientTunnel: public I2PTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
I2PClientTunnel (boost::asio::io_service& service, const std::string& destination, int port,
|
||||
ClientDestination * localDestination = nullptr);
|
||||
~I2PClientTunnel ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket);
|
||||
void HandleDestinationRequestTimer (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket);
|
||||
void CreateConnection (boost::asio::ip::tcp::socket * socket);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
boost::asio::deadline_timer m_Timer;
|
||||
std::string m_Destination;
|
||||
const i2p::data::IdentHash * m_DestinationIdentHash;
|
||||
const i2p::data::LeaseSet * m_RemoteLeaseSet;
|
||||
};
|
||||
|
||||
class I2PServerTunnel: public I2PTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
I2PServerTunnel (boost::asio::io_service& service, const std::string& address, int port,
|
||||
ClientDestination * localDestination);
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept (i2p::stream::Stream * stream);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
327
Identity.cpp
Normal file
327
Identity.cpp
Normal file
|
@ -0,0 +1,327 @@
|
|||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/dsa.h>
|
||||
#include "base64.h"
|
||||
#include "CryptoConst.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Identity.h"
|
||||
#include "I2PEndian.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)
|
||||
{
|
||||
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
|
||||
return DEFAULT_IDENTITY_SIZE;
|
||||
}
|
||||
|
||||
IdentHash Identity::Hash () const
|
||||
{
|
||||
IdentHash hash;
|
||||
CryptoPP::SHA256().CalculateDigest(hash, publicKey, DEFAULT_IDENTITY_SIZE);
|
||||
return hash;
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx ():
|
||||
m_Verifier (nullptr), 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_ECDSA_SHA256_P256)
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey + 64, signingKey, 64);
|
||||
m_StandardIdentity.certificate.type = CERTIFICATE_TYPE_KEY;
|
||||
m_ExtendedLen = 4; // 4 bytes extra
|
||||
m_StandardIdentity.certificate.length = htobe16 (4);
|
||||
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||
*(uint16_t *)m_ExtendedBuffer = htobe16 (SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
|
||||
*(uint16_t *)(m_ExtendedBuffer + 2) = htobe16 (CRYPTO_KEY_TYPE_ELGAMAL);
|
||||
uint8_t buf[DEFAULT_IDENTITY_SIZE + 4];
|
||||
ToBuffer (buf, DEFAULT_IDENTITY_SIZE + 4);
|
||||
CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ());
|
||||
}
|
||||
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_Verifier (nullptr), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
FromBuffer (buf, len);
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx (const IdentityEx& other):
|
||||
m_Verifier (nullptr), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
IdentityEx::~IdentityEx ()
|
||||
{
|
||||
delete m_Verifier;
|
||||
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;
|
||||
|
||||
delete m_Verifier;
|
||||
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;
|
||||
|
||||
delete m_Verifier;
|
||||
m_Verifier = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t IdentityEx::FromBuffer (const uint8_t * buf, size_t len)
|
||||
{
|
||||
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
|
||||
|
||||
delete[] m_ExtendedBuffer;
|
||||
if (m_StandardIdentity.certificate.length)
|
||||
{
|
||||
m_ExtendedLen = be16toh (m_StandardIdentity.certificate.length);
|
||||
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ExtendedLen = 0;
|
||||
m_ExtendedBuffer = nullptr;
|
||||
}
|
||||
CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ());
|
||||
|
||||
delete m_Verifier;
|
||||
m_Verifier = nullptr;
|
||||
|
||||
return GetFullLen ();
|
||||
}
|
||||
|
||||
size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const
|
||||
{
|
||||
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
|
||||
if (m_ExtendedLen > 0 && m_ExtendedBuffer)
|
||||
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
|
||||
return GetFullLen ();
|
||||
}
|
||||
|
||||
size_t IdentityEx::FromBase64(const std::string& s)
|
||||
{
|
||||
uint8_t buf[512];
|
||||
auto len = Base64ToByteStream (s.c_str(), s.length(), buf, 512);
|
||||
return FromBuffer (buf, len);
|
||||
}
|
||||
|
||||
size_t IdentityEx::GetSigningPublicKeyLen () const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->GetPublicKeyLen ();
|
||||
return 128;
|
||||
}
|
||||
|
||||
size_t IdentityEx::GetSignatureLen () const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->GetSignatureLen ();
|
||||
return 40;
|
||||
}
|
||||
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.type == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
|
||||
return be16toh (*(const uint16_t *)m_ExtendedBuffer); // signing key
|
||||
return SIGNING_KEY_TYPE_DSA_SHA1;
|
||||
}
|
||||
|
||||
void IdentityEx::CreateVerifier () const
|
||||
{
|
||||
auto keyType = GetSigningKeyType ();
|
||||
switch (keyType)
|
||||
{
|
||||
case SIGNING_KEY_TYPE_DSA_SHA1:
|
||||
m_Verifier = new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||
m_Verifier = new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + 64);
|
||||
break;
|
||||
default:
|
||||
LogPrint ("Signing key type ", (int)keyType, " is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
|
||||
{
|
||||
m_Public = Identity (keys);
|
||||
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
|
||||
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, 20); // 20 - DSA
|
||||
delete m_Signer;
|
||||
CreateSigner ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other)
|
||||
{
|
||||
m_Public = other.m_Public;
|
||||
memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256
|
||||
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, 128); // 128
|
||||
delete m_Signer;
|
||||
CreateSigner ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len)
|
||||
{
|
||||
size_t ret = m_Public.FromBuffer (buf, len);
|
||||
memcpy (m_PrivateKey, buf + ret, 256); // private key always 256
|
||||
ret += 256;
|
||||
size_t signingPrivateKeySize = m_Public.GetSignatureLen ()/2; // 20 for DSA
|
||||
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
|
||||
ret += signingPrivateKeySize;
|
||||
delete m_Signer;
|
||||
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.GetSignatureLen ()/2; // 20 for DSA
|
||||
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
|
||||
ret += signingPrivateKeySize;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
if (m_Signer)
|
||||
m_Signer->Sign (i2p::context.GetRandomNumberGenerator (), buf, len, signature);
|
||||
}
|
||||
|
||||
void PrivateKeys::CreateSigner ()
|
||||
{
|
||||
if (m_Public.GetSigningKeyType () == SIGNING_KEY_TYPE_ECDSA_SHA256_P256)
|
||||
m_Signer = new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey);
|
||||
else
|
||||
m_Signer = new i2p::crypto::DSASigner (m_SigningPrivateKey);
|
||||
}
|
||||
|
||||
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type)
|
||||
{
|
||||
if (type == SIGNING_KEY_TYPE_ECDSA_SHA256_P256)
|
||||
{
|
||||
PrivateKeys keys;
|
||||
auto& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
// encryption
|
||||
uint8_t publicKey[256];
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(rnd, keys.m_PrivateKey, publicKey);
|
||||
// signature
|
||||
uint8_t signingPublicKey[64];
|
||||
i2p::crypto::CreateECDSAP256RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
keys.m_Public = IdentityEx (publicKey, signingPublicKey, SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
|
||||
keys.CreateSigner ();
|
||||
return keys;
|
||||
}
|
||||
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
|
||||
}
|
||||
|
||||
Keys CreateRandomKeys ()
|
||||
{
|
||||
Keys keys;
|
||||
auto& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
// encryption
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(rnd, keys.privateKey, keys.publicKey);
|
||||
// signing
|
||||
i2p::crypto::CreateDSARandomKeys (rnd, 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;
|
||||
CryptoPP::SHA256().CalculateDigest((uint8_t *)key, buf, 40);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
243
Identity.h
Normal file
243
Identity.h
Normal file
|
@ -0,0 +1,243 @@
|
|||
#ifndef IDENTITY_H__
|
||||
#define IDENTITY_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include "base64.h"
|
||||
#include "ElGamal.h"
|
||||
#include "Signature.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
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; };
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
union // 8 bytes alignment
|
||||
{
|
||||
uint8_t m_Buf[sz];
|
||||
uint64_t ll[sz/8];
|
||||
};
|
||||
};
|
||||
typedef Tag<32> IdentHash;
|
||||
|
||||
#pragma pack(1)
|
||||
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];
|
||||
struct
|
||||
{
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
} certificate;
|
||||
|
||||
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;
|
||||
};
|
||||
#pragma pack()
|
||||
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;
|
||||
typedef uint16_t SigningKeyType;
|
||||
|
||||
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 ();
|
||||
IdentityEx& operator=(const IdentityEx& other);
|
||||
IdentityEx& operator=(const Identity& standard);
|
||||
|
||||
size_t FromBase64(const std::string& s);
|
||||
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
|
||||
const IdentHash& GetIdentHash () const { return m_IdentHash; };
|
||||
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
|
||||
size_t GetSigningPublicKeyLen () const;
|
||||
size_t GetSignatureLen () const;
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
|
||||
SigningKeyType GetSigningKeyType () const;
|
||||
|
||||
private:
|
||||
|
||||
void CreateVerifier () const;
|
||||
|
||||
private:
|
||||
|
||||
Identity m_StandardIdentity;
|
||||
IdentHash m_IdentHash;
|
||||
mutable i2p::crypto::Verifier * m_Verifier;
|
||||
size_t m_ExtendedLen;
|
||||
uint8_t * m_ExtendedBuffer;
|
||||
};
|
||||
|
||||
class PrivateKeys // for eepsites
|
||||
{
|
||||
public:
|
||||
|
||||
PrivateKeys (): m_Signer (nullptr) {};
|
||||
PrivateKeys (const PrivateKeys& other): m_Signer (nullptr) { *this = other; };
|
||||
PrivateKeys (const Keys& keys): m_Signer (nullptr) { *this = keys; };
|
||||
PrivateKeys& operator=(const Keys& keys);
|
||||
PrivateKeys& operator=(const PrivateKeys& other);
|
||||
~PrivateKeys () { delete m_Signer; };
|
||||
|
||||
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.GetSignatureLen ()/2; };
|
||||
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||
|
||||
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
|
||||
|
||||
private:
|
||||
|
||||
void CreateSigner ();
|
||||
|
||||
private:
|
||||
|
||||
IdentityEx m_Public;
|
||||
uint8_t m_PrivateKey[256];
|
||||
uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes
|
||||
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 (): m_ElGamalEncryption (nullptr) {};
|
||||
virtual ~RoutingDestination () { delete m_ElGamalEncryption; };
|
||||
|
||||
virtual const IdentHash& GetIdentHash () const = 0;
|
||||
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
||||
virtual bool IsDestination () const = 0; // for garlic
|
||||
|
||||
i2p::crypto::ElGamalEncryption * GetElGamalEncryption () const
|
||||
{
|
||||
if (!m_ElGamalEncryption)
|
||||
m_ElGamalEncryption = new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ());
|
||||
return m_ElGamalEncryption;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
mutable i2p::crypto::ElGamalEncryption * m_ElGamalEncryption; // use lazy initialization
|
||||
};
|
||||
|
||||
class LocalDestination
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~LocalDestination() {};
|
||||
virtual const PrivateKeys& GetPrivateKeys () const = 0;
|
||||
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
|
||||
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
||||
|
||||
const IdentityEx& GetIdentity () const { return GetPrivateKeys ().GetPublic (); };
|
||||
const IdentHash& GetIdentHash () const { return GetIdentity ().GetIdentHash (); };
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
GetPrivateKeys ().Sign (buf, len, signature);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
354
LICENSE
354
LICENSE
|
@ -1,27 +1,339 @@
|
|||
Copyright (c) 2013-2025, The PurpleI2P Project
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
All rights reserved.
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
Preamble
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific prior written
|
||||
permission.
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
i2p router for Linux written on C++
|
||||
Copyright (C) 2013 orignal
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
|
122
LeaseSet.cpp
Normal file
122
LeaseSet.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#include "I2PEndian.h"
|
||||
#include <cryptopp/dsa.h>
|
||||
#include "CryptoConst.h"
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "NetDb.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "LeaseSet.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
LeaseSet::LeaseSet (const uint8_t * buf, int len)
|
||||
{
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer ();
|
||||
}
|
||||
|
||||
LeaseSet::LeaseSet (const i2p::tunnel::TunnelPool& pool)
|
||||
{
|
||||
// header
|
||||
const i2p::data::LocalDestination& localDestination = pool.GetLocalDestination ();
|
||||
m_BufferLen = localDestination.GetIdentity ().ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE);
|
||||
memcpy (m_Buffer + m_BufferLen, localDestination.GetEncryptionPublicKey (), 256);
|
||||
m_BufferLen += 256;
|
||||
auto signingKeyLen = localDestination.GetIdentity ().GetSigningPublicKeyLen ();
|
||||
memset (m_Buffer + m_BufferLen, 0, signingKeyLen);
|
||||
m_BufferLen += signingKeyLen;
|
||||
auto tunnels = pool.GetInboundTunnels (5); // 5 tunnels maximum
|
||||
m_Buffer[m_BufferLen] = tunnels.size (); // num leases
|
||||
m_BufferLen++;
|
||||
// leases
|
||||
for (auto it: tunnels)
|
||||
{
|
||||
Lease * lease = (Lease *)(m_Buffer + m_BufferLen);
|
||||
memcpy (lease->tunnelGateway, it->GetNextIdentHash (), 32);
|
||||
lease->tunnelID = htobe32 (it->GetNextTunnelID ());
|
||||
uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - 60; // 1 minute before expiration
|
||||
ts *= 1000; // in milliseconds
|
||||
lease->endDate = htobe64 (ts);
|
||||
m_BufferLen += sizeof (Lease);
|
||||
}
|
||||
// signature
|
||||
localDestination.Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen);
|
||||
m_BufferLen += localDestination.GetIdentity ().GetSignatureLen ();
|
||||
LogPrint ("Local LeaseSet of ", tunnels.size (), " leases created");
|
||||
|
||||
ReadFromBuffer ();
|
||||
}
|
||||
|
||||
void LeaseSet::Update (const uint8_t * buf, int len)
|
||||
{
|
||||
m_Leases.clear ();
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer ();
|
||||
}
|
||||
|
||||
void LeaseSet::ReadFromBuffer ()
|
||||
{
|
||||
size_t size = m_Identity.FromBuffer (m_Buffer, m_BufferLen);
|
||||
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 ("LeaseSet num=", (int)num);
|
||||
|
||||
// process leases
|
||||
const uint8_t * leases = m_Buffer + size;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
Lease lease = *(Lease *)leases;
|
||||
lease.tunnelID = be32toh (lease.tunnelID);
|
||||
lease.endDate = be64toh (lease.endDate);
|
||||
m_Leases.push_back (lease);
|
||||
leases += sizeof (Lease);
|
||||
|
||||
// check if lease's gateway is in our netDb
|
||||
if (!netdb.FindRouter (lease.tunnelGateway))
|
||||
{
|
||||
// if not found request it
|
||||
LogPrint ("Lease's tunnel gateway not found. Requested");
|
||||
netdb.RequestDestination (lease.tunnelGateway);
|
||||
}
|
||||
}
|
||||
|
||||
// verify
|
||||
if (!m_Identity.Verify (m_Buffer, leases - m_Buffer, leases))
|
||||
LogPrint ("LeaseSet verification failed");
|
||||
}
|
||||
|
||||
const std::vector<Lease> LeaseSet::GetNonExpiredLeases () const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
std::vector<Lease> leases;
|
||||
for (auto& it: m_Leases)
|
||||
if (ts < it.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::HasNonExpiredLeases () const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
for (auto& it: m_Leases)
|
||||
if (ts < it.endDate) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
78
LeaseSet.h
Normal file
78
LeaseSet.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
#ifndef LEASE_SET_H__
|
||||
#define LEASE_SET_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
|
||||
namespace tunnel
|
||||
{
|
||||
class TunnelPool;
|
||||
}
|
||||
|
||||
namespace data
|
||||
{
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct Lease
|
||||
{
|
||||
uint8_t tunnelGateway[32];
|
||||
uint32_t tunnelID;
|
||||
uint64_t endDate;
|
||||
|
||||
bool operator< (const Lease& other) const
|
||||
{
|
||||
if (endDate != other.endDate)
|
||||
return endDate > other.endDate;
|
||||
else
|
||||
return tunnelID < other.tunnelID;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
const int MAX_LS_BUFFER_SIZE = 2048;
|
||||
class LeaseSet: public RoutingDestination
|
||||
{
|
||||
public:
|
||||
|
||||
LeaseSet (const uint8_t * buf, int len);
|
||||
LeaseSet (const LeaseSet& ) = default;
|
||||
LeaseSet (const i2p::tunnel::TunnelPool& pool);
|
||||
LeaseSet& operator=(const LeaseSet& ) = default;
|
||||
void Update (const uint8_t * buf, int len);
|
||||
const IdentityEx& GetIdentity () const { return m_Identity; };
|
||||
|
||||
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||
size_t GetBufferLen () const { return m_BufferLen; };
|
||||
|
||||
// implements RoutingDestination
|
||||
const IdentHash& GetIdentHash () const { return m_Identity.GetIdentHash (); };
|
||||
const std::vector<Lease>& GetLeases () const { return m_Leases; };
|
||||
const std::vector<Lease> GetNonExpiredLeases () const;
|
||||
bool HasExpiredLeases () const;
|
||||
bool HasNonExpiredLeases () const;
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
|
||||
bool IsDestination () const { return true; };
|
||||
|
||||
private:
|
||||
|
||||
void ReadFromBuffer ();
|
||||
|
||||
private:
|
||||
|
||||
std::vector<Lease> m_Leases;
|
||||
IdentityEx m_Identity;
|
||||
uint8_t m_EncryptionKey[256];
|
||||
uint8_t m_Buffer[MAX_LS_BUFFER_SIZE];
|
||||
size_t m_BufferLen;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
242
LittleBigEndian.h
Normal file
242
LittleBigEndian.h
Normal file
|
@ -0,0 +1,242 @@
|
|||
// LittleBigEndian.h fixed for 64-bits added union
|
||||
//
|
||||
|
||||
#ifndef LITTLEBIGENDIAN_H
|
||||
#define LITTLEBIGENDIAN_H
|
||||
|
||||
// Determine Little-Endian or Big-Endian
|
||||
|
||||
#define CURRENT_BYTE_ORDER (*(int *)"\x01\x02\x03\x04")
|
||||
#define LITTLE_ENDIAN_BYTE_ORDER 0x04030201
|
||||
#define BIG_ENDIAN_BYTE_ORDER 0x01020304
|
||||
#define PDP_ENDIAN_BYTE_ORDER 0x02010403
|
||||
|
||||
#define IS_LITTLE_ENDIAN (CURRENT_BYTE_ORDER == LITTLE_ENDIAN_BYTE_ORDER)
|
||||
#define IS_BIG_ENDIAN (CURRENT_BYTE_ORDER == BIG_ENDIAN_BYTE_ORDER)
|
||||
#define IS_PDP_ENDIAN (CURRENT_BYTE_ORDER == PDP_ENDIAN_BYTE_ORDER)
|
||||
|
||||
// Forward declaration
|
||||
|
||||
template<typename T>
|
||||
struct LittleEndian;
|
||||
|
||||
template<typename T>
|
||||
struct BigEndian;
|
||||
|
||||
// Little-Endian template
|
||||
|
||||
#pragma pack(push,1)
|
||||
template<typename T>
|
||||
struct LittleEndian
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned char bytes[sizeof(T)];
|
||||
T raw_value;
|
||||
};
|
||||
|
||||
LittleEndian(T t = T())
|
||||
{
|
||||
operator =(t);
|
||||
}
|
||||
|
||||
LittleEndian(const LittleEndian<T> & t)
|
||||
{
|
||||
raw_value = t.raw_value;
|
||||
}
|
||||
|
||||
LittleEndian(const BigEndian<T> & t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[i] = t.bytes[sizeof(T)-1-i];
|
||||
}
|
||||
|
||||
operator const T() const
|
||||
{
|
||||
T t = T();
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
t |= T(bytes[i]) << (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
const T operator = (const T t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[sizeof(T)-1 - i] = static_cast<unsigned char>(t >> (i << 3));
|
||||
return t;
|
||||
}
|
||||
|
||||
// operators
|
||||
|
||||
const T operator += (const T t)
|
||||
{
|
||||
return (*this = *this + t);
|
||||
}
|
||||
|
||||
const T operator -= (const T t)
|
||||
{
|
||||
return (*this = *this - t);
|
||||
}
|
||||
|
||||
const T operator *= (const T t)
|
||||
{
|
||||
return (*this = *this * t);
|
||||
}
|
||||
|
||||
const T operator /= (const T t)
|
||||
{
|
||||
return (*this = *this / t);
|
||||
}
|
||||
|
||||
const T operator %= (const T t)
|
||||
{
|
||||
return (*this = *this % t);
|
||||
}
|
||||
|
||||
LittleEndian<T> operator ++ (int)
|
||||
{
|
||||
LittleEndian<T> tmp(*this);
|
||||
operator ++ ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
LittleEndian<T> & operator ++ ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
++bytes[i];
|
||||
if (bytes[i] != 0)
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
LittleEndian<T> operator -- (int)
|
||||
{
|
||||
LittleEndian<T> tmp(*this);
|
||||
operator -- ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
LittleEndian<T> & operator -- ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
--bytes[i];
|
||||
if (bytes[i] != (T)(-1))
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Big-Endian template
|
||||
|
||||
#pragma pack(push,1)
|
||||
template<typename T>
|
||||
struct BigEndian
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned char bytes[sizeof(T)];
|
||||
T raw_value;
|
||||
};
|
||||
|
||||
BigEndian(T t = T())
|
||||
{
|
||||
operator =(t);
|
||||
}
|
||||
|
||||
BigEndian(const BigEndian<T> & t)
|
||||
{
|
||||
raw_value = t.raw_value;
|
||||
}
|
||||
|
||||
BigEndian(const LittleEndian<T> & t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[i] = t.bytes[sizeof(T)-1-i];
|
||||
}
|
||||
|
||||
operator const T() const
|
||||
{
|
||||
T t = T();
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
const T operator = (const T t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[sizeof(T) - 1 - i] = t >> (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
// operators
|
||||
|
||||
const T operator += (const T t)
|
||||
{
|
||||
return (*this = *this + t);
|
||||
}
|
||||
|
||||
const T operator -= (const T t)
|
||||
{
|
||||
return (*this = *this - t);
|
||||
}
|
||||
|
||||
const T operator *= (const T t)
|
||||
{
|
||||
return (*this = *this * t);
|
||||
}
|
||||
|
||||
const T operator /= (const T t)
|
||||
{
|
||||
return (*this = *this / t);
|
||||
}
|
||||
|
||||
const T operator %= (const T t)
|
||||
{
|
||||
return (*this = *this % t);
|
||||
}
|
||||
|
||||
BigEndian<T> operator ++ (int)
|
||||
{
|
||||
BigEndian<T> tmp(*this);
|
||||
operator ++ ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
BigEndian<T> & operator ++ ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
++bytes[sizeof(T) - 1 - i];
|
||||
if (bytes[sizeof(T) - 1 - i] != 0)
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
BigEndian<T> operator -- (int)
|
||||
{
|
||||
BigEndian<T> tmp(*this);
|
||||
operator -- ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
BigEndian<T> & operator -- ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
--bytes[sizeof(T) - 1 - i];
|
||||
if (bytes[sizeof(T) - 1 - i] != (T)(-1))
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // LITTLEBIGENDIAN_H
|
38
Log.cpp
Normal file
38
Log.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "Log.h"
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
Log * g_Log = nullptr;
|
||||
|
||||
static const char * g_LogLevelStr[eNumLogLevels] =
|
||||
{
|
||||
"error", // eLogError
|
||||
"warn", // eLogWarning
|
||||
"info", // eLogInfo
|
||||
"debug" // eLogDebug
|
||||
};
|
||||
|
||||
void LogMsg::Process()
|
||||
{
|
||||
output << boost::posix_time::second_clock::local_time().time_of_day () <<
|
||||
"/" << g_LogLevelStr[level] << " - ";
|
||||
output << s.str();
|
||||
}
|
||||
|
||||
void Log::Flush ()
|
||||
{
|
||||
if (m_LogFile)
|
||||
m_LogFile->flush();
|
||||
}
|
||||
|
||||
void Log::SetLogFile (const std::string& fullFilePath)
|
||||
{
|
||||
if (m_LogFile) delete m_LogFile;
|
||||
m_LogFile = new std::ofstream (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
|
||||
if (m_LogFile->is_open ())
|
||||
LogPrint("Logging to file ", fullFilePath, " enabled.");
|
||||
else
|
||||
{
|
||||
delete m_LogFile;
|
||||
m_LogFile = nullptr;
|
||||
}
|
||||
}
|
106
Log.h
Normal file
106
Log.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
#ifndef LOG_H__
|
||||
#define LOG_H__
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include "Queue.h"
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
eLogError = 0,
|
||||
eLogWarning,
|
||||
eLogInfo,
|
||||
eLogDebug,
|
||||
eNumLogLevels
|
||||
};
|
||||
|
||||
struct LogMsg
|
||||
{
|
||||
std::stringstream s;
|
||||
std::ostream& output;
|
||||
LogLevel level;
|
||||
|
||||
LogMsg (std::ostream& o = std::cout, LogLevel l = eLogInfo): output (o), level (l) {};
|
||||
|
||||
void Process();
|
||||
};
|
||||
|
||||
class Log: public i2p::util::MsgQueue<LogMsg>
|
||||
{
|
||||
public:
|
||||
|
||||
Log (): m_LogFile (nullptr) { SetOnEmpty (std::bind (&Log::Flush, this)); };
|
||||
~Log () { delete m_LogFile; };
|
||||
|
||||
void SetLogFile (const std::string& fullFilePath);
|
||||
std::ofstream * GetLogFile () const { return m_LogFile; };
|
||||
|
||||
private:
|
||||
|
||||
void Flush ();
|
||||
|
||||
private:
|
||||
|
||||
std::ofstream * m_LogFile;
|
||||
};
|
||||
|
||||
extern Log * g_Log;
|
||||
|
||||
inline void StartLog (const std::string& fullFilePath)
|
||||
{
|
||||
if (!g_Log)
|
||||
{
|
||||
g_Log = new Log ();
|
||||
if (fullFilePath.length () > 0)
|
||||
g_Log->SetLogFile (fullFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
inline void StopLog ()
|
||||
{
|
||||
if (g_Log)
|
||||
{
|
||||
delete g_Log;
|
||||
g_Log = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
void LogPrint (std::stringstream& s, TValue arg)
|
||||
{
|
||||
s << arg;
|
||||
}
|
||||
|
||||
template<typename TValue, typename... TArgs>
|
||||
void LogPrint (std::stringstream& s, TValue arg, TArgs... args)
|
||||
{
|
||||
LogPrint (s, arg);
|
||||
LogPrint (s, args...);
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
void LogPrint (LogLevel level, TArgs... args)
|
||||
{
|
||||
LogMsg * msg = (g_Log && g_Log->GetLogFile ()) ? new LogMsg (*g_Log->GetLogFile (), level) :
|
||||
new LogMsg (std::cout, level);
|
||||
LogPrint (msg->s, args...);
|
||||
msg->s << std::endl;
|
||||
if (g_Log)
|
||||
g_Log->Put (msg);
|
||||
else
|
||||
{
|
||||
msg->Process ();
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
void LogPrint (TArgs... args)
|
||||
{
|
||||
LogPrint (eLogInfo, args...);
|
||||
}
|
||||
|
||||
#endif
|
195
Makefile
195
Makefile
|
@ -1,192 +1,29 @@
|
|||
.DEFAULT_GOAL := all
|
||||
UNAME := $(shell uname -s)
|
||||
|
||||
SYS := $(shell $(CXX) -dumpmachine)
|
||||
|
||||
ifneq (, $(findstring darwin, $(SYS)))
|
||||
SHARED_SUFFIX = dylib
|
||||
else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
|
||||
SHARED_SUFFIX = dll
|
||||
else
|
||||
SHARED_SUFFIX = so
|
||||
endif
|
||||
|
||||
SHLIB := libi2pd.$(SHARED_SUFFIX)
|
||||
ARLIB := libi2pd.a
|
||||
SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX)
|
||||
ARLIB_LANG := libi2pdlang.a
|
||||
SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX)
|
||||
ARLIB_CLIENT := libi2pdclient.a
|
||||
SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX)
|
||||
ARLIB_WRAP := libi2pdwrapper.a
|
||||
I2PD := i2pd
|
||||
|
||||
LIB_SRC_DIR := libi2pd
|
||||
LIB_CLIENT_SRC_DIR := libi2pd_client
|
||||
WRAP_SRC_DIR := libi2pd_wrapper
|
||||
LANG_SRC_DIR := i18n
|
||||
DAEMON_SRC_DIR := daemon
|
||||
|
||||
# import source files lists
|
||||
include filelist.mk
|
||||
|
||||
USE_STATIC := $(or $(USE_STATIC),no)
|
||||
USE_UPNP := $(or $(USE_UPNP),no)
|
||||
DEBUG := $(or $(DEBUG),yes)
|
||||
|
||||
# for debugging purposes only, when commit hash needed in trunk builds in i2pd version string
|
||||
USE_GIT_VERSION := $(or $(USE_GIT_VERSION),no)
|
||||
|
||||
# for MacOS only, waiting for "1", not "yes"
|
||||
HOMEBREW := $(or $(HOMEBREW),0)
|
||||
|
||||
ifeq ($(DEBUG),yes)
|
||||
CXX_DEBUG = -g
|
||||
else
|
||||
CXX_DEBUG = -Os
|
||||
LD_DEBUG = -s
|
||||
endif
|
||||
|
||||
ifneq (, $(DESTDIR))
|
||||
PREFIX = $(DESTDIR)
|
||||
endif
|
||||
|
||||
ifneq (, $(findstring darwin, $(SYS)))
|
||||
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
|
||||
ifeq ($(HOMEBREW),1)
|
||||
include Makefile.homebrew
|
||||
else
|
||||
include Makefile.osx
|
||||
endif
|
||||
else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
|
||||
DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp
|
||||
include Makefile.mingw
|
||||
else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS)))
|
||||
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
|
||||
include Makefile.linux
|
||||
else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
|
||||
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
|
||||
ifeq ($(UNAME),Darwin)
|
||||
include Makefile.osx
|
||||
else ifeq ($(UNAME), FreeBSD)
|
||||
include Makefile.bsd
|
||||
else ifneq (, $(findstring haiku, $(SYS)))
|
||||
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
|
||||
include Makefile.haiku
|
||||
else ifneq (, $(findstring solaris, $(SYS)))
|
||||
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
|
||||
include Makefile.solaris
|
||||
else # not supported
|
||||
$(error Not supported platform)
|
||||
else
|
||||
include Makefile.linux
|
||||
endif
|
||||
|
||||
INCFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR)
|
||||
DEFINES += -DOPENSSL_SUPPRESS_DEPRECATED
|
||||
NEEDED_CXXFLAGS += -MMD -MP
|
||||
all: obj i2p
|
||||
|
||||
ifeq ($(USE_GIT_VERSION),yes)
|
||||
GIT_VERSION := $(shell git describe --tags)
|
||||
DEFINES += -DGITVER=$(GIT_VERSION)
|
||||
endif
|
||||
i2p: $(OBJECTS:obj/%=obj/%)
|
||||
$(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) $(LIBS)
|
||||
|
||||
LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
|
||||
LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC))
|
||||
LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC))
|
||||
DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC))
|
||||
WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC))
|
||||
DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) $(WRAP_LIB_OBJS:.o=.d)
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .cc .C .cpp .o
|
||||
|
||||
## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary
|
||||
all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD)
|
||||
obj/%.o : %.cpp
|
||||
$(CXX) -o $@ $< -c $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS)
|
||||
|
||||
mk_obj_dir:
|
||||
@mkdir -p obj/$(LIB_SRC_DIR)
|
||||
@mkdir -p obj/$(LIB_CLIENT_SRC_DIR)
|
||||
@mkdir -p obj/$(LANG_SRC_DIR)
|
||||
@mkdir -p obj/$(DAEMON_SRC_DIR)
|
||||
@mkdir -p obj/$(WRAP_SRC_DIR)
|
||||
@mkdir -p obj/Win32
|
||||
|
||||
api: $(SHLIB) $(ARLIB)
|
||||
client: $(SHLIB_CLIENT) $(ARLIB_CLIENT)
|
||||
lang: $(SHLIB_LANG) $(ARLIB_LANG)
|
||||
api_client: api client lang
|
||||
wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP)
|
||||
|
||||
## 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.
|
||||
|
||||
obj/%.o: %.cpp | mk_obj_dir
|
||||
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $<
|
||||
|
||||
# '-' is 'ignore if missing' on first run
|
||||
-include $(DEPS)
|
||||
|
||||
$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG)
|
||||
$(CXX) $(DEFINES) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||
|
||||
$(SHLIB): $(LIB_OBJS)
|
||||
ifneq ($(USE_STATIC),yes)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
|
||||
endif
|
||||
|
||||
$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG)
|
||||
ifneq ($(USE_STATIC),yes)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG)
|
||||
endif
|
||||
|
||||
$(SHLIB_WRAP): $(WRAP_LIB_OBJS)
|
||||
ifneq ($(USE_STATIC),yes)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
|
||||
endif
|
||||
|
||||
$(SHLIB_LANG): $(LANG_OBJS)
|
||||
ifneq ($(USE_STATIC),yes)
|
||||
$(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
|
||||
endif
|
||||
|
||||
$(ARLIB): $(LIB_OBJS)
|
||||
$(AR) -r $@ $^
|
||||
|
||||
$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS)
|
||||
$(AR) -r $@ $^
|
||||
|
||||
$(ARLIB_WRAP): $(WRAP_LIB_OBJS)
|
||||
$(AR) -r $@ $^
|
||||
|
||||
$(ARLIB_LANG): $(LANG_OBJS)
|
||||
$(AR) -r $@ $^
|
||||
obj:
|
||||
mkdir -p obj
|
||||
|
||||
clean:
|
||||
$(RM) -r obj
|
||||
$(RM) -r docs/generated
|
||||
$(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP)
|
||||
|
||||
strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG)
|
||||
strip $^
|
||||
|
||||
LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl)
|
||||
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
|
||||
dist:
|
||||
git archive --format=tar.gz -9 --worktree-attributes \
|
||||
--prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz
|
||||
|
||||
last-dist:
|
||||
git archive --format=tar.gz -9 --worktree-attributes \
|
||||
--prefix=i2pd_$(LATEST_TAG)/ $(BRANCH) -o ../i2pd_$(LATEST_TAG).orig.tar.gz
|
||||
|
||||
doxygen:
|
||||
doxygen -s docs/Doxyfile
|
||||
rm -fr obj i2p
|
||||
|
||||
.PHONY: all
|
||||
.PHONY: clean
|
||||
.PHONY: doxygen
|
||||
.PHONY: dist
|
||||
.PHONY: last-dist
|
||||
.PHONY: api
|
||||
.PHONY: api_client
|
||||
.PHONY: client
|
||||
.PHONY: lang
|
||||
.PHONY: mk_obj_dir
|
||||
.PHONY: install
|
||||
.PHONY: strip
|
||||
|
|
28
Makefile.bsd
28
Makefile.bsd
|
@ -1,22 +1,8 @@
|
|||
CXX = clang++
|
||||
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation
|
||||
DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
|
||||
CXX = g++
|
||||
CXXFLAGS = -O2
|
||||
NEEDED_CXXFLAGS = -std=c++11
|
||||
include filelist.mk
|
||||
INCFLAGS = -I/usr/include/ -I/usr/local/include/
|
||||
LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
|
||||
LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options
|
||||
|
||||
## 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.
|
||||
CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2)
|
||||
ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1
|
||||
$(error Compiler too old)
|
||||
else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19
|
||||
NEEDED_CXXFLAGS = -std=c++20
|
||||
else
|
||||
NEEDED_CXXFLAGS = -std=c++17
|
||||
endif
|
||||
|
||||
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib
|
||||
LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||
LIBS =
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
ifeq ($(shell $(CXX) -dumpmachine | cut -c 1-4), i586)
|
||||
CXX = g++-x86
|
||||
else
|
||||
CXX = g++
|
||||
endif
|
||||
CXXFLAGS := -Wall -std=c++20
|
||||
INCFLAGS = -I/system/develop/headers
|
||||
DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||
LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_program_options -lpthread
|
||||
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
DEFINES += -DUSE_UPNP
|
||||
LDLIBS += -lminiupnpc
|
||||
endif
|
|
@ -1,49 +0,0 @@
|
|||
# root directory holding homebrew
|
||||
BREWROOT = /opt/homebrew
|
||||
BOOSTROOT = ${BREWROOT}/opt/boost
|
||||
SSLROOT = ${BREWROOT}/opt/openssl@1.1
|
||||
UPNPROOT = ${BREWROOT}/opt/miniupnpc
|
||||
|
||||
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual
|
||||
NEEDED_CXXFLAGS ?= -std=c++17
|
||||
INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include
|
||||
LDFLAGS ?= ${LD_DEBUG}
|
||||
DEFINES += -DMAC_OSX
|
||||
|
||||
ifeq ($(USE_STATIC),yes)
|
||||
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
|
||||
endif
|
||||
LDLIBS += -lpthread -ldl
|
||||
else
|
||||
LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib
|
||||
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
LDFLAGS += -L${UPNPROOT}/lib
|
||||
LDLIBS += -lminiupnpc
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
DEFINES += -DUSE_UPNP
|
||||
INCFLAGS += -I${UPNPROOT}/include
|
||||
endif
|
||||
|
||||
install: all
|
||||
install -d ${PREFIX}/bin
|
||||
install -m 755 ${I2PD} ${PREFIX}/bin
|
||||
install -d ${PREFIX}/etc ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d
|
||||
install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd
|
||||
install -d ${PREFIX}/share ${PREFIX}/share/doc ${PREFIX}/share/doc/i2pd
|
||||
install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd
|
||||
install -d ${PREFIX}/share/i2pd
|
||||
@cp -R contrib/certificates ${PREFIX}/share/i2pd/
|
||||
install -d ${PREFIX}/share/man ${PREFIX}/share/man/man1
|
||||
@gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1
|
||||
install -d ${PREFIX}/var ${PREFIX}/var/lib ${PREFIX}/var/lib/i2pd
|
||||
@ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/certificates
|
||||
@ln -sf ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/var/lib/i2pd/tunnels.d
|
||||
@ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf
|
||||
@ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt
|
||||
@ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf
|
|
@ -1,70 +1,43 @@
|
|||
# set defaults instead redefine
|
||||
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi
|
||||
LDFLAGS ?= ${LD_DEBUG}
|
||||
|
||||
## 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 FDLAGS to work at build-time.
|
||||
|
||||
# detect proper flag for c++17 support by compilers
|
||||
CXXFLAGS = -g -Wall
|
||||
CXXVER := $(shell $(CXX) -dumpversion)
|
||||
ifeq ($(shell expr match $(CXX) 'clang'),5)
|
||||
NEEDED_CXXFLAGS += -std=c++17
|
||||
else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9
|
||||
NEEDED_CXXFLAGS += -std=c++17
|
||||
LDLIBS = -lboost_system -lstdc++fs
|
||||
else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12
|
||||
NEEDED_CXXFLAGS += -std=c++17
|
||||
else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+
|
||||
NEEDED_CXXFLAGS += -std=c++20
|
||||
|
||||
FGREP = fgrep
|
||||
IS_64 := $(shell $(CXX) -dumpmachine 2>&1 | $(FGREP) -c "64")
|
||||
USE_AESNI := yes
|
||||
ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # >= 4.10
|
||||
NEEDED_CXXFLAGS += -std=c++11
|
||||
else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7
|
||||
NEEDED_CXXFLAGS += -std=c++11
|
||||
else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6
|
||||
NEEDED_CXXFLAGS += -std=c++0x
|
||||
else ifeq ($(shell expr match $(CXX) 'clang'),5)
|
||||
NEEDED_CXXFLAGS += -std=c++11
|
||||
else # not supported
|
||||
$(error Compiler too old)
|
||||
endif
|
||||
|
||||
NEEDED_CXXFLAGS += -fPIC
|
||||
LIBDIR := /usr/lib
|
||||
|
||||
ifeq ($(USE_STATIC),yes)
|
||||
# NOTE: on glibc you will get this warning:
|
||||
# Using 'getaddrinfo' in statically linked applications requires at runtime
|
||||
# the shared libraries from the glibc version used for linking
|
||||
LIBDIR := /usr/lib/$(SYS)
|
||||
LDLIBS += $(LIBDIR)/libboost_program_options.a
|
||||
LDLIBS += $(LIBDIR)/libssl.a
|
||||
LDLIBS += $(LIBDIR)/libcrypto.a
|
||||
LDLIBS += $(LIBDIR)/libz.a
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
LDLIBS += $(LIBDIR)/libminiupnpc.a
|
||||
endif
|
||||
LDLIBS += -lpthread -ldl
|
||||
include filelist.mk
|
||||
INCFLAGS =
|
||||
ifeq ($(STATIC),yes)
|
||||
LDLIBS += $(LIBDIR)/libcryptopp.a $(LIBDIR)/libboost_system.a
|
||||
LDLIBS += $(LIBDIR)/libboost_date_time.a $(LIBDIR)/libboost_filesystem.a
|
||||
LDLIBS += $(LIBDIR)/libboost_regex.a $(LIBDIR)/libboost_program_options.a
|
||||
LDLIBS += -lpthread -static-libstdc++ -static-libgcc
|
||||
USE_AESNI := no
|
||||
else
|
||||
LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
LDLIBS += -lminiupnpc
|
||||
LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||
endif
|
||||
LIBS =
|
||||
|
||||
|
||||
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
|
||||
|
||||
# UPNP Support (miniupnpc 1.5 and higher)
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
DEFINES += -DUSE_UPNP
|
||||
endif
|
||||
|
||||
install: all
|
||||
install -d ${PREFIX}/bin
|
||||
install -m 755 ${I2PD} ${PREFIX}/bin
|
||||
install -d ${PREFIX}/etc ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d
|
||||
install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd
|
||||
install -d ${PREFIX}/share ${PREFIX}/share/doc ${PREFIX}/share/doc/i2pd
|
||||
install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd
|
||||
install -d ${PREFIX}/share/i2pd
|
||||
@cp -R contrib/certificates ${PREFIX}/share/i2pd/
|
||||
install -d ${PREFIX}/share/man ${PREFIX}/share/man/man1
|
||||
@gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1
|
||||
install -d ${PREFIX}/var ${PREFIX}/var/lib ${PREFIX}/var/lib/i2pd
|
||||
@ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/certificates
|
||||
@ln -sf ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/var/lib/i2pd/tunnels.d
|
||||
@ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf
|
||||
@ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt
|
||||
@ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
# Build application with GUI (tray, main window)
|
||||
USE_WIN32_APP := yes
|
||||
|
||||
WINDRES = windres
|
||||
|
||||
CXXFLAGS := $(CXX_DEBUG) -fPIC -msse
|
||||
INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32
|
||||
LDFLAGS := ${LD_DEBUG} -static -fPIC -msse
|
||||
|
||||
NEEDED_CXXFLAGS += -std=c++20
|
||||
DEFINES += -DWIN32_LEAN_AND_MEAN
|
||||
|
||||
# UPNP Support
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
DEFINES += -DUSE_UPNP -DMINIUPNP_STATICLIB
|
||||
LDLIBS = -lminiupnpc
|
||||
endif
|
||||
|
||||
ifeq ($(USE_WINXP_FLAGS), yes)
|
||||
DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
|
||||
endif
|
||||
|
||||
LDLIBS += \
|
||||
$(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \
|
||||
$(MINGW_PREFIX)/lib/libboost_program_options-mt.a \
|
||||
$(MINGW_PREFIX)/lib/libssl.a \
|
||||
$(MINGW_PREFIX)/lib/libcrypto.a \
|
||||
$(MINGW_PREFIX)/lib/libz.a \
|
||||
-lwsock32 \
|
||||
-lws2_32 \
|
||||
-liphlpapi \
|
||||
-lcrypt32 \
|
||||
-lgdi32 \
|
||||
-lole32 \
|
||||
-luuid \
|
||||
-lpthread
|
||||
|
||||
ifeq ($(USE_WIN32_APP), yes)
|
||||
DEFINES += -DWIN32_APP
|
||||
LDFLAGS += -mwindows
|
||||
DAEMON_RC += Win32/Resource.rc
|
||||
DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
|
||||
endif
|
||||
|
||||
ifeq ($(USE_ASLR),yes)
|
||||
LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols
|
||||
endif
|
||||
|
||||
obj/%.o : %.rc | mk_obj_dir
|
||||
$(WINDRES) $(DEFINES) $(INCFLAGS) --preprocessor-arg=-MMD --preprocessor-arg=-MP --preprocessor-arg=-MF$@.d -i $< -o $@
|
41
Makefile.osx
41
Makefile.osx
|
@ -1,29 +1,18 @@
|
|||
CXX = clang++
|
||||
CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17
|
||||
INCFLAGS = -I/usr/local/include
|
||||
DEFINES := -DMAC_OSX
|
||||
LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib
|
||||
LDFLAGS += -Wl,-dead_strip
|
||||
LDFLAGS += -Wl,-dead_strip_dylibs
|
||||
CXXFLAGS = -g -Wall -std=c++11 -lstdc++ -I/usr/local/include
|
||||
include filelist.mk
|
||||
INCFLAGS = -DCRYPTOPP_DISABLE_ASM
|
||||
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib
|
||||
LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||
LIBS =
|
||||
|
||||
ifeq ($(USE_STATIC),yes)
|
||||
LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
|
||||
else
|
||||
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
|
||||
endif
|
||||
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
LDFLAGS += -ldl
|
||||
DEFINES += -DUSE_UPNP
|
||||
ifeq ($(USE_STATIC),yes)
|
||||
LDLIBS += /usr/local/lib/libminiupnpc.a
|
||||
else
|
||||
LDLIBS += -lminiupnpc
|
||||
endif
|
||||
endif
|
||||
|
||||
OSARCH = $(shell uname -p)
|
||||
|
||||
ifneq ($(OSARCH),powerpc)
|
||||
CXXFLAGS += -msse
|
||||
# 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
|
||||
CXXFLAGS += -maes -DAESNI
|
||||
|
||||
# Apple Mac OSX
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
endif
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
CXX = g++
|
||||
INCFLAGS = -I/usr/openssl/3/include
|
||||
CXXFLAGS := -Wall -std=c++20
|
||||
LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket
|
||||
|
||||
ifeq ($(USE_UPNP),yes)
|
||||
DEFINES += -DUSE_UPNP
|
||||
LDLIBS += -lminiupnpc
|
||||
endif
|
640
NTCPSession.cpp
Normal file
640
NTCPSession.cpp
Normal file
|
@ -0,0 +1,640 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <boost/bind.hpp>
|
||||
#include <cryptopp/dh.h>
|
||||
#include "base64.h"
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "CryptoConst.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 (boost::asio::io_service& service, const i2p::data::RouterInfo * in_RemoteRouter):
|
||||
TransportSession (in_RemoteRouter), m_Socket (service),
|
||||
m_TerminationTimer (service), m_IsEstablished (false), m_ReceiveBufferOffset (0),
|
||||
m_NextMessage (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0)
|
||||
{
|
||||
m_DHKeysPair = transports.GetNextDHKeysPair ();
|
||||
m_Establisher = new Establisher;
|
||||
}
|
||||
|
||||
NTCPSession::~NTCPSession ()
|
||||
{
|
||||
delete m_Establisher;
|
||||
if (m_NextMessage)
|
||||
i2p::DeleteI2NPMessage (m_NextMessage);
|
||||
for (auto it :m_DelayedMessages)
|
||||
i2p::DeleteI2NPMessage (it);
|
||||
m_DelayedMessages.clear ();
|
||||
}
|
||||
|
||||
void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key)
|
||||
{
|
||||
CryptoPP::DH dh (elgp, elgg);
|
||||
uint8_t sharedKey[256];
|
||||
if (!dh.Agree (sharedKey, m_DHKeysPair->privateKey, pubKey))
|
||||
{
|
||||
LogPrint ("Couldn't create shared key");
|
||||
Terminate ();
|
||||
return;
|
||||
};
|
||||
|
||||
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 ("First 32 bytes of shared key is all zeros. Ignored");
|
||||
return;
|
||||
}
|
||||
}
|
||||
memcpy (aesKey, nonZero, 32);
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::Terminate ()
|
||||
{
|
||||
m_IsEstablished = false;
|
||||
m_Socket.close ();
|
||||
transports.RemoveNTCPSession (this);
|
||||
int numDelayed = 0;
|
||||
for (auto it :m_DelayedMessages)
|
||||
{
|
||||
// try to send them again
|
||||
if (m_RemoteRouter)
|
||||
transports.SendMessage (m_RemoteRouter->GetIdentHash (), it);
|
||||
numDelayed++;
|
||||
}
|
||||
m_DelayedMessages.clear ();
|
||||
if (numDelayed > 0)
|
||||
LogPrint ("NTCP session ", numDelayed, " not sent");
|
||||
// TODO: notify tunnels
|
||||
|
||||
delete this;
|
||||
LogPrint ("NTCP session terminated");
|
||||
}
|
||||
|
||||
void NTCPSession::Connected ()
|
||||
{
|
||||
LogPrint ("NTCP session connected");
|
||||
m_IsEstablished = true;
|
||||
|
||||
delete m_Establisher;
|
||||
m_Establisher = nullptr;
|
||||
|
||||
delete m_DHKeysPair;
|
||||
m_DHKeysPair = nullptr;
|
||||
|
||||
SendTimeSyncMessage ();
|
||||
SendI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are
|
||||
|
||||
if (!m_DelayedMessages.empty ())
|
||||
{
|
||||
for (auto it :m_DelayedMessages)
|
||||
SendI2NPMessage (it);
|
||||
m_DelayedMessages.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::ClientLogin ()
|
||||
{
|
||||
if (!m_DHKeysPair)
|
||||
m_DHKeysPair = transports.GetNextDHKeysPair ();
|
||||
// send Phase1
|
||||
const uint8_t * x = m_DHKeysPair->publicKey;
|
||||
memcpy (m_Establisher->phase1.pubKey, x, 256);
|
||||
CryptoPP::SHA256().CalculateDigest(m_Establisher->phase1.HXxorHI, x, 256);
|
||||
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 (),
|
||||
boost::bind(&NTCPSession::HandlePhase1Sent, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void NTCPSession::ServerLogin ()
|
||||
{
|
||||
// receive Phase1
|
||||
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase1Received, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Couldn't send Phase 1 message: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 1 sent: ", bytes_transferred);
|
||||
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase2Received, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Phase 1 read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 1 received: ", bytes_transferred);
|
||||
// verify ident
|
||||
uint8_t digest[32];
|
||||
CryptoPP::SHA256().CalculateDigest(digest, m_Establisher->phase1.pubKey, 256);
|
||||
const uint8_t * ident = i2p::context.GetRouterInfo ().GetIdentHash ();
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i])
|
||||
{
|
||||
LogPrint ("Wrong ident");
|
||||
Terminate ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SendPhase2 ();
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::SendPhase2 ()
|
||||
{
|
||||
if (!m_DHKeysPair)
|
||||
m_DHKeysPair = transports.GetNextDHKeysPair ();
|
||||
const uint8_t * y = m_DHKeysPair->publicKey;
|
||||
memcpy (m_Establisher->phase2.pubKey, y, 256);
|
||||
uint8_t xy[512];
|
||||
memcpy (xy, m_Establisher->phase1.pubKey, 256);
|
||||
memcpy (xy + 256, y, 256);
|
||||
CryptoPP::SHA256().CalculateDigest(m_Establisher->phase2.encrypted.hxy, xy, 512);
|
||||
uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ());
|
||||
m_Establisher->phase2.encrypted.timestamp = tsB;
|
||||
// TODO: fill filler
|
||||
|
||||
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 (),
|
||||
boost::bind(&NTCPSession::HandlePhase2Sent, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, tsB));
|
||||
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Couldn't send Phase 2 message: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 2 sent: ", bytes_transferred);
|
||||
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase3, sizeof (NTCPPhase3)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase3Received, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, tsB));
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("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
|
||||
{
|
||||
LogPrint ("Phase 2 received: ", bytes_transferred);
|
||||
|
||||
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], hxy[32];
|
||||
memcpy (xy, m_DHKeysPair->publicKey, 256);
|
||||
memcpy (xy + 256, m_Establisher->phase2.pubKey, 256);
|
||||
CryptoPP::SHA256().CalculateDigest(hxy, xy, 512);
|
||||
if (memcmp (hxy, m_Establisher->phase2.encrypted.hxy, 32))
|
||||
{
|
||||
LogPrint ("Incorrect hash");
|
||||
transports.ReuseDHKeysPair (m_DHKeysPair);
|
||||
m_DHKeysPair = nullptr;
|
||||
Terminate ();
|
||||
return ;
|
||||
}
|
||||
SendPhase3 ();
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::SendPhase3 ()
|
||||
{
|
||||
m_Establisher->phase3.size = htons (i2p::data::DEFAULT_IDENTITY_SIZE);
|
||||
memcpy (&m_Establisher->phase3.ident, &i2p::context.GetIdentity ().GetStandardIdentity (), i2p::data::DEFAULT_IDENTITY_SIZE); // TODO:
|
||||
uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ());
|
||||
m_Establisher->phase3.timestamp = tsA;
|
||||
|
||||
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); // tsB
|
||||
s.Sign (i2p::context.GetPrivateKeys (), m_Establisher->phase3.signature);
|
||||
|
||||
m_Encryption.Encrypt((uint8_t *)&m_Establisher->phase3, sizeof(NTCPPhase3), (uint8_t *)&m_Establisher->phase3);
|
||||
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase3, sizeof (NTCPPhase3)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase3Sent, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, tsA));
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Couldn't send Phase 3 message: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 3 sent: ", bytes_transferred);
|
||||
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase4, sizeof (NTCPPhase4)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase4Received, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, tsA));
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Phase 3 read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 3 received: ", bytes_transferred);
|
||||
m_Decryption.Decrypt ((uint8_t *)&m_Establisher->phase3, sizeof(NTCPPhase3), (uint8_t *)&m_Establisher->phase3);
|
||||
m_RemoteIdentity = m_Establisher->phase3.ident;
|
||||
|
||||
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 (m_Establisher->phase3.timestamp); // tsA
|
||||
s.Insert (tsB); // tsB
|
||||
if (!s.Verify (m_RemoteIdentity, m_Establisher->phase3.signature))
|
||||
{
|
||||
LogPrint ("signature verification failed");
|
||||
Terminate ();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPhase4 (tsB);
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::SendPhase4 (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 (m_Establisher->phase3.timestamp); // tsA
|
||||
s.Insert (tsB); // tsB
|
||||
s.Sign (i2p::context.GetPrivateKeys (), m_Establisher->phase4.signature);
|
||||
m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase4, sizeof(NTCPPhase4), (uint8_t *)&m_Establisher->phase4);
|
||||
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase4, sizeof (NTCPPhase4)), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandlePhase4Sent, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Couldn't send Phase 4 message: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 4 sent: ", bytes_transferred);
|
||||
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 ("Phase 4 read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
// this router doesn't like us
|
||||
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Phase 4 received: ", bytes_transferred);
|
||||
m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase4, sizeof(NTCPPhase4), (uint8_t *)&m_Establisher->phase4);
|
||||
|
||||
// verify 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 (m_Establisher->phase2.encrypted.timestamp); // tsB
|
||||
|
||||
if (!s.Verify (m_RemoteIdentity, m_Establisher->phase4.signature))
|
||||
{
|
||||
LogPrint ("signature verification failed");
|
||||
Terminate ();
|
||||
return;
|
||||
}
|
||||
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),
|
||||
boost::bind(&NTCPSession::HandleReceived, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Read error: ", ecode.message ());
|
||||
//if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_NumReceivedBytes += bytes_transferred;
|
||||
m_ReceiveBufferOffset += bytes_transferred;
|
||||
|
||||
if (m_ReceiveBufferOffset >= 16)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
ScheduleTermination (); // reset termination timer
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes
|
||||
{
|
||||
if (!m_NextMessage) // new message, header expected
|
||||
{
|
||||
m_NextMessage = i2p::NewI2NPMessage ();
|
||||
m_NextMessageOffset = 0;
|
||||
|
||||
m_Decryption.Decrypt (encrypted, m_NextMessage->buf);
|
||||
uint16_t dataSize = be16toh (*(uint16_t *)m_NextMessage->buf);
|
||||
if (dataSize)
|
||||
{
|
||||
// new message
|
||||
if (dataSize > NTCP_MAX_MESSAGE_SIZE)
|
||||
{
|
||||
LogPrint ("NTCP data size ", dataSize, " exceeds max size");
|
||||
i2p::DeleteI2NPMessage (m_NextMessage);
|
||||
m_NextMessage = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_NextMessageOffset += 16;
|
||||
m_NextMessage->offset = 2; // size field
|
||||
m_NextMessage->len = dataSize + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// timestamp
|
||||
LogPrint ("Timestamp");
|
||||
i2p::DeleteI2NPMessage (m_NextMessage);
|
||||
m_NextMessage = nullptr;
|
||||
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
|
||||
i2p::HandleI2NPMessage (m_NextMessage);
|
||||
m_NextMessage = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NTCPSession::Send (i2p::I2NPMessage * msg)
|
||||
{
|
||||
uint8_t * sendBuffer;
|
||||
int len;
|
||||
|
||||
if (msg)
|
||||
{
|
||||
// regular I2NP
|
||||
if (msg->offset < 2)
|
||||
{
|
||||
LogPrint ("Malformed I2NP message");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
sendBuffer = msg->GetBuffer () - 2;
|
||||
len = msg->GetLength ();
|
||||
*((uint16_t *)sendBuffer) = htobe16 (len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// prepare timestamp
|
||||
sendBuffer = m_TimeSyncBuffer;
|
||||
len = 4;
|
||||
*((uint16_t *)sendBuffer) = 0;
|
||||
*((uint32_t *)(sendBuffer + 2)) = htobe32 (time (0));
|
||||
}
|
||||
int rem = (len + 6) & 0x0F; // %16
|
||||
int padding = 0;
|
||||
if (rem > 0) padding = 16 - rem;
|
||||
// TODO: fill padding
|
||||
m_Adler.CalculateDigest (sendBuffer + len + 2 + padding, sendBuffer, len + 2+ padding);
|
||||
|
||||
int l = len + padding + 6;
|
||||
m_Encryption.Encrypt(sendBuffer, l, sendBuffer);
|
||||
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (sendBuffer, l), boost::asio::transfer_all (),
|
||||
boost::bind(&NTCPSession::HandleSent, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, msg));
|
||||
}
|
||||
|
||||
void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg)
|
||||
{
|
||||
if (msg)
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Couldn't send msg: ", 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;
|
||||
ScheduleTermination (); // reset termination timer
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::SendTimeSyncMessage ()
|
||||
{
|
||||
Send (nullptr);
|
||||
}
|
||||
|
||||
void NTCPSession::SendI2NPMessage (I2NPMessage * msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
if (m_IsEstablished)
|
||||
Send (msg);
|
||||
else
|
||||
m_DelayedMessages.push_back (msg);
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPSession::ScheduleTermination ()
|
||||
{
|
||||
m_TerminationTimer.cancel ();
|
||||
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT));
|
||||
m_TerminationTimer.async_wait (boost::bind (&NTCPSession::HandleTerminationTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void NTCPSession::HandleTerminationTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LogPrint ("No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds");
|
||||
//Terminate ();
|
||||
m_Socket.close ();// invoke Terminate () from HandleReceive
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NTCPClient::NTCPClient (boost::asio::io_service& service, const boost::asio::ip::address& address,
|
||||
int port, const i2p::data::RouterInfo& in_RouterInfo):
|
||||
NTCPSession (service, &in_RouterInfo), m_Endpoint (address, port)
|
||||
{
|
||||
Connect ();
|
||||
}
|
||||
|
||||
void NTCPClient::Connect ()
|
||||
{
|
||||
LogPrint ("Connecting to ", m_Endpoint.address ().to_string (),":", m_Endpoint.port ());
|
||||
GetSocket ().async_connect (m_Endpoint, boost::bind (&NTCPClient::HandleConnect,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void NTCPClient::HandleConnect (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("Connect error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Connected");
|
||||
if (GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
|
||||
context.UpdateNTCPV6Address (GetSocket ().local_endpoint ().address ());
|
||||
ClientLogin ();
|
||||
}
|
||||
}
|
||||
|
||||
void NTCPServerConnection::Connected ()
|
||||
{
|
||||
LogPrint ("NTCP server session connected");
|
||||
transports.AddNTCPSession (this);
|
||||
NTCPSession::Connected ();
|
||||
}
|
||||
}
|
||||
}
|
175
NTCPSession.h
Normal file
175
NTCPSession.h
Normal file
|
@ -0,0 +1,175 @@
|
|||
#ifndef NTCP_SESSION_H__
|
||||
#define NTCP_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <list>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/adler32.h>
|
||||
#include "aes.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TransportSession.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
|
||||
#pragma pack(1)
|
||||
struct NTCPPhase1
|
||||
{
|
||||
uint8_t pubKey[256];
|
||||
uint8_t HXxorHI[32];
|
||||
};
|
||||
|
||||
struct NTCPPhase2
|
||||
{
|
||||
uint8_t pubKey[256];
|
||||
struct
|
||||
{
|
||||
uint8_t hxy[32];
|
||||
uint32_t timestamp;
|
||||
uint8_t filler[12];
|
||||
} encrypted;
|
||||
};
|
||||
|
||||
struct NTCPPhase3
|
||||
{
|
||||
uint16_t size;
|
||||
i2p::data::Identity ident;
|
||||
uint32_t timestamp;
|
||||
uint8_t padding[15];
|
||||
uint8_t signature[40];
|
||||
};
|
||||
|
||||
|
||||
struct NTCPPhase4
|
||||
{
|
||||
uint8_t signature[40];
|
||||
uint8_t padding[8];
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
const size_t NTCP_MAX_MESSAGE_SIZE = 16384;
|
||||
const size_t NTCP_BUFFER_SIZE = 1040; // fits one tunnel message (1028)
|
||||
const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes
|
||||
|
||||
class NTCPSession: public TransportSession
|
||||
{
|
||||
public:
|
||||
|
||||
NTCPSession (boost::asio::io_service& service, const i2p::data::RouterInfo * in_RemoteRouter = nullptr);
|
||||
~NTCPSession ();
|
||||
|
||||
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||
bool IsEstablished () const { return m_IsEstablished; };
|
||||
|
||||
void ClientLogin ();
|
||||
void ServerLogin ();
|
||||
void SendI2NPMessage (I2NPMessage * msg);
|
||||
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
|
||||
protected:
|
||||
|
||||
void Terminate ();
|
||||
virtual void Connected ();
|
||||
void SendTimeSyncMessage ();
|
||||
void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; }
|
||||
|
||||
private:
|
||||
|
||||
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 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 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 (i2p::I2NPMessage * msg);
|
||||
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg);
|
||||
|
||||
|
||||
// timer
|
||||
void ScheduleTermination ();
|
||||
void HandleTerminationTimer (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::socket m_Socket;
|
||||
boost::asio::deadline_timer m_TerminationTimer;
|
||||
bool m_IsEstablished;
|
||||
|
||||
i2p::crypto::CBCDecryption m_Decryption;
|
||||
i2p::crypto::CBCEncryption m_Encryption;
|
||||
CryptoPP::Adler32 m_Adler;
|
||||
|
||||
struct Establisher
|
||||
{
|
||||
NTCPPhase1 phase1;
|
||||
NTCPPhase2 phase2;
|
||||
NTCPPhase3 phase3;
|
||||
NTCPPhase4 phase4;
|
||||
} * m_Establisher;
|
||||
|
||||
uint8_t m_ReceiveBuffer[NTCP_BUFFER_SIZE + 16], m_TimeSyncBuffer[16];
|
||||
int m_ReceiveBufferOffset;
|
||||
|
||||
i2p::I2NPMessage * m_NextMessage;
|
||||
std::list<i2p::I2NPMessage *> m_DelayedMessages;
|
||||
size_t m_NextMessageOffset;
|
||||
|
||||
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||
};
|
||||
|
||||
class NTCPClient: public NTCPSession
|
||||
{
|
||||
public:
|
||||
|
||||
NTCPClient (boost::asio::io_service& service, const boost::asio::ip::address& address, int port, const i2p::data::RouterInfo& in_RouterInfo);
|
||||
|
||||
private:
|
||||
|
||||
void Connect ();
|
||||
void HandleConnect (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||
};
|
||||
|
||||
class NTCPServerConnection: public NTCPSession
|
||||
{
|
||||
public:
|
||||
|
||||
NTCPServerConnection (boost::asio::io_service& service):
|
||||
NTCPSession (service) {};
|
||||
|
||||
protected:
|
||||
|
||||
virtual void Connected ();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
898
NetDb.cpp
Normal file
898
NetDb.cpp
Normal file
|
@ -0,0 +1,898 @@
|
|||
#include "I2PEndian.h"
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cryptopp/gzip.h>
|
||||
#include "base64.h"
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Tunnel.h"
|
||||
#include "Transports.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Garlic.h"
|
||||
#include "NetDb.h"
|
||||
#include "Reseed.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace i2p::transport;
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
I2NPMessage * RequestedDestination::CreateRequestMessage (const RouterInfo * router,
|
||||
const i2p::tunnel::InboundTunnel * replyTunnel)
|
||||
{
|
||||
I2NPMessage * msg = i2p::CreateDatabaseLookupMsg (m_Destination,
|
||||
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
|
||||
&m_ExcludedPeers, m_IsLeaseSet, m_Pool);
|
||||
if (m_IsLeaseSet) // wrap lookup message into garlic
|
||||
{
|
||||
if (m_Pool)
|
||||
msg = m_Pool->GetGarlicDestination ().WrapMessage (*router, msg);
|
||||
else
|
||||
LogPrint ("Can't create garlic message without destination");
|
||||
}
|
||||
m_ExcludedPeers.insert (router->GetIdentHash ());
|
||||
m_LastRouter = router;
|
||||
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
return msg;
|
||||
}
|
||||
|
||||
I2NPMessage * RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
|
||||
{
|
||||
I2NPMessage * msg = i2p::CreateDatabaseLookupMsg (m_Destination,
|
||||
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
|
||||
m_ExcludedPeers.insert (floodfill);
|
||||
m_LastRouter = nullptr;
|
||||
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void RequestedDestination::ClearExcludedPeers ()
|
||||
{
|
||||
m_ExcludedPeers.clear ();
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
const char NetDb::m_NetDbPath[] = "/netDb";
|
||||
#else
|
||||
const char NetDb::m_NetDbPath[] = "\\netDb";
|
||||
#endif
|
||||
NetDb netdb;
|
||||
|
||||
NetDb::NetDb (): m_IsRunning (false), m_ReseedRetries (0), m_Thread (0)
|
||||
{
|
||||
}
|
||||
|
||||
NetDb::~NetDb ()
|
||||
{
|
||||
Stop ();
|
||||
for (auto l:m_LeaseSets)
|
||||
delete l.second;
|
||||
for (auto r:m_RequestedDestinations)
|
||||
delete r.second;
|
||||
}
|
||||
|
||||
void NetDb::Start ()
|
||||
{
|
||||
Load (m_NetDbPath);
|
||||
while (m_RouterInfos.size () < 100 && m_ReseedRetries < 10)
|
||||
{
|
||||
Reseeder reseeder;
|
||||
reseeder.reseedNow();
|
||||
m_ReseedRetries++;
|
||||
Load (m_NetDbPath);
|
||||
}
|
||||
m_Thread = new std::thread (std::bind (&NetDb::Run, this));
|
||||
}
|
||||
|
||||
void NetDb::Stop ()
|
||||
{
|
||||
if (m_Thread)
|
||||
{
|
||||
m_IsRunning = false;
|
||||
m_Queue.WakeUp ();
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NetDb::Run ()
|
||||
{
|
||||
uint32_t lastSave = 0, lastPublish = 0;
|
||||
m_IsRunning = true;
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
I2NPMessage * msg = m_Queue.GetNextWithTimeout (15000); // 15 sec
|
||||
if (msg)
|
||||
{
|
||||
while (msg)
|
||||
{
|
||||
switch (msg->GetHeader ()->typeID)
|
||||
{
|
||||
case eI2NPDatabaseStore:
|
||||
LogPrint ("DatabaseStore");
|
||||
HandleDatabaseStoreMsg (msg);
|
||||
break;
|
||||
case eI2NPDatabaseSearchReply:
|
||||
LogPrint ("DatabaseSearchReply");
|
||||
HandleDatabaseSearchReplyMsg (msg);
|
||||
break;
|
||||
case eI2NPDatabaseLookup:
|
||||
LogPrint ("DatabaseLookup");
|
||||
HandleDatabaseLookupMsg (msg);
|
||||
break;
|
||||
default: // WTF?
|
||||
LogPrint ("NetDb: unexpected message type ", msg->GetHeader ()->typeID);
|
||||
i2p::HandleI2NPMessage (msg);
|
||||
}
|
||||
msg = m_Queue.Get ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_IsRunning) break;
|
||||
// if no new DatabaseStore coming, explore it
|
||||
auto numRouters = m_RouterInfos.size ();
|
||||
Explore (numRouters < 1500 ? 5 : 1);
|
||||
}
|
||||
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute
|
||||
{
|
||||
if (lastSave)
|
||||
{
|
||||
SaveUpdated (m_NetDbPath);
|
||||
ManageLeaseSets ();
|
||||
}
|
||||
lastSave = ts;
|
||||
}
|
||||
if (ts - lastPublish >= 600) // publish every 10 minutes
|
||||
{
|
||||
Publish ();
|
||||
lastPublish = ts;
|
||||
}
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint ("NetDb: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len)
|
||||
{
|
||||
DeleteRequestedDestination (ident);
|
||||
auto it = m_RouterInfos.find(ident);
|
||||
if (it != m_RouterInfos.end ())
|
||||
{
|
||||
auto ts = it->second->GetTimestamp ();
|
||||
it->second->Update (buf, len);
|
||||
if (it->second->GetTimestamp () > ts)
|
||||
LogPrint ("RouterInfo updated");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("New RouterInfo added");
|
||||
auto r = std::make_shared<RouterInfo> (buf, len);
|
||||
m_RouterInfos[r->GetIdentHash ()] = r;
|
||||
if (r->IsFloodfill ())
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_FloodfillsMutex);
|
||||
m_Floodfills.push_back (r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len,
|
||||
i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
DeleteRequestedDestination (ident);
|
||||
if (!from) // unsolicited LS must be received directly
|
||||
{
|
||||
auto it = m_LeaseSets.find(ident);
|
||||
if (it != m_LeaseSets.end ())
|
||||
{
|
||||
it->second->Update (buf, len);
|
||||
LogPrint ("LeaseSet updated");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("New LeaseSet added");
|
||||
m_LeaseSets[ident] = new LeaseSet (buf, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RouterInfo * NetDb::FindRouter (const IdentHash& ident) const
|
||||
{
|
||||
auto it = m_RouterInfos.find (ident);
|
||||
if (it != m_RouterInfos.end ())
|
||||
return it->second.get ();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LeaseSet * NetDb::FindLeaseSet (const IdentHash& destination) const
|
||||
{
|
||||
auto it = m_LeaseSets.find (destination);
|
||||
if (it != m_LeaseSets.end ())
|
||||
return it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable)
|
||||
{
|
||||
auto it = m_RouterInfos.find (ident);
|
||||
if (it != m_RouterInfos.end ())
|
||||
return it->second->SetUnreachable (unreachable);
|
||||
}
|
||||
|
||||
// TODO: Move to reseed and/or scheduled tasks. (In java version, scheduler fix this as well as sort RIs.)
|
||||
bool NetDb::CreateNetDb(boost::filesystem::path directory)
|
||||
{
|
||||
LogPrint (directory.string(), " doesn't exist, trying to create it.");
|
||||
if (!boost::filesystem::create_directory (directory))
|
||||
{
|
||||
LogPrint("Failed to create directory ", directory.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
// list of chars might appear in base64 string
|
||||
const char * chars = GetBase64SubstitutionTable (); // 64 bytes
|
||||
boost::filesystem::path suffix;
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
suffix = std::string ("/r") + chars[i];
|
||||
#else
|
||||
suffix = std::string ("\\r") + chars[i];
|
||||
#endif
|
||||
if (!boost::filesystem::create_directory( boost::filesystem::path (directory / suffix) )) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetDb::Load (const char * directory)
|
||||
{
|
||||
boost::filesystem::path p (i2p::util::filesystem::GetDataDir());
|
||||
p /= (directory);
|
||||
if (!boost::filesystem::exists (p))
|
||||
{
|
||||
// seems netDb doesn't exist yet
|
||||
if (!CreateNetDb(p)) return;
|
||||
}
|
||||
// make sure we cleanup netDb from previous attempts
|
||||
m_RouterInfos.clear ();
|
||||
m_Floodfills.clear ();
|
||||
|
||||
// load routers now
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
int numRouters = 0;
|
||||
boost::filesystem::directory_iterator end;
|
||||
for (boost::filesystem::directory_iterator it (p); it != end; ++it)
|
||||
{
|
||||
if (boost::filesystem::is_directory (it->status()))
|
||||
{
|
||||
for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1)
|
||||
{
|
||||
#if BOOST_VERSION > 10500
|
||||
const std::string& fullPath = it1->path().string();
|
||||
#else
|
||||
const std::string& fullPath = it1->path();
|
||||
#endif
|
||||
auto r = std::make_shared<RouterInfo>(fullPath);
|
||||
if (!r->IsUnreachable () && (!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour
|
||||
{
|
||||
r->DeleteBuffer ();
|
||||
m_RouterInfos[r->GetIdentHash ()] = r;
|
||||
if (r->IsFloodfill ())
|
||||
m_Floodfills.push_back (r);
|
||||
numRouters++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (boost::filesystem::exists (fullPath))
|
||||
boost::filesystem::remove (fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogPrint (numRouters, " routers loaded");
|
||||
LogPrint (m_Floodfills.size (), " floodfills loaded");
|
||||
}
|
||||
|
||||
void NetDb::SaveUpdated (const char * directory)
|
||||
{
|
||||
auto GetFilePath = [](const char * directory, const RouterInfo * routerInfo)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return std::string (directory) + "/r" +
|
||||
routerInfo->GetIdentHashBase64 ()[0] + "/routerInfo-" +
|
||||
#else
|
||||
return std::string (directory) + "\\r" +
|
||||
routerInfo->GetIdentHashBase64 ()[0] + "\\routerInfo-" +
|
||||
#endif
|
||||
routerInfo->GetIdentHashBase64 () + ".dat";
|
||||
};
|
||||
|
||||
boost::filesystem::path p (i2p::util::filesystem::GetDataDir());
|
||||
p /= (directory);
|
||||
#if BOOST_VERSION > 10500
|
||||
const char * fullDirectory = p.string().c_str ();
|
||||
#else
|
||||
const char * fullDirectory = p.c_str ();
|
||||
#endif
|
||||
int count = 0, deletedCount = 0;
|
||||
auto total = m_RouterInfos.size ();
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
for (auto it: m_RouterInfos)
|
||||
{
|
||||
if (it.second->IsUpdated ())
|
||||
{
|
||||
it.second->SaveToFile (GetFilePath(fullDirectory, it.second.get ()));
|
||||
it.second->SetUpdated (false);
|
||||
it.second->DeleteBuffer ();
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// RouterInfo expires after 1 hour if uses introducer
|
||||
if ((it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour
|
||||
// RouterInfo expires in 72 hours if more than 300
|
||||
|| (total > 300 && ts > it.second->GetTimestamp () + 3*24*3600*1000LL)) // 3 days
|
||||
{
|
||||
total--;
|
||||
it.second->SetUnreachable (true);
|
||||
}
|
||||
|
||||
if (it.second->IsUnreachable ())
|
||||
{
|
||||
if (boost::filesystem::exists (GetFilePath (fullDirectory, it.second.get ())))
|
||||
{
|
||||
boost::filesystem::remove (GetFilePath (fullDirectory, it.second.get ()));
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0)
|
||||
LogPrint (count," new/updated routers saved");
|
||||
if (deletedCount > 0)
|
||||
LogPrint (deletedCount," routers deleted");
|
||||
}
|
||||
|
||||
void NetDb::RequestDestination (const IdentHash& destination, bool isLeaseSet, i2p::tunnel::TunnelPool * pool)
|
||||
{
|
||||
if (isLeaseSet) // we request LeaseSet through tunnels
|
||||
{
|
||||
i2p::tunnel::OutboundTunnel * outbound = pool ? pool->GetNextOutboundTunnel () : i2p::tunnel::tunnels.GetNextOutboundTunnel ();
|
||||
if (outbound)
|
||||
{
|
||||
i2p::tunnel::InboundTunnel * inbound = pool ? pool->GetNextInboundTunnel () :i2p::tunnel::tunnels.GetNextInboundTunnel ();
|
||||
if (inbound)
|
||||
{
|
||||
RequestedDestination * dest = CreateRequestedDestination (destination, true, false, pool);
|
||||
auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ());
|
||||
if (floodfill)
|
||||
{
|
||||
// DatabaseLookup message
|
||||
outbound->SendTunnelDataMsg (
|
||||
{
|
||||
i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
floodfill->GetIdentHash (), 0,
|
||||
dest->CreateRequestMessage (floodfill, inbound)
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
LogPrint ("No more floodfills found");
|
||||
}
|
||||
else
|
||||
LogPrint ("No inbound tunnels found");
|
||||
}
|
||||
else
|
||||
LogPrint ("No outbound tunnels found");
|
||||
}
|
||||
else // RouterInfo is requested directly
|
||||
{
|
||||
RequestedDestination * dest = CreateRequestedDestination (destination, false, false, pool);
|
||||
auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ());
|
||||
if (floodfill)
|
||||
transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
|
||||
}
|
||||
}
|
||||
|
||||
void NetDb::HandleDatabaseStoreMsg (I2NPMessage * m)
|
||||
{
|
||||
const uint8_t * buf = m->GetPayload ();
|
||||
size_t len = be16toh (m->GetHeader ()->size);
|
||||
I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)buf;
|
||||
size_t offset = sizeof (I2NPDatabaseStoreMsg);
|
||||
if (msg->replyToken)
|
||||
offset += 36;
|
||||
if (msg->type)
|
||||
{
|
||||
LogPrint ("LeaseSet");
|
||||
AddLeaseSet (msg->key, buf + offset, len - offset, m->from);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("RouterInfo");
|
||||
size_t size = be16toh (*(uint16_t *)(buf + offset));
|
||||
if (size > 2048)
|
||||
{
|
||||
LogPrint ("Invalid RouterInfo length ", (int)size);
|
||||
return;
|
||||
}
|
||||
offset += 2;
|
||||
CryptoPP::Gunzip decompressor;
|
||||
decompressor.Put (buf + offset, size);
|
||||
decompressor.MessageEnd();
|
||||
uint8_t uncompressed[2048];
|
||||
size_t uncomressedSize = decompressor.MaxRetrievable ();
|
||||
decompressor.Get (uncompressed, uncomressedSize);
|
||||
AddRouterInfo (msg->key, uncompressed, uncomressedSize);
|
||||
}
|
||||
i2p::DeleteI2NPMessage (m);
|
||||
}
|
||||
|
||||
void NetDb::HandleDatabaseSearchReplyMsg (I2NPMessage * msg)
|
||||
{
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
char key[48];
|
||||
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
|
||||
key[l] = 0;
|
||||
int num = buf[32]; // num
|
||||
LogPrint ("DatabaseSearchReply for ", key, " num=", num);
|
||||
auto it = m_RequestedDestinations.find (IdentHash (buf));
|
||||
if (it != m_RequestedDestinations.end ())
|
||||
{
|
||||
RequestedDestination * dest = it->second;
|
||||
bool deleteDest = true;
|
||||
if (num > 0)
|
||||
{
|
||||
auto pool = dest ? dest->GetTunnelPool () : nullptr;
|
||||
auto outbound = pool ? pool->GetNextOutboundTunnel () : i2p::tunnel::tunnels.GetNextOutboundTunnel ();
|
||||
auto inbound = pool ? pool->GetNextInboundTunnel () : i2p::tunnel::tunnels.GetNextInboundTunnel ();
|
||||
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||
if (!dest->IsExploratory ())
|
||||
{
|
||||
// reply to our destination. Try other floodfills
|
||||
if (outbound && inbound )
|
||||
{
|
||||
auto count = dest->GetExcludedPeers ().size ();
|
||||
if (count < 7)
|
||||
{
|
||||
auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
|
||||
if (nextFloodfill)
|
||||
{
|
||||
if (!dest->IsLeaseSet ())
|
||||
{
|
||||
// tell floodfill about us
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
nextFloodfill->GetIdentHash (), 0,
|
||||
CreateDatabaseStoreMsg ()
|
||||
});
|
||||
}
|
||||
|
||||
// request destination
|
||||
LogPrint ("Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ());
|
||||
auto msg = dest->CreateRequestMessage (nextFloodfill, inbound);
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
nextFloodfill->GetIdentHash (), 0, msg
|
||||
});
|
||||
deleteDest = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (key, " was not found on 7 floodfills");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
uint8_t * router = buf + 33 + i*32;
|
||||
char peerHash[48];
|
||||
int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48);
|
||||
peerHash[l1] = 0;
|
||||
LogPrint (i,": ", peerHash);
|
||||
|
||||
if (dest->IsExploratory ())
|
||||
{
|
||||
auto r = FindRouter (router);
|
||||
if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL)
|
||||
{
|
||||
// router with ident not found or too old (1 hour)
|
||||
LogPrint ("Found new/outdated router. Requesting RouterInfo ...");
|
||||
if (outbound && inbound && dest->GetLastRouter ())
|
||||
{
|
||||
RequestedDestination * d1 = CreateRequestedDestination (router, false, false, pool);
|
||||
auto msg = d1->CreateRequestMessage (dest->GetLastRouter (), inbound);
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
dest->GetLastRouter ()->GetIdentHash (), 0, msg
|
||||
});
|
||||
}
|
||||
else
|
||||
RequestDestination (router, false, pool);
|
||||
}
|
||||
else
|
||||
LogPrint ("Bayan");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto r = FindRouter (router);
|
||||
// do we have that floodfill router in our database?
|
||||
if (!r)
|
||||
{
|
||||
// request router
|
||||
LogPrint ("Found new floodfill. Request it");
|
||||
RequestDestination (router, false, pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outbound && msgs.size () > 0)
|
||||
outbound->SendTunnelDataMsg (msgs);
|
||||
if (deleteDest)
|
||||
{
|
||||
// no more requests for the destinationation. delete it
|
||||
delete it->second;
|
||||
m_RequestedDestinations.erase (it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no more requests for detination possible. delete it
|
||||
delete it->second;
|
||||
m_RequestedDestinations.erase (it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Requested destination for ", key, " not found");
|
||||
// it might contain new routers
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
IdentHash router (buf + 33 + i*32);
|
||||
if (!FindRouter (router))
|
||||
{
|
||||
LogPrint ("New router ", router.ToBase64 (), " found. Request it");
|
||||
RequestDestination (router);
|
||||
}
|
||||
}
|
||||
}
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
|
||||
void NetDb::HandleDatabaseLookupMsg (I2NPMessage * msg)
|
||||
{
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
char key[48];
|
||||
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
|
||||
key[l] = 0;
|
||||
LogPrint ("DatabaseLookup for ", key, " recieved");
|
||||
uint8_t flag = buf[64];
|
||||
uint8_t * excluded = buf + 65;
|
||||
uint32_t replyTunnelID = 0;
|
||||
if (flag & 0x01) //reply to tunnel
|
||||
{
|
||||
replyTunnelID = be32toh (*(uint32_t *)(buf + 64));
|
||||
excluded += 4;
|
||||
}
|
||||
uint16_t numExcluded = be16toh (*(uint16_t *)excluded);
|
||||
excluded += 2;
|
||||
if (numExcluded > 512)
|
||||
{
|
||||
LogPrint ("Number of excluded peers", numExcluded, " exceeds 512");
|
||||
numExcluded = 0; // TODO:
|
||||
}
|
||||
|
||||
I2NPMessage * replyMsg = nullptr;
|
||||
|
||||
{
|
||||
auto router = FindRouter (buf);
|
||||
if (router)
|
||||
{
|
||||
LogPrint ("Requested RouterInfo ", key, " found");
|
||||
router->LoadBuffer ();
|
||||
if (router->GetBuffer ())
|
||||
replyMsg = CreateDatabaseStoreMsg (router);
|
||||
}
|
||||
}
|
||||
if (!replyMsg)
|
||||
{
|
||||
auto leaseSet = FindLeaseSet (buf);
|
||||
if (leaseSet) // we don't send back our LeaseSets
|
||||
{
|
||||
LogPrint ("Requested LeaseSet ", key, " found");
|
||||
replyMsg = CreateDatabaseStoreMsg (leaseSet);
|
||||
}
|
||||
}
|
||||
if (!replyMsg)
|
||||
{
|
||||
LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded");
|
||||
std::set<IdentHash> excludedRouters;
|
||||
for (int i = 0; i < numExcluded; i++)
|
||||
{
|
||||
// TODO: check for all zeroes (exploratory)
|
||||
excludedRouters.insert (excluded);
|
||||
excluded += 32;
|
||||
}
|
||||
replyMsg = CreateDatabaseSearchReply (buf, GetClosestFloodfill (buf, excludedRouters));
|
||||
}
|
||||
else
|
||||
excluded += numExcluded*32; // we don't care about exluded
|
||||
|
||||
if (replyMsg)
|
||||
{
|
||||
if (replyTunnelID)
|
||||
{
|
||||
// encryption might be used though tunnel only
|
||||
if (flag & 0x02) // encrypted reply requested
|
||||
{
|
||||
uint8_t * sessionKey = excluded;
|
||||
uint8_t numTags = sessionKey[32];
|
||||
if (numTags > 0)
|
||||
{
|
||||
uint8_t * sessionTag = sessionKey + 33; // take first tag
|
||||
i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag);
|
||||
replyMsg = garlic.WrapSingleMessage (replyMsg);
|
||||
}
|
||||
}
|
||||
auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||
auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
|
||||
if (outbound)
|
||||
outbound->SendTunnelDataMsg (buf+32, replyTunnelID, replyMsg);
|
||||
else
|
||||
transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg));
|
||||
}
|
||||
else
|
||||
transports.SendMessage (buf+32, replyMsg);
|
||||
}
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
|
||||
void NetDb::Explore (int numDestinations)
|
||||
{
|
||||
// clean up previous exploratories
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
|
||||
{
|
||||
if (it->second->IsExploratory () || ts > it->second->GetCreationTime () + 60) // no response for 1 minute
|
||||
{
|
||||
delete it->second;
|
||||
it = m_RequestedDestinations.erase (it);
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
// new requests
|
||||
auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||
auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : i2p::tunnel::tunnels.GetNextOutboundTunnel ();
|
||||
auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : i2p::tunnel::tunnels.GetNextInboundTunnel ();
|
||||
bool throughTunnels = outbound && inbound;
|
||||
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
uint8_t randomHash[32];
|
||||
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||
std::set<const RouterInfo *> floodfills;
|
||||
LogPrint ("Exploring new ", numDestinations, " routers ...");
|
||||
for (int i = 0; i < numDestinations; i++)
|
||||
{
|
||||
rnd.GenerateBlock (randomHash, 32);
|
||||
RequestedDestination * dest = CreateRequestedDestination (IdentHash (randomHash), false, true, exploratoryPool);
|
||||
auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ());
|
||||
if (floodfill && !floodfills.count (floodfill)) // request floodfill only once
|
||||
{
|
||||
floodfills.insert (floodfill);
|
||||
if (throughTunnels)
|
||||
{
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
floodfill->GetIdentHash (), 0,
|
||||
CreateDatabaseStoreMsg () // tell floodfill about us
|
||||
});
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
floodfill->GetIdentHash (), 0,
|
||||
dest->CreateRequestMessage (floodfill, inbound) // explore
|
||||
});
|
||||
}
|
||||
else
|
||||
i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
|
||||
}
|
||||
else
|
||||
DeleteRequestedDestination (dest);
|
||||
}
|
||||
if (throughTunnels && msgs.size () > 0)
|
||||
outbound->SendTunnelDataMsg (msgs);
|
||||
}
|
||||
|
||||
void NetDb::Publish ()
|
||||
{
|
||||
std::set<IdentHash> excluded; // TODO: fill up later
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded);
|
||||
if (floodfill)
|
||||
{
|
||||
LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation ());
|
||||
transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg ());
|
||||
excluded.insert (floodfill->GetIdentHash ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RequestedDestination * NetDb::CreateRequestedDestination (const IdentHash& dest,
|
||||
bool isLeaseSet, bool isExploratory, i2p::tunnel::TunnelPool * pool)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
auto it = m_RequestedDestinations.find (dest);
|
||||
if (it == m_RequestedDestinations.end ()) // not exist yet
|
||||
{
|
||||
RequestedDestination * d = new RequestedDestination (dest, isLeaseSet, isExploratory, pool);
|
||||
m_RequestedDestinations[dest] = d;
|
||||
return d;
|
||||
}
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool NetDb::DeleteRequestedDestination (const IdentHash& dest)
|
||||
{
|
||||
auto it = m_RequestedDestinations.find (dest);
|
||||
if (it != m_RequestedDestinations.end ())
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
delete it->second;
|
||||
m_RequestedDestinations.erase (it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NetDb::DeleteRequestedDestination (RequestedDestination * dest)
|
||||
{
|
||||
if (dest)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
m_RequestedDestinations.erase (dest->GetDestination ());
|
||||
delete dest;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter () const
|
||||
{
|
||||
return GetRandomRouter (
|
||||
[](std::shared_ptr<const RouterInfo> router)->bool
|
||||
{
|
||||
return !router->IsHidden ();
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (const RouterInfo * compatibleWith) const
|
||||
{
|
||||
return GetRandomRouter (
|
||||
[compatibleWith](std::shared_ptr<const RouterInfo> router)->bool
|
||||
{
|
||||
return !router->IsHidden () && router.get () != compatibleWith &&
|
||||
router->IsCompatible (*compatibleWith);
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo> NetDb::GetHighBandwidthRandomRouter (const RouterInfo * compatibleWith) const
|
||||
{
|
||||
return GetRandomRouter (
|
||||
[compatibleWith](std::shared_ptr<const RouterInfo> router)->bool
|
||||
{
|
||||
return !router->IsHidden () && router.get () != compatibleWith &&
|
||||
router->IsCompatible (*compatibleWith) && (router->GetCaps () & RouterInfo::eHighBandwidth);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Filter>
|
||||
std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (Filter filter) const
|
||||
{
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
uint32_t ind = rnd.GenerateWord32 (0, m_RouterInfos.size () - 1);
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
for (auto it: m_RouterInfos)
|
||||
{
|
||||
if (i >= ind)
|
||||
{
|
||||
if (!it.second->IsUnreachable () && filter (it.second))
|
||||
return it.second;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
// we couldn't find anything, try second pass
|
||||
ind = 0;
|
||||
}
|
||||
return nullptr; // seems we have too few routers
|
||||
}
|
||||
|
||||
void NetDb::PostI2NPMsg (I2NPMessage * msg)
|
||||
{
|
||||
if (msg) m_Queue.Put (msg);
|
||||
}
|
||||
|
||||
const RouterInfo * NetDb::GetClosestFloodfill (const IdentHash& destination,
|
||||
const std::set<IdentHash>& excluded) const
|
||||
{
|
||||
RouterInfo * r = nullptr;
|
||||
XORMetric minMetric;
|
||||
IdentHash destKey = CreateRoutingKey (destination);
|
||||
minMetric.SetMax ();
|
||||
std::unique_lock<std::mutex> l(m_FloodfillsMutex);
|
||||
for (auto it: m_Floodfills)
|
||||
{
|
||||
if (!it->IsUnreachable () && !excluded.count (it->GetIdentHash ()))
|
||||
{
|
||||
XORMetric m = destKey ^ it->GetIdentHash ();
|
||||
if (m < minMetric)
|
||||
{
|
||||
minMetric = m;
|
||||
r = it.get ();
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void NetDb::ManageLeaseSets ()
|
||||
{
|
||||
for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();)
|
||||
{
|
||||
if (it->second->HasNonExpiredLeases ()) // all leases expired
|
||||
{
|
||||
LogPrint ("LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
|
||||
delete it->second;
|
||||
it = m_LeaseSets.erase (it);
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void NetDb::PublishLeaseSet (const LeaseSet * leaseSet, i2p::tunnel::TunnelPool * pool)
|
||||
{
|
||||
if (!leaseSet || !pool) return;
|
||||
auto outbound = pool->GetNextOutboundTunnel ();
|
||||
if (!outbound)
|
||||
{
|
||||
LogPrint ("Can't publish LeaseSet. No outbound tunnels");
|
||||
return;
|
||||
}
|
||||
std::set<IdentHash> excluded;
|
||||
auto floodfill = GetClosestFloodfill (leaseSet->GetIdentHash (), excluded);
|
||||
if (!floodfill)
|
||||
{
|
||||
LogPrint ("Can't publish LeaseSet. No floodfills found");
|
||||
return;
|
||||
}
|
||||
uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
|
||||
auto msg = pool->GetGarlicDestination ().WrapMessage (*floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, replyToken));
|
||||
outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg);
|
||||
}
|
||||
}
|
||||
}
|
130
NetDb.h
Normal file
130
NetDb.h
Normal file
|
@ -0,0 +1,130 @@
|
|||
#ifndef NETDB_H__
|
||||
#define NETDB_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "Queue.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
class RequestedDestination
|
||||
{
|
||||
public:
|
||||
|
||||
RequestedDestination (const IdentHash& destination, bool isLeaseSet,
|
||||
bool isExploratory = false, i2p::tunnel::TunnelPool * pool = nullptr):
|
||||
m_Destination (destination), m_IsLeaseSet (isLeaseSet), m_IsExploratory (isExploratory),
|
||||
m_Pool (pool), m_LastRouter (nullptr), m_CreationTime (0) {};
|
||||
|
||||
const IdentHash& GetDestination () const { return m_Destination; };
|
||||
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
|
||||
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
|
||||
void ClearExcludedPeers ();
|
||||
const RouterInfo * GetLastRouter () const { return m_LastRouter; };
|
||||
i2p::tunnel::TunnelPool * GetTunnelPool () { return m_Pool; };
|
||||
bool IsExploratory () const { return m_IsExploratory; };
|
||||
bool IsLeaseSet () const { return m_IsLeaseSet; };
|
||||
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
|
||||
uint64_t GetCreationTime () const { return m_CreationTime; };
|
||||
I2NPMessage * CreateRequestMessage (const RouterInfo * router, const i2p::tunnel::InboundTunnel * replyTunnel);
|
||||
I2NPMessage * CreateRequestMessage (const IdentHash& floodfill);
|
||||
|
||||
private:
|
||||
|
||||
IdentHash m_Destination;
|
||||
bool m_IsLeaseSet, m_IsExploratory;
|
||||
i2p::tunnel::TunnelPool * m_Pool;
|
||||
std::set<IdentHash> m_ExcludedPeers;
|
||||
const RouterInfo * m_LastRouter;
|
||||
uint64_t m_CreationTime;
|
||||
};
|
||||
|
||||
class NetDb
|
||||
{
|
||||
public:
|
||||
|
||||
NetDb ();
|
||||
~NetDb ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
void AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len);
|
||||
void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from);
|
||||
RouterInfo * FindRouter (const IdentHash& ident) const;
|
||||
LeaseSet * FindLeaseSet (const IdentHash& destination) const;
|
||||
|
||||
void PublishLeaseSet (const LeaseSet * leaseSet, i2p::tunnel::TunnelPool * pool);
|
||||
void RequestDestination (const IdentHash& destination, bool isLeaseSet = false,
|
||||
i2p::tunnel::TunnelPool * pool = nullptr);
|
||||
|
||||
void HandleDatabaseStoreMsg (I2NPMessage * msg);
|
||||
void HandleDatabaseSearchReplyMsg (I2NPMessage * msg);
|
||||
void HandleDatabaseLookupMsg (I2NPMessage * msg);
|
||||
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter (const RouterInfo * compatibleWith) const;
|
||||
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (const RouterInfo * compatibleWith) const;
|
||||
void SetUnreachable (const IdentHash& ident, bool unreachable);
|
||||
|
||||
void PostI2NPMsg (I2NPMessage * msg);
|
||||
|
||||
// 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:
|
||||
|
||||
bool CreateNetDb(boost::filesystem::path directory);
|
||||
void Load (const char * directory);
|
||||
void SaveUpdated (const char * directory);
|
||||
void Run (); // exploratory thread
|
||||
void Explore (int numDestinations);
|
||||
void Publish ();
|
||||
const RouterInfo * GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
|
||||
void ManageLeaseSets ();
|
||||
|
||||
RequestedDestination * CreateRequestedDestination (const IdentHash& dest,
|
||||
bool isLeaseSet, bool isExploratory = false, i2p::tunnel::TunnelPool * pool = nullptr);
|
||||
bool DeleteRequestedDestination (const IdentHash& dest); // returns true if found
|
||||
void DeleteRequestedDestination (RequestedDestination * dest);
|
||||
|
||||
template<typename Filter>
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
|
||||
|
||||
private:
|
||||
|
||||
std::map<IdentHash, LeaseSet *> m_LeaseSets;
|
||||
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;
|
||||
mutable std::mutex m_FloodfillsMutex;
|
||||
std::vector<std::shared_ptr<RouterInfo> > m_Floodfills;
|
||||
std::mutex m_RequestedDestinationsMutex;
|
||||
std::map<IdentHash, RequestedDestination *> m_RequestedDestinations;
|
||||
|
||||
bool m_IsRunning;
|
||||
int m_ReseedRetries;
|
||||
std::thread * m_Thread;
|
||||
i2p::util::Queue<I2NPMessage> m_Queue; // of I2NPDatabaseStoreMsg
|
||||
|
||||
static const char m_NetDbPath[];
|
||||
};
|
||||
|
||||
extern NetDb netdb;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,20 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2024, 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 QUEUE_H__
|
||||
#define QUEUE_H__
|
||||
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
|
@ -22,47 +13,37 @@ namespace util
|
|||
{
|
||||
template<typename Element>
|
||||
class Queue
|
||||
{
|
||||
{
|
||||
public:
|
||||
|
||||
void Put (Element e)
|
||||
void Put (Element * e)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_Queue.push_back (std::move(e));
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_Queue.push (e);
|
||||
m_NonEmpty.notify_one ();
|
||||
}
|
||||
|
||||
void Put (std::list<Element>& list)
|
||||
{
|
||||
if (!list.empty ())
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_Queue.splice (m_Queue.end (), list);
|
||||
m_NonEmpty.notify_one ();
|
||||
}
|
||||
}
|
||||
|
||||
Element GetNext ()
|
||||
Element * GetNext ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
auto el = GetNonThreadSafe ();
|
||||
Element * el = GetNonThreadSafe ();
|
||||
if (!el)
|
||||
{
|
||||
m_NonEmpty.wait (l);
|
||||
el = GetNonThreadSafe ();
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
Element GetNextWithTimeout (int usec)
|
||||
Element * GetNextWithTimeout (int usec)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
auto el = GetNonThreadSafe ();
|
||||
Element * el = GetNonThreadSafe ();
|
||||
if (!el)
|
||||
{
|
||||
m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec));
|
||||
el = GetNonThreadSafe ();
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
|
@ -78,66 +59,93 @@ namespace util
|
|||
return m_NonEmpty.wait_for (l, std::chrono::seconds (sec) + std::chrono::milliseconds (usec)) != std::cv_status::timeout;
|
||||
}
|
||||
|
||||
bool IsEmpty ()
|
||||
{
|
||||
bool IsEmpty ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return m_Queue.empty ();
|
||||
}
|
||||
|
||||
int GetSize () const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return m_Queue.size ();
|
||||
}
|
||||
|
||||
|
||||
void WakeUp () { m_NonEmpty.notify_all (); };
|
||||
|
||||
Element Get ()
|
||||
Element * Get ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return GetNonThreadSafe ();
|
||||
}
|
||||
}
|
||||
|
||||
Element Peek ()
|
||||
Element * Peek ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return GetNonThreadSafe (true);
|
||||
}
|
||||
|
||||
void GetWholeQueue (std::list<Element>& queue)
|
||||
{
|
||||
if (!queue.empty ())
|
||||
{
|
||||
std::list<Element> newQueue;
|
||||
queue.swap (newQueue);
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_Queue.swap (queue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Element GetNonThreadSafe (bool peek = false)
|
||||
|
||||
Element * GetNonThreadSafe (bool peek = false)
|
||||
{
|
||||
if (!m_Queue.empty ())
|
||||
{
|
||||
auto el = m_Queue.front ();
|
||||
Element * el = m_Queue.front ();
|
||||
if (!peek)
|
||||
m_Queue.pop_front ();
|
||||
m_Queue.pop ();
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::queue<Element *> m_Queue;
|
||||
std::mutex m_QueueMutex;
|
||||
std::condition_variable m_NonEmpty;
|
||||
};
|
||||
|
||||
template<class Msg>
|
||||
class MsgQueue: public Queue<Msg>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::function<void()> OnEmpty;
|
||||
|
||||
MsgQueue (): m_IsRunning (true), m_Thread (std::bind (&MsgQueue<Msg>::Run, this)) {};
|
||||
~MsgQueue () { Stop (); };
|
||||
void Stop()
|
||||
{
|
||||
if (m_IsRunning)
|
||||
{
|
||||
m_IsRunning = false;
|
||||
Queue<Msg>::WakeUp ();
|
||||
m_Thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void SetOnEmpty (OnEmpty const & e) { m_OnEmpty = e; };
|
||||
|
||||
private:
|
||||
|
||||
std::list<Element> m_Queue;
|
||||
mutable std::mutex m_QueueMutex;
|
||||
std::condition_variable m_NonEmpty;
|
||||
};
|
||||
}
|
||||
}
|
||||
void Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
while (Msg * msg = Queue<Msg>::Get ())
|
||||
{
|
||||
msg->Process ();
|
||||
delete msg;
|
||||
}
|
||||
if (m_OnEmpty != nullptr)
|
||||
m_OnEmpty ();
|
||||
if (m_IsRunning)
|
||||
Queue<Msg>::Wait ();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
volatile bool m_IsRunning;
|
||||
std::thread m_Thread;
|
||||
OnEmpty m_OnEmpty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
169
README.md
169
README.md
|
@ -1,124 +1,71 @@
|
|||
[](https://github.com/PurpleI2P/i2pd/releases/latest)
|
||||
[](https://snapcraft.io/i2pd)
|
||||
[](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE)
|
||||
[](https://repology.org/project/i2pd/versions)
|
||||
[](https://hub.docker.com/r/purplei2p/i2pd)
|
||||
[](https://crowdin.com/project/i2pd)
|
||||
|
||||
*note: i2pd for Android can be found in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository and with Qt GUI in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository*
|
||||
|
||||
i2pd
|
||||
====
|
||||
|
||||
[Русская версия](https://github.com/PurpleI2P/i2pd_docs_ru/blob/master/README.md)
|
||||
I2P router written in C++
|
||||
|
||||
i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client.
|
||||
Requirements for Linux/FreeBSD/OSX
|
||||
----------------------------------
|
||||
|
||||
I2P (Invisible Internet Protocol) is a universal anonymous network layer.
|
||||
All communications over I2P are anonymous and end-to-end encrypted, participants
|
||||
don't reveal their real IP addresses.
|
||||
GCC 4.6 or newer, Boost 1.46 or newer, crypto++. Clang can be used instead of
|
||||
GCC.
|
||||
|
||||
I2P client is a software used for building and using anonymous I2P
|
||||
networks. Such networks are commonly used for anonymous peer-to-peer
|
||||
applications (filesharing, cryptocurrencies) and anonymous client-server
|
||||
applications (websites, instant messengers, chat-servers).
|
||||
Requirements for Windows
|
||||
------------------------
|
||||
|
||||
I2P allows people from all around the world to communicate and share information
|
||||
without restrictions.
|
||||
VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer,
|
||||
crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd
|
||||
and its dependencies.
|
||||
|
||||
Features
|
||||
--------
|
||||
Build Statuses
|
||||
---------------
|
||||
|
||||
* Distributed anonymous networking framework
|
||||
* End-to-end encrypted communications
|
||||
* Small footprint, simple dependencies, fast performance
|
||||
* Rich set of APIs for developers of secure applications
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [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)
|
||||
* [Specifications](https://geti2p.net/spec)
|
||||
* [Twitter](https://twitter.com/hashtag/i2pd)
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
The easiest way to install i2pd is by using precompiled packages and binaries.
|
||||
You can fetch most of them on [release](https://github.com/PurpleI2P/i2pd/releases/latest) page.
|
||||
Please see [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/install/) for more info.
|
||||
|
||||
Building
|
||||
--------
|
||||
See [documentation](https://i2pd.readthedocs.io/en/latest/) for how to build
|
||||
i2pd from source on your OS.
|
||||
|
||||
note: i2pd with Qt GUI can be found in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository and for android in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository.
|
||||
- Linux x64 - [](https://jenkins.nordcloud.no/job/i2pd-linux/)
|
||||
- Linux ARM - To be added
|
||||
- Mac OS X - To be added
|
||||
- Microsoft VC13 - To be added
|
||||
|
||||
|
||||
Build instructions:
|
||||
|
||||
* [unix](https://i2pd.readthedocs.io/en/latest/devs/building/unix/)
|
||||
* [windows](https://i2pd.readthedocs.io/en/latest/devs/building/windows/)
|
||||
* [iOS](https://i2pd.readthedocs.io/en/latest/devs/building/ios/)
|
||||
* [android](https://i2pd.readthedocs.io/en/latest/devs/building/android/)
|
||||
|
||||
|
||||
**Supported systems:**
|
||||
|
||||
* GNU/Linux (Debian, Ubuntu, etc) - [](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml)
|
||||
* CentOS, Fedora, Mageia - [](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
|
||||
* Alpine, ArchLinux, openSUSE, Gentoo, etc.
|
||||
* Windows - [](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml)
|
||||
* Mac OS - [](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml)
|
||||
* Docker image - [](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml)
|
||||
* Snap - [](https://snapcraft.io/i2pd) [](https://snapcraft.io/i2pd)
|
||||
* FreeBSD - [](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml)
|
||||
* Android - [](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml)
|
||||
* iOS
|
||||
|
||||
Using i2pd
|
||||
----------
|
||||
|
||||
See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and
|
||||
[example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf).
|
||||
|
||||
Localization
|
||||
------------
|
||||
|
||||
You can help us with translation i2pd to your language using Crowdin platform!
|
||||
Translation project can be found [here](https://crowdin.com/project/i2pd).
|
||||
|
||||
New languages can be requested on project's [discussion page](https://crowdin.com/project/i2pd/discussions).
|
||||
|
||||
Current status: [](https://crowdin.com/project/i2pd)
|
||||
|
||||
Donations
|
||||
---------
|
||||
|
||||
**E-Mail**: ```i2porignal at yandex.com```
|
||||
|
||||
**BTC**: ```3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f```
|
||||
|
||||
**LTC**: ```LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59```
|
||||
|
||||
**ETH**: ```0x9e5bac70d20d1079ceaa111127f4fb3bccce379d```
|
||||
|
||||
**GST**: ```GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG```
|
||||
|
||||
**DASH**: ```Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF```
|
||||
|
||||
**ZEC**: ```t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ```
|
||||
|
||||
**ANC**: ```AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z```
|
||||
|
||||
**XMR**: ```497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH```
|
||||
|
||||
License
|
||||
Testing
|
||||
-------
|
||||
|
||||
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.
|
||||
First, build it.
|
||||
|
||||
* $ cd i2pd
|
||||
* $ make
|
||||
|
||||
Next, find out your public ip. (find it for example at http://www.whatismyip.com/)
|
||||
|
||||
Then, run it with:
|
||||
|
||||
$ ./i2p --host=YOUR_PUBLIC_IP
|
||||
|
||||
The client should now reseed by itself.
|
||||
|
||||
To visit an I2P page, you need to find the b32 address of your destination.
|
||||
After that, go to the webconsole and add it behind the url. (Remove http:// from the address)
|
||||
|
||||
This should resulting in for example:
|
||||
http://localhost:7070/4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa.b32.i2p
|
||||
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
* --host= - The external IP
|
||||
* --port= - The port to listen on
|
||||
* --httpport= - The http port to listen on
|
||||
* --log= - Enable or disable logging to file. 1 for yes, 0 for no.
|
||||
* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no.
|
||||
* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd).
|
||||
* --unreachable= - 1 if router is declared as unreachable and works through introducers.
|
||||
* --v6= - 1 if supports communication through ipv6, off by default
|
||||
* --httpproxyport= - The port to listen on (HTTP Proxy)
|
||||
* --socksproxyport= - The port to listen on (SOCKS Proxy)
|
||||
* --ircport= - The local port of IRC tunnel to listen on. 6668 by default
|
||||
* --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p
|
||||
* --irckeys= - optional keys file for local destination
|
||||
* --eepkeys= - File name containing destination keys. For example privKeys.dat
|
||||
* --eephost= - Address incoming trafic forward to. 127.0.0.1 by default
|
||||
* --eepport= - Port incoming trafic forward to. 80 by default
|
||||
* --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified
|
||||
|
||||
|
|
179
Reseed.cpp
Normal file
179
Reseed.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <cryptopp/gzip.h>
|
||||
#include "I2PEndian.h"
|
||||
#include "Reseed.h"
|
||||
#include "Log.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
static std::vector<std::string> httpReseedHostList = {
|
||||
"http://193.150.121.66/netDb/",
|
||||
"http://netdb.i2p2.no/",
|
||||
"http://reseed.i2p-projekt.de/",
|
||||
"http://cowpuncher.drollette.com/netdb/",
|
||||
"http://i2p.mooo.com/netDb/",
|
||||
"http://reseed.info/",
|
||||
"http://uk.reseed.i2p2.no/",
|
||||
"http://us.reseed.i2p2.no/",
|
||||
"http://jp.reseed.i2p2.no/",
|
||||
"http://i2p-netdb.innovatio.no/",
|
||||
"http://ieb9oopo.mooo.com"
|
||||
};
|
||||
|
||||
//TODO: Remember to add custom port support. Not all serves on 443
|
||||
static std::vector<std::string> httpsReseedHostList = {
|
||||
"https://193.150.121.66/netDb/",
|
||||
"https://netdb.i2p2.no/",
|
||||
"https://reseed.i2p-projekt.de/",
|
||||
"https://cowpuncher.drollette.com/netdb/",
|
||||
"https://i2p.mooo.com/netDb/",
|
||||
"https://reseed.info/",
|
||||
"https://i2p-netdb.innovatio.no/",
|
||||
"https://ieb9oopo.mooo.com/",
|
||||
"https://ssl.webpack.de/ivae2he9.sg4.e-plaza.de/" // Only HTTPS and SU3 (v2) support
|
||||
};
|
||||
|
||||
//TODO: Implement v2 reseeding. Lightweight zip library is needed.
|
||||
//TODO: Implement SU3, utils.
|
||||
Reseeder::Reseeder()
|
||||
{
|
||||
}
|
||||
|
||||
Reseeder::~Reseeder()
|
||||
{
|
||||
}
|
||||
|
||||
bool Reseeder::reseedNow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Seems like the best place to try to intercept with SSL
|
||||
/*ssl_server = true;
|
||||
try {
|
||||
// SSL
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LogPrint("Exception in SSL: ", e.what());
|
||||
}*/
|
||||
std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())];
|
||||
LogPrint("Reseeding from ", reseedHost);
|
||||
std::string content = i2p::util::http::httpRequest(reseedHost);
|
||||
if (content == "")
|
||||
{
|
||||
LogPrint("Reseed failed");
|
||||
return false;
|
||||
}
|
||||
boost::regex e("<\\s*A\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"", boost::regex::normal | boost::regbase::icase);
|
||||
boost::sregex_token_iterator i(content.begin(), content.end(), e, 1);
|
||||
boost::sregex_token_iterator j;
|
||||
//TODO: Ugly code, try to clean up.
|
||||
//TODO: Try to reduce N number of variables
|
||||
std::string name;
|
||||
std::string routerInfo;
|
||||
std::string tmpUrl;
|
||||
std::string filename;
|
||||
std::string ignoreFileSuffix = ".su3";
|
||||
boost::filesystem::path root = i2p::util::filesystem::GetDataDir();
|
||||
while (i != j)
|
||||
{
|
||||
name = *i++;
|
||||
if (name.find(ignoreFileSuffix)!=std::string::npos)
|
||||
continue;
|
||||
LogPrint("Downloading ", name);
|
||||
tmpUrl = reseedHost;
|
||||
tmpUrl.append(name);
|
||||
routerInfo = i2p::util::http::httpRequest(tmpUrl);
|
||||
if (routerInfo.size()==0)
|
||||
continue;
|
||||
filename = root.string();
|
||||
#ifndef _WIN32
|
||||
filename += "/netDb/r";
|
||||
#else
|
||||
filename += "\\netDb\\r";
|
||||
#endif
|
||||
filename += name.at(11); // first char in id
|
||||
#ifndef _WIN32
|
||||
filename.append("/");
|
||||
#else
|
||||
filename.append("\\");
|
||||
#endif
|
||||
filename.append(name.c_str());
|
||||
std::ofstream outfile (filename, std::ios::binary);
|
||||
outfile << routerInfo;
|
||||
outfile.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
//TODO: error reporting
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProcessSU3File (const char * filename)
|
||||
{
|
||||
static uint32_t headerSignature = htole32 (0x04044B50);
|
||||
|
||||
std::ifstream s(filename, std::ifstream::binary);
|
||||
if (s.is_open ())
|
||||
{
|
||||
while (!s.eof ())
|
||||
{
|
||||
uint32_t signature;
|
||||
s.read ((char *)&signature, 4);
|
||||
if (signature == headerSignature)
|
||||
{
|
||||
// next local file
|
||||
s.seekg (14, std::ios::cur); // skip field we don't care about
|
||||
uint32_t compressedSize, uncompressedSize;
|
||||
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 = le32toh (fileNameLength);
|
||||
s.read ((char *)&extraFieldLength, 2);
|
||||
extraFieldLength = le32toh (extraFieldLength);
|
||||
char localFileName[255];
|
||||
s.read (localFileName, fileNameLength);
|
||||
localFileName[fileNameLength] = 0;
|
||||
s.seekg (extraFieldLength, std::ios::cur);
|
||||
|
||||
uint8_t * compressed = new uint8_t[compressedSize];
|
||||
s.read ((char *)compressed, compressedSize);
|
||||
CryptoPP::Gunzip decompressor;
|
||||
decompressor.Put (compressed, compressedSize);
|
||||
delete[] compressed;
|
||||
if (decompressor.MaxRetrievable () <= uncompressedSize)
|
||||
{
|
||||
uint8_t * uncompressed = new uint8_t[uncompressedSize];
|
||||
decompressor.Get (uncompressed, decompressor.MaxRetrievable ());
|
||||
// TODO: save file
|
||||
delete[] uncompressed;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Actual uncompressed size ", decompressor.MaxRetrievable (), " exceed ", uncompressedSize, " from header");
|
||||
}
|
||||
else
|
||||
break; // no more files
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Can't open file ", filename);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
24
Reseed.h
Normal file
24
Reseed.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef RESEED_H
|
||||
#define RESEED_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
class Reseeder
|
||||
{
|
||||
public:
|
||||
Reseeder();
|
||||
~Reseeder();
|
||||
bool reseedNow();
|
||||
};
|
||||
|
||||
void ProcessSU3File (const char * filename);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
211
RouterContext.cpp
Normal file
211
RouterContext.cpp
Normal file
|
@ -0,0 +1,211 @@
|
|||
#include <fstream>
|
||||
#include <cryptopp/dh.h>
|
||||
#include <cryptopp/dsa.h>
|
||||
#include "CryptoConst.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Timestamp.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
RouterContext context;
|
||||
|
||||
RouterContext::RouterContext ():
|
||||
m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true)
|
||||
{
|
||||
}
|
||||
|
||||
void RouterContext::Init ()
|
||||
{
|
||||
if (!Load ())
|
||||
CreateNewRouter ();
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::CreateNewRouter ()
|
||||
{
|
||||
m_Keys = i2p::data::CreateRandomKeys ();
|
||||
SaveKeys ();
|
||||
NewRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::NewRouterInfo ()
|
||||
{
|
||||
i2p::data::RouterInfo routerInfo;
|
||||
routerInfo.SetRouterIdentity (GetIdentity ());
|
||||
int port = i2p::util::config::GetArg("-port", 0);
|
||||
if (!port)
|
||||
port = m_Rnd.GenerateWord32 (9111, 30777); // I2P network ports range
|
||||
routerInfo.AddSSUAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port, routerInfo.GetIdentHash ());
|
||||
routerInfo.AddNTCPAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port);
|
||||
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
|
||||
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC
|
||||
routerInfo.SetProperty ("coreVersion", I2P_VERSION);
|
||||
routerInfo.SetProperty ("netId", "2");
|
||||
routerInfo.SetProperty ("router.version", I2P_VERSION);
|
||||
routerInfo.SetProperty ("stat_uptime", "90m");
|
||||
routerInfo.CreateBuffer (m_Keys);
|
||||
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||
}
|
||||
|
||||
void RouterContext::UpdateRouterInfo ()
|
||||
{
|
||||
m_RouterInfo.CreateBuffer (m_Keys);
|
||||
m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO));
|
||||
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
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& routerInfo, uint32_t tag)
|
||||
{
|
||||
bool ret = false;
|
||||
auto address = routerInfo.GetSSUAddress ();
|
||||
if (address)
|
||||
{
|
||||
ret = m_RouterInfo.AddIntroducer (address, tag);
|
||||
if (ret)
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
|
||||
{
|
||||
if (m_RouterInfo.RemoveIntroducer (e))
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::SetUnreachable ()
|
||||
{
|
||||
m_IsUnreachable = true;
|
||||
// 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::SetSupportsV6 (bool supportsV6)
|
||||
{
|
||||
if (supportsV6)
|
||||
m_RouterInfo.EnableV6 ();
|
||||
else
|
||||
m_RouterInfo.DisableV6 ();
|
||||
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 ("Our v6 MTU=", mtu);
|
||||
if (mtu > 1472) mtu = 1472;
|
||||
}
|
||||
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
|
||||
updated = true;
|
||||
}
|
||||
if (updated)
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
bool RouterContext::Load ()
|
||||
{
|
||||
std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ofstream::in);
|
||||
if (!fk.is_open ()) return false;
|
||||
|
||||
i2p::data::Keys keys;
|
||||
fk.read ((char *)&keys, sizeof (keys));
|
||||
m_Keys = keys;
|
||||
|
||||
i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO
|
||||
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
|
||||
m_RouterInfo.SetProperty ("router.version", I2P_VERSION);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RouterContext::SaveKeys ()
|
||||
{
|
||||
std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out);
|
||||
i2p::data::Keys keys;
|
||||
memcpy (keys.privateKey, m_Keys.GetPrivateKey (), sizeof (keys.privateKey));
|
||||
memcpy (keys.signingPrivateKey, m_Keys.GetSigningPrivateKey (), sizeof (keys.signingPrivateKey));
|
||||
auto& ident = GetIdentity ().GetStandardIdentity ();
|
||||
memcpy (keys.publicKey, ident.publicKey, sizeof (keys.publicKey));
|
||||
memcpy (keys.signingKey, ident.signingKey, sizeof (keys.signingKey));
|
||||
fk.write ((char *)&keys, sizeof (keys));
|
||||
}
|
||||
|
||||
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from)
|
||||
{
|
||||
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||
}
|
||||
}
|
77
RouterContext.h
Normal file
77
RouterContext.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
#ifndef ROUTER_CONTEXT_H__
|
||||
#define ROUTER_CONTEXT_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cryptopp/dsa.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#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
|
||||
|
||||
class RouterContext: public i2p::garlic::GarlicDestination
|
||||
{
|
||||
public:
|
||||
|
||||
RouterContext ();
|
||||
void Init ();
|
||||
|
||||
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 *) {});
|
||||
}
|
||||
CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; };
|
||||
|
||||
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& routerInfo, uint32_t tag);
|
||||
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||
bool IsUnreachable () const { return m_IsUnreachable; };
|
||||
void SetUnreachable ();
|
||||
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
|
||||
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
|
||||
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
|
||||
void SetSupportsV6 (bool supportsV6);
|
||||
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
|
||||
|
||||
// implements LocalDestination
|
||||
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
|
||||
const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ().GetStandardIdentity ().publicKey; };
|
||||
void SetLeaseSetUpdated () {};
|
||||
|
||||
// implements GarlicDestination
|
||||
const i2p::data::LeaseSet * GetLeaseSet () { return nullptr; };
|
||||
void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from);
|
||||
|
||||
private:
|
||||
|
||||
void CreateNewRouter ();
|
||||
void NewRouterInfo ();
|
||||
void UpdateRouterInfo ();
|
||||
bool Load ();
|
||||
void SaveKeys ();
|
||||
|
||||
private:
|
||||
|
||||
i2p::data::RouterInfo m_RouterInfo;
|
||||
i2p::data::PrivateKeys m_Keys;
|
||||
CryptoPP::AutoSeededRandomPool m_Rnd;
|
||||
uint64_t m_LastUpdateTime;
|
||||
bool m_IsUnreachable, m_AcceptsTunnels;
|
||||
};
|
||||
|
||||
extern RouterContext context;
|
||||
}
|
||||
|
||||
#endif
|
642
RouterInfo.cpp
Normal file
642
RouterInfo.cpp
Normal file
|
@ -0,0 +1,642 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <fstream>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <cryptopp/sha.h>
|
||||
#include <cryptopp/dsa.h>
|
||||
#include "CryptoConst.h"
|
||||
#include "base64.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Log.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.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)
|
||||
{
|
||||
if (!m_Buffer)
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
m_IsUpdated = true;
|
||||
m_IsUnreachable = false;
|
||||
m_SupportedTransports = 0;
|
||||
m_Caps = 0;
|
||||
m_Addresses.clear ();
|
||||
m_Properties.clear ();
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer (true);
|
||||
// don't delete buffer until save to file
|
||||
}
|
||||
|
||||
void RouterInfo::SetRouterIdentity (const IdentityEx& identity)
|
||||
{
|
||||
m_RouterIdentity = identity;
|
||||
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
bool RouterInfo::LoadFile ()
|
||||
{
|
||||
std::ifstream s(m_FullPath.c_str (), std::ifstream::binary);
|
||||
if (s.is_open ())
|
||||
{
|
||||
s.seekg (0,std::ios::end);
|
||||
m_BufferLen = s.tellg ();
|
||||
if (m_BufferLen < 40)
|
||||
{
|
||||
LogPrint(eLogError, "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, "Can't open file ", m_FullPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RouterInfo::ReadFromFile ()
|
||||
{
|
||||
if (LoadFile ())
|
||||
ReadFromBuffer (false);
|
||||
}
|
||||
|
||||
void RouterInfo::ReadFromBuffer (bool verifySignature)
|
||||
{
|
||||
size_t identityLen = m_RouterIdentity.FromBuffer (m_Buffer, m_BufferLen);
|
||||
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
|
||||
ReadFromStream (str);
|
||||
if (verifySignature)
|
||||
{
|
||||
// verify signature
|
||||
int l = m_BufferLen - m_RouterIdentity.GetSignatureLen ();
|
||||
if (!m_RouterIdentity.Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l))
|
||||
LogPrint (eLogError, "signature verification failed");
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
bool introducers = false;
|
||||
for (int i = 0; i < numAddresses; i++)
|
||||
{
|
||||
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));
|
||||
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)
|
||||
{
|
||||
// TODO: we should try to resolve address here
|
||||
LogPrint (eLogWarning, "Unexpected address ", value);
|
||||
isValidAddress = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add supported protocol
|
||||
if (address.host.is_v4 ())
|
||||
m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4;
|
||||
else
|
||||
m_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 (isValidAddress)
|
||||
m_Addresses.push_back(address);
|
||||
}
|
||||
// read peers
|
||||
uint8_t numPeers;
|
||||
s.read ((char *)&numPeers, sizeof (numPeers));
|
||||
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
|
||||
// read properties
|
||||
uint16_t size, r = 0;
|
||||
s.read ((char *)&size, sizeof (size));
|
||||
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);
|
||||
}
|
||||
|
||||
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_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;
|
||||
caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH1 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth
|
||||
if (m_Caps & eFloodfill) caps += CAPS_FLAG_FLOODFILL; // floodfill
|
||||
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.c_str ());
|
||||
}
|
||||
|
||||
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& address : m_Addresses)
|
||||
{
|
||||
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 ());
|
||||
}
|
||||
|
||||
const uint8_t * RouterInfo::LoadBuffer ()
|
||||
{
|
||||
if (!m_Buffer)
|
||||
{
|
||||
if (LoadFile ())
|
||||
LogPrint ("Buffer for ", GetIdentHashAbbreviation (), " 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 ();
|
||||
}
|
||||
|
||||
void RouterInfo::SaveToFile (const std::string& fullPath)
|
||||
{
|
||||
m_FullPath = fullPath;
|
||||
if (m_Buffer)
|
||||
{
|
||||
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
|
||||
f.write ((char *)m_Buffer, m_BufferLen);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Can't save to file");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Address addr;
|
||||
addr.host = boost::asio::ip::address::from_string (host);
|
||||
addr.port = port;
|
||||
addr.transportStyle = eTransportNTCP;
|
||||
addr.cost = 2;
|
||||
addr.date = 0;
|
||||
addr.mtu = 0;
|
||||
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)
|
||||
{
|
||||
Address addr;
|
||||
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);
|
||||
m_Addresses.push_back(addr);
|
||||
m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eSSUV4;
|
||||
m_Caps |= eSSUTesting;
|
||||
m_Caps |= eSSUIntroducer;
|
||||
}
|
||||
|
||||
bool RouterInfo::AddIntroducer (const Address * address, uint32_t tag)
|
||||
{
|
||||
for (auto& addr : m_Addresses)
|
||||
{
|
||||
if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ())
|
||||
{
|
||||
for (auto intro: addr.introducers)
|
||||
if (intro.iTag == tag) return false; // already presented
|
||||
Introducer x;
|
||||
x.iHost = address->host;
|
||||
x.iPort = address->port;
|
||||
x.iTag = tag;
|
||||
memcpy (x.iKey, address->key, 32); // TODO: replace to Tag<32>
|
||||
addr.introducers.push_back (x);
|
||||
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 char * key, const char * value)
|
||||
{
|
||||
m_Properties[key] = value;
|
||||
}
|
||||
|
||||
const char * RouterInfo::GetProperty (const char * key) const
|
||||
{
|
||||
auto it = m_Properties.find (key);
|
||||
if (it != m_Properties.end ())
|
||||
return it->second.c_str ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool RouterInfo::IsFloodfill () const
|
||||
{
|
||||
return m_Caps & Caps::eFloodfill;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void RouterInfo::EnableV6 ()
|
||||
{
|
||||
if (!IsV6 ())
|
||||
m_SupportedTransports |= eNTCPV6 | eSSUV6;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RouterInfo::UsesIntroducer () const
|
||||
{
|
||||
return m_Caps & Caps::eUnreachable; // non-reachable
|
||||
}
|
||||
|
||||
const RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) const
|
||||
{
|
||||
return GetAddress (eTransportNTCP, v4only);
|
||||
}
|
||||
|
||||
const RouterInfo::Address * RouterInfo::GetSSUAddress (bool v4only) const
|
||||
{
|
||||
return GetAddress (eTransportSSU, v4only);
|
||||
}
|
||||
|
||||
const RouterInfo::Address * RouterInfo::GetSSUV6Address () const
|
||||
{
|
||||
return GetAddress (eTransportSSU, false, true);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
174
RouterInfo.h
Normal file
174
RouterInfo.h
Normal file
|
@ -0,0 +1,174 @@
|
|||
#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"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
const char CAPS_FLAG_FLOODFILL = 'f';
|
||||
const char CAPS_FLAG_HIDDEN = 'H';
|
||||
const char CAPS_FLAG_REACHABLE = 'R';
|
||||
const char CAPS_FLAG_UNREACHABLE = 'U';
|
||||
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K';
|
||||
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L';
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M';
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N';
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O';
|
||||
|
||||
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,
|
||||
eReachable = 0x04,
|
||||
eSSUTesting = 0x08,
|
||||
eSSUIntroducer = 0x10,
|
||||
eHidden = 0x20,
|
||||
eUnreachable = 0x40
|
||||
};
|
||||
|
||||
enum TransportStyle
|
||||
{
|
||||
eTransportUnknown = 0,
|
||||
eTransportNTCP,
|
||||
eTransportSSU
|
||||
};
|
||||
|
||||
struct Introducer
|
||||
{
|
||||
boost::asio::ip::address iHost;
|
||||
int iPort;
|
||||
Tag<32> iKey;
|
||||
uint32_t iTag;
|
||||
};
|
||||
|
||||
struct Address
|
||||
{
|
||||
TransportStyle transportStyle;
|
||||
boost::asio::ip::address host;
|
||||
int port, mtu;
|
||||
uint64_t date;
|
||||
uint8_t cost;
|
||||
// SSU only
|
||||
Tag<32> 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 ());
|
||||
}
|
||||
};
|
||||
|
||||
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 ();
|
||||
|
||||
const IdentityEx& GetRouterIdentity () const { return m_RouterIdentity; };
|
||||
void SetRouterIdentity (const IdentityEx& identity);
|
||||
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
|
||||
std::string GetIdentHashAbbreviation () const { return GetIdentHash ().ToBase64 ().substr (0, 4); };
|
||||
uint64_t GetTimestamp () const { return m_Timestamp; };
|
||||
std::vector<Address>& GetAddresses () { return m_Addresses; };
|
||||
const Address * GetNTCPAddress (bool v4only = true) const;
|
||||
const Address * GetSSUAddress (bool v4only = true) const;
|
||||
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 Address * address, uint32_t tag);
|
||||
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||
void SetProperty (const char * key, const char * value);
|
||||
const char * GetProperty (const char * key) const;
|
||||
bool IsFloodfill () const;
|
||||
bool IsNTCP (bool v4only = true) const;
|
||||
bool IsSSU (bool v4only = true) const;
|
||||
bool IsV6 () const;
|
||||
void EnableV6 ();
|
||||
void DisableV6 ();
|
||||
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; };
|
||||
|
||||
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; };
|
||||
void SaveToFile (const std::string& fullPath);
|
||||
|
||||
void Update (const uint8_t * buf, int len);
|
||||
void DeleteBuffer () { delete m_Buffer; m_Buffer = nullptr; };
|
||||
|
||||
// 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);
|
||||
const Address * GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
|
||||
void UpdateCapsProperty ();
|
||||
|
||||
private:
|
||||
|
||||
std::string m_FullPath;
|
||||
IdentityEx m_RouterIdentity;
|
||||
uint8_t * m_Buffer;
|
||||
int m_BufferLen;
|
||||
uint64_t m_Timestamp;
|
||||
std::vector<Address> m_Addresses;
|
||||
std::map<std::string, std::string> m_Properties;
|
||||
bool m_IsUpdated, m_IsUnreachable;
|
||||
uint8_t m_SupportedTransports, m_Caps;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
757
SAM.cpp
Normal file
757
SAM.cpp
Normal file
|
@ -0,0 +1,757 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#include <boost/bind.hpp>
|
||||
#include "base64.h"
|
||||
#include "Identity.h"
|
||||
#include "Log.h"
|
||||
#include "NetDb.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_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_Stream (nullptr),
|
||||
m_Session (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SAMSocket::~SAMSocket ()
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
i2p::stream::DeleteStream (m_Stream);
|
||||
m_Stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::Terminate ()
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
i2p::stream::DeleteStream (m_Stream);
|
||||
m_Stream = nullptr;
|
||||
}
|
||||
switch (m_SocketType)
|
||||
{
|
||||
case eSAMSocketTypeSession:
|
||||
m_Owner.CloseSession (m_ID);
|
||||
break;
|
||||
case eSAMSocketTypeStream:
|
||||
{
|
||||
if (m_Session)
|
||||
m_Session->sockets.remove (this);
|
||||
break;
|
||||
}
|
||||
case eSAMSocketTypeAcceptor:
|
||||
{
|
||||
if (m_Session)
|
||||
{
|
||||
m_Session->sockets.remove (this);
|
||||
m_Session->localDestination->StopAcceptingStreams ();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
m_Socket.close ();
|
||||
// delete this;
|
||||
}
|
||||
|
||||
void SAMSocket::ReceiveHandshake ()
|
||||
{
|
||||
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
|
||||
boost::bind(&SAMSocket::HandleHandshakeReceived, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM handshake read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Buffer[bytes_transferred] = 0;
|
||||
LogPrint ("SAM handshake ", m_Buffer);
|
||||
if (!memcmp (m_Buffer, SAM_HANDSHAKE, strlen (SAM_HANDSHAKE)))
|
||||
{
|
||||
// TODO: check version
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (SAM_HANDSHAKE_REPLY, strlen (SAM_HANDSHAKE_REPLY)), boost::asio::transfer_all (),
|
||||
boost::bind(&SAMSocket::HandleHandshakeReplySent, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM handshake mismatch");
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("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),
|
||||
boost::bind(&SAMSocket::HandleMessage, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close)
|
||||
{
|
||||
if (!m_IsSilent || m_SocketType == eSAMSocketTypeAcceptor)
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (),
|
||||
boost::bind(&SAMSocket::HandleMessageReplySent, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, 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 ("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 ("SAM read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Buffer[bytes_transferred] = 0;
|
||||
char * eol = strchr (m_Buffer, '\n');
|
||||
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
|
||||
{
|
||||
LogPrint ("SAM unexpected message ", m_Buffer);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM malformed message ", m_Buffer);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM malformed message ", m_Buffer);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessSessionCreate (char * buf, size_t len)
|
||||
{
|
||||
LogPrint ("SAM session create: ", buf);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, len, 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;
|
||||
}
|
||||
m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination);
|
||||
if (m_Session)
|
||||
{
|
||||
m_SocketType = eSAMSocketTypeSession;
|
||||
if (m_Session->localDestination->IsReady ())
|
||||
{
|
||||
if (style == SAM_VALUE_DATAGRAM)
|
||||
{
|
||||
auto dest = m_Session->localDestination->CreateDatagramDestination ();
|
||||
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, this,
|
||||
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
}
|
||||
SendSessionCreateReplyOk ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
|
||||
m_Timer.async_wait (boost::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
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 (boost::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ("SAM stream connect: ", buf);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, len, 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)
|
||||
{
|
||||
uint8_t ident[1024];
|
||||
size_t l = i2p::data::Base64ToByteStream (destination.c_str (), destination.length (), ident, 1024);
|
||||
i2p::data::IdentityEx dest;
|
||||
dest.FromBuffer (ident, l);
|
||||
auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ());
|
||||
if (leaseSet)
|
||||
Connect (*leaseSet);
|
||||
else
|
||||
{
|
||||
i2p::data::netdb.RequestDestination (dest.GetIdentHash (), true, m_Session->localDestination->GetTunnelPool ());
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_CONNECT_TIMEOUT));
|
||||
m_Timer.async_wait (boost::bind (&SAMSocket::HandleStreamDestinationRequestTimer,
|
||||
this, boost::asio::placeholders::error, dest.GetIdentHash ()));
|
||||
}
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
|
||||
}
|
||||
|
||||
void SAMSocket::Connect (const i2p::data::LeaseSet& remote)
|
||||
{
|
||||
m_SocketType = eSAMSocketTypeStream;
|
||||
m_Session->sockets.push_back (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::HandleStreamDestinationRequestTimer (const boost::system::error_code& ecode, i2p::data::IdentHash ident)
|
||||
{
|
||||
if (!ecode) // timeout expired
|
||||
{
|
||||
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
|
||||
if (leaseSet)
|
||||
Connect (*leaseSet);
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM destination to connect not found");
|
||||
SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleNamingLookupDestinationRequestTimer (const boost::system::error_code& ecode, i2p::data::IdentHash ident)
|
||||
{
|
||||
if (!ecode) // timeout expired
|
||||
{
|
||||
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
|
||||
if (leaseSet)
|
||||
SendNamingLookupReply (leaseSet);
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM name destination not found");
|
||||
#ifdef _MSC_VER
|
||||
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_KEY_NOT_FOUND, (ident.ToBase32 () + ".b32.i2p").c_str ());
|
||||
#else
|
||||
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_KEY_NOT_FOUND, (ident.ToBase32 () + ".b32.i2p").c_str ());
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, len, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessStreamAccept (char * buf, size_t len)
|
||||
{
|
||||
LogPrint ("SAM stream accept: ", buf);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, len, 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->sockets.push_back (this);
|
||||
m_Session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PAccept, 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);
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessDestGenerate ()
|
||||
{
|
||||
LogPrint ("SAM dest generate");
|
||||
auto localDestination = i2p::client::context.CreateNewLocalDestination ();
|
||||
if (localDestination)
|
||||
{
|
||||
uint8_t buf[1024];
|
||||
char priv[1024], pub[1024];
|
||||
size_t l = localDestination->GetPrivateKeys ().ToBuffer (buf, 1024);
|
||||
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
|
||||
priv[l1] = 0;
|
||||
|
||||
l = localDestination->GetIdentity ().ToBuffer (buf, 1024);
|
||||
l1 = i2p::data::ByteStreamToBase64 (buf, l, pub, 1024);
|
||||
pub[l1] = 0;
|
||||
#ifdef _MSC_VER
|
||||
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, pub, priv);
|
||||
#else
|
||||
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, pub, priv);
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, len, true);
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_DEST_REPLY_I2P_ERROR, strlen(SAM_DEST_REPLY_I2P_ERROR), true);
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessNamingLookup (char * buf, size_t len)
|
||||
{
|
||||
LogPrint ("SAM naming lookup: ", buf);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, len, params);
|
||||
std::string& name = params[SAM_PARAM_NAME];
|
||||
i2p::data::IdentHash ident;
|
||||
if (name == "ME")
|
||||
SendNamingLookupReply (nullptr);
|
||||
else if (m_Session && context.GetAddressBook ().GetIdentHash (name, ident))
|
||||
{
|
||||
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
|
||||
if (leaseSet)
|
||||
SendNamingLookupReply (leaseSet);
|
||||
else
|
||||
{
|
||||
i2p::data::netdb.RequestDestination (ident, true, m_Session->localDestination->GetTunnelPool ());
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_NAMING_LOOKUP_TIMEOUT));
|
||||
m_Timer.async_wait (boost::bind (&SAMSocket::HandleNamingLookupDestinationRequestTimer,
|
||||
this, boost::asio::placeholders::error, ident));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#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::SendNamingLookupReply (const i2p::data::LeaseSet * leaseSet)
|
||||
{
|
||||
uint8_t buf[1024];
|
||||
char pub[1024];
|
||||
const i2p::data::IdentityEx& identity = leaseSet ? leaseSet->GetIdentity () : m_Session->localDestination->GetIdentity ();
|
||||
size_t l = identity.ToBuffer (buf, 1024);
|
||||
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, pub, 1024);
|
||||
pub[l1] = 0;
|
||||
#ifdef _MSC_VER
|
||||
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, pub);
|
||||
#else
|
||||
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, pub);
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, l2, false);
|
||||
}
|
||||
|
||||
void SAMSocket::ExtractParams (char * buf, size_t len, 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 ()
|
||||
{
|
||||
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
|
||||
boost::bind((m_SocketType == eSAMSocketTypeSession) ? &SAMSocket::HandleMessage : &SAMSocket::HandleReceived,
|
||||
this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Stream)
|
||||
m_Stream->Send ((uint8_t *)m_Buffer, bytes_transferred);
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::I2PReceive ()
|
||||
{
|
||||
if (m_Stream)
|
||||
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE),
|
||||
boost::bind (&SAMSocket::HandleI2PReceive, this,
|
||||
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred),
|
||||
SAM_SOCKET_CONNECTION_MAX_IDLE);
|
||||
}
|
||||
|
||||
void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("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),
|
||||
boost::bind (&SAMSocket::HandleWriteI2PData, this, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM socket write error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
I2PReceive ();
|
||||
}
|
||||
|
||||
void SAMSocket::HandleI2PAccept (i2p::stream::Stream * stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
LogPrint ("SAM incoming I2P connection for session ", m_ID);
|
||||
m_Stream = stream;
|
||||
auto session = m_Owner.FindSession (m_ID);
|
||||
if (session)
|
||||
session->localDestination->StopAcceptingStreams ();
|
||||
if (!m_IsSilent)
|
||||
{
|
||||
// send remote peer address
|
||||
uint8_t ident[1024];
|
||||
size_t l = stream->GetRemoteIdentity ().ToBuffer (ident, 1024);
|
||||
size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, m_Buffer, SAM_SOCKET_BUFFER_SIZE);
|
||||
m_Buffer[l1] = '\n';
|
||||
SendMessageReply (m_Buffer, l1 + 1, false);
|
||||
}
|
||||
I2PReceive ();
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& ident, const uint8_t * buf, size_t len)
|
||||
{
|
||||
uint8_t identBuf[1024];
|
||||
size_t l = ident.ToBuffer (identBuf, 1024);
|
||||
size_t l1 = i2p::data::ByteStreamToBase64 (identBuf, l, m_Buffer, SAM_SOCKET_BUFFER_SIZE);
|
||||
m_Buffer[l1] = 0;
|
||||
#ifdef _MSC_VER
|
||||
size_t l2 = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, m_Buffer, len);
|
||||
#else
|
||||
size_t l2 = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, m_Buffer, len);
|
||||
#endif
|
||||
if (len < SAM_SOCKET_BUFFER_SIZE - l2)
|
||||
{
|
||||
memcpy (m_StreamBuffer + l2, buf, len);
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l2),
|
||||
boost::bind (&SAMSocket::HandleWriteI2PData, this, boost::asio::placeholders::error));
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Datagram size ", len," exceeds buffer");
|
||||
}
|
||||
|
||||
SAMBridge::SAMBridge (int port):
|
||||
m_IsRunning (false), m_Thread (nullptr),
|
||||
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
|
||||
m_DatagramEndpoint (boost::asio::ip::udp::v4 (), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint),
|
||||
m_NewSocket (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SAMBridge::~SAMBridge ()
|
||||
{
|
||||
Stop ();
|
||||
delete m_NewSocket;
|
||||
}
|
||||
|
||||
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_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 ("SAM: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMBridge::Accept ()
|
||||
{
|
||||
m_NewSocket = new SAMSocket (*this);
|
||||
m_Acceptor.async_accept (m_NewSocket->GetSocket (), boost::bind (&SAMBridge::HandleAccept, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void SAMBridge::HandleAccept(const boost::system::error_code& ecode)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
LogPrint ("New SAM connection from ", m_NewSocket->GetSocket ().remote_endpoint ());
|
||||
m_NewSocket->ReceiveHandshake ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM accept error: ", ecode.message ());
|
||||
delete m_NewSocket;
|
||||
m_NewSocket = nullptr;
|
||||
}
|
||||
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Accept ();
|
||||
}
|
||||
|
||||
SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination)
|
||||
{
|
||||
ClientDestination * localDestination = nullptr;
|
||||
if (destination != "")
|
||||
{
|
||||
uint8_t * buf = new uint8_t[destination.length ()];
|
||||
size_t l = i2p::data::Base64ToByteStream (destination.c_str (), destination.length (), buf, destination.length ());
|
||||
i2p::data::PrivateKeys keys;
|
||||
keys.FromBuffer (buf, l);
|
||||
delete[] buf;
|
||||
localDestination = i2p::client::context.CreateNewLocalDestination (keys);
|
||||
}
|
||||
else // transient
|
||||
localDestination = i2p::client::context.CreateNewLocalDestination ();
|
||||
if (localDestination)
|
||||
{
|
||||
SAMSession session;
|
||||
session.localDestination = localDestination;
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto ret = m_Sessions.insert (std::pair<std::string, SAMSession>(id, session));
|
||||
if (!ret.second)
|
||||
LogPrint ("Session ", id, " already exists");
|
||||
return &(ret.first->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SAMBridge::CloseSession (const std::string& id)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto it = m_Sessions.find (id);
|
||||
if (it != m_Sessions.end ())
|
||||
{
|
||||
for (auto it1 : it->second.sockets)
|
||||
delete it1;
|
||||
it->second.sockets.clear ();
|
||||
it->second.localDestination->Stop ();
|
||||
m_Sessions.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
SAMSession * SAMBridge::FindSession (const std::string& id)
|
||||
{
|
||||
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,
|
||||
boost::bind (&SAMBridge::HandleReceivedDatagram, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
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 ("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)
|
||||
{
|
||||
uint8_t ident[1024];
|
||||
size_t l = i2p::data::Base64ToByteStream (destination, strlen(destination), ident, 1024);
|
||||
i2p::data::IdentityEx dest;
|
||||
dest.FromBuffer (ident, l);
|
||||
auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ());
|
||||
if (leaseSet)
|
||||
session->localDestination->GetDatagramDestination ()->
|
||||
SendDatagramTo ((uint8_t *)eol, payloadLen, *leaseSet);
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM datagram destination not found");
|
||||
i2p::data::netdb.RequestDestination (dest.GetIdentHash (), true,
|
||||
session->localDestination->GetTunnelPool ());
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint ("Session ", sessionID, " not found");
|
||||
}
|
||||
else
|
||||
LogPrint ("Missing destination key");
|
||||
}
|
||||
else
|
||||
LogPrint ("Missing sessionID");
|
||||
ReceiveDatagram ();
|
||||
}
|
||||
else
|
||||
LogPrint ("SAM datagram receive error: ", ecode.message ());
|
||||
}
|
||||
}
|
||||
}
|
170
SAM.h
Normal file
170
SAM.h
Normal file
|
@ -0,0 +1,170 @@
|
|||
#ifndef SAM_H__
|
||||
#define SAM_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#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 = 4096;
|
||||
const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds
|
||||
const int SAM_CONNECT_TIMEOUT = 5; // in seconds
|
||||
const int SAM_NAMING_LOOKUP_TIMEOUT = 5; // 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=3.0\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_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_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=%i\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_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_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
|
||||
};
|
||||
|
||||
class SAMBridge;
|
||||
class SAMSession;
|
||||
class SAMSocket
|
||||
{
|
||||
public:
|
||||
|
||||
SAMSocket (SAMBridge& owner);
|
||||
~SAMSocket ();
|
||||
|
||||
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||
void ReceiveHandshake ();
|
||||
|
||||
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 (i2p::stream::Stream * stream);
|
||||
void HandleWriteI2PData (const boost::system::error_code& ecode);
|
||||
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& ident, 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);
|
||||
void ExtractParams (char * buf, size_t len, std::map<std::string, std::string>& params);
|
||||
|
||||
void Connect (const i2p::data::LeaseSet& remote);
|
||||
void HandleStreamDestinationRequestTimer (const boost::system::error_code& ecode, i2p::data::IdentHash ident);
|
||||
void HandleNamingLookupDestinationRequestTimer (const boost::system::error_code& ecode, i2p::data::IdentHash ident);
|
||||
void SendNamingLookupReply (const i2p::data::LeaseSet * leaseSet);
|
||||
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];
|
||||
uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE];
|
||||
SAMSocketType m_SocketType;
|
||||
std::string m_ID; // nickname
|
||||
bool m_IsSilent;
|
||||
i2p::stream::Stream * m_Stream;
|
||||
SAMSession * m_Session;
|
||||
};
|
||||
|
||||
struct SAMSession
|
||||
{
|
||||
ClientDestination * localDestination;
|
||||
std::list<SAMSocket *> sockets;
|
||||
};
|
||||
|
||||
class SAMBridge
|
||||
{
|
||||
public:
|
||||
|
||||
SAMBridge (int port);
|
||||
~SAMBridge ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
SAMSession * CreateSession (const std::string& id, const std::string& destination = ""); // empty string means transient
|
||||
void CloseSession (const std::string& id);
|
||||
SAMSession * FindSession (const std::string& id);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept(const boost::system::error_code& ecode);
|
||||
|
||||
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;
|
||||
SAMSocket * m_NewSocket;
|
||||
std::mutex m_SessionsMutex;
|
||||
std::map<std::string, SAMSession> m_Sessions;
|
||||
uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
284
SOCKS.cpp
Normal file
284
SOCKS.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
#include "SOCKS.h"
|
||||
#include "Identity.h"
|
||||
#include "NetDb.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace proxy
|
||||
{
|
||||
const uint8_t socks_leaseset_timeout = 10;
|
||||
const uint8_t socks_timeout = 60;
|
||||
|
||||
void SOCKS4AHandler::AsyncSockRead()
|
||||
{
|
||||
LogPrint("--- socks4a async sock read");
|
||||
if(m_sock) {
|
||||
if (m_state == INITIAL) {
|
||||
m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size),
|
||||
boost::bind(&SOCKS4AHandler::HandleSockRecv, this,
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
} else {
|
||||
m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size),
|
||||
boost::bind(&SOCKS4AHandler::HandleSockForward, this,
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
} else {
|
||||
LogPrint("--- socks4a no socket for read");
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::AsyncStreamRead()
|
||||
{
|
||||
|
||||
LogPrint("--- socks4a async stream read");
|
||||
if (m_stream) {
|
||||
m_stream->AsyncReceive(
|
||||
boost::asio::buffer(m_stream_buff, socks_buffer_size),
|
||||
boost::bind(&SOCKS4AHandler::HandleStreamRecv, this,
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred), socks_timeout);
|
||||
} else {
|
||||
LogPrint("--- socks4a no stream for read");
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::Terminate() {
|
||||
CloseStream();
|
||||
CloseSock();
|
||||
delete this; // ew
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::SocksFailed()
|
||||
{
|
||||
LogPrint("--- socks4a failed");
|
||||
m_sock->send(boost::asio::buffer("\x00\x5b 12345"));
|
||||
Terminate();
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::CloseSock()
|
||||
{
|
||||
if (m_sock) {
|
||||
LogPrint("--- socks4a close sock");
|
||||
m_sock->close();
|
||||
delete m_sock;
|
||||
m_sock = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::CloseStream()
|
||||
{
|
||||
if (m_stream) {
|
||||
LogPrint("--- socks4a close stream");
|
||||
delete m_stream;
|
||||
m_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t socks_hostname_size = 1024;
|
||||
const size_t socks_ident_size = 1024;
|
||||
const size_t destb32_len = 52;
|
||||
|
||||
void SOCKS4AHandler::HandleSockForward(const boost::system::error_code & ecode, std::size_t len)
|
||||
{
|
||||
if(ecode) {
|
||||
LogPrint("--- socks4a forward got error: ", ecode);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
LogPrint("--- socks4a sock forward: ", len);
|
||||
m_stream->Send(m_sock_buff, len);
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
|
||||
{
|
||||
LogPrint("--- socks4a sock recv: ", len);
|
||||
|
||||
if(ecode) {
|
||||
LogPrint(" --- sock recv got error: ", ecode);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state == INITIAL) {
|
||||
|
||||
char hostbuff[socks_hostname_size];
|
||||
char identbuff[socks_ident_size];
|
||||
std::memset(hostbuff, 0, sizeof(hostbuff));
|
||||
std::memset(identbuff, 0, sizeof(hostbuff));
|
||||
std::string dest;
|
||||
// get port
|
||||
uint16_t port = 0;
|
||||
uint16_t idx1 = 0;
|
||||
uint16_t idx2 = 0;
|
||||
|
||||
LogPrint("--- socks4a state initial ", len);
|
||||
|
||||
// check valid request
|
||||
if( m_sock_buff[0] != 4 || m_sock_buff[1] != 1 || m_sock_buff[len-1] ) {
|
||||
LogPrint("--- socks4a rejected invalid");
|
||||
SocksFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
// get port
|
||||
port = m_sock_buff[3] | m_sock_buff[2] << 8;
|
||||
|
||||
// read ident
|
||||
do {
|
||||
LogPrint("--- socks4a ", (int) m_sock_buff[9+idx1]);
|
||||
identbuff[idx1] = m_sock_buff[8+idx1];
|
||||
} while( identbuff[idx1++] && idx1 < socks_ident_size );
|
||||
|
||||
LogPrint("--- socks4a ident ", identbuff);
|
||||
// read hostname
|
||||
do {
|
||||
hostbuff[idx2] = m_sock_buff[8+idx1+idx2];
|
||||
} while( hostbuff[idx2++] && idx2 < socks_hostname_size );
|
||||
|
||||
LogPrint("--- socks4a requested ", hostbuff, ":" , port);
|
||||
|
||||
dest = std::string(hostbuff);
|
||||
if(dest.find(".b32.i2p") == std::string::npos) {
|
||||
LogPrint("--- socks4a invalid hostname: ", dest);
|
||||
SocksFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( i2p::data::Base32ToByteStream(hostbuff, destb32_len, (uint8_t *) m_dest, 32) != 32 ) {
|
||||
LogPrint("--- sock4a invalid b32: ", dest);
|
||||
}
|
||||
|
||||
LogPrint("--- sock4a find lease set");
|
||||
m_ls = i2p::data::netdb.FindLeaseSet(m_dest);
|
||||
if (!m_ls || m_ls->HasNonExpiredLeases()) {
|
||||
i2p::data::netdb.RequestDestination (m_dest, true, i2p::client::context.GetSharedLocalDestination ()->GetTunnelPool ());
|
||||
m_ls_timer.expires_from_now(boost::posix_time::seconds(socks_leaseset_timeout));
|
||||
m_ls_timer.async_wait(boost::bind(&SOCKS4AHandler::LeaseSetTimeout, this, boost::asio::placeholders::error));
|
||||
} else {
|
||||
ConnectionSuccess();
|
||||
}
|
||||
} else {
|
||||
LogPrint("--- socks4a state?? ", m_state);
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::HandleStreamRecv(const boost::system::error_code & ecode, std::size_t len)
|
||||
{
|
||||
if(ecode) { LogPrint("--- socks4a stream recv error: ", ecode); m_state = END; }
|
||||
switch(m_state) {
|
||||
case INITIAL:
|
||||
case END:
|
||||
Terminate();
|
||||
return;
|
||||
case OKAY:
|
||||
LogPrint("--- socks4a stream recv ", len);
|
||||
boost::asio::async_write(*m_sock, boost::asio::buffer(m_stream_buff, len),
|
||||
boost::bind(&SOCKS4AHandler::StreamWrote, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::SockWrote(const boost::system::error_code & ecode)
|
||||
{
|
||||
LogPrint("--- socks4a sock wrote");
|
||||
if(ecode) { LogPrint("--- socks4a SockWrote error: ",ecode); }
|
||||
else { AsyncSockRead(); }
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::StreamWrote(const boost::system::error_code & ecode)
|
||||
{
|
||||
|
||||
LogPrint("--- socks4a stream wrote");
|
||||
if(ecode) { LogPrint("--- socks4a StreamWrote error: ",ecode); }
|
||||
else { AsyncStreamRead(); }
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::LeaseSetTimeout(const boost::system::error_code & ecode)
|
||||
{
|
||||
m_ls = i2p::data::netdb.FindLeaseSet(m_dest);
|
||||
if(m_ls) {
|
||||
ConnectionSuccess();
|
||||
} else {
|
||||
LogPrint("--- socks4a ls timeout");
|
||||
SocksFailed();
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::ConnectionSuccess()
|
||||
{
|
||||
LogPrint("--- socks4a connection success");
|
||||
boost::asio::async_write(*m_sock, boost::asio::buffer("\x00\x5a 12345"),
|
||||
boost::bind(&SOCKS4AHandler::SentConnectionSuccess, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void SOCKS4AHandler::SentConnectionSuccess(const boost::system::error_code & ecode)
|
||||
{
|
||||
LogPrint("--- socks4a making connection");
|
||||
m_stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream(*m_ls);
|
||||
m_state = OKAY;
|
||||
LogPrint("--- socks4a state is ", m_state);
|
||||
AsyncSockRead();
|
||||
AsyncStreamRead();
|
||||
}
|
||||
|
||||
void SOCKS4AServer::Run()
|
||||
{
|
||||
LogPrint("--- socks4a run");
|
||||
m_run = true;
|
||||
while(m_run) {
|
||||
try {
|
||||
m_ios.run();
|
||||
} catch (std::runtime_error & exc) {
|
||||
LogPrint("--- socks4a exception: ", exc.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AServer::Accept()
|
||||
{
|
||||
m_new_sock = new boost::asio::ip::tcp::socket(m_ios);
|
||||
m_acceptor.async_accept(*m_new_sock,
|
||||
boost::bind(
|
||||
&SOCKS4AServer::HandleAccept, this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void SOCKS4AServer::Start()
|
||||
{
|
||||
m_run = true;
|
||||
m_thread = new std::thread(std::bind(&SOCKS4AServer::Run, this));
|
||||
m_acceptor.listen();
|
||||
Accept();
|
||||
}
|
||||
|
||||
void SOCKS4AServer::Stop()
|
||||
{
|
||||
m_acceptor.close();
|
||||
m_run = false;
|
||||
m_ios.stop();
|
||||
if (m_thread) {
|
||||
m_thread->join();
|
||||
delete m_thread;
|
||||
m_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKS4AServer::HandleAccept(const boost::system::error_code & ecode)
|
||||
{
|
||||
if (!ecode) {
|
||||
LogPrint("--- socks4a accepted");
|
||||
new SOCKS4AHandler(&m_ios, m_new_sock);
|
||||
Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
SOCKS.h
Normal file
99
SOCKS.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
#ifndef SOCKS4A_H__
|
||||
#define SOCKS4A_H__
|
||||
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include "Identity.h"
|
||||
#include "Streaming.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace proxy
|
||||
{
|
||||
|
||||
const size_t socks_buffer_size = 8192;
|
||||
|
||||
class SOCKS4AHandler {
|
||||
|
||||
private:
|
||||
enum state {
|
||||
INITIAL,
|
||||
OKAY,
|
||||
END
|
||||
};
|
||||
|
||||
void GotClientRequest(boost::system::error_code & ecode, std::string & host, uint16_t port);
|
||||
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||
void HandleSockForward(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||
void HandleStreamRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||
void Terminate();
|
||||
void CloseSock();
|
||||
void CloseStream();
|
||||
void AsyncSockRead();
|
||||
void AsyncStreamRead();
|
||||
void SocksFailed();
|
||||
void LeaseSetTimeout(const boost::system::error_code & ecode);
|
||||
void StreamWrote(const boost::system::error_code & ecode);
|
||||
void SockWrote(const boost::system::error_code & ecode);
|
||||
void SentConnectionSuccess(const boost::system::error_code & ecode);
|
||||
void ConnectionSuccess();
|
||||
|
||||
uint8_t m_sock_buff[socks_buffer_size];
|
||||
uint8_t m_stream_buff[socks_buffer_size];
|
||||
|
||||
boost::asio::io_service * m_ios;
|
||||
boost::asio::ip::tcp::socket * m_sock;
|
||||
boost::asio::deadline_timer m_ls_timer;
|
||||
i2p::stream::Stream * m_stream;
|
||||
i2p::data::LeaseSet * m_ls;
|
||||
i2p::data::IdentHash m_dest;
|
||||
state m_state;
|
||||
|
||||
|
||||
public:
|
||||
SOCKS4AHandler(boost::asio::io_service * ios, boost::asio::ip::tcp::socket * sock) :
|
||||
m_ios(ios), m_sock(sock), m_ls_timer(*ios),
|
||||
m_stream(nullptr), m_ls(nullptr), m_state(INITIAL) { AsyncSockRead(); }
|
||||
|
||||
~SOCKS4AHandler() { CloseSock(); CloseStream(); }
|
||||
bool isComplete() { return m_state == END; }
|
||||
};
|
||||
|
||||
class SOCKS4AServer {
|
||||
public:
|
||||
SOCKS4AServer(int port) : m_run(false),
|
||||
m_thread(nullptr),
|
||||
m_work(m_ios),
|
||||
m_acceptor(m_ios, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
|
||||
m_new_sock(nullptr) { }
|
||||
~SOCKS4AServer() { Stop(); }
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_ios; };
|
||||
|
||||
private:
|
||||
|
||||
void Run();
|
||||
void Accept();
|
||||
void HandleAccept(const boost::system::error_code& ecode);
|
||||
|
||||
bool m_run;
|
||||
std::thread * m_thread;
|
||||
boost::asio::io_service m_ios;
|
||||
boost::asio::io_service::work m_work;
|
||||
boost::asio::ip::tcp::acceptor m_acceptor;
|
||||
boost::asio::ip::tcp::socket * m_new_sock;
|
||||
|
||||
|
||||
};
|
||||
|
||||
typedef SOCKS4AServer SOCKSProxy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
365
SSU.cpp
Normal file
365
SSU.cpp
Normal file
|
@ -0,0 +1,365 @@
|
|||
#include <string.h>
|
||||
#include <boost/bind.hpp>
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "RouterContext.h"
|
||||
#include "SSU.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
SSUServer::SSUServer (int port): m_Thread (nullptr), m_Work (m_Service),
|
||||
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
|
||||
m_Socket (m_Service, m_Endpoint), m_SocketV6 (m_Service), m_IntroducersUpdateTimer (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 ()
|
||||
{
|
||||
for (auto it: m_Sessions)
|
||||
delete it.second;
|
||||
}
|
||||
|
||||
void SSUServer::Start ()
|
||||
{
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
|
||||
m_Service.post (boost::bind (&SSUServer::Receive, this));
|
||||
if (context.SupportsV6 ())
|
||||
m_Service.post (boost::bind (&SSUServer::ReceiveV6, this));
|
||||
if (i2p::context.IsUnreachable ())
|
||||
ScheduleIntroducersUpdateTimer ();
|
||||
}
|
||||
|
||||
void SSUServer::Stop ()
|
||||
{
|
||||
DeleteAllSessions ();
|
||||
m_IsRunning = false;
|
||||
m_Service.stop ();
|
||||
m_Socket.close ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU server: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay)
|
||||
{
|
||||
m_Relays[tag] = relay;
|
||||
}
|
||||
|
||||
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 ()
|
||||
{
|
||||
m_Socket.async_receive_from (boost::asio::buffer (m_ReceiveBuffer, SSU_MTU_V4), m_SenderEndpoint,
|
||||
boost::bind (&SSUServer::HandleReceivedFrom, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void SSUServer::ReceiveV6 ()
|
||||
{
|
||||
m_SocketV6.async_receive_from (boost::asio::buffer (m_ReceiveBufferV6, SSU_MTU_V6), m_SenderEndpointV6,
|
||||
boost::bind (&SSUServer::HandleReceivedFromV6, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
HandleReceivedBuffer (m_SenderEndpoint, m_ReceiveBuffer, bytes_transferred);
|
||||
Receive ();
|
||||
}
|
||||
else
|
||||
LogPrint ("SSU receive error: ", ecode.message ());
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
HandleReceivedBuffer (m_SenderEndpointV6, m_ReceiveBufferV6, bytes_transferred);
|
||||
ReceiveV6 ();
|
||||
}
|
||||
else
|
||||
LogPrint ("SSU V6 receive error: ", ecode.message ());
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedBuffer (boost::asio::ip::udp::endpoint& from, uint8_t * buf, std::size_t bytes_transferred)
|
||||
{
|
||||
SSUSession * session = nullptr;
|
||||
auto it = m_Sessions.find (from);
|
||||
if (it != m_Sessions.end ())
|
||||
session = it->second;
|
||||
if (!session)
|
||||
{
|
||||
session = new SSUSession (*this, from);
|
||||
m_Sessions[from] = session;
|
||||
LogPrint ("New SSU session from ", from.address ().to_string (), ":", from.port (), " created");
|
||||
}
|
||||
session->ProcessNextMessage (buf, bytes_transferred, from);
|
||||
}
|
||||
|
||||
SSUSession * SSUServer::FindSession (const i2p::data::RouterInfo * router)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
SSUSession * SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e)
|
||||
{
|
||||
auto it = m_Sessions.find (e);
|
||||
if (it != m_Sessions.end ())
|
||||
return it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SSUSession * SSUServer::GetSession (const i2p::data::RouterInfo * router, bool peerTest)
|
||||
{
|
||||
SSUSession * session = nullptr;
|
||||
if (router)
|
||||
{
|
||||
auto address = router->GetSSUAddress (!context.SupportsV6 ());
|
||||
if (address)
|
||||
{
|
||||
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
|
||||
auto it = m_Sessions.find (remoteEndpoint);
|
||||
if (it != m_Sessions.end ())
|
||||
session = it->second;
|
||||
else
|
||||
{
|
||||
// otherwise create new session
|
||||
session = new SSUSession (*this, remoteEndpoint, router, peerTest);
|
||||
m_Sessions[remoteEndpoint] = session;
|
||||
|
||||
if (!router->UsesIntroducer ())
|
||||
{
|
||||
// connect directly
|
||||
LogPrint ("Creating new SSU session to [", router->GetIdentHashAbbreviation (), "] ",
|
||||
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
|
||||
session->Connect ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// connect through introducer
|
||||
int numIntroducers = address->introducers.size ();
|
||||
if (numIntroducers > 0)
|
||||
{
|
||||
SSUSession * introducerSession = nullptr;
|
||||
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
|
||||
// we might have a session to introducer already
|
||||
for (int i = 0; i < numIntroducers; i++)
|
||||
{
|
||||
introducer = &(address->introducers[i]);
|
||||
it = m_Sessions.find (boost::asio::ip::udp::endpoint (introducer->iHost, introducer->iPort));
|
||||
if (it != m_Sessions.end ())
|
||||
{
|
||||
introducerSession = it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (introducerSession) // session found
|
||||
LogPrint ("Session to introducer already exists");
|
||||
else // create new
|
||||
{
|
||||
LogPrint ("Creating new session to introducer");
|
||||
introducer = &(address->introducers[0]); // TODO:
|
||||
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
|
||||
introducerSession = new SSUSession (*this, introducerEndpoint, router);
|
||||
m_Sessions[introducerEndpoint] = introducerSession;
|
||||
}
|
||||
// introduce
|
||||
LogPrint ("Introduce new SSU session to [", router->GetIdentHashAbbreviation (),
|
||||
"] through introducer ", introducer->iHost, ":", introducer->iPort);
|
||||
session->WaitForIntroduction ();
|
||||
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable
|
||||
Send (m_ReceiveBuffer, 0, remoteEndpoint); // send HolePunch
|
||||
introducerSession->Introduce (introducer->iTag, introducer->iKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented");
|
||||
m_Sessions.erase (remoteEndpoint);
|
||||
delete session;
|
||||
session = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Router ", router->GetIdentHashAbbreviation (), " doesn't have SSU address");
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
void SSUServer::DeleteSession (SSUSession * session)
|
||||
{
|
||||
if (session)
|
||||
{
|
||||
session->Close ();
|
||||
m_Sessions.erase (session->GetRemoteEndpoint ());
|
||||
delete session;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::DeleteAllSessions ()
|
||||
{
|
||||
for (auto it: m_Sessions)
|
||||
{
|
||||
it.second->Close ();
|
||||
delete it.second;
|
||||
}
|
||||
m_Sessions.clear ();
|
||||
}
|
||||
|
||||
template<typename Filter>
|
||||
SSUSession * SSUServer::GetRandomSession (Filter filter)
|
||||
{
|
||||
std::vector<SSUSession *> filteredSessions;
|
||||
for (auto s :m_Sessions)
|
||||
if (filter (s.second)) filteredSessions.push_back (s.second);
|
||||
if (filteredSessions.size () > 0)
|
||||
{
|
||||
auto ind = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, filteredSessions.size ()-1);
|
||||
return filteredSessions[ind];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SSUSession * SSUServer::GetRandomEstablishedSession (const SSUSession * excluded)
|
||||
{
|
||||
return GetRandomSession (
|
||||
[excluded](SSUSession * session)->bool
|
||||
{
|
||||
return session->GetState () == eSessionStateEstablished &&
|
||||
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 = GetRandomSession (
|
||||
[&ret, ts](SSUSession * session)->bool
|
||||
{
|
||||
return session->GetRelayTag () && !ret.count (session) &&
|
||||
session->GetState () == eSessionStateEstablished &&
|
||||
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
|
||||
}
|
||||
);
|
||||
if (session)
|
||||
{
|
||||
ret.insert (session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SSUServer::ScheduleIntroducersUpdateTimer ()
|
||||
{
|
||||
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
|
||||
m_IntroducersUpdateTimer.async_wait (boost::bind (&SSUServer::HandleIntroducersUpdateTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
// timeout expired
|
||||
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 router = it1->GetRemoteRouter ();
|
||||
if (router && i2p::context.AddIntroducer (*router, it1->GetRelayTag ()))
|
||||
{
|
||||
newList.push_back (it1->GetRemoteEndpoint ());
|
||||
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Introducers = newList;
|
||||
ScheduleIntroducersUpdateTimer ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
87
SSU.h
Normal file
87
SSU.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#ifndef SSU_H__
|
||||
#define SSU_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include "aes.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_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
|
||||
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
|
||||
|
||||
class SSUServer
|
||||
{
|
||||
public:
|
||||
|
||||
SSUServer (int port);
|
||||
~SSUServer ();
|
||||
void Start ();
|
||||
void Stop ();
|
||||
SSUSession * GetSession (const i2p::data::RouterInfo * router, bool peerTest = false);
|
||||
SSUSession * FindSession (const i2p::data::RouterInfo * router);
|
||||
SSUSession * FindSession (const boost::asio::ip::udp::endpoint& e);
|
||||
SSUSession * GetRandomEstablishedSession (const SSUSession * excluded);
|
||||
void DeleteSession (SSUSession * session);
|
||||
void DeleteAllSessions ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Socket.get_io_service(); };
|
||||
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);
|
||||
SSUSession * FindRelaySession (uint32_t tag);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void Receive ();
|
||||
void ReceiveV6 ();
|
||||
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandleReceivedBuffer (boost::asio::ip::udp::endpoint& from, uint8_t * buf, std::size_t bytes_transferred);
|
||||
|
||||
template<typename Filter>
|
||||
SSUSession * GetRandomSession (Filter filter);
|
||||
|
||||
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers);
|
||||
void ScheduleIntroducersUpdateTimer ();
|
||||
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::io_service::work m_Work;
|
||||
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
|
||||
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
|
||||
boost::asio::ip::udp::endpoint m_SenderEndpoint, m_SenderEndpointV6;
|
||||
boost::asio::deadline_timer m_IntroducersUpdateTimer;
|
||||
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to
|
||||
i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V4> m_ReceiveBuffer;
|
||||
i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V6> m_ReceiveBufferV6;
|
||||
std::map<boost::asio::ip::udp::endpoint, SSUSession *> m_Sessions;
|
||||
std::map<uint32_t, boost::asio::ip::udp::endpoint> m_Relays; // we are introducer
|
||||
|
||||
public:
|
||||
// for HTTP only
|
||||
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
429
SSUData.cpp
Normal file
429
SSUData.cpp
Normal file
|
@ -0,0 +1,429 @@
|
|||
#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
|
||||
{
|
||||
SSUData::SSUData (SSUSession& session):
|
||||
m_Session (session), m_ResendTimer (session.m_Server.GetService ())
|
||||
{
|
||||
m_MaxPacketSize = session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE;
|
||||
m_PacketSize = m_MaxPacketSize;
|
||||
auto remoteRouter = session.GetRemoteRouter ();
|
||||
if (remoteRouter)
|
||||
AdjustPacketSize (*remoteRouter);
|
||||
}
|
||||
|
||||
SSUData::~SSUData ()
|
||||
{
|
||||
for (auto it: m_IncomleteMessages)
|
||||
if (it.second)
|
||||
{
|
||||
DeleteI2NPMessage (it.second->msg);
|
||||
delete it.second;
|
||||
}
|
||||
for (auto it: m_SentMessages)
|
||||
delete it.second;
|
||||
}
|
||||
|
||||
void SSUData::AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter)
|
||||
{
|
||||
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 ("MTU=", ssuAddress->mtu, " packet size=", m_PacketSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "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 ())
|
||||
{
|
||||
delete it->second;
|
||||
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 (be32toh (((uint32_t *)buf)[i]));
|
||||
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 = be32toh (*(uint32_t *)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)
|
||||
{
|
||||
delete it->second->fragments[fragment];
|
||||
it->second->fragments[fragment] = 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 = be32toh (*(uint32_t *)buf); // message ID
|
||||
buf += 4;
|
||||
uint8_t frag[4];
|
||||
frag[0] = 0;
|
||||
memcpy (frag + 1, buf, 3);
|
||||
buf += 3;
|
||||
uint32_t fragmentInfo = be32toh (*(uint32_t *)frag); // fragment info
|
||||
uint16_t fragmentSize = fragmentInfo & 0x1FFF; // bits 0 - 13
|
||||
bool isLast = fragmentInfo & 0x010000; // bit 16
|
||||
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
|
||||
LogPrint (eLogDebug, "SSU data fragment ", (int)fragmentNum, " of message ", msgID, " size=", (int)fragmentSize, isLast ? " last" : " non-last");
|
||||
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
|
||||
{
|
||||
LogPrint (eLogError, "Fragment size ", fragmentSize, "exceeds max SSU packet size");
|
||||
return;
|
||||
}
|
||||
|
||||
// find message with msgID
|
||||
I2NPMessage * msg = nullptr;
|
||||
IncompleteMessage * incompleteMessage = nullptr;
|
||||
auto it = m_IncomleteMessages.find (msgID);
|
||||
if (it != m_IncomleteMessages.end ())
|
||||
{
|
||||
// message exists
|
||||
incompleteMessage = it->second;
|
||||
msg = incompleteMessage->msg;
|
||||
}
|
||||
else
|
||||
{
|
||||
// create new message
|
||||
msg = NewI2NPMessage ();
|
||||
msg->len -= sizeof (I2NPHeaderShort);
|
||||
incompleteMessage = new IncompleteMessage (msg);
|
||||
m_IncomleteMessages[msgID] = incompleteMessage;
|
||||
}
|
||||
|
||||
// handle current fragment
|
||||
if (fragmentNum == incompleteMessage->nextFragmentNum)
|
||||
{
|
||||
// expected fragment
|
||||
memcpy (msg->buf + msg->len, buf, fragmentSize);
|
||||
msg->len += fragmentSize;
|
||||
incompleteMessage->nextFragmentNum++;
|
||||
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)
|
||||
{
|
||||
memcpy (msg->buf + msg->len, savedFragment->buf, savedFragment->len);
|
||||
msg->len += savedFragment->len;
|
||||
isLast = savedFragment->isLast;
|
||||
incompleteMessage->nextFragmentNum++;
|
||||
incompleteMessage->savedFragments.erase (it1++);
|
||||
delete savedFragment;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (isLast)
|
||||
LogPrint (eLogDebug, "Message ", msgID, " complete");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fragmentNum < incompleteMessage->nextFragmentNum)
|
||||
// duplicate fragment
|
||||
LogPrint (eLogWarning, "Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ". Ignored");
|
||||
else
|
||||
{
|
||||
// missing fragment
|
||||
LogPrint (eLogWarning, "Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
|
||||
auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast);
|
||||
if (!incompleteMessage->savedFragments.insert (savedFragment).second)
|
||||
{
|
||||
LogPrint (eLogWarning, "Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
|
||||
delete savedFragment;
|
||||
}
|
||||
}
|
||||
isLast = false;
|
||||
}
|
||||
|
||||
if (isLast)
|
||||
{
|
||||
// delete incomplete message
|
||||
delete incompleteMessage;
|
||||
m_IncomleteMessages.erase (msgID);
|
||||
// process message
|
||||
SendMsgAck (msgID);
|
||||
msg->FromSSU (msgID);
|
||||
if (m_Session.GetState () == eSessionStateEstablished)
|
||||
{
|
||||
if (!m_ReceivedMessages.count (msgID))
|
||||
{
|
||||
if (m_ReceivedMessages.size () > 100) m_ReceivedMessages.clear ();
|
||||
m_ReceivedMessages.insert (msgID);
|
||||
i2p::HandleI2NPMessage (msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU message ", msgID, " already received");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we expect DeliveryStatus
|
||||
if (msg->GetHeader ()->typeID == eI2NPDeliveryStatus)
|
||||
{
|
||||
LogPrint ("SSU session established");
|
||||
m_Session.Established ();
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "SSU unexpected message ", (int)msg->GetHeader ()->typeID);
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
SendFragmentAck (msgID, fragmentNum);
|
||||
buf += fragmentSize;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
|
||||
{
|
||||
//uint8_t * start = buf;
|
||||
uint8_t flag = *buf;
|
||||
buf++;
|
||||
LogPrint (eLogDebug, "Process SSU data flags=", (int)flag);
|
||||
// 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 presented");
|
||||
buf += extendedDataSize;
|
||||
}
|
||||
// process data
|
||||
ProcessFragments (buf);
|
||||
}
|
||||
|
||||
void SSUData::Send (i2p::I2NPMessage * msg)
|
||||
{
|
||||
uint32_t msgID = msg->ToSSU ();
|
||||
if (m_SentMessages.count (msgID) > 0)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU message ", msgID, " already sent");
|
||||
DeleteI2NPMessage (msg);
|
||||
return;
|
||||
}
|
||||
if (m_SentMessages.empty ()) // schedule resend at first message only
|
||||
ScheduleResend ();
|
||||
SentMessage * sentMessage = new SentMessage;
|
||||
m_SentMessages[msgID] = sentMessage;
|
||||
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
|
||||
sentMessage->numResends = 0;
|
||||
auto& fragments = sentMessage->fragments;
|
||||
msgID = htobe32 (msgID);
|
||||
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;
|
||||
fragments.push_back (fragment);
|
||||
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
|
||||
payload++;
|
||||
*payload = 1; // always 1 message fragment per message
|
||||
payload++;
|
||||
*(uint32_t *)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;
|
||||
|
||||
// encrypt message with session key
|
||||
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size);
|
||||
m_Session.Send (buf, size);
|
||||
|
||||
if (!isLast)
|
||||
{
|
||||
len -= payloadSize;
|
||||
msgBuf += payloadSize;
|
||||
}
|
||||
else
|
||||
len = 0;
|
||||
fragmentNum++;
|
||||
}
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
|
||||
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++;
|
||||
*(uint32_t *)(payload) = htobe32 (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, "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));
|
||||
m_ResendTimer.async_wait (boost::bind (&SSUData::HandleResendTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (ts >= it.second->nextResendTime && it.second->numResends < MAX_NUM_RESENDS)
|
||||
{
|
||||
for (auto f: it.second->fragments)
|
||||
if (f) m_Session.Send (f->buf, f->len); // resend
|
||||
|
||||
it.second->numResends++;
|
||||
it.second->nextResendTime += it.second->numResends*RESEND_INTERVAL;
|
||||
}
|
||||
}
|
||||
ScheduleResend ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
114
SSUData.h
Normal file
114
SSUData.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
#ifndef SSU_DATA_H__
|
||||
#define SSU_DATA_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#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;
|
||||
// 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 Fragment * f1, const Fragment * f2) const
|
||||
{
|
||||
return f1->fragmentNum < f2->fragmentNum;
|
||||
};
|
||||
};
|
||||
|
||||
struct IncompleteMessage
|
||||
{
|
||||
I2NPMessage * msg;
|
||||
int nextFragmentNum;
|
||||
std::set<Fragment *, FragmentCmp> savedFragments;
|
||||
|
||||
IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0) {};
|
||||
~IncompleteMessage () { for (auto it: savedFragments) { delete it; }; };
|
||||
};
|
||||
|
||||
struct SentMessage
|
||||
{
|
||||
std::vector<Fragment *> fragments;
|
||||
uint32_t nextResendTime; // in seconds
|
||||
int numResends;
|
||||
|
||||
~SentMessage () { for (auto it: fragments) { delete it; }; };
|
||||
};
|
||||
|
||||
class SSUSession;
|
||||
class SSUData
|
||||
{
|
||||
public:
|
||||
|
||||
SSUData (SSUSession& session);
|
||||
~SSUData ();
|
||||
|
||||
void ProcessMessage (uint8_t * buf, size_t len);
|
||||
void Send (i2p::I2NPMessage * msg);
|
||||
|
||||
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 AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter);
|
||||
|
||||
private:
|
||||
|
||||
SSUSession& m_Session;
|
||||
std::map<uint32_t, IncompleteMessage *> m_IncomleteMessages;
|
||||
std::map<uint32_t, SentMessage *> m_SentMessages;
|
||||
std::set<uint32_t> m_ReceivedMessages;
|
||||
boost::asio::deadline_timer m_ResendTimer;
|
||||
int m_MaxPacketSize, m_PacketSize;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
1012
SSUSession.cpp
Normal file
1012
SSUSession.cpp
Normal file
File diff suppressed because it is too large
Load diff
145
SSUSession.h
Normal file
145
SSUSession.h
Normal file
|
@ -0,0 +1,145 @@
|
|||
#ifndef SSU_SESSION_H__
|
||||
#define SSU_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <boost/asio.hpp>
|
||||
#include "aes.h"
|
||||
#include "hmac.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TransportSession.h"
|
||||
#include "SSUData.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
#pragma pack(1)
|
||||
struct SSUHeader
|
||||
{
|
||||
uint8_t mac[16];
|
||||
uint8_t iv[16];
|
||||
uint8_t flag;
|
||||
uint32_t time;
|
||||
|
||||
uint8_t GetPayloadType () const { return flag >> 4; };
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
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;
|
||||
|
||||
enum SessionState
|
||||
{
|
||||
eSessionStateUnknown,
|
||||
eSessionStateIntroduced,
|
||||
eSessionStateEstablished,
|
||||
eSessionStateFailed
|
||||
};
|
||||
|
||||
class SSUServer;
|
||||
class SSUSession: public TransportSession
|
||||
{
|
||||
public:
|
||||
|
||||
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
|
||||
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 Introduce (uint32_t iTag, const uint8_t * iKey);
|
||||
void WaitForIntroduction ();
|
||||
void Close ();
|
||||
boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
|
||||
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
|
||||
void SendI2NPMessage (I2NPMessage * msg);
|
||||
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; };
|
||||
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||
|
||||
private:
|
||||
|
||||
void CreateAESandMacKey (const uint8_t * pubKey);
|
||||
|
||||
void PostI2NPMessage (I2NPMessage * msg);
|
||||
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
|
||||
void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||
void SendSessionRequest ();
|
||||
void SendRelayRequest (uint32_t iTag, const uint8_t * iKey);
|
||||
void ProcessSessionCreated (uint8_t * buf, size_t len);
|
||||
void SendSessionCreated (const uint8_t * x);
|
||||
void ProcessSessionConfirmed (uint8_t * buf, size_t len);
|
||||
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
|
||||
void ProcessRelayRequest (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 (SSUSession * session, const boost::asio::ip::udp::endpoint& from);
|
||||
void ProcessRelayResponse (uint8_t * buf, size_t len);
|
||||
void ProcessRelayIntro (uint8_t * buf, size_t len);
|
||||
void Established ();
|
||||
void Failed ();
|
||||
void ScheduleConnectTimer ();
|
||||
void HandleConnectTimer (const boost::system::error_code& ecode);
|
||||
void ProcessPeerTest (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);
|
||||
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 uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey);
|
||||
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
|
||||
void Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey);
|
||||
void DecryptSessionKey (uint8_t * buf, size_t len);
|
||||
bool Validate (uint8_t * buf, size_t len, const uint8_t * macKey);
|
||||
const uint8_t * GetIntroKey () const;
|
||||
|
||||
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_PeerTest;
|
||||
SessionState m_State;
|
||||
bool m_IsSessionKey;
|
||||
uint32_t m_RelayTag;
|
||||
std::set<uint32_t> m_PeerTestNonces;
|
||||
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
|
||||
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
|
||||
i2p::crypto::AESKey m_SessionKey;
|
||||
i2p::crypto::MACKey m_MacKey;
|
||||
std::list<i2p::I2NPMessage *> m_DelayedMessages;
|
||||
SSUData m_Data;
|
||||
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||
uint32_t m_CreationTime; // seconds since epoch
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
155
Signature.h
Normal file
155
Signature.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
#ifndef SIGNATURE_H__
|
||||
#define SIGNATURE_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <cryptopp/dsa.h>
|
||||
#include <cryptopp/asn.h>
|
||||
#include <cryptopp/oids.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/eccrypto.h>
|
||||
#include "CryptoConst.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;
|
||||
};
|
||||
|
||||
class Signer
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~Signer () {};
|
||||
virtual void Sign (CryptoPP::RandomNumberGenerator& rnd, 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.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingKey, DSA_PUBLIC_KEY_LENGTH));
|
||||
}
|
||||
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
CryptoPP::DSA::Verifier verifier (m_PublicKey);
|
||||
return verifier.VerifyMessage (buf, len, signature, DSA_SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
|
||||
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
|
||||
|
||||
private:
|
||||
|
||||
CryptoPP::DSA::PublicKey m_PublicKey;
|
||||
};
|
||||
|
||||
class DSASigner: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
DSASigner (const uint8_t * signingPrivateKey)
|
||||
{
|
||||
m_PrivateKey.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH));
|
||||
}
|
||||
|
||||
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
CryptoPP::DSA::Signer signer (m_PrivateKey);
|
||||
signer.SignMessage (rnd, buf, len, signature);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
CryptoPP::DSA::PrivateKey m_PrivateKey;
|
||||
};
|
||||
|
||||
inline void CreateDSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
CryptoPP::DSA::PrivateKey privateKey;
|
||||
CryptoPP::DSA::PublicKey publicKey;
|
||||
privateKey.Initialize (rnd, dsap, dsaq, dsag);
|
||||
privateKey.MakePublicKey (publicKey);
|
||||
privateKey.GetPrivateExponent ().Encode (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
|
||||
publicKey.GetPublicElement ().Encode (signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
|
||||
}
|
||||
|
||||
|
||||
const size_t ECDSAP256_PUBLIC_KEY_LENGTH = 64;
|
||||
const size_t ECDSAP256_PUBLIC_KEY_HALF_LENGTH = ECDSAP256_PUBLIC_KEY_LENGTH/2;
|
||||
const size_t ECDSAP256_SIGNATURE_LENGTH = 64;
|
||||
const size_t ECDSAP256_PRIVATE_KEY_LENGTH = ECDSAP256_SIGNATURE_LENGTH/2;
|
||||
class ECDSAP256Verifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
ECDSAP256Verifier (const uint8_t * signingKey)
|
||||
{
|
||||
m_PublicKey.Initialize (CryptoPP::ASN1::secp256r1(),
|
||||
CryptoPP::ECP::Point (CryptoPP::Integer (signingKey, ECDSAP256_PUBLIC_KEY_HALF_LENGTH),
|
||||
CryptoPP::Integer (signingKey + ECDSAP256_PUBLIC_KEY_HALF_LENGTH, ECDSAP256_PUBLIC_KEY_HALF_LENGTH)));
|
||||
}
|
||||
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::Verifier verifier (m_PublicKey);
|
||||
return verifier.VerifyMessage (buf, len, signature, ECDSAP256_SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
size_t GetPublicKeyLen () const { return ECDSAP256_PUBLIC_KEY_LENGTH; };
|
||||
size_t GetSignatureLen () const { return ECDSAP256_SIGNATURE_LENGTH; };
|
||||
|
||||
private:
|
||||
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::PublicKey m_PublicKey;
|
||||
};
|
||||
|
||||
class ECDSAP256Signer: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
ECDSAP256Signer (const uint8_t * signingPrivateKey)
|
||||
{
|
||||
m_PrivateKey.Initialize (CryptoPP::ASN1::secp256r1(), CryptoPP::Integer (signingPrivateKey, ECDSAP256_PRIVATE_KEY_LENGTH));
|
||||
}
|
||||
|
||||
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::Signer signer (m_PrivateKey);
|
||||
signer.SignMessage (rnd, buf, len, signature);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::PrivateKey m_PrivateKey;
|
||||
};
|
||||
|
||||
inline void CreateECDSAP256RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::PrivateKey privateKey;
|
||||
CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::PublicKey publicKey;
|
||||
privateKey.Initialize (rnd, CryptoPP::ASN1::secp256r1());
|
||||
privateKey.MakePublicKey (publicKey);
|
||||
privateKey.GetPrivateExponent ().Encode (signingPrivateKey, ECDSAP256_PRIVATE_KEY_LENGTH);
|
||||
auto q = publicKey.GetPublicElement ();
|
||||
q.x.Encode (signingPublicKey, ECDSAP256_PUBLIC_KEY_HALF_LENGTH);
|
||||
q.y.Encode (signingPublicKey + ECDSAP256_PUBLIC_KEY_HALF_LENGTH, ECDSAP256_PUBLIC_KEY_HALF_LENGTH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
660
Streaming.cpp
Normal file
660
Streaming.cpp
Normal file
|
@ -0,0 +1,660 @@
|
|||
#include <cryptopp/gzip.h>
|
||||
#include "Log.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Tunnel.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Destination.h"
|
||||
#include "Streaming.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace stream
|
||||
{
|
||||
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local,
|
||||
const i2p::data::LeaseSet& remote, int port): m_Service (service), m_SendStreamID (0),
|
||||
m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false),
|
||||
m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local),
|
||||
m_RemoteLeaseSet (&remote), m_RoutingSession (nullptr), m_ReceiveTimer (m_Service),
|
||||
m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0),
|
||||
m_NumReceivedBytes (0), m_Port (port)
|
||||
{
|
||||
m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
|
||||
UpdateCurrentRemoteLease ();
|
||||
}
|
||||
|
||||
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
|
||||
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
|
||||
m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local),
|
||||
m_RemoteLeaseSet (nullptr), m_RoutingSession (nullptr), m_ReceiveTimer (m_Service),
|
||||
m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0),
|
||||
m_NumReceivedBytes (0), m_Port (0)
|
||||
{
|
||||
m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
|
||||
}
|
||||
|
||||
Stream::~Stream ()
|
||||
{
|
||||
m_AckSendTimer.cancel ();
|
||||
while (!m_ReceiveQueue.empty ())
|
||||
{
|
||||
auto packet = m_ReceiveQueue.front ();
|
||||
m_ReceiveQueue.pop ();
|
||||
delete packet;
|
||||
}
|
||||
m_ReceiveTimer.cancel ();
|
||||
|
||||
for (auto it: m_SentPackets)
|
||||
delete it;
|
||||
m_SentPackets.clear ();
|
||||
m_ResendTimer.cancel ();
|
||||
|
||||
for (auto it: m_SavedPackets)
|
||||
delete it;
|
||||
m_SavedPackets.clear ();
|
||||
|
||||
Close ();
|
||||
}
|
||||
|
||||
void Stream::HandleNextPacket (Packet * packet)
|
||||
{
|
||||
m_NumReceivedBytes += packet->GetLength ();
|
||||
if (!m_SendStreamID)
|
||||
m_SendStreamID = packet->GetReceiveStreamID ();
|
||||
|
||||
if (!packet->IsNoAck ()) // ack received
|
||||
ProcessAck (packet);
|
||||
|
||||
int32_t receivedSeqn = packet->GetSeqn ();
|
||||
bool isSyn = packet->IsSYN ();
|
||||
if (!receivedSeqn && !isSyn)
|
||||
{
|
||||
// plain ack
|
||||
LogPrint ("Plain ACK received");
|
||||
delete packet;
|
||||
return;
|
||||
}
|
||||
|
||||
LogPrint ("Received seqn=", receivedSeqn);
|
||||
if (isSyn || receivedSeqn == m_LastReceivedSequenceNumber + 1)
|
||||
{
|
||||
// we have received next in sequence message
|
||||
ProcessPacket (packet);
|
||||
|
||||
// we should also try stored messages if any
|
||||
for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();)
|
||||
{
|
||||
if ((*it)->GetSeqn () == (uint32_t)(m_LastReceivedSequenceNumber + 1))
|
||||
{
|
||||
Packet * savedPacket = *it;
|
||||
m_SavedPackets.erase (it++);
|
||||
|
||||
ProcessPacket (savedPacket);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// schedule ack for last message
|
||||
if (m_IsOpen)
|
||||
{
|
||||
if (!m_IsAckSendScheduled)
|
||||
{
|
||||
m_IsAckSendScheduled = true;
|
||||
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT));
|
||||
m_AckSendTimer.async_wait (boost::bind (&Stream::HandleAckSendTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
else if (isSyn)
|
||||
// we have to send SYN back to incoming connection
|
||||
Send (nullptr, 0); // also sets m_IsOpen
|
||||
}
|
||||
else
|
||||
{
|
||||
if (receivedSeqn <= m_LastReceivedSequenceNumber)
|
||||
{
|
||||
// we have received duplicate. Most likely our outbound tunnel is dead
|
||||
LogPrint ("Duplicate message ", receivedSeqn, " received");
|
||||
m_LocalDestination.GetOwner ().ResetCurrentOutboundTunnel (); // pick another outbound tunnel
|
||||
UpdateCurrentRemoteLease (); // pick another lease
|
||||
SendQuickAck (); // resend ack for previous message again
|
||||
delete packet; // packet dropped
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1);
|
||||
// save message and wait for missing message again
|
||||
SavePacket (packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::SavePacket (Packet * packet)
|
||||
{
|
||||
m_SavedPackets.insert (packet);
|
||||
}
|
||||
|
||||
void Stream::ProcessPacket (Packet * packet)
|
||||
{
|
||||
// process flags
|
||||
uint32_t receivedSeqn = packet->GetSeqn ();
|
||||
uint16_t flags = packet->GetFlags ();
|
||||
LogPrint ("Process seqn=", receivedSeqn, ", flags=", flags);
|
||||
|
||||
const uint8_t * optionData = packet->GetOptionData ();
|
||||
if (flags & PACKET_FLAG_SYNCHRONIZE)
|
||||
LogPrint ("Synchronize");
|
||||
|
||||
if (flags & PACKET_FLAG_DELAY_REQUESTED)
|
||||
{
|
||||
optionData += 2;
|
||||
}
|
||||
|
||||
if (flags & PACKET_FLAG_FROM_INCLUDED)
|
||||
{
|
||||
optionData += m_RemoteIdentity.FromBuffer (optionData, packet->GetOptionSize ());
|
||||
LogPrint ("From identity ", m_RemoteIdentity.GetIdentHash ().ToBase64 ());
|
||||
if (!m_RemoteLeaseSet)
|
||||
LogPrint ("Incoming stream from ", m_RemoteIdentity.GetIdentHash ().ToBase64 ());
|
||||
}
|
||||
|
||||
if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED)
|
||||
{
|
||||
uint16_t maxPacketSize = be16toh (*(uint16_t *)optionData);
|
||||
LogPrint ("Max packet size ", maxPacketSize);
|
||||
optionData += 2;
|
||||
}
|
||||
|
||||
if (flags & PACKET_FLAG_SIGNATURE_INCLUDED)
|
||||
{
|
||||
LogPrint ("Signature");
|
||||
uint8_t signature[256];
|
||||
auto signatureLen = m_RemoteIdentity.GetSignatureLen ();
|
||||
memcpy (signature, optionData, signatureLen);
|
||||
memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
|
||||
if (!m_RemoteIdentity.Verify (packet->GetBuffer (), packet->GetLength (), signature))
|
||||
{
|
||||
LogPrint ("Signature verification failed");
|
||||
Close ();
|
||||
flags |= PACKET_FLAG_CLOSE;
|
||||
}
|
||||
memcpy (const_cast<uint8_t *>(optionData), signature, signatureLen);
|
||||
optionData += signatureLen;
|
||||
}
|
||||
|
||||
packet->offset = packet->GetPayload () - packet->buf;
|
||||
if (packet->GetLength () > 0)
|
||||
{
|
||||
m_ReceiveQueue.push (packet);
|
||||
m_ReceiveTimer.cancel ();
|
||||
}
|
||||
else
|
||||
delete packet;
|
||||
|
||||
m_LastReceivedSequenceNumber = receivedSeqn;
|
||||
|
||||
if (flags & PACKET_FLAG_CLOSE)
|
||||
{
|
||||
LogPrint ("Closed");
|
||||
SendQuickAck (); // send ack for close explicitly?
|
||||
m_IsOpen = false;
|
||||
m_IsReset = true;
|
||||
m_ReceiveTimer.cancel ();
|
||||
m_ResendTimer.cancel ();
|
||||
m_AckSendTimer.cancel ();
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::ProcessAck (Packet * packet)
|
||||
{
|
||||
uint32_t ackThrough = packet->GetAckThrough ();
|
||||
int nackCount = packet->GetNACKCount ();
|
||||
for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();)
|
||||
{
|
||||
auto seqn = (*it)->GetSeqn ();
|
||||
if (seqn <= ackThrough)
|
||||
{
|
||||
if (nackCount > 0)
|
||||
{
|
||||
bool nacked = false;
|
||||
for (int i = 0; i < nackCount; i++)
|
||||
if (seqn == packet->GetNACK (i))
|
||||
{
|
||||
nacked = true;
|
||||
break;
|
||||
}
|
||||
if (nacked)
|
||||
{
|
||||
LogPrint ("Packet ", seqn, " NACK");
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto sentPacket = *it;
|
||||
LogPrint ("Packet ", seqn, " acknowledged");
|
||||
m_SentPackets.erase (it++);
|
||||
delete sentPacket;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (m_SentPackets.empty ())
|
||||
m_ResendTimer.cancel ();
|
||||
}
|
||||
|
||||
size_t Stream::Send (const uint8_t * buf, size_t len)
|
||||
{
|
||||
bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet
|
||||
while (!m_IsOpen || len > 0)
|
||||
{
|
||||
Packet * p = new Packet ();
|
||||
uint8_t * packet = p->GetBuffer ();
|
||||
// TODO: implement setters
|
||||
size_t size = 0;
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_SendStreamID);
|
||||
size += 4; // sendStreamID
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_RecvStreamID);
|
||||
size += 4; // receiveStreamID
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_SequenceNumber++);
|
||||
size += 4; // sequenceNum
|
||||
if (isNoAck)
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_LastReceivedSequenceNumber);
|
||||
else
|
||||
*(uint32_t *)(packet + size) = 0;
|
||||
size += 4; // ack Through
|
||||
packet[size] = 0;
|
||||
size++; // NACK count
|
||||
size++; // resend delay
|
||||
if (!m_IsOpen)
|
||||
{
|
||||
// initial packet
|
||||
m_IsOpen = true; m_IsReset = false;
|
||||
uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED |
|
||||
PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED;
|
||||
if (isNoAck) flags |= PACKET_FLAG_NO_ACK;
|
||||
*(uint16_t *)(packet + size) = htobe16 (flags);
|
||||
size += 2; // flags
|
||||
size_t identityLen = m_LocalDestination.GetOwner ().GetIdentity ().GetFullLen ();
|
||||
size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen ();
|
||||
*(uint16_t *)(packet + size) = htobe16 (identityLen + signatureLen + 2); // identity + signature + packet size
|
||||
size += 2; // options size
|
||||
m_LocalDestination.GetOwner ().GetIdentity ().ToBuffer (packet + size, identityLen);
|
||||
size += identityLen; // from
|
||||
*(uint16_t *)(packet + size) = htobe16 (STREAMING_MTU);
|
||||
size += 2; // max packet size
|
||||
uint8_t * signature = packet + size; // set it later
|
||||
memset (signature, 0, signatureLen); // zeroes for now
|
||||
size += signatureLen; // signature
|
||||
size_t sentLen = STREAMING_MTU - size;
|
||||
if (len < sentLen) sentLen = len;
|
||||
memcpy (packet + size, buf, sentLen);
|
||||
buf += sentLen;
|
||||
len -= sentLen;
|
||||
size += sentLen; // payload
|
||||
m_LocalDestination.GetOwner ().Sign (packet, size, signature);
|
||||
}
|
||||
else
|
||||
{
|
||||
// follow on packet
|
||||
*(uint16_t *)(packet + size) = 0;
|
||||
size += 2; // flags
|
||||
*(uint16_t *)(packet + size) = 0; // no options
|
||||
size += 2; // options size
|
||||
size_t sentLen = STREAMING_MTU - size;
|
||||
if (len < sentLen) sentLen = len;
|
||||
memcpy (packet + size, buf, sentLen);
|
||||
buf += sentLen;
|
||||
len -= sentLen;
|
||||
size += sentLen; // payload
|
||||
}
|
||||
p->len = size;
|
||||
m_Service.post (boost::bind (&Stream::SendPacket, this, p));
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
void Stream::SendQuickAck ()
|
||||
{
|
||||
Packet p;
|
||||
uint8_t * packet = p.GetBuffer ();
|
||||
size_t size = 0;
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_SendStreamID);
|
||||
size += 4; // sendStreamID
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_RecvStreamID);
|
||||
size += 4; // receiveStreamID
|
||||
*(uint32_t *)(packet + size) = 0; // this is plain Ack message
|
||||
size += 4; // sequenceNum
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_LastReceivedSequenceNumber);
|
||||
size += 4; // ack Through
|
||||
packet[size] = 0;
|
||||
size++; // NACK count
|
||||
size++; // resend delay
|
||||
*(uint16_t *)(packet + size) = 0; // nof flags set
|
||||
size += 2; // flags
|
||||
*(uint16_t *)(packet + size) = 0; // no options
|
||||
size += 2; // options size
|
||||
p.len = size;
|
||||
|
||||
SendPackets (std::vector<Packet *> { &p });
|
||||
LogPrint ("Quick Ack sent");
|
||||
}
|
||||
|
||||
void Stream::Close ()
|
||||
{
|
||||
if (m_IsOpen)
|
||||
{
|
||||
m_IsOpen = false;
|
||||
Packet * p = new Packet ();
|
||||
uint8_t * packet = p->GetBuffer ();
|
||||
size_t size = 0;
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_SendStreamID);
|
||||
size += 4; // sendStreamID
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_RecvStreamID);
|
||||
size += 4; // receiveStreamID
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_SequenceNumber++);
|
||||
size += 4; // sequenceNum
|
||||
*(uint32_t *)(packet + size) = htobe32 (m_LastReceivedSequenceNumber);
|
||||
size += 4; // ack Through
|
||||
packet[size] = 0;
|
||||
size++; // NACK count
|
||||
size++; // resend delay
|
||||
*(uint16_t *)(packet + size) = htobe16 (PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED);
|
||||
size += 2; // flags
|
||||
size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen ();
|
||||
*(uint16_t *)(packet + size) = htobe16 (signatureLen); // signature only
|
||||
size += 2; // options size
|
||||
uint8_t * signature = packet + size;
|
||||
memset (packet + size, 0, signatureLen);
|
||||
size += signatureLen; // signature
|
||||
m_LocalDestination.GetOwner ().Sign (packet, size, signature);
|
||||
|
||||
p->len = size;
|
||||
SendPacket (p);
|
||||
LogPrint ("FIN sent");
|
||||
}
|
||||
}
|
||||
|
||||
size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos < len && !m_ReceiveQueue.empty ())
|
||||
{
|
||||
Packet * packet = m_ReceiveQueue.front ();
|
||||
size_t l = std::min (packet->GetLength (), len - pos);
|
||||
memcpy (buf + pos, packet->GetBuffer (), l);
|
||||
pos += l;
|
||||
packet->offset += l;
|
||||
if (!packet->GetLength ())
|
||||
{
|
||||
m_ReceiveQueue.pop ();
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool Stream::SendPacket (Packet * packet)
|
||||
{
|
||||
if (packet)
|
||||
{
|
||||
if (m_IsAckSendScheduled)
|
||||
{
|
||||
m_IsAckSendScheduled = false;
|
||||
m_AckSendTimer.cancel ();
|
||||
}
|
||||
SendPackets (std::vector<Packet *> { packet });
|
||||
if (m_IsOpen)
|
||||
{
|
||||
bool isEmpty = m_SentPackets.empty ();
|
||||
m_SentPackets.insert (packet);
|
||||
if (isEmpty)
|
||||
ScheduleResend ();
|
||||
}
|
||||
else
|
||||
delete packet;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void Stream::SendPackets (const std::vector<Packet *>& packets)
|
||||
{
|
||||
if (!m_RemoteLeaseSet)
|
||||
{
|
||||
UpdateCurrentRemoteLease ();
|
||||
if (!m_RemoteLeaseSet)
|
||||
{
|
||||
LogPrint ("Can't send packets. Missing remote LeaseSet");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
if (ts >= m_CurrentRemoteLease.endDate)
|
||||
UpdateCurrentRemoteLease ();
|
||||
if (ts < m_CurrentRemoteLease.endDate)
|
||||
{
|
||||
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||
for (auto it: packets)
|
||||
{
|
||||
auto msg = m_RoutingSession->WrapSingleMessage (CreateDataMessage (it->GetBuffer (), it->GetLength ()));
|
||||
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeTunnel,
|
||||
m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID,
|
||||
msg
|
||||
});
|
||||
m_NumSentBytes += it->GetLength ();
|
||||
}
|
||||
m_LocalDestination.GetOwner ().SendTunnelDataMsgs (msgs);
|
||||
}
|
||||
else
|
||||
LogPrint ("All leases are expired");
|
||||
}
|
||||
|
||||
void Stream::ScheduleResend ()
|
||||
{
|
||||
m_ResendTimer.cancel ();
|
||||
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_TIMEOUT));
|
||||
m_ResendTimer.async_wait (boost::bind (&Stream::HandleResendTimer,
|
||||
this, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void Stream::HandleResendTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
std::vector<Packet *> packets;
|
||||
for (auto it : m_SentPackets)
|
||||
{
|
||||
it->numResendAttempts++;
|
||||
if (it->numResendAttempts <= MAX_NUM_RESEND_ATTEMPTS)
|
||||
packets.push_back (it);
|
||||
else
|
||||
{
|
||||
Close ();
|
||||
m_IsReset = true;
|
||||
m_ReceiveTimer.cancel ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (packets.size () > 0)
|
||||
{
|
||||
m_LocalDestination.GetOwner ().ResetCurrentOutboundTunnel (); // pick another outbound tunnel
|
||||
UpdateCurrentRemoteLease (); // pick another lease
|
||||
SendPackets (packets);
|
||||
}
|
||||
ScheduleResend ();
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::HandleAckSendTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (m_IsAckSendScheduled)
|
||||
{
|
||||
if (m_IsOpen)
|
||||
SendQuickAck ();
|
||||
m_IsAckSendScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::UpdateCurrentRemoteLease ()
|
||||
{
|
||||
if (!m_RemoteLeaseSet)
|
||||
{
|
||||
m_RemoteLeaseSet = m_LocalDestination.GetOwner ().FindLeaseSet (m_RemoteIdentity.GetIdentHash ());
|
||||
if (!m_RemoteLeaseSet)
|
||||
LogPrint ("LeaseSet ", m_RemoteIdentity.GetIdentHash ().ToBase64 (), " not found");
|
||||
}
|
||||
if (m_RemoteLeaseSet)
|
||||
{
|
||||
if (!m_RoutingSession)
|
||||
m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (*m_RemoteLeaseSet, 32);
|
||||
auto leases = m_RemoteLeaseSet->GetNonExpiredLeases ();
|
||||
if (!leases.empty ())
|
||||
{
|
||||
uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1);
|
||||
m_CurrentRemoteLease = leases[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RemoteLeaseSet = m_LocalDestination.GetOwner ().FindLeaseSet (m_RemoteIdentity.GetIdentHash ()); // re-request expired
|
||||
m_CurrentRemoteLease.endDate = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_CurrentRemoteLease.endDate = 0;
|
||||
}
|
||||
|
||||
I2NPMessage * Stream::CreateDataMessage (const uint8_t * payload, size_t len)
|
||||
{
|
||||
I2NPMessage * msg = NewI2NPShortMessage ();
|
||||
CryptoPP::Gzip compressor;
|
||||
if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)
|
||||
compressor.SetDeflateLevel (CryptoPP::Gzip::MIN_DEFLATE_LEVEL);
|
||||
else
|
||||
compressor.SetDeflateLevel (CryptoPP::Gzip::DEFAULT_DEFLATE_LEVEL);
|
||||
compressor.Put (payload, len);
|
||||
compressor.MessageEnd();
|
||||
int size = compressor.MaxRetrievable ();
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
*(uint32_t *)buf = htobe32 (size); // length
|
||||
buf += 4;
|
||||
compressor.Get (buf, size);
|
||||
*(uint16_t *)(buf + 4) = 0; // source port
|
||||
*(uint16_t *)(buf + 6) = htobe16 (m_Port); // destination port
|
||||
buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol
|
||||
msg->len += size + 4;
|
||||
FillI2NPMessageHeader (msg, eI2NPData);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
void StreamingDestination::Start ()
|
||||
{
|
||||
}
|
||||
|
||||
void StreamingDestination::Stop ()
|
||||
{
|
||||
ResetAcceptor ();
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||
for (auto it: m_Streams)
|
||||
delete it.second;
|
||||
m_Streams.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingDestination::HandleNextPacket (Packet * packet)
|
||||
{
|
||||
uint32_t sendStreamID = packet->GetSendStreamID ();
|
||||
if (sendStreamID)
|
||||
{
|
||||
auto it = m_Streams.find (sendStreamID);
|
||||
if (it != m_Streams.end ())
|
||||
it->second->HandleNextPacket (packet);
|
||||
else
|
||||
{
|
||||
LogPrint ("Unknown stream ", sendStreamID);
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
else // new incoming stream
|
||||
{
|
||||
auto incomingStream = CreateNewIncomingStream ();
|
||||
incomingStream->HandleNextPacket (packet);
|
||||
if (m_Acceptor != nullptr)
|
||||
m_Acceptor (incomingStream);
|
||||
else
|
||||
{
|
||||
LogPrint ("Acceptor for incoming stream is not set");
|
||||
DeleteStream (incomingStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream * StreamingDestination::CreateNewOutgoingStream (const i2p::data::LeaseSet& remote, int port)
|
||||
{
|
||||
Stream * s = new Stream (*m_Owner.GetService (), *this, remote, port);
|
||||
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||
m_Streams[s->GetRecvStreamID ()] = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
Stream * StreamingDestination::CreateNewIncomingStream ()
|
||||
{
|
||||
Stream * s = new Stream (*m_Owner.GetService (), *this);
|
||||
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||
m_Streams[s->GetRecvStreamID ()] = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void StreamingDestination::DeleteStream (Stream * stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||
auto it = m_Streams.find (stream->GetRecvStreamID ());
|
||||
if (it != m_Streams.end ())
|
||||
{
|
||||
m_Streams.erase (it);
|
||||
if (m_Owner.GetService ())
|
||||
m_Owner.GetService ()->post ([stream](void) { delete stream; });
|
||||
else
|
||||
delete stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len)
|
||||
{
|
||||
// unzip it
|
||||
CryptoPP::Gunzip decompressor;
|
||||
decompressor.Put (buf, len);
|
||||
decompressor.MessageEnd();
|
||||
Packet * uncompressed = new Packet;
|
||||
uncompressed->offset = 0;
|
||||
uncompressed->len = decompressor.MaxRetrievable ();
|
||||
if (uncompressed->len <= MAX_PACKET_SIZE)
|
||||
{
|
||||
decompressor.Get (uncompressed->buf, uncompressed->len);
|
||||
HandleNextPacket (uncompressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Received packet size ", uncompressed->len, " exceeds max packet size. Skipped");
|
||||
delete uncompressed;
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteStream (Stream * stream)
|
||||
{
|
||||
if (stream)
|
||||
stream->GetLocalDestination ().DeleteStream (stream);
|
||||
}
|
||||
}
|
||||
}
|
234
Streaming.h
Normal file
234
Streaming.h
Normal file
|
@ -0,0 +1,234 @@
|
|||
#ifndef STREAMING_H__
|
||||
#define STREAMING_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#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 RESEND_TIMEOUT = 10; // in seconds
|
||||
const int ACK_SEND_TIMEOUT = 200; // in milliseconds
|
||||
const int MAX_NUM_RESEND_ATTEMPTS = 5;
|
||||
|
||||
struct Packet
|
||||
{
|
||||
uint8_t buf[MAX_PACKET_SIZE];
|
||||
size_t len, offset;
|
||||
int numResendAttempts;
|
||||
|
||||
Packet (): len (0), offset (0), numResendAttempts (0) {};
|
||||
uint8_t * GetBuffer () { return buf + offset; };
|
||||
size_t GetLength () const { return len - offset; };
|
||||
|
||||
uint32_t GetSendStreamID () const { return be32toh (*(uint32_t *)buf); };
|
||||
uint32_t GetReceiveStreamID () const { return be32toh (*(uint32_t *)(buf + 4)); };
|
||||
uint32_t GetSeqn () const { return be32toh (*(uint32_t *)(buf + 8)); };
|
||||
uint32_t GetAckThrough () const { return be32toh (*(uint32_t *)(buf + 12)); };
|
||||
uint8_t GetNACKCount () const { return buf[16]; };
|
||||
uint32_t GetNACK (int i) const { return be32toh (((uint32_t *)(buf + 17))[i]); };
|
||||
const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags
|
||||
uint16_t GetFlags () const { return be16toh (*(uint16_t *)(GetOption () - 2)); };
|
||||
uint16_t GetOptionSize () const { return be16toh (*(uint16_t *)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 ();
|
||||
};
|
||||
};
|
||||
|
||||
class StreamingDestination;
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
|
||||
Stream (boost::asio::io_service& service, StreamingDestination& local,
|
||||
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; };
|
||||
const i2p::data::LeaseSet * GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
|
||||
const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; };
|
||||
bool IsOpen () const { return m_IsOpen; };
|
||||
bool IsEstablished () const { return m_SendStreamID; };
|
||||
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
|
||||
|
||||
void HandleNextPacket (Packet * packet);
|
||||
size_t Send (const uint8_t * buf, size_t len);
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
|
||||
|
||||
void Close ();
|
||||
|
||||
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 (); };
|
||||
|
||||
private:
|
||||
|
||||
void SendQuickAck ();
|
||||
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 ();
|
||||
|
||||
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);
|
||||
|
||||
I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::io_service& m_Service;
|
||||
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
|
||||
int32_t m_LastReceivedSequenceNumber;
|
||||
bool m_IsOpen, m_IsReset, m_IsAckSendScheduled;
|
||||
StreamingDestination& m_LocalDestination;
|
||||
i2p::data::IdentityEx m_RemoteIdentity;
|
||||
const i2p::data::LeaseSet * m_RemoteLeaseSet;
|
||||
i2p::garlic::GarlicRoutingSession * m_RoutingSession;
|
||||
i2p::data::Lease m_CurrentRemoteLease;
|
||||
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;
|
||||
};
|
||||
|
||||
class StreamingDestination
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::function<void (Stream *)> Acceptor;
|
||||
|
||||
StreamingDestination (i2p::client::ClientDestination& owner): m_Owner (owner) {};
|
||||
~StreamingDestination () {};
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
Stream * CreateNewOutgoingStream (const i2p::data::LeaseSet& remote, int port = 0);
|
||||
void DeleteStream (Stream * stream);
|
||||
void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; };
|
||||
void ResetAcceptor () { m_Acceptor = nullptr; };
|
||||
bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
|
||||
i2p::client::ClientDestination& GetOwner () { return m_Owner; };
|
||||
|
||||
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
void HandleNextPacket (Packet * packet);
|
||||
Stream * CreateNewIncomingStream ();
|
||||
|
||||
private:
|
||||
|
||||
i2p::client::ClientDestination& m_Owner;
|
||||
std::mutex m_StreamsMutex;
|
||||
std::map<uint32_t, Stream *> m_Streams;
|
||||
Acceptor m_Acceptor;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_Streams)& GetStreams () const { return m_Streams; };
|
||||
};
|
||||
|
||||
void DeleteStream (Stream * stream);
|
||||
|
||||
//-------------------------------------------------
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
|
||||
{
|
||||
if (!m_ReceiveQueue.empty ())
|
||||
{
|
||||
m_Service.post ([=](void) { this->HandleReceiveTimer (
|
||||
boost::asio::error::make_error_code (boost::asio::error::operation_aborted),
|
||||
buffer, handler); });
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout));
|
||||
m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode)
|
||||
{ this->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 (ecode == boost::asio::error::operation_aborted)
|
||||
{
|
||||
// timeout not expired
|
||||
if (m_IsOpen)
|
||||
// no error
|
||||
handler (boost::system::error_code (), received);
|
||||
else
|
||||
// socket closed
|
||||
handler (m_IsReset ? boost::asio::error::make_error_code (boost::asio::error::connection_reset) :
|
||||
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
|
32
Timestamp.h
Normal file
32
Timestamp.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#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
|
||||
|
85
TransitTunnel.cpp
Normal file
85
TransitTunnel.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#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):
|
||||
m_TunnelID (receiveTunnelID), m_NextTunnelID (nextTunnelID),
|
||||
m_NextIdent (nextIdent), m_NumTransmittedBytes (0)
|
||||
{
|
||||
m_Encryption.SetKeys (layerKey, ivKey);
|
||||
}
|
||||
|
||||
void TransitTunnel::EncryptTunnelMsg (I2NPMessage * tunnelMsg)
|
||||
{
|
||||
m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4);
|
||||
}
|
||||
|
||||
void TransitTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg)
|
||||
{
|
||||
EncryptTunnelMsg (tunnelMsg);
|
||||
|
||||
LogPrint ("TransitTunnel: ",m_TunnelID,"->", m_NextTunnelID);
|
||||
m_NumTransmittedBytes += tunnelMsg->GetLength ();
|
||||
*(uint32_t *)(tunnelMsg->GetPayload ()) = htobe32 (m_NextTunnelID);
|
||||
FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData);
|
||||
|
||||
i2p::transport::transports.SendMessage (m_NextIdent, tunnelMsg);
|
||||
}
|
||||
|
||||
void TransitTunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg)
|
||||
{
|
||||
LogPrint ("We are not a gateway for transit tunnel ", m_TunnelID);
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
|
||||
void TransitTunnelGateway::SendTunnelDataMsg (i2p::I2NPMessage * msg)
|
||||
{
|
||||
TunnelMessageBlock block;
|
||||
block.deliveryType = eDeliveryTypeLocal;
|
||||
block.data = msg;
|
||||
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||
m_Gateway.SendTunnelDataMsg (block);
|
||||
}
|
||||
|
||||
void TransitTunnelEndpoint::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg)
|
||||
{
|
||||
EncryptTunnelMsg (tunnelMsg);
|
||||
|
||||
LogPrint ("TransitTunnel endpoint for ", GetTunnelID ());
|
||||
m_Endpoint.HandleDecryptedTunnelDataMsg (tunnelMsg);
|
||||
}
|
||||
|
||||
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 ("TransitTunnel endpoint: ", receiveTunnelID, " created");
|
||||
return new TransitTunnelEndpoint (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
else if (isGateway)
|
||||
{
|
||||
LogPrint ("TransitTunnel gateway: ", receiveTunnelID, " created");
|
||||
return new TransitTunnelGateway (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created");
|
||||
return new TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
TransitTunnel.h
Normal file
88
TransitTunnel.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
#ifndef TRANSIT_TUNNEL_H__
|
||||
#define TRANSIT_TUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <mutex>
|
||||
#include "aes.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelEndpoint.h"
|
||||
#include "TunnelGateway.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TransitTunnel: public TunnelBase // tunnel patricipant
|
||||
{
|
||||
public:
|
||||
|
||||
TransitTunnel (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey);
|
||||
|
||||
virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg);
|
||||
virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg);
|
||||
virtual size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; };
|
||||
|
||||
uint32_t GetTunnelID () const { return m_TunnelID; };
|
||||
|
||||
// implements TunnelBase
|
||||
void EncryptTunnelMsg (I2NPMessage * tunnelMsg);
|
||||
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
|
||||
const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; };
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_TunnelID, m_NextTunnelID;
|
||||
i2p::data::IdentHash m_NextIdent;
|
||||
size_t m_NumTransmittedBytes;
|
||||
|
||||
i2p::crypto::TunnelEncryption m_Encryption;
|
||||
};
|
||||
|
||||
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 (i2p::I2NPMessage * msg);
|
||||
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 (i2p::I2NPMessage * tunnelMsg);
|
||||
size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }
|
||||
|
||||
private:
|
||||
|
||||
TunnelEndpoint m_Endpoint;
|
||||
};
|
||||
|
||||
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
|
75
TransportSession.h
Normal file
75
TransportSession.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
#ifndef TRANSPORT_SESSION_H__
|
||||
#define TRANSPORT_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <iostream>
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
struct DHKeysPair // transient keys for transport sessions
|
||||
{
|
||||
uint8_t publicKey[256];
|
||||
uint8_t privateKey[256];
|
||||
};
|
||||
|
||||
class SignedData
|
||||
{
|
||||
public:
|
||||
|
||||
SignedData () {};
|
||||
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 (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 (const i2p::data::RouterInfo * in_RemoteRouter):
|
||||
m_RemoteRouter (in_RemoteRouter), m_DHKeysPair (nullptr)
|
||||
{
|
||||
if (m_RemoteRouter)
|
||||
m_RemoteIdentity = m_RemoteRouter->GetRouterIdentity ();
|
||||
}
|
||||
|
||||
virtual ~TransportSession () { delete m_DHKeysPair; };
|
||||
|
||||
const i2p::data::RouterInfo * GetRemoteRouter () { return m_RemoteRouter; };
|
||||
const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; };
|
||||
|
||||
protected:
|
||||
|
||||
const i2p::data::RouterInfo * m_RemoteRouter;
|
||||
i2p::data::IdentityEx m_RemoteIdentity;
|
||||
DHKeysPair * m_DHKeysPair; // X - for client and Y - for server
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
378
Transports.cpp
Normal file
378
Transports.cpp
Normal file
|
@ -0,0 +1,378 @@
|
|||
#include <cryptopp/dh.h>
|
||||
#include <boost/bind.hpp>
|
||||
#include "Log.h"
|
||||
#include "CryptoConst.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)
|
||||
{
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
i2p::transport::DHKeysPair * pair = new i2p::transport::DHKeysPair ();
|
||||
dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey);
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
m_Queue.push (pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DHKeysPair * DHKeysPairSupplier::Acquire ()
|
||||
{
|
||||
if (!m_Queue.empty ())
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
auto pair = m_Queue.front ();
|
||||
m_Queue.pop ();
|
||||
m_Acquired.notify_one ();
|
||||
return pair;
|
||||
}
|
||||
else // queue is empty, create new
|
||||
{
|
||||
DHKeysPair * pair = new DHKeysPair ();
|
||||
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
|
||||
dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey);
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::Return (DHKeysPair * pair)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
m_Queue.push (pair);
|
||||
}
|
||||
|
||||
Transports transports;
|
||||
|
||||
Transports::Transports ():
|
||||
m_Thread (nullptr), m_Work (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr),
|
||||
m_SSUServer (nullptr), m_DHKeysPairSupplier (5) // 5 pre-generated keys
|
||||
{
|
||||
}
|
||||
|
||||
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 (address.transportStyle == 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 ("Start listening TCP port ", address.port);
|
||||
auto conn = new NTCPServerConnection (m_Service);
|
||||
m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this,
|
||||
conn, boost::asio::placeholders::error));
|
||||
|
||||
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 ("Start listening V6 TCP port ", address.port);
|
||||
auto conn = new NTCPServerConnection (m_Service);
|
||||
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAcceptV6,
|
||||
this, conn, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
else if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ())
|
||||
{
|
||||
if (!m_SSUServer)
|
||||
{
|
||||
m_SSUServer = new SSUServer (address.port);
|
||||
LogPrint ("Start listening UDP port ", address.port);
|
||||
m_SSUServer->Start ();
|
||||
DetectExternalIP ();
|
||||
}
|
||||
else
|
||||
LogPrint ("SSU server already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::Stop ()
|
||||
{
|
||||
if (m_SSUServer)
|
||||
{
|
||||
m_SSUServer->Stop ();
|
||||
delete m_SSUServer;
|
||||
m_SSUServer = nullptr;
|
||||
}
|
||||
|
||||
for (auto session: m_NTCPSessions)
|
||||
delete session.second;
|
||||
m_NTCPSessions.clear ();
|
||||
delete m_NTCPAcceptor;
|
||||
m_NTCPAcceptor = nullptr;
|
||||
delete m_NTCPV6Acceptor;
|
||||
m_NTCPV6Acceptor = 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 ("Transports: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::AddNTCPSession (NTCPSession * session)
|
||||
{
|
||||
if (session)
|
||||
m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session;
|
||||
}
|
||||
|
||||
void Transports::RemoveNTCPSession (NTCPSession * session)
|
||||
{
|
||||
if (session)
|
||||
m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ());
|
||||
}
|
||||
|
||||
void Transports::HandleAccept (NTCPServerConnection * conn, const boost::system::error_code& error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ());
|
||||
conn->ServerLogin ();
|
||||
}
|
||||
else
|
||||
delete conn;
|
||||
|
||||
if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
conn = new NTCPServerConnection (m_Service);
|
||||
m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this,
|
||||
conn, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::HandleAcceptV6 (NTCPServerConnection * conn, const boost::system::error_code& error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ());
|
||||
conn->ServerLogin ();
|
||||
}
|
||||
else
|
||||
delete conn;
|
||||
|
||||
if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
conn = new NTCPServerConnection (m_Service);
|
||||
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAcceptV6, this,
|
||||
conn, boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
NTCPSession * Transports::GetNextNTCPSession ()
|
||||
{
|
||||
for (auto session: m_NTCPSessions)
|
||||
if (session.second->IsEstablished ())
|
||||
return session.second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
NTCPSession * Transports::FindNTCPSession (const i2p::data::IdentHash& ident)
|
||||
{
|
||||
auto it = m_NTCPSessions.find (ident);
|
||||
if (it != m_NTCPSessions.end ())
|
||||
return it->second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg)
|
||||
{
|
||||
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
|
||||
// we send it to ourself
|
||||
i2p::HandleI2NPMessage (msg);
|
||||
else
|
||||
m_Service.post (boost::bind (&Transports::PostMessage, this, ident, msg));
|
||||
}
|
||||
|
||||
void Transports::PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg)
|
||||
{
|
||||
auto session = FindNTCPSession (ident);
|
||||
if (session)
|
||||
session->SendI2NPMessage (msg);
|
||||
else
|
||||
{
|
||||
RouterInfo * r = netdb.FindRouter (ident);
|
||||
if (r)
|
||||
{
|
||||
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (r) : nullptr;
|
||||
if (ssuSession)
|
||||
ssuSession->SendI2NPMessage (msg);
|
||||
else
|
||||
{
|
||||
// existing session not found. create new
|
||||
// try NTCP first if message size < 16K
|
||||
auto address = r->GetNTCPAddress (!context.SupportsV6 ());
|
||||
if (address && !r->UsesIntroducer () && !r->IsUnreachable () && msg->GetLength () < NTCP_MAX_MESSAGE_SIZE)
|
||||
{
|
||||
auto s = new NTCPClient (m_Service, address->host, address->port, *r);
|
||||
AddNTCPSession (s);
|
||||
s->SendI2NPMessage (msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// then SSU
|
||||
auto s = m_SSUServer ? m_SSUServer->GetSession (r) : nullptr;
|
||||
if (s)
|
||||
s->SendI2NPMessage (msg);
|
||||
else
|
||||
{
|
||||
LogPrint ("No NTCP and SSU addresses available");
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Router not found. Requested");
|
||||
i2p::data::netdb.RequestDestination (ident);
|
||||
auto resendTimer = new boost::asio::deadline_timer (m_Service);
|
||||
resendTimer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds
|
||||
resendTimer->async_wait (boost::bind (&Transports::HandleResendTimer,
|
||||
this, boost::asio::placeholders::error, resendTimer, ident, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::HandleResendTimer (const boost::system::error_code& ecode,
|
||||
boost::asio::deadline_timer * timer, const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg)
|
||||
{
|
||||
RouterInfo * r = netdb.FindRouter (ident);
|
||||
if (r)
|
||||
{
|
||||
LogPrint ("Router found. Sending message");
|
||||
PostMessage (ident, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Router not found. Failed to send message");
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
delete timer;
|
||||
}
|
||||
|
||||
void Transports::CloseSession (const i2p::data::RouterInfo * router)
|
||||
{
|
||||
if (!router) return;
|
||||
m_Service.post (boost::bind (&Transports::PostCloseSession, this, router));
|
||||
}
|
||||
|
||||
void Transports::PostCloseSession (const i2p::data::RouterInfo * router)
|
||||
{
|
||||
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr;
|
||||
if (ssuSession) // try SSU first
|
||||
{
|
||||
m_SSUServer->DeleteSession (ssuSession);
|
||||
LogPrint ("SSU session closed");
|
||||
}
|
||||
// TODO: delete NTCP
|
||||
}
|
||||
|
||||
void Transports::DetectExternalIP ()
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto router = i2p::data::netdb.GetRandomRouter ();
|
||||
if (router && router->IsSSU () && m_SSUServer)
|
||||
m_SSUServer->GetSession (router.get (), true); // peer test
|
||||
}
|
||||
}
|
||||
|
||||
DHKeysPair * Transports::GetNextDHKeysPair ()
|
||||
{
|
||||
return m_DHKeysPairSupplier.Acquire ();
|
||||
}
|
||||
|
||||
void Transports::ReuseDHKeysPair (DHKeysPair * pair)
|
||||
{
|
||||
m_DHKeysPairSupplier.Return (pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
Transports.h
Normal file
111
Transports.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
#ifndef TRANSPORTS_H__
|
||||
#define TRANSPORTS_H__
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <cryptopp/osrng.h>
|
||||
#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 ();
|
||||
DHKeysPair * Acquire ();
|
||||
void Return (DHKeysPair * pair);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void CreateDHKeysPairs (int num);
|
||||
|
||||
private:
|
||||
|
||||
const int m_QueueSize;
|
||||
std::queue<DHKeysPair *> m_Queue;
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
std::condition_variable m_Acquired;
|
||||
std::mutex m_AcquiredMutex;
|
||||
CryptoPP::AutoSeededRandomPool m_Rnd;
|
||||
};
|
||||
|
||||
class Transports
|
||||
{
|
||||
public:
|
||||
|
||||
Transports ();
|
||||
~Transports ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
i2p::transport::DHKeysPair * GetNextDHKeysPair ();
|
||||
void ReuseDHKeysPair (DHKeysPair * pair);
|
||||
|
||||
void AddNTCPSession (NTCPSession * session);
|
||||
void RemoveNTCPSession (NTCPSession * session);
|
||||
|
||||
NTCPSession * GetNextNTCPSession ();
|
||||
NTCPSession * FindNTCPSession (const i2p::data::IdentHash& ident);
|
||||
|
||||
void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg);
|
||||
void CloseSession (const i2p::data::RouterInfo * router);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void HandleAccept (NTCPServerConnection * conn, const boost::system::error_code& error);
|
||||
void HandleAcceptV6 (NTCPServerConnection * conn, const boost::system::error_code& error);
|
||||
void HandleResendTimer (const boost::system::error_code& ecode, boost::asio::deadline_timer * timer,
|
||||
const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg);
|
||||
void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg);
|
||||
void PostCloseSession (const i2p::data::RouterInfo * router);
|
||||
|
||||
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::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor;
|
||||
|
||||
std::map<i2p::data::IdentHash, NTCPSession *> m_NTCPSessions;
|
||||
SSUServer * m_SSUServer;
|
||||
|
||||
DHKeysPairSupplier m_DHKeysPairSupplier;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
|
||||
const SSUServer * GetSSUServer () const { return m_SSUServer; };
|
||||
};
|
||||
|
||||
extern Transports transports;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
618
Tunnel.cpp
Normal file
618
Tunnel.cpp
Normal file
|
@ -0,0 +1,618 @@
|
|||
#include "I2PEndian.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cryptopp/sha.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 (TunnelConfig * config):
|
||||
m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending)
|
||||
{
|
||||
}
|
||||
|
||||
Tunnel::~Tunnel ()
|
||||
{
|
||||
delete m_Config;
|
||||
}
|
||||
|
||||
void Tunnel::Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel)
|
||||
{
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
auto numHops = m_Config->GetNumHops ();
|
||||
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
|
||||
I2NPMessage * msg = NewI2NPMessage ();
|
||||
*msg->GetPayload () = numRecords;
|
||||
msg->len += numRecords*sizeof (I2NPBuildRequestRecordElGamalEncrypted) + 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
|
||||
I2NPBuildRequestRecordElGamalEncrypted * records = (I2NPBuildRequestRecordElGamalEncrypted *)(msg->GetPayload () + 1);
|
||||
TunnelHopConfig * hop = m_Config->GetFirstHop ();
|
||||
int i = 0;
|
||||
while (hop)
|
||||
{
|
||||
int idx = recordIndicies[i];
|
||||
EncryptBuildRequestRecord (*hop->router,
|
||||
CreateBuildRequestRecord (hop->router->GetIdentHash (),
|
||||
hop->tunnelID,
|
||||
hop->nextRouter->GetIdentHash (),
|
||||
hop->nextTunnelID,
|
||||
hop->layerKey, hop->ivKey,
|
||||
hop->replyKey, hop->replyIV,
|
||||
hop->next ? rnd.GenerateWord32 () : replyMsgID, // we set replyMsgID for last hop only
|
||||
hop->isGateway, hop->isEndpoint),
|
||||
records[idx]);
|
||||
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];
|
||||
rnd.GenerateBlock ((uint8_t *)(records + idx), sizeof (records[idx]));
|
||||
}
|
||||
|
||||
// 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);
|
||||
decryption.Decrypt((uint8_t *)&records[hop1->recordIndex],
|
||||
sizeof (I2NPBuildRequestRecordElGamalEncrypted),
|
||||
(uint8_t *)&records[hop1->recordIndex]);
|
||||
hop1 = hop1->next;
|
||||
}
|
||||
hop = hop->prev;
|
||||
}
|
||||
FillI2NPMessageHeader (msg, 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 ("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*sizeof (I2NPBuildResponseRecord);
|
||||
decryption.SetIV (hop->replyIV);
|
||||
decryption.Decrypt(record, sizeof (I2NPBuildResponseRecord), record);
|
||||
}
|
||||
else
|
||||
LogPrint ("Tunnel hop index ", idx, " is out of range");
|
||||
hop1 = hop1->prev;
|
||||
}
|
||||
hop = hop->prev;
|
||||
}
|
||||
|
||||
bool established = true;
|
||||
hop = m_Config->GetFirstHop ();
|
||||
while (hop)
|
||||
{
|
||||
I2NPBuildResponseRecord * record = (I2NPBuildResponseRecord *)(msg + 1 + hop->recordIndex*sizeof (I2NPBuildResponseRecord));
|
||||
LogPrint ("Ret code=", (int)record->ret);
|
||||
if (record->ret)
|
||||
// if any of participants declined the tunnel is not established
|
||||
established = false;
|
||||
hop = hop->next;
|
||||
}
|
||||
if (established)
|
||||
{
|
||||
// change reply keys to layer keys
|
||||
hop = m_Config->GetFirstHop ();
|
||||
while (hop)
|
||||
{
|
||||
hop->decryption.SetKeys (hop->layerKey, hop->ivKey);
|
||||
hop = hop->next;
|
||||
}
|
||||
}
|
||||
if (established) m_State = eTunnelStateEstablished;
|
||||
return established;
|
||||
}
|
||||
|
||||
void Tunnel::EncryptTunnelMsg (I2NPMessage * tunnelMsg)
|
||||
{
|
||||
uint8_t * payload = tunnelMsg->GetPayload () + 4;
|
||||
TunnelHopConfig * hop = m_Config->GetLastHop ();
|
||||
while (hop)
|
||||
{
|
||||
hop->decryption.Decrypt (payload);
|
||||
hop = hop->prev;
|
||||
}
|
||||
}
|
||||
|
||||
void InboundTunnel::HandleTunnelDataMsg (I2NPMessage * msg)
|
||||
{
|
||||
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
|
||||
msg->from = this;
|
||||
EncryptTunnelMsg (msg);
|
||||
m_Endpoint.HandleDecryptedTunnelDataMsg (msg);
|
||||
}
|
||||
|
||||
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, 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;
|
||||
|
||||
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||
m_Gateway.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 ();
|
||||
}
|
||||
|
||||
Tunnels tunnels;
|
||||
|
||||
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_ExploratoryPool (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Tunnels::~Tunnels ()
|
||||
{
|
||||
for (auto& it : m_OutboundTunnels)
|
||||
delete it;
|
||||
m_OutboundTunnels.clear ();
|
||||
|
||||
for (auto& it : m_InboundTunnels)
|
||||
delete it.second;
|
||||
m_InboundTunnels.clear ();
|
||||
|
||||
for (auto& it : m_TransitTunnels)
|
||||
delete it.second;
|
||||
m_TransitTunnels.clear ();
|
||||
|
||||
/*for (auto& it : m_PendingTunnels)
|
||||
delete it.second;
|
||||
m_PendingTunnels.clear ();*/
|
||||
|
||||
for (auto& it: m_Pools)
|
||||
delete it.second;
|
||||
m_Pools.clear ();
|
||||
}
|
||||
|
||||
InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID)
|
||||
{
|
||||
auto it = m_InboundTunnels.find(tunnelID);
|
||||
if (it != m_InboundTunnels.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TransitTunnel * Tunnels::GetTransitTunnel (uint32_t tunnelID)
|
||||
{
|
||||
auto it = m_TransitTunnels.find(tunnelID);
|
||||
if (it != m_TransitTunnels.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Tunnel * Tunnels::GetPendingTunnel (uint32_t replyMsgID)
|
||||
{
|
||||
auto it = m_PendingTunnels.find(replyMsgID);
|
||||
if (it != m_PendingTunnels.end () && it->second->GetState () == eTunnelStatePending)
|
||||
{
|
||||
it->second->SetState (eTunnelStateBuildReplyReceived);
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InboundTunnel * Tunnels::GetNextInboundTunnel ()
|
||||
{
|
||||
InboundTunnel * tunnel = nullptr;
|
||||
size_t minReceived = 0;
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (auto it : m_InboundTunnels)
|
||||
{
|
||||
if (!it.second->IsEstablished ()) continue;
|
||||
if (!tunnel || it.second->GetNumReceivedBytes () < minReceived)
|
||||
{
|
||||
tunnel = it.second;
|
||||
minReceived = it.second->GetNumReceivedBytes ();
|
||||
}
|
||||
}
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
OutboundTunnel * Tunnels::GetNextOutboundTunnel ()
|
||||
{
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
uint32_t ind = rnd.GenerateWord32 (0, m_OutboundTunnels.size () - 1), i = 0;
|
||||
OutboundTunnel * tunnel = nullptr;
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (auto it: m_OutboundTunnels)
|
||||
{
|
||||
if (it->IsEstablished ())
|
||||
{
|
||||
tunnel = it;
|
||||
i++;
|
||||
}
|
||||
if (i > ind && tunnel) break;
|
||||
}
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
TunnelPool * Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination& localDestination, int numHops)
|
||||
{
|
||||
auto pool = new TunnelPool (localDestination, numHops);
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
m_Pools[pool->GetIdentHash ()] = pool;
|
||||
return pool;
|
||||
}
|
||||
|
||||
void Tunnels::DeleteTunnelPool (TunnelPool * pool)
|
||||
{
|
||||
if (pool)
|
||||
{
|
||||
StopTunnelPool (pool);
|
||||
m_Pools.erase (pool->GetLocalDestination ().GetIdentHash ());
|
||||
for (auto it: m_PendingTunnels)
|
||||
if (it.second->GetTunnelPool () == pool)
|
||||
it.second->SetTunnelPool (nullptr);
|
||||
delete pool;
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::StopTunnelPool (TunnelPool * pool)
|
||||
{
|
||||
if (pool)
|
||||
{
|
||||
pool->SetActive (false);
|
||||
pool->DetachTunnels ();
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::AddTransitTunnel (TransitTunnel * tunnel)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TransitTunnelsMutex);
|
||||
m_TransitTunnels[tunnel->GetTunnelID ()] = tunnel;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
|
||||
while (msg)
|
||||
{
|
||||
uint32_t tunnelID = be32toh (*(uint32_t *)msg->GetPayload ());
|
||||
InboundTunnel * tunnel = GetInboundTunnel (tunnelID);
|
||||
if (tunnel)
|
||||
tunnel->HandleTunnelDataMsg (msg);
|
||||
else
|
||||
{
|
||||
TransitTunnel * transitTunnel = GetTransitTunnel (tunnelID);
|
||||
if (transitTunnel)
|
||||
transitTunnel->HandleTunnelDataMsg (msg);
|
||||
else
|
||||
{
|
||||
LogPrint ("Tunnel ", tunnelID, " not found");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
msg = m_Queue.Get ();
|
||||
}
|
||||
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
|
||||
{
|
||||
ManageTunnels ();
|
||||
lastTs = ts;
|
||||
}
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint ("Tunnels: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageTunnels ()
|
||||
{
|
||||
ManagePendingTunnels ();
|
||||
ManageInboundTunnels ();
|
||||
ManageOutboundTunnels ();
|
||||
ManageTransitTunnels ();
|
||||
ManageTunnelPools ();
|
||||
}
|
||||
|
||||
void Tunnels::ManagePendingTunnels ()
|
||||
{
|
||||
// check pending tunnel. delete failed or timeout
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_PendingTunnels.begin (); it != m_PendingTunnels.end ();)
|
||||
{
|
||||
auto tunnel = it->second;
|
||||
switch (tunnel->GetState ())
|
||||
{
|
||||
case eTunnelStatePending:
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT)
|
||||
{
|
||||
LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted");
|
||||
delete tunnel;
|
||||
it = m_PendingTunnels.erase (it);
|
||||
}
|
||||
else
|
||||
it++;
|
||||
break;
|
||||
case eTunnelStateBuildFailed:
|
||||
LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted");
|
||||
delete tunnel;
|
||||
it = m_PendingTunnels.erase (it);
|
||||
break;
|
||||
case eTunnelStateBuildReplyReceived:
|
||||
// intermidiate state, will be either established of build failed
|
||||
it++;
|
||||
break;
|
||||
default:
|
||||
it = m_PendingTunnels.erase (it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ("Tunnel ", tunnel->GetTunnelID (), " expired");
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->TunnelExpired (tunnel);
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
it = m_OutboundTunnels.erase (it);
|
||||
}
|
||||
delete tunnel;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tunnel->IsEstablished () && 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
|
||||
InboundTunnel * inboundTunnel = GetNextInboundTunnel ();
|
||||
if (!inboundTunnel) return;
|
||||
LogPrint ("Creating one hop outbound tunnel...");
|
||||
CreateTunnel<OutboundTunnel> (
|
||||
new TunnelConfig (std::vector<const i2p::data::RouterInfo *>
|
||||
{
|
||||
i2p::data::netdb.GetRandomRouter ().get ()
|
||||
},
|
||||
inboundTunnel->GetTunnelConfig ()));
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageInboundTunnels ()
|
||||
{
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
{
|
||||
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
|
||||
{
|
||||
auto tunnel = it->second;
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired");
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->TunnelExpired (tunnel);
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
it = m_InboundTunnels.erase (it);
|
||||
}
|
||||
delete tunnel;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tunnel->IsEstablished () && ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
tunnel->SetState (eTunnelStateExpiring);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_InboundTunnels.empty ())
|
||||
{
|
||||
LogPrint ("Creating zero hops inbound tunnel...");
|
||||
CreateZeroHopsInboundTunnel ();
|
||||
if (!m_ExploratoryPool)
|
||||
m_ExploratoryPool = CreateTunnelPool (i2p::context, 2); // 2-hop exploratory
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5)
|
||||
{
|
||||
// trying to create one more inbound tunnel
|
||||
LogPrint ("Creating one hop inbound tunnel...");
|
||||
CreateTunnel<InboundTunnel> (
|
||||
new TunnelConfig (std::vector<const i2p::data::RouterInfo *>
|
||||
{
|
||||
i2p::data::netdb.GetRandomRouter ().get ()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageTransitTunnels ()
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
|
||||
{
|
||||
if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
LogPrint ("Transit tunnel ", it->second->GetTunnelID (), " expired");
|
||||
auto tmp = it->second;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TransitTunnelsMutex);
|
||||
it = m_TransitTunnels.erase (it);
|
||||
}
|
||||
delete tmp;
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageTunnelPools ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
for (auto it: m_Pools)
|
||||
{
|
||||
TunnelPool * pool = it.second;
|
||||
if (pool->IsActive ())
|
||||
{
|
||||
pool->CreateTunnels ();
|
||||
pool->TestTunnels ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::PostTunnelData (I2NPMessage * msg)
|
||||
{
|
||||
if (msg) m_Queue.Put (msg);
|
||||
}
|
||||
|
||||
template<class TTunnel>
|
||||
TTunnel * Tunnels::CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel)
|
||||
{
|
||||
TTunnel * newTunnel = new TTunnel (config);
|
||||
uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
|
||||
m_PendingTunnels[replyMsgID] = newTunnel;
|
||||
newTunnel->Build (replyMsgID, outboundTunnel);
|
||||
return newTunnel;
|
||||
}
|
||||
|
||||
void Tunnels::AddOutboundTunnel (OutboundTunnel * newTunnel)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.push_back (newTunnel);
|
||||
auto pool = newTunnel->GetTunnelPool ();
|
||||
if (pool && pool->IsActive ())
|
||||
pool->TunnelCreated (newTunnel);
|
||||
else
|
||||
newTunnel->SetTunnelPool (nullptr);
|
||||
}
|
||||
|
||||
void Tunnels::AddInboundTunnel (InboundTunnel * newTunnel)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel;
|
||||
auto pool = newTunnel->GetTunnelPool ();
|
||||
if (!pool)
|
||||
{
|
||||
// build symmetric outbound tunnel
|
||||
CreateTunnel<OutboundTunnel> (newTunnel->GetTunnelConfig ()->Invert (), GetNextOutboundTunnel ());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pool->IsActive ())
|
||||
pool->TunnelCreated (newTunnel);
|
||||
else
|
||||
newTunnel->SetTunnelPool (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Tunnels::CreateZeroHopsInboundTunnel ()
|
||||
{
|
||||
CreateTunnel<InboundTunnel> (
|
||||
new TunnelConfig (std::vector<const i2p::data::RouterInfo *>
|
||||
{
|
||||
&i2p::context.GetRouterInfo ()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
176
Tunnel.h
Normal file
176
Tunnel.h
Normal file
|
@ -0,0 +1,176 @@
|
|||
#ifndef TUNNEL_H__
|
||||
#define TUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "Queue.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_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
|
||||
{
|
||||
public:
|
||||
|
||||
Tunnel (TunnelConfig * config);
|
||||
~Tunnel ();
|
||||
|
||||
void Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel = 0);
|
||||
|
||||
TunnelConfig * GetTunnelConfig () const { return m_Config; }
|
||||
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; };
|
||||
|
||||
TunnelPool * GetTunnelPool () const { return m_Pool; };
|
||||
void SetTunnelPool (TunnelPool * pool) { m_Pool = pool; };
|
||||
|
||||
bool HandleTunnelBuildResponse (uint8_t * msg, size_t len);
|
||||
|
||||
// implements TunnelBase
|
||||
void EncryptTunnelMsg (I2NPMessage * tunnelMsg);
|
||||
uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; };
|
||||
const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); };
|
||||
|
||||
private:
|
||||
|
||||
TunnelConfig * m_Config;
|
||||
TunnelPool * m_Pool; // pool, tunnel belongs to, or null
|
||||
TunnelState m_State;
|
||||
};
|
||||
|
||||
class OutboundTunnel: public Tunnel
|
||||
{
|
||||
public:
|
||||
|
||||
OutboundTunnel (TunnelConfig * config): Tunnel (config), m_Gateway (this) {};
|
||||
|
||||
void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg);
|
||||
void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
|
||||
const i2p::data::RouterInfo * GetEndpointRouter () const
|
||||
{ return GetTunnelConfig ()->GetLastHop ()->router; };
|
||||
size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); };
|
||||
|
||||
// implements TunnelBase
|
||||
uint32_t GetTunnelID () const { return GetNextTunnelID (); };
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_SendMutex;
|
||||
TunnelGateway m_Gateway;
|
||||
};
|
||||
|
||||
class InboundTunnel: public Tunnel
|
||||
{
|
||||
public:
|
||||
|
||||
InboundTunnel (TunnelConfig * config): Tunnel (config), m_Endpoint (true) {};
|
||||
void HandleTunnelDataMsg (I2NPMessage * msg);
|
||||
size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
|
||||
|
||||
// implements TunnelBase
|
||||
uint32_t GetTunnelID () const { return GetTunnelConfig ()->GetLastHop ()->nextTunnelID; };
|
||||
private:
|
||||
|
||||
TunnelEndpoint m_Endpoint;
|
||||
};
|
||||
|
||||
|
||||
class Tunnels
|
||||
{
|
||||
public:
|
||||
|
||||
Tunnels ();
|
||||
~Tunnels ();
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
InboundTunnel * GetInboundTunnel (uint32_t tunnelID);
|
||||
Tunnel * GetPendingTunnel (uint32_t replyMsgID);
|
||||
InboundTunnel * GetNextInboundTunnel ();
|
||||
OutboundTunnel * GetNextOutboundTunnel ();
|
||||
TunnelPool * GetExploratoryPool () const { return m_ExploratoryPool; };
|
||||
TransitTunnel * GetTransitTunnel (uint32_t tunnelID);
|
||||
void AddTransitTunnel (TransitTunnel * tunnel);
|
||||
void AddOutboundTunnel (OutboundTunnel * newTunnel);
|
||||
void AddInboundTunnel (InboundTunnel * newTunnel);
|
||||
void PostTunnelData (I2NPMessage * msg);
|
||||
template<class TTunnel>
|
||||
TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0);
|
||||
TunnelPool * CreateTunnelPool (i2p::garlic::GarlicDestination& localDestination, int numHops);
|
||||
void DeleteTunnelPool (TunnelPool * pool);
|
||||
void StopTunnelPool (TunnelPool * pool);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void ManageTunnels ();
|
||||
void ManageOutboundTunnels ();
|
||||
void ManageInboundTunnels ();
|
||||
void ManageTransitTunnels ();
|
||||
void ManagePendingTunnels ();
|
||||
void ManageTunnelPools ();
|
||||
|
||||
void CreateZeroHopsInboundTunnel ();
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
std::map<uint32_t, Tunnel *> m_PendingTunnels; // by replyMsgID
|
||||
std::mutex m_InboundTunnelsMutex;
|
||||
std::map<uint32_t, InboundTunnel *> m_InboundTunnels;
|
||||
std::mutex m_OutboundTunnelsMutex;
|
||||
std::list<OutboundTunnel *> m_OutboundTunnels;
|
||||
std::mutex m_TransitTunnelsMutex;
|
||||
std::map<uint32_t, TransitTunnel *> m_TransitTunnels;
|
||||
std::mutex m_PoolsMutex;
|
||||
std::map<i2p::data::IdentHash, TunnelPool *> m_Pools;
|
||||
TunnelPool * m_ExploratoryPool;
|
||||
i2p::util::Queue<I2NPMessage> m_Queue;
|
||||
|
||||
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; };
|
||||
};
|
||||
|
||||
extern Tunnels tunnels;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
65
TunnelBase.h
Normal file
65
TunnelBase.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef TUNNEL_BASE_H__
|
||||
#define TUNNEL_BASE_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#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;
|
||||
I2NPMessage * data;
|
||||
};
|
||||
|
||||
class TunnelBase
|
||||
{
|
||||
public:
|
||||
|
||||
//WARNING!!! GetSecondsSinceEpoch() return uint64_t
|
||||
TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
|
||||
virtual ~TunnelBase () {};
|
||||
|
||||
virtual void EncryptTunnelMsg (I2NPMessage * tunnelMsg) = 0;
|
||||
virtual uint32_t GetNextTunnelID () const = 0;
|
||||
virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0;
|
||||
virtual uint32_t GetTunnelID () const = 0; // as known at our side
|
||||
|
||||
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||
void SetCreationTime (uint32_t t) { m_CreationTime = t; };
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_CreationTime; // seconds since epoch
|
||||
};
|
||||
|
||||
struct TunnelCreationTimeCmp
|
||||
{
|
||||
bool operator() (const TunnelBase * t1, const TunnelBase * t2) const
|
||||
{
|
||||
if (t1->GetCreationTime () != t2->GetCreationTime ())
|
||||
return t1->GetCreationTime () > t2->GetCreationTime ();
|
||||
else
|
||||
return t1 < t2;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
222
TunnelConfig.h
Normal file
222
TunnelConfig.h
Normal file
|
@ -0,0 +1,222 @@
|
|||
#ifndef TUNNEL_CONFIG_H__
|
||||
#define TUNNEL_CONFIG_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "aes.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
struct TunnelHopConfig
|
||||
{
|
||||
const i2p::data::RouterInfo * router, * nextRouter;
|
||||
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;
|
||||
i2p::crypto::TunnelDecryption decryption;
|
||||
int recordIndex; // record # in tunnel build message
|
||||
|
||||
TunnelHopConfig (const i2p::data::RouterInfo * r)
|
||||
{
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
rnd.GenerateBlock (layerKey, 32);
|
||||
rnd.GenerateBlock (ivKey, 32);
|
||||
rnd.GenerateBlock (replyIV, 16);
|
||||
tunnelID = rnd.GenerateWord32 ();
|
||||
isGateway = true;
|
||||
isEndpoint = true;
|
||||
router = r;
|
||||
nextRouter = 0;
|
||||
nextTunnelID = 0;
|
||||
|
||||
next = 0;
|
||||
prev = 0;
|
||||
}
|
||||
|
||||
void SetNextRouter (const i2p::data::RouterInfo * r)
|
||||
{
|
||||
nextRouter = r;
|
||||
isEndpoint = false;
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
nextTunnelID = rnd.GenerateWord32 ();
|
||||
}
|
||||
|
||||
void SetReplyHop (const TunnelHopConfig * replyFirstHop)
|
||||
{
|
||||
nextRouter = replyFirstHop->router;
|
||||
nextTunnelID = replyFirstHop->tunnelID;
|
||||
isEndpoint = true;
|
||||
}
|
||||
|
||||
void SetNext (TunnelHopConfig * n)
|
||||
{
|
||||
next = n;
|
||||
if (next)
|
||||
{
|
||||
next->prev = this;
|
||||
next->isGateway = false;
|
||||
isEndpoint = false;
|
||||
nextRouter = next->router;
|
||||
nextTunnelID = next->tunnelID;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPrev (TunnelHopConfig * p)
|
||||
{
|
||||
prev = p;
|
||||
if (prev)
|
||||
{
|
||||
prev->next = this;
|
||||
prev->isEndpoint = false;
|
||||
isGateway = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TunnelConfig
|
||||
{
|
||||
public:
|
||||
|
||||
|
||||
TunnelConfig (std::vector<const i2p::data::RouterInfo *> peers,
|
||||
const TunnelConfig * replyTunnelConfig = nullptr) // replyTunnelConfig=nullptr means inbound
|
||||
{
|
||||
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;
|
||||
|
||||
if (replyTunnelConfig) // outbound
|
||||
{
|
||||
m_FirstHop->isGateway = false;
|
||||
m_LastHop->SetReplyHop (replyTunnelConfig->GetFirstHop ());
|
||||
}
|
||||
else // inbound
|
||||
m_LastHop->SetNextRouter (&i2p::context.GetRouterInfo ());
|
||||
}
|
||||
|
||||
~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;
|
||||
}
|
||||
|
||||
void Print (std::stringstream& s) const
|
||||
{
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
if (!m_FirstHop->isGateway)
|
||||
s << "me";
|
||||
s << "-->" << m_FirstHop->tunnelID;
|
||||
while (hop)
|
||||
{
|
||||
s << ":" << hop->router->GetIdentHashAbbreviation () << "-->";
|
||||
if (!hop->isEndpoint)
|
||||
s << hop->nextTunnelID;
|
||||
else
|
||||
return;
|
||||
hop = hop->next;
|
||||
}
|
||||
// we didn't reach enpoint that mean we are last hop
|
||||
s << ":me";
|
||||
}
|
||||
|
||||
TunnelConfig * Invert () const
|
||||
{
|
||||
TunnelConfig * newConfig = new TunnelConfig ();
|
||||
TunnelHopConfig * hop = m_FirstHop, * nextNewHop = nullptr;
|
||||
while (hop)
|
||||
{
|
||||
TunnelHopConfig * newHop = new TunnelHopConfig (hop->router);
|
||||
if (nextNewHop)
|
||||
newHop->SetNext (nextNewHop);
|
||||
nextNewHop = newHop;
|
||||
newHop->isEndpoint = hop->isGateway;
|
||||
newHop->isGateway = hop->isEndpoint;
|
||||
|
||||
if (!hop->prev) // first hop
|
||||
{
|
||||
newConfig->m_LastHop = newHop;
|
||||
if (hop->isGateway) // inbound tunnel
|
||||
newHop->SetReplyHop (m_FirstHop); // use it as reply tunnel
|
||||
else
|
||||
newHop->SetNextRouter (&i2p::context.GetRouterInfo ());
|
||||
}
|
||||
if (!hop->next) newConfig->m_FirstHop = newHop; // last hop
|
||||
|
||||
hop = hop->next;
|
||||
}
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
TunnelConfig * Clone (const TunnelConfig * replyTunnelConfig = nullptr) const
|
||||
{
|
||||
std::vector<const i2p::data::RouterInfo *> peers;
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
while (hop)
|
||||
{
|
||||
peers.push_back (hop->router);
|
||||
hop = hop->next;
|
||||
}
|
||||
return new TunnelConfig (peers, replyTunnelConfig);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// this constructor can't be called from outside
|
||||
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
TunnelHopConfig * m_FirstHop, * m_LastHop;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
270
TunnelEndpoint.cpp
Normal file
270
TunnelEndpoint.cpp
Normal file
|
@ -0,0 +1,270 @@
|
|||
#include "I2PEndian.h"
|
||||
#include <string.h>
|
||||
#include "Log.h"
|
||||
#include "NetDb.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Transports.h"
|
||||
#include "RouterContext.h"
|
||||
#include "TunnelEndpoint.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TunnelEndpoint::~TunnelEndpoint ()
|
||||
{
|
||||
for (auto it: m_IncompleteMessages)
|
||||
i2p::DeleteI2NPMessage (it.second.data);
|
||||
for (auto it: m_OutOfSequenceFragments)
|
||||
i2p::DeleteI2NPMessage (it.second.data);
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleDecryptedTunnelDataMsg (I2NPMessage * msg)
|
||||
{
|
||||
m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE;
|
||||
|
||||
uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16
|
||||
uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum
|
||||
if (zero)
|
||||
{
|
||||
LogPrint ("TunnelMessage: zero found at ", (int)(zero-decrypted));
|
||||
uint8_t * fragment = zero + 1;
|
||||
// verify checksum
|
||||
memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end
|
||||
uint8_t hash[32];
|
||||
CryptoPP::SHA256().CalculateDigest (hash, fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16); // payload + iv
|
||||
if (memcmp (hash, decrypted, 4))
|
||||
{
|
||||
LogPrint ("TunnelMessage: checksum verification failed");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
return;
|
||||
}
|
||||
// process fragments
|
||||
while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
|
||||
{
|
||||
uint8_t flag = fragment[0];
|
||||
fragment++;
|
||||
|
||||
bool isFollowOnFragment = flag & 0x80, isLastFragment = true;
|
||||
uint32_t msgID = 0;
|
||||
int fragmentNum = 0;
|
||||
TunnelMessageBlockEx m;
|
||||
if (!isFollowOnFragment)
|
||||
{
|
||||
// first fragment
|
||||
|
||||
m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03);
|
||||
switch (m.deliveryType)
|
||||
{
|
||||
case eDeliveryTypeLocal: // 0
|
||||
LogPrint ("Delivery type local");
|
||||
break;
|
||||
case eDeliveryTypeTunnel: // 1
|
||||
LogPrint ("Delivery type tunnel");
|
||||
m.tunnelID = be32toh (*(uint32_t *)fragment);
|
||||
fragment += 4; // tunnelID
|
||||
m.hash = i2p::data::IdentHash (fragment);
|
||||
fragment += 32; // hash
|
||||
break;
|
||||
case eDeliveryTypeRouter: // 2
|
||||
LogPrint ("Delivery type router");
|
||||
m.hash = i2p::data::IdentHash (fragment);
|
||||
fragment += 32; // to hash
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
bool isFragmented = flag & 0x08;
|
||||
if (isFragmented)
|
||||
{
|
||||
// Message ID
|
||||
msgID = be32toh (*(uint32_t *)fragment);
|
||||
fragment += 4;
|
||||
LogPrint ("Fragmented message ", msgID);
|
||||
isLastFragment = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// follow on
|
||||
msgID = be32toh (*(uint32_t *)fragment); // MessageID
|
||||
fragment += 4;
|
||||
fragmentNum = (flag >> 1) & 0x3F; // 6 bits
|
||||
isLastFragment = flag & 0x01;
|
||||
LogPrint ("Follow on fragment ", fragmentNum, " of message ", msgID, isLastFragment ? " last" : " non-last");
|
||||
}
|
||||
|
||||
uint16_t size = be16toh (*(uint16_t *)fragment);
|
||||
fragment += 2;
|
||||
LogPrint ("Fragment size=", (int)size);
|
||||
|
||||
msg->offset = fragment - msg->buf;
|
||||
msg->len = msg->offset + size;
|
||||
if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
|
||||
{
|
||||
// this is not last message. we have to copy it
|
||||
m.data = NewI2NPMessage ();
|
||||
m.data->offset += sizeof (TunnelGatewayHeader); // reserve room for TunnelGateway header
|
||||
m.data->len += sizeof (TunnelGatewayHeader);
|
||||
*(m.data) = *msg;
|
||||
}
|
||||
else
|
||||
m.data = msg;
|
||||
|
||||
if (!isFollowOnFragment && isLastFragment)
|
||||
HandleNextMessage (m);
|
||||
else
|
||||
{
|
||||
if (msgID) // msgID is presented, assume message is fragmented
|
||||
{
|
||||
if (!isFollowOnFragment) // create new incomlete message
|
||||
{
|
||||
m.nextFragmentNum = 1;
|
||||
auto& msg = m_IncompleteMessages[msgID];
|
||||
msg = m;
|
||||
HandleOutOfSequenceFragment (msgID, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
m.nextFragmentNum = fragmentNum;
|
||||
HandleFollowOnFragment (msgID, isLastFragment, m);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint ("Message is fragmented, but msgID is not presented");
|
||||
}
|
||||
|
||||
fragment += size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("TunnelMessage: zero not found");
|
||||
i2p::DeleteI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m)
|
||||
{
|
||||
auto fragment = m.data->GetBuffer ();
|
||||
auto size = m.data->GetLength ();
|
||||
auto it = m_IncompleteMessages.find (msgID);
|
||||
if (it != m_IncompleteMessages.end())
|
||||
{
|
||||
auto& msg = it->second;
|
||||
if (m.nextFragmentNum == msg.nextFragmentNum)
|
||||
{
|
||||
if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if messega is not too long
|
||||
{
|
||||
memcpy (msg.data->buf + msg.data->len, fragment, size); // concatenate fragment
|
||||
msg.data->len += size;
|
||||
if (isLastFragment)
|
||||
{
|
||||
// message complete
|
||||
HandleNextMessage (msg);
|
||||
m_IncompleteMessages.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.nextFragmentNum++;
|
||||
HandleOutOfSequenceFragment (msgID, msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped");
|
||||
i2p::DeleteI2NPMessage (msg.data);
|
||||
m_IncompleteMessages.erase (it);
|
||||
}
|
||||
i2p::DeleteI2NPMessage (m.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ". Saved");
|
||||
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("First fragment of message ", msgID, " not found. Saved");
|
||||
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, I2NPMessage * data)
|
||||
{
|
||||
auto it = m_OutOfSequenceFragments.find (msgID);
|
||||
if (it == m_OutOfSequenceFragments.end ())
|
||||
m_OutOfSequenceFragments.insert (std::pair<uint32_t, Fragment> (msgID, {fragmentNum, isLastFragment, data}));
|
||||
else
|
||||
i2p::DeleteI2NPMessage (data);
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg)
|
||||
{
|
||||
auto it = m_OutOfSequenceFragments.find (msgID);
|
||||
if (it != m_OutOfSequenceFragments.end ())
|
||||
{
|
||||
if (it->second.fragmentNum == msg.nextFragmentNum)
|
||||
{
|
||||
LogPrint ("Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found");
|
||||
auto size = it->second.data->GetLength ();
|
||||
memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment
|
||||
msg.data->len += size;
|
||||
if (it->second.isLastFragment)
|
||||
{
|
||||
// message complete
|
||||
HandleNextMessage (msg);
|
||||
m_IncompleteMessages.erase (msgID);
|
||||
}
|
||||
else
|
||||
msg.nextFragmentNum++;
|
||||
i2p::DeleteI2NPMessage (it->second.data);
|
||||
m_OutOfSequenceFragments.erase (it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg)
|
||||
{
|
||||
LogPrint ("TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)msg.data->GetHeader()->typeID);
|
||||
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:
|
||||
if (msg.hash == i2p::context.GetRouterInfo ().GetIdentHash ()) // check if message is sent to us
|
||||
i2p::HandleI2NPMessage (msg.data);
|
||||
else
|
||||
{
|
||||
// to somebody else
|
||||
if (!m_IsInbound) // outbound transit tunnel
|
||||
{
|
||||
if (msg.data->GetHeader()->typeID == eI2NPDatabaseStore ||
|
||||
msg.data->GetHeader()->typeID == eI2NPDatabaseSearchReply )
|
||||
{
|
||||
// catch RI or reply with new list of routers
|
||||
auto ds = NewI2NPMessage ();
|
||||
*ds = *(msg.data);
|
||||
i2p::data::netdb.PostI2NPMsg (ds);
|
||||
}
|
||||
i2p::transport::transports.SendMessage (msg.hash, msg.data);
|
||||
}
|
||||
else // we shouldn't send this message. possible leakage
|
||||
{
|
||||
LogPrint ("Message to another router arrived from an inbound tunnel. Dropped");
|
||||
i2p::DeleteI2NPMessage (msg.data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LogPrint ("TunnelMessage: Unknown delivery type ", (int)msg.deliveryType);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
54
TunnelEndpoint.h
Normal file
54
TunnelEndpoint.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
#ifndef TUNNEL_ENDPOINT_H__
|
||||
#define TUNNEL_ENDPOINT_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TunnelEndpoint
|
||||
{
|
||||
struct TunnelMessageBlockEx: public TunnelMessageBlock
|
||||
{
|
||||
uint8_t nextFragmentNum;
|
||||
};
|
||||
|
||||
struct Fragment
|
||||
{
|
||||
uint8_t fragmentNum;
|
||||
bool isLastFragment;
|
||||
I2NPMessage * data;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {};
|
||||
~TunnelEndpoint ();
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
|
||||
void HandleDecryptedTunnelDataMsg (I2NPMessage * msg);
|
||||
|
||||
private:
|
||||
|
||||
void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m);
|
||||
void HandleNextMessage (const TunnelMessageBlock& msg);
|
||||
|
||||
void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, I2NPMessage * data);
|
||||
void HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg);
|
||||
|
||||
private:
|
||||
|
||||
std::map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages;
|
||||
std::map<uint32_t, Fragment> m_OutOfSequenceFragments;
|
||||
bool m_IsInbound;
|
||||
size_t m_NumReceivedBytes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,14 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2024, 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 "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include <cryptopp/sha.h>
|
||||
#include "Log.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Transports.h"
|
||||
|
@ -18,69 +10,38 @@ namespace i2p
|
|||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TunnelGatewayBuffer::TunnelGatewayBuffer ():
|
||||
m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0), m_NonZeroRandomBuffer (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
TunnelGatewayBuffer::~TunnelGatewayBuffer ()
|
||||
{
|
||||
ClearTunnelDataMsgs ();
|
||||
if (m_NonZeroRandomBuffer) delete[] m_NonZeroRandomBuffer;
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
bool messageCreated = false;
|
||||
if (!m_CurrentTunnelDataMsg)
|
||||
{
|
||||
{
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
if (block.data && block.data->onDrop)
|
||||
{
|
||||
// onDrop is called for the first fragment in tunnel message
|
||||
// that's usually true for short TBMs or lookups
|
||||
m_CurrentTunnelDataMsg->onDrop = block.data->onDrop;
|
||||
block.data->onDrop = nullptr;
|
||||
}
|
||||
messageCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// create delivery instructions
|
||||
uint8_t di[43]; // max delivery instruction length is 43 for tunnel
|
||||
size_t diLen = 1;// flag
|
||||
if (block.deliveryType != eDeliveryTypeLocal) // tunnel or router
|
||||
{
|
||||
{
|
||||
if (block.deliveryType == eDeliveryTypeTunnel)
|
||||
{
|
||||
htobe32buf (di + diLen, block.tunnelID);
|
||||
*(uint32_t *)(di + diLen) = htobe32 (block.tunnelID);
|
||||
diLen += 4; // tunnelID
|
||||
}
|
||||
|
||||
|
||||
memcpy (di + diLen, block.hash, 32);
|
||||
diLen += 32; //len
|
||||
}
|
||||
}
|
||||
di[0] = block.deliveryType << 5; // set delivery type
|
||||
|
||||
// create fragments
|
||||
const std::shared_ptr<I2NPMessage> & msg = block.data;
|
||||
size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
|
||||
|
||||
if (!messageCreated && fullMsgLen > m_RemainingSize) // check if we should complete previous message
|
||||
{
|
||||
size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
// length of bytes doesn't fit full tunnel message
|
||||
// every follow-on fragment adds 7 bytes
|
||||
size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
if (!nonFit || nonFit > m_RemainingSize || m_RemainingSize < fullMsgLen/5)
|
||||
{
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
}
|
||||
}
|
||||
I2NPMessage * msg = block.data;
|
||||
auto fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
|
||||
if (fullMsgLen <= m_RemainingSize)
|
||||
{
|
||||
// message fits. First and last fragment
|
||||
htobe16buf (di + diLen, msg->GetLength ());
|
||||
*(uint16_t *)(di + diLen) = htobe16 (msg->GetLength ());
|
||||
diLen += 2; // size
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), msg->GetLength ());
|
||||
|
@ -88,21 +49,33 @@ namespace tunnel
|
|||
m_RemainingSize -= diLen + msg->GetLength ();
|
||||
if (!m_RemainingSize)
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
}
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!messageCreated) // check if we should complete previous message
|
||||
{
|
||||
auto numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
// length of bytes don't fit full tunnel message
|
||||
// every follow-on fragment adds 7 bytes
|
||||
auto nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
if (!nonFit || nonFit > m_RemainingSize)
|
||||
{
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
}
|
||||
}
|
||||
if (diLen + 6 <= m_RemainingSize)
|
||||
{
|
||||
// delivery instructions fit
|
||||
uint32_t msgID;
|
||||
memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // in network bytes order
|
||||
uint32_t msgID = msg->GetHeader ()->msgID; // in network bytes order
|
||||
size_t size = m_RemainingSize - diLen - 6; // 6 = 4 (msgID) + 2 (size)
|
||||
|
||||
// first fragment
|
||||
di[0] |= 0x08; // fragmented
|
||||
htobuf32 (di + diLen, msgID);
|
||||
*(uint32_t *)(di + diLen) = msgID;
|
||||
diLen += 4; // Message ID
|
||||
htobe16buf (di + diLen, size);
|
||||
*(uint16_t *)(di + diLen) = htobe16 (size);
|
||||
diLen += 2; // size
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size);
|
||||
|
@ -111,133 +84,113 @@ namespace tunnel
|
|||
// follow on fragments
|
||||
int fragmentNumber = 1;
|
||||
while (size < msg->GetLength ())
|
||||
{
|
||||
{
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
uint8_t * buf = m_CurrentTunnelDataMsg->GetBuffer ();
|
||||
buf[0] = 0x80 | (fragmentNumber << 1); // frag
|
||||
bool isLastFragment = false;
|
||||
size_t s = msg->GetLength () - size;
|
||||
if (s > TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7) // 7 follow on instructions
|
||||
s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7;
|
||||
s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7;
|
||||
else // last fragment
|
||||
{
|
||||
{
|
||||
buf[0] |= 0x01;
|
||||
isLastFragment = true;
|
||||
}
|
||||
htobuf32 (buf + 1, msgID); //Message ID
|
||||
htobe16buf (buf + 5, s); // size
|
||||
}
|
||||
*(uint32_t *)(buf + 1) = msgID; //Message ID
|
||||
*(uint16_t *)(buf + 5) = htobe16 (s); // size
|
||||
memcpy (buf + 7, msg->GetBuffer () + size, s);
|
||||
m_CurrentTunnelDataMsg->len += s+7;
|
||||
if (isLastFragment)
|
||||
{
|
||||
if(m_RemainingSize < (s+7)) {
|
||||
LogPrint (eLogError, "TunnelGateway: remaining size overflow: ", m_RemainingSize, " < ", s+7);
|
||||
} else {
|
||||
m_RemainingSize -= s+7;
|
||||
if (m_RemainingSize == 0)
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
}
|
||||
m_RemainingSize -= s+7;
|
||||
if (!m_RemainingSize)
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
}
|
||||
else
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
size += s;
|
||||
fragmentNumber++;
|
||||
}
|
||||
}
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delivery instructions don't fit. Create new message
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
PutI2NPMsg (block);
|
||||
// don't delete msg because it's taken care inside
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TunnelGatewayBuffer::ClearTunnelDataMsgs ()
|
||||
{
|
||||
m_TunnelDataMsgs.clear ();
|
||||
m_CurrentTunnelDataMsg = nullptr;
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage ()
|
||||
{
|
||||
m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size
|
||||
m_CurrentTunnelDataMsg = NewI2NPMessage ();
|
||||
// we reserve space for padding
|
||||
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE;
|
||||
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + sizeof (I2NPHeader);
|
||||
m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset;
|
||||
m_RemainingSize = TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::CompleteCurrentTunnelDataMessage ()
|
||||
{
|
||||
if (!m_CurrentTunnelDataMsg) return;
|
||||
uint8_t * payload = m_CurrentTunnelDataMsg->GetBuffer ();
|
||||
size_t size = m_CurrentTunnelDataMsg->len - m_CurrentTunnelDataMsg->offset;
|
||||
|
||||
m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE;
|
||||
|
||||
m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - sizeof (I2NPHeader);
|
||||
uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload ();
|
||||
RAND_bytes (buf + 4, 16); // original IV
|
||||
memcpy (payload + size, buf + 4, 16); // copy IV for checksum
|
||||
*(uint32_t *)(buf) = htobe32 (m_TunnelID);
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
rnd.GenerateBlock (buf + 4, 16); // original IV
|
||||
memcpy (payload + size, buf + 4, 16); // copy IV for checksum
|
||||
uint8_t hash[32];
|
||||
SHA256(payload, size+16, hash);
|
||||
memcpy (buf+20, hash, 4); // checksum
|
||||
payload[-1] = 0; // zero
|
||||
ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1
|
||||
CryptoPP::SHA256().CalculateDigest (hash, payload, size+16);
|
||||
memcpy (buf+20, hash, 4); // checksum
|
||||
payload[-1] = 0; // zero
|
||||
ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1
|
||||
if (paddingSize > 0)
|
||||
{
|
||||
// non-zero padding
|
||||
if (!m_NonZeroRandomBuffer) // first time?
|
||||
{
|
||||
m_NonZeroRandomBuffer = new uint8_t[TUNNEL_DATA_MAX_PAYLOAD_SIZE];
|
||||
RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE);
|
||||
for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++)
|
||||
if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1;
|
||||
}
|
||||
auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1);
|
||||
memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize);
|
||||
}
|
||||
memset (buf + 24, 1, paddingSize); // padding TODO: fill with random data
|
||||
|
||||
// we can't fill message header yet because encryption is required
|
||||
m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg);
|
||||
m_CurrentTunnelDataMsg = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
if (block.data)
|
||||
{
|
||||
{
|
||||
PutTunnelDataMsg (block);
|
||||
SendBuffer ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelGateway::PutTunnelDataMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
if (block.data)
|
||||
m_Buffer.PutI2NPMsg (block);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelGateway::SendBuffer ()
|
||||
{
|
||||
// create list or tunnel messages
|
||||
m_Buffer.CompleteCurrentTunnelDataMessage ();
|
||||
std::list<std::shared_ptr<I2NPMessage> > newTunnelMsgs;
|
||||
const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs ();
|
||||
for (auto& tunnelMsg : tunnelDataMsgs)
|
||||
{
|
||||
auto newMsg = CreateEmptyTunnelDataMsg (false);
|
||||
m_Tunnel.EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||
htobe32buf (newMsg->GetPayload (), m_Tunnel.GetNextTunnelID ());
|
||||
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||
if (tunnelMsg->onDrop) newMsg->onDrop = tunnelMsg->onDrop;
|
||||
newTunnelMsgs.push_back (newMsg);
|
||||
auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs ();
|
||||
for (auto tunnelMsg : tunnelMsgs)
|
||||
{
|
||||
m_Tunnel->EncryptTunnelMsg (tunnelMsg);
|
||||
FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData);
|
||||
i2p::transport::transports.SendMessage (m_Tunnel->GetNextIdentHash (), tunnelMsg);
|
||||
m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
|
||||
}
|
||||
}
|
||||
m_Buffer.ClearTunnelDataMsgs ();
|
||||
// send
|
||||
if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
|
||||
m_Sender->SendMessagesTo (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
TunnelGateway.h
Normal file
55
TunnelGateway.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef TUNNEL_GATEWAY_H__
|
||||
#define TUNNEL_GATEWAY_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TunnelGatewayBuffer
|
||||
{
|
||||
public:
|
||||
TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID),
|
||||
m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) {};
|
||||
void PutI2NPMsg (const TunnelMessageBlock& block);
|
||||
const std::vector<I2NPMessage *>& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; };
|
||||
void ClearTunnelDataMsgs ();
|
||||
void CompleteCurrentTunnelDataMessage ();
|
||||
|
||||
private:
|
||||
|
||||
void CreateCurrentTunnelDataMessage ();
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_TunnelID;
|
||||
std::vector<I2NPMessage *> m_TunnelDataMsgs;
|
||||
I2NPMessage * m_CurrentTunnelDataMsg;
|
||||
size_t m_RemainingSize;
|
||||
};
|
||||
|
||||
class TunnelGateway
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelGateway (TunnelBase * tunnel):
|
||||
m_Tunnel (tunnel), m_Buffer (tunnel->GetNextTunnelID ()), m_NumSentBytes (0) {};
|
||||
void SendTunnelDataMsg (const TunnelMessageBlock& block);
|
||||
void PutTunnelDataMsg (const TunnelMessageBlock& block);
|
||||
void SendBuffer ();
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
|
||||
private:
|
||||
|
||||
TunnelBase * m_Tunnel;
|
||||
TunnelGatewayBuffer m_Buffer;
|
||||
size_t m_NumSentBytes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
330
TunnelPool.cpp
Normal file
330
TunnelPool.cpp
Normal file
|
@ -0,0 +1,330 @@
|
|||
#include "I2PEndian.h"
|
||||
#include "CryptoConst.h"
|
||||
#include "Tunnel.h"
|
||||
#include "NetDb.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Garlic.h"
|
||||
#include "TunnelPool.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TunnelPool::TunnelPool (i2p::garlic::GarlicDestination& localDestination, int numHops, int numTunnels):
|
||||
m_LocalDestination (localDestination), m_NumHops (numHops), m_NumTunnels (numTunnels),
|
||||
m_IsActive (true)
|
||||
{
|
||||
}
|
||||
|
||||
TunnelPool::~TunnelPool ()
|
||||
{
|
||||
DetachTunnels ();
|
||||
}
|
||||
|
||||
void TunnelPool::DetachTunnels ()
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (auto it: m_InboundTunnels)
|
||||
it->SetTunnelPool (nullptr);
|
||||
m_InboundTunnels.clear ();
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (auto it: m_OutboundTunnels)
|
||||
it->SetTunnelPool (nullptr);
|
||||
m_OutboundTunnels.clear ();
|
||||
}
|
||||
m_Tests.clear ();
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelCreated (InboundTunnel * createdTunnel)
|
||||
{
|
||||
if (!m_IsActive) return;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.insert (createdTunnel);
|
||||
}
|
||||
m_LocalDestination.SetLeaseSetUpdated ();
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelExpired (InboundTunnel * expiredTunnel)
|
||||
{
|
||||
if (expiredTunnel)
|
||||
{
|
||||
expiredTunnel->SetTunnelPool (nullptr);
|
||||
for (auto it: m_Tests)
|
||||
if (it.second.second == expiredTunnel) it.second.second = nullptr;
|
||||
RecreateInboundTunnel (expiredTunnel);
|
||||
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.erase (expiredTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelCreated (OutboundTunnel * createdTunnel)
|
||||
{
|
||||
if (!m_IsActive) return;
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.insert (createdTunnel);
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelExpired (OutboundTunnel * expiredTunnel)
|
||||
{
|
||||
if (expiredTunnel)
|
||||
{
|
||||
expiredTunnel->SetTunnelPool (nullptr);
|
||||
for (auto it: m_Tests)
|
||||
if (it.second.first == expiredTunnel) it.second.first = nullptr;
|
||||
RecreateOutboundTunnel (expiredTunnel);
|
||||
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.erase (expiredTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<InboundTunnel *> TunnelPool::GetInboundTunnels (int num) const
|
||||
{
|
||||
std::vector<InboundTunnel *> v;
|
||||
int i = 0;
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (auto it : m_InboundTunnels)
|
||||
{
|
||||
if (i >= num) break;
|
||||
if (it->IsEstablished ())
|
||||
{
|
||||
v.push_back (it);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
OutboundTunnel * TunnelPool::GetNextOutboundTunnel (OutboundTunnel * suggested) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
return GetNextTunnel (m_OutboundTunnels, suggested);
|
||||
}
|
||||
|
||||
InboundTunnel * TunnelPool::GetNextInboundTunnel (InboundTunnel * suggested) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
return GetNextTunnel (m_InboundTunnels, suggested);
|
||||
}
|
||||
|
||||
template<class TTunnels>
|
||||
typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels,
|
||||
typename TTunnels::value_type suggested) const
|
||||
{
|
||||
if (tunnels.empty ()) return nullptr;
|
||||
if (suggested && tunnels.count (suggested) > 0 && suggested->IsEstablished ())
|
||||
return suggested;
|
||||
|
||||
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
uint32_t ind = rnd.GenerateWord32 (0, tunnels.size ()/2), i = 0;
|
||||
typename TTunnels::value_type tunnel = nullptr;
|
||||
for (auto it: tunnels)
|
||||
{
|
||||
if (it->IsEstablished ())
|
||||
{
|
||||
tunnel = it;
|
||||
i++;
|
||||
}
|
||||
if (i > ind && tunnel) break;
|
||||
}
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
void TunnelPool::CreateTunnels ()
|
||||
{
|
||||
int num = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (auto it : m_InboundTunnels)
|
||||
if (it->IsEstablished ()) num++;
|
||||
}
|
||||
for (int i = num; i < m_NumTunnels; i++)
|
||||
CreateInboundTunnel ();
|
||||
|
||||
num = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (auto it : m_OutboundTunnels)
|
||||
if (it->IsEstablished ()) num++;
|
||||
}
|
||||
for (int i = num; i < m_NumTunnels; i++)
|
||||
CreateOutboundTunnel ();
|
||||
}
|
||||
|
||||
void TunnelPool::TestTunnels ()
|
||||
{
|
||||
auto& rnd = i2p::context.GetRandomNumberGenerator ();
|
||||
for (auto it: m_Tests)
|
||||
{
|
||||
LogPrint ("Tunnel test ", (int)it.first, " failed");
|
||||
// if test failed again with another tunnel we consider it failed
|
||||
if (it.second.first)
|
||||
{
|
||||
if (it.second.first->GetState () == eTunnelStateTestFailed)
|
||||
{
|
||||
it.second.first->SetState (eTunnelStateFailed);
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.erase (it.second.first);
|
||||
}
|
||||
else
|
||||
it.second.first->SetState (eTunnelStateTestFailed);
|
||||
}
|
||||
if (it.second.second)
|
||||
{
|
||||
if (it.second.second->GetState () == eTunnelStateTestFailed)
|
||||
{
|
||||
it.second.second->SetState (eTunnelStateFailed);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.erase (it.second.second);
|
||||
}
|
||||
m_LocalDestination.SetLeaseSetUpdated ();
|
||||
}
|
||||
else
|
||||
it.second.second->SetState (eTunnelStateTestFailed);
|
||||
}
|
||||
}
|
||||
m_Tests.clear ();
|
||||
auto it1 = m_OutboundTunnels.begin ();
|
||||
auto it2 = m_InboundTunnels.begin ();
|
||||
while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ())
|
||||
{
|
||||
bool failed = false;
|
||||
if ((*it1)->IsFailed ())
|
||||
{
|
||||
failed = true;
|
||||
it1++;
|
||||
}
|
||||
if ((*it2)->IsFailed ())
|
||||
{
|
||||
failed = true;
|
||||
it2++;
|
||||
}
|
||||
if (!failed)
|
||||
{
|
||||
uint32_t msgID = rnd.GenerateWord32 ();
|
||||
m_Tests[msgID] = std::make_pair (*it1, *it2);
|
||||
(*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (),
|
||||
CreateDeliveryStatusMsg (msgID));
|
||||
it1++; it2++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelPool::ProcessDeliveryStatus (I2NPMessage * msg)
|
||||
{
|
||||
I2NPDeliveryStatusMsg * deliveryStatus = (I2NPDeliveryStatusMsg *)msg->GetPayload ();
|
||||
auto it = m_Tests.find (be32toh (deliveryStatus->msgID));
|
||||
if (it != m_Tests.end ())
|
||||
{
|
||||
// restore from test failed state if any
|
||||
if (it->second.first->GetState () == eTunnelStateTestFailed)
|
||||
it->second.first->SetState (eTunnelStateEstablished);
|
||||
if (it->second.second->GetState () == eTunnelStateTestFailed)
|
||||
it->second.second->SetState (eTunnelStateEstablished);
|
||||
LogPrint ("Tunnel test ", it->first, " successive. ", i2p::util::GetMillisecondsSinceEpoch () - be64toh (deliveryStatus->timestamp), " milliseconds");
|
||||
m_Tests.erase (it);
|
||||
DeleteI2NPMessage (msg);
|
||||
}
|
||||
else
|
||||
m_LocalDestination.ProcessDeliveryStatusMessage (msg);
|
||||
}
|
||||
|
||||
const i2p::data::RouterInfo * TunnelPool::SelectNextHop (const i2p::data::RouterInfo * prevHop) const
|
||||
{
|
||||
auto hop = m_NumHops >= 3 ? i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop).get () :
|
||||
i2p::data::netdb.GetRandomRouter (prevHop).get ();
|
||||
if (!hop)
|
||||
hop = i2p::data::netdb.GetRandomRouter ().get ();
|
||||
return hop;
|
||||
}
|
||||
|
||||
void TunnelPool::CreateInboundTunnel ()
|
||||
{
|
||||
OutboundTunnel * outboundTunnel = GetNextOutboundTunnel ();
|
||||
if (!outboundTunnel)
|
||||
outboundTunnel = tunnels.GetNextOutboundTunnel ();
|
||||
LogPrint ("Creating destination inbound tunnel...");
|
||||
const i2p::data::RouterInfo * prevHop = &i2p::context.GetRouterInfo ();
|
||||
std::vector<const i2p::data::RouterInfo *> hops;
|
||||
int numHops = m_NumHops;
|
||||
if (outboundTunnel)
|
||||
{
|
||||
// last hop
|
||||
auto hop = outboundTunnel->GetTunnelConfig ()->GetFirstHop ()->router;
|
||||
if (hop->GetIdentHash () != i2p::context.GetIdentHash ()) // outbound shouldn't be zero-hop tunnel
|
||||
{
|
||||
prevHop = hop;
|
||||
hops.push_back (prevHop);
|
||||
numHops--;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numHops; i++)
|
||||
{
|
||||
auto hop = SelectNextHop (prevHop);
|
||||
prevHop = hop;
|
||||
hops.push_back (hop);
|
||||
}
|
||||
std::reverse (hops.begin (), hops.end ());
|
||||
auto * tunnel = tunnels.CreateTunnel<InboundTunnel> (new TunnelConfig (hops), outboundTunnel);
|
||||
tunnel->SetTunnelPool (this);
|
||||
}
|
||||
|
||||
void TunnelPool::RecreateInboundTunnel (InboundTunnel * tunnel)
|
||||
{
|
||||
OutboundTunnel * outboundTunnel = GetNextOutboundTunnel ();
|
||||
if (!outboundTunnel)
|
||||
outboundTunnel = tunnels.GetNextOutboundTunnel ();
|
||||
LogPrint ("Re-creating destination inbound tunnel...");
|
||||
auto * newTunnel = tunnels.CreateTunnel<InboundTunnel> (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel);
|
||||
newTunnel->SetTunnelPool (this);
|
||||
}
|
||||
|
||||
void TunnelPool::CreateOutboundTunnel ()
|
||||
{
|
||||
InboundTunnel * inboundTunnel = GetNextInboundTunnel ();
|
||||
if (!inboundTunnel)
|
||||
inboundTunnel = tunnels.GetNextInboundTunnel ();
|
||||
if (inboundTunnel)
|
||||
{
|
||||
LogPrint ("Creating destination outbound tunnel...");
|
||||
|
||||
const i2p::data::RouterInfo * prevHop = &i2p::context.GetRouterInfo ();
|
||||
std::vector<const i2p::data::RouterInfo *> hops;
|
||||
for (int i = 0; i < m_NumHops; i++)
|
||||
{
|
||||
auto hop = SelectNextHop (prevHop);
|
||||
prevHop = hop;
|
||||
hops.push_back (hop);
|
||||
}
|
||||
|
||||
auto * tunnel = tunnels.CreateTunnel<OutboundTunnel> (
|
||||
new TunnelConfig (hops, inboundTunnel->GetTunnelConfig ()));
|
||||
tunnel->SetTunnelPool (this);
|
||||
}
|
||||
else
|
||||
LogPrint ("Can't create outbound tunnel. No inbound tunnels found");
|
||||
}
|
||||
|
||||
void TunnelPool::RecreateOutboundTunnel (OutboundTunnel * tunnel)
|
||||
{
|
||||
InboundTunnel * inboundTunnel = GetNextInboundTunnel ();
|
||||
if (!inboundTunnel)
|
||||
inboundTunnel = tunnels.GetNextInboundTunnel ();
|
||||
if (inboundTunnel)
|
||||
{
|
||||
LogPrint ("Re-creating destination outbound tunnel...");
|
||||
auto * newTunnel = tunnels.CreateTunnel<OutboundTunnel> (
|
||||
tunnel->GetTunnelConfig ()->Clone (inboundTunnel->GetTunnelConfig ()));
|
||||
newTunnel->SetTunnelPool (this);
|
||||
}
|
||||
else
|
||||
LogPrint ("Can't re-create outbound tunnel. No inbound tunnels found");
|
||||
}
|
||||
}
|
||||
}
|
88
TunnelPool.h
Normal file
88
TunnelPool.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
#ifndef TUNNEL_POOL__
|
||||
#define TUNNEL_POOL__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class Tunnel;
|
||||
class InboundTunnel;
|
||||
class OutboundTunnel;
|
||||
|
||||
class TunnelPool // per local destination
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelPool (i2p::garlic::GarlicDestination& localDestination, int numHops, int numTunnels = 5);
|
||||
~TunnelPool ();
|
||||
|
||||
const uint8_t * GetEncryptionPrivateKey () const { return m_LocalDestination.GetEncryptionPrivateKey (); };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_LocalDestination.GetEncryptionPublicKey (); };
|
||||
const i2p::data::LocalDestination& GetLocalDestination () const { return m_LocalDestination; };
|
||||
i2p::garlic::GarlicDestination& GetGarlicDestination () const { return m_LocalDestination; };
|
||||
bool IsExploratory () const { return GetIdentHash () == i2p::context.GetIdentHash (); };
|
||||
|
||||
void CreateTunnels ();
|
||||
void TunnelCreated (InboundTunnel * createdTunnel);
|
||||
void TunnelExpired (InboundTunnel * expiredTunnel);
|
||||
void TunnelCreated (OutboundTunnel * createdTunnel);
|
||||
void TunnelExpired (OutboundTunnel * expiredTunnel);
|
||||
std::vector<InboundTunnel *> GetInboundTunnels (int num) const;
|
||||
OutboundTunnel * GetNextOutboundTunnel (OutboundTunnel * suggested = nullptr) const;
|
||||
InboundTunnel * GetNextInboundTunnel (InboundTunnel * suggested = nullptr) const;
|
||||
const i2p::data::IdentHash& GetIdentHash () const { return m_LocalDestination.GetIdentHash (); };
|
||||
|
||||
void TestTunnels ();
|
||||
void ProcessDeliveryStatus (I2NPMessage * msg);
|
||||
|
||||
bool IsActive () const { return m_IsActive; };
|
||||
void SetActive (bool isActive) { m_IsActive = isActive; };
|
||||
void DetachTunnels ();
|
||||
|
||||
private:
|
||||
|
||||
void CreateInboundTunnel ();
|
||||
void CreateOutboundTunnel ();
|
||||
void RecreateInboundTunnel (InboundTunnel * tunnel);
|
||||
void RecreateOutboundTunnel (OutboundTunnel * tunnel);
|
||||
template<class TTunnels>
|
||||
typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels,
|
||||
typename TTunnels::value_type suggested = nullptr) const;
|
||||
const i2p::data::RouterInfo * SelectNextHop (const i2p::data::RouterInfo * prevHop) const;
|
||||
|
||||
private:
|
||||
|
||||
i2p::garlic::GarlicDestination& m_LocalDestination;
|
||||
int m_NumHops, m_NumTunnels;
|
||||
mutable std::mutex m_InboundTunnelsMutex;
|
||||
std::set<InboundTunnel *, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first
|
||||
mutable std::mutex m_OutboundTunnelsMutex;
|
||||
std::set<OutboundTunnel *, TunnelCreationTimeCmp> m_OutboundTunnels;
|
||||
std::map<uint32_t, std::pair<OutboundTunnel *, InboundTunnel *> > m_Tests;
|
||||
bool m_IsActive;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
|
||||
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
68
UPnP.cpp
Normal file
68
UPnP.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include <string>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include "Log.h"
|
||||
#include "UPnP.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
UPnP::UPnP (): m_Timer (m_Service),
|
||||
m_Endpoint (boost::asio::ip::udp::v4 (), UPNP_REPLY_PORT),
|
||||
m_MulticastEndpoint (boost::asio::ip::address::from_string (UPNP_GROUP), UPNP_PORT),
|
||||
m_Socket (m_Service, m_Endpoint.protocol ())
|
||||
{
|
||||
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535));
|
||||
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535));
|
||||
m_Socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
|
||||
}
|
||||
|
||||
UPnP::~UPnP ()
|
||||
{
|
||||
}
|
||||
|
||||
void UPnP::Run ()
|
||||
{
|
||||
DiscoverRouter ();
|
||||
m_Service.run ();
|
||||
}
|
||||
|
||||
void UPnP::DiscoverRouter ()
|
||||
{
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(5)); // 5 seconds
|
||||
m_Timer.async_wait (boost::bind (&UPnP::HandleTimer, this, boost::asio::placeholders::error));
|
||||
|
||||
std::string address = UPNP_GROUP;
|
||||
address += ":" + boost::lexical_cast<std::string>(UPNP_PORT);
|
||||
std::string request = "M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: " + address + "\r\n"
|
||||
"ST:" + UPNP_ROUTER + "\r\n"
|
||||
"MAN:\"ssdp:discover\"\r\n"
|
||||
"MX:3\r\n"
|
||||
"\r\n\r\n";
|
||||
m_Socket.send_to (boost::asio::buffer (request.c_str (), request.length ()), m_MulticastEndpoint);
|
||||
Receive ();
|
||||
}
|
||||
|
||||
void UPnP::Receive ()
|
||||
{
|
||||
m_Socket.async_receive_from (boost::asio::buffer (m_ReceiveBuffer, UPNP_MAX_PACKET_LEN), m_SenderEndpoint,
|
||||
boost::bind (&UPnP::HandleReceivedFrom, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void UPnP::HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred)
|
||||
{
|
||||
LogPrint ("UPnP: ", bytes_transferred, " received from ", m_SenderEndpoint.address ());
|
||||
std::string str (m_ReceiveBuffer, bytes_transferred);
|
||||
LogPrint (str);
|
||||
m_Timer.cancel ();
|
||||
}
|
||||
|
||||
void UPnP::HandleTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LogPrint ("UPnP: timeout expired");
|
||||
m_Service.stop ();
|
||||
}
|
||||
}
|
||||
}
|
41
UPnP.h
Normal file
41
UPnP.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef UPNP_H__
|
||||
#define UPNP_H__
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
const int UPNP_MAX_PACKET_LEN = 1500;
|
||||
const char UPNP_GROUP[] = "239.255.255.250";
|
||||
const int UPNP_PORT = 1900;
|
||||
const int UPNP_REPLY_PORT = 1901;
|
||||
const char UPNP_ROUTER[] = "urn:schemas-upnp-org:device:InternetGatewayDevice:1";
|
||||
|
||||
class UPnP
|
||||
{
|
||||
public:
|
||||
|
||||
UPnP ();
|
||||
~UPnP ();
|
||||
|
||||
void Run ();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void DiscoverRouter ();
|
||||
void Receive ();
|
||||
void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred);
|
||||
void HandleTimer (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::deadline_timer m_Timer;
|
||||
boost::asio::ip::udp::endpoint m_Endpoint, m_MulticastEndpoint, m_SenderEndpoint;
|
||||
boost::asio::ip::udp::socket m_Socket;
|
||||
char m_ReceiveBuffer[UPNP_MAX_PACKET_LEN];
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
14
Win32/.gitignore
vendored
Normal file
14
Win32/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
*
|
||||
!*/
|
||||
|
||||
!*.h
|
||||
!*.cpp
|
||||
|
||||
!*.bat
|
||||
|
||||
!*.sln
|
||||
!*.vcproj
|
||||
!*.vcxproj
|
||||
!*.vcxproj.filters
|
||||
!*.iss
|
||||
!.gitignore
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2023, 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 <thread>
|
||||
#include <clocale>
|
||||
#include "Config.h"
|
||||
#include "Daemon.h"
|
||||
#include "util.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32Service.h"
|
||||
#ifdef WIN32_APP
|
||||
#include <windows.h>
|
||||
#include "Win32App.h"
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
bool DaemonWin32::init(int argc, char* argv[])
|
||||
{
|
||||
setlocale(LC_CTYPE, "");
|
||||
SetConsoleCP(1251);
|
||||
SetConsoleOutputCP(1251);
|
||||
//setlocale(LC_ALL, "Russian");
|
||||
setlocale(LC_TIME, "C");
|
||||
|
||||
i2p::log::SetThrowFunction ([](const std::string& s)
|
||||
{
|
||||
MessageBox(0, TEXT(s.c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK );
|
||||
}
|
||||
);
|
||||
|
||||
if (!Daemon_Singleton::init(argc, argv))
|
||||
return false;
|
||||
|
||||
if (isDaemon)
|
||||
{
|
||||
LogPrint(eLogDebug, "Daemon: running as service");
|
||||
I2PService service((PSTR)SERVICE_NAME);
|
||||
if (!I2PService::Run(service))
|
||||
{
|
||||
LogPrint(eLogCritical, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaemonWin32::start()
|
||||
{
|
||||
setlocale(LC_CTYPE, "");
|
||||
SetConsoleCP(1251);
|
||||
SetConsoleOutputCP(1251);
|
||||
//setlocale(LC_ALL, "Russian");
|
||||
setlocale(LC_TIME, "C");
|
||||
#ifdef WIN32_APP
|
||||
if (!i2p::win32::StartWin32App (isDaemon)) return false;
|
||||
#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 //_WIN32
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue