Skip to content
Browse files

Added support for "noise" over I1P/Tor to mask Tx transmission.

  • Loading branch information...
vtnerd committed May 16, 2019
1 parent 3049137 commit 3eb8281460816572ba22b766dff3713d3f970bb7
@@ -160,25 +160,6 @@ the system clock is noticeably off (and therefore more fingerprintable),
linking the public IPv4/IPv6 connections with the anonymity networks will be
more difficult.

### Bandwidth Usage

An ISP can passively monitor `monerod` connections from a node and observe when
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 I2P for broadcasting Monero transactions, the total aggregate of
I2P data would also leak information.

#### Mitigation

There is no current mitigation for the user right now. This attack is fairly
sophisticated, and likely requires support from the internet host of a Monero

In the near future, "whitening" the amount of data sent over anonymity network
connections will be performed. An attempt will be made to make a transaction
broadcast indistinguishable from a peer timed sync command.

### Intermittent Monero Syncing

If a user only runs `monerod` to send a transaction then quit, this can also
@@ -208,3 +189,36 @@ 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/I2P user.

### I2P/Tor Stream Used Twice

If a single I2P/Tor stream is used 2+ times for transmitting a transaction, the
operator of the hidden service can conclude that both transactions came from the
same source. If the subsequent transactions spend a change output from the
earlier transactions, this will also reveal the "real" spend in the ring
signature. This issue was (primarily) raised by @secparam on Twitter.

#### Mitigation

`monerod` currently selects two outgoing connections every 5 minutes for
transmitting transactions over I2P/Tor. Using outgoing connections prevents an
adversary from making many incoming connections to obtain information (this
technique was taken from Dandelion). Outgoing connections also do not have a
persistent public key identity - the creation of a new circuit will generate
a new public key identity. The lock time on a change address is ~20 minutes, so
`monerod` will have rotated its selected outgoing connections several times in
most cases. However, the number of outgoing connections is typically a small
fixed number, so there is a decent probability of re-use with the same public
key identity.

@secparam (twitter) recommended changing circuits (Tor) as an additional
precaution. This is likely not a good idea - forcibly requesting Tor to change
circuits is observable by the ISP. Instead, `monerod` should likely disconnect
from peers ocassionally. Tor will rotate circuits every ~10 minutes, so
establishing new connections will use a new public key identity and make it
more difficult for the hidden service to link information. This process will
have to be done carefully because closing/reconnecting connections can also
leak information to hidden services if done improperly.

At the current time, if users need to frequently make transactions, I2P/Tor
will improve privacy from ISPs and other common adversaries, but still have
some metadata leakages to unknown hidden service operators.
@@ -0,0 +1,165 @@
# Levin Protocol
This is a document explaining the current design of the levin protocol, as
used by Monero. The protocol is largely inherited from cryptonote, but has
undergone some changes.

This document also may differ from the `struct bucket_head2` in Monero's
code slightly - the spec here is slightly more strict to allow for

One of the goals of this document is to clearly indicate what is being sent
"on the wire" to identify metadata that could de-anonymize users over I2P/Tor.
These issues will be addressed as they are found. See `` in
the top-level folder for any outstanding issues.

> This document does not currently list all data being sent by the monero
> protocol, that portion is a work-in-progress. Please take the time to do it
> if interested in learning about Monero p2p traffic!

## Header
This header is sent for every Monero p2p message.

0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
| 0x01 | 0x21 | 0x01 | 0x01 |
| 0x01 | 0x01 | 0x01 | 0x01 |
| Length |
| |
| E. Response | Command
| Return Code
|Q|S|B|E| Reserved
| 0x01 | 0x00 | 0x00 |
| 0x00 |

### Signature
The first 8 bytes are the "signature" which helps identify the protocol (in
case someone connected to the wrong port, etc). The comments indicate that byte
sequence is from "benders nightmare".

This also can be used by deep packet inspection (DPI) engines to identify
Monero when the link is not encrypted. SSL has been proposed as a means to
mitigate this issue, but BIP-151 or the Noise protocol should also be considered.

### Length
The length is an unsigned 64-bit little endian integer. The length does _not_
include the header.

The implementation currently rejects received messages that exceed 100 MB
(base 10) by default.

### Expect Response
A zero-byte if no response is expected from the peer, and a non-zero byte if a
response is expected from the peer. Peers must respond to requests with this
flag in the same order that they were received, however, other messages can be
sent between responses.

