2018-09-08 12:50:43 -07:00
|
|
|
//! The `netutil` module assists with networking
|
2018-06-29 14:12:26 -07:00
|
|
|
|
2018-09-06 14:13:40 -07:00
|
|
|
use nix::sys::socket::setsockopt;
|
2018-09-07 15:28:22 -07:00
|
|
|
use nix::sys::socket::sockopt::{ReuseAddr, ReusePort};
|
2018-08-28 16:32:40 -07:00
|
|
|
use pnet_datalink as datalink;
|
2018-06-28 11:28:28 -07:00
|
|
|
use rand::{thread_rng, Rng};
|
2018-09-08 12:50:43 -07:00
|
|
|
use reqwest;
|
2018-09-06 14:13:40 -07:00
|
|
|
use socket2::{Domain, SockAddr, Socket, Type};
|
2018-06-28 11:28:28 -07:00
|
|
|
use std::io;
|
2018-10-10 13:51:43 -07:00
|
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket};
|
2018-09-06 14:13:40 -07:00
|
|
|
use std::os::unix::io::AsRawFd;
|
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
|
|
|
|
}
|
|
|
|
|
2018-06-29 16:49:23 -07:00
|
|
|
/// 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())?;
|
|
|
|
|
|
|
|
match body.lines().next() {
|
|
|
|
Some(ip) => Result::Ok(ip.parse().unwrap()),
|
|
|
|
None => Result::Err("Empty response body".to_string()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-28 16:32:40 -07:00
|
|
|
pub fn parse_port_or_addr(optstr: Option<&str>, default_port: u16) -> SocketAddr {
|
2018-08-30 12:07:54 -07:00
|
|
|
let daddr = SocketAddr::from(([0, 0, 0, 0], default_port));
|
|
|
|
|
2018-08-28 16:32:40 -07:00
|
|
|
if let Some(addrstr) = optstr {
|
|
|
|
if let Ok(port) = addrstr.parse() {
|
|
|
|
let mut addr = daddr;
|
|
|
|
addr.set_port(port);
|
|
|
|
addr
|
|
|
|
} else if let Ok(addr) = addrstr.parse() {
|
|
|
|
addr
|
|
|
|
} else {
|
|
|
|
daddr
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
daddr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-19 18:33:40 -07:00
|
|
|
fn find_eth0ish_ip_addr(ifaces: &mut Vec<datalink::NetworkInterface>) -> Option<IpAddr> {
|
2018-09-19 14:34:21 -07:00
|
|
|
// 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())
|
|
|
|
});
|
|
|
|
|
2018-09-19 18:33:40 -07:00
|
|
|
for iface in ifaces.clone() {
|
|
|
|
trace!("get_ip_addr considering iface {}", iface.name);
|
2018-08-28 16:32:40 -07:00
|
|
|
for p in iface.ips {
|
2018-09-19 18:33:40 -07:00
|
|
|
trace!(" ip {}", p);
|
2018-09-19 14:34:21 -07:00
|
|
|
if p.ip().is_loopback() {
|
|
|
|
trace!(" loopback");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if p.ip().is_multicast() {
|
|
|
|
trace!(" multicast");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match p.ip() {
|
|
|
|
IpAddr::V4(addr) => {
|
|
|
|
if addr.is_link_local() {
|
|
|
|
trace!(" link local");
|
|
|
|
continue;
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
2018-09-19 18:33:40 -07:00
|
|
|
trace!(" picked {}", p.ip());
|
2018-09-19 14:34:21 -07:00
|
|
|
return Some(p.ip());
|
|
|
|
}
|
|
|
|
IpAddr::V6(_addr) => {
|
|
|
|
// Select an ipv6 address if the config is selected
|
|
|
|
#[cfg(feature = "ipv6")]
|
|
|
|
{
|
|
|
|
return Some(p.ip());
|
2018-08-28 16:32:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
2018-06-28 11:28:28 -07:00
|
|
|
|
2018-09-19 18:33:40 -07:00
|
|
|
pub fn get_ip_addr() -> Option<IpAddr> {
|
|
|
|
let mut ifaces = datalink::interfaces();
|
|
|
|
|
|
|
|
find_eth0ish_ip_addr(&mut ifaces)
|
|
|
|
}
|
|
|
|
|
2018-09-08 12:50:43 -07:00
|
|
|
fn udp_socket(reuseaddr: bool) -> io::Result<Socket> {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bind_in_range(range: (u16, u16)) -> io::Result<(u16, UdpSocket)> {
|
|
|
|
let sock = udp_socket(false)?;
|
|
|
|
|
2018-08-28 16:32:40 -07:00
|
|
|
let (start, end) = range;
|
|
|
|
let mut tries_left = end - start;
|
|
|
|
loop {
|
2018-06-28 11:28:28 -07:00
|
|
|
let rand_port = thread_rng().gen_range(start, end);
|
|
|
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), rand_port);
|
|
|
|
|
2018-09-06 14:13:40 -07:00
|
|
|
match sock.bind(&SockAddr::from(addr)) {
|
2018-09-08 12:50:43 -07:00
|
|
|
Ok(_) => {
|
|
|
|
let sock = sock.into_udp_socket();
|
|
|
|
break Result::Ok((sock.local_addr().unwrap().port(), sock));
|
|
|
|
}
|
|
|
|
Err(err) => if err.kind() != io::ErrorKind::AddrInUse || tries_left == 0 {
|
2018-06-28 11:28:28 -07:00
|
|
|
return Err(err);
|
|
|
|
},
|
|
|
|
}
|
2018-08-28 16:32:40 -07:00
|
|
|
tries_left -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-08 12:50:43 -07:00
|
|
|
// binds many sockets to the same port in a range
|
|
|
|
pub fn multi_bind_in_range(range: (u16, u16), num: usize) -> io::Result<(u16, Vec<UdpSocket>)> {
|
|
|
|
let mut sockets = Vec::with_capacity(num);
|
|
|
|
|
|
|
|
let port = {
|
|
|
|
let (port, _) = bind_in_range(range)?;
|
|
|
|
port
|
|
|
|
}; // drop the probe, port should be available... briefly.
|
|
|
|
|
|
|
|
for _ in 0..num {
|
|
|
|
sockets.push(bind_to(port, true)?);
|
|
|
|
}
|
|
|
|
Ok((port, sockets))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bind_to(port: u16, reuseaddr: bool) -> io::Result<UdpSocket> {
|
|
|
|
let sock = udp_socket(reuseaddr)?;
|
|
|
|
|
|
|
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port);
|
|
|
|
|
2018-09-06 14:13:40 -07:00
|
|
|
match sock.bind(&SockAddr::from(addr)) {
|
2018-09-08 12:50:43 -07:00
|
|
|
Ok(_) => Result::Ok(sock.into_udp_socket()),
|
|
|
|
Err(err) => Err(err),
|
2018-09-06 14:13:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-10 13:51:43 -07:00
|
|
|
pub fn find_available_port_in_range(range: (u16, u16)) -> io::Result<u16> {
|
|
|
|
let (start, end) = range;
|
|
|
|
let mut tries_left = end - start;
|
|
|
|
loop {
|
|
|
|
let rand_port = thread_rng().gen_range(start, end);
|
|
|
|
match TcpListener::bind(SocketAddr::new(
|
|
|
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
|
|
|
rand_port,
|
|
|
|
)) {
|
|
|
|
Ok(_) => {
|
|
|
|
break Ok(rand_port);
|
|
|
|
}
|
|
|
|
Err(err) => if err.kind() != io::ErrorKind::AddrInUse || tries_left == 0 {
|
|
|
|
return Err(err);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
tries_left -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-28 16:32:40 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-09-19 18:33:40 -07:00
|
|
|
use ipnetwork::IpNetwork;
|
|
|
|
use logger;
|
2018-09-08 12:50:43 -07:00
|
|
|
use netutil::*;
|
2018-09-19 18:33:40 -07:00
|
|
|
use pnet_datalink as datalink;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_eth0ish_ip_addr() {
|
|
|
|
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
|
|
|
|
assert_eq!(
|
|
|
|
find_eth0ish_ip_addr(&mut vec![mock_interface!(lo, "127.0.0.1/24")]),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
// multicast bad
|
|
|
|
assert_eq!(
|
|
|
|
find_eth0ish_ip_addr(&mut vec![mock_interface!(eth0, "224.0.1.5/24")]),
|
|
|
|
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"),
|
|
|
|
]),
|
|
|
|
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"),
|
|
|
|
]),
|
|
|
|
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"),
|
|
|
|
]),
|
|
|
|
Some(mock_interface!(eth2, "192.168.137.1/8").ips[0].ip())
|
|
|
|
);
|
|
|
|
}
|
2018-08-28 16:32:40 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_port_or_addr() {
|
|
|
|
let p1 = parse_port_or_addr(Some("9000"), 1);
|
|
|
|
assert_eq!(p1.port(), 9000);
|
|
|
|
let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), 1);
|
|
|
|
assert_eq!(p2.port(), 7000);
|
|
|
|
let p2 = parse_port_or_addr(Some("hi there"), 1);
|
|
|
|
assert_eq!(p2.port(), 1);
|
|
|
|
let p3 = parse_port_or_addr(None, 1);
|
|
|
|
assert_eq!(p3.port(), 1);
|
2018-06-28 11:28:28 -07:00
|
|
|
}
|
2018-09-08 12:50:43 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bind() {
|
|
|
|
assert_eq!(bind_in_range((2000, 2001)).unwrap().0, 2000);
|
|
|
|
let x = bind_to(2002, true).unwrap();
|
|
|
|
let y = bind_to(2002, true).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
x.local_addr().unwrap().port(),
|
|
|
|
y.local_addr().unwrap().port()
|
|
|
|
);
|
|
|
|
let (port, v) = multi_bind_in_range((2010, 2110), 10).unwrap();
|
|
|
|
for sock in &v {
|
|
|
|
assert_eq!(port, sock.local_addr().unwrap().port());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_bind_in_range_nil() {
|
|
|
|
let _ = bind_in_range((2000, 2000));
|
|
|
|
}
|
|
|
|
|
2018-10-15 10:01:40 -07:00
|
|
|
#[test]
|
|
|
|
fn test_find_available_port_in_range() {
|
|
|
|
assert_eq!(find_available_port_in_range((3000, 3001)).unwrap(), 3000);
|
|
|
|
let port = find_available_port_in_range((3000, 3050)).unwrap();
|
|
|
|
assert!(3000 <= port && port < 3050);
|
|
|
|
}
|
2018-06-28 11:28:28 -07:00
|
|
|
}
|