Browse Source

test encoding/decoding neighbors. add MAC.

cl-refactor
subtly 10 years ago
parent
commit
bf05c50c78
  1. 21
      libp2p/NodeTable.cpp
  2. 24
      libp2p/NodeTable.h
  3. 33
      libp2p/UDP.cpp
  4. 101
      test/net.cpp

21
libp2p/NodeTable.cpp

@ -235,7 +235,7 @@ void NodeTable::noteNode(Public const& _pubk, bi::udp::endpoint const& _endpoint
{ {
Address id = right160(sha3(_pubk)); Address id = right160(sha3(_pubk));
// Don't add ourself (would result in -1 bucket lookup) // Don't add ourself
if (id == m_node.address()) if (id == m_node.address())
return; return;
@ -303,18 +303,27 @@ NodeTable::NodeBucket& NodeTable::bucket(NodeEntry const* _n)
void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet) void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet)
{ {
if (_packet.size() < 69) // h256 + Signature + RLP
if (_packet.size() < 100)
{ {
clog(NodeTableMessageSummary) << "Invalid Message received from " << _from.address().to_string() << ":" << _from.port(); clog(NodeTableMessageSummary) << "Invalid Message size received from " << _from.address().to_string() << ":" << _from.port();
return;
}
bytesConstRef signedBytes(_packet.cropped(h256::size, _packet.size() - h256::size));
h256 hashSigned(sha3(signedBytes));
if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes()))
{
clog(NodeTableMessageSummary) << "Invalid Message hash received from " << _from.address().to_string() << ":" << _from.port();
return; return;
} }
// 3 items is PingNode, 2 items w/no lists is FindNode, 2 items w/first item as list is Neighbors, 1 item is Pong // 3 items is PingNode, 2 items w/no lists is FindNode, 2 items w/first item as list is Neighbors, 1 item is Pong
bytesConstRef rlpBytes(_packet.cropped(65, _packet.size() - 65)); bytesConstRef rlpBytes(signedBytes.cropped(Signature::size, signedBytes.size() - Signature::size));
RLP rlp(rlpBytes); RLP rlp(rlpBytes);
unsigned itemCount = rlp.itemCount(); unsigned itemCount = rlp.itemCount();
bytesConstRef sigBytes(_packet.cropped(0, 65)); bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size));
Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(rlpBytes))); Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(rlpBytes)));
if (!nodeid) if (!nodeid)
{ {
@ -339,8 +348,8 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
if (rlp[0].isList()) if (rlp[0].isList())
{ {
// todo: chunk neighbors packet // todo: chunk neighbors packet
clog(NodeTableMessageSummary) << "Received Neighbors from " << _from.address().to_string() << ":" << _from.port();
Neighbors in = Neighbors::fromBytesConstRef(_from, rlpBytes); Neighbors in = Neighbors::fromBytesConstRef(_from, rlpBytes);
clog(NodeTableMessageSummary) << "Received " << in.nodes.size() << " Neighbors from " << _from.address().to_string() << ":" << _from.port();
for (auto n: in.nodes) for (auto n: in.nodes)
noteNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port)); noteNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port));
} }

24
libp2p/NodeTable.h

