pax_global_header00006660000000000000000000000064141107252560014515gustar00rootroot0000000000000052 comment=24eeadea765931d46697b3e44746f24eb65d1c53 i2pd-2.39.0/000077500000000000000000000000001411072525600124465ustar00rootroot00000000000000i2pd-2.39.0/.dir-locals.el000066400000000000000000000001031411072525600150710ustar00rootroot00000000000000((c++-mode . ((indent-tabs-mode . t))) (c-mode . ((mode . c++)))) i2pd-2.39.0/.github/000077500000000000000000000000001411072525600140065ustar00rootroot00000000000000i2pd-2.39.0/.github/workflows/000077500000000000000000000000001411072525600160435ustar00rootroot00000000000000i2pd-2.39.0/.github/workflows/build-freebsd.yml000066400000000000000000000007501411072525600212770ustar00rootroot00000000000000name: Build on FreeBSD on: [push, pull_request] jobs: build: runs-on: macos-latest name: with UPnP steps: - uses: actions/checkout@v2 - name: Test in FreeBSD id: test uses: vmactions/freebsd-vm@v0.1.4 with: usesh: 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 i2pd-2.39.0/.github/workflows/build-osx.yml000066400000000000000000000007621411072525600205010ustar00rootroot00000000000000name: Build on OSX on: [push, pull_request] jobs: build: name: With USE_UPNP=${{ matrix.with_upnp }} runs-on: macOS-latest strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] steps: - uses: actions/checkout@v2 - name: install packages run: | brew update brew install boost miniupnpc openssl@1.1 - name: build application run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3 i2pd-2.39.0/.github/workflows/build-windows.yml000066400000000000000000000016561411072525600213650ustar00rootroot00000000000000name: Build on Windows on: [push, pull_request] defaults: run: shell: msys2 {0} jobs: build: name: Building for ${{ matrix.arch }} runs-on: windows-latest strategy: fail-fast: true matrix: include: [ { msystem: MINGW64, arch: x86_64 }, { msystem: MINGW32, arch: i686 } ] steps: - uses: actions/checkout@v2 - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} install: base-devel mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc update: true - name: build application run: | mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon make USE_UPNP=yes DEBUG=no -j3 - name: Upload artifacts uses: actions/upload-artifact@v2 with: path: i2pd.exe i2pd-2.39.0/.github/workflows/build.yml000066400000000000000000000053671411072525600177000ustar00rootroot00000000000000name: Build on Ubuntu on: [push, pull_request] jobs: build-make: name: Make with USE_UPNP=${{ matrix.with_upnp }} runs-on: ubuntu-18.04 strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] steps: - uses: actions/checkout@v2 - name: install packages run: | sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update sudo apt-get install build-essential libboost1.74-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-18.04 strategy: fail-fast: true matrix: with_upnp: ['ON', 'OFF'] steps: - uses: actions/checkout@v2 - name: install packages run: | sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: | cd build cmake -DWITH_UPNP=${{ matrix.with_upnp }} . make -j3 build-deb-stretch: name: Build package for stretch runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: change debian changelog run: | sudo apt-get update sudo apt-get install devscripts debchange -v "`git describe --tags`-stretch" -M --distribution stretch "trunk build" - uses: singingwolfboy/build-dpkg-stretch@v1 id: build with: args: --unsigned-source --unsigned-changes -b - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} path: ${{ steps.build.outputs.filename }} - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename-dbgsym }} path: ${{ steps.build.outputs.filename-dbgsym }} build-deb-buster: name: Build package for buster runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: change debian changelog run: | sudo apt-get update sudo apt-get install devscripts debchange -v "`git describe --tags`-buster" -M --distribution buster "trunk build" - uses: singingwolfboy/build-dpkg-buster@v1 id: build with: args: --unsigned-source --unsigned-changes -b - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} path: ${{ steps.build.outputs.filename }} - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename-dbgsym }} path: ${{ steps.build.outputs.filename-dbgsym }} i2pd-2.39.0/.github/workflows/docker.yml000066400000000000000000000035421411072525600200410ustar00rootroot00000000000000name: Build containers on: [push] jobs: docker: runs-on: ubuntu-latest permissions: packages: write contents: read steps: - name: Checkout uses: actions/checkout@v2 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push trunk container if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: docker/build-push-action@v2 with: context: ./contrib/docker file: ./contrib/docker/Dockerfile platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 push: true tags: | purplei2p/i2pd:latest ghcr.io/purplei2p/i2pd:latest - name: Set env if: ${{ startsWith(github.ref, 'refs/tags/') }} run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - name: Build and push release container if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: docker/build-push-action@v2 with: context: ./contrib/docker file: ./contrib/docker/Dockerfile platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 push: true tags: | purplei2p/i2pd:latest purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} ghcr.io/purplei2p/i2pd:latest ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} i2pd-2.39.0/.gitignore000066400000000000000000000062571411072525600144500ustar00rootroot00000000000000# i2pd *.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 config.h config.h.in~ config.log config.status config.sub ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ [Bb]in/ [Oo]bj/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # Sphinx docs/_build /androidIdea/ # Doxygen docs/generated # emacs files *~ *\#* # gdb files .gdb_history # cmake makefile build/Makefile # debian stuff .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* i2pd-2.39.0/ChangeLog000066400000000000000000000553201411072525600142250ustar00rootroot00000000000000# for this file format description, # see https://github.com/olivierlacan/keep-a-changelog ## [2.39.0] - 2021-08-23 ### Added - Short tunnel build messages - Localization. To: Russian, Ukrainian, Turkmen, Uzbek and Afrikaans - Custom CSS styles for webconsole - Avoid slow tunnels with more than 250 ms per hop - Process DELAY_REQUESTED streaming option - "certsdir" options for certificates location - Keep own RouterInfo in NetBb - Pick ECIES routers only for tunnels on non-x64 - NTP sync through ipv6 - Allow ipv6 addresses for UDP server tunnels ### Changed - Rekey of all routers to ECIES - Better distribution for random tunnel's peer selection - Yggdrasil reseed for v0.4, added two more - Encryption type 0,4 by default for server tunnels - Handle i2cp.dontPublishLeaseSet param for all destinations - reg.i2p for subscriptions - LeaseSet type 3 by default - Don't allocate payload buffer for every single ECIESx25519 message - Prefer public ipv6 instead rfc4941 - Optimal padding for one-time ECIESx25519 message - Don't send datetime block for one-time ECIESx25519 message with one-time key - Router with expired introducer is still valid - Don't disable floodfill if still reachable by ipv6 - Set minimal version for floodfill to 0.9.38 - Eliminate extra lookups for sequential fragments on tunnel endpoint - Consistent path for explicit peers - Always create new tunnel from exploratory pool - Don't try to connect to a router not reachable from us - Mark additional ipv6 addresses/nets as reserved (#1679) ### Fixed - Zero-hop tunnels - Crash upon SAM session termination - Build with boost < 1.55.0 - Address type for NTCP2 acceptors - Check of ipv4/ipv6 address - Request router to send to if not in NetDb - Count outbound traffic for zero-hop tunnels - URLdecode domain for registration string generator in webconsole ## [2.38.0] - 2021-05-17 ### Added - Publish ipv6 introducers - Bind ipv6 or yggdrasil NTCP2 acceptor to specified address - Support .b32.i2p addresses and hostnames for SAM STREAM CREATE - ipv6 peer tests - Publish iexp param for introducers - Show ipv6 network status on the webconsole - EdDSA signing keys can also be blinded - Show router version on the webconsole ### Changed - Rekey of all routers but floodfills to ECIES - Increased number of precalculated x25519 keys to 15 - Don't publish LeaseSet without inbound tunnels - Reseed from compatible address(ipv4 or ipv6) - Recongnize v4 and v6 SSU addresses without host - Inbound tunnel gateway must be ipv4 compatible - Don't select next introducers from existing sessions - Set X bandwidth for floodfill by default ### Fixed - Incoming ECIES-x25519 session doesn't send updated LeaseSet - Unique local address for server tunnels - Race condition for LeaseSet creation in I2CP - Relay tag for ipv6 introducer - Already expired introducers - Find connected router for first peer in tunnel - Failed outgoing ECIES-x25519 session's tagset stays forever - Yggdrasil address disappears if router becomes unreachable through ipv6 - Ignore SSU address/introducers if port is not specified - Check identity and signature length for SSU SessionConfirmed ## [2.37.0] - 2021-03-15 ### Added - Address registration line for reg.i2p and stats.i2p through the web console - "4" and "6" caps for addresses without published IP address - Mesh and Proxy network statuses - Symmetric NAT network status error - Bind server tunnel connection to specified address - lookuplocal BOB extended command - address4 and address6 parameters to bind outgoing connections to - Rekey of low-bandwidth routers to ECIES - Popup notification windows when unable to parse config for Windows ### Changed - Floodfills with "U" cap are not ignored anymore - Check transports reachability between tunnel peers and between router and floodfill - NTCP2 and reseed HTTP proxy support authorization now - Show actual IP addresses for proxy connections - Publish and handle SSU addreses without host - Outbound tunnel endpoint must be ipv4 compatible - Logging optimization - Removed Windows service ### Fixed - Incoming SSU session terminates after 5 seconds - Outgoing NTCP2 ipv4 session even if ipv4 is disabled - No incoming Yggdrasil connection if connected through NTCP2 proxy - Race condition between tunnel build and floodfill requests decryption for ECIES routers - Numeric bandwidth limitation - Yggdrasil for Android ## [2.36.0] - 2021-02-15 ### Added - Encrypted lookup and publications to ECIES-x25519 floodfiils - Yggdrasil transports and reseeds - Dump addressbook in hosts.txt format - Request RouterInfo through exploratory tunnels if direct connection to fllodfill is not possible - Threads naming - Check if public x25519 key is valid - ECIES-X25519-AEAD-Ratchet for shared local destination - LeaseSet creation timeout for I2CP session - Resend RouterInfo after some interval for longer NTCP2 sessions - Select reachable router of inbound tunnel gateway - Reseed if no compatible routers in netdb - Refresh on swipe in Android webconsole ### Changed - reg.i2p for default addressbook instead inr.i2p - ECIES-x25519 (crypto type 4) for new routers - Try to connect to all compatible addresses from peer's RouterInfo - Replace LeaseSet completely if store type changes - Try ECIES-X25519-AEAD-Ratchet tag before ElGamal - Don't detach ECIES-X25519-AEAD-Ratchet session from destination immediately - Viewport and styles on error in HTTP proxy - Don't create notification when Windows taskbar restarted - Cumulative SSU ACK bitfields - limit tunnel length to 8 hops - Limit tunnels quantity to 16 ### Fixed - Handling chunked HTTP response in addressbook - Missing ECIES-X25519-AEAD-Ratchet tags for multiple streams with the same destination - Correct NAME for NAMING REPLY in SAM - SSU crash on termination - Offline signature length for stream close packet - Don't send updated LeaseSet through a terminated session - Decryption of follow-on ECIES-X25519-AEAD-Ratchet NSR messages - Non-confirmed LeaseSet is resent too late for ECIES-X25519-AEAD-Ratchet session ## [2.35.0] - 2020-11-30 ### Added - ECIES-x25519 routers - Random intro keys for SSU - Graceful shutdown timer for windows - Send queue for I2CP messages - Update DSA router keys to EdDSA - TCP_QUICKACK for NTCP2 sockets on Linux ### Changed - Exclude floodfills with DSA signatures and < 0.9.28 - Random intervals between tunnel tests and manage for tunnel pools - Don't replace an addressbook record by one with DSA signature - Publish RouterInfo after update - Create paired inbound tunnels if no inbound tunnels yet - Reseed servers list ### Fixed - Transient signature length, if different from identity - Terminate I2CP session if destroyed - RouterInfo publishing confirmation - Check if ECIES-X25519-AEAD-Ratchet session expired before generating more tags - Correct block size for delivery type local for ECIES-X25519-AEAD-Ratchet ## [2.34.0] - 2020-10-27 ### Added - Ping responses for streaming - STREAM FORWARD for SAM - Tunnels through ECIES-x25519 routers - Single thread for I2CP - Shared transient destination between proxies - Database lookups from ECIES destinations with ratchets response - Handle WebDAV HTTP methods - Don't try to connect or build tunnels if offline - Validate IP when trying connect to remote peer - Handle ICMP responses and WinAPI errors for SSU ### Changed - Removed NTCP - Dropped gcc 4.7 support - Encyption type 0,4 by default for client tunnels - Stripped out some HTTP header for HTTP server response - HTTP 1.1 addressbook requests - Set LeaseSet type to 3 for ratchets if not specified - Handle SSU v4 and v6 messages in one thread - Eliminate DH keys thread ### Fixed - Random crashes on I2CP session disconnect - Stream through racthets hangs if first SYN was not acked - Check "Last-Modified" instead "If-Modified-Since" for addressbook reponse - Trim behind ECIESx25519 tags - Few bugs with Android main activity - QT visual and layout issues ## [2.33.0] - 2020-08-24 ### Added - Shared transient addresses - crypto.ratchet.inboundTags paramater - Multiple encryption keys through I2CP - Pre-calculated x25519 ephemeral keys - Change datagram routing path if nothing comes back in 10 seconds - Shared routing path for datagram session ### Changed - UDP tunnels send mix of repliable and raw datagrams in bulk - Encrypt SSU packet again upon resend - Start new tunnel message if remaining buffer is too small - Use LeaseSet2 for ECIES-X25519-AEAD-Ratchet automatically - Save new ECIES-X25519-AEAD-Ratchet session with NSR tagset - Generate random padding lengths for ECIES-X25519-AEAD-Ratchet in bulk - Webconsole layout - Reseed servers list ### Fixed - Don't connect through terminated SAM destination - Differentiate UDP server sessions by port - ECIES-X25519-AEAD-Ratchet through I2CP - Don't save invalid address to AddressBook - ECDSA signatures names in SAM - AppArmor profile ## [2.32.1] - 2020-06-02 ### Added - Read explicit peers in tunnels config ### Fixed - Generation of tags for detached sessions - Non-updating LeaseSet1 - Start when deprecated websocket options present in i2pd.conf ## [2.32.0] - 2020-05-25 ### Added - Multiple encryption types for local destinations - Next key and tagset for ECIES-X25519-AEAD-Ratchet - NTCP2 through SOCKS proxy - Throw error message if any port to bind is occupied - gzip parameter for UDP tunnels - Show ECIES-X25519-AEAD-Ratchet sessions and tags on the web console - Simplified implementation of gzip for no compression mode - Allow ECIES-X25519-AEAD-Ratchet session restart after 2 minutes - Added logrotate config for rpm package ### Changed - Select peers for client tunnels among routers >= 0.9.36 - Check ECIES flag for encrypted lookup reply - Streaming MTU size 1812 for ECIES-X25519-AEAD-Ratchet - Don't calculate checksum for Data message send through ECIES-X25519-AEAD-Ratchet - Catch network connectivity status for Windows - Stop as soon as no more transit tunnels during graceful shutdown for Android - RouterInfo gzip compression level depends on size - Send response to received datagram from ECIES-X25519-AEAD-Ratchet session - Update webconsole functional - Increased max transit tunnels limit - Reseeds list - Dropped windows support in cmake ### Fixed - Correct timestamp check for LeaseSet2 - Encrypted leaseset without authentication - Change SOCKS proxy connection response for clients without socks5h support (#1336) ## [2.31.0] - 2020-04-10 ### Added - NTCP2 through HTTP proxy - Publish LeaseSet2 for I2CP destinations - Show status page on main activity for android - Handle ECIESFlag in DatabaseLookup at floodfill - C++17 features for eligible compilers ### Changed - Droped Websockets and Lua support - Send DeliveryStatusMsg for LeaseSet for ECIES-X25519-AEAD-Ratchet - Keep sending new session reply until established for ECIES-X25519-AEAD-Ratchet - Updated SSU log messages - Reopen SSU socket on exception - Security hardening headers in web console - Various web console changes - Various QT changes ### Fixed - NTCP2 socket descriptors leak - Race condition with router's identity in transport sessions - Not terminated streams remain forever ## [2.30.0] - 2020-02-25 ### Added - Single threaded SAM - Experimental support of ECIES-X25519-AEAD-Ratchet crypto type ### Changed - Minimal MTU size is 1280 for ipv6 - Use unordered_map instead map for destination's sessions and tags list - Use std::shuffle instead std::random_shuffle - SAM is single threaded by default - Reseeds list ### Fixed - Correct termination of streaming destination - Extra ',' in RouterInfo response in I2PControl - SAM crash on session termination - Storage for Android 10 ## [2.29.0] - 2019-10-21 ### Added - Client auth flag for b33 address ### Changed - Remove incoming NTCP2 session from pending list when established - Handle errors for NTCP2 SessionConfrimed send ### Fixed - Failure to start on Windows XP - SAM crash if invalid lookup address - Possible crash when UPnP enabled on shutdown ## [2.28.0] - 2019-08-27 ### Added - RAW datagrams in SAM - Publishing encrypted LeaseSet2 with DH or PSH authentication - Ability to disable battery optimization for Android - Transport Network ID Check ### Changed - Set and handle published encrypted flag for LeaseSet2 ### Fixed - ReceiveID changes in the same stream - "\r\n" command terminator in SAM - Addressbook lines with signatures ## [2.27.0] - 2019-07-03 ### Added - Support of PSK and DH authentication for encrypted LeaseSet2 ### Changed - Uptime is based on monotonic timer ### Fixed - BOB status command response - Correct NTCP2 port if NTCP is disabled - Flood encrypted LeaseSet2 with store hash ## [2.26.0] - 2019-06-07 ### Added - HTTP method "PROPFIND" - Detection of external ipv6 address through the SSU - NTCP2 publishing depends on network status ### Changed - ntcp is disabled by default, ntcp2 is published by default - Response to BOB's "list" command - ipv6 address is not longer NTCP's local endpoint's address - Reseeds list - HTTP_REFERER stripping in httpproxy (#823) ### Fixed - Check and handle incorrect BOB input - Ignore introducers for NTCP or NTCP2 addresses - RouterInfo check from NTCP2 ## [2.25.0] - 2019-05-09 ### Added - Create, publish and handle encrypted LeaseSet2 - Support of b33 addresses - RedDSA key blinding - .b32.i2p addresses in jump links - ntcp2.addressv6 parameter ### Changed - Allow HTTP headers without value - Set data directory from external storage path for Android - addresshelper support is configurable per tunnel - gradlew script for android build ### Fixed - Deletion of expired encrypted LeaseSet2 on floodfills - ipv6 fallback address - SSU incoming packets routing ## [2.24.0] - 2019-03-21 ### Added - Support of transient keys for LeaseSet2 - Support of encrypted LeaseSet2 - Recognize signature type 11 (RedDSA) - Support websocket connections over HTTP proxy - Ability to disable full addressbook persist ### Changed - Don't load peer profiles if non-persistant - REUSE_ADDR for ipv6 acceptors - Reset eTags if addressbook can't be loaded ### Fixed - Build with boost 1.70 - Filter out unspecified addresses from RouterInfo - Check floodfill status change - Correct SAM response for invalid key - SAM crash on termination for Windows - Race condition for publishing ## [2.23.0] - 2019-01-21 ### Added - Standard LeaseSet2 support - Ability to adjust timestamps through the NTP - Ability to disable peer profile persist - Request permission for android >= 6 - Initial addressbook to android assets - Cancel graceful shutdown for android - Russian translation for android ### Changed - Chacha20 and Poly1305 implementation - Eliminate extra copy of NTCP2 send buffers - Extract content of tunnel.d from assets on android - Removed name resolvers from transports - Update reseed certificates ### Fixed - LeaseSet published content verification - Exclude invalid LeaseSets from the list on a floodfill - Build for OpenWrt with openssl 1.1.1 ## [2.22.0] - 2018-11-09 ### Added - Multiple tunnel config files from tunnels.d folder ### Changed - Fetch own RouterInfo upon SessionRequest for NTCP2 - Faster XOR between AES blocks for non AVX capable CPUs ### Fixed - Fixed NTCP2 termination send ## [2.21.1] - 2018-10-22 ### Changed - cost=13 for unpublished NTCP2 address ### Fixed - Handle I2NP messages longer than 32K ## [2.21.0] - 2018-10-04 ### Added - EdDSA, x25519 and SipHash from openssl 1.1.1 - NTCP2 ipv6 incoming connections - Show total number of destination's outgoing tags in the web console ### Changed - Android build with openssl 1.1.1/boost 1.64 - Bandwidth classes 'P' and 'X' don't add 'O' anymore ### Fixed - Update own RouterInfo if no SSU - Recognize 'P' and 'X' routers as high bandwidth without 'O' - NTCP address doesn't disappear if NTCP2 enabled - Android with api 26+ ## [2.20.0] - 2018-08-23 ### Added - Full implementation of NTCP2 - Assets for android ### Changed - armeabi-v7a and x86 in one apk for android - NTCP2 is enabled by default - Show lease's expiration time in readable format in the web console ### Fixed - Correct names for transports in the web console ## [2.19.0] - 2018-06-26 ### Added - ECIES support for RouterInfo - HTTP outproxy authorization - AVX/AESNI runtime detection - Initial implementation of NTCP2 - I2CP session reconfigure - I2CP method ClientServicesInfo - Datagrams to websocks ### Changed - RouterInfo uses EdDSA signature by default - Remove stream bans - Android build system changed to gradle - Multiple changes in QT GUI - Dockerfile ### Fixed - zero tunnelID issue - tunnels reload - headers in webconsole - XSS in webconsole from SAM session name - build for gcc 8 - cmake build scripts - systemd service files - some netbsd issues ## [2.18.0] - 2018-01-30 ### Added - Show tunnel nicknames for I2CP destination in WebUI - Re-create HTTP and SOCKS proxy by tunnel reload - Graceful shutdown as soon as no more transit tunnels ### Changed - Regenerate shared local destination by tunnel reload - Use transient local destination by default if not specified - Return correct code if pid file can't be created - Timing and number of attempts for adressbook requests - Certificates list ### Fixed - Malformed addressbook subsctiption request - Build with boost 1.66 - Few race conditions for SAM - Check LeaseSet's signature before update ## [2.17.0] - 2017-12-04 ### Added - Reseed through HTTP and SOCKS proxy - Show status of client services through web console - Change log level through web connsole - transient keys for tunnels - i2p.streaming.initialAckDelay parameter - CRYPTO_TYPE for SAM destination - signature and crypto type for newkeys BOB command ### Changed - Correct publication of ECIES destinations - Disable RSA signatures completely ### Fixed - CVE-2017-17066 - Possible buffer overflow for RSA-4096 - Shutdown from web console for Windows - Web console page layout ## [2.16.0] - 2017-11-13 ### Added - https and "Connect" method for HTTP proxy - outproxy for HTTP proxy - initial support of ECIES crypto - NTCP soft and hard descriptors limits - Support full timestamps in logs ### Changed - Faster implementation of GOST R 34.11 hash - Reject routers with RSA signtures - Reload config and shudown from Windows GUI - Update tunnels address(destination) without restart ### Fixed - BOB crashes if destination is not set - Correct SAM tunnel name - QT GUI issues ## [2.15.0] - 2017-08-17 ### Added - QT GUI - Ability to add and remove I2P tunnels without restart - Ability to disable SOCKS outproxy option ### Changed - Strip-out Accept-* hedaers in HTTP proxy - Don't run peer test if nat=false - Separate output of NTCP and SSU sessions in Transports tab ### Fixed - Handle lines with comments in hosts.txt file for address book - Run router with empty netdb for testnet - Skip expired introducers by iexp ## [2.14.0] - 2017-06-01 ### Added - Transit traffic bandwidth limitation - NTCP connections through HTTP and SOCKS proxies - Ability to disable address helper for HTTP proxy ### Changed - Reseed servers list - Minimal required version is 4.0 for Android ### Fixed - Ignore comments in addressbook feed ## [2.13.0] - 2017-04-06 ### Added - Persist local destination's tags - GOST signature types 9 and 10 - Exploratory tunnels configuration ### Changed - Reseed servers list - Inactive NTCP sockets get closed faster - Some EdDSA speed up ### Fixed - Multiple acceptors for SAM - Follow on data after STREAM CREATE for SAM - Memory leaks ## [2.12.0] - 2017-02-14 ### Added - Additional HTTP and SOCKS proxy tunnels - Reseed from ZIP archive - Some stats in a main window for Windows version ### Changed - Reseed servers list - MTU of 1488 for ipv6 - Android and Mac OS X versions use OpenSSL 1.1 - New logo for Android ### Fixed - Multiple memory leaks - Incomptibility of some EdDSA private keys with Java - Clock skew for Windows XP - Occasional crashes with I2PSnark ## [2.11.0] - 2016-12-18 ### Added - Websockets support - Reseed through a floodfill - Tunnel configuration for HTTP and SOCKS proxy - Zero-hops tunnels for destinations - Multiple acceptors for SAM ### Changed - Reseed servers list - DHT uses AVX if applicable - New logo - LeaseSet lookups ### Fixed - HTTP Proxy connection reset for Windows - Crash upon SAM session termination - Can't connect to a destination for a longer time after restart - Mass packet loss for UDP tunnels ## [2.10.2] - 2016-12-04 ### Fixed - Fixes UPnP discovery bug, producing excessive CPU usage - Fixes sudden SSU thread stop for Windows. ## [2.10.1] - 2016-11-07 ### Fixed - Fixed some performance issues for Windows and Android ## [2.10.0] - 2016-10-17 ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels - Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support ### Changed - Reduced file descriptors usage - Strict reseed checks enabled by default ## Fixed - Multiple fixes in I2CP and BOB implementations ## [2.9.0] - 2016-08-12 ### Changed - Proxy refactoring & speedup - Transmission-I2P support - Graceful shutdown for Windows - Android without QT - Reduced number of timers in SSU - ipv6 peer test support - Reseed from SU3 file ## [2.8.0] - 2016-06-20 ### Added - Basic Android support - I2CP implementation - 'doxygen' target ### Changed - I2PControl refactoring & fixes (proper jsonrpc responses on errors) - boost::regex no more needed ### Fixed - initscripts: added openrc one, in sysv-ish make I2PD_PORT optional - properly close NTCP sessions (memleak) ## [2.7.0] - 2016-05-18 ### Added - Precomputed El-Gamal/DH tables - Configurable limit of transit tunnels ### Changed - Speed-up of asymmetric crypto for non-x64 platforms - Refactoring of web-console ## [2.6.0] - 2016-03-31 ### Added - Graceful shutdown on SIGINT - Numeric bandwidth limits (was: by router class) - Jumpservices in web-console - Logging to syslog - Tray icon for windows application ### Changed - Logs refactoring - Improved statistics in web-console ### Deprecated: - Renamed main/tunnels config files (will use old, if found, but emits warning) ## [2.5.1] - 2016-03-10 ### Fixed - Doesn't create ~/.i2pd dir if missing ## [2.5.0] - 2016-03-04 ### Added - IRC server tunnels - SOCKS outproxy support - Support for gzipped addressbook updates - Support for router families ### Changed - Shared RTT/RTO between streams - Filesystem work refactoring ## [2.4.0] - 2016-02-03 ### Added - X-I2P-* headers for server http-tunnels - I2CP options for I2P tunnels - Show I2P tunnels in webconsole ### Changed - Refactoring of cmdline/config parsing ## [2.3.0] - 2016-01-12 ### Added - Support for new router bandwidth class codes (P and X) - I2PControl supports external webui - Added --pidfile and --notransit parameters - Ability to specify signature type for i2p tunnel ### Changed - Fixed multiple floodfill-related bugs - New webconsole layout ## [2.2.0] - 2015-12-22 ### Added - Ability to connect to router without ip via introducer ### Changed - Persist temporary encryption keys for local destinations - Performance improvements for EdDSA - New addressbook structure ## [2.1.0] - 2015-11-12 ### Added - Implementation of EdDSA ### Changed - EdDSA is default signature type for new RouterInfos i2pd-2.39.0/LICENSE000066400000000000000000000027321411072525600134570ustar00rootroot00000000000000Copyright (c) 2013-2020, The PurpleI2P Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 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. 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. 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. i2pd-2.39.0/Makefile000066400000000000000000000115611411072525600141120ustar00rootroot00000000000000SYS := $(shell $(CXX) -dumpmachine) ifneq (, $(findstring darwin, $(SYS))) SHARED_SUFFIX = dylib else ifneq (, $(findstring mingw, $(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_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) USE_MESHNET := $(or $(USE_MESHNET),no) USE_UPNP := $(or $(USE_UPNP),no) DEBUG := $(or $(DEBUG),yes) ifeq ($(DEBUG),yes) CXX_DEBUG = -g else CXX_DEBUG = -Os LD_DEBUG = -s 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 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 include Makefile.bsd else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32NetState.cpp include Makefile.mingw else # not supported $(error Not supported platform) endif ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) 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) ## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) 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) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) $(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) 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 $@ $^ 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 .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 i2pd-2.39.0/Makefile.bsd000066400000000000000000000015131411072525600146550ustar00rootroot00000000000000CXX = clang++ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread i2pd-2.39.0/Makefile.homebrew000066400000000000000000000041661411072525600157240ustar00rootroot00000000000000# root directory holding homebrew BREWROOT = /usr/local BOOSTROOT = ${BREWROOT}/opt/boost SSLROOT = ${BREWROOT}/opt/openssl@1.1 UPNPROOT = ${BREWROOT}/opt/miniupnpc CXXFLAGS = ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include LDFLAGS = ${LD_DEBUG} ifndef TRAVIS CXX = clang++ endif ifeq ($(USE_STATIC),yes) LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread else LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP INCFLAGS += -I${UPNPROOT}/include ifeq ($(USE_STATIC),yes) LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a else LDFLAGS += -L${UPNPROOT}/lib LDLIBS += -lminiupnpc endif endif # OSX Notes # http://www.hutsby.net/2011/08/macs-with-aes-ni.html # Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 # Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic ifeq ($(USE_AESNI),yes) CXXFLAGS += -D__AES__ -maes endif install: all install -d ${PREFIX}/bin ${PREFIX}/etc/i2pd ${PREFIX}/share/doc/i2pd ${PREFIX}/share/i2pd ${PREFIX}/share/man/man1 ${PREFIX}/var/lib/i2pd install -m 755 ${I2PD} ${PREFIX}/bin/ install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd @cp -R contrib/certificates ${PREFIX}/share/i2pd/ install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd @gzip debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @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 i2pd-2.39.0/Makefile.linux000066400000000000000000000045061411072525600152510ustar00rootroot00000000000000# 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++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match $(CXX) 'clang'),5) NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 NEEDED_CXXFLAGS += -std=c++11 LDLIBS = -latomic else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 LDLIBS = -latomic else # not supported $(error Compiler too old) endif NEEDED_CXXFLAGS += -fPIC 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_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a 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 else LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) NEEDED_CXXFLAGS += -DUSE_UPNP endif ifeq ($(USE_AESNI),yes) ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that NEEDED_CXXFLAGS += -D__AES__ -maes endif endif i2pd-2.39.0/Makefile.mingw000066400000000000000000000027511411072525600152330ustar00rootroot00000000000000# Build application with GUI (tray, main window) USE_WIN32_APP := yes WINDRES = windres CXXFLAGS := $(CXX_DEBUG) -DWIN32_LEAN_AND_MEAN -fPIC -msse INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 LDFLAGS := ${LD_DEBUG} -static # detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match ${CXXVER} "[4]\.[7-9]\|4\.1[0-9]\|[5-6]"),4) # gcc 4.7 - 6 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 else # not supported $(error Compiler too old) endif # Boost libraries suffix BOOST_SUFFIX = -mt # UPNP Support ifeq ($(USE_UPNP),yes) CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -lminiupnpc endif LDLIBS += \ -lboost_system$(BOOST_SUFFIX) \ -lboost_date_time$(BOOST_SUFFIX) \ -lboost_filesystem$(BOOST_SUFFIX) \ -lboost_program_options$(BOOST_SUFFIX) \ -lssl \ -lcrypto \ -lz \ -lwsock32 \ -lws2_32 \ -lgdi32 \ -liphlpapi \ -lole32 \ -luuid \ -lpthread ifeq ($(USE_WIN32_APP), yes) NEEDED_CXXFLAGS += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif ifeq ($(USE_WINXP_FLAGS), yes) NEEDED_CXXFLAGS += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 endif ifeq ($(USE_AESNI),yes) NEEDED_CXXFLAGS += -D__AES__ -maes 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) -i $< -o $@ i2pd-2.39.0/Makefile.osx000066400000000000000000000015551411072525600147240ustar00rootroot00000000000000CXX = clang++ CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX INCFLAGS = -I/usr/local/include LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDFLAGS += -Wl,-dead_strip LDFLAGS += -Wl,-dead_strip_dylibs LDFLAGS += -Wl,-bind_at_load ifeq ($(USE_STATIC),yes) LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread else LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else LDLIBS += -lminiupnpc endif endif ifeq ($(USE_AESNI),yes) CXXFLAGS += -D__AES__ -maes else CXXFLAGS += -msse endif i2pd-2.39.0/README.md000066400000000000000000000134441411072525600137330ustar00rootroot00000000000000[![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)](https://github.com/PurpleI2P/i2pd/releases/latest) [![Snapcraft release](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) [![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](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) i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client. 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. 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). I2P allows people from all around the world to communicate and share information without restrictions. Features -------- * 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. 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 - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. * Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) * Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) * Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) * Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) * FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) * Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](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: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) Donations --------- BTC: 3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ETH: 0x9e5bac70d20d1079ceaa111127f4fb3bccce379d DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG XMR: 497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH License ------- This project is licensed under the BSD 3-clause license, which can be found in the file LICENSE in the root of the project source code. i2pd-2.39.0/Win32/000077500000000000000000000000001411072525600133505ustar00rootroot00000000000000i2pd-2.39.0/Win32/DaemonWin32.cpp000066400000000000000000000034711411072525600161070ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include "Config.h" #include "Daemon.h" #include "util.h" #include "Log.h" #ifdef _WIN32 #ifdef WIN32_APP #include #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; 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 ()) 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 i2pd-2.39.0/Win32/Resource.rc000066400000000000000000000011671411072525600154720ustar00rootroot00000000000000#include "resource.h" #define APSTUDIO_READONLY_SYMBOLS #include "winres.h" #undef APSTUDIO_READONLY_SYMBOLS #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #ifdef APSTUDIO_INVOKED 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED MAINICON ICON "mask.ico" #endif // English (United States) resources #ifndef APSTUDIO_INVOKED #include "Resource.rc2" #endif // not APSTUDIO_INVOKED i2pd-2.39.0/Win32/Resource.rc2000066400000000000000000000020661411072525600155530ustar00rootroot00000000000000#ifdef APSTUDIO_INVOKED #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED #include "../libi2pd/version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Purple I2P" VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME VALUE "LegalCopyright", "Copyright (C) 2013-2020, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END i2pd-2.39.0/Win32/Win32App.cpp000066400000000000000000000331751411072525600154300ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include #include "ClientContext.h" #include "Config.h" #include "NetDb.hpp" #include "RouterContext.h" #include "Transports.h" #include "Tunnel.h" #include "version.h" #include "resource.h" #include "Daemon.h" #include "Win32App.h" #include "Win32NetState.h" #define ID_ABOUT 2000 #define ID_EXIT 2001 #define ID_CONSOLE 2002 #define ID_APP 2003 #define ID_GRACEFUL_SHUTDOWN 2004 #define ID_STOP_GRACEFUL_SHUTDOWN 2005 #define ID_RELOAD 2006 #define ID_ACCEPT_TRANSIT 2007 #define ID_DECLINE_TRANSIT 2008 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) #define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 #define FRAME_UPDATE_TIMER 2101 #define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 namespace i2p { namespace win32 { DWORD g_GracefulShutdownEndtime = 0; static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { HMENU hPopup = CreatePopupMenu(); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if(!i2p::context.AcceptsTunnels()) InsertMenu (hPopup, -1, i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, ID_ACCEPT_TRANSIT, "Accept &transit"); else InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); if (!i2p::util::DaemonWin32::Instance ().isGraceful) InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); else InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); POINT p; if (!curpos) { GetCursorPos (&p); curpos = &p; } WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); SendMessage (hWnd, WM_COMMAND, cmd, 0); DestroyMenu(hPopup); } static void AddTrayIcon (HWND hWnd, bool notify = false) { NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); strcpy (nid.szTip, "i2pd"); if (notify) strcpy (nid.szInfo, "i2pd is starting"); Shell_NotifyIcon(NIM_ADD, &nid ); } static void RemoveTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; Shell_NotifyIcon (NIM_DELETE, &nid); } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " days, "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; seconds -= num * 60; } s << seconds << " seconds\n"; } template static void ShowTransfered (std::stringstream& s, size transfer) { auto bytes = transfer & 0x03ff; transfer >>= 10; auto kbytes = transfer & 0x03ff; transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; auto gbytes = transfer; if (gbytes) s << gbytes << " GB, "; if (mbytes) s << mbytes << " MB, "; if (kbytes) s << kbytes << " KB, "; s << bytes << " Bytes\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { switch (status) { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Test"; break; case eRouterStatusFirewalled: s << "FW"; break; case eRouterStatusUnknown: s << "Unk"; break; case eRouterStatusProxy: s << "Proxy"; break; case eRouterStatusMesh: s << "Mesh"; break; case eRouterStatusError: { s << "Err"; switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: s << " - Clock skew"; break; case eRouterErrorOffline: s << " - Offline"; break; case eRouterErrorSymmetricNAT: s << " - Symmetric NAT"; break; default: ; } break; } default: s << "Unk"; } } static void PrintMainWindowText (std::stringstream& s) { s << "\n"; s << "Status: "; ShowNetworkStatus (s, i2p::context.GetStatus ()); if (i2p::context.SupportsV6 ()) { s << " / "; ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); if (g_GracefulShutdownEndtime != 0) { DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); } else s << "\n"; s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); s << "\n"; s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; s << "Tunnels: "; s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; s << "\n"; } static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static UINT s_uTaskbarRestart; switch (uMsg) { case WM_CREATE: { s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); AddTrayIcon (hWnd, true); break; } case WM_CLOSE: { RemoveTrayIcon (hWnd); KillTimer (hWnd, FRAME_UPDATE_TIMER); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); PostQuitMessage (0); break; } case WM_COMMAND: { switch (LOWORD(wParam)) { case ID_ABOUT: { std::stringstream text; text << "Version: " << I2PD_VERSION << " " << CODENAME; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_EXIT: { PostMessage (hWnd, WM_CLOSE, 0, 0); return 0; } case ID_ACCEPT_TRANSIT: { i2p::context.SetAcceptsTunnels (true); std::stringstream text; text << "I2Pd now accept transit tunnels"; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_DECLINE_TRANSIT: { i2p::context.SetAcceptsTunnels (false); std::stringstream text; text << "I2Pd now decline new transit tunnels"; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (false); SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; i2p::util::DaemonWin32::Instance ().isGraceful = true; return 0; } case ID_STOP_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (true); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); g_GracefulShutdownEndtime = 0; i2p::util::DaemonWin32::Instance ().isGraceful = false; return 0; } case ID_RELOAD: { i2p::client::context.ReloadConfig(); std::stringstream text; text << "I2Pd reloading configs..."; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_CONSOLE: { char buf[30]; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); return 0; } case ID_APP: { ShowWindow(hWnd, SW_SHOW); SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } } break; } case WM_SYSCOMMAND: { switch (wParam) { case SC_MINIMIZE: { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } case SC_CLOSE: { std::string close; i2p::config::GetOption("close", close); if (0 == close.compare("ask")) switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) { case IDYES: close = "minimize"; break; case IDNO: close = "exit"; break; default: return 0; } if (0 == close.compare("minimize")) { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } if (0 != close.compare("exit")) { ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); return 0; } } } } case WM_TRAYICON: { switch (lParam) { case WM_LBUTTONUP: case WM_RBUTTONUP: { SetForegroundWindow (hWnd); ShowPopupMenu(hWnd, NULL, -1); PostMessage (hWnd, WM_APP + 1, 0, 0); break; } } break; } case WM_TIMER: { switch(wParam) { case IDT_GRACEFUL_SHUTDOWN_TIMER: { g_GracefulShutdownEndtime = 0; PostMessage (hWnd, WM_CLOSE, 0, 0); // exit return 0; } case IDT_GRACEFUL_TUNNELCHECK_TIMER: { if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) PostMessage (hWnd, WM_CLOSE, 0, 0); else SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); return 0; } case FRAME_UPDATE_TIMER: { InvalidateRect(hWnd, NULL, TRUE); return 0; } } break; } case WM_PAINT: { HDC hDC; PAINTSTRUCT ps; RECT rp; HFONT hFont; std::stringstream s; PrintMainWindowText (s); hDC = BeginPaint (hWnd, &ps); GetClientRect(hWnd, &rp); SetTextColor(hDC, 0x00D43B69); hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); SelectObject(hDC,hFont); DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); DeleteObject(hFont); EndPaint(hWnd, &ps); break; } default: { if (uMsg == s_uTaskbarRestart) AddTrayIcon (hWnd, false); break; } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } bool StartWin32App () { if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); return false; } // register main window auto hInst = GetModuleHandle(NULL); WNDCLASSEX wclx; memset (&wclx, 0, sizeof(wclx)); wclx.cbSize = sizeof(wclx); wclx.style = 0; wclx.lpfnWndProc = WndProc; //wclx.cbClsExtra = 0; //wclx.cbWndExtra = 0; wclx.hInstance = hInst; wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wclx.lpszMenuName = NULL; wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } SubscribeToEvents(); return true; } int RunWin32App () { MSG msg; while (GetMessage (&msg, NULL, 0, 0 )) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } void StopWin32App () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } bool GracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } bool StopGracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } } } i2pd-2.39.0/Win32/Win32App.h000066400000000000000000000007771411072525600150770ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 WIN32APP_H__ #define WIN32APP_H__ #define I2PD_WIN32_CLASSNAME "i2pd main window" namespace i2p { namespace win32 { extern DWORD g_GracefulShutdownEndtime; bool StartWin32App (); void StopWin32App (); int RunWin32App (); bool GracefulShutdown (); bool StopGracefulShutdown (); } } #endif // WIN32APP_H__ i2pd-2.39.0/Win32/Win32NetState.cpp000066400000000000000000000050751411072525600164350ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 */ #if WINVER != 0x0501 // supported since Vista #include "Win32NetState.h" #include #include "Log.h" IUnknown *pUnknown = nullptr; INetworkListManager *pNetworkListManager = nullptr; IConnectionPointContainer *pCPContainer = nullptr; IConnectionPoint *pConnectPoint = nullptr; DWORD Cookie = 0; void SubscribeToEvents() { LogPrint(eLogInfo, "NetState: Trying to subscribe to NetworkListManagerEvents"); CoInitialize(NULL); HRESULT Result = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_IUnknown, (void **)&pUnknown); if (SUCCEEDED(Result)) { Result = pUnknown->QueryInterface(IID_INetworkListManager, (void **)&pNetworkListManager); if (SUCCEEDED(Result)) { VARIANT_BOOL IsConnect = VARIANT_FALSE; Result = pNetworkListManager->IsConnectedToInternet(&IsConnect); if (SUCCEEDED(Result)) { i2p::transport::transports.SetOnline (true); LogPrint(eLogInfo, "NetState: current state: ", IsConnect == VARIANT_TRUE ? "connected" : "disconnected"); } Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPContainer); if (SUCCEEDED(Result)) { Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); if(SUCCEEDED(Result)) { CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); if (SUCCEEDED(Result)) LogPrint(eLogInfo, "NetState: Successfully subscribed to NetworkListManagerEvent messages"); else LogPrint(eLogError, "NetState: Unable to subscribe to NetworkListManagerEvent messages"); } else LogPrint(eLogError, "NetState: Unable to find interface connection point"); } else LogPrint(eLogError, "NetState: Unable to query NetworkListManager interface"); } else LogPrint(eLogError, "NetState: Unable to query global interface"); } else LogPrint(eLogError, "NetState: Unable to create INetworkListManager interface"); } void UnSubscribeFromEvents() { try { if (pConnectPoint) { pConnectPoint->Unadvise(Cookie); pConnectPoint->Release(); } if (pCPContainer) pCPContainer->Release(); if (pNetworkListManager) pNetworkListManager->Release(); if (pUnknown) pUnknown->Release(); CoUninitialize(); } catch (std::exception& ex) { LogPrint (eLogError, "NetState: received exception: ", ex.what ()); } } #endif // WINVER i2pd-2.39.0/Win32/Win32NetState.h000066400000000000000000000044101411072525600160720ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 WIN_32_NETSTATE_H__ #define WIN_32_NETSTATE_H__ #if WINVER != 0x0501 // supported since Vista #include #include #include "Log.h" #include "Transports.h" class CNetworkListManagerEvent : public INetworkListManagerEvents { public: CNetworkListManagerEvent() : m_ref(1) { } ~CNetworkListManagerEvent() { } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { HRESULT Result = S_OK; if (IsEqualIID(riid, IID_IUnknown)) { *ppvObject = (IUnknown *)this; } else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) { *ppvObject = (INetworkListManagerEvents *)this; } else { Result = E_NOINTERFACE; } AddRef(); return Result; } ULONG STDMETHODCALLTYPE AddRef() { return (ULONG)InterlockedIncrement(&m_ref); } ULONG STDMETHODCALLTYPE Release() { LONG Result = InterlockedDecrement(&m_ref); if (Result == 0) delete this; return (ULONG)Result; } virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) { if (newConnectivity == NLM_CONNECTIVITY_DISCONNECTED) { i2p::transport::transports.SetOnline (false); LogPrint(eLogInfo, "NetState: disconnected from network"); } if (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV4_INTERNET) != 0) { i2p::transport::transports.SetOnline (true); LogPrint(eLogInfo, "NetState: connected to internet with IPv4 capability"); } if (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV6_INTERNET) != 0) { i2p::transport::transports.SetOnline (true); LogPrint(eLogInfo, "NetState: connected to internet with IPv6 capability"); } if ( (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV4_INTERNET) == 0) && (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV6_INTERNET) == 0) ) { i2p::transport::transports.SetOnline (false); LogPrint(eLogInfo, "NetState: connected without internet access"); } return S_OK; } private: LONG m_ref; }; void SubscribeToEvents(); void UnSubscribeFromEvents(); #else // WINVER == 0x0501 void SubscribeToEvents() { } void UnSubscribeFromEvents() { } #endif // WINVER #endif i2pd-2.39.0/Win32/mask.bmp000066400000000000000000000623321411072525600150110ustar00rootroot00000000000000BMd6(9#.#.ův}bGJtX)2o(o(o(m'o=}VwOWW"n(o(o(o(o(o(o(n(h)m/g%o(o(o(o(o(o(o(o(J#4AHGFHHHF9Hlm(o(o(o(o(n<{u|[-6l'o(o(o(o(o(X*=yH@I1JD 78NGHC6iP#6o(o(o(o(uo'󓇉[)n(o(o(g(7ZH?/+4MJOaTXG"7R{HH7m=`l(o(o(n(j~xeu\`d%n(?4MG;|1K5o3e:q8Wx;yHC:uR 0o(o(m'i|xpgUe>/@3m-_G4d3bHH?2LFH5hBOwo(o(l'mStlxxW~ˆ\p%cw8m3iAHG9tC7n8oH>*XH7m@|g)o(a$[mwxxo!漢u;txw=3e6k@4fFG9t=|H>HH?9s5iDS#5o([/yxxxqꘃp/vxxxl;rHHGAG:t7lHHE6j6j1_=|CD>^n([Hwxxq1𖃜k(}xxxxxt=DFHHHHG:t6kHHHHGHCE?bk'bnxxr ļ뛳k,|wxxxxxwXEB8THG2j"Iq(V?9r6kHHHHHHHH_$Jq>HHHHHCOYxxU^fiwwo"xxxxh}Y hhDry_$[(?&: '2K#A`3dFG)= !DhFHHHHS&lxzGl@Irvk2zvwj~c/p|b[co(E/G?2b4f3b)Ik0[:vFC-X' !0?HHHB5o}P𪟡o>Hb-]uMi}YiuQk(l(8XHG=|4e3d=~@5h9s:v3c1d0d+Y0eHHHGj9Bb&2_#m'o(g)l'o(T*;wDHDA:tHG:v:v=}7mHHHHHGGDIon(o(o(o(o(o(o(o(l'vJSr@Jo(o(S-??HF5hF?OwP'<AD3Nn)o(o(o(D9U4f=|6j><|5g?4f?Nvn(m(e)xEOj&o(o(o(o(F*R-tO4yd"{ZLo(o(o(o(f)TRn!!=hqo:7ynؑ{W(b) F@N*V ),O\{Uȼ]xEp!1墺j>GWӋ/{H)IKF@N*x< ~}ojP72Z.CjTu2{#W!G'ClWܔ-Z^P,=o5͙Ikq:0t^EZٿ^ҫ9wb?ubAY\#&s$ *j?رeu {~Y~Nu߿Er~mb,M0K`î΂w7xY!Gbk~Ǹ]KrsC\;@KK w`LL<y}Yg#OE$,c68W74u矜7/}#\8*~Y6[ uThm~oެ^~ϱo9ZΙh#Egٸ2#yخ]Mv}6_>]0ۯxΊvDuhYgEի,kJׯq?SXRw;'a!:7q%0.}ɨX+k. $^ŸH8sĒ ҌTB짭|vى ;UW$a(^{*?EZ~pyef)TV:,@#S(Pqdq \4azW9]^we_Q -f5`:럭=b7(pW'NUgg2 [Qeʱu0P()53'G>=0SYWn9܊ɀ0Xſ)vtٴ5/YFÏ2AY55rPRW <4HbF{|rzGWo(E}fzZS}ef֕SEih}w]~u'pj?%@eQpmR=s;~Ɠ5vs; of8AN_c"͚)iE V ݯuu.8UG4 aul.[(ń.c{zDs;339?+ lV*֗n XیS]3pE5*#ށG浯}xe٧9jVyjgk KxUZ5K@eCLX70#i0%?T#E:Esm\囃_LKqڡ|;)/EqQOXP1(wHK,FI%~8A}}#~NhaY|#+Wn}~qBd_6qv 6⭝飯z4ϷlܠDOD TXAM7~i-YUW-Nʆ%=}-W;57UXŐzS}D׏E@D#FKlWШUeg9`O_ϩ>yd332/v9|Ybix1uHҮ`$wN|t}%~P#1O~R7?3?of5]$ImsБxѺu뺠?#Y?j7PfbZ+7qDZ?tjǓ@KOpK0wr`|R 晢:U3`3ߡO~.J](m*۴wɀB3d9 cDžB|ww2ȐtS, X.7/- ?ZtBQj˞.FX'qtFU n $4v㨾Fz ;q `ؐWrLGkln9mJ{#UEwn)[I0klw@31RŴ]ݨ s'y{O0h$o" X(+qG6trUjS?]YcѓJ+ӫ>v߲T ;zf~vK 9i6QFpp@? -ZfYwv _=emaVGDh0.`gG$8|{M2]Ӓ4 Sgaȳ:>0ҘgI(Qe??ɩݸh)Wx@&gE6>sj 7)2 93$bV 0 /+zy.%ew9Af47 ZHa # %ά7K U;Y==X8`_5yXtMO2)?#F\}i:@E 횤\_lgd_pRx}?;ok+ ,YAYSN 7ǀoᏏ-d߇TמH AAsGY{8Т;&])mZn~@2]`ܸq9 M Tٌ^4 PoPHYjuƞhk?Ur胡-Տgv̛o 8˫TCIo8 @+;J/]~%>n_-W V>بxLb 0 78M.nEr'?ˀTvG/njnђT^htyh r!O]3Mn.l ;E{:R8^`vu)/G*yd2{١-Xt.v}rM:{j €ۿgq-1 ËZ~i|Vo辀>?[ugb[l, S@xu@nl> 2)t]#D'V&,8w5~%qqLh__Q霳2VAsXPuh[CZtmNu"n>H(;>a~%rF#IJ(jYX=sYO)i:[K䍂S?]놆<HH7t6&k+yq{*^:tP.wS nטN.V[ϪMz$hU%c.LH] ^a]woLweaG˿_ޒԋ`d*e_ue Ȍa#wK [×C=4Rc϶Z=ݎ9TQlˁ:]O+>| A64fkN{D`Lׯٹ;cٓq;xrV !FQXAZ3rXͳ^z7|ifL|TxP.le} DѰ5]!@EBˤUҖgBbVpUb;h,?fes!/ܠZ"rq ai#RݻK_;C_;ŗK$ڦwuO@ʙlVe$=hzg4#l{M7iр'Pd86^0QDu %uM'B}Ҝa&f݀iK?Tuӎ?HUߡ`k'7-jWsՖ'|bPq-Dޖ\t.-,ǿ3gL7Y)5)q5rXb#Lc Ṋ65 ?_r0'z_ǩ,L]FAR+x1k")8qgY\Z|GԔ;:bNƹTVǺihLFTj.HŸ8O'#H==qG 滭F2{0-P8%24`DպXJe n6I:[5 9LuJ?I퀰5??fmキƿ tqvY ~d]X'Q?HuE jCZÿZ]=wyRMO+*|p6}}86jnim E `qō bo;qy82 8@@Qe2Wp;[Go 4)/w BAnvd`0<`f$kԕ9&n7^<_׭U;3*p/N/bЫ N/ՈXF+}Q@+ĉ73?4K\TA-C8! q@#vV3.Z/׋[ 34b]֟btCj2$@XI5Dv+YczOZ6 Q*_a=a0U*~9$&i Qd տ&N8w?d⋭Le#-24#~2+1X&0^?x4}}}^B8=-:&~"x܎>zd`<90 )wnK5Q&\zh*RRրYQeTŎv0NJAN/̺ZxhPEc$F7mh xXգBۨ6qc8{[Pa)bW1\`%2,)S} G@b^pe[qͶ=5}82F0=u-_Fl$)GtIr;U`p]~uΝ;7/1Ve"Vlltt0xMmq\?Ԝc cwލ(}Cڅ!bǹ4Q:ql,7]vٕ}NF6|Lѻh^K *t*]iPH*maBO]sOϽ/fAV㘡3'DLzo}@c.dWn|]UܘHHf3̽%%Cif;Q~[!"#IYw 2AX񠪢Swᄱ+@3/ҢE[zÑ7^SoMDS;&J;F+cԲuՒRӶatlrpOL0_>t |mqNQcuLH7}*հZ@oꯉ[G*L{(UV+\r,r-ɉBtTŸc20jdmb]oT~, (L+XL;?A7׃ ` (iSa֗~O>B'7kxٯFZh 1=.Z@guZ\$5MWEq Hf8m3A+T!3,eo+#GzA˳dUTbZ{Ze_|'̇{K=?=eeNZe%L 1Jr w+/j^}gO̻/={mP1- *>wҁߓw7*mׅcw[#8_G9 y!mL!WR'=~?m]k~ꖆ.hGO\7@f#]X_esJzAHK" C 57yz/zj OjŒ ɰnz\GAqE4+vݰ!e|>IJ9Y1W_}NޢOw^ %-c=4{2ȪEZ6ahn3fw'L8iXLƇ`t>pa^ qvsNZ]ߠv&Lף/Dvvb|+t /qźf!Fp@H3ꨰSKJL^d,+*Jt?Y{饗~Y?S) ?-78"S~zN;VOg30ʈN;$s7؟RH*(X#+Wy|X XRyܿqXb&k+Rn8U!طV_{G]oOuXۻyV ӎ9C@3TIS"J,Y4,y?x$EUdr(!߻qD& D,Y+`e&`=-TOZ.څ+"r ضm8v5˽<8P nVԑqYNqiun#4 W;ڪϷ9/F* Y،$ %Ko8}~Cv= "d"SEcc;XqNr93/[O-V TFK Y5]UUQIIXRbE9Eh7xrY_@,~C%w`8G$,fi%0H$h>h0FkJ b,а%daCׂR:)h)@UhvNF(|^~NbhvH4 lnlh>l~e+G=a8 3~[6Xqb~C@b#$mZ#Ək*f)Fak\#41H몛I%nwJKM,CjFJkswt9&V-fkLo± 03**!2? ĵoQ#rmkwu[LCȔ'^.la\bevB)ƌbR7!mS6Hu0RYQq[IIROB7ТbR##8NQ5YvwBٞM8EISTƙS +6bNXX Xh+6lؐNVoh.v`!=2#q! !yjwI&zLf U/`Â" 7 Xe6euZTP1 4jwJ3|$C+!Ifld>,`#ɈxCݛ|<Ł-ϙzN=;#$wYדd|.N3@7JU n ƾ#rcMHg%ق="0db Y0 GYF(T`.^4`"$̺Vib+V+rÛj='%~%7$9T2gFʤ҂ ȴUn NJ7Ǻ4ۗ돨5u#Y4o| ns%E)/om7:@i)S;֮xϴq6l}]q/V<3I}R ?ͻҗ;_2j`~{N$aTS\8 l[.lU,-jcj5 $Zlw11 02`2DA!< =qE]9)!=x&A!ӊɺH5K5 òr.s*qe&;̅Hl!Uf+pY1OwL*&7'5L06|~?2MôȄi 2i"D46*S5;ؖ ;>=dT}m79:  7`AK fT~kTۿ'%ZnH68٢r,nnxfk!"cF<6_5tՐ kt?í aQɡV)X8^a8ƏF|S=Nl#+q{=Ѡ<R_C[FcӞ~l75A4>KBHB%&'~;fMnQN|@RKtp D]er8 l!${U5ݱ5sfzlzz!WDvFdOh+ym=b;s^yݫ>&BŊy@8-+޿4H3M1S~%7;7nDo?/rkϳ߯{ML{J4-12R]I2k;̕'Ց2=Iû0Ս8rwgMVwn!L_yuMxt+;luJۿ{&HBӠC4]zbs?WsB8'QTZ]ʏrgr%DA#LΊz2tŖ#wl~m۶nYX``W9a6. a3 M1vmx\ldt] 7w<|xQav'h V k/vl*K]V8ܤ[ kW+:N7|;WY  G" b(O i2)!`4:2WyAf2?k ԭG#ZHi;@N{nc|~W(:#A~4g,w'Fo|VhPC|j36lǮl^KKˆӍ;j@!?3I\Y,+S KS=>:IRB6CL]8QK~Q+۟) {ُt;lĀD[vXo[dMr-~7sE$G<9 $!GlI:-UEBY6=ǥنs k.bdF= j*4ka > u<$ЫDQ'n!dt5ˬZ>{YʲLQfIf[q7{pQRQOlr\8}Y s?/7=r#!XsS %8vp63SoyM]($m\T׼;ȁb/eW8aEڦп#oJI^TG S+,c]k|6߾ڌ"8g>0 93mh_O`kXi(h^5e[1C(KOhk ZobZu!hڠ.YBdvqԽF$`n!OcD6@C + KׯgFƟQ/IE"oMGL0;3@ÌvdPb" :㠸 o揯͜93 [NN?C{PJKa?aة Ԉ[3O˹)w Xa__EB)ZM (  /ڢ**1E\@?vn VPddwpKS@G"O[vDXOv rc0_@滶cw>d _zE@i)xD1wFb S奅1NeOF{ V$v4Bcx6}T|GtEUhT#hO&ҙ í!GefP r8-.Gub-{MQ&1غj ͜|ZMw"e:ٷjY.0!Q>n $wE-%Ů?=͐77g\66B,QQ'(}3E߅[\`+g QkJ`@\m #!.D0 ^|V \*yOn@B &bP-@c hkz`B"cZ;壥VBzlwiwG.6V_1keƿ>pd@>#^c$FY9ƑI@zAf!Z`DSҍvl 6vk,5Wsm_|kn/LwAPt|o}p٠V;q8eB>6F6*nJIV(2(ƚtu󻙚L{s:IYƠ k":F+A ʅlc4rV!s]^m [!!2_y0V¢}-NN9 3(<#$+G;-Y%+C긴9Fu ؄eJ|XP_ޘha=}8N:sj63[ HZQnn ߶BR+#1*pl[A 7_;b#Vn@&n uz P?OV2y%v2KZql՟1ks6!#e .ĸE|1'ؾzEN5؋o~O/)+rsln1ϤW-J[qL*u>r|}HX⯋rN2dkKNFqí4S{dۦvV. (\9XY7v4:Po]D+Н`CSEakk>`@8 c.R[4%F d$V;+s`BNڪ Ƙښ[A=puݺGzp8 `/}U~]ʙme=UvbIi @wj>c`}n7zg0P?Ȁ Nqx-Zx,߾:IZb37M&>HJATr=0sL -1+H5p`tz k`kȌ%Ls"bMVXO?,d:к;ߋq*@Gηke?qS-q"V\:!. a{'4!(H">ăSwgv{<ȆDBU d5DžLb8H窅}F1D'sۡ3 _⅋GgCUh:jsgvOO> ?g6坰h{bv4xPACxN)-])ZY# Zjp8ufμ#ǩL8u+71v~LE8ι^O( 4W0!m<.<A!5i$Xj!cIj؍!q {+a}>fmM0< ?FTX 5 :\87eYLN}E; )״q9"O78ȟ4FR{V`qmn 1kKkq1z>%B+fʨR )9Ѩ%bD4!jk%3'><7=ql\?$y `tPV{~C[0a @f&~vCPv6`n(!EB7ڥh4eƘI!BY 0kT" D$.ɂo EXwwϗMF^v Јp~|psaP?,y o?k|6\::tShߢdLl5aX e8<_ VCGTzojI&s1}6E~!5!_elf+p!+!w[uIˠ69<4f o-yet@\>)$V6UV/rl8#lzۿR*iTgu4tTP^>{h+mTI1>/v k6"[K+&~`ڈAϵ%X5^"x(3n)Ãci*O3,h q|8; <%l1 g tAjQ8x"9Y3쉚VM f p]%6yss^\ oo MS<~?Cpk@@y0&DϩfC7JH-7 UHLV[tFX.;S|w8ӡg-Ĵja[ h*q{ ʉ\B|`R3xGo7I hx")Ԥ~kC&D4 8"No6 N@5cAܨc\?oT?l7!BuIx/<ˆӫ\qӨ>xo*MPT\#=fĠn >S׫=UAĕpkq9PkAg`(8`Rk|Ig J/bqӑ_e%Ij;!&Q)Zhw Vi䦺 YU![/wWi~hePFmfx:yݚښnxhSj^TIM!'V5i24&i7m eYw)E3q<.`Wfo2?tYrF3o¸~.$Uxfi Jo1 ~Z:S >~=!`B3b\cj%J jQxҺ\{Z.; V3M"DiE:agæ\`>Ӭg.2mMG LV aQkĈu ,pp}ϫ OOnC_\+޴i3a䟳zٶɂP~H,n8s L3J{h0hxh@Hk1Y[p7G͇Ʃ±> ZhmV8ta.`]2T=#."ZA4Tbeu^_ p!bB+]1⦅A4c$ĕ^">i ?P gO@"dVR"(fM#6Xm: ##u׎UF&j``^ˀ%HR9h֖`-Ro%ԦͰ1?Jj@+yPN^:V`rE8Cf)J09G|iFxV50n>Lz&q Hi|Z%g!-qw­tB9bVğ7͟MXb aԔ:iPaccOjr+O>]5aj9(~bPOuPE40md.h9)ex3[0iZ?BXB| &/'(i%p^CtB&q 8ku\wDI`5HۇJi.P8$e2qz>猄H l~Lf=^o%^U 1bbGW%i{ hSv;.; %u5 >|cPiE*"Q+fw/χ. )0-"8 Dx~9I^D׋/,$" VuCPx \6SJV\Jуiuh61zvP a NE*&Y `OnasAKB ֈAb@4.(I(.Ӧ $7G1=1JJ DSűr έdq`z'Ph`:{?FZ@%zE.Ǡז|b-LL:  nHӁh*DR)N0"9% 4Hw1 z0[}%E?܂GŮX[۸oYXha >*p22TzA"f,o4͇~?F1*>H# 'v~\mLG:T v،SۉqOA8aOT0!_/.U.(#Dn^E=n[7p4?d50GE->L@ 1/f5a1J@\ !Lt=t ,kdzG7jc'1k /OXI s)l8T7L[N/i#w*SOcu/_nyx*|س{[ 6 PF_AhsN.wh;L3TEM6ؘ5=ugC1@"hH`k3: !KedaNn:9&Tۚ.Z{ˉezG;f"*!eH`=PMM] F7 -tpCIK76b)`0 }iq.M= xXS?y1sA,@Tc[lM3']IH{IܸΛ;`4hr1Gv;y=9UqrfU‡Xmu8hK]Aq|oWQMYٯfE'"u8?)ߍDP0ؗ__] e~bsq+"pDžEp7ht;sd^|!jRe7kɎ{~ \2,X#p['jtgy_PN؛[to 3H! |gzٸ 0Z}bC7`ʚ0ju37X軽*!XJ``f==Ȩ`3Y/s~b ~xa1u3/~OiVtH,kfgUd Ԩ >5H5)tZ5H1hsW:DPRqM~EYƘEe:&X(1-%uǣ3#pہxfC9bc̼v6_ҒT>2lCgV<|>ҵ7X9G~2Π:dz qMlIե nv=qhьƑaLX2^?YeNuY>TXip,AՋ0ZHpC $V32a=H]fg33a(*ºTf<~^5G3t55Hkd$3oBpC>TJJd < NPY;-k=qn;o [oz1df>gu;\C?)/O(,y V]`E8ӜI^?!˟X?(O Y*]Zp+5s+w}TNJ{,yTt^d̉g7U˿@A2Ƿ 섻|&8Oʺ=S'/Ϟ&i|vH.pu!ݰ~3Jgn Igu7oL{@2aVg:Ls]Ռs_VYy,ze`9iUe|pmO^Ko>Ҍ|ɠe޳~㸆YI r}k>/Lv>5|9o1#zwܼժ={z?Cn{<{c WpŠg%")0,15vPQ#5z"*ᶧ[`[s*:0wG~߽MSaDpts;@㰨Ez _(CNl[iO.-q6Zֻ5_|^=[/}eO~r;ܲ$>7'6#z|zyڪ%Z.$H(쾑 PePd:b^fUIΆ\` d%p[Rr钳7¬c2$ϔaQՍQW2 (G4fწ`&!&A(+dsɏW}ˆ]0 DeNũ{&Xv@gϔӘ+Fp @X Wc'>8{:>Kp87\4KGlՄ XGe0 f z= DdZ{\|X!ÊG6S-E+`z6Y`3POH%OKD$4=`QX@o`rNڴEn$(9hRlEax 85Nd9!"!P('piܤtZXe#Aeʲ>92SҬGѥP@9/9O1CV|;Ċ}xc?K?~`tK .ܵxQ_9Ő\okk_񩶾v#qpvza !% A9 )V"w|Ar .oi#u <':N>2t P•7"' KNmp(BLin!;֨*3VOͱ`0w>?M|b?=b-2faP&–(PGi$8 ͏BC- b992: 9^5D[wﺤ/ѴG' {t@&Aزɵe;PyFI#KAA~ ! TYMs%˞OA`HPC*$;Z,6[Fx=}%Ճ' kߏA8ɴV7'﷾a8L$p_v5\3pwGgola֝w)>W+mdVхxbK9>yK4" `w!:pFӸըN5ɊnM?cGD#:d7{ǁ-~O'tHSR{A9@YX 9`eUCKxƢbKD3a%̅(( !P{Iw{- rjgl $@ t>ೲr/cۘVM?™̙w"װ>?/:ht"E>e(]y+v i=8+Og!uva lCc7vR2m\0KgϳψyUY'N jkj2IYy4B F@vb ^Sꃀ&b s2iQ;7#rfew QH!3_ f>,y nUhFgiPz/|l7|""&#hPA!,[ ڞ eH; #]DJ/, (}6v5tJZz{-c{}, ؆˛9q hyN4dL=6oV $):V͇: ePkͱQnu87¿ގ5eP%hmNf]tW~a! IS[?j kKϙ6vw͢Pfe +5% p~B4{:r^b$ 4p_E1L[[cbV0YP`Ŋ͛7608t];R,+ldK59 kmU[3ro8Oy ?RrLS\vnyHA\T*ICA&^':r˛.{kb2iu t^/.8kv~|J!*bNjxZ_ʒ"(˶RMglͥAU7Q+fjP@SC\*-b@hʟ; ^2ax>2*et៰7 jE Fxew?T4+iI&?Iݐ_2.CFQh:XjÓ[N+|N8t-a}xOr?Ӹ[]jPyxIi7rFVfzۋ& 9Ț*j9>VqU#*+1-8Ȫl/2?ʛF#XǓZeEyLaC+Nv^dXYB#& jpo0íõXkj2)XI@  aTBП DR84TNDT[BJF9``7Js=îPac..3 dc#kGu0 H n cاhr,qYh$X2KчՃia,H0Hރ5mC) HE|4 ,kl}IhOZyؔ3qSzp9wnx~{/u%D-p%7bIzN+r—,e3!c^o>" 袼]7nAK.`]Ɠ#LM@dN@:~L1&t,<^/GTHmlȃUw}YKxs+ T aSS gUNtVlQ*3[cӼKcg!e5X&h EҢ11.N4CTCXѯa@ jx(eFYy(* BdU;X VmV~]dY63]˲{`~Ev< a= ĬCDzPAyfڠ4%B3{T]m͇D8TDcFܩa={`Szd`нg5ĥ`Z6\Wzsd,MթjJ9m /Z 3u@).05B'[LHˌ +bV)3)Yܳ8{z2eu6b(1."AklS="57|<&xA}ay])?k_ͰOHqG4 k}0'!!=ej PJguph&iq% 9F@CX 1bp${- 7w9-ˏuT6íXml".s !3d%&jkW͠10!?T`Ra y֦M]2G L+4%~v^6uLW1zAc<ᵚ~, %<JSJ}y5̯ҮBEe>ʦ ~tv`Qyf-WӶX0Ej0N +ń\K+l‡ 8t[)N~_s]Vf)&VC_(3˰AzC"UgK7m1/*'np#EU"rHM(1-Nr]ўX_ƨ-OȹY&ٖӉzIUe!$:8Flbi.v DR5pZAgنMnԵS nrZ Qs]Q m ylZLi0g49c_k@`aQ2O`*֪\\57c, E Tn{6l%3 ##&423dvD뒻}oZONxQ;YWRg) IX+RVb"_zinwoG2pv̙ۇɢt>n0G h_cig{ʄBiVR: e$ͨ7/Rj0%M$bҦ5LLLZMATK@T/j䀒ұClJ*) N!XY2w#Aej8?Nio_n`L #c'jekEIȿ;: 3˼pьpktv}ei8d$=t;ԀiŠSU}:`W^̄N'EXzص7 v1#TT2[uZKnOho5g9b q Y(X)"b4 !#9WjjbG}'ZL@|t_eV0%Uټ[>t(5 O>2I̮,ѰX(ɪHjdF8BW /eDz2̣qqFp;oV#g<[GS}gKDL FUEL_}'=#l7L·i h(0N"C2,/ eVxhbXQ_!_ > Ls@߅s"DӇWwgTw wh)>2Mm#ڡr=?Kw?ٜ+VsS ٮHE (0v-lkKQxִ AMC;' vY<ב^S=g.OMkqM >yҦ0LcP`ЅɍmT'*6'}Z, !k vڹ𹹙P!h՜h߭M!6xbc$, AьC1eX%'R4sKe;O<l cA4Bu  _[ c9NNC0眖M_Q"c9e]EAk: .悏G2Z 8֒b&kuhnn7#[ҹp\<c@6!D Hrؔ=Uek544GߏpCejHˮi,4)56)8!p15:/gcJVE^Myhnoc S aZqSx0r4~ֶT!›`-sdSŠ9t 4U&AhKO0%9 cC;qK6X9^[Vz0gor <5 M| c*CI.G{+=\'48 4bq~;8}`K`*_Yg [X-ygs#' 9ly.~e?>ʬ*yZ\McN68|1"H4D4#eQi (BW^ (-2UҎ^xzKl&VҚj[0ѱRPF^b3h#~eAq~c2|"('.@@)x%Z)p V( Y>]vU<[@ |4 7?86a̳ҏYV#zȦz'[|% BOYw}u/)̍TYL//KB4TV)x 38mu/p-9!(`[|lIT-I\jc WxِuB zw8h}6 O5aK`%q# V00fWx\4/~J `{KO]ۈF0 آMiH|Y1?b#3H#Ɠe;tJx|7i^K.>?HdI2;2xxWH瞝իyG؝q{c'=->}{E+g;&8gn"WOc{rX$<3 t0DRpP0(bO+v@ᴙߝv$nb<N,LY/I̷SAp XIfӀߎ@gzj{?1ӊ?Bg`dZgB,aZ&ޑ/O8Xړv0 /9;oVxXSlwt Da,6r_Ӻi;=+{x7,l ܚF%r=1lHN0~6exjM`N ɭyiEp4? R,?1X1ɱczE^o ٱʑV Dl"Z  t/ ( :zFoz3+Ld2 0O,k1d9ŒӸGz.|2v@[hy1h#glӃ7pC1a*泱dX<ں;N0y '+b&^Zdf?7L@a1%E.Usύ~-Q:k2H#! 6FjnXh-9NF>#¨+@ĻH3M^ƃLC:hNZn))8"3Έqa*W:ܹCR\8d-,€|^676~z&,sY.zsOD *8X;D#q>`-,xpn=cUլ5o1yG6\;< Vݔ 8B+ 1Li6|c$=n/\x< ]ts'֖lú)`g6I䷥=$C0dq'ZSQqyy+ +-mƲuĪ<mq?><k mYqMyN;2|us^J# xYDT;<z${9XlmpT qFMM2,[춎 'W=lç*l@J5ڑ_^Ne395Qʤ*31=@t<+gsf)ʺG `ؕ9kV@P 3d<=.d4BleSH08)G=PdŠJv h dm0`k>qmÅY$/ЬJcEء8Oh@e\*$ _y}ŋ+ksy/>DނHAE6}E.(Q3'UXK\b>'8y+5]bFa J1S$`z\91nv|Bx-bd1/|$6 xvRZ`W[6q!ĉܱ]~)hq<#|!eH7 _1r8b)ėmqpD? "QPx&l5GkIbuϼ<% b nfsj`Mp䒯O:`ue"d#[sy>pO,r6vd%GU3{Y)xln[3,,]SNE9F<a0C\fπ !l샥su `w[ &pc-@ |uR4+!B;yJ0qa,"ӑ%@DXz `%%I{̹=t e<3v8neߙ oj_spCic ,B[T͗flؖk^hHP2胼kjZp.(03=|>e_MXM\I#Zju_w^RUT@џMϙNhވZcQJIQ9kY/姃Jq1iM<# rdyNȊQD5!$uSslyPf)Pz[S*C# KuJ>Q\'gZ8dzʸ%b- "1KG0mN);00Ku Vm>h—gÕ3h.?6`ęcmR?D`*%F&ftaOoRR`V08Fzk;cAxmoNRhy.Rl Ck_w:e3}z/l<@k'!_]~chܢxN2rv )%EX hjBUDha5Mp=׿4k4IEe-pօe|\WT(A Y?b,1Y&3ч&`g jZX8Y8هy(Sz=A APC{ƾҒco RSQ9.cx 8?yF;#LBM}' N+vR ?}y /&OK0)Tw"߂,w _ʺJBo>gU[21Hgs~VdDUd@b-.se| 8dZ`$1'sax(n֞`O' U!BuUm8;86hU( v"? T 2\!!ra(y(3?9q:\32HN:U.,&·{Cwb8hV)b 38hC79lGI 5;Fkz(]>Sw[XoR : 97J, #XFl,U5I@DMTJ`\=E^QWGKXh%\{r ,qזS>3{Ӏ\d#C@1HӀX j(.Q0t6#PIƘs]kjGc xm ehBq V7L"? 阳anY\1+#fMhEyc7{(32E~ǺDqߘ;s.ә ?i)1X?( i,͘%b1 Jq<&뵵{ _+p8S>#/vx(Ls=?*?T[:$Qs&XBg7 x%N)>: 4+6P 3Np zЍ- >77 ] 2mm+π+.'`$m FAXK3 ?~_ !/=Fv*:p?']I|sad..uͅ qBc0AQ%{buѧ⯿N7^p8ʰN6'qLa2+x/b}\iFf,,׆Bjp' ϴes`=;Zt~ 雷Px 'CLfZ7oXnjƒB `x v:څ蚟Hr}, 3p Zy6a#-4FBdGSf+~n\0 3&VJJR?"CB(~\Me^3'N5'UzX?,LdA#(sZj& [ d́*Wy)y,?m8-k6 50M_Œ?`$\lD/)b~nXNk54 NƐ=ޘLT qnO!M|ZϞ3'z!S<,`0oH#[n;G10F9Jr0¿%>t0%1]m89Qpm;, } @-qg~L+ ]KkN B$".C`okptrØ;daQQRHlUL{%eD԰(hBX:Pp ^b?MDȇS >bRj]J4B('۪"1a5FUX% R:LVQQ=LPK'YgB)rA,JRAƟ q0Nݏʨɳ[Ϟ%O(7Q.kH#M?q; |FMgIHimPzPO|v̰LEZe8:] UaG[~jN\>PENի-vKA5=z4u\+18+"dfP&lfrpMҧKiܧ6|fy 9_%R̼R9Vpp6%ɴA{~frFpi> {K0 W"NYף{XOmǼr~"rKiz_^gi iztW i߾{\ Yoy!( {{l.q-}tvٓhS< bw0L+t„B;zJAu.)ɑ89P m 0+Ï.QC,?Xw ܗ"oׅuKئo*2TJ#UR!,RF_(/ aR-2HƑm5]ZۈQ"̺C}C BS NКxD߾5CLfV %ټzDqwN43˄nH-rj#Ԗ.Xo.EGrW8279X]3ooUDw =`Y斻 Gf,K4in{Ctb!,5'zqu wcA XfK4*se {'7ʇҢ _ {:r Nң,Pz_@V^veSWm\uc<_%;Zǃ9r~x]T # e7'woz6H7051n9Sϟc疟 rJ%; J5*tx ߎ:gUY;@xݰ<% 88h#\1OCrN5=(]s2>ƌӰrkw DdN|"U0I#aVDOoWkp/\37  {~R &zLt { :h'2H˶/ɘLW)g I@e̜l}]ޖݻM Sf|?_g϶Z[N \?;{/,,b/K4QK%O!䧉ILb%QAAz/˲la{mk}3,guν{ι|0Ζ #5ˇO&s#\>9MttzFK1턿n3c |TN7 f<]T`CJ<"%}1J4`ɩGF{6tÿֵC}w6lT' :u+ =!3t3=X`1#۷QD$J}~.HWUU088.AbgN9)ɧgjg$bn'm>3~ "C.6o~Mg] @IgaQ}M-YT;n'E}t FKtYv_ॵ l;71U_Xi08RqL 2t2q4ᅽ. u h=|VУ:V۾#BooG7tz1['VX|F&ܥ/fHcCl(}$g -PmU9 8y m~8 G1,G'Ο@(h}VK`I2م-SД bh„ᰄ5In> o$x4pxbfM,gա{I6ih. &aZÀ` ]''gÔq4h@hPzv5n>8 3K3rDخ̧[pc Ixy( m@z چl (_~ 64`g ) NyfyPkx3$h$ }O]FaE~N݉W_V) /b=>\XTpK;²Ol!_H_/?Z%LϵM) X.A CT#2ŃqVtw9O'8:}EA@P1NMUX,Xa2}YlD]Էj'4s~*|mV!Ahc?!ZNL66A@gpNy|ez\09)P==!bjQc@Ux~B!v`]] ۛrb0i p$3:7hcNwXshV> 3+t??WLM~n$#ԆOs诔et#a2bM&=T|ܰr_ 4:ဝ`ʔRO^L):̜{b 7ĐFF˅_Za,2ڬN{d}ʯ+:[))v XCQfDq D2K*lcz5(*)Ys{`-ԭHOɆ@#\2 s28bD!LhJ( >2.ʃ<yE. ]`q{P#' TUws5=3rM 3o_\5ulKeDdKj݁zzq=F q8:S2>;9/,CFKȲek3j]ۑGgBPzy~\0& F.2ƻO~j w9gD@owI.Ҡw7:?^xL ]]vjx5ÀXKqv"?>9ֆ8,L BPyH7dxfX^(  aD:(iRi*z7<UE>^pߜs{x$ xL0QPqш8LxݬT>}=G q>>X3d fDz)38o9YbNhX N$xldg;\\w-}V.%T߯^L<sn?A',Jusz@ X6;.4>OTȨ}Ix6 >D0_TQ6R- sl%ݦ0u;r,H>DNZ/լ#sk 1U[oٲnU]Jt%Zb;V!MQ4>X@Kn9z4ȴ¬1ey5>Jc >d77/kMFKZ //rX`/oഌ@d*}@]VCSVZ52?z2[~oh!Y]44td?"v[Km5CtYzv?Sʧmn.i^5М0O IR9<'ÃNA];hŊZV.OsNc7a!ؑ@Wݼú3OE46 =ʄV*F}Hthz} %'ݼ]B/p&tn- ; n 9V<cm^,H4H;Ϝ#CgG t•16g H$#TCcNdU~~ǽ^ut"5d<^gq@釞S?aL34YjF9v 2*23hA=6$aV|mfGJu $[/|NO"6^%7, &kLRY< MK+&f=?33jӏL#dt"^2q[x']$Z`J'_#csNqhЙ@j(vť} oio z=^paz{{XEaf{ܾDhL/3.=fQ?4 UQ¹#o8;. ǑSXjaI7uò]^hp%.A*0 &I]! )u+OЯN]Ȑ% k&Mm~n* f73yLW8:+gu,R`EkZ_ ?ZWO}#:A$%OGUUꋯN/^uM0 O63SpXy}7sxB$"d ~yi!7y~$p ѵ0s=֎>@ P APu>>d(3cF/uWTgMn͟  WG)xc[:ۛS!6dvXQ:dDܼͬ4 '8ے{x9 N&D4.ؑIl#ɾ4(]S%| _}޽{Q׸r)N03N WZ,X+b#i-~( k {`V~@N/bybDZC]>&!'hԭ0Tk4CNR#bPP&&=z/rk~bI_r<13}0NX3u0K o>RM Nᜀ/ |'0k޴{U"&ah3YVΐpy`bSto#o'TML˦MBiM~LjfMKuќt{$ *4#mVL}V1}A$@zt7+|:ɕe$p8h[N%HzPPk wqQ^>K::FscK;EA [wC+jqњ#/ Xdb;)ɈVw@$eT f>h5dGa}`f3tHhȮa#ҋgxvѡz2 IHʼ25" $-: ?!on@ `erPX EF0}6>>u->a ܋ga9La[MNZm\4=><_t'TUU \X8cGcNX<yh4أ1Izz}UpVpNQ2ci8#@oO9SneWwv@4)je&xHLnPP$ao Şؠk]egPY01FU( Kqs];q})8 㾄.nZ]ޅ|/1ory8/;?$=Qx@zpyydzCG~Y=jT1+5Aw5Q#I:/iC>ʱ' v8+Q$u%f±ʛC_ N8iEOg Bz~y'e˖IA9p8R{m?;\*4j:ViD*+yLc.ao4/DMvJ{ד՟.Iu\qr@)`˲9Y'j$)"X9h5q6ApZ1BqpiFoD:LA1Xa u cQ$d,vCve,< $ЃQnE1\Y_% |M?ʉj:z%cKƝH`X3b?9EF6P$[Z.5HگLyegɶYqrP8Z7@M@GQخG _Vm*Z'x3R->K$kX^pLiĖ* * \4Fd=g9 FMD9 cXƪ%pi>MA3ɣl\whW/k3ze 6]WbFb jnp{;}SY4􆫫ExIjt%j,QUHI.BBc9d)+`UƏ:}'B*M"nKE2y| 9ċzR,ȁaX-~%7 ~;Y =mVUAzT  j Mm|3)b&< _(qm.7>Q aci=ګ4WHk,+ ޶uwʌ7BMgHQ/$VIsll v73sڶnLb(̙q޽ ҮT. D_z"2PӞ@%px *wgo; ,֟5>8`rr&f M֧D(f^>{~ɞ^{^A*%4gbLf&.t_^3a"ADަe ^BT fi*S"oaS \ 6M*5 eV@w b=EދFdk\19Shtvw>eÄ?z EI  % ̈-5;f[rJhil12=|>LX \sqbHP T0'h$[dzL-:ܲѮ;UT?>opsO{us?bXQ\yvii~kKM  U|=PuSϦ˻t25i[aUp$Zr:]˛WD|4[UwmaW$uz.$Y&m1ʴ0tQfb9l}5_a^F"ݶ}WM;`VqbEUQw>5K$)GР}t$B3A 9e}CFq D9KSPza<[8e'Q ZN.J5, v fHs0j8W~}\!YmtCj@'ߞ$_jӐysDqıS=Dta*; Z h$8QWowO(!x_ kye_W"|&[0V(S-}+q1{nm:hU˻|/$ <T~tCB8'Vlv-3m9 dwg$CLD1x0!}ےYYNwIi֪,+!@ØM)j}6szE )ŞNPSҥ5J g  eJJwO /$YQm[Ȍj:^hc\ĥO Q Ku_٬=U/h֔~ _o(2.p~fs{ǝ 5R,ST#a7Z-ʝN\U͵Un#ցNL|@TJy``S<0D69tn@o?LF?ӓVԿ9Yj䗿DSt>h-C]{^vٷ\gq˯J:WEQR#(4G{uBj *hbJJڵ#`3~n=oc& |$>cXx !v- qOz 5icn B뼫Ni>f '%|KSg 8zM]uyI7;kjϭ&M!-C_voSk3;v~G/S$'`LyIȭ hm5Pu[̌U#]+6ZuW]P{A߰qpH1c2 WNeL5iXQ}fGFtGƢڥ3XKmR=~হ\ս:fgksN ި>+xaqǀS3U ߞ;"VX7e˖sQ Ň)ϟov643U PP|JZ$AEn뻻,O3/,Nrfby #ٳݾ:A朥hGBVH@i/d~e7#p)rRUŖ9S`W7\o5*-:(ʯNBLYVhmBOY_?gHЄ˯L\hcg9:ZA2{n}xG.vLS>wԼjَs% Hap# x;1oq`ٵ5q77n八]$'l nꤺ p xQ4HHZS v>HHK8_rJZ,Zp(`嬸Sb?K5j}{g4¦$iyX`bl4@"ͪ5!tT/]Fh@ʠ+*ܛRܭ&^ *.0/1 &?n) 9S:T瞈6!^Udhf`?ڄǡO:d̴^j V.1'J.|4lMoi=D ,s`@ 7 $V?eb^pj-Yc|;UI,ˌ*DO5i`!"^-lme1*{wo}[E7'p(HTpx& e}_z2,W䁞Eܴvc@q0&]qqNO!S/D_hP@>Tی]RB\[nSXzk JyzMwp*(p=A&Yށ|(JIL 2j@Nn1Z̋ck2O'Xǀ/HagiJ?KuAQ%]%׭ЧnѶh2!RoM-I_(3׈6kI@0/?r o%p$l:MfiYz "N'/b榐[{ ~giJَH-:+Xa։ٯYסC2],'uNjjƷAK&u1sDZ? z)دykCj}a?#@cNΤ35sbՔ0SWVquj|D|8N> QtkZٹifY& YBH\?O?,}b 4?*>-8I"(7`5py]%,k>'V!a~z YTCP^Ԭܞ& \J%9MoȺicGNN`MJ7^+0eJud/t߇HZq.,| ̫V}RF,.sZƲٗ81$$s;oX4Ao{{. q1XhQiK?*|L$6OWzL7DaNՆn\CNJZ!d3bϓVKUQA5C4k OT6Ӧ5aX(w^;:e{g{8c< ØXv$j"[ DRf! ;.0Mr~6-8*i]O?C8:UG'/;C\Nʁ6n?HCm'z81 b*}p!C%́t> ˺A-?Lģ jlM^pk60џU.[ NxHO"3X;lBXvŏ>=Bhڄ1ߛT鹭rb,'WV3lfo?ڱ'z1\%1&Kos3f$P`]i#;cYG}Im{[(40QJ98RP2&یr pv_TYN TzDL>6/_E4ϸRl&y뭕L8>[k+1g'X LeCSeSbfLak&hTZQ{Jj@bi1u:4jqgղ2 S I'8N#GaG6U xaI lٴ;m lYv[ q}Dn¹Iӭ"!jdn>92~Tj ōzY{L{'ɿc> 3f|GX\4Ɩ0-h p83AŔXP{{/U©Az'-04= -gL鄹-`DqTVjB;>ܹS3LVSX UP5Ћ͂Cbf/ $("wo,jjjL2ct`L%.;Sw &36AK\1CY36cAkWաsi\/x7A2a8w! xm͘.!$-&|0oh+۷˿ԣ)u-)9'p18A|oL1/l@VTӤo`UnSjK_7EV}d/Y2=τoQ(LPfIXդGI90c\~7_?穻:қOU bpv:-ӸRpp"cB\ 6O;؆]xyaIy d 3c2R|a?C`zʓ Fʆ|ХpjxqƢELj֍g=uNz9|(D<:[^޿c]\ :~ K[c[fsl2@F{A%w_8]q8~0rgzJ ۯip-#}[=_~ɌW_]>xӸ1, ?n5hF@*l8%lO `_54g{)'̶8Y =_ bU oeU/!*'uJ;PZU=ZͅMµtft>\_zD8,"I)`KIi ,!p A%VSx7d+^޿NtLٱW}$[?ʙKmVA.&F/Ppo=L=\tywO.\X (o!S OvaW :[i;~rHu&/lBo[F.>*ǀSUWT 0U]yM[ř gfl:@ڥ^qcxW>eI_C|o{>a\vsDk̶ 6cLW;3q+~础[q!UT5]9Zf<[C{`]ҵfu> zi'[㟴*<1j ;WhxNg@VH3mkWS8:U'WPpŬ/ECF: ΩMHI;jqK_7C {v鯣xOpPL~_üZ8 oET?xzV^Q߹{ 4b0Q|}ISe3gb12K:{49LUBRs3Ql,j äR f&y2iD!U2 ~wA `(.$%9xg1TElm<{ɖ}Bm[z+<՞CmGlfZ3bc&T61,a1} aG46k \ sotCd%m?<)&=`rb`&M歏vE_NVD<"Bi`O, Y +ob˰ί( †ޱݪְVhq*!Nܸ2f7vT3?tt ٹ'k /TO᛿4uw>$jk_z8D%9ł:ILLQaYLj(!d2d½RCw4(}#8|.曋dy_n%3 aYApJ"ƻ#?JG`_법?;w~#BNHr+hcV4%$2SNG\+)wޓ`Xz)tTA>U|Y<3) ]%W\D?} jvμoڴ'WONB2JKL3T; ѐ@$`$?(St}\}kqWj'/f\d~ Ζ'ׯVq6jocpiiekn}68QT>+SM,kS1 $ (Lgym0*FF##m^>d$ k0b;g,I'k[^>{:#NGc_G%K Γ֟T4a.8֤"Ĭ?dYSSEn\~tFu n͸D &/IEUx֢i8b7ȦŽ6׋KQ?@p$ku ƧIJ yFFp? ϴw[='8F,Hi71$~+$x!#awGff[)gTD}X '͚vn!S95YRM`2Dqca")[6G~"Zޭnn&zs֟Gqcԑ @ww{Ұ_j1ԝ~<'6 ˨ Nq: H(°Ga]&3~j?813@nٗyRv{5 m}_& Qǘq7,w Af?dK~6Ǩb̙ڦ[dKcaxYmO}'8F ~grud7{&po&~ QAZZ ;jݛ_W"ٹ NLyK͝Owv^~˄81*(+?.+G||reINUMTCR>zõn}?Xv?81 w~^ռ+6!/OCc^{ͮ6mLZqQ@bxWc0<ɔ):gz{-.O%.: @Wٜ%}70JS4! gi^7;kx( Nq Tk]wyR }yW|`kް)g};% Qٳr@]qxtlC#_5$3]~881j/N×r3lw{7uwkN=~'8F\XRbuXK#8Ǘq#/1G_b;F{IENDB`( ...6NT*66?33'88-Z_jo#5 NUU'7Z /  5f$H"Y?? 1a"a"3'' -Z l'J%RUUU$$$8:' U$$$'tSn'_!35**AFidkaf%>AJ*** `Nn&k&C#:H$$#=Bl%BG`H$$SMo'm'X *x***8 EK OSD1'HIm'n'e#8(7<3bhfl (9b=Dk&n'l'C  7UUU-44";BQFLa(/q$ -;G2< V 4@h%n'n'T(e33*D2 """,:f$n(o(n'Y +u??kBPmwug|IU(-k3<g$n(o(n(`"6"""*?  dAMkvwsWg2< ->?i&n'o(n(h%9 U%*0,.m",/Q$66 b@Mmwwuav<GBJDk&n'o(o(m'? &*"*X` ~=@W??? kDQqwwvjDP &V ?[Gn&o(o(o(n'E+515H ah"~KYsxxxmJX#(d $HHuNp'o(o(o(n(O*N?U!88M ls( #(XiwxxxpM\%*g --)Vo(o(o(o(o(Y &j$#/5+cj ov V\ 5.7f{wxxwpN]#(d !-c"o(o(o(o(o(a#&  15wGNLS/50***fBPrxxxwpJY!'[=5i%o(o(o(o(o(b#+  ?).c '!%DUUU?33/%/8 " &]pwxxxwnFS!L33iEl&o(o(o(o(o(d$7///2<:F#':333%80)0%em '+/A7=+p(f(f(f)h*k)n*s,x+{.---.~.y-q,g/[%K@4(  3U`ANswxvjOa9&Q(1O!9R#6K!-: %*f%%%"333333 mAm&n'o(o(o(o(n(m'M ;07dxN_$(8**1$Z` ry "- AKQRTUVY Y [!["["[#["Z!Y TRMIHD<1'z%^"< '   %' -e{wu_q7(Q.Lt8o=~A?9u.T~$4H!'*U''' ---$Wn'o(o(o(o(o(o(o'O H4AMrMZ %/333&15CINX]BGh?$o,?N[h$m&m'm(m'm(n(n(n(n(n(n'n'o'n&o&n'j%d#^"["T H>90} \<$$ |KZv[n10U5iC8p9t=~EGE9v)Fh*4y!!,UUUQ5g%n'o(o(o(o(o(o(o'O P$(,lXiuJV 'U80$$$$$ :c,4@O\!c"i%o'n'n(n(o(n(n(o(o(o(n(o(o(n'n(m'k'j&e$[!SM<2/kC ) ;09Sc3/V8qFF.W/Z-UCGHF@-U~!-:(((55#Nn'o(o(o(o(o(o(o(n'R,V5@ktCP 33" ?#f'5=FSb"h$l&n'n(n(o(o(o(o(o(o(o(o(o(n(o(o(n(l'g%d$Y KC4 %m+: &,G8nFHG:x.W8qGHHHGC/X"-9...!!!E. d#n'o(o(o(o(o(o(o(o'V4aUUU &WO_wn=I 333 /&P)-9IXf%m'o(o(o(o(o(o(o(o(o(o(o(o(n(o(o(o(n'n'j&b#\!N>5#O^/A7nGHHHGEGHHHHHHB.T~%-5 Im&o'o(o(o(o(o(o(o(o(Y!3m330;f{wh|4= UUU***'""4"_*3F\"j&o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(m(i&^"B "+<6lFHHHGGGHHHHHHHHA)Ge,_#n'o(o(o(o(o(o(o(o(o(]"3v$$"YM]tv^r&.c$$$###+#l(<Vi&n(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(n'`&5%55eFHHHHGHHGHHHHHHHF\!l'n(o(o(o(o(o(o(o(o(o(o(o(o(j'E(/LrCGHHE9n7-T}?5NtU)n'o(o(o(o(o(n(o(`"5{"%*Yiuwwm8D"33A 7Xk%n(o(o(o(o(n'f'< -2^EGHF3f -!QOPOW^FTcptxiefC(-IE /1G<|GHHHF1_7n4j+Ot)<'3dGHHGC-V<}:qE$5k'o(o(o(o(o(n'T.X @!'O_sxxxw[p' .s ':' !!!K&Df$e'= ,2\FH@$Lv+B(W@F?=}3c+PxBGHF<$Mx "*==GHGG4h4fA7GGGGGD,`,&IoDHHH<}-WD6\R,n'o(o(o(n(n'O MA 8Dbvwwxxwv\n'0t 407j:EPaYk1;)CcAF7v#Ku-`AGGG2b5iB2b.V:wADGGGE+]$74jGHGC-T?=|@+?j'o(o(o(o(l'F, 3O`oxxxxxwo@L*$ (-Y7EL\_sns`q14Z<~E7v.dGGF0\9tD7@^_(o(o(o(o(h%? %GTi}vxxxxxxvXj)0i!%609tCP[nktvvo@$U2]8q,U&Gj2dAGHHGH1]9tGGGEA7n.WEGHGA"Jr&PzDFD5h3fF6^R,n(o(o(o(d$MDtvwxxxxxwl:E  !$E3=K[cvrwwwww\n.8\,T~.X8q9u2b,S|;zGHGH1^9tF=EGH>1^EGHHGA-[8q?3g8p0\E;wC(9k'o(o(o(_&RVwwxxxxxxuPa O#&V6AO_g{rwwwxxwrC$Y,Mt0^BGHF;x,R{<{GGH1^:tC/ZAGHEBGGDGGHD9s5k-W;y.WDA8:Te'n(o(n'T'_lwxxxxxxwey2;$! 'b9ETdjuwwxxxxxwdv47`=CGHHHG:v0\DGH1^:uC.VAHHHHHE3e3e?DGD/[2a@-TBF5Lr[)n(o(l&K/kwxxxxxwpFS!!5 *#(l;FWglvwwwxxxxxxvPa1UFGHHHHHFAFGH1^:uC.WAHHHHHG@4f,T}/Y2c0^,S|?B,S|AG7dM!0n(o(d$OEqwxxxxxuWg" '`$$$  ("(p;HWimwwxxxxxxxxxwdu2#F9rHHHHHHHHHHGH1^:tC.WAHHHHHHGGB<{6m7q@GC-S}AH<{A(9l(n'Y$W^wxxxxwvg{9B UUU!$j9DXgmvxxxxxxxxxxxwh{A;*4M@GHHHHHHHHHGH1]9tB-WAHHHHHHHHGGGGGHE9tDHA:6Ne(k&O.ewxxxxxwoES#(33""!/2K3:r(55L:::  ]5?VglwxxxxxxxxxxxxwlKBB!1JnFGHHHHHHHHHGH1^8sB.WAHHHHHHHHHHHHHHGFGHD5Ik]'d%LCrwxxxxuSe&+W3 FKf|.GJR???$I. 6P^j~wxxxxxxxxxxxxxxpNKX$E*4aGGHGG?4q-a+]0g8yBH1`7oB.WAGHHHHHHHHHHHHHHHHHF4YW*S&\fwxxxwv_r19+$$#tzW\33 1(1GUfyvxxxxxxxxxxxxxxwtUZU'h%=%5;vGGG?(U)@     &4P0h/\7oB.VBFFHGGHHHHHHHHHHHHHG8iC .I8lxxxxwg{;E $$1sy OUw "$h>I_qsxxxxxxxxxxxwxxxxw_lO,j%b%31F@GF6u0J    7U+Qz:vB-WA0h"Js-`:}DGGHHHHHHHHHHHHR[vxxxwlCP4***"""%+/]d y`f(49, E/7Rbmwxxxxxxxxxxxxxwtng|XhI6e%n(T'0JnDF3n"4   #5/b,R|?B0^@>` )<]2kBGHHHHHHHHHHH?31Xh|wxxwpJY!(E33 &d," %69Y"7;R*33UUU '$ *}ERfzvwxxxxwwxxxwuoeyYlL\@L2;%-+Y"n'k'@!.6hG5t"4      7T7t5j0\EFAD=^   :Z5sEHHHHHHHHHHC6K2<( /"sW>-fOk&m'b&20E?>1M     /-D9X!?^#A_/Y6l,R{>GHHF&Q}  &&R@HHHHHHHHHD6Jyb!wvwqQ_( -_ $$$ ![4IO7Q   7(/JYj~vwwwwwvph|WhGT9D+2 ! }Z  9$*** =d#m'n'R'0JnE;}+Z-]0e2i3i2e/\+Nt&Cb)Jn*Ns.W>GGHG>3k$8  7U:{GHHHHHHHF6XZ!nvqO_'/` 88  '$c5L^!R2% 7$$$!#m9F\oswvwvtjZkIX6A& + ! f A) ? ,nW!n'n'j(@ /6jD2c0]0]/Z-V-U~-V*Mq&Ba*Kp-U3c;x?CGGG8q-V/c*A    &:3mFGHHHHHG5dHZi~JY).W ??9$w:Ob"g#J+"U**E9E<<<UUU  S,8P`lvwvodxRb=H- 5"a=3%0Ei%o'n'a&12I?F<{9v:v;x?>1`,Rz3f5h/X-S}-T},S|-U9sEB9r2b.V4l(U2L '    ,.bEGHHHHG7o%-,1')n"""33':' ,X)@Se$l&a!?%_55C#AFf[aMR /2FD&,CQdzrpi~WiFS6@% +vG!3** 4] n'n'm'Q(0LrEGGGGG?.X0]8q0\-U8rADC<|-U4h?,S{;{7n,R{3c8u7v2j*X$Kt!Bf=^<\%Nx>GHHHHH;x6$07!y,,,4*88f33888 !!!-"`&:I^!i%n'j&S5<$$$***#DGd CN/ 7L\XkTfFT5A' .#U .  3*3e-T~<}F;z,S|1_6k/[.W@=,R|<~.W8rGGC;w2b.W-U~.X7pFHHHHHHHH>;/Ef%n'l'b#QB7/)"! " (1;CKVc#k&n'o(o(n(j&W7%J** 8 OS } {X_(  UUU#AJk&o(o(n'Z&0,Rz<{GGGGE0^5i?-S{?5j0[EGHHHHHHHHHHHHHHH>(d??--6a#n(o(o(o(m)M)4`E-U>HHHHHE2b2`FHF=~6^5X:tD4g2d<}-S|B@-U7oFHHHHHGBEHHE2b;wG9qF!/m*n(o(o(o(o(o(o(o(o(o(o(o(o(o(n(n'j&\!D+z,***UU'GKm'o(o(o(o(m)M)5`E/[;yHHHHF6n-WBHD6b;1FO-S+=*=6a>,S|>1`5jF>,T}7mFGGGG?.YAGF6m.XCF6^O)m'o(o(o(o(o(o(o(o(o(o(o(o(n'n'l&]!G05 $004`"o(o(o(o(o(n(Q'2XE3e5jHHHG9r,S{?GB4S{D$4c(n(n'c(=):7j3d3e>+Qy>G?.W1_>DD;w,S{7qFF9v-Rz?GC4Ee]*n'o(o(o(o(o(o(o(o(o(n(m(n'j%\ F1 7 UU"IIl&o(o(o(o(o(n(X&2HjD;z.VDHE7o,R{=G@5LpK"0h)m(o(o(m'_(67P8r,S{<~7n.VCGC4h,S{.V-U-U:vFF:w+Qy<|==~=};.Bh'n'o(o(o(o(o(o(n'n'n'k%d"TC0t1* 0_"o'o(o(o(o(o(n(e(80E>D0\1`8q0\.V>G@5GiO /j)n(o(o(o(n(l(O,3S}5k.XB3d/ZBGGC>>DHD7n,Qz<|A,T}9u5\P-l'n(o(o(o(n(n(n(n&g$\ M<-Y#UFGk&n(o(o(o(o(o(n(m(M.5XEB3e0]7nCF=~6BaS-l)n(o(o(o(o(o(n(f';,?9r0^4eC3d-U~;zEGGGF=0\.W>C/Z4g>87Oc)n(n(n(n'm'k&f$^!N?0n':*?&0]!o'n(o(o(o(o(o(o(n(f(@):5bBGGGA8f35[H+l'n'o(o(o(o(o(o(o(m'U*3Lq>-U6lD9t-V.X3d4i3d.Y-T~6lCC0\1`C6VQ.l'm'j'h%_"SK=,r,9***U FFl&o(o(o(o(o(o(o(o(n(n'S#- 65Co7Z8_7S:=lM&e^pL5i$o'o(o(o(o(o(o(o(o(i'A(98m?-U~5gDC;y5k4g5k:yBF?.W2bC8n4(8V#Y!PC86z-N0---??((.["n(o(o(o(o(o(o(o(o(o(c#3 LXdvY"mV$l]pj}ttRR^#o'n(o(o(o(o(o(o(o(n']'68S?@0[/X<{EGGGFA4h,R{6mD;y'FD<~7o5j6m:vAFD8h;4c?K %)FEj%n'o(o(o(o(o(o(o(m'P%###+ #^[lxwxxxxwQaOm'o(o(o(o(o(o(o(o(o(o(l(U+85M6c@EGGGFC:s5JwH)di ~pO`%,D  +[!n'o(o(o(o(o(o(n(n'Y(  ?333 P[lxxxxxxrANY:c#o(o(o(o(o(o(o(o(o(o(o(m(c(= -(4O4N}8a8f8c6T8>kF,b_ srwwu]q6Ax  ($IEj%n'o(o(o(o(o(o(n'^!/V*** HScg|rvvwwxxxxwui}O^7A"(}> *$$ &,5**UUU!MDk&n'o(o(o(o(o(n(g$; &&& =Jpxxxwwdw+1#2Fi%o(o(o(o(o(o(o(o(o(o(o'c#+ 3 %-D4?KY^qnvvwwxxxxxwkZkBO3< %t L&  *???,a*** !-.4]<GN]`rjqvwxxxwxxti~[oP_=J09+4+0i!'MB  ;3 . * *  , /!E_d %(-YUUU$UHk&o(o(o(o(o(o(j&F"%%%"$09ocvvxxwx[n$, "9a$m'o(o(o(o(o(o(o(o(n(n'T"$00((,',[;DETN_\pjortwxxyxvqmey[lQaK[JXGTBM<F7B-6  ip v|-)->%%%1`!n'o(o(o(o(o(k'J$''''#.GTcwxxwx[l&,(3Dg$n(o(o(o(o(o(o(o(o(n'e$6A3 !&5,5[/67@FTQ`XiZm[m_qbvcwdxexcvat]p[lWhP`FU8C) 1 "YCDIfmkrho$BKT... ***#eJm&n(o(o(o(n(k'K%!!!- +IVuxxwx\m(,*(XMk&n'o(o(o(o(o(o(o(o(n'O! %1 E U# (^/5e2;o1_"n'o(o(o(o(o(o(o(Z!.'''-"vCh%j&W9#&&&(UUU =Hpvw^r#+zUU%0;\"m'o(o(o(o(o(o(m'S+$$$33 #f@c#Y ?$H$6$*+4o`tvwfz/8'3V k'n(o(o(o(o(o(l&L$<----28(04?#^5N<(Q"""UUU4GVtvo6A /Nh%n'o(o(o(o(n'i&Dp** (55LR:?/'Q((UUU2;gzvv?K+&(lGe$n(n(o(o(o(n(f$<W$$$(+/F -0 33 !MQauwJXG* %Q?^"m'o(o(o(o(n'a"5?UUU+.1R  %CGu8  9CmwUg%f***86Vl&o(o(o(o(n'Z.)//7 OUah ho,5:4UUU$ )bVgv`t! )#.Mi$n'n(o(n'm&T*(..&AFeFJm(-22333 '>Ioh})2$(eDb"m'o(o(n(k&G#s.. 333*** %+tZll6@ #H9Y l&n(o(n(f$<!KUUU />Kj>I +-0Qi%n'n(n'\!/ ' (.ZlCR 7)|Ge$o(n(m&N&"""5AMBNB"33%^<_"n(n'h%<H'/.;#e88 $$$E4X m'n'Y ****5.5&KQ xrx!HM\?333.Ok&l'A"X333!$'M $;;8#*#$(Hh%\!(' ))> AFm$**$xB`#?QT 5((OVhp }'?BT???""i<>8=HLx&,1.??&..!>CnGM!;=c888$$$ $}\c [a/'/ **** ***33DKs !$$E???!EJc z x##(9-6?GJeknu"FMp666&&&!.$*??? ??????????? ???????????0 ?? ????  ???????(@ BUU'NX/OO[[5??]aD !7B... &*0,%,$$$8pN**5Z]!@~?TT*pvTOW .B^!U )33 }***(3]"d#9X^bFntET1:% +)%)X!k&E333&!;GO_ERy// %"U n'U!,""BN`sZlAK^' S n']#.1$;HxcwjQa06/*$Tn(d$:O$$ 9Bocws^p;GR$$%/X!n(i%@s[bCy~PW# 8Eyg{vfy@Lk 'A]!o(l'H???3sx_ANoxh}BOp2ec#o(n(O PXbgp[d8???&??$$$ %/Sdvwg{?Ke3***<i%o(o(S*.4M)7***ciM swu'''        333???5?lxv]p*5Z$$***Jn'o(o(Z!)ALBQUhow mr2CJLMNOOMIFBo6P23 !.Ugt\$w?Az3\2a+Ij"0>%+W[!o(o(o(^"'-#+TfIX8BTYX=A:-T>NZ!b#h%l'm'l'l&k&i%d$]"W!MD{8M- ' 9DQ&n=e;y6lDA2a#5G9""3>i%o(o(o(_"45:CWezFUx***!+59]@LX c#l'o(o(o(o(o(m'j&e$[!RGz5C!*>8fFC=GHE5f($-qSo(o(o(o(b$< ?P`lBNX???.16lEZ"j&o(o(o(o(o(o(o(n(i&J/6eFHFGHHHC5Eha&o(o(o(o(e$;I6?Ug|f|7<3+R@[!l'o(o(o(o(o(o(^+Ip:TBHHA7gO+Bm'o(o(o(f$>N%"SctZk( ($$$,bF`"n'o(o(o(f(@BcCF8`D.;U3;I-96XAHF6n=Vc+o(o(o(f$=K CNnqGTy 1|Og%o(l'I+B?E.^/A=TnGa}KIU9)4FiEH>8lO.Gn(o(o(c$<C!!<Ei|wcv/:0333$8?]!X.Ih|wsKY'' 4>5BM::8Nw>/e6uD6n5j:wCF<#Gn?G9s=qY"7o(o([",,L\nxwbv095-6BP\Vhf|lC6q7o2h?GF8pD=7n>G:|-_D;{DC@EF=9u5k:xBXh)m']Guxxw^q( 5&-22GV`sqwxxuI7xAGHC@F8p:uBHH@7p;{6k:w9u@s^0i'a^xxxk@M_)41FT`tqwxxxw^l:TGHHHHF8p9tCHHHD?AD=~BT*@a-jwxxrRb$$GO PT6???$+#CM_rrxxxxxxhsK8>qHF>8x;A7o9tCHHHHHHHGEH:X[Bsxv^p4:'(5C cg@;E_Xjoxxxxxxxn\;N'*$333**? 1;RPakpf{XhDS3>{" )J &(V l'P&;>|8r6m5k.Y-U0\6l=}C5j'Nw*? &-`FHH?{AT>Fw.. 333/KDX!V @w(RY(px4?PaXkM\=G-3T !& Ejg$j'A>]AFD7n2c6m:v:x:v6n5i9t3f0c/a.a4nDHHAB#7>0s#:' $$.!&55ZAO`#e#S 7J$$$_fP *8q,3J"",.!Y!n'c(:S}9tHE6m;z9v9t<{7n7p5j;yD>:v;zFHHHBQ(?k'`#U NKLS Z!c$j&l'_"G-"&??U]hT\B??Cug&o(],:e9sHFB=~:xFE=7n6m7o@HHGHHGEBT'=n(o(o(o(o(o(o(o(m'e$Q5L 1$Y"n(o([0a5k6l<}7oAGG>EA:x@y[ 3o(o(o(o(o(o(n'g%V >f,Bwg%o(o(_,=j7oDA7nABT]1l(Y 48X6l6m@8r9t8r:wA6m>@]d*o(o(o(n(k&d$V Af++#V!n(o(o(h(CKr=~6l8r?CMua.n(o(m(J1K4d8r7oAA@=7n9u6lJ6Sm(n(k&e$]!O?L0 ?uf%o(o(o(n(V1;S@qAgG;zY2n(o(o(o(c+=T6l:w8r7n7o;y:x7o:ZV+^#V KBQ.!$$$-"T n'o(o(o(i&D%\mf#j qZFl'o(o(o(n(U&<=k8r8r;{;z7p:v8k+3H{594"=tf$o(o(o(l'O/+|i}xxvIK`#o(o(o(o(l(S+B>f>=~=~>|CZR(q>H!+#T m'o(o(n(V-_-73h|xxp:COl'o(o(o(o(m(S,32WADJBW0}l vlN^/:[?yf%o(o(n([!1{ //`sxxk4>b8?_"o(o(o(o(o(b#/DPx[nltwxug{Sa=Iy-6=$ ***im:{vRZ;,(U n(o(o(`#7 Whwxh}2;Q$$Jh&o(o(o(o(j&@(2CMLQa^qg}nrtndyYkQaGS@L6?y(.r"w qv???***Ag$o(o(a#9$LYsxh|29K2R k'o(o(o(n(X!*B$02B2=IWIXvN^SbVhXiTdK[EQ9Fs' -T 7Y`jelsRZ"0:X!n(n(b"<(@LSmxi~6?P45Z"m'o(o(o(i&@"""**   **Ej&o(b#<%07%ewwl6A];M]"n(o(o(o(^"5`34j`#n'`";33 Ufup>Go>Z_"n(o(o(m'S05.BTk&X 8~EOcnrFR**@[_#n(o(o(j&J)(2Ma"L0T 07%atuN] 333>R[#m'o(o(g%C-$?H"+/;=K7{%"$$$N]sWi "9CW k&o(o(b#9| ?G &!&(3337BEh|`s%+)0/Og%o(n(\!5_"KK%| QV5Uefy0I34DRi&e$A 3?q.@888 1)Hd%[!2KUio tz^'A]"Ekrm |""":vB AFm&?L??KRG!IP&"""gmuzu???dhz nqa**? ==!???????|????0 x 0??(0` %UUUrw1ip"UU ***$HH BG#VK&***[[?_<?Z!BTfo> xt3330/`"S __qvn 6N;Do3;0%^"^!4", ,CQUgLYd*8/ \#f$CH$$GV|cw]n>L9-"]"k&Kp$$$ FTsi}g|JW` .,_#m'OruCZZ IVolM]s88 5Cc$o(W  qvc ]f???(PPUU''UfvmM[m**=ph%o(\"( ';@!(Cq???orG SWR2#0%3(5+5+4'1. . 8C[jv\s-/Qp'8I-$$$* Km'o(a$/ AOtCRi]f<}!.4BR[!`#b#a$`#]#W PHj?@/   RdX.=]8oFmNY a#j&o(n(m'j&f%`#W Ow=B.2W@vB=GB1^? i%o(o(e$C5@MOeyJXK***,8?DV h%o(o(o(o(n(k'M&;=wGDDHH?H/Hm(o(o(g%B=& &Ykh}>D)???+)AY!j&o(o(o(_1AfE?cH6M?8]@{H@~1h0f7p7nA/W,WF=?\f-o(d$0IBKf{vau1:;DM]_[oG%a8h6t?A:x:w<}D2k9xA;qX+Do(^)P[nxpIYd$$;F/Tdf{qo>E~:v9vAB=}B@CC:{9u9tKFkm(a?sxv]q& 3;D8Tdi}txw]'|@{GFBB:u?HF<|;x9u:wEbf*bUwxlEQB:?0Rai}txxxlyC8iEGDDA9t@HHGEFCCzY7jrxrSdz**[ev{:/8M[zfztxxxwp]@CRA(R*>*?(R~9s;{:}CHHHHDM)[su]n5 C$$$BE\ X >KN^pqwuoh|^qSaQ=]/=h Ad %7,Y?2k$0G3lEHHER:tat?F$$$$419(q?E0LL ???.9,Pai}pg{ZjIX;Go07E" )%=Od$Q.H:t,Y+V(Lr+Rz8rE8t-C  %LuCHGHF\nAM+<?LN=d. qv6+!EM^ZlQ`BMp09: !6Y i'CJqCA6l3e7n9t9t6m4j,Y'Nu&Oy;~HH=P~A 3T&((-'<DGzT^"U!=>pud $7Oj4=:"* EQg%c+;ZAE9u;y<{=8q6k<|B>@HHHFXg&a$Z!W Y ^#d$i%b#N/$HHRY%???<X!n(_1;g@GA;zCBs9r6m9u@GGEEAHP{l(o(o(o(o(m'f%U >=***ETg%o(a0=i=B:wAtR4Qa0DDh5i:v;{<|<{>:x=|P9Xn(o(o(l&d$X!DJ' -U!n(o(i)FOy:v:v?jV-Fm(o(`29X7p;y=<}:v8r;`^4j&e%^!QD8. ARe%o(o(m'N6M>S@\$pe-o(o(m(O5S9n9u:x:y9v8p9AaP!KbB65 -U m'o(n(Z!CBrwmT0n'o(o(i+LBe5).19DG ~333A^f$o(e%C/8fzxat%/>%]#n(o(o(d$:\/?BQ2M]_Vg[m_q]pViL[AL,3h*9YxszK1W n(f%F&\nvcw*3DGc$n(o(n'R'$3 ': **  ???Fi%e$F$$Q`grg|-4'?I^d%o(o(h&G$$$:S`#`#B->I-jj9F:***Jcd%o(o(b#=W3@RS;V 3 [mnBNT**IZa$n(n(["8;_g }."*=p+IWWkJYwDG\"l'm'U -'hkG job22`sRc<.V!j%k&O$3L TZ-*UU KZsSd$$5Nf$h%G$$$18$BO''3 Fe`#^#;E5Cu~iUUU?<HW!M":N ??739#,eiy"UU"DD??mt{ bh1NN^b>TT <<<p<8800 ??? ??( @ D2bio[`_D?,["G+??w{>*,NrHT[?L<"_#Sb3DN]x]oSbC33? c%Y"UU$HH33 Teyh|Zjn88 ;+d$`"3 8 q3ff"ff': Zko\mtHHDJh%c%<  @X7Rb`x}36$UK[QaPbQXPFH.7**=J:i~Y+4Q.S}7$$$Pl'f%E!HV[GVGbb V\/6AKyX!_#e%i&g%c$^"W!rC#H@@{Ax?@4Lqu^#o(g%K,3D\mRb> 9J VW!g%n(o(k'Q0MADwC~G>m_ 4o(h%L2Q_XeyJZ/CZZ"j&`"8Co6fDJbAGB==MNzfMvlJ])wwnyHT*^powwrX#Y@y._%Ks3i:y?FHGFigpqXjVjqnt8FTeij~ojauVfV?P6X*U);1G4k8u'9#Fk?HG{f#]or$$H3 F:>r$!$E]];!Ea\N]]oVhKWU<B*-F3a'FW=3g3d9u9t*TfD<|=~=|7n<}BDGE\&=d%a$b$e%`#T u?UUUH5g%d4>jB>|JXQ=`8d:x?@>Ane4o(m&g%["I-U**Y"m'j*GKyAjKH{h/l)IAd8q;z;y8rCIoc&`#X"hI&3D4e$o(c$TTm%cfh'o(e4DV:r?@(  @io)dk&u!XnI!&P Vmm@%[KXhBH$mM!]"?ffss ??LL _pvbxtE\ T!-c$Q"_o hs, x%DKJRi^"b#`%gSAYOG>vR,Ei&W$#Pa?Nb'N' T!-^"g&U:\?gArLTi*V=Gbt\hLf bqAU*k?lkoQE6q:y@BY=~mXn333 sx5_r(Tedbwat{ZJAU*R}5l(Qz>MVYLSR"%R GGO"DLev-H[??W#`!6CrBo@b<{AQMyf%b$X!\D"`$og,S<])U_(B?e@hN2Y`#OUR e%[&j}aSj(Y#?Z+dwVg[<K3py;mH$W"w\#N' f{raqac$qg&Z"IO_Xc?O\\ UU W^FH$FT _o0[md_a$ic$T*xx?_66gTUU?Y GT#{6$6w -GL2kq-i2pd-2.39.0/Win32/resource.h000066400000000000000000000004401411072525600153460ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} #define MAINICON 101 #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif i2pd-2.39.0/Win32/winres.h000066400000000000000000000001051411072525600150240ustar00rootroot00000000000000#ifndef WINRES_H__ #define WINRES_H__ #include #endif i2pd-2.39.0/build/000077500000000000000000000000001411072525600135455ustar00rootroot00000000000000i2pd-2.39.0/build/.gitignore000066400000000000000000000003631411072525600155370ustar00rootroot00000000000000# Various generated files /CMakeFiles/ /i2pd /libi2pd.a /libi2pdclient.a /libi2pdlang.a /cmake_install.cmake /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake /install_manifest.txt /arch.c # windows build script i2pd*.zip build*.log i2pd-2.39.0/build/CMakeLists.txt000066400000000000000000000271511411072525600163130ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.12) # this addresses CMP0059 with CMake > 3.3 for PCH flags cmake_policy(VERSION 2.8.12) project("i2pd") # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) # Win32 build with cmake is not supported if(WIN32 OR MSVC OR MSYS OR MINGW) message(SEND_ERROR "cmake build for windows is not supported. Please use MSYS2 with makefiles in project root.") endif() # configurable options option(WITH_AESNI "Use AES-NI instructions set" ON) option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_MESHNET "Build for cjdns test network" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) # paths set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") set(CMAKE_SOURCE_DIR "..") #Handle paths nicely include(GNUInstallDirs) # architecture include(TargetArch) target_architecture(ARCHITECTURE) set(LIBI2PD_SRC_DIR ../libi2pd) set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) set(LANG_SRC_DIR ../i18n) set(DAEMON_SRC_DIR ../daemon) include_directories(${LIBI2PD_SRC_DIR}) include_directories(${LIBI2PD_CLIENT_SRC_DIR}) include_directories(${LANG_SRC_DIR}) include_directories(${DAEMON_SRC_DIR}) FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) # TODO Make libi2pd available to 3rd party projects via CMake as imported target # FIXME This pulls stdafx # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) add_library(libi2pdclient ${CLIENT_SRC}) set_target_properties(libi2pdclient PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdclient EXPORT libi2pdclient ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) add_library(libi2pdlang ${LANG_SRC}) set_target_properties(libi2pdlang PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdlang EXPORT libi2pdlang ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) if(WITH_MESHNET) add_definitions(-DMESHNET) endif() if(WITH_UPNP) add_definitions(-DUSE_UPNP) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. # Multiple definitions of __stack_chk_fail(libssp & libc) set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections") set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above # check for c++17 & c++11 support include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) if(CXX17_SUPPORTED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") elseif(CXX11_SUPPORTED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") else() message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") if(WITH_HARDENING) add_definitions("-D_FORTIFY_SOURCE=2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4") endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if(LINUX) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libstdc++") # required for list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") # required to link with -stdlib=libstdc++ endif() if(NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions") endif() endif() # compiler flags customization(by system) if(UNIX) list(APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") if(NOT(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR APPLE)) # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 add_definitions("-D_GLIBCXX_USE_NANOSLEEP=1") endif() endif() # Note: AES-NI and AVX is available on x86-based CPU's. # Here also ARM64 implementation, but currently we don't support it. if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386")) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") add_definitions(-D__AES__) endif() if(WITH_ADDRSANITIZER) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") endif() if(WITH_THREADSANITIZER) if(WITH_ADDRSANITIZER) message(FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") endif() endif() # libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) if(IOS) set(CMAKE_THREAD_LIBS_INIT "-lpthread") set(CMAKE_HAVE_THREADS_LIBRARY 1) set(CMAKE_USE_WIN32_THREADS_INIT 0) set(CMAKE_USE_PTHREADS_INIT 1) else() find_package(Threads REQUIRED) endif() if(THREADS_HAVE_PTHREAD_ARG) # compile time flag set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() if(WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) set(BUILD_SHARED_LIBS OFF) if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") # set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive") set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel") endif() else() # TODO: Consider separate compilation for LIBI2PD_SRC for library. # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) endif() if(WITH_PCH) include_directories(BEFORE ${CMAKE_BINARY_DIR}) add_library(stdafx STATIC "${LIBI2PD_SRC_DIR}/stdafx.cpp") string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) get_directory_property(DEFS DEFINITIONS) string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") add_custom_command(TARGET stdafx PRE_BUILD COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch ) target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) target_compile_options(libi2pdlang PRIVATE -include libi2pd/stdafx.h) target_link_libraries(libi2pd stdafx) endif() target_link_libraries(libi2pdclient libi2pd libi2pdlang) find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!") endif() find_package(OpenSSL REQUIRED) if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() if(WITH_UPNP) find_package(MiniUPnPc REQUIRED) if(NOT MINIUPNPC_FOUND) message(SEND_ERROR "Could not find MiniUPnPc. Please download and install it first!") else() include_directories(SYSTEM ${MINIUPNPC_INCLUDE_DIR}) endif() endif() find_package(ZLIB) if(ZLIB_FOUND) link_directories(${ZLIB_ROOT}/lib) endif() # load includes include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) # warn if for meshnet if(WITH_MESHNET) message(STATUS "Building for testnet") message(WARNING "This build will NOT work on mainline i2p") endif() if(NOT MSYS) include(CheckAtomic) endif() # show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Architecture : ${ARCHITECTURE}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") if(WITH_BINARY) add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) if(WITH_STATIC) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") endif() if(WITH_PCH) target_compile_options("${PROJECT_NAME}" PRIVATE -include libi2pd/stdafx.h) endif() if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now") endif() if(WITH_UPNP) set(UPNP_LIB ${MINIUPNPC_LIBRARY}) endif() # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() if(WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") endif() i2pd-2.39.0/build/build_mingw.cmd000066400000000000000000000053101411072525600165310ustar00rootroot00000000000000@echo off setlocal enableextensions enabledelayedexpansion title Building i2pd REM Copyright (c) 2013-2020, The PurpleI2P Project REM This file is part of Purple i2pd project and licensed under BSD3 REM See full license text in LICENSE file at top of project tree REM To use that script, you must have installed in your MSYS installation these packages: REM Base: git make zip REM x86_64: mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-gcc REM i686: mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc REM setting up variables for MSYS REM Note: if you installed MSYS64 to different path, edit WD variable (only C:\msys64 needed to edit)! set "WD=C:\msys64\usr\bin\" set MSYS2_PATH_TYPE=inherit set CHERE_INVOKING=enabled_from_arguments REM set MSYSTEM=MSYS set MSYSTEM=MINGW32 set "xSH=%WD%bash -lc" set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" REM detecting number of processors set /a threads=%NUMBER_OF_PROCESSORS% REM we must work in root of repo cd .. REM deleting old log files del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... %xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 echo. REM set to variable current commit hash FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( set tag=%%a ) %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul REM converting configuration files to DOS format (usable in default notepad) %xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/*" >> build\build.log 2>&1 REM starting building set MSYSTEM=MINGW32 set bitness=32 call :BUILDING set MSYSTEM=MINGW64 set bitness=64 call :BUILDING REM building for WinXP set "WD=C:\msys64-xp\usr\bin\" set MSYSTEM=MINGW32 set bitness=32 set "xSH=%WD%bash -lc" call :BUILDING_XP echo. REM compile installer C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%tag%.0" build\win_installer.iss >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul echo Build complete... pause exit /b 0 :BUILDING %xSH% "make clean" >> nul echo Building i2pd %tag% for win%bitness% %xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build\build_win%bitness%_%tag%.log 2>&1 goto EOF :BUILDING_XP %xSH% "make clean" >> nul echo Building i2pd %tag% for winxp %xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build\build_winxp_%tag%.log 2>&1 :EOFi2pd-2.39.0/build/cmake_modules/000077500000000000000000000000001411072525600163555ustar00rootroot00000000000000i2pd-2.39.0/build/cmake_modules/CheckAtomic.cmake000066400000000000000000000065711411072525600215420ustar00rootroot00000000000000# atomic builtins are required for threading support. INCLUDE(CheckCXXSourceCompiles) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "-std=c++11") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; int main() { return x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); return 0; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics64) # This isn't necessary on MSVC, so avoid command-line switch annoyance # by only running on GCC-like hosts. if (LLVM_COMPILER_IS_GCC_COMPATIBLE) # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) if( HAVE_LIBATOMIC ) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) message(FATAL_ERROR "Host compiler must support std::atomic!") endif() else() message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() endif() # Check for 64 bit atomic operations. if(MSVC) set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) else() check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) endif() # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) if(HAVE_CXX_LIBATOMICS64) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) message(FATAL_ERROR "Host compiler must support std::atomic!") endif() else() message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() ## TODO: This define is only used for the legacy atomic operations in ## llvm's Atomic.h, which should be replaced. Other code simply ## assumes C++11 works. CHECK_CXX_SOURCE_COMPILES(" #ifdef _MSC_VER #include /* Workaround for PR19898. */ #include #endif int main() { #ifdef _MSC_VER volatile LONG val = 1; MemoryBarrier(); InterlockedCompareExchange(&val, 0, 1); InterlockedIncrement(&val); InterlockedDecrement(&val); #else volatile unsigned long val = 1; __sync_synchronize(); __sync_val_compare_and_swap(&val, 1, 0); __sync_add_and_fetch(&val, 1); __sync_sub_and_fetch(&val, 1); #endif return 0; } " LLVM_HAS_ATOMICS) if( NOT LLVM_HAS_ATOMICS ) message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing") endif() i2pd-2.39.0/build/cmake_modules/FindMiniUPnPc.cmake000066400000000000000000000013221411072525600217600ustar00rootroot00000000000000# - Find MINIUPNPC if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) else() find_path(MINIUPNPC_INCLUDE_DIR miniupnpc/miniupnpc.h /usr/include /usr/local/include /opt/local/include $ENV{SystemDrive} ${PROJECT_SOURCE_DIR}/../.. ) find_library(MINIUPNPC_LIBRARY miniupnpc) if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) message(STATUS "Found MiniUPnP headers: ${MINIUPNPC_INCLUDE_DIR}") message(STATUS "Found MiniUPnP library: ${MINIUPNPC_LIBRARY}") else() set(MINIUPNPC_FOUND FALSE) message(STATUS "MiniUPnP not found.") endif() mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY) endif() i2pd-2.39.0/build/cmake_modules/TargetArch.cmake000066400000000000000000000126561411072525600214150ustar00rootroot00000000000000# Based on the Qt 5 processor detection code, so should be very accurate # https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h # Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64) # Regarding POWER/PowerPC, just as is noted in the Qt source, # "There are many more known variants/revisions that we do not handle/detect." set(archdetect_c_code " #if defined(__arm__) || defined(__TARGET_ARCH_ARM) #if defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7) #error cmake_ARCH armv7 #elif defined(__ARM_ARCH_6__) \\ || defined(__ARM_ARCH_6J__) \\ || defined(__ARM_ARCH_6T2__) \\ || defined(__ARM_ARCH_6Z__) \\ || defined(__ARM_ARCH_6K__) \\ || defined(__ARM_ARCH_6ZK__) \\ || defined(__ARM_ARCH_6M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6) #error cmake_ARCH armv6 #elif defined(__ARM_ARCH_5TEJ__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5) #error cmake_ARCH armv5 #else #error cmake_ARCH arm #endif #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) #error cmake_ARCH i386 #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) #error cmake_ARCH x86_64 #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) #error cmake_ARCH ia64 #elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) #error cmake_ARCH ppc64 #else #error cmake_ARCH ppc #endif #endif #error cmake_ARCH unknown ") # Set ppc_support to TRUE before including this file or ppc and ppc64 # will be treated as invalid architectures since they are no longer supported by Apple function(target_architecture output_var) if(APPLE AND CMAKE_OSX_ARCHITECTURES) # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we # disable it by default # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) set(osx_arch_ppc TRUE) elseif("${osx_arch}" STREQUAL "i386") set(osx_arch_i386 TRUE) elseif("${osx_arch}" STREQUAL "x86_64") set(osx_arch_x86_64 TRUE) elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) set(osx_arch_ppc64 TRUE) else() message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") endif() endforeach() # Now add all the architectures in our normalized order if(osx_arch_ppc) list(APPEND ARCH ppc) endif() if(osx_arch_i386) list(APPEND ARCH i386) endif() if(osx_arch_x86_64) list(APPEND ARCH x86_64) endif() if(osx_arch_ppc64) list(APPEND ARCH ppc64) endif() else() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") enable_language(C) # Detect the architecture in a rather creative way... # This compiles a small C program which is a series of ifdefs that selects a # particular #error preprocessor directive whose message string contains the # target architecture. The program will always fail to compile (both because # file is not a valid C program, and obviously because of the presence of the # #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( run_result_unused compile_result_unused "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/arch.c" COMPILE_OUTPUT_VARIABLE ARCH CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} ) # Parse the architecture name from the compiler output string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") # Get rid of the value marker leaving just the architecture name string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") # If we are compiling with an unknown architecture this variable should # already be set to "unknown" but in the case that it's empty (i.e. due # to a typo in the code), then set it to unknown if (NOT ARCH) set(ARCH unknown) endif() endif() set(${output_var} "${ARCH}" PARENT_SCOPE) endfunction() i2pd-2.39.0/build/win_installer.iss000066400000000000000000000042151411072525600171410ustar00rootroot00000000000000#define I2Pd_AppName "i2pd" #define I2Pd_Publisher "PurpleI2P" ; Get application version from compiled binary ; Disabled to use definition from command line ;#define I2Pd_ver GetFileVersionString(AddBackslash(SourcePath) + "..\i2pd_x64.exe") [Setup] AppName={#I2Pd_AppName} AppVersion={#I2Pd_TextVer} AppPublisher={#I2Pd_Publisher} DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_TextVer} LicenseFile=..\LICENSE SetupIconFile=..\Win32\mask.ico InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true ArchitecturesInstallIn64BitMode=x64 ExtraDiskSpaceRequired=15 AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppVerName={#I2Pd_AppName} AppCopyright=Copyright (c) 2013-2020, The PurpleI2P Project AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases VersionInfoProductVersion={#I2Pd_Ver} VersionInfoVersion={#I2Pd_Ver} CloseApplications=yes [Files] Source: ..\i2pd_x32.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64; MinVersion: 6.0 Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; MinVersion: 6.0 Source: ..\i2pd_xp.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; OnlyBelowVersion: 6.0 Source: ..\README.md; DestDir: {app}; DestName: Readme.txt; Flags: onlyifdoesntexist Source: ..\contrib\i2pd.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\subscriptions.txt; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\tunnels.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\certificates\*; DestDir: {userappdata}\i2pd\certificates; Flags: onlyifdoesntexist recursesubdirs createallsubdirs Source: ..\contrib\tunnels.d\*; DestDir: {userappdata}\i2pd\tunnels.d; Flags: onlyifdoesntexist recursesubdirs createallsubdirs [Icons] Name: {group}\I2Pd; Filename: {app}\i2pd.exe Name: {group}\Readme; Filename: {app}\Readme.txt [UninstallDelete] Type: filesandordirs; Name: {app} i2pd-2.39.0/contrib/000077500000000000000000000000001411072525600141065ustar00rootroot00000000000000i2pd-2.39.0/contrib/android_binary_only/000077500000000000000000000000001411072525600201335ustar00rootroot00000000000000i2pd-2.39.0/contrib/android_binary_only/.gitignore000066400000000000000000000002261411072525600221230ustar00rootroot00000000000000gen tests bin libs log* obj .gradle .idea .externalNativeBuild ant.properties local.properties build.sh android.iml build gradle gradlew gradlew.bat i2pd-2.39.0/contrib/android_binary_only/jni/000077500000000000000000000000001411072525600207135ustar00rootroot00000000000000i2pd-2.39.0/contrib/android_binary_only/jni/Android.mk000066400000000000000000000051301411072525600226230ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := i2pd LOCAL_CPP_FEATURES := rtti exceptions LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) LOCAL_STATIC_LIBRARIES := \ boost_system \ boost_date_time \ boost_filesystem \ boost_program_options \ crypto ssl \ miniupnpc LOCAL_LDLIBS := -lz LOCAL_SRC_FILES := $(IFADDRS_PATH)/ifaddrs.c \ $(wildcard $(LIB_SRC_PATH)/*.cpp)\ $(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp)\ $(DAEMON_SRC_PATH)/UnixDaemon.cpp \ $(DAEMON_SRC_PATH)/Daemon.cpp \ $(DAEMON_SRC_PATH)/UPnP.cpp \ $(DAEMON_SRC_PATH)/HTTPServer.cpp \ $(DAEMON_SRC_PATH)/I2PControl.cpp \ $(DAEMON_SRC_PATH)/i2pd.cpp include $(BUILD_EXECUTABLE) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_system LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_date_time LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_filesystem LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_program_options LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := crypto LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ssl LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libssl.a LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include LOCAL_STATIC_LIBRARIES := crypto include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := miniupnpc LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnpc-2.1/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnpc-2.1/include include $(PREBUILT_STATIC_LIBRARY) i2pd-2.39.0/contrib/android_binary_only/jni/Application.mk000066400000000000000000000026471411072525600235200ustar00rootroot00000000000000APP_ABI := all #APP_ABI += x86 #APP_ABI += x86_64 #APP_ABI += armeabi-v7a #APP_ABI += arm64-v8a #can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there. APP_PLATFORM := android-14 NDK_TOOLCHAIN_VERSION := clang APP_STL := c++_static # Enable c++17 extensions in source code APP_CPPFLAGS += -std=c++17 -fvisibility=default -fPIE APP_CPPFLAGS += -DANDROID_BINARY -DANDROID -D__ANDROID__ -DUSE_UPNP APP_LDFLAGS += -rdynamic -fPIE -pie ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) APP_CPPFLAGS += -DANDROID_ARM7A endif # Forcing debug optimization. Use `ndk-build NDK_DEBUG=1` instead. #APP_OPTIM := debug # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git # change to your own I2PD_LIBS_PATH = /path/to/libraries BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs # don't change me I2PD_SRC_PATH = $(PWD)/../.. LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon i2pd-2.39.0/contrib/android_binary_pack/000077500000000000000000000000001411072525600200705ustar00rootroot00000000000000i2pd-2.39.0/contrib/android_binary_pack/.gitignore000066400000000000000000000000421411072525600220540ustar00rootroot00000000000000archive i2pd_*_android_binary.zip i2pd-2.39.0/contrib/android_binary_pack/build-archive000077500000000000000000000020211411072525600225270ustar00rootroot00000000000000#!/bin/bash # Copyright (c) 2013-2020, 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 GITDESC=$(git describe --tags) declare -A ABILIST=( ["armeabi-v7a"]="armv7l" ["arm64-v8a"]="aarch64" ["x86"]="x86" ["x86_64"]="x86_64" ) # Remove old files and archives if [ -d archive ]; then rm -r archive fi if [ -f ../i2pd_*_android_binary.zip ]; then rm i2pd_*_android_binary.zip fi # Prepare files for package mkdir archive for ABI in "${!ABILIST[@]}"; do if [ -f ../android_binary_only/libs/${ABI}/i2pd ]; then cp ../android_binary_only/libs/${ABI}/i2pd archive/i2pd-${ABILIST[$ABI]} fi done cp i2pd archive/i2pd cp -rH ../android/assets/certificates archive/ cp -rH ../android/assets/tunnels.conf.d archive/ cp -H ../android/assets/i2pd.conf archive/ cp -H ../android/assets/tunnels.conf archive/ # Compress files cd archive zip -r6 ../i2pd_${GITDESC}_android_binary.zip . # Remove temporary folder cd .. rm -r archive i2pd-2.39.0/contrib/android_binary_pack/i2pd000077500000000000000000000017471411072525600206650ustar00rootroot00000000000000#!/bin/sh # Copyright (c) 2013-2020, 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 # # That script written for use with Termux. # https://stackoverflow.com/a/246128 SOURCE="${0}" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" SOURCE="$(readlink "$SOURCE")" [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located done DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" arch=$(uname -m) screenfind=$(which screen) if [ -z $screenfind ]; then echo "Can't find 'screen' installed. That script needs it!"; exit 1; fi if [ -z i2pd-$arch ]; then echo "Can't find i2pd binary for your archtecture."; exit 1; fi screen -AmdS i2pd ./i2pd-$arch --datadir=$DIR i2pd-2.39.0/contrib/apparmor/000077500000000000000000000000001411072525600157275ustar00rootroot00000000000000i2pd-2.39.0/contrib/apparmor/usr.sbin.i2pd000066400000000000000000000012671411072525600202600ustar00rootroot00000000000000# Basic profile for i2pd # Should work without modifications with Ubuntu/Debian packages # Author: Darknet Villain # #include profile i2pd /{usr/,}sbin/i2pd { #include #include #include # path specific (feel free to modify if you have another paths) /etc/i2pd/** r, /var/lib/i2pd/** rw, /var/log/i2pd/i2pd.log w, /{var/,}run/i2pd/i2pd.pid rwk, /{usr/,}sbin/i2pd mr, @{system_share_dirs}/i2pd/** r, # user homedir (if started not by init.d or systemd) owner @{HOME}/.i2pd/ rw, owner @{HOME}/.i2pd/** rwk, #include if exists } i2pd-2.39.0/contrib/certificates/000077500000000000000000000000001411072525600165535ustar00rootroot00000000000000i2pd-2.39.0/contrib/certificates/family/000077500000000000000000000000001411072525600200345ustar00rootroot00000000000000i2pd-2.39.0/contrib/certificates/family/gostcoin.crt000066400000000000000000000013251411072525600223740ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6jCCAY+gAwIBAgIJAPeWi4iUKLBJMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdnb3N0Y29p bi5mYW1pbHkuaTJwLm5ldDAeFw0xNzA4MDExMzQ4MzdaFw0yNzA3MzAxMzQ4Mzda MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdnb3N0Y29pbi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABC+9iIYumUNnsqKbnTluHimV8OdGvo7yeGxuqhfNNB2b3jvbFJ81scgH dsZtMQmUxgKM5nH+NQJMoCxHhSlRy2QwCgYIKoZIzj0EAwIDSQAwRgIhANNh7mOp nBBPRh2a/ipG1VYS0d+mNjSrpz8xWcG3CXPLAiEAjM5MTfv9sOJ74PeZVhFZ02w4 vhgyZCeLJ57f123Lm1A= -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/family/i2p-dev.crt000066400000000000000000000014011411072525600220100ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICCjCCAa2gAwIBAgIEfT9YJTAMBggqhkjOPQQDAgUAMHkxCzAJBgNVBAYTAlhY MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v dXMgTmV0d29yazEPMA0GA1UECxMGZmFtaWx5MR8wHQYDVQQDExZpMnAtZGV2LmZh bWlseS5pMnAubmV0MB4XDTE1MTIwOTIxNDIzM1oXDTI1MTIwODIxNDIzM1oweTEL MAkGA1UEBhMCWFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMV STJQIEFub255bW91cyBOZXR3b3JrMQ8wDQYDVQQLEwZmYW1pbHkxHzAdBgNVBAMT FmkycC1kZXYuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AAR7FPSglYrxeSPzv74A1fTwjajZWV0TljqEMBS/56juZQB/7xOwrsHFHA0eEEF9 dTH64wx3lhV/9sh/stwPU2MToyEwHzAdBgNVHQ4EFgQUQh4uRP1aaX8TJX5dljrS CeFNjcAwDAYIKoZIzj0EAwIFAANJADBGAiEAhXlEKGCjJ4urpi2db3OIMl9pB+9t M+oVtAqBamWvVBICIQDBaIqfwLzFameO5ULgGRMysKQkL0O5mH6xo910YQV8jQ== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/family/i2pd-dev.crt000066400000000000000000000013251411072525600221610ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6TCCAY+gAwIBAgIJAI7G9MXxh7OjMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAxNDE2MzhaFw0yNjAyMTcxNDE2Mzha MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMlWL3loKVOfsA8Rm91QR53Il69mQiaB7n3rUhfPkJb9MYc1S4198azE iSnNZSXicKDPIifaCgvONmbACzElHc8wCgYIKoZIzj0EAwIDSAAwRQIgYWmSFuai TJvVrlB5RlbiiNFCEootjWP8BFM3t/yFeaQCIQDkg4xcQIRGTHhjrCsxmlz9KcRF G+eIF+ATfI93nPseLw== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/family/mca2-i2p.crt000066400000000000000000000012341411072525600220600ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBwTCCAWigAwIBAgIJAOZBC10+/38EMAkGByqGSM49BAEwZzELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDEgMB4GA1UEAwwXbWNhMi1pMnAuZmFtaWx5LmkycC5uZXQwHhcN MTYwMzI4MjIwMjMxWhcNMjYwMzI2MjIwMjMxWjBnMQswCQYDVQQGEwJBVTETMBEG A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg THRkMSAwHgYDVQQDDBdtY2EyLWkycC5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49 AgEGCCqGSM49AwEHA0IABNNyfzJr/rMSUeWliVBbJHRF2+qMypOlHEZ9m1nNATVX 64OhuyuVCmbF9R3oDkcZZJQQK1ovXd/EsbAIWDI8K/gwCQYHKoZIzj0EAQNIADBF AiEApmv2tvMwzlvPjHJG1/5aXOSjYWw2s4ETeGt4abWPQkACIBbF3RuCHuzg+KN8 N0n9hAJztAqhRCdG3hilxF4fbVLp -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/family/volatile.crt000066400000000000000000000012401411072525600223620ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBxDCCAWmgAwIBAgIJAJnJIdKHYwWcMAoGCCqGSM49BAMCMGcxCzAJBgNVBAYT AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn aXRzIFB0eSBMdGQxIDAeBgNVBAMMF3ZvbGF0aWxlLmZhbWlseS5pMnAubmV0MB4X DTE2MDQyNjE1MjAyNloXDTI2MDQyNDE1MjAyNlowZzELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDEgMB4GA1UEAwwXdm9sYXRpbGUuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjO PQIBBggqhkjOPQMBBwNCAARf6LBfbbfL6HInvC/4wAGaN3rj0eeLE/OdBpA93R3L s8EUp0YTEJHWPo9APiKMmAwQSsMJfjhNrbp+UWEnnx2LMAoGCCqGSM49BAMCA0kA MEYCIQDpQu2KPV5G1JOFLoZvdj+rcvEnjxM/FxkaqikwkVx8FAIhANP7DkUal+GT SuiCtcqM4QyIBsfsCJBWEMzovft164Bo -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/000077500000000000000000000000001411072525600200225ustar00rootroot00000000000000i2pd-2.39.0/contrib/certificates/reseed/acetone_at_mail.i2p.crt000066400000000000000000000036601411072525600243360ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIEctG1gDANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQYWNldG9uZUBtYWls LmkycDAeFw0yMTAxMjUxMDMyMjBaFw0zMTAxMjMxMDMyMjBaMHAxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBhY2V0b25lQG1h aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwqF/BRRmvZ54 5XArgxbytDi7m7MDjFE/whUADruHj/9jXGCxE8DDiiKTt3yhfakV0SNo5xk7AMD+ wqiSNC5JCHTm18gd2M4cQLIaOVRqucLLge4XVgk2WPX6OT98wfxh7mqA3wlSdEpj dY3Txtkf7VfZLicG76/RBtLFW3aBdsn63hZaQqZE4x/5MJyPVZx59+lys5RmMi0o LpXJy4HOu1/Gl1iKDJoI/ARFG3y7uP/B+ZtZBitJetTs0HcqycnNJq0tVZf2HiGF JNy67AL4foxNYPXP6QsvXvp6LRpGANaBCkFCBlriSF+x1zO2H3uAkRnuLYXuKIfB HudejTp4R57VgZGiHYoawHaF17FVAApue9G8O82XYECjhET35B9yFoOBHTvaMxLU CKrmayH8KMQon95mfe1qpoO3/YDa8DCxkjAfjdtytat7nt2pGZMH6/cLJxcFiofh RtRVvb+omv/X12j/6iCFrwP4NvBnAZsa736igbjpyee5n+CSyYxd9cJkRX1vQVk7 WFSqL58Pz+g6CKJmdMPvqNOfUQ6mieBeejmx35B4pLzLcoNxw8R3O1+I2l4dg042 dEydKRQNwdzOec4jYwnKR40iwIyZxpchXWGRbBdyF5RQCbIIo60QBJlfXMJ2svan q5lYIeWeY3mlODXu4KH4K09y10KT8FsCAwEAAaMhMB8wHQYDVR0OBBYEFMh+DoIL APNiu2o+6I9A49joNYQuMA0GCSqGSIb3DQEBDQUAA4ICAQBFeOJi0rmkqN5/E3IB nE2x4mUeLI82tUcN2D3Yu8J81vy4DnH+oMRQFDtYEHW5pfirRmgSZ7MQwYQnqWLp iTE7SyCxlqGrmVsYp7PzfS1pUT2QeWPtsNYUDdraG0Zr9BkIGB60VMhjMSa9WUrj lbchzr6E/j/EsEOE7IK08JxIDKCDZM2LLwis4tAM6tmiylkMf2RlUBIRBs1TCO+q x3yByttNE2P4nQyQVQpjc1qsaOMvJvbxun37dwo+oTQy+hwkA86BWTDRYdN3xwOk OfAOtlX6zM/wCKMN0ZRnjZoh59ZCn4JXokt3IjZ4n8qJOuJFRKeKGmGeKA8uaGW8 ih5tdB99Gu5Z8LOT1FxAJKwQBn5My0JijPoMit4B0WKNC8hy2zc2YvNfflu1ZRj5 wF4E5ktbtT/LWFSoRPas/GFS8wSXk/kbSB0ArDcRRszb3JHqbALmSQxngz3rfwb3 SHwQIIg956gjMDueEX5CrGrMqigiK53b9fqtpghUrHDsqtEXqeImpAY65PX1asqo metDNuETHF7XrAjP7TGJfnrYQyeK90iS7j1G68ScBGkKY2nsTnFoXkSk5s5D338E SUzPaOlh91spmkVY6gQTVQ7BakADBHw+zBgDA1gBN/4JPvgN36hquj63+aG1cKy3 3ZUnv2ipo2fpr69NtuBnutK6gw== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/creativecowpat_at_mail.i2p.crt000066400000000000000000000041431411072525600257350ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIGAzCCA+ugAwIBAgIRAJNGLpTSm2U3GjXmFkjT/0cwDQYJKoZIhvcNAQELBQAw dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM F2NyZWF0aXZlY293cGF0QG1haWwuaTJwMB4XDTE3MDUyNjE5NDQzOVoXDTI3MDUy NjE5NDQzOVowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx IDAeBgNVBAMMF2NyZWF0aXZlY293cGF0QG1haWwuaTJwMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAo3XP4JToVbfM5e4GxyAqzu2DJV7ohpzlLqMLyz/9 XgZ7ipctNoxVZytoaNgMeAHInJn5OhUC4D+emsgsLJqFjnb2pxf6v45sRZLBMieb wJlxUmskucpTXwDwuHBk/s3xmH4IluadmzwiCMyycQFH/CNXmu5bonAuZ075rT1Q a8W0vb8eSfNYXn+FKQBROqsL5Ep+iJM6FX+oWMxJPk/zNluIu9qTdZL7Fts2+ObP X5WLE4Dtot57vMI2Tg3fjnpgvk3ynQjacS8+CBEbvA/j32PBS1mQB+ebl56CQTBv glHrXiNdp24TAwy8mwWHknhpt4cvRXOJGZphSVNRYFVk0vv7CTjmQg6WOBGD+d/P cosvyKxQz4WUSmtaKUftgCBdnemeM7BppZv2URflEOY6Uv3f9xlEC6yVEzSaa2Md tG6XRkDyupWCBFwmSm1uS+SXXhxAQGn3eMXPFA1XkwNnZtmM9kvSVt34FBE231oN 4oM7rE3ZDyTocZw7cv7bl8idmqsLXDTSFn5Q2iLwvw6ZeTenk8qHrq9kVH1UVE2l 31iKDNdGQkkVcnTWYfiqriwGLpTqbeD/8n9OBgCke1TiKQzP1o66nhkGJTiiRLFK A8rlSpqBcjGbXDs/X+Ote9MrCxE089eCqN51kzDeQ4Yvy8gDOTBPGEhBLirx+3pp yWkCAwEAAaOBiTCBhjAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUH AwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0OBBkEF2NyZWF0aXZl Y293cGF0QG1haWwuaTJwMCIGA1UdIwQbMBmAF2NyZWF0aXZlY293cGF0QG1haWwu aTJwMA0GCSqGSIb3DQEBCwUAA4ICAQCYzeYyPYhW+/SZSfpDl6JTzXy8S6NG+yjq pcinxaIF4XFoXLwWD3uHR4jgpU750mhHJjpGIaltZjFaqLbqtysbqb0vdShyaK/n Td4CXrNBvEHvLI6DZyDX4BcDlhCI7/dMCSHXwFIhRHhYSnTsJO32BdP5DsUUAlSW G0FlEEWjlxcdRwIITv70cFNlNOqJeyvtk9DPT+nEzssKWxVZcqN4GK8dvQVWgL91 8uzrcAYpAEQfmkKzsGmV4v5gWumLZmnzc24hUhVsHhIph4HAmjPMFCppI1tgiwg7 fH71MYB8b9KBJKipkLdAL292mDLS4G3MGQwMbcjnTyIqOktmyyj/1CorZAKqBtzu Qyo7z8FM2pd5nzk7QDx/vsJ4bNAYvVu7titDW5mv5JDoQcp2uDVGePlonX3I8iFx CqKFzGHiR0EU8oWw0Pqf+y2rEV4L74agmUR7VbA+/ovz0UnDUoXIynSwpK7Kfo8D B7ky9RnmsxJX6TXaMVW06IlYuwIUsAWbMhKvdXbGZur5VVi1ZY1/HgZZnoXejzCe w3mMl6movkcA0noDXQ+eauUDHjktrVUJdZKYvZNjfnz2rB+MI5wB/hzeBv4KuYFE oTFt8SwTzs0joM4c7RomTxc+QFe832SvjPAnxQn17qSjD8z4c7Ako6sCKvpdBSDm Hz8KWVkHZg== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/echelon_at_mail.i2p.crt000066400000000000000000000036601411072525600243350ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIESg3kkzANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQZWNoZWxvbkBtYWls LmkycDAeFw0xNDA3MzExNjQ3MDJaFw0yNDA3MzAxNjQ3MDJaMHAxCzAJBgNVBAYT AlhYMQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9u eW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQDDBBlY2hlbG9uQG1h aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmcEgLwwhzLNe XLOMSrhwB8hWpOhfjo4s6S/wjBtjjUc8nI3D0hSn3HY26p0rvcvNEWexPUpPULmC exGkU463nu7PiFONiORI1eJAiUFHibRiaA7Wboyo38pO73KirwjG07Y+Ua0jp+HS +4FQ/I/9H/bPplReTOU/6hmRbgQ69U8nE68HzZHQxP68yVJ2rPHSXMPhF4R1h0G1 1mCAT+TgTsnwHNGF77XHJnY4/M4e2cgycEZjZow36C3t2mNDVkMgF19QQeb9WmLR zREn3nq9BJqHpUkn9yWw0kKXTZSds+7UxESfzf3BzK0+hky2fh5H+qbYAo2lz4yj 81MXTAu+4RRkg4DBLlF+2dkclhwQLxxzvkRC6tPkn5i33Yltg7EfzA9IoQ05potJ I+iOcF+aStfFgFj9u3B5UkcF4P0cH1QD3c6BK4hIezQYqRoPly1gHqg+XdwjG/dr 4as7HA9FTz3p2E8nClpIC1x3hfgwAdfd29aeBxO1WW/z99iMF7TBAF+u5T86XEW1 WpknqCbTli36yJ8a5fPWxZHrryBRJT5yLxejjFeadtutBSwljiVFq+Y38VqwFivq VLiBt7IxAsZ8iilgfnnnAvBH6chWfSKb4H7kB4TJvDiV96QmmvoEaWYNHZozMhyK tO3b5w+xqbJXyCLA3Q75jD0km76hjcECAwEAAaMhMB8wHQYDVR0OBBYEFAHQcAam QRS/EUhuCSr9pB4Ux0rYMA0GCSqGSIb3DQEBDQUAA4ICAQBq1+1QLmgLAjrTg3tb 4XKgAVICQRoBDNUEobQg3pYeUX9eFNya2RxNljuvYpwT80ilGMPOXcjddmr5ngiK dbGRcuuJk9MPEHtPaPT3+JJlvKQ3B3g2wva2Wz2OAyLZUGQs389K4nTbwh4QF0n2 aHFL8BHiD62hiKnCoNaW4ZovUNNvOxo9lMyAiaFU2gqQNcdad8hP9EAllbvbxDx9 Tjww2UbwQUIHS9rna4Tlu+f0hDXTWIutc2A51W2fJCb7L3+lYO7Wv55ND/WtryLZ XpMp27+MpuEnN3kQmz/l9R0hIJsWc/x9GQkjm5wEaIZEyTtenqwRKGmVCtAj0Pgv jn1L3/lWmrNq+OZHb/QeyfKtA3nXfQKVmT98ewQiK/S5i1xIAXCJPytOD887b/o1 cdurTmCiZMwgiQ+HLJqCg3MDa5mvKqRkRdZXfE6aQWEcSbpAhpV15R17q7L+Fg0W shLSNucxyGNU8PjiC/nOmqfqUiPiMltJjPmscxBLim8foyxjakC4+6N6m+Jzgznj PocBehFAfKYj66XEwzIBN7Z2uuXoYH9YptkocFjTzvchcryVulDWZ4FWxreUMhpM 4oyjjhSB4tB9clXlwMqg577q3D6Ms0zLTqsztyPN3zr6jGev3jpVq7Q1GOlciHPv JNJOWTH/Vas1W6XlwGcOOAARTQ== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt000066400000000000000000000040561411072525600253540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF3TCCA8WgAwIBAgIRAKye34BRrKyQN6kMVPHddykwDQYJKoZIhvcNAQELBQAw dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM F2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMB4XDTIwMDUwNzA1MDkxMFoXDTMwMDUw NzA1MDkxMFowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx IDAeBgNVBAMMF2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA5Vt7c0SeUdVkcXXEYe3M9LmCTUyiCv/PHF2Puys6 8luLH8lO0U/pQ4j703kFKK7s4rV65jVpGNncjHWbfSCNevvs6VcbAFoo7oJX7Yjt 5+Z4oU1g7JG86feTwU6pzfFjAs0RO2lNq2L8AyLYKWOnPsVrmuGYl2c6N5WDzTxA Et66IudfGsppTv7oZkgX6VNUMioV8tCjBTLaPCkSfyYKBX7r6ByHY86PflhFgYES zIB92Ma75YFtCB0ktCM+o6d7wmnt10Iy4I6craZ+z7szCDRF73jhf3Vk7vGzb2cN aCfr2riwlRJBaKrLJP5m0dGf5RdhviMgxc6JAgkN7Ius5lkxO/p3OSy5co0DrMJ7 lvwdZ2hu0dnO75unTt6ImR4RQ90Sqj7MUdorKR/8FcYEo+twBV8cV3s9kjuO5jxV g976Q+GD3zDoixiege3W5UT4ff/Anm4mJpE5PKbNuO+KUjk6WA4B1PeudkEcxkO4 tQYy0aBzfjeyENee9otd4TgN1epY4wlHIORCa3HUFmFZd9VZMQcxwv7c47wl2kc9 Cv1L6Nae78wRzRu2CHD8zWhq+tv5q7Md2eRd3mFPI09ljsOgG2TQv6300WvHvI5M enNdjYjLqOTRCzUJ2Jst4BZsvDxjWYkHsSZc1UORzm2LQmh2bJvbhC3m81qANGw6 ZhcCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMC BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCAGA1UdDgQZBBdoYW5raGlsbDE5 NTgwQGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAVtMF7lrgkDLTNXlavI7h HJqFxFHjmxPk3iu2Qrgwk302Gowqg5NjVVamT20cXeuJaUa6maTTHzDyyCai3+3e roaosGxZQRpRf5/RBz2yhdEPLZBV9IqxGgIxvCWNqNIYB1SNk00rwC4q5heW1me0 EsOK4Mw5IbS2jUjbi9E5th781QDj91elwltghxwtDvpE2vzAJwmxwwBhjySGsKfq w8SBZOxN+Ih5/IIpDnYGNoN1LSkJnBVGSkjY6OpstuJRIPYWl5zX5tJtYdaxiD+8 qNbFHBIZ5WrktMopJ3QJJxHdERyK6BFYYSzX/a1gO7woOFCkx8qMCsVzfcE/z1pp JxJvshT32hnrKZ6MbZMd9JpTFclQ62RV5tNs3FPP3sbDsFtKBUtj87SW7XsimHbZ OrWlPacSnQDbOoV5TfDDCqWi4PW2EqzDsDcg+Lc8EnBRIquWcAox2+4zmcQI29wO C1TUpMT5o/wGyL/i9pf6GuTbH0D+aYukULropgSrK57EALbuvqnN3vh5l2QlX/rM +7lCKsGCNLiJFXb0m6l/B9CC1947XVEbpMEAC/80Shwxl/UB+mKFpJxcNLFtPXzv FYv2ixarBPbJx/FclOO8G91QC4ZhAKbsVZn5HPMSgtZe+xWM1r0/UJVChsMTafpd CCOJyu3XtyzFf+tAeixOnuQ= -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/hottuna_at_mail.i2p.crt000066400000000000000000000040211411072525600243720ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFxzCCA6+gAwIBAgIQZfqn0yiJL3dGgCjeOeWS6DANBgkqhkiG9w0BAQsFADBw MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ aG90dHVuYUBtYWlsLmkycDAeFw0xNjExMDkwMzE1MzJaFw0yNjExMDkwMzE1MzJa MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD DBBob3R0dW5hQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC AgEA21Bfgcc9VVH4l2u1YvYlTw2OPUyQb16X2IOW0PzdsUO5W78Loueu974BkiKi 84lQZanLr0OwEopdfutGc6gegSLmwaWx5YCG5uwpLOPkDiObfX+nptH6As/B1cn+ mzejYdVKRnWd7EtHW0iseSsILBK1YbGw4AGpXJ8k18DJSzUt2+spOkpBW6XqectN 8y2JDSTns8yiNxietVeRN/clolDXT9ZwWHkd+QMHTKhgl3Uz1knOffU0L9l4ij4E oFgPfQo8NL63kLM24hF1hM/At7XvE4iOlObFwPXE+H5EGZpT5+A7Oezepvd/VMzM tCJ49hM0OlR393tKFONye5GCYeSDJGdPEB6+rBptpRrlch63tG9ktpCRrg2wQWgC e3aOE1xVRrmwiTZ+jpfsOCbZrrSA/C4Bmp6AfGchyHuDGGkRU/FJwa1YLJe0dkWG ITLWeh4zeVuAS5mctdv9NQ5wflSGz9S8HjsPBS5+CDOFHh4cexXRG3ITfk6aLhuY KTMlkIO4SHKmnwAvy1sFlsqj6PbfVjpHPLg625fdNxBpe57TLxtIdBB3C7ccQSRW +UG6Cmbcmh80PbsSR132NLMlzLhbaOjxeCWWJRo6cLuHBptAFMNwqsXt8xVf9M0N NdJoKUmblyvjnq0N8aMEqtQ1uGMTaCB39cutHQq+reD/uzsCAwEAAaNdMFswDgYD VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV HRMBAf8EBTADAQH/MBkGA1UdDgQSBBBob3R0dW5hQG1haWwuaTJwMA0GCSqGSIb3 DQEBCwUAA4ICAQCibFV8t4pajP176u3jx31x1kgqX6Nd+0YFARPZQjq99kUyoZer GyHGsMWgM281RxiZkveHxR7Hm7pEd1nkhG3rm+d7GdJ2p2hujr9xUvl0zEqAAqtm lkYI6uJ13WBjFc9/QuRIdeIeSUN+eazSXNg2nJhoV4pF9n2Q2xDc9dH4GWO93cMX JPKVGujT3s0b7LWsEguZBPdaPW7wwZd902Cg/M5fE1hZQ8/SIAGUtylb/ZilVeTS spxWP1gX3NT1SSvv0s6oL7eADCgtggWaMxEjZhi6WMnPUeeFY8X+6trkTlnF9+r/ HiVvvzQKrPPtB3j1xfQCAF6gUKN4iY+2AOExv4rl/l+JJbPhpd/FuvD8AVkLMZ8X uPe0Ew2xv30cc8JjGDzQvoSpBmVTra4f+xqH+w8UEmxnx97Ye2aUCtnPykACnFte oT97K5052B1zq+4fu4xaHZnEzPYVK5POzOufNLPgciJsWrR5GDWtHd+ht/ZD37+b +j1BXpeBWUBQgluFv+lNMVNPJxc2OMELR1EtEwXD7mTuuUEtF5Pi63IerQ5LzD3G KBvXhMB0XhpE6WG6pBwAvkGf5zVv/CxClJH4BQbdZwj9HYddfEQlPl0z/XFR2M0+ 9/8nBfGSPYIt6KeHBCeyQWTdE9gqSzMwTMFsennXmaT8gyc7eKqKF6adqw== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/igor_at_novg.net.crt000066400000000000000000000040051411072525600237750ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxst4 cam3YibBtQHGPCPX13uRQti56U3XZytSZntaKrUFmJxjt41Q/mOy3KYo+lBvhfDF x3tWKjgP9LJOJ28zvddFhZVNxqZRjcnAoPuSOVCw88g01D9OAasKF11hCfdxZP6h vGm8WCnjD8KPcYFxJC4HJUiFeProAwuTzEAESTRk4CAQe3Ie91JspuqoLUc5Qxlm w5QpjnjfZY4kaVHmZDKGIZDgNIt5v85bu4pWwZ6O+o90xQqjxvjyz/xccIec3sHw MHJ8h8ZKMokCKEJTaRWBvdeNXki7nf3gUy/3GjYQlzo0Nxk/Hw4svPcA+eL0AYiy Jn83bIB5VToW2zYUdV4u3qHeAhEg8Y7HI0kKcSUGm9AQXzbzP8YCHxi0sbb0GAJy f1Xf3XzoPfT64giD8ReUHhwKpyMB6uvG/NfWSZAzeAO/NT7DAwXpKIVQdkVdqy8b mvHvjf9/kWKOirA2Nygf3r79Vbg2mqbYC/b63XI9hheU689+O7qyhTEhNz+11X0d Zax7UPrLrwOeB9TNfEnztsmrHNdv2n+KcOO2o11Wvz2nHP9g+dgwoZSD1ZEpFzWP 0sD5knKLwAL/64qLlAQ1feqW7hMr80IADcKjLSODkIDIIGm0ksXqEzTjz1JzbRDq jUjq7EAlkw3G69rv1gHxIntllJRQidAqecyWHOMCAwEAAaNaMFgwDgYDVR0PAQH/ BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC AQADyPaec28qc1HQtAV5dscJr47k92RTfvan+GEgIwyQDHZQm38eyTb05xipQCdk 5ruUDFXLB5qXXFJKUbQM6IpaktmWDJqk4Zn+1nGbtFEbKgrF55pd63+NQer5QW9o 3+dGj0eZJa3HX5EBkd2r7j2LFuB6uxv3r/xiTeHaaflCnsmyDLfb7axvYhyEzHQS AUi1bR+ln+dXewdtuojqc1+YmVGDgzWZK2T0oOz2E21CpZUDiP3wv9QfMaotLEal zECnbhS++q889inN3GB4kIoN6WpPpeYtTV+/r7FLv9+KUOV1s2z6mxIqC5wBFhZs 0Sr1kVo8hB/EW/YYhDp99LoAOjIO6nn1h+qttfzBYr6C16j+8lGK2A12REJ4LiUQ cQI/0zTjt2C8Ns6ueNzMLQN1Mvmlg1Z8wIB7Az7jsIbY2zFJ0M5qR5VJveTj33K4 4WSbC/zMWOBYHTVBvGmc6JGhu5ZUTZ+mWP7QfimGu+tdhvtrybFjE9ROIE/4yFr6 GkxEyt0UY87TeKXJ/3KygvkMwdvqGWiZhItb807iy99+cySujtbGfF2ZXYGjBXVW dJOVRbyGQkHh6lrWHQM4ntBv4x+5QA+OAan5PBF3tcDx1vefPx+asYslbOXpzII5 qhvoQxuRs6j5jsVFG6RdsKNeQAt87Mb2u2zK2ZakMdyD1w== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/lazygravy_at_mail.i2p.crt000066400000000000000000000040321411072525600247420ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFzTCCA7WgAwIBAgIQCnVoosrOolXsY+bR5kByeTANBgkqhkiG9w0BAQsFADBy MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEbMBkGA1UEAwwS bGF6eWdyYXZ5QG1haWwuaTJwMB4XDTE2MTIyNzE1NDEzNloXDTI2MTIyNzE1NDEz NlowcjELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwG A1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGzAZBgNV BAMMEmxhenlncmF2eUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAN3q+0nUzz9+CBSoXUNf8K6kIc9zF+OP1NVBmOu3zTtkcEnhTtoDNXeU EV8DhlBhEACbPomA+szQ5zp3O3OYQc2NV50S7KKqlfn5LBBE3BL2grTeBxUMysDd 0TlpxcHKwaog4TZtkHxeNO94F1vgeOkOnlpCQ6H3cMkPEGG3zu1A1ccgPiYO838/ HNMkSF//VZJLOfPe1vmn9xTB7wZ0DLpEh12QZGg3irA+QDX5zy6Ffl+/Lp+L4tXT uPZUaC6CL6EABX4DvQcFrOtiWfkbi/ROgYCeTrYw1XbDHfPc+MBxGo1bX7JjnD0o mFFvo+PjxvWDmCad2TaITh6DwGEeWKu8NtJAyaO5p1ntauuWGB5Xzua4aMmIy7GT esHQkhW+5IooM0R5bZI8/KXo4Bj52bX5qv+oBiExc6PUUTLWyjoWHb7fKdddwGfc lUfniV/fw7/9ysIkQZcXLDCXR6O/nH9aGDZ7bxHedw4/LxAXYPfNojb5j7ZVa65o PWD5xuQfbE+95DdbnKjcjYiam4kjApe7YPwOhtoRJYSGAkrpIMfzFxCXgjTsi3Kw Ov+sYmBvWBK4ROWQZTgHei3x4FpAGWHCAeTeeQGKmWQ8tT7ZklWD9fBm3J/KXo7I WCxRW9oedItyqbRuAGxqaoaGSk6TtPVjyPIUExDp1dr4p1nM1TOLAgMBAAGjXzBd MA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw DwYDVR0TAQH/BAUwAwEB/zAbBgNVHQ4EFAQSbGF6eWdyYXZ5QG1haWwuaTJwMA0G CSqGSIb3DQEBCwUAA4ICAQA2fei/JajeQ7Rn0Hu3IhgF9FDXyxDfcS9Kp+gHE56A 50VOtOcvAQabi/+lt5DqkiBwanj0Ti/ydFRyEmPo45+fUfFuCgXcofro8PGGqFEz rZGtknH/0hiGfhLR9yQXY8xFS4yvLZvuIcTHa9QPJg3tB9KeYQzF91NQVb5XAyE7 O3RvollADTV31Xbhxjb7lgra6ff9dZQJE6xtlSk/mnhILjlW80+iPKuj3exBgbJv ktiR4ZT4xjh1ZgNJX5br86MZrhyyyGWwHWHS0e443eSrrmAPD69zxsfvhoikRX1z tDz0zB70DwS4pSbVrFuWaIAcbg36vWO8tYPBzV8iBB/tBTURGJjv6Q0EoI5GHmJi LOhU3B6xublv8Tcoc3tgMqI9STnWROtTiCS6LsWNSXhVpIZqvaiOEtPN4HyL33sf j5rfPq76gKrTloeLnwLGq0Rs94ScffYkBap3fQ/ALb87LQcwSN4EkObur5pcd7TS qNdanvCGK8v1UYVzH4l9jekPGsM5euohwAkIl1kZ6+tqGY/MTa7HwTTQyLDTco1t sPy6neN46+H5DYHADyU5H2G39Kk3WcLmPtfxlPDM6e73+47fJkXnmiaWM0Lrt80y Enng6bFGMZH01ZsqBk09H+Uswv8h7k69q9uWAS95KE0omCMVtIpoPZXTnRhe6mBC +g== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/orignal_at_mail.i2p.crt000066400000000000000000000036601411072525600243530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIEbNbRPjANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQb3JpZ25hbEBtYWls LmkycDAeFw0yMTA3MDYyMjExMDFaFw0zMTA3MDQyMjExMDFaMHAxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBvcmlnbmFsQG1h aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvNJz2KGuAkHP tGFobfLvpybtxB50fkcEsTc9opmiy7wBKK9rSI01VS616IhABkWKZVfK2A9NqpGv v/CyhTKoaeSNeXY7+zORUWgWK/zA9fA4GRZFqlW8j4tbompDwcLYNqRBCsn1C0OY YA5JhXPBixMcnXl8N8x4sXhQ4l9R3+QrydhUHRvgDc8dOxRyIX7zuQAyf8tmA2Xo xZLdvDcCJdLBIbFwxhIceIhgcOwaOx7oRkZDZdYcLJd3zjyPbu8JtOM2ZkwH7r+0 ro5PktuDp2LAS6SII5yYNcwcrvPZGPqhLdifIw1BrdTIb/rIkQZ5iXOOdyPmT7e8 IwAJcPFlfvrS4Vbi9oDqyx3aDUBoubgmFnO1TirL56ck83R/ubcKtdnyzAn5dp+f ZNYW6/foSBpDDOCViylbFAR5H0HJEbBns7PZx6mGEEI4tUAJdNYl7Ly7Df60a9Rz cD/gz08U9UwFXYKoT6roEjToADGAzb5MI4cVlAb2AmQaMNXNe04HcDL1bU50mkNU amqPv8nxf72fBQCEmZz2G57T6QiYTtcCwiWS1QdWsuaOtCo9zO0MKcjzSdUxuxEc dXhjQdNegsgg/Xk7bJ8lKOsACqMpFftdPmuyeZU2t+3RPuBpV/0j2qUfg/y6kb0z CxAOYmlcL4kqw4VT+5V/EeZLIG0h9I0CAwEAAaMhMB8wHQYDVR0OBBYEFD/wJObg CCDuhMJCVWTSTj+B3rsUMA0GCSqGSIb3DQEBDQUAA4ICAQC0PjsTSPWlGbLNeeI8 F0B5xAwXYJzZ7/LRxh8u42HDUqVIDjqkuls1l3v9D7htty2Gr3Ws2dcvcOr2KcOy mEWg+jdP/N3vt9IkZeVS4YQoPgq6orn7lVkk00bcKb24f7ZnoQnnVV0/m42Y5P4j LLh+8MBxsez9azXyZbDVEkgsMUAkdVO6KNz6scqz7wb8egV2GAMAp7cwChC6lanK gv9ZyJhG/HdTv6VyuMZhJy6rX4geM97tm1iHu1VLsQcIzBKAdEvWJv8ofMeiyINe hqAP9NYaeowKi975NOrmf+XZwxd0niApIohV684RCVUfL8H7HSPbdXhBJ/WslyDP cTGhA2BLqEXZBn/nLQknlnl0SZTQxG2n4fEgD1E5YS/aoBrig/uXtWm2Zdf8U3mM +bNXhbi9s7LneN2ye8LlNJBSRklNn/bNo8OmzLII1RQwf1+vaHT96lASbTVepMZ/ Y9VcC8fAmho/zfQEKueLEB03K+gr2dGD+1crmMtUBjWJ9vPjtooZArtkDbh+kVYA cx4N4NXULRwxVWZe5wTQOqcZ3qSS1ClMwaziwychGaj8xRAirHMZnlPOZO1UK4+5 8F4RMJktyZjNgSLP76XPS4rJK5fobuPqFeA4OpDFn/5+/XeQFF6i6wntx1tzztzH zc+BrVZOdcYPqu9iLXyRQ9JwwA== -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/r4sas-reseed_at_mail.i2p.crt000066400000000000000000000036741411072525600252260ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIEY2XeQjANBgkqhkiG9w0BAQ0FADB1MQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxHjAcBgNVBAcMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL MAkGA1UECgwCWFgxDDAKBgNVBAsMA0kyUDEeMBwGA1UEAwwVcjRzYXMtcmVzZWVk QG1haWwuaTJwMB4XDTE3MDYyMjEwNTQ1NFoXDTI3MDYyMDEwNTQ1NFowdTELMAkG A1UEBhMCWFgxCzAJBgNVBAgMAlhYMR4wHAYDVQQHDBVJMlAgQW5vbnltb3VzIE5l dHdvcmsxCzAJBgNVBAoMAlhYMQwwCgYDVQQLDANJMlAxHjAcBgNVBAMMFXI0c2Fz LXJlc2VlZEBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB ANgsj5LhF4uGG4RDueShqYQZsG5Rz6XUAtK9sVGFdmdJTDZirUMZcCGCGZP/Harz QaZU9EYxOCztnpLCQksSCpdRsij56MURS0tW/1x7LHIDUOi911Of57jgIHH+3E5n 6tuRxEk6J/9Ji3PI+89kl0sPKMVFMyKkINprVTA5zr/keyYEG0p6HSEYYiJkQH78 8uoOCAmlk9mxkJFb+zviCk6jsYwdH+ofD6Lw5ueOlYUbeZ9Nd7jfSdf20XM7ofIw W2COtsbq3J7vNrQJMV7HkHxVx/7OqmjQF02OahZFZREVZqbHpL501iTn9Iqd5qKq IsxYjk7ZnP4UUCBk8NOU5TuWsy0qNw+TJDI9s55Fi4KPtXWf47HIl6CdpM5y/D5L eufCojSwPKlrD6x9gTyJdBggBZRIyplXdKffo/95hUhEkv86yfsVVR7Gu1uy0O8T Gtb8Da/oi5eEZBHWonLVicLPei5jeo+1gbR09PQ6s41uMZlOhMe4RSgiIQj/7UVo ffKdl1MPNKr1u2fgVj8kxqg8ZivWKQ2taEgimU2EkQcNcE96M9yQlNNpNvqSAQVk wYXlHt0AN6A1A8u1pItxaTwXnbmx+OBJZoKl4ZQeaC8wtKjTgAgVXp+g5iot2gir LjxCRx1WLG1c8vRg1W8CDZII8Swc8EWpMhI+0hPv7/4/AgMBAAGjITAfMB0GA1Ud DgQWBBTN5sKbrNzwE8sgMGDekfOPgX8/JDANBgkqhkiG9w0BAQ0FAAOCAgEAjLaB bHqvFTs0ikAtesk9r8+8XVIsP5FR57zZCek2vxkHcCQWw8Uqs3ndInRX4FirKSLT WRb4aSwFCkrmwueecTpXN/RBC+fZj+POCfdILEsA+FGreAM2q5ZXv/Q0jyIXOXEM +KL0JZXnNS0/dqR3IYbC7f39CL6Sf40gRGTwTWWGg3KnynoS0v1zQcZLTMhHBD2X tgdIPbroq9t4gXa7Dhm0egYfQOI/7re2wiZT7UWVVwEpYqKf6JApFHa1nNOFMrLF 45JHQIHArkoxpQdfSe9HBoyJiB5vz398rHZeqbJaF3PIg9rxWWY/NvvOVuIk8U5z 0jExhg29a88B32U7ndvQJqIuGiQghzCiLxC/y1+wAdpeDSbD3OAOHqplvMj3BUn9 yhDSLSjtfBJjnXKxtEcWLR0edHCGEk5mAcL7q1WNxDpxaICwGGpNZN53CtFx7amb egYil448DmiqoQTCTE9pBz8YjwiVfCYLYv17O0NJyYM9Efy/wL3rFlsPJniWHMuH imZybVU4ukjvfOZ+LY4COTwz6w4sfA7a+i+2mOynC7eKX8Yg6i1nXlcY1Z8ykNgi 7B3kz1T/DV56CIm6QUWtepfuKTYq4C6QrBBIXLk1d5g95aWA21u1LRqNZ9GLH+eA gfvIm7v+cELj8a53EQY0LafzZqNC5kQAp916coU= -----END CERTIFICATE----- i2pd-2.39.0/contrib/certificates/reseed/reseed_at_diva.exchange.crt000066400000000000000000000040421411072525600252530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF0zCCA7ugAwIBAgIQWjHyC+NRh3emuuAwcEnKSjANBgkqhkiG9w0BAQsFADB0 MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEdMBsGA1UEAwwU cmVzZWVkQGRpdmEuZXhjaGFuZ2UwHhcNMjAwNjA5MDUzNjQ1WhcNMzAwNjA5MDUz NjQ1WjB0MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4w HAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEdMBsG A1UEAwwUcmVzZWVkQGRpdmEuZXhjaGFuZ2UwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQC6BJGeMEgoXk9dlzKVfmwHrT2VpwTT+wRJvh3eAM746u4uDT2y NPHXhdGcQ9dRRZ63T98IshWCwOmWSlm1kdWkmKkVVb93GUoMQ3gziCi0apLJMAau gEu/sPCbORS2dPsQeAPW2eIsJO7dSjTRiQAuquW//NcIXG4gnxDA52lgke1BvpKr 83SJlCrqECAy6OKtZ49yn75CqmPPWFn0b/E8bxruN5ffeipTTospvdEtT41gXUqk hOz3k8ang+QTWiP//jOjk31KXZ2dbh0LOlNJOvRxCqQmBZafNxxCR4DH8RewfPlL qOiOJVzbLSP9RjqPLwnny5BOjbLWXcaybN5Qv2Pyd4mKtN3EpqBwRu7VnzXpsuuG gRbxNmfKJ/vBEGrZAHAxi0NkHHEEne3B7pPDc2dVZHOfTfCu31m9uDHZ4eHEsNOJ SJRiGjq74l0chCSlBGLrD1Y9LPyqadjdwuB9bzM0tMFC1wPflanQCflhhnEzAfbN BaU2GRXo/I1UCDW/dH1FIkqEe61eMW1Lwqr5tdlrUpdr5VIddTyNJRBJogbZ+HZE 8mcoJW2lXRAkYi7KEm4b4EQNe7sbRNTF0j+fAJ+3ZOZ3O3SMHss6ignlSa+giVim VvL+Joc6wpSzxpeNPf6m82cEO/UvifFYeOC9TpiRriSt+vvgQVzQtfQ+fQIDAQAB o2EwXzAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF BwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHJlc2VlZEBkaXZhLmV4Y2hh bmdlMA0GCSqGSIb3DQEBCwUAA4ICAQCFGOb1dHlwjmgFHEER6oMiGWl1mI3Hb7GX NNI6QUhZQ+iEWGYtsOTk3Q8xejL8t6AG/ZLXfZviLIJXZc5XZfPXk0ezDSC2cYxQ ZAyYPw2dRP14brI86sCSqNAFIax/U5SM3zXhCbBiTfaEoBPfDpvKjx+VliaITUnc sHTRn+C5ID5M8cZIqUSGECPEMU/bDtuRNJLTKYaJ98yXtYuS2CWsMEM4o0GGcnYQ 5HOZT/lbbwfq1Ks7IyJpeIpRaS5qckGcfgkxFY4eGujDuaFeWC+HCIh9RzBJrqZR 73Aly4Pyu7Jjg8xCCf9MswDjtqAjEHgWCmRLWL7p3H6cPipFKNMY6yomYZl5urE7 q6DUAZFKwPqlZpyeaY4/SVvaHTxuPp7484s3db4kPhdmuQS/DOB/7d+cn/S580Vy ALqlFQjtjLEaT16upceAV0gYktDInE6Rtym/OsqilrtYks/Sc0GROSz8lJhDDWbr W3t92muSXDh0rYrEUYWl+xl1gSTpbIP75zzU+cUr1E/qlRY9qZn66FsJpOuN0I0q UXsQS/bPDcA+IW48Hd9LfO9gtTWZslwFTimjEvQ2nJAnUlUQP6OfuPUKHoYX/CwY 2LCN8+pv2bKPDVHvp0lf6xrbbZNvFtzfR0G3AprZjYpuu2XgjVB5nJnwmbH74b9w LD8d2z2Lgg== -----END CERTIFICATE----- i2pd-2.39.0/contrib/debian/000077500000000000000000000000001411072525600153305ustar00rootroot00000000000000i2pd-2.39.0/contrib/debian/README000066400000000000000000000002211411072525600162030ustar00rootroot00000000000000This forder contain systemd unit files. To use systemd daemon control, place files from this directory to debian folder before building package. i2pd-2.39.0/contrib/debian/i2pd.service000077700000000000000000000000001411072525600221762../i2pd.serviceustar00rootroot00000000000000i2pd-2.39.0/contrib/debian/i2pd.tmpfile000066400000000000000000000001021411072525600175410ustar00rootroot00000000000000d /run/i2pd 0755 i2pd i2pd - - d /var/log/i2pd 0755 i2pd i2pd - - i2pd-2.39.0/contrib/docker/000077500000000000000000000000001411072525600153555ustar00rootroot00000000000000i2pd-2.39.0/contrib/docker/Dockerfile000066400000000000000000000047161411072525600173570ustar00rootroot00000000000000FROM alpine:latest LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="openssl" ENV GIT_BRANCH=${GIT_BRANCH} ARG GIT_TAG="" ENV GIT_TAG=${GIT_TAG} ARG REPO_URL="https://github.com/PurpleI2P/i2pd.git" ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" # # Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the # image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. # # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. RUN apk update \ && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ && make USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ && cd /usr/local/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ miniupnpc-dev boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre2 \ libtool g++ gcc # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh RUN echo "export DATA_DIR=${DATA_DIR}" >> /etc/profile VOLUME "$DATA_DIR" EXPOSE 7070 4444 4447 7656 2827 7654 7650 USER i2pd ENTRYPOINT [ "/entrypoint.sh" ] i2pd-2.39.0/contrib/docker/entrypoint.sh000066400000000000000000000004251411072525600201250ustar00rootroot00000000000000#!/bin/sh COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile if [ "$1" = "--help" ]; then set -- $COMMAND --help else ln -s /i2pd_certificates "$DATA_DIR"/certificates set -- $COMMAND $DEFAULT_ARGS $@ fi exec "$@" i2pd-2.39.0/contrib/i18n/000077500000000000000000000000001411072525600146655ustar00rootroot00000000000000i2pd-2.39.0/contrib/i18n/English.po000066400000000000000000000335541411072525600166300ustar00rootroot00000000000000# i2pd # Copyright (C) 2021 PurpleI2P team # This file is distributed under the same license as the i2pd package. # R4SAS , 2021. # msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" "POT-Creation-Date: 2021-08-06 17:12\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-Basepath: .\n" "X-Poedit-KeywordsList: ;tr\n" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" #: daemon/HTTPServer.cpp:177 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:181 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:185 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:188 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" #. tr: Kibibit #: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 msgid "KiB" msgstr "" #. tr: Mebibit #: daemon/HTTPServer.cpp:198 msgid "MiB" msgstr "" #. tr: Gibibit #: daemon/HTTPServer.cpp:200 msgid "GiB" msgstr "" #: daemon/HTTPServer.cpp:217 msgid "building" msgstr "" #: daemon/HTTPServer.cpp:218 msgid "failed" msgstr "" #: daemon/HTTPServer.cpp:219 msgid "expiring" msgstr "" #: daemon/HTTPServer.cpp:220 msgid "established" msgstr "" #: daemon/HTTPServer.cpp:221 msgid "unknown" msgstr "" #: daemon/HTTPServer.cpp:223 msgid "exploratory" msgstr "" #: daemon/HTTPServer.cpp:259 msgid "i2pd webconsole" msgstr "" #: daemon/HTTPServer.cpp:262 msgid "Main page" msgstr "" #: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 msgid "Router commands" msgstr "" #: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 #: daemon/HTTPServer.cpp:460 msgid "Local Destinations" msgstr "" #: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:418 #: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 #: daemon/HTTPServer.cpp:641 daemon/HTTPServer.cpp:684 #: daemon/HTTPServer.cpp:688 msgid "LeaseSets" msgstr "" #: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 msgid "Tunnels" msgstr "" #: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 #: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 msgid "Transit Tunnels" msgstr "" #: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 msgid "Transports" msgstr "" #: daemon/HTTPServer.cpp:271 msgid "I2P tunnels" msgstr "" #: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 #: daemon/HTTPServer.cpp:924 msgid "SAM sessions" msgstr "" #: daemon/HTTPServer.cpp:289 daemon/HTTPServer.cpp:1306 #: daemon/HTTPServer.cpp:1309 daemon/HTTPServer.cpp:1312 #: daemon/HTTPServer.cpp:1326 daemon/HTTPServer.cpp:1371 #: daemon/HTTPServer.cpp:1374 daemon/HTTPServer.cpp:1377 msgid "ERROR" msgstr "" #: daemon/HTTPServer.cpp:296 msgid "OK" msgstr "" #: daemon/HTTPServer.cpp:297 msgid "Testing" msgstr "" #: daemon/HTTPServer.cpp:298 msgid "Firewalled" msgstr "" #: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 #: daemon/HTTPServer.cpp:406 msgid "Unknown" msgstr "" #: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 #: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 #: daemon/HTTPServer.cpp:991 msgid "Proxy" msgstr "" #: daemon/HTTPServer.cpp:301 msgid "Mesh" msgstr "" #: daemon/HTTPServer.cpp:304 msgid "Error" msgstr "" #: daemon/HTTPServer.cpp:308 msgid "Clock skew" msgstr "" #: daemon/HTTPServer.cpp:311 msgid "Offline" msgstr "" #: daemon/HTTPServer.cpp:314 msgid "Symmetric NAT" msgstr "" #: daemon/HTTPServer.cpp:326 msgid "Uptime" msgstr "" #: daemon/HTTPServer.cpp:329 msgid "Network status" msgstr "" #: daemon/HTTPServer.cpp:334 msgid "Network status v6" msgstr "" #: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 msgid "Stopping in" msgstr "" #: daemon/HTTPServer.cpp:354 msgid "Family" msgstr "" #: daemon/HTTPServer.cpp:355 msgid "Tunnel creation success rate" msgstr "" #: daemon/HTTPServer.cpp:356 msgid "Received" msgstr "" #. tr: Kibibit/s #: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 #: daemon/HTTPServer.cpp:364 msgid "KiB/s" msgstr "" #: daemon/HTTPServer.cpp:359 msgid "Sent" msgstr "" #: daemon/HTTPServer.cpp:362 msgid "Transit" msgstr "" #: daemon/HTTPServer.cpp:365 msgid "Data path" msgstr "" #: daemon/HTTPServer.cpp:368 msgid "Hidden content. Press on text to see." msgstr "" #: daemon/HTTPServer.cpp:371 msgid "Router Ident" msgstr "" #: daemon/HTTPServer.cpp:373 msgid "Router Family" msgstr "" #: daemon/HTTPServer.cpp:374 msgid "Router Caps" msgstr "" #: daemon/HTTPServer.cpp:375 msgid "Version" msgstr "" #: daemon/HTTPServer.cpp:376 msgid "Our external address" msgstr "" #: daemon/HTTPServer.cpp:384 msgid "supported" msgstr "" #: daemon/HTTPServer.cpp:416 msgid "Routers" msgstr "" #: daemon/HTTPServer.cpp:417 msgid "Floodfills" msgstr "" #: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 msgid "Client Tunnels" msgstr "" #: daemon/HTTPServer.cpp:434 msgid "Services" msgstr "" #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 #: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Enabled" msgstr "" #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 #: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Disabled" msgstr "" #: daemon/HTTPServer.cpp:483 msgid "Encrypted B33 address" msgstr "" #: daemon/HTTPServer.cpp:492 msgid "Address registration line" msgstr "" #: daemon/HTTPServer.cpp:497 msgid "Domain" msgstr "" #: daemon/HTTPServer.cpp:498 msgid "Generate" msgstr "" #: daemon/HTTPServer.cpp:499 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" #: daemon/HTTPServer.cpp:505 msgid "Address" msgstr "" #: daemon/HTTPServer.cpp:505 msgid "Type" msgstr "" #: daemon/HTTPServer.cpp:505 msgid "EncType" msgstr "" #: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds #: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 #: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 msgid "ms" msgstr "" #: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 msgid "Outbound tunnels" msgstr "" #: daemon/HTTPServer.cpp:537 msgid "Tags" msgstr "" #: daemon/HTTPServer.cpp:537 msgid "Incoming" msgstr "" #: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 msgid "Outgoing" msgstr "" #: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 msgid "Destination" msgstr "" #: daemon/HTTPServer.cpp:545 msgid "Amount" msgstr "" #: daemon/HTTPServer.cpp:552 msgid "Incoming Tags" msgstr "" #: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 msgid "Tags sessions" msgstr "" #: daemon/HTTPServer.cpp:561 msgid "Status" msgstr "" #: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 msgid "Local Destination" msgstr "" #: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 msgid "Streams" msgstr "" #: daemon/HTTPServer.cpp:602 msgid "Close stream" msgstr "" #: daemon/HTTPServer.cpp:631 msgid "I2CP session not found" msgstr "" #: daemon/HTTPServer.cpp:634 msgid "I2CP is not enabled" msgstr "" #: daemon/HTTPServer.cpp:660 msgid "Invalid" msgstr "" #: daemon/HTTPServer.cpp:663 msgid "Store type" msgstr "" #: daemon/HTTPServer.cpp:664 msgid "Expires" msgstr "" #: daemon/HTTPServer.cpp:669 msgid "Non Expired Leases" msgstr "" #: daemon/HTTPServer.cpp:672 msgid "Gateway" msgstr "" #: daemon/HTTPServer.cpp:673 msgid "TunnelID" msgstr "" #: daemon/HTTPServer.cpp:674 msgid "EndDate" msgstr "" #: daemon/HTTPServer.cpp:684 msgid "not floodfill" msgstr "" #: daemon/HTTPServer.cpp:695 msgid "Queue size" msgstr "" #: daemon/HTTPServer.cpp:726 msgid "Run peer test" msgstr "" #: daemon/HTTPServer.cpp:731 msgid "Decline transit tunnels" msgstr "" #: daemon/HTTPServer.cpp:733 msgid "Accept transit tunnels" msgstr "" #: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 msgid "Cancel graceful shutdown" msgstr "" #: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 msgid "Start graceful shutdown" msgstr "" #: daemon/HTTPServer.cpp:747 msgid "Force shutdown" msgstr "" #: daemon/HTTPServer.cpp:748 msgid "Reload external CSS styles" msgstr "" #: daemon/HTTPServer.cpp:751 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" #: daemon/HTTPServer.cpp:753 msgid "Logging level" msgstr "" #: daemon/HTTPServer.cpp:761 msgid "Transit tunnels limit" msgstr "" #: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 msgid "Change" msgstr "" #: daemon/HTTPServer.cpp:770 msgid "Change language" msgstr "" #: daemon/HTTPServer.cpp:803 msgid "no transit tunnels currently built" msgstr "" #: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 msgid "SAM disabled" msgstr "" #: daemon/HTTPServer.cpp:924 msgid "no sessions currently running" msgstr "" #: daemon/HTTPServer.cpp:937 msgid "SAM session not found" msgstr "" #: daemon/HTTPServer.cpp:942 msgid "SAM Session" msgstr "" #: daemon/HTTPServer.cpp:999 msgid "Server Tunnels" msgstr "" #: daemon/HTTPServer.cpp:1015 msgid "Client Forwards" msgstr "" #: daemon/HTTPServer.cpp:1029 msgid "Server Forwards" msgstr "" #: daemon/HTTPServer.cpp:1227 msgid "Unknown page" msgstr "" #: daemon/HTTPServer.cpp:1246 msgid "Invalid token" msgstr "" #: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 #: daemon/HTTPServer.cpp:1401 msgid "SUCCESS" msgstr "" #: daemon/HTTPServer.cpp:1304 msgid "Stream closed" msgstr "" #: daemon/HTTPServer.cpp:1306 msgid "Stream not found or already was closed" msgstr "" #: daemon/HTTPServer.cpp:1309 msgid "Destination not found" msgstr "" #: daemon/HTTPServer.cpp:1312 msgid "StreamID can't be null" msgstr "" #: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 msgid "Return to destination page" msgstr "" #: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 #: daemon/HTTPServer.cpp:1403 msgid "You will be redirected in 5 seconds" msgstr "" #: daemon/HTTPServer.cpp:1326 msgid "Transit tunnels count must not exceed 65535" msgstr "" #: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 msgid "Back to commands list" msgstr "" #: daemon/HTTPServer.cpp:1363 msgid "Register at reg.i2p" msgstr "" #: daemon/HTTPServer.cpp:1364 msgid "Description" msgstr "" #: daemon/HTTPServer.cpp:1364 msgid "A bit information about service on domain" msgstr "" #: daemon/HTTPServer.cpp:1365 msgid "Submit" msgstr "" #: daemon/HTTPServer.cpp:1371 msgid "Domain can't end with .b32.i2p" msgstr "" #: daemon/HTTPServer.cpp:1374 msgid "Domain must end with .i2p" msgstr "" #: daemon/HTTPServer.cpp:1377 msgid "Such destination is not found" msgstr "" #: daemon/HTTPServer.cpp:1397 msgid "Unknown command" msgstr "" #: daemon/HTTPServer.cpp:1401 msgid "Command accepted" msgstr "" #: libi2pd_client/HTTPProxy.cpp:157 msgid "Proxy error" msgstr "" #: libi2pd_client/HTTPProxy.cpp:165 msgid "Proxy info" msgstr "" #: libi2pd_client/HTTPProxy.cpp:173 msgid "Proxy error: Host not found" msgstr "" #: libi2pd_client/HTTPProxy.cpp:174 msgid "Remote host not found in router's addressbook" msgstr "" #: libi2pd_client/HTTPProxy.cpp:175 msgid "You may try to find this host on jump services below" msgstr "" #: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 #: libi2pd_client/HTTPProxy.cpp:322 libi2pd_client/HTTPProxy.cpp:365 msgid "Invalid request" msgstr "" #: libi2pd_client/HTTPProxy.cpp:273 msgid "Proxy unable to parse your request" msgstr "" #: libi2pd_client/HTTPProxy.cpp:288 msgid "addresshelper is not supported" msgstr "" #: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 #: libi2pd_client/HTTPProxy.cpp:385 msgid "Host" msgstr "" #: libi2pd_client/HTTPProxy.cpp:297 msgid "added to router's addressbook from helper" msgstr "" #: libi2pd_client/HTTPProxy.cpp:298 msgid "Click here to proceed:" msgstr "" #: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 msgid "Continue" msgstr "" #: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 msgid "Addresshelper found" msgstr "" #: libi2pd_client/HTTPProxy.cpp:306 msgid "already in router's addressbook" msgstr "" #: libi2pd_client/HTTPProxy.cpp:307 msgid "Click here to update record:" msgstr "" #: libi2pd_client/HTTPProxy.cpp:322 msgid "invalid request uri" msgstr "" #: libi2pd_client/HTTPProxy.cpp:365 msgid "Can't detect destination host from request" msgstr "" #: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 msgid "Outproxy failure" msgstr "" #: libi2pd_client/HTTPProxy.cpp:382 msgid "bad outproxy settings" msgstr "" #: libi2pd_client/HTTPProxy.cpp:385 msgid "not inside I2P network, but outproxy is not enabled" msgstr "" #: libi2pd_client/HTTPProxy.cpp:474 msgid "unknown outproxy url" msgstr "" #: libi2pd_client/HTTPProxy.cpp:480 msgid "cannot resolve upstream proxy" msgstr "" #: libi2pd_client/HTTPProxy.cpp:488 msgid "hostname too long" msgstr "" #: libi2pd_client/HTTPProxy.cpp:515 msgid "cannot connect to upstream socks proxy" msgstr "" #: libi2pd_client/HTTPProxy.cpp:521 msgid "Cannot negotiate with socks proxy" msgstr "" #: libi2pd_client/HTTPProxy.cpp:563 msgid "CONNECT error" msgstr "" #: libi2pd_client/HTTPProxy.cpp:563 msgid "Failed to Connect" msgstr "" #: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 msgid "socks proxy error" msgstr "" #: libi2pd_client/HTTPProxy.cpp:582 msgid "failed to send request to upstream" msgstr "" #: libi2pd_client/HTTPProxy.cpp:603 msgid "No Reply From socks proxy" msgstr "" #: libi2pd_client/HTTPProxy.cpp:610 msgid "cannot connect" msgstr "" #: libi2pd_client/HTTPProxy.cpp:610 msgid "http out proxy not implemented" msgstr "" #: libi2pd_client/HTTPProxy.cpp:611 msgid "cannot connect to upstream http proxy" msgstr "" #: libi2pd_client/HTTPProxy.cpp:644 msgid "Host is down" msgstr "" #: libi2pd_client/HTTPProxy.cpp:644 msgid "" "Can't create connection to requested host, it may be down. Please try again " "later." msgstr "" i2pd-2.39.0/contrib/i18n/README.md000066400000000000000000000012101411072525600161360ustar00rootroot00000000000000`xgettext` command for extracting translation --- ``` xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp ``` Regex for transforming gettext translations to our format: --- ``` in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? out: #{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n ``` ``` in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n out: {"$1", "$2"},\n ``` ``` in: ^#[:.](.*)$\n out: ``` ``` in: \n\n out: \n ``` i2pd-2.39.0/contrib/i2pd.conf000066400000000000000000000207151411072525600156200ustar00rootroot00000000000000## Configuration file for a typical i2pd user ## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ ## for more options you can use in this file. ## Lines that begin with "## " try to explain what's going on. Lines ## that begin with just "#" are disabled commands: you can enable them ## by removing the "#" symbol. ## Tunnels config file ## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf # tunconf = /var/lib/i2pd/tunnels.conf ## Tunnels config files path ## Use that path to store separated tunnels in different config files. ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d # tunnelsdir = /var/lib/i2pd/tunnels.d ## Path to certificates used for verifying .su3, families ## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates # certsdir = /var/lib/i2pd/certificates ## Where to write pidfile (default: i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid ## Logging configuration section ## By default logs go to stdout with level 'info' and higher ## For Windows OS by default logs go to file with level 'warn' and higher ## ## Logs destination (valid values: stdout, file, syslog) ## * stdout - print log entries to stdout ## * file - log entries to a file ## * syslog - use syslog, see man 3 syslog # log = file ## Path to logfile (default - autodetect) # logfile = /var/log/i2pd/i2pd.log ## Log messages above this level (debug, info, *warn, error, none) ## If you set it to none, logging will be disabled # loglevel = warn ## Write full CLF-formatted date and time to log (default: write only time) # logclftime = true ## Daemon mode. Router will go to background after start. Ignored on Windows # daemon = true ## Specify a family, router belongs to (default - none) # family = ## Network interface to bind to ## Updates address4/6 options if they are not set # ifname = ## You can specify different interfaces for IPv4 and IPv6 # ifname4 = # ifname6 = ## Local address to bind transport sockets to ## Overrides host option if: ## For ipv4: if ipv4 = true and nat = false ## For ipv6: if 'host' is not set or ipv4 = true # address4 = # address6 = ## External IPv4 or IPv6 address to listen for connections ## By default i2pd sets IP automatically ## Sets published NTCP2v4/SSUv4 address to 'host' value if nat = true ## Sets published NTCP2v6/SSUv6 address to 'host' value if ipv4 = false # host = 1.2.3.4 ## Port to listen for connections ## By default i2pd picks random port. You MUST pick a random number too, ## don't just uncomment this # port = 4567 ## Enable communication through ipv4 ipv4 = true ## Enable communication through ipv6 ipv6 = false ## Enable SSU transport (default = true) # ssu = true ## Bandwidth configuration ## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, ## X - unlimited ## Default is X for floodfill, L for regular node # bandwidth = L ## Max % of bandwidth limit for transit. 0-100. 100 by default # share = 100 ## Router will not accept transit tunnels, disabling transit traffic completely ## (default = false) # notransit = true ## Router will be floodfill ## Note: that mode uses much more network connections and CPU! # floodfill = true [http] ## Web Console settings ## Uncomment and set to 'false' to disable Web Console # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 7070 ## Path to web console, default "/" # webroot = / ## Uncomment following lines to enable Web Console authentication # auth = true # user = i2pd # pass = changeme ## Select webconsole language ## Currently supported english (default), afrikaans, russian, turkmen, ukrainian and uzbek languages # lang = english [httpproxy] ## Uncomment and set to 'false' to disable HTTP Proxy # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 4444 ## Optional keys file for proxy local destination # keys = http-proxy-keys.dat ## Enable address helper for adding .i2p domains with "jump URLs" (default: true) # addresshelper = true ## Address of a proxy server inside I2P, which is used to visit regular Internet # outproxy = http://false.i2p ## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. [socksproxy] ## Uncomment and set to 'false' to disable SOCKS Proxy # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 4447 ## Optional keys file for proxy local destination # keys = socks-proxy-keys.dat ## Socks outproxy. Example below is set to use Tor for all connections except i2p ## Uncomment and set to 'true' to enable using of SOCKS outproxy # outproxy.enabled = false ## Address and port of outproxy # outproxy = 127.0.0.1 # outproxyport = 9050 ## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. [sam] ## Comment or set to 'false' to disable SAM Bridge enabled = true ## Address and port service will listen on # address = 127.0.0.1 # port = 7656 [bob] ## Uncomment and set to 'true' to enable BOB command channel # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 2827 [i2cp] ## Uncomment and set to 'true' to enable I2CP protocol # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 7654 [i2pcontrol] ## Uncomment and set to 'true' to enable I2PControl protocol # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 7650 ## Authentication password. "itoopie" by default # password = itoopie [precomputation] ## Enable or disable elgamal precomputation table ## By default, enabled on i386 hosts # elgamal = true [upnp] ## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) # enabled = false ## Name i2pd appears in UPnP forwardings list (default = I2Pd) # name = I2Pd [meshnets] ## Enable connectivity over the Yggdrasil network # yggdrasil = false ## You can bind address from your Yggdrasil subnet 300::/64 ## The address must first be added to the network interface # yggaddress = [reseed] ## Options for bootstrapping into I2P network, aka reseeding ## Enable or disable reseed data verification. verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ ## Reseed URLs through the Yggdrasil, separated by comma # yggurls = http://[324:9de3:fea4:f6ac::ace]:7070/ ## Path to local reseed data file (.su3) for manual reseeding # file = /path/to/i2pseeds.su3 ## or HTTPS URL to reseed from # file = https://legit-website.com/i2pseeds.su3 ## Path to local ZIP file or HTTPS URL to reseed from # zipfile = /path/to/netDb.zip ## If you run i2pd behind a proxy server, set proxy server for reseeding here ## Should be http://address:port or socks://address:port # proxy = http://127.0.0.1:8118 ## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default # threshold = 25 [addressbook] ## AddressBook subscription URL for initial setup ## Default: reg.i2p at "mainline" I2P Network # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt ## Optional subscriptions URLs, separated by comma # subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt [limits] ## Maximum active transit sessions (default:2500) # transittunnels = 2500 ## Limit number of open file descriptors (0 - use system limit) # openfiles = 0 ## Maximum size of corefile in Kb (0 - use system limit) # coresize = 0 [trust] ## Enable explicit trust options. false by default # enabled = true ## Make direct I2P connections only to routers in specified Family. # family = MyFamily ## Make direct I2P connections only to routers specified here. Comma separated list of base64 identities. # routers = ## Should we hide our router from other routers? false by default # hidden = true [exploratory] ## Exploratory tunnels settings with default values # inbound.length = 2 # inbound.quantity = 3 # outbound.length = 2 # outbound.quantity = 3 [persist] ## Save peer profiles on disk (default: true) # profiles = true ## Save full addresses on disk (default: true) # addressbook = true [cpuext] ## Use CPU AES-NI instructions set when work with cryptography when available (default: true) # aesni = true ## Use CPU AVX instructions set when work with cryptography when available (default: true) # avx = true ## Force usage of CPU instructions set, even if they not found ## DO NOT TOUCH that option if you really don't know what are you doing! # force = false i2pd-2.39.0/contrib/i2pd.logrotate000066400000000000000000000001761411072525600166720ustar00rootroot00000000000000"/var/log/i2pd/*.log" { copytruncate daily rotate 5 compress delaycompress missingok notifempty } i2pd-2.39.0/contrib/i2pd.service000066400000000000000000000022401411072525600163240ustar00rootroot00000000000000[Unit] Description=I2P Router written in C++ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ After=network.target [Service] User=i2pd Group=i2pd RuntimeDirectory=i2pd RuntimeDirectoryMode=0700 LogsDirectory=i2pd LogsDirectoryMode=0700 Type=forking ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service ExecReload=/bin/sh -c "kill -HUP $MAINPID" PIDFile=/run/i2pd/i2pd.pid ### Uncomment, if auto restart needed #Restart=on-failure # Use SIGTERM to stop i2pd immediately. # Some cleanup processes can delay stopping, so we set 30 seconds timeout and then SIGKILL i2pd. KillSignal=SIGTERM TimeoutStopSec=30s SendSIGKILL=yes # If you have the patience waiting 10 min on restarting/stopping it, uncomment this. # i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. #KillSignal=SIGINT #TimeoutStopSec=10m # If you have problems with hanging i2pd, you can try increase this LimitNOFILE=4096 # To enable write of coredump uncomment this #LimitCORE=infinity [Install] WantedBy=multi-user.target i2pd-2.39.0/contrib/openrc/000077500000000000000000000000001411072525600153745ustar00rootroot00000000000000i2pd-2.39.0/contrib/openrc/i2pd.openrc000066400000000000000000000016041411072525600174430ustar00rootroot00000000000000#!/sbin/openrc-run pidfile="/var/run/i2pd/i2pd.pid" logfile="/var/log/i2pd/i2pd.log" mainconf="/etc/i2pd/i2pd.conf" tunconf="/etc/i2pd/tunnels.conf" tundir="/etc/i2pd/tunnels.conf.d" name="i2pd" command="/usr/sbin/i2pd" command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --tunnelsdir=$tundir --pidfile=$pidfile" description="i2p router written in C++" required_dirs="/var/lib/i2pd" required_files="$mainconf" start_stop_daemon_args="--chuid i2pd" depend() { need mountall use net after bootmisc } start_pre() { if [ -r /etc/default/i2pd ]; then . /etc/default/i2pd fi if [ "x$I2PD_ENABLED" != "xyes" ]; then ewarn "i2pd disabled in /etc/default/i2pd" exit 1 fi checkpath -f -o i2pd:adm $logfile checkpath -f -o i2pd:adm $pidfile if [ -n "$DAEMON_OPTS" ]; then command_args="$command_args $DAEMON_OPTS" fi } i2pd-2.39.0/contrib/rpm/000077500000000000000000000000001411072525600147045ustar00rootroot00000000000000i2pd-2.39.0/contrib/rpm/i2pd-git.spec000066400000000000000000000126061411072525600172040ustar00rootroot00000000000000%define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git Version: 2.39.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz %if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake %endif BuildRequires: chrpath BuildRequires: gcc-c++ BuildRequires: zlib-devel BuildRequires: boost-devel BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description C++ implementation of I2P. %prep %setup -q %build cd build %if 0%{?rhel} == 7 %cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else %cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ %if 0%{?fedora} > 29 -DBUILD_SHARED_LIBS:BOOL=OFF \ . %else -DBUILD_SHARED_LIBS:BOOL=OFF %endif %endif %if 0%{?fedora} >= 36 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif %endif %if 0%{?mageia} > 7 pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 popd %endif %if 0%{?mageia} > 7 popd %endif %install pushd build %if 0%{?fedora} >= 36 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif %endif %if 0%{?mageia} pushd build %endif chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates %pre getent group i2pd >/dev/null || %{_sbindir}/groupadd -r i2pd getent passwd i2pd >/dev/null || \ %{_sbindir}/useradd -r -g i2pd -s %{_sbindir}/nologin \ -d %{_sharedstatedir}/i2pd -c 'I2P Service' i2pd %post %systemd_post i2pd.service %preun %systemd_preun i2pd.service %postun %systemd_postun_with_restart i2pd.service %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt %doc %{_sysconfdir}/i2pd/tunnels.conf.d/README %{_sysconfdir}/logrotate.d/i2pd %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates %changelog * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 - fixed build on fedora 36 * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 * Mon Mar 15 2021 orignal - 2.37.0 - update to 2.37.0 * Mon Feb 15 2021 orignal - 2.36.0 - update to 2.36.0 * Mon Nov 30 2020 orignal - 2.35.0 - update to 2.35.0 * Tue Oct 27 2020 orignal - 2.34.0 - update to 2.34.0 * Mon Aug 24 2020 orignal - 2.33.0 - update to 2.33.0 * Tue Jun 02 2020 r4sas - 2.32.1 - update to 2.32.1 * Mon May 25 2020 r4sas - 2.32.0 - update to 2.32.0 - updated systemd service file (#1394) * Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config * Fri Apr 10 2020 orignal - 2.31.0 - update to 2.31.0 * Tue Feb 25 2020 orignal - 2.30.0 - update to 2.30.0 * Mon Oct 21 2019 orignal - 2.29.0 - update to 2.29.0 * Tue Aug 27 2019 orignal - 2.28.0 - update to 2.28.0 * Wed Jul 3 2019 orignal - 2.27.0 - update to 2.27.0 * Fri Jun 7 2019 orignal - 2.26.0 - update to 2.26.0 * Thu May 9 2019 orignal - 2.25.0 - update to 2.25.0 * Thu Mar 21 2019 orignal - 2.24.0 - update to 2.24.0 * Mon Jan 21 2019 orignal - 2.23.0 - update to 2.23.0 * Fri Nov 09 2018 r4sas - 2.22.0 - add support of tunnelsdir option * Thu Feb 01 2018 r4sas - 2.18.0 - Initial i2pd-git based on i2pd 2.18.0-1 spec i2pd-2.39.0/contrib/rpm/i2pd.spec000066400000000000000000000166351411072525600164310ustar00rootroot00000000000000Name: i2pd Version: 2.39.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake %endif BuildRequires: chrpath BuildRequires: gcc-c++ BuildRequires: zlib-devel BuildRequires: boost-devel BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description C++ implementation of I2P. %prep %setup -q %build cd build %if 0%{?rhel} == 7 %cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else %cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ %if 0%{?fedora} > 29 -DBUILD_SHARED_LIBS:BOOL=OFF \ . %else -DBUILD_SHARED_LIBS:BOOL=OFF %endif %endif %if 0%{?fedora} >= 36 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif %endif %if 0%{?mageia} > 7 pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 popd %endif %if 0%{?mageia} > 7 popd %endif %install pushd build %if 0%{?fedora} >= 36 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif %endif %if 0%{?mageia} pushd build %endif chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates %pre getent group i2pd >/dev/null || %{_sbindir}/groupadd -r i2pd getent passwd i2pd >/dev/null || \ %{_sbindir}/useradd -r -g i2pd -s %{_sbindir}/nologin \ -d %{_sharedstatedir}/i2pd -c 'I2P Service' i2pd %post %systemd_post i2pd.service %preun %systemd_preun i2pd.service %postun %systemd_postun_with_restart i2pd.service %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt %doc %{_sysconfdir}/i2pd/tunnels.conf.d/README %{_sysconfdir}/logrotate.d/i2pd %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates %changelog * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 - fixed build on fedora 36 * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 * Mon Mar 15 2021 orignal - 2.37.0 - update to 2.37.0 * Mon Feb 15 2021 orignal - 2.36.0 - update to 2.36.0 * Mon Nov 30 2020 orignal - 2.35.0 - update to 2.35.0 * Tue Oct 27 2020 orignal - 2.34.0 - update to 2.34.0 * Mon Aug 24 2020 orignal - 2.33.0 - update to 2.33.0 * Tue Jun 02 2020 r4sas - 2.32.1 - update to 2.32.1 * Mon May 25 2020 r4sas - 2.32.0 - update to 2.32.0 - updated systemd service file (#1394) * Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config * Fri Apr 10 2020 orignal - 2.31.0 - update to 2.31.0 * Tue Feb 25 2020 orignal - 2.30.0 - update to 2.30.0 * Mon Oct 21 2019 orignal - 2.29.0 - update to 2.29.0 * Tue Aug 27 2019 orignal - 2.28.0 - update to 2.28.0 * Wed Jul 3 2019 orignal - 2.27.0 - update to 2.27.0 * Fri Jun 7 2019 orignal - 2.26.0 - update to 2.26.0 * Thu May 9 2019 orignal - 2.25.0 - update to 2.25.0 * Thu Mar 21 2019 orignal - 2.24.0 - update to 2.24.0 * Mon Jan 21 2019 orignal - 2.23.0 - update to 2.23.0 * Fri Nov 09 2018 r4sas - 2.22.0 - update to 2.22.0 - add support of tunnelsdir option * Mon Oct 22 2018 orignal - 2.21.1 - update to 2.21.1 * Thu Oct 4 2018 orignal - 2.21.0 - update to 2.21.0 * Thu Aug 23 2018 orignal - 2.20.0 - update to 2.20.0 * Tue Jun 26 2018 orignal - 2.19.0 - update to 2.19.0 * Mon Feb 05 2018 r4sas - 2.18.0-2 - Fixed blocking system shutdown for 10 minutes (#1089) * Thu Feb 01 2018 r4sas - 2.18.0-1 - Added to conflicts i2pd-git package - Fixed release versioning - Fixed paths with double slashes * Tue Jan 30 2018 orignal - 2.18.0 - update to 2.18.0 * Sat Jan 27 2018 l-n-s - 2.17.0-1 - Added certificates and default configuration files - Merge i2pd with i2pd-systemd package - Fixed package changelogs to comply with guidelines * Mon Dec 04 2017 orignal - 2.17.0 - update to 2.17.0 * Mon Nov 13 2017 orignal - 2.16.0 - update to 2.16.0 * Thu Aug 17 2017 orignal - 2.15.0 - update to 2.15.0 * Thu Jun 01 2017 orignal - 2.14.0 - update to 2.14.0 * Thu Apr 06 2017 orignal - 2.13.0 - update to 2.13.0 * Tue Feb 14 2017 orignal - 2.12.0 - update to 2.12.0 * Mon Dec 19 2016 orignal - 2.11.0 - update to 2.11.0 * Thu Oct 20 2016 Anatolii Vorona - 2.10.0-3 - add support C7 - move rpm-related files to contrib folder * Sun Oct 16 2016 Oleg Girko - 2.10.0-1 - update to 2.10.0 * Sun Aug 14 2016 Oleg Girko - 2.9.0-1 - update to 2.9.0 * Sun Aug 07 2016 Oleg Girko - 2.8.0-2 - rename daemon subpackage to systemd * Sat Aug 06 2016 Oleg Girko - 2.8.0-1 - update to 2.8.0 - remove wrong rpath from i2pd binary - add daemon subpackage with systemd unit file * Sat May 21 2016 Oleg Girko - 2.7.0-1 - update to 2.7.0 * Tue Apr 05 2016 Oleg Girko - 2.6.0-1 - update to 2.6.0 * Tue Jan 26 2016 Yaroslav Sidlovsky - 2.3.0-1 - initial package for version 2.3.0 i2pd-2.39.0/contrib/subscriptions.txt000066400000000000000000000001771411072525600175630ustar00rootroot00000000000000http://reg.i2p/hosts.txt http://identiguy.i2p/hosts.txt http://stats.i2p/cgi-bin/newhosts.txt http://i2p-projekt.i2p/hosts.txt i2pd-2.39.0/contrib/tunnels.conf000066400000000000000000000011521411072525600164440ustar00rootroot00000000000000[IRC-ILITA] type = client address = 127.0.0.1 port = 6668 destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat #[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6669 #destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat #[SMTP] #type = client #address = 127.0.0.1 #port = 7659 #destination = smtp.postman.i2p #destinationport = 25 #keys = smtp-keys.dat #[POP3] #type = client #address = 127.0.0.1 #port = 7660 #destination = pop.postman.i2p #destinationport = 110 #keys = pop3-keys.dat # see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ i2pd-2.39.0/contrib/tunnels.d/000077500000000000000000000000001411072525600160205ustar00rootroot00000000000000i2pd-2.39.0/contrib/tunnels.d/IRC-Ilita.conf000066400000000000000000000002101411072525600203350ustar00rootroot00000000000000#[IRC-ILITA] #type = client #address = 127.0.0.1 #port = 6669 #destination = irc.ilita.i2p #destinationport = 6667 #keys = irc-keys.dat i2pd-2.39.0/contrib/tunnels.d/IRC-Irc2P.conf000066400000000000000000000002121411072525600202140ustar00rootroot00000000000000#[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6668 #destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat i2pd-2.39.0/contrib/tunnels.d/README000066400000000000000000000002731411072525600167020ustar00rootroot00000000000000# In that directory you can store separated config files for every tunnel. # Please read documentation for more info. # # You can find examples in /usr/share/doc/i2pd/tunnels.d directory i2pd-2.39.0/contrib/upstart/000077500000000000000000000000001411072525600156105ustar00rootroot00000000000000i2pd-2.39.0/contrib/upstart/i2pd.upstart000066400000000000000000000004301411072525600200670ustar00rootroot00000000000000description "i2p client daemon" start on runlevel [2345] stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override env LOGFILE="/var/log/i2pd/i2pd.log" expect fork exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE i2pd-2.39.0/contrib/webconsole/000077500000000000000000000000001411072525600162465ustar00rootroot00000000000000i2pd-2.39.0/contrib/webconsole/style.css000066400000000000000000000070061411072525600201230ustar00rootroot00000000000000body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; } a, .slide label { text-decoration: none; color: #894C84; } a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; } a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; padding: 0 5px; border: 1px solid #894C84; } .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; } .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; } .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; } .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; } .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; } .content { float: left; font-size: 1em; margin-left: 4em; max-width: 48em; overflow: auto; } .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; } .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; } caption { font-size: 1.5em; text-align: center; color: #894C84; } table { display: table; border-collapse: collapse; text-align: center; } table.extaddr { text-align: left; } table.services { width: 100%; } textarea { word-break: break-all; } .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis; } .slide div.slidecontent, .slide [type="checkbox"] { display: none; } .slide [type="checkbox"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; } .disabled { color: #D33F3F; } .enabled { color: #56B734; } @media screen and (max-width: 1150px) { /* adaptive style */ .wrapper { max-width: 58em; } .menu { max-width: 10em; } .content { margin-left: 2em; max-width: 42em; } } @media screen and (max-width: 980px) { body { padding: 1.5em 0 0 0; } .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px; text-align: center; } .menu a, .commands a { display: inline-block; padding: 4px; } .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%; text-align: center; } a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ } .header { margin: unset; font-size: 1.5em; } small { display: block } a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; } input, select { width: 35%; text-align: center; padding: 5px; border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; } table.extaddr { margin: auto; text-align: unset; } textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; } button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer; -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 36px; display: -webkit-inline-box; margin-top: 10px; } }i2pd-2.39.0/daemon/000077500000000000000000000000001411072525600137115ustar00rootroot00000000000000i2pd-2.39.0/daemon/Daemon.cpp000066400000000000000000000355271411072525600156340ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include "Daemon.h" #include "Config.h" #include "Log.h" #include "FS.h" #include "Base.h" #include "version.h" #include "Transports.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" #include "HTTP.h" #include "NetDb.hpp" #include "Garlic.h" #include "Streaming.h" #include "Destination.h" #include "HTTPServer.h" #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" #include "UPnP.h" #include "Timestamp.h" #include "util.h" #include "I18N.h" namespace i2p { namespace util { class Daemon_Singleton::Daemon_Singleton_Private { public: Daemon_Singleton_Private() {}; ~Daemon_Singleton_Private() {}; std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; std::unique_ptr UPnP; std::unique_ptr m_NTPSync; }; Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} Daemon_Singleton::~Daemon_Singleton() { delete &d; } bool Daemon_Singleton::IsService () const { bool service = false; #ifndef _WIN32 i2p::config::GetOption("service", service); #endif return service; } bool Daemon_Singleton::init(int argc, char* argv[]) { return init(argc, argv, nullptr); } bool Daemon_Singleton::init(int argc, char* argv[], std::shared_ptr logstream) { i2p::config::Init(); i2p::config::ParseCmdline(argc, argv); std::string config; i2p::config::GetOption("conf", config); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::DetectDataDir(datadir, IsService()); i2p::fs::Init(); datadir = i2p::fs::GetDataDir(); if (config == "") { config = i2p::fs::DataDirPath("i2pd.conf"); if (!i2p::fs::Exists (config)) { // use i2pd.conf only if exists config = ""; /* reset */ } } i2p::config::ParseConfig(config); i2p::config::Finalize(); i2p::config::GetOption("daemon", isDaemon); std::string certsdir; i2p::config::GetOption("certsdir", certsdir); i2p::fs::SetCertsDir(certsdir); certsdir = i2p::fs::GetCertsDir(); std::string logs = ""; i2p::config::GetOption("log", logs); std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); bool logclftime; i2p::config::GetOption("logclftime", logclftime); /* setup logging */ if (logclftime) i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); #ifdef WIN32_APP // Win32 app with GUI supports only logging to file logs = "file"; #else if (isDaemon && (logs == "" || logs == "stdout")) logs = "file"; #endif i2p::log::Logger().SetLogLevel(loglevel); if (logstream) { LogPrint(eLogInfo, "Log: will send messages to std::ostream"); i2p::log::Logger().SendTo (logstream); } else if (logs == "file") { if (logfile == "") logfile = i2p::fs::DataDirPath("i2pd.log"); LogPrint(eLogInfo, "Log: will send messages to ", logfile); i2p::log::Logger().SendTo (logfile); #ifndef _WIN32 } else if (logs == "syslog") { LogPrint(eLogInfo, "Log: will send messages to syslog"); i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); #endif } else { // use stdout -- default } LogPrint(eLogNone, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); LogPrint(eLogDebug, "FS: certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); bool avx; i2p::config::GetOption("cpuext.avx", avx); bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); i2p::context.Init (); bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET // manual override for meshnet ipv4 = false; ipv6 = true; #endif // ifname -> address std::string ifname; i2p::config::GetOption("ifname", ifname); if (ipv4 && i2p::config::IsDefault ("address4")) { std::string ifname4; i2p::config::GetOption("ifname4", ifname4); if (!ifname4.empty ()) i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 else if (!ifname.empty ()) i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 } if (ipv6 && i2p::config::IsDefault ("address6")) { std::string ifname6; i2p::config::GetOption("ifname6", ifname6); if (!ifname6.empty ()) i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 else if (!ifname.empty ()) i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 } bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); boost::asio::ip::address_v6 yggaddr; if (ygg) { std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); if (!yggaddress.empty ()) { yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || !i2p::util::net::IsLocalAddress (yggaddr)) { LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); ygg = false; } } else { yggaddr = i2p::util::net::GetYggdrasilAddress (); if (yggaddr.is_unspecified ()) { LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); ygg = false; } } } uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) { LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); i2p::context.UpdatePort (port); } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); i2p::context.SetSupportsMesh (ygg, yggaddr); i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { bool published; i2p::config::GetOption("ntcp2.published", published); if (published) { std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); if (!ntcp2proxy.empty ()) published = false; } if (published) { uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); if (!ntcp2port) ntcp2port = port; // use standard port i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish if (ipv6) { std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured } } else i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish } if (ygg) { i2p::context.PublishNTCP2Address (port, true, false, false, true); i2p::context.UpdateNTCP2V6Address (yggaddr); if (!ipv4 && !ipv6) i2p::context.SetStatus (eRouterStatusMesh); } bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); if (isFloodfill) { LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); } else { i2p::context.SetFloodfill (false); } /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); if (bandwidth.length () > 0) { if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') { i2p::context.SetBandwidth (bandwidth[0]); LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); } else { auto value = std::atoi(bandwidth.c_str()); if (value > 0) { i2p::context.SetBandwidth (value); LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); } else { LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } } } else if (isFloodfill) { LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2); } else { LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } int shareRatio; i2p::config::GetOption("share", shareRatio); i2p::context.SetShareRatio (shareRatio); std::string family; i2p::config::GetOption("family", family); i2p::context.SetFamily (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); bool trust; i2p::config::GetOption("trust.enabled", trust); if (trust) { LogPrint(eLogInfo, "Daemon: explicit trust enabled"); std::string fam; i2p::config::GetOption("trust.family", fam); std::string routers; i2p::config::GetOption("trust.routers", routers); bool restricted = false; if (fam.length() > 0) { std::set fams; size_t pos = 0, comma; do { comma = fam.find (',', pos); fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); pos = comma + 1; } while (comma != std::string::npos); i2p::transport::transports.RestrictRoutesToFamilies(fams); restricted = fams.size() > 0; } if (routers.length() > 0) { std::set idents; size_t pos = 0, comma; do { comma = routers.find (',', pos); i2p::data::IdentHash ident; ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); idents.insert (ident); pos = comma + 1; } while (comma != std::string::npos); LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routers"); i2p::transport::transports.RestrictRoutesToRouters(idents); restricted = idents.size() > 0; } if(!restricted) LogPrint(eLogError, "Daemon: no trusted routers of families specified"); } bool hidden; i2p::config::GetOption("trust.hidden", hidden); if (hidden) { LogPrint(eLogInfo, "Daemon: using hidden mode"); i2p::data::netdb.SetHidden(true); } std::string httpLang; i2p::config::GetOption("http.lang", httpLang); i2p::i18n::SetLanguage(httpLang); return true; } bool Daemon_Singleton::start() { i2p::log::Logger().Start(); LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); bool upnp; i2p::config::GetOption("upnp.enabled", upnp); if (upnp) { d.UPnP = std::unique_ptr(new i2p::transport::UPnP); d.UPnP->Start (); } bool nettime; i2p::config::GetOption("nettime.enabled", nettime); if (nettime) { d.m_NTPSync = std::unique_ptr(new i2p::util::NTPTimeSync); d.m_NTPSync->Start (); } bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu; i2p::config::GetOption("ssu", ssu); bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); LogPrint(eLogInfo, "Daemon: starting Transports"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); i2p::transport::transports.SetCheckReserved(checkInReserved); i2p::transport::transports.Start(ntcp2, ssu); if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); return false; } bool http; i2p::config::GetOption("http.enabled", http); if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); try { d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); } catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); } } LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); LogPrint(eLogInfo, "Daemon: starting Client"); i2p::client::context.Start (); // I2P Control Protocol bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); if (i2pcontrol) { std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); try { d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); } catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); } } return true; } bool Daemon_Singleton::stop() { LogPrint(eLogInfo, "Daemon: shutting down"); LogPrint(eLogInfo, "Daemon: stopping Client"); i2p::client::context.Stop(); LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); if (d.UPnP) { d.UPnP->Stop (); d.UPnP = nullptr; } if (d.m_NTPSync) { d.m_NTPSync->Stop (); d.m_NTPSync = nullptr; } LogPrint(eLogInfo, "Daemon: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "Daemon: stopping NetDB"); i2p::data::netdb.Stop(); if (d.httpServer) { LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); d.httpServer->Stop(); d.httpServer = nullptr; } if (d.m_I2PControlService) { LogPrint(eLogInfo, "Daemon: stopping I2PControl"); d.m_I2PControlService->Stop (); d.m_I2PControlService = nullptr; } i2p::crypto::TerminateCrypto (); i2p::log::Logger().Stop(); return true; } } } i2pd-2.39.0/daemon/Daemon.h000066400000000000000000000043231411072525600152670ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 DAEMON_H__ #define DAEMON_H__ #include #include #include namespace i2p { namespace util { class Daemon_Singleton_Private; class Daemon_Singleton { public: virtual bool init(int argc, char* argv[], std::shared_ptr logstream); virtual bool init(int argc, char* argv[]); virtual bool start(); virtual bool stop(); virtual void run () {}; bool isDaemon; bool running; protected: Daemon_Singleton(); virtual ~Daemon_Singleton(); bool IsService () const; // d-pointer for httpServer, httpProxy, etc. class Daemon_Singleton_Private; Daemon_Singleton_Private &d; }; #if defined(QT_GUI_LIB) // check if QT #define Daemon i2p::util::DaemonQT::Instance() // dummy, invoked from RunQT class DaemonQT: public i2p::util::Daemon_Singleton { public: static DaemonQT& Instance() { static DaemonQT instance; return instance; } }; #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton { public: static DaemonWin32& Instance() { static DaemonWin32 instance; return instance; } bool init(int argc, char* argv[]); bool start(); bool stop(); void run (); bool isGraceful; DaemonWin32 ():isGraceful(false) {} }; #elif (defined(ANDROID) && !defined(ANDROID_BINARY)) #define Daemon i2p::util::DaemonAndroid::Instance() // dummy, invoked from android/jni/DaemonAndroid.* class DaemonAndroid: public i2p::util::Daemon_Singleton { public: static DaemonAndroid& Instance() { static DaemonAndroid instance; return instance; } }; #else #define Daemon i2p::util::DaemonLinux::Instance() class DaemonLinux : public Daemon_Singleton { public: static DaemonLinux& Instance() { static DaemonLinux instance; return instance; } bool start(); bool stop(); void run (); private: std::string pidfile; int pidFH; public: int gracefulShutdownInterval; // in seconds }; #endif } } #endif // DAEMON_H__ i2pd-2.39.0/daemon/HTTPServer.cpp000066400000000000000000001734321411072525600163750ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include #include #include #include "Base.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "Tunnel.h" #include "Transports.h" #include "NetDb.hpp" #include "HTTP.h" #include "LeaseSet.h" #include "Destination.h" #include "RouterContext.h" #include "ClientContext.h" #include "HTTPServer.h" #include "Daemon.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" #include "I18N.h" #ifdef WIN32_APP #include "Win32App.h" #endif // For image and info #include "version.h" namespace i2p { namespace http { const std::string itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" "RU5ErkJggg=="; // Bundled style const std::string internalCSS = "\r\n"; // for external style sheet std::string externalCSS; static void LoadExtCSS () { std::stringstream s; std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); if (i2p::fs::Exists(styleFile)) { std::ifstream f(styleFile, std::ifstream::binary); s << f.rdbuf(); externalCSS = s.str(); } else if (externalCSS.length() != 0) { // clean up external style if file was removed externalCSS = ""; } } static void GetStyles (std::stringstream& s) { if (externalCSS.length() != 0) s << "\r\n"; else s << internalCSS; } const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; const char HTTP_PAGE_TRANSPORTS[] = "transports"; const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_PAGE_LEASESETS[] = "leasesets"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time) { lldiv_t divTime = lldiv(time, 1000); time_t t = divTime.quot; struct tm *tm = localtime(&t); char date[128]; snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); return date; } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " " << tr("day", "days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " " << tr("hour", "hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " " << tr("minute", "minutes", num) << ", "; seconds -= num * 60; } s << seconds << " " << tr("second", "seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); else if (numKBytes < 1024 * 1024) s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); else s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { std::string state, stateText; switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStatePending : state = "building"; break; case i2p::tunnel::eTunnelStateBuildFailed : case i2p::tunnel::eTunnelStateTestFailed : case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } if (state == "building") stateText = tr("building"); else if (state == "failed") stateText = tr("failed"); else if (state == "expiring") stateText = tr("expiring"); else if (state == "established") stateText = tr("established"); else stateText = tr("unknown"); s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; } static void SetLogLevel (const std::string& level) { if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { LogPrint(eLogError, "HTTPServer: unknown loglevel set attempted"); return; } i2p::log::Logger().Reopen (); } static void ShowPageHead (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); // Page language std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language auto it = i2p::i18n::languages.find(currLang); std::string langCode = it->second.ShortCode; s << "\r\n" "\r\n" " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ " \r\n" " \r\n" " \r\n" " Purple I2P " VERSION " Webconsole\r\n"; GetStyles(s); s << "\r\n" "\r\n" "
" << tr("i2pd webconsole") << "
\r\n" "
\r\n" "
\r\n" " " << tr("Main page") << "

\r\n" " " << tr("Router commands") << "
\r\n" " " << tr("Local Destinations") << "
\r\n"; if (i2p::context.IsFloodfill ()) s << " " << tr("LeaseSets") << "
\r\n"; s << " " << tr("Tunnels") << "
\r\n" " " << tr("Transit Tunnels") << "
\r\n" " " << tr ("Transports") << "
\r\n" " " << tr("I2P tunnels") << "
\r\n"; if (i2p::client::context.GetSAMBridge ()) s << " " << tr("SAM sessions") << "
\r\n"; s << "
\r\n" "
"; } static void ShowPageTail (std::stringstream& s) { s << "
\r\n
\r\n" "\r\n" "\r\n"; } static void ShowError(std::stringstream& s, const std::string& string) { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { switch (status) { case eRouterStatusOK: s << tr("OK"); break; case eRouterStatusTesting: s << tr("Testing"); break; case eRouterStatusFirewalled: s << tr("Firewalled"); break; case eRouterStatusUnknown: s << tr("Unknown"); break; case eRouterStatusProxy: s << tr("Proxy"); break; case eRouterStatusMesh: s << tr("Mesh"); break; case eRouterStatusError: { s << tr("Error"); switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: s << " - " << tr("Clock skew"); break; case eRouterErrorOffline: s << " - " << tr("Offline"); break; case eRouterErrorSymmetricNAT: s << " - " << tr("Symmetric NAT"); break; default: ; } break; } default: s << tr("Unknown"); } } void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) { s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; s << "" << tr("Network status") << ": "; ShowNetworkStatus (s, i2p::context.GetStatus ()); s << "
\r\n"; if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); s << "
\r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (auto remains = Daemon.gracefulShutdownInterval) { s << "" << tr("Stopping in") << ": "; ShowUptime(s, remains); s << "
\r\n"; } #elif defined(WIN32_APP) if (i2p::win32::g_GracefulShutdownEndtime != 0) { uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "" << tr("Stopping in") << ": "; ShowUptime(s, remains); s << "
\r\n"; } #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) s << ""<< tr("Family") << ": " << family << "
\r\n"; s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; s << "
"; if((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { s << "\r\n\r\n
\r\n"; } if(includeHiddenContent) { s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; s << "" << tr("Version") << ": " VERSION "
\r\n"; s << ""<< tr("Our external address") << ":" << "
\r\n\r\n"; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { s << "\r\n"; if (address->IsNTCP2 () && !address->IsPublishedNTCP2 ()) { s << "\r\n\r\n"; continue; } switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: { s << "\r\n"; break; } case i2p::data::RouterInfo::eTransportSSU: { s << "\r\n"; break; } default: s << "\r\n"; } s << "\r\n\r\n"; } s << "
NTCP2"; if (address->host.is_v6 ()) s << "v6"; s << "" << tr("supported") << "
NTCP"; if (address->IsPublishedNTCP2 ()) s << "2"; if (address->host.is_v6 ()) s << "v6"; s << "SSU"; if (address->host.is_v6 ()) s << "v6"; s << "" << tr("Unknown") << "" << address->host.to_string() << ":" << address->port << "
\r\n"; } s << "
\r\n
\r\n"; if(outputFormat == OutputFormatEnum::forQtUi) { s << "
"; } s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << " "; s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << " "; s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << " "; s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; bool sam = i2p::client::context.GetSAMBridge () ? true : false; bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "
" << tr("Services") << "
" << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
" << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
" << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
" << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
" << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
" << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
\r\n"; } } void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Local Destinations") << ":
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); s << "\r\n" << std::endl; } s << "
\r\n"; auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { s << "
I2CP "<< tr("Local Destinations") << ":
\r\n
\r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); if (dest) { auto ident = dest->GetIdentHash (); auto& name = dest->GetNickname (); s << "
[ "; s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
\r\n" << std::endl; } } s << "
\r\n"; } } static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) { s << "Base64:
\r\n
\r\n
\r\n"; if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); s << "
\r\n\r\n
\r\n"; s << blinded.ToB33 () << ".b32.i2p
\r\n"; s << "
\r\n
\r\n"; } if(dest->IsPublic()) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto base32 = dest->GetIdentHash ().ToBase32 (); s << "
\r\n\r\n
\r\n" "
\r\n" " \r\n" " \r\n" " \r\n" " " << tr("Domain") << ":\r\n\r\n" " \r\n" "
\r\n" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n
\r\n
\r\n
\r\n"; } if(dest->GetNumRemoteLeaseSets()) { s << "
\r\n\r\n
\r\n"; for(auto& it: dest->GetLeaseSets ()) s << "\r\n"; s << "
"<< tr("Address") << "" << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n
\r\n
\r\n
\r\n"; } else s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; for (auto & it : pool->GetInboundTunnels ()) { s << "
"; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
\r\n"; } s << "
\r\n"; s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; for (auto & it : pool->GetOutboundTunnels ()) { s << "
"; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
\r\n"; } } s << "
\r\n"; s << "" << tr("Tags") << "
\r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } s << "
\r\n\r\n" << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; } else s << tr("Outgoing") << ": 0
\r\n"; s << "
\r\n"; auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); if (numECIESx25519Tags > 0) { s << "ECIESx25519
\r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
\r\n"; if (!dest->GetECIESx25519Sessions ().empty ()) { std::stringstream tmp_s; uint32_t ecies_sessions = 0; for (const auto& it: dest->GetECIESx25519Sessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } s << "
\r\n\r\n" << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; } else s << tr("Tags sessions") << ": 0
\r\n"; s << "
\r\n"; } } void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { s << "" << tr("Local Destination") << ":
\r\n
\r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { ShowLeaseSetDestination (s, dest, token); // Print table with streams information s << "\r\n\r\n\r\n"; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; s << ""; s << ""; if (it->GetRecvStreamID ()) { s << ""; } else { s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n"; } s << "\r\n
" << tr("Streams") << "
StreamID"; // Stream closing button column s << "DestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetRecvStreamID () << ""; } s << "" << streamDestShort << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
"; } } void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) { s << "I2CP " << tr("Local Destination") << ":
\r\n
\r\n"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) ShowLeaseSetDestination (s, it->second->GetDestination (), 0); else ShowError(s, tr("I2CP session not found")); } else ShowError(s, tr("I2CP is not enabled")); } void ShowLeasesSets(std::stringstream& s) { if (i2p::data::netdb.GetNumLeaseSets ()) { s << "" << tr("LeaseSets") << ":
\r\n
\r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) { // create copy of lease set so we extract leases auto storeType = leaseSet->GetStoreType (); std::unique_ptr ls; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); else ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); if (!ls) return; s << "
IsExpired()) s << " expired"; // additional css class for expired s << "\">\r\n"; if (!ls->IsValid()) s << "
!! " << tr("Invalid") << " !!
\r\n"; s << "
\r\n"; s << "\r\n
\r\n"; s << "" << tr("Store type") << ": " << (int)storeType << "
\r\n"; s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
\r\n"; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) { // leases information is available auto leases = ls->GetNonExpiredLeases(); s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
\r\n"; for ( auto & l : leases ) { s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
\r\n"; s << "" << tr("TunnelID") << ": " << l->tunnelID << "
\r\n"; s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
\r\n"; } } s << "
\r\n
\r\n
\r\n"; } ); // end for each lease set } else if (!i2p::context.IsFloodfill ()) { s << "" << tr("LeaseSets") << ": " << tr("not floodfill") << ".
\r\n"; } else { s << "" << tr("LeaseSets") << ": 0
\r\n"; } } void ShowTunnels (std::stringstream& s) { s << "" << tr("Tunnels") << ":
\r\n"; s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n
\r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { s << "
"; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
\r\n"; } s << "
\r\n
\r\n"; s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { s << "
"; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "
\r\n"; } s << "
\r\n"; } static void ShowCommands (std::stringstream& s, uint32_t token) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; s << " " << tr("Run peer test") << "
\r\n"; // s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
\r\n"; else s << " " << tr("Accept transit tunnels") << "
\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else s << " " << tr("Start graceful shutdown") << "
\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else s << " " << tr("Start graceful shutdown") << "
\r\n"; #endif s << " " << tr("Force shutdown") << "

\r\n"; s << " " << tr("Reload external CSS styles") << "\r\n"; s << "
"; s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; s << "" << tr("Logging level") << "
\r\n"; s << " none \r\n"; s << " error \r\n"; s << " warn \r\n"; s << " info \r\n"; s << " debug
\r\n
\r\n"; uint16_t maxTunnels = GetMaxNumTransitTunnels (); s << "" << tr("Transit tunnels limit") << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n"; std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language s << "" << tr("Change language") << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n"; } void ShowTransitTunnels (std::stringstream& s) { if(i2p::tunnel::tunnels.CountTransitTunnels()) { s << "" << tr("Transit Tunnels") << ":
\r\n
\r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { s << "
\r\n"; if (std::dynamic_pointer_cast(it)) s << it->GetTunnelID () << " ⇒ "; else if (std::dynamic_pointer_cast(it)) s << " ⇒ " << it->GetTunnelID (); else s << " ⇒ " << it->GetTunnelID () << " ⇒ "; s << " " << it->GetNumTransmittedBytes () << "
\r\n"; } s << "
\r\n"; } else { s << "" << tr("Transit Tunnels") << ": " << tr("no transit tunnels currently built") << ".
\r\n"; } } template static void ShowNTCPTransports (std::stringstream& s, const Sessions& sessions, const std::string name) { std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; for (const auto& it: sessions ) { if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s << "
\r\n"; if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetRemoteEndpoint ().address ().to_string (); if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s << "
\r\n" << std::endl; cnt++; } if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s6 << "
\r\n"; if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << "[" << it.second->GetRemoteEndpoint ().address ().to_string () << "]"; if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s6 << "
\r\n" << std::endl; cnt6++; } } if (!tmp_s.str ().empty ()) { s << "
\r\n\r\n
" << tmp_s.str () << "
\r\n
\r\n"; } if (!tmp_s6.str ().empty ()) { s << "
\r\n\r\n
" << tmp_s6.str () << "
\r\n
\r\n"; } } void ShowTransports (std::stringstream& s) { s << "" << tr("Transports") << ":
\r\n"; auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { auto sessions = ntcp2Server->GetNTCP2Sessions (); if (!sessions.empty ()) ShowNTCPTransports (s, sessions, "NTCP2"); } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { s << "
\r\n\r\n
"; for (const auto& it: sessions) { s << "
\r\n"; auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
\r\n" << std::endl; } s << "
\r\n
\r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { s << "
\r\n\r\n
"; for (const auto& it: sessions6) { s << "
\r\n"; auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
\r\n" << std::endl; } s << "
\r\n
\r\n"; } } } void ShowSAMSessions (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { ShowError(s, tr("SAM disabled")); return; } if(sam->GetSessions ().size ()) { s << "" << tr("SAM sessions") << ":
\r\n
\r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); s << "\r\n" << std::endl; } s << "
\r\n"; } else s << "" << tr("SAM sessions") << ": " << tr("no sessions currently running") << ".
\r\n"; } void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { ShowError(s, tr("SAM disabled")); return; } auto session = sam->FindSession (id); if (!session) { ShowError(s, tr("SAM session not found")); return; } std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("SAM Session") << ":
\r\n
\r\n"; auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
\r\n"; s << "" << tr("Streams") << ":
\r\n
\r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
"; switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
\r\n"; } s << "
\r\n"; } void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto httpProxy = i2p::client::context.GetHttpProxy (); if (httpProxy) { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); s << "
"; s << "HTTP " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto socksProxy = i2p::client::context.GetSocksProxy (); if (socksProxy) { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); s << "
"; s << "SOCKS " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } s << "
\r\n"; auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); s << "
\r\n"<< std::endl; } s << "
\r\n"; } auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { s << "
\r\n" << tr("Client Forwards") << ":
\r\n
\r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } s << "
\r\n"; } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { s << "
\r\n" << tr("Server Forwards") << ":
\r\n
\r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } s << "
\r\n"; } } HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): m_Socket (socket), m_BufferLen (0), expected_host(hostname) { /* cache options */ i2p::config::GetOption("http.auth", needAuth); i2p::config::GetOption("http.user", user); i2p::config::GetOption("http.pass", pass); } void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), std::bind(&HTTPConnection::HandleReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) Terminate (ecode); return; } m_Buffer[bytes_transferred] = '\0'; m_BufferLen = bytes_transferred; RunRequest(); Receive (); } void HTTPConnection::RunRequest () { HTTPReq request; int ret = request.parse(m_Buffer); if (ret < 0) { m_Buffer[0] = '\0'; m_BufferLen = 0; return; /* error */ } if (ret == 0) return; /* need more data */ HandleRequest (request); } void HTTPConnection::Terminate (const boost::system::error_code& ecode) { if (ecode == boost::asio::error::operation_aborted) return; boost::system::error_code ignored_ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); m_Socket->close (); } bool HTTPConnection::CheckAuth (const HTTPReq & req) { /* method #1: http://user:pass@127.0.0.1:7070/ */ if (req.uri.find('@') != std::string::npos) { URL url; if (url.parse(req.uri) && url.user == user && url.pass == pass) return true; } /* method #2: 'Authorization' header sent */ auto provided = req.GetHeader ("Authorization"); if (provided.length () > 0) { std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); if (expected == provided) return true; } LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); return false; } void HTTPConnection::HandleRequest (const HTTPReq & req) { std::stringstream s; std::string content; HTTPRes res; LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); if (needAuth && !CheckAuth(req)) { res.code = 401; res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); SendReply(res, content); return; } bool strictheaders; i2p::config::GetOption("http.strictheaders", strictheaders); if (strictheaders) { std::string http_hostname; i2p::config::GetOption("http.hostname", http_hostname); std::string host = req.GetHeader("Host"); auto idx = host.find(':'); /* strip out port so it's just host */ if (idx != std::string::npos && idx > 0) { host = host.substr(0, idx); } if (!(host == expected_host || host == http_hostname)) { /* deny request as it's from a non whitelisted hostname */ res.code = 403; content = "host mismatch"; SendReply(res, content); return; } } // HTML head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); } else { ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); res.add_header("Refresh", "10"); } ShowPageTail (s); res.code = 200; content = s.str (); SendReply (res, content); } std::map HTTPConnection::m_Tokens; uint32_t HTTPConnection::CreateToken () { uint32_t token; RAND_bytes ((uint8_t *)&token, 4); token &= 0x7FFFFFFF; // clear first bit auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) { if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) it = m_Tokens.erase (it); else ++it; } m_Tokens[token] = ts; return token; } void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; std::string page(""); URL url; url.parse(req.uri); url.parse_query(params); page = params["page"]; if (page == HTTP_PAGE_TRANSPORTS) ShowTransports (s); else if (page == HTTP_PAGE_TUNNELS) ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { uint32_t token = CreateToken (); ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) ShowTransitTunnels (s); else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (page == HTTP_PAGE_LOCAL_DESTINATION) { uint32_t token = CreateToken (); ShowLocalDestination (s, params["b32"], token); } else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) ShowI2CPLocalDestination (s, params["i2cp_id"]); else if (page == HTTP_PAGE_SAM_SESSIONS) ShowSAMSessions (s); else if (page == HTTP_PAGE_SAM_SESSION) ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); else if (page == HTTP_PAGE_LEASESETS) ShowLeasesSets(s); else { res.code = 400; ShowError(s, tr("Unknown page") + ": " + page); return; } } void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; URL url; url.parse(req.uri); url.parse_query(params); std::string webroot; i2p::config::GetOption("http.webroot", webroot); std::string redirect = "5; url=" + webroot + "?page=commands"; std::string token = params["token"]; if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { ShowError(s, tr("Invalid token")); return; } std::string cmd = params["cmd"]; if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 10*60; #elif defined(WIN32_APP) i2p::win32::GracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif } else if (cmd == HTTP_COMMAND_LOGLEVEL) { std::string level = params["level"]; SetLogLevel (level); } else if (cmd == HTTP_COMMAND_KILLSTREAM) { std::string b32 = params["b32"]; uint32_t streamID = std::stoul(params["streamID"], nullptr); i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (streamID) { if (dest) { if(dest->DeleteStream (streamID)) s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
\r\n
\r\n"; else s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
\r\n
\r\n"; } else s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; } else s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; s << "" << tr("Return to destination page") << "
\r\n"; s << "

" << tr("You will be redirected in 5 seconds") << ""; redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; } else if (cmd == HTTP_COMMAND_LIMITTRANSIT) { uint32_t limit = std::stoul(params["limit"], nullptr); if (limit > 0 && limit <= 65535) SetMaxNumTransitTunnels (limit); else { s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
\r\n
\r\n"; s << "" << tr("Back to commands list") << "\r\n
\r\n"; s << "

" << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } } else if (cmd == HTTP_COMMAND_GET_REG_STRING) { std::string b32 = params["b32"]; std::string name = i2p::http::UrlDecode(params["name"]); i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { std::size_t pos; pos = name.find (".i2p"); if (pos == (name.length () - 4)) { pos = name.find (".b32.i2p"); if (pos == std::string::npos) { auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); uint8_t * signature = new uint8_t[signatureLen]; char * sig = new char[signatureLen*2]; std::stringstream out; out << name << "=" << dest->GetIdentity ()->ToBase64 (); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); sig[len] = 0; out << "#!sig=" << sig; s << "" << tr("SUCCESS") << ":
\r\n

\r\n" "\r\n
\r\n
\r\n" "" << tr("Register at reg.i2p") << ":\r\n
\r\n" "" << tr("Description") << ":\r\n\r\n" "\r\n" "
\r\n
\r\n"; delete[] signature; delete[] sig; } else s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
\r\n
\r\n"; } else s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
\r\n
\r\n"; } else s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
\r\n
\r\n"; s << "" << tr("Return to destination page") << "\r\n"; return; } else if (cmd == HTTP_COMMAND_SETLANGUAGE) { std::string lang = params["lang"]; std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); if (currLang.compare(lang) != 0) i2p::i18n::SetLanguage(lang); } else if (cmd == HTTP_COMMAND_RELOAD_CSS) { LoadExtCSS(); } else { res.code = 400; ShowError(s, tr("Unknown command") + ": " + cmd); return; } s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

\r\n"; s << "" << tr("Back to commands list") << "
\r\n"; s << "

" << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { reply.add_header("X-Frame-Options", "SAMEORIGIN"); reply.add_header("X-Content-Type-Options", "nosniff"); reply.add_header("X-XSS-Protection", "1; mode=block"); reply.add_header("Content-Type", "text/html"); reply.body = content; m_SendBuffer = reply.to_string(); boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } HTTPServer::HTTPServer (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)), m_Hostname(address) { } HTTPServer::~HTTPServer () { Stop (); } void HTTPServer::Start () { bool needAuth; i2p::config::GetOption("http.auth", needAuth); std::string user; i2p::config::GetOption("http.user", user); std::string pass; i2p::config::GetOption("http.pass", pass); /* generate pass if needed */ if (needAuth && pass == "") { uint8_t random[16]; char alnum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; pass.resize(sizeof(random)); RAND_bytes(random, sizeof(random)); for (size_t i = 0; i < sizeof(random); i++) { pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); LoadExtCSS(); } void HTTPServer::Stop () { m_IsRunning = false; m_Acceptor.close(); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread = nullptr; } } void HTTPServer::Run () { i2p::util::SetThreadName("Webconsole"); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); } } } void HTTPServer::Accept () { auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { if (ecode) { if(newSocket) newSocket->close(); LogPrint(eLogError, "HTTP Server: error handling accept ", ecode.message()); if(ecode != boost::asio::error::operation_aborted) Accept(); return; } CreateConnection(newSocket); Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) { auto conn = std::make_shared (m_Hostname, newSocket); conn->Receive (); } } // http } // i2p i2pd-2.39.0/daemon/HTTPServer.h000066400000000000000000000060631411072525600160350ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ #include #include #include #include #include #include #include #include "HTTP.h" namespace i2p { namespace http { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds class HTTPConnection: public std::enable_shared_from_this { public: HTTPConnection (std::string serverhost, std::shared_ptr socket); void Receive (); private: void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Terminate (const boost::system::error_code& ecode); void RunRequest (); bool CheckAuth (const HTTPReq & req); void HandleRequest (const HTTPReq & req); void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void SendReply (HTTPRes & res, std::string & content); uint32_t CreateToken (); private: std::shared_ptr m_Socket; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; std::string m_SendBuffer; bool needAuth; std::string user; std::string pass; std::string expected_host; static std::map m_Tokens; // token->timestamp in seconds }; class HTTPServer { public: HTTPServer (const std::string& address, int port); ~HTTPServer (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket); void CreateConnection(std::shared_ptr newSocket); private: bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; std::string m_Hostname; }; //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml enum OutputFormatEnum { forWebConsole, forQtUi }; void ShowStatus (std::stringstream& s, bool includeHiddenContent, OutputFormatEnum outputFormat); void ShowLocalDestinations (std::stringstream& s); void ShowLeasesSets(std::stringstream& s); void ShowTunnels (std::stringstream& s); void ShowTransitTunnels (std::stringstream& s); void ShowTransports (std::stringstream& s); void ShowSAMSessions (std::stringstream& s); void ShowI2PTunnels (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); void ShowSAMSession (std::stringstream& s, const std::string& id); void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id); } // http } // i2p #endif /* HTTP_SERVER_H__ */ i2pd-2.39.0/daemon/I2PControl.cpp000066400000000000000000000641611411072525600163600ustar00rootroot00000000000000#include #include #include #include // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include #include #include #include #include #include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" #include "RouterContext.h" #include "Daemon.h" #include "Tunnel.h" #include "Timestamp.h" #include "Transports.h" #include "version.h" #include "util.h" #include "ClientContext.h" #include "I2PControl.h" namespace i2p { namespace client { I2PControlService::I2PControlService (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_SSLContext (boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate / keys std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); if (i2pcp_crt.at(0) != '/') i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt); if (i2pcp_key.at(0) != '/') i2pcp_key = i2p::fs::DataDirPath(i2pcp_key); if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) { LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); } else { LogPrint(eLogDebug, "I2PControl: using cert from ", i2pcp_crt); } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem); m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterInfo m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; // NetworkSetting m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; // ClientServicesInfo m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () { Stop (); } void I2PControlService::Start () { if (!m_IsRunning) { Accept (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); } } void I2PControlService::Stop () { if (m_IsRunning) { m_IsRunning = false; m_Acceptor.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } } void I2PControlService::Run () { i2p::util::SetThreadName("I2PC"); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ()); } } } void I2PControlService::Accept () { auto newSocket = std::make_shared (m_Service, m_SSLContext); m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (ecode) { LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ()); return; } LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ()); Handshake (socket); } void I2PControlService::Handshake (std::shared_ptr socket) { socket->async_handshake(boost::asio::ssl::stream_base::server, std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket)); } void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode) { LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ()); return; } //std::this_thread::sleep_for (std::chrono::milliseconds(5)); ReadRequest (socket); } void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); socket->async_read_some ( #if defined(BOOST_ASIO_HAS_STD_ARRAY) boost::asio::buffer (*request), #else boost::asio::buffer (request->data (), request->size ()), #endif std::bind(&I2PControlService::HandleRequestReceived, this, std::placeholders::_1, std::placeholders::_2, socket, request)); } void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); return; } else { bool isHtml = !memcmp (buf->data (), "POST", 4); try { std::stringstream ss; ss.write (buf->data (), bytes_transferred); if (isHtml) { std::string header; size_t contentLength = 0; while (!ss.eof () && header != "\r") { std::getline(ss, header); auto colon = header.find (':'); if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") contentLength = std::stoi (header.substr (colon + 1)); } if (ss.eof ()) { LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read if (rem > 0) { bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); ss.write (buf->data (), bytes_transferred); } } std::ostringstream response; boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); std::string id = pt.get("id"); std::string method = pt.get("method"); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { response << "{\"id\":" << id << ",\"result\":{"; (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; } else { LogPrint (eLogWarning, "I2PControl: unknown method ", method); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; } SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); std::ostringstream response; response << "{\"id\":null,\"error\":"; response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; response << "\"jsonrpc\":\"2.0\"}"; SendResponse (socket, buf, response, isHtml); } catch (...) { LogPrint (eLogError, "I2PControl: handle request unknown exception"); } } } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const { ss << "\"" << name << "\":" << value; } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const { ss << "\"" << name << "\":"; if (value.length () > 0) ss << "\"" << value << "\""; else ss << "null"; } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const { ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const { std::ostringstream buf; boost::property_tree::write_json (buf, value, false); ss << "\"" << name << "\":" << buf.str(); } void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { size_t len = response.str ().length (), offset = 0; if (isHtml) { std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; header << "Content-Type: application/json\r\n"; header << "Date: "; auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); header.imbue(std::locale (header.getloc(), facet)); header << boost::posix_time::second_clock::local_time() << "\r\n"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); } memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); } void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); } } // handlers void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { int api = params.get ("API"); auto password = params.get ("Password"); LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password); if (password != m_Password) { LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password); return; } InsertParam (results, "API", api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { auto echo = params.get ("Echo"); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); InsertParam (results, "Result", echo); } // I2PControl void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto& it: params) { LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) { (this->*(it1->second))(it.second.data ()); InsertParam (results, it.first, ""); } else LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first); } } void I2PControlService::PasswordHandler (const std::string& value) { LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } // RouterInfo void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { bool first = true; for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { if (!first) results << ","; else first = false; (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); } } void I2PControlService::UptimeHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL)); } void I2PControlService::VersionHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.version", VERSION); } void I2PControlService::StatusHandler (std::ostringstream& results) { auto dest = i2p::client::context.GetSharedLocalDestination (); InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); } void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); } void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); } void I2PControlService::NetStatusHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); } void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) { int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); InsertParam (results, "i2p.router.net.tunnels.participating", transit); } void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) { int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); InsertParam (results, "i2p.router.net.tunnels.successrate", rate); } void I2PControlService::InboundBandwidth1S (std::ostringstream& results) { double bw = i2p::transport::transports.GetInBandwidth (); InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); } void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) { double bw = i2p::transport::transports.GetOutBandwidth (); InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); } void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) { InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); } void I2PControlService::NetTotalSentBytes (std::ostringstream& results) { InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); } // RouterManager void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { if (it != params.begin ()) results << ","; LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); auto it1 = m_RouterManagerHandlers.find (it->first); if (it1 != m_RouterManagerHandlers.end ()) { (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); } } void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Shutdown requested"); InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { Daemon.running = 0; }); } void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results) { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains"); InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { Daemon.running = 0; }); } void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Reseed requested"); InsertParam (results, "Reseed", ""); i2p::data::netdb.Reseed (); } // network setting void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); auto it1 = m_NetworkSettingHandlers.find (it->first); if (it1 != m_NetworkSettingHandlers.end ()) { if (it != params.begin ()) results << ","; (this->*(it1->second))(it->second.data (), results); } else LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); } } void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) { if (value != "null") i2p::context.SetBandwidth (std::atoi(value.c_str())); int bw = i2p::context.GetBandwidthLimit(); InsertParam (results, "i2p.router.net.bw.in", bw); } void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) { if (value != "null") i2p::context.SetBandwidth (std::atoi(value.c_str())); int bw = i2p::context.GetBandwidthLimit(); InsertParam (results, "i2p.router.net.bw.out", bw); } // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { FILE *f = NULL; EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); RSA_generate_key_ex (rsa, 4096, e, NULL); BN_free (e); if (rsa) { EVP_PKEY_assign_RSA (pkey, rsa); X509 * x509 = X509_new (); ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); X509_gmtime_adj (X509_getm_notBefore (x509), 0); X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration X509_set_pubkey (x509, pkey); // public key X509_NAME * name = X509_get_subject_name (x509); X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves X509_sign (x509, pkey, EVP_sha1 ()); // sign // save cert if ((f = fopen (crt_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path); PEM_write_X509 (f, x509); fclose (f); } else { LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno)); } // save key if ((f = fopen (key_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno)); } X509_free (x509); } else { LogPrint (eLogError, "I2PControl: can't create RSA key for certificate"); } EVP_PKEY_free (pkey); } // ClientServicesInfo void I2PControlService::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); auto it1 = m_ClientServicesInfoHandlers.find (it->first); if (it1 != m_ClientServicesInfoHandlers.end ()) { if (it != params.begin ()) results << ","; (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); } } void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; boost::property_tree::ptree client_tunnels, server_tunnels; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); boost::property_tree::ptree ct; ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); client_tunnels.add_child(it.second->GetName (), ct); } auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); boost::property_tree::ptree st; st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); st.put("port", it.second->GetLocalPort ()); server_tunnels.add_child(it.second->GetName (), st); } } auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); boost::property_tree::ptree ct; ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); client_tunnels.add_child(it.second->GetName (), ct); } } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); boost::property_tree::ptree st; st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); server_tunnels.add_child(it.second->GetName (), st); } } pt.add_child("client", client_tunnels); pt.add_child("server", server_tunnels); InsertParam (results, "I2PTunnel", pt); } void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; auto httpProxy = i2p::client::context.GetHttpProxy (); if (httpProxy) { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); pt.put("enabled", true); pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); } else pt.put("enabled", false); InsertParam (results, "HTTPProxy", pt); } void I2PControlService::SOCKSInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; auto socksProxy = i2p::client::context.GetSocksProxy (); if (socksProxy) { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); pt.put("enabled", true); pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); } else pt.put("enabled", false); InsertParam (results, "SOCKS", pt); } void I2PControlService::SAMInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; auto sam = i2p::client::context.GetSAMBridge (); if (sam) { pt.put("enabled", true); boost::property_tree::ptree sam_sessions; for (auto& it: sam->GetSessions ()) { boost::property_tree::ptree sam_session, sam_session_sockets; auto& name = it.second->GetLocalDestination ()->GetNickname (); auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); sam_session.put("name", name); sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); for (const auto& socket: sam->ListSockets(it.first)) { boost::property_tree::ptree stream; stream.put("type", socket->GetSocketType ()); stream.put("peer", socket->GetSocket ().remote_endpoint()); sam_session_sockets.push_back(std::make_pair("", stream)); } sam_session.add_child("sockets", sam_session_sockets); sam_sessions.add_child(it.first, sam_session); } pt.add_child("sessions", sam_sessions); } else pt.put("enabled", false); InsertParam (results, "SAM", pt); } void I2PControlService::BOBInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; auto bob = i2p::client::context.GetBOBCommandChannel (); if (bob) { /* TODO more info */ pt.put("enabled", true); } else pt.put("enabled", false); InsertParam (results, "BOB", pt); } void I2PControlService::I2CPInfoHandler (std::ostringstream& results) { boost::property_tree::ptree pt; auto i2cp = i2p::client::context.GetI2CPServer (); if (i2cp) { /* TODO more info */ pt.put("enabled", true); } else pt.put("enabled", false); InsertParam (results, "I2CP", pt); } } } i2pd-2.39.0/daemon/I2PControl.h000066400000000000000000000137371411072525600160300ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 I2P_CONTROL_H__ #define I2P_CONTROL_H__ #include #include #include #include #include #include #include #include #include #include #include namespace i2p { namespace client { const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; class I2PControlService { typedef boost::asio::ssl::stream ssl_socket; public: I2PControlService (const std::string& address, int port); ~I2PControlService (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void Handshake (std::shared_ptr socket); void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); private: void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const; void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); void UptimeHandler (std::ostringstream& results); void VersionHandler (std::ostringstream& results); void StatusHandler (std::ostringstream& results); void NetDbKnownPeersHandler (std::ostringstream& results); void NetDbActivePeersHandler (std::ostringstream& results); void NetStatusHandler (std::ostringstream& results); void TunnelsParticipatingHandler (std::ostringstream& results); void TunnelsSuccessRateHandler (std::ostringstream& results); void InboundBandwidth1S (std::ostringstream& results); void OutboundBandwidth1S (std::ostringstream& results); void NetTotalReceivedBytes (std::ostringstream& results); void NetTotalSentBytes (std::ostringstream& results); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); // NetworkSetting typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); // ClientServicesInfo typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results); void I2PTunnelInfoHandler (std::ostringstream& results); void HTTPProxyInfoHandler (std::ostringstream& results); void SOCKSInfoHandler (std::ostringstream& results); void SAMInfoHandler (std::ostringstream& results); void BOBInfoHandler (std::ostringstream& results); void I2CPInfoHandler (std::ostringstream& results); private: std::string m_Password; bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; std::map m_MethodHandlers; std::map m_I2PControlHandlers; std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; std::map m_NetworkSettingHandlers; std::map m_ClientServicesInfoHandlers; }; } } #endif i2pd-2.39.0/daemon/UPnP.cpp000066400000000000000000000160041411072525600152400ustar00rootroot00000000000000#ifdef USE_UPNP #include #include #include #include #include "Log.h" #include "RouterContext.h" #include "UPnP.h" #include "NetDb.hpp" #include "util.h" #include "RouterInfo.h" #include "Config.h" #include #include namespace i2p { namespace transport { UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) { } void UPnP::Stop () { if (m_IsRunning) { LogPrint(eLogInfo, "UPnP: stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread.reset (nullptr); } CloseMapping (); Close (); } } void UPnP::Start() { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: starting"); m_Service.post (std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum } UPnP::~UPnP () { Stop (); } void UPnP::Run () { i2p::util::SetThreadName("UPnP"); while (m_IsRunning) { try { m_Service.run (); // Discover failed break; // terminate the thread } catch (std::exception& ex) { LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); PortMapping (); } } } void UPnP::Discover () { bool isError; int err; #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNPDISCOVER_SUCCESS; #if (MINIUPNPC_API_VERSION >= 14) m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); #else m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, &err); #endif isError = err != UPNPDISCOVER_SUCCESS; #else // MINIUPNPC_API_VERSION >= 8 err = 0; m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); isError = m_Devlist == NULL; #endif // MINIUPNPC_API_VERSION >= 8 { // notify starting thread std::unique_lock l(m_StartedMutex); m_Started.notify_all (); } if (isError) { LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); return; } err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); m_upnpUrlsInitialized=err!=0; if (err == UPNP_IGD_VALID_CONNECTED) { err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(err != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: unable to get external address: error ", err); return; } else { LogPrint (eLogError, "UPnP: found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); return; } } } else { LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); return; } // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); // port mapping PortMapping (); } int UPnP::CheckMapping (const char* port, const char* type) { int err = UPNPCOMMAND_SUCCESS; #if (MINIUPNPC_API_VERSION >= 10) err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL, NULL); #elif ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL); #else err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL); #endif return err; } void UPnP::PortMapping () { const auto& a = context.GetRouterInfo().GetAddresses(); for (const auto& address : a) { if (!address->host.is_v6 () && address->port) TryPortMapping (address); } m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) PortMapping (); }); } void UPnP::TryPortMapping (std::shared_ptr address) { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); int err = UPNPCOMMAND_SUCCESS; // check for existing mapping err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err != UPNPCOMMAND_SUCCESS) // if mapping not found { LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); #else err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL); #endif if (err != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: port forwarding to ", m_NetworkAddr, ":", strPort, " failed: return code ", err); return; } else { LogPrint (eLogInfo, "UPnP: port successfully forwarded (", m_externalIPAddress ,":", strPort, " type ", strType, " -> ", m_NetworkAddr ,":", strPort ,")"); return; } } else { LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); return; } } void UPnP::CloseMapping () { const auto& a = context.GetRouterInfo().GetAddresses(); for (const auto& address : a) { if (!address->host.is_v6 () && address->port) CloseMapping (address); } } void UPnP::CloseMapping (std::shared_ptr address) { if(!m_upnpUrlsInitialized) { return; } std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int err = UPNPCOMMAND_SUCCESS; err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err == UPNPCOMMAND_SUCCESS) { err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); } } void UPnP::Close () { freeUPNPDevlist (m_Devlist); m_Devlist = 0; if(m_upnpUrlsInitialized){ FreeUPNPUrls (&m_upnpUrls); m_upnpUrlsInitialized=false; } } std::string UPnP::GetProto (std::shared_ptr address) { switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: return "TCP"; break; case i2p::data::RouterInfo::eTransportSSU: default: return "UDP"; } } } } #else /* USE_UPNP */ namespace i2p { namespace transport { } } #endif /* USE_UPNP */ i2pd-2.39.0/daemon/UPnP.h000066400000000000000000000037011411072525600147050ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 __UPNP_H__ #define __UPNP_H__ #ifdef USE_UPNP #include #include #include #include #include #include #include #include #include #include namespace i2p { namespace transport { const int UPNP_RESPONSE_TIMEOUT = 2000; // in milliseconds enum { UPNP_IGD_NONE = 0, UPNP_IGD_VALID_CONNECTED = 1, UPNP_IGD_VALID_NOT_CONNECTED = 2, UPNP_IGD_INVALID = 3 }; class UPnP { public: UPnP (); ~UPnP (); void Close (); void Start (); void Stop (); private: void Discover (); int CheckMapping (const char* port, const char* type); void PortMapping (); void TryPortMapping (std::shared_ptr address); void CloseMapping (); void CloseMapping (std::shared_ptr address); void Run (); std::string GetProto (std::shared_ptr address); private: bool m_IsRunning; std::unique_ptr m_Thread; std::condition_variable m_Started; std::mutex m_StartedMutex; boost::asio::io_service m_Service; boost::asio::deadline_timer m_Timer; bool m_upnpUrlsInitialized = false; struct UPNPUrls m_upnpUrls; struct IGDdatas m_upnpData; // For miniupnpc struct UPNPDev * m_Devlist = 0; char m_NetworkAddr[64]; char m_externalIPAddress[40]; }; } } #else // USE_UPNP namespace i2p { namespace transport { /* class stub */ class UPnP { public: UPnP () {}; ~UPnP () {}; void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } void Stop () {}; }; } } #endif // USE_UPNP #endif // __UPNP_H__ i2pd-2.39.0/daemon/UnixDaemon.cpp000066400000000000000000000124101411072525600164620ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 "Daemon.h" #ifndef _WIN32 #include #include #include #include #include #include #include #include "Config.h" #include "FS.h" #include "Log.h" #include "Tunnel.h" #include "RouterContext.h" #include "ClientContext.h" void handle_signal(int sig) { switch (sig) { case SIGHUP: LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening tunnel configuration..."); i2p::client::context.ReloadConfig(); break; case SIGUSR1: LogPrint(eLogInfo, "Daemon: Got SIGUSR1, reopening logs..."); i2p::log::Logger().Reopen (); break; case SIGINT: if (i2p::context.AcceptsTunnels () && !Daemon.gracefulShutdownInterval) { i2p::context.SetAcceptsTunnels (false); Daemon.gracefulShutdownInterval = 10*60; // 10 minutes LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefulShutdownInterval, " seconds"); } else Daemon.running = 0; break; case SIGABRT: case SIGTERM: Daemon.running = 0; // Exit loop break; case SIGPIPE: LogPrint(eLogInfo, "SIGPIPE received"); break; } } namespace i2p { namespace util { bool DaemonLinux::start() { if (isDaemon) { pid_t pid; pid = fork(); if (pid > 0) // parent ::exit (EXIT_SUCCESS); if (pid < 0) // error { LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); return false; } // child umask(S_IWGRP | S_IRWXO); // 0027 int sid = setsid(); if (sid < 0) { LogPrint(eLogError, "Daemon: could not create process group."); return false; } std::string d = i2p::fs::GetDataDir(); if (chdir(d.c_str()) != 0) { LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); return false; } // point std{in,out,err} descriptors to /dev/null freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); } // set proc limits struct rlimit limit; uint16_t nfiles; i2p::config::GetOption("limits.openfiles", nfiles); getrlimit(RLIMIT_NOFILE, &limit); if (nfiles == 0) { LogPrint(eLogInfo, "Daemon: using system limit in ", limit.rlim_cur, " max open files"); } else if (nfiles <= limit.rlim_max) { limit.rlim_cur = nfiles; if (setrlimit(RLIMIT_NOFILE, &limit) == 0) { LogPrint(eLogInfo, "Daemon: set max number of open files to ", nfiles, " (system limit is ", limit.rlim_max, ")"); } else { LogPrint(eLogError, "Daemon: can't set max number of open files: ", strerror(errno)); } } else { LogPrint(eLogError, "Daemon: limits.openfiles exceeds system limit: ", limit.rlim_max); } uint32_t cfsize; i2p::config::GetOption("limits.coresize", cfsize); if (cfsize) // core file size set { cfsize *= 1024; getrlimit(RLIMIT_CORE, &limit); if (cfsize <= limit.rlim_max) { limit.rlim_cur = cfsize; if (setrlimit(RLIMIT_CORE, &limit) != 0) { LogPrint(eLogError, "Daemon: can't set max size of coredump: ", strerror(errno)); } else if (cfsize == 0) { LogPrint(eLogInfo, "Daemon: coredumps disabled"); } else { LogPrint(eLogInfo, "Daemon: set max size of core files to ", cfsize / 1024, "Kb"); } } else { LogPrint(eLogError, "Daemon: limits.coresize exceeds system limit: ", limit.rlim_max); } } // Pidfile // this code is c-styled and a bit ugly, but we need fd for locking pidfile std::string pidfile; i2p::config::GetOption("pidfile", pidfile); if (pidfile == "") { pidfile = i2p::fs::DataDirPath("i2pd.pid"); } if (pidfile != "") { pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFH < 0) { LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } #ifndef ANDROID if (lockf(pidFH, F_TLOCK, 0) != 0) { LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); return false; } #endif char pid[10]; sprintf(pid, "%d\n", getpid()); ftruncate(pidFH, 0); if (write(pidFH, pid, strlen(pid)) < 0) { LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); return false; } } gracefulShutdownInterval = 0; // not specified // Signal handler struct sigaction sa; sa.sa_handler = handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGHUP, &sa, 0); sigaction(SIGUSR1, &sa, 0); sigaction(SIGABRT, &sa, 0); sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGPIPE, &sa, 0); return Daemon_Singleton::start(); } bool DaemonLinux::stop() { i2p::fs::Remove(pidfile); return Daemon_Singleton::stop(); } void DaemonLinux::run () { while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); if (gracefulShutdownInterval) { gracefulShutdownInterval--; // - 1 second if (gracefulShutdownInterval <= 0 || i2p::tunnel::tunnels.CountTransitTunnels() <= 0) { LogPrint(eLogInfo, "Graceful shutdown"); return; } } } } } } #endif i2pd-2.39.0/daemon/i2pd.cpp000066400000000000000000000014571411072525600152620ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Daemon.h" #if defined(QT_GUI_LIB) namespace i2p { namespace qt { int RunQT (int argc, char* argv[]); } } int main( int argc, char* argv[] ) { return i2p::qt::RunQT (argc, argv); } #else int main( int argc, char* argv[] ) { if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); else return EXIT_FAILURE; Daemon.stop(); } return EXIT_SUCCESS; } #endif #ifdef _WIN32 #include int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { return main(__argc, __argv); } #endif i2pd-2.39.0/debian/000077500000000000000000000000001411072525600136705ustar00rootroot00000000000000i2pd-2.39.0/debian/.gitignore000066400000000000000000000002211411072525600156530ustar00rootroot00000000000000debhelper-build-stamp files i2pd-dbg.substvars i2pd.postinst.debhelper i2pd.postrm.debhelper i2pd.prerm.debhelper i2pd.substvars i2pd/ i2pd-dbg/ i2pd-2.39.0/debian/changelog000066400000000000000000000143731411072525600155520ustar00rootroot00000000000000i2pd (2.39.0-1) unstable; urgency=medium * updated to version 2.39.0/0.9.51 -- orignal Mon, 23 Aug 2021 16:00:00 +0000 i2pd (2.38.0-1) unstable; urgency=medium * updated to version 2.38.0/0.9.50 -- orignal Mon, 17 May 2021 16:00:00 +0000 i2pd (2.37.0-1) unstable; urgency=medium * updated to version 2.37.0 -- orignal Mon, 15 Mar 2021 16:00:00 +0000 i2pd (2.36.0-1) unstable; urgency=high * updated to version 2.36.0/0.9.49 -- orignal Mon, 15 Feb 2021 16:00:00 +0000 i2pd (2.35.0-1) unstable; urgency=high * updated to version 2.35.0/0.9.48 -- orignal Mon, 30 Nov 2020 16:00:00 +0000 i2pd (2.34.0-1) unstable; urgency=medium * updated to version 2.34.0 -- orignal Tue, 27 Oct 2020 16:00:00 +0000 i2pd (2.33.0-1) unstable; urgency=medium * updated to version 2.33.0/0.9.47 -- orignal Mon, 24 Aug 2020 16:00:00 +0000 i2pd (2.32.1-1) unstable; urgency=high * updated to version 2.32.1 -- r4sas Tue, 02 Jun 2020 16:30:00 +0000 i2pd (2.32.0-1) unstable; urgency=high * updated to version 2.32.0/0.9.46 * updated systemd service file (see #1394) * updated apparmor profile (see 9318388007cff0495b4b360d0480f4fc1219a9dc) * updated logrotate config and moved it to contrib -- r4sas Mon, 25 May 2020 12:45:00 +0000 i2pd (2.31.0-1) unstable; urgency=medium * updated to version 2.31.0 -- orignal Fri, 10 Apr 2020 16:00:00 +0000 i2pd (2.30.0-1) unstable; urgency=medium * updated to version 2.30.0/0.9.45 -- orignal Tue, 25 Feb 2020 16:00:00 +0000 i2pd (2.29.0-1) unstable; urgency=medium * updated to version 2.29.0/0.9.43 -- orignal Mon, 21 Oct 2019 16:00:00 +0000 i2pd (2.28.0-1) unstable; urgency=medium * updated to version 2.28.0/0.9.42 -- orignal Tue, 27 Aug 2019 16:00:00 +0000 i2pd (2.27.0-1) unstable; urgency=medium * updated to version 2.27.0/0.9.41 -- orignal Wed, 3 Jul 2019 16:00:00 +0000 i2pd (2.26.0-1) unstable; urgency=medium * updated to version 2.26.0 -- orignal Fri, 7 Jun 2019 16:00:00 +0000 i2pd (2.25.0-1) unstable; urgency=medium * updated to version 2.25.0/0.9.40 -- orignal Thu, 9 May 2019 16:00:00 +0000 i2pd (2.24.0-1) unstable; urgency=medium * updated to version 2.24.0/0.9.39 -- orignal Thu, 21 Mar 2019 16:00:00 +0000 i2pd (2.23.0-1) unstable; urgency=medium * updated to version 2.23.0/0.9.38 * update docs, dirs, install, links files -- orignal Mon, 21 Jan 2019 16:00:00 +0000 i2pd (2.22.0-1) unstable; urgency=medium * updated to version 2.22.0/0.9.37 * update manpage (1) * update links, install files to support tunnelsdir option * renamed and updated patch (#1210) -- r4sas Fri, 09 Nov 2018 02:00:00 +0000 i2pd (2.21.1-1) unstable; urgency=medium * updated to version 2.21.1 -- orignal Mon, 22 Oct 2018 16:00:00 +0000 i2pd (2.21.0-1) unstable; urgency=medium * updated to version 2.21.0/0.9.37 -- orignal Thu, 4 Oct 2018 16:00:00 +0000 i2pd (2.20.0-1) unstable; urgency=medium * updated to version 2.20.0/0.9.36 -- orignal Thu, 23 Aug 2018 16:00:00 +0000 i2pd (2.19.0-1) unstable; urgency=medium * updated to version 2.19.0/0.9.35 * update manpage (1) * update docfiles * update build rules * fixes in systemd unit (#1089, #1142, #1154, #1155) * package now building with systemd support -- R4SAS Tue, 26 Jun 2018 16:27:45 +0000 i2pd (2.18.0-1) unstable; urgency=low * updated to version 2.18.0/0.9.33 -- orignal Tue, 30 Jan 2018 16:00:00 +0000 i2pd (2.17.0-1) unstable; urgency=low * updated to version 2.17.0/0.9.32 -- orignal Mon, 4 Dec 2017 18:00:00 +0000 i2pd (2.16.0-1) unstable; urgency=low * updated to version 2.16.0/0.9.32 -- orignal Mon, 13 Nov 2017 18:00:00 +0000 i2pd (2.15.0-1) unstable; urgency=low * updated to version 2.15.0/0.9.31 -- orignal Thu, 17 Aug 2017 18:00:00 +0000 i2pd (2.14.0-1) unstable; urgency=low * updated to version 2.14.0/0.9.30 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 1 Jun 2017 14:00:00 +0000 i2pd (2.13.0-1) unstable; urgency=low * updated to version 2.13.0/0.9.29 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 6 Apr 2017 14:00:00 +0000 i2pd (2.12.0-1) unstable; urgency=low * updated to version 2.12.0/0.9.28 -- orignal Tue, 14 Feb 2017 17:59:30 +0000 i2pd (2.11.0-1) unstable; urgency=low * updated to version 2.11.0/0.9.28 -- orignal Sun, 18 Dec 2016 21:01:30 +0000 i2pd (2.10.2-1) unstable; urgency=low * updated to version 2.10.2 -- orignal Sun, 4 Dec 2016 19:38:30 +0000 i2pd (2.10.1-1) unstable; urgency=low * updated to version 2.10.1 -- orignal Mon, 7 Nov 2016 14:18:30 +0000 i2pd (2.10.0-1) unstable; urgency=low * updated to version 2.10.0/0.9.27 * reseed.verify set to true by default -- orignal Sun, 16 Oct 2016 13:55:40 +0000 i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 * updated tune-patch * removed I2PD_PORT in i2pd.default * removed all port assigments in services files * fixed logrotate * subscriptions.txt and tunnels.conf taken from docs folder -- orignal Fri, 12 Aug 2016 14:25:40 +0000 i2pd (2.7.0-1) unstable; urgency=low * updated to version 2.7.0/0.9.25 -- hagen Wed, 18 May 2016 01:11:04 +0000 i2pd (2.2.0-2) unstable; urgency=low * updated to version 2.2.0 -- hagen Wed, 23 Dec 2015 01:29:40 +0000 i2pd (2.1.0-1) unstable; urgency=low * updated to version 2.1.0/0.9.23 * updated deps -- hagen Fri, 19 Sep 2014 05:16:12 +0000 i2pd-2.39.0/debian/compat000066400000000000000000000000021411072525600150660ustar00rootroot000000000000009 i2pd-2.39.0/debian/control000066400000000000000000000015661411072525600153030ustar00rootroot00000000000000Source: i2pd Section: net Priority: optional Maintainer: r4sas Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev Standards-Version: 3.9.8 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd Package: i2pd Architecture: any Pre-Depends: ${misc:Pre-Depends}, adduser Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, Description: Full-featured C++ implementation of I2P client. 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. i2pd-2.39.0/debian/copyright000066400000000000000000000053121411072525600156240ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * Copyright: 2013-2020 PurpleI2P License: BSD-3-clause Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen 2016-2020 R4SAS 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause Copyright (c) 2013-2017, The PurpleI2P Project . All rights reserved. . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 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. . 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. . 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. License: GPL-2+ This package 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 package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". i2pd-2.39.0/debian/docs000066400000000000000000000000121411072525600145340ustar00rootroot00000000000000README.md i2pd-2.39.0/debian/i2pd.1000066400000000000000000000104441411072525600146130ustar00rootroot00000000000000.TH "I2PD" "1" "June 20, 2018" .SH "NAME" i2pd \- Full-featured C++ implementation of I2P client. .SH "SYNOPSIS" .B i2pd [\fIOPTION1\fR] [\fIOPTION2\fR]... .SH "DESCRIPTION" i2pd is a C++ implementation of the router for the I2P anonymizing network, offering a simple layer that identity-sensitive applications can use to securely communicate. All data is wrapped with several layers of encryption, and the network is both distributed and dynamic, with no trusted parties. .PP Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. .SH "OPTIONS" .TP \fB\-\-help\fR Show available options. .TP \fB\-\-conf=\fR Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) .BR This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. .TP \fB\-\-tunconf=\fR Tunnels config file (default: \fI~/.i2pd/tunnels.conf\fR or \fI/var/lib/i2pd/tunnels.conf\fR) .TP \fB\-\-pidfile=\fR Where to write pidfile (don\'t write by default) .TP \fB\-\-log=\fR Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP \fB\-\-logfile=\fR Path to logfile (default - autodetect) .TP \fB\-\-loglevel=\fR Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR, \fInone\fR) .TP \fB\-\-logclftime\fR Log messages with full CLF-formatted date and time (\fIdisabled\fR by default) .TP \fB\-\-datadir=\fR Path to storage of i2pd data (RI, keys, peer profiles, ...) .TP \fB\-\-tunnelsdir=\fR Path to tunnels configuration files (default: \fI~/.i2pd/tunnels.d\fR or \fI/var/lib/i2pd/tunnels.d\fR) .TP \fB\-\-host=\fR The external IP address .TP \fB\-\-port=\fR The port to listen on for incoming connections .TP \fB\-\-ifname=\fR The network interface to bind to .TP \fB\-\-ifname4=\fR The network interface to bind to for IPv4 connections .TP \fB\-\-ifname6=\fR The network interface to bind to for IPv6 connections .TP \fB\-\-ipv4=\fR Enable communication through ipv6 (\fIenabled\fR by default) .TP \fB\-\-ipv6\fR Enable communication through ipv6 (\fIdisabled\fR by default) .TP \fB\-\-ntcp=\fR Enable usage of NTCP transport (\fIenabled\fR by default) .TP \fB\-\-ntcpproxy=\fR Set proxy URL for NTCP transport .TP \fB\-\-ssu=\fR Enable usage of SSU transport (\fIenabled\fR by default) .TP \fB\-\-notransit\fR Router will not accept transit tunnels at startup (\fIdisabled\fR by default) .TP \fB\-\-floodfill\fR Router will be floodfill (\fIdisabled\fR by default) .TP \fB\-\-bandwidth=\fR Bandwidth limit: integer in KBps or letter aliases: \fBL (32KBps)\fR, \fIO (256)\fR, \fIP (2048)\fR, \fIX (>9000)\fR .TP \fB\-\-share=\fR Limit of transit traffic from max bandwidth in percents. (default: 100) .TP \fB\-\-daemon\fR Router will go to background after start (\fIdisabled\fR by default) .TP \fB\-\-service\fR Router will use system folders like \fI/var/lib/i2pd\fR (\fIdisabled\fR by default) .TP \fB\-\-family=\fR Name of a family, router belongs to. .PP Switches, which enabled by default (like \fB\-\-ssu\fR, \fB\-\-ntcp\fR, etc.), can be disabled in config file. .RE See service-specific parameters in example config file \fI/usr/share/doc/i2pd/i2pd.conf.gz\fR .SH "FILES" /etc/i2pd/i2pd.conf, /etc/i2pd/tunnels.conf, /etc/default/i2pd .RS 4 i2pd configuration files (when running as a system service) .RE .PP /var/lib/i2pd/ .RS 4 i2pd profile directory (when running as a system service, see \fB\-\-service\fR above) .RE .PP $HOME/.i2pd/ .RS 4 i2pd profile directory (when running as a normal user) .SH "SEE ALSO" Documentation at Read the Docs: \m[blue]\fBhttps://i2pd\&.readthedocs\&.io/en/latest/\fR\m[] .SH "AUTHOR" This manual page was written by kytv <\m[blue]\fBkillyourtv@i2pmail\&.org\fR\m[]> for the Debian system (but may be used by others). .RE Updated by hagen <\m[blue]\fBhagen@i2pmail\&.org\fR\m[]> in 2016. .RE Updated by R4SAS <\m[blue]\fBr4sas@i2pmail\&.org\fR\m[]> in 2018. .PP Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation. .RE On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR i2pd-2.39.0/debian/i2pd.default000066400000000000000000000005441411072525600160770ustar00rootroot00000000000000# Defaults for i2pd initscript # sourced by /etc/init.d/i2pd # installed at /etc/default/i2pd by the maintainer scripts I2PD_ENABLED="yes" # Additional options that are passed to the Daemon. # see possible switches in /usr/share/doc/i2pd/configuration.md.gz DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this ulimit -n 4096 i2pd-2.39.0/debian/i2pd.init000066400000000000000000000067751411072525600154320ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: i2pd # Required-Start: $network $local_fs $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: i2p router written in C++ ### END INIT INFO # Author: hagen PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=i2pd # Introduce a short description here NAME=i2pd # Introduce the short server's name here DAEMON=/usr/sbin/$NAME # Introduce the server's location here DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME/$NAME.pid I2PCONF=/etc/$NAME/i2pd.conf TUNCONF=/etc/$NAME/tunnels.conf TUNDIR=/etc/$NAME/tunnels.conf.d LOGFILE=/var/log/$NAME/$NAME.log USER="i2pd" # Exit if the package is not installed [ -x $DAEMON ] || exit 0 [ -r /etc/default/$NAME ] && . /etc/default/$NAME . /lib/init/vars.sh . /lib/lsb/init-functions # Function that starts the daemon/service do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started if [ "x$I2PD_ENABLED" != "xyes" ]; then log_warning_msg "$NAME disabled in config" return 2 fi test -e /var/run/i2pd || install -m 755 -o i2pd -g i2pd -d /var/run/i2pd touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" test -e /var/log/i2pd || install -m 755 -o i2pd -g i2pd -d /var/log/i2pd touch "$LOGFILE" chown -f $USER:adm "$LOGFILE" start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ --service --daemon --log=file --logfile=$LOGFILE --conf=$I2PCONF --tunconf=$TUNCONF \ --tunnelsdir=$TUNDIR --pidfile=$PIDFILE $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? } # Function that stops the daemon/service do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 rm -f $PIDFILE return "$RETVAL" } # Function that sends a SIGHUP to the daemon/service do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; reload|force-reload) log_daemon_msg "Reloading $DESC" "$NAME" do_reload log_end_msg $? ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $0 {start|stop|status|restart|reload}" >&2 exit 3 ;; esac : i2pd-2.39.0/debian/i2pd.install000066400000000000000000000003641411072525600161210ustar00rootroot00000000000000i2pd usr/sbin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/ contrib/apparmor/usr.sbin.i2pd etc/apparmor.d i2pd-2.39.0/debian/i2pd.links000066400000000000000000000003741411072525600155740ustar00rootroot00000000000000etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d usr/share/i2pd/certificates var/lib/i2pd/certificates i2pd-2.39.0/debian/i2pd.logrotate000077700000000000000000000000001411072525600230762../contrib/i2pd.logrotateustar00rootroot00000000000000i2pd-2.39.0/debian/i2pd.manpages000066400000000000000000000000161411072525600162400ustar00rootroot00000000000000debian/i2pd.1 i2pd-2.39.0/debian/i2pd.service000077700000000000000000000000001411072525600234202../contrib/debian/i2pd.serviceustar00rootroot00000000000000i2pd-2.39.0/debian/i2pd.tmpfile000077700000000000000000000000001411072525600234202../contrib/debian/i2pd.tmpfileustar00rootroot00000000000000i2pd-2.39.0/debian/lintian-overrides000066400000000000000000000001031411072525600172430ustar00rootroot00000000000000# GPL come from debian/ i2pd: possible-gpl-code-linked-with-openssli2pd-2.39.0/debian/patches/000077500000000000000000000000001411072525600153175ustar00rootroot00000000000000i2pd-2.39.0/debian/patches/01-fix-1210.patch000066400000000000000000000016461411072525600177340ustar00rootroot00000000000000Description: fix #1210 Disables two options, which not presented in old systemd versions Author: r4sas Bug: https://github.com/PurpleI2P/i2pd/issues/1210 Reviewed-By: r4sas Last-Update: 2020-05-25 Index: i2pd/contrib/i2pd.service =================================================================== --- i2pd.orig/contrib/i2pd.service +++ i2pd/contrib/i2pd.service @@ -6,10 +6,10 @@ After=network.target [Service] User=i2pd Group=i2pd -RuntimeDirectory=i2pd -RuntimeDirectoryMode=0700 -LogsDirectory=i2pd -LogsDirectoryMode=0700 +#RuntimeDirectory=i2pd +#RuntimeDirectoryMode=0700 +#LogsDirectory=i2pd +#LogsDirectoryMode=0700 Type=forking ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service ExecReload=/bin/sh -c "kill -HUP $MAINPID" i2pd-2.39.0/debian/patches/02-upnp.patch000066400000000000000000000007111411072525600175400ustar00rootroot00000000000000Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas Last-Update: 2021-01-16 --- i2pd.orig/Makefile +++ i2pd/Makefile @@ -21,7 +21,7 @@ include filelist.mk USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) USE_MESHNET := $(or $(USE_MESHNET),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) DEBUG := $(or $(DEBUG),yes) ifeq ($(DEBUG),yes) i2pd-2.39.0/debian/patches/series000066400000000000000000000000401411072525600165260ustar00rootroot0000000000000001-fix-1210.patch 02-upnp.patch i2pd-2.39.0/debian/postinst000077500000000000000000000016151411072525600155040ustar00rootroot00000000000000#!/bin/sh set -e LOGFILE='/var/log/i2pd/i2pd.log' I2PDHOME='/var/lib/i2pd' I2PDUSER='i2pd' case "$1" in configure|reconfigure) # Older versions of adduser created the home directory. # The version of adduser in Debian unstable does not. # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi mkdir -p -m0750 /var/log/i2pd chown -f ${I2PDUSER}:adm /var/log/i2pd touch $LOGFILE chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" exit 0 ;; *) echo "postinst called with unknown argument '$1'" >&2 exit 0 ;; esac #DEBHELPER# exit 0 i2pd-2.39.0/debian/postrm000077500000000000000000000002571411072525600151460ustar00rootroot00000000000000#!/bin/sh set -e if [ "$1" = "purge" ]; then rm -f /etc/default/i2pd rm -rf /etc/i2pd rm -rf /var/lib/i2pd rm -rf /var/log/i2pd rm -rf /run/i2pd fi #DEBHELPER# exit 0 i2pd-2.39.0/debian/rules000077500000000000000000000003671411072525600147560ustar00rootroot00000000000000#!/usr/bin/make -f #export DH_VERBOSE=1 export DEB_BUILD_MAINT_OPTIONS = hardening=+all include /usr/share/dpkg/architecture.mk export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -O3 export DEB_LDFLAGS_MAINT_APPEND = %: dh $@ --parallel i2pd-2.39.0/debian/source/000077500000000000000000000000001411072525600151705ustar00rootroot00000000000000i2pd-2.39.0/debian/source/format000066400000000000000000000000141411072525600163760ustar00rootroot000000000000003.0 (quilt) i2pd-2.39.0/debian/watch000066400000000000000000000002551411072525600147230ustar00rootroot00000000000000version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ https://github.com/PurpleI2P/i2pd/tags \ (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate i2pd-2.39.0/docs/000077500000000000000000000000001411072525600133765ustar00rootroot00000000000000i2pd-2.39.0/docs/Doxyfile000066400000000000000000003106711411072525600151140ustar00rootroot00000000000000# Doxyfile 1.8.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "i2pd" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "load-balanced unspoofable packet switching network" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = docs/generated # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.cpp \ *.h \ *.hpp # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO # If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # compiled with the --with-libclang option. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra stylesheet files is of importance (e.g. the last # stylesheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /

(); uint8_t cost; // ignore s.read ((char *)&cost, sizeof (cost)); s.read ((char *)&address->date, sizeof (address->date)); bool isHost = false, isIntroKey = false, isStaticKey = false; char transportStyle[6]; ReadString (transportStyle, 6, s); if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 { address->transportStyle = eTransportNTCP; address->ntcp2.reset (new NTCP2Ext ()); } else if (!strcmp (transportStyle, "SSU")) { address->transportStyle = eTransportSSU; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } else address->transportStyle = eTransportUnknown; address->caps = 0; address->port = 0; uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { char key[255], value[255]; r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; if (!strcmp (key, "host")) { boost::system::error_code ecode; address->host = boost::asio::ip::address::from_string (value, ecode); if (!ecode && !address->host.is_unspecified ()) isHost = true; } else if (!strcmp (key, "port")) address->port = boost::lexical_cast(value); else if (!strcmp (key, "mtu")) { if (address->ssu) address->ssu->mtu = boost::lexical_cast(value); else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP"); } else if (!strcmp (key, "key")) { if (address->ssu) isIntroKey = (Base64ToByteStream (value, strlen (value), address->ssu->key, 32) == 32); else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } else if (!strcmp (key, "caps")) address->caps = ExtractAddressCaps (value); else if (!strcmp (key, "s")) // ntcp2 static key { Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); isStaticKey = true; } else if (!strcmp (key, "i")) // ntcp2 iv { Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); address->published = true; // presence if "i" means "published" } else if (key[0] == 'i') { // introducers if (!address->ssu) { LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; } size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: key[l-1] = 0; if (index > 9) { LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) address->ssu->introducers.resize (index + 1); Introducer& introducer = address->ssu->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(value); else if (!strcmp (key, "itag")) introducer.iTag = boost::lexical_cast(value); else if (!strcmp (key, "ikey")) Base64ToByteStream (value, strlen (value), introducer.iKey, 32); else if (!strcmp (key, "iexp")) introducer.iExp = boost::lexical_cast(value); } if (!s) return; } if (address->transportStyle == eTransportNTCP) { if (isStaticKey) { if (isHost) { if (address->host.is_v6 ()) supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); else supportedTransports |= eNTCP2V4; m_ReachableTransports |= supportedTransports; } else if (!address->published) { if (address->caps) { if (address->caps & AddressCaps::eV4) supportedTransports |= eNTCP2V4; if (address->caps & AddressCaps::eV6) supportedTransports |= eNTCP2V6; } else supportedTransports |= eNTCP2V4; // most likely, since we don't have host } } } else if (address->transportStyle == eTransportSSU) { if (isIntroKey) { if (isHost) supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6; else if (address->caps & AddressCaps::eV6) { supportedTransports |= eSSUV6; if (address->caps & AddressCaps::eV4) supportedTransports |= eSSUV4; // in additional to v6 } else supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4 if (address->ssu && !address->ssu->introducers.empty ()) { // exclude invalid introducers uint32_t ts = i2p::util::GetSecondsSinceEpoch (); int numValid = 0; for (auto& it: address->ssu->introducers) { if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; if (ts <= it.iExp && it.iPort > 0 && ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) numValid++; else it.iPort = 0; } if (numValid) m_ReachableTransports |= supportedTransports; else address->ssu->introducers.resize (0); } else if (isHost && address->port) { address->published = true; m_ReachableTransports |= supportedTransports; } } } if (supportedTransports) { addresses->push_back(address); m_SupportedTransports |= supportedTransports; } } #if (BOOST_VERSION >= 105300) boost::atomic_store (&m_Addresses, addresses); #else m_Addresses = addresses; // race condition #endif // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers // read properties uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { char key[255], value[255]; r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; m_Properties[key] = value; // extract caps if (!strcmp (key, "caps")) ExtractCaps (value); // extract version else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION)) { m_Version = 0; char * ch = value; while (*ch) { if (*ch >= '0' && *ch <= '9') { m_Version *= 10; m_Version += (*ch - '0'); } ch++; } } // check netId else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } // family else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) { m_Family = value; boost::to_lower (m_Family); } else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) { if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) { LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); m_Family.clear (); } } if (!s) return; } if (!m_SupportedTransports) SetUnreachable (true); } bool RouterInfo::IsFamily(const std::string & fam) const { return m_Family == fam; } void RouterInfo::ExtractCaps (const char * value) { const char * cap = value; while (*cap) { switch (*cap) { case CAPS_FLAG_FLOODFILL: m_Caps |= Caps::eFloodfill; break; case CAPS_FLAG_HIGH_BANDWIDTH1: case CAPS_FLAG_HIGH_BANDWIDTH2: case CAPS_FLAG_HIGH_BANDWIDTH3: m_Caps |= Caps::eHighBandwidth; break; case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH2: m_Caps |= Caps::eExtraBandwidth | 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; default: ; } cap++; } } uint8_t RouterInfo::ExtractAddressCaps (const char * value) const { uint8_t caps = 0; const char * cap = value; while (*cap) { switch (*cap) { case CAPS_FLAG_V4: caps |= AddressCaps::eV4; break; case CAPS_FLAG_V6: caps |= AddressCaps::eV6; break; case CAPS_FLAG_SSU_TESTING: caps |= AddressCaps::eSSUTesting; break; case CAPS_FLAG_SSU_INTRODUCER: caps |= AddressCaps::eSSUIntroducer; break; default: ; } cap++; } return caps; } void RouterInfo::UpdateCapsProperty () { std::string caps; if (m_Caps & eFloodfill) { if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else { if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ else caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable SetProperty ("caps", caps); } void RouterInfo::WriteToStream (std::ostream& s) const { uint64_t ts = htobe64 (m_Timestamp); s.write ((const char *)&ts, sizeof (ts)); // addresses uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); for (const auto& addr_ptr : *m_Addresses) { const Address& address = *addr_ptr; // calculate cost uint8_t cost = 0x7f; if (address.transportStyle == eTransportNTCP) cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; else if (address.transportStyle == eTransportSSU) cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS; s.write ((const char *)&cost, sizeof (cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; bool isPublished = false; if (address.transportStyle == eTransportNTCP) { if (address.IsNTCP2 ()) { WriteString ("NTCP2", s); if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) isPublished = true; else { WriteString ("caps", properties); properties << '='; std::string caps; if (address.IsV4 ()) caps += CAPS_FLAG_V4; if (address.IsV6 ()) caps += CAPS_FLAG_V6; if (caps.empty ()) caps += CAPS_FLAG_V4; WriteString (caps, properties); properties << ';'; } } else continue; // don't write NTCP address } else if (address.transportStyle == eTransportSSU) { WriteString ("SSU", s); // caps WriteString ("caps", properties); properties << '='; std::string caps; if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; if (address.host.is_v4 ()) { if (address.published) { isPublished = true; if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; } else caps += CAPS_FLAG_V4; } else if (address.host.is_v6 ()) { if (address.published) { isPublished = true; if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; } else caps += CAPS_FLAG_V6; } else { if (address.IsV4 ()) caps += CAPS_FLAG_V4; if (address.IsV6 ()) caps += CAPS_FLAG_V6; if (caps.empty ()) caps += CAPS_FLAG_V4; } WriteString (caps, properties); properties << ';'; } else WriteString ("", s); if (isPublished) { WriteString ("host", properties); properties << '='; WriteString (address.host.to_string (), properties); properties << ';'; } if (address.transportStyle == eTransportSSU) { // write introducers if any if (!address.ssu->introducers.empty()) { int i = 0; for (const auto& introducer: address.ssu->introducers) { if (introducer.iExp) // expiration is specified { WriteString ("iexp" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iExp), properties); properties << ';'; } i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("ihost" + boost::lexical_cast(i), properties); properties << '='; WriteString (introducer.iHost.to_string (), properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("ikey" + boost::lexical_cast(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 (const auto& introducer: address.ssu->introducers) { WriteString ("iport" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iPort), properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("itag" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iTag), properties); properties << ';'; i++; } } // write intro key WriteString ("key", properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); value[l] = 0; WriteString (value, properties); properties << ';'; // write mtu if (address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; WriteString (boost::lexical_cast(address.ssu->mtu), properties); properties << ';'; } } if (address.IsNTCP2 () && isPublished) { // publish i for NTCP2 WriteString ("i", properties); properties << '='; WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; } if (isPublished || address.ssu) { WriteString ("port", properties); properties << '='; WriteString (boost::lexical_cast(address.port), properties); properties << ';'; } if (address.IsNTCP2 ()) { // publish s and v for NTCP2 WriteString ("s", properties); properties << '='; WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; WriteString ("v", properties); properties << '='; WriteString ("2", 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 (const auto& p : m_Properties) { WriteString (p.first, properties); properties << '='; WriteString (p.second, properties); properties << ';'; } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); s.write (properties.str ().c_str (), properties.str ().size ()); } bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { if (!m_RouterIdentity) return false; size_t size = m_RouterIdentity->GetFullLen (); if (size + 8 > len) return false; return bufbe64toh (buf + size) > m_Timestamp; } const uint8_t * RouterInfo::LoadBuffer () { if (!m_Buffer) { if (LoadFile ()) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; } void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys) { m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp std::stringstream s; uint8_t ident[1024]; auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); auto signatureLen = privateKeys.GetPublic ()->GetSignatureLen (); s.write ((char *)ident, identLen); WriteToStream (s); m_BufferLen = s.str ().size (); if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; if (m_BufferLen + signatureLen < 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 += signatureLen; } else LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", m_BufferLen + signatureLen); } bool RouterInfo::SaveToFile (const std::string& fullPath) { m_FullPath = fullPath; if (!m_Buffer) { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); return false; } f.write ((char *)m_Buffer, m_BufferLen); return true; } size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const { uint8_t l; s.read ((char *)&l, 1); if (l < len) { s.read (str, l); if (!s) l = 0; // failed, return empty string str[l] = 0; } else { LogPrint (eLogWarning, "RouterInfo: string length ", (int)l, " exceeds buffer size ", len); s.seekg (l, std::ios::cur); // skip str[0] = 0; } return l+1; } void RouterInfo::WriteString (const std::string& str, std::ostream& s) const { uint8_t len = str.size (); s.write ((char *)&len, 1); s.write (str.c_str (), len); } void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { auto addr = std::make_shared
(); addr->host = boost::asio::ip::address::from_string (host); addr->port = port; addr->transportStyle = eTransportSSU; addr->published = true; addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = mtu; if (key) memcpy (addr->ssu->key, key, 32); else RAND_bytes (addr->ssu->key, 32); for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Addresses->push_back(std::move(addr)); } void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port, uint8_t caps) { auto addr = std::make_shared
(); addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP; addr->caps = caps; addr->date = 0; addr->ntcp2.reset (new NTCP2Ext ()); if (port) addr->published = true; memcpy (addr->ntcp2->staticKey, staticKey, 32); memcpy (addr->ntcp2->iv, iv, 16); if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; if (addr->published) m_ReachableTransports |= eNTCP2V4; } if (addr->IsV6 ()) { m_SupportedTransports |= eNTCP2V6; if (addr->published) m_ReachableTransports |= eNTCP2V6; } m_Addresses->push_back(std::move(addr)); } bool RouterInfo::AddIntroducer (const Introducer& introducer) { for (auto& addr : *m_Addresses) { if (addr->transportStyle == eTransportSSU && ((addr->IsV4 () && introducer.iHost.is_v4 ()) || (addr->IsV6 () && introducer.iHost.is_v6 ()))) { for (auto& intro: addr->ssu->introducers) if (intro.iTag == introducer.iTag) return false; // already presented addr->ssu->introducers.push_back (introducer); m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6); return true; } } return false; } bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { for (auto& addr: *m_Addresses) { if (addr->transportStyle == eTransportSSU && ((addr->IsV4 () && e.address ().is_v4 ()) || (addr->IsV6 () && e.address ().is_v6 ()))) { for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { addr->ssu->introducers.erase (it); if (addr->ssu->introducers.empty ()) m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6); return true; } } } return false; } void RouterInfo::SetCaps (uint8_t caps) { m_Caps = caps; UpdateCapsProperty (); } void RouterInfo::SetCaps (const char * caps) { SetProperty ("caps", caps); m_Caps = 0; ExtractCaps (caps); } void RouterInfo::SetProperty (const std::string& key, const std::string& value) { m_Properties[key] = value; } void RouterInfo::DeleteProperty (const std::string& key) { m_Properties.erase (key); } std::string RouterInfo::GetProperty (const std::string& key) const { auto it = m_Properties.find (key); if (it != m_Properties.end ()) return it->second; return ""; } bool RouterInfo::IsSSU (bool v4only) const { if (v4only) return m_SupportedTransports & eSSUV4; else return m_SupportedTransports & (eSSUV4 | eSSUV6); } bool RouterInfo::IsSSUV6 () const { return m_SupportedTransports & eSSUV6; } bool RouterInfo::IsNTCP2 (bool v4only) const { if (v4only) return m_SupportedTransports & eNTCP2V4; else return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6); } bool RouterInfo::IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; } bool RouterInfo::IsV6 () const { return m_SupportedTransports & (eSSUV6 | eNTCP2V6); } bool RouterInfo::IsV4 () const { return m_SupportedTransports & (eSSUV4 | eNTCP2V4); } bool RouterInfo::IsMesh () const { return m_SupportedTransports & eNTCP2V6Mesh; } void RouterInfo::EnableV6 () { if (!IsV6 ()) { uint8_t addressCaps = AddressCaps::eV6; if (IsV4 ()) addressCaps |= AddressCaps::eV4; SetUnreachableAddressesTransportCaps (addressCaps); UpdateSupportedTransports (); } } void RouterInfo::EnableV4 () { if (!IsV4 ()) { uint8_t addressCaps = AddressCaps::eV4; if (IsV6 ()) addressCaps |= AddressCaps::eV6; SetUnreachableAddressesTransportCaps (addressCaps); UpdateSupportedTransports (); } } void RouterInfo::DisableV6 () { if (IsV6 ()) { for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; if (addr->IsV6 ()) { if (addr->IsV4 ()) { addr->caps &= ~AddressCaps::eV6; ++it; } else it = m_Addresses->erase (it); } else ++it; } UpdateSupportedTransports (); } } void RouterInfo::DisableV4 () { if (IsV4 ()) { for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; if (addr->IsV4 ()) { if (addr->IsV6 ()) { addr->caps &= ~AddressCaps::eV4; ++it; } else it = m_Addresses->erase (it); } else ++it; } UpdateSupportedTransports (); } } void RouterInfo::EnableMesh () { if (!IsMesh ()) { m_SupportedTransports |= eNTCP2V6Mesh; m_ReachableTransports |= eNTCP2V6Mesh; } } void RouterInfo::DisableMesh () { if (IsMesh ()) { m_SupportedTransports &= ~eNTCP2V6Mesh; m_ReachableTransports &= ~eNTCP2V6Mesh; for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; if (i2p::util::net::IsYggdrasilAddress (addr->host)) it = m_Addresses->erase (it); else ++it; } } } std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const { return GetAddress ( [v4only](std::shared_ptr address)->bool { return (address->transportStyle == eTransportSSU) && (!v4only || address->IsV4 ()); }); } std::shared_ptr RouterInfo::GetSSUV6Address () const { return GetAddress ( [](std::shared_ptr address)->bool { return (address->transportStyle == eTransportSSU) && address->IsV6(); }); } template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator #if (BOOST_VERSION >= 105300) auto addresses = boost::atomic_load (&m_Addresses); #else auto addresses = m_Addresses; #endif for (const auto& address : *addresses) if (filter (address)) return address; return nullptr; } std::shared_ptr RouterInfo::GetNTCP2AddressWithStaticKey (const uint8_t * key) const { if (!key) return nullptr; return GetAddress ( [key](std::shared_ptr address)->bool { return address->IsNTCP2 () && !memcmp (address->ntcp2->staticKey, key, 32); }); } std::shared_ptr RouterInfo::GetPublishedNTCP2V4Address () const { return GetAddress ( [](std::shared_ptr address)->bool { return address->IsPublishedNTCP2 () && address->host.is_v4 (); }); } std::shared_ptr RouterInfo::GetPublishedNTCP2V6Address () const { return GetAddress ( [](std::shared_ptr address)->bool { return address->IsPublishedNTCP2 () && address->host.is_v6 () && !i2p::util::net::IsYggdrasilAddress (address->host); }); } std::shared_ptr RouterInfo::GetYggdrasilAddress () const { return GetAddress ( [](std::shared_ptr address)->bool { return address->IsPublishedNTCP2 () && i2p::util::net::IsYggdrasilAddress (address->host); }); } std::shared_ptr RouterInfo::GetProfile () const { if (!m_Profile) m_Profile = GetRouterProfile (GetIdentHash ()); return m_Profile; } void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); if (encryptor) encryptor->Encrypt (data, encrypted, ctx, true); } bool RouterInfo::IsEligibleFloodfill () const { // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } bool RouterInfo::IsPeerTesting (bool v4) const { if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; return (bool)GetAddress ( [v4](std::shared_ptr address)->bool { return (address->transportStyle == eTransportSSU) && address->IsPeerTesting () && ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU (); }); } bool RouterInfo::IsIntroducer (bool v4) const { if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; return (bool)GetAddress ( [v4](std::shared_ptr address)->bool { return (address->transportStyle == eTransportSSU) && address->IsIntroducer () && ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); }); } void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { for (auto& addr: *m_Addresses) { // TODO: implement SSU if (addr->transportStyle == eTransportNTCP && !addr->IsPublishedNTCP2 ()) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; } } } void RouterInfo::UpdateSupportedTransports () { m_SupportedTransports = 0; m_ReachableTransports = 0; for (const auto& addr: *m_Addresses) { uint8_t transports = 0; if (addr->transportStyle == eTransportNTCP) { if (addr->IsV4 ()) transports |= eNTCP2V4; if (addr->IsV6 ()) transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); if (addr->IsPublishedNTCP2 ()) m_ReachableTransports |= transports; } else if (addr->transportStyle == eTransportSSU) { if (addr->IsV4 ()) transports |= eSSUV4; if (addr->IsV6 ()) transports |= eSSUV6; if (addr->IsReachableSSU ()) m_ReachableTransports |= transports; } m_SupportedTransports |= transports; } } } } i2pd-2.39.0/libi2pd/RouterInfo.h000066400000000000000000000235711411072525600162500ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 ROUTER_INFO_H__ #define ROUTER_INFO_H__ #include #include #include #include #include #include #include #include #include "Identity.h" #include "Profiling.h" namespace i2p { namespace data { const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; const char ROUTER_INFO_PROPERTY_VERSION[] = "router.version"; const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; const char CAPS_FLAG_FLOODFILL = 'f'; const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; const char CAPS_FLAG_UNREACHABLE = 'U'; /* bandwidth flags */ const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */ const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ const char CAPS_FLAG_V4 = '4'; const char CAPS_FLAG_V6 = '6'; const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; const uint8_t COST_NTCP2_PUBLISHED = 3; const uint8_t COST_NTCP2_NON_PUBLISHED = 14; const uint8_t COST_SSU_DIRECT = 9; const uint8_t COST_SSU_THROUGH_INTRODUCERS = 11; const int MAX_RI_BUFFER_SIZE = 2048; // if RouterInfo exceeds 2048 we consider it as malformed, might be changed later class RouterInfo: public RoutingDestination { public: enum SupportedTransports { eNTCP2V4 = 0x01, eNTCP2V6 = 0x02, eSSUV4 = 0x04, eSSUV6 = 0x08, eNTCP2V6Mesh = 0x10 }; enum Caps { eFloodfill = 0x01, eHighBandwidth = 0x02, eExtraBandwidth = 0x04, eReachable = 0x08, eHidden = 0x10, eUnreachable = 0x20 }; enum AddressCaps { eV4 = 0x01, eV6 = 0x02, eSSUTesting = 0x04, eSSUIntroducer = 0x08 }; enum TransportStyle { eTransportUnknown = 0, eTransportNTCP, eTransportSSU }; typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { Introducer (): iPort (0), iExp (0) {}; boost::asio::ip::address iHost; int iPort; IntroKey iKey; uint32_t iTag; uint32_t iExp; }; struct SSUExt { int mtu; IntroKey key; // intro key for SSU std::vector introducers; }; struct NTCP2Ext { Tag<32> staticKey; Tag<16> iv; }; struct Address { TransportStyle transportStyle; boost::asio::ip::address host; int port; uint64_t date; uint8_t caps; bool published = false; std::unique_ptr ssu; // not null for SSU std::unique_ptr ntcp2; // not null for NTCP2 bool IsCompatible (const boost::asio::ip::address& other) const { return (IsV4 () && other.is_v4 ()) || (IsV6 () && other.is_v6 ()); } bool operator==(const Address& other) const { return transportStyle == other.transportStyle && IsNTCP2 () == other.IsNTCP2 () && host == other.host && port == other.port; } bool operator!=(const Address& other) const { return !(*this == other); } bool IsNTCP2 () const { return (bool)ntcp2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; bool IsReachableSSU () const { return (bool)ssu && (published || !ssu->introducers.empty ()); }; bool UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); }; bool IsIntroducer () const { return caps & eSSUIntroducer; }; bool IsPeerTesting () const { return caps & eSSUTesting; }; bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); }; bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); }; }; typedef std::list > Addresses; RouterInfo (); RouterInfo (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (const uint8_t * buf, int len); ~RouterInfo (); std::shared_ptr GetRouterIdentity () const { return m_RouterIdentity; }; void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2AddressWithStaticKey (const uint8_t * key) const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; std::shared_ptr GetYggdrasilAddress () const; void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only std::string GetProperty (const std::string& key) const; // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsSSU (bool v4only = true) const; bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; bool IsNTCP2V6 () const; bool IsV6 () const; bool IsV4 () const; bool IsMesh () const; void EnableV6 (); void DisableV6 (); void EnableV4 (); void DisableV4 (); void EnableMesh (); void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; bool IsReachableBy (uint8_t transports) const { return m_ReachableTransports & transports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsEligibleFloodfill () const; bool IsPeerTesting (bool v4) const; bool IsIntroducer (bool v4) const; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); void SetCaps (const char * caps); void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; const uint8_t * GetBuffer () const { return m_Buffer; }; const uint8_t * LoadBuffer (); // load if necessary int GetBufferLen () const { return m_BufferLen; }; void CreateBuffer (const PrivateKeys& privateKeys); bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; void Update (const uint8_t * buf, size_t len); void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; /** return true if we are in a router family and the signature is valid */ bool IsFamily(const std::string & fam) const; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; bool IsDestination () const { return false; }; private: bool LoadFile (); void ReadFromFile (); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); void WriteToStream (std::ostream& s) const; size_t ReadString (char* str, size_t len, std::istream& s) const; void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); uint8_t ExtractAddressCaps (const char * value) const; template std::shared_ptr GetAddress (Filter filter) const; void UpdateCapsProperty (); private: std::string m_FullPath, m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_ReachableTransports, m_Caps; int m_Version; mutable std::shared_ptr m_Profile; }; } } #endif i2pd-2.39.0/libi2pd/SSU.cpp000066400000000000000000000750301411072525600151560ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.hpp" #include "SSU.h" #include "util.h" #ifdef __linux__ #include #endif #ifdef _WIN32 #include #endif namespace i2p { namespace transport { SSUServer::SSUServer (int port): m_IsRunning(false), m_Thread (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service), m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) { } SSUServer::~SSUServer () { } void SSUServer::OpenSocket () { try { m_Socket.open (boost::asio::ip::udp::v4()); m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_Socket.bind (m_Endpoint); LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); } catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); } } void SSUServer::OpenSocketV6 () { try { 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 (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); #ifdef __linux__ if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others #if (BOOST_VERSION >= 105500) typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; #else typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; #endif m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } #endif m_SocketV6.bind (m_EndpointV6); LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); } catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); } } void SSUServer::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); if (context.SupportsV4 ()) { OpenSocket (); m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); ScheduleTermination (); ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } if (context.SupportsV6 ()) { OpenSocketV6 (); m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers } SchedulePeerTestsCleanupTimer (); } void SSUServer::Stop () { DeleteAllSessions (); m_IsRunning = false; m_TerminationTimer.cancel (); m_TerminationTimerV6.cancel (); m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimerV6.cancel (); m_Service.stop (); m_Socket.close (); m_SocketV6.close (); m_ReceiversService.stop (); m_ReceiversServiceV6.stop (); if (m_ReceiversThread) { m_ReceiversThread->join (); delete m_ReceiversThread; m_ReceiversThread = nullptr; } if (m_ReceiversThreadV6) { m_ReceiversThreadV6->join (); delete m_ReceiversThreadV6; m_ReceiversThreadV6 = nullptr; } if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void SSUServer::Run () { i2p::util::SetThreadName("SSU"); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ()); } } } void SSUServer::RunReceivers () { i2p::util::SetThreadName("SSUv4"); while (m_IsRunning) { try { m_ReceiversService.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); if (m_IsRunning) { // restart socket m_Socket.close (); OpenSocket (); Receive (); } } } } void SSUServer::RunReceiversV6 () { i2p::util::SetThreadName("SSUv6"); while (m_IsRunning) { try { m_ReceiversServiceV6.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); if (m_IsRunning) { m_SocketV6.close (); OpenSocketV6 (); ReceiveV6 (); } } } } void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress) { if (localAddress.is_v6 ()) m_EndpointV6.address (localAddress); else if (localAddress.is_v4 ()) m_Endpoint.address (localAddress); } void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { m_Relays[tag] = relay; } void SSUServer::RemoveRelay (uint32_t tag) { m_Relays.erase (tag); } std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) { auto it = m_Relays.find (tag); if (it != m_Relays.end ()) { if (it->second->GetState () == eSessionStateEstablished) return it->second; else m_Relays.erase (it); } return nullptr; } void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) { boost::system::error_code ec; if (to.protocol () == boost::asio::ip::udp::v4()) m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec); else m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec); if (ec) { LogPrint (eLogError, "SSU: send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")"); } } void SSUServer::Receive () { SSUPacket * packet = new SSUPacket (); m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::ReceiveV6 () { SSUPacket * packet = new SSUPacket (); m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode || ecode == boost::asio::error::connection_refused || ecode == boost::asio::error::connection_reset || ecode == boost::asio::error::network_unreachable || ecode == boost::asio::error::host_unreachable #ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ #endif ) // just try continue reading when received ICMP response otherwise socket can crash, // but better to find out which host were sent it and mark that router as unreachable { packet->len = bytes_transferred; std::vector packets; packets.push_back (packet); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); if (!ec) { while (moreBytes && packets.size () < 25) { packet = new SSUPacket (); packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); if (!ec) { packets.push_back (packet); moreBytes = m_Socket.available(ec); if (ec) break; } else { LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); delete packet; break; } } } m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); Receive (); } else { delete packet; if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: receive error: code ", ecode.value(), ": ", ecode.message ()); m_Socket.close (); OpenSocket (); Receive (); } } } void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode || ecode == boost::asio::error::connection_refused || ecode == boost::asio::error::connection_reset || ecode == boost::asio::error::network_unreachable || ecode == boost::asio::error::host_unreachable #ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ #endif ) // just try continue reading when received ICMP response otherwise socket can crash, // but better to find out which host were sent it and mark that router as unreachable { packet->len = bytes_transferred; std::vector packets; packets.push_back (packet); boost::system::error_code ec; size_t moreBytes = m_SocketV6.available (ec); if (!ec) { while (moreBytes && packets.size () < 25) { packet = new SSUPacket (); packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); if (!ec) { packets.push_back (packet); moreBytes = m_SocketV6.available(ec); if (ec) break; } else { LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); delete packet; break; } } } m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); ReceiveV6 (); } else { delete packet; if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); m_SocketV6.close (); OpenSocketV6 (); ReceiveV6 (); } } } void SSUServer::HandleReceivedPackets (std::vector packets, std::map > * sessions) { if (!m_IsRunning) return; std::shared_ptr session; for (auto& packet: packets) { try { if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { if (session) { session->FlushData (); session = nullptr; } auto it = sessions->find (packet->from); if (it != sessions->end ()) session = it->second; if (!session && packet->len > 0) { session = std::make_shared (*this, packet->from); session->WaitForConnect (); (*sessions)[packet->from] = session; LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } if (session) session->ProcessNextMessage (packet->buf, packet->len, packet->from); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); if (session) session->FlushData (); session = nullptr; } delete packet; } if (session) session->FlushData (); } std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const { auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; auto it = sessions.find (e); if (it != sessions.end ()) return it->second; else return nullptr; } bool SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) { auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); if (address) return CreateSession (router, address, peerTest); else LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); return false; } bool SSUServer::CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest) { if (router && address) { if (address->UsesIntroducer ()) m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread else { if (address->host.is_unspecified () || !address->port) return false; boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } } else return false; return true; } void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) { auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; auto it = sessions.find (remoteEndpoint); if (it != sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) session->SendPeerTest (); } else { // otherwise create new session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); sessions[remoteEndpoint] = session; // connect LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } } void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, std::shared_ptr address, bool peerTest) { if (router && address && address->UsesIntroducer ()) { if (address->IsV4 () && !i2p::context.SupportsV4 ()) return; if (address->IsV6 () && !i2p::context.SupportsV6 ()) return; if (!address->host.is_unspecified () && address->port) { // we rarely come here auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions; boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto it = sessions.find (remoteEndpoint); // check if session is presented already if (it != sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) session->SendPeerTest (); return; } } // create new session int numIntroducers = address->ssu->introducers.size (); if (numIntroducers > 0) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); std::shared_ptr introducerSession; const i2p::data::RouterInfo::Introducer * introducer = nullptr; // we might have a session to introducer already auto offset = rand (); for (int i = 0; i < numIntroducers; i++) { auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]); if (!intr->iPort) continue; // skip invalid introducer if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4 { if (!introducer) introducer = intr; auto it = m_Sessions.find (ep); if (it != m_Sessions.end ()) { introducerSession = it->second; break; } } if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6 { if (!introducer) introducer = intr; auto it = m_SessionsV6.find (ep); if (it != m_SessionsV6.end ()) { introducerSession = it->second; break; } } } if (!introducer) { LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented"); return; } if (introducerSession) // session found LogPrint (eLogWarning, "SSU: Session to introducer already exists"); else // create new { LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); if (introducerEndpoint.address ().is_v4 ()) m_Sessions[introducerEndpoint] = introducerSession; else if (introducerEndpoint.address ().is_v6 ()) m_SessionsV6[introducerEndpoint] = introducerSession; } if (!address->host.is_unspecified () && address->port) { // create session boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); if (address->host.is_v4 ()) m_Sessions[remoteEndpoint] = session; else if (address->host.is_v6 ()) m_SessionsV6[remoteEndpoint] = session; // introduce LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); session->WaitForIntroduction (); if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || (address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) { uint8_t buf[1]; Send (buf, 0, remoteEndpoint); // send HolePunch } } introducerSession->Introduce (*introducer, router); } else LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); } } void SSUServer::DeleteSession (std::shared_ptr session) { if (session) { session->Close (); auto& ep = session->GetRemoteEndpoint (); if (ep.address ().is_v6 ()) m_SessionsV6.erase (ep); else m_Sessions.erase (ep); } } void SSUServer::DeleteAllSessions () { for (auto& it: m_Sessions) it.second->Close (); m_Sessions.clear (); for (auto& it: m_SessionsV6) it.second->Close (); m_SessionsV6.clear (); } template std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only { std::vector > filteredSessions; for (const auto& s :m_Sessions) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { auto ind = rand () % filteredSessions.size (); return filteredSessions[ind]; } return nullptr; } std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only { return GetRandomV4Session ( [excluded](std::shared_ptr session)->bool { return session->GetState () == eSessionStateEstablished && session != excluded; } ); } template std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only { std::vector > filteredSessions; for (const auto& s :m_SessionsV6) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { auto ind = rand () % filteredSessions.size (); return filteredSessions[ind]; } return nullptr; } std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only { return GetRandomV6Session ( [excluded](std::shared_ptr session)->bool { return session->GetState () == eSessionStateEstablished && session != excluded; } ); } std::list > SSUServer::FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); std::list > ret; const auto& sessions = v4 ? m_Sessions : m_SessionsV6; for (const auto& s : sessions) { if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished && ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) ret.push_back (s.second); else if (s.second->GetRemoteIdentity ()) excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ()); } if ((int)ret.size () > maxNumIntroducers) { // shink ret randomly int sz = ret.size () - maxNumIntroducers; for (int i = 0; i < sz; i++) { auto ind = rand () % ret.size (); auto it = ret.begin (); std::advance (it, ind); ret.erase (it); } } return ret; } void SSUServer::RescheduleIntroducersUpdateTimer () { m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, this, std::placeholders::_1, true)); } void SSUServer::ScheduleIntroducersUpdateTimer () { m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, this, std::placeholders::_1, true)); } void SSUServer::RescheduleIntroducersUpdateTimerV6 () { m_IntroducersUpdateTimerV6.cancel (); m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, this, std::placeholders::_1, false)); } void SSUServer::ScheduleIntroducersUpdateTimerV6 () { m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, this, std::placeholders::_1, false)); } void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) { if (ecode != boost::asio::error::operation_aborted) { // timeout expired if (v4) { if (i2p::context.GetStatus () == eRouterStatusTesting) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimer (); return; } if (i2p::context.GetStatus () != eRouterStatusFirewalled) { // we don't need introducers m_Introducers.clear (); return; } // we are firewalled if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4 } else { if (i2p::context.GetStatusV6 () == eRouterStatusTesting) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimerV6 (); return; } if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) { // we don't need introducers m_IntroducersV6.clear (); return; } // we are firewalled auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address (); if (addr && addr->ssu && addr->ssu->introducers.empty ()) i2p::context.SetUnreachable (false, true); // v6 } std::list newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); std::set excluded; auto& introducers = v4 ? m_Introducers : m_IntroducersV6; for (const auto& it : introducers) { auto session = FindSession (it); if (session) { if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) session->SendKeepAlive (); if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) { newList.push_back (it); numIntroducers++; if (session->GetRemoteIdentity ()) excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ()); } else session = nullptr; } if (!session) i2p::context.RemoveIntroducer (it); } if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) { // create new auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates if (sessions.empty () && !introducers.empty ()) { // bump creation time for previous introducers if no new sessions found LogPrint (eLogDebug, "SSU: no new introducers found. Trying to reuse existing"); for (const auto& it : introducers) { auto session = FindSession (it); if (session) session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION); } // try again excluded.clear (); sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); } for (const auto& it1: sessions) { const auto& ep = it1->GetRemoteEndpoint (); i2p::data::RouterInfo::Introducer introducer; introducer.iHost = ep.address (); introducer.iPort = ep.port (); introducer.iTag = it1->GetRelayTag (); introducer.iKey = it1->GetIntroKey (); introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION; if (i2p::context.AddIntroducer (introducer)) { newList.push_back (ep); if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; } if (it1->GetRemoteIdentity ()) excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ()); } } introducers = newList; if (introducers.size () < SSU_MAX_NUM_INTRODUCERS) { for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) { auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded); if (introducer) { auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address (); if (address && !address->host.is_unspecified () && address->port) { boost::asio::ip::udp::endpoint ep (address->host, address->port); if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet { CreateDirectSession (introducer, ep, false); excluded.insert (introducer->GetIdentHash ()); } } } else { LogPrint (eLogDebug, "SSU: can't find more introducers"); break; } } } if (v4) ScheduleIntroducersUpdateTimer (); else ScheduleIntroducersUpdateTimerV6 (); } } void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) { m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; } PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) return it->second.role; else return ePeerTestParticipantUnknown; } std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) return it->second.session; else return nullptr; } void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) it->second.role = role; } void SSUServer::RemovePeerTest (uint32_t nonce) { m_PeerTests.erase (nonce); } void SSUServer::SchedulePeerTestsCleanupTimer () { m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, this, std::placeholders::_1)); } void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { int numDeleted = 0; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) { if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) { numDeleted++; it = m_PeerTests.erase (it); } else ++it; } if (numDeleted > 0) LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); SchedulePeerTestsCleanupTimer (); } } void SSUServer::ScheduleTermination () { m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, this, std::placeholders::_1)); } void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto& it: m_Sessions) if (it.second->IsTerminationTimeoutExpired (ts)) { auto session = it.second; if (it.first != session->GetRemoteEndpoint ()) LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted"); m_Service.post ([session] { LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); session->Failed (); }); } ScheduleTermination (); } } void SSUServer::ScheduleTerminationV6 () { m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, this, std::placeholders::_1)); } void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto& it: m_SessionsV6) if (it.second->IsTerminationTimeoutExpired (ts)) { auto session = it.second; if (it.first != session->GetRemoteEndpoint ()) LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); m_Service.post ([session] { LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); session->Failed (); }); } ScheduleTerminationV6 (); } } } } i2pd-2.39.0/libi2pd/SSU.h000066400000000000000000000134221411072525600146200ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SSU_H__ #define SSU_H__ #include #include #include #include #include #include #include #include #include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "SSUSession.h" namespace i2p { namespace transport { const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const size_t SSU_MAX_NUM_INTRODUCERS = 3; const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K struct SSUPacket { i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size boost::asio::ip::udp::endpoint from; size_t len; }; class SSUServer { public: SSUServer (int port); ~SSUServer (); void Start (); void Stop (); bool CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); bool CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); void DeleteSession (std::shared_ptr session); void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; uint16_t GetPort () const { return m_Endpoint.port (); }; void SetLocalAddress (const boost::asio::ip::address& localAddress); void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); void AddRelay (uint32_t tag, std::shared_ptr relay); void RemoveRelay (uint32_t tag); std::shared_ptr FindRelaySession (uint32_t tag); void RescheduleIntroducersUpdateTimer (); void RescheduleIntroducersUpdateTimerV6 (); void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); std::shared_ptr GetPeerTestSession (uint32_t nonce); void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); void RemovePeerTest (uint32_t nonce); private: void OpenSocket (); void OpenSocketV6 (); void Run (); void RunReceivers (); void RunReceiversV6 (); void Receive (); void ReceiveV6 (); void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedPackets (std::vector packets, std::map >* sessions); void CreateSessionThroughIntroducer (std::shared_ptr router, std::shared_ptr address, bool peerTest = false); template std::shared_ptr GetRandomV4Session (Filter filter); template std::shared_ptr GetRandomV6Session (Filter filter); std::list > FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded); void ScheduleIntroducersUpdateTimer (); void ScheduleIntroducersUpdateTimerV6 (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); void SchedulePeerTestsCleanupTimer (); void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); void ScheduleTerminationV6 (); void HandleTerminationTimerV6 (const boost::system::error_code& ecode); private: struct PeerTest { uint64_t creationTime; PeerTestParticipant role; std::shared_ptr session; // for Bob to Alice }; volatile bool m_IsRunning; std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6, m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; std::list m_Introducers, m_IntroducersV6; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map > m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds public: // for HTTP only const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; }; } } #endif i2pd-2.39.0/libi2pd/SSUData.cpp000066400000000000000000000363601411072525600157530ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Log.h" #include "Timestamp.h" #include "NetDb.hpp" #include "SSU.h" #include "SSUData.h" namespace i2p { namespace transport { void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) { if (msg->len + fragmentSize > msg->maxLen) { LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *msg; msg = newMsg; } if (msg->Concat (fragment, fragmentSize) < fragmentSize) LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); nextFragmentNum++; } SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), m_IncompleteMessagesCleanupTimer (session.GetService ()), m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) { } SSUData::~SSUData () { } void SSUData::Start () { ScheduleIncompleteMessagesCleanup (); } void SSUData::Stop () { m_ResendTimer.cancel (); m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessages.clear (); m_SentMessages.clear (); m_ReceivedMessages.clear (); } void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) { if (!remoteRouter) return; auto ssuAddress = remoteRouter->GetSSUAddress (); if (ssuAddress && ssuAddress->ssu->mtu) { if (m_Session.IsV6 ()) m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; else m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; if (m_PacketSize > 0) { // make sure packet size multiple of 16 m_PacketSize >>= 4; m_PacketSize <<= 4; if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize); } else { LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu); m_PacketSize = m_MaxPacketSize; } } } void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent) { auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); if (routerInfo) AdjustPacketSize (routerInfo); } void SSUData::ProcessSentMessageAck (uint32_t msgID) { auto it = m_SentMessages.find (msgID); if (it != m_SentMessages.end ()) { m_SentMessages.erase (it); if (m_SentMessages.empty ()) m_ResendTimer.cancel (); } } void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag) { if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) { // explicit ACKs uint8_t numAcks =*buf; buf++; for (int i = 0; i < numAcks; i++) ProcessSentMessageAck (bufbe32toh (buf+i*4)); buf += numAcks*4; } if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) { // explicit ACK bitfields uint8_t numBitfields =*buf; buf++; for (int i = 0; i < numBitfields; i++) { uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID auto it = m_SentMessages.find (msgID); // process individual Ack bitfields bool isNonLast = false; int fragment = 0; do { uint8_t bitfield = *buf; isNonLast = bitfield & 0x80; bitfield &= 0x7F; // clear MSB if (bitfield && it != m_SentMessages.end ()) { int numSentFragments = it->second->fragments.size (); // process bits uint8_t mask = 0x01; for (int j = 0; j < 7; j++) { if (bitfield & mask) { if (fragment < numSentFragments) it->second->fragments[fragment].reset (nullptr); } fragment++; mask <<= 1; } } buf++; } while (isNonLast); } } } void SSUData::ProcessFragments (uint8_t * buf) { uint8_t numFragments = *buf; // number of fragments buf++; for (int i = 0; i < numFragments; i++) { uint32_t msgID = bufbe32toh (buf); // message ID buf += 4; uint8_t frag[4] = {0}; memcpy (frag + 1, buf, 3); buf += 3; uint32_t fragmentInfo = bufbe32toh (frag); // fragment info uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 bool isLast = fragmentInfo & 0x010000; // bit 16 uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) { LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); return; } // find message with msgID auto it = m_IncompleteMessages.find (msgID); if (it == m_IncompleteMessages.end ()) { // create new message auto msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; } std::unique_ptr& incompleteMessage = it->second; // mark fragment as received if (fragmentNum < 64) incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); else LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); // handle current fragment if (fragmentNum == incompleteMessage->nextFragmentNum) { // expected fragment incompleteMessage->AttachNextFragment (buf, fragmentSize); if (!isLast && !incompleteMessage->savedFragments.empty ()) { // try saved fragments for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) { auto& savedFragment = *it1; if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) { incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); isLast = savedFragment->isLast; incompleteMessage->savedFragments.erase (it1++); } else break; } if (isLast) LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); } } else { if (fragmentNum < incompleteMessage->nextFragmentNum) // duplicate fragment LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); else { // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); } isLast = false; } if (isLast) { // delete incomplete message auto msg = incompleteMessage->msg; incompleteMessage->msg = nullptr; m_IncompleteMessages.erase (msgID); // process message SendMsgAck (msgID); msg->FromSSU (msgID); if (m_Session.GetState () == eSessionStateEstablished) { if (!m_ReceivedMessages.count (msgID)) { m_ReceivedMessages.insert (msgID); m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); if (!msg->IsExpired ()) { m_Handler.PutNextMessage (msg); } else LogPrint (eLogDebug, "SSU: message expired"); } else LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); } else { // we expect DeliveryStatus if (msg->GetTypeID () == eI2NPDeliveryStatus) { LogPrint (eLogDebug, "SSU: session established"); m_Session.Established (); } else LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); } } else SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits); buf += fragmentSize; } } void SSUData::FlushReceivedMessage () { m_Handler.Flush (); } void SSUData::ProcessMessage (uint8_t * buf, size_t len) { //uint8_t * start = buf; uint8_t flag = *buf; buf++; LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); // process acks if presented if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) ProcessAcks (buf, flag); // extended data if presented if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) { uint8_t extendedDataSize = *buf; buf++; // size LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); buf += extendedDataSize; } // process data ProcessFragments (buf); } void SSUData::Send (std::shared_ptr msg) { uint32_t msgID = msg->ToSSU (); if (m_SentMessages.find (msgID) != m_SentMessages.end()) { LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); return; } if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); std::unique_ptr& sentMessage = ret.first->second; if (ret.second) { sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; sentMessage->numResends = 0; } auto& fragments = sentMessage->fragments; size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) size_t len = msg->GetLength (); uint8_t * msgBuf = msg->GetSSUHeader (); uint32_t fragmentNum = 0; while (len > 0 && fragmentNum <= 127) { Fragment * fragment = new Fragment; fragment->fragmentNum = fragmentNum; uint8_t * payload = fragment->buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility payload++; *payload = 1; // always 1 message fragment per message payload++; htobe32buf (payload, msgID); payload += 4; bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max 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 - fragment->buf; uint8_t rem = size & 0x0F; if (rem) // make sure 16 bytes boundary { auto padding = 16 - rem; memset (fragment->buf + size, 0, padding); size += padding; } fragment->len = size; fragments.push_back (std::unique_ptr (fragment)); // encrypt message with session key uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf); try { m_Session.Send (buf, size); } catch (boost::system::system_error& ec) { LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); } if (!isLast) { len -= payloadSize; msgBuf += payloadSize; } else len = 0; fragmentNum++; } } void SSUData::SendMsgAck (uint32_t msgID) { uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16 uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag payload++; *payload = 1; // number of ACKs payload++; htobe32buf (payload, msgID); // msgID payload += 4; *payload = 0; // number of fragments // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); m_Session.Send (buf, 48); } void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits) { if (!bits) return; uint8_t buf[64 + 18] = {0}; 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; size_t len = 0; while (bits) { *payload = (bits & 0x7F); // next 7 bits bits >>= 7; if (bits) *payload &= 0x80; // 0x80 means non-last payload++; len++; } *payload = 0; // number of fragments len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4 // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); m_Session.Send (buf, len); } void SSUData::ScheduleResend() { m_ResendTimer.cancel (); m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL)); auto s = m_Session.shared_from_this(); m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) { s->m_Data.HandleResendTimer (ecode); }); } void SSUData::HandleResendTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); int numResent = 0; for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) { if (ts >= it->second->nextResendTime) { if (it->second->numResends < MAX_NUM_RESENDS) { for (auto& f: it->second->fragments) if (f) { try { m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf); m_Session.Send (buf, f->len); // resend numResent++; } catch (boost::system::system_error& ec) { LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ()); } } it->second->numResends++; it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; ++it; } else { LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); it = m_SentMessages.erase (it); } } else ++it; } if (m_SentMessages.empty ()) return; // nothing to resend if (numResent < MAX_OUTGOING_WINDOW_SIZE) ScheduleResend (); else { LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); m_Session.Close (); } } } void SSUData::ScheduleIncompleteMessagesCleanup () { m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); auto s = m_Session.shared_from_this(); m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); } void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); it = m_IncompleteMessages.erase (it); } else ++it; } // decay if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) m_ReceivedMessages.clear (); ScheduleIncompleteMessagesCleanup (); } } } } i2pd-2.39.0/libi2pd/SSUData.h000066400000000000000000000100321411072525600154040ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SSU_DATA_H__ #define SSU_DATA_H__ #include #include #include #include #include #include #include #include "I2NPProtocol.h" #include "Identity.h" #include "RouterInfo.h" namespace i2p { namespace transport { const size_t SSU_MTU_V4 = 1484; #ifdef MESHNET const size_t SSU_MTU_V6 = 1286; #else const size_t SSU_MTU_V6 = 1488; #endif 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; // 1440 const int RESEND_INTERVAL = 3; // in seconds const int MAX_NUM_RESENDS = 5; const int DECAY_INTERVAL = 20; // in seconds const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store // data flags const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; const uint8_t DATA_FLAG_WANT_REPLY = 0x04; const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; struct Fragment { int fragmentNum; size_t len; bool isLast; uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest Fragment () = default; Fragment (int n, const uint8_t * b, int l, bool last): fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; }; struct FragmentCmp { bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const { return f1->fragmentNum < f2->fragmentNum; }; }; struct IncompleteMessage { std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds uint64_t receivedFragmentsBits; std::set, FragmentCmp> savedFragments; IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; struct SentMessage { std::vector > fragments; uint32_t nextResendTime; // in seconds int numResends; }; class SSUSession; class SSUData { public: SSUData (SSUSession& session); ~SSUData (); void Start (); void Stop (); void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); void Send (std::shared_ptr msg); void AdjustPacketSize (std::shared_ptr remoteRouter); void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); private: void SendMsgAck (uint32_t msgID); void SendFragmentAck (uint32_t msgID, uint64_t bits); 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 ScheduleIncompleteMessagesCleanup (); void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); private: SSUSession& m_Session; std::unordered_map > m_IncompleteMessages; std::unordered_map > m_SentMessages; std::unordered_set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; uint32_t m_LastMessageReceivedTime; // in second }; } } #endif i2pd-2.39.0/libi2pd/SSUSession.cpp000066400000000000000000001242361411072525600165250ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 "version.h" #include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" #include "Transports.h" #include "NetDb.hpp" #include "SSU.h" #include "SSUSession.h" namespace i2p { namespace transport { SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router, SSU_TERMINATION_TIMEOUT), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) { if (router) { // we are client auto address = IsV6 () ? router->GetSSUV6Address () : router->GetSSUAddress (true); if (address) m_IntroKey = address->ssu->key; m_Data.AdjustPacketSize (router); // mtu } else { // we are server auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); if (address) m_IntroKey = address->ssu->key; } m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } SSUSession::~SSUSession () { } boost::asio::io_service& SSUSession::GetService () { return m_Server.GetService (); } void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) { uint8_t sharedKey[256]; m_DHKeysPair->Agree (pubKey, sharedKey); uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; if (sharedKey[0] & 0x80) { sessionKey[0] = 0; memcpy (sessionKey + 1, sharedKey, 31); memcpy (macKey, sharedKey + 31, 32); } else if (sharedKey[0]) { memcpy (sessionKey, sharedKey, 32); memcpy (macKey, sharedKey + 32, 32); } else { // find first non-zero byte uint8_t * nonZero = sharedKey + 1; while (!*nonZero) { nonZero++; if (nonZero - sharedKey > 32) { LogPrint (eLogWarning, "SSU: first 32 bytes of shared key is all zeros. Ignored"); return; } } memcpy (sessionKey, nonZero, 32); SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); } m_IsSessionKey = true; m_SessionKeyEncryption.SetKey (m_SessionKey); m_SessionKeyDecryption.SetKey (m_SessionKey); } void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { m_NumReceivedBytes += len; i2p::transport::transports.UpdateReceivedBytes (len); if (m_State == eSessionStateIntroduced) { // HolePunch received LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); m_State = eSessionStateUnknown; Connect (); } else { if (!len) return; // ignore zero-length packets if (m_State == eSessionStateEstablished) m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first DecryptSessionKey (buf, len); else { if (m_State == eSessionStateEstablished) Reset (); // new session key required // try intro key depending on side if (Validate (buf, len, m_IntroKey)) Decrypt (buf, len, m_IntroKey); else { // try own intro key auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } if (Validate (buf, len, address->ssu->key)) Decrypt (buf, len, address->ssu->key); else { LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); m_Server.DeleteSession (shared_from_this ()); return; } } } // successfully decrypted ProcessMessage (buf, len, senderEndpoint); } } size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const { size_t s = sizeof (SSUHeader); if (((const SSUHeader *)buf)->IsExtendedOptions ()) s += buf[s] + 1; // byte right after header is extended options length return s; } void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { len -= (len & 0x0F); // %16, delete extra padding if (len <= sizeof (SSUHeader)) return; // drop empty message //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "SSU header size ", headerSize, " exceeds packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; switch (header->GetPayloadType ()) { case PAYLOAD_TYPE_DATA: ProcessData (buf + headerSize, len - headerSize); break; case PAYLOAD_TYPE_SESSION_REQUEST: ProcessSessionRequest (buf, len); // buf with header break; case PAYLOAD_TYPE_SESSION_CREATED: ProcessSessionCreated (buf, len); // buf with header break; case PAYLOAD_TYPE_SESSION_CONFIRMED: ProcessSessionConfirmed (buf, len); // buf with header break; case PAYLOAD_TYPE_PEER_TEST: LogPrint (eLogDebug, "SSU: peer test received"); ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_SESSION_DESTROYED: { LogPrint (eLogDebug, "SSU: session destroy received"); m_Server.DeleteSession (shared_from_this ()); break; } case PAYLOAD_TYPE_RELAY_RESPONSE: ProcessRelayResponse (buf + headerSize, len - headerSize); if (m_State != eSessionStateEstablished) m_Server.DeleteSession (shared_from_this ()); break; case PAYLOAD_TYPE_RELAY_REQUEST: LogPrint (eLogDebug, "SSU: relay request received"); ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_RELAY_INTRO: LogPrint (eLogDebug, "SSU: relay intro received"); ProcessRelayIntro (buf + headerSize, len - headerSize); break; default: LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); } } void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU message: session request"); bool sendRelayTag = true; auto headerSize = sizeof (SSUHeader); if (((SSUHeader *)buf)->IsExtendedOptions ()) { uint8_t extendedOptionsLen = buf[headerSize]; headerSize++; if (extendedOptionsLen >= 2) // options are presented { uint16_t flags = bufbe16toh (buf + headerSize); sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; } headerSize += extendedOptionsLen; } if (headerSize >= len) { LogPrint (eLogError, "Session request header size ", headerSize, " exceeds packet length ", len); return; } if (!m_DHKeysPair) { auto pair = std::make_shared (); pair->GenerateKeys (); m_DHKeysPair = pair; } CreateAESandMacKey (buf + headerSize); SendSessionCreated (buf + headerSize, sendRelayTag); } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) { if (!IsOutgoing () || !m_DHKeysPair) { LogPrint (eLogWarning, "SSU: Unsolicited session created message"); return; } LogPrint (eLogDebug, "SSU message: session created"); m_ConnectTimer.cancel (); // connect timer SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "Session created header size ", headerSize, " exceeds packet length ", len); return; } uint8_t * payload = buf + headerSize; uint8_t * y = payload; CreateAESandMacKey (y); s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y payload += 256; boost::asio::ip::address ourIP; uint16_t ourPort = 0; auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort); if (!addressAndPortLen) return; uint8_t * ourAddressAndPort = payload + 1; payload += addressAndPortLen; addressAndPortLen--; // -1 byte address size s.Insert (ourAddressAndPort, addressAndPortLen); // address + port if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (payload, 8); // relayTag and signed on time m_RelayTag = bufbe32toh (payload); payload += 4; // relayTag if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) { auto ts = i2p::util::GetSecondsSinceEpoch (); uint32_t signedOnTime = bufbe32toh(payload); if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) { LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); i2p::context.SetError (eRouterErrorClockSkew); } } payload += 4; // signed on time // decrypt signature size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload // verify signature if (s.Verify (m_RemoteIdentity, payload)) { LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); if (!i2p::util::net::IsInReservedRange (ourIP)) { i2p::context.UpdateAddress (ourIP); SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen); } else { LogPrint (eLogError, "SSU: Wrong external address ", ourIP.to_string ()); Failed (); } } else { LogPrint (eLogError, "SSU: message 'created' signature verification failed"); Failed (); } } void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU: Session confirmed received"); m_ConnectTimer.cancel (); auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "SSU: Session confirmed header size ", headerSize, " exceeds packet length ", len); return; } const uint8_t * payload = buf + headerSize; payload++; // identity fragment info uint16_t identitySize = bufbe16toh (payload); if (identitySize + headerSize + 7 > len) // 7 = fragment info + fragment size + signed on time { LogPrint (eLogError, "SSU: Session confirmed identity size ", identitySize, " exceeds packet length ", len); return; } payload += 2; // size of identity fragment auto identity = std::make_shared (payload, identitySize); auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); payload += identitySize; // identity auto ts = i2p::util::GetSecondsSinceEpoch (); uint32_t signedOnTime = bufbe32toh(payload); if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) { LogPrint (eLogError, "SSU message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); Failed (); return; } if (m_SignedData) m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); size_t paddingSize = fullSize & 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; if (fullSize + paddingSize > len) { LogPrint (eLogError, "SSU: Session confirmed message is too short ", len); return; } // verify signature if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) { m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } else { LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); Failed (); } } void SSUSession::SendSessionRequest () { uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); uint8_t flag = 0; // fill extended options, 3 bytes extended options don't change message size bool isV4 = m_RemoteEndpoint.address ().is_v4 (); if ((isV4 && i2p::context.GetStatus () == eRouterStatusOK) || (!isV4 && i2p::context.GetStatusV6 () == eRouterStatusOK)) // we don't need relays { // tell out peer to now assign relay tag flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; } // fill payload memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x if (isV4) { payload[256] = 4; memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); } else { payload[256] = 16; memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); } // encrypt and send uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); } void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) { auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } uint8_t buf[96 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); htobe32buf (payload, introducer.iTag); payload += 4; *payload = 0; // no address payload++; htobuf16(payload, 0); // port = 0 payload += 2; *payload = 0; // challenge payload++; memcpy (payload, (const uint8_t *)address->ssu->key, 32); payload += 32; htobe32buf (payload, nonce); // nonce uint8_t iv[16]; RAND_bytes (iv, 16); // random iv if (m_State == eSessionStateEstablished) FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); else FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); m_Server.Send (buf, 96, m_RemoteEndpoint); LogPrint (eLogDebug, "SSU: relay request sent"); } void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) { auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time s.Insert (x, 256); // x uint8_t buf[384 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); s.Insert (payload, 256); // y payload += 256; if (m_RemoteEndpoint.address ().is_v4 ()) { // ipv4 *payload = 4; payload++; memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); s.Insert (payload, 4); // remote endpoint IP V4 payload += 4; } else { // ipv6 *payload = 16; payload++; memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); s.Insert (payload, 16); // remote endpoint IP V6 payload += 16; } htobe16buf (payload, m_RemoteEndpoint.port ()); s.Insert (payload, 2); // remote port payload += 2; if (address->host.is_v4 ()) s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 else s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (htobe16 (address->port)); // our port if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer (!IsV6 ())) { RAND_bytes((uint8_t *)&m_SentRelayTag, 4); if (!m_SentRelayTag) m_SentRelayTag = 1; } htobe32buf (payload, m_SentRelayTag); payload += 4; // relay tag htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time payload += 4; s.Insert (payload - 8, 4); // relayTag // we have to store this signed data for session confirmed // same data but signed on time, it will Alice's there m_SignedData = std::unique_ptr(new SignedData (s)); s.Insert (payload - 4, 4); // BOB's signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature uint8_t iv[16]; RAND_bytes (iv, 16); // random iv // encrypt signature and padding with newly created session key size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) { // fill random padding RAND_bytes(payload + signatureLen, (16 - paddingSize)); signatureLen += (16 - paddingSize); } m_SessionKeyEncryption.SetIV (iv); m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); payload += signatureLen; size_t msgLen = payload - buf; // encrypt message with intro key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); Send (buf, msgLen); } void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) { uint8_t buf[512 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = 1; // 1 fragment payload++; // info size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes htobe16buf (payload, identLen); payload += 2; // cursize i2p::context.GetIdentity ()->ToBuffer (payload, identLen); payload += identLen; uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); htobe32buf (payload, signedOnTime); // signed on time payload += 4; auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = ((payload - buf) + signatureLen)%16; if (paddingSize > 0) paddingSize = 16 - paddingSize; RAND_bytes(payload, paddingSize); // fill padding with random payload += paddingSize; // padding size // signature SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (htobe32 (m_RelayTag)); // relay tag s.Insert (htobe32 (signedOnTime)); // signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature payload += signatureLen; size_t msgLen = payload - buf; uint8_t iv[16]; RAND_bytes (iv, 16); // random iv // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); Send (buf, msgLen); } void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { uint32_t relayTag = bufbe32toh (buf); auto session = m_Server.FindRelaySession (relayTag); if (session) { buf += 4; // relay tag uint8_t size = *buf; buf++; // size buf += size; // address buf += 2; // port uint8_t challengeSize = *buf; buf++; // challenge size buf += challengeSize; const uint8_t * introKey = buf; buf += 32; // introkey uint32_t nonce = bufbe32toh (buf); SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); SendRelayIntro (session, from); } } void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) { bool isV4 = to.address ().is_v4 (); // Charle's bool isV4A = from.address ().is_v4 (); // Alice's if ((isV4 && !isV4A) || (!isV4 && isV4A)) { LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay response"); return; } uint8_t buf[80 + 18] = {0}; // 64 for ipv4 and 80 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); // Charlie if (isV4) { *payload = 4; payload++; // size memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4 payload += 4; // address } else { *payload = 16; payload++; // size memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 payload += 16; // address } htobe16buf (payload, to.port ()); // Charlie's port payload += 2; // port // Alice if (isV4) { *payload = 4; payload++; // size memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 payload += 4; // address } else { *payload = 16; payload++; // size memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 payload += 16; // address } htobe16buf (payload, from.port ()); // Alice's port payload += 2; // port htobe32buf (payload, nonce); if (m_State == eSessionStateEstablished) { // encrypt with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); Send (buf, isV4 ? 64 : 80); } else { // ecrypt with Alice's intro key uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); m_Server.Send (buf, isV4 ? 64 : 80, from); } LogPrint (eLogDebug, "SSU: relay response sent"); } void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) { if (!session) return; bool isV4 = from.address ().is_v4 (); // Alice's bool isV4C = session->m_RemoteEndpoint.address ().is_v4 (); // Charlie's if ((isV4 && !isV4C) || (!isV4 && isV4C)) { LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay intro"); return; } uint8_t buf[64 + 18] = {0}; // 48 for ipv4 and 64 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); if (isV4) { *payload = 4; payload++; // size memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 payload += 4; // address } else { *payload = 16; payload++; // size memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 payload += 16; // address } htobe16buf (payload, from.port ()); // Alice's port payload += 2; // port *payload = 0; // challenge size uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey); m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint); LogPrint (eLogDebug, "SSU: relay intro sent"); } void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU message: Relay response received"); boost::asio::ip::address remoteIP; uint16_t remotePort = 0; auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort); if (!remoteSize) return; buf += remoteSize; len -= remoteSize; boost::asio::ip::address ourIP; uint16_t ourPort = 0; auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort); if (!ourSize) return; buf += ourSize; len -= ourSize; LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); if (!i2p::util::net::IsInReservedRange (ourIP)) i2p::context.UpdateAddress (ourIP); else LogPrint (eLogWarning, "SSU: Wrong external address ", ourIP.to_string ()); if (ourIP.is_v4 ()) { if (ourPort != m_Server.GetPort ()) { if (i2p::context.GetStatus () == eRouterStatusTesting) i2p::context.SetError (eRouterErrorSymmetricNAT); } else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) i2p::context.SetStatus (eRouterStatusTesting); } uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce auto it = m_RelayRequests.find (nonce); if (it != m_RelayRequests.end ()) { // check if we are waiting for introduction boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); if (!m_Server.FindSession (remoteEndpoint)) { // we didn't have correct endpoint when sent relay request // now we do LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); if ((remoteIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch // we assume that HolePunch has been sent by this time and our SessionRequest will go through m_Server.CreateDirectSession (it->second, remoteEndpoint, false); } // delete request m_RelayRequests.erase (it); // cancel connect timer m_ConnectTimer.cancel (); } else LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); } void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) { boost::asio::ip::address ip; uint16_t port = 0; ExtractIPAddressAndPort (buf, len, ip, port); if (!ip.is_unspecified () && port) // send hole punch of 0 bytes m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port)); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; memcpy (header->iv, iv, 16); header->flag = flag | (payloadType << 4); // MSB is 0 htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); i2p::crypto::CBCEncryption encryption; encryption.SetKey (aesKey); encryption.SetIV (iv); encryption.Encrypt (encrypted, encryptedLen, encrypted); // assume actual buffer size is 18 (16 + 2) bytes more memcpy (buf + len, iv, 16); uint16_t netid = i2p::context.GetNetID (); htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) { FillHeaderAndEncrypt (payloadType, buf, len, buf); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)out; RAND_bytes (header->iv, 16); // random iv m_SessionKeyEncryption.SetIV (header->iv); SSUHeader * inHeader = (SSUHeader *)in; inHeader->flag = payloadType << 4; // MSB is 0 htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; uint16_t encryptedLen = len - (encrypted - out); m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); // assume actual out buffer size is 18 (16 + 2) bytes more memcpy (out + len, header->iv, 16); uint16_t netid = i2p::context.GetNetID (); htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); } void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); i2p::crypto::CBCDecryption decryption; decryption.SetKey (aesKey); decryption.SetIV (header->iv); decryption.Decrypt (encrypted, encryptedLen, encrypted); } void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); if (encryptedLen > 0) { m_SessionKeyDecryption.SetIV (header->iv); m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); } } bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return false; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); // assume actual buffer size is 18 (16 + 2) bytes more memcpy (buf + len, header->iv, 16); uint16_t netid = i2p::context.GetNetID (); htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); uint8_t digest[16]; i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); return !memcmp (header->mac, digest, 16); } void SSUSession::Connect () { if (m_State == eSessionStateUnknown) { ScheduleConnectTimer (); // set connect timer m_DHKeysPair = std::make_shared (); m_DHKeysPair->GenerateKeys (); SendSessionRequest (); } } void SSUSession::WaitForConnect () { if (!IsOutgoing ()) // incoming session ScheduleConnectTimer (); else LogPrint (eLogError, "SSU: wait for connect for outgoing session"); } void SSUSession::ScheduleConnectTimer () { m_ConnectTimer.cancel (); m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) { if (!ecode) { // timeout expired LogPrint (eLogWarning, "SSU: session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); Failed (); } } void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, std::shared_ptr to) { if (m_State == eSessionStateUnknown) { // set connect timer m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); m_RelayRequests[nonce] = to; SendRelayRequest (introducer, nonce); } void SSUSession::WaitForIntroduction () { m_State = eSessionStateIntroduced; // set connect timer m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } void SSUSession::Close () { SendSessionDestroyed (); Reset (); m_State = eSessionStateClosed; } void SSUSession::Reset () { m_State = eSessionStateUnknown; transports.PeerDisconnected (shared_from_this ()); m_Data.Stop (); m_ConnectTimer.cancel (); if (m_SentRelayTag) { m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore m_SentRelayTag = 0; } m_DHKeysPair = nullptr; m_SignedData = nullptr; m_IsSessionKey = false; } void SSUSession::Done () { GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); } void SSUSession::Established () { m_State = eSessionStateEstablished; m_DHKeysPair = nullptr; m_SignedData = nullptr; m_Data.Start (); transports.PeerConnected (shared_from_this ()); if (m_IsPeerTest) SendPeerTest (); if (m_SentRelayTag) m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } void SSUSession::Failed () { if (m_State != eSessionStateFailed) { m_State = eSessionStateFailed; m_Server.DeleteSession (shared_from_this ()); } } void SSUSession::SendI2NPMessages (const std::vector >& msgs) { GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); } void SSUSession::PostI2NPMessages (std::vector > msgs) { if (m_State == eSessionStateEstablished) { for (const auto& it: msgs) if (it) { if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) m_Data.Send (it); else LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); } } } void SSUSession::ProcessData (uint8_t * buf, size_t len) { m_Data.ProcessMessage (buf, len); m_IsDataReceived = true; } void SSUSession::FlushData () { if (m_IsDataReceived) { m_Data.FlushReceivedMessage (); m_IsDataReceived = false; } } void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { uint32_t nonce = bufbe32toh (buf); // 4 bytes boost::asio::ip::address addr; // Alice's addresss uint16_t port = 0; // and port auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port); if (port && (size != 7) && (size != 19)) { LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported"); return; } const uint8_t * introKey = buf + 4 + size; switch (m_Server.GetPeerTestParticipant (nonce)) { // existing test case ePeerTestParticipantAlice1: { if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); if (IsV6 ()) { if (i2p::context.GetStatusV6 () == eRouterStatusTesting) { i2p::context.SetStatusV6 (eRouterStatusFirewalled); m_Server.RescheduleIntroducersUpdateTimerV6 (); } } else if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK { i2p::context.SetStatus (eRouterStatusFirewalled); m_Server.RescheduleIntroducersUpdateTimer (); } } else { LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); if (m_State == eSessionStateEstablished) LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice"); if (IsV6 ()) i2p::context.SetStatusV6 (eRouterStatusOK); else i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie } break; } case ePeerTestParticipantAlice2: { if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); else { // peer test successive LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); if (IsV6 ()) i2p::context.SetStatusV6 (eRouterStatusOK); else i2p::context.SetStatus (eRouterStatusOK); m_Server.RemovePeerTest (nonce); } break; } case ePeerTestParticipantBob: { LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest if (session && session->m_State == eSessionStateEstablished) { const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice } m_Server.RemovePeerTest (nonce); // nonce has been used break; } case ePeerTestParticipantCharlie: { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Charlie"); SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address m_Server.RemovePeerTest (nonce); // nonce has been used break; } // test not found case ePeerTestParticipantUnknown: { if (m_State == eSessionStateEstablished) { // new test if (port) { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) { m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob } } else { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Bob"); auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie if (session) { m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address } } } else LogPrint (eLogError, "SSU: unexpected peer test"); } } } void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress, bool sendAddress) // toAddress is true for Alice<->Chalie communications only // sendAddress is false if message comes from Alice { uint8_t buf[80 + 18] = {0}; uint8_t iv[16]; uint8_t * payload = buf + sizeof (SSUHeader); htobe32buf (payload, nonce); payload += 4; // nonce // address and port if (sendAddress) { if (address.is_v4 ()) { *payload = 4; memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 } else if (address.is_v6 ()) { *payload = 16; memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 } else *payload = 0; payload += (payload[0] + 1); } else { *payload = 0; payload++; //size } htobe16buf (payload, port); payload += 2; // port // intro key if (toAddress) { // send our intro key to address instead of its own auto addr = address.is_v4 () ? i2p::context.GetRouterInfo ().GetSSUAddress (true) : // ipv4 i2p::context.GetRouterInfo ().GetSSUV6Address (); if (addr) memcpy (payload, addr->ssu->key, 32); // intro key else LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); } else memcpy (payload, introKey, 32); // intro key // send RAND_bytes (iv, 16); // random iv if (toAddress) { // encrypt message with specified intro key FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); boost::asio::ip::udp::endpoint e (address, port); m_Server.Send (buf, 80, e); } else { // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); Send (buf, 80); } } void SSUSession::SendPeerTest () { // we are Alice LogPrint (eLogDebug, "SSU: sending peer test"); auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); if (!address) { LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); return; } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); if (!nonce) nonce = 1; m_IsPeerTest = false; m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); SendPeerTest (nonce, boost::asio::ip::address(), 0, address->ssu->key, false, false); // address and port always zero for Alice } void SSUSession::SendKeepAlive () { if (m_State == eSessionStateEstablished) { uint8_t buf[48 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = 0; // flags payload++; *payload = 0; // num fragments // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); Send (buf, 48); LogPrint (eLogDebug, "SSU: keep-alive sent"); m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } } void SSUSession::SendSessionDestroyed () { if (m_IsSessionKey) { uint8_t buf[48 + 18] = {0}; // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); try { Send (buf, 48); } catch (std::exception& ex) { LogPrint (eLogWarning, "SSU: exception while sending session destoroyed: ", ex.what ()); } LogPrint (eLogDebug, "SSU: session destroyed sent"); } } void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) { uint8_t buf[SSU_MTU_V4 + 18] = {0}; size_t msgSize = len + sizeof (SSUHeader); size_t paddingSize = msgSize & 0x0F; // %16 if (paddingSize > 0) msgSize += (16 - paddingSize); if (msgSize > SSU_MTU_V4) { LogPrint (eLogWarning, "SSU: payload size ", msgSize, " exceeds MTU"); return; } memcpy (buf + sizeof (SSUHeader), payload, len); // encrypt message with session key FillHeaderAndEncrypt (type, buf, msgSize); Send (buf, msgSize); } void SSUSession::Send (const uint8_t * buf, size_t size) { m_NumSentBytes += size; i2p::transport::transports.UpdateSentBytes (size); m_Server.Send (buf, size, m_RemoteEndpoint); } size_t SSUSession::ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port) { if (!len) return 0; uint8_t size = *buf; size_t s = 1 + size + 2; // size + address + port if (len < s) { LogPrint (eLogWarning, "SSU: Address is too short ", len); port = 0; return len; } buf++; // size if (size == 4) { boost::asio::ip::address_v4::bytes_type bytes; memcpy (bytes.data (), buf, 4); ip = boost::asio::ip::address_v4 (bytes); } else if (size == 16) { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), buf, 16); ip = boost::asio::ip::address_v6 (bytes); } else LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); buf += size; port = bufbe16toh (buf); return s; } } } i2pd-2.39.0/libi2pd/SSUSession.h000066400000000000000000000156621411072525600161740ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SSU_SESSION_H__ #define SSU_SESSION_H__ #include #include #include #include "Crypto.h" #include "I2NPProtocol.h" #include "TransportSession.h" #include "SSUData.h" namespace i2p { namespace transport { const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; struct SSUHeader { uint8_t mac[16]; uint8_t iv[16]; uint8_t flag; uint8_t time[4]; uint8_t GetPayloadType () const { return flag >> 4; }; bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; }; const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes const int SSU_CLOCK_SKEW = 60; // in seconds const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; const uint8_t PAYLOAD_TYPE_DATA = 6; const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; // extended options const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; enum SessionState { eSessionStateUnknown, eSessionStateIntroduced, eSessionStateEstablished, eSessionStateClosed, eSessionStateFailed }; enum PeerTestParticipant { ePeerTestParticipantUnknown = 0, ePeerTestParticipantAlice1, ePeerTestParticipantAlice2, ePeerTestParticipantBob, ePeerTestParticipantCharlie }; class SSUServer; class SSUSession: public TransportSession, public std::enable_shared_from_this { public: SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router = nullptr, bool peerTest = false); void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); ~SSUSession (); void Connect (); void WaitForConnect (); void Introduce (const i2p::data::RouterInfo::Introducer& introducer, std::shared_ptr to); // Alice to Charlie void WaitForIntroduction (); void Close (); void Done (); void Failed (); const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice SessionState GetState () const { return m_State; }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void SendKeepAlive (); uint32_t GetRelayTag () const { return m_RelayTag; }; const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers void FlushData (); private: boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); size_t GetSSUHeaderSize (const uint8_t * buf) const; void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (const uint8_t * buf, size_t len); void SendSessionRequest (); void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); void ProcessSessionConfirmed (const uint8_t * buf, size_t len); void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); void ProcessRelayResponse (const uint8_t * buf, size_t len); void ProcessRelayIntro (const uint8_t * buf, size_t len); void Established (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); void ProcessData (uint8_t * buf, size_t len); void SendSessionDestroyed (); void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key void Send (const uint8_t * buf, size_t size); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); void DecryptSessionKey (uint8_t * buf, size_t len); bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); void Reset (); static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size private: friend class SSUData; // TODO: change in later SSUServer& m_Server; const boost::asio::ip::udp::endpoint m_RemoteEndpoint; boost::asio::deadline_timer m_ConnectTimer; bool m_IsPeerTest; SessionState m_State; bool m_IsSessionKey; uint32_t m_RelayTag; // received from peer uint32_t m_SentRelayTag; // sent by us i2p::crypto::CBCEncryption m_SessionKeyEncryption; i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; i2p::data::RouterInfo::IntroKey m_IntroKey; uint32_t m_CreationTime; // seconds since epoch SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only std::map > m_RelayRequests; // nonce->Charlie std::shared_ptr m_DHKeysPair; // X - for client and Y - for server }; } } #endif i2pd-2.39.0/libi2pd/Signature.cpp000066400000000000000000000101761411072525600164450ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Log.h" #include "Signature.h" namespace i2p { namespace crypto { #if OPENSSL_EDDSA EDDSA25519Verifier::EDDSA25519Verifier (): m_Pkey (nullptr) { m_MDCtx = EVP_MD_CTX_create (); } EDDSA25519Verifier::~EDDSA25519Verifier () { EVP_MD_CTX_destroy (m_MDCtx); if (m_Pkey) EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); } #else EDDSA25519Verifier::EDDSA25519Verifier () { } EDDSA25519Verifier::~EDDSA25519Verifier () { } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); BN_CTX * ctx = BN_CTX_new (); m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[64]; SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); return GetEd25519 ()->Verify (m_PublicKey, digest, signature); } #endif EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { // expand key Ed25519::ExpandPrivateKey (signingPrivateKey, m_ExpandedPrivateKey); // generate and encode public key BN_CTX * ctx = BN_CTX_new (); auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); if (signingPublicKey && memcmp (m_PublicKeyEncoded, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { // keys don't match, it means older key with 0x1F LogPrint (eLogWarning, "Older EdDSA key detected"); m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0xDF; // drop third bit publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); } BN_CTX_free (ctx); } EDDSA25519SignerCompat::~EDDSA25519SignerCompat () { } void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } #if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): m_Fallback (nullptr) { m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); EVP_PKEY_free (m_Pkey); m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } else { m_MDCtx = EVP_MD_CTX_create (); EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); } } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; else { EVP_MD_CTX_destroy (m_MDCtx); EVP_PKEY_free (m_Pkey); } } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { if (m_Fallback) return m_Fallback->Sign (buf, len, signature); else { size_t l = 64; uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 EVP_DigestSign (m_MDCtx, sig, &l, buf, len); memcpy (signature, sig, 64); } } #endif } } i2pd-2.39.0/libi2pd/Signature.h000066400000000000000000000356311411072525600161150ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SIGNATURE_H__ #define SIGNATURE_H__ #include #include #include #include #include #include #include "Crypto.h" #include "Ed25519.h" #include "Gost.h" namespace i2p { namespace crypto { class Verifier { public: virtual ~Verifier () {}; virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0; virtual size_t GetPublicKeyLen () const = 0; virtual size_t GetSignatureLen () const = 0; virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; }; virtual void SetPublicKey (const uint8_t * signingKey) = 0; }; class Signer { public: virtual ~Signer () {}; virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; }; const size_t DSA_PUBLIC_KEY_LENGTH = 128; const size_t DSA_SIGNATURE_LENGTH = 40; const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2; class DSAVerifier: public Verifier { public: DSAVerifier () { m_PublicKey = CreateDSA (); } void SetPublicKey (const uint8_t * signingKey) { DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); } ~DSAVerifier () { DSA_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { // calculate SHA1 digest uint8_t digest[20]; SHA1 (buf, len, digest); // signature DSA_SIG * sig = DSA_SIG_new(); DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); // DSA verification int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); DSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; }; private: DSA * m_PublicKey; }; class DSASigner: public Signer { public: DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) // openssl 1.1 always requires DSA public key even for signing { m_PrivateKey = CreateDSA (); DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); } ~DSASigner () { DSA_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[20]; SHA1 (buf, len, digest); DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); const BIGNUM * r, * s; DSA_SIG_get0 (sig, &r, &s); bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); DSA_SIG_free(sig); } private: DSA * m_PrivateKey; }; inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { DSA * dsa = CreateDSA (); DSA_generate_key (dsa); const BIGNUM * pub_key, * priv_key; DSA_get0_key(dsa, &pub_key, &priv_key); bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); DSA_free (dsa); } struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA256 (buf, len, digest); } enum { hashLen = 32 }; }; struct SHA384Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA384 (buf, len, digest); } enum { hashLen = 48 }; }; struct SHA512Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA512 (buf, len, digest); } enum { hashLen = 64 }; }; // EcDSA template class ECDSAVerifier: public Verifier { public: ECDSAVerifier () { m_PublicKey = EC_KEY_new_by_curve_name (curve); } void SetPublicKey (const uint8_t * signingKey) { BIGNUM * x = BN_bin2bn (signingKey, keyLen/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL); EC_KEY_set_public_key_affine_coordinates (m_PublicKey, x, y); BN_free (x); BN_free (y); } ~ECDSAVerifier () { EC_KEY_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); ECDSA_SIG * sig = ECDSA_SIG_new(); auto r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); auto s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); ECDSA_SIG_set0(sig, r, s); // ECDSA verification int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); ECDSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return keyLen; }; size_t GetSignatureLen () const { return keyLen; }; // signature length = key length private: EC_KEY * m_PublicKey; }; template class ECDSASigner: public Signer { public: ECDSASigner (const uint8_t * signingPrivateKey) { m_PrivateKey = EC_KEY_new_by_curve_name (curve); EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL)); } ~ECDSASigner () { EC_KEY_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey); const BIGNUM * r, * s; ECDSA_SIG_get0 (sig, &r, &s); // signatureLen = keyLen bn2buf (r, signature, keyLen/2); bn2buf (s, signature + keyLen/2, keyLen/2); ECDSA_SIG_free(sig); } private: EC_KEY * m_PrivateKey; }; inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve); EC_KEY_generate_key (signingKey); bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2); BIGNUM * x = BN_new(), * y = BN_new(); EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey), EC_KEY_get0_public_key (signingKey), x, y, NULL); bn2buf (x, signingPublicKey, keyLen/2); bn2buf (y, signingPublicKey + keyLen/2, keyLen/2); BN_free (x); BN_free (y); EC_KEY_free (signingKey); } // ECDSA_SHA256_P256 const size_t ECDSAP256_KEY_LENGTH = 64; typedef ECDSAVerifier ECDSAP256Verifier; typedef ECDSASigner ECDSAP256Signer; inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA384_P384 const size_t ECDSAP384_KEY_LENGTH = 96; typedef ECDSAVerifier ECDSAP384Verifier; typedef ECDSASigner ECDSAP384Signer; inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA512_P521 const size_t ECDSAP521_KEY_LENGTH = 132; typedef ECDSAVerifier ECDSAP521Verifier; typedef ECDSASigner ECDSAP521Signer; inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // EdDSA class EDDSA25519Verifier: public Verifier { public: EDDSA25519Verifier (); void SetPublicKey (const uint8_t * signingKey); ~EDDSA25519Verifier (); bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: #if OPENSSL_EDDSA EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; #else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; #endif }; class EDDSA25519SignerCompat: public Signer { public: EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519SignerCompat (); void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; #if OPENSSL_EDDSA class EDDSA25519Signer: public Signer { public: EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519Signer (); void Sign (const uint8_t * buf, int len, uint8_t * signature) const; private: EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; #else typedef EDDSA25519SignerCompat EDDSA25519Signer; #endif inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { #if OPENSSL_EDDSA EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL); EVP_PKEY_keygen_init (pctx); EVP_PKEY_keygen (pctx, &pkey); EVP_PKEY_CTX_free (pctx); size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; EVP_PKEY_get_raw_public_key (pkey, signingPublicKey, &len); len = EDDSA25519_PRIVATE_KEY_LENGTH; EVP_PKEY_get_raw_private_key (pkey, signingPrivateKey, &len); EVP_PKEY_free (pkey); #else RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); EDDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); #endif } // ГОСТ Р 34.11 struct GOSTR3411_256_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_256 (buf, len, digest); } enum { hashLen = 32 }; }; struct GOSTR3411_512_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_512 (buf, len, digest); } enum { hashLen = 64 }; }; // ГОСТ Р 34.10 const size_t GOSTR3410_256_PUBLIC_KEY_LENGTH = 64; const size_t GOSTR3410_512_PUBLIC_KEY_LENGTH = 128; template class GOSTR3410Verifier: public Verifier { public: enum { keyLen = Hash::hashLen }; GOSTR3410Verifier (GOSTR3410ParamSet paramSet): m_ParamSet (paramSet), m_PublicKey (nullptr) { } void SetPublicKey (const uint8_t * signingKey) { BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL); m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y); BN_free (x); BN_free (y); } ~GOSTR3410Verifier () { if (m_PublicKey) EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); BIGNUM * s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); bool ret = GetGOSTR3410Curve (m_ParamSet)->Verify (m_PublicKey, d, r, s); BN_free (d); BN_free (r); BN_free (s); return ret; } size_t GetPublicKeyLen () const { return keyLen*2; } size_t GetSignatureLen () const { return keyLen*2; } private: GOSTR3410ParamSet m_ParamSet; EC_POINT * m_PublicKey; }; template class GOSTR3410Signer: public Signer { public: enum { keyLen = Hash::hashLen }; GOSTR3410Signer (GOSTR3410ParamSet paramSet, const uint8_t * signingPrivateKey): m_ParamSet (paramSet) { m_PrivateKey = BN_bin2bn (signingPrivateKey, keyLen, nullptr); } ~GOSTR3410Signer () { BN_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_new (), * s = BN_new (); GetGOSTR3410Curve (m_ParamSet)->Sign (m_PrivateKey, d, r, s); bn2buf (r, signature, keyLen); bn2buf (s, signature + keyLen, keyLen); BN_free (d); BN_free (r); BN_free (s); } private: GOSTR3410ParamSet m_ParamSet; BIGNUM * m_PrivateKey; }; inline void CreateGOSTR3410RandomKeys (GOSTR3410ParamSet paramSet, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { const auto& curve = GetGOSTR3410Curve (paramSet); auto keyLen = curve->GetKeyLen (); RAND_bytes (signingPrivateKey, keyLen); BIGNUM * priv = BN_bin2bn (signingPrivateKey, keyLen, nullptr); auto pub = curve->MulP (priv); BN_free (priv); BIGNUM * x = BN_new (), * y = BN_new (); curve->GetXY (pub, x, y); EC_POINT_free (pub); bn2buf (x, signingPublicKey, keyLen); bn2buf (y, signingPublicKey + keyLen, keyLen); BN_free (x); BN_free (y); } typedef GOSTR3410Verifier GOSTR3410_256_Verifier; typedef GOSTR3410Signer GOSTR3410_256_Signer; typedef GOSTR3410Verifier GOSTR3410_512_Verifier; typedef GOSTR3410Signer GOSTR3410_512_Signer; // RedDSA typedef EDDSA25519Verifier RedDSA25519Verifier; class RedDSA25519Signer: public Signer { public: RedDSA25519Signer (const uint8_t * signingPrivateKey) { memcpy (m_PrivateKey, signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); BN_CTX * ctx = BN_CTX_new (); auto publicKey = GetEd25519 ()->GeneratePublicKey (m_PrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); } ~RedDSA25519Signer () {}; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature); } const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: uint8_t m_PrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; inline void CreateRedDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { GetEd25519 ()->CreateRedDSAPrivateKey (signingPrivateKey); RedDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); } } } #endif i2pd-2.39.0/libi2pd/Siphash.h000066400000000000000000000054701411072525600155510ustar00rootroot00000000000000/** * This code is licensed under the MCGSI Public License * Copyright 2018 Jeff Becker * * Kovri go write your own code * */ #ifndef SIPHASH_H #define SIPHASH_H #include #include "Crypto.h" #if !OPENSSL_SIPHASH namespace i2p { namespace crypto { namespace siphash { constexpr int crounds = 2; constexpr int drounds = 4; inline uint64_t rotl(const uint64_t & x, int b) { uint64_t ret = x << b; ret |= x >> (64 - b); return ret; } inline void u32to8le(const uint32_t & v, uint8_t * p) { p[0] = (uint8_t) v; p[1] = (uint8_t) (v >> 8); p[2] = (uint8_t) (v >> 16); p[3] = (uint8_t) (v >> 24); } inline void u64to8le(const uint64_t & v, uint8_t * p) { p[0] = v & 0xff; p[1] = (v >> 8) & 0xff; p[2] = (v >> 16) & 0xff; p[3] = (v >> 24) & 0xff; p[4] = (v >> 32) & 0xff; p[5] = (v >> 40) & 0xff; p[6] = (v >> 48) & 0xff; p[7] = (v >> 56) & 0xff; } inline uint64_t u8to64le(const uint8_t * p) { uint64_t i = 0; int idx = 0; while(idx < 8) { i |= ((uint64_t) p[idx]) << (idx * 8); ++idx; } return i; } inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3) { _v0 += _v1; _v1 = rotl(_v1, 13); _v1 ^= _v0; _v0 = rotl(_v0, 32); _v2 += _v3; _v3 = rotl(_v3, 16); _v3 ^= _v2; _v0 += _v3; _v3 = rotl(_v3, 21); _v3 ^= _v0; _v2 += _v1; _v1 = rotl(_v1, 17); _v1 ^= _v2; _v2 = rotl(_v2, 32); } } /** hashsz must be 8 or 16 */ template inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key) { uint64_t v0 = 0x736f6d6570736575ULL; uint64_t v1 = 0x646f72616e646f6dULL; uint64_t v2 = 0x6c7967656e657261ULL; uint64_t v3 = 0x7465646279746573ULL; const uint64_t k0 = siphash::u8to64le(key); const uint64_t k1 = siphash::u8to64le(key + 8); uint64_t msg; int i; const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t)); auto left = bufsz & 7; uint64_t b = ((uint64_t)bufsz) << 56; v3 ^= k1; v2 ^= k0; v1 ^= k1; v0 ^= k0; if(hashsz == 16) v1 ^= 0xee; while(buf != end) { msg = siphash::u8to64le(buf); v3 ^= msg; for(i = 0; i < siphash::crounds; ++i) siphash::round(v0, v1, v2, v3); v0 ^= msg; buf += 8; } while(left) { --left; b |= ((uint64_t)(buf[left])) << (left * 8); } v3 ^= b; for(i = 0; i < siphash::crounds; ++i) siphash::round(v0, v1, v2, v3); v0 ^= b; if(hashsz == 16) v2 ^= 0xee; else v2 ^= 0xff; for(i = 0; i < siphash::drounds; ++i) siphash::round(v0, v1, v2, v3); b = v0 ^ v1 ^ v2 ^ v3; siphash::u64to8le(b, h); if(hashsz == 8) return; v1 ^= 0xdd; for (i = 0; i < siphash::drounds; ++i) siphash::round(v0, v1, v2, v3); b = v0 ^ v1 ^ v2 ^ v3; siphash::u64to8le(b, h + 8); } } } #endif #endif i2pd-2.39.0/libi2pd/Streaming.cpp000066400000000000000000001220621411072525600164330ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 "Crypto.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 { void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) { Add (std::make_shared(buf, len, handler)); } void SendBufferQueue::Add (std::shared_ptr buf) { if (buf) { m_Buffers.push_back (buf); m_Size += buf->len; } } size_t SendBufferQueue::Get (uint8_t * buf, size_t len) { size_t offset = 0; while (!m_Buffers.empty () && offset < len) { auto nextBuffer = m_Buffers.front (); auto rem = nextBuffer->GetRemainingSize (); if (offset + rem <= len) { // whole buffer memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); offset += rem; m_Buffers.pop_front (); // delete it } else { // partially rem = len - offset; memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); nextBuffer->offset += (len - offset); offset = len; // break } } m_Size -= offset; return offset; } void SendBufferQueue::CleanUp () { if (!m_Buffers.empty ()) { for (auto it: m_Buffers) it->Cancel (); m_Buffers.clear (); m_Size = 0; } } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); } Stream::~Stream () { CleanUp (); LogPrint (eLogDebug, "Streaming: Stream deleted"); } void Stream::Terminate (bool deleteFromDestination) // shoudl be called from StreamingDestination::Stop only { m_Status = eStreamStatusTerminated; m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); //CleanUp (); /* Need to recheck - broke working on windows */ if (deleteFromDestination) m_LocalDestination.DeleteStream (shared_from_this ()); } void Stream::CleanUp () { { std::unique_lock l(m_SendBufferMutex); m_SendBuffer.CleanUp (); } while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } for (auto it: m_SentPackets) m_LocalDestination.DeletePacket (it); m_SentPackets.clear (); for (auto it: m_SavedPackets) m_LocalDestination.DeletePacket (it); m_SavedPackets.clear (); } 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 (eLogDebug, "Streaming: Plain ACK received"); m_LocalDestination.DeletePacket (packet); return; } LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn, " on sSID=", m_SendStreamID); if (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_Status == eStreamStatusOpen) { if (!m_IsAckSendScheduled) { m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } else if (isSyn) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } else { if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); SendQuickAck (); // resend ack for previous message again m_LocalDestination.DeletePacket (packet); // packet dropped } else { LogPrint (eLogWarning, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) { // send NACKs for missing messages ASAP if (m_IsAckSendScheduled) { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } SendQuickAck (); } else { // wait for SYN m_IsAckSendScheduled = true; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } } } void Stream::SavePacket (Packet * packet) { if (!m_SavedPackets.insert (packet).second) m_LocalDestination.DeletePacket (packet); } void Stream::ProcessPacket (Packet * packet) { uint32_t receivedSeqn = packet->GetSeqn (); uint16_t flags = packet->GetFlags (); LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags); if (!ProcessOptions (flags, packet)) { m_LocalDestination.DeletePacket (packet); Terminate (); return; } packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) { m_ReceiveQueue.push (packet); m_ReceiveTimer.cancel (); } else m_LocalDestination.DeletePacket (packet); m_LastReceivedSequenceNumber = receivedSeqn; if (flags & PACKET_FLAG_RESET) { LogPrint (eLogDebug, "Streaming: closing stream sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ": reset flag received in packet #", receivedSeqn); m_Status = eStreamStatusReset; Close (); } else if (flags & PACKET_FLAG_CLOSE) { if (m_Status != eStreamStatusClosed) SendClose (); m_Status = eStreamStatusClosed; Terminate (); } } bool Stream::ProcessOptions (uint16_t flags, Packet * packet) { const uint8_t * optionData = packet->GetOptionData (); size_t optionSize = packet->GetOptionSize (); if (flags & PACKET_FLAG_DELAY_REQUESTED) { if (!m_IsAckSendScheduled) { uint16_t delayRequested = bufbe16toh (optionData); if (delayRequested > 0 && delayRequested < m_RTT) { m_IsAckSendScheduled = true; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } optionData += 2; } if (flags & PACKET_FLAG_FROM_INCLUDED) { if (m_RemoteLeaseSet) m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); if (!m_RemoteIdentity) m_RemoteIdentity = std::make_shared(optionData, optionSize); if (m_RemoteIdentity->IsRSA ()) { LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); return false; } optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) { uint16_t maxPacketSize = bufbe16toh (optionData); LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize); optionData += 2; } if (flags & PACKET_FLAG_OFFLINE_SIGNATURE) { if (!m_RemoteIdentity) { LogPrint (eLogInfo, "Streaming: offline signature without identity"); return false; } // if we have it in LeaseSet already we don't need to parse it again if (m_RemoteLeaseSet) m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); if (m_TransientVerifier) { // skip option data optionData += 6; // timestamp and key type optionData += m_TransientVerifier->GetPublicKeyLen (); // public key optionData += m_RemoteIdentity->GetSignatureLen (); // signature } else { // transient key size_t offset = 0; m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); optionData += offset; if (!m_TransientVerifier) { LogPrint (eLogError, "Streaming: offline signature failed"); return false; } } } if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { uint8_t signature[256]; auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); bool verified = m_TransientVerifier ? m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); if (!verified) { LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); Close (); flags |= PACKET_FLAG_CLOSE; } memcpy (const_cast(optionData), signature, signatureLen); optionData += signatureLen; } else { LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); return false; } } return true; } void Stream::HandlePing (Packet * packet) { uint16_t flags = packet->GetFlags (); if (ProcessOptions (flags, packet) && m_RemoteIdentity) { // send pong Packet p; memset (p.buf, 0, 22); // minimal header all zeroes memcpy (p.buf + 4, packet->buf, 4); // but receiveStreamID is the sendStreamID from the ping htobe16buf (p.buf + 18, PACKET_FLAG_ECHO); // and echo flag ssize_t payloadLen = packet->len - (packet->GetPayload () - packet->buf); if (payloadLen > 0) memcpy (p.buf + 22, packet->GetPayload (), payloadLen); else payloadLen = 0; p.len = payloadLen + 22; SendPackets (std::vector { &p }); LogPrint (eLogDebug, "Streaming: Pong of ", p.len, " bytes sent"); } m_LocalDestination.DeletePacket (packet); } void Stream::ProcessAck (Packet * packet) { bool acknowledged = false; auto ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t ackThrough = packet->GetAckThrough (); if (ackThrough > m_SequenceNumber) { LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber); return; } 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 (eLogDebug, "Streaming: Packet ", seqn, " NACK"); ++it; continue; } } auto sentPacket = *it; uint64_t rtt = ts - sentPacket->sendTime; if(ts < sentPacket->sendTime) { LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); rtt = 1; } m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; if (m_WindowSize < WINDOW_SIZE) m_WindowSize++; // slow start else { // linear growth if (ts > m_LastWindowSizeIncreaseTime + m_RTT) { m_WindowSize++; if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; m_LastWindowSizeIncreaseTime = ts; } } if (!seqn && m_RoutingSession) // first message confirmed m_RoutingSession->SetSharedRoutingPath ( std::make_shared ( i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0})); } else break; } if (m_SentPackets.empty ()) m_ResendTimer.cancel (); if (acknowledged) { m_NumResendAttempts = 0; SendBuffer (); } if (m_Status == eStreamStatusClosed) Terminate (); else if (m_Status == eStreamStatusClosing) Close (); // check is all outgoing messages have been sent and we can send close } size_t Stream::Send (const uint8_t * buf, size_t len) { AsyncSend (buf, len, nullptr); return len; } void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) { if (len > 0 && buf) { std::unique_lock l(m_SendBufferMutex); m_SendBuffer.Add (buf, len, handler); } else if (handler) handler(boost::system::error_code ()); m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } void Stream::SendBuffer () { int numMsgs = m_WindowSize - m_SentPackets.size (); if (numMsgs <= 0) return; // window is full bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; { std::unique_lock l(m_SendBufferMutex); while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) { Packet * p = m_LocalDestination.NewPacket (); uint8_t * packet = p->GetBuffer (); // TODO: implement setters size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum if (isNoAck) htobuf32 (packet + size, 0); else htobe32buf (packet + size, m_LastReceivedSequenceNumber); size += 4; // ack Through packet[size] = 0; size++; // NACK count packet[size] = m_RTO/1000; size++; // resend delay if (m_Status == eStreamStatusNew) { // initial packet m_Status = eStreamStatusOpen; if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; if (m_RemoteLeaseSet) { m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; } 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; bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; htobe16buf (packet + size, flags); size += 2; // flags size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); uint8_t * optionsSize = packet + size; // set options size later size += 2; // options size m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from htobe16buf (packet + size, m_MTU); size += 2; // max packet size if (isOfflineSignature) { const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); size += offlineSignature.size (); // offline signature } uint8_t * signature = packet + size; // set it later memset (signature, 0, signatureLen); // zeroes for now size += signatureLen; // signature htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size size += m_SendBuffer.Get (packet + size, m_MTU); // payload m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } else { // follow on packet htobuf16 (packet + size, 0); size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size size += m_SendBuffer.Get(packet + size, m_MTU); // payload } p->len = size; packets.push_back (p); numMsgs--; } } if (packets.size () > 0) { if (m_SavedPackets.empty ()) // no NACKS { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } bool isEmpty = m_SentPackets.empty (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto& it: packets) { it->sendTime = ts; m_SentPackets.insert (it); } SendPackets (packets); if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) SendClose (); if (isEmpty) ScheduleResend (); } } void Stream::SendQuickAck () { int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber; if (!m_SavedPackets.empty ()) { int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn (); if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn; } if (lastReceivedSeqn < 0) { LogPrint (eLogError, "Streaming: No packets have been received yet"); return; } Packet p; uint8_t * packet = p.GetBuffer (); size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobuf32 (packet + size, 0); // this is plain Ack message size += 4; // sequenceNum htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs uint8_t * nacks = packet + size + 1; auto nextSeqn = m_LastReceivedSequenceNumber + 1; for (auto it: m_SavedPackets) { auto seqn = it->GetSeqn (); if (numNacks + (seqn - nextSeqn) >= 256) { LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); htobe32buf (packet + 12, nextSeqn); // change ack Through break; } for (uint32_t i = nextSeqn; i < seqn; i++) { htobe32buf (nacks, i); nacks += 4; numNacks++; } nextSeqn = seqn + 1; } packet[size] = numNacks; size++; // NACK count size += numNacks*4; // NACKs } else { // No NACKs packet[size] = 0; size++; // NACK count } packet[size] = 0; size++; // resend delay htobuf16 (packet + size, 0); // no flags set size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size p.len = size; SendPackets (std::vector { &p }); LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } void Stream::Close () { LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); switch (m_Status) { case eStreamStatusOpen: m_Status = eStreamStatusClosing; Close (); // recursion if (m_Status == eStreamStatusClosing) //still closing LogPrint (eLogDebug, "Streaming: Trying to send stream data before closing, sSID=", m_SendStreamID); break; case eStreamStatusReset: // TODO: send reset Terminate (); break; case eStreamStatusClosing: if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) // nothing to send { m_Status = eStreamStatusClosed; SendClose(); } break; case eStreamStatusClosed: // already closed Terminate (); break; default: LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); }; } void Stream::SendClose () { Packet * p = m_LocalDestination.NewPacket (); uint8_t * packet = p->GetBuffer (); size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); size += 4; // ack Through packet[size] = 0; size++; // NACK count packet[size] = 0; size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); htobe16buf (packet + size, 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; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } 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 (); m_LocalDestination.DeletePacket (packet); } } return pos; } bool Stream::SendPacket (Packet * packet) { if (packet) { if (m_IsAckSendScheduled) { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } SendPackets (std::vector { packet }); bool isEmpty = m_SentPackets.empty (); m_SentPackets.insert (packet); if (isEmpty) ScheduleResend (); return true; } else return false; } void Stream::SendPackets (const std::vector& packets) { if (!m_RemoteLeaseSet) { UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet, sSID=", m_SendStreamID); return; } } if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first auto routingPath = m_RoutingSession->GetSharedRoutingPath (); if (routingPath) { m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; m_RTO = m_RTT*1.5; // TODO: implement it better } } if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); return; } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { std::vector msgs; for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, msg }); m_NumSentBytes += it->GetLength (); } m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else { LogPrint (eLogWarning, "Streaming: Remote lease is not available, sSID=", m_SendStreamID); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); // invalidate routing path } } void Stream::SendUpdatedLeaseSet () { if (m_RoutingSession && !m_RoutingSession->IsTerminated ()) { if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; SendQuickAck (); } } else if (m_RoutingSession->IsLeaseSetUpdated ()) { LogPrint (eLogDebug, "Streaming: sending updated LeaseSet"); SendQuickAck (); } } else SendQuickAck (); } void Stream::ScheduleResend () { if (m_Status != eStreamStatusTerminated) { m_ResendTimer.cancel (); // check for invalid value if (m_RTO <= 0) m_RTO = INITIAL_RTO; m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); } } void Stream::HandleResendTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; } // collect packets to resend auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector packets; for (auto it : m_SentPackets) { if (ts >= it->sendTime + m_RTO) { it->sendTime = ts; packets.push_back (it); } } // select tunnels if necessary and send if (packets.size () > 0) { m_NumResendAttempts++; m_RTO *= 2; switch (m_NumResendAttempts) { case 1: // congesion avoidance m_WindowSize >>= 1; // /2 if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; break; case 2: m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif // no break here case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); break; case 3: // pick another outbound tunnel if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); break; default: ; } SendPackets (packets); } ScheduleResend (); } } void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) { if (m_LastReceivedSequenceNumber < 0) { LogPrint (eLogWarning, "Streaming: SYN has not been received after ", SYN_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; } if (m_Status == eStreamStatusOpen) { if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ()) { // seems something went wrong and we should re-select tunnels m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; } SendQuickAck (); } m_IsAckSendScheduled = false; } } void Stream::UpdateCurrentRemoteLease (bool expired) { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!remoteLeaseSet) { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) { m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( std::make_shared(m_RemoteIdentity)); return; // we keep m_RemoteLeaseSet for possible next request } else { m_RemoteLeaseSet = nullptr; m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt } } else { // LeaseSet updated m_RemoteLeaseSet = remoteLeaseSet; m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); } } if (m_RemoteLeaseSet) { if (!m_RoutingSession) m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first if (leases.empty ()) { expired = false; // time to request if (m_RemoteLeaseSet->IsPublishedEncrypted ()) m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( std::make_shared(m_RemoteIdentity)); else m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) { bool updated = false; if (expired && m_CurrentRemoteLease) { for (const auto& it: leases) if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID)) { m_CurrentRemoteLease = it; updated = true; break; } } if (!updated) { uint32_t i = rand () % leases.size (); if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) // make sure we don't select previous i = (i + 1) % leases.size (); // if so, pick next m_CurrentRemoteLease = leases[i]; } } else { LogPrint (eLogWarning, "Streaming: All remote leases are expired"); m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; // we have requested expired before, no need to do it twice } } else { LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; } } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_PendingIncomingTimer (m_Owner->GetService ()) { } StreamingDestination::~StreamingDestination () { for (auto& it: m_SavedPackets) { for (auto it1: it.second) DeletePacket (it1); it.second.clear (); } m_SavedPackets.clear (); } void StreamingDestination::Start () { } void StreamingDestination::Stop () { ResetAcceptor (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); { std::unique_lock l(m_StreamsMutex); for (auto it: m_Streams) it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); m_LastStream = nullptr; } } void StreamingDestination::HandleNextPacket (Packet * packet) { uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { if (!m_LastStream || sendStreamID != m_LastStream->GetRecvStreamID ()) { auto it = m_Streams.find (sendStreamID); if (it != m_Streams.end ()) m_LastStream = it->second; else m_LastStream = nullptr; } if (m_LastStream) m_LastStream->HandleNextPacket (packet); else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) { // ping LogPrint (eLogInfo, "Streaming: Ping received sSID=", sendStreamID); auto s = std::make_shared (m_Owner->GetService (), *this); s->HandlePing (packet); } else { LogPrint (eLogInfo, "Streaming: Unknown stream sSID=", sendStreamID); DeletePacket (packet); } } else { if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); auto it1 = m_IncomingStreams.find (receiveStreamID); if (it1 != m_IncomingStreams.end ()) { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); DeletePacket (packet); // drop it, because previous should be connected return; } auto incomingStream = CreateNewIncomingStream (receiveStreamID); incomingStream->HandleNextPacket (packet); // SYN auto ident = incomingStream->GetRemoteIdentity(); // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) { LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for rSID=", receiveStreamID); for (auto it1: it->second) incomingStream->HandleNextPacket (it1); m_SavedPackets.erase (it); } } // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); else { LogPrint (eLogWarning, "Streaming: Acceptor for incoming stream is not set"); if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG) { m_PendingIncomingStreams.push_back (incomingStream); m_PendingIncomingTimer.cancel (); m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, shared_from_this (), std::placeholders::_1)); LogPrint (eLogDebug, "Streaming: Pending incoming stream added, rSID=", receiveStreamID); } else { LogPrint (eLogWarning, "Streaming: Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); incomingStream->Close (); } } } else // follow on packet without SYN { uint32_t receiveStreamID = packet->GetReceiveStreamID (); auto it1 = m_IncomingStreams.find (receiveStreamID); if (it1 != m_IncomingStreams.end ()) { // found it1->second->HandleNextPacket (packet); return; } // save follow on packet auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) it->second.push_back (packet); else { m_SavedPackets[receiveStreamID] = std::list{ packet }; auto timer = std::make_shared (m_Owner->GetService ()); timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); auto s = shared_from_this (); timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto it = s->m_SavedPackets.find (receiveStreamID); if (it != s->m_SavedPackets.end ()) { for (auto it1: it->second) s->DeletePacket (it1); it->second.clear (); s->m_SavedPackets.erase (it); } } }); } } } } std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); m_Streams.emplace (s->GetRecvStreamID (), s); return s; } std::shared_ptr StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID) { auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); m_Streams.emplace (s->GetRecvStreamID (), s); m_IncomingStreams.emplace (receiveStreamID, s); return s; } void StreamingDestination::DeleteStream (std::shared_ptr stream) { if (stream) { std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); m_IncomingStreams.erase (stream->GetSendStreamID ()); if (m_LastStream == stream) m_LastStream = nullptr; } } bool StreamingDestination::DeleteStream (uint32_t recvStreamID) { auto it = m_Streams.find (recvStreamID); if (it == m_Streams.end ()) return false; DeleteStream (it->second); return true; } void StreamingDestination::SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet auto s = shared_from_this (); m_Owner->GetService ().post([s](void) { // take care about incoming queue for (auto& it: s->m_PendingIncomingStreams) if (it->GetStatus () == eStreamStatusOpen) // still open? s->m_Acceptor (it); s->m_PendingIncomingStreams.clear (); s->m_PendingIncomingTimer.cancel (); }); } void StreamingDestination::ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; } void StreamingDestination::AcceptOnce (const Acceptor& acceptor) { m_Owner->GetService ().post([acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) { acceptor (m_PendingIncomingStreams.front ()); m_PendingIncomingStreams.pop_front (); if (m_PendingIncomingStreams.empty ()) m_PendingIncomingTimer.cancel (); } else // we must save old acceptor and set it back { m_Acceptor = std::bind (&StreamingDestination::AcceptOnceAcceptor, this, std::placeholders::_1, acceptor, m_Acceptor); } }); } void StreamingDestination::AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev) { m_Acceptor = prev; acceptor (stream); } void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired"); for (auto& it: m_PendingIncomingStreams) it->Close (); m_PendingIncomingStreams.clear (); } } void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // unzip it Packet * uncompressed = NewPacket (); uncompressed->offset = 0; uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE); if (uncompressed->len) HandleNextPacket (uncompressed); else DeletePacket (uncompressed); } std::shared_ptr StreamingDestination::CreateDataMessage ( const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; size_t size = (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)? i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len): m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, m_LocalPort); // source port htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size; msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } else msg = nullptr; return msg; } } } i2pd-2.39.0/libi2pd/Streaming.h000066400000000000000000000325641411072525600161070ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 STREAMING_H__ #define STREAMING_H__ #include #include #include #include #include #include #include #include #include #include "Base.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" #include "Garlic.h" #include "Tunnel.h" #include "util.h" // MemoryPool 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 uint16_t PACKET_FLAG_OFFLINE_SIGNATURE = 0x0800; const size_t STREAMING_MTU = 1730; const size_t STREAMING_MTU_RATCHETS = 1812; const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; const int MAX_NUM_RESEND_ATTEMPTS = 6; const int WINDOW_SIZE = 6; // in messages const int MIN_WINDOW_SIZE = 1; const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds const int MAX_RECEIVE_TIMEOUT = 20; // in seconds struct Packet { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; uint64_t sendTime; Packet (): len (0), offset (0), sendTime (0) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len - offset; }; uint32_t GetSendStreamID () const { return bufbe32toh (buf); }; uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); }; uint32_t GetSeqn () const { return bufbe32toh (buf + 8); }; uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); }; uint8_t GetNACKCount () const { return buf[16]; }; uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); }; const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); }; uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); }; const uint8_t * GetOptionData () const { return GetOption () + 2; }; const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); }; bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; }; bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; }; bool IsEcho () const { return GetFlags () & PACKET_FLAG_ECHO; }; }; struct PacketCmp { bool operator() (const Packet * p1, const Packet * p2) const { return p1->GetSeqn () < p2->GetSeqn (); }; }; typedef std::function SendHandler; struct SendBuffer { uint8_t * buf; size_t len, offset; SendHandler handler; SendBuffer (const uint8_t * b, size_t l, SendHandler h): len(l), offset (0), handler(h) { buf = new uint8_t[len]; memcpy (buf, b, len); } SendBuffer (size_t l): // creat empty buffer len(l), offset (0) { buf = new uint8_t[len]; } ~SendBuffer () { delete[] buf; if (handler) handler(boost::system::error_code ()); } size_t GetRemainingSize () const { return len - offset; }; const uint8_t * GetRemaningBuffer () const { return buf + offset; }; void Cancel () { if (handler) handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); handler = nullptr; }; }; class SendBufferQueue { public: SendBufferQueue (): m_Size (0) {}; ~SendBufferQueue () { CleanUp (); }; void Add (const uint8_t * buf, size_t len, SendHandler handler); void Add (std::shared_ptr buf); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; void CleanUp (); private: std::list > m_Buffers; size_t m_Size; }; enum StreamStatus { eStreamStatusNew = 0, eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, eStreamStatusClosed, eStreamStatusTerminated }; class StreamingDestination; class Stream: public std::enable_shared_from_this { public: Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; std::shared_ptr GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; StreamStatus GetStatus () const { return m_Status; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; void HandleNextPacket (Packet * packet); void HandlePing (Packet * packet); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); }; /** only call close from destination thread, use Stream::AsyncClose for other threads */ void Close (); void Cancel () { m_ReceiveTimer.cancel (); }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; size_t GetSendQueueSize () const { return m_SentPackets.size (); }; size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); }; size_t GetSendBufferSize () const { return m_SendBuffer.GetSize (); }; int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; void Terminate (bool deleteFromDestination = true); private: void CleanUp (); void SendBuffer (); void SendQuickAck (); void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); void SendUpdatedLeaseSet (); void SavePacket (Packet * packet); void ProcessPacket (Packet * packet); bool ProcessOptions (uint16_t flags, Packet * packet); void ProcessAck (Packet * packet); size_t ConcatenatePackets (uint8_t * buf, size_t len); void UpdateCurrentRemoteLease (bool expired = false); template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode); private: boost::asio::io_service& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; int32_t m_LastReceivedSequenceNumber; StreamStatus m_Status; bool m_IsAckSendScheduled; StreamingDestination& m_LocalDestination; std::shared_ptr m_RemoteIdentity; std::shared_ptr m_TransientVerifier; // in case of offline key std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; std::shared_ptr m_CurrentRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; std::set m_SentPackets; boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; std::mutex m_SendBufferMutex; SendBufferQueue m_SendBuffer; int m_WindowSize, m_RTT, m_RTO, m_AckDelay; uint64_t m_LastWindowSizeIncreaseTime; int m_NumResendAttempts; size_t m_MTU; }; class StreamingDestination: public std::enable_shared_from_this { public: typedef std::function)> Acceptor; StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); void Stop (); std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); void DeleteStream (std::shared_ptr stream); bool DeleteStream (uint32_t recvStreamID); void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; void AcceptOnce (const Acceptor& acceptor); void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); std::shared_ptr GetOwner () const { return m_Owner; }; void SetOwner (std::shared_ptr owner) { m_Owner = owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } private: void HandleNextPacket (Packet * packet); std::shared_ptr CreateNewIncomingStream (uint32_t receiveStreamID); void HandlePendingIncomingTimer (const boost::system::error_code& ecode); private: std::shared_ptr m_Owner; uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; std::unordered_map > m_Streams; // sendStreamID->stream std::unordered_map > m_IncomingStreams; // receiveStreamID->stream std::shared_ptr m_LastStream; Acceptor m_Acceptor; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; i2p::util::MemoryPool > m_I2NPMsgsPool; public: i2p::data::GzipInflator m_Inflator; i2p::data::GzipDeflator m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; }; //------------------------------------------------- template void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout) { auto s = shared_from_this(); m_Service.post ([s, buffer, handler, timeout](void) { if (!s->m_ReceiveQueue.empty () || s->m_Status == eStreamStatusReset) s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0); else { int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); int left = timeout - t; auto self = s->shared_from_this(); self->m_ReceiveTimer.async_wait ( [self, buffer, handler, left](const boost::system::error_code & ec) { self->HandleReceiveTimer(ec, buffer, handler, left); }); } }); } template void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); if (received > 0) handler (boost::system::error_code (), received); else if (ecode == boost::asio::error::operation_aborted) { // timeout not expired if (m_Status == eStreamStatusReset) handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0); else handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0); } else { // timeout expired if (remainingTimeout <= 0) handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); else { // itermediate iterrupt SendUpdatedLeaseSet (); // send our leaseset if applicable AsyncReceive (buffer, handler, remainingTimeout); } } } } } #endif i2pd-2.39.0/libi2pd/Tag.h000066400000000000000000000047061411072525600146660ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TAG_H__ #define TAG_H__ /* * Copyright (c) 2013-2017, 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 #include #include #include "Base.h" namespace i2p { namespace data { template class Tag { BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); public: Tag () = default; Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } bool operator!= (const Tag& other) const { return !(*this == other); } bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } 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 uint8_t * data() const { return m_Buf; } const uint64_t * GetLL () const { return ll; } bool IsZero () const { for (size_t i = 0; i < sz/8; ++i) if (ll[i]) return false; return true; } void Fill(uint8_t c) { memset(m_Buf, c, sz); } void Randomize() { RAND_bytes(m_Buf, sz); } std::string ToBase64 () const { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); return std::string (str, str + l); } std::string ToBase32 () const { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); return std::string (str, str + l); } size_t FromBase32 (const std::string& s) { return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); } size_t FromBase64 (const std::string& s) { return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); } private: union // 8 bytes aligned { uint8_t m_Buf[sz]; uint64_t ll[sz/8]; }; }; } // data } // i2p namespace std { // hash for std::unordered_map template struct hash > { size_t operator()(const i2p::data::Tag& s) const { return s.GetLL ()[0]; } }; } #endif /* TAG_H__ */ i2pd-2.39.0/libi2pd/Timestamp.cpp000066400000000000000000000141561411072525600164510ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 #include #include #include #include #include #include #include #include "Config.h" #include "Log.h" #include "RouterContext.h" #include "I2PEndian.h" #include "Timestamp.h" #include "util.h" #ifdef _WIN32 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif #endif namespace i2p { namespace util { static uint64_t GetLocalMillisecondsSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } static uint64_t GetLocalSecondsSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } static uint32_t GetLocalMinutesSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } static uint32_t GetLocalHoursSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } static int64_t g_TimeOffset = 0; // in seconds static void SyncTimeWithNTP (const std::string& address) { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); boost::asio::io_service service; boost::system::error_code ec; auto it = boost::asio::ip::udp::resolver (service).resolve ( boost::asio::ip::udp::resolver::query (address, "ntp"), ec); if (!ec) { bool found = false; boost::asio::ip::udp::resolver::iterator end; boost::asio::ip::udp::endpoint ep; while (it != end) { ep = *it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) { if (i2p::context.SupportsV4 ()) found = true; } else if (ep.address ().is_v6 ()) { if (i2p::util::net::IsYggdrasilAddress (ep.address ())) { if (i2p::context.SupportsMesh ()) found = true; } else if (i2p::context.SupportsV6 ()) found = true; } } if (found) break; it++; } if (!found) { LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); return; } boost::asio::ip::udp::socket socket (service); socket.open (ep.protocol (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response memset (buf, 0, 48); htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330 size_t len = 0; try { socket.send_to (boost::asio::buffer (buf, 48), ep); int i = 0; while (!socket.available() && i < 10) // 10 seconds max { std::this_thread::sleep_for (std::chrono::seconds(1)); i++; } if (socket.available ()) len = socket.receive_from (boost::asio::buffer (buf, 48), ep); } catch (std::exception& e) { LogPrint (eLogError, "Timestamp: NTP error: ", e.what ()); } if (len >= 8) { auto ourTs = GetLocalSecondsSinceEpoch (); uint32_t ts = bufbe32toh (buf + 32); if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900 g_TimeOffset = ts - ourTs; LogPrint (eLogInfo, "Timestamp: ", address, " time offset from system time is ", g_TimeOffset, " seconds"); } } else LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } else LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); } NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) { i2p::config::GetOption("nettime.ntpsyncinterval", m_SyncInterval); std::string ntpservers; i2p::config::GetOption("nettime.ntpservers", ntpservers); boost::split (m_NTPServersList, ntpservers, boost::is_any_of(","), boost::token_compress_on); } NTPTimeSync::~NTPTimeSync () { Stop (); } void NTPTimeSync::Start() { if (m_NTPServersList.size () > 0) { m_IsRunning = true; LogPrint(eLogInfo, "Timestamp: NTP time sync starting"); m_Service.post (std::bind (&NTPTimeSync::Sync, this)); m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this))); } else LogPrint (eLogWarning, "Timestamp: No NTP server found"); } void NTPTimeSync::Stop () { if (m_IsRunning) { LogPrint(eLogInfo, "Timestamp: NTP time sync stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread.reset (nullptr); } } } void NTPTimeSync::Run () { i2p::util::SetThreadName("Timesync"); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "Timestamp: NTP time sync exception: ", ex.what ()); } } } void NTPTimeSync::Sync () { if (m_NTPServersList.size () > 0) SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]); else m_IsRunning = false; if (m_IsRunning) { m_Timer.expires_from_now (boost::posix_time::hours (m_SyncInterval)); m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) Sync (); }); } } uint64_t GetMillisecondsSinceEpoch () { return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000; } uint64_t GetSecondsSinceEpoch () { return GetLocalSecondsSinceEpoch () + g_TimeOffset; } uint32_t GetMinutesSinceEpoch () { return GetLocalMinutesSinceEpoch () + g_TimeOffset/60; } uint32_t GetHoursSinceEpoch () { return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; } void GetCurrentDate (char * date) { GetDateString (GetSecondsSinceEpoch (), date); } void GetDateString (uint64_t timestamp, char * date) { using clock = std::chrono::system_clock; auto t = clock::to_time_t (clock::time_point (std::chrono::seconds(timestamp))); struct tm tm; #ifdef _WIN32 gmtime_s(&tm, &t); sprintf_s(date, 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); #else gmtime_r(&t, &tm); sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); #endif } } } i2pd-2.39.0/libi2pd/Timestamp.h000066400000000000000000000021411411072525600161050ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TIMESTAMP_H__ #define TIMESTAMP_H__ #include #include #include #include #include namespace i2p { namespace util { uint64_t GetMillisecondsSinceEpoch (); uint64_t GetSecondsSinceEpoch (); uint32_t GetMinutesSinceEpoch (); uint32_t GetHoursSinceEpoch (); void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes class NTPTimeSync { public: NTPTimeSync (); ~NTPTimeSync (); void Start (); void Stop (); private: void Run (); void Sync (); private: bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::deadline_timer m_Timer; int m_SyncInterval; std::vector m_NTPServersList; }; } } #endif i2pd-2.39.0/libi2pd/TransitTunnel.cpp000066400000000000000000000073061411072525600173170ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" #include "Transports.h" #include "TransitTunnel.h" namespace i2p { namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) { m_Encryption.SetKeys (layerKey, ivKey); } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } TransitTunnelParticipant::~TransitTunnelParticipant () { } void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (false); EncryptTunnelMsg (tunnelMsg, newMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_TunnelDataMsgs.push_back (newMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () { if (!m_TunnelDataMsgs.empty ()) { auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); m_TunnelDataMsgs.clear (); } } void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; std::unique_lock l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { std::unique_lock l(m_SendMutex); m_Gateway.SendBuffer (); } void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } std::shared_ptr 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 (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } } } i2pd-2.39.0/libi2pd/TransitTunnel.h000066400000000000000000000062311411072525600167600ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TRANSIT_TUNNEL_H__ #define TRANSIT_TUNNEL_H__ #include #include #include #include #include "Crypto.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TransitTunnel: public TunnelBase { public: TransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: i2p::crypto::TunnelEncryption m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel { public: TransitTunnelParticipant (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_NumTransmittedBytes (0) {}; ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void FlushTunnelDataMsgs (); private: size_t m_NumTransmittedBytes; std::vector > m_TunnelDataMsgs; }; class TransitTunnelGateway: public TransitTunnel { public: TransitTunnelGateway (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(this) {}; void SendTunnelDataMsg (std::shared_ptr msg); void FlushTunnelDataMsgs (); size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; private: std::mutex m_SendMutex; TunnelGateway m_Gateway; }; class TransitTunnelEndpoint: public TransitTunnel { public: TransitTunnelEndpoint (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound void Cleanup () { m_Endpoint.Cleanup (); } void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: TunnelEndpoint m_Endpoint; }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint); } } #endif i2pd-2.39.0/libi2pd/TransportSession.h000066400000000000000000000061551411072525600175130ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__ #include #include #include #include #include #include "Identity.h" #include "Crypto.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Timestamp.h" namespace i2p { namespace transport { class SignedData { public: SignedData () {} SignedData (const SignedData& other) { m_Stream << other.m_Stream.rdbuf (); } void Insert (const uint8_t * buf, size_t len) { m_Stream.write ((char *)buf, len); } template void Insert (T t) { m_Stream.write ((char *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } private: std::stringstream m_Stream; }; class TransportSession { public: TransportSession (std::shared_ptr router, int terminationTimeout): m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); } virtual ~TransportSession () {}; virtual void Done () = 0; std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; } std::shared_ptr GetRemoteIdentity () { std::lock_guard l(m_RemoteIdentityMutex); return m_RemoteIdentity; } void SetRemoteIdentity (std::shared_ptr ident) { std::lock_guard l(m_RemoteIdentityMutex); m_RemoteIdentity = ident; } size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; bool IsOutgoing () const { return m_IsOutgoing; }; int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; bool IsTerminationTimeoutExpired (uint64_t ts) const { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; virtual void SendLocalRouterInfo () { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; uint64_t m_LastActivityTimestamp; }; } } #endif i2pd-2.39.0/libi2pd/Transports.cpp000066400000000000000000000620121411072525600166570ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include "Log.h" #include "Crypto.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "NetDb.hpp" #include "Transports.h" #include "Config.h" #include "HTTP.h" #include "util.h" using namespace i2p::data; namespace i2p { namespace transport { template EphemeralKeysSupplier::EphemeralKeysSupplier (int size): m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) { } template EphemeralKeysSupplier::~EphemeralKeysSupplier () { Stop (); } template void EphemeralKeysSupplier::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); } template void EphemeralKeysSupplier::Stop () { { std::unique_lock l(m_AcquiredMutex); m_IsRunning = false; m_Acquired.notify_one (); } if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = 0; } } template void EphemeralKeysSupplier::Run () { i2p::util::SetThreadName("Ephemerals"); while (m_IsRunning) { int num, total = 0; while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10) { CreateEphemeralKeys (num); total += num; } if (total >= 10) { LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else { std::unique_lock l(m_AcquiredMutex); if (!m_IsRunning) break; m_Acquired.wait (l); // wait for element gets acquired } } } template void EphemeralKeysSupplier::CreateEphemeralKeys (int num) { if (num > 0) { for (int i = 0; i < num; i++) { auto pair = std::make_shared (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } template std::shared_ptr EphemeralKeysSupplier::Acquire () { { std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); m_Queue.pop (); m_Acquired.notify_one (); return pair; } } // queue is empty, create new auto pair = std::make_shared (); pair->GenerateKeys (); return pair; } template void EphemeralKeysSupplier::Return (std::shared_ptr pair) { if (pair) { std::unique_lockl(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else LogPrint(eLogError, "Transports: return null DHKeys"); } Transports transports; Transports::Transports (): m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), m_X25519KeysPairSupplier (15), // 15 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) { } Transports::~Transports () { Stop (); if (m_Service) { delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr; delete m_PeerTestTimer; m_PeerTestTimer = nullptr; delete m_Work; m_Work = nullptr; delete m_Service; m_Service = nullptr; } } void Transports::Start (bool enableNTCP2, bool enableSSU) { if (!m_Service) { m_Service = new boost::asio::io_service (); m_Work = new boost::asio::io_service::work (*m_Service); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); } i2p::config::GetOption("nat", m_IsNAT); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); i2p::http::URL proxyurl; // create NTCP2. TODO: move to acceptor if (enableNTCP2 || i2p::context.SupportsMesh ()) { if(!ntcp2proxy.empty() && enableNTCP2) { if(proxyurl.parse(ntcp2proxy)) { if(proxyurl.schema == "socks" || proxyurl.schema == "http") { m_NTCP2Server = new NTCP2Server (); NTCP2Server::ProxyType proxytype = NTCP2Server::eSocksProxy; if (proxyurl.schema == "http") proxytype = NTCP2Server::eHTTPProxy; m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); i2p::context.SetStatus (eRouterStatusProxy); } else LogPrint(eLogError, "Transports: unsupported NTCP2 proxy URL ", ntcp2proxy); } else LogPrint(eLogError, "Transports: invalid NTCP2 proxy url ", ntcp2proxy); } else m_NTCP2Server = new NTCP2Server (); } // create SSU server int ssuPort = 0; if (enableSSU) { auto& addresses = context.GetRouterInfo ().GetAddresses (); for (const auto& address: addresses) { if (!address) continue; if (address->transportStyle == RouterInfo::eTransportSSU) { ssuPort = address->port; m_SSUServer = new SSUServer (address->port); break; } } } // bind to interfaces bool ipv4; i2p::config::GetOption("ipv4", ipv4); if (ipv4) { std::string address; i2p::config::GetOption("address4", address); if (!address.empty ()) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); } } } bool ipv6; i2p::config::GetOption("ipv6", ipv6); if (ipv6) { std::string address; i2p::config::GetOption("address6", address); if (!address.empty ()) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); } } } bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); if (ygg) { std::string address; i2p::config::GetOption("meshnets.yggaddress", address); if (!address.empty ()) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr)) m_NTCP2Server->SetLocalAddress (addr); } } // start servers if (m_NTCP2Server) m_NTCP2Server->Start (); if (m_SSUServer) { LogPrint (eLogInfo, "Transports: Start listening UDP port ", ssuPort); try { m_SSUServer->Start (); } catch (std::exception& ex ) { LogPrint(eLogError, "Transports: Failed to bind to UDP port", ssuPort); m_SSUServer->Stop (); delete m_SSUServer; m_SSUServer = nullptr; } if (m_SSUServer) DetectExternalIP (); } m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); if (m_IsNAT) { m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } void Transports::Stop () { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); m_Peers.clear (); if (m_SSUServer) { m_SSUServer->Stop (); delete m_SSUServer; m_SSUServer = nullptr; } if (m_NTCP2Server) { m_NTCP2Server->Stop (); delete m_NTCP2Server; m_NTCP2Server = nullptr; } m_X25519KeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void Transports::Run () { i2p::util::SetThreadName("Transports"); while (m_IsRunning && m_Service) { try { m_Service->run (); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: runtime exception: ", ex.what ()); } } } void Transports::UpdateBandwidth () { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); if (m_LastBandwidthUpdateTime > 0) { auto delta = ts - m_LastBandwidthUpdateTime; if (delta > 0) { m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta; } } m_LastBandwidthUpdateTime = ts; m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; } bool Transports::IsBandwidthExceeded () const { auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes auto bw = std::max (m_InBandwidth, m_OutBandwidth); return bw > limit; } bool Transports::IsTransitBandwidthExceeded () const { auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes return m_TransitBandwidth > limit; } void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { if (m_IsOnline) SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) { m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); } void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { // we send it to ourself for (auto& it: msgs) m_LoopbackHandler.PutNextMessage (it); m_LoopbackHandler.Flush (); return; } if(RoutesRestricted() && !IsRestrictedPeer(ident)) return; auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { bool connected = false; try { auto r = netdb.FindRouter (ident); if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, i2p::util::GetSecondsSinceEpoch (), {} })).first; } connected = ConnectToPeer (ident, it->second); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } if (!connected) return; } if (!it->second.sessions.empty ()) it->second.sessions.front ()->SendI2NPMessages (msgs); else { if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) { for (auto& it1: msgs) it->second.delayedMessages.push_back (it1); } else { LogPrint (eLogWarning, "Transports: delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { if (!peer.router) // reconnect peer.router = netdb.FindRouter (ident); // try to get new one from netdb if (peer.router) // we have RI already { if (peer.numAttempts < 2) // NTCP2, 0 - ipv6, 1- ipv4 { if (m_NTCP2Server) // we support NTCP2 { std::shared_ptr address; if (!peer.numAttempts) // NTCP2 ipv6 { if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) { address = peer.router->GetPublishedNTCP2V6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; } if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) { address = peer.router->GetPublishedNTCP2V4Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; } if (address) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); if( m_NTCP2Server->UsingProxy()) m_NTCP2Server->ConnectWithProxy(s); else m_NTCP2Server->Connect (s); return true; } } else peer.numAttempts = 2; // switch to SSU } if (peer.numAttempts == 2 || peer.numAttempts == 3) // SSU { if (m_SSUServer) { std::shared_ptr address; if (peer.numAttempts == 2) // SSU ipv6 { if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) { address = peer.router->GetSSUV6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; } if (!address && peer.numAttempts == 3) // SSU ipv4 { if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) { address = peer.router->GetSSUAddress (true); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; } if (address && address->IsReachableSSU ()) { if (m_SSUServer->CreateSession (peer.router, address)) return true; } } else peer.numAttempts += 2; // switch to Mesh } if (peer.numAttempts == 4) // Mesh { peer.numAttempts++; if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) { auto address = peer.router->GetYggdrasilAddress (); if (address) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); m_NTCP2Server->Connect (s); return true; } } } LogPrint (eLogInfo, "Transports: No compatble NTCP2 or SSU addresses available"); i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed peer.Done (); std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } else // otherwise request RI { LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested"); i2p::data::netdb.RequestDestination (ident, std::bind ( &Transports::RequestComplete, this, std::placeholders::_1, ident)); } return true; } void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); } void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { if (r) { LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect"); it->second.router = r; ConnectToPeer (ident, it->second); } else { LogPrint (eLogWarning, "Transports: RouterInfo not found, Failed to send messages"); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } void Transports::DetectExternalIP () { if (RoutesRestricted()) { LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); i2p::context.SetStatus (eRouterStatusOK); return; } if (m_SSUServer) PeerTest (); else LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); } void Transports::PeerTest (bool ipv4, bool ipv6) { if (RoutesRestricted() || !m_SSUServer) return; if (ipv4 && i2p::context.SupportsV4 ()) { LogPrint (eLogInfo, "Transports: Started peer test ipv4"); std::set excluded; bool statusChanged = false; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4 if (router) { auto addr = router->GetSSUAddress (true); // ipv4 if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { if (!statusChanged) { statusChanged = true; i2p::context.SetStatus (eRouterStatusTesting); // first time only } m_SSUServer->CreateSession (router, addr, true); // peer test v4 } excluded.insert (router->GetIdentHash ()); } } if (!statusChanged) LogPrint (eLogWarning, "Transports: Can't find routers for peer test ipv4"); } if (ipv6 && i2p::context.SupportsV6 ()) { LogPrint (eLogInfo, "Transports: Started peer test ipv6"); std::set excluded; bool statusChanged = false; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6 if (router) { auto addr = router->GetSSUV6Address (); if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { if (!statusChanged) { statusChanged = true; i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only } m_SSUServer->CreateSession (router, addr, true); // peer test v6 } excluded.insert (router->GetIdentHash ()); } } if (!statusChanged) LogPrint (eLogWarning, "Transports: Can't find routers for peer test ipv6"); } } std::shared_ptr Transports::GetNextX25519KeysPair () { return m_X25519KeysPairSupplier.Acquire (); } void Transports::ReuseX25519KeysPair (std::shared_ptr pair) { m_X25519KeysPairSupplier.Return (pair); } void Transports::PeerConnected (std::shared_ptr session) { m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { it->second.router = nullptr; // we don't need RouterInfo after successive connect bool sendDatabaseStore = true; if (it->second.delayedMessages.size () > 0) { // check if first message is our DatabaseStore (publishing) auto firstMsg = it->second.delayedMessages[0]; if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already } if (sendDatabaseStore) session->SendLocalRouterInfo (); else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds it->second.sessions.push_back (session); session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); } else // incoming connection { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64()); session->Done(); return; } session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); } }); } void Transports::PeerDisconnected (std::shared_ptr session) { m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { auto before = it->second.sessions.size (); it->second.sessions.remove (session); if (it->second.sessions.empty ()) { if (it->second.delayedMessages.size () > 0) { if (before > 0) // we had an active session before it->second.numAttempts = 0; // start over ConnectToPeer (ident, it->second); } else { std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } }); } bool Transports::IsConnected (const i2p::data::IdentHash& ident) const { std::unique_lock l(m_PeersMutex); auto it = m_Peers.find (ident); return it != m_Peers.end (); } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); auto profile = i2p::data::GetRouterProfile(it->first); if (profile) { profile->TunnelNonReplied(); } std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else ++it; } UpdateBandwidth (); // TODO: use separate timer(s) for it bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting; bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting; // if still testing, repeat peer test if (ipv4Testing || ipv6Testing) PeerTest (ipv4Testing, ipv6Testing); m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } void Transports::HandlePeerTestTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { PeerTest (); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } std::shared_ptr Transports::GetRandomPeer () const { if (m_Peers.empty ()) return nullptr; i2p::data::IdentHash ident; { std::unique_lock l(m_PeersMutex); auto it = m_Peers.begin (); std::advance (it, rand () % m_Peers.size ()); if (it == m_Peers.end () || it->second.router) return nullptr; // not connected ident = it->first; } return i2p::data::netdb.FindRouter (ident); } void Transports::RestrictRoutesToFamilies(std::set families) { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); for ( const auto& fam : families ) m_TrustedFamilies.push_back(fam); } void Transports::RestrictRoutesToRouters(std::set routers) { std::unique_lock lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) m_TrustedRouters.push_back(ri); } bool Transports::RoutesRestricted() const { std::unique_lock famlock(m_FamilyMutex); std::unique_lock routerslock(m_TrustedRoutersMutex); return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0; } /** XXX: if routes are not restricted this dies */ std::shared_ptr Transports::GetRestrictedPeer() const { { std::lock_guard l(m_FamilyMutex); std::string fam; auto sz = m_TrustedFamilies.size(); if(sz > 1) { auto it = m_TrustedFamilies.begin (); std::advance(it, rand() % sz); fam = *it; boost::to_lower(fam); } else if (sz == 1) { fam = m_TrustedFamilies[0]; } if (fam.size()) return i2p::data::netdb.GetRandomRouterInFamily(fam); } { std::unique_lock l(m_TrustedRoutersMutex); auto sz = m_TrustedRouters.size(); if (sz) { if(sz == 1) return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); auto it = m_TrustedRouters.begin(); std::advance(it, rand() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const { { std::unique_lock l(m_TrustedRoutersMutex); for (const auto & r : m_TrustedRouters ) if ( r == ih ) return true; } { std::unique_lock l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); for (const auto & fam : m_TrustedFamilies) if(ri->IsFamily(fam)) return true; } return false; } void Transports::SetOnline (bool online) { if (m_IsOnline != online) { m_IsOnline = online; if (online) PeerTest (); else i2p::context.SetError (eRouterErrorOffline); } } } } i2pd-2.39.0/libi2pd/Transports.h000066400000000000000000000145531411072525600163330ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TRANSPORTS_H__ #define TRANSPORTS_H__ #include #include #include #include #include #include #include #include #include #include #include #include "TransportSession.h" #include "SSU.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { namespace transport { template class EphemeralKeysSupplier { // called from this file only, so implementation is in Transports.cpp public: EphemeralKeysSupplier (int size); ~EphemeralKeysSupplier (); void Start (); void Stop (); std::shared_ptr Acquire (); void Return (std::shared_ptr pair); private: void Run (); void CreateEphemeralKeys (int num); private: const int m_QueueSize; std::queue > m_Queue; bool m_IsRunning; std::thread * m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; typedef EphemeralKeysSupplier X25519KeysPairSupplier; struct Peer { int numAttempts; std::shared_ptr router; std::list > sessions; uint64_t creationTime; std::vector > delayedMessages; void Done () { for (auto& it: sessions) it->Done (); } }; const size_t SESSION_CREATION_TIMEOUT = 15; // in seconds const int PEER_TEST_INTERVAL = 71; // in minutes const int MAX_NUM_DELAYED_MESSAGES = 150; class Transports { public: Transports (); ~Transports (); void Start (bool enableNTCP2=true, bool enableSSU=true); void Stop (); bool IsBoundSSU() const { return m_SSUServer != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; void SetOnline (bool online); boost::asio::io_service& GetService () { return *m_Service; }; std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); bool IsConnected (const i2p::data::IdentHash& ident) const; void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; uint64_t GetTotalTransitTransmittedBytes () const { return m_TotalTransitTransmittedBytes; } void UpdateTotalTransitTransmittedBytes (uint32_t add) { m_TotalTransitTransmittedBytes += add; }; uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; bool IsBandwidthExceeded () const; bool IsTransitBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer () const; /** get a trusted first hop for restricted routes */ std::shared_ptr GetRestrictedPeer() const; /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ void RestrictRoutesToFamilies(std::set families); /** restrict routes to use only these routers for first hops */ void RestrictRoutesToRouters(std::set routers); bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; void PeerTest (bool ipv4 = true, bool ipv6 = true); void SetCheckReserved (bool check) { m_CheckReserved = check; }; bool IsCheckReserved () { return m_CheckReserved; }; private: void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); void UpdateBandwidth (); void DetectExternalIP (); private: volatile bool m_IsOnline; bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; SSUServer * m_SSUServer; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; std::unordered_map m_Peers; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; uint64_t m_LastBandwidthUpdateTime; /** which router families to trust for first hops */ std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ std::vector m_TrustedRouters; mutable std::mutex m_TrustedRoutersMutex; i2p::I2NPMessagesHandler m_LoopbackHandler; public: // for HTTP only const SSUServer * GetSSUServer () const { return m_SSUServer; }; const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; } } #endif i2pd-2.39.0/libi2pd/Tunnel.cpp000066400000000000000000000664751411072525600157660ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "I2PEndian.h" #include #include #include #include #include "Crypto.h" #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Transports.h" #include "NetDb.hpp" #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" namespace i2p { namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), m_Latency (0) { } Tunnel::~Tunnel () { } void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; msg->len += numRecords*recordSize + 1; // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()())); // create real records uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; while (hop) { uint32_t msgID; if (hop->next) // we set replyMsgID for last hop only RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; hop->recordIndex = recordIndicies[i]; i++; hop->CreateBuildRequestRecord (records, msgID); hop = hop->next; } // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; RAND_bytes (records + idx*recordSize, recordSize); } // decrypt real records hop = m_Config->GetLastHop ()->prev; while (hop) { // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { hop->DecryptRecord (records, hop1->recordIndex); hop1 = hop1->next; } hop = hop->prev; } msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); // send message if (outboundTunnel) { if (m_Config->IsShort ()) { auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); if (msg1) msg = msg1; } } outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); } else { if (m_Config->IsShort () && m_Config->GetLastHop () && m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent) { // add garlic key/tag for reply uint8_t key[32]; uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); if (m_Pool && m_Pool->GetLocalDestination ()) m_Pool->GetLocalDestination ()->AddECIESx25519Key (key, tag); else i2p::context.AddECIESx25519Key (key, tag); } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { // decrypt current hop if (hop->recordIndex >= 0 && hop->recordIndex < msg[0]) { if (!hop->DecryptBuildResponseRecord (msg + 1)) return false; } else { LogPrint (eLogWarning, "Tunnel: hop index ", hop->recordIndex, " is out of range"); return false; } // decrypt records before current hop TunnelHopConfig * hop1 = hop->prev; while (hop1) { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; } hop = hop->prev; } bool established = true; hop = m_Config->GetFirstHop (); while (hop) { uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) profile->TunnelBuildResponse (ret); if (ret) // if any of participants declined the tunnel is not established established = false; hop = hop->next; } if (established) { // create tunnel decryptions from layer and iv keys in reverse order hop = m_Config->GetLastHop (); while (hop) { auto tunnelHop = new TunnelHop; tunnelHop->ident = hop->ident; tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; } m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; return established; } bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const { auto latency = GetMeanLatency(); return latency >= lower && latency <= upper; } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { const uint8_t * inPayload = in->GetPayload () + 4; uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { it->decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } std::vector > Tunnel::GetPeers () const { auto peers = GetInvertedPeers (); std::reverse (peers.begin (), peers.end ()); return peers; } std::vector > Tunnel::GetInvertedPeers () const { // hops are in inverted order std::vector > ret; for (auto& it: m_Hops) ret.push_back (it->ident); return ret; } void Tunnel::SetState(TunnelState state) { m_State = state; } void Tunnel::PrintHops (std::stringstream& s) const { // hops are in inverted order, we must print in direct order for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) { s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); } } void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (msg, newMsg); newMsg->from = shared_from_this (); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); s << " ⇒ " << GetTunnelID () << ":me"; } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) { } void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) { if (msg) { m_NumReceivedBytes += msg->GetLength (); msg->from = shared_from_this (); HandleI2NPMessage (msg); } } void ZeroHopsInboundTunnel::Print (std::stringstream& s) const { s << " ⇒ " << GetTunnelID () << ":me"; } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; if (gwHash) { block.hash = gwHash; if (gwTunnel) { block.deliveryType = eDeliveryTypeTunnel; block.tunnelID = gwTunnel; } else block.deliveryType = eDeliveryTypeRouter; } else block.deliveryType = eDeliveryTypeLocal; block.data = msg; SendTunnelDataMsg ({block}); } void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) m_Gateway.PutTunnelDataMsg (it); m_Gateway.SendBuffer (); } void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } void OutboundTunnel::Print (std::stringstream& s) const { s << GetTunnelID () << ":me"; PrintHops (s); s << " ⇒ "; } ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) { } void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { for (auto& msg : msgs) { if (!msg.data) continue; m_NumSentBytes += msg.data->GetLength (); switch (msg.deliveryType) { case eDeliveryTypeLocal: HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); break; case eDeliveryTypeRouter: i2p::transport::transports.SendMessage (msg.hash, msg.data); break; default: LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType); } } } void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const { s << GetTunnelID () << ":me ⇒ "; } Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) { } Tunnels::~Tunnels () { } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { auto it = m_Tunnels.find(tunnelID); if (it != m_Tunnels.end ()) return it->second; return nullptr; } std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } template std::shared_ptr Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels) { auto it = pendingTunnels.find(replyMsgID); if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) { it->second->SetState (eTunnelStateBuildReplyReceived); return it->second; } return nullptr; } std::shared_ptr Tunnels::GetNextInboundTunnel () { std::shared_ptr tunnel; size_t minReceived = 0; for (const auto& it : m_InboundTunnels) { if (!it->IsEstablished ()) continue; if (!tunnel || it->GetNumReceivedBytes () < minReceived) { tunnel = it; minReceived = it->GetNumReceivedBytes (); } } return tunnel; } std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { if (it->IsEstablished ()) { tunnel = it; i++; } if (i > ind && tunnel) break; } return tunnel; } std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) { auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; } void Tunnels::DeleteTunnelPool (std::shared_ptr pool) { if (pool) { StopTunnelPool (pool); { std::unique_lock l(m_PoolsMutex); m_Pools.remove (pool); } } } void Tunnels::StopTunnelPool (std::shared_ptr pool) { if (pool) { pool->SetActive (false); pool->DetachTunnels (); } } void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); else LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); } void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); } void Tunnels::Stop () { m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = 0; } } void Tunnels::Run () { i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0, lastPoolsTs = 0; while (m_IsRunning) { try { auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; do { std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) { case eI2NPTunnelData: case eI2NPTunnelGateway: { tunnelID = bufbe32toh (msg->GetPayload ()); if (tunnelID == prevTunnelID) tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) { if (typeID == eI2NPTunnelData) tunnel->HandleTunnelDataMsg (msg); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } else LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); break; } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: case eI2NPShortTunnelBuild: case eI2NPShortTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: LogPrint (eLogWarning, "Tunnel: unexpected message type ", (int) typeID); } msg = m_Queue.Get (); if (msg) { prevTunnelID = tunnelID; prevTunnel = tunnel; } else if (tunnel) tunnel->FlushTunnelDataMsgs (); } while (msg); } if (i2p::transport::transports.IsOnline()) { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastTs >= 15) // manage tunnels every 15 seconds { ManageTunnels (); lastTs = ts; } if (ts - lastPoolsTs >= 5) // manage pools every 5 seconds { ManageTunnelPools (ts); lastPoolsTs = ts; } } } catch (std::exception& ex) { LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ()); } } } void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg) { if (!tunnel) { LogPrint (eLogError, "Tunnel: missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); // we make payload as new I2NP message to send msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; if (msg->offset + len > msg->len) { LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len); return; } msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg)); tunnel->SendTunnelDataMsg (msg); } void Tunnels::ManageTunnels () { ManagePendingTunnels (); ManageInboundTunnels (); ManageOutboundTunnels (); ManageTransitTunnels (); } void Tunnels::ManagePendingTunnels () { ManagePendingTunnels (m_PendingInboundTunnels); ManagePendingTunnels (m_PendingOutboundTunnels); } template void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) { // check pending tunnel. delete failed or timeout uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) { case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) { auto hop = config->GetFirstHop (); while (hop) { if (hop->ident) { auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) profile->TunnelNonReplied (); } hop = hop->next; } } // delete it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; } else ++it; break; case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed ++it; break; default: // success it = pendingTunnels.erase (it); m_NumSuccesiveTunnelCreations++; } } } void Tunnels::ManageOutboundTunnels () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); // we don't have outbound tunnels in m_Tunnels it = m_OutboundTunnels.erase (it); } else { if (tunnel->IsEstablished ()) { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool has been reconfigured and this is old if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) { tunnel->SetIsRecreated (); pool->RecreateOutboundTunnel (tunnel); } } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); } ++it; } } } if (m_OutboundTunnels.size () < 3) { // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); // reachable by us if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()), nullptr ); } } void Tunnels::ManageInboundTunnels () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); } else { if (tunnel->IsEstablished ()) { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool was reconfigured and has different number of hops if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) { tunnel->SetIsRecreated (); pool->RecreateInboundTunnel (tunnel); } } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); else // we don't need to cleanup expiring tunnels tunnel->Cleanup (); } it++; } } } if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); CreateZeroHopsInboundTunnel (nullptr); CreateZeroHopsOutboundTunnel (nullptr); if (!m_ExploratoryPool) { int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen); int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum); int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum); m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; } if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) { // trying to create one more inbound tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : // should be reachable by us because we send build request directly i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); if (!router) { LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); return; } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }), nullptr ); } } void Tunnels::ManageTransitTunnels () { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_TransitTunnels.erase (it); } else { tunnel->Cleanup (); it++; } } } void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) pool->ManageTunnels (ts); } } void Tunnels::PostTunnelData (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } void Tunnels::PostTunnelData (const std::vector >& msgs) { m_Queue.Put (msgs); } template std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); newTunnel->SetTunnelPool (pool); uint32_t replyMsgID; RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; } std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel) { if (config) return CreateTunnel(config, pool, outboundTunnel); else return CreateZeroHopsInboundTunnel (pool); } std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) { if (config) return CreateTunnel(config, pool); else return CreateZeroHopsOutboundTunnel (pool); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingInboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingOutboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { // we don't need to insert it to m_Tunnels m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), nullptr, GetNextOutboundTunnel ()); } else { if (pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } } else LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) { auto inboundTunnel = std::make_shared (); inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr pool) { auto outboundTunnel = std::make_shared (); outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; } int Tunnels::GetTransitTunnelsExpirationTimeout () { int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // TODO: possible race condition with I2PControl for (const auto& it : m_TransitTunnels) { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; if (t > timeout) timeout = t; } return timeout; } size_t Tunnels::CountTransitTunnels() const { // TODO: locking return m_TransitTunnels.size(); } size_t Tunnels::CountInboundTunnels() const { // TODO: locking return m_InboundTunnels.size(); } size_t Tunnels::CountOutboundTunnels() const { // TODO: locking return m_OutboundTunnels.size(); } } } i2pd-2.39.0/libi2pd/Tunnel.h000066400000000000000000000236251411072525600154210ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TUNNEL_H__ #define TUNNEL_H__ #include #include #include #include #include #include #include #include #include #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" #include "TunnelPool.h" #include "TransitTunnel.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" #include "I2NPProtocol.h" namespace i2p { namespace tunnel { const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message const int MAX_NUM_RECORDS = 8; const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds enum TunnelState { eTunnelStatePending, eTunnelStateBuildReplyReceived, eTunnelStateBuildFailed, eTunnelStateEstablished, eTunnelStateTestFailed, eTunnelStateFailed, eTunnelStateExpiring }; class OutboundTunnel; class InboundTunnel; class Tunnel: public TunnelBase { struct TunnelHop { std::shared_ptr ident; i2p::crypto::TunnelDecryption decryption; }; public: Tunnel (std::shared_ptr config); ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; void SetIsRecreated () { m_IsRecreated = true; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); virtual void Print (std::stringstream&) const {}; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); /** @brief add latency sample */ void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; } /** @brief get this tunnel's estimated latency */ uint64_t GetMeanLatency() const { return m_Latency; } /** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */ bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; bool LatencyIsKnown() const { return m_Latency > 0; } bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } protected: void PrintHops (std::stringstream& s) const; private: std::shared_ptr m_Config; std::vector > m_Hops; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; bool m_IsRecreated; uint64_t m_Latency; // in milliseconds }; class OutboundTunnel: public Tunnel { public: OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); virtual void SendTunnelDataMsg (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; void Print (std::stringstream& s) const; // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); bool IsInbound() const { return false; } private: std::mutex m_SendMutex; TunnelGateway m_Gateway; i2p::data::IdentHash m_EndpointIdentHash; }; class InboundTunnel: public Tunnel, public std::enable_shared_from_this { public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; bool IsInbound() const { return true; } // override TunnelBase void Cleanup () { m_Endpoint.Cleanup (); }; private: TunnelEndpoint m_Endpoint; }; class ZeroHopsInboundTunnel: public InboundTunnel { public: ZeroHopsInboundTunnel (); void SendTunnelDataMsg (std::shared_ptr msg); void Print (std::stringstream& s) const; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; private: size_t m_NumReceivedBytes; }; class ZeroHopsOutboundTunnel: public OutboundTunnel { public: ZeroHopsOutboundTunnel (); void SendTunnelDataMsg (const std::vector& msgs); void Print (std::stringstream& s) const; size_t GetNumSentBytes () const { return m_NumSentBytes; }; private: size_t m_NumSentBytes; }; class Tunnels { public: Tunnels (); ~Tunnels (); void Start (); void Stop (); std::shared_ptr GetPendingInboundTunnel (uint32_t replyMsgID); std::shared_ptr GetPendingOutboundTunnel (uint32_t replyMsgID); std::shared_ptr GetNextInboundTunnel (); std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); private: template std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel = nullptr); template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); void Run (); void ManageTunnels (); void ManageOutboundTunnels (); void ManageInboundTunnels (); void ManageTransitTunnels (); void ManagePendingTunnels (); template void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (uint64_t ts); std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); private: bool m_IsRunning; std::thread * m_Thread; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; std::list > m_TransitTunnels; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; size_t CountTransitTunnels() const; size_t CountInboundTunnels() const; size_t CountOutboundTunnels() const; int GetQueueSize () { return m_Queue.GetSize (); }; int GetTunnelCreationSuccessRate () const // in percents { int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; } }; extern Tunnels tunnels; } } #endif i2pd-2.39.0/libi2pd/TunnelBase.h000066400000000000000000000043421411072525600162070ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TUNNEL_BASE_H__ #define TUNNEL_BASE_H__ #include #include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { namespace tunnel { const size_t TUNNEL_DATA_MSG_SIZE = 1028; const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008; const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003; enum TunnelDeliveryType { eDeliveryTypeLocal = 0, eDeliveryTypeTunnel = 1, eDeliveryTypeRouter = 2 }; struct TunnelMessageBlock { TunnelDeliveryType deliveryType; i2p::data::IdentHash hash; uint32_t tunnelID; std::shared_ptr data; }; class TunnelBase { public: TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; virtual void Cleanup () {}; virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t t) { m_CreationTime = t; }; private: uint32_t m_TunnelID, m_NextTunnelID; i2p::data::IdentHash m_NextIdent; uint32_t m_CreationTime; // seconds since epoch }; struct TunnelCreationTimeCmp { template bool operator() (const std::shared_ptr & t1, const std::shared_ptr & t2) const { if (t1->GetCreationTime () != t2->GetCreationTime ()) return t1->GetCreationTime () > t2->GetCreationTime (); else return t1 < t2; } }; } } #endif i2pd-2.39.0/libi2pd/TunnelConfig.cpp000066400000000000000000000246171411072525600171040ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 #include #include #include "Log.h" #include "Transports.h" #include "Timestamp.h" #include "I2PEndian.h" #include "I2NPProtocol.h" #include "TunnelConfig.h" namespace i2p { namespace tunnel { TunnelHopConfig::TunnelHopConfig (std::shared_ptr r) { RAND_bytes ((uint8_t *)&tunnelID, 4); if (!tunnelID) tunnelID = 1; // tunnelID can't be zero isGateway = true; isEndpoint = true; ident = r; //nextRouter = nullptr; nextTunnelID = 0; next = nullptr; prev = nullptr; } void TunnelHopConfig::SetNextIdent (const i2p::data::IdentHash& ident) { nextIdent = ident; isEndpoint = false; RAND_bytes ((uint8_t *)&nextTunnelID, 4); if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero } void TunnelHopConfig::SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) { nextIdent = replyIdent; nextTunnelID = replyTunnelID; isEndpoint = true; } void TunnelHopConfig::SetNext (TunnelHopConfig * n) { next = n; if (next) { next->prev = this; next->isGateway = false; isEndpoint = false; nextIdent = next->ident->GetIdentHash (); nextTunnelID = next->tunnelID; } } void TunnelHopConfig::SetPrev (TunnelHopConfig * p) { prev = p; if (prev) { prev->next = this; prev->isEndpoint = false; isGateway = false; } } void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const { uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); decryption.SetIV (replyIV); decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // generate keys RAND_bytes (layerKey, 32); RAND_bytes (ivKey, 32); RAND_bytes (replyKey, 32); RAND_bytes (replyIV, 16); // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); // encrypt uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; auto encryptor = ident->CreateEncryptor (nullptr); if (encryptor) { BN_CTX * ctx = BN_CTX_new (); encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); BN_CTX_free (ctx); } memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); decryption.SetIV (replyIV); decryption.Decrypt (record, TUNNEL_BUILD_RECORD_SIZE, record); return true; } void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) { if (!ident) return; i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); MixHash (encrypted, 32); // h = SHA256(h || sepk) encrypted += 32; uint8_t sharedSecret[32]; ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) MixKey (sharedSecret); uint8_t nonce[12]; memset (nonce, 0, 12); if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); return; } MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) } bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const { return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt } void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // generate keys RAND_bytes (layerKey, 32); RAND_bytes (ivKey, 32); RAND_bytes (replyKey, 32); RAND_bytes (replyIV, 16); // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); // encrypt uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t nonce[12]; memset (nonce, 0, 12); if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; } return true; } void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); // encrypt uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); // derive keys i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); memcpy (replyKey, m_CK + 32, 32); i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); memcpy (layerKey, m_CK + 32, 32); if (isEndpoint) { i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); memcpy (ivKey, m_CK + 32, 32); i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK } else memcpy (ivKey, m_CK, 32); // last HKDF memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; uint8_t nonce[12]; memset (nonce, 0, 12); nonce[4] = recordIndex; // nonce is record index if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; } return true; } void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const { uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; uint8_t nonce[12]; memset (nonce, 0, 12); nonce[4] = index; // nonce is index i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); } uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const { uint64_t tag; memcpy (&tag, m_CK, 8); memcpy (key, m_CK + 32, 32); return tag; } } }i2pd-2.39.0/libi2pd/TunnelConfig.h000066400000000000000000000154451411072525600165500ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ #include #include "Identity.h" #include "RouterContext.h" #include "Crypto.h" namespace i2p { namespace tunnel { struct TunnelHopConfig { std::shared_ptr ident; i2p::data::IdentHash nextIdent; uint32_t tunnelID, nextTunnelID; uint8_t layerKey[32]; uint8_t ivKey[32]; uint8_t replyKey[32]; uint8_t replyIV[16]; bool isGateway, isEndpoint; TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message TunnelHopConfig (std::shared_ptr r); virtual ~TunnelHopConfig () {}; void SetNextIdent (const i2p::data::IdentHash& ident); void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); void SetNext (TunnelHopConfig * n); void SetPrev (TunnelHopConfig * p); virtual uint8_t GetRetCode (const uint8_t * records) const = 0; virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; virtual void DecryptRecord (uint8_t * records, int index) const; // AES virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag }; struct ElGamalTunnelHopConfig: public TunnelHopConfig { ElGamalTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; }; struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState { ECIESTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; }; struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig { LongECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; }; struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig { ShortECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 uint64_t GetGarlicKey (uint8_t * key) const override; }; class TunnelConfig { public: TunnelConfig (const std::vector >& peers, bool isShort = false): // inbound m_IsShort (isShort) { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } TunnelConfig (const std::vector >& peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort = false): // outbound m_IsShort (isShort) { CreatePeers (peers); m_FirstHop->isGateway = false; m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; while (hop) { auto tmp = hop; hop = hop->next; delete tmp; } } bool IsShort () const { return m_IsShort; } 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; } bool IsEmpty () const { return !m_FirstHop; } virtual bool IsInbound () const { return m_FirstHop->isGateway; } virtual uint32_t GetTunnelID () const { if (!m_FirstHop) return 0; return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID; } virtual uint32_t GetNextTunnelID () const { if (!m_FirstHop) return 0; return m_FirstHop->tunnelID; } virtual const i2p::data::IdentHash& GetNextIdentHash () const { return m_FirstHop->ident->GetIdentHash (); } virtual const i2p::data::IdentHash& GetLastIdentHash () const { return m_LastHop->ident->GetIdentHash (); } std::vector > GetPeers () const { std::vector > peers; TunnelHopConfig * hop = m_FirstHop; while (hop) { peers.push_back (hop->ident); hop = hop->next; } return peers; } protected: // this constructor can't be called from outside TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false) { } private: void CreatePeers (const std::vector >& peers) { TunnelHopConfig * prev = nullptr; for (const auto& it: peers) { TunnelHopConfig * hop; if (m_IsShort) hop = new ShortECIESTunnelHopConfig (it); else { if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) hop = new LongECIESTunnelHopConfig (it); else hop = new ElGamalTunnelHopConfig (it); } if (prev) prev->SetNext (hop); else m_FirstHop = hop; prev = hop; } m_LastHop = prev; } private: TunnelHopConfig * m_FirstHop, * m_LastHop; bool m_IsShort; }; class ZeroHopsTunnelConfig: public TunnelConfig { public: ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);}; bool IsInbound () const { return true; }; // TODO: uint32_t GetTunnelID () const { return m_TunnelID; }; uint32_t GetNextTunnelID () const { return m_TunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); }; const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); }; private: uint32_t m_TunnelID; }; } } #endif i2pd-2.39.0/libi2pd/TunnelEndpoint.cpp000066400000000000000000000271021411072525600174470ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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" #include #include "Crypto.h" #include "Log.h" #include "NetDb.hpp" #include "I2NPProtocol.h" #include "Transports.h" #include "RouterContext.h" #include "Timestamp.h" #include "TunnelEndpoint.h" namespace i2p { namespace tunnel { TunnelEndpoint::~TunnelEndpoint () { } void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr 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) { 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]; SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { LogPrint (eLogError, "TunnelMessage: checksum verification failed"); 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; if (!isFollowOnFragment) { // first fragment if (m_CurrentMsgID) AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); switch (m_CurrentMessage.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 m_CurrentMessage.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; } bool isFragmented = flag & 0x08; if (isFragmented) { // Message ID msgID = bufbe32toh (fragment); fragment += 4; m_CurrentMsgID = msgID; isLastFragment = false; } } else { // follow on msgID = bufbe32toh (fragment); // MessageID fragment += 4; fragmentNum = (flag >> 1) & 0x3F; // 6 bits isLastFragment = flag & 0x01; } uint16_t size = bufbe16toh (fragment); fragment += 2; // handle fragment if (isFollowOnFragment) { // existing message if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous else { HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } } else { // new message msg->offset = fragment - msg->buf; msg->len = msg->offset + size; // check message size if (msg->len > msg->maxLen) { LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; return; } // create new or assign I2NP message if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it m_CurrentMessage.data = NewI2NPTunnelMessage (true); *(m_CurrentMessage.data) = *msg; } else m_CurrentMessage.data = msg; if (isLastFragment) { // single message HandleNextMessage (m_CurrentMessage); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } else if (msgID) { // first fragment of a new message m_CurrentMessage.nextFragmentNum = 1; m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); HandleOutOfSequenceFragments (msgID, m_CurrentMessage); } else { LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } } fragment += size; } } else LogPrint (eLogError, "TunnelMessage: zero not found"); } void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size) { auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; if (fragmentNum == msg.nextFragmentNum) { if (ConcatFollowOnFragment (msg, fragment, size)) { if (isLastFragment) { // message complete HandleNextMessage (msg); m_IncompleteMessages.erase (it); } else { msg.nextFragmentNum++; HandleOutOfSequenceFragments (msgID, msg); } } else { LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } else { LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const { if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long { if (msg.data->len + size > msg.data->maxLen) { // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (fragment, size) < size) // concatenate fragment { LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); return false; } } else return false; return true; } void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment) { if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size)) { if (isLastFragment) { // message complete HandleNextMessage (m_CurrentMessage); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } else { m_CurrentMessage.nextFragmentNum++; HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage); } } else { LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped"); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } } void TunnelEndpoint::AddIncompleteCurrentMessage () { if (m_CurrentMsgID) { auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage); if (!ret.second) LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists"); m_CurrentMessage.data = nullptr; m_CurrentMsgID = 0; } } void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size) { std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); memcpy (f->data.data (), fragment, size); if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg) { while (ConcatNextOutOfSequenceFragment (msgID, msg)) { if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); if (&msg == &m_CurrentMessage) { m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } else m_IncompleteMessages.erase (msgID); LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found"); break; } } } bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum); if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); size_t size = it->second->data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (it->second->isLastFragment) // message complete msg.nextFragmentNum = 0; else msg.nextFragmentNum++; m_OutOfSequenceFragments.erase (it); return true; } return false; } void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { if (!m_IsInbound && msg.data->IsExpired ()) { LogPrint (eLogInfo, "TunnelMessage: message expired"); return; } uint8_t typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); // catch RI or reply with new list of routers if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data)); switch (msg.deliveryType) { case eDeliveryTypeLocal: i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, msg.data); else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; } void TunnelEndpoint::Cleanup () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; } // incomplete messages for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_IncompleteMessages.erase (it); else ++it; } } } } i2pd-2.39.0/libi2pd/TunnelEndpoint.h000066400000000000000000000043641411072525600171210ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 TUNNEL_ENDPOINT_H__ #define TUNNEL_ENDPOINT_H__ #include #include #include #include #include "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelEndpoint { struct TunnelMessageBlockEx: public TunnelMessageBlock { uint64_t receiveTime; // milliseconds since epoch uint8_t nextFragmentNum; }; struct Fragment { Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; bool isLastFragment; uint64_t receiveTime; // milliseconds since epoch std::vector data; }; public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success void HandleCurrenMessageFollowOnFragment (const uint8_t * frgament, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); void AddIncompleteCurrentMessage (); private: std::unordered_map m_IncompleteMessages; std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; uint32_t m_CurrentMsgID; }; } } #endif i2pd-2.39.0/libi2pd/TunnelGateway.cpp000066400000000000000000000162571411072525600173010ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" #include "Transports.h" #include "TunnelGateway.h" namespace i2p { namespace tunnel { TunnelGatewayBuffer::TunnelGatewayBuffer (): m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) { 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; } TunnelGatewayBuffer::~TunnelGatewayBuffer () { ClearTunnelDataMsgs (); } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) { bool messageCreated = false; if (!m_CurrentTunnelDataMsg) { CreateCurrentTunnelDataMessage (); 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); 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 & 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 (); } } if (fullMsgLen <= m_RemainingSize) { // message fits. First and last fragment htobe16buf (di + diLen, 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 ()); m_CurrentTunnelDataMsg->len += diLen + msg->GetLength (); m_RemainingSize -= diLen + msg->GetLength (); if (!m_RemainingSize) CompleteCurrentTunnelDataMessage (); } else { if (diLen + 6 <= m_RemainingSize) { // delivery instructions fit uint32_t msgID; memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // 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); diLen += 4; // Message ID htobe16buf (di + diLen, size); diLen += 2; // size memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen); memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size); m_CurrentTunnelDataMsg->len += diLen + size; CompleteCurrentTunnelDataMessage (); // 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; else // last fragment { buf[0] |= 0x01; isLastFragment = true; } htobuf32 (buf + 1, msgID); //Message ID htobe16buf (buf + 5, 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 (); } } else CompleteCurrentTunnelDataMessage (); size += s; fragmentNumber++; } } 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 = nullptr; m_CurrentTunnelDataMsg = NewI2NPShortMessage (); m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; 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; uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload (); RAND_bytes (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 if (paddingSize > 0) { // non-zero padding auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1); memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize); } // 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 () { m_Buffer.CompleteCurrentTunnelDataMessage (); std::vector > 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); newTunnelMsgs.push_back (newMsg); m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } m_Buffer.ClearTunnelDataMsgs (); i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs); } } } i2pd-2.39.0/libi2pd/TunnelGateway.h000066400000000000000000000026701411072525600167400ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 TUNNEL_GATEWAY_H__ #define TUNNEL_GATEWAY_H__ #include #include #include #include "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelGatewayBuffer { public: TunnelGatewayBuffer (); ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); const std::vector >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; void ClearTunnelDataMsgs (); void CompleteCurrentTunnelDataMessage (); private: void CreateCurrentTunnelDataMessage (); private: std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; }; class TunnelGateway { public: TunnelGateway (TunnelBase * tunnel): m_Tunnel (tunnel), 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 i2pd-2.39.0/libi2pd/TunnelPool.cpp000066400000000000000000000476271411072525600166160ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 #include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" #include "NetDb.hpp" #include "Timestamp.h" #include "Garlic.h" #include "Transports.h" #include "Log.h" #include "Tunnel.h" #include "TunnelPool.h" #include "Destination.h" namespace i2p { namespace tunnel { void Path::Add (std::shared_ptr r) { if (r) { peers.push_back (r->GetRouterIdentity ()); if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION || r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) isShort = false; } } void Path::Reverse () { std::reverse (peers.begin (), peers.end ()); } TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), m_CustomPeerSelector(nullptr) { if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY; if (m_NumOutboundTunnels > TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY) m_NumOutboundTunnels = TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY; m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL; } TunnelPool::~TunnelPool () { DetachTunnels (); } void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) { m_ExplicitPeers = explicitPeers; if (m_ExplicitPeers) { int size = m_ExplicitPeers->size (); if (m_NumInboundHops > size) { m_NumInboundHops = size; LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; } } void TunnelPool::DetachTunnels () { { std::unique_lock l(m_InboundTunnelsMutex); for (auto& it: m_InboundTunnels) it->SetTunnelPool (nullptr); m_InboundTunnels.clear (); } { std::unique_lock l(m_OutboundTunnelsMutex); for (auto& it: m_OutboundTunnels) it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } m_Tests.clear (); } bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) { if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0) { m_NumInboundHops = inHops; m_NumOutboundHops = outHops; m_NumInboundTunnels = inQuant; m_NumOutboundTunnels = outQuant; return true; } return false; } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); } } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); } } std::vector > TunnelPool::GetInboundTunnels (int num) const { std::vector > v; int i = 0; std::shared_ptr slowTunnel; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { if (it->IsSlow () && !slowTunnel) slowTunnel = it; else { v.push_back (it); i++; } } } if (slowTunnel && (int)v.size () < (num/2+1)) v.push_back (slowTunnel); return v; } std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, excluded); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded); } template typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; bool skipped = false; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) { i++; skipped = true; continue; } tunnel = it; i++; } if (i > ind && tunnel) break; } if (!tunnel && skipped) { ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { tunnel = it; i++; } if (i > ind && tunnel) break; } } if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded; return tunnel; } std::shared_ptr TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) const { if (old && old->IsEstablished ()) return old; std::shared_ptr tunnel; if (old) { std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it: m_OutboundTunnels) if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ()) { tunnel = it; break; } } if (!tunnel) tunnel = GetNextOutboundTunnel (); return tunnel; } void TunnelPool::CreateTunnels () { int num = 0; { std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } for (int i = num; i < m_NumOutboundTunnels; i++) CreateOutboundTunnel (); num = 0; { std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { for (auto it: m_OutboundTunnels) { CreatePairedInboundTunnel (it); num++; if (num >= m_NumInboundTunnels) break; } } for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately } void TunnelPool::TestTunnels () { decltype(m_Tests) tests; { std::unique_lock l(m_TestsMutex); tests.swap(m_Tests); } for (auto& it: tests) { LogPrint (eLogWarning, "Tunnels: test of tunnel ", 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 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 l(m_InboundTunnelsMutex); m_InboundTunnels.erase (it.second.second); } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } else it.second.second->SetState (eTunnelStateTestFailed); } } // new tests 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; RAND_bytes ((uint8_t *)&msgID, 4); { std::unique_lock l(m_TestsMutex); m_Tests[msgID] = std::make_pair (*it1, *it2); } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); ++it1; ++it2; } } } void TunnelPool::ManageTunnels (uint64_t ts) { if (ts > m_NextManageTime) { CreateTunnels (); TestTunnels (); m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2; } } void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); uint32_t msgID = bufbe32toh (buf); buf += 4; uint64_t timestamp = bufbe64toh (buf); decltype(m_Tests)::mapped_type test; bool found = false; { std::unique_lock l(m_TestsMutex); auto it = m_Tests.find (msgID); if (it != m_Tests.end ()) { found = true; test = it->second; m_Tests.erase (it); } } if (found) { uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); uint64_t latency = dlt / 2; // restore from test failed state if any if (test.first) { if (test.first->GetState () == eTunnelStateTestFailed) test.first->SetState (eTunnelStateEstablished); // update latency test.first->AddLatencySample(latency); } if (test.second) { if (test.second->GetState () == eTunnelStateTestFailed) test.second->SetState (eTunnelStateEstablished); // update latency test.second->AddLatencySample(latency); } } else { if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } } bool TunnelPool::IsExploratory () const { return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); } std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, bool reverse) const { auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse); if (!hop || hop->GetProfile ()->IsBad ()) hop = i2p::data::netdb.GetRandomRouter (prevHop, reverse); return hop; } bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ auto hop = i2p::transport::transports.GetRestrictedPeer(); if(!hop) return false; path.Add (hop); prevHop = hop; start++; } else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { auto r = i2p::transport::transports.GetRandomPeer (); if (r && !r->GetProfile ()->IsBad () && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { prevHop = r; path.Add (r); start++; } } for(int i = start; i < numHops; i++ ) { auto hop = nextHop (prevHop, inbound); if (!hop && !i) // if no suitable peer found for first hop, try already connected { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); hop = i2p::transport::transports.GetRandomPeer (); } if (!hop) { LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } if ((i == numHops - 1) && (!hop->IsV4 () || // doesn't support ipv4 (inbound && !hop->IsReachable ()))) // IBGW is not reachable { auto hop1 = nextHop (prevHop, true); if (hop1) hop = hop1; } prevHop = hop; path.Add (hop); } return true; } bool TunnelPool::SelectPeers (Path& path, bool isInbound) { int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty if (numHops <= 0) return true; // custom peer selector in use ? { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } // explicit peers in use if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); if (!numHops) return false; for (int i = 0; i < numHops; i++) { auto& ident = (*m_ExplicitPeers)[i]; auto r = i2p::data::netdb.FindRouter (ident); if (r) path.Add (r); else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); i2p::data::netdb.RequestDestination (ident); return false; } } return true; } void TunnelPool::CreateInboundTunnel () { auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); Path path; if (SelectPeers (path, true)) { std::shared_ptr config; if (m_NumInboundHops > 0) { path.Reverse (); config = std::make_shared (path.peers, path.isShort); } auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available"); } void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow { CreateInboundTunnel (); return; } auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) { config = std::make_shared(tunnel->GetPeers ()); } if (!m_NumInboundHops || config) { auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } } void TunnelPool::CreateOutboundTunnel () { auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); Path path; if (SelectPeers (path, false)) { std::shared_ptr config; if (m_NumOutboundHops > 0) config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), path.isShort); std::shared_ptr tunnel; if (path.isShort) { // TODO: implement it better tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); tunnel->SetTunnelPool (shared_from_this ()); } else tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (tunnel && tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow { CreateOutboundTunnel (); return; } auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); std::shared_ptr config; if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) { config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); } if (!m_NumOutboundHops || config) { auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } } else LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); } void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel ( m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) { std::lock_guard lock(m_CustomPeerSelectorMutex); m_CustomPeerSelector = selector; } void TunnelPool::UnsetCustomPeerSelector() { SetCustomPeerSelector(nullptr); } bool TunnelPool::HasCustomPeerSelector() { std::lock_guard lock(m_CustomPeerSelectorMutex); return m_CustomPeerSelector != nullptr; } std::shared_ptr TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_InboundTunnelsMutex); uint64_t min = 1000000; for (const auto & itr : m_InboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } std::shared_ptr TunnelPool::GetLowestLatencyOutboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_OutboundTunnelsMutex); uint64_t min = 1000000; for (const auto & itr : m_OutboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } } } i2pd-2.39.0/libi2pd/TunnelPool.h000066400000000000000000000142771411072525600162560ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 TUNNEL_POOL__ #define TUNNEL_POOL__ #include #include #include #include #include #include #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 { const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds const int TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY = 16; const int TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY = 16; class Tunnel; class InboundTunnel; class OutboundTunnel; typedef std::shared_ptr Peer; struct Path { std::vector peers; bool isShort = true; void Add (std::shared_ptr r); void Reverse (); }; /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector { virtual ~ITunnelPeerSelector() {}; virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; }; typedef std::function(std::shared_ptr, bool)> SelectHopFunc; bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); class TunnelPool: public std::enable_shared_from_this // per local destination { public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); void ManageTunnels (uint64_t ts); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); bool IsExploratory () const; bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); int GetNumInboundTunnels () const { return m_NumInboundTunnels; }; int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; int GetNumInboundHops() const { return m_NumInboundHops; }; int GetNumOutboundHops() const { return m_NumOutboundHops; }; /** i2cp reconfigure */ bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant); void SetCustomPeerSelector(ITunnelPeerSelector * selector); void UnsetCustomPeerSelector(); bool HasCustomPeerSelector(); /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } /** @brief return true if this tunnel pool has a latency requirement */ bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude = nullptr) const; std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude = nullptr) const; // for overriding tunnel peer selection std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse) const; private: void CreateInboundTunnel (); void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; bool SelectPeers (Path& path, bool isInbound); bool SelectExplicitPeers (Path& path, bool isInbound); private: std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; uint64_t m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms uint64_t m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; }; } } #endif i2pd-2.39.0/libi2pd/api.cpp000066400000000000000000000077441411072525600152640ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include "Config.h" #include "Log.h" #include "NetDb.hpp" #include "Transports.h" #include "Tunnel.h" #include "RouterContext.h" #include "Identity.h" #include "Destination.h" #include "Crypto.h" #include "FS.h" #include "api.h" namespace i2p { namespace api { void InitI2P (int argc, char* argv[], const char * appName) { i2p::config::Init (); i2p::config::ParseCmdline (argc, argv, true); // ignore unknown options and help i2p::config::Finalize (); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::SetAppName (appName); i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); bool avx; i2p::config::GetOption("cpuext.avx", avx); bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); i2p::context.Init (); } void TerminateI2P () { i2p::crypto::TerminateCrypto (); } void StartI2P (std::shared_ptr logStream) { if (logStream) i2p::log::Logger().SendTo (logStream); else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: starting Transports"); i2p::transport::transports.Start(); LogPrint(eLogInfo, "API: starting Tunnels"); i2p::tunnel::tunnels.Start(); } void StopI2P () { LogPrint(eLogInfo, "API: shutting down"); LogPrint(eLogInfo, "API: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); LogPrint(eLogInfo, "API: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "API: stopping NetDB"); i2p::data::netdb.Stop(); i2p::log::Logger().Stop (); } void RunPeerTest () { i2p::transport::transports.PeerTest (); } std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } std::shared_ptr CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } void DestroyLocalDestination (std::shared_ptr dest) { if (dest) dest->Stop (); } void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (dest) dest->RequestDestination (remote); } std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (!dest) return nullptr; auto leaseSet = dest->FindLeaseSet (remote); if (leaseSet) { auto stream = dest->CreateStream (leaseSet); stream->Send (nullptr, 0); // connect return stream; } else { RequestLeaseSet (dest, remote); return nullptr; } } void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (dest) dest->AcceptStreams (acceptor); } void DestroyStream (std::shared_ptr stream) { if (stream) stream->Close (); } } } i2pd-2.39.0/libi2pd/api.h000066400000000000000000000033761411072525600147260ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 API_H__ #define API_H__ #include #include #include "Identity.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace api { // initialization start and stop void InitI2P (int argc, char* argv[], const char * appName); void TerminateI2P (); void StartI2P (std::shared_ptr logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); void RunPeerTest (); // should be called after UPnP // destinations std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); std::shared_ptr CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, const std::map * params = nullptr); // transient destinations usually not published void DestroyLocalDestination (std::shared_ptr dest); // streams void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); void DestroyStream (std::shared_ptr stream); } } #endif i2pd-2.39.0/libi2pd/util.cpp000066400000000000000000000375441411072525600154710ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include "util.h" #include "Log.h" #if not defined (__FreeBSD__) #include #endif #if defined(__OpenBSD__) || defined(__FreeBSD__) #include #endif #ifdef _WIN32 #include #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma comment(lib, "IPHLPAPI.lib") #endif // _MSC_VER #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) // inet_pton exists Windows since Vista, but XP doesn't have that function! // This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found int inet_pton_xp (int af, const char *src, void *dst) { struct sockaddr_storage ss; int size = sizeof (ss); char src_copy[INET6_ADDRSTRLEN + 1]; ZeroMemory (&ss, sizeof (ss)); strncpy (src_copy, src, INET6_ADDRSTRLEN + 1); src_copy[INET6_ADDRSTRLEN] = 0; if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { switch (af) { case AF_INET: *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; return 1; case AF_INET6: *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; return 1; } } return 0; } #else /* !_WIN32 => UNIX */ #include #ifdef ANDROID #include "ifaddrs.h" #else #include #endif #endif #define address_pair_v4(a,b) { boost::asio::ip::address_v4::from_string (a).to_ulong (), boost::asio::ip::address_v4::from_string (b).to_ulong () } #define address_pair_v6(a,b) { boost::asio::ip::address_v6::from_string (a).to_bytes (), boost::asio::ip::address_v6::from_string (b).to_bytes () } namespace i2p { namespace util { void RunnableService::StartIOService () { if (!m_IsRunning) { m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (& RunnableService::Run, this))); } } void RunnableService::StopIOService () { if (m_IsRunning) { m_IsRunning = false; m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread = nullptr; } } } void RunnableService::Run () { SetThreadName(m_Name.c_str()); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, m_Name, ": runtime exception: ", ex.what ()); } } } void SetThreadName (const char *name) { #if defined(__APPLE__) pthread_setname_np(name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); #else pthread_setname_np(pthread_self(), name); #endif } namespace net { #ifdef _WIN32 bool IsWindowsXPorLater () { static bool isRequested = false; static bool isXP = false; if (!isRequested) { // request OSVERSIONINFO osvi; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); isXP = osvi.dwMajorVersion <= 5; isRequested = true; } return isXP; } int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv4 address, this is not supported"); for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { auto result = pAddresses->Mtu; FREE(pAddresses); return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported"); for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; for (int j = 0; j != 8; ++j) { if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) break; else found_address = true; } if (found_address) { auto result = pAddresses->Mtu; FREE(pAddresses); pAddresses = nullptr; return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindows (const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else std::string localAddressUniversal = localAddress.to_string(); #endif typedef int (* IPN)(int af, const char *src, void *dst); IPN inetpton = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found if(localAddress.is_v4()) { sockaddr_in inputAddress; inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } else if(localAddress.is_v6()) { sockaddr_in6 inputAddress; inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); } else { LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); return fallback; } } #else // assume unix int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) { LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); return fallback; } int family = 0; // look for interface matching local address for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if(!ifa->ifa_addr) continue; family = ifa->ifa_addr->sa_family; if(family == AF_INET && localAddress.is_v4()) { sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) break; // address matches } else if(family == AF_INET6 && localAddress.is_v6()) { sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) break; // address matches } } int mtu = fallback; if(ifa && family) { // interface found? int fd = socket(family, SOCK_DGRAM, 0); if(fd > 0) { ifreq ifr; strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ-1); // set interface for query if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); close(fd); } else LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; } #endif // _WIN32 int GetMTU (const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU #ifdef _WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); #endif return fallback; } const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6) { #ifdef _WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); if(ipv6) return boost::asio::ip::address::from_string("::1"); else return boost::asio::ip::address::from_string("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs *addrs, *cur = nullptr; if(getifaddrs(&addrs) == 0) { // got ifaddrs cur = addrs; while(cur) { std::string cur_ifname(cur->ifa_name); if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match char addr[INET6_ADDRSTRLEN]; memset (addr, 0, INET6_ADDRSTRLEN); if(af == AF_INET) inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN); else inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN); freeifaddrs(addrs); std::string cur_ifaddr(addr); return boost::asio::ip::address::from_string(cur_ifaddr); } cur = cur->ifa_next; } } if(addrs) freeifaddrs(addrs); std::string fallback; if(ipv6) { fallback = "::1"; LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname); } else { fallback = "127.0.0.1"; LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname); } return boost::asio::ip::address::from_string(fallback); #endif } static bool IsYggdrasilAddress (const uint8_t addr[16]) { return addr[0] == 0x02 || addr[0] == 0x03; } bool IsYggdrasilAddress (const boost::asio::ip::address& addr) { if (!addr.is_v6 ()) return false; return IsYggdrasilAddress (addr.to_v6 ().to_bytes ().data ()); } boost::asio::ip::address_v6 GetYggdrasilAddress () { #if defined(_WIN32) ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetYggdrasilAddress(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return boost::asio::ip::address_v6 (); } pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; if (IsYggdrasilAddress(localInterfaceAddress->sin6_addr.u.Byte)) { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), &localInterfaceAddress->sin6_addr.u.Byte, 16); FREE(pAddresses); return boost::asio::ip::address_v6 (bytes); } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogWarning, "NetIface: interface with yggdrasil network address not found"); FREE(pAddresses); return boost::asio::ip::address_v6 (); #else ifaddrs *addrs, *cur = nullptr; auto err = getifaddrs(&addrs); if (!err) { cur = addrs; while(cur) { if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET6) { sockaddr_in6* sa = (sockaddr_in6*)cur->ifa_addr; if (IsYggdrasilAddress(sa->sin6_addr.s6_addr)) { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), &sa->sin6_addr, 16); freeifaddrs(addrs); return boost::asio::ip::address_v6 (bytes); } } cur = cur->ifa_next; } } LogPrint(eLogWarning, "NetIface: interface with yggdrasil network address not found"); if(addrs) freeifaddrs(addrs); return boost::asio::ip::address_v6 (); #endif } bool IsLocalAddress (const boost::asio::ip::address& addr) { auto mtu = // TODO: implement better #ifdef _WIN32 GetMTUWindows(addr, 0); #else GetMTUUnix(addr, 0); #endif return mtu > 0; } bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if (host.is_unspecified ()) return false; if(host.is_v4()) { static const std::vector< std::pair > reservedIPv4Ranges { address_pair_v4("0.0.0.0", "0.255.255.255"), address_pair_v4("10.0.0.0", "10.255.255.255"), address_pair_v4("100.64.0.0", "100.127.255.255"), address_pair_v4("127.0.0.0", "127.255.255.255"), address_pair_v4("169.254.0.0", "169.254.255.255"), address_pair_v4("172.16.0.0", "172.31.255.255"), address_pair_v4("192.0.0.0", "192.0.0.255"), address_pair_v4("192.0.2.0", "192.0.2.255"), address_pair_v4("192.88.99.0", "192.88.99.255"), address_pair_v4("192.168.0.0", "192.168.255.255"), address_pair_v4("198.18.0.0", "192.19.255.255"), address_pair_v4("198.51.100.0", "198.51.100.255"), address_pair_v4("203.0.113.0", "203.0.113.255"), address_pair_v4("224.0.0.0", "255.255.255.255") }; uint32_t ipv4_address = host.to_v4 ().to_ulong (); for(const auto& it : reservedIPv4Ranges) { if (ipv4_address >= it.first && ipv4_address <= it.second) return true; } } if(host.is_v6()) { static const std::vector< std::pair > reservedIPv6Ranges { address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("::", "::"), address_pair_v6("::1", "::1") }; boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); for(const auto& it : reservedIPv6Ranges) { if (ipv6_address >= it.first && ipv6_address <= it.second) return true; } if (IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? return true; } return false; } } // net } // util } // i2p i2pd-2.39.0/libi2pd/util.h000066400000000000000000000101461411072525600151230ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 UTIL_H #define UTIL_H #include #include #include #include #include #include #include #ifdef ANDROID #ifndef __clang__ #include namespace std { template std::string to_string(T value) { return boost::lexical_cast(value); } inline int stoi(const std::string& str) { return boost::lexical_cast(str); } } #endif #endif namespace i2p { namespace util { template class MemoryPool { //BOOST_STATIC_ASSERT_MSG(sizeof(T) >= sizeof(void*), "size cannot be less that general pointer size"); public: MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { while (m_Head) { auto tmp = m_Head; m_Head = static_cast(*(void * *)m_Head); // next ::operator delete ((void *)tmp); } } template T * Acquire (TArgs&&... args) { if (!m_Head) return new T(std::forward(args)...); else { auto tmp = m_Head; m_Head = static_cast(*(void * *)m_Head); // next return new (tmp)T(std::forward(args)...); } } void Release (T * t) { if (!t) return; t->~T (); *(void * *)t = m_Head; // next m_Head = t; } template std::unique_ptr > AcquireUnique (TArgs&&... args) { return std::unique_ptr >(Acquire (std::forward(args)...), std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } template std::shared_ptr AcquireShared (TArgs&&... args) { return std::shared_ptr(Acquire (std::forward(args)...), std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } protected: T * m_Head; }; template class MemoryPoolMt: public MemoryPool { public: MemoryPoolMt () {} template T * AcquireMt (TArgs&&... args) { if (!this->m_Head) return new T(std::forward(args)...); std::lock_guard l(m_Mutex); return this->Acquire (std::forward(args)...); } void ReleaseMt (T * t) { std::lock_guard l(m_Mutex); this->Release (t); } templateclass C, typename... R> void ReleaseMt(const C& c) { std::lock_guard l(m_Mutex); for (auto& it: c) this->Release (it); } private: std::mutex m_Mutex; }; class RunnableService { protected: RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {} virtual ~RunnableService () {} boost::asio::io_service& GetIOService () { return m_Service; } bool IsRunning () const { return m_IsRunning; }; void StartIOService (); void StopIOService (); private: void Run (); private: std::string m_Name; volatile bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; }; class RunnableServiceWithWork: public RunnableService { protected: RunnableServiceWithWork (const std::string& name): RunnableService (name), m_Work (GetIOService ()) {} private: boost::asio::io_service::work m_Work; }; void SetThreadName (const char *name); template class SaveStateHelper { public: SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; ~SaveStateHelper () { m_Original = m_Copy; }; private: T& m_Original; T m_Copy; }; namespace net { int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); bool IsLocalAddress (const boost::asio::ip::address& addr); bool IsInReservedRange (const boost::asio::ip::address& host); bool IsYggdrasilAddress (const boost::asio::ip::address& addr); } } } #endif i2pd-2.39.0/libi2pd/version.h000066400000000000000000000020201411072525600156230ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 _VERSION_H_ #define _VERSION_H_ #define CODENAME "Purple" #define STRINGIZE(x) #x #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MINOR 39 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION #ifdef MESHNET #define I2PD_NET_ID 3 #else #define I2PD_NET_ID 2 #endif #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 #define I2P_VERSION_MICRO 51 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #endif i2pd-2.39.0/libi2pd_client/000077500000000000000000000000001411072525600153315ustar00rootroot00000000000000i2pd-2.39.0/libi2pd_client/AddressBook.cpp000066400000000000000000000753241411072525600202500ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include #include #include #include #include #include #include #include "Base.h" #include "util.h" #include "Identity.h" #include "FS.h" #include "Log.h" #include "HTTP.h" #include "NetDb.hpp" #include "ClientContext.h" #include "AddressBook.h" #include "Config.h" namespace i2p { namespace client { // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { public: AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") { i2p::config::GetOption("persist.addressbook", m_IsPersist); if (m_IsPersist) i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); } std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); bool Init (); int Load (std::map > & addresses); int LoadLocal (std::map >& addresses); int Save (const std::map >& addresses); void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); void ResetEtags (); private: int LoadFromFile (const std::string& filename, std::map >& addresses); // returns -1 if can't open file, otherwise number of records private: i2p::fs::HashedStorage storage; std::string etagsPath, indexPath, localPath; bool m_IsPersist; std::string m_HostsFile; // file to dump hosts.txt, empty if not used }; bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); // init storage if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32)) { // init ETags etagsPath = i2p::fs::StorageRootPath (storage, "etags"); if (!i2p::fs::Exists (etagsPath)) i2p::fs::CreateDirectory (etagsPath); // init address files indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); localPath = i2p::fs::StorageRootPath (storage, "local.csv"); return true; } return false; } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); return nullptr; } std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); if (!f.is_open ()) { LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); return nullptr; } f.seekg (0,std::ios::end); size_t len = f.tellg (); if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); return nullptr; } f.seekg(0, std::ios::beg); uint8_t * buf = new uint8_t[len]; f.read((char *)buf, len); auto address = std::make_shared(buf, len); delete[] buf; return address; } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { if (!m_IsPersist) return; std::string path = storage.Path( address->GetIdentHash().ToBase32() ); std::ofstream f (path, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint (eLogError, "Addressbook: can't open file ", path); return; } size_t len = address->GetFullLen (); uint8_t * buf = new uint8_t[len]; address->ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map >& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode if (!f) return -1; addresses.clear (); while (!f.eof ()) { std::string s; getline(f, s); if (!s.length()) continue; // skip empty line std::size_t pos = s.find(','); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); addresses[name] = std::make_shared
(addr); num++; } } return num; } int AddressBookFilesystemStorage::Load (std::map >& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) { LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; } int AddressBookFilesystemStorage::LoadLocal (std::map >& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded"); return num; } int AddressBookFilesystemStorage::Save (const std::map >& addresses) { if (addresses.empty()) { LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); return 0; } int num = 0; { // save index file std::ofstream f (indexPath, std::ofstream::out); // in text mode if (f.is_open ()) { for (const auto& it: addresses) { if (it.second->IsValid ()) { f << it.first << ","; if (it.second->IsIdentHash ()) f << it.second->identHash.ToBase32 (); else f << it.second->blindedPublicKey->ToB33 (); f << std::endl; num++; } else LogPrint (eLogWarning, "Addressbook: invalid address ", it.first); } LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); } else LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); } if (!m_HostsFile.empty ()) { // dump full hosts.txt std::ofstream f (m_HostsFile, std::ofstream::out); // in text mode if (f.is_open ()) { for (const auto& it: addresses) { std::shared_ptr addr; if (it.second->IsIdentHash ()) { addr = GetAddress (it.second->identHash); if (addr) f << it.first << "=" << addr->ToBase64 () << std::endl; } } } else LogPrint (eLogWarning, "Addressbook: Can't open ", m_HostsFile); } return num; } void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); if (f) { f << etag << std::endl; f<< lastModified << std::endl; } } bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ifstream f (fname, std::ofstream::in); if (!f || f.eof ()) return false; std::getline (f, etag); if (f.eof ()) return false; std::getline (f, lastModified); return true; } void AddressBookFilesystemStorage::ResetEtags () { LogPrint (eLogError, "Addressbook: resetting eTags"); for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it) { if (!boost::filesystem::is_regular_file (it->status ())) continue; boost::filesystem::remove (it->path ()); } } //--------------------------------------------------------------------- Address::Address (const std::string& b32): addressType (eAddressInvalid) { if (b32.length () <= B33_ADDRESS_THRESHOLD) { if (identHash.FromBase32 (b32) > 0) addressType = eAddressIndentHash; } else { blindedPublicKey = std::make_shared(b32); if (blindedPublicKey->IsValid ()) addressType = eAddressBlindedPublicKey; } } Address::Address (const i2p::data::IdentHash& hash) { addressType = eAddressIndentHash; identHash = hash; } AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } AddressBook::~AddressBook () { Stop (); } void AddressBook::Start () { if (!m_Storage) m_Storage = new AddressBookFilesystemStorage; m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); StartLookups (); } void AddressBook::StartResolvers () { LoadLocal (); } void AddressBook::Stop () { StopLookups (); StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { delete m_SubscriptionsUpdateTimer; m_SubscriptionsUpdateTimer = nullptr; } if (m_IsDownloading) { LogPrint (eLogInfo, "Addressbook: subscriptions are downloading, abort"); for (int i = 0; i < 30; i++) { if (!m_IsDownloading) { LogPrint (eLogInfo, "Addressbook: subscriptions download complete"); break; } std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds } LogPrint (eLogError, "Addressbook: subscription download timeout"); m_IsDownloading = false; } if (m_Storage) { m_Storage->Save (m_Addresses); delete m_Storage; m_Storage = nullptr; } m_DefaultSubscription = nullptr; m_Subscriptions.clear (); } std::shared_ptr AddressBook::GetAddress (const std::string& address) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) { auto addr = std::make_shared(address.substr (0, pos)); return addr->IsValid () ? addr : nullptr; } else { pos = address.find (".i2p"); if (pos != std::string::npos) { auto addr = FindAddress (address); if (!addr) LookupAddress (address); // TODO: return addr; } } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; if (!dest.FromBase64 (address)) return nullptr; return std::make_shared(dest.GetIdentHash ()); } std::shared_ptr AddressBook::FindAddress (const std::string& address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) return it->second; return nullptr; } void AddressBook::InsertAddress (const std::string& address, const std::string& jump) { auto pos = jump.find(".b32.i2p"); if (pos != std::string::npos) { m_Addresses[address] = std::make_shared
(jump.substr (0, pos)); LogPrint (eLogInfo, "Addressbook: added ", address," -> ", jump); } else { // assume base64 auto ident = std::make_shared(); if (ident->FromBase64 (jump)) { m_Storage->AddAddress (ident); m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); } else LogPrint (eLogError, "Addressbook: malformed address ", jump); } } void AddressBook::InsertFullAddress (std::shared_ptr address) { m_Storage->AddAddress (address); } std::shared_ptr AddressBook::GetFullAddress (const std::string& address) { auto addr = GetAddress (address); if (!addr || !addr->IsIdentHash ()) return nullptr; return m_Storage->GetAddress (addr->identHash); } void AddressBook::LoadHosts () { if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; return; } // then try hosts.txt std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { LoadHostsFromStream (f, false); m_IsLoaded = true; } // reset eTags, because we don’t know how old hosts.txt is or can't load addressbook m_Storage->ResetEtags (); } bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; bool incomplete = false; std::string s; while (!f.eof ()) { getline(f, s); if (!s.length() || s[0] == '#') continue; // skip empty or comment line size_t pos = s.find('='); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); size_t pos = addr.find('#'); if (pos != std::string::npos) addr = addr.substr(0, pos); // remove comments auto ident = std::make_shared (); if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); incomplete = f.eof (); continue; } numAddresses++; auto it = m_Addresses.find (name); if (it != m_Addresses.end ()) // already exists ? { if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash () && // address changed? ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); m_Storage->AddAddress (ident); m_Storage->RemoveAddress (it->second->identHash); LogPrint (eLogInfo, "Addressbook: updated host: ", name); } } else { m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: added new host: ", name); } } else incomplete = f.eof (); } LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; m_Storage->Save (m_Addresses); } return !incomplete; } void AddressBook::LoadSubscriptions () { if (!m_Subscriptions.size ()) { std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { std::string s; while (!f.eof ()) { getline(f, s); if (s.empty () || s[0] == '#') continue; // skip empty line or comment m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); } else if (!i2p::config::IsDefault("addressbook.subscriptions")) { // using config file items std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); for (const auto& s: subsList) { if (s.empty () || s[0] == '#') continue; // skip empty line or comment m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } void AddressBook::LoadLocal () { std::map> localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { if (!it.second->IsIdentHash ()) continue; // skip blinded for now auto dot = it.first.find ('.'); if (dot != std::string::npos) { auto domain = it.first.substr (dot + 1); auto it1 = m_Addresses.find (domain); // find domain in our addressbook if (it1 != m_Addresses.end () && it1->second->IsIdentHash ()) { auto dest = context.FindLocalDestination (it1->second->identHash); if (dest) { // address is ours std::shared_ptr resolver; auto it2 = m_Resolvers.find (it1->second->identHash); if (it2 != m_Resolvers.end ()) resolver = it2->second; // resolver exists else { // create new resolver resolver = std::make_shared(dest); m_Resolvers.insert (std::make_pair(it1->second->identHash, resolver)); } resolver->AddAddress (it.first, it.second->identHash); } } } } } bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { if (m_Storage) return m_Storage->GetEtag (subscription, etag, lastModified); else return false; } void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { m_IsDownloading = false; m_NumRetries++; int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; if (success) { m_NumRetries = 0; if (m_DefaultSubscription) m_DefaultSubscription = nullptr; if (m_IsLoaded) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; else m_IsLoaded = true; if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified); } if (m_SubscriptionsUpdateTimer) { m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } void AddressBook::StartSubscriptions () { LoadSubscriptions (); if (m_IsLoaded && m_Subscriptions.empty ()) return; auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ()); m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } else LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () { if (m_SubscriptionsUpdateTimer) m_SubscriptionsUpdateTimer->cancel (); } void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) { LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update"); return; } if (!m_IsDownloading && dest->IsReady ()) { if (!m_IsLoaded) { // download it from default subscription LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); m_IsDownloading = true; std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); load_hosts.detach(); // TODO: use join } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); load_hosts.detach(); // TODO: use join } } else { // try it again later m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } } void AddressBook::StartLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (!datagram) datagram = dest->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::StopLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::LookupAddress (const std::string& address) { std::shared_ptr addr; auto dot = address.find ('.'); if (dot != std::string::npos) addr = FindAddress (address.substr (dot + 1)); if (!addr || !addr->IsIdentHash ()) // TODO: { LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; } auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) { uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); { std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", addr->identHash.ToBase32 (), " nonce=", nonce); size_t len = address.length () + 9; uint8_t * buf = new uint8_t[len]; memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); memcpy (buf + 9, address.c_str (), address.length ()); datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } } } void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 44) { LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); return; } uint32_t nonce = bufbe32toh (buf + 4); LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); std::string address; { std::unique_lock l(m_LookupsMutex); auto it = m_Lookups.find (nonce); if (it != m_Lookups.end ()) { address = it->second; m_Lookups.erase (it); } } if (address.length () > 0) { // TODO: verify from i2p::data::IdentHash hash(buf + 8); if (!hash.IsZero ()) m_Addresses[address] = std::make_shared
(hash); else LogPrint (eLogInfo, "AddressBook: Lookup response: ", address, " not found"); } } AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): m_Book (book), m_Link (link) { } void AddressBookSubscription::CheckUpdates () { i2p::util::SetThreadName("Addressbook"); bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } bool AddressBookSubscription::MakeRequest () { i2p::http::URL url; // must be run in separate thread LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); if (!url.parse(m_Link)) { LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); return false; } auto addr = m_Book.GetAddress (url.host); if (!addr || !addr->IsIdentHash ()) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return false; } else m_Ident = addr->identHash; /* this code block still needs some love */ std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); if (!leaseSet) { std::unique_lock l(newDataReceivedMutex); i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) { leaseSet = ls; std::unique_lock l1(newDataReceivedMutex); newDataReceived.notify_all (); }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already return false; } } if (!leaseSet) { /* still no leaseset found */ LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; } if (m_Etag.empty() && m_LastModified.empty()) { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } /* save url parts for later use */ std::string dest_host = url.host; int dest_port = url.port ? url.port : 80; /* create http request & send it */ i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); req.AddHeader("Accept-Encoding", "gzip"); req.AddHeader("X-Accept-Encoding", "x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0"); req.AddHeader("Connection", "close"); if (!m_Etag.empty()) req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); /* convert url to relative */ url.schema = ""; url.host = ""; req.uri = url.to_string(); req.version = "HTTP/1.1"; auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); /* read response */ std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (bytes_transferred) response.append ((char *)recv_buf, bytes_transferred); if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) end = true; newDataReceived.notify_all (); }, SUBSCRIPTION_REQUEST_TIMEOUT); std::unique_lock l(newDataReceivedMutex); // wait 1 more second if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT + 1)) == std::cv_status::timeout) { LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; } } // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); /* parse response */ i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) { LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); return false; } if (res_head_len == 0) { LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } /* assert: res_head_len > 0 */ response.erase(0, res_head_len); if (res.code == 304) { LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); return false; } if (res.code != 200) { LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); return false; } int len = res.content_length(); if (response.empty()) { LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); return false; } if (!res.is_gzipped () && len > 0 && len != (int) response.length()) { LogPrint(eLogError, "Addressbook: response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } /* assert: res.code == 200 */ auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("Last-Modified"); if (it != res.headers.end()) m_LastModified = it->second; if (res.is_chunked()) { std::stringstream in(response), out; i2p::http::MergeChunkedResponse (in, out); response = out.str(); } if (res.is_gzipped()) { std::stringstream out; i2p::data::GzipInflator inflator; inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); if (out.fail()) { LogPrint(eLogError, "Addressbook: can't gunzip http response"); return false; } response = out.str(); } std::stringstream ss(response); LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); m_Book.LoadHostsFromStream (ss, true); return true; } AddressResolver::AddressResolver (std::shared_ptr destination): m_LocalDestination (destination) { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (!datagram) datagram = m_LocalDestination->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESOLVER_DATAGRAM_PORT); } } AddressResolver::~AddressResolver () { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); } } void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 9 || len < buf[8] + 9U) { LogPrint (eLogError, "Addressbook: Address request is too short ", len); return; } // read requested address uint8_t l = buf[8]; char address[255]; memcpy (address, buf + 9, l); address[l] = 0; LogPrint (eLogDebug, "Addressbook: Address request ", address); // send response uint8_t response[44]; memset (response, 0, 4); // reserved memcpy (response + 4, buf + 4, 4); // nonce auto it = m_LocalAddresses.find (address); // address lookup if (it != m_LocalAddresses.end ()) memcpy (response + 8, it->second, 32); // ident else memset (response + 8, 0, 32); // not found memset (response + 40, 0, 4); // set expiration time to zero m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash(), toPort, fromPort); } void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) { m_LocalAddresses[name] = ident; } } } i2pd-2.39.0/libi2pd_client/AddressBook.h000066400000000000000000000132701411072525600177050ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 ADDRESS_BOOK_H__ #define ADDRESS_BOOK_H__ #include #include #include #include #include #include #include #include #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" #include "LeaseSet.h" namespace i2p { namespace client { const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES = 10; // then update timeout const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in second const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; const size_t B33_ADDRESS_THRESHOLD = 52; // characters struct Address { enum { eAddressIndentHash, eAddressBlindedPublicKey, eAddressInvalid } addressType; i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; Address (const std::string& b32); Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; bool IsValid () const { return addressType != eAddressInvalid; }; }; inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } class AddressBookStorage // interface for storage { public: virtual ~AddressBookStorage () {}; virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 0; virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; virtual bool Init () = 0; virtual int Load (std::map >& addresses) = 0; virtual int LoadLocal (std::map >& addresses) = 0; virtual int Save (const std::map >& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0; virtual void ResetEtags () = 0; }; class AddressBookSubscription; class AddressResolver; class AddressBook { public: AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); std::shared_ptr GetAddress (const std::string& address); std::shared_ptr GetFullAddress (const std::string& address); std::shared_ptr FindAddress (const std::string& address); void LookupAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& jump); // for jump links void InsertFullAddress (std::shared_ptr address); bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); private: void StartSubscriptions (); void StopSubscriptions (); void LoadHosts (); void LoadSubscriptions (); void LoadLocal (); void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); void StartLookups (); void StopLookups (); void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: std::mutex m_AddressBookMutex; std::map > m_Addresses; std::map > m_Resolvers; // local destination->resolver std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; int m_NumRetries; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; }; class AddressBookSubscription { public: AddressBookSubscription (AddressBook& book, const std::string& link); void CheckUpdates (); private: bool MakeRequest (); private: AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; i2p::data::IdentHash m_Ident; // m_Etag must be surrounded by "" }; class AddressResolver { public: AddressResolver (std::shared_ptr destination); ~AddressResolver (); void AddAddress (const std::string& name, const i2p::data::IdentHash& ident); private: void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: std::shared_ptr m_LocalDestination; std::map m_LocalAddresses; }; } } #endif i2pd-2.39.0/libi2pd_client/BOB.cpp000066400000000000000000000644521411072525600164520ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Log.h" #include "ClientContext.h" #include "util.h" #include "BOB.h" namespace i2p { namespace client { BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) { } BOBI2PInboundTunnel::~BOBI2PInboundTunnel () { Stop (); } void BOBI2PInboundTunnel::Start () { m_Acceptor.listen (); Accept (); } void BOBI2PInboundTunnel::Stop () { m_Acceptor.close(); ClearHandlers (); } void BOBI2PInboundTunnel::Accept () { auto receiver = std::make_shared (); receiver->socket = std::make_shared (GetService ()); m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, std::placeholders::_1, receiver)); } void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver) { if (!ecode) { Accept (); ReceiveAddress (receiver); } } void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( receiver->buffer + receiver->bufferOffset, BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, std::placeholders::_1, std::placeholders::_2, receiver)); } void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver) { if (ecode) LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ()); else { receiver->bufferOffset += bytes_transferred; receiver->buffer[receiver->bufferOffset] = 0; char * eol = strchr (receiver->buffer, '\n'); if (eol) { *eol = 0; if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address receiver->data = (uint8_t *)eol + 1; receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1); auto addr = context.GetAddressBook ().GetAddress (receiver->buffer); if (!addr) { LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); return; } if (addr->IsIdentHash ()) { auto leaseSet = GetLocalDestination ()->FindLeaseSet (addr->identHash); if (leaseSet) CreateConnection (receiver, leaseSet); else GetLocalDestination ()->RequestDestination (addr->identHash, std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, this, std::placeholders::_1, receiver)); } else GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, this, std::placeholders::_1, receiver)); } else { if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else LogPrint (eLogError, "BOB: missing inbound address"); } } } void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver) { if (leaseSet) CreateConnection (receiver, leaseSet); else LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found"); } void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) { LogPrint (eLogDebug, "BOB: New inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); } BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet) { } void BOBI2POutboundTunnel::Start () { Accept (); } void BOBI2POutboundTunnel::Stop () { ClearHandlers (); } void BOBI2POutboundTunnel::Accept () { auto localDestination = GetLocalDestination (); if (localDestination) localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1)); else LogPrint (eLogError, "BOB: Local destination not set for server tunnel"); } void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } } BOBDestination::BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, const int inport, const int outport, const bool quiet): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), m_InPort(inport), m_OutPort(outport), m_Quiet(quiet) { } BOBDestination::~BOBDestination () { delete m_OutboundTunnel; delete m_InboundTunnel; i2p::client::context.DeleteLocalDestination (m_LocalDestination); } void BOBDestination::Start () { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); } void BOBDestination::Stop () { StopTunnels (); m_LocalDestination->Stop (); } void BOBDestination::StopTunnels () { if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); delete m_OutboundTunnel; m_OutboundTunnel = nullptr; } if (m_InboundTunnel) { m_InboundTunnel->Stop (); delete m_InboundTunnel; m_InboundTunnel = nullptr; } } void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost) { if (!m_InboundTunnel) { // update inport and inhost (user can stop tunnel and change) m_InPort = port; m_InHost = inhost; boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port); if (!inhost.empty ()) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (inhost, ec); if (!ec) ep.address (addr); else LogPrint (eLogError, "BOB: ", ec.message ()); } m_InboundTunnel = new BOBI2PInboundTunnel (ep, m_LocalDestination); } } void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet) { if (!m_OutboundTunnel) { // update outport and outhost (user can stop tunnel and change) m_OutPort = port; m_OutHost = outhost; m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet); } } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } BOBCommandSession::~BOBCommandSession () { } void BOBCommandSession::Terminate () { m_Socket.close (); m_IsOpen = false; } void BOBCommandSession::Receive () { boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n', std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) { if(ecode) { LogPrint (eLogError, "BOB: command channel read error: ", ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { std::string line; std::istream is(&m_ReceiveBuffer); std::getline(is, line); std::string command, operand; std::istringstream iss(line); iss >> command >> operand; // process command auto& handlers = m_Owner.GetCommandHandlers(); auto it = handlers.find(command); if(it != handlers.end()) { (this->*(it->second))(operand.c_str(), operand.length()); } else { LogPrint (eLogError, "BOB: unknown command ", command.c_str()); SendReplyError ("unknown command"); } } } void BOBCommandSession::Send () { boost::asio::async_write (m_Socket, m_SendBuffer, boost::asio::transfer_all (), std::bind(&BOBCommandSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { if (m_IsOpen) Receive (); else Terminate (); } } void BOBCommandSession::SendReplyOK (const char * msg) { std::ostream os(&m_SendBuffer); os << "OK"; if(msg) { os << " " << msg; } os << std::endl; Send (); } void BOBCommandSession::SendReplyError (const char * msg) { std::ostream os(&m_SendBuffer); os << "ERROR " << msg << std::endl; Send (); } void BOBCommandSession::SendVersion () { std::ostream os(&m_SendBuffer); os << "BOB 00.00.10" << std::endl; SendReplyOK(); } void BOBCommandSession::SendRaw (const char * data) { std::ostream os(&m_SendBuffer); os << data << std::endl; } void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out) { // helper lambdas const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); }; const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str // tunnel info const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet(); const std::string inhost = issetStr(currentTunnel ? m_InHost : dest->GetInHost()); const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost()); const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort()); const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort()); const bool keys = destExists(dest); // key must exist when destination is created const bool starting = destExists(dest) && !destReady(dest); const bool running = destExists(dest) && destReady(dest); const bool stopping = false; // build line std::stringstream ss; ss << "DATA " << "NICKNAME: " << nickname << " " << "STARTING: " << bool_str(starting) << " " << "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " " << "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " " << "INPORT: " << inport << " " << "INHOST: " << inhost << " " << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost; out = ss.str(); } void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: zap"); Terminate (); } void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quit"); m_IsOpen = false; SendReplyOK ("Bye!"); } void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (m_IsActive) { SendReplyError ("tunnel is active"); return; } if (!m_Keys.GetPublic ()) // keys are set ? { SendReplyError("Keys must be set."); return; } if (m_InPort == 0 && m_OutHost.empty() && m_OutPort == 0) { SendReplyError("(inhost):inport or outhost:outport must be set."); return; } if(!m_InHost.empty()) { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; boost::asio::ip::address::from_string(m_InHost, ec); if (ec) { SendReplyError("inhost must be a valid IPv4 address."); return; } } if(!m_OutHost.empty()) { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; boost::asio::ip::address::from_string(m_OutHost, ec); if (ec) { SendReplyError("outhost must be a IPv4 address."); return; } } if (!m_CurrentDestination) { m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); if (m_OutPort && !m_OutHost.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); SendReplyOK ("Tunnel starting"); m_IsActive = true; } void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: stop ", m_Nickname); if (!m_IsActive) { SendReplyError ("tunnel is inactive"); return; } auto dest = m_Owner.FindDestination (m_Nickname); if (dest) { dest->StopTunnels (); SendReplyOK ("Tunnel stopping"); } else SendReplyError ("tunnel not found"); m_IsActive = false; } void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setnick ", operand); m_Nickname = operand; std::string msg ("Nickname set to "); msg += m_Nickname; SendReplyOK (msg.c_str ()); } void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getnick ", operand); m_CurrentDestination = m_Owner.FindDestination (operand); if (m_CurrentDestination) { m_Keys = m_CurrentDestination->GetKeys (); m_Nickname = operand; } if (m_Nickname == operand) { std::string msg ("Nickname set to "); msg += m_Nickname; SendReplyOK (msg.c_str ()); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (*operand) { try { char * operand1 = (char *)strchr (operand, ' '); if (operand1) { *operand1 = 0; operand1++; cryptoType = std::stoi(operand1); } signatureType = std::stoi(operand); } catch (std::invalid_argument& ex) { LogPrint (eLogWarning, "BOB: newkeys ", ex.what ()); } } m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); if (m_Keys.FromBase64 (operand)) SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); } void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getkeys"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getdest"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); m_OutHost = operand; SendReplyOK ("outhost set"); } void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outport ", operand); m_OutPort = std::stoi(operand); if (m_OutPort >= 0) SendReplyOK ("outbound port set"); else SendReplyError ("port out of range"); } void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); m_InHost = operand; SendReplyOK ("inhost set"); } void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inport ", operand); m_InPort = std::stoi(operand); if (m_InPort >= 0) SendReplyOK ("inbound port set"); else SendReplyError ("port out of range"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quiet"); if (m_Nickname.length () > 0) { if (!m_IsActive) { m_IsQuiet = true; SendReplyOK ("Quiet set"); } else SendReplyError ("tunnel is active"); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); auto addr = context.GetAddressBook ().GetAddress (operand); if (!addr) { SendReplyError ("Address Not found"); return; } auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); if (addr->IsIdentHash ()) { // we might have leaseset already auto leaseSet = localDestination->FindLeaseSet (addr->identHash); if (leaseSet) { SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); return; } } // trying to request auto s = shared_from_this (); auto requstCallback = [s](std::shared_ptr ls) { if (ls) s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else s->SendReplyError ("LeaseSet Not found"); }; if (addr->IsIdentHash ()) localDestination->RequestDestination (addr->identHash, requstCallback); else localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); } void BOBCommandSession::LookupLocalCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup local ", operand); auto addr = context.GetAddressBook ().GetAddress (operand); if (!addr) { SendReplyError ("Address Not found"); return; } auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); if (ls) SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else SendReplyError ("Local LeaseSet Not found"); } void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); m_Nickname = ""; SendReplyOK ("cleared"); } void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); std::string statusLine; bool sentCurrent = false; const auto& destinations = m_Owner.GetDestinations (); for (const auto& it: destinations) { BuildStatusLine(false, it.second, statusLine); SendRaw(statusLine.c_str()); if(m_Nickname.compare(it.second->GetNickname()) == 0) sentCurrent = true; } if(!sentCurrent && !m_Nickname.empty()) { // add the current tunnel to the list. // this is for the incomplete tunnel which has not been started yet. BuildStatusLine(true, m_CurrentDestination, statusLine); SendRaw(statusLine.c_str()); } SendReplyOK ("Listing done"); } void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: option ", operand); const char * value = strchr (operand, '='); if (value) { std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; msg += operand; *(const_cast(value)) = '='; msg += " set to "; msg += value; SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); } void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); const std::string name = operand; std::string statusLine; // always prefer destination auto ptr = m_Owner.FindDestination(name); if(ptr != nullptr) { // tunnel destination exists BuildStatusLine(false, ptr, statusLine); SendReplyOK(statusLine.c_str()); } else { if(m_Nickname == name && !name.empty()) { // tunnel is incomplete / has not been started yet BuildStatusLine(true, nullptr, statusLine); SendReplyOK(statusLine.c_str()); } else { SendReplyError("no nickname has been set"); } } } void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) { auto helpStrings = m_Owner.GetHelpStrings(); if(len == 0) { std::stringstream ss; ss << "COMMANDS:"; for (auto const& x : helpStrings) { ss << " " << x.first; } const std::string &str = ss.str(); SendReplyOK(str.c_str()); } else { auto it = helpStrings.find(operand); if (it != helpStrings.end ()) { SendReplyOK(it->second.c_str()); return; } SendReplyError("No such command"); } } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): RunnableService ("BOB"), m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler; m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler; m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler; m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler; m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler; m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler; // command -> help string m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP; m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT; m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START; m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP; m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK; m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK; m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS; m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS; m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS; m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST; m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST; m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT; m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST; m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT; m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET; m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP; m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR; m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST; m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION; m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS; m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP; } BOBCommandChannel::~BOBCommandChannel () { if (IsRunning ()) Stop (); for (const auto& it: m_Destinations) delete it.second; } void BOBCommandChannel::Start () { Accept (); StartIOService (); } void BOBCommandChannel::Stop () { for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); StopIOService (); } void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) { m_Destinations[name] = dest; } void BOBCommandChannel::DeleteDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) { it->second->Stop (); delete it->second; m_Destinations.erase (it); } } BOBDestination * BOBCommandChannel::FindDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) return it->second; return nullptr; } void BOBCommandChannel::Accept () { auto newSession = std::make_shared (*this); m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this, std::placeholders::_1, newSession)); } void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (!ecode) { LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ()); session->SendVersion (); } else LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); } } } i2pd-2.39.0/libi2pd_client/BOB.h000066400000000000000000000244011411072525600161050ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 BOB_H__ #define BOB_H__ #include #include #include #include #include #include #include "util.h" #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" #include "LeaseSet.h" namespace i2p { namespace client { const size_t BOB_COMMAND_BUFFER_SIZE = 1024; const char BOB_COMMAND_ZAP[] = "zap"; const char BOB_COMMAND_QUIT[] = "quit"; const char BOB_COMMAND_START[] = "start"; const char BOB_COMMAND_STOP[] = "stop"; const char BOB_COMMAND_SETNICK[] = "setnick"; const char BOB_COMMAND_GETNICK[] = "getnick"; const char BOB_COMMAND_NEWKEYS[] = "newkeys"; const char BOB_COMMAND_GETKEYS[] = "getkeys"; const char BOB_COMMAND_SETKEYS[] = "setkeys"; const char BOB_COMMAND_GETDEST[] = "getdest"; const char BOB_COMMAND_OUTHOST[] = "outhost"; const char BOB_COMMAND_OUTPORT[] = "outport"; const char BOB_COMMAND_INHOST[] = "inhost"; const char BOB_COMMAND_INPORT[] = "inport"; const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; const char BOB_COMMAND_LOOKUP_LOCAL[] = "lookuplocal"; const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; const char BOB_COMMAND_STATUS[] = "status"; const char BOB_COMMAND_HELP[] = "help"; const char BOB_HELP_ZAP[] = "zap - Shuts down BOB."; const char BOB_HELP_QUIT[] = "quit - Quits this session with BOB."; const char BOB_HELP_START[] = "start - Starts the current nicknamed tunnel."; const char BOB_HELP_STOP[] = "stop - Stops the current nicknamed tunnel."; const char BOB_HELP_SETNICK[] = "setnick - Creates a new nickname."; const char BOB_HELP_GETNICK[] = "getnick - Sets the nickname from the database."; const char BOB_HELP_NEWKEYS[] = "newkeys - Generate a new keypair for the current nickname."; const char BOB_HELP_GETKEYS[] = "getkeys - Return the keypair for the current nickname."; const char BOB_HELP_SETKEYS[] = "setkeys - Sets the keypair for the current nickname."; const char BOB_HELP_GETDEST[] = "getdest - Return the destination for the current nickname."; const char BOB_HELP_OUTHOST[] = "outhost - Set the outhound hostname or IP."; const char BOB_HELP_OUTPORT[] = "outport - Set the outbound port that nickname contacts."; const char BOB_HELP_INHOST[] = "inhost - Set the inbound hostname or IP."; const char BOB_HELP_INPORT[] = "inport - Set the inbound port number nickname listens on."; const char BOB_HELP_QUIET[] = "quiet - Wether to send the incoming destination."; const char BOB_HELP_LOOKUP[] = "lookup - Look up an I2P hostname."; const char BOB_HELP_CLEAR[] = "clear - Clear the current nickname out of the list."; const char BOB_HELP_LIST[] = "list - List all tunnels."; const char BOB_HELP_OPTION[] = "option = - Set an option. NOTE: Don't use any spaces."; const char BOB_HELP_STATUS[] = "status - Display status of a nicknamed tunnel."; const char BOB_HELP_HELP [] = "help - Get help on a command."; class BOBI2PTunnel: public I2PService { public: BOBI2PTunnel (std::shared_ptr localDestination): I2PService (localDestination) {}; virtual void Start () {}; virtual void Stop () {}; }; class BOBI2PInboundTunnel: public BOBI2PTunnel { struct AddressReceiver { std::shared_ptr socket; char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address uint8_t * data; // pointer to buffer size_t dataLen, bufferOffset; AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; }; public: BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination); ~BOBI2PInboundTunnel (); void Start (); void Stop (); private: void Accept (); void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver); void ReceiveAddress (std::shared_ptr receiver); void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver); void HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver); void CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet); private: boost::asio::ip::tcp::acceptor m_Acceptor; }; class BOBI2POutboundTunnel: public BOBI2PTunnel { public: BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); void SetQuiet () { m_IsQuiet = true; }; private: void Accept (); void HandleAccept (std::shared_ptr stream); private: boost::asio::ip::tcp::endpoint m_Endpoint; bool m_IsQuiet; }; class BOBDestination { public: BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, const int inport, const int outport, const bool quiet); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); void CreateInboundTunnel (int port, const std::string& inhost); void CreateOutboundTunnel (const std::string& outhost, int port, bool quiet); const std::string& GetNickname() const { return m_Nickname; } const std::string& GetInHost() const { return m_InHost; } const std::string& GetOutHost() const { return m_OutHost; } int GetInPort() const { return m_InPort; } int GetOutPort() const { return m_OutPort; } bool GetQuiet() const { return m_Quiet; } const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; private: std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; std::string m_Nickname; std::string m_InHost, m_OutHost; int m_InPort, m_OutPort; bool m_Quiet; }; class BOBCommandChannel; class BOBCommandSession: public std::enable_shared_from_this { public: BOBCommandSession (BOBCommandChannel& owner); ~BOBCommandSession (); void Terminate (); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; void SendVersion (); // command handlers void ZapCommandHandler (const char * operand, size_t len); void QuitCommandHandler (const char * operand, size_t len); void StartCommandHandler (const char * operand, size_t len); void StopCommandHandler (const char * operand, size_t len); void SetNickCommandHandler (const char * operand, size_t len); void GetNickCommandHandler (const char * operand, size_t len); void NewkeysCommandHandler (const char * operand, size_t len); void SetkeysCommandHandler (const char * operand, size_t len); void GetkeysCommandHandler (const char * operand, size_t len); void GetdestCommandHandler (const char * operand, size_t len); void OuthostCommandHandler (const char * operand, size_t len); void OutportCommandHandler (const char * operand, size_t len); void InhostCommandHandler (const char * operand, size_t len); void InportCommandHandler (const char * operand, size_t len); void QuietCommandHandler (const char * operand, size_t len); void LookupCommandHandler (const char * operand, size_t len); void LookupLocalCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); void StatusCommandHandler (const char * operand, size_t len); void HelpCommandHandler (const char * operand, size_t len); private: void Receive (); void HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Send (); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendReplyOK (const char * msg = nullptr); void SendReplyError (const char * msg); void SendRaw (const char * data); void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; BOBDestination * m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); class BOBCommandChannel: private i2p::util::RunnableService { public: BOBCommandChannel (const std::string& address, int port); ~BOBCommandChannel (); void Start (); void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; void AddDestination (const std::string& name, BOBDestination * dest); void DeleteDestination (const std::string& name); BOBDestination * FindDestination (const std::string& name); private: void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session); private: boost::asio::ip::tcp::acceptor m_Acceptor; std::map m_Destinations; std::map m_CommandHandlers; std::map m_HelpStrings; public: const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; }; const decltype(m_HelpStrings)& GetHelpStrings () const { return m_HelpStrings; }; const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; }; } } #endif i2pd-2.39.0/libi2pd_client/ClientContext.cpp000066400000000000000000001055651411072525600206340ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include #include "Config.h" #include "FS.h" #include "Log.h" #include "Identity.h" #include "util.h" #include "ClientContext.h" #include "SOCKS.h" #include "MatchedDestination.h" namespace i2p { namespace client { ClientContext context; ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } ClientContext::~ClientContext () { delete m_HttpProxy; delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; delete m_I2CPServer; } void ClientContext::Start () { // shared local destination if (!m_SharedLocalDestination) CreateNewSharedLocalDestination (); // addressbook m_AddressBook.Start (); // HTTP proxy ReadHttpProxy (); // SOCKS proxy ReadSocksProxy (); // I2P tunnels ReadTunnels (); // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); uint16_t samPort; i2p::config::GetOption("sam.port", samPort); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); try { m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); m_SamBridge->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); } } // BOB bool bob; i2p::config::GetOption("bob.enabled", bob); if (bob) { std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); try { m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); } } // I2CP bool i2cp; i2p::config::GetOption("i2cp.enabled", i2cp); if (i2cp) { std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); bool singleThread; i2p::config::GetOption("i2cp.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort, singleThread); m_I2CPServer->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); } } m_AddressBook.StartResolvers (); // start UDP cleanup if (!m_ServerForwards.empty ()) { m_CleanupUDPTimer.reset (new boost::asio::deadline_timer(m_SharedLocalDestination->GetService ())); ScheduleCleanupUDP(); } } void ClientContext::Stop () { if (m_HttpProxy) { LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; } if (m_SocksProxy) { LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; } for (auto& it: m_ClientTunnels) { LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first); it.second->Stop (); } m_ClientTunnels.clear (); for (auto& it: m_ServerTunnels) { LogPrint(eLogInfo, "Clients: stopping I2P server tunnel"); it.second->Stop (); } m_ServerTunnels.clear (); if (m_SamBridge) { LogPrint(eLogInfo, "Clients: stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; } if (m_BOBCommandChannel) { LogPrint(eLogInfo, "Clients: stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; } if (m_I2CPServer) { LogPrint(eLogInfo, "Clients: stopping I2CP"); m_I2CPServer->Stop (); delete m_I2CPServer; m_I2CPServer = nullptr; } LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); } if (m_CleanupUDPTimer) { m_CleanupUDPTimer->cancel (); m_CleanupUDPTimer = nullptr; } for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); m_SharedLocalDestination = nullptr; } void ClientContext::ReloadConfig () { // TODO: handle config changes /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ // handle tunnels // reset isUpdated for each tunnel VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); // reload tunnels ReadTunnels(); // delete not updated tunnels (not in config anymore) VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); // recreate HTTP proxy if (m_HttpProxy) { m_HttpProxy->Stop (); m_HttpProxy = nullptr; } ReadHttpProxy (); // recreate SOCKS proxy if (m_SocksProxy) { m_SocksProxy->Stop (); m_SocksProxy = nullptr; } ReadSocksProxy (); // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) { auto dest = it->second; if (dest->GetRefCounter () > 0) ++it; // skip else { dest->Stop (); it = m_Destinations.erase (it); } } } bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { static const std::string transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } bool success = true; std::string fullPath = i2p::fs::DataDirPath (filename); std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0, std::ios::end); size_t len = s.tellg(); s.seekg (0, std::ios::beg); uint8_t * buf = new uint8_t[len]; s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { LogPrint (eLogError, "Clients: failed to load keyfile ", filename); success = false; } else LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); delete[] buf; } else { LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; len = keys.ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } return success; } std::vector > ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) { std::vector > infos; std::lock_guard lock(m_ForwardsMutex); for(const auto & c : m_ClientForwards) { if (c.second->IsLocalDestination(destination)) { for (auto & i : c.second->GetSessions()) infos.push_back(i); break; } } for(const auto & s : m_ServerForwards) { if(std::get<0>(s.first) == destination) { for( auto & i : s.second->GetSessions()) infos.push_back(i); break; } } return infos; } std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); auto localDestination = std::make_shared (keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewLocalDestination ( boost::asio::io_service& service, bool isPublic, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); auto localDestination = std::make_shared (service, keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params) { auto localDestination = std::make_shared(keys, name, params); AddLocalDestination (localDestination); return localDestination; } void ClientContext::AddLocalDestination (std::shared_ptr localDestination) { std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); } void ClientContext::DeleteLocalDestination (std::shared_ptr destination) { if (!destination) return; auto it = m_Destinations.find (destination->GetIdentHash ()); if (it != m_Destinations.end ()) { auto d = it->second; { std::unique_lock l(m_DestinationsMutex); m_Destinations.erase (it); } d->Stop (); } } std::shared_ptr ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); it->second->Start (); // make sure to start return it->second; } auto localDestination = std::make_shared (keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); it->second->Start (); // make sure to start return it->second; } auto localDestination = std::make_shared (service, keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } void ClientContext::CreateNewSharedLocalDestination () { std::map params { { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, { I2CP_PARAM_LEASESET_TYPE, "3" }, { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" } }; m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA m_SharedLocalDestination->Acquire (); } std::shared_ptr ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const { auto it = m_Destinations.find (destination); if (it != m_Destinations.end ()) return it->second; return nullptr; } template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template std::string ClientContext::GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const { return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); } template void ClientContext::ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const { for (auto it: section.second) { if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group)) options[it.first] = it.second.get_value (""); } } template void ClientContext::ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const { options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; auto authType = GetI2CPOption(section, I2CP_PARAM_LEASESET_AUTH_TYPE, 0); if (authType != "0") // auth is set { options[I2CP_PARAM_LEASESET_AUTH_TYPE] = authType; if (authType == "1") // DH ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_DH, options); else if (authType == "2") // PSK ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_PSK, options); } std::string explicitPeers = GetI2CPStringOption(section, I2CP_PARAM_EXPLICIT_PEERS, ""); if (explicitPeers.length () > 0) options[I2CP_PARAM_EXPLICIT_PEERS] = explicitPeers; std::string ratchetInboundTags = GetI2CPStringOption(section, I2CP_PARAM_RATCHET_INBOUND_TAGS, ""); if (ratchetInboundTags.length () > 0) options[I2CP_PARAM_RATCHET_INBOUND_TAGS] = ratchetInboundTags; } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const { std::string value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_TYPE, value)) options[I2CP_PARAM_LEASESET_TYPE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, value)) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ()) options[I2CP_PARAM_LEASESET_PRIV_KEY] = value; } void ClientContext::ReadTunnels () { int numClientTunnels = 0, numServerTunnels = 0; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf.empty ()) { // TODO: cleanup this in 2.8.0 tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); if (i2p::fs::Exists(tunConf)) LogPrint(eLogWarning, "Clients: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); else tunConf = i2p::fs::DataDirPath ("tunnels.conf"); } LogPrint(eLogDebug, "Clients: tunnels config file: ", tunConf); ReadTunnels (tunConf, numClientTunnels, numServerTunnels); std::string tunDir; i2p::config::GetOption("tunnelsdir", tunDir); if (tunDir.empty ()) tunDir = i2p::fs::DataDirPath ("tunnels.d"); if (i2p::fs::Exists (tunDir)) { std::vector files; if (i2p::fs::ReadDir (tunDir, files)) { for (auto& it: files) { if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" LogPrint(eLogDebug, "Clients: tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } } } LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } void ClientContext::ReadTunnels (const std::string& tunConf, int& numClientTunnels, int& numServerTunnels) { boost::property_tree::ptree pt; try { boost::property_tree::read_ini (tunConf, pt); } catch (std::exception& ex) { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } std::map > destinations; // keys -> destination for (auto& section: pt) { std::string name = section.first; try { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_SOCKS || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params bool matchTunnels = section.second.get(I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP std::map options; ReadI2CPOptions (section, false, options); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) { auto it = destinations.find (keys); if (it != destinations.end ()) localDestination = it->second; else { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) { if(matchTunnels) localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); else localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); if (keys != "transient") destinations[keys] = localDestination; } } } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // udp client // TODO: hostnames boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); if (!localDestination) localDestination = m_SharedLocalDestination; bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort, gzip); if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) clientTunnel->Start(); else LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); } else { boost::asio::ip::tcp::endpoint clientEndpoint; std::shared_ptr clientTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_SOCKS) { // socks proxy std::string outproxy = section.second.get("outproxy", ""); auto tun = std::make_shared(name, address, port, !outproxy.empty(), outproxy, destinationPort, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } else if (type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY) { // http proxy std::string outproxy = section.second.get("outproxy", ""); bool addresshelper = section.second.get("addresshelper", true); auto tun = std::make_shared(name, address, port, outproxy, addresshelper, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { LogPrint(eLogWarning, "Clients: I2P Client tunnel websocks is deprecated, not starting ", name, " tunnel"); continue; } else { // tcp client auto tun = std::make_shared (name, dest, address, port, localDestination, destinationPort); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { clientTunnel->SetConnectTimeout(timeout); LogPrint(eLogInfo, "Clients: I2P Client tunnel connect timeout set to ", timeout); } auto ins = m_ClientTunnels.insert (std::make_pair (clientEndpoint, clientTunnel)); if (ins.second) { clientTunnel->Start (); numClientTunnels++; } else { // TODO: update if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); if(accessList == "") accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, ""); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); // I2CP std::map options; ReadI2CPOptions (section, true, options); std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); if (it != destinations.end ()) localDestination = it->second; else { i2p::data::PrivateKeys k; if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) continue; localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) { localDestination = CreateNewLocalDestination (k, true, &options); destinations[keys] = localDestination; } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: hostnames boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); if (address.empty ()) { if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) address = "::1"; else address = "127.0.0.1"; } auto localAddress = boost::asio::ip::address::from_string(address); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); if(m_ServerForwards.insert( std::make_pair( std::make_pair( localDestination->GetIdentHash(), port), serverTunnel)).second) { serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); continue; } std::shared_ptr serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = std::make_shared (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) serverTunnel = std::make_shared (name, host, port, localDestination, webircpass, inPort, gzip); else // regular server tunnel by default serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); if (!address.empty ()) serverTunnel->SetLocalAddress (address); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } if (accessList.length () > 0) { std::set idents; size_t pos = 0, comma; do { comma = accessList.find (',', pos); i2p::data::IdentHash ident; ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); idents.insert (ident); pos = comma + 1; } while (comma != std::string::npos); serverTunnel->SetAccessList (idents); } auto ins = m_ServerTunnels.insert (std::make_pair ( std::make_pair (localDestination->GetIdentHash (), inPort), serverTunnel)); if (ins.second) { serverTunnel->Start (); numServerTunnels++; } else { // TODO: update if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); } } else LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); } } } void ClientContext::ReadHttpProxy () { std::shared_ptr localDestination; bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("httpproxy.", params); localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } try { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); } } } void ClientContext::ReadSocksProxy () { std::shared_ptr localDestination; bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); // we still need httpProxyKeys to compare with sockProxyKeys std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); if (httpProxyKeys == socksProxyKeys && m_HttpProxy) { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); } else if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if (LoadPrivateKeys (keys, socksProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("socksproxy.", params); localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } try { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); } } } void ClientContext::ScheduleCleanupUDP() { if (m_CleanupUDPTimer) { // schedule cleanup in 17 seconds m_CleanupUDPTimer->expires_from_now (boost::posix_time::seconds (17)); m_CleanupUDPTimer->async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); } } void ClientContext::CleanupUDP(const boost::system::error_code & ecode) { if(!ecode) { std::lock_guard lock(m_ForwardsMutex); for (auto & s : m_ServerForwards ) s.second->ExpireStale(); ScheduleCleanupUDP(); } } template void VisitTunnelsContainer (Container& c, Visitor v) { for (auto it = c.begin (); it != c.end ();) { if (!v (it->second.get ())) { it->second->Stop (); it = c.erase (it); } else it++; } } template void ClientContext::VisitTunnels (Visitor v) { VisitTunnelsContainer (m_ClientTunnels, v); VisitTunnelsContainer (m_ServerTunnels, v); // TODO: implement UDP forwards } } } i2pd-2.39.0/libi2pd_client/ClientContext.h000066400000000000000000000174621411072525600202770ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ #include #include #include #include #include "Destination.h" #include "I2PService.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" #include "SAM.h" #include "BOB.h" #include "I2CP.h" #include "AddressBook.h" namespace i2p { namespace client { const char I2P_TUNNELS_SECTION_TYPE[] = "type"; const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; const char I2P_CLIENT_TUNNEL_GZIP[] = "gzip"; const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_CRYPTO_TYPE[] = "cryptotype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_CLIENT_TUNNEL_MATCH_TUNNELS[] = "matchtunnels"; const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; const char I2P_SERVER_TUNNEL_WHITE_LIST[] = "whitelist"; const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; class ClientContext { public: ClientContext (); ~ClientContext (); void Start (); void Stop (); void ReloadConfig (); std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, // transient i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // used by SAM only std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); AddressBook& GetAddressBook () { return m_AddressBook; }; const BOBCommandChannel * GetBOBCommandChannel () const { return m_BOBCommandChannel; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; const I2CPServer * GetI2CPServer () const { return m_I2CPServer; }; std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); private: void ReadTunnels (); void ReadTunnels (const std::string& tunConf, int& numClientTunnels, int& numServerTunnels); void ReadHttpProxy (); void ReadSocksProxy (); template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const; template std::string GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const; // GetI2CPOption with string default value template void ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const; template void ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const; // for tunnels void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); template void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); private: std::mutex m_DestinationsMutex; std::map > m_Destinations; std::shared_ptr m_SharedLocalDestination; AddressBook m_AddressBook; i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint->tunnel std::map, std::shared_ptr > m_ServerTunnels; // ->tunnel std::mutex m_ForwardsMutex; std::map > m_ClientForwards; // local endpoint -> udp tunnel std::map, std::shared_ptr > m_ServerForwards; // -> udp tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; std::unique_ptr m_CleanupUDPTimer; public: // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; } const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; } }; extern ClientContext context; } } #endif i2pd-2.39.0/libi2pd_client/HTTPProxy.cpp000066400000000000000000000602301411072525600176570ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 #include #include #include #include #include #include #include #include #include "I2PService.h" #include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" #include "I18N.h" namespace i2p { namespace proxy { std::map jumpservices = { { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; static const char *pageHead = "\r\n" " \r\n" " I2Pd HTTP proxy\r\n" " \r\n" "\r\n" ; bool str_rmatch(std::string & str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ if (str.length() == (pos + std::strlen(suffix))) return true; /* match */ return false; } class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: bool HandleRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ void GenericProxyError(const std::string& title, const std::string& description); void GenericProxyInfo(const std::string& title, const std::string& description); void HostNotFound(std::string & host); void SendProxyError(std::string & content); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); void HTTPConnect(const std::string & host, uint16_t port); void HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); void HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transfered); void HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transfered); typedef std::function ProxyResolvedHandler; void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler); void SocksProxySuccess(); void HandoverToUpstreamProxy(); uint8_t m_recv_chunk[8192]; std::string m_recv_buf; // from client std::string m_send_buf; // to upstream std::shared_ptr m_sock; std::shared_ptr m_proxysock; boost::asio::ip::tcp::resolver m_proxy_resolver; std::string m_OutproxyUrl; bool m_Addresshelper; i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; uint8_t m_socks_buf[255+8]; // for socks request/response ssize_t m_req_len; i2p::http::URL m_ClientRequestURL; i2p::http::HTTPReq m_ClientRequest; i2p::http::HTTPRes m_ClientResponse; std::stringstream m_ClientRequestBuffer; public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock), m_proxysock(std::make_shared(parent->GetService())), m_proxy_resolver(parent->GetService()), m_OutproxyUrl(parent->GetOutproxyURL()), m_Addresshelper(parent->GetHelperSupport()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); if (!m_sock) { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "HTTPProxy: close sock"); m_sock->close(); m_sock = nullptr; } if(m_proxysock) { LogPrint(eLogDebug, "HTTPProxy: close proxysock"); if(m_proxysock->is_open()) m_proxysock->close(); m_proxysock = nullptr; } Done(shared_from_this()); } void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { std::stringstream ss; ss << "

" << tr("Proxy error") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::GenericProxyInfo(const std::string& title, const std::string& description) { std::stringstream ss; ss << "

" << tr("Proxy info") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::HostNotFound(std::string & host) { std::stringstream ss; ss << "

" << tr("Proxy error: Host not found") << "

\r\n" << "

" << tr("Remote host not found in router's addressbook") << "

\r\n" << "

" << tr("You may try to find this host on jump services below") << ":

\r\n" << "\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::SendProxyError(std::string & content) { i2p::http::HTTPRes res; res.code = 500; res.add_header("Content-Type", "text/html; charset=UTF-8"); res.add_header("Connection", "close"); std::stringstream ss; ss << "\r\n" << pageHead << "" << content << "\r\n" << "\r\n"; res.body = ss.str(); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) { confirm = false; const char *param = "i2paddresshelper="; std::size_t pos = url.query.find(param); std::size_t len = std::strlen(param); std::map params; if (pos == std::string::npos) return false; /* not found */ if (!url.parse_query(params)) return false; std::string value = params["i2paddresshelper"]; len += value.length(); b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option url.query.replace(pos, len, ""); return true; } void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { /* drop common headers */ req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /** * according to i2p ticket #1862: * leave Referrer if requested URL with same schema, host and port, * otherwise, drop it. */ if(req.GetHeader("Referrer") != "") { i2p::http::URL reqURL; reqURL.parse(req.uri); i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer")); if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) req.RemoveHeader("Referrer"); } /* add headers */ /* close connection, if not Connection: (U|u)pgrade (for websocket) */ auto h = req.GetHeader ("Connection"); auto x = h.find("pgrade"); if (!(x != std::string::npos && std::tolower(h[x - 1]) == 'u')) req.UpdateHeader("Connection", "close"); } /** * @brief Try to parse request from @a m_recv_buf * If parsing success, rebuild request and store to @a m_send_buf * with remaining data tail * @return true on processed request or false if more data needed */ bool HTTPReqHandler::HandleRequest() { m_req_len = m_ClientRequest.parse(m_recv_buf); if (m_req_len == 0) return false; /* need more data */ if (m_req_len < 0) { LogPrint(eLogError, "HTTPProxy: unable to parse request"); GenericProxyError(tr("Invalid request"), tr("Proxy unable to parse your request")); return true; /* parse error */ } /* parsing success, now let's look inside request */ LogPrint(eLogDebug, "HTTPProxy: requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); bool m_Confirm; std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { if (!m_Addresshelper) { LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); return true; } if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) { i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); LogPrint (eLogInfo, "HTTPProxy: added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; ss << tr("Click here to proceed:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } else { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; ss << tr("Click here to update record:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } } std::string dest_host; uint16_t dest_port; bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { std::string uri(m_ClientRequest.uri); auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { GenericProxyError(tr("Invalid request"), tr("invalid request uri")); return true; } else { useConnect = true; dest_port = std::stoi(uri.substr(pos+1)); dest_host = uri.substr(0, pos); } } else { SanitizeHTTPRequest(m_ClientRequest); dest_host = m_RequestURL.host; dest_port = m_RequestURL.port; /* always set port, even if missing in request */ if (!dest_port) dest_port = (m_RequestURL.schema == "https") ? 443 : 80; /* detect dest_host, set proper 'Host' header in upstream request */ if (dest_host != "") { /* absolute url, replace 'Host' header */ std::string h = dest_host; if (dest_port != 0 && dest_port != 80) h += ":" + std::to_string(dest_port); m_ClientRequest.UpdateHeader("Host", h); } else { auto h = m_ClientRequest.GetHeader ("Host"); if (h.length () > 0) { /* relative url and 'Host' header provided. transparent proxy mode? */ i2p::http::URL u; std::string t = "http://" + h; u.parse(t); dest_host = u.host; dest_port = u.port; } else { /* relative url and missing 'Host' header */ GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); return true; } } } /* check dest_host really exists and inside I2P network */ if (str_rmatch(dest_host, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetAddress (dest_host)) { HostNotFound(dest_host); return true; /* request processed */ } } else { if(m_OutproxyUrl.size()) { LogPrint (eLogDebug, "HTTPProxy: use outproxy ", m_OutproxyUrl); if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outproxy enabled"); std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled"); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; } if(useConnect) { HTTPConnect(dest_host, dest_port); return true; } /* make relative url */ m_RequestURL.schema = ""; m_RequestURL.host = ""; m_ClientRequest.uri = m_RequestURL.to_string(); /* drop original request from recv buffer */ m_recv_buf.erase(0, m_req_len); /* build new buffer from modified request and data from original request */ m_send_buf = m_ClientRequest.to_string(); m_send_buf.append(m_recv_buf); /* connect to destination */ LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: forward to upstream"); // build http request m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; m_ClientRequestURL.host = ""; std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? m_ClientRequest.uri = m_ClientRequestURL.to_string(); // update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections if(m_ClientRequest.method != "CONNECT") m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0"); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); // assume http if empty schema if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") { // handle upstream http proxy if (!m_ProxyURL.port) m_ProxyURL.port = 80; if (m_ProxyURL.is_i2p()) { m_ClientRequest.uri = origURI; auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); if (!auth.empty ()) { // remove existing authorization if any m_ClientRequest.RemoveHeader("Proxy-"); // add own http proxy authorization m_ClientRequest.AddHeader("Proxy-Authorization", auth); } m_send_buf = m_ClientRequest.to_string(); m_recv_buf.erase(0, m_req_len); m_send_buf.append(m_recv_buf); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_ProxyURL.host, m_ProxyURL.port); } else { boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); })); } } else if (m_ProxyURL.schema == "socks") { // handle upstream socks proxy if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); })); } else { // unknown type, complain GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); } } void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) { if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); else handler(*it); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { if(!ec) { if(m_RequestURL.host.size() > 255) { GenericProxyError(tr("hostname too long"), m_RequestURL.host); return; } uint16_t port = m_RequestURL.port; if(!port) port = 80; LogPrint(eLogDebug, "HTTPProxy: connected to socks upstream"); std::string host = m_RequestURL.host; std::size_t reqsize = 0; m_socks_buf[0] = '\x04'; m_socks_buf[1] = 1; htobe16buf(m_socks_buf+2, port); m_socks_buf[4] = 0; m_socks_buf[5] = 0; m_socks_buf[6] = 0; m_socks_buf[7] = 1; // user id m_socks_buf[8] = 'i'; m_socks_buf[9] = '2'; m_socks_buf[10] = 'p'; m_socks_buf[11] = 'd'; m_socks_buf[12] = 0; reqsize += 13; memcpy(m_socks_buf+ reqsize, host.c_str(), host.size()); reqsize += host.size(); m_socks_buf[++reqsize] = 0; boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); } else GenericProxyError(tr("cannot connect to upstream socks proxy"), ec.message()); } void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) { LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); if(ec) GenericProxyError(tr("Cannot negotiate with socks proxy"), ec.message()); else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::HandoverToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: handover to socks proxy"); auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); connection->Start(); Terminate(); } void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); std::string hostname(host); if(str_rmatch(hostname, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else ForwardToUpstreamProxy(); } void HTTPReqHandler::HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream) { if(stream) { m_ClientResponse.code = 200; m_ClientResponse.status = "OK"; m_send_buf = m_ClientResponse.to_string(); m_sock->send(boost::asio::buffer(m_send_buf)); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler(connection); connection->I2PConnect(); m_sock = nullptr; Terminate(); } else { GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); } } void HTTPReqHandler::SocksProxySuccess() { if(m_ClientRequest.method == "CONNECT") { m_ClientResponse.code = 200; m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { if(ec) GenericProxyError(tr("socks proxy error"), ec.message()); else HandoverToUpstreamProxy(); }); } else { m_send_buf = m_ClientRequestBuffer.str(); LogPrint(eLogDebug, "HTTPProxy: send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message()); else HandoverToUpstreamProxy(); }); } } void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred) { if(!ec) { if(m_socks_buf[1] == 90) { // success SocksProxySuccess(); } else { std::stringstream ss; ss << "error code: "; ss << (int) m_socks_buf[1]; std::string msg = ss.str(); GenericProxyError(tr("socks proxy error"), msg); } } else GenericProxyError(tr("No Reply From socks proxy"), ec.message()); } void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented")); } else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message()); } /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); Terminate(); return; } m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); if (HandleRequest()) { m_recv_buf.clear(); return; } AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); return; } if (Kill()) return; LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); Done (shared_from_this()); } HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper) { } std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket); } } // http } // i2p i2pd-2.39.0/libi2pd_client/HTTPProxy.h000066400000000000000000000023211411072525600173210ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ namespace i2p { namespace proxy { class HTTPProxy: public i2p::client::TCPIPAcceptor { public: HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination); HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : HTTPProxy(name, address, port, "", true, localDestination) {} ; ~HTTPProxy() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } bool GetHelperSupport() { return m_Addresshelper; } protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: std::string m_Name; std::string m_OutproxyUrl; bool m_Addresshelper; }; } // http } // i2p #endif i2pd-2.39.0/libi2pd_client/I2CP.cpp000066400000000000000000000744151411072525600165450ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" #include "LeaseSet.h" #include "ClientContext.h" #include "Transports.h" #include "Signature.h" #include "I2CP.h" namespace i2p { namespace client { I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) { } void I2CPDestination::Stop () { LeaseSetDestination::Stop (); m_Owner = nullptr; m_LeaseSetCreationTimer.cancel (); } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); } void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) { if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? { m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public memcpy (m_ECIESx25519PrivateKey, key, 32); } } bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx, true); else LogPrint (eLogError, "I2CP: decryptor is not set"); return false; } const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) return m_ECIESx25519Decryptor->GetPubicKey (); return nullptr; } bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; } void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; if (m_Owner) m_Owner->SendMessagePayloadMessage (buf + 4, length); } void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); } void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) { if (m_IsCreatingLeaseSet) { LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); return; } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); if (m_Owner) { uint16_t sessionID = m_Owner->GetSessionID (); if (sessionID != 0xFFFF) { m_IsCreatingLeaseSet = true; htobe16buf (leases - 3, sessionID); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); auto s = GetSharedFromThis (); m_LeaseSetCreationTimer.async_wait ([s](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "I2CP: LeaseSet creation timeout expired. Terminate"); if (s->m_Owner) s->m_Owner->Stop (); } }); } } } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) { m_IsCreatingLeaseSet = false; m_LeaseSetCreationTimer.cancel (); auto ls = std::make_shared (m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); } void I2CPDestination::LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len) { m_IsCreatingLeaseSet = false; m_LeaseSetCreationTimer.cancel (); auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); } void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce) { auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); htobe32buf (buf, len); memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); auto s = GetSharedFromThis (); auto remote = FindLeaseSet (ident); if (remote) { GetService ().post ( [s, msg, remote, nonce]() { bool sent = s->SendMsg (msg, remote); if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); }); } else { RequestDestination (ident, [s, msg, nonce](std::shared_ptr ls) { if (ls) { bool sent = s->SendMsg (msg, ls); if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } else if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } } bool I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) { auto remoteSession = GetRoutingSession (remote, true); if (!remoteSession) { LogPrint (eLogError, "I2CP: Failed to create remote session"); return false; } auto path = remoteSession->GetSharedRoutingPath (); std::shared_ptr outboundTunnel; std::shared_ptr remoteLease; if (path) { if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags { outboundTunnel = path->outboundTunnel; remoteLease = path->remoteLease; } else remoteSession->SetSharedRoutingPath (nullptr); } else { outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); if (!leases.empty ()) remoteLease = leases[rand () % leases.size ()]; if (remoteLease && outboundTunnel) remoteSession->SetSharedRoutingPath (std::make_shared ( i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT else remoteSession->SetSharedRoutingPath (nullptr); } if (remoteLease && outboundTunnel) { std::vector msgs; auto garlic = remoteSession->WrapSingleMessage (msg); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, remoteLease->tunnelGateway, remoteLease->tunnelID, garlic }); outboundTunnel->SendTunnelDataMsg (msgs); return true; } else { if (outboundTunnel) LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired"); else LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels"); return false; } } RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): RunnableService ("I2CP"), I2CPDestination (GetIOService (), owner, identity, isPublic, params) { } RunnableI2CPDestination::~RunnableI2CPDestination () { if (IsRunning ()) Stop (); } void RunnableI2CPDestination::Start () { if (!IsRunning ()) { I2CPDestination::Start (); StartIOService (); } } void RunnableI2CPDestination::Stop () { if (IsRunning ()) { I2CPDestination::Stop (); StopIOService (); } } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true), m_IsSending (false) { } I2CPSession::~I2CPSession () { Terminate (); } void I2CPSession::Start () { ReadProtocolByte (); } void I2CPSession::Stop () { Terminate (); } void I2CPSession::ReadProtocolByte () { if (m_Socket) { auto s = shared_from_this (); m_Socket->async_read_some (boost::asio::buffer (m_Header, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE) s->ReceiveHeader (); else s->Terminate (); }); } } void I2CPSession::ReceiveHeader () { if (!m_Socket) { LogPrint (eLogError, "I2CP: Can't receive header"); return; } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET); if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) ReceivePayload (); else { LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); Terminate (); } } else // no following payload { HandleMessage (); ReceiveHeader (); // next message } } } void I2CPSession::ReceivePayload () { if (!m_Socket) { LogPrint (eLogError, "I2CP: Can't receive payload"); return; } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { HandleMessage (); m_PayloadLen = 0; ReceiveHeader (); // next message } } void I2CPSession::HandleMessage () { auto handler = m_Owner.GetMessagesHandlers ()[m_Header[I2CP_HEADER_TYPE_OFFSET]]; if (handler) (this->*handler)(m_Payload, m_PayloadLen); else LogPrint (eLogError, "I2CP: Unknown I2CP message ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () { if (m_Destination) { m_Destination->Stop (); m_Destination = nullptr; } if (m_Socket) { m_Socket->close (); m_Socket = nullptr; } if (!m_SendQueue.IsEmpty ()) m_SendQueue.CleanUp (); if (m_SessionID != 0xFFFF) { m_Owner.RemoveSession (GetSessionID ()); LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); m_SessionID = 0xFFFF; } } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { auto l = len + I2CP_HEADER_SIZE; if (l > I2CP_MAX_MESSAGE_LENGTH) { LogPrint (eLogError, "I2CP: Message to send is too long ", l); return; } auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); buf[I2CP_HEADER_TYPE_OFFSET] = type; memcpy (buf + I2CP_HEADER_SIZE, payload, len); if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) m_SendQueue.Add (sendBuf); else { LogPrint (eLogWarning, "I2CP: send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); return; } } else { auto socket = m_Socket; if (socket) { m_IsSending = true; boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } } } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) Terminate (); } else if (!m_SendQueue.IsEmpty ()) { auto socket = m_Socket; if (socket) { auto len = m_SendQueue.Get (m_SendBuffer, I2CP_MAX_MESSAGE_LENGTH); boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, len), boost::asio::transfer_all (),std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else m_IsSending = false; } else m_IsSending = false; } std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { uint8_t l = buf[0]; if (l > len) l = len; return std::string ((const char *)(buf + 1), l); } size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) { auto l = str.length (); if (l + 1 >= len) l = len - 1; if (l > 255) l = 255; // 1 byte max buf[0] = l; memcpy (buf + 1, str.c_str (), l); return l + 1; } void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) // TODO: move to Base.cpp { size_t offset = 0; while (offset < len) { std::string param = ExtractString (buf + offset, len - offset); offset += param.length () + 1; if (buf[offset] != '=') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param); break; } offset++; std::string value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value); break; } offset++; mapping.insert (std::make_pair (param, value)); } } void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { // get version auto version = ExtractString (buf, len); auto l = version.length () + 1 + 8; uint8_t * payload = new uint8_t[l]; // set date auto ts = i2p::util::GetMillisecondsSinceEpoch (); htobe64buf (payload, ts); // echo vesrion back PutString (payload + 8, l - 8, version); SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); delete[] payload; } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { RAND_bytes ((uint8_t *)&m_SessionID, 2); m_Owner.InsertSession (shared_from_this ()); auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); if (!offset) { LogPrint (eLogError, "I2CP: create session malformed identity"); SendSessionStatusMessage (3); // invalid return; } uint16_t optionsSize = bufbe16toh (buf + offset); offset += 2; if (optionsSize > len - offset) { LogPrint (eLogError, "I2CP: options size ", optionsSize, "exceeds message size"); SendSessionStatusMessage (3); // invalid return; } std::map params; ExtractMapping (buf + offset, optionsSize, params); offset += optionsSize; // options if (params[I2CP_PARAM_MESSAGE_RELIABILITY] == "none") m_IsSendAccepted = false; offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { if (!m_Destination) { m_Destination = m_Owner.IsSingleThread () ? std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, params): std::make_shared(shared_from_this (), identity, true, params); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); m_Destination->Start (); } else { LogPrint (eLogError, "I2CP: session already exists"); SendSessionStatusMessage (4); // refused } } else { LogPrint (eLogError, "I2CP: create session signature verification failed"); SendSessionStatusMessage (3); // invalid } } void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); Terminate (); } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) { uint8_t status = 3; // rejected if(len > sizeof(uint16_t)) { uint16_t sessionID = bufbe16toh(buf); if(sessionID == m_SessionID) { buf += sizeof(uint16_t); const uint8_t * body = buf; i2p::data::IdentityEx ident; if(ident.FromBuffer(buf, len - sizeof(uint16_t))) { if (ident == *m_Destination->GetIdentity()) { size_t identsz = ident.GetFullLen(); buf += identsz; uint16_t optssize = bufbe16toh(buf); if (optssize <= len - sizeof(uint16_t) - sizeof(uint64_t) - identsz - ident.GetSignatureLen() - sizeof(uint16_t)) { buf += sizeof(uint16_t); std::map opts; ExtractMapping(buf, optssize, opts); buf += optssize; //uint64_t date = bufbe64toh(buf); buf += sizeof(uint64_t); const uint8_t * sig = buf; if(ident.Verify(body, len - sizeof(uint16_t) - ident.GetSignatureLen(), sig)) { if(m_Destination->Reconfigure(opts)) { LogPrint(eLogInfo, "I2CP: reconfigured destination"); status = 2; // updated } else LogPrint(eLogWarning, "I2CP: failed to reconfigure destination"); } else LogPrint(eLogError, "I2CP: invalid reconfigure message signature"); } else LogPrint(eLogError, "I2CP: mapping size mismatch"); } else LogPrint(eLogError, "I2CP: destination mismatch"); } else LogPrint(eLogError, "I2CP: malfromed destination"); } else LogPrint(eLogError, "I2CP: session mismatch"); } else LogPrint(eLogError, "I2CP: short message"); SendSessionStatusMessage (status); } void I2CPSession::SendSessionStatusMessage (uint8_t status) { uint8_t buf[3]; htobe16buf (buf, m_SessionID); buf[2] = status; SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status) { if (!nonce) return; // don't send status with zero nonce uint8_t buf[15]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, m_MessageID++); buf[6] = (uint8_t)status; memset (buf + 7, 0, 4); // size htobe32buf (buf + 11, nonce); SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15); } void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { offset += i2p::crypto::DSA_PRIVATE_KEY_LENGTH; // skip signing private key // we always assume this field as 20 bytes (DSA) regardless actual size // instead of //offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { uint8_t storeType = buf[offset]; offset++; // store type i2p::data::LeaseSet2 ls (storeType, buf + offset, len - offset); // outer layer only for encrypted if (!ls.IsValid ()) { LogPrint (eLogError, "I2CP: invalid LeaseSet2 of type ", storeType); return; } offset += ls.GetBufferLen (); // private keys int numPrivateKeys = buf[offset]; offset++; for (int i = 0; i < numPrivateKeys; i++) { if (offset + 4 > len) return; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); else { m_Destination->SetEncryptionType (keyType); m_Destination->SetEncryptionPrivateKey (buf + offset); } offset += keyLen; } m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { i2p::data::IdentityEx identity; size_t identsize = identity.FromBuffer (buf + offset, len - offset); if (identsize) { offset += identsize; uint32_t payloadLen = bufbe32toh (buf + offset); if (payloadLen + offset <= len) { offset += 4; uint32_t nonce = bufbe32toh (buf + offset + payloadLen); if (m_IsSendAccepted) SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } else LogPrint(eLogError, "I2CP: cannot send message, too big"); } else LogPrint(eLogError, "I2CP: invalid identity"); } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) { SendMessageMessageHandler (buf, len - 8); // ignore flags(2) and expiration(6) } void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID || sessionID == 0xFFFF) // -1 means without session { uint32_t requestID = bufbe32toh (buf + 2); //uint32_t timeout = bufbe32toh (buf + 6); i2p::data::IdentHash ident; switch (buf[10]) { case 0: // hash ident = i2p::data::IdentHash (buf + 11); break; case 1: // address { auto name = ExtractString (buf + 11, len - 11); auto addr = i2p::client::context.GetAddressBook ().GetAddress (name); if (!addr || !addr->IsIdentHash ()) { // TODO: handle blinded addresses LogPrint (eLogError, "I2CP: address ", name, " not found"); SendHostReplyMessage (requestID, nullptr); return; } else ident = addr->identHash; break; } default: LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported"); SendHostReplyMessage (requestID, nullptr); return; } std::shared_ptr destination = m_Destination; if(!destination) destination = i2p::client::context.GetSharedLocalDestination (); if (destination) { auto ls = destination->FindLeaseSet (ident); if (ls) SendHostReplyMessage (requestID, ls->GetIdentity ()); else { auto s = shared_from_this (); destination->RequestDestination (ident, [s, requestID](std::shared_ptr leaseSet) { s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); }); } } else SendHostReplyMessage (requestID, nullptr); } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) { if (identity) { size_t l = identity->GetFullLen () + 7; uint8_t * buf = new uint8_t[l]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 0; // result code identity->ToBuffer (buf + 7, l - 7); SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, l); delete[] buf; } else { uint8_t buf[7]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 1; // result code SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, 7); } } void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) { if (m_Destination) { auto ls = m_Destination->FindLeaseSet (buf); if (ls) { auto l = ls->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; ls->GetIdentity ()->ToBuffer (identBuf, l); SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else { auto s = shared_from_this (); i2p::data::IdentHash ident (buf); m_Destination->RequestDestination (ident, [s, ident](std::shared_ptr leaseSet) { if (leaseSet) // found { auto l = leaseSet->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; leaseSet->GetIdentity ()->ToBuffer (identBuf, l); s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, ident, 32); // not found }); } } else SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) { uint8_t limits[64]; memset (limits, 0, 64); htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); } void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy auto l = len + 10 + I2CP_HEADER_SIZE; if (l > I2CP_MAX_MESSAGE_LENGTH) { LogPrint (eLogError, "I2CP: Message to send is too long ", l); return; } auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) m_SendQueue.Add (sendBuf); else { LogPrint (eLogWarning, "I2CP: send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); return; } } else { auto socket = m_Socket; if (socket) { m_IsSending = true; boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } } } I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), m_Acceptor (GetIOService (), #ifdef ANDROID I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address #else I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) #endif { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_RECONFIGURE_SESSION_MESSAGE] = &I2CPSession::ReconfigureSessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET2_MESSAGE] = &I2CPSession::CreateLeaseSet2MessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; m_MessagesHandlers[I2CP_GET_BANDWIDTH_LIMITS_MESSAGE] = &I2CPSession::GetBandwidthLimitsMessageHandler; } I2CPServer::~I2CPServer () { if (IsRunning ()) Stop (); } void I2CPServer::Start () { Accept (); StartIOService (); } void I2CPServer::Stop () { m_Acceptor.cancel (); { auto sessions = m_Sessions; for (auto& it: sessions) it.second->Stop (); } m_Sessions.clear (); StopIOService (); } void I2CPServer::Accept () { auto newSocket = std::make_shared (GetIOService ()); m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2CPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode && socket) { boost::system::error_code ec; auto ep = socket->remote_endpoint (ec); if (!ec) { LogPrint (eLogDebug, "I2CP: new connection from ", ep); auto session = std::make_shared(*this, socket); session->Start (); } else LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); } else LogPrint (eLogError, "I2CP: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } bool I2CPServer::InsertSession (std::shared_ptr session) { if (!session) return false; if (!m_Sessions.insert({session->GetSessionID (), session}).second) { LogPrint (eLogError, "I2CP: duplicate session id ", session->GetSessionID ()); return false; } return true; } void I2CPServer::RemoveSession (uint16_t sessionID) { m_Sessions.erase (sessionID); } } } i2pd-2.39.0/libi2pd_client/I2CP.h000066400000000000000000000211651411072525600162040ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #ifndef I2CP_H__ #define I2CP_H__ #include #include #include #include #include #include #include "util.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace client { const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_RECONFIGURE_SESSION_MESSAGE = 2; const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_CREATE_LEASESET2_MESSAGE = 41; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; const uint8_t I2CP_SEND_MESSAGE_EXPIRES_MESSAGE = 36; const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34; const uint8_t I2CP_DEST_REPLY_MESSAGE = 35; const uint8_t I2CP_GET_BANDWIDTH_LIMITS_MESSAGE = 8; const uint8_t I2CP_BANDWIDTH_LIMITS_MESSAGE = 23; enum I2CPMessageStatus { eI2CPMessageStatusAccepted = 1, eI2CPMessageStatusGuaranteedSuccess = 4, eI2CPMessageStatusGuaranteedFailure = 5, eI2CPMessageStatusNoLeaseSet = 21 }; // params const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; class I2CPDestination: public LeaseSetDestination { public: I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); ~I2CPDestination () {}; void Stop (); void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; void SetECIESx25519EncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession void LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only std::shared_ptr GetIdentity () const { return m_Identity; }; protected: // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (const std::vector >& tunnels); private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); void PostCreateNewLeaseSet (std::vector > tunnels); private: std::shared_ptr m_Owner; std::shared_ptr m_Identity; i2p::data::CryptoKeyType m_EncryptionKeyType; std::shared_ptr m_Decryptor; // standard std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; bool m_IsCreatingLeaseSet; boost::asio::deadline_timer m_LeaseSetCreationTimer; }; class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination { public: RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); ~RunnableI2CPDestination (); void Start (); void Stop (); }; class I2CPServer; class I2CPSession: public std::enable_shared_from_this { public: #ifdef ANDROID typedef boost::asio::local::stream_protocol proto; #else typedef boost::asio::ip::tcp proto; #endif I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); void Start (); void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; std::shared_ptr GetDestination () const { return m_Destination; }; // called from I2CPDestination void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); void SendMessagePayloadMessage (const uint8_t * payload, size_t len); void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status); // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); void DestLookupMessageHandler (const uint8_t * buf, size_t len); void GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len); private: void ReadProtocolByte (); void ReceiveHeader (); void HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred); void ReceivePayload (); void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (); void Terminate (); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); void SendSessionStatusMessage (uint8_t status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); private: I2CPServer& m_Owner; std::shared_ptr m_Socket; uint8_t m_Header[I2CP_HEADER_SIZE], m_Payload[I2CP_MAX_MESSAGE_LENGTH]; size_t m_PayloadLen; std::shared_ptr m_Destination; uint16_t m_SessionID; uint32_t m_MessageID; bool m_IsSendAccepted; // to client bool m_IsSending; uint8_t m_SendBuffer[I2CP_MAX_MESSAGE_LENGTH]; i2p::stream::SendBufferQueue m_SendQueue; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); class I2CPServer: private i2p::util::RunnableService { public: I2CPServer (const std::string& interface, int port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; bool IsSingleThread () const { return m_IsSingleThread; }; bool InsertSession (std::shared_ptr session); void RemoveSession (uint16_t sessionID); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: bool m_IsSingleThread; I2CPMessageHandler m_MessagesHandlers[256]; std::map > m_Sessions; I2CPSession::proto::acceptor m_Acceptor; public: const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; }; // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.39.0/libi2pd_client/I2PService.cpp000066400000000000000000000225621411072525600177570ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 "Destination.h" #include "Identity.h" #include "ClientContext.h" #include "I2PService.h" #include namespace i2p { namespace client { static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; I2PService::I2PService (std::shared_ptr localDestination): m_LocalDestination (localDestination ? localDestination : i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)), m_ReadyTimer(m_LocalDestination->GetService()), m_ReadyTimerTriggered(false), m_ConnectTimeout(0), isUpdated (true) { m_LocalDestination->Acquire (); } I2PService::I2PService (i2p::data::SigningKeyType kt): m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt)), m_ReadyTimer(m_LocalDestination->GetService()), m_ConnectTimeout(0), isUpdated (true) { m_LocalDestination->Acquire (); } I2PService::~I2PService () { ClearHandlers (); if (m_LocalDestination) m_LocalDestination->Release (); } void I2PService::ClearHandlers () { if(m_ConnectTimeout) m_ReadyTimer.cancel(); std::unique_lock l(m_HandlersMutex); for (auto it: m_Handlers) it->Terminate (); m_Handlers.clear(); } void I2PService::SetConnectTimeout(uint32_t timeout) { m_ConnectTimeout = timeout; } void I2PService::AddReadyCallback(ReadyCallback cb) { uint32_t now = i2p::util::GetSecondsSinceEpoch(); uint32_t tm = (m_ConnectTimeout) ? now + m_ConnectTimeout : NEVER_TIMES_OUT; LogPrint(eLogDebug, "I2PService::AddReadyCallback() ", tm, " ", now); m_ReadyCallbacks.push_back({cb, tm}); if (!m_ReadyTimerTriggered) TriggerReadyCheckTimer(); } void I2PService::TriggerReadyCheckTimer() { m_ReadyTimer.expires_from_now(boost::posix_time::seconds (1)); m_ReadyTimer.async_wait(std::bind(&I2PService::HandleReadyCheckTimer, shared_from_this (), std::placeholders::_1)); m_ReadyTimerTriggered = true; } void I2PService::HandleReadyCheckTimer(const boost::system::error_code &ec) { if(ec || m_LocalDestination->IsReady()) { for(auto & itr : m_ReadyCallbacks) itr.first(ec); m_ReadyCallbacks.clear(); } else if(!m_LocalDestination->IsReady()) { // expire timed out requests uint32_t now = i2p::util::GetSecondsSinceEpoch (); auto itr = m_ReadyCallbacks.begin(); while(itr != m_ReadyCallbacks.end()) { if(itr->second != NEVER_TIMES_OUT && now >= itr->second) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); } else ++itr; } } if(!ec && m_ReadyCallbacks.size()) TriggerReadyCheckTimer(); else m_ReadyTimerTriggered = false; } void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { assert(streamRequestComplete); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); if (address) CreateStream(streamRequestComplete, address, port); else { LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest); streamRequestComplete (nullptr); } } void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, int port) { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) { if(ec) { LogPrint(eLogWarning, "I2PService::CreateStream() ", ec.message()); streamRequestComplete(nullptr); } else { if (address->IsIdentHash ()) this->m_LocalDestination->CreateStream(streamRequestComplete, address->identHash, port); else this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } }); } else { if (address->IsIdentHash ()) m_LocalDestination->CreateStream (streamRequestComplete, address->identHash, port); else m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } } TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) { boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE); upstream->set_option(option); downstream->set_option(option); } TCPIPPipe::~TCPIPPipe() { Terminate(); } void TCPIPPipe::Start() { AsyncReceiveUpstream(); AsyncReceiveDownstream(); } void TCPIPPipe::Terminate() { if(Kill()) return; if (m_up) { if (m_up->is_open()) m_up->close(); m_up = nullptr; } if (m_down) { if (m_down->is_open()) m_down->close(); m_down = nullptr; } Done(shared_from_this()); } void TCPIPPipe::AsyncReceiveUpstream() { if (m_up) { m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket"); } void TCPIPPipe::AsyncReceiveDownstream() { if (m_down) { m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket"); } void TCPIPPipe::UpstreamWrite(size_t len) { if (m_up) { LogPrint(eLogDebug, "TCPIPPipe: upstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleUpstreamWrite, shared_from_this(), std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: upstream write: no socket"); } void TCPIPPipe::DownstreamWrite(size_t len) { if (m_down) { LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleDownstreamWrite, shared_from_this(), std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: downstream write: no socket"); } void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) bytes_transfered, " bytes received"); if (ecode) { LogPrint(eLogError, "TCPIPPipe: downstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { if (bytes_transfered > 0 ) memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered); UpstreamWrite(bytes_transfered); } } void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { if (ecode) { LogPrint(eLogError, "TCPIPPipe: downstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else AsyncReceiveUpstream(); } void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { if (ecode) { LogPrint(eLogError, "TCPIPPipe: upstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else AsyncReceiveDownstream(); } void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int)bytes_transfered, " bytes received"); if (ecode) { LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { if (bytes_transfered > 0 ) memcpy(m_downstream_buf, m_upstream_to_down_buf, bytes_transfered); DownstreamWrite(bytes_transfered); } } void TCPIPAcceptor::Start () { m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint)); // update the local end point in case port has been set zero and got updated now m_LocalEndpoint = m_Acceptor->local_endpoint(); m_Acceptor->listen (); Accept (); } void TCPIPAcceptor::Stop () { if (m_Acceptor) { m_Acceptor->close(); m_Acceptor.reset (nullptr); } m_Timer.cancel (); ClearHandlers(); } void TCPIPAcceptor::Accept () { auto newSocket = std::make_shared (GetService ()); m_Acceptor->async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this, std::placeholders::_1, newSocket)); } void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) { LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted"); auto handler = CreateHandler(socket); if (handler) { AddHandler(handler); handler->Handle(); } else socket->close(); Accept(); } else { if (ecode != boost::asio::error::operation_aborted) LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ()); } } } } i2pd-2.39.0/libi2pd_client/I2PService.h000066400000000000000000000143531411072525600174230ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 I2PSERVICE_H__ #define I2PSERVICE_H__ #include #include #include #include #include #include "Destination.h" #include "Identity.h" #include "AddressBook.h" namespace i2p { namespace client { class I2PServiceHandler; class I2PService : public std::enable_shared_from_this { public: typedef std::function ReadyCallback; public: I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService (); inline void AddHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.insert(conn); } inline void RemoveHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.erase(conn); } void ClearHandlers (); void SetConnectTimeout(uint32_t timeout); void AddReadyCallback(ReadyCallback cb); inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } inline void SetLocalDestination (std::shared_ptr dest) { if (m_LocalDestination) m_LocalDestination->Release (); if (dest) dest->Acquire (); m_LocalDestination = dest; } void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); void CreateStream(StreamRequestComplete complete, std::shared_ptr address, int port); inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; virtual void Stop () = 0; virtual const char* GetName() { return "Generic I2P Service"; } private: void TriggerReadyCheckTimer(); void HandleReadyCheckTimer(const boost::system::error_code & ec); private: std::shared_ptr m_LocalDestination; std::unordered_set > m_Handlers; std::mutex m_HandlersMutex; std::vector > m_ReadyCallbacks; boost::asio::deadline_timer m_ReadyTimer; bool m_ReadyTimerTriggered; uint32_t m_ConnectTimeout; const size_t NEVER_TIMES_OUT = 0; public: bool isUpdated; // transient, used during reload only }; /*Simple interface for I2PHandlers, allows detection of finalization amongst other things */ class I2PServiceHandler { public: I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } virtual ~I2PServiceHandler() { } //If you override this make sure you call it from the children virtual void Handle() {}; //Start handling the socket void Terminate () { Kill (); }; protected: // Call when terminating or handing over to avoid race conditions inline bool Kill () { return m_Dead.exchange(true); } // Call to know if the handler is dead inline bool Dead () { return m_Dead; } // Call when done to clean up (make sure Kill is called first) inline void Done (std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } // Call to talk with the owner inline I2PService * GetOwner() { return m_Service; } private: I2PService *m_Service; std::atomic m_Dead; //To avoid cleaning up multiple times }; const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8; // bidirectional pipe for 2 tcp/ip sockets class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this { public: TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); ~TCPIPPipe(); void Start(); protected: void Terminate(); void AsyncReceiveUpstream(); void AsyncReceiveDownstream(); void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); void HandleUpstreamWrite(const boost::system::error_code & ecode); void HandleDownstreamWrite(const boost::system::error_code & ecode); void UpstreamWrite(size_t len); void DownstreamWrite(size_t len); private: uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE]; uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE]; std::shared_ptr m_up, m_down; }; /* TODO: support IPv6 too */ //This is a service that listens for connections on the IP network and interacts with I2P class TCPIPAcceptor: public I2PService { public: TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) : I2PService(kt), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } //If you override this make sure you call it from the children void Start (); //If you override this make sure you call it from the children void Stop (); const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } protected: virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; private: void Accept(); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); boost::asio::ip::tcp::endpoint m_LocalEndpoint; std::unique_ptr m_Acceptor; boost::asio::deadline_timer m_Timer; }; } } #endif i2pd-2.39.0/libi2pd_client/I2PTunnel.cpp000066400000000000000000001046341411072525600176250ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" #include "util.h" namespace i2p { namespace client { /** set standard socket options */ static void I2PTunnelSetSocketOptions(std::shared_ptr socket) { if (socket && socket->is_open()) { boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); socket->set_option(option); } } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { } I2PTunnelConnection::~I2PTunnelConnection () { } void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) { if (m_Stream) { if (msg) m_Stream->Send (msg, len); // connect and send else m_Stream->Send (m_Buffer, 0); // connect } StreamReceive (); Receive (); } static boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; bytes[0] = 127; memcpy (bytes.data ()+1, ident, 3); boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); return ourIP; } #ifdef __linux__ static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) { // bind to 127.x.x.x address // where x.x.x are first three bytes from ident auto ourIP = GetLoopbackAddressFor(addr); boost::system::error_code ec; sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); if (ec) LogPrint (eLogError, "I2PTunnel: can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); } #endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) { m_Socket->open (boost::asio::ip::tcp::v4 ()); auto ident = m_Stream->GetRemoteIdentity()->GetIdentHash(); MapToLoopback(m_Socket, ident); } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); } } void I2PTunnelConnection::Connect (const boost::asio::ip::address& localAddress) { if (m_Socket) { if (m_RemoteEndpoint.address().is_v6 ()) m_Socket->open (boost::asio::ip::tcp::v6 ()); else m_Socket->open (boost::asio::ip::tcp::v4 ()); boost::system::error_code ec; m_Socket->bind (boost::asio::ip::tcp::endpoint (localAddress, 0), ec); if (ec) LogPrint (eLogError, "I2PTunnel: can't bind to ", localAddress.to_string (), ": ", ec.message ()); } Connect (false); } void I2PTunnelConnection::Terminate () { if (Kill()) return; if (m_Stream) { m_Stream->Close (); m_Stream.reset (); } boost::system::error_code ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); // avoid RST m_Socket->close (); Done(shared_from_this ()); } void I2PTunnelConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ()); Terminate (); } } else WriteToStream (m_Buffer, bytes_transferred); } void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) { if (m_Stream) { auto s = shared_from_this (); m_Stream->AsyncSend (buf, len, [s](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); else s->Terminate (); }); } } void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else StreamReceive (); } void I2PTunnelConnection::StreamReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); } else // closed by peer { // get remaning data auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); else // no more data Terminate (); } } } void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); if (bytes_transferred > 0) Write (m_StreamBuffer, bytes_transferred); // postpone termination else if (ecode == boost::asio::error::timed_out && m_Stream && m_Stream->IsOpen ()) StreamReceive (); else Terminate (); } else Terminate (); } else Write (m_StreamBuffer, bytes_transferred); } void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ()); Terminate (); } else { LogPrint (eLogDebug, "I2PTunnel: connected"); if (m_IsQuiet) StreamReceive (); else { // send destination first like received from I2P std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); dest += "\n"; if(sizeof(m_StreamBuffer) >= dest.size()) { memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); } HandleStreamReceive (boost::system::error_code (), dest.size ()); } Receive (); } } void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { if (!m_ConnectionSent && !line.compare(0, 10, "Connection")) { /* close connection, if not Connection: (U|u)pgrade (for websocket) */ auto x = line.find("pgrade"); if (x != std::string::npos && std::tolower(line[x - 1]) == 'u') m_OutHeader << line << "\r\n"; else m_OutHeader << "Connection: close\r\n"; m_ConnectionSent = true; } else if (!m_ProxyConnectionSent && !line.compare(0, 16, "Proxy-Connection")) { m_OutHeader << "Proxy-Connection: close\r\n"; m_ProxyConnectionSent = true; } else m_OutHeader << line << "\n"; } } else break; } if (endOfHeader) { if (!m_ConnectionSent) m_OutHeader << "Connection: close\r\n"; if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\r\n"; m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } } } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) { } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { if (m_Host.length () > 0 && !line.compare(0, 5, "Host:")) m_OutHeader << "Host: " << m_Host << "\r\n"; // override host else m_OutHeader << line << "\n"; } } else break; } if (endOfHeader) { // add X-I2P fields if (m_From) { m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; } m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); m_From = nullptr; m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } } } void I2PServerTunnelConnectionHTTP::WriteToStream (const uint8_t * buf, size_t len) { if (m_ResponseHeaderSent) I2PTunnelConnection::WriteToStream (buf, len); else { m_InHeader.clear (); if (m_InHeader.str ().empty ()) m_OutHeader.str (""); // start of response m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { static const std::vector excluded // list of excluded headers { "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" }; bool matched = false; for (const auto& it: excluded) if (!line.compare(0, it.length (), it)) { matched = true; break; } if (!matched) m_OutHeader << line << "\n"; } } else break; } if (endOfHeader) { m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); m_ResponseHeaderSent = true; I2PTunnelConnection::WriteToStream ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); m_OutHeader.str (""); } else Receive (); } } I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { m_OutPacket.str (""); if (m_NeedsWebIrc) { m_NeedsWebIrc = false; m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; } m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); while (!m_InPacket.eof () && !m_InPacket.fail ()) { std::string line; std::getline (m_InPacket, line); if (line.length () == 0 && m_InPacket.eof ()) m_InPacket.str (""); auto pos = line.find ("USER"); if (!pos) // start of line { pos = line.find (" "); pos++; pos = line.find (" ", pos); pos++; auto nextpos = line.find (" ", pos); m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); m_OutPacket << line.substr (nextpos) << '\n'; } else m_OutPacket << line << '\n'; } I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } /* This handler tries to establish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PClientTunnelHandler (I2PClientTunnel * parent, std::shared_ptr address, int destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_Address(address), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); void Terminate(); private: void HandleStreamRequestComplete (std::shared_ptr stream); std::shared_ptr m_Address; int m_DestinationPort; std::shared_ptr m_Socket; }; void I2PClientTunnelHandler::Handle() { GetOwner()->CreateStream ( std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_Address, m_DestinationPort); } void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { if (Kill()) return; LogPrint (eLogDebug, "I2PTunnel: new connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); Done(shared_from_this()); } else { LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info."); Terminate(); } } void I2PClientTunnelHandler::Terminate() { if (Kill()) return; if (m_Socket) { m_Socket->close(); m_Socket = nullptr; } Done(shared_from_this()); } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), m_DestinationPort (destinationPort) { } void I2PClientTunnel::Start () { TCPIPAcceptor::Start (); GetAddress (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ std::shared_ptr I2PClientTunnel::GetAddress () { if (!m_Address) { m_Address = i2p::client::context.GetAddressBook ().GetAddress (m_Destination); if (!m_Address) LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found"); } return m_Address; } std::shared_ptr I2PClientTunnel::CreateHandler(std::shared_ptr socket) { auto address = GetAddress (); if (address) return std::make_shared(this, address, m_DestinationPort, socket); else return nullptr; } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); } void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (m_Address, ec); if (!ec) { m_Endpoint.address (addr); Accept (); } else { auto resolver = std::make_shared(GetService ()); resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), std::bind (&I2PServerTunnel::HandleResolve, this, std::placeholders::_1, std::placeholders::_2, resolver)); } } void I2PServerTunnel::Stop () { ClearHandlers (); } void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver) { if (!ecode) { bool found = false; boost::asio::ip::tcp::endpoint ep; if (m_LocalAddress) { boost::asio::ip::tcp::resolver::iterator end; while (it != end) { ep = *it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) { if (m_LocalAddress->is_v4 ()) found = true; } else if (ep.address ().is_v6 ()) { if (i2p::util::net::IsYggdrasilAddress (ep.address ())) { if (i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) found = true; } else if (m_LocalAddress->is_v6 ()) found = true; } } if (found) break; it++; } } else { found = true; ep = *it; // first available } if (!found) { LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); return; } auto addr = ep.address (); LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); } else LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) { m_AccessList = accessList; m_IsAccessList = true; } void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string(localAddress, ec); if (!ec) m_LocalAddress.reset (new boost::asio::ip::address (addr)); else LogPrint (eLogError, "I2PTunnel: can't set local address ", localAddress); } void I2PServerTunnel::Accept () { if (m_PortDestination) m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); auto localDestination = GetLocalDestination (); if (localDestination) { if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); } else LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel"); } void I2PServerTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { if (m_IsAccessList) { if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ())) { LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); stream->Close (); return; } } // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); if (m_LocalAddress) conn->Connect (*m_LocalAddress); else conn->Connect (m_IsUniqueLocal); } } std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint ()); } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { } std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), m_Host); } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { } std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); } void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) { std::lock_guard lock(m_SessionsMutex); m_LastSession = ObtainUDPSession(from, toPort, fromPort); } m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) { if (m_LastSession) { m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } } void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); auto itr = m_Sessions.begin(); while(itr != m_Sessions.end()) { if(now - (*itr)->LastActivity >= delta ) itr = m_Sessions.erase(itr); else ++itr; } } void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); std::vector removePorts; for (const auto & s : m_Sessions) { if (now - s.second->second >= delta) removePorts.push_back(s.first); } for(auto port : removePorts) { m_Sessions.erase(port); } } UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) { auto ih = from.GetIdentHash(); for (auto & s : m_Sessions ) { if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) { /** found existing session */ LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); return s; } } boost::asio::ip::address addr; /** create new udp session */ if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) { auto ident = from.GetIdentHash(); addr = GetLoopbackAddressFor(ident); } else addr = m_LocalAddress; boost::asio::ip::udp::endpoint ep(addr, 0); m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); auto & back = m_Sessions.back(); return back; } UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination->GetDatagramDestination()), IPSocket(localDestination->GetService(), localEndpoint), SendEndpoint(endpoint), LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) { IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); memcpy(Identity, to->data(), 32); Receive(); } void UDPSession::Receive() { LogPrint(eLogDebug, "UDPSession: Receive"); IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); } void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); auto ts = i2p::util::GetMillisecondsSinceEpoch(); auto session = m_Destination->GetSession (Identity); if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); else m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); size_t numPackets = 0; while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) { boost::system::error_code ec; size_t moreBytes = IPSocket.available(ec); if (ec || !moreBytes) break; len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); numPackets++; } if (numPackets > 0) LogPrint(eLogDebug, "UDPSession: forward more ", numPackets, "packets B from ", FromEndpoint); m_Destination->FlushSendQueue (session); LastActivity = ts; Receive(); } else LogPrint(eLogError, "UDPSession: ", ecode.message()); } I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip) : m_IsUniqueLocal(true), m_Name(name), m_LocalAddress(localAddress), m_RemoteEndpoint(forwardTo) { m_LocalDest = localDestination; m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); dgram->SetRawReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); LogPrint(eLogInfo, "UDPServer: done"); } void I2PUDPServerTunnel::Start() { m_LocalDest->Start(); } std::vector > I2PUDPServerTunnel::GetSessions() { std::vector > sessions; std::lock_guard lock(m_SessionsMutex); for ( UDPSessionPtr s : m_Sessions ) { if (!s->m_Destination) continue; auto info = s->m_Destination->GetInfoForRemote(s->Identity); if(!info) continue; auto sinfo = std::make_shared(); sinfo->Name = m_Name; sinfo->LocalIdent = std::make_shared(m_LocalDest->GetIdentHash().data()); sinfo->RemoteIdent = std::make_shared(s->Identity.data()); sinfo->CurrentIBGW = info->IBGW; sinfo->CurrentOBEP = info->OBEP; sessions.push_back(sinfo); } return sessions; } I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, bool gzip) : m_Name(name), m_RemoteDest(remoteDest), m_LocalDest(localDestination), m_LocalEndpoint(localEndpoint), m_RemoteIdent(nullptr), m_ResolveThread(nullptr), m_LocalSocket(localDestination->GetService(), localEndpoint), RemotePort(remotePort), m_LastPort (0), m_cancel_resolve(false) { m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); dgram->SetRawReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } void I2PUDPClientTunnel::Start() { m_LocalDest->Start(); if (m_ResolveThread == nullptr) m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); RecvFromLocal(); } void I2PUDPClientTunnel::RecvFromLocal() { m_LocalSocket.async_receive_from(boost::asio::buffer(m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, std::bind(&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); } void I2PUDPClientTunnel::HandleRecvFromLocal(const boost::system::error_code & ec, std::size_t transferred) { if(ec) { LogPrint(eLogError, "UDP Client: ", ec.message()); return; } if(!m_RemoteIdent) { LogPrint(eLogWarning, "UDP Client: remote endpoint not resolved yet"); RecvFromLocal(); return; // drop, remote not resolved } auto remotePort = m_RecvEndpoint.port(); if (!m_LastPort || m_LastPort != remotePort) { auto itr = m_Sessions.find(remotePort); if (itr != m_Sessions.end()) m_LastSession = itr->second; else { m_LastSession = std::make_shared(boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0); m_Sessions.emplace (remotePort, m_LastSession); } m_LastPort = remotePort; } // send off to remote i2p destination auto ts = i2p::util::GetMillisecondsSinceEpoch(); LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); auto session = m_LocalDest->GetDatagramDestination()->GetSession (*m_RemoteIdent); if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); else m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); size_t numPackets = 0; while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) { boost::system::error_code ec; size_t moreBytes = m_LocalSocket.available(ec); if (ec || !moreBytes) break; transferred = m_LocalSocket.receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); remotePort = m_RecvEndpoint.port(); // TODO: check remotePort m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); numPackets++; } if (numPackets) LogPrint(eLogDebug, "UDP Client: sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32()); m_LocalDest->GetDatagramDestination()->FlushSendQueue (session); // mark convo as active if (m_LastSession) m_LastSession->second = ts; RecvFromLocal(); } std::vector > I2PUDPClientTunnel::GetSessions() { // TODO: implement std::vector > infos; return infos; } void I2PUDPClientTunnel::TryResolving() { i2p::util::SetThreadName("UDP Resolver"); LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); std::shared_ptr addr; while(!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) { LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); std::this_thread::sleep_for(std::chrono::seconds(1)); } if(m_cancel_resolve) { LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); return; } if (!addr || !addr->IsIdentHash ()) { LogPrint(eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); return; } m_RemoteIdent = new i2p::data::IdentHash; *m_RemoteIdent = addr->identHash; LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) HandleRecvFromI2PRaw (fromPort, toPort, buf, len); else LogPrint(eLogWarning, "UDP Client: unwarranted traffic from ", from.GetIdentHash().ToBase32()); } void I2PUDPClientTunnel::HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { auto itr = m_Sessions.find(toPort); // found convo ? if(itr != m_Sessions.end()) { // found convo if (len > 0) { LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32() : ""); m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second->first); // mark convo as active itr->second->second = i2p::util::GetMillisecondsSinceEpoch(); } } else LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); } I2PUDPClientTunnel::~I2PUDPClientTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); m_Sessions.clear(); if(m_LocalSocket.is_open()) m_LocalSocket.close(); m_cancel_resolve = true; if(m_ResolveThread) { m_ResolveThread->join(); delete m_ResolveThread; m_ResolveThread = nullptr; } if (m_RemoteIdent) delete m_RemoteIdent; } } } i2pd-2.39.0/libi2pd_client/I2PTunnel.h000066400000000000000000000313671411072525600172740ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 I2PTUNNEL_H__ #define I2PTUNNEL_H__ #include #include #include #include #include #include #include #include "Identity.h" #include "Destination.h" #include "Datagram.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" namespace i2p { namespace client { const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (bool isUniqueLocal = true); void Connect (const boost::asio::ip::address& localAddress); protected: void Terminate (); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded void HandleWrite (const boost::system::error_code& ecode); virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded void StreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleConnect (const boost::system::error_code& ecode); std::shared_ptr GetSocket () const { return m_Socket; }; private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection { public: I2PClientTunnelConnectionHTTP (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PTunnelConnection (owner, socket, stream), m_HeaderSent (false), m_ConnectionSent (false), m_ProxyConnectionSent (false) {}; protected: void Write (const uint8_t * buf, size_t len); private: std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ConnectionSent, m_ProxyConnectionSent; }; class I2PServerTunnelConnectionHTTP: public I2PTunnelConnection { public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host); protected: void Write (const uint8_t * buf, size_t len); void WriteToStream (const uint8_t * buf, size_t len); private: std::string m_Host; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ResponseHeaderSent; std::shared_ptr m_From; }; class I2PTunnelConnectionIRC: public I2PTunnelConnection { public: I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); protected: void Write (const uint8_t * buf, size_t len); private: std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; bool m_NeedsWebIrc; std::string m_WebircPass; }; class I2PClientTunnel: public TCPIPAcceptor { protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); public: I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); void Stop (); const char* GetName() { return m_Name.c_str (); } private: std::shared_ptr GetAddress (); private: std::string m_Name, m_Destination; std::shared_ptr m_Address; int m_DestinationPort; }; /** 2 minute timeout for udp sessions */ const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds /** max size for i2p udp */ const size_t I2P_UDP_MAX_MTU = 64*1024; struct UDPSession { i2p::datagram::DatagramDestination * m_Destination; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; boost::asio::ip::udp::endpoint FromEndpoint; boost::asio::ip::udp::endpoint SendEndpoint; uint64_t LastActivity; uint16_t LocalPort; uint16_t RemotePort; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); }; /** read only info about a datagram session */ struct DatagramSessionInfo { /** the name of this forward */ std::string Name; /** ident hash of local destination */ std::shared_ptr LocalIdent; /** ident hash of remote destination */ std::shared_ptr RemoteIdent; /** ident hash of IBGW in use currently in this session or nullptr if none is set */ std::shared_ptr CurrentIBGW; /** ident hash of OBEP in use for this session or nullptr if none is set */ std::shared_ptr CurrentOBEP; /** i2p router's udp endpoint */ boost::asio::ip::udp::endpoint LocalEndpoint; /** client's udp endpoint */ boost::asio::ip::udp::endpoint RemoteEndpoint; /** how long has this converstation been idle in ms */ uint64_t idle; }; typedef std::shared_ptr UDPSessionPtr; /** server side udp tunnel, many i2p inbound to 1 ip outbound */ class I2PUDPServerTunnel { public: I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); ~I2PUDPServerTunnel(); /** expire stale udp conversations */ void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); std::shared_ptr GetLocalDestination () const { return m_LocalDest; } void SetUniqueLocal(bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSessionPtr ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); private: bool m_IsUniqueLocal; const std::string m_Name; boost::asio::ip::address m_LocalAddress; boost::asio::ip::udp::endpoint m_RemoteEndpoint; std::mutex m_SessionsMutex; std::vector m_Sessions; std::shared_ptr m_LocalDest; UDPSessionPtr m_LastSession; }; class I2PUDPClientTunnel { public: I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, bool gzip); ~I2PUDPClientTunnel(); void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } std::shared_ptr GetLocalDestination () const { return m_LocalDest; } void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); private: typedef std::pair UDPConvo; void RecvFromLocal(); void HandleRecvFromLocal(const boost::system::error_code & e, std::size_t transferred); void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void TryResolving(); private: const std::string m_Name; std::mutex m_SessionsMutex; std::unordered_map > m_Sessions; // maps i2p port -> local udp convo const std::string m_RemoteDest; std::shared_ptr m_LocalDest; const boost::asio::ip::udp::endpoint m_LocalEndpoint; i2p::data::IdentHash * m_RemoteIdent; std::thread * m_ResolveThread; boost::asio::ip::udp::socket m_LocalSocket; boost::asio::ip::udp::endpoint m_RecvEndpoint; uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; std::shared_ptr m_LastSession; }; class I2PServerTunnel: public I2PService { public: I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport = 0, bool gzip = true); void Start (); void Stop (); void SetAccessList (const std::set& accessList); void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } void SetLocalAddress (const std::string& localAddress); const std::string& GetAddress() const { return m_Address; } int GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } const char* GetName() { return m_Name.c_str (); } private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); void Accept (); void HandleAccept (std::shared_ptr stream); virtual std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: bool m_IsUniqueLocal; std::string m_Name, m_Address; int m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; bool m_IsAccessList; std::unique_ptr m_LocalAddress; }; class I2PServerTunnelHTTP: public I2PServerTunnel { public: I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, int inport = 0, bool gzip = true); private: std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: std::string m_Host; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, int inport = 0, bool gzip = true); private: std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: std::string m_WebircPass; }; } } #endif i2pd-2.39.0/libi2pd_client/MatchedDestination.cpp000066400000000000000000000057501411072525600216130ustar00rootroot00000000000000/* * Copyright (c) 2013-2021, 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 "MatchedDestination.h" #include "Log.h" #include "ClientContext.h" namespace i2p { namespace client { MatchedTunnelDestination::MatchedTunnelDestination(const i2p::data::PrivateKeys & keys, const std::string & remoteName, const std::map * params) : RunnableClientDestination(keys, false, params), m_RemoteName(remoteName) {} void MatchedTunnelDestination::ResolveCurrentLeaseSet() { auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); if(addr && addr->IsIdentHash ()) { m_RemoteIdent = addr->identHash; auto ls = FindLeaseSet(m_RemoteIdent); if(ls) HandleFoundCurrentLeaseSet(ls); else RequestDestination(m_RemoteIdent, std::bind(&MatchedTunnelDestination::HandleFoundCurrentLeaseSet, this, std::placeholders::_1)); } else LogPrint(eLogWarning, "Destination: failed to resolve ", m_RemoteName); } void MatchedTunnelDestination::HandleFoundCurrentLeaseSet(std::shared_ptr ls) { if(ls) { LogPrint(eLogDebug, "Destination: resolved remote lease set for ", m_RemoteName); m_RemoteLeaseSet = ls; } else { m_ResolveTimer->expires_from_now(boost::posix_time::seconds(1)); m_ResolveTimer->async_wait([&](const boost::system::error_code & ec) { if(!ec) ResolveCurrentLeaseSet(); }); } } void MatchedTunnelDestination::Start() { ClientDestination::Start(); m_ResolveTimer = std::make_shared(GetService()); GetTunnelPool()->SetCustomPeerSelector(this); ResolveCurrentLeaseSet(); } void MatchedTunnelDestination::Stop() { ClientDestination::Stop(); if(m_ResolveTimer) m_ResolveTimer->cancel(); } bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, std::placeholders::_2))) return false; // more here for outbound tunnels if(!inbound && m_RemoteLeaseSet) { if(m_RemoteLeaseSet->IsExpired()) ResolveCurrentLeaseSet(); if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) { // remote lease set is good auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); // pick lease std::shared_ptr obep; while(!obep && leases.size() > 0) { auto idx = rand() % leases.size(); auto lease = leases[idx]; obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); leases.erase(leases.begin()+idx); } if(obep) { path.Add (obep); LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); } else LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); } } return true; } } } i2pd-2.39.0/libi2pd_client/MatchedDestination.h000066400000000000000000000022321411072525600212500ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 MATCHED_DESTINATION_H_ #define MATCHED_DESTINATION_H_ #include "Destination.h" #include namespace i2p { namespace client { /** * client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ class MatchedTunnelDestination : public RunnableClientDestination, public i2p::tunnel::ITunnelPeerSelector { public: MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, const std::map * params = nullptr); void Start(); void Stop(); bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); private: void ResolveCurrentLeaseSet(); void HandleFoundCurrentLeaseSet(std::shared_ptr ls); private: std::string m_RemoteName; i2p::data::IdentHash m_RemoteIdent; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_ResolveTimer; }; } } #endif i2pd-2.39.0/libi2pd_client/SAM.cpp000066400000000000000000001347401411072525600164660ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #ifdef _MSC_VER #include #endif #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "util.h" #include "SAM.h" namespace i2p { namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_IsAccepting (false), m_Stream (nullptr) { } SAMSocket::~SAMSocket () { m_Stream = nullptr; } void SAMSocket::Terminate (const char* reason) { if(m_Stream) { m_Stream->AsyncClose (); m_Stream = nullptr; } auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; case eSAMSocketTypeStream: { break; } case eSAMSocketTypeAcceptor: case eSAMSocketTypeForward: { if (Session) { if (m_IsAccepting && Session->GetLocalDestination ()) Session->GetLocalDestination ()->StopAcceptingStreams (); } break; } default: ; } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open ()) { boost::system::error_code ec; m_Socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both, ec); m_Socket.close (); } m_Owner.RemoveSocket(shared_from_this()); } void SAMSocket::ReceiveHandshake () { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } static bool SAMVersionAcceptable(const std::string & ver) { return ver == "3.0" || ver == "3.1"; } static bool SAMVersionTooLow(const std::string & ver) { return ver.size() && ver[0] < '3'; } static bool SAMVersionTooHigh(const std::string & ver) { return ver.size() && ver > "3.1"; } void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake read error"); } else { m_Buffer[bytes_transferred] = 0; char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; LogPrint (eLogDebug, "SAM: handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { separator = strchr (separator + 1, ' '); if (separator) *separator = 0; } if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { std::string maxver("3.1"); std::string minver("3.0"); // try to find MIN and MAX, 3.0 if not found if (separator) { separator++; std::map params; ExtractParams (separator, params); auto it = params.find (SAM_PARAM_MAX); if (it != params.end ()) maxver = it->second; it = params.find(SAM_PARAM_MIN); if (it != params.end ()) minver = it->second; } // version negotiation std::string version; if (SAMVersionAcceptable(maxver)) { version = maxver; } else if (SAMVersionAcceptable(minver)) { version = minver; } else if (SAMVersionTooLow(minver) && SAMVersionTooHigh(maxver)) { version = "3.0"; } if (SAMVersionAcceptable(version)) { #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #endif boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true); } else { LogPrint (eLogError, "SAM: handshake mismatch"); Terminate ("SAM: handshake mismatch"); } } } bool SAMSocket::IsSession(const std::string & id) const { return id == m_ID; } void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake reply send error"); } else { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } } void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else { if (close) Terminate ("SAMSocket::SendMessageReply(close=true)"); else Receive (); } } void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close) { if (ecode) { LogPrint (eLogError, "SAM: reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: reply send error"); } else { if (close) Terminate ("SAMSocket::HandleMessageReplySent(close=true)"); else Receive (); } } void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } else if (m_SocketType == eSAMSocketTypeStream) HandleReceived (ecode, bytes_transferred); else { bytes_transferred += m_BufferOffset; m_BufferOffset = 0; m_Buffer[bytes_transferred] = 0; char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) { if (eol > m_Buffer && eol[-1] == '\r') 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, bytes_transferred - (eol - 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_STREAM_FORWARD)) ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_SESSION_ADD)) ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE)) ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1); if (processed < len) { m_BufferOffset = len - processed; if (processed > 0) memmove (m_Buffer, separator + 1 + processed, m_BufferOffset); else { // restore string back *separator = ' '; *eol = '\n'; } } // since it's SAM v1 reply is not expected Receive (); } else { LogPrint (eLogError, "SAM: unexpected message ", m_Buffer); Terminate ("SAM: unexpected message"); } } else { LogPrint (eLogError, "SAM: malformed message ", m_Buffer); Terminate ("malformed message"); } } else { LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); } } } static bool IsAcceptableSessionName(const std::string & str) { auto itr = str.begin(); while(itr != str.end()) { char ch = *itr; ++itr; if (ch == '<' || ch == '>' || ch == '"' || ch == '\'' || ch == '/') return false; } return true; } void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; if(!IsAcceptableSessionName(id)) { // invalid session id SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, strlen(SAM_SESSION_CREATE_INVALID_ID), true); return; } m_ID = id; if (m_Owner.FindSession (id)) { // session exists SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true); return; } SAMSessionType type = eSAMSessionTypeUnknown; if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster; if (type == eSAMSessionTypeUnknown) { // unknown style SendI2PError("Unknown STYLE"); return; } std::shared_ptr forward = nullptr; if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && params.find(SAM_PARAM_HOST) != params.end() && params.find(SAM_PARAM_PORT) != params.end()) { // udp forward selected boost::system::error_code e; // TODO: support hostnames in udp forward auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e); if (e) { // not an ip address SendI2PError("Invalid IP Address in HOST"); return; } auto port = std::stoi(params[SAM_PARAM_PORT]); if (port == -1) { SendI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); } //ensure we actually received a destination if (destination.empty()) { SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); return; } if (destination != SAM_VALUE_TRANSIENT) { //ensure it's a base64 string i2p::data::PrivateKeys keys; if (!keys.FromBase64(destination)) { SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); return; } } // create destination auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); if (session) { m_SocketType = eSAMSocketTypeSession; if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) { session->UDPEndpoint = forward; auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); if (type == eSAMSessionTypeDatagram) dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); else // raw dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } else SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true); } void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto session = m_Owner.FindSession(m_ID); if(session) { if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } } } void SAMSocket::SendSessionCreateReplyOk () { auto session = m_Owner.FindSession(m_ID); if (session) { uint8_t buf[1024]; char priv[1024]; size_t l = session->GetLocalDestination ()->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, size_t rem) { LogPrint (eLogDebug, "SAM: stream connect: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { SendI2PError ("Socket already in use"); return; } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); if (session) { if (rem > 0) // handle follow on data { memmove (m_Buffer, buf + len + 1, rem); // buf is a pointer to m_Buffer's content m_BufferOffset = rem; } else m_BufferOffset = 0; std::shared_ptr addr; if (destination.find(".i2p") != std::string::npos) addr = context.GetAddressBook().GetAddress (destination); else { auto dest = std::make_shared (); size_t l = dest->FromBase64(destination); if (l > 0) { context.GetAddressBook().InsertFullAddress(dest); addr = std::make_shared
(dest->GetIdentHash ()); } } if (addr && addr->IsValid ()) { if (addr->IsIdentHash ()) { auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash); if (leaseSet) Connect(leaseSet, session); else { session->GetLocalDestination ()->RequestDestination(addr->identHash, std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); } } else // B33 session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::Connect (std::shared_ptr remote, std::shared_ptr session) { if (!session) session = m_Owner.FindSession(m_ID); if (session) { m_SocketType = eSAMSocketTypeStream; m_Stream = session->GetLocalDestination ()->CreateStream (remote); if (m_Stream) { m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send m_BufferOffset = 0; I2PReceive (); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) { if (leaseSet) Connect (leaseSet); else { LogPrint (eLogError, "SAM: destination to connect not found"); SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream accept: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { SendI2PError ("Socket already in use"); return; } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); if (session) { m_SocketType = eSAMSocketTypeAcceptor; if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { m_IsAccepting = true; session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::ProcessStreamForward (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream forward: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; auto session = m_Owner.FindSession (id); if (!session) { SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); return; } if (session->GetLocalDestination ()->IsAcceptingStreams ()) { SendI2PError ("Already accepting"); return; } auto it = params.find (SAM_PARAM_PORT); if (it == params.end ()) { SendI2PError ("PORT is missing"); return; } auto port = std::stoi (it->second); if (port <= 0 || port >= 0xFFFF) { SendI2PError ("Invalid PORT"); return; } boost::system::error_code ec; auto ep = m_Socket.remote_endpoint (ec); if (ec) { SendI2PError ("Socket error"); return; } ep.port (port); m_SocketType = eSAMSocketTypeForward; m_ID = id; m_IsAccepting = true; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; session->GetLocalDestination ()->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, shared_from_this (), std::placeholders::_1, ep)); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; if (offset + size <= len) { auto session = m_Owner.FindSession(m_ID); if (session) { auto d = session->GetLocalDestination ()->GetDatagramDestination (); if (d) { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); if (session->Type == eSAMSessionTypeDatagram) d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); else // raw d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: missing datagram destination"); } else LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND"); } else { LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; } void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: dest generate"); std::map params; ExtractParams (buf, params); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; auto it = params.find (SAM_PARAM_SIGNATURE_TYPE); if (it != params.end ()) { if (!m_Owner.ResolveSignatureType (it->second, signatureType)) LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } it = params.find (SAM_PARAM_CRYPTO_TYPE); if (it != params.end ()) { try { cryptoType = std::stoi(it->second); } catch (const std::exception& ex) { LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; std::shared_ptr identity; std::shared_ptr addr; auto session = m_Owner.FindSession(m_ID); auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->GetLocalDestination (); if (name == "ME") SendNamingLookupReply (name, dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) SendNamingLookupReply (name, identity); else if ((addr = context.GetAddressBook ().GetAddress (name))) { if (addr->IsIdentHash ()) { auto leaseSet = dest->FindLeaseSet (addr->identHash); if (leaseSet) SendNamingLookupReply (name, leaseSet->GetIdentity ()); else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1, name)); } else dest->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1, name)); } else { LogPrint (eLogError, "SAM: naming failed, unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif SendMessageReply (m_Buffer, len, false); } } void SAMSocket::ProcessSessionAdd (char * buf, size_t len) { auto session = m_Owner.FindSession(m_ID); if (session && session->Type == eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: subsession add: ", buf); auto masterSession = std::static_pointer_cast(session); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; if (masterSession->subsessions.count (id) > 1) { // session exists SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); return; } std::string& style = params[SAM_PARAM_STYLE]; SAMSessionType type = eSAMSessionTypeUnknown; if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; // TODO: implement other styles if (type == eSAMSessionTypeUnknown) { // unknown style SendI2PError("Unsupported STYLE"); return; } auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); if (fromPort == -1) { SendI2PError("Invalid from port"); return; } auto subsession = std::make_shared(masterSession, id, type, fromPort); if (m_Owner.AddSession (subsession)) { masterSession->subsessions.insert (id); SendSessionCreateReplyOk (); } else SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); } else SendI2PError ("Wrong session type"); } void SAMSocket::ProcessSessionRemove (char * buf, size_t len) { auto session = m_Owner.FindSession(m_ID); if (session && session->Type == eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: subsession remove: ", buf); auto masterSession = std::static_pointer_cast(session); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; if (!masterSession->subsessions.erase (id)) { SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), false); return; } m_Owner.CloseSession (id); SendSessionCreateReplyOk (); } else SendI2PError ("Wrong session type"); } void SAMSocket::SendI2PError(const std::string & msg) { LogPrint (eLogError, "SAM: i2p error ", msg); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #endif SendMessageReply (m_Buffer, len, true); } void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name) { if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); SendNamingLookupReply (name, leaseSet->GetIdentity ()); } else { LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", name, " not found"); #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 std::string& name, std::shared_ptr identity) { auto base64 = identity->ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ExtractParams (char * buf, std::map& 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 + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("read error"); } else { if (m_Stream) { bytes_transferred += m_BufferOffset; m_BufferOffset = 0; m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, std::bind(&SAMSocket::HandleStreamSend, shared_from_this(), std::placeholders::_1)); } else { Terminate("No Stream Remaining"); } } } void SAMSocket::I2PReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), std::placeholders::_1, std::placeholders::_2), SAM_SOCKET_CONNECTION_MAX_IDLE); } else // closed by peer { uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; // get remaning data auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { WriteI2PDataImmediate(buff, len); } else // no more data { delete [] buff; Terminate ("no more data"); } } } } void SAMSocket::WriteI2PDataImmediate(uint8_t * buff, size_t sz) { boost::asio::async_write ( m_Socket, boost::asio::buffer (buff, sz), boost::asio::transfer_all(), std::bind (&SAMSocket::HandleWriteI2PDataImmediate, shared_from_this (), std::placeholders::_1, buff)); // postpone termination } void SAMSocket::HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff) { delete [] buff; } void SAMSocket::WriteI2PData(size_t sz) { boost::asio::async_write ( m_Socket, boost::asio::buffer (m_StreamBuffer, sz), boost::asio::transfer_all(), std::bind(&SAMSocket::HandleWriteI2PData, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { if (bytes_transferred > 0) { WriteI2PData(bytes_transferred); } else { auto s = shared_from_this (); m_Owner.GetService ().post ([s] { s->Terminate ("stream read error"); }); } } else { auto s = shared_from_this (); m_Owner.GetService ().post ([s] { s->Terminate ("stream read error (op aborted)"); }); } } else { if (m_SocketType != eSAMSocketTypeTerminated) { if (bytes_transferred > 0) { WriteI2PData(bytes_transferred); } else I2PReceive(); } } } void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode, size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("socket write error at HandleWriteI2PData"); } else { I2PReceive (); } } void SAMSocket::HandleI2PAccept (std::shared_ptr stream) { if (stream) { LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID); m_SocketType = eSAMSocketTypeStream; m_IsAccepting = false; m_Stream = stream; context.GetAddressBook ().InsertFullAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); if (session) { // find more pending acceptors for (auto & it: m_Owner.ListSockets (m_ID)) if (it->m_SocketType == eSAMSocketTypeAcceptor) { it->m_IsAccepting = true; session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); break; } } if (!m_IsSilent) { // get remote peer address auto ident_ptr = stream->GetRemoteIdentity(); const size_t ident_len = ident_ptr->GetFullLen(); uint8_t* ident = new uint8_t[ident_len]; // send remote peer address as base64 const size_t l = ident_ptr->ToBuffer (ident, ident_len); const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); delete[] ident; m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream } else I2PReceive (); } else LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } void SAMSocket::HandleI2PForward (std::shared_ptr stream, boost::asio::ip::tcp::endpoint ep) { if (stream) { LogPrint (eLogDebug, "SAM: incoming forward I2P connection for session ", m_ID); auto newSocket = std::make_shared(m_Owner); newSocket->SetSocketType (eSAMSocketTypeStream); auto s = shared_from_this (); newSocket->GetSocket ().async_connect (ep, [s, newSocket, stream](const boost::system::error_code& ecode) { if (!ecode) { s->m_Owner.AddSocket (newSocket); newSocket->Receive (); newSocket->m_Stream = stream; newSocket->m_ID = s->m_ID; if (!s->m_IsSilent) { // get remote peer address auto dest = stream->GetRemoteIdentity()->ToBase64 (); memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); newSocket->m_StreamBuffer[dest.length ()] = '\n'; newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream } else newSocket->I2PReceive (); } else stream->AsyncClose (); }); } else LogPrint (eLogWarning, "SAM: I2P forward acceptor has been reset"); } void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); auto session = m_Owner.FindSession(m_ID); if(session) { auto ep = session->UDPEndpoint; if (ep) { // udp forward enabled size_t bsz = base64.size(); size_t sz = bsz + 1 + len; // build datagram body uint8_t * data = new uint8_t[sz]; // Destination memcpy(data, base64.c_str(), bsz); // linefeed data[bsz] = '\n'; // Payload memcpy(data+bsz+1, buf, len); // send to remote endpoint m_Owner.SendTo(data, sz, ep); delete [] data; } else { #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #endif if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); } else LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer"); } } } void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: raw datagram received ", len); auto session = m_Owner.FindSession(m_ID); if(session) { auto ep = session->UDPEndpoint; if (ep) // udp forward enabled m_Owner.SendTo(buf, len, ep); else { #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #else size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #endif if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); } else LogPrint (eLogWarning, "SAM: received raw datagram size ", len," exceeds buffer"); } } } void SAMSocket::HandleStreamSend(const boost::system::error_code & ec) { m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); } SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) { } void SAMSession::CloseStreams () { for(const auto & itr : m_Bridge.ListSockets(Name)) { itr->Terminate(nullptr); } } SAMSingleSession::SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest): SAMSession (parent, name, type), localDestination (dest) { } SAMSingleSession::~SAMSingleSession () { i2p::client::context.DeleteLocalDestination (localDestination); } void SAMSingleSession::StopLocalDestination () { localDestination->Release (); // stop accepting new streams localDestination->StopAcceptingStreams (); // terminate existing streams auto s = localDestination->GetStreamingDestination (); // TODO: take care about datagrams if (s) s->Stop (); } void SAMMasterSession::Close () { SAMSingleSession::Close (); for (const auto& it: subsessions) m_Bridge.CloseSession (it); subsessions.clear (); } SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port): SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port) { if (Type == eSAMSessionTypeStream) { auto d = masterSession->GetLocalDestination ()->CreateStreamingDestination (inPort); if (d) d->Start (); } // TODO: implement datagrams } std::shared_ptr SAMSubSession::GetLocalDestination () { return masterSession ? masterSession->GetLocalDestination () : nullptr; } void SAMSubSession::StopLocalDestination () { auto dest = GetLocalDestination (); if (dest && Type == eSAMSessionTypeStream) { auto d = dest->RemoveStreamingDestination (inPort); if (d) d->Stop (); } // TODO: implement datagrams } SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): RunnableService ("SAM"), m_IsSingleThread (singleThread), m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), m_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, {"ECDSA_SHA256_P256", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256}, {"ECDSA_SHA384_P384", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384}, {"ECDSA_SHA512_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521}, {"EdDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519}, {"GOST_GOSTR3411256_GOSTR3410CRYPTOPROA", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256}, {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512}, {"RedDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519}, } { } SAMBridge::~SAMBridge () { if (IsRunning ()) Stop (); } void SAMBridge::Start () { Accept (); ReceiveDatagram (); StartIOService (); } void SAMBridge::Stop () { try { m_Acceptor.cancel (); } catch (const std::exception& ex) { LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); } { std::unique_lock l(m_SessionsMutex); for (auto& it: m_Sessions) it.second->Close (); m_Sessions.clear (); } StopIOService (); } void SAMBridge::Accept () { auto newSocket = std::make_shared(*this); m_Acceptor.async_accept (newSocket->GetSocket(), std::bind (&SAMBridge::HandleAccept, this, std::placeholders::_1, newSocket)); } void SAMBridge::AddSocket(std::shared_ptr socket) { std::unique_lock lock(m_OpenSocketsMutex); m_OpenSockets.push_back(socket); } void SAMBridge::RemoveSocket(const std::shared_ptr & socket) { std::unique_lock lock(m_OpenSocketsMutex); m_OpenSockets.remove_if([socket](const std::shared_ptr & item) -> bool { return item == socket; }); } void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) { boost::system::error_code ec; auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { LogPrint (eLogDebug, "SAM: new connection from ", ep); AddSocket (socket); socket->ReceiveHandshake (); } else LogPrint (eLogError, "SAM: incoming connection error ", ec.message ()); } else LogPrint (eLogError, "SAM: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, const std::map * params) { std::shared_ptr localDestination = nullptr; if (destination != "") { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; localDestination = m_IsSingleThread ? i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : i2p::client::context.CreateNewLocalDestination (keys, true, params); } else // transient { // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (params) { auto it = params->find (SAM_PARAM_SIGNATURE_TYPE); if (it != params->end ()) { if (!ResolveSignatureType (it->second, signatureType)) LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) { try { cryptoType = std::stoi(it->second); } catch (const std::exception& ex) { LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } } localDestination = m_IsSingleThread ? i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { localDestination->Acquire (); auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; } return nullptr; } bool SAMBridge::AddSession (std::shared_ptr session) { if (!session) return false; auto ret = m_Sessions.emplace (session->Name, session); return ret.second; } void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) { session = it->second; m_Sessions.erase (it); } } if (session) { session->StopLocalDestination (); session->Close (); if (m_IsSingleThread) { auto timer = std::make_shared(GetService ()); timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds timer->async_wait ([timer, session](const boost::system::error_code& ecode) { // session's destructor is called here }); } } } std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) return it->second; return nullptr; } std::list > SAMBridge::ListSockets(const std::string & id) const { std::list > list; { std::unique_lock l(m_OpenSocketsMutex); for (const auto & itr : m_OpenSockets) if (itr->IsSession(id)) list.push_back(itr); } return list; } void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) { if(remote) { m_DatagramSocket.send_to(boost::asio::buffer(buf, len), *remote); } } void SAMBridge::ReceiveDatagram () { m_DatagramSocket.async_receive_from ( boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE), m_SenderEndpoint, std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2)); } void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (!ecode) { m_DatagramReceiveBuffer[bytes_transferred] = 0; char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n'); if(eol) { *eol = 0; eol++; size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); if (sessionID) { sessionID++; char * destination = strchr (sessionID, ' '); if (destination) { *destination = 0; destination++; auto session = FindSession (sessionID); if (session) { i2p::data::IdentityEx dest; dest.FromBase64 (destination); if (session->Type == eSAMSessionTypeDatagram) session->GetLocalDestination ()->GetDatagramDestination ()-> SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); else // raw session->GetLocalDestination ()->GetDatagramDestination ()-> SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); } else LogPrint (eLogError, "SAM: Missing destination key"); } else LogPrint (eLogError, "SAM: Missing sessionID"); } else LogPrint(eLogError, "SAM: invalid datagram"); ReceiveDatagram (); } else LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); } bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const { try { type = std::stoi (name); } catch (const std::invalid_argument& ex) { // name is not numeric, resolving auto it = m_SignatureTypes.find (name); if (it != m_SignatureTypes.end ()) type = it->second; else return false; } catch (const std::exception& ex) { return false; } // name has been resolved return true; } } } i2pd-2.39.0/libi2pd_client/SAM.h000066400000000000000000000262711411072525600161320ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SAM_H__ #define SAM_H__ #include #include #include #include #include #include #include #include #include #include "util.h" #include "Identity.h" #include "LeaseSet.h" #include "Streaming.h" #include "Destination.h" namespace i2p { namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds const char SAM_HANDSHAKE[] = "HELLO VERSION"; const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n"; const char SAM_HANDSHAKE_NOVERSION[] = "HELLO REPLY RESULT=NOVERSION\n"; const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n"; const char SAM_SESSION_CREATE[] = "SESSION CREATE"; const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n"; const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n"; const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n"; const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n"; const char SAM_SESSION_ADD[] = "SESSION ADD"; const char SAM_SESSION_REMOVE[] = "SESSION REMOVE"; 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_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\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_STREAM_FORWARD[] = "STREAM FORWARD"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; const char SAM_RAW_SEND[] = "RAW SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=%s VALUE=%s\n"; const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; const char SAM_PARAM_MAX[] = "MAX"; const char SAM_PARAM_STYLE[] = "STYLE"; const char SAM_PARAM_ID[] = "ID"; const char SAM_PARAM_SILENT[] = "SILENT"; const char SAM_PARAM_DESTINATION[] = "DESTINATION"; const char SAM_PARAM_NAME[] = "NAME"; const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; const char SAM_PARAM_CRYPTO_TYPE[] = "CRYPTO_TYPE"; const char SAM_PARAM_SIZE[] = "SIZE"; const char SAM_PARAM_HOST[] = "HOST"; const char SAM_PARAM_PORT[] = "PORT"; const char SAM_PARAM_FROM_PORT[] = "FROM_PORT"; 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_MASTER[] = "MASTER"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; enum SAMSocketType { eSAMSocketTypeUnknown, eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, eSAMSocketTypeForward, eSAMSocketTypeTerminated }; class SAMBridge; struct SAMSession; class SAMSocket: public std::enable_shared_from_this { public: typedef boost::asio::ip::tcp::socket Socket_t; SAMSocket (SAMBridge& owner); ~SAMSocket (); Socket_t& GetSocket () { return m_Socket; }; void ReceiveHandshake (); void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; }; SAMSocketType GetSocketType () const { return m_SocketType; }; void Terminate (const char* reason); bool IsSession(const std::string & id) const; private: void TerminateClose() { Terminate(nullptr); } void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendMessageReply (const char * msg, size_t len, bool close); void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void I2PReceive (); void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); void HandleI2PForward (std::shared_ptr stream, boost::asio::ip::tcp::endpoint ep); void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz); void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len, size_t rem); void ProcessStreamAccept (char * buf, size_t len); void ProcessStreamForward (char * buf, size_t len); void ProcessDestGenerate (char * buf, size_t len); void ProcessNamingLookup (char * buf, size_t len); void ProcessSessionAdd (char * buf, size_t len); void ProcessSessionRemove (char * buf, size_t len); void SendI2PError(const std::string & msg); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); void SendNamingLookupReply (const std::string& name, std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); void WriteI2PData(size_t sz); void WriteI2PDataImmediate(uint8_t * ptr, size_t sz); void HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff); void HandleStreamSend(const boost::system::error_code & ec); private: SAMBridge& m_Owner; Socket_t m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; size_t m_BufferOffset; uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname bool m_IsSilent; bool m_IsAccepting; // for eSAMSocketTypeAcceptor only std::shared_ptr m_Stream; }; enum SAMSessionType { eSAMSessionTypeUnknown, eSAMSessionTypeStream, eSAMSessionTypeDatagram, eSAMSessionTypeRaw, eSAMSessionTypeMaster }; struct SAMSession { SAMBridge & m_Bridge; std::string Name; SAMSessionType Type; std::shared_ptr UDPEndpoint; // TODO: move SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); virtual ~SAMSession () {}; virtual std::shared_ptr GetLocalDestination () = 0; virtual void StopLocalDestination () = 0; virtual void Close () { CloseStreams (); }; void CloseStreams (); }; struct SAMSingleSession: public SAMSession { std::shared_ptr localDestination; SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); ~SAMSingleSession (); std::shared_ptr GetLocalDestination () { return localDestination; }; void StopLocalDestination (); }; struct SAMMasterSession: public SAMSingleSession { std::set subsessions; SAMMasterSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest): SAMSingleSession (parent, name, eSAMSessionTypeMaster, dest) {}; void Close (); }; struct SAMSubSession: public SAMSession { std::shared_ptr masterSession; int inPort; SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port); // implements SAMSession std::shared_ptr GetLocalDestination (); void StopLocalDestination (); }; class SAMBridge: private i2p::util::RunnableService { public: SAMBridge (const std::string& address, int port, bool singleThread); ~SAMBridge (); void Start (); void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); bool AddSession (std::shared_ptr session); void CloseSession (const std::string& id); std::shared_ptr FindSession (const std::string& id) const; std::list > ListSockets(const std::string & id) const; /** send raw data to remote endpoint from our UDP Socket */ void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); void AddSocket(std::shared_ptr socket); void RemoveSocket(const std::shared_ptr & socket); bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; private: void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void ReceiveDatagram (); void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred); private: bool m_IsSingleThread; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; mutable std::mutex m_SessionsMutex; std::map > m_Sessions; mutable std::mutex m_OpenSocketsMutex; std::list > m_OpenSockets; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; std::map m_SignatureTypes; public: // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.39.0/libi2pd_client/SOCKS.cpp000066400000000000000000000624461411072525600167330ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 #include #include #include #include "SOCKS.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" namespace i2p { namespace proxy { static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; struct SOCKSDnsAddress { uint8_t size; char value[max_socks_hostname_size]; void FromString (const std::string& str) { size = str.length(); if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; memcpy(value,str.c_str(),size); } std::string ToString() { return std::string(value, size); } void push_back (char c) { value[size++] = c; } }; class SOCKSServer; class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: enum state { GET_SOCKSV, GET_COMMAND, GET_PORT, GET_IPV4, GET4_IDENT, GET4A_HOST, GET5_AUTHNUM, GET5_AUTH, GET5_REQUESTV, GET5_GETRSV, GET5_GETADDRTYPE, GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, READY, UPSTREAM_RESOLVE, UPSTREAM_CONNECT, UPSTREAM_HANDSHAKE }; enum authMethods { AUTH_NONE = 0, //No authentication, skip to next step AUTH_GSSAPI = 1, //GSSAPI authentication AUTH_USERPASSWD = 2, //Username and password AUTH_UNACCEPTABLE = 0xff //No acceptable method found }; enum addrTypes { ADDR_IPV4 = 1, //IPv4 address (4 octets) ADDR_DNS = 3, // DNS name (up to 255 octets) ADDR_IPV6 = 4 //IPV6 address (16 octets) }; enum errTypes { SOCKS5_OK = 0, // No error for SOCKS5 SOCKS5_GEN_FAIL = 1, // General server failure SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset SOCKS5_NET_UNREACH = 3, // Network unreachable SOCKS5_HOST_UNREACH = 4, // Host unreachable SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer SOCKS5_TTL_EXPIRED = 6, // TTL Expired SOCKS5_CMD_UNSUP = 7, // Command unsupported SOCKS5_ADDR_UNSUP = 8, // Address type unsupported SOCKS4_OK = 90, // No error for SOCKS4 SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ }; enum cmdTypes { CMD_CONNECT = 1, // TCP Connect CMD_BIND = 2, // TCP Bind CMD_UDP = 3 // UDP associate }; enum socksVersions { SOCKS4 = 4, // SOCKS4 SOCKS5 = 5 // SOCKS5 }; union address { uint32_t ip; SOCKSDnsAddress dns; uint8_t ipv6[16]; }; void EnterState(state nstate, uint8_t parseleft = 1); bool HandleData(uint8_t *sock_buff, std::size_t len); bool ValidateSOCKSRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); boost::asio::const_buffers_1 GenerateUpstreamRequest(); bool Socks5ChooseAuth(); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); void SentSocksDone(const boost::system::error_code & ecode); void SentSocksResponse(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); void SocksUpstreamSuccess(); void AsyncUpstreamSockRead(); void SendUpstreamRequest(); void HandleUpstreamData(uint8_t * buff, std::size_t len); void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); void HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; std::shared_ptr m_sock, m_upstreamSock; std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; uint8_t m_upstream_request[14+max_socks_hostname_size]; std::size_t m_upstream_response_len; address m_address; //Address std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests uint16_t m_port; uint8_t m_command; uint8_t m_parseleft; //Octets left to parse authMethods m_authchosen; //Authentication chosen addrTypes m_addrtype; //Address type chosen socksVersions m_socksv; //Socks version cmdTypes m_cmd; // Command requested state m_state; const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses? const std::string m_UpstreamProxyAddress; const uint16_t m_UpstreamProxyPort; public: SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : I2PServiceHandler(parent), m_proxy_resolver(parent->GetService()), m_sock(sock), m_stream(nullptr), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), m_UseUpstreamProxy(useUpstream), m_UpstreamProxyAddress(upstreamAddr), m_UpstreamProxyPort(upstreamPort) { m_address.ip = 0; EnterState(GET_SOCKSV); } ~SOCKSHandler() { Terminate(); } void Handle() { AsyncSockRead(); } }; void SOCKSHandler::AsyncSockRead() { LogPrint(eLogDebug, "SOCKS: async sock read"); if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError,"SOCKS: no socket for read"); } } void SOCKSHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "SOCKS: closing socket"); m_sock->close(); m_sock = nullptr; } if (m_upstreamSock) { LogPrint(eLogDebug, "SOCKS: closing upstream socket"); m_upstreamSock->close(); m_upstreamSock = nullptr; } if (m_stream) { LogPrint(eLogDebug, "SOCKS: closing stream"); m_stream.reset (); } Done(shared_from_this()); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) { assert(error >= SOCKS4_OK); m_response[0] = '\x00'; // version m_response[1] = error; // response code htobe16buf(m_response + 2, port); // port htobe32buf(m_response + 4, ip); // IP return boost::asio::const_buffers_1(m_response,8); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { size_t size = 6; // header + port assert(error <= SOCKS5_ADDR_UNSUP); m_response[0] = '\x05'; // version m_response[1] = error; // response code m_response[2] = '\x00'; // reserved m_response[3] = type; // address type switch (type) { case ADDR_IPV4: size += 4; htobe32buf(m_response + 4, addr.ip); htobe16buf(m_response + size - 2, port); break; case ADDR_IPV6: size += 16; memcpy(m_response + 4, addr.ipv6, 16); htobe16buf(m_response + size - 2, port); break; case ADDR_DNS: std::string address(addr.dns.value, addr.dns.size); if(address.substr(addr.dns.size - 4, 4) == ".i2p") // overwrite if requested address inside I2P { m_response[3] = ADDR_IPV4; size += 4; memset(m_response + 4, 0, 6); // six HEX zeros } else { size += (1 + addr.dns.size); /* name length + resolved address */ m_response[4] = addr.dns.size; memcpy(m_response + 5, addr.dns.value, addr.dns.size); htobe16buf(m_response + size - 2, port); } break; } return boost::asio::const_buffers_1(m_response, size); } boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest() { size_t upstreamRequestSize = 0; // TODO: negotiate with upstream // SOCKS 4a m_upstream_request[0] = '\x04'; //version m_upstream_request[1] = m_cmd; htobe16buf(m_upstream_request + 2, m_port); m_upstream_request[4] = 0; m_upstream_request[5] = 0; m_upstream_request[6] = 0; m_upstream_request[7] = 1; // user id m_upstream_request[8] = 'i'; m_upstream_request[9] = '2'; m_upstream_request[10] = 'p'; m_upstream_request[11] = 'd'; m_upstream_request[12] = 0; upstreamRequestSize += 13; if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) { // bounds check okay memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size); upstreamRequestSize += m_address.dns.size; // null terminate m_upstream_request[++upstreamRequestSize] = 0; } else { LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )"); } return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize); } bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; // Version m_response[1] = m_authchosen; // Response code boost::asio::const_buffers_1 response(m_response, 2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); return true; } } /* All hope is lost beyond this point */ void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error) { boost::asio::const_buffers_1 response(nullptr,0); assert(error != SOCKS4_OK && error != SOCKS5_OK); switch (m_socksv) { case SOCKS4: LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error); if (error < SOCKS4_OK) error = SOCKS4_FAIL; // Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error); response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() { boost::asio::const_buffers_1 response(nullptr,0); // TODO: this should depend on things like the command type and callbacks may change switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogInfo, "SOCKS: v5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); // HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { switch (nstate) { case GET_PORT: parseleft = 2; break; case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break; case GET4_IDENT: m_4aip = m_address.ip; break; case GET4A_HOST: case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break; case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break; default:; } m_parseleft = parseleft; m_state = nstate; } bool SOCKSHandler::ValidateSOCKSRequest() { if ( m_cmd != CMD_CONNECT ) { // TODO: we need to support binds and other shit! LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } // TODO: we may want to support other address types! if ( m_addrtype != ADDR_DNS ) { switch (m_socksv) { case SOCKS5: LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } return true; } bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse while (len > 0) { switch (m_state) { case GET_SOCKSV: m_socksv = (SOCKSHandler::socksVersions) *sock_buff; switch (*sock_buff) { case SOCKS4: EnterState(GET_COMMAND); //Initialize the parser at the right position break; case SOCKS5: EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } break; case GET5_AUTHNUM: EnterState(GET5_AUTH, *sock_buff); break; case GET5_AUTH: m_parseleft --; if (*sock_buff == AUTH_NONE) m_authchosen = AUTH_NONE; if ( m_parseleft == 0 ) { if (!Socks5ChooseAuth()) return false; EnterState(GET5_REQUESTV); } break; case GET_COMMAND: switch (*sock_buff) { case CMD_CONNECT: case CMD_BIND: break; case CMD_UDP: if (m_socksv == SOCKS5) break; #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif default: LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } m_cmd = (SOCKSHandler::cmdTypes)*sock_buff; switch (m_socksv) { case SOCKS5: EnterState(GET5_GETRSV); break; case SOCKS4: EnterState(GET_PORT); break; } break; case GET_PORT: m_port = (m_port << 8)|((uint16_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(READY); break; case SOCKS4: EnterState(GET_IPV4); break; } } break; case GET_IPV4: m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(GET_PORT); break; case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break; } } break; case GET4_IDENT: if (!*sock_buff) { if( m_4aip == 0 || m_4aip > 255 ) EnterState(READY); else EnterState(GET4A_HOST); } break; case GET4A_HOST: if (!*sock_buff) { EnterState(READY); break; } if (m_address.dns.size >= max_socks_hostname_size) { LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large"); SocksRequestFailed(SOCKS4_FAIL); return false; } m_address.dns.push_back(*sock_buff); break; case GET5_REQUESTV: if (*sock_buff != SOCKS5) { LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET_COMMAND); break; case GET5_GETRSV: if ( *sock_buff != 0 ) { LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET5_GETADDRTYPE); break; case GET5_GETADDRTYPE: switch (*sock_buff) { case ADDR_IPV4: EnterState(GET_IPV4); break; case ADDR_IPV6: EnterState(GET5_IPV6); break; case ADDR_DNS : EnterState(GET5_HOST_SIZE); break; default: LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } break; case GET5_IPV6: m_address.ipv6[16-m_parseleft] = *sock_buff; m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; case GET5_HOST_SIZE: EnterState(GET5_HOST, *sock_buff); break; case GET5_HOST: m_address.dns.push_back(*sock_buff); m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; default: LogPrint(eLogError, "SOCKS: parse state?? ", m_state); Terminate(); return false; } sock_buff++; len--; if (m_state == READY) { m_remaining_data_len = len; m_remaining_data = sock_buff; return ValidateSOCKSRequest(); } } return true; } void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "SOCKS: received ", len, " bytes"); if(ecode) { LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode); Terminate(); return; } if (HandleData(m_sock_buff, len)) { if (m_state == READY) { const std::string addr = m_address.dns.ToString(); LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port); const size_t addrlen = addr.size(); // does it end with .i2p? if ( addr.rfind(".i2p") == addrlen - 4) { // yes it does, make an i2p session GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); } else if (m_UseUpstreamProxy) { // forward it to upstream proxy ForwardSOCKS(); } else { // no upstream proxy SocksRequestFailed(SOCKS5_ADDR_UNSUP); } } else AsyncSockRead(); } } void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ()); Terminate(); } void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) { if (!ecode) { if (Kill()) return; LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); Done(shared_from_this()); } else { LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode) { if (ecode) { LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { m_stream = stream; SocksRequestSuccess(); } else { LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info"); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } void SOCKSHandler::ForwardSOCKS() { LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); EnterState(UPSTREAM_RESOLVE); boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort)); m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void SOCKSHandler::AsyncUpstreamSockRead() { LogPrint(eLogDebug, "SOCKS: async upstream sock read"); if (m_upstreamSock) { m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "SOCKS: no upstream socket for read"); SocksRequestFailed(SOCKS5_GEN_FAIL); } } void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered) { if (ecode) { if (m_state == UPSTREAM_HANDSHAKE ) { // we are trying to handshake but it failed SocksRequestFailed(SOCKS5_NET_UNREACH); } else { LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state); } return; } HandleUpstreamData(m_upstream_response, bytes_transfered); } void SOCKSHandler::SocksUpstreamSuccess() { LogPrint(eLogInfo, "SOCKS: upstream success"); boost::asio::const_buffers_1 response(nullptr, 0); switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogInfo, "SOCKS: v5 connection success"); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port); break; } m_sock->send(response); auto forwarder = std::make_shared(GetOwner(), m_sock, m_upstreamSock); m_upstreamSock = nullptr; m_sock = nullptr; GetOwner()->AddHandler(forwarder); forwarder->Start(); Terminate(); } void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) { if (m_state == UPSTREAM_HANDSHAKE) { m_upstream_response_len += len; // handle handshake data if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { // too small, continue reading AsyncUpstreamSockRead(); } else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { // just right uint8_t resp = m_upstream_response[1]; if (resp == SOCKS4_OK) { // we have connected ! SocksUpstreamSuccess(); } else { // upstream failure LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp); // TODO: runtime error? SocksRequestFailed(SOCKS5_GEN_FAIL); } } else { // too big SocksRequestFailed(SOCKS5_GEN_FAIL); } } else { // invalid state LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state); } } void SOCKSHandler::SendUpstreamRequest() { LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); if (m_upstreamSock) { boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); AsyncUpstreamSockRead(); } else { LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to"); } } void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } LogPrint(eLogInfo, "SOCKS: connected to upstream proxy"); SendUpstreamRequest(); } void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { // error resolving LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } LogPrint(eLogInfo, "SOCKS: upstream proxy resolved"); EnterState(UPSTREAM_CONNECT); auto & service = GetOwner()->GetService(); m_upstreamSock = std::make_shared(service); boost::asio::async_connect(*m_upstreamSock, itr, std::bind(&SOCKSHandler::HandleUpstreamConnected, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) { m_UseUpstreamProxy = false; if (outAddress.length() > 0 && outEnable) SetUpstreamProxy(outAddress, outPort); } std::shared_ptr SOCKSServer::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy); } void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port) { m_UpstreamProxyAddress = addr; m_UpstreamProxyPort = port; m_UseUpstreamProxy = true; } } } i2pd-2.39.0/libi2pd_client/SOCKS.h000066400000000000000000000022021411072525600163600ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 SOCKS_H__ #define SOCKS_H__ #include #include #include #include #include "I2PService.h" namespace i2p { namespace proxy { class SOCKSServer: public i2p::client::TCPIPAcceptor { public: SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; void SetUpstreamProxy(const std::string & addr, const uint16_t port); protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: std::string m_Name; std::string m_UpstreamProxyAddress; uint16_t m_UpstreamProxyPort; bool m_UseUpstreamProxy; }; typedef SOCKSServer SOCKSProxy; } } #endif i2pd-2.39.0/libi2pd_wrapper/000077500000000000000000000000001411072525600155335ustar00rootroot00000000000000i2pd-2.39.0/libi2pd_wrapper/api.go000066400000000000000000000010751411072525600166360ustar00rootroot00000000000000package api /* * Copyright (c) 2013-2020, 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 */ /* #cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes #cgo LDFLAGS: -L${SRCDIR}/../ -l:libi2pd.a -l:libi2pdlang.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ */ import "C" i2pd-2.39.0/libi2pd_wrapper/api.swigcxx000066400000000000000000000001741411072525600177240ustar00rootroot00000000000000// See swig.org for more inteface options, // e.g. map std::string to Go string %{ #include "capi.h" %} %include "capi.h" i2pd-2.39.0/libi2pd_wrapper/capi.cpp000066400000000000000000000042421411072525600171550ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 "api.h" #include "capi.h" #include #include #include #include // Uses the example from: https://stackoverflow.com/a/9210560 // See also https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c/9210560# // Does not handle consecutive delimiters, this is only for passing // lists of arguments by value to InitI2P from C_InitI2P char** str_split(char* a_str, const char a_delim) { char** result = 0; size_t count = 0; char* tmp = a_str; char* last_comma = 0; char delim[2]; delim[0] = a_delim; delim[1] = 0; /* Count how many elements will be extracted. */ while (*tmp) { if (a_delim == *tmp) { count++; last_comma = tmp; } tmp++; } /* Add space for trailing token. */ count += last_comma < (a_str + strlen(a_str) - 1); /* Add space for terminating null string so caller knows where the list of returned strings ends. */ count++; result = (char**) malloc(sizeof(char*) * count); if (result) { size_t idx = 0; char* token = strtok(a_str, delim); while (token) { assert(idx < count); *(result + idx++) = strdup(token); token = strtok(0, delim); } assert(idx == count - 1); *(result + idx) = 0; } return result; } #ifdef __cplusplus extern "C" { #endif void C_InitI2P (int argc, char argv[], const char * appName) { const char* delim = " "; char* vargs = strdup(argv); char** args = str_split(vargs, *delim); std::cout << argv; return i2p::api::InitI2P(argc, args, appName); } void C_TerminateI2P () { return i2p::api::TerminateI2P(); } void C_StartI2P () { std::shared_ptr logStream; return i2p::api::StartI2P(logStream); } void C_StopI2P () { return i2p::api::StopI2P(); } void C_RunPeerTest () { return i2p::api::RunPeerTest(); } #ifdef __cplusplus } #endif i2pd-2.39.0/libi2pd_wrapper/capi.h000066400000000000000000000012361411072525600166220ustar00rootroot00000000000000/* * Copyright (c) 2013-2020, 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 CAPI_H__ #define CAPI_H__ #ifdef __cplusplus extern "C" { #endif // initialization start and stop void C_InitI2P (int argc, char argv[], const char * appName); //void C_InitI2P (int argc, char** argv, const char * appName); void C_TerminateI2P (); void C_StartI2P (); // write system log to logStream, if not specified to .log in application's folder void C_StopI2P (); void C_RunPeerTest (); // should be called after UPnP #ifdef __cplusplus } #endif #endif i2pd-2.39.0/tests/000077500000000000000000000000001411072525600136105ustar00rootroot00000000000000i2pd-2.39.0/tests/Makefile000066400000000000000000000035171411072525600152560ustar00rootroot00000000000000CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator all: $(TESTS) run test-http-%: ../libi2pd/HTTP.cpp test-http-%.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ test-base-%: ../libi2pd/Base.cpp test-base-%.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ test-gost: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp test-gost.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto test-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/Crypto.cpp test-x25519.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system run: $(TESTS) @for TEST in $(TESTS); do ./$$TEST ; done clean: rm -f $(TESTS) i2pd-2.39.0/tests/test-aeadchacha20poly1305.cpp000066400000000000000000000045271411072525600207020ustar00rootroot00000000000000#include #include #include #include "Crypto.h" char text[] = "Ladies and Gentlemen of the class of '99: If I could offer you " "only one tip for the future, sunscreen would be it."; // 114 bytes uint8_t key[32] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f }; uint8_t ad[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; uint8_t nonce[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 }; uint8_t tag[16] = { 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 }; uint8_t encrypted[114] = { 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, 0x61, 0x16 }; int main () { uint8_t buf[114+16]; // test encryption i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true); assert (memcmp (buf, encrypted, 114) == 0); assert (memcmp (buf + 114, tag, 16) == 0); // test decryption uint8_t buf1[114]; assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false)); assert (memcmp (buf1, text, 114) == 0); // test encryption of multiple buffers memcpy (buf, text, 114); std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); assert (memcmp (buf1, text, 114) == 0); } i2pd-2.39.0/tests/test-base-64.cpp000066400000000000000000000026671411072525600164450ustar00rootroot00000000000000#include #include #include "Base.h" using namespace i2p::data; int main() { const char *in = "test"; size_t in_len = strlen(in); char out[16]; /* bytes -> b64 */ assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); assert(Base64EncodingBufferSize(2) == 4); assert(Base64EncodingBufferSize(4) == 8); assert(Base64EncodingBufferSize(6) == 8); assert(Base64EncodingBufferSize(7) == 12); assert(Base64EncodingBufferSize(9) == 12); assert(Base64EncodingBufferSize(10) == 16); assert(Base64EncodingBufferSize(12) == 16); assert(Base64EncodingBufferSize(13) == 20); assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); assert(memcmp(out, "dGVzdA==", 8) == 0); /* b64 -> bytes */ assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); in = "dGVzdA=="; /* valid b64 */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); assert(memcmp(out, "test", 4) == 0); in = "dGVzdA="; /* invalid b64 : not padded */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); in = "dG/z.A=="; /* invalid b64 : char not from alphabet */ // assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); // ^^^ fails, current implementation not checks acceptable symbols return 0; } i2pd-2.39.0/tests/test-blinding.cpp000066400000000000000000000026451411072525600170660ustar00rootroot00000000000000#include #include #include #include "Blinding.h" #include "Identity.h" #include "Timestamp.h" using namespace i2p::data; using namespace i2p::util; using namespace i2p::crypto; void BlindTest (SigningKeyType sigType) { auto keys = PrivateKeys::CreateRandomKeys (sigType); BlindedPublicKey blindedKey (keys.GetPublic ()); auto timestamp = GetSecondsSinceEpoch (); char date[9]; GetDateString (timestamp, date); uint8_t blindedPriv[64], blindedPub[128]; auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); uint8_t blindedPub1[128]; blindedKey.GetBlindedKey (date, blindedPub1); // check if public key produced from private blinded key matches blided public key assert (!memcmp (blindedPub, blindedPub1, publicKeyLen)); // try to sign and verify std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv)); uint8_t buf[100], signature[128]; memset (buf, 1, 100); blindedSigner->Sign (buf, 100, signature); std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (sigType)); blindedVerifier->SetPublicKey (blindedPub1); assert (blindedVerifier->Verify (buf, 100, signature)); } int main () { // RedDSA test BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); // P256 test BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // P384 test BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384); } i2pd-2.39.0/tests/test-elligator.cpp000066400000000000000000000055511411072525600172610ustar00rootroot00000000000000#include #include #include #include "Elligator.h" const uint8_t key[32] = { 0x33, 0x95, 0x19, 0x64, 0x00, 0x3c, 0x94, 0x08, 0x78, 0x06, 0x3c, 0xcf, 0xd0, 0x34, 0x8a, 0xf4, 0x21, 0x50, 0xca, 0x16, 0xd2, 0x64, 0x6f, 0x2c, 0x58, 0x56, 0xe8, 0x33, 0x83, 0x77, 0xd8, 0x80 }; const uint8_t encoded_key[32] = { 0x28, 0x20, 0xb6, 0xb2, 0x41, 0xe0, 0xf6, 0x8a, 0x6c, 0x4a, 0x7f, 0xee, 0x3d, 0x97, 0x82, 0x28, 0xef, 0x3a, 0xe4, 0x55, 0x33, 0xcd, 0x41, 0x0a, 0xa9, 0x1a, 0x41, 0x53, 0x31, 0xd8, 0x61, 0x2d }; const uint8_t encoded_key_high_y[32] = { 0x3c, 0xfb, 0x87, 0xc4, 0x6c, 0x0b, 0x45, 0x75, 0xca, 0x81, 0x75, 0xe0, 0xed, 0x1c, 0x0a, 0xe9, 0xda, 0xe7, 0x9d, 0xb7, 0x8d, 0xf8, 0x69, 0x97, 0xc4, 0x84, 0x7b, 0x9f, 0x20, 0xb2, 0x77, 0x18 }; const uint8_t encoded1[32] = { 0xe7, 0x35, 0x07, 0xd3, 0x8b, 0xae, 0x63, 0x99, 0x2b, 0x3f, 0x57, 0xaa, 0xc4, 0x8c, 0x0a, 0xbc, 0x14, 0x50, 0x95, 0x89, 0x28, 0x84, 0x57, 0x99, 0x5a, 0x2b, 0x4c, 0xa3, 0x49, 0x0a, 0xa2, 0x07 }; const uint8_t key1[32] = { 0x1e, 0x8a, 0xff, 0xfe, 0xd6, 0xbf, 0x53, 0xfe, 0x27, 0x1a, 0xd5, 0x72, 0x47, 0x32, 0x62, 0xde, 0xd8, 0xfa, 0xec, 0x68, 0xe5, 0xe6, 0x7e, 0xf4, 0x5e, 0xbb, 0x82, 0xee, 0xba, 0x52, 0x60, 0x4f }; const uint8_t encoded2[32] = { 0x95, 0xa1, 0x60, 0x19, 0x04, 0x1d, 0xbe, 0xfe, 0xd9, 0x83, 0x20, 0x48, 0xed, 0xe1, 0x19, 0x28, 0xd9, 0x03, 0x65, 0xf2, 0x4a, 0x38, 0xaa, 0x7a, 0xef, 0x1b, 0x97, 0xe2, 0x39, 0x54, 0x10, 0x1b }; const uint8_t key2[32] = { 0x79, 0x4f, 0x05, 0xba, 0x3e, 0x3a, 0x72, 0x95, 0x80, 0x22, 0x46, 0x8c, 0x88, 0x98, 0x1e, 0x0b, 0xe5, 0x78, 0x2b, 0xe1, 0xe1, 0x14, 0x5c, 0xe2, 0xc3, 0xc6, 0xfd, 0xe1, 0x6d, 0xed, 0x53, 0x63 }; const uint8_t encoded3[32] = { 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f }; const uint8_t key3[32] = { 0x9c, 0xdb, 0x52, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; const uint8_t failed_key[32] = { 0xe6, 0xf6, 0x6f, 0xdf, 0x6e, 0x23, 0x0c, 0x60, 0x3c, 0x5e, 0x6e, 0x59, 0xa2, 0x54, 0xea, 0x14, 0x76, 0xa1, 0x3e, 0xb9, 0x51, 0x1b, 0x95, 0x49, 0x84, 0x67, 0x81, 0xe1, 0x2e, 0x52, 0x23, 0x0a }; int main () { uint8_t buf[32]; i2p::crypto::Elligator2 el; // encoding tests el.Encode (key, buf, false, false); assert(memcmp (buf, encoded_key, 32) == 0); el.Encode (key, buf, true, false); // with highY assert(memcmp (buf, encoded_key_high_y, 32) == 0); // decoding tests el.Decode (encoded1, buf); assert(memcmp (buf, key1, 32) == 0); el.Decode (encoded2, buf); assert(memcmp (buf, key2, 32) == 0); el.Decode (encoded3, buf); assert(memcmp (buf, key3, 32) == 0); // encoding fails assert (!el.Encode (failed_key, buf)); } i2pd-2.39.0/tests/test-gost-sig.cpp000066400000000000000000000024601411072525600170270ustar00rootroot00000000000000#include #include #include #include "Gost.h" #include "Signature.h" const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; int main () { uint8_t priv[64], pub[128], signature[128]; i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub); i2p::crypto::GOSTR3410_512_Signer signer (i2p::crypto::eGOSTR3410TC26A512, priv); signer.Sign (example2, 72, signature); i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512); verifier.SetPublicKey (pub); assert (verifier.Verify (example2, 72, signature)); i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub); i2p::crypto::GOSTR3410_256_Signer signer1 (i2p::crypto::eGOSTR3410CryptoProA, priv); signer1.Sign (example2, 72, signature); i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA); verifier1.SetPublicKey (pub); assert (verifier1.Verify (example2, 72, signature)); } i2pd-2.39.0/tests/test-gost.cpp000066400000000000000000000046621411072525600162550ustar00rootroot00000000000000#include #include #include #include "Gost.h" const uint8_t example1[63] = { 0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37, 0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31, 0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35, 0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30 }; const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; const uint8_t example1_hash_512[64] = { 0x48,0x6f,0x64,0xc1,0x91,0x78,0x79,0x41,0x7f,0xef,0x08,0x2b,0x33,0x81,0xa4,0xe2, 0x11,0xc3,0x24,0xf0,0x74,0x65,0x4c,0x38,0x82,0x3a,0x7b,0x76,0xf8,0x30,0xad,0x00, 0xfa,0x1f,0xba,0xe4,0x2b,0x12,0x85,0xc0,0x35,0x2f,0x22,0x75,0x24,0xbc,0x9a,0xb1, 0x62,0x54,0x28,0x8d,0xd6,0x86,0x3d,0xcc,0xd5,0xb9,0xf5,0x4a,0x1a,0xd0,0x54,0x1b }; const uint8_t example1_hash_256[32] = { 0x00,0x55,0x7b,0xe5,0xe5,0x84,0xfd,0x52,0xa4,0x49,0xb1,0x6b,0x02,0x51,0xd0,0x5d, 0x27,0xf9,0x4a,0xb7,0x6c,0xba,0xa6,0xda,0x89,0x0b,0x59,0xd8,0xef,0x1e,0x15,0x9d }; const uint8_t example2_hash_512[64] = { 0x28,0xfb,0xc9,0xba,0xda,0x03,0x3b,0x14,0x60,0x64,0x2b,0xdc,0xdd,0xb9,0x0c,0x3f, 0xb3,0xe5,0x6c,0x49,0x7c,0xcd,0x0f,0x62,0xb8,0xa2,0xad,0x49,0x35,0xe8,0x5f,0x03, 0x76,0x13,0x96,0x6d,0xe4,0xee,0x00,0x53,0x1a,0xe6,0x0f,0x3b,0x5a,0x47,0xf8,0xda, 0xe0,0x69,0x15,0xd5,0xf2,0xf1,0x94,0x99,0x6f,0xca,0xbf,0x26,0x22,0xe6,0x88,0x1e }; const uint8_t example2_hash_256[32] = { 0x50,0x8f,0x7e,0x55,0x3c,0x06,0x50,0x1d,0x74,0x9a,0x66,0xfc,0x28,0xc6,0xca,0xc0, 0xb0,0x05,0x74,0x6d,0x97,0x53,0x7f,0xa8,0x5d,0x9e,0x40,0x90,0x4e,0xfe,0xd2,0x9d }; int main () { uint8_t digest[64]; i2p::crypto::GOSTR3411_2012_512 (example1, 63, digest); assert(memcmp (digest, example1_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example1, 63, digest); assert(memcmp (digest, example1_hash_256, 32) == 0); i2p::crypto::GOSTR3411_2012_512 (example2, 72, digest); assert(memcmp (digest, example2_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example2, 72, digest); assert(memcmp (digest, example2_hash_256, 32) == 0); } i2pd-2.39.0/tests/test-http-merge_chunked.cpp000066400000000000000000000006351411072525600210520ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { const char *buf = "4\r\n" "HTTP\r\n" "A\r\n" " response \r\n" "E\r\n" "with \r\n" "chunks.\r\n" "0\r\n" "\r\n" ; std::stringstream in(buf); std::stringstream out; assert(MergeChunkedResponse(in, out) == true); assert(out.str() == "HTTP response with \r\nchunks."); return 0; } i2pd-2.39.0/tests/test-http-req.cpp000066400000000000000000000044611411072525600170420ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { HTTPReq *req; int ret = 0, len = 0; const char *buf; /* test: parsing request with body */ buf = "GET / HTTP/1.0\r\n" "User-Agent: curl/7.26.0\r\n" "Host: inr.i2p\r\n" "Accept: */*\r\n" "\r\n" "test"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len - 4); assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); assert(req->headers.count("User-Agent") == 1); assert(req->headers.find("Host")->second == "inr.i2p"); assert(req->headers.find("Accept")->second == "*/*"); assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.0\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len); assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); assert(req->headers.size() == 0); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.1\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) > 0); delete req; /* test: parsing incomplete request */ buf = "GET / HTTP/1.0\r\n" ""; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == 0); /* request not completed */ delete req; /* test: parsing slightly malformed request */ buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" "Accept-Encoding: \r\n" "Accept: */*\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); assert(req->headers.count("Accept-Encoding") == 1); assert(req->headers["Host"] == "stats.i2p"); assert(req->headers["Accept"] == "*/*"); assert(req->headers["Accept-Encoding"] == ""); delete req; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.39.0/tests/test-http-res.cpp000066400000000000000000000025231411072525600170410ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { HTTPRes *res; int ret = 0, len = 0; const char *buf; /* test: parsing valid response without body */ buf = "HTTP/1.1 304 Not Modified\r\n" "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" "Server: nginx/1.2.1\r\n" "Content-Length: 536\r\n" "\r\n"; len = strlen(buf); res = new HTTPRes; assert((ret = res->parse(buf, len)) == len); assert(res->version == "HTTP/1.1"); assert(res->status == "Not Modified"); assert(res->code == 304); assert(res->headers.size() == 3); assert(res->headers.count("Date") == 1); assert(res->headers.count("Server") == 1); assert(res->headers.count("Content-Length") == 1); assert(res->headers.find("Date")->second == "Thu, 14 Apr 2016 00:00:00 GMT"); assert(res->headers.find("Server")->second == "nginx/1.2.1"); assert(res->headers.find("Content-Length")->second == "536"); assert(res->is_chunked() == false); assert(res->content_length() == 536); delete res; /* test: building request */ buf = "HTTP/1.0 304 Not Modified\r\n" "Content-Length: 0\r\n" "\r\n"; res = new HTTPRes; res->version = "HTTP/1.0"; res->code = 304; res->status = "Not Modified"; res->add_header("Content-Length", "0"); assert(res->to_string() == buf); return 0; } /* vim: expandtab:ts=2 */ i2pd-2.39.0/tests/test-http-url.cpp000066400000000000000000000070321411072525600170520ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { std::map params; URL *url; url = new URL; assert(url->parse("https://127.0.0.1:7070/asdasd?12345") == true); assert(url->schema == "https"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "127.0.0.1"); assert(url->port == 7070); assert(url->path == "/asdasd"); assert(url->query == "12345"); assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); delete url; url = new URL; assert(url->parse("http://user:password@site.com:8080/asdasd?123456") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); assert(url->query == "123456"); delete url; url = new URL; assert(url->parse("http://user:password@site.com/asdasd?name=value") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name=value"); delete url; url = new URL; assert(url->parse("http://user:@site.com/asdasd?name=value1&name=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name=value1&name=value2"); delete url; url = new URL; assert(url->parse("http://user@site.com/asdasd?name1=value1&name2&name3=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name1=value1&name2&name3=value2"); assert(url->parse_query(params)); assert(params.size() == 3); assert(params.count("name1") == 1); assert(params.count("name2") == 1); assert(params.count("name3") == 1); assert(params.find("name1")->second == "value1"); assert(params.find("name2")->second == ""); assert(params.find("name3")->second == "value2"); delete url; url = new URL; assert(url->parse("http://@site.com:800/asdasd?") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://@site.com:17") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:err_port/asdasd") == false); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:84/asdasd/@17#frag") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 84); assert(url->path == "/asdasd/@17"); assert(url->query == ""); assert(url->frag == "frag"); delete url; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.39.0/tests/test-http-url_decode.cpp000066400000000000000000000006541411072525600203600ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); std::string out = UrlDecode(in); assert(strcmp(out.c_str(), "/страница/") == 0); in = "/%00/"; out = UrlDecode(in, false); assert(strcmp(out.c_str(), "/%00/") == 0); out = UrlDecode(in, true); assert(strcmp(out.c_str(), "/\0/") == 0); return 0; } i2pd-2.39.0/tests/test-x25519.cpp000066400000000000000000000020041411072525600161420ustar00rootroot00000000000000#include #include #include #include "Ed25519.h" const uint8_t k[32] = { 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4 }; const uint8_t u[32] = { 0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c }; uint8_t p[32] = { 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52 }; int main () { #if !OPENSSL_X25519 // we test it for openssl < 1.1.0 uint8_t buf[32]; BN_CTX * ctx = BN_CTX_new (); i2p::crypto::GetEd25519 ()->ScalarMul (u, k, buf, ctx); BN_CTX_free (ctx); assert(memcmp (buf, p, 32) == 0); #endif }