Skip to content

Commit

Permalink
Merge pull request #5091
Browse files Browse the repository at this point in the history
123fc2a i2p: initial support (Jethro Grassie)
  • Loading branch information
fluffypony committed Mar 4, 2019
2 parents b0d326b + 123fc2a commit 4466f45
Show file tree
Hide file tree
Showing 14 changed files with 450 additions and 40 deletions.
57 changes: 27 additions & 30 deletions ANONYMITY_NETWORKS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Anonymity Networks with Monero

Currently only Tor has been integrated into Monero. Providing support for
Kovri/I2P should be minimal, but has not yet been attempted. The usage of
Currently only Tor and I2P have been integrated into Monero. The usage of
these networks is still considered experimental - there are a few pessimistic
cases where privacy is leaked. The design is intended to maximize privacy of
the source of a transaction by broadcasting it over an anonymity network, while
Expand All @@ -23,8 +22,8 @@ connections enabled.

## P2P Commands

Only handshakes, peer timed syncs, and transaction broadcast messages are
supported over anonymity networks. If one `--add-exclusive-node` onion address
Only handshakes, peer timed syncs and transaction broadcast messages are
supported over anonymity networks. If one `--add-exclusive-node` p2p address
is specified, then no syncing will take place and only transaction broadcasting
can occur. It is therefore recommended that `--add-exclusive-node` be combined
with additional exclusive IPv4 address(es).
Expand All @@ -47,9 +46,9 @@ separate process. On most systems the configuration will look like:
which tells `monerod` that ".onion" p2p addresses can be forwarded to a socks
proxy at IP 127.0.0.1 port 9050 with a max of 10 outgoing connections and
".i2p" p2p addresses can be forwarded to a socks proxy at IP 127.0.0.1 port 9000
with the default max outgoing connections. Since there are no seed nodes for
anonymity connections, peers must be manually specified:
".b32.i2p" p2p addresses can be forwarded to a socks proxy at IP 127.0.0.1 port
9000 with the default max outgoing connections. Since there are no seed nodes
for anonymity connections, peers must be manually specified:

> `--add-exclusive-node rveahdfho7wo4b2m.onion:28083`
> `--add-peer rveahdfho7wo4b2m.onion:28083`
Expand All @@ -64,27 +63,28 @@ Receiving anonymity connections is done through the option
`--anonymous-inbound`. This option tells `monerod` the inbound address, network
type, and max connections:

> `--anonymous-inbound rveahdfho7wo4b2m.onion:28083,127.0.0.28083,25`
> `--anonymous-inbound foobar.i2p:5000,127.0.0.1:30000`
> `--anonymous-inbound rveahdfho7wo4b2m.onion:28083,127.0.0.1:28083,25`
> `--anonymous-inbound cmeua5767mz2q5jsaelk2rxhf67agrwuetaso5dzbenyzwlbkg2q.b32.i2p:5000,127.0.0.1:30000`
which tells `monerod` that a max of 25 inbound Tor connections are being
received at address "rveahdfho7wo4b2m.onion:28083" and forwarded to `monerod`
localhost port 28083, and a default max I2P connections are being received at
address "foobar.i2p:5000" and forwarded to `monerod` localhost port 30000.
address "cmeua5767mz2q5jsaelk2rxhf67agrwuetaso5dzbenyzwlbkg2q.b32.i2p:5000" and
forwarded to `monerod` localhost port 30000.
These addresses will be shared with outgoing peers, over the same network type,
otherwise the peer will not be notified of the peer address by the proxy.

### Network Types

#### Tor
#### Tor & I2P

Options `--add-exclusive-node` and `--add-peer` recognize ".onion" addresses,
and will properly forward those addresses to the proxy provided with
`--proxy tor,...`.
Options `--add-exclusive-node` and `--add-peer` recognize ".onion" and
".b32.i2p" addresses, and will properly forward those addresses to the proxy
provided with `--proxy tor,...` or `--proxy i2p,...`.

Option `--anonymous-inbound` also recognizes ".onion" addresses, and will
automatically be sent out to outgoing Tor connections so the peer can
distribute the address to its other peers.
Option `--anonymous-inbound` also recognizes ".onion" and ".b32.i2p" addresses,
and will automatically be sent out to outgoing Tor/I2P connections so the peer
can distribute the address to its other peers.

##### Configuration

Expand All @@ -99,11 +99,8 @@ This will store key information in `/var/lib/tor/data/monero` and will forward
`/usr/lib/tor/data/monero/hostname` will contain the ".onion" address for use
with `--anonymous-inbound`.

#### Kovri/I2P

