diff --git a/Cargo.toml b/Cargo.toml index f2cb4df..feda0c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] -members = ["accumulator", "hintfile", "network", "node"] -default-members = ["accumulator", "network"] +members = ["accumulator", "hintfile", "node"] +default-members = ["accumulator"] resolver = "2" [workspace.dependencies] bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", default-features = false, rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } kernel = { package = "bitcoinkernel", git = "https://github.com/alexanderwiederin/rust-bitcoinkernel.git", rev = "353533221e3ba91d672418eab1ae7b83a61214f9" } -p2p = { package = "bitcoin-p2p", git = "https://github.com/2140-dev/bitcoin-p2p.git", rev = "4f67fbeced98e3ddcc65a1333c46823a2b56332a" } +p2p = { package = "bitcoin-p2p", git = "https://github.com/2140-dev/bitcoin-p2p.git", rev = "44fbf6a50aec6cd8a30301deff438b7436069106" } diff --git a/network/Cargo.toml b/network/Cargo.toml deleted file mode 100644 index 5938847..0000000 --- a/network/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "network" -version = "0.1.0" -edition = "2021" - -[dependencies] -tokio = { version = "1", default-features = false, optional = true, features = [ - "io-util", - "net", -]} - -[dev-dependencies] -tokio = { version = "1", default-features = false, features = ["full"] } - -[features] -default = [] -tokio = ["dep:tokio"] diff --git a/network/README.md b/network/README.md deleted file mode 100644 index 3c56918..0000000 --- a/network/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Bitcoin Peers - -This crate defines utilities to find Bitcoin peers. In particular, it offers a DNS implementation to lookup hostnames. Future work for this crate will be to emulate/re-create Bitcoin Core's `addrman`. diff --git a/network/src/dns.rs b/network/src/dns.rs deleted file mode 100644 index e190d8b..0000000 --- a/network/src/dns.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::{ - fmt::Display, - io::Read, - net::{IpAddr, Ipv4Addr, SocketAddr}, -}; - -use crate::{encode_qname, rand_bytes}; - -const CLOUDFLARE: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53); - -const LOCAL_HOST: &str = "0.0.0.0:0"; -const HEADER_BYTES: usize = 12; - -const RECURSIVE_FLAGS: [u8; 2] = [ - 0x01, 0x00, // Default flags with recursive resolver -]; - -const QTYPE: [u8; 4] = [ - 0x00, 0x01, // QType: A Record - 0x00, 0x01, // IN -]; - -const COUNTS: [u8; 6] = [ - 0x00, 0x00, // ANCOUNT - 0x00, 0x00, // NSCOUNT - 0x00, 0x00, // ARCOUNT -]; - -const A_RECORD: u16 = 0x01; -const A_CLASS: u16 = 0x01; -const EXPECTED_RDATA_LEN: u16 = 0x04; - -pub struct DnsQuery { - message_id: [u8; 2], - message: Vec, - question: Vec, - resolver: SocketAddr, -} - -impl DnsQuery { - pub fn new(seed: &str, dns_resolver: SocketAddr) -> Self { - // Build a header - let message_id = rand_bytes(); - let mut message = message_id.to_vec(); - message.extend(RECURSIVE_FLAGS); - message.push(0x00); // QDCOUNT - message.push(0x01); // QDCOUNT - message.extend(COUNTS); - let mut question = encode_qname(seed, None); - question.extend(QTYPE); - message.extend_from_slice(&question); - Self { - message_id, - message, - question, - resolver: dns_resolver, - } - } - - pub fn new_cloudflare(seed: &str) -> Self { - let message_id = rand_bytes(); - let mut message = message_id.to_vec(); - message.extend(RECURSIVE_FLAGS); - message.push(0x00); // QDCOUNT - message.push(0x01); // QDCOUNT - message.extend(COUNTS); - let mut question = encode_qname(seed, None); - question.extend(QTYPE); - message.extend_from_slice(&question); - Self { - message_id, - message, - question, - resolver: CLOUDFLARE, - } - } - - pub fn lookup(self) -> Result, DnsResponseError> { - let sock = std::net::UdpSocket::bind(LOCAL_HOST)?; - sock.connect(self.resolver)?; - sock.send(&self.message)?; - let mut response_buf = [0u8; 512]; - let (amt, _src) = sock.recv_from(&mut response_buf)?; - if amt < HEADER_BYTES { - return Err(DnsResponseError::MalformedHeader); - } - let ips = self.parse_message(&response_buf[..amt])?; - Ok(ips) - } - - fn parse_message(&self, mut response: &[u8]) -> Result, DnsResponseError> { - let mut ips = Vec::with_capacity(10); - let mut buf: [u8; 2] = [0, 0]; - response.read_exact(&mut buf)?; // Read 2 bytes - if self.message_id != buf { - return Err(DnsResponseError::MessageId); - } - // Read flags and ignore - response.read_exact(&mut buf)?; // Read 4 bytes - response.read_exact(&mut buf)?; // Read 6 bytes - let _qdcount = u16::from_be_bytes(buf); - response.read_exact(&mut buf)?; // Read 8 bytes - let ancount = u16::from_be_bytes(buf); - response.read_exact(&mut buf)?; // Read 10 bytes - let _nscount = u16::from_be_bytes(buf); - response.read_exact(&mut buf)?; // Read 12 bytes - let _arcount = u16::from_be_bytes(buf); - // The question should be repeated back to us - let mut buf: Vec = vec![0; self.question.len()]; - response.read_exact(&mut buf)?; - if self.question != buf { - return Err(DnsResponseError::Question); - } - for _ in 0..ancount { - let mut buf: [u8; 2] = [0, 0]; - // Read the compressed NAME field of the record and ignore - response.read_exact(&mut buf)?; - // Read the TYPE - response.read_exact(&mut buf)?; - let atype = u16::from_be_bytes(buf); - // Read the CLASS - response.read_exact(&mut buf)?; - let aclass = u16::from_be_bytes(buf); - let mut buf: [u8; 4] = [0, 0, 0, 0]; - // Read the TTL - response.read_exact(&mut buf)?; - let _ttl = u32::from_be_bytes(buf); - let mut buf: [u8; 2] = [0, 0]; - // Read the RDLENGTH - response.read_exact(&mut buf)?; - let rdlength = u16::from_be_bytes(buf); - // Read RDATA - let mut rdata: Vec = vec![0; rdlength as usize]; - response.read_exact(&mut rdata)?; - if atype == A_RECORD && aclass == A_CLASS && rdlength == EXPECTED_RDATA_LEN { - ips.push(IpAddr::V4(Ipv4Addr::new( - rdata[0], rdata[1], rdata[2], rdata[3], - ))) - } - } - Ok(ips) - } -} - -#[cfg(feature = "tokio")] -pub trait TokioDnsExt { - #[allow(async_fn_in_trait)] - async fn lookup_async(self) -> Result, TokioDnsQueryError>; -} - -#[cfg(feature = "tokio")] -impl TokioDnsExt for DnsQuery { - async fn lookup_async(self) -> Result, TokioDnsQueryError> { - let sock = tokio::net::UdpSocket::bind(LOCAL_HOST).await?; - sock.connect(self.resolver).await?; - sock.send(&self.message).await?; - let mut response_buf = [0u8; 512]; - let (amt, _src) = sock.recv_from(&mut response_buf).await?; - if amt < HEADER_BYTES { - return Err(TokioDnsQueryError::Protocol( - DnsResponseError::MalformedHeader, - )); - } - let ips = self.parse_message(&response_buf[..amt])?; - Ok(ips) - } -} - -#[derive(Debug)] -pub enum DnsResponseError { - MessageId, - MalformedHeader, - Question, - Eof(std::io::Error), -} - -impl Display for DnsResponseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Question => write!(f, "question section was not repeated back."), - Self::MalformedHeader => write!(f, "the response header was undersized."), - Self::MessageId => write!(f, "the response ID does not match the request."), - Self::Eof(io) => write!(f, "std::io error: {io}"), - } - } -} - -impl From for DnsResponseError { - fn from(value: std::io::Error) -> Self { - DnsResponseError::Eof(value) - } -} - -impl std::error::Error for DnsResponseError {} - -#[cfg(feature = "tokio")] -#[derive(Debug)] -pub enum TokioDnsQueryError { - Io(tokio::io::Error), - Protocol(DnsResponseError), -} - -#[cfg(feature = "tokio")] -impl Display for TokioDnsQueryError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Io(io) => write!(f, "{io}"), - Self::Protocol(dns) => write!(f, "{dns}"), - } - } -} - -#[cfg(feature = "tokio")] -impl std::error::Error for TokioDnsQueryError {} - -#[cfg(feature = "tokio")] -impl From for TokioDnsQueryError { - fn from(value: tokio::io::Error) -> Self { - TokioDnsQueryError::Io(value) - } -} - -#[cfg(feature = "tokio")] -impl From for TokioDnsQueryError { - fn from(value: DnsResponseError) -> Self { - TokioDnsQueryError::Protocol(value) - } -} diff --git a/network/src/lib.rs b/network/src/lib.rs deleted file mode 100644 index e2bca0d..0000000 --- a/network/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{ - hash::{DefaultHasher, Hash, Hasher}, - time::SystemTime, -}; - -pub mod dns; - -fn encode_qname>(hostname: S, filter: Option) -> Vec { - let mut qname = Vec::new(); - let str = hostname.as_ref(); - if let Some(filter) = filter { - let prefix = filter.as_ref(); - qname.push(prefix.len() as u8); - qname.extend(prefix.as_bytes()); - } - for label in str.split(".") { - qname.push(label.len() as u8); - qname.extend(label.as_bytes()); - } - qname.push(0x00); - qname -} - -fn rand_bytes() -> [u8; 2] { - let mut hasher = DefaultHasher::new(); - SystemTime::now().hash(&mut hasher); - let mut hash = hasher.finish(); - hash ^= hash << 13; - hash ^= hash >> 17; - hash ^= hash << 5; - hash.to_be_bytes()[..2].try_into().expect("trivial cast") -} - -#[cfg(test)] -mod test { - use crate::rand_bytes; - - #[test] - fn test_rand_bytes() { - rand_bytes(); - } -} diff --git a/network/tests/test.rs b/network/tests/test.rs deleted file mode 100644 index 1cba257..0000000 --- a/network/tests/test.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - -use network::dns::DnsQuery; - -#[tokio::test] -#[cfg(feature = "tokio")] -async fn test_tokio_dns_ext() { - use peers::dns::TokioDnsExt; - let resolver = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(46, 166, 189, 67)), 53); - let query = DnsQuery::new("seed.bitcoin.sipa.be", resolver); - let addrs = query.lookup_async().await.unwrap(); - assert!(!addrs.is_empty()); -} - -#[test] -fn test_dns_blocking() { - let resolver = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(46, 166, 189, 67)), 53); - let query = DnsQuery::new("seed.bitcoin.sprovoost.nl", resolver); - let addrs = query.lookup().unwrap(); - assert!(!addrs.is_empty()); -} - -#[test] -fn test_cloudflare() { - let query = DnsQuery::new_cloudflare("seed.bitcoin.sprovoost.nl"); - let addrs = query.lookup().unwrap(); - assert!(!addrs.is_empty()); -} diff --git a/node/Cargo.toml b/node/Cargo.toml index becdc01..0f2639d 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -9,7 +9,6 @@ accumulator = { path = "../accumulator/" } bitcoin = { workspace = true } kernel = { workspace = true } hintfile = { path = "../hintfile/" } -network = { path = "../network/" } p2p = { workspace = true } configure_me = "0.4.0" diff --git a/node/src/lib.rs b/node/src/lib.rs index 4289958..58c0c3e 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -2,7 +2,7 @@ use std::{ collections::HashSet, fs::File, io::Write, - net::SocketAddr, + net::{IpAddr, Ipv4Addr, SocketAddr}, path::Path, sync::{ mpsc::{Receiver, Sender}, @@ -21,8 +21,8 @@ use bitcoin::{ }; use hintfile::Hints; use kernel::{ChainType, ChainstateManager}; -use network::dns::DnsQuery; use p2p::{ + dns::DnsQueryExt, handshake::ConnectionConfig, net::{ConnectionExt, TimeoutParams}, p2p::{ @@ -30,7 +30,7 @@ use p2p::{ message_blockdata::{GetHeadersMessage, Inventory}, NetworkExt, ProtocolVersion, ServiceFlags, }, - SeedsExt, TimedMessage, + TimedMessage, }; const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::WTXID_RELAY_VERSION; @@ -64,14 +64,11 @@ impl AccumulatorState { } pub fn bootstrap_dns(network: Network) -> Vec { - let mut all_hosts = Vec::new(); - for seed in network.seeds() { - let hosts = DnsQuery::new_cloudflare(seed).lookup().unwrap_or_default(); - all_hosts.extend_from_slice(&hosts); - } - all_hosts + let cloudflare = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53); + network + .query_dns_seeds(cloudflare) .into_iter() - .map(|host| SocketAddr::new(host, network.default_p2p_port())) + .map(|ip| SocketAddr::new(ip, network.default_p2p_port())) .collect() }