@ -104,6 +104,8 @@ class NodeTable: UDPSocketEvents, public std::enable_shared_from_this<NodeTable>
}; };
public: public:
NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _port = 30300);
~NodeTable();
/// Constants for Kademlia, mostly derived from address space. /// Constants for Kademlia, mostly derived from address space.
@ -116,7 +118,6 @@ public:
static constexpr unsigned s_bucketSize = 16; ///< Denoted by k in [Kademlia]. Number of nodes stored in each bucket. static constexpr unsigned s_bucketSize = 16; ///< Denoted by k in [Kademlia]. Number of nodes stored in each bucket.
static constexpr unsigned s_alpha = 3; ///< Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests. static constexpr unsigned s_alpha = 3; ///< Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests.
static constexpr uint16_t s_defaultPort = 30300; ///< Default port to listen on.
/// Intervals /// Intervals
@ -125,9 +126,6 @@ public:
std::chrono::seconds const c_bucketRefresh = std::chrono::seconds(3600); ///< Refresh interval prevents bucket from becoming stale. [Kademlia] std::chrono::seconds const c_bucketRefresh = std::chrono::seconds(3600); ///< Refresh interval prevents bucket from becoming stale. [Kademlia]
static unsigned dist(Address const& _a, Address const& _b) { u160 d = _a ^ _b; unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; } static unsigned dist(Address const& _a, Address const& _b) { u160 d = _a ^ _b; unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; }
NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _port = s_defaultPort);
~NodeTable();
void join(); void join();
@ -233,9 +231,9 @@ struct PingNode: RLPXDatagram<PingNode>
using RLPXDatagram::RLPXDatagram; using RLPXDatagram::RLPXDatagram;
PingNode(bi::udp::endpoint _ep, std::string _src, uint16_t _srcPort, std::chrono::seconds _expiration = std::chrono::seconds(60)): RLPXDatagram(_ep), ipAddress(_src), port(_srcPort), expiration(futureFromEpoch(_expiration)) {} PingNode(bi::udp::endpoint _ep, std::string _src, uint16_t _srcPort, std::chrono::seconds _expiration = std::chrono::seconds(60)): RLPXDatagram(_ep), ipAddress(_src), port(_srcPort), expiration(futureFromEpoch(_expiration)) {}
std::string ipAddress; // rlp encoded bytes min: 16 std::string ipAddress;
unsigned port; // rlp encoded bytes min: 1 max: 3 unsigned port;
unsigned expiration; // rlp encoded bytes min: 1 max: 9 unsigned expiration;
void streamRLP(RLPStream& _s) const { _s.appendList(3); _s << ipAddress << port << expiration; } void streamRLP(RLPStream& _s) const { _s.appendList(3); _s << ipAddress << port << expiration; }
void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); ipAddress = r[0].toString(); port = r[1].toInt<unsigned>(); expiration = r[2].toInt<unsigned>(); } void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); ipAddress = r[0].toString(); port = r[1].toInt<unsigned>(); expiration = r[2].toInt<unsigned>(); }
@ -248,6 +246,7 @@ struct PingNode: RLPXDatagram<PingNode>
* Minimum Encoded Size: 33 bytes * Minimum Encoded Size: 33 bytes
* Maximum Encoded Size: 33 bytes * Maximum Encoded Size: 33 bytes
* *
* @todo expiration
* @todo value of replyTo * @todo value of replyTo
* @todo create from PingNode (reqs RLPXDatagram verify flag) * @todo create from PingNode (reqs RLPXDatagram verify flag)
*/ */
@ -256,6 +255,7 @@ struct Pong: RLPXDatagram<Pong>
using RLPXDatagram::RLPXDatagram; using RLPXDatagram::RLPXDatagram;
h256 replyTo; // hash of rlp of PingNode h256 replyTo; // hash of rlp of PingNode
unsigned expiration;
void streamRLP(RLPStream& _s) const { _s.appendList(1); _s << replyTo; } void streamRLP(RLPStream& _s) const { _s.appendList(1); _s << replyTo; }
void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); replyTo = (h256)r[0]; } void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); replyTo = (h256)r[0]; }
@ -292,7 +292,7 @@ struct FindNode: RLPXDatagram<FindNode>
* RLP Encoded Items: 2 (first item is list) * RLP Encoded Items: 2 (first item is list)
* Minimum Encoded Size: 10 bytes * Minimum Encoded Size: 10 bytes
* *
* @todo nonce * @todo nonce: Should be replaced with expiration.
*/ */
struct Neighbors: RLPXDatagram<Neighbors> struct Neighbors: RLPXDatagram<Neighbors>
{ {
@ -312,7 +312,7 @@ struct Neighbors: RLPXDatagram<Neighbors>
}; };
using RLPXDatagram::RLPXDatagram; using RLPXDatagram::RLPXDatagram;
Neighbors(bi::udp::endpoint _to, std::vector<std::shared_ptr<NodeTable::NodeEntry>> const& _nearest): RLPXDatagram(_to), nonce(h256()) Neighbors(bi::udp::endpoint _to, std::vector<std::shared_ptr<NodeTable::NodeEntry>> const& _nearest): RLPXDatagram(_to)
{ {
for (auto& n: _nearest) for (auto& n: _nearest)
{ {
@ -325,11 +325,11 @@ struct Neighbors: RLPXDatagram<Neighbors>
} }
std::list<Node> nodes; std::list<Node> nodes;
h256 nonce; unsigned expiration = 1;
void streamRLP(RLPStream& _s) const { _s.appendList(2); _s.appendList(nodes.size()); for (auto& n: nodes) n.streamRLP(_s); _s << 1; } void streamRLP(RLPStream& _s) const { _s.appendList(2); _s.appendList(nodes.size()); for (auto& n: nodes) n.streamRLP(_s); _s << expiration; }
void interpretRLP(bytesConstRef _bytes) { void interpretRLP(bytesConstRef _bytes) {
RLP r(_bytes); for (auto n: r[0]) nodes.push_back(Node(n)); nonce = (h256)r[1]; RLP r(_bytes); for (auto n: r[0]) nodes.push_back(Node(n)); expiration = r[1].toInt<unsigned>();
} }
}; };

