Skip to content

Commit

Permalink
Ipv6 bootnodes (sigp#3752)
Browse files Browse the repository at this point in the history
## Issue Addressed
our bootnodes as of now support only ipv4. this makes it so that they support ipv6

## Proposed Changes
- Adds code necessary to update the bootnodes to run on dual stack nodes and therefore contact and store ipv6 nodes.
- Adds some metrics about connectivity type of stored peers. It might have been nice to see some metrics over the sessions but that feels out of scope right now.

## Additional Info
- some code quality improvements sneaked in since the changes seemed small
- I think it depends on the OS, but enabling mapped addresses on an ipv6 node without dual stack support enabled could fail silently, making these nodes effectively ipv6 only. In the future I'll probably change this to use two sockets, which should fail loudly
  • Loading branch information
divagant-martian authored and Woodpile37 committed Jan 6, 2024
1 parent d4b5e03 commit 1dee86e
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 32 deletions.
9 changes: 6 additions & 3 deletions beacon_node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::cmp::max;
use std::fmt::Debug;
use std::fmt::Write;
use std::fs;
use std::net::Ipv6Addr;
use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::str::FromStr;
Expand Down Expand Up @@ -848,9 +849,11 @@ pub fn set_network_config(
}

if cli_args.is_present("enr-match") {
// set the enr address to localhost if the address is 0.0.0.0
if config.listen_address == "0.0.0.0".parse::<IpAddr>().expect("valid ip addr") {
config.enr_address = Some("127.0.0.1".parse::<IpAddr>().expect("valid ip addr"));
// set the enr address to localhost if the address is unspecified
if config.listen_address == IpAddr::V4(Ipv4Addr::UNSPECIFIED) {
config.enr_address = Some(IpAddr::V4(Ipv4Addr::LOCALHOST));
} else if config.listen_address == IpAddr::V6(Ipv6Addr::UNSPECIFIED) {
config.enr_address = Some(IpAddr::V6(Ipv6Addr::LOCALHOST));
} else {
config.enr_address = Some(config.listen_address);
}
Expand Down
37 changes: 35 additions & 2 deletions boot_node/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use beacon_node::{get_data_dir, set_network_config};
use clap::ArgMatches;
use eth2_network_config::Eth2NetworkConfig;
use lighthouse_network::discv5::enr::EnrBuilder;
use lighthouse_network::discv5::IpMode;
use lighthouse_network::discv5::{enr::CombinedKey, Discv5Config, Enr};
use lighthouse_network::{
discovery::{create_enr_builder_from_config, load_enr_from_disk, use_or_load_enr},
discovery::{load_enr_from_disk, use_or_load_enr},
load_private_key, CombinedKeyExt, NetworkConfig,
};
use serde_derive::{Deserialize, Serialize};
Expand Down Expand Up @@ -70,6 +72,15 @@ impl<T: EthSpec> BootNodeConfig<T> {
// the address to listen on
let listen_socket =
SocketAddr::new(network_config.listen_address, network_config.discovery_port);
if listen_socket.is_ipv6() {
// create ipv6 sockets and enable ipv4 mapped addresses.
network_config.discv5_config.ip_mode = IpMode::Ip6 {
enable_mapped_addresses: true,
};
} else {
// Set explicitly as ipv4 otherwise
network_config.discv5_config.ip_mode = IpMode::Ip4;
}

let private_key = load_private_key(&network_config, &logger);
let local_key = CombinedKey::from_libp2p(&private_key)?;
Expand Down Expand Up @@ -104,7 +115,29 @@ impl<T: EthSpec> BootNodeConfig<T> {
// Build the local ENR

let mut local_enr = {
let mut builder = create_enr_builder_from_config(&network_config, false);
let mut builder = EnrBuilder::new("v4");
// Set the enr address if specified. Set also the port.
// NOTE: if the port is specified but the the address is not, the port won't be
// set since it can't be known if it's an ipv6 or ipv4 udp port.
if let Some(enr_address) = network_config.enr_address {
match enr_address {
std::net::IpAddr::V4(ipv4_addr) => {
builder.ip4(ipv4_addr);
if let Some(port) = network_config.enr_udp_port {
builder.udp4(port);
}
}
std::net::IpAddr::V6(ipv6_addr) => {
builder.ip6(ipv6_addr);
if let Some(port) = network_config.enr_udp_port {
builder.udp6(port);
// We are enabling mapped addresses in the boot node in this case,
// so advertise an udp4 port as well.
builder.udp4(port);
}
}
}
};

// If we know of the ENR field, add it to the initial construction
if let Some(enr_fork_bytes) = enr_fork {
Expand Down
90 changes: 63 additions & 27 deletions boot_node/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,63 @@ use slog::info;
use types::EthSpec;

pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
let BootNodeConfig {
listen_socket,
boot_nodes,
local_enr,
local_key,
discv5_config,
..
} = config;

// Print out useful information about the generated ENR

let enr_socket = config
.local_enr
.udp4_socket()
.expect("Enr has a UDP socket");
let eth2_field = config
.local_enr
let enr_v4_socket = local_enr.udp4_socket();
let enr_v6_socket = local_enr.udp6_socket();
let eth2_field = local_enr
.eth2()
.map(|fork_id| hex::encode(fork_id.fork_digest))
.unwrap_or_default();

info!(log, "Configuration parameters"; "listening_address" => format!("{}:{}", config.listen_socket.ip(), config.listen_socket.port()), "broadcast_address" => format!("{}:{}",enr_socket.ip(), enr_socket.port()), "eth2" => eth2_field);
let pretty_v4_socket = enr_v4_socket.as_ref().map(|addr| addr.to_string());
let pretty_v6_socket = enr_v6_socket.as_ref().map(|addr| addr.to_string());
info!(
log, "Configuration parameters";
"listening_address" => %listen_socket,
"advertised_v4_address" => ?pretty_v4_socket,
"advertised_v6_address" => ?pretty_v6_socket,
"eth2" => eth2_field
);

info!(log, "Identity established"; "peer_id" => config.local_enr.peer_id().to_string(), "node_id" => config.local_enr.node_id().to_string());
info!(log, "Identity established"; "peer_id" => %local_enr.peer_id(), "node_id" => %local_enr.node_id());

// build the contactable multiaddr list, adding the p2p protocol
info!(log, "Contact information"; "enr" => config.local_enr.to_base64());
info!(log, "Contact information"; "multiaddrs" => format!("{:?}", config.local_enr.multiaddr_p2p()));
info!(log, "Contact information"; "enr" => local_enr.to_base64());
info!(log, "Contact information"; "multiaddrs" => ?local_enr.multiaddr_p2p());

// construct the discv5 server
let mut discv5 = Discv5::new(
config.local_enr.clone(),
config.local_key,
config.discv5_config,
)
.unwrap();
let mut discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap();

// If there are any bootnodes add them to the routing table
for enr in config.boot_nodes {
for enr in boot_nodes {
info!(
log,
"Adding bootnode";
"address" => ?enr.udp4_socket(),
"peer_id" => enr.peer_id().to_string(),
"node_id" => enr.node_id().to_string()
"ipv4_address" => ?enr.udp4_socket(),
"ipv6_address" => ?enr.udp6_socket(),
"peer_id" => ?enr.peer_id(),
"node_id" => ?enr.node_id()
);
if enr != config.local_enr {
if enr != local_enr {
if let Err(e) = discv5.add_enr(enr) {
slog::warn!(log, "Failed adding ENR"; "error" => e.to_string());
slog::warn!(log, "Failed adding ENR"; "error" => ?e);
}
}
}

// start the server
if let Err(e) = discv5.start(config.listen_socket).await {
slog::crit!(log, "Could not start discv5 server"; "error" => e.to_string());
if let Err(e) = discv5.start(listen_socket).await {
slog::crit!(log, "Could not start discv5 server"; "error" => %e);
return;
}

Expand All @@ -72,7 +82,7 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
let mut event_stream = match discv5.event_stream().await {
Ok(stream) => stream,
Err(e) => {
slog::crit!(log, "Failed to obtain event stream"; "error" => e.to_string());
slog::crit!(log, "Failed to obtain event stream"; "error" => %e);
return;
}
};
Expand All @@ -81,9 +91,35 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
loop {
tokio::select! {
_ = metric_interval.tick() => {
// Get some ipv4/ipv6 stats to add in the metrics.
let mut ipv4_only_reachable: usize = 0;
let mut ipv6_only_reachable: usize= 0;
let mut ipv4_ipv6_reachable: usize = 0;
let mut unreachable_nodes: usize = 0;
for enr in discv5.kbuckets().iter_ref().filter_map(|entry| entry.status.is_connected().then_some(entry.node.value)) {
let declares_ipv4 = enr.udp4_socket().is_some();
let declares_ipv6 = enr.udp6_socket().is_some();
match (declares_ipv4, declares_ipv6) {
(true, true) => ipv4_ipv6_reachable += 1,
(true, false) => ipv4_only_reachable += 1,
(false, true) => ipv6_only_reachable += 1,
(false, false) => unreachable_nodes += 1,
}
}

// display server metrics
let metrics = discv5.metrics();
info!(log, "Server metrics"; "connected_peers" => discv5.connected_peers(), "active_sessions" => metrics.active_sessions, "requests/s" => format!("{:.2}", metrics.unsolicited_requests_per_second));
info!(
log, "Server metrics";
"connected_peers" => discv5.connected_peers(),
"active_sessions" => metrics.active_sessions,
"requests/s" => format_args!("{:.2}", metrics.unsolicited_requests_per_second),
"ipv4_nodes" => ipv4_only_reachable,
"ipv6_nodes" => ipv6_only_reachable,
"ipv6_and_ipv4_nodes" => ipv4_ipv6_reachable,
"unreachable_nodes" => unreachable_nodes,
);

}
Some(event) = event_stream.recv() => {
match event {
Expand All @@ -95,7 +131,7 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
Discv5Event::TalkRequest(_) => {} // Ignore
Discv5Event::NodeInserted { .. } => {} // Ignore
Discv5Event::SocketUpdated(socket_addr) => {
info!(log, "External socket address updated"; "socket_addr" => format!("{:?}", socket_addr));
info!(log, "Advertised socket address updated"; "socket_addr" => %socket_addr);
}
Discv5Event::SessionEstablished{ .. } => {} // Ignore
}
Expand Down

0 comments on commit 1dee86e

Please sign in to comment.