test: add plaintext & encrypted handshake tests (#3455)
This commit is contained in:
parent
46874dff91
commit
fe91f04351
|
@ -135,24 +135,24 @@ struct tr_handshake
|
|||
|
||||
std::shared_ptr<tr_handshake_mediator> const mediator;
|
||||
|
||||
bool haveReadAnythingFromPeer;
|
||||
bool haveSentBitTorrentHandshake;
|
||||
tr_peerIo* io;
|
||||
DH dh;
|
||||
bool haveReadAnythingFromPeer = false;
|
||||
bool haveSentBitTorrentHandshake = false;
|
||||
tr_peerIo* io = nullptr;
|
||||
DH dh = {};
|
||||
handshake_state_t state;
|
||||
tr_encryption_mode encryptionMode;
|
||||
uint16_t pad_c_len;
|
||||
uint16_t pad_d_len;
|
||||
uint16_t ia_len;
|
||||
uint32_t crypto_select;
|
||||
uint32_t crypto_provide;
|
||||
tr_sha1_digest_t myReq1;
|
||||
struct event* timeout_timer;
|
||||
uint16_t pad_c_len = {};
|
||||
uint16_t pad_d_len = {};
|
||||
uint16_t ia_len = {};
|
||||
uint32_t crypto_select = {};
|
||||
uint32_t crypto_provide = {};
|
||||
tr_sha1_digest_t myReq1 = {};
|
||||
struct event* timeout_timer = nullptr;
|
||||
|
||||
std::optional<tr_peer_id_t> peer_id;
|
||||
|
||||
tr_handshake_done_func done_func;
|
||||
void* done_func_user_data;
|
||||
tr_handshake_done_func done_func = nullptr;
|
||||
void* done_func_user_data = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -474,7 +474,7 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
/* cleanup */
|
||||
evbuffer_free(outbuf);
|
||||
return READ_LATER;
|
||||
return READ_NOW;
|
||||
}
|
||||
|
||||
// MSE spec: "Since the length of [PadB is] unknown,
|
||||
|
@ -719,13 +719,15 @@ static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
evbuffer_remove(inbuf, std::data(peer_public_key), std::size(peer_public_key));
|
||||
handshake->dh.setPeerPublicKey(peer_public_key);
|
||||
|
||||
auto req1 = tr_sha1("req1"sv, handshake->dh.secret());
|
||||
if (!req1)
|
||||
if (auto const req1 = tr_sha1("req1"sv, handshake->dh.secret()); req1)
|
||||
{
|
||||
handshake->myReq1 = *req1;
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req1 hash after Ya");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
handshake->myReq1 = *req1;
|
||||
|
||||
// send our public key to the peer
|
||||
tr_logAddTraceHand(handshake, "sending B->A: Diffie Hellman Yb, PadB");
|
||||
|
|
|
@ -98,7 +98,7 @@ void tr_logAddMessage(
|
|||
#define tr_logAddLevel(level, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (tr_logGetLevel() >= level) \
|
||||
if (tr_logLevelIsActive(level)) \
|
||||
{ \
|
||||
tr_logAddMessage(__FILE__, __LINE__, level, __VA_ARGS__); \
|
||||
} \
|
||||
|
|
|
@ -351,17 +351,16 @@ static void maybeSetCongestionAlgorithm(tr_socket_t socket, std::string const& a
|
|||
#ifdef WITH_UTP
|
||||
/* UTP callbacks */
|
||||
|
||||
static void utp_on_read(tr_peerIo* const io, uint8_t const* const buf, size_t const buflen)
|
||||
void tr_peerIo::readBufferAdd(void const* data, size_t n_bytes)
|
||||
{
|
||||
if (auto const rc = evbuffer_add(io->inbuf.get(), buf, buflen); rc < 0)
|
||||
if (auto const rc = evbuffer_add(inbuf.get(), data, n_bytes); rc < 0)
|
||||
{
|
||||
tr_logAddWarn(_("Couldn't write to peer"));
|
||||
return;
|
||||
}
|
||||
|
||||
tr_logAddTraceIo(io, fmt::format("utp_on_read got {} bytes", buflen));
|
||||
tr_peerIoSetEnabled(io, TR_DOWN, true);
|
||||
canReadWrapper(io);
|
||||
tr_peerIoSetEnabled(this, TR_DOWN, true);
|
||||
canReadWrapper(this);
|
||||
}
|
||||
|
||||
static size_t utp_get_rb_size(tr_peerIo* const io)
|
||||
|
@ -467,7 +466,7 @@ static uint64 utp_callback(utp_callback_arguments* args)
|
|||
switch (args->callback_type)
|
||||
{
|
||||
case UTP_ON_READ:
|
||||
utp_on_read(io, args->buf, args->len);
|
||||
io->readBufferAdd(args->buf, args->len);
|
||||
break;
|
||||
|
||||
case UTP_GET_READ_BUFFER_SIZE:
|
||||
|
@ -491,7 +490,7 @@ static uint64 utp_callback(utp_callback_arguments* args)
|
|||
|
||||
#endif /* #ifdef WITH_UTP */
|
||||
|
||||
static tr_peerIo* tr_peerIoNew(
|
||||
tr_peerIo* tr_peerIoNew(
|
||||
tr_session* session,
|
||||
tr_bandwidth* parent,
|
||||
tr_address const* addr,
|
||||
|
@ -504,7 +503,7 @@ static tr_peerIo* tr_peerIoNew(
|
|||
{
|
||||
TR_ASSERT(session != nullptr);
|
||||
TR_ASSERT(session->events != nullptr);
|
||||
TR_ASSERT(tr_amInEventThread(session));
|
||||
auto lock = session->unique_lock();
|
||||
|
||||
#ifdef WITH_UTP
|
||||
TR_ASSERT(socket.type == TR_PEER_SOCKET_TYPE_TCP || socket.type == TR_PEER_SOCKET_TYPE_UTP);
|
||||
|
|
|
@ -112,6 +112,8 @@ public:
|
|||
return inbuf.get();
|
||||
}
|
||||
|
||||
void readBufferAdd(void const* data, size_t n_bytes);
|
||||
|
||||
[[nodiscard]] auto hasBandwidthLeft(tr_direction dir) noexcept
|
||||
{
|
||||
return bandwidth_.clamp(dir, 1024) > 0;
|
||||
|
@ -304,6 +306,19 @@ tr_peerIo* tr_peerIoNewIncoming(
|
|||
time_t current_time,
|
||||
struct tr_peer_socket const socket);
|
||||
|
||||
// this is only public for testing purposes.
|
||||
// production code should use tr_peerIoNewOutgoing() or tr_peerIoNewIncoming()
|
||||
tr_peerIo* tr_peerIoNew(
|
||||
tr_session* session,
|
||||
tr_bandwidth* parent,
|
||||
tr_address const* addr,
|
||||
tr_port port,
|
||||
time_t current_time,
|
||||
tr_sha1_digest_t const* torrent_hash,
|
||||
bool is_incoming,
|
||||
bool is_seed,
|
||||
struct tr_peer_socket const socket);
|
||||
|
||||
void tr_peerIoRefImpl(char const* file, int line, tr_peerIo* io);
|
||||
|
||||
#define tr_peerIoRef(io) tr_peerIoRefImpl(__FILE__, __LINE__, (io))
|
||||
|
|
|
@ -13,18 +13,10 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h" // tr_sha1
|
||||
#include "net.h" // includes the headers for htonl()
|
||||
#include "peer-mse.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
// source: https://stackoverflow.com/a/1001330/6568470
|
||||
// nb: when we bump to std=C++20, use `std::endian`
|
||||
static bool is_big_endian()
|
||||
{
|
||||
return htonl(47) == 47;
|
||||
}
|
||||
|
||||
namespace wi
|
||||
{
|
||||
using key_t = math::wide_integer::uintwide_t<
|
||||
|
@ -37,22 +29,12 @@ template<typename UIntWide>
|
|||
auto import_bits(std::array<std::byte, UIntWide::my_width2 / std::numeric_limits<uint8_t>::digits> const& bigend_bin)
|
||||
{
|
||||
auto ret = UIntWide{};
|
||||
static_assert(sizeof(UIntWide) == sizeof(bigend_bin));
|
||||
|
||||
if (is_big_endian())
|
||||
for (auto const walk : bigend_bin)
|
||||
{
|
||||
for (auto walk = std::rbegin(bigend_bin), end = std::rend(bigend_bin); walk != end; ++walk)
|
||||
{
|
||||
ret <<= 8;
|
||||
ret += static_cast<uint8_t>(*walk);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const walk : bigend_bin)
|
||||
{
|
||||
ret <<= 8;
|
||||
ret += static_cast<uint8_t>(walk);
|
||||
}
|
||||
ret <<= 8;
|
||||
ret += static_cast<uint8_t>(walk);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -63,21 +45,10 @@ auto export_bits(UIntWide i)
|
|||
{
|
||||
auto ret = std::array<std::byte, UIntWide::my_width2 / std::numeric_limits<uint8_t>::digits>{};
|
||||
|
||||
if (is_big_endian())
|
||||
for (auto walk = std::rbegin(ret), end = std::rend(ret); walk != end; ++walk)
|
||||
{
|
||||
for (auto& walk : ret)
|
||||
{
|
||||
walk = std::byte(static_cast<uint8_t>(i & 0xFF));
|
||||
i >>= 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto walk = std::rbegin(ret), end = std::rend(ret); walk != end; ++walk)
|
||||
{
|
||||
*walk = std::byte(static_cast<uint8_t>(i & 0xFF));
|
||||
i >>= 8;
|
||||
}
|
||||
*walk = std::byte(static_cast<uint8_t>(i & 0xFF));
|
||||
i >>= 8;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -227,7 +227,7 @@ using UniqueTimer = std::unique_ptr<struct event, EventDeleter>;
|
|||
#define myLogMacro(msgs, level, text) \
|
||||
do \
|
||||
{ \
|
||||
if (tr_logGetLevel() >= (level)) \
|
||||
if (tr_logLevelIsActive(level)) \
|
||||
{ \
|
||||
tr_logAddMessage( \
|
||||
__FILE__, \
|
||||
|
|
|
@ -14,6 +14,7 @@ add_executable(libtransmission-test
|
|||
file-piece-map-test.cc
|
||||
file-test.cc
|
||||
getopt-test.cc
|
||||
handshake-test.cc
|
||||
history-test.cc
|
||||
json-test.cc
|
||||
magnet-metainfo-test.cc
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
// This file Copyright (C) 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
#include <event2/util.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "handshake.h"
|
||||
#include "peer-io.h"
|
||||
#include "session.h" // tr_peerIdInit()
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define LOCAL_SOCKETPAIR_AF AF_INET
|
||||
#else
|
||||
#define LOCAL_SOCKETPAIR_AF AF_UNIX
|
||||
#endif
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
auto constexpr MaxWaitMsec = int{ 5000 };
|
||||
|
||||
using HandshakeTest = SessionTest;
|
||||
|
||||
class MediatorMock final : public tr_handshake_mediator
|
||||
{
|
||||
public:
|
||||
MediatorMock(tr_session* session)
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<torrent_info> torrentInfo(tr_sha1_digest_t const& info_hash) const override
|
||||
{
|
||||
if (auto const iter = torrents.find(info_hash); iter != std::end(torrents))
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<torrent_info> torrentInfoFromObfuscated(tr_sha1_digest_t const& obfuscated) const override
|
||||
{
|
||||
for (auto const& [info_hash, info] : torrents)
|
||||
{
|
||||
if (obfuscated == *tr_sha1("req2"sv, info.info_hash))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] event_base* eventBase() const override
|
||||
{
|
||||
return session_->event_base;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isDHTEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isPeerKnownSeed(tr_torrent_id_t /*tor_id*/, tr_address /*addr*/) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t pad(void* setme, [[maybe_unused]] size_t maxlen) const override
|
||||
{
|
||||
TR_ASSERT(maxlen > 10);
|
||||
auto const len = size_t{ 10 };
|
||||
std::fill_n(static_cast<char*>(setme), 10, ' ');
|
||||
return len;
|
||||
}
|
||||
|
||||
[[nodiscard]] tr_message_stream_encryption::DH::private_key_bigend_t privateKey() const override
|
||||
{
|
||||
return private_key_;
|
||||
}
|
||||
|
||||
void setUTPFailed(tr_sha1_digest_t const& /*info_hash*/, tr_address /*addr*/) override
|
||||
{
|
||||
}
|
||||
|
||||
void setPrivateKeyFromBase64(std::string_view b64)
|
||||
{
|
||||
auto const str = tr_base64_decode(b64);
|
||||
assert(std::size(str) == std::size(private_key_));
|
||||
std::copy_n(reinterpret_cast<std::byte const*>(std::data(str)), std::size(str), std::begin(private_key_));
|
||||
}
|
||||
|
||||
tr_session* const session_;
|
||||
std::map<tr_sha1_digest_t, torrent_info> torrents;
|
||||
tr_message_stream_encryption::DH::private_key_bigend_t private_key_ = {};
|
||||
};
|
||||
|
||||
template<typename Span>
|
||||
void sendToClient(evutil_socket_t sock, Span const& data)
|
||||
{
|
||||
auto const* walk = std::data(data);
|
||||
static_assert(sizeof(*walk) == 1);
|
||||
size_t len = std::size(data);
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
auto const n = send(sock, reinterpret_cast<char const*>(walk), len, 0);
|
||||
#else
|
||||
auto const n = write(sock, walk, len);
|
||||
#endif
|
||||
assert(n >= 0);
|
||||
len -= n;
|
||||
walk += n;
|
||||
}
|
||||
}
|
||||
|
||||
void sendB64ToClient(evutil_socket_t sock, std::string_view b64)
|
||||
{
|
||||
sendToClient(sock, tr_base64_decode(b64));
|
||||
}
|
||||
|
||||
auto constexpr ReservedBytesNoExtensions = std::array<uint8_t, 8>{ 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
auto constexpr PlaintextProtocolName = "\023BitTorrent protocol"sv;
|
||||
auto const default_peer_addr = *tr_address::fromString("127.0.0.1"sv);
|
||||
auto const default_peer_port = tr_port::fromHost(8080);
|
||||
auto const torrent_we_are_seeding = tr_handshake_mediator::torrent_info{ *tr_sha1("abcde"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 100 },
|
||||
true /*is_done*/ };
|
||||
auto const ubuntu_torrent = tr_handshake_mediator::torrent_info{ *tr_sha1_from_string(
|
||||
"2c6b6858d61da9543d4231a71db4b1c9264b0685"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 101 },
|
||||
false /*is_done*/ };
|
||||
|
||||
auto createIncomingIo(tr_session* session)
|
||||
{
|
||||
auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 };
|
||||
EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno);
|
||||
auto const now = tr_time();
|
||||
auto const peer_socket = tr_peer_socket_tcp_create(sockpair[0]);
|
||||
auto* const
|
||||
io = tr_peerIoNewIncoming(session, &session->top_bandwidth_, &default_peer_addr, default_peer_port, now, peer_socket);
|
||||
return std::make_pair(io, sockpair[1]);
|
||||
}
|
||||
|
||||
auto createOutgoingIo(tr_session* session, tr_sha1_digest_t const& info_hash)
|
||||
{
|
||||
auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 };
|
||||
EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno);
|
||||
auto const now = tr_time();
|
||||
auto const peer_socket = tr_peer_socket_tcp_create(sockpair[0]);
|
||||
auto* const io = tr_peerIoNew(
|
||||
session,
|
||||
&session->top_bandwidth_,
|
||||
&default_peer_addr,
|
||||
default_peer_port,
|
||||
now,
|
||||
&info_hash,
|
||||
false /*is_incoming*/,
|
||||
false /*is_seed*/,
|
||||
peer_socket);
|
||||
return std::make_pair(io, sockpair[1]);
|
||||
}
|
||||
|
||||
constexpr auto makePeerId(std::string_view sv)
|
||||
{
|
||||
auto peer_id = tr_peer_id_t{};
|
||||
for (size_t i = 0, n = std::size(sv); i < n; ++i)
|
||||
{
|
||||
peer_id[i] = sv[i];
|
||||
}
|
||||
return peer_id;
|
||||
}
|
||||
|
||||
auto makeRandomPeerId()
|
||||
{
|
||||
auto peer_id = tr_peer_id_t{};
|
||||
tr_rand_buffer(std::data(peer_id), std::size(peer_id));
|
||||
auto const peer_id_prefix = "-UW110Q-"sv;
|
||||
std::copy(std::begin(peer_id_prefix), std::end(peer_id_prefix), std::begin(peer_id));
|
||||
return peer_id;
|
||||
}
|
||||
|
||||
auto runHandshake(
|
||||
std::shared_ptr<tr_handshake_mediator> mediator,
|
||||
tr_peerIo* io,
|
||||
tr_encryption_mode encryption_mode = TR_CLEAR_PREFERRED)
|
||||
{
|
||||
auto result = std::optional<tr_handshake_result>{};
|
||||
|
||||
static auto const done_callback = [](auto const& resin)
|
||||
{
|
||||
*static_cast<std::optional<tr_handshake_result>*>(resin.userData) = resin;
|
||||
return true;
|
||||
};
|
||||
|
||||
tr_handshakeNew(mediator, io, encryption_mode, done_callback, &result);
|
||||
|
||||
waitFor([&result]() { return result.has_value(); }, MaxWaitMsec);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_F(HandshakeTest, incomingPlaintext)
|
||||
{
|
||||
auto const peer_id = makeRandomPeerId();
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(torrent_we_are_seeding.info_hash, torrent_we_are_seeding);
|
||||
|
||||
// The simplest handshake there is. "The handshake starts with character
|
||||
// nineteen (decimal) followed by the string 'BitTorrent protocol'.
|
||||
// The leading character is a length prefix[.]. After the fixed headers
|
||||
// come eight reserved bytes, which are all zero in all current
|
||||
// implementations[.] Next comes the 20 byte sha1 hash of the bencoded
|
||||
// form of the info value from the metainfo file[.] After the download
|
||||
// hash comes the 20-byte peer id which is reported in tracker requests
|
||||
// and contained in peer lists in tracker responses.
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
sendToClient(sock, ReservedBytesNoExtensions);
|
||||
sendToClient(sock, torrent_we_are_seeding.info_hash);
|
||||
sendToClient(sock, peer_id);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_TRUE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(peer_id, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(torrent_we_are_seeding.info_hash, *io->torrentHash());
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
// The datastream is identical to HandshakeTest.incomingPlaintext,
|
||||
// but this time we don't recognize the infohash sent by the peer.
|
||||
TEST_F(HandshakeTest, incomingPlaintextUnknownInfoHash)
|
||||
{
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(torrent_we_are_seeding.info_hash, torrent_we_are_seeding);
|
||||
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
sendToClient(sock, ReservedBytesNoExtensions);
|
||||
sendToClient(sock, *tr_sha1("some other torrent unknown to us"sv));
|
||||
sendToClient(sock, makeRandomPeerId());
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_FALSE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
EXPECT_FALSE(res->peer_id);
|
||||
EXPECT_FALSE(io->torrentHash());
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
TEST_F(HandshakeTest, outgoingPlaintext)
|
||||
{
|
||||
auto const peer_id = makeRandomPeerId();
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, torrent_we_are_seeding);
|
||||
|
||||
auto [io, sock] = createOutgoingIo(session_, ubuntu_torrent.info_hash);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
sendToClient(sock, ReservedBytesNoExtensions);
|
||||
sendToClient(sock, ubuntu_torrent.info_hash);
|
||||
sendToClient(sock, peer_id);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_TRUE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(peer_id, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
TEST_F(HandshakeTest, incomingEncrypted)
|
||||
{
|
||||
static auto constexpr ExpectedPeerId = makePeerId("-TR300Z-w4bd4mkebkbi"sv);
|
||||
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, ubuntu_torrent);
|
||||
mediator->setPrivateKeyFromBase64("0EYKCwBWQ4Dg9kX3c5xxjVtBDKw="sv);
|
||||
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
|
||||
// Peer->Client data from a successful encrypted handshake recorded
|
||||
// in the wild for replay here
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
|
||||
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
|
||||
"J7nJ"sv);
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
|
||||
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
|
||||
"04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_TRUE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(ExpectedPeerId, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
// The datastream is identical to HandshakeTest.incomingEncrypted,
|
||||
// but this time we don't recognize the infohash sent by the peer.
|
||||
TEST_F(HandshakeTest, incomingEncryptedUnknownInfoHash)
|
||||
{
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->setPrivateKeyFromBase64("0EYKCwBWQ4Dg9kX3c5xxjVtBDKw="sv);
|
||||
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
|
||||
// Peer->Client data from a successful encrypted handshake recorded
|
||||
// in the wild for replay here
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
|
||||
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
|
||||
"J7nJ"sv);
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
|
||||
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
|
||||
"04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_FALSE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_FALSE(io->torrentHash());
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
TEST_F(HandshakeTest, outgoingEncrypted)
|
||||
{
|
||||
static auto constexpr ExpectedPeerId = makePeerId("-qB4250-scysDI_JuVN3"sv);
|
||||
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, ubuntu_torrent);
|
||||
mediator->setPrivateKeyFromBase64("0EYKCwBWQ4Dg9kX3c5xxjVtBDKw="sv);
|
||||
|
||||
auto [io, sock] = createOutgoingIo(session_, ubuntu_torrent.info_hash);
|
||||
|
||||
// Peer->Client data from a successful encrypted handshake recorded
|
||||
// in the wild for replay here
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"Sfgoq/nrQfD4Iwirfk+uhOmQMOC/QwK/vYiOact1NF9TpWXms3cvlKEKxs0VU"
|
||||
"mnmytRh9bh4Lcs1bswlC6R05XrJGzLhZqAqcLUUAR1VTLA5oKSjR1038zFbhn"
|
||||
"c71jqlpney15ChMTnx02Qt+88l0Z9OWLUUJrUVy+OoIaTMSKDDFVOjuj0y+Ii"
|
||||
"cE0ZnN61e0/R/g+APRK5tegw0SLZ3Nr8+y4Dl77sZyc141PR9xvDj0da1eAvf"
|
||||
"BvXyyDem4vUjqiLUNCEV8KDXEMPCPYAQoDZzLvMyOEtJM/if0o0UN88SWtt1k"
|
||||
"jRD8UNvUlXIfM0YsnJhKA6fJ7/4geK7+Wo2aicfaLFOyG5IEJbTg9OQYbDHFa"
|
||||
"oVzD0xY0Dx+J0loqM+CzrPj8UpeXIcbD7pJrT3XPECbFQ12cCY5LW5RymVIx8"
|
||||
"TP0ajGiTxou1L7DbGD54SYgV/4qFbafRsWp9AO+YDJcouFd/jiVN+r3loxvfT"
|
||||
"0A9H9DRAMR0rZKpQpXZ1ZAhAuAOXGHFIvtw8wd6dPybeu5+LoR2S90/IpwHWI"
|
||||
"jbNbypQZuA9hn4JfFMWPP9TG/E11loB4+MkrP22U72ezjL5ipd74AEEP0/u8w"
|
||||
"Gj1t2kXhND9ONfasA+pY25y8GM04M0B7+0xKmsHP7tntwQLAGZATH83rOxaSO"
|
||||
"3+o/RdiKQJAsGxMIU08scBc5VOmrAmjeYrLNpFnpXVuavH5if7490zMCu3DEn"
|
||||
"G9hpbYbiX95T+EUcRbM6pSCvr3Twq1Q="sv);
|
||||
|
||||
auto const res = runHandshake(mediator, io, TR_ENCRYPTION_PREFERRED);
|
||||
|
||||
// check the results
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_TRUE(res->isConnected);
|
||||
EXPECT_TRUE(res->readAnythingFromPeer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(ExpectedPeerId, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace libtransmission
|
Loading…
Reference in New Issue