├── .gitignore ├── .rustfmt.toml ├── packages ├── tokio_krpc │ ├── src │ │ ├── port_type.rs │ │ ├── responses │ │ │ ├── mod.rs │ │ │ ├── node_id_response.rs │ │ │ ├── find_node_response.rs │ │ │ └── get_peers_response.rs │ │ ├── inbound_response_envelope.rs │ │ ├── inbound_query.rs │ │ ├── transaction_id.rs │ │ ├── recv_errors.rs │ │ ├── send_errors.rs │ │ ├── inbound.rs │ │ ├── response_future.rs │ │ ├── send_transport.rs │ │ ├── lib.rs │ │ ├── krpc_node.rs │ │ ├── request_transport.rs │ │ └── active_transactions.rs │ ├── Cargo.toml │ └── tests │ │ └── tests.rs ├── dht_crawler │ ├── src │ │ ├── routing │ │ │ ├── mod.rs │ │ │ ├── token_validator.rs │ │ │ ├── table.rs │ │ │ ├── node.rs │ │ │ └── bucket.rs │ │ ├── lib.rs │ │ ├── addr.rs │ │ ├── errors.rs │ │ └── dht │ │ │ ├── mod.rs │ │ │ └── handler.rs │ └── Cargo.toml ├── routing_table │ ├── src │ │ ├── lib.rs │ │ ├── full_b_tree.rs │ │ ├── generator.rs │ │ ├── transport.rs │ │ ├── routing_table.rs │ │ ├── node_contact_state.rs │ │ └── k_bucket.rs │ ├── Cargo.toml │ └── tests │ │ └── tests.rs └── krpc_encoding │ ├── Cargo.toml │ ├── src │ ├── errors.rs │ ├── booleans.rs │ ├── lib.rs │ ├── node_info.rs │ ├── addr.rs │ ├── node_id.rs │ └── messages.rs │ └── tests │ └── tests.rs ├── Cargo.toml ├── .circleci └── config.yml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity="Crate" 2 | imports_layout="Vertical" 3 | wrap_comments=true -------------------------------------------------------------------------------- /packages/tokio_krpc/src/port_type.rs: -------------------------------------------------------------------------------- 1 | pub enum PortType { 2 | Implied, 3 | Port(u16), 4 | } 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "packages/dht_crawler", 5 | "packages/krpc_encoding", 6 | "packages/tokio_krpc", 7 | "packages/routing_table" 8 | ] 9 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/responses/mod.rs: -------------------------------------------------------------------------------- 1 | mod find_node_response; 2 | mod get_peers_response; 3 | mod node_id_response; 4 | 5 | pub use find_node_response::FindNodeResponse; 6 | pub use get_peers_response::GetPeersResponse; 7 | pub use node_id_response::NodeIDResponse; 8 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/routing/mod.rs: -------------------------------------------------------------------------------- 1 | mod bucket; 2 | mod node; 3 | mod table; 4 | mod token_validator; 5 | 6 | pub use self::{ 7 | node::Node, 8 | table::{ 9 | FindNodeResult, 10 | RoutingTable, 11 | }, 12 | token_validator::TokenValidator, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/routing_table/src/lib.rs: -------------------------------------------------------------------------------- 1 | // TODO: Tests 2 | // TODO: Docs 3 | 4 | #![feature(generators, generator_trait)] 5 | #![feature(error_generic_member_access, provide_any)] 6 | 7 | mod full_b_tree; 8 | mod generator; 9 | mod k_bucket; 10 | mod node_contact_state; 11 | mod routing_table; 12 | mod transport; 13 | 14 | pub use crate::routing_table::RoutingTable; 15 | -------------------------------------------------------------------------------- /packages/routing_table/src/full_b_tree.rs: -------------------------------------------------------------------------------- 1 | /// A node in a binary tree where every node has 0 or 2 children. 2 | pub enum FullBTreeNode { 3 | Leaf(T), 4 | Inner(Box>), 5 | } 6 | 7 | // todo: this representation is kinda silly because the tree is always a list 8 | // basically 9 | pub struct FullBTreeInnerNode { 10 | pub left: FullBTreeNode, 11 | pub right: FullBTreeNode, 12 | } 13 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/inbound_response_envelope.rs: -------------------------------------------------------------------------------- 1 | use krpc_encoding as proto; 2 | 3 | /// Inbound response sent from another node associated with an earlier query 4 | /// originating from this node 5 | pub struct InboundResponseEnvelope { 6 | pub transaction_id: Vec, 7 | pub response: ResponseType, 8 | } 9 | 10 | pub enum ResponseType { 11 | Error { error: proto::KRPCError }, 12 | Response { response: proto::Response }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/tokio_krpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_krpc" 3 | version = "0.1.0" 4 | authors = ["Martin Charles "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | byteorder = "1.2.6" 9 | bytes = "0.4.10" 10 | rand = "0.5.5" 11 | thiserror = "1.0.38" 12 | tokio = { version = "1.23.0", features = ["net"] } 13 | futures = "0.3.25" 14 | futures-util = "0.3.25" 15 | krpc_encoding = { path = "../krpc_encoding" } 16 | tracing = "0.1" 17 | 18 | [dev-dependencies] 19 | tokio = { version = "1.23.0", features = ["net", "macros", "rt"] } 20 | -------------------------------------------------------------------------------- /packages/krpc_encoding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "krpc_encoding" 3 | version = "0.1.0" 4 | authors = ["Martin Charles "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = "1.0.152" 9 | serde_derive = "1.0.152" 10 | serde_bencode = { git = "https://github.com/0xcaff/serde-bencode.git", rev = "0c1e6f4672c925c629b84fab2b66b24ec9f3458e" } 11 | serde_bytes = "0.10.4" 12 | thiserror = "1.0.38" 13 | byteorder = "1.2.6" 14 | rand = "0.5.5" 15 | hex = "0.3.2" 16 | num-bigint = "0.2.0" 17 | num-traits = "0.2.6" 18 | 19 | [dev-dependencies] 20 | serde_test = "1.0.79" 21 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/responses/node_id_response.rs: -------------------------------------------------------------------------------- 1 | use crate::send_errors::{ 2 | ErrorKind, 3 | Result, 4 | }; 5 | 6 | use krpc_encoding::{ 7 | self as proto, 8 | NodeID, 9 | }; 10 | 11 | pub struct NodeIDResponse; 12 | 13 | impl NodeIDResponse { 14 | pub fn from_response(response: proto::Response) -> Result { 15 | Ok(match response { 16 | proto::Response::OnlyID { id } => id, 17 | got => Err(ErrorKind::InvalidResponseType { 18 | expected: "NodeIDResponse", 19 | got, 20 | })?, 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | aliases: 4 | - &save_cargo_cache 5 | key: cargo-{{ checksum "Cargo.lock" }} 6 | paths: 7 | - target 8 | - ~/.cargo 9 | 10 | - &restore_cargo_cache 11 | keys: 12 | - cargo-{{ checksum "Cargo.lock" }} 13 | 14 | jobs: 15 | test: 16 | docker: 17 | - image: rustlang/rust:nightly 18 | 19 | steps: 20 | - checkout 21 | 22 | - restore_cache: *restore_cargo_cache 23 | 24 | - run: 25 | name: Tests 26 | command: cargo test 27 | 28 | - save_cache: *save_cargo_cache 29 | 30 | workflows: 31 | version: 2 32 | test: 33 | jobs: 34 | - test 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dht-crawler 2 | 3 | ![CircleCI branch](https://img.shields.io/circleci/project/github/0xcaff/dht-crawler/master.svg) 4 | 5 | A tool to collect information about nodes and info-hashes in the DHT. Built 6 | using [tokio]. 7 | 8 | ## Running 9 | 10 | To run, install a [rust development environment][dev] and run 11 | 12 | cargo run 13 | 14 | to start crawling. 15 | 16 | ## Tests 17 | 18 | There are a number of tests in the project to make sure things work. After 19 | setting up rust, just run: 20 | 21 | cargo test 22 | 23 | [dev]: https://doc.rust-lang.org/1.27.2/book/second-edition/ch01-01-installation.html 24 | [tokio]: tokio.rs 25 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/inbound_query.rs: -------------------------------------------------------------------------------- 1 | use krpc_encoding::{ 2 | self as proto, 3 | Query, 4 | }; 5 | 6 | /// Inbound query originating from another node 7 | #[derive(Debug)] 8 | pub struct InboundQuery { 9 | pub transaction_id: Vec, 10 | pub version: Option>, 11 | pub query: Query, 12 | pub read_only: bool, 13 | } 14 | 15 | impl InboundQuery { 16 | pub fn new(transaction_id: Vec, query: proto::Query, read_only: bool) -> InboundQuery { 17 | InboundQuery { 18 | transaction_id, 19 | version: None, 20 | query, 21 | read_only, 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Please note the terminology used in this library to avoid confusion. A 2 | //! "peer" is a client/server listening on a TCP port that implements the 3 | //! BitTorrent protocol. A "node" is a client/server listening on a UDP port 4 | //! implementing the distributed hash table protocol. The DHT is composed of 5 | //! nodes and stores the location of peers. BitTorrent clients include a DHT 6 | //! node, which is used to contact other nodes in the DHT to get the location of 7 | //! peers to download from using the BitTorrent protocol. 8 | 9 | pub mod addr; 10 | pub mod dht; 11 | pub mod errors; 12 | pub mod routing; 13 | 14 | pub use crate::dht::Dht; 15 | -------------------------------------------------------------------------------- /packages/dht_crawler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dht_crawler" 3 | version = "0.1.0" 4 | authors = ["Martin Charles "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | byteorder = "1.2.6" 9 | failure_derive = "0.1.2" 10 | failure = "0.1.2" 11 | tokio = { version = "1.23.0", features = ["net"] } 12 | futures = "0.3.25" 13 | futures-util = "0.3.25" 14 | bytes = "0.4.10" 15 | chrono = "0.4.6" 16 | rand = "0.5.5" 17 | num-bigint = "0.2.0" 18 | num-traits = "0.2.6" 19 | sha1 = "0.10.5" 20 | krpc_encoding = { path = "../krpc_encoding" } 21 | tokio_krpc = { path = "../tokio_krpc" } 22 | 23 | [dev-dependencies] 24 | tokio = { version = "1.23.0", features = ["net", "macros", "rt"] } 25 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/errors.rs: -------------------------------------------------------------------------------- 1 | use serde_bencode::Error as BencodeError; 2 | use std::backtrace::Backtrace; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum ErrorKind { 7 | #[error("error while encoding message")] 8 | EncodeError { 9 | #[source] 10 | cause: BencodeError, 11 | }, 12 | 13 | #[error("error while decoding message")] 14 | DecodeError { 15 | #[source] 16 | cause: BencodeError, 17 | }, 18 | } 19 | 20 | pub type Result = std::result::Result; 21 | 22 | #[derive(Debug, Error)] 23 | #[error("{}", inner)] 24 | pub struct Error { 25 | #[from] 26 | inner: ErrorKind, 27 | 28 | backtrace: Backtrace, 29 | } 30 | -------------------------------------------------------------------------------- /packages/routing_table/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "routing_table" 3 | version = "0.1.0" 4 | authors = ["Martin Charles "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | chrono = "0.4.6" 9 | futures = "0.3.25" 10 | futures-util = "0.3.25" 11 | tokio = { version = "1.23.0", features = ["net", "time"] } 12 | num-bigint = "0.2.0" 13 | async-recursion = "1.0.0" 14 | tokio_krpc = { path = "../tokio_krpc" } 15 | krpc_encoding = { path = "../krpc_encoding" } 16 | log = { version = "0.4.17", features = ["kv_unstable_serde"] } 17 | tracing = "0.1.37" 18 | serde = "1.0.152" 19 | thiserror = "1.0.38" 20 | 21 | [dev-dependencies] 22 | tokio = { version = "1.23.0", features = ["net", "macros", "rt"] } 23 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/responses/find_node_response.rs: -------------------------------------------------------------------------------- 1 | use crate::send_errors::{ 2 | ErrorKind, 3 | Result, 4 | }; 5 | 6 | use krpc_encoding::{ 7 | self as proto, 8 | NodeID, 9 | NodeInfo, 10 | }; 11 | 12 | pub struct FindNodeResponse { 13 | pub id: NodeID, 14 | pub nodes: Vec, 15 | } 16 | 17 | impl FindNodeResponse { 18 | pub fn from_response(response: proto::Response) -> Result { 19 | Ok(match response { 20 | proto::Response::NextHop { id, nodes, .. } => FindNodeResponse { id, nodes }, 21 | got => Err(ErrorKind::InvalidResponseType { 22 | expected: "FindNodeResponse (NextHop)", 23 | got, 24 | })?, 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/booleans.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::{ 3 | self, 4 | Visitor, 5 | }, 6 | Deserializer, 7 | }; 8 | use std::fmt; 9 | 10 | pub fn is_false(b: &bool) -> bool { 11 | return !b; 12 | } 13 | 14 | pub fn deserialize<'de, D>(deserializer: D) -> Result 15 | where 16 | D: Deserializer<'de>, 17 | { 18 | deserializer.deserialize_i64(BooleanVisitor) 19 | } 20 | 21 | struct BooleanVisitor; 22 | 23 | impl<'de> Visitor<'de> for BooleanVisitor { 24 | type Value = bool; 25 | 26 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | formatter.write_str("a number") 28 | } 29 | 30 | fn visit_i64(self, v: i64) -> Result 31 | where 32 | E: de::Error, 33 | { 34 | Ok(v == 1) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/addr.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{ 2 | ErrorKind, 3 | Result, 4 | }; 5 | use std::net::{ 6 | SocketAddr, 7 | SocketAddrV4, 8 | ToSocketAddrs, 9 | }; 10 | 11 | pub trait AsV4Address { 12 | fn into_v4(self) -> Result; 13 | } 14 | 15 | impl AsV4Address for SocketAddr { 16 | fn into_v4(self) -> Result { 17 | match self { 18 | SocketAddr::V4(addr) => Ok(addr), 19 | SocketAddr::V6(addr) => Err(ErrorKind::UnsupportedAddressTypeError { addr })?, 20 | } 21 | } 22 | } 23 | 24 | pub trait IntoSocketAddr { 25 | fn into_addr(self) -> SocketAddr; 26 | } 27 | 28 | impl IntoSocketAddr for T 29 | where 30 | T: ToSocketAddrs, 31 | { 32 | fn into_addr(self) -> SocketAddr { 33 | self.to_socket_addrs().unwrap().nth(0).unwrap() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/transaction_id.rs: -------------------------------------------------------------------------------- 1 | use crate::recv_errors::{ 2 | ErrorKind, 3 | Result, 4 | }; 5 | use byteorder::{ 6 | NetworkEndian, 7 | ReadBytesExt, 8 | }; 9 | 10 | /// Transaction identifier used for requests originating from this client. 11 | /// Requests originating from other clients use a `Vec` to represent the 12 | /// transaction id. 13 | pub type TransactionId = u32; 14 | 15 | /// Extracts a [TransactionId] from a response to a request originating from 16 | /// this client. If the transaction id is malformed, returns an error. 17 | pub fn parse_originating_transaction_id(mut bytes: &[u8]) -> Result { 18 | if bytes.len() != 4 { 19 | Err(ErrorKind::InvalidResponseTransactionId)?; 20 | } 21 | 22 | Ok(bytes 23 | .read_u32::() 24 | .map_err(|_cause| ErrorKind::InvalidResponseTransactionId)?) 25 | } 26 | -------------------------------------------------------------------------------- /packages/routing_table/src/generator.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{ 3 | Generator, 4 | GeneratorState, 5 | }, 6 | pin::Pin, 7 | }; 8 | 9 | pub struct GeneratorToIterator(G); 10 | 11 | impl Iterator for GeneratorToIterator 12 | where 13 | G: Generator, 14 | { 15 | type Item = G::Yield; 16 | 17 | fn next(&mut self) -> Option { 18 | let me = unsafe { Pin::new_unchecked(&mut self.0) }; 19 | match me.resume(()) { 20 | GeneratorState::Yielded(x) => Some(x), 21 | GeneratorState::Complete(_) => None, 22 | } 23 | } 24 | } 25 | 26 | pub trait GeneratorExt: Generator + Sized { 27 | fn iter(self) -> GeneratorToIterator; 28 | } 29 | 30 | impl GeneratorExt for G 31 | where 32 | G: Generator, 33 | { 34 | fn iter(self) -> GeneratorToIterator { 35 | GeneratorToIterator(self) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/recv_errors.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | backtrace::Backtrace, 3 | io, 4 | }; 5 | use thiserror::Error; 6 | 7 | // TODO: Review ErrorKinds 8 | #[derive(Error, Debug)] 9 | pub enum ErrorKind { 10 | #[error("failed to receive inbound message")] 11 | FailedToReceiveMessage { 12 | #[source] 13 | cause: io::Error, 14 | }, 15 | 16 | #[error("invalid transaction id")] 17 | InvalidResponseTransactionId, 18 | 19 | #[error("failed to parse inbound message")] 20 | ParseInboundMessageError { 21 | #[source] 22 | cause: krpc_encoding::errors::Error, 23 | }, 24 | 25 | #[error( 26 | "received response for an unknown transaction transaction_id={}", 27 | transaction_id 28 | )] 29 | UnknownTransactionReceived { transaction_id: u32 }, 30 | } 31 | 32 | pub type Result = std::result::Result; 33 | 34 | #[derive(Error, Debug)] 35 | #[error("{}", inner)] 36 | pub struct Error { 37 | #[from] 38 | inner: ErrorKind, 39 | backtrace: Backtrace, 40 | } 41 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/send_errors.rs: -------------------------------------------------------------------------------- 1 | use krpc_encoding as proto; 2 | use std::{ 3 | backtrace::Backtrace, 4 | io, 5 | }; 6 | use thiserror::Error; 7 | 8 | // TODO: Review ErrorKinds 9 | #[derive(Error, Debug)] 10 | pub enum ErrorKind { 11 | #[error("received an error message from node {}", error)] 12 | ReceivedKRPCError { error: proto::KRPCError }, 13 | 14 | #[error("invalid response type, expected {} got {:?}", expected, got)] 15 | InvalidResponseType { 16 | expected: &'static str, 17 | got: krpc_encoding::Response, 18 | }, 19 | 20 | #[error("failed to send")] 21 | SendError { 22 | #[source] 23 | cause: io::Error, 24 | }, 25 | 26 | #[error("failed to encode message for sending")] 27 | SendEncodingError { 28 | #[source] 29 | cause: krpc_encoding::errors::Error, 30 | }, 31 | 32 | #[error("transaction state missing for transaction_id={}", transaction_id)] 33 | UnknownTransactionPolled { transaction_id: u32 }, 34 | } 35 | 36 | pub type Result = std::result::Result; 37 | 38 | #[derive(Error, Debug)] 39 | #[error("{}", inner)] 40 | pub struct Error { 41 | #[from] 42 | inner: ErrorKind, 43 | backtrace: Backtrace, 44 | } 45 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/responses/get_peers_response.rs: -------------------------------------------------------------------------------- 1 | use crate::send_errors::{ 2 | ErrorKind, 3 | Result, 4 | }; 5 | 6 | use krpc_encoding::{ 7 | self as proto, 8 | Addr, 9 | NodeID, 10 | NodeInfo, 11 | }; 12 | use std::net::SocketAddrV4; 13 | 14 | pub struct GetPeersResponse { 15 | pub id: NodeID, 16 | pub token: Option>, 17 | pub message_type: GetPeersResponseType, 18 | } 19 | 20 | impl GetPeersResponse { 21 | pub fn from_response(response: proto::Response) -> Result { 22 | Ok(match response { 23 | proto::Response::GetPeers { id, token, peers } => GetPeersResponse { 24 | id, 25 | token, 26 | message_type: GetPeersResponseType::Peers( 27 | peers.into_iter().map(Addr::into).collect(), 28 | ), 29 | }, 30 | proto::Response::NextHop { id, token, nodes } => GetPeersResponse { 31 | id, 32 | token, 33 | message_type: GetPeersResponseType::NextHop(nodes), 34 | }, 35 | got => Err(ErrorKind::InvalidResponseType { 36 | expected: "GetPeersResponse (GetPeers or NextHop)", 37 | got, 38 | })?, 39 | }) 40 | } 41 | } 42 | 43 | pub enum GetPeersResponseType { 44 | Peers(Vec), 45 | NextHop(Vec), 46 | } 47 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/inbound.rs: -------------------------------------------------------------------------------- 1 | //! Handle incoming responses and queries from other nodes. 2 | 3 | use crate::recv_errors::{ 4 | Error, 5 | ErrorKind, 6 | Result, 7 | }; 8 | use futures::{ 9 | stream, 10 | TryStream, 11 | }; 12 | use krpc_encoding::Envelope; 13 | use std::{ 14 | self, 15 | net::SocketAddr, 16 | sync::Arc, 17 | }; 18 | use tokio::{ 19 | self, 20 | net::UdpSocket, 21 | }; 22 | 23 | pub fn receive_inbound_messages( 24 | recv_socket: Arc, 25 | ) -> impl TryStream { 26 | let recv_buffer = [0 as u8; 1024]; 27 | 28 | stream::unfold( 29 | (recv_socket, recv_buffer), 30 | |(recv_socket, mut recv_buffer)| async move { 31 | let result = receive_inbound_message(recv_socket.clone(), &mut recv_buffer).await; 32 | 33 | Some((result, (recv_socket, recv_buffer))) 34 | }, 35 | ) 36 | } 37 | 38 | async fn receive_inbound_message( 39 | recv_socket: Arc, 40 | recv_buffer: &mut [u8; 1024], 41 | ) -> Result<(Envelope, SocketAddr)> { 42 | let (size, from_addr) = recv_socket 43 | .recv_from(recv_buffer) 44 | .await 45 | .map_err(|cause| ErrorKind::FailedToReceiveMessage { cause })?; 46 | 47 | let envelope = Envelope::decode(&recv_buffer[..size]) 48 | .map_err(|cause| ErrorKind::ParseInboundMessageError { cause })?; 49 | 50 | Ok((envelope, from_addr)) 51 | } 52 | -------------------------------------------------------------------------------- /packages/routing_table/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{ 2 | future, 3 | TryStreamExt, 4 | StreamExt, 5 | }; 6 | use krpc_encoding::NodeID; 7 | use routing_table::RoutingTable; 8 | use std::{ 9 | error::Error, 10 | net::{ 11 | SocketAddr, 12 | ToSocketAddrs, 13 | }, 14 | str::FromStr, 15 | }; 16 | use tokio::{ 17 | net::UdpSocket, 18 | spawn, 19 | }; 20 | use tokio_krpc::{ 21 | KRPCNode, 22 | RequestTransport, 23 | }; 24 | 25 | #[tokio::test] 26 | async fn bootstrap() -> Result<(), Box> { 27 | let id = NodeID::random(); 28 | 29 | let remote = "router.bittorrent.com:6881" 30 | .to_socket_addrs() 31 | .unwrap() 32 | .nth(0) 33 | .unwrap(); 34 | 35 | let remote_v4 = match remote { 36 | SocketAddr::V4(v4) => v4, 37 | SocketAddr::V6(_) => panic!("not v4"), 38 | }; 39 | 40 | let socket = UdpSocket::bind(SocketAddr::from_str("0.0.0.0:0")?).await?; 41 | 42 | let node = KRPCNode::new(socket); 43 | let (send_transport, request_stream) = node.serve(); 44 | let request_transport = RequestTransport::new(id.clone(), send_transport); 45 | 46 | spawn( 47 | request_stream 48 | .map_err(|err| println!("Error in Request Stream: {}", err)) 49 | .for_each(|_| future::ready(())), 50 | ); 51 | 52 | let mut routing_table = RoutingTable::new(id, request_transport); 53 | 54 | routing_table.bootstrap(remote_v4).await; 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /packages/tokio_krpc/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use futures::{ 2 | future, 3 | StreamExt, 4 | TryStreamExt, 5 | }; 6 | use krpc_encoding::NodeID; 7 | use std::{ 8 | net::{ 9 | SocketAddr, 10 | ToSocketAddrs, 11 | }, 12 | str::FromStr, 13 | }; 14 | use tokio::{ 15 | net::UdpSocket, 16 | spawn, 17 | }; 18 | use tokio_krpc::{ 19 | KRPCNode, 20 | RequestTransport, 21 | }; 22 | 23 | type Error = Box; 24 | 25 | #[tokio::test] 26 | async fn ping() -> Result<(), Error> { 27 | let bind = SocketAddr::from_str("0.0.0.0:0")?; 28 | let remote = "router.bittorrent.com:6881" 29 | .to_socket_addrs() 30 | .unwrap() 31 | .nth(0) 32 | .unwrap(); 33 | 34 | let remote_v4 = match remote { 35 | SocketAddr::V4(v4) => v4, 36 | SocketAddr::V6(_) => panic!("not v4"), 37 | }; 38 | 39 | let id = NodeID::random(); 40 | let socket = UdpSocket::bind(&bind).await?; 41 | let recv_transport = KRPCNode::new(socket); 42 | let (send_transport, request_stream) = recv_transport.serve(); 43 | let request_transport = RequestTransport::new(id, send_transport); 44 | 45 | spawn( 46 | request_stream 47 | .map_err(|err| println!("Error in Request Stream: {}", err)) 48 | .for_each(|_| future::ready(())), 49 | ); 50 | 51 | let response = request_transport.ping(remote_v4).await?; 52 | 53 | assert_ne!(response, b"0000000000000000000000000000000000000000".into()); 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/response_future.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | active_transactions::ActiveTransactions, 3 | inbound_response_envelope::{ 4 | InboundResponseEnvelope, 5 | ResponseType, 6 | }, 7 | send_errors::{ 8 | ErrorKind, 9 | Result, 10 | }, 11 | transaction_id::TransactionId, 12 | }; 13 | use futures::TryFutureExt; 14 | use std::future::Future; 15 | 16 | use krpc_encoding as proto; 17 | use std::{ 18 | pin::Pin, 19 | task::{ 20 | Context, 21 | Poll, 22 | }, 23 | }; 24 | 25 | /// A future which resolves when the response for a transaction appears in a 26 | /// peer's transaction map. 27 | pub struct ResponseFuture { 28 | transaction_id: TransactionId, 29 | transactions: ActiveTransactions, 30 | } 31 | 32 | impl ResponseFuture { 33 | pub async fn wait_for_tx( 34 | transaction_id: TransactionId, 35 | transactions: ActiveTransactions, 36 | ) -> Result { 37 | transactions.add_transaction(transaction_id); 38 | let envelope = ResponseFuture::new(transaction_id, transactions) 39 | .into_future() 40 | .await?; 41 | 42 | match envelope.response { 43 | ResponseType::Response { response } => Ok(response), 44 | ResponseType::Error { error } => Err(ErrorKind::ReceivedKRPCError { error })?, 45 | } 46 | } 47 | 48 | fn new(transaction_id: TransactionId, transactions: ActiveTransactions) -> ResponseFuture { 49 | ResponseFuture { 50 | transaction_id, 51 | transactions, 52 | } 53 | } 54 | } 55 | 56 | impl Future for ResponseFuture { 57 | type Output = Result; 58 | 59 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 60 | self.transactions 61 | .poll_response(self.transaction_id, cx.waker()) 62 | } 63 | } 64 | 65 | impl Drop for ResponseFuture { 66 | fn drop(&mut self) { 67 | self.transactions.drop_transaction(self.transaction_id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(error_generic_member_access, provide_any)] 2 | 3 | //! Library for serializing and de-serializing krpc messages defined in 4 | //! [BEP-0005]. 5 | //! 6 | //! # Encode 7 | //! 8 | //! ``` 9 | //! use krpc_encoding::{Envelope, Message, Query}; 10 | //! 11 | //! # fn main() -> krpc_encoding::errors::Result<()> { 12 | //! let message = Envelope { 13 | //! ip: None, 14 | //! transaction_id: b"aa".to_vec(), 15 | //! version: None, 16 | //! message_type: Message::Query { 17 | //! query: Query::Ping { 18 | //! id: b"abcdefghij0123456789".into(), 19 | //! }, 20 | //! }, 21 | //! read_only: false, 22 | //! }; 23 | //! 24 | //! let encoded = message.encode()?; 25 | //! 26 | //! assert_eq!( 27 | //! encoded[..], 28 | //! b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe"[..], 29 | //! ); 30 | //! # Ok(()) 31 | //! # } 32 | //! ``` 33 | //! 34 | //! # Decode 35 | //! 36 | //! ``` 37 | //! use krpc_encoding::{Envelope, Query, Message}; 38 | //! 39 | //! # fn main() -> krpc_encoding::errors::Result<()> { 40 | //! let encoded = b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe"; 41 | //! 42 | //! assert_eq!( 43 | //! Envelope::decode(encoded)?, 44 | //! Envelope { 45 | //! ip: None, 46 | //! transaction_id: b"aa".to_vec(), 47 | //! version: None, 48 | //! message_type: Message::Query { 49 | //! query: Query::Ping { 50 | //! id: b"abcdefghij0123456789".into(), 51 | //! }, 52 | //! }, 53 | //! read_only: false, 54 | //! }, 55 | //! ); 56 | //! 57 | //! # Ok(()) 58 | //! # } 59 | //! ``` 60 | //! 61 | //! [BEP-0005]: https://www.bittorrent.org/beps/bep_0005.html 62 | 63 | mod addr; 64 | mod booleans; 65 | pub mod errors; 66 | mod messages; 67 | mod node_id; 68 | mod node_info; 69 | 70 | pub use self::{ 71 | addr::{ 72 | to_bytes as addr_to_bytes, 73 | Addr, 74 | }, 75 | messages::{ 76 | Envelope, 77 | KRPCError, 78 | Message, 79 | Query, 80 | Response, 81 | }, 82 | node_id::{ 83 | NodeID, 84 | NODE_ID_SIZE_BITS, 85 | }, 86 | node_info::NodeInfo, 87 | }; 88 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/send_transport.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | active_transactions::ActiveTransactions, 3 | response_future::ResponseFuture, 4 | send_errors::{ 5 | ErrorKind, 6 | Result, 7 | }, 8 | transaction_id::TransactionId, 9 | }; 10 | use futures::lock::Mutex; 11 | use krpc_encoding::{ 12 | self as proto, 13 | Envelope, 14 | Message, 15 | Query, 16 | }; 17 | use std::{ 18 | net::SocketAddr, 19 | sync::Arc, 20 | }; 21 | use tokio::net::UdpSocket; 22 | 23 | /// Low-level wrapper around a UDP socket for sending KRPC queries and 24 | /// responses. 25 | pub struct SendTransport { 26 | socket: Mutex>, 27 | transactions: ActiveTransactions, 28 | } 29 | 30 | impl SendTransport { 31 | pub(crate) fn new(socket: Arc, transactions: ActiveTransactions) -> SendTransport { 32 | SendTransport { 33 | socket: Mutex::new(socket), 34 | transactions, 35 | } 36 | } 37 | 38 | /// Encodes and sends `message` to `address` without waiting for a response. 39 | pub async fn send(&self, address: SocketAddr, message: Envelope) -> Result<()> { 40 | let encoded = message 41 | .encode() 42 | .map_err(|cause| ErrorKind::SendEncodingError { cause })?; 43 | 44 | let socket = self.socket.lock().await; 45 | 46 | socket 47 | .send_to(&encoded, &address) 48 | .await 49 | .map_err(|cause| ErrorKind::SendError { cause })?; 50 | 51 | Ok(()) 52 | } 53 | 54 | pub async fn request(&self, address: SocketAddr, query: Query) -> Result { 55 | let transaction_id = Self::random_transaction_id(); 56 | 57 | let envelope = Envelope { 58 | ip: None, 59 | transaction_id: transaction_id.to_be_bytes().to_vec(), 60 | version: None, 61 | message_type: Message::Query { query }, 62 | read_only: false, 63 | }; 64 | 65 | self.send(address, envelope).await?; 66 | 67 | Ok(ResponseFuture::wait_for_tx(transaction_id, self.transactions.clone()).await?) 68 | } 69 | 70 | fn random_transaction_id() -> TransactionId { 71 | rand::random::() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(error_generic_member_access, provide_any)] 2 | 3 | //! KRPC protocol built on top of `tokio`. 4 | //! 5 | //! # Send Only KRPC Node 6 | //! 7 | //! ``` 8 | //! use std::net::{SocketAddrV4, SocketAddr}; 9 | //! use std::error::Error; 10 | //! use futures::{future, StreamExt, TryStreamExt}; 11 | //! use tokio::{net::UdpSocket}; 12 | //! 13 | //! use tokio_krpc::{KRPCNode, RequestTransport}; 14 | //! use krpc_encoding::NodeID; 15 | //! use tokio::spawn; 16 | //! 17 | //! #[tokio::main(flavor = "current_thread")] 18 | //! async fn main() -> Result<(), Box> { 19 | //! let bind_addr: SocketAddrV4 = "0.0.0.0:0".parse()?; 20 | //! let socket = UdpSocket::bind::(bind_addr).await?; 21 | //! let node = KRPCNode::new(socket); 22 | //! let node_id = NodeID::random(); 23 | //! let (send_transport, inbound_requests) = node.serve(); 24 | //! let request_transport = RequestTransport::new(node_id, send_transport); 25 | //! 26 | //! spawn( 27 | //! inbound_requests 28 | //! .map_err(|err| println!("Error in Inbound Requests: {}", err)) 29 | //! .for_each(|_| future::ready(())), 30 | //! ); 31 | //! 32 | //! let bootstrap_node_addr: SocketAddrV4 = "67.215.246.10:6881".parse()?; 33 | //! let response = request_transport.ping(bootstrap_node_addr).await?; 34 | //! 35 | //! println!("{:?}", response); 36 | //! 37 | //! Ok(()) 38 | //! } 39 | //! ``` 40 | 41 | // TODO: Not Sold on SendTransport Name 42 | // TODO: Consider Moving Requests into Structs 43 | // TODO: Consider Moving Responses + PortType into responses module 44 | // TODO: Consider sharing response + request types between inbound and outbound 45 | // TODO: Write Docs for responses module 46 | 47 | mod active_transactions; 48 | mod inbound; 49 | mod inbound_query; 50 | mod inbound_response_envelope; 51 | mod krpc_node; 52 | mod port_type; 53 | pub mod recv_errors; 54 | mod request_transport; 55 | mod response_future; 56 | pub mod responses; 57 | pub mod send_errors; 58 | mod send_transport; 59 | mod transaction_id; 60 | 61 | pub use self::{ 62 | inbound_query::InboundQuery, 63 | krpc_node::KRPCNode, 64 | port_type::PortType, 65 | request_transport::RequestTransport, 66 | send_transport::SendTransport, 67 | }; 68 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/routing/token_validator.rs: -------------------------------------------------------------------------------- 1 | use krpc_encoding as proto; 2 | use rand; 3 | use sha1::{ 4 | digest::FixedOutput, 5 | Digest, 6 | Sha1, 7 | }; 8 | use std::net::SocketAddrV4; 9 | 10 | /// Generates and validates tokens. A token generated with 11 | /// [`TokenValidator::generate_token`] is valid until 12 | /// [`TokenValidator::rotate_tokens`] is called twice. 13 | /// 14 | /// ``` 15 | /// # use std::net::SocketAddrV4; 16 | /// # use dht_crawler::routing::TokenValidator; 17 | /// let mut validator = TokenValidator::new(); 18 | /// let addr: SocketAddrV4 = "129.21.63.170:34238".parse().unwrap(); 19 | /// 20 | /// let token = validator.generate_token(&addr); 21 | /// assert_eq!(true, validator.verify_token(&addr, &token)); 22 | /// 23 | /// validator.rotate_tokens(); 24 | /// assert_eq!(true, validator.verify_token(&addr, &token)); 25 | /// 26 | /// validator.rotate_tokens(); 27 | /// assert_eq!(false, validator.verify_token(&addr, &token)); 28 | /// ``` 29 | pub struct TokenValidator { 30 | /// Secret used when generating tokens for `get_peers` and `announce_peer`. 31 | token_secret: [u8; 4], 32 | 33 | /// Last secret. Tokens generated with this secret are also valid. 34 | last_token_secret: [u8; 4], 35 | } 36 | 37 | impl TokenValidator { 38 | pub fn new() -> TokenValidator { 39 | TokenValidator { 40 | token_secret: rand::random(), 41 | last_token_secret: rand::random(), 42 | } 43 | } 44 | 45 | /// Generates a token for `addr`. This token will be valid 46 | pub fn generate_token(&self, addr: &SocketAddrV4) -> [u8; 20] { 47 | generate_token(addr, &self.token_secret) 48 | } 49 | 50 | pub fn verify_token(&self, addr: &SocketAddrV4, token: &[u8]) -> bool { 51 | // This is vulnerable to a side-channel attack. 52 | generate_token(addr, &self.token_secret) == token 53 | || generate_token(addr, &self.last_token_secret) == token 54 | } 55 | 56 | pub fn rotate_tokens(&mut self) { 57 | let new_secret: [u8; 4] = rand::random(); 58 | self.last_token_secret = self.token_secret; 59 | self.token_secret = new_secret; 60 | } 61 | } 62 | 63 | /// Generates a token given an address and secret. 64 | fn generate_token(addr: &SocketAddrV4, secret: &[u8; 4]) -> [u8; 20] { 65 | let mut hasher = Sha1::new(); 66 | 67 | let addr_bytes = proto::addr_to_bytes(addr); 68 | 69 | hasher.update(&addr_bytes); 70 | hasher.update(secret); 71 | 72 | hasher.finalize_fixed().into() 73 | } 74 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/krpc_node.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | active_transactions::ActiveTransactions, 3 | inbound::receive_inbound_messages, 4 | inbound_response_envelope::{ 5 | InboundResponseEnvelope, 6 | ResponseType, 7 | }, 8 | recv_errors::Error, 9 | InboundQuery, 10 | SendTransport, 11 | }; 12 | use futures::{ 13 | future, 14 | Stream, 15 | TryStreamExt, 16 | }; 17 | use krpc_encoding::Message; 18 | use std::{ 19 | self, 20 | net::SocketAddr, 21 | sync::Arc, 22 | }; 23 | use tokio::{ 24 | self, 25 | net::UdpSocket, 26 | }; 27 | 28 | /// Handles making queries to other nodes, receiving responses and processing 29 | /// queries from other nodes 30 | pub struct KRPCNode { 31 | socket: Arc, 32 | transactions: ActiveTransactions, 33 | } 34 | 35 | impl KRPCNode { 36 | pub fn new(socket: UdpSocket) -> KRPCNode { 37 | let transactions = ActiveTransactions::new(); 38 | 39 | KRPCNode { 40 | socket: Arc::new(socket), 41 | transactions, 42 | } 43 | } 44 | 45 | // TODO: Separate the returned stream 46 | 47 | /// Starts listening for inbound queries and responses. The stream **MUST** 48 | /// be polled to process responses to outbound requests. 49 | /// 50 | /// # Returns 51 | /// A handle to send messages to other nodes and a stream of inbound 52 | /// requests. Errors occur on the stream whenever an error occurs while 53 | /// processing an inbound message. 54 | pub fn serve( 55 | self, 56 | ) -> ( 57 | SendTransport, 58 | impl Stream>, 59 | ) { 60 | let transactions = self.transactions.clone(); 61 | 62 | let recv_half = self.socket.clone(); 63 | let send_half = self.socket; 64 | 65 | let query_stream = receive_inbound_messages(recv_half) 66 | .map_ok(move |(envelope, from_addr)| match envelope.message_type { 67 | Message::Response { response } => { 68 | transactions.handle_response(InboundResponseEnvelope { 69 | transaction_id: envelope.transaction_id, 70 | response: ResponseType::Response { response }, 71 | })?; 72 | 73 | Ok(None) 74 | } 75 | Message::Error { error } => { 76 | transactions.handle_response(InboundResponseEnvelope { 77 | transaction_id: envelope.transaction_id, 78 | response: ResponseType::Error { error }, 79 | })?; 80 | 81 | Ok(None) 82 | } 83 | Message::Query { query } => Ok(Some(( 84 | InboundQuery::new(envelope.transaction_id, query, envelope.read_only), 85 | from_addr, 86 | ))), 87 | }) 88 | .try_filter_map(|result| future::ready(result)); 89 | 90 | ( 91 | SendTransport::new(send_half, self.transactions), 92 | query_stream, 93 | ) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/node_info.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addr, 3 | NodeID, 4 | }; 5 | use serde::{ 6 | de::{ 7 | self, 8 | Visitor, 9 | }, 10 | Deserializer, 11 | Serializer, 12 | }; 13 | use std::{ 14 | fmt, 15 | net::SocketAddrV4, 16 | }; 17 | 18 | /// Contact information for a node in the DHT network 19 | /// 20 | /// Implements "Compact node info" serialization and de-serialization. 21 | #[derive(Debug, PartialEq, Eq, Clone)] 22 | pub struct NodeInfo { 23 | pub node_id: NodeID, 24 | pub address: SocketAddrV4, 25 | } 26 | 27 | impl NodeInfo { 28 | pub fn new(node_id: NodeID, addr: SocketAddrV4) -> NodeInfo { 29 | NodeInfo { 30 | node_id, 31 | address: addr, 32 | } 33 | } 34 | 35 | fn to_bytes(&self) -> [u8; 26] { 36 | let mut output = [0u8; 26]; 37 | (&mut output[..20]).copy_from_slice(&self.node_id.as_bytes()); 38 | addr::write_to(&self.address, &mut output[20..]); 39 | 40 | output 41 | } 42 | 43 | fn from_bytes(bytes: &[u8]) -> NodeInfo { 44 | let node_id = NodeID::from_bytes(&bytes[..20]); 45 | let address = addr::from_bytes(&bytes[20..]); 46 | 47 | NodeInfo { node_id, address } 48 | } 49 | } 50 | 51 | pub fn serialize(nodes: &Vec, serializer: S) -> Result 52 | where 53 | S: Serializer, 54 | { 55 | let bytes = nodes 56 | .iter() 57 | .flat_map(|node| node.to_bytes().to_vec()) 58 | .collect::>(); 59 | 60 | serializer.serialize_bytes(&bytes) 61 | } 62 | 63 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 64 | where 65 | D: Deserializer<'de>, 66 | { 67 | deserializer.deserialize_bytes(NodeInfoVecVisitor) 68 | } 69 | 70 | struct NodeInfoVecVisitor; 71 | 72 | impl<'de> Visitor<'de> for NodeInfoVecVisitor { 73 | type Value = Vec; 74 | 75 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | formatter.write_str("a byte array with a size which is a multiple of 26") 77 | } 78 | 79 | fn visit_bytes(self, v: &[u8]) -> Result 80 | where 81 | E: de::Error, 82 | { 83 | let len = v.len(); 84 | if len % 26 != 0 { 85 | return Err(de::Error::invalid_length(len, &self)); 86 | } 87 | 88 | let mut output: Vec = Vec::with_capacity(len / 26); 89 | 90 | for idx in (0..len).step_by(26) { 91 | let node_info = NodeInfo::from_bytes(&v[idx..]); 92 | output.push(node_info); 93 | } 94 | 95 | Ok(output) 96 | } 97 | 98 | fn visit_byte_buf(self, v: Vec) -> Result 99 | where 100 | E: de::Error, 101 | { 102 | self.visit_bytes(&v) 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::NodeInfo; 109 | type Error = Box; 110 | use std::{ 111 | net::SocketAddrV4, 112 | str::FromStr, 113 | }; 114 | 115 | #[test] 116 | fn test_to_bytes() -> Result<(), Error> { 117 | let node = NodeInfo::new( 118 | b"abcdefghij0123456789".into(), 119 | SocketAddrV4::from_str("129.21.60.68:3454")?.into(), 120 | ); 121 | 122 | let _bytes = node.to_bytes(); 123 | 124 | Ok(()) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/request_transport.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | responses::{ 3 | FindNodeResponse, 4 | GetPeersResponse, 5 | NodeIDResponse, 6 | }, 7 | send_errors::Result, 8 | PortType, 9 | SendTransport, 10 | }; 11 | use krpc_encoding::{ 12 | NodeID, 13 | Query, 14 | }; 15 | use std::{ 16 | borrow::Borrow, 17 | net::SocketAddrV4, 18 | }; 19 | 20 | /// High level wrapper around a UDP socket for sending typed queries and 21 | /// receiving typed responses. 22 | pub struct RequestTransport { 23 | id: NodeID, 24 | send_transport: Box>, 25 | } 26 | 27 | impl RequestTransport { 28 | pub fn new + 'static>( 29 | id: NodeID, 30 | send_transport: T, 31 | ) -> RequestTransport { 32 | RequestTransport { 33 | id, 34 | send_transport: Box::new(send_transport), 35 | } 36 | } 37 | 38 | pub async fn ping(&self, address: SocketAddrV4) -> Result { 39 | let response = (*self.send_transport) 40 | .borrow() 41 | .request( 42 | address.into(), 43 | Query::Ping { 44 | id: self.id.clone(), 45 | }, 46 | ) 47 | .await?; 48 | 49 | Ok(NodeIDResponse::from_response(response)?) 50 | } 51 | 52 | pub async fn find_node( 53 | &self, 54 | address: SocketAddrV4, 55 | target: NodeID, 56 | ) -> Result { 57 | let response = (*self.send_transport) 58 | .borrow() 59 | .request( 60 | address.into(), 61 | Query::FindNode { 62 | id: self.id.clone(), 63 | target, 64 | }, 65 | ) 66 | .await?; 67 | 68 | Ok(FindNodeResponse::from_response(response)?) 69 | } 70 | 71 | pub async fn get_peers( 72 | &self, 73 | address: SocketAddrV4, 74 | info_hash: NodeID, 75 | ) -> Result { 76 | let response = (*self.send_transport) 77 | .borrow() 78 | .request( 79 | address.into(), 80 | Query::GetPeers { 81 | id: self.id.clone(), 82 | info_hash, 83 | }, 84 | ) 85 | .await?; 86 | 87 | Ok(GetPeersResponse::from_response(response)?) 88 | } 89 | 90 | pub async fn announce_peer( 91 | &self, 92 | token: Vec, 93 | address: SocketAddrV4, 94 | info_hash: NodeID, 95 | port_type: PortType, 96 | ) -> Result { 97 | let (port, implied_port) = match port_type { 98 | PortType::Implied => (None, true), 99 | PortType::Port(port) => (Some(port), false), 100 | }; 101 | 102 | let response = (*self.send_transport) 103 | .borrow() 104 | .request( 105 | address.into(), 106 | Query::AnnouncePeer { 107 | id: self.id.clone(), 108 | token, 109 | info_hash, 110 | port, 111 | implied_port, 112 | }, 113 | ) 114 | .await?; 115 | 116 | Ok(NodeIDResponse::from_response(response)?) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/errors.rs: -------------------------------------------------------------------------------- 1 | use failure::{ 2 | Backtrace, 3 | Context, 4 | Fail, 5 | }; 6 | use krpc_encoding as proto; 7 | use std::{ 8 | self, 9 | fmt, 10 | io, 11 | net::SocketAddrV6, 12 | sync::PoisonError, 13 | }; 14 | 15 | pub type Result = std::result::Result; 16 | 17 | #[derive(Debug)] 18 | pub struct Error { 19 | inner: Context, 20 | } 21 | 22 | #[derive(Debug, Fail)] 23 | pub enum ErrorKind { 24 | //// Originating Errors 25 | #[fail(display = "Received IPv6 Address where an IPv4 address was expected")] 26 | UnsupportedAddressTypeError { addr: SocketAddrV6 }, 27 | 28 | //// Protocol Errors 29 | #[fail(display = "Unimplemented request type")] 30 | UnimplementedRequestType, 31 | 32 | #[fail(display = "Invalid Token")] 33 | InvalidToken, 34 | 35 | #[fail(display = "Insufficient address information provided")] 36 | InsufficientAddress, 37 | 38 | //// Wrapping Other Errors 39 | #[fail(display = "Lock poisoned")] 40 | LockPoisoned, 41 | 42 | #[fail(display = "Timeout")] 43 | Timeout, 44 | 45 | #[fail(display = "Something broke in the transport")] 46 | RecvTransportError { 47 | #[fail(cause)] 48 | cause: tokio_krpc::recv_errors::Error, 49 | }, 50 | 51 | #[fail(display = "Something broke in the transport")] 52 | SendTransportError { 53 | #[fail(cause)] 54 | cause: tokio_krpc::send_errors::Error, 55 | }, 56 | 57 | #[fail(display = "Failed to bind")] 58 | BindError { 59 | #[fail(cause)] 60 | cause: io::Error, 61 | }, 62 | } 63 | 64 | impl Fail for Error { 65 | fn cause(&self) -> Option<&dyn Fail> { 66 | self.inner.cause() 67 | } 68 | 69 | fn backtrace(&self) -> Option<&Backtrace> { 70 | self.inner.backtrace() 71 | } 72 | } 73 | 74 | impl Error { 75 | pub fn as_request_error(&self) -> proto::KRPCError { 76 | let (code, message) = match self.inner.get_context() { 77 | ErrorKind::UnimplementedRequestType => (204, "Unimplemented"), 78 | ErrorKind::InvalidToken => (203, "Invalid Token"), 79 | ErrorKind::InsufficientAddress => (203, "Not enough address info provided"), 80 | _ => (202, "Server Error"), 81 | }; 82 | 83 | proto::KRPCError::new(code, message) 84 | } 85 | } 86 | 87 | impl fmt::Display for Error { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 89 | fmt::Display::fmt(&self.inner, f) 90 | } 91 | } 92 | 93 | impl From for Error { 94 | fn from(kind: ErrorKind) -> Error { 95 | Error { 96 | inner: Context::new(kind), 97 | } 98 | } 99 | } 100 | 101 | impl From> for Error { 102 | fn from(inner: Context) -> Error { 103 | Error { inner } 104 | } 105 | } 106 | 107 | /// Implementation allowing for converting to a `Fail` compatible error even 108 | /// when the lock isn't sync. 109 | impl From> for Error { 110 | fn from(_err: PoisonError) -> Error { 111 | ErrorKind::LockPoisoned.into() 112 | } 113 | } 114 | 115 | impl From for Error { 116 | fn from(cause: tokio_krpc::recv_errors::Error) -> Self { 117 | ErrorKind::RecvTransportError { cause }.into() 118 | } 119 | } 120 | 121 | impl From for Error { 122 | fn from(cause: tokio_krpc::send_errors::Error) -> Self { 123 | ErrorKind::SendTransportError { cause }.into() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/addr.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ 2 | NetworkEndian, 3 | ReadBytesExt, 4 | WriteBytesExt, 5 | }; 6 | use serde::{ 7 | de::{ 8 | self, 9 | Visitor, 10 | }, 11 | Deserialize, 12 | Deserializer, 13 | Serialize, 14 | Serializer, 15 | }; 16 | use std::{ 17 | fmt, 18 | net::{ 19 | Ipv4Addr, 20 | SocketAddrV4, 21 | }, 22 | ops::Deref, 23 | str::FromStr, 24 | }; 25 | 26 | /// Contact information for a BitTorrent peer 27 | /// 28 | /// Implements "Compact IP-address/port info" serialization and 29 | /// de-serialization. 30 | #[derive(Eq, PartialEq, Debug)] 31 | pub struct Addr(SocketAddrV4); 32 | 33 | impl Deref for Addr { 34 | type Target = SocketAddrV4; 35 | 36 | fn deref(&self) -> &Self::Target { 37 | &self.0 38 | } 39 | } 40 | 41 | impl Into for Addr { 42 | fn into(self) -> SocketAddrV4 { 43 | self.0 44 | } 45 | } 46 | 47 | impl From for Addr { 48 | fn from(addr: SocketAddrV4) -> Self { 49 | Addr(addr) 50 | } 51 | } 52 | 53 | impl FromStr for Addr { 54 | type Err = ::Err; 55 | 56 | fn from_str(s: &str) -> Result { 57 | let addr: SocketAddrV4 = s.parse()?; 58 | 59 | Ok(Addr::from(addr)) 60 | } 61 | } 62 | 63 | pub fn write_to(addr: &SocketAddrV4, raw: &mut [u8]) { 64 | let ip = addr.ip(); 65 | let port = addr.port(); 66 | 67 | raw[..4].clone_from_slice(&ip.octets()); 68 | (&mut raw[4..]) 69 | .write_u16::(port) 70 | .expect("Failed to encode port."); 71 | } 72 | 73 | /// Encode `addr` with the "Compact IP-address/port info" format 74 | pub fn to_bytes(addr: &SocketAddrV4) -> [u8; 6] { 75 | let mut raw = [0u8; 6]; 76 | write_to(addr, &mut raw); 77 | 78 | raw 79 | } 80 | 81 | pub fn from_bytes(v: &[u8]) -> SocketAddrV4 { 82 | let ip = Ipv4Addr::new(v[0], v[1], v[2], v[3]); 83 | let port = (&v[4..]).read_u16::().unwrap(); 84 | 85 | SocketAddrV4::new(ip, port) 86 | } 87 | 88 | impl Serialize for Addr { 89 | fn serialize(&self, serializer: S) -> Result 90 | where 91 | S: Serializer, 92 | { 93 | serializer.serialize_bytes(&to_bytes(&self.0)) 94 | } 95 | } 96 | 97 | impl<'de> Deserialize<'de> for Addr { 98 | fn deserialize(deserializer: D) -> Result 99 | where 100 | D: Deserializer<'de>, 101 | { 102 | deserializer.deserialize_bytes(NodeInfoVisitor) 103 | } 104 | } 105 | 106 | struct NodeInfoVisitor; 107 | 108 | impl<'de> Visitor<'de> for NodeInfoVisitor { 109 | type Value = Addr; 110 | 111 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 112 | formatter.write_str("a byte array of size 6") 113 | } 114 | 115 | fn visit_bytes(self, v: &[u8]) -> Result 116 | where 117 | E: de::Error, 118 | { 119 | let len = v.len(); 120 | if len != 6 { 121 | return Err(de::Error::invalid_length(len, &self)); 122 | } 123 | 124 | Ok(Addr(from_bytes(v))) 125 | } 126 | } 127 | 128 | #[cfg(test)] 129 | mod tests { 130 | use super::Addr; 131 | use serde_test::{ 132 | assert_tokens, 133 | Token, 134 | }; 135 | use std::net::{ 136 | Ipv4Addr, 137 | SocketAddrV4, 138 | }; 139 | 140 | #[test] 141 | fn serde() { 142 | let addr = Ipv4Addr::new(129, 21, 60, 66); 143 | let port = 12019; 144 | let socket_addr = SocketAddrV4::new(addr, port); 145 | 146 | assert_tokens( 147 | &Addr::from(socket_addr), 148 | &[Token::Bytes(&[129, 21, 60, 66, 0x2e, 0xf3])], 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /packages/routing_table/src/transport.rs: -------------------------------------------------------------------------------- 1 | use self::errors::Result; 2 | use crate::{ 3 | node_contact_state::NodeContactState, 4 | transport::errors::{ 5 | Error, 6 | ErrorKind, 7 | TimeoutExt, 8 | }, 9 | }; 10 | use krpc_encoding::NodeID; 11 | use std::net::SocketAddrV4; 12 | use tokio_krpc::{ 13 | responses::FindNodeResponse, 14 | RequestTransport, 15 | }; 16 | 17 | /// A transport used for communicating with other nodes which keeps liveness 18 | /// information up to date. 19 | pub struct LivenessTransport { 20 | request_transport: RequestTransport, 21 | } 22 | 23 | impl LivenessTransport { 24 | pub fn new(request_transport: RequestTransport) -> LivenessTransport { 25 | LivenessTransport { request_transport } 26 | } 27 | 28 | pub async fn find_node( 29 | &self, 30 | address: SocketAddrV4, 31 | target: NodeID, 32 | ) -> Result { 33 | Ok(self 34 | .request_transport 35 | .find_node(address, target) 36 | .timeout() 37 | .await?) 38 | } 39 | 40 | pub async fn ping(&self, node: &mut NodeContactState) -> Result<()> { 41 | Ok(node.update_from_result( 42 | self.request_transport 43 | .ping(node.address) 44 | .timeout() 45 | .await 46 | .and_then(|node_id| { 47 | if node_id != node.id { 48 | Err(Error::from(ErrorKind::PingIdMismatch { 49 | got: node_id, 50 | expected: node.id.clone(), 51 | })) 52 | } else { 53 | Ok(()) 54 | } 55 | }), 56 | )?) 57 | } 58 | } 59 | 60 | mod errors { 61 | use futures_util::{ 62 | future::Map, 63 | FutureExt, 64 | }; 65 | use krpc_encoding::NodeID; 66 | use std::{ 67 | backtrace::Backtrace, 68 | future::Future, 69 | time::Duration, 70 | }; 71 | use thiserror::Error; 72 | use tokio::time::{ 73 | error::Elapsed, 74 | timeout, 75 | Timeout, 76 | }; 77 | use tokio_krpc::{ 78 | send_errors, 79 | send_errors::Result as TokioKrpcSendResult, 80 | }; 81 | 82 | type WithTimeoutFuture = Map< 83 | Timeout, 84 | fn( 85 | std::result::Result, Elapsed>, 86 | ) -> Result, 87 | >; 88 | 89 | fn with_timeout(future: F, duration: Duration) -> WithTimeoutFuture 90 | where 91 | F: Future>, 92 | { 93 | timeout(duration, future).map(|result| match result { 94 | Err(_cause) => Err(ErrorKind::Timeout.into()), 95 | Ok(Err(cause)) => Err(ErrorKind::SendError { cause }.into()), 96 | Ok(Ok(value)) => Ok(value), 97 | }) 98 | } 99 | 100 | pub trait TimeoutExt: Future> + Sized { 101 | fn timeout(self) -> WithTimeoutFuture; 102 | } 103 | 104 | const TIMEOUT_SECONDS: u64 = 1; 105 | 106 | impl TimeoutExt for F 107 | where 108 | F: Future> + Sized, 109 | { 110 | fn timeout(self) -> WithTimeoutFuture { 111 | with_timeout(self, Duration::new(TIMEOUT_SECONDS, 0)) 112 | } 113 | } 114 | 115 | #[derive(Debug, Error)] 116 | pub enum ErrorKind { 117 | #[error("failed to send query")] 118 | SendError { 119 | #[from] 120 | cause: send_errors::Error, 121 | }, 122 | 123 | #[error("node responded with unexpected id")] 124 | PingIdMismatch { got: NodeID, expected: NodeID }, 125 | 126 | #[error("request timed out")] 127 | Timeout, 128 | } 129 | 130 | pub type Result = std::result::Result; 131 | 132 | #[derive(Error, Debug)] 133 | #[error("{}", inner)] 134 | pub struct Error { 135 | #[from] 136 | inner: ErrorKind, 137 | 138 | backtrace: Backtrace, 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /packages/tokio_krpc/src/active_transactions.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | inbound_response_envelope::InboundResponseEnvelope, 3 | recv_errors, 4 | send_errors, 5 | transaction_id::{ 6 | parse_originating_transaction_id, 7 | TransactionId, 8 | }, 9 | }; 10 | 11 | use std::{ 12 | collections::HashMap, 13 | sync::{ 14 | Arc, 15 | Mutex, 16 | }, 17 | task::{ 18 | Poll, 19 | Waker, 20 | }, 21 | }; 22 | 23 | /// A thread-safe container for information about active transactions. Shared 24 | /// between many [`ResponseFuture`]s and a single [`RecvTransport`]. 25 | #[derive(Clone)] 26 | pub struct ActiveTransactions { 27 | transactions: Arc>>, 28 | } 29 | 30 | enum TxState { 31 | GotResponse { 32 | response: InboundResponseEnvelope, 33 | }, 34 | AwaitingResponse { 35 | /// Waker used when response is received. None if poll hasn't been 36 | /// called for this tx yet. 37 | waker: Option, 38 | }, 39 | } 40 | 41 | impl ActiveTransactions { 42 | pub fn new() -> ActiveTransactions { 43 | let transactions = Arc::new(Mutex::new(HashMap::new())); 44 | 45 | ActiveTransactions { transactions } 46 | } 47 | 48 | /// Adds an un-polled pending transaction to the set of active transactions. 49 | pub fn add_transaction(&self, transaction_id: TransactionId) { 50 | let mut map = self.transactions.lock().unwrap(); 51 | map.insert(transaction_id, TxState::AwaitingResponse { waker: None }); 52 | } 53 | 54 | /// Stops tracking a transaction. Subsequent calls to [`handle_response`], 55 | /// [`poll_response`] with `transaction_id` will now fail. 56 | pub fn drop_transaction(&self, transaction_id: TransactionId) { 57 | let mut map = self.transactions.lock().unwrap(); 58 | map.remove(&transaction_id); 59 | } 60 | 61 | /// Updates transaction associated with `message` such that the next call to 62 | /// [`poll_response`] for the transaction will return [`Async::Ready`]. 63 | /// Awakens the associated waker if there is one. 64 | /// 65 | /// # Errors 66 | /// 67 | /// If the transaction id associated with `message` isn't known, returns 68 | /// failure. 69 | pub fn handle_response(&self, message: InboundResponseEnvelope) -> recv_errors::Result<()> { 70 | let transaction_id = parse_originating_transaction_id(&message.transaction_id)?; 71 | let mut map = self.transactions.lock().unwrap(); 72 | 73 | let current_tx_state = map 74 | .remove(&transaction_id) 75 | .ok_or_else(|| recv_errors::ErrorKind::UnknownTransactionReceived { transaction_id })?; 76 | 77 | match current_tx_state { 78 | TxState::GotResponse { .. } => { 79 | // Multiple responses received for a single transaction. This shouldn't happen. 80 | map.insert(transaction_id, current_tx_state); 81 | } 82 | TxState::AwaitingResponse { waker } => { 83 | map.insert(transaction_id, TxState::GotResponse { response: message }); 84 | waker.map(|waker| waker.wake()); 85 | } 86 | }; 87 | 88 | Ok(()) 89 | } 90 | 91 | /// Associates `waker` with `transaction_id` and returns [`NotReady`] until 92 | /// a message with the same `transaction_id` is provided to 93 | /// [`handle_response`], then returns that message and awakes the `waker`. 94 | pub fn poll_response( 95 | &self, 96 | transaction_id: TransactionId, 97 | waker: &Waker, 98 | ) -> Poll> { 99 | let mut map = self.transactions.lock().unwrap(); 100 | 101 | let tx_state = map 102 | .remove(&transaction_id) 103 | .ok_or_else(|| send_errors::ErrorKind::UnknownTransactionPolled { transaction_id })?; 104 | 105 | match tx_state { 106 | TxState::GotResponse { response } => Poll::Ready(Ok(response)), 107 | TxState::AwaitingResponse { waker: Some(..) } => { 108 | map.insert(transaction_id, tx_state); 109 | 110 | Poll::Pending 111 | } 112 | TxState::AwaitingResponse { waker: None } => { 113 | map.insert( 114 | transaction_id, 115 | TxState::AwaitingResponse { 116 | waker: Some(waker.clone()), 117 | }, 118 | ); 119 | 120 | Poll::Pending 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/routing/table.rs: -------------------------------------------------------------------------------- 1 | use crate::routing::{ 2 | bucket::Bucket, 3 | node::Node, 4 | token_validator::TokenValidator, 5 | }; 6 | use krpc_encoding::{ 7 | NodeID, 8 | NodeInfo, 9 | }; 10 | use std::{ 11 | cmp, 12 | net::SocketAddrV4, 13 | }; 14 | 15 | pub enum FindNodeResult { 16 | Node(NodeInfo), 17 | Nodes(Vec), 18 | } 19 | 20 | pub struct RoutingTable { 21 | /// Node identifier of the node which the table is based around. There will 22 | /// be more buckets closer to this identifier. 23 | id: NodeID, 24 | 25 | /// Ordered list of buckets covering the key space. The first bucket starts 26 | /// at key 0 and the last bucket ends at key 2^160. 27 | buckets: Vec, 28 | 29 | token_validator: TokenValidator, 30 | } 31 | 32 | impl RoutingTable { 33 | pub fn new(id: NodeID) -> RoutingTable { 34 | let mut buckets = Vec::new(); 35 | buckets.push(Bucket::initial_bucket()); 36 | 37 | RoutingTable { 38 | id, 39 | buckets, 40 | token_validator: TokenValidator::new(), 41 | } 42 | } 43 | 44 | /// Adds a node to the routing table. 45 | pub fn add_node(&mut self, node: Node) { 46 | let bucket_idx = self.get_bucket_idx(&node.id); 47 | 48 | let bucket_to_add_to_idx = if self.buckets[bucket_idx].is_full() { 49 | if !self.buckets[bucket_idx].could_hold_node(&node.id) { 50 | return; 51 | } 52 | 53 | let (prev_bucket_idx, next_bucket_idx) = self.split_bucket(bucket_idx); 54 | 55 | if self.buckets[prev_bucket_idx].could_hold_node(&node.id) { 56 | prev_bucket_idx 57 | } else { 58 | next_bucket_idx 59 | } 60 | } else { 61 | bucket_idx 62 | }; 63 | 64 | &mut self.buckets[bucket_to_add_to_idx].add_node(node); 65 | } 66 | 67 | /// Finds the node with `id`, or about the `k` nearest good nodes to the 68 | /// `id` if the exact node couldn't be found. More or less than `k` 69 | /// nodes may be returned. 70 | pub fn find_node(&self, id: &NodeID) -> FindNodeResult { 71 | let bucket_idx = self.get_bucket_idx(id); 72 | let bucket = &self.buckets[bucket_idx]; 73 | 74 | match bucket.get(id) { 75 | None => FindNodeResult::Nodes(bucket.good_nodes().map(|node| node.into()).collect()), 76 | Some(node) => FindNodeResult::Node((node as &Node).into()), 77 | } 78 | } 79 | 80 | /// Finds nodes in the same bucket as `id` in the routing table. 81 | pub fn find_nodes(&self, id: &NodeID) -> Vec { 82 | let bucket_idx = self.get_bucket_idx(id); 83 | let bucket = &self.buckets[bucket_idx]; 84 | 85 | bucket.good_nodes().map(|node| node.into()).collect() 86 | } 87 | 88 | /// Gets the node with `id` from the table. 89 | pub fn get_node(&self, id: &NodeID) -> Option<&Node> { 90 | let bucket_idx = self.get_bucket_idx(id); 91 | let bucket = &self.buckets[bucket_idx]; 92 | 93 | bucket.get(id) 94 | } 95 | 96 | /// Gets the index of the bucket which can hold `id`. 97 | fn get_bucket_idx(&self, id: &NodeID) -> usize { 98 | self.buckets 99 | .binary_search_by(|bucket| { 100 | if bucket.could_hold_node(id) { 101 | cmp::Ordering::Equal 102 | } else { 103 | bucket.start.cmp(id) 104 | } 105 | }) 106 | .expect("No bucket was found for NodeID.") 107 | } 108 | 109 | /// Splits the bucket at `idx` into two buckets. 110 | fn split_bucket(&mut self, idx: usize) -> (usize, usize) { 111 | let next_bucket = { 112 | let bucket = &mut self.buckets[idx]; 113 | bucket.split() 114 | }; 115 | 116 | let next_bucket_idx = idx + 1; 117 | self.buckets.insert(next_bucket_idx, next_bucket); 118 | 119 | (idx, next_bucket_idx) 120 | } 121 | 122 | pub fn verify_token(&self, token: &[u8], addr: &SocketAddrV4) -> bool { 123 | self.token_validator.verify_token(addr, token) 124 | } 125 | 126 | pub fn generate_token(&self, addr: &SocketAddrV4) -> [u8; 20] { 127 | self.token_validator.generate_token(addr) 128 | } 129 | 130 | pub fn update_token(&mut self) { 131 | self.token_validator.rotate_tokens(); 132 | } 133 | 134 | pub fn get_or_add(&mut self, id: NodeID, address: SocketAddrV4) -> Option<&mut Node> { 135 | let bucket_idx = self.get_bucket_idx(&id); 136 | let bucket = &mut self.buckets[bucket_idx]; 137 | 138 | if bucket.get(&id).is_none() { 139 | bucket.add_node(Node::new(id.clone(), address)); 140 | } 141 | 142 | bucket.get_mut(&id) 143 | } 144 | 145 | pub fn len(&self) -> usize { 146 | self.buckets.iter().map(|bucket| bucket.nodes.len()).sum() 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/node_id.rs: -------------------------------------------------------------------------------- 1 | use hex; 2 | use num_bigint::BigUint; 3 | use num_traits::One; 4 | use rand; 5 | use serde::{ 6 | de::{ 7 | self, 8 | Visitor, 9 | }, 10 | Deserialize, 11 | Deserializer, 12 | Serialize, 13 | Serializer, 14 | }; 15 | use std::{ 16 | fmt, 17 | ops::Deref, 18 | }; 19 | 20 | /// Value representing a key or node ID in the DHT 21 | #[derive(PartialEq, Eq, Clone, Hash)] 22 | pub struct NodeID(BigUint); 23 | 24 | pub const NODE_ID_SIZE_BITS: usize = 20 * 8; 25 | 26 | impl NodeID { 27 | pub fn new(id: BigUint) -> NodeID { 28 | NodeID(id) 29 | } 30 | 31 | pub fn random() -> NodeID { 32 | rand::random::<[u8; 20]>().into() 33 | } 34 | 35 | pub fn from_bytes(bytes: &[u8]) -> NodeID { 36 | NodeID(BigUint::from_bytes_be(bytes)) 37 | } 38 | 39 | pub fn from_hex(bytes: &[u8; 40]) -> NodeID { 40 | let raw: &[u8] = bytes; 41 | let bytes = hex::decode(raw).unwrap(); 42 | 43 | NodeID::from_bytes(&bytes) 44 | } 45 | 46 | pub fn as_bytes(&self) -> [u8; 20] { 47 | let mut bytes = self.0.to_bytes_be(); 48 | bytes.resize(20, 0); 49 | let mut output = [0u8; 20]; 50 | output.copy_from_slice(&bytes[..]); 51 | 52 | output 53 | } 54 | 55 | /// Returns true if the value of the nth bit is 1. The 0th bit is the most 56 | /// significant bit. 57 | pub fn nth_bit(&self, n: usize) -> bool { 58 | let one = BigUint::one(); 59 | return ((self.deref() >> n) & &one) == one; 60 | } 61 | } 62 | 63 | impl Deref for NodeID { 64 | type Target = BigUint; 65 | 66 | fn deref(&self) -> &Self::Target { 67 | &self.0 68 | } 69 | } 70 | 71 | impl fmt::Debug for NodeID { 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 73 | ::fmt(self, f) 74 | } 75 | } 76 | 77 | impl fmt::Display for NodeID { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | write!(f, "{}", hex::encode(self.as_bytes())) 80 | } 81 | } 82 | 83 | impl Serialize for NodeID { 84 | fn serialize(&self, serializer: S) -> Result 85 | where 86 | S: Serializer, 87 | { 88 | serializer.serialize_bytes(&self.as_bytes()) 89 | } 90 | } 91 | 92 | impl<'de> Deserialize<'de> for NodeID { 93 | fn deserialize(deserializer: D) -> Result 94 | where 95 | D: Deserializer<'de>, 96 | { 97 | deserializer.deserialize_bytes(NodeIDVisitor) 98 | } 99 | } 100 | 101 | struct NodeIDVisitor; 102 | 103 | impl<'de> Visitor<'de> for NodeIDVisitor { 104 | type Value = NodeID; 105 | 106 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 107 | formatter.write_str("a byte array of size 20") 108 | } 109 | 110 | fn visit_bytes(self, v: &[u8]) -> Result 111 | where 112 | E: de::Error, 113 | { 114 | let len = v.len(); 115 | if len != 20 { 116 | return Err(de::Error::invalid_length(len, &self)); 117 | }; 118 | 119 | Ok(NodeID::from_bytes(v)) 120 | } 121 | 122 | fn visit_byte_buf(self, v: Vec) -> Result 123 | where 124 | E: de::Error, 125 | { 126 | self.visit_bytes(&v) 127 | } 128 | } 129 | 130 | impl<'a> From<&'a [u8; 20]> for NodeID { 131 | fn from(bytes: &[u8; 20]) -> Self { 132 | NodeID::from_bytes(bytes) 133 | } 134 | } 135 | 136 | impl<'a> From<&'a [u8; 40]> for NodeID { 137 | fn from(bytes: &[u8; 40]) -> Self { 138 | NodeID::from_hex(bytes) 139 | } 140 | } 141 | 142 | impl From<[u8; 20]> for NodeID { 143 | fn from(arr: [u8; 20]) -> Self { 144 | NodeID::from_bytes(&arr) 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::NodeID; 151 | use num_bigint::BigUint; 152 | 153 | #[test] 154 | fn as_bytes() { 155 | let id = NodeID::new(BigUint::from(1u8)); 156 | let bytes = id.as_bytes(); 157 | let mut expected = [0u8; 20]; 158 | expected[0] = 1; 159 | 160 | assert_eq!(bytes, expected); 161 | } 162 | 163 | #[test] 164 | fn first_bit() { 165 | ensure_bits_for( 166 | b"8b9292b2f75d127720ebcd8afe66bfa50c2adc7f".into(), 167 | "10001011 10010010 10010010 10110010 11110111 01011101 00010010 01110111 00100000 11101011 11001101 10001010 11111110 01100110 10111111 10100101 00001100 00101010 11011100 01111111" 168 | ) 169 | } 170 | 171 | fn ensure_bits_for(id: NodeID, expected_bits: &str) { 172 | let mut bit_strings = (0..160) 173 | .map(|n| id.nth_bit(n)) 174 | .map(|n| if n { "1" } else { "0" }) 175 | .map(|s| String::from(s)) 176 | .collect::>(); 177 | 178 | bit_strings.reverse(); 179 | 180 | let actual_bits = bit_strings.join(""); 181 | 182 | assert_eq!(actual_bits, expected_bits.replace(" ", "")) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/routing/node.rs: -------------------------------------------------------------------------------- 1 | use chrono::{ 2 | NaiveDateTime, 3 | Utc, 4 | }; 5 | use krpc_encoding::{ 6 | NodeID, 7 | NodeInfo, 8 | }; 9 | use std::net::SocketAddrV4; 10 | 11 | #[derive(Debug, PartialEq)] 12 | pub struct Node { 13 | pub id: NodeID, 14 | pub address: SocketAddrV4, 15 | 16 | /// Last time a message was sent from ourselves to this node and a response 17 | /// was received successfully. 18 | last_request_to: Option, 19 | 20 | /// Last time a valid request was received from this node. 21 | last_request_from: Option, 22 | 23 | /// Number of failed requests from us to the node since `last_request_to`. 24 | failed_requests: u8, 25 | } 26 | 27 | impl<'a> Into for &'a Node { 28 | fn into(self) -> NodeInfo { 29 | NodeInfo::new(self.id.clone(), self.address) 30 | } 31 | } 32 | 33 | impl Into for Node { 34 | fn into(self) -> NodeInfo { 35 | NodeInfo::new(self.id, self.address) 36 | } 37 | } 38 | 39 | #[derive(Debug, PartialEq)] 40 | pub enum NodeState { 41 | /// A good node is a node has responded to one of our queries within the 42 | /// last 15 minutes. A node is also good if it has ever responded to one 43 | /// of our queries and has sent us a query within the last 15 minutes. 44 | Good, 45 | 46 | /// After 15 minutes of inactivity, a node becomes questionable. 47 | Questionable, 48 | 49 | /// Nodes become bad when they fail to respond to multiple queries in a row. 50 | /// At this point, they are not sent to other peers. They are replaced 51 | /// with new good nodes. 52 | Bad, 53 | } 54 | 55 | impl Node { 56 | pub fn new(id: NodeID, address: SocketAddrV4) -> Node { 57 | Node { 58 | id, 59 | address, 60 | last_request_to: None, 61 | last_request_from: None, 62 | failed_requests: 0, 63 | } 64 | } 65 | 66 | pub fn mark_successful_request(&mut self) { 67 | self.failed_requests = 0; 68 | self.last_request_to = Some(Utc::now().naive_utc()); 69 | } 70 | 71 | pub fn mark_failed_request(&mut self) { 72 | self.failed_requests += 1; 73 | } 74 | 75 | pub fn mark_successful_request_from(&mut self) { 76 | self.last_request_from = Some(Utc::now().naive_utc()); 77 | } 78 | 79 | pub fn state(&self) -> NodeState { 80 | let now = Utc::now().naive_utc(); 81 | 82 | if self.failed_requests >= 2 { 83 | return NodeState::Bad; 84 | }; 85 | 86 | match (self.last_request_from, self.last_request_to) { 87 | (Some(last_request_from), Some(..)) 88 | if now.signed_duration_since(last_request_from).num_minutes() < 15 => 89 | { 90 | NodeState::Good 91 | } 92 | (_, Some(last_request_to)) 93 | if now.signed_duration_since(last_request_to).num_minutes() < 15 => 94 | { 95 | NodeState::Good 96 | } 97 | _ => NodeState::Questionable, 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | pub fn new_with_id(id: u8) -> Node { 103 | use num_bigint::BigUint; 104 | 105 | let addr: SocketAddrV4 = "127.0.0.1:3000".parse().unwrap(); 106 | 107 | Node::new(NodeID::new(BigUint::from(id)), addr.clone()) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::{ 114 | Node, 115 | NodeID, 116 | NodeState, 117 | }; 118 | use chrono::{ 119 | prelude::*, 120 | Duration, 121 | }; 122 | use failure::Error; 123 | use num_bigint::BigUint; 124 | 125 | #[test] 126 | fn starting_state() { 127 | let node = Node::new_with_id(10u8); 128 | 129 | assert_eq!(node.state(), NodeState::Questionable); 130 | } 131 | 132 | #[test] 133 | fn good_state_request() { 134 | let mut node = Node::new_with_id(10); 135 | node.mark_successful_request(); 136 | 137 | assert_eq!(node.state(), NodeState::Good); 138 | } 139 | 140 | #[test] 141 | fn response_only_questionable() { 142 | let mut node = Node::new_with_id(10); 143 | node.mark_successful_request_from(); 144 | 145 | assert_eq!(node.state(), NodeState::Questionable); 146 | } 147 | 148 | #[test] 149 | fn bad_state() { 150 | let mut node = Node::new_with_id(10); 151 | node.mark_failed_request(); 152 | assert_eq!(node.state(), NodeState::Questionable); 153 | 154 | node.mark_failed_request(); 155 | assert_eq!(node.state(), NodeState::Bad); 156 | } 157 | 158 | #[test] 159 | fn request_response_good() -> Result<(), Error> { 160 | let epoch = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 980); 161 | 162 | let node = Node { 163 | id: NodeID::new(BigUint::from(10u8)), 164 | address: "127.0.0.1:3000".parse()?, 165 | last_request_to: Some(epoch), 166 | last_request_from: Some(Utc::now().naive_utc() - Duration::minutes(10)), 167 | failed_requests: 0, 168 | }; 169 | 170 | assert_eq!(node.state(), NodeState::Good); 171 | 172 | Ok(()) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/dht/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | errors::{ 3 | ErrorKind, 4 | Result, 5 | }, 6 | routing::{ 7 | Node, 8 | RoutingTable, 9 | }, 10 | }; 11 | use futures::future; 12 | use futures_util::TryStreamExt; 13 | use krpc_encoding::{ 14 | NodeID, 15 | NodeInfo, 16 | }; 17 | use std::{ 18 | collections::HashMap, 19 | net::{ 20 | SocketAddr, 21 | SocketAddrV4, 22 | }, 23 | pin::Pin, 24 | sync::{ 25 | Arc, 26 | Mutex, 27 | }, 28 | }; 29 | use tokio::net::UdpSocket; 30 | use tokio_krpc::{ 31 | KRPCNode, 32 | PortType, 33 | RequestTransport, 34 | SendTransport, 35 | }; 36 | 37 | mod handler; 38 | 39 | /// BitTorrent DHT node 40 | #[derive(Clone)] 41 | pub struct Dht { 42 | id: NodeID, 43 | torrents: Arc>>>, 44 | request_transport: Arc, 45 | send_transport: Arc, 46 | routing_table: Arc>, 47 | } 48 | 49 | impl Dht { 50 | // todo: why the mutex and arc everwhere? 51 | 52 | /// Start handling inbound messages from other peers in the network. 53 | /// Continues to handle while the future is polled. 54 | pub async fn start(bind_addr: SocketAddr) -> Result<(Dht, impl future::Future)> { 55 | let socket = UdpSocket::bind(&bind_addr) 56 | .await 57 | .map_err(|cause| ErrorKind::BindError { cause })?; 58 | let transport = KRPCNode::new(socket); 59 | let (send_transport, request_stream) = transport.serve(); 60 | 61 | let id = NodeID::random(); 62 | let torrents = HashMap::new(); 63 | let routing_table = RoutingTable::new(id.clone()); 64 | let send_transport_arc = Arc::new(send_transport); 65 | 66 | let dht = Dht { 67 | id: id.clone(), 68 | torrents: Arc::new(Mutex::new(torrents)), 69 | request_transport: Arc::new(RequestTransport::new(id, send_transport_arc.clone())), 70 | send_transport: send_transport_arc, 71 | routing_table: Arc::new(Mutex::new(routing_table)), 72 | }; 73 | 74 | Ok((dht.clone(), dht.handle_requests(request_stream.err_into()))) 75 | } 76 | 77 | /// Bootstraps the routing table by finding nodes near our node id and 78 | /// adding them to the routing table. 79 | pub async fn bootstrap_routing_table(&self, addrs: Vec) -> Result<()> { 80 | let send_transport = self.request_transport.clone(); 81 | let routing_table_arc = self.routing_table.clone(); 82 | let id = self.id.clone(); 83 | 84 | future::join_all(addrs.into_iter().map(move |addr| { 85 | Self::discover_nodes_of( 86 | addr, 87 | id.clone(), 88 | send_transport.clone(), 89 | routing_table_arc.clone(), 90 | ) 91 | })) 92 | .await; 93 | 94 | Ok(()) 95 | } 96 | 97 | async fn discover_nodes_of( 98 | addr: SocketAddrV4, 99 | self_id: NodeID, 100 | request_transport: Arc, 101 | routing_table_arc: Arc>, 102 | ) -> Result<()> { 103 | // todo: weird recursive thing 104 | // todo: populate routing table 105 | 106 | let response = request_transport 107 | .find_node(addr.clone().into(), self_id.clone()) 108 | // todo: standardize timeout 109 | .await?; 110 | 111 | let mut node = Node::new(response.id, addr.into()); 112 | node.mark_successful_request(); 113 | 114 | { 115 | let mut routing_table = routing_table_arc.lock()?; 116 | routing_table.add_node(node); 117 | } 118 | 119 | let f: Pin>> = 120 | Box::pin(future::join_all(response.nodes.into_iter().map(|node| { 121 | Self::discover_neighbors_of( 122 | node, 123 | self_id.clone(), 124 | request_transport.clone(), 125 | routing_table_arc.clone(), 126 | ) 127 | }))); 128 | 129 | f.await; 130 | 131 | Ok(()) 132 | } 133 | 134 | async fn discover_neighbors_of( 135 | node: NodeInfo, 136 | self_id: NodeID, 137 | request_transport: Arc, 138 | routing_table_arc: Arc>, 139 | ) { 140 | Self::discover_nodes_of(node.address, self_id, request_transport, routing_table_arc) 141 | .await 142 | .unwrap_or_else(|e| eprintln!("Error While Bootstrapping {}", e)); 143 | } 144 | 145 | /// Gets a list of peers seeding `info_hash`. 146 | pub async fn get_peers(&self, _info_hash: NodeID) -> Result> { 147 | // TODO: 148 | // * Return From torrents Table if Exists 149 | // * Fetch By Calling get_nodes otherwise 150 | unimplemented!() 151 | } 152 | 153 | /// Announces that we have information about an info_hash on `port`. 154 | pub async fn announce(&self, _info_hash: NodeID, _port: PortType) -> Result<()> { 155 | // TODO: 156 | // * Send Announce to all Peers With Tokens 157 | unimplemented!() 158 | } 159 | } 160 | 161 | #[cfg(test)] 162 | mod tests { 163 | use crate::{ 164 | addr::{ 165 | AsV4Address, 166 | IntoSocketAddr, 167 | }, 168 | errors::Error as DhtError, 169 | Dht, 170 | }; 171 | use failure::Error; 172 | use tokio::{ 173 | spawn, 174 | task::spawn_local, 175 | }; 176 | 177 | #[tokio::test] 178 | #[ignore] 179 | async fn test_bootstrap() -> Result<(), Error> { 180 | let addr = "0.0.0.0:23170".into_addr(); 181 | let (dht, dht_future) = Dht::start(addr).await?; 182 | 183 | let bootstrap_future = dht.bootstrap_routing_table(vec![ 184 | "router.utorrent.com:6881".into_addr().into_v4()?, 185 | "router.bittorrent.com:6881".into_addr().into_v4()?, 186 | ]); 187 | 188 | spawn_local(dht_future); 189 | bootstrap_future.await?; 190 | 191 | let routing_table = dht.routing_table.lock().map_err(DhtError::from)?; 192 | 193 | assert!(routing_table.len() > 0); 194 | 195 | Ok(()) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/routing/bucket.rs: -------------------------------------------------------------------------------- 1 | use crate::routing::node::{ 2 | Node, 3 | NodeState, 4 | }; 5 | use krpc_encoding::NodeID; 6 | use num_bigint::BigUint; 7 | use std::{ 8 | mem, 9 | ops::Deref, 10 | }; 11 | 12 | const MAX_BUCKET_SIZE: usize = 8; 13 | 14 | #[derive(Debug)] 15 | pub struct Bucket { 16 | /// Inclusive start key of nodes in the bucket. 17 | pub start: NodeID, 18 | 19 | /// Exclusive end key of nodes in the bucket. 20 | pub end: NodeID, 21 | 22 | /// Nodes in the bucket. These nodes could be in any state. 23 | pub nodes: Vec, 24 | } 25 | 26 | impl Bucket { 27 | pub fn new(start: NodeID, end: NodeID) -> Bucket { 28 | Bucket { 29 | start, 30 | end, 31 | nodes: Vec::new(), 32 | } 33 | } 34 | 35 | /// Creates a bucket spanning from key zero to key 2^160. 36 | pub fn initial_bucket() -> Bucket { 37 | let start = NodeID::new(BigUint::new(Vec::new())); 38 | let end = NodeID::new(BigUint::from_bytes_be(&[0xffu8; 20]) + 1u8); 39 | 40 | Bucket::new(start, end) 41 | } 42 | 43 | pub fn could_hold_node(&self, id: &NodeID) -> bool { 44 | id.deref() >= self.start.deref() && id.deref() < self.end.deref() 45 | } 46 | 47 | fn midpoint(&self) -> NodeID { 48 | NodeID::new(self.start.deref() + (self.end.deref() - self.start.deref()) / 2u8) 49 | } 50 | 51 | pub fn split(&mut self) -> Bucket { 52 | let midpoint = self.midpoint(); 53 | 54 | let next_bucket_end = mem::replace(&mut self.end, midpoint.clone()); 55 | let mut next_bucket = Bucket::new(midpoint, next_bucket_end); 56 | 57 | let previous_bucket_nodes = Vec::with_capacity(MAX_BUCKET_SIZE); 58 | let mut all_nodes = mem::replace(&mut self.nodes, previous_bucket_nodes); 59 | 60 | for node in all_nodes.drain(..) { 61 | let nodes = if self.could_hold_node(&node.id) { 62 | &mut self.nodes 63 | } else { 64 | &mut next_bucket.nodes 65 | }; 66 | 67 | nodes.push(node); 68 | } 69 | 70 | next_bucket 71 | } 72 | 73 | pub fn is_full(&self) -> bool { 74 | self.good_nodes().count() >= MAX_BUCKET_SIZE 75 | } 76 | 77 | pub fn add_node(&mut self, node: Node) { 78 | if !self.could_hold_node(&node.id) { 79 | panic!("Called add_node on a bucket which can't hold a node"); 80 | } 81 | 82 | if self.nodes.iter().find(|n| n.id == node.id).is_some() { 83 | return; 84 | } 85 | 86 | if self.nodes.len() < MAX_BUCKET_SIZE { 87 | self.nodes.push(node); 88 | return; 89 | } 90 | 91 | let bad_node_opt = self 92 | .nodes 93 | .iter_mut() 94 | .find(|node| node.state() == NodeState::Bad); 95 | 96 | if let Some(bad_node) = bad_node_opt { 97 | mem::replace(bad_node, node); 98 | } 99 | } 100 | 101 | pub fn good_nodes(&self) -> impl Iterator { 102 | self.nodes 103 | .iter() 104 | .filter(|node| node.state() == NodeState::Good) 105 | } 106 | 107 | pub fn get(&self, id: &NodeID) -> Option<&Node> { 108 | self.nodes.iter().find(|node| &node.id == id) 109 | } 110 | 111 | pub fn get_mut(&mut self, id: &NodeID) -> Option<&mut Node> { 112 | self.nodes.iter_mut().find(|node| &node.id == id) 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::{ 119 | BigUint, 120 | Bucket, 121 | NodeID, 122 | }; 123 | use crate::routing::node::Node; 124 | use num_traits as num; 125 | 126 | #[test] 127 | fn lower_bound_initial_bucket() { 128 | let bucket = Bucket::initial_bucket(); 129 | let lower_bound = BigUint::from(0u8); 130 | 131 | assert!(bucket.could_hold_node(&NodeID::new(lower_bound))); 132 | } 133 | 134 | #[test] 135 | fn upper_bound_initial_bucket() { 136 | let bucket = Bucket::initial_bucket(); 137 | let upper_bound = BigUint::from_bytes_be(&[0xffu8; 20]); 138 | 139 | assert!(bucket.could_hold_node(&NodeID::new(upper_bound))); 140 | } 141 | 142 | #[test] 143 | fn inner_value_initial_bucket() { 144 | let bucket = Bucket::initial_bucket(); 145 | let value = BigUint::from(80192381092u128); 146 | 147 | assert!(bucket.could_hold_node(&NodeID::new(value))); 148 | } 149 | 150 | #[test] 151 | fn outside_upper_bound_initial_bucket() { 152 | let bucket = Bucket::initial_bucket(); 153 | let value = BigUint::from_bytes_be(&[0xffu8; 20]) + 10u8; 154 | 155 | assert!(!bucket.could_hold_node(&NodeID::new(value))); 156 | } 157 | 158 | #[test] 159 | fn initial_bucket_midpoint() { 160 | let bucket = Bucket::initial_bucket(); 161 | let expected_midpoint = num::pow(BigUint::from(2u8), 159); 162 | 163 | assert_eq!(expected_midpoint, *bucket.midpoint()); 164 | } 165 | 166 | #[test] 167 | fn after_beginning_midpoint() { 168 | let start = NodeID::new(BigUint::from(10u8)); 169 | let end = NodeID::new(BigUint::from(20u8)); 170 | let bucket = Bucket::new(start, end); 171 | assert_eq!(BigUint::from(15u8), *bucket.midpoint()); 172 | } 173 | 174 | #[test] 175 | fn split() { 176 | let start = NodeID::new(BigUint::from(10u8)); 177 | let end = NodeID::new(BigUint::from(16u8)); 178 | let mut bucket = Bucket::new(start, end); 179 | 180 | for i in 10..16 { 181 | bucket.add_node(Node::new_with_id(i)); 182 | } 183 | 184 | assert_eq!(bucket.nodes.len(), 6); 185 | 186 | let next_bucket = bucket.split(); 187 | 188 | for i in (10 as u8)..13 { 189 | let id = NodeID::new(BigUint::from(i)); 190 | 191 | assert!(bucket.get(&id).is_some()); 192 | assert!(next_bucket.get(&id).is_none()); 193 | } 194 | 195 | for i in (13 as u8)..16 { 196 | let id = NodeID::new(BigUint::from(i)); 197 | 198 | assert!(bucket.get(&id).is_none()); 199 | assert!(next_bucket.get(&id).is_some()); 200 | } 201 | } 202 | 203 | #[test] 204 | fn get_empty() { 205 | let bucket = Bucket::initial_bucket(); 206 | assert_eq!(bucket.get(&NodeID::new(BigUint::from(10u8))), None); 207 | } 208 | 209 | #[test] 210 | fn get_some() { 211 | let mut bucket = Bucket::initial_bucket(); 212 | let node = Node::new_with_id(113); 213 | let id = node.id.clone(); 214 | bucket.add_node(node); 215 | 216 | assert!(bucket.get(&id).is_some()); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /packages/dht_crawler/src/dht/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addr::AsV4Address, 3 | dht::Dht, 4 | errors::{ 5 | ErrorKind, 6 | Result, 7 | }, 8 | routing::{ 9 | FindNodeResult, 10 | RoutingTable, 11 | }, 12 | }; 13 | use futures::Stream; 14 | use futures_util::stream::StreamExt; 15 | use krpc_encoding::{ 16 | Addr, 17 | Envelope, 18 | Message, 19 | NodeID, 20 | Query, 21 | Response, 22 | }; 23 | use std::{ 24 | net::{ 25 | SocketAddr, 26 | SocketAddrV4, 27 | }, 28 | ops::DerefMut, 29 | }; 30 | use tokio_krpc::InboundQuery; 31 | 32 | impl Dht { 33 | pub(super) async fn handle_requests>>( 34 | self, 35 | stream: S, 36 | ) { 37 | let mut stream = stream.boxed_local(); 38 | 39 | loop { 40 | let (head, tail) = stream.into_future().await; 41 | if let Some(result) = head { 42 | self.process_request(result) 43 | .await 44 | .unwrap_or_else(|err| eprintln!("Error While Handling Requests: {}", err)); 45 | } else { 46 | return; 47 | } 48 | 49 | stream = tail 50 | } 51 | } 52 | 53 | async fn process_request(&self, result: Result<(InboundQuery, SocketAddr)>) -> Result<()> { 54 | let (request, from) = result?; 55 | let response = self.handle_request(request, from.into_v4()?); 56 | self.send_transport.send(from, response).await?; 57 | 58 | Ok(()) 59 | } 60 | 61 | fn handle_request(&self, request: InboundQuery, from: SocketAddrV4) -> Envelope { 62 | let result = match request.query { 63 | Query::Ping { id } => self.handle_ping(from, id, request.read_only), 64 | Query::FindNode { id, target } => { 65 | self.handle_find_node(from, id, target, request.read_only) 66 | } 67 | Query::GetPeers { id, info_hash } => { 68 | self.handle_get_peers(from, id, info_hash, request.read_only) 69 | } 70 | Query::AnnouncePeer { 71 | id, 72 | implied_port, 73 | port, 74 | info_hash, 75 | token, 76 | } => self.handle_announce_peer( 77 | from, 78 | id, 79 | implied_port, 80 | port, 81 | info_hash, 82 | token, 83 | request.read_only, 84 | ), 85 | _ => Err(ErrorKind::UnimplementedRequestType.into()), 86 | }; 87 | 88 | let message_type = match result { 89 | Ok(response) => Message::Response { response }, 90 | Err(err) => Message::Error { 91 | error: err.as_request_error(), 92 | }, 93 | }; 94 | 95 | Envelope { 96 | ip: None, 97 | transaction_id: request.transaction_id, 98 | version: None, 99 | message_type, 100 | read_only: false, 101 | } 102 | } 103 | 104 | fn handle_ping(&self, from: SocketAddrV4, id: NodeID, read_only: bool) -> Result { 105 | let mut routing_table = self.routing_table.lock()?; 106 | record_request(&mut routing_table, id, from, read_only)?; 107 | 108 | Ok(Response::OnlyID { 109 | id: self.id.clone(), 110 | }) 111 | } 112 | 113 | fn handle_find_node( 114 | &self, 115 | from: SocketAddrV4, 116 | id: NodeID, 117 | target: NodeID, 118 | read_only: bool, 119 | ) -> Result { 120 | let mut routing_table = self.routing_table.lock()?; 121 | record_request(&mut routing_table, id, from, read_only)?; 122 | 123 | let nodes = match routing_table.find_node(&target) { 124 | FindNodeResult::Node(node) => vec![node], 125 | FindNodeResult::Nodes(nodes) => nodes, 126 | }; 127 | 128 | Ok(Response::NextHop { 129 | id: self.id.clone(), 130 | token: None, 131 | nodes, 132 | }) 133 | } 134 | 135 | fn handle_get_peers( 136 | &self, 137 | from: SocketAddrV4, 138 | id: NodeID, 139 | info_hash: NodeID, 140 | read_only: bool, 141 | ) -> Result { 142 | let mut routing_table = self.routing_table.lock()?; 143 | record_request(&mut routing_table, id, from, read_only)?; 144 | 145 | let token_bytes = routing_table.generate_token(&from).to_vec(); 146 | let token = Some(token_bytes); 147 | let torrents = self.torrents.lock()?; 148 | let torrent = torrents.get(&info_hash); 149 | 150 | if let Some(peers) = torrent { 151 | Ok(Response::GetPeers { 152 | id: self.id.clone(), 153 | token, 154 | peers: peers.iter().map(|peer| Addr::from(peer.clone())).collect(), 155 | }) 156 | } else { 157 | let nodes = routing_table.find_nodes(&info_hash); 158 | 159 | Ok(Response::NextHop { 160 | id: self.id.clone(), 161 | token, 162 | nodes, 163 | }) 164 | } 165 | } 166 | 167 | fn handle_announce_peer( 168 | &self, 169 | mut from: SocketAddrV4, 170 | id: NodeID, 171 | implied_port: bool, 172 | port: Option, 173 | info_hash: NodeID, 174 | token: Vec, 175 | read_only: bool, 176 | ) -> Result { 177 | let mut routing_table = self.routing_table.lock()?; 178 | 179 | if !routing_table.verify_token(&token, &from) { 180 | return Err(ErrorKind::InvalidToken)?; 181 | }; 182 | 183 | let addr = if implied_port { 184 | from 185 | } else { 186 | let actual_port = match port { 187 | None => return Err(ErrorKind::InsufficientAddress)?, 188 | Some(port) => port, 189 | }; 190 | 191 | from.set_port(actual_port); 192 | from 193 | }; 194 | 195 | record_request(&mut routing_table, id, from, read_only)?; 196 | 197 | let mut torrents = self.torrents.lock()?; 198 | 199 | torrents 200 | .entry(info_hash) 201 | .or_insert_with(Vec::new) 202 | .push(addr); 203 | 204 | Ok(Response::OnlyID { 205 | id: self.id.clone(), 206 | }) 207 | } 208 | } 209 | 210 | fn record_request>( 211 | routing_table: &mut T, 212 | id: NodeID, 213 | from: SocketAddrV4, 214 | read_only: bool, 215 | ) -> Result<()> { 216 | if !read_only { 217 | routing_table 218 | .deref_mut() 219 | .get_or_add(id, from) 220 | .map(|node| node.mark_successful_request_from()); 221 | } 222 | 223 | Ok(()) 224 | } 225 | -------------------------------------------------------------------------------- /packages/routing_table/src/routing_table.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | full_b_tree::FullBTreeNode, 3 | generator::GeneratorExt, 4 | k_bucket::KBucket, 5 | node_contact_state::NodeContactState, 6 | transport::LivenessTransport, 7 | }; 8 | use async_recursion::async_recursion; 9 | use krpc_encoding::{ 10 | NodeID, 11 | NodeInfo, 12 | NODE_ID_SIZE_BITS, 13 | }; 14 | use log::{ 15 | as_error, 16 | debug, 17 | }; 18 | use std::{ 19 | collections::{ 20 | HashSet, 21 | VecDeque, 22 | }, 23 | net::SocketAddrV4, 24 | }; 25 | use tokio_krpc::RequestTransport; 26 | 27 | /// A routing table which holds information about nodes in the network. 28 | pub struct RoutingTable { 29 | id: NodeID, 30 | root: FullBTreeNode, 31 | transport: LivenessTransport, 32 | } 33 | 34 | impl RoutingTable { 35 | pub fn new(id: NodeID, request_transport: RequestTransport) -> RoutingTable { 36 | RoutingTable { 37 | id, 38 | root: FullBTreeNode::Leaf(KBucket::initial()), 39 | transport: LivenessTransport::new(request_transport), 40 | } 41 | } 42 | 43 | pub async fn bootstrap(&mut self, address: SocketAddrV4) { 44 | let mut nodes = VecDeque::from([address]); 45 | let mut visited = HashSet::new(); 46 | visited.insert(address); 47 | 48 | while let Some(next_node) = nodes.pop_front() { 49 | let result = self.transport.find_node(next_node.clone(), self.id.clone()).await; 50 | 51 | match result { 52 | Err(err) => { 53 | debug!(err = as_error!(err); "find_node failed during bootstrap"); 54 | } 55 | Ok(response) => { 56 | // add to routing table 57 | match self 58 | .add_node(&NodeInfo { 59 | address, 60 | node_id: response.id.clone(), 61 | }) 62 | .await 63 | { 64 | Some(it) => it.mark_successful_query(), 65 | None => { 66 | // todo: how to know when to stop 67 | return; 68 | } 69 | } 70 | 71 | for node in response.nodes { 72 | if !visited.contains(&node.address) { 73 | visited.insert(node.address.clone()); 74 | nodes.push_back(node.address); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | /// Tries to add a node to the routing table, evicting nodes which have 83 | /// gone offline and growing the routing table as needed. 84 | /// 85 | /// If the routing table is full, returns None. 86 | pub async fn add_node(&mut self, node_info: &NodeInfo) -> Option<&mut NodeContactState> { 87 | Self::add_node_rec(&self.id, &self.transport, &mut self.root, node_info, 0).await 88 | } 89 | 90 | fn find_nodes_generator_rec( 91 | root: &FullBTreeNode, 92 | node_id: NodeID, 93 | depth: usize, 94 | ) -> Box + '_> { 95 | Box::new( 96 | (move || match root { 97 | FullBTreeNode::Inner(ref inner) => { 98 | let bit = node_id.nth_bit(depth); 99 | let (matching_branch, other_branch) = if bit { 100 | (&inner.left, &inner.right) 101 | } else { 102 | (&inner.right, &inner.left) 103 | }; 104 | 105 | for value in 106 | Self::find_nodes_generator_rec(matching_branch, node_id.clone(), depth + 1) 107 | { 108 | yield value; 109 | } 110 | 111 | for value in 112 | Self::find_nodes_generator_rec(other_branch, node_id.clone(), depth + 1) 113 | { 114 | yield value; 115 | } 116 | } 117 | FullBTreeNode::Leaf(values) => { 118 | for node in values.good_nodes() { 119 | yield node; 120 | } 121 | } 122 | }) 123 | .iter(), 124 | ) 125 | } 126 | 127 | fn find_nodes_generator(&self, id: NodeID) -> impl Iterator + '_ { 128 | Self::find_nodes_generator_rec(&self.root, id, 0) 129 | } 130 | 131 | pub fn find_node(&self, id: NodeID) -> FindNodeResult { 132 | let closest_nodes = self 133 | .find_nodes_generator(id.clone()) 134 | .into_iter() 135 | .take(8) 136 | .collect::>(); 137 | 138 | match closest_nodes 139 | .iter() 140 | .enumerate() 141 | .find(|(_, node)| &node.node_id == &id) 142 | .map(|(idx, _)| idx) 143 | { 144 | Some(node) => FindNodeResult::Node(closest_nodes[node].clone()), 145 | None => FindNodeResult::Nodes(closest_nodes), 146 | } 147 | } 148 | 149 | fn find_bucket_mut_recursive<'a>( 150 | root: &'a mut FullBTreeNode, 151 | node_id: &NodeID, 152 | depth: usize, 153 | ) -> (&'a mut FullBTreeNode, usize) { 154 | match root { 155 | FullBTreeNode::Inner(ref mut inner) => { 156 | let bit = node_id.nth_bit(depth); 157 | let root = if bit { 158 | &mut inner.left 159 | } else { 160 | &mut inner.right 161 | }; 162 | 163 | return Self::find_bucket_mut_recursive(root, node_id, depth + 1); 164 | } 165 | FullBTreeNode::Leaf(_) => (root, depth), 166 | } 167 | } 168 | 169 | #[async_recursion(?Send)] 170 | async fn add_node_rec<'a>( 171 | owner_id: &NodeID, 172 | transport: &LivenessTransport, 173 | root_node: &'a mut FullBTreeNode, 174 | node_info: &NodeInfo, 175 | starting_depth: usize, 176 | ) -> Option<&'a mut NodeContactState> { 177 | let (leaf_bucket, depth) = 178 | Self::find_bucket_mut_recursive(root_node, &node_info.node_id, starting_depth); 179 | 180 | let leaf_k_bucket = leaf_bucket.unwrap_as_leaf(); 181 | 182 | let result = leaf_k_bucket.try_add(node_info, transport).await; 183 | 184 | if let Some(node_index) = result { 185 | let raw = leaf_k_bucket as *mut KBucket; 186 | 187 | // Ignore the borrow checker, it is incorrect here. We can safely 188 | // return a reference here because the branches of the if statement 189 | // are exclusive. If this if statement executes, we can safely take 190 | // a mutable borrow over leaf_k_bucket. If it does not, the split 191 | // can safely take a reference over leaf_bucket. 192 | // https://blog.rust-lang.org/2022/08/05/nll-by-default.html 193 | unsafe { 194 | let raw_ref = &mut *raw; 195 | return Some(raw_ref.get_node_mut(node_index)); 196 | } 197 | } 198 | 199 | if !leaf_k_bucket.can_split() { 200 | return None; 201 | } 202 | 203 | // don't allow a tree with more than 160 levels 204 | if depth >= NODE_ID_SIZE_BITS - 1 { 205 | return None; 206 | } 207 | 208 | leaf_bucket.split(owner_id, depth); 209 | 210 | Self::add_node_rec(owner_id, transport, leaf_bucket, node_info, depth).await 211 | } 212 | } 213 | 214 | pub enum FindNodeResult { 215 | Node(NodeInfo), 216 | Nodes(Vec), 217 | } 218 | -------------------------------------------------------------------------------- /packages/krpc_encoding/src/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | booleans, 3 | errors::{ 4 | ErrorKind, 5 | Result, 6 | }, 7 | node_info, 8 | Addr, 9 | NodeID, 10 | NodeInfo, 11 | }; 12 | use serde_bencode; 13 | use serde_bytes::{ 14 | self, 15 | ByteBuf, 16 | }; 17 | use serde_derive::{ 18 | Deserialize, 19 | Serialize, 20 | }; 21 | use std::fmt; 22 | 23 | /// Envelope holding information common to requests and responses 24 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 25 | pub struct Envelope { 26 | /// Public IP address of the requester. Only sent by peers supporting 27 | /// [BEP-0042]. 28 | /// 29 | /// [BEP-0042]: http://www.bittorrent.org/beps/bep_0042.html 30 | pub ip: Option, 31 | 32 | /// Transaction ID generated by the querying node and echoed in the 33 | /// response. Used to correlate requests and responses. 34 | #[serde(rename = "t", with = "serde_bytes")] 35 | pub transaction_id: Vec, 36 | 37 | /// Client version string 38 | #[serde(rename = "v")] 39 | pub version: Option, 40 | 41 | #[serde(flatten)] 42 | pub message_type: Message, 43 | 44 | /// Sent by read-only DHT nodes defined in [BEP-0043] 45 | /// 46 | /// [BEP-0043]: http://www.bittorrent.org/beps/bep_0043.html 47 | #[serde( 48 | rename = "ro", 49 | default, 50 | skip_serializing_if = "booleans::is_false", 51 | deserialize_with = "booleans::deserialize" 52 | )] 53 | pub read_only: bool, 54 | } 55 | 56 | impl Envelope { 57 | pub fn decode(bytes: &[u8]) -> Result { 58 | Ok(serde_bencode::de::from_bytes(bytes) 59 | .map_err(|cause| ErrorKind::DecodeError { cause })?) 60 | } 61 | 62 | pub fn encode(&self) -> Result> { 63 | Ok(serde_bencode::ser::to_bytes(self).map_err(|cause| ErrorKind::EncodeError { cause })?) 64 | } 65 | } 66 | 67 | /// Messages sent and received by nodes 68 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 69 | #[serde(tag = "y")] 70 | pub enum Message { 71 | #[serde(rename = "q")] 72 | Query { 73 | #[serde(flatten)] 74 | query: Query, 75 | }, 76 | 77 | #[serde(rename = "r")] 78 | Response { 79 | #[serde(rename = "r")] 80 | response: Response, 81 | }, 82 | 83 | #[serde(rename = "e")] 84 | Error { 85 | #[serde(rename = "e")] 86 | error: KRPCError, 87 | }, 88 | } 89 | 90 | /// Error sent when a query cannot be fulfilled 91 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] 92 | pub struct KRPCError(u8, String); 93 | 94 | impl KRPCError { 95 | pub fn new(error_code: u8, message: &str) -> KRPCError { 96 | KRPCError(error_code, message.to_string()) 97 | } 98 | } 99 | 100 | impl fmt::Display for KRPCError { 101 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | write!(f, "{:?}", self) 103 | } 104 | } 105 | 106 | /// Possible queries 107 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 108 | #[serde(tag = "q", content = "a")] 109 | pub enum Query { 110 | /// Most basic query 111 | /// 112 | /// The appropriate response to a ping is [`Response::OnlyID`] with the node 113 | /// ID of the responding node. 114 | #[serde(rename = "ping")] 115 | Ping { 116 | /// Sender's node ID 117 | id: NodeID, 118 | }, 119 | 120 | /// Used to find the contact information for a node given its ID. 121 | /// 122 | /// When a node receives this query, it should respond with a 123 | /// [`Response::NextHop`] with the target node or the K (8) closest good 124 | /// nodes in its own routing table. 125 | #[serde(rename = "find_node")] 126 | FindNode { 127 | /// Node ID of the querying node 128 | id: NodeID, 129 | 130 | /// ID of the node being searched for 131 | target: NodeID, 132 | }, 133 | 134 | /// Get peers associated with a torrent infohash. 135 | /// 136 | /// If the queried node has no peers for the infohash, [`Response::NextHop`] 137 | /// will be returned containing the K nodes in the queried nodes routing 138 | /// table closest to the infohash supplied in the query. Otherwise, 139 | /// [`Response::GetPeers`] will be returned. 140 | /// 141 | /// In either case a `token` is included in the return value. The 142 | /// token value is a required argument for a future [Query::AnnouncePeer]. 143 | /// The token value should be a short binary string. 144 | #[serde(rename = "get_peers")] 145 | GetPeers { 146 | /// Node ID of the querying node 147 | id: NodeID, 148 | 149 | /// Infohash of the torrent searching for peers of 150 | info_hash: NodeID, 151 | }, 152 | 153 | /// Announce that the peer, controlling the querying node, is downloading a 154 | /// torrent on a port. 155 | /// 156 | /// The queried node must verify that the token was previously sent to the 157 | /// same IP address as the querying node. Then the queried node should 158 | /// store the IP address of the querying node and the supplied port 159 | /// number under the infohash in its store of peer contact information. 160 | #[serde(rename = "announce_peer")] 161 | AnnouncePeer { 162 | /// Node ID of the querying node 163 | id: NodeID, 164 | 165 | /// Whether or not the peer's port is implied by the source port of the 166 | /// UDP packet containing this query 167 | /// 168 | /// If `true`, the value of `port` should be ignored. This is useful for 169 | /// peers behind a NAT that may not know their external port, and 170 | /// supporting uTP, they accept incoming connections on the same port as 171 | /// the DHT port. 172 | #[serde(deserialize_with = "booleans::deserialize")] 173 | implied_port: bool, 174 | 175 | /// Peer's port 176 | port: Option, 177 | 178 | /// Infohash of the torrent being announced 179 | info_hash: NodeID, 180 | 181 | /// Token received in response to a previous [Query::GetPeers] 182 | #[serde(with = "serde_bytes")] 183 | token: Vec, 184 | }, 185 | 186 | /// `sample_infohashes` query from [BEP-0051] 187 | /// 188 | /// [BEP-0051]: http://www.bittorrent.org/beps/bep_0051.html 189 | #[serde(rename = "sample_infohashes")] 190 | SampleInfoHashes { 191 | /// Node ID of the querying node 192 | id: NodeID, 193 | target: NodeID, 194 | }, 195 | } 196 | 197 | /// Possible responses 198 | /// 199 | /// See [`Query`] to understand when each variant is used. 200 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 201 | #[serde(untagged)] 202 | pub enum Response { 203 | NextHop { 204 | /// Identifier of queried node 205 | id: NodeID, 206 | 207 | /// Token used in [Query::AnnouncePeer] 208 | /// 209 | /// Empty when the responder decides we are unfit to send AnnouncePeer 210 | /// messages by [BEP-0042]. 211 | /// 212 | /// [BEP-0042]: http://www.bittorrent.org/beps/bep_0042.html 213 | token: Option>, 214 | 215 | #[serde(with = "node_info")] 216 | nodes: Vec, 217 | }, 218 | 219 | GetPeers { 220 | /// Identifier of queried node 221 | id: NodeID, 222 | 223 | /// Token used in [`Query::AnnouncePeer`] 224 | /// 225 | /// Empty when the responder decides we are unfit to send AnnouncePeer 226 | /// messages by [BEP-0042]. 227 | /// 228 | /// [BEP-0042]: http://www.bittorrent.org/beps/bep_0042.html 229 | token: Option>, 230 | 231 | #[serde(rename = "values")] 232 | peers: Vec, 233 | }, 234 | 235 | /// Response to [`Query::Ping`] and [`Query::AnnouncePeer`] 236 | OnlyID { 237 | /// Identifier of queried node 238 | id: NodeID, 239 | }, 240 | 241 | /// Response to [`Query::SampleInfoHashes`] 242 | Samples { 243 | /// Identifier of queried node 244 | id: NodeID, 245 | 246 | /// Number of seconds this node should not be queried again for 247 | interval: Option, 248 | 249 | /// Nodes close to target in request 250 | #[serde(with = "node_info")] 251 | nodes: Vec, 252 | 253 | /// Number of info hashes this peer has 254 | num: Option, 255 | 256 | /// Sample of info-hashes 257 | samples: Vec, 258 | }, 259 | } 260 | -------------------------------------------------------------------------------- /packages/routing_table/src/node_contact_state.rs: -------------------------------------------------------------------------------- 1 | use chrono::{ 2 | NaiveDateTime, 3 | Utc, 4 | }; 5 | use krpc_encoding::NodeID; 6 | use std::net::SocketAddrV4; 7 | 8 | pub struct NodeContactState { 9 | pub id: NodeID, 10 | 11 | pub address: SocketAddrV4, 12 | 13 | /// Last time a successful query was made to this node. 14 | last_successful_query_to: Option, 15 | 16 | /// Last time a valid query was received from this node. 17 | last_request_from: Option, 18 | 19 | /// Number of failed queries to the node since [`last_successful_query_to`]. 20 | failed_queries: u8, 21 | } 22 | 23 | impl NodeContactState { 24 | pub fn new(id: NodeID, address: SocketAddrV4) -> Self { 25 | NodeContactState { 26 | id, 27 | address, 28 | last_successful_query_to: None, 29 | last_request_from: None, 30 | failed_queries: 0, 31 | } 32 | } 33 | 34 | pub fn failed_queries(&self) -> u8 { 35 | self.failed_queries 36 | } 37 | 38 | /// Update internal state to reflect a successful query happened. 39 | pub fn mark_successful_query(&mut self) { 40 | self.failed_queries = 0; 41 | self.last_successful_query_to = Some(Utc::now().naive_utc()); 42 | } 43 | 44 | /// Update internal state to reflect a query to this node failed. 45 | pub fn mark_failed_query(&mut self) { 46 | self.failed_queries += 1; 47 | } 48 | 49 | /// Update internal state to reflect a successful request has been received 50 | /// from a node. 51 | pub fn mark_successful_request(&mut self) { 52 | self.last_request_from = Some(Utc::now().naive_utc()); 53 | } 54 | 55 | pub fn state(&self) -> NodeState { 56 | let now = Utc::now().naive_utc(); 57 | 58 | if self.failed_queries >= 2 { 59 | return NodeState::Bad; 60 | }; 61 | 62 | match (self.last_request_from, self.last_successful_query_to) { 63 | (_, Some(last_request_to)) 64 | if now.signed_duration_since(last_request_to).num_minutes() < 15 => 65 | { 66 | NodeState::Good 67 | } 68 | (Some(last_request_from), Some(..)) 69 | if now.signed_duration_since(last_request_from).num_minutes() < 15 => 70 | { 71 | NodeState::Good 72 | } 73 | _ => NodeState::Questionable, 74 | } 75 | } 76 | 77 | pub fn last_contacted(&self) -> Option { 78 | match (self.last_request_from, self.last_successful_query_to) { 79 | (Some(last_request_from), Some(last_request_to)) => { 80 | Some(last_request_from.max(last_request_to)) 81 | } 82 | (Some(last_request_from), None) => Some(last_request_from), 83 | (None, Some(last_request_to)) => Some(last_request_to), 84 | (None, None) => None, 85 | } 86 | } 87 | 88 | pub fn update_from_result(&mut self, result: Result) -> Result { 89 | match result { 90 | Ok(_) => self.mark_successful_query(), 91 | Err(_) => self.mark_failed_query(), 92 | }; 93 | 94 | result 95 | } 96 | } 97 | 98 | #[derive(Debug, PartialEq)] 99 | pub enum NodeState { 100 | /// A good node is a node has responded to one of our queries within the 101 | /// last 15 minutes. A node is also good if it has ever responded to one 102 | /// of our queries and has sent us a query within the last 15 minutes. 103 | Good, 104 | 105 | /// After 15 minutes of inactivity, a node becomes questionable. 106 | Questionable, 107 | 108 | /// Nodes become bad when they fail to respond to multiple queries in a row. 109 | /// At this point, they are not sent to other peers. They are replaced 110 | /// with new good nodes. 111 | Bad, 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::{ 117 | NodeContactState, 118 | NodeState, 119 | }; 120 | use chrono::{ 121 | prelude::*, 122 | Duration, 123 | }; 124 | type Error = Box; 125 | 126 | fn make_node() -> Result { 127 | Ok(NodeContactState::new( 128 | b"0000000000000000000000000000000000000000".into(), 129 | "127.0.0.1:3000".parse()?, 130 | )) 131 | } 132 | 133 | #[test] 134 | fn starting_state() -> Result<(), Error> { 135 | let node = make_node()?; 136 | 137 | assert_eq!(node.state(), NodeState::Questionable); 138 | 139 | Ok(()) 140 | } 141 | 142 | #[test] 143 | fn good_state_request() -> Result<(), Error> { 144 | let mut node = make_node()?; 145 | node.mark_successful_query(); 146 | 147 | assert_eq!(node.state(), NodeState::Good); 148 | 149 | Ok(()) 150 | } 151 | 152 | #[test] 153 | fn response_only_questionable() -> Result<(), Error> { 154 | let mut node = make_node()?; 155 | node.mark_successful_request(); 156 | 157 | assert_eq!(node.state(), NodeState::Questionable); 158 | 159 | Ok(()) 160 | } 161 | 162 | #[test] 163 | fn bad_state() -> Result<(), Error> { 164 | let mut node = make_node()?; 165 | node.mark_failed_query(); 166 | assert_eq!(node.state(), NodeState::Questionable); 167 | 168 | node.mark_failed_query(); 169 | assert_eq!(node.state(), NodeState::Bad); 170 | 171 | Ok(()) 172 | } 173 | 174 | #[test] 175 | fn request_response_good() -> Result<(), Error> { 176 | let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) 177 | .unwrap() 178 | .and_hms_milli_opt(0, 0, 1, 980) 179 | .unwrap(); 180 | 181 | let node = NodeContactState { 182 | id: b"0000000000000000000000000000000000000000".into(), 183 | address: "127.0.0.1:3000".parse()?, 184 | last_successful_query_to: Some(epoch), 185 | last_request_from: Some(Utc::now().naive_utc() - Duration::minutes(10)), 186 | failed_queries: 0, 187 | }; 188 | 189 | assert_eq!(node.state(), NodeState::Good); 190 | 191 | Ok(()) 192 | } 193 | 194 | #[test] 195 | fn last_contacted_none() -> Result<(), Error> { 196 | let node = NodeContactState { 197 | id: b"0000000000000000000000000000000000000000".into(), 198 | address: "127.0.0.1:3000".parse()?, 199 | last_successful_query_to: None, 200 | last_request_from: None, 201 | failed_queries: 0, 202 | }; 203 | 204 | assert_eq!(node.last_contacted(), None); 205 | 206 | Ok(()) 207 | } 208 | 209 | #[test] 210 | fn last_contacted_query() -> Result<(), Error> { 211 | let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) 212 | .unwrap() 213 | .and_hms_milli_opt(0, 0, 1, 980) 214 | .unwrap(); 215 | 216 | let node = NodeContactState { 217 | id: b"0000000000000000000000000000000000000000".into(), 218 | address: "127.0.0.1:3000".parse()?, 219 | last_successful_query_to: Some(epoch), 220 | last_request_from: None, 221 | failed_queries: 0, 222 | }; 223 | 224 | assert_eq!(node.last_contacted(), Some(epoch)); 225 | 226 | Ok(()) 227 | } 228 | 229 | #[test] 230 | fn last_contacted_request() -> Result<(), Error> { 231 | let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) 232 | .unwrap() 233 | .and_hms_milli_opt(0, 0, 1, 980) 234 | .unwrap(); 235 | 236 | let node = NodeContactState { 237 | id: b"0000000000000000000000000000000000000000".into(), 238 | address: "127.0.0.1:3000".parse()?, 239 | last_successful_query_to: None, 240 | last_request_from: Some(epoch), 241 | failed_queries: 0, 242 | }; 243 | 244 | assert_eq!(node.last_contacted(), Some(epoch)); 245 | 246 | Ok(()) 247 | } 248 | 249 | #[test] 250 | fn last_contacted_both() -> Result<(), Error> { 251 | let earlier = NaiveDate::from_ymd_opt(1970, 1, 1) 252 | .unwrap() 253 | .and_hms_milli_opt(0, 0, 1, 980) 254 | .unwrap(); 255 | let later = NaiveDate::from_ymd_opt(1970, 1, 1) 256 | .unwrap() 257 | .and_hms_milli_opt(0, 0, 10, 980) 258 | .unwrap(); 259 | 260 | let node = NodeContactState { 261 | id: b"0000000000000000000000000000000000000000".into(), 262 | address: "127.0.0.1:3000".parse()?, 263 | last_successful_query_to: Some(earlier), 264 | last_request_from: Some(later), 265 | failed_queries: 0, 266 | }; 267 | 268 | assert_eq!(node.last_contacted(), Some(later)); 269 | 270 | Ok(()) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /packages/routing_table/src/k_bucket.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | full_b_tree::{ 3 | FullBTreeInnerNode, 4 | FullBTreeNode, 5 | }, 6 | node_contact_state::{ 7 | NodeContactState, 8 | NodeState, 9 | }, 10 | transport::LivenessTransport, 11 | }; 12 | use krpc_encoding::{ 13 | NodeID, 14 | NodeInfo, 15 | }; 16 | use log::{ 17 | as_error, 18 | debug, 19 | }; 20 | use std::cmp::Ordering; 21 | 22 | const K_BUCKET_SIZE: usize = 8; 23 | 24 | /// A bucket which holds a maximum of `k` nodes. 25 | pub struct KBucket { 26 | contacts: Vec, 27 | leaf_type: LeafType, 28 | } 29 | 30 | impl KBucket { 31 | pub fn initial() -> KBucket { 32 | KBucket { 33 | contacts: Vec::new(), 34 | leaf_type: LeafType::Near, 35 | } 36 | } 37 | 38 | pub fn get_node_index(&self, node_id: &NodeID) -> Option { 39 | self.contacts 40 | .iter() 41 | .enumerate() 42 | .find(|(_, node)| &node.id == node_id) 43 | .map(|(idx, _node)| idx) 44 | } 45 | 46 | pub fn get_node_mut(&mut self, index: usize) -> &mut NodeContactState { 47 | &mut self.contacts[index] 48 | } 49 | 50 | pub fn good_nodes(&self) -> impl Iterator + '_ { 51 | self.contacts 52 | .iter() 53 | .filter(|it| it.state() == NodeState::Good) 54 | .map(|it| NodeInfo::new(it.id.clone(), it.address.clone())) 55 | } 56 | 57 | /// Removes a bad node if there is one. 58 | pub fn take_bad_node(&mut self) -> Option { 59 | let idx = self 60 | .contacts 61 | .iter() 62 | .enumerate() 63 | .filter(|(_idx, it)| it.state() == NodeState::Bad) 64 | .map(|(idx, _)| idx) 65 | .next()?; 66 | 67 | Some(self.contacts.remove(idx)) 68 | } 69 | 70 | /// Returns the least recently seen questionable node. 71 | pub fn take_questionable_node(&mut self) -> Option { 72 | let idx = self 73 | .contacts 74 | .iter() 75 | .enumerate() 76 | .filter(|(_idx, it)| it.state() == NodeState::Questionable) 77 | .min_by(|(_, lhs), (_, rhs)| { 78 | let failed_queries_cmp = lhs.failed_queries().cmp(&rhs.failed_queries()); 79 | if let Ordering::Greater | Ordering::Less = failed_queries_cmp { 80 | return failed_queries_cmp; 81 | } 82 | 83 | match (lhs.last_contacted(), rhs.last_contacted()) { 84 | (None, None) => Ordering::Equal, 85 | (Some(lhs_last_contacted), Some(rhs_last_contacted)) => { 86 | lhs_last_contacted.cmp(&rhs_last_contacted) 87 | } 88 | (Some(_), None) => Ordering::Greater, 89 | (None, Some(_)) => Ordering::Less, 90 | } 91 | }) 92 | .map(|(idx, _)| idx)?; 93 | 94 | Some(self.contacts.remove(idx)) 95 | } 96 | 97 | /// Returns true if there definitely is space in the bucket. 98 | pub fn definitely_has_remaining_space(&self) -> bool { 99 | self.contacts.len() < K_BUCKET_SIZE 100 | } 101 | 102 | fn add_node(&mut self, node_info: &NodeInfo) -> usize { 103 | let node_contact_state = 104 | NodeContactState::new(node_info.node_id.clone(), node_info.address); 105 | 106 | self.contacts.push(node_contact_state); 107 | let len = self.contacts.len(); 108 | len - 1 109 | } 110 | 111 | /// Try to add node to this bucket. If the bucket is full, first tries to 112 | /// evict bad nodes then tries to evict questionable nodes. If all fails, 113 | /// returns control to the caller to handle splitting the bucket. 114 | pub async fn try_add( 115 | &mut self, 116 | node_info: &NodeInfo, 117 | transport: &LivenessTransport, 118 | ) -> Option { 119 | // It is necessary to split this into a check and then a separate get 120 | // which does not borrow self because of limitations in the borrow 121 | // checker. 122 | // https://blog.rust-lang.org/2022/08/05/nll-by-default.html 123 | if let Some(node_index) = self.get_node_index(&node_info.node_id) { 124 | return Some(node_index); 125 | } 126 | 127 | // if there's space, add without worrying about evictions 128 | let has_space = self.definitely_has_remaining_space(); 129 | if has_space { 130 | return Some(self.add_node(node_info)); 131 | } 132 | 133 | // evict a bad node to make space 134 | if let Some(_) = self.take_bad_node() { 135 | return Some(self.add_node(node_info)); 136 | } 137 | 138 | loop { 139 | // try to evict questionable nodes until there are no more 140 | // questionable nodes 141 | match self.evict_questionable_node(transport).await { 142 | None => { 143 | break; 144 | } 145 | Some(true) => { 146 | return Some(self.add_node(node_info)); 147 | } 148 | Some(false) => { 149 | continue; 150 | } 151 | } 152 | } 153 | 154 | None 155 | } 156 | 157 | pub fn split(&mut self, owner_id: &NodeID, depth: usize) -> (KBucket, KBucket) { 158 | let (zero_bit_nodes, one_bit_nodes) = self 159 | .contacts 160 | .drain(..) 161 | .partition(|node| !node.id.nth_bit(depth)); 162 | let owner_is_one_bit = owner_id.nth_bit(depth); 163 | 164 | ( 165 | KBucket { 166 | contacts: zero_bit_nodes, 167 | leaf_type: if owner_is_one_bit { 168 | LeafType::Far 169 | } else { 170 | LeafType::Near 171 | }, 172 | }, 173 | KBucket { 174 | contacts: one_bit_nodes, 175 | leaf_type: if owner_is_one_bit { 176 | LeafType::Near 177 | } else { 178 | LeafType::Far 179 | }, 180 | }, 181 | ) 182 | } 183 | 184 | pub fn can_split(&self) -> bool { 185 | self.leaf_type.can_split() 186 | } 187 | 188 | /// Tries to evict a questionable node. 189 | /// 190 | /// Returns: 191 | /// * `None` if there are no questionable nodes to try and evict. 192 | /// * `Some(true)` if a node was evicted. 193 | /// * `Some(false)` if pinging the node resulted in the node going from 194 | /// questionable to good 195 | pub async fn evict_questionable_node( 196 | &mut self, 197 | request_transport: &LivenessTransport, 198 | ) -> Option { 199 | let mut questionable_node = self.take_questionable_node()?; 200 | 201 | let result = request_transport.ping(&mut questionable_node).await; 202 | 203 | match result { 204 | Ok(_) => {} 205 | Err(err) => { 206 | debug!(err = as_error!(err); "ping failed") 207 | } 208 | }; 209 | 210 | match questionable_node.state() { 211 | NodeState::Questionable | NodeState::Good => { 212 | self.contacts.push(questionable_node); 213 | Some(false) 214 | } 215 | NodeState::Bad => Some(true), 216 | } 217 | } 218 | } 219 | 220 | impl FullBTreeNode { 221 | pub fn unwrap_as_leaf(&mut self) -> &mut KBucket { 222 | match self { 223 | FullBTreeNode::Leaf(leaf) => leaf, 224 | FullBTreeNode::Inner(_) => panic!("unwrap_as_leaf called on non-leaf"), 225 | } 226 | } 227 | 228 | pub fn split(&mut self, owner_id: &NodeID, depth: usize) { 229 | let leaf = self.unwrap_as_leaf(); 230 | let (lhs, rhs) = leaf.split(owner_id, depth); 231 | 232 | *self = FullBTreeNode::Inner(Box::new(FullBTreeInnerNode { 233 | left: FullBTreeNode::Leaf(lhs), 234 | right: FullBTreeNode::Leaf(rhs), 235 | })); 236 | } 237 | } 238 | 239 | #[derive(PartialEq)] 240 | enum LeafType { 241 | /// This bucket is near our node id. When it becomes too big, it is split. 242 | Near, 243 | 244 | /// This bucket is far from our node id. When it becomes too big, new nodes 245 | /// are ignored. 246 | Far, 247 | } 248 | 249 | impl LeafType { 250 | fn can_split(&self) -> bool { 251 | match self { 252 | LeafType::Far => false, 253 | LeafType::Near => true, 254 | } 255 | } 256 | } 257 | 258 | #[cfg(test)] 259 | mod tests { 260 | use crate::{ 261 | k_bucket::{ 262 | KBucket, 263 | LeafType, 264 | }, 265 | node_contact_state::NodeContactState, 266 | }; 267 | use krpc_encoding::NodeID; 268 | type Error = Box; 269 | 270 | fn make_node() -> Result { 271 | Ok(NodeContactState::new( 272 | NodeID::random(), 273 | "127.0.0.1:3000".parse()?, 274 | )) 275 | } 276 | 277 | #[test] 278 | fn test_take_bad_node() -> Result<(), Error> { 279 | let questionable_node = make_node()?; 280 | 281 | let mut bad_node = make_node()?; 282 | bad_node.mark_failed_query(); 283 | bad_node.mark_failed_query(); 284 | 285 | let bad_node_id = bad_node.id.clone(); 286 | 287 | let mut contacts = KBucket { 288 | contacts: vec![questionable_node, bad_node], 289 | leaf_type: LeafType::Near, 290 | }; 291 | 292 | assert_eq!( 293 | contacts.take_bad_node().map(|node| node.id), 294 | Some(bad_node_id) 295 | ); 296 | 297 | Ok(()) 298 | } 299 | } 300 | 301 | // todo: write tests (run coverage and see what's missing) 302 | -------------------------------------------------------------------------------- /packages/krpc_encoding/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use krpc_encoding::{ 2 | Envelope, 3 | KRPCError, 4 | Message, 5 | NodeInfo, 6 | Query, 7 | Response, 8 | }; 9 | use std::{ 10 | net::SocketAddrV4, 11 | str::FromStr, 12 | }; 13 | type Error = Box; 14 | 15 | fn test_serialize_deserialize(parsed: Envelope, raw: &[u8]) -> Result<(), Error> { 16 | let parsed_encoded = parsed.encode()?; 17 | assert_eq!(raw, &parsed_encoded[..]); 18 | 19 | let raw_decoded = Envelope::decode(raw)?; 20 | assert_eq!(parsed, raw_decoded); 21 | 22 | Ok(()) 23 | } 24 | 25 | #[test] 26 | fn ping_request() -> Result<(), Error> { 27 | let parsed = Envelope { 28 | ip: None, 29 | transaction_id: b"aa".to_vec(), 30 | version: None, 31 | message_type: Message::Query { 32 | query: Query::Ping { 33 | id: b"abcdefghij0123456789".into(), 34 | }, 35 | }, 36 | read_only: false, 37 | }; 38 | 39 | let raw = b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe"; 40 | test_serialize_deserialize(parsed, raw) 41 | } 42 | 43 | #[test] 44 | fn ping_read_only() -> Result<(), Error> { 45 | let parsed = Envelope { 46 | ip: None, 47 | transaction_id: b"aa".to_vec(), 48 | version: None, 49 | message_type: Message::Query { 50 | query: Query::Ping { 51 | id: b"abcdefghij0123456789".into(), 52 | }, 53 | }, 54 | read_only: true, 55 | }; 56 | 57 | let raw = b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping2:roi1e1:t2:aa1:y1:qe"; 58 | test_serialize_deserialize(parsed, raw) 59 | } 60 | 61 | #[test] 62 | fn ping_response() -> Result<(), Error> { 63 | let parsed = Envelope { 64 | ip: None, 65 | transaction_id: b"aa".to_vec(), 66 | version: None, 67 | message_type: Message::Response { 68 | response: Response::OnlyID { 69 | id: b"mnopqrstuvwxyz123456".into(), 70 | }, 71 | }, 72 | read_only: false, 73 | }; 74 | 75 | let raw = b"d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re"; 76 | test_serialize_deserialize(parsed, raw) 77 | } 78 | 79 | #[test] 80 | fn error() -> Result<(), Error> { 81 | let parsed = Envelope { 82 | ip: None, 83 | transaction_id: b"aa".to_vec(), 84 | version: None, 85 | message_type: Message::Error { 86 | error: KRPCError::new(201, "A Generic Error Ocurred"), 87 | }, 88 | read_only: false, 89 | }; 90 | 91 | let raw = b"d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee"; 92 | test_serialize_deserialize(parsed, raw) 93 | } 94 | 95 | #[test] 96 | fn announce_peer_request() -> Result<(), Error> { 97 | let parsed = Envelope { 98 | ip: None, 99 | transaction_id: b"aa".to_vec(), 100 | version: None, 101 | message_type: Message::Query { 102 | query: Query::AnnouncePeer { 103 | id: b"abcdefghij0123456789".into(), 104 | implied_port: true, 105 | port: Some(6881), 106 | info_hash: b"mnopqrstuvwxyz123456".into(), 107 | token: b"aoeusnth".to_vec(), 108 | }, 109 | }, 110 | read_only: false, 111 | }; 112 | 113 | let raw = b"d1:ad2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe"; 114 | test_serialize_deserialize(parsed, raw) 115 | } 116 | 117 | #[test] 118 | fn get_nodes_response() -> Result<(), Error> { 119 | let parsed = Envelope { 120 | ip: None, 121 | transaction_id: b"aa".to_vec(), 122 | version: None, 123 | message_type: Message::Response { 124 | response: Response::NextHop { 125 | id: b"abcdefghij0123456789".into(), 126 | token: None, 127 | nodes: Vec::new(), 128 | }, 129 | }, 130 | read_only: false, 131 | }; 132 | 133 | let serialized = parsed.encode()?; 134 | let decoded = Envelope::decode(&serialized)?; 135 | 136 | assert_eq!(parsed, decoded); 137 | 138 | Ok(()) 139 | } 140 | 141 | #[test] 142 | fn get_nodes_response_decode() -> Result<(), Error> { 143 | let encoded: &[u8] = &[ 144 | 100, 50, 58, 105, 112, 54, 58, 129, 21, 63, 170, 133, 190, 49, 58, 114, 100, 50, 58, 105, 145 | 100, 50, 48, 58, 50, 245, 78, 105, 115, 81, 255, 74, 236, 41, 205, 186, 171, 242, 251, 227, 146 | 70, 124, 194, 103, 53, 58, 110, 111, 100, 101, 115, 52, 49, 54, 58, 48, 33, 11, 23, 67, 40, 147 | 27, 83, 194, 152, 189, 83, 184, 116, 44, 224, 100, 119, 227, 172, 180, 211, 234, 53, 5, 148 | 136, 247, 55, 4, 69, 93, 133, 57, 156, 104, 27, 0, 231, 29, 145, 49, 172, 172, 170, 50, 51, 149 | 36, 37, 147, 240, 49, 120, 205, 9, 249, 147, 103, 202, 47, 147, 118, 247, 56, 14, 110, 23, 150 | 186, 53, 174, 165, 170, 186, 95, 24, 216, 93, 124, 7, 192, 112, 119, 16, 106, 92, 58, 112, 151 | 137, 128, 138, 141, 79, 23, 69, 24, 183, 4, 85, 166, 93, 172, 43, 127, 90, 117, 12, 129, 152 | 47, 223, 197, 10, 15, 183, 213, 97, 35, 240, 235, 237, 50, 252, 249, 194, 225, 219, 70, 153 | 124, 69, 205, 196, 145, 102, 100, 250, 166, 128, 104, 68, 91, 140, 182, 54, 54, 90, 21, 2, 154 | 241, 200, 141, 23, 37, 46, 153, 74, 174, 251, 147, 165, 79, 20, 85, 75, 125, 77, 206, 96, 155 | 25, 32, 99, 225, 224, 103, 85, 243, 146, 250, 181, 81, 97, 116, 190, 26, 225, 222, 157, 156 | 234, 191, 56, 113, 115, 126, 188, 27, 149, 83, 240, 151, 53, 226, 74, 241, 83, 226, 84, 157 | 251, 160, 222, 188, 171, 86, 40, 168, 238, 141, 18, 184, 130, 83, 38, 118, 45, 28, 54, 40, 158 | 41, 156, 202, 216, 46, 98, 13, 2, 205, 26, 225, 63, 156, 12, 215, 19, 180, 67, 243, 186, 159 | 19, 109, 221, 5, 80, 152, 247, 35, 243, 248, 56, 42, 98, 51, 123, 36, 88, 116, 101, 114, 160 | 42, 208, 241, 77, 164, 158, 29, 72, 206, 241, 52, 116, 105, 188, 110, 109, 117, 79, 114, 161 | 47, 76, 250, 186, 139, 146, 146, 178, 247, 93, 18, 119, 32, 235, 205, 138, 254, 102, 191, 162 | 165, 12, 42, 220, 127, 2, 87, 195, 123, 244, 241, 208, 251, 133, 56, 218, 180, 25, 130, 48, 163 | 88, 121, 190, 163, 198, 23, 107, 74, 12, 187, 222, 49, 70, 2, 154, 62, 129, 127, 66, 65, 164 | 164, 135, 151, 240, 82, 208, 230, 231, 249, 209, 128, 98, 123, 231, 28, 218, 245, 70, 55, 165 | 32, 213, 70, 20, 52, 38, 230, 211, 179, 139, 75, 33, 144, 222, 204, 108, 131, 204, 243, 166 | 102, 133, 52, 64, 145, 124, 77, 137, 19, 62, 129, 9, 0, 237, 24, 24, 39, 3, 64, 227, 246, 167 | 41, 203, 19, 170, 174, 98, 102, 66, 33, 245, 119, 237, 152, 161, 26, 234, 101, 49, 58, 116, 168 | 52, 58, 0, 0, 175, 218, 49, 58, 121, 49, 58, 114, 101, 169 | ]; 170 | 171 | let expected = Envelope { 172 | ip: Some("129.21.63.170:34238".parse()?), 173 | transaction_id: vec![0x00, 0x00, 0xAF, 0xDA], 174 | version: None, 175 | message_type: Message::Response { 176 | response: Response::NextHop { 177 | id: b"32f54e697351ff4aec29cdbaabf2fbe3467cc267".into(), 178 | token: None, 179 | nodes: vec![ 180 | NodeInfo::new( 181 | b"30210b1743281b53c298bd53b8742ce06477e3ac".into(), 182 | "180.211.234.53:1416".parse()?, 183 | ), 184 | NodeInfo::new( 185 | b"f73704455d85399c681b00e71d9131acacaa3233".into(), 186 | "36.37.147.240:12664".parse()?, 187 | ), 188 | NodeInfo::new( 189 | b"cd09f99367ca2f9376f7380e6e17ba35aea5aaba".into(), 190 | "95.24.216.93:31751".parse()?, 191 | ), 192 | NodeInfo::new( 193 | b"c07077106a5c3a7089808a8d4f174518b70455a6".into(), 194 | "93.172.43.127:23157".parse()?, 195 | ), 196 | NodeInfo::new( 197 | b"0c812fdfc50a0fb7d56123f0ebed32fcf9c2e1db".into(), 198 | "70.124.69.205:50321".parse()?, 199 | ), 200 | NodeInfo::new( 201 | b"6664faa68068445b8cb636365a1502f1c88d1725".into(), 202 | "46.153.74.174:64403".parse()?, 203 | ), 204 | NodeInfo::new( 205 | b"a54f14554b7d4dce60192063e1e06755f392fab5".into(), 206 | "81.97.116.190:6881".parse()?, 207 | ), 208 | NodeInfo::new( 209 | b"de9deabf3871737ebc1b9553f09735e24af153e2".into(), 210 | "84.251.160.222:48299".parse()?, 211 | ), 212 | NodeInfo::new( 213 | b"5628a8ee8d12b8825326762d1c3628299ccad82e".into(), 214 | "98.13.2.205:6881".parse()?, 215 | ), 216 | NodeInfo::new( 217 | b"3f9c0cd713b443f3ba136ddd055098f723f3f838".into(), 218 | "42.98.51.123:9304".parse()?, 219 | ), 220 | NodeInfo::new( 221 | b"7465722ad0f14da49e1d48cef1347469bc6e6d75".into(), 222 | "79.114.47.76:64186".parse()?, 223 | ), 224 | NodeInfo::new( 225 | b"8b9292b2f75d127720ebcd8afe66bfa50c2adc7f".into(), 226 | "2.87.195.123:62705".parse()?, 227 | ), 228 | NodeInfo::new( 229 | b"d0fb8538dab41982305879bea3c6176b4a0cbbde".into(), 230 | "49.70.2.154:16001".parse()?, 231 | ), 232 | NodeInfo::new( 233 | b"7f4241a48797f052d0e6e7f9d180627be71cdaf5".into(), 234 | "70.55.32.213:17940".parse()?, 235 | ), 236 | NodeInfo::new( 237 | b"3426e6d3b38b4b2190decc6c83ccf36685344091".into(), 238 | "124.77.137.19:16001".parse()?, 239 | ), 240 | NodeInfo::new( 241 | b"0900ed1818270340e3f629cb13aaae62664221f5".into(), 242 | "119.237.152.161:6890".parse()?, 243 | ), 244 | ], 245 | }, 246 | }, 247 | read_only: false, 248 | }; 249 | 250 | let message = Envelope::decode(encoded)?; 251 | 252 | assert_eq!(message, expected); 253 | 254 | Ok(()) 255 | } 256 | 257 | #[test] 258 | fn with_version() -> Result<(), Error> { 259 | let encoded: &[u8] = &[ 260 | 100, 50, 58, 105, 112, 54, 58, 129, 21, 60, 68, 133, 206, 49, 58, 114, 100, 50, 58, 105, 261 | 100, 50, 48, 58, 189, 93, 60, 187, 233, 235, 179, 166, 219, 60, 135, 12, 62, 153, 36, 94, 262 | 13, 28, 6, 241, 101, 49, 58, 116, 52, 58, 0, 0, 138, 186, 49, 58, 118, 52, 58, 85, 84, 174, 263 | 88, 49, 58, 121, 49, 58, 114, 101, 264 | ]; 265 | 266 | let expected = Envelope { 267 | ip: Some(SocketAddrV4::from_str("129.21.60.68:34254")?.into()), 268 | transaction_id: vec![0, 0, 138, 186], 269 | version: Some(vec![85, 84, 174, 88].into()), 270 | message_type: Message::Response { 271 | response: Response::OnlyID { 272 | id: b"bd5d3cbbe9ebb3a6db3c870c3e99245e0d1c06f1".into(), 273 | }, 274 | }, 275 | read_only: false, 276 | }; 277 | 278 | let message = Envelope::decode(encoded)?; 279 | 280 | assert_eq!(message, expected); 281 | 282 | Ok(()) 283 | } 284 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "android_system_properties" 7 | version = "0.1.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 10 | dependencies = [ 11 | "libc", 12 | ] 13 | 14 | [[package]] 15 | name = "async-recursion" 16 | version = "1.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" 19 | dependencies = [ 20 | "proc-macro2 1.0.49", 21 | "quote 1.0.23", 22 | "syn 1.0.107", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "backtrace" 33 | version = "0.3.33" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "88fb679bc9af8fa639198790a77f52d345fe13656c08b43afa9424c206b731c6" 36 | dependencies = [ 37 | "backtrace-sys", 38 | "cfg-if 0.1.9", 39 | "libc", 40 | "rustc-demangle", 41 | ] 42 | 43 | [[package]] 44 | name = "backtrace-sys" 45 | version = "0.1.31" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" 48 | dependencies = [ 49 | "cc", 50 | "libc", 51 | ] 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "1.3.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 58 | 59 | [[package]] 60 | name = "block-buffer" 61 | version = "0.10.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 64 | dependencies = [ 65 | "generic-array", 66 | ] 67 | 68 | [[package]] 69 | name = "bumpalo" 70 | version = "3.11.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 73 | 74 | [[package]] 75 | name = "byteorder" 76 | version = "1.3.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 79 | 80 | [[package]] 81 | name = "bytes" 82 | version = "0.4.12" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 85 | dependencies = [ 86 | "byteorder", 87 | "iovec", 88 | ] 89 | 90 | [[package]] 91 | name = "cc" 92 | version = "1.0.78" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "0.1.9" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "chrono" 110 | version = "0.4.23" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 113 | dependencies = [ 114 | "iana-time-zone", 115 | "js-sys", 116 | "num-integer", 117 | "num-traits", 118 | "time", 119 | "wasm-bindgen", 120 | "winapi", 121 | ] 122 | 123 | [[package]] 124 | name = "cloudabi" 125 | version = "0.0.3" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 128 | dependencies = [ 129 | "bitflags", 130 | ] 131 | 132 | [[package]] 133 | name = "codespan-reporting" 134 | version = "0.11.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 137 | dependencies = [ 138 | "termcolor", 139 | "unicode-width", 140 | ] 141 | 142 | [[package]] 143 | name = "core-foundation-sys" 144 | version = "0.8.3" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 147 | 148 | [[package]] 149 | name = "cpufeatures" 150 | version = "0.2.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 153 | dependencies = [ 154 | "libc", 155 | ] 156 | 157 | [[package]] 158 | name = "crypto-common" 159 | version = "0.1.6" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 162 | dependencies = [ 163 | "generic-array", 164 | "typenum", 165 | ] 166 | 167 | [[package]] 168 | name = "ctor" 169 | version = "0.1.26" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 172 | dependencies = [ 173 | "quote 1.0.23", 174 | "syn 1.0.107", 175 | ] 176 | 177 | [[package]] 178 | name = "cxx" 179 | version = "1.0.85" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" 182 | dependencies = [ 183 | "cc", 184 | "cxxbridge-flags", 185 | "cxxbridge-macro", 186 | "link-cplusplus", 187 | ] 188 | 189 | [[package]] 190 | name = "cxx-build" 191 | version = "1.0.85" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" 194 | dependencies = [ 195 | "cc", 196 | "codespan-reporting", 197 | "once_cell", 198 | "proc-macro2 1.0.49", 199 | "quote 1.0.23", 200 | "scratch", 201 | "syn 1.0.107", 202 | ] 203 | 204 | [[package]] 205 | name = "cxxbridge-flags" 206 | version = "1.0.85" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" 209 | 210 | [[package]] 211 | name = "cxxbridge-macro" 212 | version = "1.0.85" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" 215 | dependencies = [ 216 | "proc-macro2 1.0.49", 217 | "quote 1.0.23", 218 | "syn 1.0.107", 219 | ] 220 | 221 | [[package]] 222 | name = "dht_crawler" 223 | version = "0.1.0" 224 | dependencies = [ 225 | "byteorder", 226 | "bytes", 227 | "chrono", 228 | "failure", 229 | "failure_derive", 230 | "futures", 231 | "futures-util", 232 | "krpc_encoding", 233 | "num-bigint", 234 | "num-traits", 235 | "rand", 236 | "sha1", 237 | "tokio", 238 | "tokio_krpc", 239 | ] 240 | 241 | [[package]] 242 | name = "digest" 243 | version = "0.10.6" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 246 | dependencies = [ 247 | "block-buffer", 248 | "crypto-common", 249 | ] 250 | 251 | [[package]] 252 | name = "erased-serde" 253 | version = "0.3.24" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" 256 | dependencies = [ 257 | "serde", 258 | ] 259 | 260 | [[package]] 261 | name = "failure" 262 | version = "0.1.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" 265 | dependencies = [ 266 | "backtrace", 267 | "failure_derive", 268 | ] 269 | 270 | [[package]] 271 | name = "failure_derive" 272 | version = "0.1.5" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" 275 | dependencies = [ 276 | "proc-macro2 0.4.30", 277 | "quote 0.6.13", 278 | "syn 0.15.39", 279 | "synstructure", 280 | ] 281 | 282 | [[package]] 283 | name = "fuchsia-cprng" 284 | version = "0.1.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 287 | 288 | [[package]] 289 | name = "futures" 290 | version = "0.3.25" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 293 | dependencies = [ 294 | "futures-channel", 295 | "futures-core", 296 | "futures-executor", 297 | "futures-io", 298 | "futures-sink", 299 | "futures-task", 300 | "futures-util", 301 | ] 302 | 303 | [[package]] 304 | name = "futures-channel" 305 | version = "0.3.25" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 308 | dependencies = [ 309 | "futures-core", 310 | "futures-sink", 311 | ] 312 | 313 | [[package]] 314 | name = "futures-core" 315 | version = "0.3.25" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 318 | 319 | [[package]] 320 | name = "futures-executor" 321 | version = "0.3.25" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 324 | dependencies = [ 325 | "futures-core", 326 | "futures-task", 327 | "futures-util", 328 | ] 329 | 330 | [[package]] 331 | name = "futures-io" 332 | version = "0.3.25" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 335 | 336 | [[package]] 337 | name = "futures-macro" 338 | version = "0.3.25" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 341 | dependencies = [ 342 | "proc-macro2 1.0.49", 343 | "quote 1.0.23", 344 | "syn 1.0.107", 345 | ] 346 | 347 | [[package]] 348 | name = "futures-sink" 349 | version = "0.3.25" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 352 | 353 | [[package]] 354 | name = "futures-task" 355 | version = "0.3.25" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 358 | 359 | [[package]] 360 | name = "futures-util" 361 | version = "0.3.25" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 364 | dependencies = [ 365 | "futures-channel", 366 | "futures-core", 367 | "futures-io", 368 | "futures-macro", 369 | "futures-sink", 370 | "futures-task", 371 | "memchr", 372 | "pin-project-lite", 373 | "pin-utils", 374 | "slab", 375 | ] 376 | 377 | [[package]] 378 | name = "generic-array" 379 | version = "0.14.6" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 382 | dependencies = [ 383 | "typenum", 384 | "version_check", 385 | ] 386 | 387 | [[package]] 388 | name = "hex" 389 | version = "0.3.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" 392 | 393 | [[package]] 394 | name = "iana-time-zone" 395 | version = "0.1.53" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 398 | dependencies = [ 399 | "android_system_properties", 400 | "core-foundation-sys", 401 | "iana-time-zone-haiku", 402 | "js-sys", 403 | "wasm-bindgen", 404 | "winapi", 405 | ] 406 | 407 | [[package]] 408 | name = "iana-time-zone-haiku" 409 | version = "0.1.1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 412 | dependencies = [ 413 | "cxx", 414 | "cxx-build", 415 | ] 416 | 417 | [[package]] 418 | name = "iovec" 419 | version = "0.1.4" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 422 | dependencies = [ 423 | "libc", 424 | ] 425 | 426 | [[package]] 427 | name = "js-sys" 428 | version = "0.3.60" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 431 | dependencies = [ 432 | "wasm-bindgen", 433 | ] 434 | 435 | [[package]] 436 | name = "krpc_encoding" 437 | version = "0.1.0" 438 | dependencies = [ 439 | "byteorder", 440 | "hex", 441 | "num-bigint", 442 | "num-traits", 443 | "rand", 444 | "serde", 445 | "serde_bencode", 446 | "serde_bytes", 447 | "serde_derive", 448 | "serde_test", 449 | "thiserror", 450 | ] 451 | 452 | [[package]] 453 | name = "libc" 454 | version = "0.2.139" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 457 | 458 | [[package]] 459 | name = "link-cplusplus" 460 | version = "1.0.8" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 463 | dependencies = [ 464 | "cc", 465 | ] 466 | 467 | [[package]] 468 | name = "log" 469 | version = "0.4.17" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 472 | dependencies = [ 473 | "cfg-if 1.0.0", 474 | "serde", 475 | "value-bag", 476 | ] 477 | 478 | [[package]] 479 | name = "memchr" 480 | version = "2.5.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 483 | 484 | [[package]] 485 | name = "mio" 486 | version = "0.8.5" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 489 | dependencies = [ 490 | "libc", 491 | "log", 492 | "wasi 0.11.0+wasi-snapshot-preview1", 493 | "windows-sys", 494 | ] 495 | 496 | [[package]] 497 | name = "num-bigint" 498 | version = "0.2.6" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 501 | dependencies = [ 502 | "autocfg", 503 | "num-integer", 504 | "num-traits", 505 | ] 506 | 507 | [[package]] 508 | name = "num-integer" 509 | version = "0.1.45" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 512 | dependencies = [ 513 | "autocfg", 514 | "num-traits", 515 | ] 516 | 517 | [[package]] 518 | name = "num-traits" 519 | version = "0.2.15" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 522 | dependencies = [ 523 | "autocfg", 524 | ] 525 | 526 | [[package]] 527 | name = "once_cell" 528 | version = "1.16.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 531 | 532 | [[package]] 533 | name = "pin-project-lite" 534 | version = "0.2.9" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 537 | 538 | [[package]] 539 | name = "pin-utils" 540 | version = "0.1.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 543 | 544 | [[package]] 545 | name = "proc-macro2" 546 | version = "0.4.30" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 549 | dependencies = [ 550 | "unicode-xid", 551 | ] 552 | 553 | [[package]] 554 | name = "proc-macro2" 555 | version = "1.0.49" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 558 | dependencies = [ 559 | "unicode-ident", 560 | ] 561 | 562 | [[package]] 563 | name = "quote" 564 | version = "0.6.13" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 567 | dependencies = [ 568 | "proc-macro2 0.4.30", 569 | ] 570 | 571 | [[package]] 572 | name = "quote" 573 | version = "1.0.23" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 576 | dependencies = [ 577 | "proc-macro2 1.0.49", 578 | ] 579 | 580 | [[package]] 581 | name = "rand" 582 | version = "0.5.6" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" 585 | dependencies = [ 586 | "cloudabi", 587 | "fuchsia-cprng", 588 | "libc", 589 | "rand_core 0.3.1", 590 | "winapi", 591 | ] 592 | 593 | [[package]] 594 | name = "rand_core" 595 | version = "0.3.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 598 | dependencies = [ 599 | "rand_core 0.4.2", 600 | ] 601 | 602 | [[package]] 603 | name = "rand_core" 604 | version = "0.4.2" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 607 | 608 | [[package]] 609 | name = "routing_table" 610 | version = "0.1.0" 611 | dependencies = [ 612 | "async-recursion", 613 | "chrono", 614 | "futures", 615 | "futures-util", 616 | "krpc_encoding", 617 | "log", 618 | "num-bigint", 619 | "serde", 620 | "thiserror", 621 | "tokio", 622 | "tokio_krpc", 623 | "tracing", 624 | ] 625 | 626 | [[package]] 627 | name = "rustc-demangle" 628 | version = "0.1.15" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" 631 | 632 | [[package]] 633 | name = "scratch" 634 | version = "1.0.3" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" 637 | 638 | [[package]] 639 | name = "serde" 640 | version = "1.0.152" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 643 | 644 | [[package]] 645 | name = "serde_bencode" 646 | version = "0.2.0" 647 | source = "git+https://github.com/0xcaff/serde-bencode.git?rev=0c1e6f4672c925c629b84fab2b66b24ec9f3458e#0c1e6f4672c925c629b84fab2b66b24ec9f3458e" 648 | dependencies = [ 649 | "serde", 650 | "serde_bytes", 651 | ] 652 | 653 | [[package]] 654 | name = "serde_bytes" 655 | version = "0.10.5" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "defbb8a83d7f34cc8380751eeb892b825944222888aff18996ea7901f24aec88" 658 | dependencies = [ 659 | "serde", 660 | ] 661 | 662 | [[package]] 663 | name = "serde_derive" 664 | version = "1.0.152" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 667 | dependencies = [ 668 | "proc-macro2 1.0.49", 669 | "quote 1.0.23", 670 | "syn 1.0.107", 671 | ] 672 | 673 | [[package]] 674 | name = "serde_fmt" 675 | version = "1.0.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "2963a69a2b3918c1dc75a45a18bd3fcd1120e31d3f59deb1b2f9b5d5ffb8baa4" 678 | dependencies = [ 679 | "serde", 680 | ] 681 | 682 | [[package]] 683 | name = "serde_test" 684 | version = "1.0.97" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "2253731396193ca15417f9dfc3f6c029d1236a849a40b353b82ca3e05f932744" 687 | dependencies = [ 688 | "serde", 689 | ] 690 | 691 | [[package]] 692 | name = "sha1" 693 | version = "0.10.5" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 696 | dependencies = [ 697 | "cfg-if 1.0.0", 698 | "cpufeatures", 699 | "digest", 700 | ] 701 | 702 | [[package]] 703 | name = "slab" 704 | version = "0.4.7" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 707 | dependencies = [ 708 | "autocfg", 709 | ] 710 | 711 | [[package]] 712 | name = "socket2" 713 | version = "0.4.7" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 716 | dependencies = [ 717 | "libc", 718 | "winapi", 719 | ] 720 | 721 | [[package]] 722 | name = "sval" 723 | version = "1.0.0-alpha.5" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08" 726 | dependencies = [ 727 | "serde", 728 | ] 729 | 730 | [[package]] 731 | name = "syn" 732 | version = "0.15.39" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "b4d960b829a55e56db167e861ddb43602c003c7be0bee1d345021703fac2fb7c" 735 | dependencies = [ 736 | "proc-macro2 0.4.30", 737 | "quote 0.6.13", 738 | "unicode-xid", 739 | ] 740 | 741 | [[package]] 742 | name = "syn" 743 | version = "1.0.107" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 746 | dependencies = [ 747 | "proc-macro2 1.0.49", 748 | "quote 1.0.23", 749 | "unicode-ident", 750 | ] 751 | 752 | [[package]] 753 | name = "synstructure" 754 | version = "0.10.2" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" 757 | dependencies = [ 758 | "proc-macro2 0.4.30", 759 | "quote 0.6.13", 760 | "syn 0.15.39", 761 | "unicode-xid", 762 | ] 763 | 764 | [[package]] 765 | name = "termcolor" 766 | version = "1.1.3" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 769 | dependencies = [ 770 | "winapi-util", 771 | ] 772 | 773 | [[package]] 774 | name = "thiserror" 775 | version = "1.0.38" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 778 | dependencies = [ 779 | "thiserror-impl", 780 | ] 781 | 782 | [[package]] 783 | name = "thiserror-impl" 784 | version = "1.0.38" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 787 | dependencies = [ 788 | "proc-macro2 1.0.49", 789 | "quote 1.0.23", 790 | "syn 1.0.107", 791 | ] 792 | 793 | [[package]] 794 | name = "time" 795 | version = "0.1.45" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 798 | dependencies = [ 799 | "libc", 800 | "wasi 0.10.0+wasi-snapshot-preview1", 801 | "winapi", 802 | ] 803 | 804 | [[package]] 805 | name = "tokio" 806 | version = "1.23.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" 809 | dependencies = [ 810 | "autocfg", 811 | "libc", 812 | "mio", 813 | "pin-project-lite", 814 | "socket2", 815 | "tokio-macros", 816 | "windows-sys", 817 | ] 818 | 819 | [[package]] 820 | name = "tokio-macros" 821 | version = "1.8.2" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 824 | dependencies = [ 825 | "proc-macro2 1.0.49", 826 | "quote 1.0.23", 827 | "syn 1.0.107", 828 | ] 829 | 830 | [[package]] 831 | name = "tokio_krpc" 832 | version = "0.1.0" 833 | dependencies = [ 834 | "byteorder", 835 | "bytes", 836 | "futures", 837 | "futures-util", 838 | "krpc_encoding", 839 | "rand", 840 | "thiserror", 841 | "tokio", 842 | "tracing", 843 | ] 844 | 845 | [[package]] 846 | name = "tracing" 847 | version = "0.1.37" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 850 | dependencies = [ 851 | "cfg-if 1.0.0", 852 | "pin-project-lite", 853 | "tracing-attributes", 854 | "tracing-core", 855 | ] 856 | 857 | [[package]] 858 | name = "tracing-attributes" 859 | version = "0.1.23" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 862 | dependencies = [ 863 | "proc-macro2 1.0.49", 864 | "quote 1.0.23", 865 | "syn 1.0.107", 866 | ] 867 | 868 | [[package]] 869 | name = "tracing-core" 870 | version = "0.1.30" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 873 | dependencies = [ 874 | "once_cell", 875 | ] 876 | 877 | [[package]] 878 | name = "typenum" 879 | version = "1.16.0" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 882 | 883 | [[package]] 884 | name = "unicode-ident" 885 | version = "1.0.6" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 888 | 889 | [[package]] 890 | name = "unicode-width" 891 | version = "0.1.10" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 894 | 895 | [[package]] 896 | name = "unicode-xid" 897 | version = "0.1.0" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 900 | 901 | [[package]] 902 | name = "value-bag" 903 | version = "1.0.0-alpha.9" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" 906 | dependencies = [ 907 | "ctor", 908 | "erased-serde", 909 | "serde", 910 | "serde_fmt", 911 | "sval", 912 | "version_check", 913 | ] 914 | 915 | [[package]] 916 | name = "version_check" 917 | version = "0.9.4" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 920 | 921 | [[package]] 922 | name = "wasi" 923 | version = "0.10.0+wasi-snapshot-preview1" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 926 | 927 | [[package]] 928 | name = "wasi" 929 | version = "0.11.0+wasi-snapshot-preview1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 932 | 933 | [[package]] 934 | name = "wasm-bindgen" 935 | version = "0.2.83" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 938 | dependencies = [ 939 | "cfg-if 1.0.0", 940 | "wasm-bindgen-macro", 941 | ] 942 | 943 | [[package]] 944 | name = "wasm-bindgen-backend" 945 | version = "0.2.83" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 948 | dependencies = [ 949 | "bumpalo", 950 | "log", 951 | "once_cell", 952 | "proc-macro2 1.0.49", 953 | "quote 1.0.23", 954 | "syn 1.0.107", 955 | "wasm-bindgen-shared", 956 | ] 957 | 958 | [[package]] 959 | name = "wasm-bindgen-macro" 960 | version = "0.2.83" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 963 | dependencies = [ 964 | "quote 1.0.23", 965 | "wasm-bindgen-macro-support", 966 | ] 967 | 968 | [[package]] 969 | name = "wasm-bindgen-macro-support" 970 | version = "0.2.83" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 973 | dependencies = [ 974 | "proc-macro2 1.0.49", 975 | "quote 1.0.23", 976 | "syn 1.0.107", 977 | "wasm-bindgen-backend", 978 | "wasm-bindgen-shared", 979 | ] 980 | 981 | [[package]] 982 | name = "wasm-bindgen-shared" 983 | version = "0.2.83" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 986 | 987 | [[package]] 988 | name = "winapi" 989 | version = "0.3.9" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 992 | dependencies = [ 993 | "winapi-i686-pc-windows-gnu", 994 | "winapi-x86_64-pc-windows-gnu", 995 | ] 996 | 997 | [[package]] 998 | name = "winapi-i686-pc-windows-gnu" 999 | version = "0.4.0" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1002 | 1003 | [[package]] 1004 | name = "winapi-util" 1005 | version = "0.1.5" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1008 | dependencies = [ 1009 | "winapi", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "winapi-x86_64-pc-windows-gnu" 1014 | version = "0.4.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1017 | 1018 | [[package]] 1019 | name = "windows-sys" 1020 | version = "0.42.0" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1023 | dependencies = [ 1024 | "windows_aarch64_gnullvm", 1025 | "windows_aarch64_msvc", 1026 | "windows_i686_gnu", 1027 | "windows_i686_msvc", 1028 | "windows_x86_64_gnu", 1029 | "windows_x86_64_gnullvm", 1030 | "windows_x86_64_msvc", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "windows_aarch64_gnullvm" 1035 | version = "0.42.0" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 1038 | 1039 | [[package]] 1040 | name = "windows_aarch64_msvc" 1041 | version = "0.42.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 1044 | 1045 | [[package]] 1046 | name = "windows_i686_gnu" 1047 | version = "0.42.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 1050 | 1051 | [[package]] 1052 | name = "windows_i686_msvc" 1053 | version = "0.42.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 1056 | 1057 | [[package]] 1058 | name = "windows_x86_64_gnu" 1059 | version = "0.42.0" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 1062 | 1063 | [[package]] 1064 | name = "windows_x86_64_gnullvm" 1065 | version = "0.42.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 1068 | 1069 | [[package]] 1070 | name = "windows_x86_64_msvc" 1071 | version = "0.42.0" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 1074 | --------------------------------------------------------------------------------