Ipv6 bootnodes (#3752)

## 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
This commit is contained in:
Divma 2022-11-30 03:21:35 +00:00
parent 3534c85e30
commit b4f4c0d253
3 changed files with 104 additions and 32 deletions

View File

@ -14,6 +14,7 @@ use std::cmp::max;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Write; use std::fmt::Write;
use std::fs; use std::fs;
use std::net::Ipv6Addr;
use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs}; use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
@ -843,9 +844,11 @@ pub fn set_network_config(
} }
if cli_args.is_present("enr-match") { if cli_args.is_present("enr-match") {
// set the enr address to localhost if the address is 0.0.0.0 // set the enr address to localhost if the address is unspecified
if config.listen_address == "0.0.0.0".parse::<IpAddr>().expect("valid ip addr") { if config.listen_address == IpAddr::V4(Ipv4Addr::UNSPECIFIED) {
config.enr_address = Some("127.0.0.1".parse::<IpAddr>().expect("valid ip addr")); 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 { } else {
config.enr_address = Some(config.listen_address); config.enr_address = Some(config.listen_address);
} }

View File

@ -1,9 +1,11 @@
use beacon_node::{get_data_dir, set_network_config}; use beacon_node::{get_data_dir, set_network_config};
use clap::ArgMatches; use clap::ArgMatches;
use eth2_network_config::Eth2NetworkConfig; 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::discv5::{enr::CombinedKey, Discv5Config, Enr};
use lighthouse_network::{ 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, load_private_key, CombinedKeyExt, NetworkConfig,
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -70,6 +72,15 @@ impl<T: EthSpec> BootNodeConfig<T> {
// the address to listen on // the address to listen on
let listen_socket = let listen_socket =
SocketAddr::new(network_config.listen_address, network_config.discovery_port); 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 private_key = load_private_key(&network_config, &logger);
let local_key = CombinedKey::from_libp2p(&private_key)?; let local_key = CombinedKey::from_libp2p(&private_key)?;
@ -104,7 +115,29 @@ impl<T: EthSpec> BootNodeConfig<T> {
// Build the local ENR // Build the local ENR
let mut 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 we know of the ENR field, add it to the initial construction
if let Some(enr_fork_bytes) = enr_fork { if let Some(enr_fork_bytes) = enr_fork {

View File

@ -9,53 +9,63 @@ use slog::info;
use types::EthSpec; use types::EthSpec;
pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) { 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 // Print out useful information about the generated ENR
let enr_socket = config let enr_v4_socket = local_enr.udp4_socket();
.local_enr let enr_v6_socket = local_enr.udp6_socket();
.udp4_socket() let eth2_field = local_enr
.expect("Enr has a UDP socket");
let eth2_field = config
.local_enr
.eth2() .eth2()
.map(|fork_id| hex::encode(fork_id.fork_digest)) .map(|fork_id| hex::encode(fork_id.fork_digest))
.unwrap_or_default(); .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 // build the contactable multiaddr list, adding the p2p protocol
info!(log, "Contact information"; "enr" => config.local_enr.to_base64()); info!(log, "Contact information"; "enr" => local_enr.to_base64());
info!(log, "Contact information"; "multiaddrs" => format!("{:?}", config.local_enr.multiaddr_p2p())); info!(log, "Contact information"; "multiaddrs" => ?local_enr.multiaddr_p2p());
// construct the discv5 server // construct the discv5 server
let mut discv5 = Discv5::new( let mut discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap();
config.local_enr.clone(),
config.local_key,
config.discv5_config,
)
.unwrap();
// If there are any bootnodes add them to the routing table // If there are any bootnodes add them to the routing table
for enr in config.boot_nodes { for enr in boot_nodes {
info!( info!(
log, log,
"Adding bootnode"; "Adding bootnode";
"address" => ?enr.udp4_socket(), "ipv4_address" => ?enr.udp4_socket(),
"peer_id" => enr.peer_id().to_string(), "ipv6_address" => ?enr.udp6_socket(),
"node_id" => enr.node_id().to_string() "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) { 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 // start the server
if let Err(e) = discv5.start(config.listen_socket).await { if let Err(e) = discv5.start(listen_socket).await {
slog::crit!(log, "Could not start discv5 server"; "error" => e.to_string()); slog::crit!(log, "Could not start discv5 server"; "error" => %e);
return; return;
} }
@ -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 { let mut event_stream = match discv5.event_stream().await {
Ok(stream) => stream, Ok(stream) => stream,
Err(e) => { 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; return;
} }
}; };
@ -81,9 +91,35 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
loop { loop {
tokio::select! { tokio::select! {
_ = metric_interval.tick() => { _ = 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 // display server metrics
let metrics = discv5.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() => { Some(event) = event_stream.recv() => {
match event { match event {
@ -95,7 +131,7 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
Discv5Event::TalkRequest(_) => {} // Ignore Discv5Event::TalkRequest(_) => {} // Ignore
Discv5Event::NodeInserted { .. } => {} // Ignore Discv5Event::NodeInserted { .. } => {} // Ignore
Discv5Event::SocketUpdated(socket_addr) => { 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 Discv5Event::SessionEstablished{ .. } => {} // Ignore
} }