|
1 | | -use std::{ |
2 | | - fmt::Display, |
3 | | - hash::{DefaultHasher, Hash, Hasher}, |
4 | | - io::Read, |
5 | | - net::{IpAddr, Ipv4Addr, SocketAddr}, |
6 | | - time::SystemTime, |
7 | | -}; |
8 | | - |
9 | | -use bitcoin::Network; |
10 | | - |
11 | | -const BITCOIN_SEEDS: [&str; 9] = [ |
| 1 | +/// Hostnames for the Bitcoin network. |
| 2 | +pub const BITCOIN_SEEDS: [&str; 9] = [ |
12 | 3 | "seed.bitcoin.sipa.be", |
13 | 4 | "dnsseed.bluematt.me", |
14 | | - "dnsseed.bitcoin.dashjr.org", |
15 | | - "seed.bitcoinstats.com", |
| 5 | + "dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us", |
16 | 6 | "seed.bitcoin.jonasschnelli.ch", |
17 | | - "seed.btc.petertodd.org", |
| 7 | + "seed.btc.petertodd.net", |
18 | 8 | "seed.bitcoin.sprovoost.nl", |
19 | 9 | "dnsseed.emzy.de", |
20 | 10 | "seed.bitcoin.wiz.biz", |
| 11 | + "seed.mainnet.achownodes.xyz", |
21 | 12 | ]; |
22 | 13 |
|
23 | | -const SIGNET_SEEDS: [&str; 2] = [ |
| 14 | +/// Hostnames for the Signet test network. |
| 15 | +pub const SIGNET_SEEDS: [&str; 2] = [ |
24 | 16 | "seed.signet.bitcoin.sprovoost.nl", |
25 | 17 | "seed.signet.achownodes.xyz", |
26 | 18 | ]; |
27 | 19 |
|
28 | | -const LOCAL_HOST: &str = "0.0.0.0:0"; |
29 | | -const HEADER_BYTES: usize = 12; |
30 | | - |
31 | | -const RECURSIVE_FLAGS: [u8; 2] = [ |
32 | | - 0x01, 0x00, // Default flags with recursive resolver |
33 | | -]; |
34 | | - |
35 | | -const QTYPE: [u8; 4] = [ |
36 | | - 0x00, 0x01, // QType: A Record |
37 | | - 0x00, 0x01, // IN |
| 20 | +/// Hostnames for the Testnet 3 network. |
| 21 | +pub const TESTNET3_SEEDS: [&str; 5] = [ |
| 22 | + "testnet-seed.bitcoin.jonasschnelli.ch", |
| 23 | + "seed.tbtc.petertodd.net", |
| 24 | + "seed.testnet.bitcoin.sprovoost.nl", |
| 25 | + "testnet-seed.bluematt.me", |
| 26 | + "seed.testnet.achownodes.xyz", |
38 | 27 | ]; |
39 | 28 |
|
40 | | -const COUNTS: [u8; 6] = [ |
41 | | - 0x00, 0x00, // ANCOUNT |
42 | | - 0x00, 0x00, // NSCOUNT |
43 | | - 0x00, 0x00, // ARCOUNT |
| 29 | +/// Hostnames for the Testnet 4 network. |
| 30 | +pub const TESTNET4_SEEDS: [&str; 2] = [ |
| 31 | + "seed.testnet4.bitcoin.sprovoost.nl", |
| 32 | + "seed.testnet4.wiz.biz", |
44 | 33 | ]; |
45 | | - |
46 | | -const A_RECORD: u16 = 0x01; |
47 | | -const A_CLASS: u16 = 0x01; |
48 | | -const EXPECTED_RDATA_LEN: u16 = 0x04; |
49 | | - |
50 | | -/// Query DNS seeds to find potential peers. |
51 | | -pub trait DnsQueryExt { |
52 | | - /// Return as many potential peers as possible, potentially zero. |
53 | | - fn query_dns_seeds(&self, resolver: impl Into<SocketAddr>) -> Vec<IpAddr>; |
54 | | -} |
55 | | - |
56 | | -impl DnsQueryExt for Network { |
57 | | - fn query_dns_seeds(&self, resolver: impl Into<SocketAddr>) -> Vec<IpAddr> { |
58 | | - let resolver = resolver.into(); |
59 | | - match self { |
60 | | - Network::Bitcoin => do_dns_query(&BITCOIN_SEEDS, resolver), |
61 | | - Network::Signet => do_dns_query(&SIGNET_SEEDS, resolver), |
62 | | - _ => Vec::new(), |
63 | | - } |
64 | | - } |
65 | | -} |
66 | | - |
67 | | -fn do_dns_query(seeds: &[&str], resolver: SocketAddr) -> Vec<IpAddr> { |
68 | | - let mut vals = Vec::new(); |
69 | | - for seed in seeds { |
70 | | - let query = DnsQuery::new(seed, resolver); |
71 | | - if let Ok(hosts) = query.lookup() { |
72 | | - vals.extend(&hosts); |
73 | | - } |
74 | | - } |
75 | | - vals |
76 | | -} |
77 | | - |
78 | | -#[derive(Debug)] |
79 | | -struct DnsQuery { |
80 | | - message_id: [u8; 2], |
81 | | - message: Vec<u8>, |
82 | | - question: Vec<u8>, |
83 | | - resolver: SocketAddr, |
84 | | -} |
85 | | - |
86 | | -impl DnsQuery { |
87 | | - fn new(seed: &str, dns_resolver: SocketAddr) -> Self { |
88 | | - // Build a header |
89 | | - let message_id = rand_bytes(); |
90 | | - let mut message = message_id.to_vec(); |
91 | | - message.extend(RECURSIVE_FLAGS); |
92 | | - message.push(0x00); // QDCOUNT |
93 | | - message.push(0x01); // QDCOUNT |
94 | | - message.extend(COUNTS); |
95 | | - let mut question = encode_qname(seed, None); |
96 | | - question.extend(QTYPE); |
97 | | - message.extend_from_slice(&question); |
98 | | - Self { |
99 | | - message_id, |
100 | | - message, |
101 | | - question, |
102 | | - resolver: dns_resolver, |
103 | | - } |
104 | | - } |
105 | | - |
106 | | - fn lookup(self) -> Result<Vec<IpAddr>, Error> { |
107 | | - let sock = std::net::UdpSocket::bind(LOCAL_HOST)?; |
108 | | - sock.connect(self.resolver)?; |
109 | | - sock.send(&self.message)?; |
110 | | - let mut response_buf = [0u8; 512]; |
111 | | - let (amt, _src) = sock.recv_from(&mut response_buf)?; |
112 | | - if amt < HEADER_BYTES { |
113 | | - return Err(Error::MalformedHeader); |
114 | | - } |
115 | | - let ips = self.parse_message(&response_buf[..amt])?; |
116 | | - Ok(ips) |
117 | | - } |
118 | | - |
119 | | - fn parse_message(&self, mut response: &[u8]) -> Result<Vec<IpAddr>, Error> { |
120 | | - let mut ips = Vec::with_capacity(10); |
121 | | - let mut buf: [u8; 2] = [0, 0]; |
122 | | - response.read_exact(&mut buf)?; // Read 2 bytes |
123 | | - if self.message_id != buf { |
124 | | - return Err(Error::MessageId); |
125 | | - } |
126 | | - // Read flags and ignore |
127 | | - response.read_exact(&mut buf)?; // Read 4 bytes |
128 | | - response.read_exact(&mut buf)?; // Read 6 bytes |
129 | | - let _qdcount = u16::from_be_bytes(buf); |
130 | | - response.read_exact(&mut buf)?; // Read 8 bytes |
131 | | - let ancount = u16::from_be_bytes(buf); |
132 | | - response.read_exact(&mut buf)?; // Read 10 bytes |
133 | | - let _nscount = u16::from_be_bytes(buf); |
134 | | - response.read_exact(&mut buf)?; // Read 12 bytes |
135 | | - let _arcount = u16::from_be_bytes(buf); |
136 | | - // The question should be repeated back to us |
137 | | - let mut buf: Vec<u8> = vec![0; self.question.len()]; |
138 | | - response.read_exact(&mut buf)?; |
139 | | - if self.question != buf { |
140 | | - return Err(Error::Question); |
141 | | - } |
142 | | - for _ in 0..ancount { |
143 | | - let mut buf: [u8; 2] = [0, 0]; |
144 | | - // Read the compressed NAME field of the record and ignore |
145 | | - response.read_exact(&mut buf)?; |
146 | | - // Read the TYPE |
147 | | - response.read_exact(&mut buf)?; |
148 | | - let atype = u16::from_be_bytes(buf); |
149 | | - // Read the CLASS |
150 | | - response.read_exact(&mut buf)?; |
151 | | - let aclass = u16::from_be_bytes(buf); |
152 | | - let mut buf: [u8; 4] = [0, 0, 0, 0]; |
153 | | - // Read the TTL |
154 | | - response.read_exact(&mut buf)?; |
155 | | - let _ttl = u32::from_be_bytes(buf); |
156 | | - let mut buf: [u8; 2] = [0, 0]; |
157 | | - // Read the RDLENGTH |
158 | | - response.read_exact(&mut buf)?; |
159 | | - let rdlength = u16::from_be_bytes(buf); |
160 | | - // Read RDATA |
161 | | - let mut rdata: Vec<u8> = vec![0; rdlength as usize]; |
162 | | - response.read_exact(&mut rdata)?; |
163 | | - if atype == A_RECORD && aclass == A_CLASS && rdlength == EXPECTED_RDATA_LEN { |
164 | | - ips.push(IpAddr::V4(Ipv4Addr::new( |
165 | | - rdata[0], rdata[1], rdata[2], rdata[3], |
166 | | - ))) |
167 | | - } |
168 | | - } |
169 | | - Ok(ips) |
170 | | - } |
171 | | -} |
172 | | - |
173 | | -#[derive(Debug)] |
174 | | -enum Error { |
175 | | - MessageId, |
176 | | - MalformedHeader, |
177 | | - Question, |
178 | | - Io(std::io::Error), |
179 | | -} |
180 | | - |
181 | | -impl Display for Error { |
182 | | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
183 | | - match self { |
184 | | - Self::Question => write!(f, "question section was not repeated back."), |
185 | | - Self::MalformedHeader => write!(f, "the response header was undersized."), |
186 | | - Self::MessageId => write!(f, "the response ID does not match the request."), |
187 | | - Self::Io(io) => write!(f, "std::io error: {io}"), |
188 | | - } |
189 | | - } |
190 | | -} |
191 | | - |
192 | | -impl From<std::io::Error> for Error { |
193 | | - fn from(value: std::io::Error) -> Self { |
194 | | - Error::Io(value) |
195 | | - } |
196 | | -} |
197 | | - |
198 | | -impl std::error::Error for Error {} |
199 | | - |
200 | | -fn encode_qname<S: AsRef<str>>(hostname: S, filter: Option<S>) -> Vec<u8> { |
201 | | - let mut qname = Vec::new(); |
202 | | - let str = hostname.as_ref(); |
203 | | - if let Some(filter) = filter { |
204 | | - let prefix = filter.as_ref(); |
205 | | - qname.push(prefix.len() as u8); |
206 | | - qname.extend(prefix.as_bytes()); |
207 | | - } |
208 | | - for label in str.split(".") { |
209 | | - qname.push(label.len() as u8); |
210 | | - qname.extend(label.as_bytes()); |
211 | | - } |
212 | | - qname.push(0x00); |
213 | | - qname |
214 | | -} |
215 | | - |
216 | | -fn rand_bytes() -> [u8; 2] { |
217 | | - let mut hasher = DefaultHasher::new(); |
218 | | - SystemTime::now().hash(&mut hasher); |
219 | | - let mut hash = hasher.finish(); |
220 | | - hash ^= hash << 13; |
221 | | - hash ^= hash >> 17; |
222 | | - hash ^= hash << 5; |
223 | | - hash.to_be_bytes()[..2].try_into().expect("trivial cast") |
224 | | -} |
0 commit comments