2019-11-12 12:37:13 -08:00
|
|
|
//! The `net_utils` module assists with networking
|
2019-06-04 20:55:02 -07:00
|
|
|
use log::*;
|
2018-06-28 11:28:28 -07:00
|
|
|
use rand::{thread_rng, Rng};
|
2018-09-06 14:13:40 -07:00
|
|
|
use socket2::{Domain, SockAddr, Socket, Type};
|
2020-06-14 15:36:08 -07:00
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
2019-09-04 23:10:35 -07:00
|
|
|
use std::io::{self, Read, Write};
|
2020-03-04 21:46:43 -08:00
|
|
|
use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket};
|
2019-09-04 23:10:35 -07:00
|
|
|
use std::sync::mpsc::channel;
|
2019-05-03 11:01:35 -07:00
|
|
|
use std::time::Duration;
|
2020-06-30 08:57:16 -07:00
|
|
|
use url::Url;
|
2019-05-03 11:01:35 -07:00
|
|
|
|
|
|
|
mod ip_echo_server;
|
2019-09-04 23:10:35 -07:00
|
|
|
use ip_echo_server::IpEchoServerMessage;
|
2020-06-14 15:36:08 -07:00
|
|
|
pub use ip_echo_server::{ip_echo_server, IpEchoServer, MAX_PORT_COUNT_PER_MESSAGE};
|
2018-06-29 14:12:26 -07:00
|
|
|
|
|
|
|
/// A data type representing a public Udp socket
|
|
|
|
pub struct UdpSocketPair {
|
|
|
|
pub addr: SocketAddr, // Public address of the socket
|
|
|
|
pub receiver: UdpSocket, // Locally bound socket that can receive from the public address
|
|
|
|
pub sender: UdpSocket, // Locally bound socket to send via public address
|
|
|
|
}
|
|
|
|
|
2019-04-12 18:17:34 -07:00
|
|
|
pub type PortRange = (u16, u16);
|
|
|
|
|
2020-11-11 19:37:43 -08:00
|
|
|
pub(crate) const HEADER_LENGTH: usize = 4;
|
2020-11-11 18:27:03 -08:00
|
|
|
pub(crate) fn ip_echo_server_reply_length() -> usize {
|
|
|
|
let largest_ip_addr = IpAddr::from([0u16; 8]); // IPv6 variant
|
|
|
|
HEADER_LENGTH + bincode::serialized_size(&largest_ip_addr).unwrap() as usize
|
|
|
|
}
|
2020-11-11 19:37:43 -08:00
|
|
|
|
2019-09-04 23:10:35 -07:00
|
|
|
fn ip_echo_server_request(
|
|
|
|
ip_echo_server_addr: &SocketAddr,
|
|
|
|
msg: IpEchoServerMessage,
|
|
|
|
) -> Result<IpAddr, String> {
|
2020-11-11 18:29:04 -08:00
|
|
|
let mut data = vec![0u8; ip_echo_server_reply_length()];
|
2019-05-03 11:01:35 -07:00
|
|
|
|
|
|
|
let timeout = Duration::new(5, 0);
|
|
|
|
TcpStream::connect_timeout(ip_echo_server_addr, timeout)
|
|
|
|
.and_then(|mut stream| {
|
2020-11-11 19:37:43 -08:00
|
|
|
// Start with HEADER_LENGTH null bytes to avoid looking like an HTTP GET/POST request
|
|
|
|
let mut bytes = vec![0; HEADER_LENGTH];
|
2019-12-02 09:01:25 -08:00
|
|
|
|
2019-12-02 22:32:43 -08:00
|
|
|
bytes.append(&mut bincode::serialize(&msg).expect("serialize IpEchoServerMessage"));
|
2019-12-02 09:01:25 -08:00
|
|
|
|
2019-12-02 22:32:43 -08:00
|
|
|
// End with '\n' to make this request look HTTP-ish and tickle an error response back
|
|
|
|
// from an HTTP server
|
|
|
|
bytes.push(b'\n');
|
|
|
|
|
2020-02-11 12:31:55 -08:00
|
|
|
stream.set_read_timeout(Some(Duration::new(10, 0)))?;
|
2019-12-02 22:32:43 -08:00
|
|
|
stream.write_all(&bytes)?;
|
2019-09-04 23:10:35 -07:00
|
|
|
stream.shutdown(std::net::Shutdown::Write)?;
|
2020-11-11 18:29:04 -08:00
|
|
|
stream.read(data.as_mut_slice())
|
2019-05-03 11:01:35 -07:00
|
|
|
})
|
|
|
|
.and_then(|_| {
|
2019-12-02 09:01:25 -08:00
|
|
|
// It's common for users to accidentally confuse the validator's gossip port and JSON
|
|
|
|
// RPC port. Attempt to detect when this occurs by looking for the standard HTTP
|
|
|
|
// response header and provide the user with a helpful error message
|
2020-11-11 19:37:43 -08:00
|
|
|
if data.len() < HEADER_LENGTH {
|
2019-12-02 09:01:25 -08:00
|
|
|
return Err(io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!("Response too short, received {} bytes", data.len()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-11-11 19:37:43 -08:00
|
|
|
let response_header: String =
|
|
|
|
data[0..HEADER_LENGTH].iter().map(|b| *b as char).collect();
|
2019-12-02 09:01:25 -08:00
|
|
|
if response_header != "\0\0\0\0" {
|
|
|
|
if response_header == "HTTP" {
|
|
|
|
let http_response = data.iter().map(|b| *b as char).collect::<String>();
|
|
|
|
return Err(io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!(
|
|
|
|
"Invalid gossip entrypoint. {} looks to be an HTTP port: {}",
|
|
|
|
ip_echo_server_addr, http_response
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
return Err(io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!(
|
|
|
|
"Invalid gossip entrypoint. {} provided an invalid response header: '{}'",
|
|
|
|
ip_echo_server_addr, response_header
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-11-11 19:37:43 -08:00
|
|
|
bincode::deserialize(&data[HEADER_LENGTH..]).map_err(|err| {
|
2019-05-03 11:01:35 -07:00
|
|
|
io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!("Failed to deserialize: {:?}", err),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.map_err(|err| err.to_string())
|
2018-06-29 16:49:23 -07:00
|
|
|
}
|
|
|
|
|
2019-09-04 23:10:35 -07:00
|
|
|
/// 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> {
|
|
|
|
ip_echo_server_request(ip_echo_server_addr, IpEchoServerMessage::default())
|
|
|
|
}
|
|
|
|
|
2020-05-22 14:33:01 -07:00
|
|
|
// Checks if any of the provided TCP/UDP ports are not reachable by the machine at
|
2019-09-04 23:10:35 -07:00
|
|
|
// `ip_echo_server_addr`
|
2020-06-14 15:36:08 -07:00
|
|
|
const DEFAULT_TIMEOUT_SECS: u64 = 5;
|
|
|
|
const DEFAULT_RETRY_COUNT: usize = 5;
|
|
|
|
|
|
|
|
fn do_verify_reachable_ports(
|
2019-09-04 23:10:35 -07:00
|
|
|
ip_echo_server_addr: &SocketAddr,
|
2019-09-19 17:16:22 -07:00
|
|
|
tcp_listeners: Vec<(u16, TcpListener)>,
|
2019-09-04 23:10:35 -07:00
|
|
|
udp_sockets: &[&UdpSocket],
|
2020-06-14 15:36:08 -07:00
|
|
|
timeout: u64,
|
|
|
|
udp_retry_count: usize,
|
2020-05-22 14:33:01 -07:00
|
|
|
) -> bool {
|
2019-09-04 23:10:35 -07:00
|
|
|
info!(
|
2020-06-14 15:36:08 -07:00
|
|
|
"Checking that tcp ports {:?} from {:?}",
|
|
|
|
tcp_listeners, ip_echo_server_addr
|
2019-09-04 23:10:35 -07:00
|
|
|
);
|
|
|
|
|
2019-09-19 17:16:22 -07:00
|
|
|
let tcp_ports: Vec<_> = tcp_listeners.iter().map(|(port, _)| *port).collect();
|
2019-09-04 23:10:35 -07:00
|
|
|
let _ = ip_echo_server_request(
|
|
|
|
ip_echo_server_addr,
|
2020-06-14 15:36:08 -07:00
|
|
|
IpEchoServerMessage::new(&tcp_ports, &[]),
|
2019-09-04 23:10:35 -07:00
|
|
|
)
|
|
|
|
.map_err(|err| warn!("ip_echo_server request failed: {}", err));
|
|
|
|
|
2020-05-22 14:33:01 -07:00
|
|
|
let mut ok = true;
|
2020-06-14 15:36:08 -07:00
|
|
|
let timeout = Duration::from_secs(timeout);
|
2020-05-22 14:33:01 -07:00
|
|
|
|
2019-09-04 23:10:35 -07:00
|
|
|
// Wait for a connection to open on each TCP port
|
2019-09-19 17:16:22 -07:00
|
|
|
for (port, tcp_listener) in tcp_listeners {
|
2019-09-04 23:10:35 -07:00
|
|
|
let (sender, receiver) = channel();
|
2020-06-14 15:36:08 -07:00
|
|
|
let listening_addr = tcp_listener.local_addr().unwrap();
|
|
|
|
let thread_handle = std::thread::spawn(move || {
|
2019-09-04 23:10:35 -07:00
|
|
|
debug!("Waiting for incoming connection on tcp/{}", port);
|
2020-05-28 14:16:53 -07:00
|
|
|
match tcp_listener.incoming().next() {
|
|
|
|
Some(_) => sender
|
|
|
|
.send(())
|
|
|
|
.unwrap_or_else(|err| warn!("send failure: {}", err)),
|
|
|
|
None => warn!("tcp incoming failed"),
|
|
|
|
}
|
2019-09-04 23:10:35 -07:00
|
|
|
});
|
2020-06-14 15:36:08 -07:00
|
|
|
match receiver.recv_timeout(timeout) {
|
2020-05-22 14:33:01 -07:00
|
|
|
Ok(_) => {
|
|
|
|
info!("tcp/{} is reachable", port);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2019-09-04 23:10:35 -07:00
|
|
|
error!(
|
|
|
|
"Received no response at tcp/{}, check your port configuration: {}",
|
|
|
|
port, err
|
|
|
|
);
|
2020-06-14 15:36:08 -07:00
|
|
|
// Ugh, std rustc doesn't provide acceptng with timeout or restoring original
|
|
|
|
// nonblocking-status of sockets because of lack of getter, only the setter...
|
|
|
|
// So, to close the thread cleanly, just connect from here.
|
|
|
|
// ref: https://github.com/rust-lang/rust/issues/31615
|
|
|
|
TcpStream::connect_timeout(&listening_addr, timeout).unwrap();
|
2020-05-22 14:33:01 -07:00
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
}
|
2020-06-14 15:36:08 -07:00
|
|
|
// ensure to reap the thread
|
|
|
|
thread_handle.join().unwrap();
|
2019-09-04 23:10:35 -07:00
|
|
|
}
|
|
|
|
|
2020-05-22 14:33:01 -07:00
|
|
|
if !ok {
|
|
|
|
// No retries for TCP, abort on the first failure
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2020-06-14 15:36:08 -07:00
|
|
|
let mut udp_ports: BTreeMap<_, _> = BTreeMap::new();
|
|
|
|
udp_sockets.iter().for_each(|udp_socket| {
|
|
|
|
let port = udp_socket.local_addr().unwrap().port();
|
|
|
|
udp_ports
|
|
|
|
.entry(port)
|
|
|
|
.or_insert_with(Vec::new)
|
|
|
|
.push(udp_socket);
|
|
|
|
});
|
|
|
|
let udp_ports: Vec<_> = udp_ports.into_iter().collect();
|
|
|
|
|
|
|
|
info!(
|
|
|
|
"Checking that udp ports {:?} are reachable from {:?}",
|
|
|
|
udp_ports.iter().map(|(port, _)| port).collect::<Vec<_>>(),
|
|
|
|
ip_echo_server_addr
|
|
|
|
);
|
|
|
|
|
|
|
|
'outer: for checked_ports_and_sockets in udp_ports.chunks(MAX_PORT_COUNT_PER_MESSAGE) {
|
|
|
|
ok = false;
|
|
|
|
|
|
|
|
for udp_remaining_retry in (0_usize..udp_retry_count).rev() {
|
|
|
|
let (checked_ports, checked_socket_iter) = (
|
|
|
|
checked_ports_and_sockets
|
|
|
|
.iter()
|
|
|
|
.map(|(port, _)| *port)
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
checked_ports_and_sockets
|
|
|
|
.iter()
|
|
|
|
.map(|(_, sockets)| sockets)
|
|
|
|
.flatten(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = ip_echo_server_request(
|
|
|
|
ip_echo_server_addr,
|
|
|
|
IpEchoServerMessage::new(&[], &checked_ports),
|
|
|
|
)
|
|
|
|
.map_err(|err| warn!("ip_echo_server request failed: {}", err));
|
|
|
|
|
|
|
|
// Spawn threads at once!
|
|
|
|
let thread_handles: Vec<_> = checked_socket_iter
|
|
|
|
.map(|udp_socket| {
|
|
|
|
let port = udp_socket.local_addr().unwrap().port();
|
|
|
|
let udp_socket = udp_socket.try_clone().expect("Unable to clone udp socket");
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
let mut buf = [0; 1];
|
|
|
|
let original_read_timeout = udp_socket.read_timeout().unwrap();
|
|
|
|
udp_socket.set_read_timeout(Some(timeout)).unwrap();
|
|
|
|
let recv_result = udp_socket.recv(&mut buf);
|
|
|
|
debug!(
|
|
|
|
"Waited for incoming datagram on udp/{}: {:?}",
|
|
|
|
port, recv_result
|
|
|
|
);
|
|
|
|
udp_socket.set_read_timeout(original_read_timeout).unwrap();
|
|
|
|
recv_result.map(|_| port).ok()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
// Now join threads!
|
|
|
|
// Separate from the above by collect()-ing as an intermediately step to make the iterator
|
|
|
|
// eager not lazy so that joining happens here at once after creating bunch of threads
|
|
|
|
// at once.
|
|
|
|
let reachable_ports: BTreeSet<_> = thread_handles
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|t| t.join().unwrap())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
if reachable_ports.len() == checked_ports.len() {
|
|
|
|
info!(
|
|
|
|
"checked udp ports: {:?}, reachable udp ports: {:?}",
|
|
|
|
checked_ports, reachable_ports
|
|
|
|
);
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
} else if udp_remaining_retry > 0 {
|
|
|
|
// Might have lost a UDP packet, retry a couple times
|
|
|
|
error!(
|
|
|
|
"checked udp ports: {:?}, reachable udp ports: {:?}",
|
|
|
|
checked_ports, reachable_ports
|
|
|
|
);
|
|
|
|
error!("There are some udp ports with no response!! Retrying...");
|
|
|
|
} else {
|
|
|
|
error!("Maximum retry count is reached....");
|
|
|
|
break 'outer;
|
2020-05-22 14:33:01 -07:00
|
|
|
}
|
|
|
|
}
|
2019-09-04 23:10:35 -07:00
|
|
|
}
|
2020-05-22 14:33:01 -07:00
|
|
|
|
|
|
|
ok
|
2019-09-04 23:10:35 -07:00
|
|
|
}
|
|
|
|
|
2020-06-14 15:36:08 -07:00
|
|
|
pub fn verify_reachable_ports(
|
|
|
|
ip_echo_server_addr: &SocketAddr,
|
|
|
|
tcp_listeners: Vec<(u16, TcpListener)>,
|
|
|
|
udp_sockets: &[&UdpSocket],
|
|
|
|
) -> bool {
|
|
|
|
do_verify_reachable_ports(
|
|
|
|
ip_echo_server_addr,
|
|
|
|
tcp_listeners,
|
|
|
|
udp_sockets,
|
|
|
|
DEFAULT_TIMEOUT_SECS,
|
|
|
|
DEFAULT_RETRY_COUNT,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-05-03 11:01:35 -07:00
|
|
|
pub fn parse_port_or_addr(optstr: Option<&str>, default_addr: SocketAddr) -> SocketAddr {
|
2018-08-28 16:32:40 -07:00
|
|
|
if let Some(addrstr) = optstr {
|
|
|
|
if let Ok(port) = addrstr.parse() {
|
2019-05-03 11:01:35 -07:00
|
|
|
let mut addr = default_addr;
|
2018-08-28 16:32:40 -07:00
|
|
|
addr.set_port(port);
|
|
|
|
addr
|
|
|
|
} else if let Ok(addr) = addrstr.parse() {
|
|
|
|
addr
|
|
|
|
} else {
|
2019-05-03 11:01:35 -07:00
|
|
|
default_addr
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
|
|
|
} else {
|
2019-05-03 11:01:35 -07:00
|
|
|
default_addr
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-12 18:17:34 -07:00
|
|
|
pub fn parse_port_range(port_range: &str) -> Option<PortRange> {
|
|
|
|
let ports: Vec<&str> = port_range.split('-').collect();
|
|
|
|
if ports.len() != 2 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let start_port = ports[0].parse();
|
|
|
|
let end_port = ports[1].parse();
|
|
|
|
|
|
|
|
if start_port.is_err() || end_port.is_err() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let start_port = start_port.unwrap();
|
|
|
|
let end_port = end_port.unwrap();
|
|
|
|
if end_port < start_port {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some((start_port, end_port))
|
|
|
|
}
|
|
|
|
|
2019-04-13 19:34:27 -07:00
|
|
|
pub fn parse_host(host: &str) -> Result<IpAddr, String> {
|
2020-06-30 08:57:16 -07:00
|
|
|
// First, check if the host syntax is valid. This check is needed because addresses
|
|
|
|
// such as `("localhost:1234", 0)` will resolve to IPs on some networks.
|
|
|
|
let parsed_url = Url::parse(&format!("http://{}", host)).map_err(|e| e.to_string())?;
|
|
|
|
if parsed_url.port().is_some() {
|
|
|
|
return Err(format!("Expected port in URL: {}", host));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, check to see if it resolves to an IP address
|
2019-04-13 19:34:27 -07:00
|
|
|
let ips: Vec<_> = (host, 0)
|
|
|
|
.to_socket_addrs()
|
|
|
|
.map_err(|err| err.to_string())?
|
|
|
|
.map(|socket_address| socket_address.ip())
|
|
|
|
.collect();
|
|
|
|
if ips.is_empty() {
|
|
|
|
Err(format!("Unable to resolve host: {}", host))
|
|
|
|
} else {
|
|
|
|
Ok(ips[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-20 14:21:34 -08:00
|
|
|
pub fn is_host(string: String) -> Result<(), String> {
|
|
|
|
parse_host(&string).map(|_| ())
|
|
|
|
}
|
|
|
|
|
2019-04-13 19:34:27 -07:00
|
|
|
pub fn parse_host_port(host_port: &str) -> Result<SocketAddr, String> {
|
|
|
|
let addrs: Vec<_> = host_port
|
|
|
|
.to_socket_addrs()
|
|
|
|
.map_err(|err| err.to_string())?
|
|
|
|
.collect();
|
|
|
|
if addrs.is_empty() {
|
|
|
|
Err(format!("Unable to resolve host: {}", host_port))
|
|
|
|
} else {
|
|
|
|
Ok(addrs[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-30 09:27:35 -07:00
|
|
|
pub fn is_host_port(string: String) -> Result<(), String> {
|
2019-11-20 14:21:34 -08:00
|
|
|
parse_host_port(&string).map(|_| ())
|
2019-08-30 09:27:35 -07:00
|
|
|
}
|
|
|
|
|
2019-06-04 08:51:20 -07:00
|
|
|
#[cfg(windows)]
|
|
|
|
fn udp_socket(_reuseaddr: bool) -> io::Result<Socket> {
|
|
|
|
let sock = Socket::new(Domain::ipv4(), Type::dgram(), None)?;
|
|
|
|
Ok(sock)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(windows))]
|
2018-09-08 12:50:43 -07:00
|
|
|
fn udp_socket(reuseaddr: bool) -> io::Result<Socket> {
|
2019-06-04 08:51:20 -07:00
|
|
|
use nix::sys::socket::setsockopt;
|
|
|
|
use nix::sys::socket::sockopt::{ReuseAddr, ReusePort};
|
|
|
|
use std::os::unix::io::AsRawFd;
|
|
|
|
|
2018-09-08 12:50:43 -07:00
|
|
|
let sock = Socket::new(Domain::ipv4(), Type::dgram(), None)?;
|
|
|
|
let sock_fd = sock.as_raw_fd();
|
|
|
|
|
|
|
|
if reuseaddr {
|
|
|
|
// best effort, i.e. ignore errors here, we'll get the failure in caller
|
|
|
|
setsockopt(sock_fd, ReusePort, &true).ok();
|
|
|
|
setsockopt(sock_fd, ReuseAddr, &true).ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(sock)
|
|
|
|
}
|
|
|
|
|
2019-09-19 17:16:22 -07:00
|
|
|
// Find a port in the given range that is available for both TCP and UDP
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn bind_common_in_range(
|
|
|
|
ip_addr: IpAddr,
|
|
|
|
range: PortRange,
|
|
|
|
) -> io::Result<(u16, (UdpSocket, TcpListener))> {
|
2020-04-09 13:35:23 -07:00
|
|
|
for port in range.0..range.1 {
|
|
|
|
if let Ok((sock, listener)) = bind_common(ip_addr, port, false) {
|
|
|
|
return Result::Ok((sock.local_addr().unwrap().port(), (sock, listener)));
|
2019-09-19 17:16:22 -07:00
|
|
|
}
|
|
|
|
}
|
2020-04-09 13:35:23 -07:00
|
|
|
|
2020-04-09 14:07:11 -07:00
|
|
|
Err(io::Error::new(
|
2020-04-09 13:35:23 -07:00
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!("No available TCP/UDP ports in {:?}", range),
|
2020-04-09 14:07:11 -07:00
|
|
|
))
|
2019-09-19 17:16:22 -07:00
|
|
|
}
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> {
|
2018-09-08 12:50:43 -07:00
|
|
|
let sock = udp_socket(false)?;
|
|
|
|
|
2020-04-09 13:35:23 -07:00
|
|
|
for port in range.0..range.1 {
|
|
|
|
let addr = SocketAddr::new(ip_addr, port);
|
2018-06-28 11:28:28 -07:00
|
|
|
|
2020-04-09 13:35:23 -07:00
|
|
|
if sock.bind(&SockAddr::from(addr)).is_ok() {
|
|
|
|
let sock = sock.into_udp_socket();
|
|
|
|
return Result::Ok((sock.local_addr().unwrap().port(), sock));
|
2018-11-15 08:50:37 -08:00
|
|
|
}
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
2020-04-09 13:35:23 -07:00
|
|
|
|
2020-04-09 14:07:11 -07:00
|
|
|
Err(io::Error::new(
|
2020-04-09 13:35:23 -07:00
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!("No available UDP ports in {:?}", range),
|
2020-04-09 14:07:11 -07:00
|
|
|
))
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
|
|
|
|
2018-09-08 12:50:43 -07:00
|
|
|
// binds many sockets to the same port in a range
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn multi_bind_in_range(
|
|
|
|
ip_addr: IpAddr,
|
|
|
|
range: PortRange,
|
|
|
|
mut num: usize,
|
|
|
|
) -> io::Result<(u16, Vec<UdpSocket>)> {
|
2019-06-04 20:55:02 -07:00
|
|
|
if cfg!(windows) && num != 1 {
|
2019-10-21 16:43:11 -07:00
|
|
|
// See https://github.com/solana-labs/solana/issues/4607
|
2019-06-04 20:55:02 -07:00
|
|
|
warn!(
|
|
|
|
"multi_bind_in_range() only supports 1 socket in windows ({} requested)",
|
|
|
|
num
|
|
|
|
);
|
|
|
|
num = 1;
|
|
|
|
}
|
2018-09-08 12:50:43 -07:00
|
|
|
let mut sockets = Vec::with_capacity(num);
|
|
|
|
|
2020-04-14 13:34:41 -07:00
|
|
|
const NUM_TRIES: usize = 100;
|
|
|
|
let mut port = 0;
|
|
|
|
let mut error = None;
|
|
|
|
for _ in 0..NUM_TRIES {
|
|
|
|
port = {
|
|
|
|
let (port, _) = bind_in_range(ip_addr, range)?;
|
|
|
|
port
|
|
|
|
}; // drop the probe, port should be available... briefly.
|
|
|
|
|
|
|
|
for _ in 0..num {
|
|
|
|
let sock = bind_to(ip_addr, port, true);
|
|
|
|
if let Ok(sock) = sock {
|
|
|
|
sockets.push(sock);
|
|
|
|
} else {
|
|
|
|
error = Some(sock);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sockets.len() == num {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
sockets.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sockets.len() != num {
|
|
|
|
error.unwrap()?;
|
2018-09-08 12:50:43 -07:00
|
|
|
}
|
|
|
|
Ok((port, sockets))
|
|
|
|
}
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn bind_to(ip_addr: IpAddr, port: u16, reuseaddr: bool) -> io::Result<UdpSocket> {
|
2018-09-08 12:50:43 -07:00
|
|
|
let sock = udp_socket(reuseaddr)?;
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
let addr = SocketAddr::new(ip_addr, port);
|
2018-09-08 12:50:43 -07:00
|
|
|
|
Maintenance : simplify a few patterns, remove unneeded dependencies (#8137)
* Simplify a few pattern matches
* Removing unneeded dependencies, upgrading internal version #s
net-shaper: Removing log, semver, serde_derive
bench-tps: Removing serde, serde_derive
banking-bench: Removing solana
ledger-tool: Removing bincode, serde, serde_derive
librapay: Removing solana, language_e2e_tests
log-analyzer: Removing log, semver, serde_derive
exchange: Removing solana
core: Removing crc, memmap, symlink, untrusted
perf: Removing serde_derive
genesis: Removing hex, serde_derive
sdk-c: Removing sha2
sys-tuner: Removing semver
bench-exchange: Removing bincode, bs58, env_logger, serde, serde_derive, untrusted, ws
btc_spv_bin: Removing serde_json
btc_spv: Removing chrono
bpf_loader: Removing serde
ledger: Removing dlopen, dlopen_derive, serde_derive
move_loader: Removing byteorder, libc, language_e2e_tests
ownable: Removing serde, serde_derive
client: Removing rand
archiver-utils: Removing rand_chacha
validator: Removing serde_json, tempfile
param_passing_dep: Removing solana
failure: Removing log
vest: Removing log
vote-signer: Removing bs58, serde
local-cluster: Removing symlink
keygen: Removing rpassword
install: Removing bs58, log
upload-perf: Removing log
runtime: Removing serde_json
stake: Removing rand
* Add modified Cargo.lock
* fixup! Simplify a few pattern matches
* fixup! Simplify a few pattern matches
2020-02-06 09:02:38 -08:00
|
|
|
sock.bind(&SockAddr::from(addr))
|
2020-06-08 17:38:14 -07:00
|
|
|
.map(|_| sock.into_udp_socket())
|
2018-09-06 14:13:40 -07:00
|
|
|
}
|
|
|
|
|
2019-09-19 17:16:22 -07:00
|
|
|
// binds both a UdpSocket and a TcpListener
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn bind_common(
|
|
|
|
ip_addr: IpAddr,
|
|
|
|
port: u16,
|
|
|
|
reuseaddr: bool,
|
|
|
|
) -> io::Result<(UdpSocket, TcpListener)> {
|
2019-09-19 17:16:22 -07:00
|
|
|
let sock = udp_socket(reuseaddr)?;
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
let addr = SocketAddr::new(ip_addr, port);
|
2019-09-19 17:16:22 -07:00
|
|
|
let sock_addr = SockAddr::from(addr);
|
2020-06-08 17:38:14 -07:00
|
|
|
sock.bind(&sock_addr)
|
|
|
|
.and_then(|_| TcpListener::bind(&addr).map(|listener| (sock.into_udp_socket(), listener)))
|
2019-09-19 17:16:22 -07:00
|
|
|
}
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
pub fn find_available_port_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<u16> {
|
2018-10-10 13:51:43 -07:00
|
|
|
let (start, end) = range;
|
|
|
|
let mut tries_left = end - start;
|
2018-11-15 08:50:37 -08:00
|
|
|
let mut rand_port = thread_rng().gen_range(start, end);
|
2018-10-10 13:51:43 -07:00
|
|
|
loop {
|
2020-03-04 21:46:43 -08:00
|
|
|
match bind_common(ip_addr, rand_port, false) {
|
2018-10-10 13:51:43 -07:00
|
|
|
Ok(_) => {
|
|
|
|
break Ok(rand_port);
|
|
|
|
}
|
2018-11-02 14:32:05 -07:00
|
|
|
Err(err) => {
|
2018-11-15 15:49:37 -08:00
|
|
|
if tries_left == 0 {
|
2018-11-02 14:32:05 -07:00
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 13:51:43 -07:00
|
|
|
}
|
2018-11-15 08:50:37 -08:00
|
|
|
rand_port += 1;
|
|
|
|
if rand_port == end {
|
|
|
|
rand_port = start;
|
|
|
|
}
|
2018-10-10 13:51:43 -07:00
|
|
|
tries_left -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-28 16:32:40 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-12-14 17:14:49 -08:00
|
|
|
use super::*;
|
2020-03-04 21:46:43 -08:00
|
|
|
use std::net::Ipv4Addr;
|
2018-08-28 16:32:40 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_port_or_addr() {
|
2019-05-03 11:01:35 -07:00
|
|
|
let p1 = parse_port_or_addr(Some("9000"), SocketAddr::from(([1, 2, 3, 4], 1)));
|
2018-08-28 16:32:40 -07:00
|
|
|
assert_eq!(p1.port(), 9000);
|
2019-05-03 11:01:35 -07:00
|
|
|
let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), SocketAddr::from(([1, 2, 3, 4], 1)));
|
2018-08-28 16:32:40 -07:00
|
|
|
assert_eq!(p2.port(), 7000);
|
2019-05-03 11:01:35 -07:00
|
|
|
let p2 = parse_port_or_addr(Some("hi there"), SocketAddr::from(([1, 2, 3, 4], 1)));
|
2018-08-28 16:32:40 -07:00
|
|
|
assert_eq!(p2.port(), 1);
|
2019-05-03 11:01:35 -07:00
|
|
|
let p3 = parse_port_or_addr(None, SocketAddr::from(([1, 2, 3, 4], 1)));
|
2018-08-28 16:32:40 -07:00
|
|
|
assert_eq!(p3.port(), 1);
|
2018-06-28 11:28:28 -07:00
|
|
|
}
|
2018-09-08 12:50:43 -07:00
|
|
|
|
2019-04-12 18:17:34 -07:00
|
|
|
#[test]
|
|
|
|
fn test_parse_port_range() {
|
|
|
|
assert_eq!(parse_port_range("garbage"), None);
|
|
|
|
assert_eq!(parse_port_range("1-"), None);
|
|
|
|
assert_eq!(parse_port_range("1-2"), Some((1, 2)));
|
|
|
|
assert_eq!(parse_port_range("1-2-3"), None);
|
|
|
|
assert_eq!(parse_port_range("2-1"), None);
|
|
|
|
}
|
|
|
|
|
2019-04-13 19:34:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_parse_host() {
|
|
|
|
parse_host("localhost:1234").unwrap_err();
|
|
|
|
parse_host("localhost").unwrap();
|
|
|
|
parse_host("127.0.0.0:1234").unwrap_err();
|
|
|
|
parse_host("127.0.0.0").unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_host_port() {
|
|
|
|
parse_host_port("localhost:1234").unwrap();
|
|
|
|
parse_host_port("localhost").unwrap_err();
|
|
|
|
parse_host_port("127.0.0.0:1234").unwrap();
|
|
|
|
parse_host_port("127.0.0.0").unwrap_err();
|
|
|
|
}
|
|
|
|
|
2019-11-08 15:07:11 -08:00
|
|
|
#[test]
|
|
|
|
fn test_is_host_port() {
|
|
|
|
assert!(is_host_port("localhost:1234".to_string()).is_ok());
|
|
|
|
assert!(is_host_port("localhost".to_string()).is_err());
|
|
|
|
}
|
|
|
|
|
2018-09-08 12:50:43 -07:00
|
|
|
#[test]
|
|
|
|
fn test_bind() {
|
2020-03-04 21:46:43 -08:00
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
assert_eq!(bind_in_range(ip_addr, (2000, 2001)).unwrap().0, 2000);
|
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
let x = bind_to(ip_addr, 2002, true).unwrap();
|
|
|
|
let y = bind_to(ip_addr, 2002, true).unwrap();
|
2018-09-08 12:50:43 -07:00
|
|
|
assert_eq!(
|
|
|
|
x.local_addr().unwrap().port(),
|
|
|
|
y.local_addr().unwrap().port()
|
|
|
|
);
|
2020-03-04 21:46:43 -08:00
|
|
|
bind_to(ip_addr, 2002, false).unwrap_err();
|
|
|
|
bind_in_range(ip_addr, (2002, 2003)).unwrap_err();
|
2019-11-08 15:07:11 -08:00
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
let (port, v) = multi_bind_in_range(ip_addr, (2010, 2110), 10).unwrap();
|
2018-09-08 12:50:43 -07:00
|
|
|
for sock in &v {
|
|
|
|
assert_eq!(port, sock.local_addr().unwrap().port());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bind_in_range_nil() {
|
2020-03-04 21:46:43 -08:00
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
2020-04-09 13:35:23 -07:00
|
|
|
bind_in_range(ip_addr, (2000, 2000)).unwrap_err();
|
|
|
|
bind_in_range(ip_addr, (2000, 1999)).unwrap_err();
|
2018-09-08 12:50:43 -07:00
|
|
|
}
|
|
|
|
|
2018-10-15 10:01:40 -07:00
|
|
|
#[test]
|
|
|
|
fn test_find_available_port_in_range() {
|
2020-03-04 21:46:43 -08:00
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
assert_eq!(
|
|
|
|
find_available_port_in_range(ip_addr, (3000, 3001)).unwrap(),
|
|
|
|
3000
|
|
|
|
);
|
|
|
|
let port = find_available_port_in_range(ip_addr, (3000, 3050)).unwrap();
|
2018-10-15 10:01:40 -07:00
|
|
|
assert!(3000 <= port && port < 3050);
|
2019-11-08 15:07:11 -08:00
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
let _socket = bind_to(ip_addr, port, false).unwrap();
|
|
|
|
find_available_port_in_range(ip_addr, (port, port + 1)).unwrap_err();
|
2018-10-15 10:01:40 -07:00
|
|
|
}
|
2019-09-19 17:16:22 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bind_common_in_range() {
|
2020-03-04 21:46:43 -08:00
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
let (port, _sockets) = bind_common_in_range(ip_addr, (3100, 3150)).unwrap();
|
2019-11-08 15:07:11 -08:00
|
|
|
assert!(3100 <= port && port < 3150);
|
|
|
|
|
2020-03-04 21:46:43 -08:00
|
|
|
bind_common_in_range(ip_addr, (port, port + 1)).unwrap_err();
|
2019-11-08 15:07:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-06-14 15:36:08 -07:00
|
|
|
fn test_get_public_ip_addr_none() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
let (_server_port, (server_udp_socket, server_tcp_listener)) =
|
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
|
|
|
|
|
|
|
let _runtime = ip_echo_server(server_tcp_listener);
|
|
|
|
|
|
|
|
let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
get_public_ip_addr(&server_ip_echo_addr),
|
|
|
|
parse_host("127.0.0.1"),
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(verify_reachable_ports(&server_ip_echo_addr, vec![], &[],));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_public_ip_addr_reachable() {
|
2019-12-02 09:01:25 -08:00
|
|
|
solana_logger::setup();
|
2020-03-04 21:46:43 -08:00
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
2019-11-08 15:07:11 -08:00
|
|
|
let (_server_port, (server_udp_socket, server_tcp_listener)) =
|
2020-03-04 21:46:43 -08:00
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
2019-11-08 15:07:11 -08:00
|
|
|
let (client_port, (client_udp_socket, client_tcp_listener)) =
|
2020-03-04 21:46:43 -08:00
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
2019-11-08 15:07:11 -08:00
|
|
|
|
|
|
|
let _runtime = ip_echo_server(server_tcp_listener);
|
|
|
|
|
|
|
|
let ip_echo_server_addr = server_udp_socket.local_addr().unwrap();
|
2019-12-02 17:37:01 -08:00
|
|
|
assert_eq!(
|
|
|
|
get_public_ip_addr(&ip_echo_server_addr),
|
|
|
|
parse_host("127.0.0.1"),
|
|
|
|
);
|
2019-11-08 15:07:11 -08:00
|
|
|
|
2020-05-22 14:33:01 -07:00
|
|
|
assert!(verify_reachable_ports(
|
2019-11-08 15:07:11 -08:00
|
|
|
&ip_echo_server_addr,
|
|
|
|
vec![(client_port, client_tcp_listener)],
|
|
|
|
&[&client_udp_socket],
|
2020-05-22 14:33:01 -07:00
|
|
|
));
|
2019-09-19 17:16:22 -07:00
|
|
|
}
|
2020-06-14 15:36:08 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_public_ip_addr_tcp_unreachable() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
let (_server_port, (server_udp_socket, _server_tcp_listener)) =
|
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
|
|
|
|
|
|
|
// make the socket unreachable by not running the ip echo server!
|
|
|
|
|
|
|
|
let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
|
|
|
|
|
|
|
|
let (correct_client_port, (_client_udp_socket, client_tcp_listener)) =
|
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
|
|
|
|
|
|
|
assert!(!do_verify_reachable_ports(
|
|
|
|
&server_ip_echo_addr,
|
|
|
|
vec![(correct_client_port, client_tcp_listener)],
|
|
|
|
&[],
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_public_ip_addr_udp_unreachable() {
|
|
|
|
solana_logger::setup();
|
|
|
|
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
|
|
|
let (_server_port, (server_udp_socket, _server_tcp_listener)) =
|
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
|
|
|
|
|
|
|
// make the socket unreachable by not running the ip echo server!
|
|
|
|
|
|
|
|
let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
|
|
|
|
|
|
|
|
let (_correct_client_port, (client_udp_socket, _client_tcp_listener)) =
|
|
|
|
bind_common_in_range(ip_addr, (3200, 3250)).unwrap();
|
|
|
|
|
|
|
|
assert!(!do_verify_reachable_ports(
|
|
|
|
&server_ip_echo_addr,
|
|
|
|
vec![],
|
|
|
|
&[&client_udp_socket],
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
));
|
|
|
|
}
|
2018-06-28 11:28:28 -07:00
|
|
|
}
|