diff --git a/Cargo.lock b/Cargo.lock index 8e6b78cae..3f7cdd7fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1056,11 +1056,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ipnetwork" -version = "0.12.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "itertools" version = "0.7.11" @@ -1633,33 +1628,6 @@ name = "pkg-config" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "pnet_base" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pnet_datalink" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ipnetwork 0.12.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", - "pnet_base 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pnet_sys 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pnet_sys" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "predicates" version = "1.0.0" @@ -2587,14 +2555,14 @@ dependencies = [ name = "solana-netutil" version = "0.15.0" dependencies = [ - "ipnetwork 0.12.8 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pnet_datalink 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.9.16 (registry+https://github.com/rust-lang/crates.io-index)", "socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.15.0", + "tokio 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3532,7 +3500,6 @@ dependencies = [ "checksum indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c60da1c9abea75996b70a931bba6c750730399005b61ccd853cee50ef3d0d0c" "checksum influx_db_client 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1af8df5705f0b30bcb504bafc9396d995a555c4d6bd6f9097729ad47b8a49a38" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum ipnetwork 0.12.8 (registry+https://github.com/rust-lang/crates.io-index)" = "70783119ac90828aaba91eae39db32c6c1b8838deea3637e5238efa0130801ab" "checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" @@ -3596,9 +3563,6 @@ dependencies = [ "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" -"checksum pnet_base 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "948dbdd36f46888ada1d497703e6cae53d227ab0e8871638aba492ad1e4a76dc" -"checksum pnet_datalink 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d3b3dd76a11ad99d92fef54b2489f76f4045ebd5251bd1af485d55a7e13db6" -"checksum pnet_sys 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "963b9109a05c3ac370abc3fda61bff20d03743c2947942173871b9cac2b9acb0" "checksum predicates 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa984b7cd021a0bf5315bcce4c4ae61d2a535db2a8d288fc7578638690a7b7c3" "checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" "checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" diff --git a/book/src/testnet-participation.md b/book/src/testnet-participation.md index 34ab539bc..07f37a5d3 100644 --- a/book/src/testnet-participation.md +++ b/book/src/testnet-participation.md @@ -119,20 +119,20 @@ Then the following command will start a new validator node. If this is a `solana-install`-installation: ```bash $ clear-fullnode-config.sh -$ fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com +$ fullnode.sh --poll-for-new-genesis-block testnet.solana.com ``` Alternatively, the `solana-install run` command can be used to run the validator node while periodically checking for and applying software updates: ```bash $ clear-fullnode-config.sh -$ solana-install run fullnode.sh -- --public-address --poll-for-new-genesis-block testnet.solana.com +$ solana-install run fullnode.sh -- --poll-for-new-genesis-block testnet.solana.com ``` If you built from source: ```bash $ USE_INSTALL=1 ./multinode-demo/clear-fullnode-config.sh -$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com +$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --poll-for-new-genesis-block testnet.solana.com ``` #### Controlling local network port allocation diff --git a/ci/nits.sh b/ci/nits.sh index 0f502088c..8dd39340c 100755 --- a/ci/nits.sh +++ b/ci/nits.sh @@ -18,14 +18,14 @@ declare prints=( # Parts of the tree that are expected to be print free declare print_free_tree=( 'core/src' - 'drone' - 'metrics' - 'netutil' - 'runtime' - 'sdk' + 'drone/src' + 'metrics/src' + 'netutil/src' + 'runtime/src' + 'sdk/src' ) -if _ git grep "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then +if _ git grep --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then exit 1 fi diff --git a/core/src/contact_info.rs b/core/src/contact_info.rs index e56570b6a..c11fa6e43 100644 --- a/core/src/contact_info.rs +++ b/core/src/contact_info.rs @@ -7,7 +7,7 @@ use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Signable, Signature}; use solana_sdk::timing::timestamp; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::{IpAddr, SocketAddr}; /// Structure representing a node on the network #[derive(Serialize, Deserialize, Clone, Debug)] @@ -56,7 +56,7 @@ impl Eq for ContactInfo {} #[macro_export] macro_rules! socketaddr { ($ip:expr, $port:expr) => { - std::net::SocketAddr::from((Ipv4Addr::from($ip), $port)) + std::net::SocketAddr::from((std::net::Ipv4Addr::from($ip), $port)) }; ($str:expr) => {{ let a: std::net::SocketAddr = $str.parse().unwrap(); diff --git a/core/src/fullnode.rs b/core/src/fullnode.rs index 38aec6d26..ecfee3e3b 100644 --- a/core/src/fullnode.rs +++ b/core/src/fullnode.rs @@ -74,6 +74,7 @@ pub struct Fullnode { poh_service: PohService, tpu: Tpu, tvu: Tvu, + ip_echo_server: solana_netutil::IpEchoServer, } impl Fullnode { @@ -159,6 +160,9 @@ impl Fullnode { )) }; + let ip_echo_server = + solana_netutil::ip_echo_server(node.sockets.gossip.local_addr().unwrap().port()); + let subscriptions = Arc::new(RpcSubscriptions::default()); let rpc_pubsub_service = if node.info.rpc_pubsub.port() == 0 { None @@ -270,6 +274,7 @@ impl Fullnode { exit, poh_service, poh_recorder, + ip_echo_server, } } @@ -329,6 +334,7 @@ impl Service for Fullnode { self.gossip_service.join()?; self.tpu.join()?; self.tvu.join()?; + self.ip_echo_server.shutdown_now(); Ok(()) } diff --git a/core/src/packet.rs b/core/src/packet.rs index 73da0c9c8..ec78eee76 100644 --- a/core/src/packet.rs +++ b/core/src/packet.rs @@ -618,7 +618,7 @@ mod tests { use solana_sdk::system_transaction; use std::io; use std::io::Write; - use std::net::{Ipv4Addr, SocketAddr, UdpSocket}; + use std::net::{SocketAddr, UdpSocket}; #[test] fn test_packets_set_addr() { diff --git a/fullnode/src/main.rs b/fullnode/src/main.rs index 31d5ea4b1..afdc5b82f 100644 --- a/fullnode/src/main.rs +++ b/fullnode/src/main.rs @@ -5,6 +5,7 @@ use solana::contact_info::ContactInfo; use solana::fullnode::{Fullnode, FullnodeConfig}; use solana::local_vote_signer_service::LocalVoteSignerService; use solana::service::Service; +use solana::socketaddr; use solana_netutil::parse_port_range; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use std::fs::File; @@ -131,16 +132,10 @@ fn main() { .takes_value(true) .help("Comma separated persistent accounts location"), ) - .arg( - clap::Arg::with_name("public_address") - .long("public-address") - .takes_value(false) - .help("Advertise public machine address in gossip. By default the local machine address is advertised"), - ) .arg( clap::Arg::with_name("gossip_port") .long("gossip-port") - .value_name("PORT") + .value_name("HOST:PORT") .takes_value(true) .help("Gossip port number for the node"), ) @@ -195,19 +190,14 @@ fn main() { let dynamic_port_range = parse_port_range(matches.value_of("dynamic_port_range").unwrap()) .expect("invalid dynamic_port_range"); - let gossip_addr = { - let mut addr = solana_netutil::parse_port_or_addr( - matches.value_of("gossip_port"), + let mut gossip_addr = solana_netutil::parse_port_or_addr( + matches.value_of("gossip_port"), + socketaddr!( + [127, 0, 0, 1], solana_netutil::find_available_port_in_range(dynamic_port_range) - .expect("unable to allocate gossip_port"), - ); - if matches.is_present("public_address") { - addr.set_ip(solana_netutil::get_public_ip_addr().unwrap()); - } else { - addr.set_ip(solana_netutil::get_ip_addr(false).unwrap()); - } - addr - }; + .expect("unable to find an available gossip port") + ), + ); if let Some(paths) = matches.value_of("accounts") { fullnode_config.account_paths = Some(paths.to_string()); @@ -215,9 +205,11 @@ fn main() { fullnode_config.account_paths = None; } let cluster_entrypoint = matches.value_of("network").map(|network| { - let gossip_addr = + let entrypoint_addr = solana_netutil::parse_host_port(network).expect("failed to parse network address"); - ContactInfo::new_gossip_entry_point(&gossip_addr) + gossip_addr.set_ip(solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap()); + + ContactInfo::new_gossip_entry_point(&entrypoint_addr) }); let (_signer_service, _signer_addr) = if let Some(signer_addr) = matches.value_of("signer") { ( diff --git a/gossip/src/main.rs b/gossip/src/main.rs index 0ae042322..9bdb73ebb 100644 --- a/gossip/src/main.rs +++ b/gossip/src/main.rs @@ -9,7 +9,6 @@ use solana::gossip_service::discover; use solana_client::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; use std::error; -use std::net::Ipv4Addr; use std::net::SocketAddr; use std::process::exit; @@ -42,12 +41,6 @@ fn main() -> Result<(), Box> { SubCommand::with_name("spy") .about("Monitor the gossip network") .setting(AppSettings::DisableVersion) - .arg( - clap::Arg::with_name("public_address") - .long("public-address") - .takes_value(false) - .help("Advertise public machine address in gossip. By default the local machine address is advertised"), - ) .arg( clap::Arg::with_name("pull_only") .long("pull-only") @@ -131,11 +124,12 @@ fn main() -> Result<(), Box> { None } else { let mut addr = socketaddr_any!(); - if matches.is_present("public_address") { - addr.set_ip(solana_netutil::get_public_ip_addr().unwrap()); - } else { - addr.set_ip(solana_netutil::get_ip_addr(false).unwrap()); - } + addr.set_ip( + solana_netutil::get_public_ip_addr(&network_addr).unwrap_or_else(|err| { + eprintln!("failed to contact {}: {}", network_addr, err); + exit(1) + }), + ); Some(addr) }; diff --git a/multinode-demo/bootstrap-leader.sh b/multinode-demo/bootstrap-leader.sh index 070513cd3..541bec49d 100755 --- a/multinode-demo/bootstrap-leader.sh +++ b/multinode-demo/bootstrap-leader.sh @@ -44,17 +44,18 @@ bootstrap_leader_vote_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json bootstrap_leader_vote_id=$($solana_keygen pubkey "$bootstrap_leader_vote_id_path") trap 'kill "$pid" && wait "$pid"' INT TERM ERR -$program \ - --identity "$bootstrap_leader_id_path" \ - --voting-keypair "$bootstrap_leader_vote_id_path" \ - --vote-account "$bootstrap_leader_vote_id" \ - --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \ - --accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \ - --gossip-port 8001 \ - --rpc-port 8899 \ - --rpc-drone-address 127.0.0.1:9900 \ - "${extra_fullnode_args[@]}" \ - > >($bootstrap_leader_logger) 2>&1 & + +default_fullnode_arg --identity "$bootstrap_leader_id_path" +default_fullnode_arg --voting-keypair "$bootstrap_leader_vote_id_path" +default_fullnode_arg --vote-account "$bootstrap_leader_vote_id" +default_fullnode_arg --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger +default_fullnode_arg --accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts +default_fullnode_arg --rpc-port 8899 +default_fullnode_arg --rpc-drone-address 127.0.0.1:9900 +default_fullnode_arg --gossip-port 8001 + +echo "$PS4 $program ${extra_fullnode_args[*]}" +$program "${extra_fullnode_args[@]}" > >($bootstrap_leader_logger) 2>&1 & pid=$! oom_score_adj "$pid" 1000 diff --git a/multinode-demo/common.sh b/multinode-demo/common.sh index 6bd2d05e4..b0b206780 100644 --- a/multinode-demo/common.sh +++ b/multinode-demo/common.sh @@ -82,7 +82,6 @@ Start a full node --label LABEL - Append the given label to the fullnode configuration files, useful when running multiple fullnodes from the same filesystem location --stake LAMPORTS - Number of lamports to stake - --public-address - advertise public machine address in gossip. By default the local machine address is advertised --no-voting - start node without vote signer --rpc-port port - custom RPC port for this node diff --git a/multinode-demo/extra-fullnode-args.sh b/multinode-demo/extra-fullnode-args.sh index 91c87fd23..549da4712 100644 --- a/multinode-demo/extra-fullnode-args.sh +++ b/multinode-demo/extra-fullnode-args.sh @@ -34,9 +34,6 @@ while [[ ${1:0:1} = - ]]; do elif [[ $1 = --init-complete-file ]]; then extra_fullnode_args+=("$1" "$2") shift 2 - elif [[ $1 = --public-address ]]; then - extra_fullnode_args+=("$1") - shift elif [[ $1 = --stake ]]; then stake="$2" shift 2 @@ -64,3 +61,20 @@ done if [[ -n $3 ]]; then fullnode_usage "$@" fi + +default_fullnode_arg() { + declare name=$1 + declare value=$2 + + for arg in "${extra_fullnode_args[@]}"; do + if [[ $arg = "$name" ]]; then + return + fi + done + + if [[ -n $value ]]; then + extra_fullnode_args+=("$name" "$value") + else + extra_fullnode_args+=("$name") + fi +} diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index cff28629d..d42aab932 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -164,23 +164,21 @@ while true; do if ((stake)); then setup_vote_account "${leader_address%:*}" "$fullnode_id_path" "$fullnode_vote_id_path" "$stake" fi + set +x - $program \ - --identity "$fullnode_id_path" \ - --voting-keypair "$fullnode_vote_id_path" \ - --vote-account "$fullnode_vote_id" \ - --network "$leader_address" \ - --ledger "$ledger_config_dir" \ - --accounts "$accounts_config_dir" \ - --rpc-drone-address "${leader_address%:*}:9900" \ - "${extra_fullnode_args[@]}" \ - > >($fullnode_logger) 2>&1 & + default_fullnode_arg --identity "$fullnode_id_path" + default_fullnode_arg --voting-keypair "$fullnode_vote_id_path" + default_fullnode_arg --vote-account "$fullnode_vote_id" + default_fullnode_arg --network "$leader_address" + default_fullnode_arg --ledger "$ledger_config_dir" + default_fullnode_arg --accounts "$accounts_config_dir" + default_fullnode_arg --rpc-drone-address "${leader_address%:*}:9900" + echo "$PS4 $program ${extra_fullnode_args[*]}" + $program "${extra_fullnode_args[@]}" > >($fullnode_logger) 2>&1 & pid=$! oom_score_adj "$pid" 1000 - set +x while true; do - if ! kill -0 "$pid"; then wait "$pid" exit 0 diff --git a/net/gce.sh b/net/gce.sh index 6158eddaf..0da3334e9 100755 --- a/net/gce.sh +++ b/net/gce.sh @@ -350,7 +350,7 @@ EOF echo "Waiting for $name to finish booting..." ( set -x +e - for i in $(seq 1 30); do + for i in $(seq 1 60); do timeout --preserve-status --foreground 20s ssh "${sshOptions[@]}" "$publicIp" "ls -l /.instance-startup-complete" ret=$? if [[ $ret -eq 0 ]]; then diff --git a/net/net.sh b/net/net.sh index 6aedf0704..f1d48f3cd 100755 --- a/net/net.sh +++ b/net/net.sh @@ -290,7 +290,6 @@ startBootstrapLeader() { "./solana/net/remote/remote-node.sh \ $deployMethod \ bootstrap-leader \ - $publicNetwork \ $entrypointIp \ $((${#fullnodeIpList[@]} + ${#blockstreamerIpList[@]})) \ \"$RUST_LOG\" \ @@ -319,7 +318,6 @@ startNode() { "./solana/net/remote/remote-node.sh \ $deployMethod \ $nodeType \ - $publicNetwork \ $entrypointIp \ $((${#fullnodeIpList[@]} + ${#blockstreamerIpList[@]})) \ \"$RUST_LOG\" \ diff --git a/net/remote/remote-node.sh b/net/remote/remote-node.sh index 2732ad128..8fc27edd5 100755 --- a/net/remote/remote-node.sh +++ b/net/remote/remote-node.sh @@ -6,13 +6,12 @@ cd "$(dirname "$0")"/../.. set -x deployMethod="$1" nodeType="$2" -publicNetwork="$3" -entrypointIp="$4" -numNodes="$5" -RUST_LOG="$6" -skipSetup="$7" -leaderRotation="$8" -failOnValidatorBootupFailure="$9" +entrypointIp="$3" +numNodes="$4" +RUST_LOG="$5" +skipSetup="$6" +leaderRotation="$7" +failOnValidatorBootupFailure="$8" set +x export RUST_LOG @@ -31,7 +30,6 @@ missing() { [[ -n $deployMethod ]] || missing deployMethod [[ -n $nodeType ]] || missing nodeType -[[ -n $publicNetwork ]] || missing publicNetwork [[ -n $entrypointIp ]] || missing entrypointIp [[ -n $numNodes ]] || missing numNodes [[ -n $skipSetup ]] || missing skipSetup @@ -84,11 +82,10 @@ local|tar) fi ./multinode-demo/drone.sh > drone.log 2>&1 & - args=() - if $publicNetwork; then - args+=(--public-address) - fi - args+=(--enable-rpc-exit) + args=( + --enable-rpc-exit + --gossip-port "$entrypointIp":8001 + ) ./multinode-demo/bootstrap-leader.sh "${args[@]}" > bootstrap-leader.log 2>&1 & ln -sTf bootstrap-leader.log fullnode.log @@ -102,9 +99,6 @@ local|tar) fi args=() - if $publicNetwork; then - args+=("--public-address") - fi if [[ $nodeType = blockstreamer ]]; then args+=( --blockstream /tmp/solana-blockstream.sock diff --git a/netutil/Cargo.toml b/netutil/Cargo.toml index 3127fb1cf..9261ecf73 100644 --- a/netutil/Cargo.toml +++ b/netutil/Cargo.toml @@ -9,16 +9,22 @@ homepage = "https://solana.com/" edition = "2018" [dependencies] +bincode = "1.1.3" +clap = "2.33.0" log = "0.4.2" -ipnetwork = "0.12.7" nix = "0.12.0" -pnet_datalink = "0.21.0" rand = "0.6.1" -reqwest = "0.9.0" socket2 = "0.3.8" - -[dev-dependencies] solana-logger = { path = "../logger", version = "0.15.0" } +tokio = "0.1" [lib] name = "solana_netutil" + +[[bin]] +name = "solana-ip-address" +path = "src/bin/ip_address.rs" + +[[bin]] +name = "solana-ip-address-server" +path = "src/bin/ip_address_server.rs" diff --git a/netutil/src/bin/ip_address.rs b/netutil/src/bin/ip_address.rs new file mode 100644 index 000000000..29e824066 --- /dev/null +++ b/netutil/src/bin/ip_address.rs @@ -0,0 +1,26 @@ +use clap::{crate_version, App, Arg}; + +fn main() { + solana_logger::setup(); + let matches = App::new("solana-ip-address") + .version(crate_version!()) + .arg( + Arg::with_name("host_port") + .index(1) + .required(true) + .help("Host:port to connect to"), + ) + .get_matches(); + + let host_port = matches.value_of("host_port").unwrap(); + let addr = solana_netutil::parse_host_port(host_port) + .unwrap_or_else(|_| panic!("failed to parse {}", host_port)); + + match solana_netutil::get_public_ip_addr(&addr) { + Ok(ip) => println!("{}", ip), + Err(err) => { + eprintln!("{}: {}", addr, err); + std::process::exit(1) + } + } +} diff --git a/netutil/src/bin/ip_address_server.rs b/netutil/src/bin/ip_address_server.rs new file mode 100644 index 000000000..105d0733d --- /dev/null +++ b/netutil/src/bin/ip_address_server.rs @@ -0,0 +1,23 @@ +use clap::{crate_version, App, Arg}; + +fn main() { + solana_logger::setup(); + let matches = App::new("solana-ip-address-server") + .version(crate_version!()) + .arg( + Arg::with_name("port") + .index(1) + .required(true) + .help("TCP port to bind to"), + ) + .get_matches(); + + let port = matches.value_of("port").unwrap(); + let port = port + .parse() + .unwrap_or_else(|_| panic!("Unable to parse {}", port)); + let _runtime = solana_netutil::ip_echo_server(port); + loop { + std::thread::park(); + } +} diff --git a/netutil/src/ip_echo_server.rs b/netutil/src/ip_echo_server.rs new file mode 100644 index 000000000..a608c67ab --- /dev/null +++ b/netutil/src/ip_echo_server.rs @@ -0,0 +1,44 @@ +use log::*; +use std::net::SocketAddr; +use tokio; +use tokio::net::TcpListener; +use tokio::prelude::{Future, Stream}; +use tokio::runtime::Runtime; + +pub type IpEchoServer = Runtime; + +/// Starts a simple TCP server on the given port that echos the IP address of any peer that +/// connects. Used by |get_public_ip_addr| +pub fn ip_echo_server(port: u16) -> IpEchoServer { + let bind_addr = SocketAddr::from(([0, 0, 0, 0], port)); + let tcp = + TcpListener::bind(&bind_addr).unwrap_or_else(|_| panic!("Unable to bind to {}", bind_addr)); + info!("bound to {:?}", bind_addr); + + let server = tcp + .incoming() + .map_err(|err| warn!("accept failed: {:?}", err)) + .for_each(move |socket| { + let ip = socket + .peer_addr() + .and_then(|peer_addr| { + bincode::serialize(&peer_addr.ip()).map_err(|err| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to serialize: {:?}", err), + ) + }) + }) + .unwrap_or_else(|_| vec![]); + + let write_future = tokio::io::write_all(socket, ip) + .map_err(|err| warn!("write error: {:?}", err)) + .map(|_| ()); + + tokio::spawn(write_future) + }); + + let mut rt = Runtime::new().expect("Failed to create Runtime"); + rt.spawn(server); + rt +} diff --git a/netutil/src/lib.rs b/netutil/src/lib.rs index c04149e08..797f66f75 100644 --- a/netutil/src/lib.rs +++ b/netutil/src/lib.rs @@ -1,14 +1,16 @@ //! The `netutil` module assists with networking -use log::*; use nix::sys::socket::setsockopt; use nix::sys::socket::sockopt::{ReuseAddr, ReusePort}; -use pnet_datalink as datalink; use rand::{thread_rng, Rng}; -use reqwest; use socket2::{Domain, SockAddr, Socket, Type}; use std::io; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket}; +use std::io::Read; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket}; use std::os::unix::io::AsRawFd; +use std::time::Duration; + +mod ip_echo_server; +pub use ip_echo_server::*; /// A data type representing a public Udp socket pub struct UdpSocketPair { @@ -19,34 +21,43 @@ pub struct UdpSocketPair { pub type PortRange = (u16, u16); -/// Tries to determine the public IP address of this machine -pub fn get_public_ip_addr() -> Result { - let body = reqwest::get("http://ifconfig.co/ip") - .map_err(|err| err.to_string())? - .text() - .map_err(|err| err.to_string())?; +/// Determine the public IP address of this machine by asking an ip_echo_server at the given +/// address +pub fn get_public_ip_addr(ip_echo_server_addr: &SocketAddr) -> Result { + let mut data = Vec::new(); - match body.lines().next() { - Some(ip) => Result::Ok(ip.parse().unwrap()), - None => Result::Err("Empty response body".to_string()), - } + let timeout = Duration::new(5, 0); + TcpStream::connect_timeout(ip_echo_server_addr, timeout) + .and_then(|mut stream| { + stream + .set_read_timeout(Some(Duration::new(10, 0))) + .expect("set_read_timeout"); + stream.read_to_end(&mut data) + }) + .and_then(|_| { + bincode::deserialize(&data).map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to deserialize: {:?}", err), + ) + }) + }) + .map_err(|err| err.to_string()) } -pub fn parse_port_or_addr(optstr: Option<&str>, default_port: u16) -> SocketAddr { - let daddr = SocketAddr::from(([0, 0, 0, 0], default_port)); - +pub fn parse_port_or_addr(optstr: Option<&str>, default_addr: SocketAddr) -> SocketAddr { if let Some(addrstr) = optstr { if let Ok(port) = addrstr.parse() { - let mut addr = daddr; + let mut addr = default_addr; addr.set_port(port); addr } else if let Ok(addr) = addrstr.parse() { addr } else { - daddr + default_addr } } else { - daddr + default_addr } } @@ -95,67 +106,6 @@ pub fn parse_host_port(host_port: &str) -> Result { } } -fn find_eth0ish_ip_addr( - ifaces: &mut Vec, - enable_ipv6: bool, -) -> Option { - // put eth0 and wifi0, etc. up front of our list of candidates - ifaces.sort_by(|a, b| { - a.name - .chars() - .last() - .unwrap() - .cmp(&b.name.chars().last().unwrap()) - }); - - let mut lo = None; - for iface in ifaces.clone() { - trace!("get_ip_addr considering iface {}", iface.name); - for p in iface.ips { - trace!(" ip {}", p); - if p.ip().is_multicast() { - trace!(" multicast"); - continue; - } - match p.ip() { - IpAddr::V4(addr) => { - if addr.is_link_local() { - trace!(" link local"); - continue; - } - if p.ip().is_loopback() { - // Fall back to loopback if no better option exists - // (local development and test) - trace!(" loopback"); - lo = Some(p.ip()); - continue; - } - trace!(" picked {}", p.ip()); - return Some(p.ip()); - } - IpAddr::V6(_addr) => { - // Select an ipv6 address if requested - if enable_ipv6 { - if p.ip().is_loopback() { - trace!(" loopback"); - lo = Some(p.ip()); - continue; - } - return Some(p.ip()); - } - } - } - } - } - trace!(" picked {:?}", lo); - lo -} - -pub fn get_ip_addr(enable_ipv6: bool) -> Option { - let mut ifaces = datalink::interfaces(); - find_eth0ish_ip_addr(&mut ifaces, enable_ipv6) -} - fn udp_socket(reuseaddr: bool) -> io::Result { let sock = Socket::new(Domain::ipv4(), Type::dgram(), None)?; let sock_fd = sock.as_raw_fd(); @@ -252,101 +202,16 @@ pub fn find_available_port_in_range(range: PortRange) -> io::Result { #[cfg(test)] mod tests { use super::*; - use ipnetwork::IpNetwork; - - #[test] - fn test_find_eth0ish_ip_addr() { - solana_logger::setup(); - - macro_rules! mock_interface { - ($name:ident, $ip_mask:expr) => { - datalink::NetworkInterface { - name: stringify!($name).to_string(), - index: 0, - mac: None, - ips: vec![IpNetwork::V4($ip_mask.parse().unwrap())], - flags: 0, - } - }; - } - - // loopback bad when alternatives exist - assert_eq!( - find_eth0ish_ip_addr( - &mut vec![ - mock_interface!(eth0, "192.168.137.1/8"), - mock_interface!(lo, "127.0.0.1/24") - ], - false - ), - Some(mock_interface!(eth0, "192.168.137.1/8").ips[0].ip()) - ); - // find loopback as a last resort - assert_eq!( - find_eth0ish_ip_addr(&mut vec![mock_interface!(lo, "127.0.0.1/24")], false), - Some(mock_interface!(lo, "127.0.0.1/24").ips[0].ip()), - ); - // multicast bad - assert_eq!( - find_eth0ish_ip_addr(&mut vec![mock_interface!(eth0, "224.0.1.5/24")], false), - None - ); - - // finds "wifi0" - assert_eq!( - find_eth0ish_ip_addr( - &mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - mock_interface!(wifi0, "192.168.1.184/8"), - ], - false - ), - Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) - ); - // finds "wifi0" in the middle - assert_eq!( - find_eth0ish_ip_addr( - &mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(wifi0, "192.168.1.184/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - ], - false - ), - Some(mock_interface!(wifi0, "192.168.1.184/8").ips[0].ip()) - ); - // picks "eth2", is lowest valid "eth" - assert_eq!( - find_eth0ish_ip_addr( - &mut vec![ - mock_interface!(eth0, "224.0.1.5/24"), - mock_interface!(eth2, "192.168.137.1/8"), - mock_interface!(eth3, "10.0.75.1/8"), - mock_interface!(eth4, "172.22.140.113/4"), - mock_interface!(lo, "127.0.0.1/24"), - ], - false - ), - Some(mock_interface!(eth2, "192.168.137.1/8").ips[0].ip()) - ); - } #[test] fn test_parse_port_or_addr() { - let p1 = parse_port_or_addr(Some("9000"), 1); + let p1 = parse_port_or_addr(Some("9000"), SocketAddr::from(([1, 2, 3, 4], 1))); assert_eq!(p1.port(), 9000); - let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), 1); + let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), SocketAddr::from(([1, 2, 3, 4], 1))); assert_eq!(p2.port(), 7000); - let p2 = parse_port_or_addr(Some("hi there"), 1); + let p2 = parse_port_or_addr(Some("hi there"), SocketAddr::from(([1, 2, 3, 4], 1))); assert_eq!(p2.port(), 1); - let p3 = parse_port_or_addr(None, 1); + let p3 = parse_port_or_addr(None, SocketAddr::from(([1, 2, 3, 4], 1))); assert_eq!(p3.port(), 1); } diff --git a/replicator/src/main.rs b/replicator/src/main.rs index 3073ae116..9af7974e8 100644 --- a/replicator/src/main.rs +++ b/replicator/src/main.rs @@ -4,14 +4,14 @@ use solana::contact_info::ContactInfo; use solana::replicator::Replicator; use solana::socketaddr; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; -use std::net::Ipv4Addr; use std::process::exit; use std::sync::Arc; fn main() { solana_logger::setup(); - let matches = App::new(crate_name!()).about(crate_description!()) + let matches = App::new(crate_name!()) + .about(crate_description!()) .version(crate_version!()) .arg( Arg::with_name("identity") @@ -39,12 +39,6 @@ fn main() { .required(true) .help("use DIR as persistent ledger location"), ) - .arg( - clap::Arg::with_name("public_address") - .long("public-address") - .takes_value(false) - .help("Advertise public machine address in gossip. By default the local machine address is advertised"), - ) .get_matches(); let ledger_path = matches.value_of("ledger").unwrap(); @@ -58,13 +52,16 @@ fn main() { Keypair::new() }; + let network_addr = matches + .value_of("network") + .map(|network| { + solana_netutil::parse_host_port(network).expect("failed to parse network address") + }) + .unwrap(); + let gossip_addr = { let mut addr = socketaddr!([127, 0, 0, 1], 8700); - if matches.is_present("public_address") { - addr.set_ip(solana_netutil::get_public_ip_addr().unwrap()); - } else { - addr.set_ip(solana_netutil::get_ip_addr(false).unwrap()); - } + addr.set_ip(solana_netutil::get_public_ip_addr(&network_addr).unwrap()); addr }; let node = @@ -76,13 +73,6 @@ fn main() { gossip_addr ); - let network_addr = matches - .value_of("network") - .map(|network| { - solana_netutil::parse_host_port(network).expect("failed to parse network address") - }) - .unwrap(); - let leader_info = ContactInfo::new_gossip_entry_point(&network_addr); let storage_keypair = Arc::new(Keypair::new()); let mut replicator = Replicator::new(