feat(net): IP resolution in docker (#10681)

Co-authored-by: Cody Wang <cody.wang@coinbase.com>
This commit is contained in:
Emilia Hane
2024-09-11 11:57:58 +02:00
committed by GitHub
parent 3d7bcb037f
commit 6764f7bc96
14 changed files with 147 additions and 3 deletions

11
Cargo.lock generated
View File

@ -3845,6 +3845,16 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "if-addrs"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78a89907582615b19f6f0da1af18abf6ff08be259395669b834b057a7ee92d8"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "impl-codec" name = "impl-codec"
version = "0.6.0" version = "0.6.0"
@ -7435,6 +7445,7 @@ name = "reth-net-nat"
version = "1.0.6" version = "1.0.6"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"if-addrs",
"reqwest", "reqwest",
"reth-tracing", "reth-tracing",
"serde_with", "serde_with",

View File

@ -536,6 +536,7 @@ tower-http = "0.5"
# p2p # p2p
discv5 = "0.7.0" discv5 = "0.7.0"
if-addrs = "0.13"
# rpc # rpc
jsonrpsee = "0.24" jsonrpsee = "0.24"

View File

@ -228,6 +228,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
--to <TO> --to <TO>
The maximum block height The maximum block height

View File

@ -228,6 +228,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
--retries <RETRIES> --retries <RETRIES>
The number of retries per request The number of retries per request

View File

@ -228,6 +228,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
--retries <RETRIES> --retries <RETRIES>
The number of retries per request The number of retries per request

View File

@ -228,6 +228,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
--engine-api-store <PATH> --engine-api-store <PATH>
The path to read engine API messages from The path to read engine API messages from

View File

@ -220,6 +220,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
RPC: RPC:
--http --http
Enable the HTTP-RPC server Enable the HTTP-RPC server

View File

@ -205,6 +205,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
Datadir: Datadir:
--datadir <DATA_DIR> --datadir <DATA_DIR>
The path to the data dir for all reth files and subdirectories. The path to the data dir for all reth files and subdirectories.

View File

@ -271,6 +271,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
Logging: Logging:
--log.stdout.format <FORMAT> --log.stdout.format <FORMAT>
The format to use for logs written to stdout The format to use for logs written to stdout

View File

@ -233,6 +233,11 @@ Networking:
[default: 25600] [default: 25600]
--net-if.experimental <IF_NAME>
Name of network interface used to communicate with peers.
If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
--offline --offline
If this is enabled, then all stages except headers, bodies, and sender recovery will be unwound If this is enabled, then all stages except headers, bodies, and sender recovery will be unwound

View File

@ -17,6 +17,7 @@ reqwest.workspace = true
serde_with = { workspace = true, optional = true } serde_with = { workspace = true, optional = true }
thiserror.workspace = true thiserror.workspace = true
tokio = { workspace = true, features = ["time"] } tokio = { workspace = true, features = ["time"] }
if-addrs.workspace = true
[dev-dependencies] [dev-dependencies]
reth-tracing.workspace = true reth-tracing.workspace = true

View File

@ -12,6 +12,10 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod net_if;
pub use net_if::{NetInterfaceError, DEFAULT_NET_IF_NAME};
use std::{ use std::{
fmt, fmt,
future::{poll_fn, Future}, future::{poll_fn, Future},

View File

@ -0,0 +1,55 @@
//! IP resolution on non-host Docker network.
#![cfg(not(target_os = "windows"))]
use std::{io, net::IpAddr};
/// The 'eth0' interface tends to be the default interface that docker containers use to
/// communicate with each other.
pub const DEFAULT_NET_IF_NAME: &str = "eth0";
/// Errors resolving network interface IP.
#[derive(Debug, thiserror::Error)]
pub enum NetInterfaceError {
/// Error reading OS interfaces.
#[error("failed to read OS interfaces: {0}")]
Io(io::Error),
/// No interface found with given name.
#[error("interface not found: {0}, found other interfaces: {1:?}")]
IFNotFound(String, Vec<String>),
}
/// Reads IP of OS interface with given name, if exists.
#[cfg(not(target_os = "windows"))]
pub fn resolve_net_if_ip(if_name: &str) -> Result<IpAddr, NetInterfaceError> {
match if_addrs::get_if_addrs() {
Ok(ifs) => {
let ip = ifs.iter().find(|i| i.name == if_name).map(|i| i.ip());
match ip {
Some(ip) => Ok(ip),
None => {
let ifs = ifs.into_iter().map(|i| i.name.as_str().into()).collect();
Err(NetInterfaceError::IFNotFound(if_name.into(), ifs))
}
}
}
Err(err) => Err(NetInterfaceError::Io(err)),
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
#[test]
fn read_docker_if_addr() {
const LOCALHOST_IF: [&str; 2] = ["lo0", "lo"];
let ip = resolve_net_if_ip(LOCALHOST_IF[0])
.unwrap_or_else(|_| resolve_net_if_ip(LOCALHOST_IF[1]).unwrap());
assert_eq!(ip, Ipv4Addr::LOCALHOST);
}
}

View File

@ -15,7 +15,7 @@ use reth_discv5::{
discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT, discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL, DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
}; };
use reth_net_nat::NatResolver; use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
use reth_network::{ use reth_network::{
transactions::{ transactions::{
constants::{ constants::{
@ -35,6 +35,7 @@ use reth_network::{
}; };
use reth_network_peers::{mainnet_nodes, TrustedPeer}; use reth_network_peers::{mainnet_nodes, TrustedPeer};
use secp256k1::SecretKey; use secp256k1::SecretKey;
use tracing::error;
use crate::version::P2P_CLIENT_VERSION; use crate::version::P2P_CLIENT_VERSION;
@ -148,9 +149,38 @@ pub struct NetworkArgs {
/// Max capacity of cache of hashes for transactions pending fetch. /// Max capacity of cache of hashes for transactions pending fetch.
#[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)] #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
pub max_capacity_cache_txns_pending_fetch: u32, pub max_capacity_cache_txns_pending_fetch: u32,
/// Name of network interface used to communicate with peers.
///
/// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
#[cfg(not(target_os = "windows"))]
#[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
pub net_if: Option<String>,
} }
impl NetworkArgs { impl NetworkArgs {
/// Returns the resolved IP address.
pub fn resolved_addr(&self) -> IpAddr {
#[cfg(not(target_os = "windows"))]
if let Some(ref if_name) = self.net_if {
let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
Ok(addr) => addr,
Err(err) => {
error!(target: "reth::cli",
if_name,
%err,
"Failed to read network interface IP"
);
DEFAULT_DISCOVERY_ADDR
}
}
}
self.addr
}
/// Returns the resolved bootnodes if any are provided. /// Returns the resolved bootnodes if any are provided.
pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> { pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
self.bootnodes.clone().map(|bootnodes| { self.bootnodes.clone().map(|bootnodes| {
@ -176,6 +206,7 @@ impl NetworkArgs {
secret_key: SecretKey, secret_key: SecretKey,
default_peers_file: PathBuf, default_peers_file: PathBuf,
) -> NetworkConfigBuilder { ) -> NetworkConfigBuilder {
let addr = self.resolved_addr();
let chain_bootnodes = self let chain_bootnodes = self
.resolved_bootnodes() .resolved_bootnodes()
.unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes)); .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
@ -224,11 +255,11 @@ impl NetworkArgs {
}) })
// apply discovery settings // apply discovery settings
.apply(|builder| { .apply(|builder| {
let rlpx_socket = (self.addr, self.port).into(); let rlpx_socket = (addr, self.port).into();
self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes) self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
}) })
.listener_addr(SocketAddr::new( .listener_addr(SocketAddr::new(
self.addr, // set discovery port based on instance number addr, // set discovery port based on instance number
self.port, self.port,
)) ))
.discovery_addr(SocketAddr::new( .discovery_addr(SocketAddr::new(
@ -303,6 +334,7 @@ impl Default for NetworkArgs {
max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
net_if: None,
} }
} }
} }