33
libp2p/UDP.cpp

@ -23,23 +23,32 @@
using namespace dev; using namespace dev;
using namespace dev::p2p; using namespace dev::p2p;
//template <class T>
h256 RLPXDatagramFace::sign(Secret const& _k) h256 RLPXDatagramFace::sign(Secret const& _k)
{ {
RLPStream packet; RLPStream rlpstream;
streamRLP(packet); streamRLP(rlpstream);
bytes b(packet.out()); bytes rlpBytes(rlpstream.out());
h256 h(dev::sha3(b));
Signature sig = dev::sign(_k, h); bytesConstRef rlp(&rlpBytes);
data.resize(b.size() + Signature::size); h256 hash(dev::sha3(rlp));
sig.ref().copyTo(&data); Signature sig = dev::sign(_k, hash);
memcpy(data.data() + sizeof(Signature), b.data(), b.size());
return std::move(h); data.resize(h256::size + Signature::size + rlp.size());
bytesConstRef packetHash(&data[0], h256::size);
bytesConstRef signedPayload(&data[h256::size], Signature::size + rlp.size());
bytesConstRef payloadSig(&data[h256::size], Signature::size);
bytesConstRef payload(&data[h256::size+Signature::size], rlp.size());
sig.ref().copyTo(payloadSig);
rlp.copyTo(payload);
dev::sha3(signedPayload).ref().copyTo(packetHash);
return std::move(hash);
}; };
//template <class T>
Public RLPXDatagramFace::authenticate(bytesConstRef _sig, bytesConstRef _rlp) Public RLPXDatagramFace::authenticate(bytesConstRef _sig, bytesConstRef _rlp)
{ {
Signature const& sig = *(Signature const*)_sig.data(); Signature const& sig = *(Signature const*)_sig.data();
return std::move(dev::recover(sig, sha3(_rlp))); return std::move(dev::recover(sig, sha3(_rlp)));
}; };

101
test/net.cpp

