void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint) { if (_pubk == m_hostNodeID || !isAllowedEndpoint(NodeIPEndpoint(_endpoint.address(), _endpoint.port(), _endpoint.port()))) return; shared_ptr<NodeEntry> newNode = nodeEntry(_pubk); if (newNode && RLPXDatagramFace::secondsSinceEpoch() < newNode->lastPongReceivedTime + c_bondingTimeSeconds) { LOG(m_logger) << "Active node " << _pubk << '@' << _endpoint; newNode->endpoint.setAddress(_endpoint.address()); newNode->endpoint.setUdpPort(_endpoint.port()); shared_ptr<NodeEntry> nodeToEvict; { Guard l(x_state); // Find a bucket to put a node to NodeBucket& s = bucket_UNSAFE(newNode.get()); auto& nodes = s.nodes; // check if the node is already in the bucket auto it = std::find(nodes.begin(), nodes.end(), newNode); if (it != nodes.end()) { // if it was in the bucket, move it to the last position nodes.splice(nodes.end(), nodes, it); } else { if (nodes.size() < s_bucketSize) { // if it was not there, just add it as a most recently seen node // (i.e. to the end of the list) nodes.push_back(newNode); if (m_nodeEventHandler) m_nodeEventHandler->appendEvent(newNode->id, NodeEntryAdded); } else { // if bucket is full, start eviction process for the least recently seen node nodeToEvict = nodes.front().lock(); // It could have been replaced in addNode(), then weak_ptr is expired. // If so, just add a new one instead of expired if (!nodeToEvict) { nodes.pop_front(); nodes.push_back(newNode); if (m_nodeEventHandler) m_nodeEventHandler->appendEvent(newNode->id, NodeEntryAdded); } } } } if (nodeToEvict) evict(*nodeToEvict, *newNode); } }
unique_ptr<DiscoveryDatagram> DiscoveryDatagram::interpretUDP(bi::udp::endpoint const& _from, bytesConstRef _packet) { unique_ptr<DiscoveryDatagram> decoded; // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) if (_packet.size() < h256::size + Signature::size + 1 + 3) { LOG(g_discoveryWarnLogger::get()) << "Invalid packet (too small) from " << _from.address().to_string() << ":" << _from.port(); return decoded; } bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size)); bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); bytesConstRef signatureBytes(_packet.cropped(h256::size, Signature::size)); bytesConstRef bodyBytes(_packet.cropped(h256::size + Signature::size + 1)); h256 echo(sha3(hashedBytes)); if (!_packet.cropped(0, h256::size).contentsEqual(echo.asBytes())) { LOG(g_discoveryWarnLogger::get()) << "Invalid packet (bad hash) from " << _from.address().to_string() << ":" << _from.port(); return decoded; } Public sourceid(dev::recover(*(Signature const*)signatureBytes.data(), sha3(signedBytes))); if (!sourceid) { LOG(g_discoveryWarnLogger::get()) << "Invalid packet (bad signature) from " << _from.address().to_string() << ":" << _from.port(); return decoded; } switch (signedBytes[0]) { case PingNode::type: decoded.reset(new PingNode(_from, sourceid, echo)); break; case Pong::type: decoded.reset(new Pong(_from, sourceid, echo)); break; case FindNode::type: decoded.reset(new FindNode(_from, sourceid, echo)); break; case Neighbours::type: decoded.reset(new Neighbours(_from, sourceid, echo)); break; default: LOG(g_discoveryWarnLogger::get()) << "Invalid packet (unknown packet type) from " << _from.address().to_string() << ":" << _from.port(); return decoded; } decoded->interpretRLP(bodyBytes); return decoded; }
void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint) { if (_pubk == m_node.address() || !NodeIPEndpoint(_endpoint.address(), _endpoint.port(), _endpoint.port()).isAllowed()) return; shared_ptr<NodeEntry> node = nodeEntry(_pubk); if (!!node && !node->pending) { clog(NodeTableConnect) << "Noting active node:" << _pubk << _endpoint.address().to_string() << ":" << _endpoint.port(); node->endpoint.address = _endpoint.address(); node->endpoint.udpPort = _endpoint.port(); shared_ptr<NodeEntry> contested; { Guard l(x_state); NodeBucket& s = bucket_UNSAFE(node.get()); bool removed = false; s.nodes.remove_if([&node, &removed](weak_ptr<NodeEntry> const& n) { if (n.lock() == node) removed = true; return removed; }); if (s.nodes.size() >= s_bucketSize) { if (removed) clog(NodeTableWarn) << "DANGER: Bucket overflow when swapping node position."; // It's only contested iff nodeentry exists contested = s.nodes.front().lock(); if (!contested) { s.nodes.pop_front(); s.nodes.push_back(node); if (!removed && m_nodeEventHandler) m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); } } else { s.nodes.push_back(node); if (!removed && m_nodeEventHandler) m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); } } if (contested) evict(contested, node); } }
void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint) { if (_pubk == m_node.address()) return; clog(NodeTableNote) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port(); shared_ptr<NodeEntry> node(addNode(_pubk, _endpoint, bi::tcp::endpoint(_endpoint.address(), _endpoint.port()))); // TODO p2p: old bug (maybe gone now) sometimes node is nullptr here if (!!node) { shared_ptr<NodeEntry> contested; { Guard l(x_state); NodeBucket& s = bucket_UNSAFE(node.get()); s.nodes.remove_if([&node](weak_ptr<NodeEntry> n) { if (n.lock() == node) return true; return false; }); if (s.nodes.size() >= s_bucketSize) { // It's only contested iff nodeentry exists contested = s.nodes.front().lock(); if (!contested) { s.nodes.pop_front(); s.nodes.push_back(node); s.touch(); } } else { s.nodes.push_back(node); s.touch(); } } if (contested) evict(contested, node); } }
void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet) { // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) if (_packet.size() < h256::size + Signature::size + 1 + 3) { clog(NodeTableMessageSummary) << "Invalid Message size from " << _from.address().to_string() << ":" << _from.port(); return; } bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size)); h256 hashSigned(sha3(hashedBytes)); if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes())) { clog(NodeTableMessageSummary) << "Invalid Message hash from " << _from.address().to_string() << ":" << _from.port(); return; } bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); // todo: verify sig via known-nodeid and MDC, or, do ping/pong auth if node/endpoint is unknown/untrusted bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size)); Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes))); if (!nodeid) { clog(NodeTableMessageSummary) << "Invalid Message signature from " << _from.address().to_string() << ":" << _from.port(); return; } unsigned packetType = signedBytes[0]; if (packetType && packetType < 4) noteActiveNode(nodeid, _from); bytesConstRef rlpBytes(_packet.cropped(h256::size + Signature::size + 1)); RLP rlp(rlpBytes); try { switch (packetType) { case Pong::type: { // clog(NodeTableMessageSummary) << "Received Pong from " << _from.address().to_string() << ":" << _from.port(); Pong in = Pong::fromBytesConstRef(_from, rlpBytes); // whenever a pong is received, check if it's in m_evictions Guard le(x_evictions); for (auto it = m_evictions.begin(); it != m_evictions.end(); it++) if (it->first.first == nodeid && it->first.second > std::chrono::steady_clock::now()) { if (auto n = nodeEntry(it->second)) dropNode(n); if (auto n = node(it->first.first)) addNode(n); it = m_evictions.erase(it); } break; } case Neighbours::type: { Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes); // clog(NodeTableMessageSummary) << "Received " << in.nodes.size() << " Neighbours from " << _from.address().to_string() << ":" << _from.port(); for (auto n: in.nodes) noteActiveNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port)); break; } case FindNode::type: { // clog(NodeTableMessageSummary) << "Received FindNode from " << _from.address().to_string() << ":" << _from.port(); FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes); vector<shared_ptr<NodeEntry>> nearest = nearestNodeEntries(in.target); static unsigned const nlimit = (m_socketPointer->maxDatagramSize - 11) / 86; for (unsigned offset = 0; offset < nearest.size(); offset += nlimit) { Neighbours out(_from, nearest, offset, nlimit); out.sign(m_secret); m_socketPointer->send(out); } break; } case PingNode::type: { // clog(NodeTableMessageSummary) << "Received PingNode from " << _from.address().to_string() << ":" << _from.port(); PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes); Pong p(_from); p.echo = sha3(rlpBytes); p.sign(m_secret); m_socketPointer->send(p); break; } default: clog(NodeTableWarn) << "Invalid Message, " << hex << packetType << ", received from " << _from.address().to_string() << ":" << dec << _from.port(); return; } } catch (...) { clog(NodeTableWarn) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port(); } }
void NodeTable::onPacketReceived( UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet) { try { unique_ptr<DiscoveryDatagram> packet = DiscoveryDatagram::interpretUDP(_from, _packet); if (!packet) return; if (packet->isExpired()) { LOG(m_logger) << "Expired " << packet->typeName() << " from " << packet->sourceid << "@" << _from; return; } LOG(m_logger) << packet->typeName() << " from " << packet->sourceid << "@" << _from; switch (packet->packetType()) { case Pong::type: { auto const& pong = dynamic_cast<Pong const&>(*packet); auto const& sourceId = pong.sourceid; // validate pong auto const sentPing = m_sentPings.find(sourceId); if (sentPing == m_sentPings.end()) { LOG(m_logger) << "Unexpected PONG from " << _from.address().to_string() << ":" << _from.port(); return; } if (pong.echo != sentPing->second.pingHash) { LOG(m_logger) << "Invalid PONG from " << _from.address().to_string() << ":" << _from.port(); return; } auto const sourceNodeEntry = nodeEntry(sourceId); assert(sourceNodeEntry); sourceNodeEntry->lastPongReceivedTime = RLPXDatagramFace::secondsSinceEpoch(); // Valid PONG received, so we don't want to evict this node, // and we don't need to remember replacement node anymore auto const& optionalReplacementID = sentPing->second.replacementNodeID; if (optionalReplacementID) if (auto replacementNode = nodeEntry(*optionalReplacementID)) dropNode(move(replacementNode)); m_sentPings.erase(sentPing); // update our endpoint address and UDP port DEV_GUARDED(x_nodes) { if ((!m_hostNodeEndpoint || !isAllowedEndpoint(m_hostNodeEndpoint)) && isPublicAddress(pong.destination.address())) m_hostNodeEndpoint.setAddress(pong.destination.address()); m_hostNodeEndpoint.setUdpPort(pong.destination.udpPort()); } break; } case Neighbours::type: { auto const& in = dynamic_cast<Neighbours const&>(*packet); bool expected = false; auto now = chrono::steady_clock::now(); m_sentFindNodes.remove_if([&](NodeIdTimePoint const& _t) noexcept { if (_t.first != in.sourceid) return false; if (now - _t.second < c_reqTimeout) expected = true; return true; }); if (!expected) { cnetdetails << "Dropping unsolicited neighbours packet from " << _from.address(); break; } for (auto const& n : in.neighbours) addNode(Node(n.node, n.endpoint)); break; } case FindNode::type: { auto const& sourceNodeEntry = nodeEntry(packet->sourceid); if (!sourceNodeEntry) { LOG(m_logger) << "Source node (" << packet->sourceid << "@" << _from << ") not found in node table. Ignoring FindNode request."; return; } if (!sourceNodeEntry->lastPongReceivedTime) { LOG(m_logger) << "Unexpected FindNode packet! Endpoint proof hasn't been performed yet."; return; } if (!sourceNodeEntry->hasValidEndpointProof()) { LOG(m_logger) << "Unexpected FindNode packet! Endpoint proof has expired."; return; } auto const& in = dynamic_cast<FindNode const&>(*packet); vector<shared_ptr<NodeEntry>> nearest = nearestNodeEntries(in.target); static unsigned constexpr nlimit = (NodeSocket::maxDatagramSize - 109) / 90; for (unsigned offset = 0; offset < nearest.size(); offset += nlimit) { Neighbours out(_from, nearest, offset, nlimit); out.ts = nextRequestExpirationTime(); LOG(m_logger) << out.typeName() << " to " << in.sourceid << "@" << _from; out.sign(m_secret); if (out.data.size() > 1280) cnetlog << "Sending truncated datagram, size: " << out.data.size(); m_socket->send(out); } break; } case PingNode::type: { auto& in = dynamic_cast<PingNode&>(*packet); in.source.setAddress(_from.address()); in.source.setUdpPort(_from.port()); addNode(Node(in.sourceid, in.source)); Pong p(in.source); LOG(m_logger) << p.typeName() << " to " << in.sourceid << "@" << _from; p.ts = nextRequestExpirationTime(); p.echo = in.echo; p.sign(m_secret); m_socket->send(p); m_allNodes[in.sourceid]->lastPongSentTime = RLPXDatagramFace::secondsSinceEpoch(); break; } } noteActiveNode(packet->sourceid, _from); } catch (std::exception const& _e) { LOG(m_logger) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port() << ": " << _e.what(); } catch (...) { LOG(m_logger) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port(); } }
void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet) { // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) if (_packet.size() < h256::size + Signature::size + 1 + 3) { clog(NodeTableTriviaSummary) << "Invalid message size from " << _from.address().to_string() << ":" << _from.port(); return; } bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size)); h256 hashSigned(sha3(hashedBytes)); if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes())) { clog(NodeTableTriviaSummary) << "Invalid message hash from " << _from.address().to_string() << ":" << _from.port(); return; } bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); // todo: verify sig via known-nodeid and MDC bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size)); Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes))); if (!nodeid) { clog(NodeTableTriviaSummary) << "Invalid message signature from " << _from.address().to_string() << ":" << _from.port(); return; } unsigned packetType = signedBytes[0]; bytesConstRef rlpBytes(_packet.cropped(h256::size + Signature::size + 1)); try { RLP rlp(rlpBytes); switch (packetType) { case Pong::type: { Pong in = Pong::fromBytesConstRef(_from, rlpBytes); // whenever a pong is received, check if it's in m_evictions bool found = false; EvictionTimeout evictionEntry; DEV_GUARDED(x_evictions) for (auto it = m_evictions.begin(); it != m_evictions.end(); ++it) if (it->first.first == nodeid && it->first.second > std::chrono::steady_clock::now()) { found = true; evictionEntry = *it; m_evictions.erase(it); break; } if (found) { if (auto n = nodeEntry(evictionEntry.second)) dropNode(n); if (auto n = nodeEntry(evictionEntry.first.first)) n->pending = false; } else { // if not, check if it's known/pending or a pubk discovery ping if (auto n = nodeEntry(nodeid)) n->pending = false; else { DEV_GUARDED(x_pubkDiscoverPings) { if (!m_pubkDiscoverPings.count(_from.address())) return; // unsolicited pong; don't note node as active m_pubkDiscoverPings.erase(_from.address()); } if (!haveNode(nodeid)) addNode(Node(nodeid, NodeIPEndpoint(_from.address(), _from.port(), _from.port()))); } } // update our endpoint address and UDP port DEV_GUARDED(x_nodes) { if ((!m_node.endpoint || !m_node.endpoint.isAllowed()) && isPublicAddress(in.destination.address)) m_node.endpoint.address = in.destination.address; m_node.endpoint.udpPort = in.destination.udpPort; } clog(NodeTableConnect) << "PONG from " << nodeid << _from; break; } case Neighbours::type: { bool expected = false; auto now = chrono::steady_clock::now(); DEV_GUARDED(x_findNodeTimeout) m_findNodeTimeout.remove_if([&](NodeIdTimePoint const& t) { if (t.first == nodeid && now - t.second < c_reqTimeout) expected = true; else if (t.first == nodeid) return true; return false; }); if (!expected) { clog(NetConnect) << "Dropping unsolicited neighbours packet from " << _from.address(); break; } Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes); for (auto n: in.neighbours) addNode(Node(n.node, n.endpoint)); break; } case FindNode::type: { FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes); if (RLPXDatagramFace::secondsSinceEpoch() > in.ts) { clog(NodeTableTriviaSummary) << "Received expired FindNode from " << _from.address().to_string() << ":" << _from.port(); return; } vector<shared_ptr<NodeEntry>> nearest = nearestNodeEntries(in.target); static unsigned const nlimit = (m_socketPointer->maxDatagramSize - 109) / 90; for (unsigned offset = 0; offset < nearest.size(); offset += nlimit) { Neighbours out(_from, nearest, offset, nlimit); out.sign(m_secret); if (out.data.size() > 1280) clog(NetWarn) << "Sending truncated datagram, size: " << out.data.size(); m_socketPointer->send(out); } break; } case PingNode::type: { PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes); if (in.version < dev::p2p::c_protocolVersion) { if (in.version == 3) { compat::Pong p(in.source); p.echo = sha3(rlpBytes); p.sign(m_secret); m_socketPointer->send(p); } else return; } if (RLPXDatagramFace::secondsSinceEpoch() > in.ts) { clog(NodeTableTriviaSummary) << "Received expired PingNode from " << _from.address().to_string() << ":" << _from.port(); return; } in.source.address = _from.address(); in.source.udpPort = _from.port(); addNode(Node(nodeid, in.source)); Pong p(in.source); p.echo = sha3(rlpBytes); p.sign(m_secret); m_socketPointer->send(p); break; } default: clog(NodeTableWarn) << "Invalid message, " << hex << packetType << ", received from " << _from.address().to_string() << ":" << dec << _from.port(); return; } noteActiveNode(nodeid, _from); } catch (...) { clog(NodeTableWarn) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port(); } }