Add a node-specific ip echo service to remove dependency on ifconfig.co (#4137)

This commit is contained in:
Michael Vines 2019-05-03 11:01:35 -07:00 committed by GitHub
parent c8ed41167a
commit 7fe3c75c6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 239 additions and 325 deletions

42
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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") {
(

View File

@ -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<dyn error::Error>> {
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<dyn error::Error>> {
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)
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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\" \

View File

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

View File

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

View File

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

View File

@ -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();
}
}

View File

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

View File

@ -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<IpAddr, String> {
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<IpAddr, String> {
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<SocketAddr, String> {
}
}
fn find_eth0ish_ip_addr(
ifaces: &mut Vec<datalink::NetworkInterface>,
enable_ipv6: bool,
) -> Option<IpAddr> {
// 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<IpAddr> {
let mut ifaces = datalink::interfaces();
find_eth0ish_ip_addr(&mut ifaces, enable_ipv6)
}
fn udp_socket(reuseaddr: bool) -> io::Result<Socket> {
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<u16> {
#[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);
}

View File

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