Support for this network has not been implemented. Using ".i2p" addresses or
specifying "i2p" will currently generate an error.

I2P must be configured with a standard server tunnel. Configuration differs by
I2P implementation.

## Privacy Limitations

Expand Down Expand Up @@ -132,11 +129,11 @@ more difficult.
### Bandwidth Usage

An ISP can passively monitor `monerod` connections from a node and observe when
a transaction is sent over a Tor/Kovri connection via timing analysis + size of
data sent during that timeframe. Kovri should provide better protection against
a transaction is sent over a Tor/I2P connection via timing analysis + size of
data sent during that timeframe. I2P should provide better protection against
this attack - its connections are not circuit based. However, if a node is
only using Kovri for broadcasting Monero transactions, the total aggregate of
Kovri/I2P data would also leak information.
only using I2P for broadcasting Monero transactions, the total aggregate of
I2P data would also leak information.

#### Mitigation

Expand Down Expand Up @@ -165,15 +162,15 @@ simply a best effort attempt.
### Active Bandwidth Shaping

An attacker could attempt to bandwidth shape traffic in an attempt to determine
the source of a Tor/Kovri/I2P connection. There isn't great mitigation against
this, but Kovri/I2P should provide better protection against this attack since
the source of a Tor/I2P connection. There isn't great mitigation against
this, but I2P should provide better protection against this attack since
the connections are not circuit based.

#### Mitigation

The best mitigiation is to use Kovri/I2P instead of Tor. However, Kovri/I2P
The best mitigiation is to use I2P instead of Tor. However, I2P
has a smaller set of users (less cover traffic) and academic reviews, so there
is a tradeoff in potential isses. Also, anyone attempting this strategy really
wants to uncover a user, it seems unlikely that this would be performed against
every Tor/Kovri/I2P user.
every Tor/I2P user.

5 changes: 4 additions & 1 deletion contrib/epee/include/net/net_utils_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
namespace net
{
class tor_address;
class i2p_address;
}

namespace epee
Expand Down Expand Up @@ -196,7 +197,7 @@ namespace net_utils
template<typename Type> const Type &as() const { return as_mutable<const Type>(); }

BEGIN_KV_SERIALIZE_MAP()
// need to `#include "net/tor_address.h"` when serializing `network_address`
// need to `#include "net/[i2p|tor]_address.h"` when serializing `network_address`
static constexpr std::integral_constant<bool, is_store> is_store_{};

std::uint8_t type = std::uint8_t(is_store ? this_ref.get_type_id() : address_type::invalid);
Expand All @@ -209,6 +210,8 @@ namespace net_utils
return this_ref.template serialize_addr<ipv4_network_address>(is_store_, stg, hparent_section);
case address_type::tor:
return this_ref.template serialize_addr<net::tor_address>(is_store_, stg, hparent_section);
case address_type::i2p:
return this_ref.template serialize_addr<net::i2p_address>(is_store_, stg, hparent_section);
case address_type::invalid:
default:
break;
Expand Down
2 changes: 1 addition & 1 deletion src/cryptonote_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
#define P2P_DEFAULT_PACKET_MAX_SIZE 50000000 //50000000 bytes maximum packet size
#define P2P_DEFAULT_PEERS_IN_HANDSHAKE 250
#define P2P_DEFAULT_CONNECTION_TIMEOUT 5000 //5 seconds
#define P2P_DEFAULT_TOR_CONNECT_TIMEOUT 20 // seconds
#define P2P_DEFAULT_SOCKS_CONNECT_TIMEOUT 45 // seconds
#define P2P_DEFAULT_PING_CONNECTION_TIMEOUT 2000 //2 seconds
#define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes
#define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds
Expand Down
4 changes: 2 additions & 2 deletions src/net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
# 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.

set(net_sources error.cpp parse.cpp socks.cpp tor_address.cpp)
set(net_headers error.h parse.h socks.h tor_address.h)
set(net_sources error.cpp parse.cpp socks.cpp tor_address.cpp i2p_address.cpp)
set(net_headers error.h parse.h socks.h tor_address.h i2p_address.h)

