Support for Ipv6 (#4046)

## Issue Addressed
Add support for ipv6 and dual stack in lighthouse. 

## Proposed Changes
From an user perspective, now setting an ipv6 address, optionally configuring the ports should feel exactly the same as using an ipv4 address. If listening over both ipv4 and ipv6 then the user needs to:
- use the `--listen-address` two times (ipv4 and ipv6 addresses)
- `--port6` becomes then required
- `--discovery-port6` can now be used to additionally configure the ipv6 udp port

### Rough list of code changes
- Discovery:
  - Table filter and ip mode set to match the listening config. 
  - Ipv6 address, tcp port and udp port set in the ENR builder
  - Reported addresses now check which tcp port to give to libp2p
- LH Network Service:
  - Can listen over Ipv6, Ipv4, or both. This uses two sockets. Using mapped addresses is disabled from libp2p and it's the most compatible option.
- NetworkGlobals:
  - No longer stores udp port since was not used at all. Instead, stores the Ipv4 and Ipv6 TCP ports.
- NetworkConfig:
  - Update names to make it clear that previous udp and tcp ports in ENR were Ipv4
  - Add fields to configure Ipv6 udp and tcp ports in the ENR
  - Include advertised enr Ipv6 address.
  - Add type to model Listening address that's either Ipv4, Ipv6 or both. A listening address includes the ip, udp port and tcp port.
- UPnP:
  - Kept only for ipv4
- Cli flags:
  - `--listen-addresses` now can take up to two values
  - `--port` will apply to ipv4 or ipv6 if only one listening address is given. If two listening addresses are given it will apply only to Ipv4.
  - `--port6` New flag required when listening over ipv4 and ipv6 that applies exclusively to Ipv6.
  - `--discovery-port` will now apply to ipv4 and ipv6 if only one listening address is given.
  - `--discovery-port6` New flag to configure the individual udp port of ipv6 if listening over both ipv4 and ipv6.
  - `--enr-udp-port` Updated docs to specify that it only applies to ipv4. This is an old behaviour.
  - `--enr-udp6-port` Added to configure the enr udp6 field.
  - `--enr-tcp-port` Updated docs to specify that it only applies to ipv4. This is an old behaviour.
  - `--enr-tcp6-port` Added to configure the enr tcp6 field.
  - `--enr-addresses` now can take two values.
  - `--enr-match` updated behaviour.
- Common:
  - rename `unused_port` functions to specify that they are over ipv4.
  - add functions to get unused ports over ipv6.
- Testing binaries
  - Updated code to reflect network config changes and unused_port changes.

## Additional Info

TODOs:
- use two sockets in discovery. I'll get back to this and it's on https://github.com/sigp/discv5/pull/160
- lcli allow listening over two sockets in generate_bootnodes_enr
- add at least one smoke flag for ipv6 (I have tested this and works for me)
- update the book
This commit is contained in:
Divma 2023-03-14 01:13:34 +00:00
parent 06af31a66a
commit e190ebb8a0
33 changed files with 1194 additions and 361 deletions

View File

@ -457,7 +457,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");

View File

@ -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.

View File

@ -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
}
@ -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,

View File

@ -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(

View File

@ -1,3 +1,4 @@
use crate::listen_addr::{ListenAddr, ListenAddress};
use crate::rpc::config::OutboundRateLimiterConfig;
use crate::types::GossipKind;
use crate::{Enr, PeerIdSerialized};
@ -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;
@ -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 udp port to broadcast to peers in order to reach back for discovery.
pub enr_udp_port: Option<u16>,
/// The udp4 port to broadcast to peers in order to reach back for discovery.
pub enr_udp4_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 tcp4 port to broadcast to peers in order to reach back for libp2p services.
pub enr_tcp4_port: Option<u16>,
/// The udp6 port to broadcast to peers in order to reach back for discovery.
pub enr_udp6_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,
@ -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 {
@ -183,7 +284,7 @@ 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();
@ -191,12 +292,16 @@ impl Default for Config {
// 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,
@ -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
@ -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))
}

View File

