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 _ => eyre::bail!("--nat can be provided at most twice"),
206 }
207 }
208
209 fn discv4_config(&self, nat: &BootnodeNat) -> Discv4Config {
210 Discv4Config::builder().external_ip_resolver(Some(nat.resolver.clone())).build()
211 }
212
213 fn discv5_config(&self, nat: &BootnodeNat) -> Config {
214 let mut builder = Config::builder(SocketAddr::new(self.addr.ip(), 0));
217
218 let port = self.addr.port();
220
221 let bind_ipv4 = self.addr.is_ipv4() || nat.advertised_ips.iter().any(IpAddr::is_ipv4);
225 let bind_ipv6 = self.addr.is_ipv6() || nat.advertised_ips.iter().any(IpAddr::is_ipv6);
226 let listen = if bind_ipv4 && bind_ipv6 {
227 ListenConfig::DualStack {
228 ipv4: Ipv4Addr::UNSPECIFIED,
229 ipv4_port: port,
230 ipv6: Ipv6Addr::UNSPECIFIED,
231 ipv6_port: port,
232 }
233 } else if bind_ipv6 {
234 ListenConfig::Ipv6 { ip: Ipv6Addr::UNSPECIFIED, port }
235 } else {
236 ListenConfig::Ipv4 { ip: Ipv4Addr::UNSPECIFIED, port }
237 };
238 builder = builder.discv5_config(discv5::ConfigBuilder::new(listen).build());
239
240 for ip in &nat.advertised_ips {
241 builder = builder.advertised_ip(*ip);
242 }
243
244 builder.build()
245 }
246}
247
248#[derive(Debug)]
250struct BootnodeNat {
251 resolver: NatResolver,
253 advertised_ips: Vec<IpAddr>,
255}
256
257impl BootnodeNat {
258 fn single(resolver: NatResolver, port: u16) -> Self {
259 let advertised_ips = resolver.clone().as_external_ip(port).into_iter().collect();
260 Self { resolver, advertised_ips }
261 }
262}
263
264async fn bind_socket(addr: SocketAddr) -> eyre::Result<Arc<UdpSocket>> {
269 let socket = match addr {
270 SocketAddr::V4(_) => UdpSocket::bind(addr).await?,
271 SocketAddr::V6(_) => {
272 use socket2::{Domain, Protocol, Socket, Type};
273 let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
274 socket.set_only_v6(true)?;
275 socket.set_nonblocking(true)?;
276 socket.bind(&addr.into())?;
277 UdpSocket::from_std(socket.into())?
278 }
279 };
280 Ok(Arc::new(socket))
281}
282
283fn fixed_external_ip(nat: &NatResolver) -> eyre::Result<IpAddr> {
284 match nat {
285 NatResolver::ExternalIp(ip) => Ok(*ip),
286 _ => eyre::bail!("--nat can only be repeated with extip:<IP> values"),
287 }
288}
289
290fn log_discv5_enr(discv5: &Discv5) {
291 let enr = discv5.local_enr();
292 info!(
293 id = ?enr.node_id(),
294 ip4 = ?enr.ip4(),
295 ip6 = ?enr.ip6(),
296 udp4 = ?enr.udp4(),
297 udp6 = ?enr.udp6(),
298 tcp4 = ?enr.tcp4(),
299 tcp6 = ?enr.tcp6(),
300 "(Discv5) advertised endpoints",
301 );
302 info!("(Discv5) enr: {enr}");
303 if let Some(record) = discv5.node_record() {
304 info!("(Discv5) enode: {record}");
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use reth_discv5::build_local_enr;
312
313 #[test]
314 fn repeated_nat_uses_matching_addr_family_as_primary() {
315 let command = Command::parse_from([
316 "reth",
317 "--addr",
318 "[::]:30303",
319 "--nat",
320 "extip:1.2.3.4",
321 "--nat",
322 "extip:2001:db8::1",
323 ]);
324
325 let nat = command.resolved_nat().unwrap();
326
327 assert_eq!(nat.resolver, NatResolver::ExternalIp("2001:db8::1".parse().unwrap()));
330 assert_eq!(nat.advertised_ips.len(), 2);
331 }
332
333 #[test]
334 fn repeated_nat_requires_extip_values() {
335 let command = Command::parse_from(["reth", "--nat", "any", "--nat", "extip:2001:db8::1"]);
336
337 assert!(command.resolved_nat().is_err());
338 }
339
340 #[test]
341 fn repeated_nat_requires_distinct_ip_families() {
342 let command =
343 Command::parse_from(["reth", "--nat", "extip:1.2.3.4", "--nat", "extip:5.6.7.8"]);
344
345 assert!(command.resolved_nat().is_err());
346 }
347
348 #[test]
349 fn single_extip_is_advertised() {
350 let command =
351 Command::parse_from(["reth", "--addr", "0.0.0.0:30301", "--nat", "extip:1.2.3.4"]);
352
353 let nat = command.resolved_nat().unwrap();
354
355 assert_eq!(nat.advertised_ips, vec!["1.2.3.4".parse::<IpAddr>().unwrap()]);
356 }
357
358 #[test]
359 fn discv5_advertises_single_extip_of_other_family_than_addr() {
360 let command = Command::parse_from([
362 "reth",
363 "--addr",
364 "0.0.0.0:45678",
365 "--v5",
366 "--nat",
367 "extip:2001:db8::1",
368 ]);
369 let nat = command.resolved_nat().unwrap();
370 let config = command.discv5_config(&nat);
371 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
372
373 let (enr, _, _, _) = build_local_enr(&sk, &config);
374
375 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
377 assert_eq!(enr.udp6(), Some(45678));
378 }
379
380 #[test]
381 fn discv5_config_single_stack_ipv4_uses_addr_port() {
382 let command = Command::parse_from([
384 "reth",
385 "--addr",
386 "0.0.0.0:45678",
387 "--v5",
388 "--nat",
389 "extip:1.2.3.4",
390 ]);
391 let nat = command.resolved_nat().unwrap();
392 let config = command.discv5_config(&nat);
393 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
394
395 let (enr, _, _, _) = build_local_enr(&sk, &config);
396
397 assert_eq!(enr.ip4(), Some("1.2.3.4".parse().unwrap()));
398 assert_eq!(enr.udp4(), Some(45678));
399 assert_eq!(enr.udp6(), None);
400 }
401
402 #[test]
403 fn discv5_config_single_stack_ipv6_uses_addr_port() {
404 let command = Command::parse_from([
406 "reth",
407 "--addr",
408 "[::]:45678",
409 "--v5",
410 "--nat",
411 "extip:2001:db8::1",
412 ]);
413 let nat = command.resolved_nat().unwrap();
414 let config = command.discv5_config(&nat);
415 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
416
417 let (enr, _, _, _) = build_local_enr(&sk, &config);
418
419 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
420 assert_eq!(enr.udp6(), Some(45678));
421 assert_eq!(enr.udp4(), None);
422 }
423
424 #[test]
425 fn single_opposite_family_extip_does_not_drive_discv4() {
426 let command = Command::parse_from([
429 "reth",
430 "--addr",
431 "0.0.0.0:45678",
432 "--v5",
433 "--nat",
434 "extip:2001:db8::1",
435 ]);
436 let nat = command.resolved_nat().unwrap();
437
438 assert_eq!(nat.resolver, NatResolver::None);
439 assert_eq!(nat.advertised_ips, vec!["2001:db8::1".parse::<IpAddr>().unwrap()]);
440 }
441
442 #[tokio::test]
443 async fn shared_socket_binds_both_families_on_one_port() {
444 let (Ok(probe4), Ok(probe6)) = (
447 bind_socket("0.0.0.0:0".parse().unwrap()).await,
448 bind_socket("[::]:0".parse().unwrap()).await,
449 ) else {
450 return;
451 };
452 drop((probe4, probe6));
453
454 let v4 = bind_socket("0.0.0.0:0".parse().unwrap()).await.unwrap();
455 let port = v4.local_addr().unwrap().port();
456 let v6 = bind_socket(format!("[::]:{port}").parse().unwrap()).await.unwrap();
457 assert_eq!(v6.local_addr().unwrap().port(), port);
458 }
459
460 #[test]
461 fn discv4_config_advertises_only_the_addr_family() {
462 let command = Command::parse_from([
465 "reth",
466 "--addr",
467 "0.0.0.0:30303",
468 "--nat",
469 "extip:1.2.3.4",
470 "--nat",
471 "extip:2001:db8::1",
472 ]);
473 let nat = command.resolved_nat().unwrap();
474 let config = command.discv4_config(&nat);
475
476 assert_eq!(nat.resolver, NatResolver::ExternalIp("1.2.3.4".parse().unwrap()));
477 assert!(config.additional_eip868_rlp_pairs.is_empty());
478 }
479
480 #[test]
481 fn discv5_config_advertises_dual_stack_nat_endpoints() {
482 let command = Command::parse_from([
483 "reth",
484 "--addr",
485 "0.0.0.0:45678",
486 "--v5",
487 "--nat",
488 "extip:1.2.3.4",
489 "--nat",
490 "extip:2001:db8::1",
491 ]);
492 let nat = command.resolved_nat().unwrap();
493 let config = command.discv5_config(&nat);
494 let sk = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
495
496 let (enr, _, _, _) = build_local_enr(&sk, &config);
497
498 assert_eq!(enr.ip4(), Some("1.2.3.4".parse().unwrap()));
500 assert_eq!(enr.ip6(), Some("2001:db8::1".parse().unwrap()));
501 assert_eq!(enr.udp4(), Some(45678));
502 assert_eq!(enr.udp6(), Some(45678));
503 assert_eq!(enr.tcp4(), Some(0));
505 assert_eq!(enr.tcp6(), None);
506
507 let record = NodeRecord::try_from(&enr).unwrap();
509 assert_eq!(record.tcp_port, 0);
510 assert_eq!(record.udp_port, 45678);
511 }
512}