Skip to content
This repository has been archived by the owner on Oct 28, 2021. It is now read-only.

Commit

Permalink
Check whether node's endpoint changed when handling discovery packets
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Mar 14, 2019
1 parent 5f9b990 commit e757bbb
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 41 deletions.
31 changes: 21 additions & 10 deletions libp2p/NodeTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ bool NodeTable::addNode(Node const& _node)
DEV_GUARDED(x_nodes)
{
auto const it = m_allNodes.find(_node.id);
needToPing = (it == m_allNodes.end() || !it->second->hasValidEndpointProof());
needToPing = (it == m_allNodes.end() || it->second->endpoint != _node.endpoint ||
!it->second->hasValidEndpointProof());
}

if (needToPing)
Expand Down Expand Up @@ -115,7 +116,7 @@ bool NodeTable::addKnownNode(
if (entry->hasValidEndpointProof())
{
LOG(m_logger) << "Known " << _node;
noteActiveNode(move(entry), _node.endpoint);
noteActiveNode(move(entry));
}
else
{
Expand Down Expand Up @@ -356,7 +357,7 @@ void NodeTable::evict(NodeEntry const& _leastSeen, shared_ptr<NodeEntry> _replac
m_nodeEventHandler->appendEvent(_leastSeen.id, NodeEntryScheduledForEviction);
}

void NodeTable::noteActiveNode(shared_ptr<NodeEntry> _nodeEntry, bi::udp::endpoint const& _endpoint)
void NodeTable::noteActiveNode(shared_ptr<NodeEntry> _nodeEntry)
{
assert(_nodeEntry);

Expand All @@ -365,7 +366,7 @@ void NodeTable::noteActiveNode(shared_ptr<NodeEntry> _nodeEntry, bi::udp::endpoi
LOG(m_logger) << "Skipping making self active.";
return;
}
if (!isAllowedEndpoint(NodeIPEndpoint(_endpoint.address(), _endpoint.port(), _endpoint.port())))
if (!isAllowedEndpoint(_nodeEntry->endpoint))
{
LOG(m_logger) << "Skipping making node with unallowed endpoint active. Node " << *_nodeEntry;
return;
Expand All @@ -375,10 +376,6 @@ void NodeTable::noteActiveNode(shared_ptr<NodeEntry> _nodeEntry, bi::udp::endpoi
return;

LOG(m_logger) << "Active node " << *_nodeEntry;
// TODO: don't activate in case endpoint has changed
_nodeEntry->endpoint.setAddress(_endpoint.address());
_nodeEntry->endpoint.setUdpPort(_endpoint.port());


shared_ptr<NodeEntry> nodeToEvict;
{
Expand Down Expand Up @@ -513,6 +510,10 @@ void NodeTable::onPacketReceived(
sourceNodeEntry = it->second;
sourceNodeEntry->lastPongReceivedTime =
RLPXDatagramFace::secondsSinceEpoch();

if (sourceNodeEntry->endpoint != _from)
sourceNodeEntry->endpoint = NodeIPEndpoint{
_from.address(), _from.port(), nodeValidation.tcpPort};
}
}

Expand All @@ -538,6 +539,11 @@ void NodeTable::onPacketReceived(
<< ") not found in node table. Ignoring Neighbours packet.";
return;
}
if (sourceNodeEntry->endpoint != _from)
{
LOG(m_logger) << "Neighbours packet from unexpected endpoint.";
return;
}

auto const& in = dynamic_cast<Neighbours const&>(*packet);

Expand Down Expand Up @@ -571,6 +577,11 @@ void NodeTable::onPacketReceived(
<< ") not found in node table. Ignoring FindNode request.";
return;
}
if (sourceNodeEntry->endpoint != _from)
{
LOG(m_logger) << "FindNode packet from unexpected endpoint.";
return;
}
if (!sourceNodeEntry->lastPongReceivedTime)
{
LOG(m_logger) << "Unexpected FindNode packet! Endpoint proof hasn't been performed yet.";
Expand Down Expand Up @@ -627,7 +638,7 @@ void NodeTable::onPacketReceived(
}

if (sourceNodeEntry)
noteActiveNode(move(sourceNodeEntry), _from);
noteActiveNode(move(sourceNodeEntry));
}
catch (exception const& _e)
{
Expand Down Expand Up @@ -711,7 +722,7 @@ void NodeTable::doHandleTimeouts()

// activate replacement nodes and put them into buckets
for (auto const& n : nodesToActivate)
noteActiveNode(n, n->endpoint);
noteActiveNode(n);

doHandleTimeouts();
});
Expand Down
2 changes: 1 addition & 1 deletion libp2p/NodeTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ class NodeTable : UDPSocketEvents

/// Called whenever activity is received from a node in order to maintain node table. Only
/// called for nodes for which we've completed an endpoint proof.
void noteActiveNode(std::shared_ptr<NodeEntry> _nodeEntry, bi::udp::endpoint const& _endpoint);
void noteActiveNode(std::shared_ptr<NodeEntry> _nodeEntry);

/// Used to drop node when timeout occurs or when evict() result is to keep previous node.
void dropNode(std::shared_ptr<NodeEntry> _n);
Expand Down
156 changes: 126 additions & 30 deletions test/unittests/libp2p/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct TestNodeTable: public NodeTable
auto entry = make_shared<NodeEntry>(m_hostNodeID, n.first,
NodeIPEndpoint(ourIp, n.second, n.second),
RLPXDatagramFace::secondsSinceEpoch(), RLPXDatagramFace::secondsSinceEpoch());
noteActiveNode(move(entry), bi::udp::endpoint(ourIp, n.second));
noteActiveNode(move(entry));
}
else
break;
Expand All @@ -100,7 +100,7 @@ struct TestNodeTable: public NodeTable
NodeIPEndpoint(ourIp, testNode->second, testNode->second),
RLPXDatagramFace::secondsSinceEpoch(), RLPXDatagramFace::secondsSinceEpoch()));
auto distance = node->distance;
noteActiveNode(move(node), bi::udp::endpoint(ourIp, testNode->second));
noteActiveNode(move(node));

{
Guard stateGuard(x_state);
Expand Down Expand Up @@ -138,7 +138,7 @@ struct TestNodeTable: public NodeTable
auto entry = make_shared<NodeEntry>(m_hostNodeID, testNode->first,
NodeIPEndpoint(ourIp, testNode->second, testNode->second),
RLPXDatagramFace::secondsSinceEpoch(), RLPXDatagramFace::secondsSinceEpoch());
noteActiveNode(move(entry), bi::udp::endpoint(ourIp, testNode->second));
noteActiveNode(move(entry));

++testNode;
}
Expand Down Expand Up @@ -500,7 +500,7 @@ BOOST_AUTO_TEST_CASE(noteActiveNodeUpdatesKnownNode)
auto& nodeTable = nodeTableHost.nodeTable;
auto knownNode = nodeTable->bucketFirstNode(bucketIndex);

nodeTable->noteActiveNode(knownNode, knownNode->endpoint);
nodeTable->noteActiveNode(knownNode);

// check that node was moved to the back of the bucket
BOOST_CHECK_NE(nodeTable->bucketFirstNode(bucketIndex), knownNode);
Expand Down Expand Up @@ -550,32 +550,6 @@ BOOST_AUTO_TEST_CASE(noteActiveNodeEvictsTheNodeWhenBucketIsFull)
BOOST_CHECK_EQUAL(evicted->replacementNodeEntry->id, newNodeId);
}

