From bf05c50c7855b25b73733b1abeb89892d7eb4509 Mon Sep 17 00:00:00 2001 From: subtly Date: Fri, 26 Dec 2014 00:56:50 +0100 Subject: [PATCH] test encoding/decoding neighbors. add MAC. --- libp2p/NodeTable.cpp | 21 ++++++--- libp2p/NodeTable.h | 24 +++++----- libp2p/UDP.cpp | 33 +++++++++----- test/net.cpp | 101 ++++++++++++++++++++++++++----------------- 4 files changed, 110 insertions(+), 69 deletions(-) diff --git a/libp2p/NodeTable.cpp b/libp2p/NodeTable.cpp index b5b261321..8024f0151 100644 --- a/libp2p/NodeTable.cpp +++ b/libp2p/NodeTable.cpp @@ -235,7 +235,7 @@ void NodeTable::noteNode(Public const& _pubk, bi::udp::endpoint const& _endpoint { Address id = right160(sha3(_pubk)); - // Don't add ourself (would result in -1 bucket lookup) + // Don't add ourself if (id == m_node.address()) return; @@ -303,18 +303,27 @@ NodeTable::NodeBucket& NodeTable::bucket(NodeEntry const* _n) 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; } // 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); 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))); if (!nodeid) { @@ -339,8 +348,8 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes if (rlp[0].isList()) { // todo: chunk neighbors packet - clog(NodeTableMessageSummary) << "Received Neighbors from " << _from.address().to_string() << ":" << _from.port(); 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) noteNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port)); } diff --git a/libp2p/NodeTable.h b/libp2p/NodeTable.h index fe925fea8..399b48eaa 100644 --- a/libp2p/NodeTable.h +++ b/libp2p/NodeTable.h @@ -104,6 +104,8 @@ class NodeTable: UDPSocketEvents, public std::enable_shared_from_this }; public: + NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _port = 30300); + ~NodeTable(); /// 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_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 @@ -125,9 +126,6 @@ public: 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; } - - NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _port = s_defaultPort); - ~NodeTable(); void join(); @@ -233,9 +231,9 @@ struct PingNode: 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)) {} - std::string ipAddress; // rlp encoded bytes min: 16 - unsigned port; // rlp encoded bytes min: 1 max: 3 - unsigned expiration; // rlp encoded bytes min: 1 max: 9 + std::string ipAddress; + unsigned port; + unsigned 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(); expiration = r[2].toInt(); } @@ -248,6 +246,7 @@ struct PingNode: RLPXDatagram * Minimum Encoded Size: 33 bytes * Maximum Encoded Size: 33 bytes * + * @todo expiration * @todo value of replyTo * @todo create from PingNode (reqs RLPXDatagram verify flag) */ @@ -256,6 +255,7 @@ struct Pong: RLPXDatagram using RLPXDatagram::RLPXDatagram; h256 replyTo; // hash of rlp of PingNode + unsigned expiration; void streamRLP(RLPStream& _s) const { _s.appendList(1); _s << replyTo; } void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); replyTo = (h256)r[0]; } @@ -292,7 +292,7 @@ struct FindNode: RLPXDatagram * RLP Encoded Items: 2 (first item is list) * Minimum Encoded Size: 10 bytes * - * @todo nonce + * @todo nonce: Should be replaced with expiration. */ struct Neighbors: RLPXDatagram { @@ -312,7 +312,7 @@ struct Neighbors: RLPXDatagram }; using RLPXDatagram::RLPXDatagram; - Neighbors(bi::udp::endpoint _to, std::vector> const& _nearest): RLPXDatagram(_to), nonce(h256()) + Neighbors(bi::udp::endpoint _to, std::vector> const& _nearest): RLPXDatagram(_to) { for (auto& n: _nearest) { @@ -325,11 +325,11 @@ struct Neighbors: RLPXDatagram } std::list 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) { - 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(); } }; diff --git a/libp2p/UDP.cpp b/libp2p/UDP.cpp index 42f5e4cab..8b60196e9 100644 --- a/libp2p/UDP.cpp +++ b/libp2p/UDP.cpp @@ -23,23 +23,32 @@ using namespace dev; using namespace dev::p2p; -//template h256 RLPXDatagramFace::sign(Secret const& _k) { - RLPStream packet; - streamRLP(packet); - bytes b(packet.out()); - h256 h(dev::sha3(b)); - Signature sig = dev::sign(_k, h); - data.resize(b.size() + Signature::size); - sig.ref().copyTo(&data); - memcpy(data.data() + sizeof(Signature), b.data(), b.size()); - return std::move(h); + RLPStream rlpstream; + streamRLP(rlpstream); + bytes rlpBytes(rlpstream.out()); + + bytesConstRef rlp(&rlpBytes); + h256 hash(dev::sha3(rlp)); + Signature sig = dev::sign(_k, hash); + + 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 Public RLPXDatagramFace::authenticate(bytesConstRef _sig, bytesConstRef _rlp) { Signature const& sig = *(Signature const*)_sig.data(); return std::move(dev::recover(sig, sha3(_rlp))); -}; \ No newline at end of file +}; + diff --git a/test/net.cpp b/test/net.cpp index d5113b8b4..4c45475f3 100644 --- a/test/net.cpp +++ b/test/net.cpp @@ -53,7 +53,23 @@ struct TestNodeTable: public NodeTable /// Constructor using NodeTable::NodeTable; - void pingAll(std::vector> const& _testNodes) + static std::vector> createTestNodes(int _count = 16) + { + std::vector> 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> const& _testNodes) { bi::address ourIp = bi::address::from_string("127.0.0.1"); for (auto& n: _testNodes) @@ -63,7 +79,7 @@ struct TestNodeTable: public NodeTable } } - void populate(std::vector> const& _testNodes, size_t _count = 0) + void populateTestNodes(std::vector> const& _testNodes, size_t _count = 0) { if (!_count) _count = _testNodes.size(); @@ -88,44 +104,19 @@ struct TestNodeTable: public NodeTable */ 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(); } - - 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(m_io,k,s_basePort+i)); - } - } - std::vector> m_testNodes; // keypair and port - void setup() - { - generateTestNodes(); - } + void setup() { for (auto n: testNodes) nodeTables.push_back(make_shared(m_io,n.first,n.second)); } - void pingAll() - { - nodeTable->pingAll(m_testNodes); -// for (auto& n: testNodes) -// n->pingAll(m_testNodes); - } + void pingAll() { for (auto& t: nodeTables) t->pingTestNodes(testNodes); } - void populate(size_t _count = 0) - { - nodeTable->populate(m_testNodes, _count); - } + void populate(size_t _count = 0) { nodeTable->populateTestNodes(testNodes, _count); } KeyPair m_alias; shared_ptr nodeTable; - std::vector> testNodes; + std::vector> testNodes; // keypair and port + std::vector> nodeTables; }; class TestUDPSocket: UDPSocketEvents, public TestHost @@ -141,6 +132,36 @@ public: bool success = false; }; +BOOST_AUTO_TEST_CASE(test_neighbors_packet) +{ + KeyPair k = KeyPair::create(); + std::vector> 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) { // Executing findNode should result in a list which is serialized @@ -154,21 +175,23 @@ BOOST_AUTO_TEST_CASE(kademlia) node.start(); node.nodeTable->join(); // ideally, joining with empty node table logs warning we can check for node.setup(); + node.populate(); + clog << "NodeTable:\n" << *node.nodeTable.get() << endl; + node.pingAll(); + this_thread::sleep_for(chrono::milliseconds(4000)); clog << "NodeTable:\n" << *node.nodeTable.get() << endl; - this_thread::sleep_for(chrono::milliseconds(10000)); node.nodeTable->reset(); clog << "NodeTable:\n" << *node.nodeTable.get() << endl; node.populate(2); - clog << "NodeTable:\n" << *node.nodeTable.get() << endl; this_thread::sleep_for(chrono::milliseconds(500)); + clog << "NodeTable:\n" << *node.nodeTable.get() << endl; -// node.nodeTable->join(); -// this_thread::sleep_for(chrono::milliseconds(2000)); -// -// clog << "NodeTable:\n" << *node.nodeTable.get() << endl; + node.nodeTable->join(); + this_thread::sleep_for(chrono::milliseconds(2000)); + clog << "NodeTable:\n" << *node.nodeTable.get() << endl; } BOOST_AUTO_TEST_CASE(test_udp_once)