monero_add_library(net ${net_sources} ${net_headers})
target_link_libraries(net epee ${Boost_ASIO_LIBRARY})
Expand Down
1 change: 1 addition & 0 deletions src/net/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace net
{
enum class error : int;
class tor_address;
class i2p_address;

namespace socks
{
Expand Down
200 changes: 200 additions & 0 deletions src/net/i2p_address.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright (c) 2019, The Monero 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.

#include "i2p_address.h"

#include <algorithm>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <cassert>
#include <cstring>
#include <limits>

#include "net/error.h"
#include "serialization/keyvalue_serialization.h"
#include "storages/portable_storage.h"
#include "string_tools.h"

namespace net
{
namespace
{
// !TODO only b32 addresses right now
constexpr const char tld[] = u8".b32.i2p";
constexpr const char unknown_host[] = "<unknown i2p host>";

constexpr const unsigned b32_length = 52;

constexpr const char base32_alphabet[] =
u8"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz234567";

expect<void> host_check(boost::string_ref host) noexcept
{
if (!host.ends_with(tld))
return {net::error::expected_tld};

host.remove_suffix(sizeof(tld) - 1);

if (host.size() != b32_length)
return {net::error::invalid_i2p_address};
if (host.find_first_not_of(base32_alphabet) != boost::string_ref::npos)
return {net::error::invalid_i2p_address};

return success();
}

struct i2p_serialized
{
std::string host;
std::uint16_t port;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(host)
KV_SERIALIZE(port)
END_KV_SERIALIZE_MAP()
};
}

i2p_address::i2p_address(const boost::string_ref host, const std::uint16_t port) noexcept
: port_(port)
{
// this is a private constructor, throw if moved to public
assert(host.size() < sizeof(host_));

const std::size_t length = std::min(sizeof(host_) - 1, host.size());
std::memcpy(host_, host.data(), length);
std::memset(host_ + length, 0, sizeof(host_) - length);
}

const char* i2p_address::unknown_str() noexcept
{
return unknown_host;
}

i2p_address::i2p_address() noexcept
: port_(0)
{
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host));
std::memset(host_ + sizeof(unknown_host), 0, sizeof(host_) - sizeof(unknown_host));
}

expect<i2p_address> i2p_address::make(const boost::string_ref address, const std::uint16_t default_port)
{
boost::string_ref host = address.substr(0, address.rfind(':'));
const boost::string_ref port =
address.substr(host.size() + (host.size() == address.size() ? 0 : 1));

MONERO_CHECK(host_check(host));

std::uint16_t porti = default_port;
if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port}))
return {net::error::invalid_port};

static_assert(b32_length + sizeof(tld) == sizeof(i2p_address::host_), "bad internal host size");
return i2p_address{host, porti};
}

bool i2p_address::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
{
i2p_serialized in{};
if (in._load(src, hparent) && in.host.size() < sizeof(host_) && (in.host == unknown_host || !host_check(in.host).has_error()))
{
std::memcpy(host_, in.host.data(), in.host.size());
std::memset(host_ + in.host.size(), 0, sizeof(host_) - in.host.size());
port_ = in.port;
return true;
}
static_assert(sizeof(unknown_host) <= sizeof(host_), "bad buffer size");
std::memcpy(host_, unknown_host, sizeof(unknown_host)); // include null terminator
port_ = 0;
return false;
}

bool i2p_address::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
{
const i2p_serialized out{std::string{host_}, port_};
return out.store(dest, hparent);
}

i2p_address::i2p_address(const i2p_address& rhs) noexcept
: port_(rhs.port_)
{
std::memcpy(host_, rhs.host_, sizeof(host_));
}

i2p_address& i2p_address::operator=(const i2p_address& rhs) noexcept
{
if (this != std::addressof(rhs))
{
port_ = rhs.port_;
std::memcpy(host_, rhs.host_, sizeof(host_));
}
return *this;
}

bool i2p_address::is_unknown() const noexcept
{
static_assert(1 <= sizeof(host_), "host size too small");
return host_[0] == '<'; // character is not allowed otherwise
}

bool i2p_address::equal(const i2p_address& rhs) const noexcept
{
return port_ == rhs.port_ && is_same_host(rhs);
}

bool i2p_address::less(const i2p_address& rhs) const noexcept
{
return std::strcmp(host_str(), rhs.host_str()) < 0 || port() < rhs.port();
}

bool i2p_address::is_same_host(const i2p_address& rhs) const noexcept
{
return std::strcmp(host_str(), rhs.host_str()) == 0;
}

std::string i2p_address::str() const
{
const std::size_t host_length = std::strlen(host_str());
const std::size_t port_length =
port_ == 0 ? 0 : std::numeric_limits<std::uint16_t>::digits10 + 2;

std::string out{};
out.reserve(host_length + port_length);
out.assign(host_str(), host_length);

if (port_ != 0)
{
out.push_back(':');
namespace karma = boost::spirit::karma;
karma::generate(std::back_inserter(out), karma::ushort_, port());
}
return out;
}
}
Loading

0 comments on commit 4466f45

Please sign in to comment.