@ -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(udp_port) = config.enr_udp_port {
builder.udp4(udp_port);
if let Some(ip) = maybe_ipv6_address {
builder.ip6(*ip);
}
// we always give it our listening tcp port
if let Some(udp4_port) = config.enr_udp4_port {
builder.udp4(udp4_port);
}
if let Some(udp6_port) = config.enr_udp6_port {
builder.udp6(udp6_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
}

View File

@ -201,8 +201,13 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
info!(log, "ENR Initialised"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(),
"ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp6()
);
let listen_socket = SocketAddr::new(config.listen_address, config.discovery_port);
let listen_socket = match config.listen_addrs() {
crate::listen_addr::ListenAddress::V4(v4_addr) => v4_addr.udp_socket_addr(),
crate::listen_addr::ListenAddress::V6(v6_addr) => v6_addr.udp_socket_addr(),
crate::listen_addr::ListenAddress::DualStack(_v4_addr, v6_addr) => {
v6_addr.udp_socket_addr()
}
};
// convert the keypair into an ENR key
let enr_key: CombinedKey = CombinedKey::from_libp2p(local_key)?;
@ -1015,15 +1020,28 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
*self.network_globals.local_enr.write() = enr;
// A new UDP socket has been detected.
// Build a multiaddr to report to libp2p
let mut address = Multiaddr::from(socket_addr.ip());
let addr = match socket_addr.ip() {
IpAddr::V4(v4_addr) => {
self.network_globals.listen_port_tcp4().map(|tcp4_port| {
Multiaddr::from(v4_addr).with(Protocol::Tcp(tcp4_port))
})
}
IpAddr::V6(v6_addr) => {
self.network_globals.listen_port_tcp6().map(|tcp6_port| {
Multiaddr::from(v6_addr).with(Protocol::Tcp(tcp6_port))
})
}
};
if let Some(address) = addr {
// NOTE: This doesn't actually track the external TCP port. More sophisticated NAT handling
// should handle this.
address.push(Protocol::Tcp(self.network_globals.listen_port_tcp()));
return Poll::Ready(NBAction::ReportObservedAddr {
address,
score: AddressScore::Finite(1),
});
}
}
Discv5Event::EnrAdded { .. }
| Discv5Event::TalkRequest(_)
| Discv5Event::NodeInserted { .. }
@ -1087,7 +1105,6 @@ mod tests {
use enr::EnrBuilder;
use slog::{o, Drain};
use types::{BitVector, MinimalEthSpec, SubnetId};
use unused_port::unused_udp_port;
type E = MinimalEthSpec;
@ -1105,17 +1122,15 @@ mod tests {
async fn build_discovery() -> Discovery<E> {
let keypair = libp2p::identity::Keypair::generate_secp256k1();
let config = NetworkConfig {
discovery_port: unused_udp_port().unwrap(),
..Default::default()
};
let mut config = NetworkConfig::default();
config.set_listening_addr(crate::ListenAddress::unused_v4_ports());
let enr_key: CombinedKey = CombinedKey::from_libp2p(&keypair).unwrap();
let enr: Enr = build_enr::<E>(&enr_key, &config, &EnrForkId::default()).unwrap();
let log = build_log(slog::Level::Debug, false);
let globals = NetworkGlobals::new(
enr,
9000,
9000,
Some(9000),
None,
MetaData::V2(MetaDataV2 {
seq_number: 0,
attnets: Default::default(),

View File

@ -10,12 +10,14 @@ pub mod service;
#[allow(clippy::mutable_key_type)] // PeerId in hashmaps are no longer permitted by clippy
pub mod discovery;
pub mod listen_addr;
pub mod metrics;
pub mod peer_manager;
pub mod rpc;
pub mod types;
pub use config::gossip_max_size;
pub use listen_addr::*;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;

View File

@ -0,0 +1,97 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use libp2p::{multiaddr::Protocol, Multiaddr};
use serde::{Deserialize, Serialize};
/// A listening address composed by an Ip, an UDP port and a TCP port.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListenAddr<Ip> {
pub addr: Ip,
pub udp_port: u16,
pub tcp_port: u16,
}
impl<Ip: Into<IpAddr> + Clone> ListenAddr<Ip> {
pub fn udp_socket_addr(&self) -> SocketAddr {
(self.addr.clone().into(), self.udp_port).into()
}
pub fn tcp_socket_addr(&self) -> SocketAddr {
(self.addr.clone().into(), self.tcp_port).into()
}
}
/// Types of listening addresses Lighthouse can accept.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ListenAddress {
V4(ListenAddr<Ipv4Addr>),
V6(ListenAddr<Ipv6Addr>),
DualStack(ListenAddr<Ipv4Addr>, ListenAddr<Ipv6Addr>),
}
impl ListenAddress {
/// Return the listening address over IpV4 if any.
pub fn v4(&self) -> Option<&ListenAddr<Ipv4Addr>> {
match self {
ListenAddress::V4(v4_addr) | ListenAddress::DualStack(v4_addr, _) => Some(v4_addr),
ListenAddress::V6(_) => None,
}
}
/// Return the listening address over IpV6 if any.
pub fn v6(&self) -> Option<&ListenAddr<Ipv6Addr>> {
match self {
ListenAddress::V6(v6_addr) | ListenAddress::DualStack(_, v6_addr) => Some(v6_addr),
ListenAddress::V4(_) => None,
}
}
/// Returns the TCP addresses.
pub fn tcp_addresses(&self) -> impl Iterator<Item = Multiaddr> + '_ {
let v4_multiaddr = self
.v4()
.map(|v4_addr| Multiaddr::from(v4_addr.addr).with(Protocol::Tcp(v4_addr.tcp_port)));
let v6_multiaddr = self
.v6()
.map(|v6_addr| Multiaddr::from(v6_addr.addr).with(Protocol::Tcp(v6_addr.tcp_port)));
v4_multiaddr.into_iter().chain(v6_multiaddr)
}
#[cfg(test)]
pub fn unused_v4_ports() -> Self {
ListenAddress::V4(ListenAddr {
addr: Ipv4Addr::UNSPECIFIED,
udp_port: unused_port::unused_udp4_port().unwrap(),
tcp_port: unused_port::unused_tcp4_port().unwrap(),
})
}
#[cfg(test)]
pub fn unused_v6_ports() -> Self {
ListenAddress::V6(ListenAddr {
addr: Ipv6Addr::UNSPECIFIED,
udp_port: unused_port::unused_udp6_port().unwrap(),
tcp_port: unused_port::unused_tcp6_port().unwrap(),
})
}
}
impl slog::KV for ListenAddress {
fn serialize(
&self,
_record: &slog::Record,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
if let Some(v4_addr) = self.v4() {
serializer.emit_arguments("ip4_address", &format_args!("{}", v4_addr.addr))?;
serializer.emit_u16("udp4_port", v4_addr.udp_port)?;
serializer.emit_u16("tcp4_port", v4_addr.tcp_port)?;
}
if let Some(v6_addr) = self.v6() {
serializer.emit_arguments("ip6_address", &format_args!("{}", v6_addr.addr))?;
serializer.emit_u16("udp6_port", v6_addr.udp_port)?;
serializer.emit_u16("tcp6_port", v6_addr.tcp_port)?;
}
slog::Result::Ok(())
}
}

View File

@ -163,8 +163,8 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
let meta_data = utils::load_or_build_metadata(&config.network_dir, &log);
let globals = NetworkGlobals::new(
enr,
config.libp2p_port,
config.discovery_port,
config.listen_addrs().v4().map(|v4_addr| v4_addr.tcp_port),
config.listen_addrs().v6().map(|v6_addr| v6_addr.tcp_port),
meta_data,
config
.trusted_peers
@ -388,20 +388,9 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
async fn start(&mut self, config: &crate::NetworkConfig) -> error::Result<()> {
let enr = self.network_globals.local_enr();
info!(self.log, "Libp2p Starting"; "peer_id" => %enr.peer_id(), "bandwidth_config" => format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name));
let discovery_string = if config.disable_discovery {
"None".into()
} else {
config.discovery_port.to_string()
};
debug!(self.log, "Attempting to open listening ports"; "address" => ?config.listen_address, "tcp_port" => config.libp2p_port, "udp_port" => discovery_string);
let listen_multiaddr = {
let mut m = Multiaddr::from(config.listen_address);
m.push(MProtocol::Tcp(config.libp2p_port));
m
};
debug!(self.log, "Attempting to open listening ports"; config.listen_addrs(), "discovery_enabled" => !config.disable_discovery);
for listen_multiaddr in config.listen_addrs().tcp_addresses() {
match self.swarm.listen_on(listen_multiaddr.clone()) {
Ok(_) => {
let mut log_address = listen_multiaddr;
@ -418,6 +407,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
return Err("Libp2p was unable to listen on the given listen address.".into());
}
};
}
// helper closure for dialing peers
let mut dial = |mut multiaddr: Multiaddr| {

View File

@ -7,7 +7,6 @@ use crate::EnrExt;
use crate::{Enr, GossipTopic, Multiaddr, PeerId};
use parking_lot::RwLock;
use std::collections::HashSet;
use std::sync::atomic::{AtomicU16, Ordering};
use types::EthSpec;
pub struct NetworkGlobals<TSpec: EthSpec> {
@ -17,10 +16,10 @@ pub struct NetworkGlobals<TSpec: EthSpec> {
pub peer_id: RwLock<PeerId>,
/// Listening multiaddrs.
pub listen_multiaddrs: RwLock<Vec<Multiaddr>>,
/// The TCP port that the libp2p service is listening on
pub listen_port_tcp: AtomicU16,
/// The UDP port that the discovery service is listening on
pub listen_port_udp: AtomicU16,
/// The TCP port that the libp2p service is listening on over Ipv4.
listen_port_tcp4: Option<u16>,
/// The TCP port that the libp2p service is listening on over Ipv6.
listen_port_tcp6: Option<u16>,
/// The collection of known peers.
pub peers: RwLock<PeerDB<TSpec>>,
// The local meta data of our node.
@ -36,8 +35,8 @@ pub struct NetworkGlobals<TSpec: EthSpec> {
impl<TSpec: EthSpec> NetworkGlobals<TSpec> {
pub fn new(
enr: Enr,
tcp_port: u16,
udp_port: u16,
listen_port_tcp4: Option<u16>,
listen_port_tcp6: Option<u16>,
local_metadata: MetaData<TSpec>,
trusted_peers: Vec<PeerId>,
log: &slog::Logger,
@ -46,8 +45,8 @@ impl<TSpec: EthSpec> NetworkGlobals<TSpec> {
local_enr: RwLock::new(enr.clone()),
peer_id: RwLock::new(enr.peer_id()),
listen_multiaddrs: RwLock::new(Vec::new()),
listen_port_tcp: AtomicU16::new(tcp_port),
listen_port_udp: AtomicU16::new(udp_port),
listen_port_tcp4,
listen_port_tcp6,
local_metadata: RwLock::new(local_metadata),
peers: RwLock::new(PeerDB::new(trusted_peers, log)),
gossipsub_subscriptions: RwLock::new(HashSet::new()),
@ -73,13 +72,13 @@ impl<TSpec: EthSpec> NetworkGlobals<TSpec> {
}
/// Returns the libp2p TCP port that this node has been configured to listen on.
pub fn listen_port_tcp(&self) -> u16 {
self.listen_port_tcp.load(Ordering::Relaxed)
pub fn listen_port_tcp4(&self) -> Option<u16> {
self.listen_port_tcp4
}
/// Returns the UDP discovery port that this node has been configured to listen on.
pub fn listen_port_udp(&self) -> u16 {
self.listen_port_udp.load(Ordering::Relaxed)
pub fn listen_port_tcp6(&self) -> Option<u16> {
self.listen_port_tcp6
}
/// Returns the number of libp2p connected peers.
@ -137,8 +136,8 @@ impl<TSpec: EthSpec> NetworkGlobals<TSpec> {
let enr = discv5::enr::EnrBuilder::new("v4").build(&enr_key).unwrap();
NetworkGlobals::new(
enr,
9000,
9000,
Some(9000),
None,
MetaData::V2(MetaDataV2 {
seq_number: 0,
attnets: Default::default(),

View File

@ -13,7 +13,7 @@ use tokio::runtime::Runtime;
use types::{
ChainSpec, EnrForkId, Epoch, EthSpec, ForkContext, ForkName, Hash256, MinimalEthSpec, Slot,
};
use unused_port::unused_tcp_port;
use unused_port::unused_tcp4_port;
type E = MinimalEthSpec;
type ReqId = usize;
@ -75,11 +75,9 @@ pub fn build_config(port: u16, mut boot_nodes: Vec<Enr>) -> NetworkConfig {
.tempdir()
.unwrap();
config.libp2p_port = port; // tcp port
config.discovery_port = port; // udp port
config.enr_tcp_port = Some(port);
config.enr_udp_port = Some(port);
config.enr_address = Some("127.0.0.1".parse().unwrap());
config.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, port, port);
config.enr_udp4_port = Some(port);
config.enr_address = (Some(std::net::Ipv4Addr::LOCALHOST), None);
config.boot_nodes_enr.append(&mut boot_nodes);
config.network_dir = path.into_path();
// Reduce gossipsub heartbeat parameters
@ -97,7 +95,7 @@ pub async fn build_libp2p_instance(
log: slog::Logger,
fork_name: ForkName,
) -> Libp2pInstance {
let port = unused_tcp_port().unwrap();
let port = unused_tcp4_port().unwrap();
let config = build_config(port, boot_nodes);
// launch libp2p service

View File

@ -36,7 +36,6 @@ const SMALL_CHAIN: u64 = 2;
const LONG_CHAIN: u64 = SLOTS_PER_EPOCH * 2;
const TCP_PORT: u16 = 42;
const UDP_PORT: u16 = 42;
const SEQ_NUMBER: u64 = 0;
/// The default time to wait for `BeaconProcessor` events.
@ -177,8 +176,8 @@ impl TestRig {
let enr = EnrBuilder::new("v4").build(&enr_key).unwrap();
let network_globals = Arc::new(NetworkGlobals::new(
enr,
TCP_PORT,
UDP_PORT,
Some(TCP_PORT),
None,
meta_data,
vec![],
&log,

View File

@ -20,13 +20,13 @@ pub struct UPnPConfig {
disable_discovery: bool,
}
impl From<&NetworkConfig> for UPnPConfig {
fn from(config: &NetworkConfig) -> Self {
UPnPConfig {
tcp_port: config.libp2p_port,
udp_port: config.discovery_port,
impl UPnPConfig {
pub fn from_config(config: &NetworkConfig) -> Option<Self> {
config.listen_addrs().v4().map(|v4_addr| UPnPConfig {
tcp_port: v4_addr.tcp_port,
udp_port: v4_addr.udp_port,
disable_discovery: config.disable_discovery,
}
})
}
}

View File

@ -228,17 +228,22 @@ impl<T: BeaconChainTypes> NetworkService<T> {
let (network_senders, network_recievers) = NetworkSenders::new();
// try and construct UPnP port mappings if required.
let upnp_config = crate::nat::UPnPConfig::from(config);
if let Some(upnp_config) = crate::nat::UPnPConfig::from_config(config) {
let upnp_log = network_log.new(o!("service" => "UPnP"));
let upnp_network_send = network_senders.network_send();
if config.upnp_enabled {
executor.spawn_blocking(
move || {
crate::nat::construct_upnp_mappings(upnp_config, upnp_network_send, upnp_log)
crate::nat::construct_upnp_mappings(
upnp_config,
upnp_network_send,
upnp_log,
)
},
"UPnP",
);
}
}
// get a reference to the beacon chain store
let store = beacon_chain.store.clone();

View File

@ -59,10 +59,9 @@ mod tests {
);
let mut config = NetworkConfig::default();
config.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, 21212, 21212);
config.discv5_config.table_filter = |_| true; // Do not ignore local IPs
config.libp2p_port = 21212;
config.upnp_enabled = false;
config.discovery_port = 21212;
config.boot_nodes_enr = enrs.clone();
runtime.block_on(async move {
// Create a new network service which implicitly gets dropped at the

View File

@ -71,7 +71,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Arg::with_name("listen-address")
.long("listen-address")
.value_name("ADDRESS")
.help("The address lighthouse will listen for UDP and TCP connections.")
.help("The address lighthouse will listen for UDP and TCP connections. To listen \
over IpV4 and IpV6 set this flag twice with the different values.\n\
Examples:\n\
- --listen-address '0.0.0.0' will listen over Ipv4.\n\
- --listen-address '::' will listen over Ipv6.\n\
- --listen-address '0.0.0.0' --listen-address '::' will listen over both \
Ipv4 and Ipv6. The order of the given addresses is not relevant. However, \
multiple Ipv4, or multiple Ipv6 addresses will not be accepted.")
.multiple(true)
.max_values(2)
.default_value("0.0.0.0")
.takes_value(true)
)
@ -79,10 +88,21 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Arg::with_name("port")
.long("port")
.value_name("PORT")
.help("The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.")
.help("The TCP/UDP port to listen on. The UDP port can be modified by the \
--discovery-port flag. If listening over both Ipv4 and Ipv6 the --port flag \
will apply to the Ipv4 address and --port6 to the Ipv6 address.")
.default_value("9000")
.takes_value(true),
)
.arg(
Arg::with_name("port6")
.long("port6")
.value_name("PORT")
.help("The TCP/UDP port to listen on over IpV6 when listening over both Ipv4 and \
Ipv6. Defaults to 9090 when required.")
.default_value("9090")
.takes_value(true),
)
.arg(
Arg::with_name("discovery-port")
.long("discovery-port")
@ -90,6 +110,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.help("The UDP port that discovery will listen on. Defaults to `port`")
.takes_value(true),
)
.arg(
Arg::with_name("discovery-port6")
.long("discovery-port6")
.value_name("PORT")
.help("The UDP port that discovery will listen on over IpV6 if listening over \
both Ipv4 and IpV6. Defaults to `port6`")
.hidden(true) // TODO: implement dual stack via two sockets in discv5.
.takes_value(true),
)
.arg(
Arg::with_name("target-peers")
.long("target-peers")
@ -130,27 +159,49 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Arg::with_name("enr-udp-port")
.long("enr-udp-port")
.value_name("PORT")
.help("The UDP port of the local ENR. Set this only if you are sure other nodes can connect to your local node on this port.")
.help("The UDP4 port of the local ENR. Set this only if you are sure other nodes \
can connect to your local node on this port over IpV4.")
.takes_value(true),
)
.arg(
Arg::with_name("enr-udp6-port")
.long("enr-udp6-port")
.value_name("PORT")
.help("The UDP6 port of the local ENR. Set this only if you are sure other nodes \
can connect to your local node on this port over IpV6.")
.takes_value(true),
)
.arg(
Arg::with_name("enr-tcp-port")
.long("enr-tcp-port")
.value_name("PORT")
.help("The TCP port of the local ENR. Set this only if you are sure other nodes can connect to your local node on this port.\
The --port flag is used if this is not set.")
.help("The TCP4 port of the local ENR. Set this only if you are sure other nodes \
can connect to your local node on this port over IpV4. The --port flag is \
used if this is not set.")
.takes_value(true),
)
.arg(
Arg::with_name("enr-tcp6-port")
.long("enr-tcp6-port")
.value_name("PORT")
.help("The TCP6 port of the local ENR. Set this only if you are sure other nodes \
can connect to your local node on this port over IpV6. The --port6 flag is \
used if this is not set.")
.takes_value(true),
)
.arg(
Arg::with_name("enr-address")
.long("enr-address")
.value_name("ADDRESS")
.help("The IP address/ DNS address to broadcast to other peers on how to reach this node. \
If a DNS address is provided, the enr-address is set to the IP address it resolves to and \
does not auto-update based on PONG responses in discovery. \
Set this only if you are sure other nodes can connect to your local node on this address. \
Discovery will automatically find your external address, if possible.")
.help("The IP address/ DNS address to broadcast to other peers on how to reach \
this node. If a DNS address is provided, the enr-address is set to the IP \
address it resolves to and does not auto-update based on PONG responses in \
discovery. Set this only if you are sure other nodes can connect to your \
local node on this address. This will update the `ip4` or `ip6` ENR fields \
accordingly. To update both, set this flag twice with the different values.")
.requires("enr-udp-port")
.multiple(true)
.max_values(2)
.takes_value(true),
)
.arg(
@ -158,7 +209,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.short("e")
.long("enr-match")
.help("Sets the local ENR IP address and port to match those set for lighthouse. \
Specifically, the IP address will be the value of --listen-address and the UDP port will be --discovery-port.")
Specifically, the IP address will be the value of --listen-address and the \
UDP port will be --discovery-port.")
)
.arg(
Arg::with_name("disable-enr-auto-update")

View File

@ -10,13 +10,13 @@ use environment::RuntimeContext;
use execution_layer::DEFAULT_JWT_FILE;
use genesis::Eth1Endpoint;
use http_api::TlsConfig;
use lighthouse_network::ListenAddress;
use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, PeerIdSerialized};
use sensitive_url::SensitiveUrl;
use slog::{info, warn, Logger};
use std::cmp;
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};
@ -24,7 +24,6 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes, GRAFFITI_BYTES_LEN};
use unused_port::{unused_tcp_port, unused_udp_port};
/// Gets the fully-initialized global client.
///
@ -78,13 +77,7 @@ pub fn get_config<E: EthSpec>(
let data_dir_ref = client_config.data_dir().clone();
set_network_config(
&mut client_config.network,
cli_args,
&data_dir_ref,
log,
false,
)?;
set_network_config(&mut client_config.network, cli_args, &data_dir_ref, log)?;
/*
* Staking flag
@ -404,13 +397,6 @@ pub fn get_config<E: EthSpec>(
* Discovery address is set to localhost by default.
*/
if cli_args.is_present("zero-ports") {
if client_config.network.enr_address == Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))) {
client_config.network.enr_address = None
}
client_config.network.libp2p_port =
unused_tcp_port().map_err(|e| format!("Failed to get port for libp2p: {}", e))?;
client_config.network.discovery_port =
unused_udp_port().map_err(|e| format!("Failed to get port for discovery: {}", e))?;
client_config.http_api.listen_port = 0;
client_config.http_metrics.listen_port = 0;
}
@ -761,13 +747,177 @@ pub fn get_config<E: EthSpec>(
Ok(client_config)
}
/// Sets the network config from the command line arguments
/// Gets the listening_addresses for lighthouse based on the cli options.
pub fn parse_listening_addresses(
cli_args: &ArgMatches,
log: &Logger,
) -> Result<ListenAddress, String> {
let listen_addresses_str = cli_args
.values_of("listen-address")
.expect("--listen_addresses has a default value");
let use_zero_ports = cli_args.is_present("zero-ports");
// parse the possible ips
let mut maybe_ipv4 = None;
let mut maybe_ipv6 = None;
for addr_str in listen_addresses_str {
let addr = addr_str.parse::<IpAddr>().map_err(|parse_error| {
format!("Failed to parse listen-address ({addr_str}) as an Ip address: {parse_error}")
})?;
match addr {
IpAddr::V4(v4_addr) => match &maybe_ipv4 {
Some(first_ipv4_addr) => {
return Err(format!(
"When setting the --listen-address option twice, use an IpV4 address and an Ipv6 address. \
Got two IpV4 addresses {first_ipv4_addr} and {v4_addr}"
));
}
None => maybe_ipv4 = Some(v4_addr),
},
IpAddr::V6(v6_addr) => match &maybe_ipv6 {
Some(first_ipv6_addr) => {
return Err(format!(
"When setting the --listen-address option twice, use an IpV4 address and an Ipv6 address. \
Got two IpV6 addresses {first_ipv6_addr} and {v6_addr}"
));
}
None => maybe_ipv6 = Some(v6_addr),
},
}
}
// parse the possible tcp ports
let port = cli_args
.value_of("port")
.expect("--port has a default value")
.parse::<u16>()
.map_err(|parse_error| format!("Failed to parse --port as an integer: {parse_error}"))?;
let port6 = cli_args
.value_of("port6")
.map(str::parse::<u16>)
.transpose()
.map_err(|parse_error| format!("Failed to parse --port6 as an integer: {parse_error}"))?
.unwrap_or(9090);
// parse the possible udp ports
let maybe_udp_port = cli_args
.value_of("discovery-port")
.map(str::parse::<u16>)
.transpose()
.map_err(|parse_error| {
format!("Failed to parse --discovery-port as an integer: {parse_error}")
})?;
let maybe_udp6_port = cli_args
.value_of("discovery-port6")
.map(str::parse::<u16>)
.transpose()
.map_err(|parse_error| {
format!("Failed to parse --discovery-port6 as an integer: {parse_error}")
})?;
// Now put everything together
let listening_addresses = match (maybe_ipv4, maybe_ipv6) {
(None, None) => {
// This should never happen unless clap is broken
return Err("No listening addresses provided".into());
}
(None, Some(ipv6)) => {
// A single ipv6 address was provided. Set the ports
if cli_args.is_present("port6") {
warn!(log, "When listening only over IpV6, use the --port flag. The value of --port6 will be ignored.")
}
// use zero ports if required. If not, use the given port.
let tcp_port = use_zero_ports
.then(unused_port::unused_tcp6_port)
.transpose()?
.unwrap_or(port);
if maybe_udp6_port.is_some() {
warn!(log, "When listening only over IpV6, use the --discovery-port flag. The value of --discovery-port6 will be ignored.")
}
// use zero ports if required. If not, use the specific udp port. If none given, use
// the tcp port.
let udp_port = use_zero_ports
.then(unused_port::unused_udp6_port)
.transpose()?
.or(maybe_udp_port)
.unwrap_or(port);
ListenAddress::V6(lighthouse_network::ListenAddr {
addr: ipv6,
udp_port,
tcp_port,
})
}
(Some(ipv4), None) => {
// A single ipv4 address was provided. Set the ports
// use zero ports if required. If not, use the given port.
let tcp_port = use_zero_ports
.then(unused_port::unused_tcp4_port)
.transpose()?
.unwrap_or(port);
// use zero ports if required. If not, use the specific udp port. If none given, use
// the tcp port.
let udp_port = use_zero_ports
.then(unused_port::unused_udp4_port)
.transpose()?
.or(maybe_udp_port)
.unwrap_or(port);
ListenAddress::V4(lighthouse_network::ListenAddr {
addr: ipv4,
udp_port,
tcp_port,
})
}
(Some(ipv4), Some(ipv6)) => {
let ipv4_tcp_port = use_zero_ports
.then(unused_port::unused_tcp4_port)
.transpose()?
.unwrap_or(port);
let ipv4_udp_port = use_zero_ports
.then(unused_port::unused_udp4_port)
.transpose()?
.or(maybe_udp_port)
.unwrap_or(ipv4_tcp_port);
// Defaults to 9090 when required
let ipv6_tcp_port = use_zero_ports
.then(unused_port::unused_tcp6_port)
.transpose()?
.unwrap_or(port6);
let ipv6_udp_port = use_zero_ports
.then(unused_port::unused_udp6_port)
.transpose()?
.or(maybe_udp6_port)
.unwrap_or(ipv6_tcp_port);
ListenAddress::DualStack(
lighthouse_network::ListenAddr {
addr: ipv4,
udp_port: ipv4_udp_port,
tcp_port: ipv4_tcp_port,
},
lighthouse_network::ListenAddr {
addr: ipv6,
udp_port: ipv6_udp_port,
tcp_port: ipv6_tcp_port,
},
)
}
};
Ok(listening_addresses)
}
/// Sets the network config from the command line arguments.
pub fn set_network_config(
config: &mut NetworkConfig,
cli_args: &ArgMatches,
data_dir: &Path,
log: &Logger,
use_listening_port_as_enr_port_by_default: bool,
) -> Result<(), String> {
// If a network dir has been specified, override the `datadir` definition.
if let Some(dir) = cli_args.value_of("network-dir") {
@ -788,12 +938,7 @@ pub fn set_network_config(
config.shutdown_after_sync = true;
}
if let Some(listen_address_str) = cli_args.value_of("listen-address") {
let listen_address = listen_address_str
.parse()
.map_err(|_| format!("Invalid listen address: {:?}", listen_address_str))?;
config.listen_address = listen_address;
}
config.set_listening_addr(parse_listening_addresses(cli_args, log)?);
if let Some(target_peers_str) = cli_args.value_of("target-peers") {
config.target_peers = target_peers_str
@ -801,21 +946,6 @@ pub fn set_network_config(
.map_err(|_| format!("Invalid number of target peers: {}", target_peers_str))?;
}
if let Some(port_str) = cli_args.value_of("port") {
let port = port_str
.parse::<u16>()
.map_err(|_| format!("Invalid port: {}", port_str))?;
config.libp2p_port = port;
config.discovery_port = port;
}
if let Some(port_str) = cli_args.value_of("discovery-port") {
let port = port_str
.parse::<u16>()
.map_err(|_| format!("Invalid port: {}", port_str))?;
config.discovery_port = port;
}
if let Some(value) = cli_args.value_of("network-load") {
let network_load = value
.parse::<u8>()
@ -871,7 +1001,7 @@ pub fn set_network_config(
}
if let Some(enr_udp_port_str) = cli_args.value_of("enr-udp-port") {
config.enr_udp_port = Some(
config.enr_udp4_port = Some(
enr_udp_port_str
.parse::<u16>()
.map_err(|_| format!("Invalid discovery port: {}", enr_udp_port_str))?,
@ -879,7 +1009,23 @@ pub fn set_network_config(
}
if let Some(enr_tcp_port_str) = cli_args.value_of("enr-tcp-port") {
config.enr_tcp_port = Some(
config.enr_tcp4_port = Some(
enr_tcp_port_str
.parse::<u16>()
.map_err(|_| format!("Invalid ENR TCP port: {}", enr_tcp_port_str))?,
);
}
if let Some(enr_udp_port_str) = cli_args.value_of("enr-udp6-port") {
config.enr_udp6_port = Some(
enr_udp_port_str
.parse::<u16>()
.map_err(|_| format!("Invalid discovery port: {}", enr_udp_port_str))?,
);
}
if let Some(enr_tcp_port_str) = cli_args.value_of("enr-tcp6-port") {
config.enr_tcp6_port = Some(
enr_tcp_port_str
.parse::<u16>()
.map_err(|_| format!("Invalid ENR TCP port: {}", enr_tcp_port_str))?,
@ -887,58 +1033,106 @@ pub fn set_network_config(
}
if cli_args.is_present("enr-match") {
// Match the Ip and UDP port in the enr.
// 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));
if let Some(ipv4_addr) = config.listen_addrs().v4().cloned() {
let ipv4_enr_addr = if ipv4_addr.addr == Ipv4Addr::UNSPECIFIED {
Ipv4Addr::LOCALHOST
} else {
config.enr_address = Some(config.listen_address);
}
config.enr_udp_port = Some(config.discovery_port);
ipv4_addr.addr
};
config.enr_address.0 = Some(ipv4_enr_addr);
config.enr_udp4_port = Some(ipv4_addr.udp_port);
}
if let Some(enr_address) = cli_args.value_of("enr-address") {
let resolved_addr = match enr_address.parse::<IpAddr>() {
Ok(addr) => addr, // // Input is an IpAddr
if let Some(ipv6_addr) = config.listen_addrs().v6().cloned() {
let ipv6_enr_addr = if ipv6_addr.addr == Ipv6Addr::UNSPECIFIED {
Ipv6Addr::LOCALHOST
} else {
ipv6_addr.addr
};
config.enr_address.1 = Some(ipv6_enr_addr);
config.enr_udp6_port = Some(ipv6_addr.udp_port);
}
}
if let Some(enr_addresses) = cli_args.values_of("enr-address") {
let mut enr_ip4 = None;
let mut enr_ip6 = None;
let mut resolved_enr_ip4 = None;
let mut resolved_enr_ip6 = None;
for addr in enr_addresses {
match addr.parse::<IpAddr>() {
Ok(IpAddr::V4(v4_addr)) => {
if let Some(used) = enr_ip4.as_ref() {
warn!(log, "More than one Ipv4 ENR address provided"; "used" => %used, "ignored" => %v4_addr)
} else {
enr_ip4 = Some(v4_addr)
}
}
Ok(IpAddr::V6(v6_addr)) => {
if let Some(used) = enr_ip6.as_ref() {
warn!(log, "More than one Ipv6 ENR address provided"; "used" => %used, "ignored" => %v6_addr)
} else {
enr_ip6 = Some(v6_addr)
}
}
Err(_) => {
let mut addr = enr_address.to_string();
// Appending enr-port to the dns hostname to appease `to_socket_addrs()` parsing.
// Since enr-update is disabled with a dns address, not setting the enr-udp-port
// will make the node undiscoverable.
if let Some(enr_udp_port) =
config
.enr_udp_port
.or(if use_listening_port_as_enr_port_by_default {
Some(config.discovery_port)
} else {
None
})
{
write!(addr, ":{}", enr_udp_port)
.map_err(|e| format!("Failed to write enr address {}", e))?;
} else {
return Err(
"enr-udp-port must be set for node to be discoverable with dns address"
.into(),
);
// Try to resolve the address
// NOTE: From checking the `to_socket_addrs` code I don't think the port
// actually matters. Just use the udp port.
let port = match config.listen_addrs() {
ListenAddress::V4(v4_addr) => v4_addr.udp_port,
ListenAddress::V6(v6_addr) => v6_addr.udp_port,
ListenAddress::DualStack(v4_addr, _v6_addr) => {
// NOTE: slight preference for ipv4 that I don't think is of importance.
v4_addr.udp_port
}
// `to_socket_addr()` does the dns resolution
// Note: `to_socket_addrs()` is a blocking call
let resolved_addr = if let Ok(mut resolved_addrs) = addr.to_socket_addrs() {
// Pick the first ip from the list of resolved addresses
resolved_addrs
.next()
.map(|a| a.ip())
.ok_or("Resolved dns addr contains no entries")?
} else {
return Err(format!("Failed to parse enr-address: {}", enr_address));
};
let addr_str = format!("{addr}:{port}");
match addr_str.to_socket_addrs() {
Err(_e) => {
return Err(format!("Failed to parse or resolve address {addr}."))
}
Ok(resolved_addresses) => {
for socket_addr in resolved_addresses {
// Use the first ipv4 and first ipv6 addresses present.
// NOTE: this means that if two dns addresses are provided, we
// might end up using the ipv4 and ipv6 resolved addresses of just
// the first.
match socket_addr.ip() {
IpAddr::V4(v4_addr) => {
if resolved_enr_ip4.is_none() {
resolved_enr_ip4 = Some(v4_addr)
}
}
IpAddr::V6(v6_addr) => {
if resolved_enr_ip6.is_none() {
resolved_enr_ip6 = Some(v6_addr)
}
}
}
}
}
}
}
}
}
// The ENR addresses given as ips should take preference over any resolved address
let used_host_resolution = resolved_enr_ip4.is_some() || resolved_enr_ip6.is_some();
let ip4 = enr_ip4.or(resolved_enr_ip4);
let ip6 = enr_ip6.or(resolved_enr_ip6);
config.enr_address = (ip4, ip6);
if used_host_resolution {
config.discv5_config.enr_update = false;
resolved_addr
}
};
config.enr_address = Some(resolved_addr);
}
if cli_args.is_present("disable-enr-auto-update") {

View File

@ -53,6 +53,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.conflicts_with("network-dir")
)
.arg(
Arg::with_name("enr-udp6-port")
.long("enr-udp6-port")
.value_name("PORT")
.help("The UDP6 port of the local ENR. Set this only if you are sure other nodes \
can connect to your local node on this port over IpV6.")
.takes_value(true),
)
.arg(
Arg::with_name("enable-enr-auto-update")
.short("x")

View File

@ -1,7 +1,7 @@
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::discovery::create_enr_builder_from_config;
use lighthouse_network::discv5::IpMode;
use lighthouse_network::discv5::{enr::CombinedKey, Discv5Config, Enr};
use lighthouse_network::{
@ -57,12 +57,24 @@ impl<T: EthSpec> BootNodeConfig<T> {
let logger = slog_scope::logger();
set_network_config(&mut network_config, matches, &data_dir, &logger, true)?;
set_network_config(&mut network_config, matches, &data_dir, &logger)?;
// Set the enr-udp-port to the default listening port if it was not specified.
if !matches.is_present("enr-udp-port") {
network_config.enr_udp_port = Some(network_config.discovery_port);
}
// Set the Enr UDP ports to the listening ports if not present.
if let Some(listening_addr_v4) = network_config.listen_addrs().v4() {
network_config.enr_udp4_port = Some(
network_config
.enr_udp4_port
.unwrap_or(listening_addr_v4.udp_port),
)
};
if let Some(listening_addr_v6) = network_config.listen_addrs().v6() {
network_config.enr_udp6_port = Some(
network_config
.enr_udp6_port
.unwrap_or(listening_addr_v6.udp_port),
)
};
// By default this is enabled. If it is not set, revert to false.
if !matches.is_present("enable-enr-auto-update") {
@ -70,17 +82,29 @@ 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() {
let listen_socket = match network_config.listen_addrs().clone() {
lighthouse_network::ListenAddress::V4(v4_addr) => {
// Set explicitly as ipv4 otherwise
network_config.discv5_config.ip_mode = IpMode::Ip4;
v4_addr.udp_socket_addr()
}
lighthouse_network::ListenAddress::V6(v6_addr) => {
// create ipv6 sockets and enable ipv4 mapped addresses.
network_config.discv5_config.ip_mode = IpMode::Ip6 {
enable_mapped_addresses: false,
};
v6_addr.udp_socket_addr()
}
lighthouse_network::ListenAddress::DualStack(_v4_addr, v6_addr) => {
// 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;
v6_addr.udp_socket_addr()
}
};
let private_key = load_private_key(&network_config, &logger);
let local_key = CombinedKey::from_libp2p(&private_key)?;
@ -115,30 +139,8 @@ impl<T: EthSpec> BootNodeConfig<T> {
// Build the local ENR
let mut local_enr = {
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);
}
}
}
};
let enable_tcp = false;
let mut builder = create_enr_builder_from_config(&network_config, enable_tcp);
// If we know of the ENR field, add it to the initial construction
if let Some(enr_fork_bytes) = enr_fork {
builder.add_value("eth2", enr_fork_bytes.as_slice());

View File

@ -6,14 +6,30 @@ pub enum Transport {
Udp,
}
/// A convenience function for `unused_port(Transport::Tcp)`.
pub fn unused_tcp_port() -> Result<u16, String> {
unused_port(Transport::Tcp)
#[derive(Copy, Clone)]
pub enum IpVersion {
Ipv4,
Ipv6,
}
/// A convenience function for `unused_port(Transport::Tcp)`.
pub fn unused_udp_port() -> Result<u16, String> {
unused_port(Transport::Udp)
/// A convenience wrapper over [`zero_port`].
pub fn unused_tcp4_port() -> Result<u16, String> {
zero_port(Transport::Tcp, IpVersion::Ipv4)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_udp4_port() -> Result<u16, String> {
zero_port(Transport::Udp, IpVersion::Ipv4)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_tcp6_port() -> Result<u16, String> {
zero_port(Transport::Tcp, IpVersion::Ipv6)
}
/// A convenience wrapper over [`zero_port`].
pub fn unused_udp6_port() -> Result<u16, String> {
zero_port(Transport::Udp, IpVersion::Ipv6)
}
/// A bit of hack to find an unused port.
@ -26,10 +42,15 @@ pub fn unused_udp_port() -> Result<u16, String> {
/// It is possible that users are unable to bind to the ports returned by this function as the OS
/// has a buffer period where it doesn't allow binding to the same port even after the socket is
/// closed. We might have to use SO_REUSEADDR socket option from `std::net2` crate in that case.
pub fn unused_port(transport: Transport) -> Result<u16, String> {
pub fn zero_port(transport: Transport, ipv: IpVersion) -> Result<u16, String> {
let localhost = match ipv {
IpVersion::Ipv4 => std::net::Ipv4Addr::LOCALHOST.into(),
IpVersion::Ipv6 => std::net::Ipv6Addr::LOCALHOST.into(),
};
let socket_addr = std::net::SocketAddr::new(localhost, 0);
let local_addr = match transport {
Transport::Tcp => {
let listener = TcpListener::bind("127.0.0.1:0").map_err(|e| {
let listener = TcpListener::bind(socket_addr).map_err(|e| {
format!("Failed to create TCP listener to find unused port: {:?}", e)
})?;
listener.local_addr().map_err(|e| {
@ -40,7 +61,7 @@ pub fn unused_port(transport: Transport) -> Result<u16, String> {
})?
}
Transport::Udp => {
let socket = UdpSocket::bind("127.0.0.1:0")
let socket = UdpSocket::bind(socket_addr)
.map_err(|e| format!("Failed to create UDP socket to find unused port: {:?}", e))?;
socket.local_addr().map_err(|e| {
format!(

View File

@ -3,15 +3,14 @@ use lighthouse_network::{
discovery::{build_enr, CombinedKey, CombinedKeyExt, Keypair, ENR_FILENAME},
NetworkConfig, NETWORK_KEY_FILENAME,
};
use std::fs;
use std::fs::File;
use std::io::Write;
use std::net::IpAddr;
use std::path::PathBuf;
use std::{fs, net::Ipv4Addr};
use types::{ChainSpec, EnrForkId, Epoch, EthSpec, Hash256};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let ip: IpAddr = clap_utils::parse_required(matches, "ip")?;
let ip: Ipv4Addr = clap_utils::parse_required(matches, "ip")?;
let udp_port: u16 = clap_utils::parse_required(matches, "udp-port")?;
let tcp_port: u16 = clap_utils::parse_required(matches, "tcp-port")?;
let output_dir: PathBuf = clap_utils::parse_required(matches, "output-dir")?;
@ -25,12 +24,10 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
));
}
let config = NetworkConfig {
enr_address: Some(ip),
enr_udp_port: Some(udp_port),
enr_tcp_port: Some(tcp_port),
..Default::default()
};
let mut config = NetworkConfig::default();
config.enr_address = (Some(ip), None);
config.enr_udp4_port = Some(udp_port);
config.enr_tcp6_port = Some(tcp_port);
let local_keypair = Keypair::generate_secp256k1();
let enr_key = CombinedKey::from_libp2p(&local_keypair)?;

View File

@ -8,7 +8,7 @@ use eth1::Eth1Endpoint;
use lighthouse_network::PeerId;
use std::fs::File;
use std::io::{Read, Write};
use std::net::IpAddr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
@ -16,7 +16,7 @@ use std::string::ToString;
use std::time::Duration;
use tempfile::TempDir;
use types::{Address, Checkpoint, Epoch, ExecutionBlockHash, ForkName, Hash256, MainnetEthSpec};
use unused_port::{unused_tcp_port, unused_udp_port};
use unused_port::{unused_tcp4_port, unused_tcp6_port, unused_udp4_port, unused_udp6_port};
const DEFAULT_ETH1_ENDPOINT: &str = "http://localhost:8545/";
@ -851,37 +851,188 @@ fn network_shutdown_after_sync_disabled_flag() {
.with_config(|config| assert!(!config.network.shutdown_after_sync));
}
#[test]
fn network_listen_address_flag() {
let addr = "127.0.0.2".parse::<IpAddr>().unwrap();
fn network_listen_address_flag_v4() {
let addr = "127.0.0.2".parse::<Ipv4Addr>().unwrap();
CommandLineTest::new()
.flag("listen-address", Some("127.0.0.2"))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.network.listen_address, addr));
.with_config(|config| {
assert_eq!(
config.network.listen_addrs().v4().map(|addr| addr.addr),
Some(addr)
)
});
}
#[test]
fn network_port_flag() {
let port = unused_tcp_port().expect("Unable to find unused port.");
fn network_listen_address_flag_v6() {
const ADDR: &str = "::1";
let addr = ADDR.parse::<Ipv6Addr>().unwrap();
CommandLineTest::new()
.flag("listen-address", Some(ADDR))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.network.listen_addrs().v6().map(|addr| addr.addr),
Some(addr)
)
});
}
#[test]
fn network_listen_address_flag_dual_stack() {
const V4_ADDR: &str = "127.0.0.1";
const V6_ADDR: &str = "::1";
let ipv6_addr = V6_ADDR.parse::<Ipv6Addr>().unwrap();
let ipv4_addr = V4_ADDR.parse::<Ipv4Addr>().unwrap();
CommandLineTest::new()
.flag("listen-address", Some(V6_ADDR))
.flag("listen-address", Some(V4_ADDR))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.network.listen_addrs().v6().map(|addr| addr.addr),
Some(ipv6_addr)
);
assert_eq!(
config.network.listen_addrs().v4().map(|addr| addr.addr),
Some(ipv4_addr)
)
});
}
#[test]
#[should_panic]
fn network_listen_address_flag_wrong_double_v4_value_config() {
// It's actually possible to listen over multiple sockets in libp2p over the same ip version.
// However this is not compatible with the single contactable address over each version in ENR.
// Because of this, it's important to test this is disallowed.
const V4_ADDR1: &str = "127.0.0.1";
const V4_ADDR2: &str = "0.0.0.0";
CommandLineTest::new()
.flag("listen-address", Some(V4_ADDR1))
.flag("listen-address", Some(V4_ADDR2))
.run_with_zero_port();
}
#[test]
#[should_panic]
fn network_listen_address_flag_wrong_double_v6_value_config() {
// It's actually possible to listen over multiple sockets in libp2p over the same ip version.
// However this is not compatible with the single contactable address over each version in ENR.
// Because of this, it's important to test this is disallowed.
const V6_ADDR1: &str = "::3";
const V6_ADDR2: &str = "::1";
CommandLineTest::new()
.flag("listen-address", Some(V6_ADDR1))
.flag("listen-address", Some(V6_ADDR2))
.run_with_zero_port();
}
#[test]
fn network_port_flag_over_ipv4() {
let port = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("port", Some(port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(config.network.libp2p_port, port);
assert_eq!(config.network.discovery_port, port);
assert_eq!(
config
.network
.listen_addrs()
.v4()
.map(|listen_addr| (listen_addr.udp_port, listen_addr.tcp_port)),
Some((port, port))
);
});
}
#[test]
fn network_port_and_discovery_port_flags() {
let port1 = unused_tcp_port().expect("Unable to find unused port.");
let port2 = unused_udp_port().expect("Unable to find unused port.");
fn network_port_flag_over_ipv6() {
let port = unused_tcp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("port", Some(port1.to_string().as_str()))
.flag("discovery-port", Some(port2.to_string().as_str()))
.flag("listen-address", Some("::1"))
.flag("port", Some(port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(config.network.libp2p_port, port1);
assert_eq!(config.network.discovery_port, port2);
assert_eq!(
config
.network
.listen_addrs()
.v6()
.map(|listen_addr| (listen_addr.udp_port, listen_addr.tcp_port)),
Some((port, port))
);
});
}
#[test]
fn network_port_and_discovery_port_flags_over_ipv4() {
let tcp4_port = unused_tcp4_port().expect("Unable to find unused port.");
let udp4_port = unused_udp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("port", Some(tcp4_port.to_string().as_str()))
.flag("discovery-port", Some(udp4_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(
config
.network
.listen_addrs()
.v4()
.map(|listen_addr| (listen_addr.tcp_port, listen_addr.udp_port)),
Some((tcp4_port, udp4_port))
);
});
}
#[test]
fn network_port_and_discovery_port_flags_over_ipv6() {
let tcp6_port = unused_tcp6_port().expect("Unable to find unused port.");
let udp6_port = unused_udp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("listen-address", Some("::1"))
.flag("port", Some(tcp6_port.to_string().as_str()))
.flag("discovery-port", Some(udp6_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(
config
.network
.listen_addrs()
.v6()
.map(|listen_addr| (listen_addr.tcp_port, listen_addr.udp_port)),
Some((tcp6_port, udp6_port))
);
});
}
#[test]
fn network_port_and_discovery_port_flags_over_ipv4_and_ipv6() {
let tcp4_port = unused_tcp4_port().expect("Unable to find unused port.");
let udp4_port = unused_udp4_port().expect("Unable to find unused port.");
let tcp6_port = unused_tcp6_port().expect("Unable to find unused port.");
let udp6_port = unused_udp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("listen-address", Some("::1"))
.flag("listen-address", Some("127.0.0.1"))
.flag("port", Some(tcp4_port.to_string().as_str()))
.flag("discovery-port", Some(udp4_port.to_string().as_str()))
.flag("port6", Some(tcp6_port.to_string().as_str()))
.flag("discovery-port6", Some(udp6_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(
config
.network
.listen_addrs()
.v4()
.map(|listen_addr| (listen_addr.tcp_port, listen_addr.udp_port)),
Some((tcp4_port, udp4_port))
);
assert_eq!(
config
.network
.listen_addrs()
.v6()
.map(|listen_addr| (listen_addr.tcp_port, listen_addr.udp_port)),
Some((tcp6_port, udp6_port))
);
});
}
#[test]
fn disable_discovery_flag() {
CommandLineTest::new()
@ -986,7 +1137,6 @@ fn zero_ports_flag() {
CommandLineTest::new()
.run_with_zero_port()
.with_config(|config| {
assert_eq!(config.network.enr_address, None);
assert_eq!(config.http_api.listen_port, 0);
assert_eq!(config.http_metrics.listen_port, 0);
});
@ -1003,67 +1153,171 @@ fn network_load_flag() {
// Tests for ENR flags.
#[test]
fn enr_udp_port_flags() {
let port = unused_udp_port().expect("Unable to find unused port.");
fn enr_udp_port_flag() {
let port = unused_udp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-udp-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.network.enr_udp_port, Some(port)));
.with_config(|config| assert_eq!(config.network.enr_udp4_port, Some(port)));
}
#[test]
fn enr_tcp_port_flags() {
let port = unused_tcp_port().expect("Unable to find unused port.");
fn enr_tcp_port_flag() {
let port = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-tcp-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.network.enr_tcp_port, Some(port)));
.with_config(|config| assert_eq!(config.network.enr_tcp4_port, Some(port)));
}
#[test]
fn enr_match_flag() {
let addr = "127.0.0.2".parse::<IpAddr>().unwrap();
let port1 = unused_udp_port().expect("Unable to find unused port.");
let port2 = unused_udp_port().expect("Unable to find unused port.");
fn enr_udp6_port_flag() {
let port = unused_udp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-udp6-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.network.enr_udp6_port, Some(port)));
}
#[test]
fn enr_tcp6_port_flag() {
let port = unused_tcp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-tcp6-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.network.enr_tcp6_port, Some(port)));
}
#[test]
fn enr_match_flag_over_ipv4() {
let addr = "127.0.0.2".parse::<Ipv4Addr>().unwrap();
let udp4_port = unused_udp4_port().expect("Unable to find unused port.");
let tcp4_port = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-match", None)
.flag("listen-address", Some("127.0.0.2"))
.flag("discovery-port", Some(port1.to_string().as_str()))
.flag("port", Some(port2.to_string().as_str()))
.flag("discovery-port", Some(udp4_port.to_string().as_str()))
.flag("port", Some(tcp4_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(config.network.listen_address, addr);
assert_eq!(config.network.enr_address, Some(addr));
assert_eq!(config.network.discovery_port, port1);
assert_eq!(config.network.enr_udp_port, Some(port1));
assert_eq!(
config.network.listen_addrs().v4().map(|listen_addr| (
listen_addr.addr,
listen_addr.udp_port,
listen_addr.tcp_port
)),
Some((addr, udp4_port, tcp4_port))
);
assert_eq!(config.network.enr_address, (Some(addr), None));
assert_eq!(config.network.enr_udp4_port, Some(udp4_port));
});
}
#[test]
fn enr_address_flag() {
let addr = "192.167.1.1".parse::<IpAddr>().unwrap();
let port = unused_udp_port().expect("Unable to find unused port.");
fn enr_match_flag_over_ipv6() {
const ADDR: &str = "::1";
let addr = ADDR.parse::<Ipv6Addr>().unwrap();
let udp6_port = unused_udp6_port().expect("Unable to find unused port.");
let tcp6_port = unused_tcp6_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-match", None)
.flag("listen-address", Some(ADDR))
.flag("discovery-port", Some(udp6_port.to_string().as_str()))
.flag("port", Some(tcp6_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(
config.network.listen_addrs().v6().map(|listen_addr| (
listen_addr.addr,
listen_addr.udp_port,
listen_addr.tcp_port
)),
Some((addr, udp6_port, tcp6_port))
);
assert_eq!(config.network.enr_address, (None, Some(addr)));
assert_eq!(config.network.enr_udp6_port, Some(udp6_port));
});
}
#[test]
fn enr_match_flag_over_ipv4_and_ipv6() {
const IPV6_ADDR: &str = "::1";
let ipv6_addr = IPV6_ADDR.parse::<Ipv6Addr>().unwrap();
let udp6_port = unused_udp6_port().expect("Unable to find unused port.");
let tcp6_port = unused_tcp6_port().expect("Unable to find unused port.");
const IPV4_ADDR: &str = "127.0.0.1";
let ipv4_addr = IPV4_ADDR.parse::<Ipv4Addr>().unwrap();
let udp4_port = unused_udp4_port().expect("Unable to find unused port.");
let tcp4_port = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-match", None)
.flag("listen-address", Some(IPV4_ADDR))
.flag("discovery-port", Some(udp4_port.to_string().as_str()))
.flag("port", Some(tcp4_port.to_string().as_str()))
.flag("listen-address", Some(IPV6_ADDR))
.flag("discovery-port6", Some(udp6_port.to_string().as_str()))
.flag("port6", Some(tcp6_port.to_string().as_str()))
.run()
.with_config(|config| {
assert_eq!(
config.network.listen_addrs().v6().map(|listen_addr| (
listen_addr.addr,
listen_addr.udp_port,
listen_addr.tcp_port
)),
Some((ipv6_addr, udp6_port, tcp6_port))
);
assert_eq!(
config.network.listen_addrs().v4().map(|listen_addr| (
listen_addr.addr,
listen_addr.udp_port,
listen_addr.tcp_port
)),
Some((ipv4_addr, udp4_port, tcp4_port))
);
assert_eq!(
config.network.enr_address,
(Some(ipv4_addr), Some(ipv6_addr))
);
assert_eq!(config.network.enr_udp6_port, Some(udp6_port));
assert_eq!(config.network.enr_udp4_port, Some(udp4_port));
});
}
#[test]
fn enr_address_flag_with_ipv4() {
let addr = "192.167.1.1".parse::<Ipv4Addr>().unwrap();
let port = unused_udp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-address", Some("192.167.1.1"))
.flag("enr-udp-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(config.network.enr_address, Some(addr));
assert_eq!(config.network.enr_udp_port, Some(port));
assert_eq!(config.network.enr_address, (Some(addr), None));
assert_eq!(config.network.enr_udp4_port, Some(port));
});
}
#[test]
fn enr_address_flag_with_ipv6() {
let addr = "192.167.1.1".parse::<Ipv4Addr>().unwrap();
let port = unused_udp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-address", Some("192.167.1.1"))
.flag("enr-udp-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(config.network.enr_address, (Some(addr), None));
assert_eq!(config.network.enr_udp4_port, Some(port));
});
}
#[test]
fn enr_address_dns_flag() {
let addr = "127.0.0.1".parse::<IpAddr>().unwrap();
let ipv6addr = "::1".parse::<IpAddr>().unwrap();
let port = unused_udp_port().expect("Unable to find unused port.");
let addr = Ipv4Addr::LOCALHOST;
let ipv6addr = Ipv6Addr::LOCALHOST;
let port = unused_udp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("enr-address", Some("localhost"))
.flag("enr-udp-port", Some(port.to_string().as_str()))
.run_with_zero_port()
.with_config(|config| {
assert!(
config.network.enr_address == Some(addr)
|| config.network.enr_address == Some(ipv6addr)
config.network.enr_address.0 == Some(addr)
|| config.network.enr_address.1 == Some(ipv6addr)
);
assert_eq!(config.network.enr_udp_port, Some(port));
assert_eq!(config.network.enr_udp4_port, Some(port));
});
}
#[test]
@ -1100,8 +1354,8 @@ fn http_address_ipv6_flag() {
}
#[test]
fn http_port_flag() {
let port1 = unused_tcp_port().expect("Unable to find unused port.");
let port2 = unused_tcp_port().expect("Unable to find unused port.");
let port1 = unused_tcp4_port().expect("Unable to find unused port.");
let port2 = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("http-port", Some(port1.to_string().as_str()))
.flag("port", Some(port2.to_string().as_str()))
@ -1215,8 +1469,8 @@ fn metrics_address_ipv6_flag() {
}
#[test]
fn metrics_port_flag() {
let port1 = unused_tcp_port().expect("Unable to find unused port.");
let port2 = unused_tcp_port().expect("Unable to find unused port.");
let port1 = unused_tcp4_port().expect("Unable to find unused port.");
let port2 = unused_tcp4_port().expect("Unable to find unused port.");
CommandLineTest::new()
.flag("metrics", None)
.flag("metrics-port", Some(port1.to_string().as_str()))

View File

@ -12,7 +12,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use tempfile::TempDir;
use unused_port::unused_udp_port;
use unused_port::unused_udp4_port;
const IP_ADDRESS: &str = "192.168.2.108";
@ -62,7 +62,7 @@ fn enr_address_arg() {
#[test]
fn port_flag() {
let port = unused_udp_port().unwrap();
let port = unused_udp4_port().unwrap();
CommandLineTest::new()
.flag("port", Some(port.to_string().as_str()))
.run_with_ip()
@ -122,7 +122,7 @@ fn boot_nodes_flag() {
#[test]
fn enr_port_flag() {
let port = unused_udp_port().unwrap();
let port = unused_udp4_port().unwrap();
CommandLineTest::new()
.flag("enr-port", Some(port.to_string().as_str()))
.run_with_ip()

View File

@ -3,7 +3,7 @@ use std::io::prelude::*;
use std::io::BufReader;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
use unused_port::unused_tcp_port;
use unused_port::unused_tcp4_port;
use web3::{transports::Http, Transport, Web3};
/// How long we will wait for ganache to indicate that it is ready.
@ -65,7 +65,7 @@ impl GanacheInstance {
/// Start a new `ganache` process, waiting until it indicates that it is ready to accept
/// RPC connections.
pub fn new(chain_id: u64) -> Result<Self, String> {
let port = unused_tcp_port()?;
let port = unused_tcp4_port()?;
let binary = match cfg!(windows) {
true => "ganache.cmd",
false => "ganache",
@ -97,7 +97,7 @@ impl GanacheInstance {
}
pub fn fork(&self) -> Result<Self, String> {
let port = unused_tcp_port()?;
let port = unused_tcp4_port()?;
let binary = match cfg!(windows) {
true => "ganache.cmd",
false => "ganache",

View File

@ -4,7 +4,7 @@ use sensitive_url::SensitiveUrl;
use std::path::PathBuf;
use std::process::Child;
use tempfile::TempDir;
use unused_port::unused_tcp_port;
use unused_port::unused_tcp4_port;
pub const KEYSTORE_PASSWORD: &str = "testpwd";
pub const ACCOUNT1: &str = "7b8C3a386C0eea54693fFB0DA17373ffC9228139";
@ -50,8 +50,8 @@ impl<E: GenericExecutionEngine> ExecutionEngine<E> {
pub fn new(engine: E) -> Self {
let datadir = E::init_datadir();
let jwt_secret_path = datadir.path().join(DEFAULT_JWT_FILE);
let http_port = unused_tcp_port().unwrap();
let http_auth_port = unused_tcp_port().unwrap();
let http_port = unused_tcp4_port().unwrap();
let http_auth_port = unused_tcp4_port().unwrap();
let child = E::start_client(&datadir, http_port, http_auth_port, jwt_secret_path);
let provider = Provider::<Http>::try_from(format!("http://localhost:{}", http_port))
.expect("failed to instantiate ethers provider");

View File

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use std::{env, fs::File};
use tempfile::TempDir;
use unused_port::unused_tcp_port;
use unused_port::unused_tcp4_port;
const GETH_BRANCH: &str = "master";
const GETH_REPO_URL: &str = "https://github.com/ethereum/go-ethereum";
@ -83,7 +83,7 @@ impl GenericExecutionEngine for GethEngine {
http_auth_port: u16,
jwt_secret_path: PathBuf,
) -> Child {
let network_port = unused_tcp_port().unwrap();
let network_port = unused_tcp4_port().unwrap();
Command::new(Self::binary_path())
.arg("--datadir")

View File

@ -6,7 +6,7 @@ use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use tempfile::TempDir;
use unused_port::unused_tcp_port;
use unused_port::unused_tcp4_port;
/// We've pinned the Nethermind version since our method of using the `master` branch to
/// find the latest tag isn't working. It appears Nethermind don't always tag on `master`.
@ -88,7 +88,7 @@ impl GenericExecutionEngine for NethermindEngine {
http_auth_port: u16,
jwt_secret_path: PathBuf,
) -> Child {
let network_port = unused_tcp_port().unwrap();
let network_port = unused_tcp4_port().unwrap();
let genesis_json_path = datadir.path().join("genesis.json");
Command::new(Self::binary_path())

View File

@ -89,8 +89,9 @@ pub fn testing_client_config() -> ClientConfig {
let mut client_config = ClientConfig::default();
// Setting ports to `0` means that the OS will choose some available port.
client_config.network.libp2p_port = 0;
client_config.network.discovery_port = 0;
client_config
.network
.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, 0, 0);
client_config.network.upnp_enabled = false;
client_config.http_api.enabled = true;
client_config.http_api.listen_port = 0;

View File

@ -13,7 +13,7 @@ use node_test_rig::{
use rayon::prelude::*;
use sensitive_url::SensitiveUrl;
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr};
use std::net::Ipv4Addr;
use std::time::Duration;
use tokio::time::sleep;
use types::{Epoch, EthSpec, MinimalEthSpec};
@ -149,7 +149,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
beacon_config.eth1.chain_id = Eth1Id::from(chain_id);
beacon_config.network.target_peers = node_count - 1;
beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None);
if post_merge_sim {
let el_config = execution_layer::Config {

View File

@ -58,10 +58,13 @@ impl<E: EthSpec> LocalNetwork<E> {
context: RuntimeContext<E>,
mut beacon_config: ClientConfig,
) -> Result<Self, String> {
beacon_config.network.discovery_port = BOOTNODE_PORT;
beacon_config.network.libp2p_port = BOOTNODE_PORT;
beacon_config.network.enr_udp_port = Some(BOOTNODE_PORT);
beacon_config.network.enr_tcp_port = Some(BOOTNODE_PORT);
beacon_config.network.set_ipv4_listening_address(
std::net::Ipv4Addr::UNSPECIFIED,
BOOTNODE_PORT,
BOOTNODE_PORT,
);
beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT);
beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT);
beacon_config.network.discv5_config.table_filter = |_| true;
let execution_node = if let Some(el_config) = &mut beacon_config.execution_layer {
@ -132,10 +135,13 @@ impl<E: EthSpec> LocalNetwork<E> {
.enr()
.expect("bootnode must have a network"),
);
beacon_config.network.discovery_port = BOOTNODE_PORT + count;
beacon_config.network.libp2p_port = BOOTNODE_PORT + count;
beacon_config.network.enr_udp_port = Some(BOOTNODE_PORT + count);
beacon_config.network.enr_tcp_port = Some(BOOTNODE_PORT + count);
beacon_config.network.set_ipv4_listening_address(
std::net::Ipv4Addr::UNSPECIFIED,
BOOTNODE_PORT + count,
BOOTNODE_PORT + count,
);
beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT + count);
beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT + count);
beacon_config.network.discv5_config.table_filter = |_| true;
}
if let Some(el_config) = &mut beacon_config.execution_layer {

View File

@ -7,7 +7,7 @@ use node_test_rig::{
};
use rayon::prelude::*;
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr};
use std::net::Ipv4Addr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::time::sleep;
use types::{Epoch, EthSpec, MainnetEthSpec};
@ -91,7 +91,7 @@ pub fn run_no_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
beacon_config.dummy_eth1_backend = true;
beacon_config.sync_eth1_chain = true;
beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None);
let main_future = async {
let network = LocalNetwork::new(context.clone(), beacon_config.clone()).await?;

View File

@ -8,7 +8,7 @@ use node_test_rig::{
};
use node_test_rig::{testing_validator_config, ClientConfig};
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr};
use std::net::Ipv4Addr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use types::{Epoch, EthSpec};
@ -95,7 +95,7 @@ fn syncing_sim(
beacon_config.http_api.allow_sync_stalled = true;
beacon_config.network.enr_address = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None);
// Generate the directories and keystores required for the validator clients.
let validator_indices = (0..num_validators).collect::<Vec<_>>();