BOOST_AUTO_TEST_CASE(noteActiveNodeReplacesNodeInFullBucketWhenEndpointChanged)
{
TestNodeTableHost nodeTableHost(512);
int const bucketIndex = nodeTableHost.populateUntilBucketSize(16);
BOOST_REQUIRE(bucketIndex >= 0);

auto& nodeTable = nodeTableHost.nodeTable;
auto leastRecentlySeenNode = nodeTable->bucketFirstNode(bucketIndex);

// addNode will replace the node in the m_allNodes map, because it's the same id with enother
// endpoint
auto const port = randomPortNumber();
NodeIPEndpoint newEndpoint{bi::address::from_string(c_localhostIp), port, port };
nodeTable->noteActiveNode(leastRecentlySeenNode, newEndpoint);

// the bucket is still max size
BOOST_CHECK_EQUAL(nodeTable->bucketSize(bucketIndex), 16);
// least recently seen node removed
BOOST_CHECK_NE(nodeTable->bucketFirstNode(bucketIndex)->id, leastRecentlySeenNode->id);
// but added as most recently seen with new endpoint
auto mostRecentNodeEntry = nodeTable->bucketLastNode(bucketIndex);
BOOST_CHECK_EQUAL(mostRecentNodeEntry->id, leastRecentlySeenNode->id);
BOOST_CHECK_EQUAL(mostRecentNodeEntry->endpoint.address(), newEndpoint.address());
BOOST_CHECK_EQUAL(mostRecentNodeEntry->endpoint.udpPort(), newEndpoint.udpPort());
}

BOOST_AUTO_TEST_CASE(unexpectedPong)
{
// NodeTable receiving PONG
Expand Down Expand Up @@ -1171,6 +1145,128 @@ BOOST_AUTO_TEST_CASE(addNodePingsNodeOnlyOnce)
BOOST_REQUIRE_EQUAL(sentPing->pingHash, sentPing2->pingHash);
}

