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",
]
[[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]]
name = "impl-codec"
version = "0.6.0"
@ -7435,6 +7445,7 @@ name = "reth-net-nat"
version = "1.0.6"
dependencies = [
"futures-util",
"if-addrs",
"reqwest",
"reth-tracing",
"serde_with",

View File

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

View File

@ -228,6 +228,11 @@ Networking:
[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>
The maximum block height

View File

@ -228,6 +228,11 @@ Networking:
[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>
The number of retries per request

View File

@ -228,6 +228,11 @@ Networking:
[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>
The number of retries per request

View File

@ -228,6 +228,11 @@ Networking:
[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>
The path to read engine API messages from

View File

@ -220,6 +220,11 @@ Networking:
[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:
--http
Enable the HTTP-RPC server

View File

@ -205,6 +205,11 @@ Networking:
[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 <DATA_DIR>
The path to the data dir for all reth files and subdirectories.

View File

@ -271,6 +271,11 @@ Networking:
[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:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout

View File

@ -233,6 +233,11 @@ Networking:
[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
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 }
thiserror.workspace = true
tokio = { workspace = true, features = ["time"] }
if-addrs.workspace = true
[dev-dependencies]
reth-tracing.workspace = true

View File

@ -12,6 +12,10 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod net_if;
pub use net_if::{NetInterfaceError, DEFAULT_NET_IF_NAME};
use std::{
fmt,
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,
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::{
transactions::{
constants::{
@ -35,6 +35,7 @@ use reth_network::{
};
use reth_network_peers::{mainnet_nodes, TrustedPeer};
use secp256k1::SecretKey;
use tracing::error;
use crate::version::P2P_CLIENT_VERSION;
@ -148,9 +149,38 @@ pub struct NetworkArgs {
/// 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)]
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 {
/// 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.
pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
self.bootnodes.clone().map(|bootnodes| {
@ -176,6 +206,7 @@ impl NetworkArgs {
secret_key: SecretKey,
default_peers_file: PathBuf,
) -> NetworkConfigBuilder {
let addr = self.resolved_addr();
let chain_bootnodes = self
.resolved_bootnodes()
.unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
@ -224,11 +255,11 @@ impl NetworkArgs {
})
// apply discovery settings
.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)
})
.listener_addr(SocketAddr::new(
self.addr, // set discovery port based on instance number
addr, // set discovery port based on instance number
self.port,
))
.discovery_addr(SocketAddr::new(
@ -303,6 +334,7 @@ impl Default for NetworkArgs {
max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
net_if: None,
}
}
}