@ -53,7 +53,23 @@ struct TestNodeTable: public NodeTable
/// Constructor /// Constructor
using NodeTable::NodeTable; using NodeTable::NodeTable;
void pingAll(std::vector<std::pair<KeyPair,unsigned>> const& _testNodes) static std::vector<std::pair<KeyPair,unsigned>> createTestNodes(int _count = 16)
{
std::vector<std::pair<KeyPair,unsigned>> ret;
asserts(_count < 1000);
static uint16_t s_basePort = 30500;
ret.clear();
for (auto i = 0; i < _count; i++)
{
KeyPair k = KeyPair::create();
ret.push_back(make_pair(k,s_basePort+i));
}
return std::move(ret);
}
void pingTestNodes(std::vector<std::pair<KeyPair,unsigned>> const& _testNodes)
{ {
bi::address ourIp = bi::address::from_string("127.0.0.1"); bi::address ourIp = bi::address::from_string("127.0.0.1");
for (auto& n: _testNodes) for (auto& n: _testNodes)
@ -63,7 +79,7 @@ struct TestNodeTable: public NodeTable
} }
} }
void populate(std::vector<std::pair<KeyPair,unsigned>> const& _testNodes, size_t _count = 0) void populateTestNodes(std::vector<std::pair<KeyPair,unsigned>> const& _testNodes, size_t _count = 0)
{ {
if (!_count) if (!_count)
_count = _testNodes.size(); _count = _testNodes.size();
@ -88,44 +104,19 @@ struct TestNodeTable: public NodeTable
*/ */
struct TestNodeTableHost: public TestHost struct TestNodeTableHost: public TestHost
{ {
TestNodeTableHost(): m_alias(KeyPair::create()), nodeTable(new TestNodeTable(m_io, m_alias)) {}; TestNodeTableHost(): m_alias(KeyPair::create()), nodeTable(new TestNodeTable(m_io, m_alias)), testNodes(TestNodeTable::createTestNodes()) {};
~TestNodeTableHost() { m_io.stop(); stopWorking(); } ~TestNodeTableHost() { m_io.stop(); stopWorking(); }
void generateTestNodes(int _count = 16)
{
asserts(_count < 1000);
static uint16_t s_basePort = 30500;
m_testNodes.clear();
for (auto i = 0; i < _count; i++)
{
KeyPair k = KeyPair::create();
m_testNodes.push_back(make_pair(k,s_basePort+i));
testNodes.push_back(make_shared<TestNodeTable>(m_io,k,s_basePort+i));
}
}
std::vector<std::pair<KeyPair,unsigned>> m_testNodes; // keypair and port
void setup() void setup() { for (auto n: testNodes) nodeTables.push_back(make_shared<TestNodeTable>(m_io,n.first,n.second)); }
{
generateTestNodes();
}
void pingAll() void pingAll() { for (auto& t: nodeTables) t->pingTestNodes(testNodes); }
{
nodeTable->pingAll(m_testNodes);
// for (auto& n: testNodes)
// n->pingAll(m_testNodes);
}
void populate(size_t _count = 0) void populate(size_t _count = 0) { nodeTable->populateTestNodes(testNodes, _count); }
{
nodeTable->populate(m_testNodes, _count);
}
KeyPair m_alias; KeyPair m_alias;
shared_ptr<TestNodeTable> nodeTable; shared_ptr<TestNodeTable> nodeTable;
std::vector<shared_ptr<TestNodeTable>> testNodes; std::vector<std::pair<KeyPair,unsigned>> testNodes; // keypair and port
std::vector<shared_ptr<TestNodeTable>> nodeTables;
}; };
class TestUDPSocket: UDPSocketEvents, public TestHost class TestUDPSocket: UDPSocketEvents, public TestHost
@ -141,6 +132,36 @@ public:
bool success = false; bool success = false;
}; };
BOOST_AUTO_TEST_CASE(test_neighbors_packet)
{
KeyPair k = KeyPair::create();
std::vector<std::pair<KeyPair,unsigned>> testNodes(TestNodeTable::createTestNodes());
bi::udp::endpoint to(boost::asio::ip::address::from_string("127.0.0.1"), 30000);
Neighbors out(to);
for (auto n: testNodes)
{
Neighbors::Node node;
node.ipAddress = boost::asio::ip::address::from_string("127.0.0.1").to_string();
node.port = n.second;
node.node = n.first.pub();
out.nodes.push_back(node);
}
out.sign(k.sec());
bytesConstRef packet(out.data.data(), out.data.size());
bytesConstRef rlpBytes(packet.cropped(97, packet.size() - 97));
Neighbors in = Neighbors::fromBytesConstRef(to, rlpBytes);
int count = 0;
for (auto n: in.nodes)
{
BOOST_REQUIRE_EQUAL(testNodes[count].second, n.port);
BOOST_REQUIRE_EQUAL(testNodes[count].first.pub(), n.node);
BOOST_REQUIRE_EQUAL(sha3(testNodes[count].first.pub()), sha3(n.node));
count++;
}
}
BOOST_AUTO_TEST_CASE(test_findnode_neighbors) BOOST_AUTO_TEST_CASE(test_findnode_neighbors)
{ {
// Executing findNode should result in a list which is serialized // Executing findNode should result in a list which is serialized
@ -154,21 +175,23 @@ BOOST_AUTO_TEST_CASE(kademlia)
node.start(); node.start();
node.nodeTable->join(); // ideally, joining with empty node table logs warning we can check for node.nodeTable->join(); // ideally, joining with empty node table logs warning we can check for
node.setup(); node.setup();
node.populate();
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
node.pingAll(); node.pingAll();
this_thread::sleep_for(chrono::milliseconds(4000));
clog << "NodeTable:\n" << *node.nodeTable.get() << endl; clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
this_thread::sleep_for(chrono::milliseconds(10000));
node.nodeTable->reset(); node.nodeTable->reset();
clog << "NodeTable:\n" << *node.nodeTable.get() << endl; clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
node.populate(2); node.populate(2);
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
this_thread::sleep_for(chrono::milliseconds(500)); this_thread::sleep_for(chrono::milliseconds(500));
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
// node.nodeTable->join(); node.nodeTable->join();
// this_thread::sleep_for(chrono::milliseconds(2000)); this_thread::sleep_for(chrono::milliseconds(2000));
// clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
// clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
} }
BOOST_AUTO_TEST_CASE(test_udp_once) BOOST_AUTO_TEST_CASE(test_udp_once)

Loading…
Cancel
Save