1use clap::Parser;
4use reth_cli_util::{get_secret_key, load_secret_key::rng_secret_key};
5use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config};
6use reth_discv5::{
7 discv5::{self, Event, ListenConfig},
8 Config, Discv5,
9};
10use reth_net_nat::NatResolver;
11use reth_network_peers::NodeRecord;
12use secp256k1::SecretKey;
13use std::{
14 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
15 path::PathBuf,
16 sync::Arc,
17};
18use tokio::{net::UdpSocket, select};
19use tokio_stream::StreamExt;
20use tracing::info;
21
22#[derive(Parser, Debug)]
24pub struct Command {
25 #[arg(long, default_value = "0.0.0.0:30301")]
27 pub addr: SocketAddr,
28
29 #[arg(long, value_name = "PATH")]
36 pub p2p_secret_key: Option<PathBuf>,
37
38 #[arg(long, default_value = "any")]
43 pub nat: Vec<NatResolver>,
44
45 #[arg(long)]
47 pub v5: bool,
48}
49
50impl Command {
51 pub async fn execute(self) -> eyre::Result<()> {
53 info!("Bootnode started with config: {self:?}");
54
55 let sk = self.network_secret()?;
56 let local_enr = NodeRecord::from_secret_key(self.addr, &sk).with_tcp_port(0);
59 let nat = self.resolved_nat()?;
60
61 let discv4_config = self.discv4_config(&nat);
62
63 let (_discv4, mut discv4_service, _discv5, mut discv5_forwarder) = if self.v5 {
67 let shared_socket = bind_socket(self.addr).await?;
68 let shared_port = shared_socket.local_addr()?.port();
70
71 let (discv4, discv4_service, mut ingress) =
72 Discv4::bind_shared(shared_socket.clone(), local_enr, sk, discv4_config)?;
73 info!("Started discv4 (shared port) at address: {local_enr:?}");
74
75 let mut discv5_config = self.discv5_config(&nat);
78 let discv5_cfg = discv5_config.discv5_config_mut();
79 let (mut ipv4, mut ipv6) = (None, None);
80 if self.addr.is_ipv4() {
81 ipv4 = Some(shared_socket);
82 if let Some(mut addr) = reth_discv5::config::ipv6(&discv5_cfg.listen_config) {
83 addr.set_port(shared_port);
84 ipv6 = Some(bind_socket(SocketAddr::V6(addr)).await?);
85 }
86 } else {
87 ipv6 = Some(shared_socket);
88 if let Some(mut addr) = reth_discv5::config::ipv4(&discv5_cfg.listen_config) {
89 addr.set_port(shared_port);
90 ipv4 = Some(bind_socket(SocketAddr::V4(addr)).await?);
91 }
92 }
93 discv5_cfg.listen_config = ListenConfig::FromSockets { ipv4, ipv6 };
94
95 info!("Starting discv5 (shared port)");
96 let (discv5, mut updates) = Discv5::start(&sk, discv5_config).await?;
97 log_discv5_enr(&discv5);
98
99 let forwarder = tokio::spawn(async move {
101 while let Some(event) = updates.recv().await {
102 match event {
103 Event::UnrecognizedFrame(frame) => {
104 ingress.handle_packet(&frame.packet, frame.src_address).await
105 }
106 Event::SessionEstablished(enr, _) => {
107 info!("(Discv5) new peer added, peer_id={:?}", enr.id())
108 }
109 _ => {}
110 }
111 }
112 info!("(Discv5) update stream ended.");
113 });
114
115 (discv4, discv4_service, Some(discv5), Some(forwarder))
116 } else {
117 let (discv4, discv4_service) =
118 Discv4::bind(self.addr, local_enr, sk, discv4_config).await?;
119 info!("Started discv4 at address: {local_enr:?}");
120 (discv4, discv4_service, None, None)
121 };
122
123 let mut discv4_updates = discv4_service.update_stream();
124 discv4_service.spawn();
125
126 loop {
128 select! {
129 update = discv4_updates.next() => {
130 if let Some(update) = update {
131 match update {
132 DiscoveryUpdate::Added(record) => {
133 info!("(Discv4) new peer added, peer_id={:?}", record.id);
134 }
135 DiscoveryUpdate::Removed(peer_id) => {
136 info!("(Discv4) peer with peer-id={:?} removed", peer_id);
137 }
138 _ => {}
139 }
140 } else {
141 info!("(Discv4) update stream ended.");
142 break;
143 }
144 }
145 _ = async {
147 if let Some(forwarder) = &mut discv5_forwarder {
148 let _ = forwarder.await;
149 } else {
150 futures::future::pending::<()>().await
151 }
152 } => break,
153 }
154 }
155
156 Ok(())
157 }
158
159 fn network_secret(&self) -> eyre::Result<SecretKey> {
160 match &self.p2p_secret_key {
161 Some(path) => Ok(get_secret_key(path)?),
162 None => Ok(rng_secret_key()),
163 }
164 }
165
166 fn resolved_nat(&self) -> eyre::Result<BootnodeNat> {
173 match self.nat.as_slice() {
174 [] => Ok(BootnodeNat::single(NatResolver::Any, self.addr.port())),
175 [nat] => {
176 let mut single = BootnodeNat::single(nat.clone(), self.addr.port());
177 if let [ip] = single.advertised_ips[..] &&
182 ip.is_ipv4() != self.addr.is_ipv4()
183 {
184 single.resolver = NatResolver::None;
185 }
186 Ok(single)
187 }
188 [first, second] => {
189 let first_ip = fixed_external_ip(first)?;
190 let second_ip = fixed_external_ip(second)?;
191
192 if first_ip.is_ipv4() == second_ip.is_ipv4() {
193 eyre::bail!("repeated --nat requires one IPv4 and one IPv6 extip:<IP> value");
194 }
195
196 let primary_ip =
198 if first_ip.is_ipv4() == self.addr.is_ipv4() { first_ip } else { second_ip };
199
200 Ok(BootnodeNat {
201 resolver: NatResolver::ExternalIp(primary_ip),
202 advertised_ips: vec![first_ip, second_ip],
203 })
204 }
205 _ => {
206 eyre::bail!("--nat can be provided at most twice");
207 }
208 }
209 }
210
211 fn discv4_config(&self, nat: &BootnodeNat) -> Discv4Config {
212 Discv4Config::builder().external_ip_resolver(Some(nat.resolver.clone())).build()
213 }
214
215 fn discv5_config(&self, nat: &BootnodeNat) -> Config {
216 let mut builder = Config::builder(SocketAddr::new(self.addr.ip(), 0));
219
220 let port = self.addr.port();
222
223 let bind_ipv4 = self.addr.is_ipv4() || nat.advertised_ips.iter().any(IpAddr::is_ipv4);
227 let bind_ipv6 = self.addr.is_ipv6() || nat.advertised_ips.iter().any(IpAddr::is_ipv6);
228 let listen = if bind_ipv4 && bind_ipv6 {
229 ListenConfig::DualStack {
230 ipv4: Ipv4Addr::UNSPECIFIED,
231 ipv4_port: port,
232 ipv6: Ipv6Addr::UNSPECIFIED,
233 ipv6_port: port,
234 }
235 } else if bind_ipv6 {
236 ListenConfig::Ipv6 { ip: Ipv6Addr::UNSPECIFIED, port }
237 } else {
238 ListenConfig::Ipv4 { ip: Ipv4Addr::UNSPECIFIED, port }
239 };
240 builder = builder.discv5_config(discv5::ConfigBuilder::new(listen).build());
241
242 for ip in &nat.advertised_ips {
243 builder = builder.advertised_ip(*ip);
244 }
245
246 builder.build()
247 }
248}
249
250#[derive(Debug)]
252struct BootnodeNat {
253 resolver: NatResolver,
255 advertised_ips: Vec<IpAddr>,
257}
258
259impl BootnodeNat {
260 fn single(resolver: NatResolver, port: u16) -> Self {
261 let advertised_ips = resolver.clone().as_external_ip(port).into_iter().collect();
262 Self { resolver, advertised_ips }
263 }
264}
265
266async fn bind_socket(addr: SocketAddr) -> eyre::Result<Arc<UdpSocket>> {
271 let socket = match addr {
272 SocketAddr::V4(_) => UdpSocket::bind(addr).await?,
273 SocketAddr::V6(_) => {
274 use socket2::{Domain, Protocol, Socket, Type};
275 let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
276 socket.set_only_v6(true)?;
277 socket.set_nonblocking(true)?;
278 socket.bind(&addr.into())?;
279 UdpSocket::from_std(socket.into())?
280 }
281 };
282 Ok(Arc::new(socket))
283}
284
285fn fixed_external_ip(nat: &NatResolver) -> eyre::Result<IpAddr> {
286 match nat {
287 NatResolver::ExternalIp(ip) => Ok(*ip),
288 _ => {
289 eyre::bail!("--nat can only be repeated with extip:<IP> values");
290 }
291 }
292}
293
294fn log_discv5_enr(discv5: &Discv5) {
295 let enr = discv5.local_enr();
296 info!(
297 id = ?enr.node_id(),
298 ip4 = ?enr.ip4(),
299 ip6 = ?enr.ip6(),
300 udp4 = ?enr.udp4(),
301 udp6 = ?enr.udp6(),
302 tcp4 = ?enr.tcp4(),
303 tcp6 = ?enr.tcp6(),
304 "(Discv5) advertised endpoints",
305 );
306 info!("(Discv5) enr: {enr}");
307 if let Some(record) = discv5.node_record() {
308 info!("(Discv5) enode: {record}");
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use reth_discv5::build_local_enr;
316
317 #[test]
318 fn repeated_nat_uses_matching_addr_family_as_primary() {
319 let command = Command::parse_from([
320 "reth",
321 "--addr",
322 "[::]:30303",
323 "--nat",
324 "extip:1.2.3.4",
325 "--nat",
326 "extip:2001:db8::1",
327 ]);
328
329 let nat = command.resolved_nat().unwrap();
330
331 assert_eq!(nat.resolver, NatResolver::ExternalIp("2001:db8::1".parse().unwrap()));
334 assert_eq!(nat.advertised_ips.len(), 2);
335 }
336
337 #[test]
338 fn repeated_nat_requires_extip_values() {
339 let command = Command::parse_from(["reth", "--nat", "any", "--nat", "extip:2001:db8::1"]);
340
341 assert!(command.resolved_nat().is_err());
342 }
343
344 #[test]
345 fn repeated_nat_requires_distinct_ip_families() {
346 let command =
347 Command::parse_from(["reth", "--nat", "extip:1.2.3.4", "--nat", "extip:5.6.7.8"]);
348
349 assert!(command.resolved_nat().is_err());
350 }
351
352 #[test]
353 fn single_extip_is_advertised() {
354 let command =
355 Command::parse_from(["reth", "--addr", "0.0.0.0:30301", "--nat", "extip:1.2.3.4"]);
356
357 let nat = command.resolved_nat().unwrap();
358
359 assert_eq!(nat.advertised_ips, vec!["1.2.3.4".parse::<IpAddr>().unwrap()]);
360 }
361
362 #[test]
363 fn discv5_advertises_single_extip_of_other_family_than_addr() {
364 let command = Command::parse_from([
366 "reth",
367 "--addr",
368 "0.0.0.0:45678",
369 "--v5",
370 "--nat",
371 "extip:2001:db8::1",
372 ]);
373 let nat = command.resolved_nat().unwrap();
374 let config = command.discv5_config(&nat);
375 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
376
377 let (enr, _, _, _) = build_local_enr(&sk, &config);
378
379 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
381 assert_eq!(enr.udp6(), Some(45678));
382 }
383
384 #[test]
385 fn discv5_config_single_stack_ipv4_uses_addr_port() {
386 let command = Command::parse_from([
388 "reth",
389 "--addr",
390 "0.0.0.0:45678",
391 "--v5",
392 "--nat",
393 "extip:1.2.3.4",
394 ]);
395 let nat = command.resolved_nat().unwrap();
396 let config = command.discv5_config(&nat);
397 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
398
399 let (enr, _, _, _) = build_local_enr(&sk, &config);
400
401 assert_eq!(enr.ip4(), Some("1.2.3.4".parse().unwrap()));
402 assert_eq!(enr.udp4(), Some(45678));
403 assert_eq!(enr.udp6(), None);
404 }
405
406 #[test]
407 fn discv5_config_single_stack_ipv6_uses_addr_port() {
408 let command = Command::parse_from([
410 "reth",
411 "--addr",
412 "[::]:45678",
413 "--v5",
414 "--nat",
415 "extip:2001:db8::1",
416 ]);
417 let nat = command.resolved_nat().unwrap();
418 let config = command.discv5_config(&nat);
419 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
420
421 let (enr, _, _, _) = build_local_enr(&sk, &config);
422
423 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
424 assert_eq!(enr.udp6(), Some(45678));
425 assert_eq!(enr.udp4(), None);
426 }
427
428 #[test]
429 fn single_opposite_family_extip_does_not_drive_discv4() {
430 let command = Command::parse_from([
433 "reth",
434 "--addr",
435 "0.0.0.0:45678",
436 "--v5",
437 "--nat",
438 "extip:2001:db8::1",
439 ]);
440 let nat = command.resolved_nat().unwrap();
441
442 assert_eq!(nat.resolver, NatResolver::None);
443 assert_eq!(nat.advertised_ips, vec!["2001:db8::1".parse::<IpAddr>().unwrap()]);
444 }
445
446 #[tokio::test]
447 async fn shared_socket_binds_both_families_on_one_port() {
448 let (Ok(probe4), Ok(probe6)) = (
451 bind_socket("0.0.0.0:0".parse().unwrap()).await,
452 bind_socket("[::]:0".parse().unwrap()).await,
453 ) else {
454 return;
455 };
456 drop((probe4, probe6));
457
458 let v4 = bind_socket("0.0.0.0:0".parse().unwrap()).await.unwrap();
459 let port = v4.local_addr().unwrap().port();
460 let v6 = bind_socket(format!("[::]:{port}").parse().unwrap()).await.unwrap();
461 assert_eq!(v6.local_addr().unwrap().port(), port);
462 }
463
464 #[test]
465 fn discv4_config_advertises_only_the_addr_family() {
466 let command = Command::parse_from([
469 "reth",
470 "--addr",
471 "0.0.0.0:30303",
472 "--nat",
473 "extip:1.2.3.4",
474 "--nat",
475 "extip:2001:db8::1",
476 ]);
477 let nat = command.resolved_nat().unwrap();
478 let config = command.discv4_config(&nat);
479
480 assert_eq!(nat.resolver, NatResolver::ExternalIp("1.2.3.4".parse().unwrap()));
481 assert!(config.additional_eip868_rlp_pairs.is_empty());
482 }
483
484 #[test]
485 fn discv5_config_advertises_dual_stack_nat_endpoints() {
486 let command = Command::parse_from([
487 "reth",
488 "--addr",
489 "0.0.0.0:45678",
490 "--v5",
491 "--nat",
492 "extip:1.2.3.4",
493 "--nat",
494 "extip:2001:db8::1",
495 ]);
496 let nat = command.resolved_nat().unwrap();
497 let config = command.discv5_config(&nat);
498 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
499
500 let (enr, _, _, _) = build_local_enr(&sk, &config);
501
502 assert_eq!(enr.ip4(), Some("1.2.3.4".parse().unwrap()));
504 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
505 assert_eq!(enr.udp4(), Some(45678));
506 assert_eq!(enr.udp6(), Some(45678));
507 assert_eq!(enr.tcp4(), Some(0));
509 assert_eq!(enr.tcp6(), None);
510
511 let record = NodeRecord::try_from(&enr).unwrap();
513 assert_eq!(record.tcp_port, 0);
514 assert_eq!(record.udp_port, 45678);
515 }
516}