Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Support for Ipv6 #4046

Closed
wants to merge 12 commits into from
2 changes: 1 addition & 1 deletion beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ where
builder_threshold: Option<u128>,
) -> Self {
// Get a random unused port
let port = unused_port::unused_tcp_port().unwrap();
let port = unused_port::unused_tcp4_port().unwrap();
let builder_url = SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap();

let spec = self.spec.clone().expect("cannot build without spec");
Expand Down
15 changes: 12 additions & 3 deletions beacon_node/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,18 @@ impl<T: BeaconChainTypes> Client<T> {
self.http_metrics_listen_addr
}

/// Returns the port of the client's libp2p stack, if it was started.
pub fn libp2p_listen_port(&self) -> Option<u16> {
self.network_globals.as_ref().map(|n| n.listen_port_tcp())
/// Returns the ipv4 port of the client's libp2p stack, if it was started.
pub fn libp2p_listen_ipv4_port(&self) -> Option<u16> {
self.network_globals
.as_ref()
.and_then(|n| n.listen_port_tcp4())
}

/// Returns the ipv6 port of the client's libp2p stack, if it was started.
pub fn libp2p_listen_ipv6_port(&self) -> Option<u16> {
self.network_globals
.as_ref()
.and_then(|n| n.listen_port_tcp6())
}

/// Returns the list of libp2p addresses the client is listening to.
Expand Down
6 changes: 3 additions & 3 deletions beacon_node/http_api/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub async fn create_api_server<T: BeaconChainTypes>(
log: Logger,
) -> ApiServer<T::EthSpec, impl Future<Output = ()>> {
// Get a random unused port.
let port = unused_port::unused_tcp_port().unwrap();
let port = unused_port::unused_tcp4_port().unwrap();
create_api_server_on_port(chain, log, port).await
}

Expand All @@ -151,8 +151,8 @@ pub async fn create_api_server_on_port<T: BeaconChainTypes>(
let enr = EnrBuilder::new("v4").build(&enr_key).unwrap();
let network_globals = Arc::new(NetworkGlobals::new(
enr.clone(),
TCP_PORT,
UDP_PORT,
Some(TCP_PORT),
None,
meta_data,
vec![],
&log,
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl ApiTester {
pub async fn new_from_config(config: ApiTesterConfig) -> Self {
// Get a random unused port
let spec = config.spec;
let port = unused_port::unused_tcp_port().unwrap();
let port = unused_port::unused_tcp4_port().unwrap();
let beacon_url = SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap();

let harness = Arc::new(
Expand Down
204 changes: 183 additions & 21 deletions beacon_node/lighthouse_network/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::listen_addr::{ListenAddr, ListenAddress};
use crate::rpc::config::OutboundRateLimiterConfig;
use crate::types::GossipKind;
use crate::{Enr, PeerIdSerialized};
Expand All @@ -12,6 +13,7 @@ use libp2p::gossipsub::{
use libp2p::Multiaddr;
use serde_derive::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
Expand Down Expand Up @@ -57,24 +59,24 @@ pub struct Config {
/// Data directory where node's keyfile is stored
pub network_dir: PathBuf,

/// IP address to listen on.
pub listen_address: std::net::IpAddr,

/// The TCP port that libp2p listens on.
pub libp2p_port: u16,

/// UDP port that discovery listens on.
pub discovery_port: u16,
/// IP addresses to listen on.
listen_addresses: ListenAddress,

/// The address to broadcast to peers about which address we are listening on. None indicates
/// that no discovery address has been set in the CLI args.
pub enr_address: Option<std::net::IpAddr>,
pub enr_address: (Option<Ipv4Addr>, Option<Ipv6Addr>),

/// The udp4 port to broadcast to peers in order to reach back for discovery.
pub enr_udp4_port: Option<u16>,

/// The tcp4 port to broadcast to peers in order to reach back for libp2p services.
pub enr_tcp4_port: Option<u16>,

/// The udp port to broadcast to peers in order to reach back for discovery.
pub enr_udp_port: Option<u16>,
/// The udp6 port to broadcast to peers in order to reach back for discovery.
pub enr_udp6_port: Option<u16>,

/// The tcp port to broadcast to peers in order to reach back for libp2p services.
pub enr_tcp_port: Option<u16>,
/// The tcp6 port to broadcast to peers in order to reach back for libp2p services.
pub enr_tcp6_port: Option<u16>,

/// Target number of connected peers.
pub target_peers: usize,
Expand Down Expand Up @@ -139,6 +141,105 @@ pub struct Config {
pub outbound_rate_limiter_config: Option<OutboundRateLimiterConfig>,
}

impl Config {
/// Sets the listening address to use an ipv4 address. The discv5 ip_mode and table filter are
/// adjusted accordingly to ensure addresses that are present in the enr are globally
/// reachable.
pub fn set_ipv4_listening_address(&mut self, addr: Ipv4Addr, tcp_port: u16, udp_port: u16) {
self.listen_addresses = ListenAddress::V4(ListenAddr {
addr,
udp_port,
tcp_port,
});
self.discv5_config.ip_mode = discv5::IpMode::Ip4;
self.discv5_config.table_filter = |enr| enr.ip4().as_ref().map_or(false, is_global_ipv4)
}

/// Sets the listening address to use an ipv6 address. The discv5 ip_mode and table filter is
/// adjusted accordingly to ensure addresses that are present in the enr are globally
/// reachable.
pub fn set_ipv6_listening_address(&mut self, addr: Ipv6Addr, tcp_port: u16, udp_port: u16) {
self.listen_addresses = ListenAddress::V6(ListenAddr {
addr,
udp_port,
tcp_port,
});
self.discv5_config.ip_mode = discv5::IpMode::Ip6 {
enable_mapped_addresses: false,
};
self.discv5_config.table_filter = |enr| enr.ip6().as_ref().map_or(false, is_global_ipv6)
}

/// Sets the listening address to use both an ipv4 and ipv6 address. The discv5 ip_mode and
/// table filter is adjusted accordingly to ensure addresses that are present in the enr are
/// globally reachable.
pub fn set_ipv4_ipv6_listening_addresses(
&mut self,
v4_addr: Ipv4Addr,
tcp4_port: u16,
udp4_port: u16,
v6_addr: Ipv6Addr,
tcp6_port: u16,
udp6_port: u16,
) {
self.listen_addresses = ListenAddress::DualStack(
ListenAddr {
addr: v4_addr,
udp_port: udp4_port,
tcp_port: tcp4_port,
},
ListenAddr {
addr: v6_addr,
udp_port: udp6_port,
tcp_port: tcp6_port,
},
);

self.discv5_config.ip_mode = discv5::IpMode::Ip6 {
enable_mapped_addresses: true,
};
self.discv5_config.table_filter = |enr| match (&enr.ip4(), &enr.ip6()) {
(None, None) => false,
(None, Some(ip6)) => is_global_ipv6(ip6),
(Some(ip4), None) => is_global_ipv4(ip4),
(Some(ip4), Some(ip6)) => is_global_ipv4(ip4) && is_global_ipv6(ip6),
};
}

pub fn set_listening_addr(&mut self, listen_addr: ListenAddress) {
match listen_addr {
ListenAddress::V4(ListenAddr {
addr,
udp_port,
tcp_port,
}) => self.set_ipv4_listening_address(addr, tcp_port, udp_port),
ListenAddress::V6(ListenAddr {
addr,
udp_port,
tcp_port,
}) => self.set_ipv6_listening_address(addr, tcp_port, udp_port),
ListenAddress::DualStack(
ListenAddr {
addr: ip4addr,
udp_port: udp4_port,
tcp_port: tcp4_port,
},
ListenAddr {
addr: ip6addr,
udp_port: udp6_port,
tcp_port: tcp6_port,
},
) => self.set_ipv4_ipv6_listening_addresses(
ip4addr, tcp4_port, udp4_port, ip6addr, tcp6_port, udp6_port,
),
}
}

pub fn listen_addrs(&self) -> &ListenAddress {
&self.listen_addresses
}
}

impl Default for Config {
/// Generate a default network configuration.
fn default() -> Self {
Expand Down Expand Up @@ -183,20 +284,24 @@ impl Default for Config {
.filter_rate_limiter(filter_rate_limiter)
.filter_max_bans_per_ip(Some(5))
.filter_max_nodes_per_ip(Some(10))
.table_filter(|enr| enr.ip4().map_or(false, |ip| is_global(&ip))) // Filter non-global IPs
.table_filter(|enr| enr.ip4().map_or(false, |ip| is_global_ipv4(&ip))) // Filter non-global IPs
.ban_duration(Some(Duration::from_secs(3600)))
.ping_interval(Duration::from_secs(300))
.build();

// NOTE: Some of these get overridden by the corresponding CLI default values.
Config {
network_dir,
listen_address: "0.0.0.0".parse().expect("valid ip address"),
libp2p_port: 9000,
discovery_port: 9000,
enr_address: None,
enr_udp_port: None,
enr_tcp_port: None,
listen_addresses: ListenAddress::V4(ListenAddr {
addr: Ipv4Addr::UNSPECIFIED,
udp_port: 9000,
tcp_port: 9000,
}),
enr_address: (None, None),
enr_udp4_port: None,
enr_tcp4_port: None,
enr_udp6_port: None,
enr_tcp6_port: None,
target_peers: 50,
gs_config,
discv5_config,
Expand Down Expand Up @@ -361,7 +466,7 @@ pub fn gossipsub_config(network_load: u8, fork_context: Arc<ForkContext>) -> Gos
/// Helper function to determine if the IpAddr is a global address or not. The `is_global()`
/// function is not yet stable on IpAddr.
#[allow(clippy::nonminimal_bool)]
fn is_global(addr: &std::net::Ipv4Addr) -> bool {
fn is_global_ipv4(addr: &Ipv4Addr) -> bool {
// check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two
// globally routable addresses in the 192.0.0.0/24 range.
if u32::from_be_bytes(addr.octets()) == 0xc0000009
Expand All @@ -382,3 +487,60 @@ fn is_global(addr: &std::net::Ipv4Addr) -> bool {
// Make sure the address is not in 0.0.0.0/8
&& addr.octets()[0] != 0
}

/// NOTE: Docs taken from https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_global
///
/// Returns true if the address appears to be globally reachable as specified by the IANA IPv6
/// Special-Purpose Address Registry. Whether or not an address is practically reachable will
/// depend on your network configuration.
///
/// Most IPv6 addresses are globally reachable; unless they are specifically defined as not
/// globally reachable.
///
/// Non-exhaustive list of notable addresses that are not globally reachable:
///
/// - The unspecified address (is_unspecified)
/// - The loopback address (is_loopback)
/// - IPv4-mapped addresses
/// - Addresses reserved for benchmarking
/// - Addresses reserved for documentation (is_documentation)
/// - Unique local addresses (is_unique_local)
/// - Unicast addresses with link-local scope (is_unicast_link_local)
// TODO: replace with [`Ipv6Addr::is_global`] once
// [Ip](https://github.com/rust-lang/rust/issues/27709) is stable.
pub const fn is_global_ipv6(addr: &Ipv6Addr) -> bool {
const fn is_documentation(addr: &Ipv6Addr) -> bool {
(addr.segments()[0] == 0x2001) && (addr.segments()[1] == 0xdb8)
}
const fn is_unique_local(addr: &Ipv6Addr) -> bool {
(addr.segments()[0] & 0xfe00) == 0xfc00
}
const fn is_unicast_link_local(addr: &Ipv6Addr) -> bool {
(addr.segments()[0] & 0xffc0) == 0xfe80
}
!(addr.is_unspecified()
|| addr.is_loopback()
// IPv4-mapped Address (`::ffff:0:0/96`)
|| matches!(addr.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
// IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
|| matches!(addr.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
// Discard-Only Address Block (`100::/64`)
|| matches!(addr.segments(), [0x100, 0, 0, 0, _, _, _, _])
// IETF Protocol Assignments (`2001::/23`)
|| (matches!(addr.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
&& !(
// Port Control Protocol Anycast (`2001:1::1`)
u128::from_be_bytes(addr.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
// Traversal Using Relays around NAT Anycast (`2001:1::2`)
|| u128::from_be_bytes(addr.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
// AMT (`2001:3::/32`)
|| matches!(addr.segments(), [0x2001, 3, _, _, _, _, _, _])
// AS112-v6 (`2001:4:112::/48`)
|| matches!(addr.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
// ORCHIDv2 (`2001:20::/28`)
|| matches!(addr.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x2F)
))
|| is_documentation(addr)
|| is_unique_local(addr)
|| is_unicast_link_local(addr))
}
37 changes: 30 additions & 7 deletions beacon_node/lighthouse_network/src/discovery/enr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,39 @@ pub fn create_enr_builder_from_config<T: EnrKey>(
enable_tcp: bool,
) -> EnrBuilder<T> {
let mut builder = EnrBuilder::new("v4");
if let Some(enr_address) = config.enr_address {
builder.ip(enr_address);
let (maybe_ipv4_address, maybe_ipv6_address) = &config.enr_address;

if let Some(ip) = maybe_ipv4_address {
builder.ip4(*ip);
}

if let Some(ip) = maybe_ipv6_address {
builder.ip6(*ip);
}

if let Some(udp4_port) = config.enr_udp4_port {
builder.udp4(udp4_port);
}
if let Some(udp_port) = config.enr_udp_port {
builder.udp4(udp_port);

if let Some(udp6_port) = config.enr_udp6_port {
builder.udp6(udp6_port);
}
// we always give it our listening tcp port

if enable_tcp {
let tcp_port = config.enr_tcp_port.unwrap_or(config.libp2p_port);
builder.tcp4(tcp_port);
// If the ENR port is not set, and we are listening over that ip version, use the listening port instead.
let tcp4_port = config
.enr_tcp4_port
.or_else(|| config.listen_addrs().v4().map(|v4_addr| v4_addr.tcp_port));
if let Some(tcp4_port) = tcp4_port {
builder.tcp4(tcp4_port);
}

let tcp6_port = config
.enr_tcp6_port
.or_else(|| config.listen_addrs().v6().map(|v6_addr| v6_addr.tcp_port));
if let Some(tcp6_port) = tcp6_port {
builder.tcp6(tcp6_port);
}
}
builder
}
Expand Down
Loading