diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b43f67..8c9cec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ This project follows [semantic versioning](http://semver.org). +### UNRELEASED + +- [added] Added option to listen on specified IP +- [changed] No longer using two sockets for ipv4 and ipv6 + ### v1.3.0 (2020-01-25) - [added] Building for aarch64 aka arm64 (thanks to Ivan) diff --git a/src/benches.rs b/src/benches.rs index 8e00f3b..75e5d2c 100644 --- a/src/benches.rs +++ b/src/benches.rs @@ -130,10 +130,9 @@ fn now(b: &mut Bencher) { #[bench] fn epoll_wait(b: &mut Bencher) { - let socketv4 = UdpSocket::bind("0.0.0.0:0").unwrap(); - let socketv6 = UdpSocket::bind("[::]:0").unwrap(); + let socket = UdpSocket::bind("[::]:0").unwrap(); let device = TunTapDevice::dummy("dummy", "/dev/zero", Type::Dummy).unwrap(); - let mut waiter = WaitImpl::testing(&socketv4, &socketv6, &device, 1000).unwrap(); + let mut waiter = WaitImpl::testing(&socket, &device, 1000).unwrap(); b.iter(|| assert!(waiter.next().is_some())); b.bytes = 1400; } diff --git a/src/cloud.rs b/src/cloud.rs index f5733e2..6bd78da 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -222,8 +222,7 @@ pub struct GenericCloud, own_addresses: Vec, table: T, - socket4: S, - socket6: S, + socket: S, device: D, crypto: Crypto, next_peerlist: Time, @@ -248,13 +247,9 @@ impl GenericCloud, stats_file: Option ) -> Self { - let socket4 = match S::listen_v4("0.0.0.0", config.port) { + let socket = match S::listen(config.listen) { Ok(socket) => socket, - Err(err) => fail!("Failed to open ipv4 address 0.0.0.0:{}: {}", config.port, err) - }; - let socket6 = match S::listen_v6("::", config.port) { - Ok(socket) => socket, - Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err) + Err(err) => fail!("Failed to open socket {}: {}", config.listen, err) }; let now = TS::now(); let update_freq = config.get_keepalive() as u16; @@ -269,8 +264,7 @@ impl GenericCloud GenericCloud &mut self.socket4, - SocketAddr::V6(_) => &mut self.socket6 - }; - match socket.send(msg_data, *addr) { + match self.socket.send(msg_data, *addr) { Ok(written) if written == msg_data.len() => Ok(()), Ok(_) => { Err(Error::Socket("Sent out truncated packet", io::Error::new(io::ErrorKind::Other, "truncated"))) @@ -335,11 +325,7 @@ impl GenericCloud &mut self.socket4, - SocketAddr::V6(_) => &mut self.socket6 - }; - match socket.send(msg_data, addr) { + match self.socket.send(msg_data, addr) { Ok(written) if written == msg_data.len() => Ok(()), Ok(_) => Err(Error::Socket("Sent out truncated packet", io::Error::new(io::ErrorKind::Other, "truncated"))), Err(e) => Err(Error::Socket("IOError when sending", e)) @@ -354,8 +340,8 @@ impl GenericCloud io::Result<(SocketAddr, SocketAddr)> { - Ok((self.socket4.address()?, self.socket6.address()?)) + pub fn address(&self) -> io::Result { + Ok(self.socket.address()?) } /// Returns the number of peers @@ -747,10 +733,7 @@ impl GenericCloud error!("Failed to obtain local addresses: {}", err), - Ok((v4, v6)) => { - self.own_addresses.push(v4); - self.own_addresses.push(v6); - } + Ok(addr) => self.own_addresses.push(addr) } } @@ -764,13 +747,8 @@ impl GenericCloud GenericCloud GenericCloud {} - WaitResult::SocketV4 => self.handle_socket_v4_event(&mut buffer), - WaitResult::SocketV6 => self.handle_socket_v6_event(&mut buffer), + WaitResult::Socket => self.handle_socket_event(&mut buffer), WaitResult::Device => self.handle_device_event(&mut buffer) } if self.next_housekeep < TS::now() { @@ -842,26 +818,17 @@ impl GenericCloud GenericCloud { - pub fn socket4(&mut self) -> &mut MockSocket { - &mut self.socket4 - } - - pub fn socket6(&mut self) -> &mut MockSocket { - &mut self.socket6 + pub fn socket(&mut self) -> &mut MockSocket { + &mut self.socket } pub fn device(&mut self) -> &mut MockDevice { &mut self.device } - pub fn trigger_socket_v4_event(&mut self) { + pub fn trigger_socket_event(&mut self) { let mut buffer = [0; 64 * 1024]; - self.handle_socket_v4_event(&mut buffer); - } - - pub fn trigger_socket_v6_event(&mut self) { - let mut buffer = [0; 64 * 1024]; - self.handle_socket_v6_event(&mut buffer); + self.handle_socket_event(&mut buffer); } pub fn trigger_device_event(&mut self) { diff --git a/src/config.rs b/src/config.rs index 7e5f431..b43d077 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,13 +12,28 @@ use super::{ }; use siphasher::sip::SipHasher24; -use std::hash::{Hash, Hasher}; +use std::{ + hash::{Hash, Hasher}, + net::{IpAddr, Ipv6Addr, SocketAddr} +}; const HASH_PREFIX: &str = "hash:"; pub const DEFAULT_PEER_TIMEOUT: u16 = 600; +fn parse_listen(addr: &str) -> SocketAddr { + if addr.starts_with("*:") { + let port = try_fail!(addr[2..].parse::(), "Invalid port: {}"); + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) + } else if addr.contains(':') { + try_fail!(addr.parse::(), "Invalid address: {}: {}", addr) + } else { + let port = try_fail!(addr.parse::(), "Invalid port: {}"); + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) + } +} + #[derive(Deserialize, Debug, PartialEq, Clone)] pub struct Config { pub device_type: Type, @@ -29,7 +44,7 @@ pub struct Config { pub crypto: CryptoMethod, pub shared_key: Option, pub magic: Option, - pub port: u16, + pub listen: SocketAddr, pub peers: Vec, pub peer_timeout: Duration, pub keepalive: Option, @@ -58,7 +73,7 @@ impl Default for Config { crypto: CryptoMethod::ChaCha20, shared_key: None, magic: None, - port: 3210, + listen: "[::]:3210".parse::().unwrap(), peers: vec![], peer_timeout: DEFAULT_PEER_TIMEOUT as Duration, keepalive: None, @@ -79,6 +94,7 @@ impl Default for Config { } impl Config { + #[allow(clippy::cognitive_complexity)] pub fn merge_file(&mut self, file: ConfigFile) { if let Some(val) = file.device_type { self.device_type = val; @@ -105,7 +121,11 @@ impl Config { self.magic = Some(val); } if let Some(val) = file.port { - self.port = val; + self.listen = parse_listen(&format!("{}", &val)); + warn!("The config option 'port' is deprecated, use 'listen' instead."); + } + if let Some(val) = file.listen { + self.listen = parse_listen(&val); } if let Some(mut val) = file.peers { self.peers.append(&mut val); @@ -181,7 +201,7 @@ impl Config { self.magic = Some(val); } if let Some(val) = args.flag_listen { - self.port = val; + self.listen = parse_listen(&val); } self.peers.append(&mut args.flag_connect); if let Some(val) = args.flag_peer_timeout { @@ -265,6 +285,7 @@ pub struct ConfigFile { pub shared_key: Option, pub magic: Option, pub port: Option, + pub listen: Option, pub peers: Option>, pub peer_timeout: Option, pub keepalive: Option, @@ -322,6 +343,7 @@ stats_file: /var/log/vpncloud.stats shared_key: Some("mysecret".to_string()), magic: Some("0123ABCD".to_string()), port: Some(3210), + listen: None, peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peer_timeout: Some(600), keepalive: Some(840), @@ -352,6 +374,7 @@ fn config_merge() { shared_key: Some("mysecret".to_string()), magic: Some("0123ABCD".to_string()), port: Some(3210), + listen: None, peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peer_timeout: Some(600), keepalive: Some(840), @@ -376,7 +399,7 @@ fn config_merge() { magic: Some("0123ABCD".to_string()), crypto: CryptoMethod::AES256, shared_key: Some("mysecret".to_string()), - port: 3210, + listen: "[::]:3210".parse::().unwrap(), peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()], peer_timeout: 600, keepalive: Some(840), @@ -402,7 +425,7 @@ fn config_merge() { flag_crypto: Some(CryptoMethod::ChaCha20), flag_shared_key: Some("anothersecret".to_string()), flag_magic: Some("hash:mynet".to_string()), - flag_listen: Some(3211), + flag_listen: Some("3211".to_string()), flag_peer_timeout: Some(1801), flag_keepalive: Some(850), flag_dst_timeout: Some(301), @@ -429,7 +452,7 @@ fn config_merge() { magic: Some("hash:mynet".to_string()), crypto: CryptoMethod::ChaCha20, shared_key: Some("anothersecret".to_string()), - port: 3211, + listen: "[::]:3211".parse::().unwrap(), peers: vec![ "remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string(), diff --git a/src/main.rs b/src/main.rs index 0243064..15fd657 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,7 @@ pub struct Args { flag_crypto: Option, flag_subnet: Vec, flag_device: Option, - flag_listen: Option, + flag_listen: Option, flag_network_id: Option, flag_magic: Option, flag_connect: Vec, @@ -270,7 +270,7 @@ fn run(config: Config) { Some(ref key) => Crypto::from_shared_key(config.crypto, key), None => Crypto::None }; - let port_forwarding = if config.port_forwarding { PortForwarding::new(config.port) } else { None }; + let port_forwarding = if config.port_forwarding { PortForwarding::new(config.listen.port()) } else { None }; let stats_file = match config.stats_file { None => None, Some(ref name) => { diff --git a/src/net.rs b/src/net.rs index 7c12e45..84d61ec 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,34 +1,24 @@ use std::{ collections::{HashMap, VecDeque}, io::{self, ErrorKind}, - net::{SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket}, + net::{SocketAddr, UdpSocket}, os::unix::io::{AsRawFd, RawFd}, sync::atomic::{AtomicBool, Ordering} }; use super::util::{MockTimeSource, Time, TimeSource}; -use net2::UdpBuilder; - pub trait Socket: AsRawFd + Sized { - fn listen_v4(host: &str, port: u16) -> Result; - fn listen_v6(host: &str, port: u16) -> Result; + fn listen(addr: SocketAddr) -> Result; fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error>; fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result; fn address(&self) -> Result; } impl Socket for UdpSocket { - fn listen_v4(host: &str, port: u16) -> Result { - UdpBuilder::new_v4().expect("Failed to obtain ipv4 socket builder").bind((host, port)) - } - fn listen_v6(host: &str, port: u16) -> Result { - UdpBuilder::new_v6() - .expect("Failed to obtain ipv4 socket builder") - .only_v6(true) - .expect("Failed to set only_v6") - .bind((host, port)) + fn listen(addr: SocketAddr) -> Result { + UdpSocket::bind(addr) } fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error> { self.recv_from(buffer) @@ -99,13 +89,8 @@ impl AsRawFd for MockSocket { } impl Socket for MockSocket { - fn listen_v4(host: &str, port: u16) -> Result { - let ip = try_fail!(host.parse(), "Failed to parse IPv4 address: {}"); - Ok(Self::new(SocketAddr::V4(SocketAddrV4::new(ip, port)))) - } - fn listen_v6(host: &str, port: u16) -> Result { - let ip = try_fail!(host.parse(), "Failed to parse IPv6 address: {}"); - Ok(Self::new(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)))) + fn listen(addr: SocketAddr) -> Result { + Ok(Self::new(addr)) } fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error> { if let Some((addr, data)) = self.inbound.pop_front() { diff --git a/src/poll/epoll.rs b/src/poll/epoll.rs index 3e6f36f..999cdb3 100644 --- a/src/poll/epoll.rs +++ b/src/poll/epoll.rs @@ -13,33 +13,30 @@ use crate::{device::Type, net::Socket}; pub struct EpollWait { poll_fd: RawFd, event: libc::epoll_event, - socketv4: RawFd, - socketv6: RawFd, + socket: RawFd, device: RawFd, timeout: u32 } impl EpollWait { - pub fn new(socketv4: &S, socketv6: &S, device: &dyn Device, timeout: u32) -> io::Result { - Self::create(socketv4, socketv6, device, timeout, libc::EPOLLIN as u32) + pub fn new(socket: &S, device: &dyn Device, timeout: u32) -> io::Result { + Self::create(socket, device, timeout, libc::EPOLLIN as u32) } - pub fn testing(socketv4: &S, socketv6: &S, device: &dyn Device, timeout: u32) -> io::Result { - Self::create(socketv4, socketv6, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32) + pub fn testing(socket: &S, device: &dyn Device, timeout: u32) -> io::Result { + Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32) } - fn create( - socketv4: &S, socketv6: &S, device: &dyn Device, timeout: u32, flags: u32 - ) -> io::Result { + fn create(socket: &S, device: &dyn Device, timeout: u32, flags: u32) -> io::Result { let mut event = libc::epoll_event { u64: 0, events: 0 }; let poll_fd = unsafe { libc::epoll_create(3) }; if poll_fd == -1 { return Err(io::Error::last_os_error()) } let raw_fds = if device.get_type() != Type::Dummy { - vec![socketv4.as_raw_fd(), socketv6.as_raw_fd(), device.as_raw_fd()] + vec![socket.as_raw_fd(), device.as_raw_fd()] } else { - vec![socketv4.as_raw_fd(), socketv6.as_raw_fd()] + vec![socket.as_raw_fd()] }; for fd in raw_fds { event.u64 = fd as u64; @@ -52,8 +49,7 @@ impl EpollWait { Ok(Self { poll_fd, event, - socketv4: socketv4.as_raw_fd(), - socketv6: socketv6.as_raw_fd(), + socket: socket.as_raw_fd(), device: device.as_raw_fd(), timeout }) @@ -74,10 +70,8 @@ impl Iterator for EpollWait { -1 => WaitResult::Error(io::Error::last_os_error()), 0 => WaitResult::Timeout, 1 => { - if self.event.u64 == self.socketv4 as u64 { - WaitResult::SocketV4 - } else if self.event.u64 == self.socketv6 as u64 { - WaitResult::SocketV6 + if self.event.u64 == self.socket as u64 { + WaitResult::Socket } else if self.event.u64 == self.device as u64 { WaitResult::Device } else { diff --git a/src/poll/mod.rs b/src/poll/mod.rs index 4b86c34..753c9af 100644 --- a/src/poll/mod.rs +++ b/src/poll/mod.rs @@ -13,8 +13,7 @@ use std::io; pub enum WaitResult { Timeout, - SocketV4, - SocketV6, + Socket, Device, Error(io::Error) } diff --git a/src/tests/helper.rs b/src/tests/helper.rs index 8302df8..9c46d76 100644 --- a/src/tests/helper.rs +++ b/src/tests/helper.rs @@ -1,8 +1,7 @@ macro_rules! assert_clean { ($($node: expr),*) => { $( - assert_eq!($node.socket4().pop_outbound().map(|(addr, mut msg)| (addr, $node.decode_message(&mut msg).unwrap().without_data())), None); - assert_eq!($node.socket6().pop_outbound().map(|(addr, mut msg)| (addr, $node.decode_message(&mut msg).unwrap().without_data())), None); + assert_eq!($node.socket().pop_outbound().map(|(addr, mut msg)| (addr, $node.decode_message(&mut msg).unwrap().without_data())), None); assert_eq!($node.device().pop_outbound(), None); )* }; @@ -10,13 +9,13 @@ macro_rules! assert_clean { macro_rules! assert_message4 { ($from: expr, $from_addr: expr, $to: expr, $to_addr: expr, $message: expr) => { - let (addr, mut data) = msg4_get(&mut $from); + let (addr, mut data) = msg_get(&mut $from); assert_eq!($to_addr, addr); { let message = $from.decode_message(&mut data).unwrap(); assert_eq!($message, message.without_data()); } - msg4_put(&mut $to, $from_addr, data); + msg_put(&mut $to, $from_addr, data); }; } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d5d45e1..b1d1008 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -8,9 +8,9 @@ mod nat; mod payload; mod peers; -pub use std::net::SocketAddr; use std::{ io::Write, + net::{IpAddr, Ipv6Addr, SocketAddr}, sync::{ atomic::{AtomicUsize, Ordering}, Once @@ -73,13 +73,17 @@ thread_local! { static NEXT_PORT: AtomicUsize = AtomicUsize::new(1); } +fn next_sock_addr() -> SocketAddr { + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16) +} + fn create_tap_node(nat: bool) -> TapTestNode { create_tap_node_with_config(nat, Config::default()) } fn create_tap_node_with_config(nat: bool, mut config: Config) -> TapTestNode { MockSocket::set_nat(nat); - config.port = NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16; + config.listen = next_sock_addr(); TestNode::new(&config, MockDevice::new(), SwitchTable::new(1800, 10), true, true, vec![], Crypto::None, None, None) } @@ -87,7 +91,7 @@ fn create_tap_node_with_config(nat: bool, mut config: Config) -> TapTestNode { fn create_tun_node(nat: bool, addresses: Vec) -> TunTestNode { MockSocket::set_nat(nat); TestNode::new( - &Config { port: NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16, ..Config::default() }, + &Config { listen: next_sock_addr(), ..Config::default() }, MockDevice::new(), RoutingTable::new(), false, @@ -100,28 +104,15 @@ fn create_tun_node(nat: bool, addresses: Vec) -> TunTestNode { } -fn msg4_get(node: &mut TestNode) -> (SocketAddr, Vec) { - let msg = node.socket4().pop_outbound(); +fn msg_get(node: &mut TestNode) -> (SocketAddr, Vec) { + let msg = node.socket().pop_outbound(); assert!(msg.is_some()); msg.unwrap() } -#[allow(dead_code)] -fn msg6_get(node: &mut TestNode) -> (SocketAddr, Vec) { - let msg = node.socket6().pop_outbound(); - assert!(msg.is_some()); - msg.unwrap() -} - -fn msg4_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { - if node.socket4().put_inbound(from, msg) { - node.trigger_socket_v4_event(); - } -} - -fn msg6_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { - if node.socket6().put_inbound(from, msg) { - node.trigger_socket_v6_event(); +fn msg_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { + if node.socket().put_inbound(from, msg) { + node.trigger_socket_event(); } } @@ -136,7 +127,7 @@ fn simulate(nodes: &mut [(&mut TestNode, SocketAddr clean = true; let mut msgs = Vec::new(); for (ref mut node, ref from_addr) in nodes.iter_mut() { - while let Some((to_addr, msg)) = node.socket4().pop_outbound() { + while let Some((to_addr, msg)) = node.socket().pop_outbound() { msgs.push((msg, *from_addr, to_addr)); } } @@ -144,22 +135,7 @@ fn simulate(nodes: &mut [(&mut TestNode, SocketAddr for (msg, from_addr, to_addr) in msgs { for (ref mut node, ref addr) in nodes.iter_mut() { if *addr == to_addr { - msg4_put(node, from_addr, msg); - break - } - } - } - let mut msgs = Vec::new(); - for (ref mut node, ref from_addr) in nodes.iter_mut() { - while let Some((to_addr, msg)) = node.socket6().pop_outbound() { - msgs.push((msg, *from_addr, to_addr)); - } - } - clean &= msgs.is_empty(); - for (msg, from_addr, to_addr) in msgs { - for (ref mut node, ref addr) in nodes.iter_mut() { - if *addr == to_addr { - msg6_put(node, from_addr, msg); + msg_put(node, from_addr, msg); break } } diff --git a/src/tests/peers.rs b/src/tests/peers.rs index 9baac11..9a4dab3 100644 --- a/src/tests/peers.rs +++ b/src/tests/peers.rs @@ -101,7 +101,7 @@ fn connect_via_beacons() { let beacon_path = "target/.vpncloud_test"; let mut node1 = create_tap_node_with_config(false, Config { beacon_store: Some(beacon_path.to_string()), ..Config::default() }); - let node1_addr = node1.address().unwrap().0; + let node1_addr = node1.address().unwrap(); let mut node2 = create_tap_node_with_config(false, Config { beacon_load: Some(beacon_path.to_string()), ..Config::default() }); let node2_addr = addr!("2.2.2.2:2222"); @@ -162,7 +162,7 @@ fn lost_init1() { assert_clean!(node1); // Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers - assert!(node2.socket4().pop_outbound().is_some()); + assert!(node2.socket().pop_outbound().is_some()); assert!(!node1.peers().contains_node(&node2.node_id())); simulate!(node1 => node1_addr, node2 => node2_addr); diff --git a/vpncloud.md b/vpncloud.md index 5a9fbad..03fd5bf 100644 --- a/vpncloud.md +++ b/vpncloud.md @@ -38,9 +38,12 @@ vpncloud(1) -- Peer-to-peer VPN peers and ignore them otherwise. The **normal** mode is switch for tap devices and router for tun devices. [default: `normal`] - * `-l `, `--listen `: + * `-l `, `--listen `: - The port number on which to listen for data. [default: `3210`] + The address on which to listen for data. This can be simply a port number + or a full address in form IP:PORT. If the IP is specified as '*' or only + a port number is given, then the socket will listen on all IPs (v4 and v6), + otherwise the socket will only listen on the given IP. [default: `3210`] * `-c `, `--connect `: @@ -311,7 +314,8 @@ detailed descriptions of the options. * `crypto`: The encryption method to use. Same as `--crypto` * `shared_key`: The shared key to encrypt all traffic. Same as `--shared-key` * `magic`: Override the 4-byte magic header of each packet. Same as `--magic` -* `port`: The port number on which to listen for data. Same as `--listen` +* `port`: A port number to listen on. This option is DEPRECATED. +* `listen`: The address on which to listen for data. Same as `--listen` * `peers`: A list of addresses to connect to. See `--connect` * `peer_timeout`: Peer timeout in seconds. Same as`--peer-timeout` * `beacon_store`: Path or command to store beacons. Same as `--beacon-store` @@ -334,7 +338,7 @@ device_name: vpncloud%d ifup: ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up crypto: aes256 shared_key: mysecret -port: 3210 +listen: 3210 peers: - remote.machine.foo:3210 - remote.machine.bar:3210