There are some commands in the
[cryptonote protocol](#cryptonote-protocol-commands) where a response is
expected from the peer, but this flag is not set. Those responses are returned
as notify messages and can be sent in any order by the peer.

### Command
An unsigned 32-bit little endian integer representing the Monero specific
command being invoked.

### Return Code
A signed 32-bit little integer integer representing the response from the peer
from the last command that was invoked. This is `0` for request messages.

### Flags
* `Q` - Bit is set if the message is a request.
* `S` - Bit is set if the message is a response.
* `B` - Bit is set if this is a the beginning of a [fragmented message](#fragmented-messages).
* `E` - Bit is set if this is the end of a [fragmented message](#fragmented-messages).

### Version
A fixed value of `1` as an unsigned 32-bit little endian integer.

## Message Flow
The protocol can be subdivided into: (1) notifications, (2) requests,
(3) responses, (4) fragmented messages, and (5) dummy messages. Response
messages must be sent in the same order that a peer issued a request message.
A peer does not have to send a response immediately following a request - any
other message type can be sent instead.

### Notifications
Notifications are one-way messages that can be sent at any time without
an expectation of a response from the peer. The `Q` bit must be set, the `S`,
`B` and `E` bits must be unset, and the `Expect Response` field must be zeroed.

Some notifications must be in response to other notifications. This is not
part of the levin messaging layer, and is described in the
[commands](#commands) section.

### Requests
Requests are the basis of the admin protocol for Monero. The `Q` bit must be
set, the `S`, `B` and `E` bits must be unset, and the `Expect Response` field
must be non-zero. The peer is expected to send a response message with the same
`command` number.

### Responses
Response message can only be sent after a peer first issues a request message.
Responses must have the `S` bit set, the `Q`, `B` and `E` bits unset, and have
a zeroed `Expect Response` field. The `Command` field must be the same value
that was sent in the request message. The `Return Code` is specific to the
`Command` being issued (see [commands])(#commands)).

### Fragmented
Fragmented messages were introduced for the "white noise" feature for i2p/tor.
A transaction can be sent in fragments to conceal when "real" data is being
sent instead of dummy messages. Only one fragmented message can be sent at a
time, and bits `B` and `E` are never set at the same time
(see [dummy messages](#dummy)). The re-constructed message must contain a
levin header for a different (non-fragment) message type.

The `Q` and `S` bits are never set and the `Expect Response` field must always
be zero. The first fragment has the `B` bit set, neither `B` nor `E` is set for
"middle" fragments, and `E` is set for the last fragment.

### Dummy
Dummy messages have the `B` and `E` bits set, the `Q` and `S` bits unset, and
the `Expect Reponse` field zeroed. When a message of this type is received, the
contents can be safely ignored.

## Commands
### P2P (Admin) Commands

#### (`1001` Request) Handshake
#### (`1001` Response) Handshake
#### (`1002` Request) Timed Sync
#### (`1002` Response) Timed Sync
#### (`1003` Request) Ping
#### (`1003` Response) Ping
#### (`1004` Request) Stat Info
#### (`1004` Response) Stat Info
#### (`1005` Request) Network State
#### (`1005` Response) Network State
#### (`1006` Request) Peer ID
#### (`1006` Reponse) Peer ID
#### (`1007` Request) Support Flags
#### (`1007` Response) Support Flags

### Cryptonote Protocol Commands

#### (`2001` Notification) New Block
#### (`2002` Notification) New Transactions
#### (`2003` Notification) Request Get Objects
#### (`2004` Notification) Response Get Objects
#### (`2006` Notification) Request Chain
#### (`2007` Notification) Response Chain Entry
#### (`2008` Notification) New Fluffy Block
#### (`2009` Notification) Request Fluffy Missing TX
@@ -49,10 +49,11 @@
#include <boost/asio/ssl.hpp>
#include <boost/array.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/shared_ptr.hpp> //! \TODO Convert to std::shared_ptr
#include <boost/enable_shared_from_this.hpp>
#include <boost/interprocess/detail/atomic.hpp>
#include <boost/thread/thread.hpp>
#include <memory>
#include "byte_slice.h"
#include "net_utils_base.h"
#include "syncobj.h"
@@ -91,25 +92,24 @@ namespace net_utils
typedef typename t_protocol_handler::connection_context t_connection_context;

struct shared_state : connection_basic_shared_state
struct shared_state : connection_basic_shared_state, t_protocol_handler::config_type
: connection_basic_shared_state(), pfilter(nullptr), config(), stop_signal_sent(false)
: connection_basic_shared_state(), t_protocol_handler::config_type(), pfilter(nullptr), stop_signal_sent(false)

i_connection_filter* pfilter;
typename t_protocol_handler::config_type config;
bool stop_signal_sent;

/// Construct a connection with the given io_service.
explicit connection( boost::asio::io_service& io_service,
boost::shared_ptr<shared_state> state,
std::shared_ptr<shared_state> state,
t_connection_type connection_type,
epee::net_utils::ssl_support_t ssl_support);

explicit connection( boost::asio::ip::tcp::socket&& sock,
boost::shared_ptr<shared_state> state,
std::shared_ptr<shared_state> state,
t_connection_type connection_type,
epee::net_utils::ssl_support_t ssl_support);

@@ -267,7 +267,13 @@ namespace net_utils
typename t_protocol_handler::config_type& get_config_object()
assert(m_state != nullptr); // always set in constructor
return m_state->config;
return *m_state;

std::shared_ptr<typename t_protocol_handler::config_type> get_config_shared()
assert(m_state != nullptr); // always set in constructor
return {m_state};

int get_binded_port(){return m_port;}
@@ -345,7 +351,7 @@ namespace net_utils

bool is_thread_worker();

const boost::shared_ptr<typename connection<t_protocol_handler>::shared_state> m_state;
const std::shared_ptr<typename connection<t_protocol_handler>::shared_state> m_state;

/// The io_service used to perform asynchronous operations.
struct worker
@@ -68,7 +68,7 @@ namespace epee
namespace net_utils
template<typename T>
T& check_and_get(boost::shared_ptr<T>& ptr)
T& check_and_get(std::shared_ptr<T>& ptr)
CHECK_AND_ASSERT_THROW_MES(bool(ptr), "shared_state cannot be null");
return *ptr;
@@ -81,7 +81,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)

template<class t_protocol_handler>
connection<t_protocol_handler>::connection( boost::asio::io_service& io_service,
boost::shared_ptr<shared_state> state,
std::shared_ptr<shared_state> state,
t_connection_type connection_type,
ssl_support_t ssl_support
@@ -91,13 +91,13 @@ PRAGMA_WARNING_DISABLE_VS(4355)

template<class t_protocol_handler>
connection<t_protocol_handler>::connection( boost::asio::ip::tcp::socket&& sock,
boost::shared_ptr<shared_state> state,
std::shared_ptr<shared_state> state,
t_connection_type connection_type,
ssl_support_t ssl_support
connection_basic(std::move(sock), state, ssl_support),
m_protocol_handler(this, check_and_get(state).config, context),
m_protocol_handler(this, check_and_get(state), context),
m_connection_type( connection_type ),
m_throttle_speed_in("speed_in", "throttle_speed_in"),
@@ -370,7 +370,6 @@ PRAGMA_WARNING_DISABLE_VS(4355)
//_info("[sock " << socket().native_handle() << "] protocol_want_close");

//some error in protocol, protocol handler ask to close connection
boost::interprocess::ipcdetail::atomic_write32(&m_want_close_connection, 1);
bool do_shutdown = false;
@@ -593,7 +592,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
double current_speed_up;
current_speed_up = m_throttle_speed_out.get_current_speed();
context.m_current_speed_up = current_speed_up;
@@ -657,7 +656,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
auto size_now = m_send_que.front().size();
MDEBUG("do_send_chunk() NOW SENSD: packet="<<size_now<<" B");
if (speed_limit_is_enabled())
do_send_handler_write(, chunk.size() ); // (((H)))
do_send_handler_write( m_send_que.back().data(), m_send_que.back().size() ); // (((H)))

CHECK_AND_ASSERT_MES( size_now == m_send_que.front().size(), false, "Unexpected queue size");
reset_timer(get_default_timeout(), false);
@@ -885,7 +884,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)

template<class t_protocol_handler>
boosted_tcp_server<t_protocol_handler>::boosted_tcp_server( t_connection_type connection_type ) :
m_state(boost::make_shared<typename connection<t_protocol_handler>::shared_state>()),
m_state(std::make_shared<typename connection<t_protocol_handler>::shared_state>()),
m_io_service_local_instance(new worker()),
@@ -902,7 +901,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)

template<class t_protocol_handler>
boosted_tcp_server<t_protocol_handler>::boosted_tcp_server(boost::asio::io_service& extarnal_io_service, t_connection_type connection_type) :
m_state(boost::make_shared<typename connection<t_protocol_handler>::shared_state>()),
m_state(std::make_shared<typename connection<t_protocol_handler>::shared_state>()),
@@ -100,7 +100,7 @@ class connection_basic_pimpl; // PIMPL for this class

class connection_basic { // not-templated base class for rapid developmet of some code parts
// beware of removing const, net_utils::connection is sketchily doing a cast to prevent storing ptr twice
const boost::shared_ptr<connection_basic_shared_state> m_state;
const std::shared_ptr<connection_basic_shared_state> m_state;

std::unique_ptr< connection_basic_pimpl > mI; // my Implementation
@@ -119,8 +119,8 @@ class connection_basic { // not-templated base class for rapid developmet of som

// first counter is the ++/-- count of current sockets, the other socket_number is only-increasing ++ number generator
connection_basic(boost::asio::ip::tcp::socket&& socket, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support);
connection_basic(boost::asio::io_service &io_service, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support);
connection_basic(boost::asio::ip::tcp::socket&& socket, std::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support);
connection_basic(boost::asio::io_service &io_service, std::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support);

virtual ~connection_basic() noexcept(false);

0 comments on commit 3eb8281

Please sign in to comment.
You can’t perform that action at this time.