class PacketsWithChangedEndpointFixture : public TestOutputHelperFixture
{
public:
PacketsWithChangedEndpointFixture()
{
nodeTableHost.start();
nodeSocketHost1.start();
nodePort1 = nodeSocketHost1.port;
nodeSocketHost2.start();
nodePort2 = nodeSocketHost2.port;

nodeEndpoint1 =
NodeIPEndpoint{bi::address::from_string(c_localhostIp), nodePort1, nodePort1};
nodeEndpoint2 =
NodeIPEndpoint{bi::address::from_string(c_localhostIp), nodePort2, nodePort2};

// add a node to node table, initiating PING
nodeTable->addNode(Node{nodePubKey, nodeEndpoint1});

// handle received PING
auto pingDataReceived = nodeSocketHost1.packetsReceived.pop();
auto pingDatagram =
DiscoveryDatagram::interpretUDP(bi::udp::endpoint{}, dev::ref(pingDataReceived));
auto ping = dynamic_cast<PingNode const&>(*pingDatagram);

// send PONG
Pong pong(nodeTable->m_hostNodeEndpoint);
pong.echo = ping.echo;
pong.sign(nodeKeyPair.secret());
nodeSocketHost1.socket->send(pong);

// wait for PONG to be received and handled
nodeTable->packetsReceived.pop(chrono::seconds(5));

nodeEntry = nodeTable->nodeEntry(nodePubKey);
}

TestNodeTableHost nodeTableHost{0};
shared_ptr<TestNodeTable>& nodeTable = nodeTableHost.nodeTable;

// socket representing initial peer node
TestUDPSocketHost nodeSocketHost1;
uint16_t nodePort1 = 0;

// socket representing peer with changed endpoint
TestUDPSocketHost nodeSocketHost2;
uint16_t nodePort2 = 0;

NodeIPEndpoint nodeEndpoint1;
NodeIPEndpoint nodeEndpoint2;
KeyPair nodeKeyPair = KeyPair::create();
NodeID nodePubKey = nodeKeyPair.pub();

shared_ptr<NodeEntry> nodeEntry;
};

BOOST_FIXTURE_TEST_SUITE(packetsWithChangedEndpointSuite, PacketsWithChangedEndpointFixture)

BOOST_AUTO_TEST_CASE(addNode)
{
// this should initiate Ping to endpoint 2
nodeTable->addNode(Node{nodePubKey, nodeEndpoint2});

// handle received PING
auto pingDataReceived = nodeSocketHost2.packetsReceived.pop();
auto pingDatagram =
DiscoveryDatagram::interpretUDP(bi::udp::endpoint{}, dev::ref(pingDataReceived));
auto ping = dynamic_cast<PingNode const&>(*pingDatagram);

// send PONG
Pong pong(nodeTable->m_hostNodeEndpoint);
pong.echo = ping.echo;
pong.sign(nodeKeyPair.secret());
nodeSocketHost2.socket->send(pong);

// wait for PONG to be received and handled
nodeTable->packetsReceived.pop(chrono::seconds(5));

BOOST_REQUIRE_EQUAL(nodeEntry->endpoint, nodeEndpoint2);
}

BOOST_AUTO_TEST_CASE(findNode)
{
// Create and send the FindNode packet through endpoint 2
FindNode findNode(nodeTable->m_hostNodeEndpoint, KeyPair::create().pub() /* target */);
findNode.sign(nodeKeyPair.secret());
nodeSocketHost2.socket->send(findNode);

// Wait for FindNode to be received
nodeTable->packetsReceived.pop(chrono::seconds(5));

// Verify that no neighbours response is received
BOOST_CHECK_THROW(nodeSocketHost2.packetsReceived.pop(chrono::seconds(5)), WaitTimeout);
}

BOOST_AUTO_TEST_CASE(neighbours)
{
// Wait for FindNode arriving to endpoint 1
auto findNodeDataReceived = nodeSocketHost1.packetsReceived.pop(chrono::seconds(10));
auto findNodeDatagram =
DiscoveryDatagram::interpretUDP(bi::udp::endpoint{}, dev::ref(findNodeDataReceived));
auto findNode = dynamic_cast<FindNode const&>(*findNodeDatagram);

// send Neighbours through endpoint 2
// TODO fill nearest with one node
NodeIPEndpoint neighbourEndpoint{boost::asio::ip::address::from_string("200.200.200.200"),
c_defaultListenPort, c_defaultListenPort};
vector<shared_ptr<NodeEntry>> nearest{make_shared<NodeEntry>(nodeTable->m_hostNodeID,
KeyPair::create().pub(), neighbourEndpoint, RLPXDatagramFace::secondsSinceEpoch(), 0)};
Neighbours neighbours(nodeTable->m_hostNodeEndpoint, nearest);
neighbours.sign(nodeKeyPair.secret());
nodeSocketHost2.socket->send(neighbours);

// Wait for Neighbours to be received
nodeTable->packetsReceived.pop(chrono::seconds(5));

// no Ping is sent to neighbour
auto sentPing = nodeTable->nodeValidation(neighbourEndpoint);
BOOST_REQUIRE(!sentPing.is_initialized());
}

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()

BOOST_FIXTURE_TEST_SUITE(netTypes, TestOutputHelperFixture)
Expand Down

0 comments on commit e757bbb

Please sign in to comment.