Compare commits

..

No commits in common. "eb620781a83ad3401e6366b8323147ceb6fe5cde" and "ed81c0de881b56a9b3084f27014d2d7547ad393d" have entirely different histories.

10 changed files with 142 additions and 183 deletions

View File

@ -2,12 +2,6 @@
This project follows [semantic versioning](http://semver.org). This project follows [semantic versioning](http://semver.org).
### Unreleased
- [added] Added feature to disable special NAT support
- [changed] Improved port forwarding on quirky routers
- [changed] Reduced peer timeout to 5min to work better with NAT
### v1.2.1 (2019-12-22) ### v1.2.1 (2019-12-22)
- [fixed] Fixed a problem with service restrictions - [fixed] Fixed a problem with service restrictions

View File

@ -24,7 +24,7 @@ rand = "0.7"
fnv = "1" fnv = "1"
net2 = "0.2" net2 = "0.2"
yaml-rust = "0.4" yaml-rust = "0.4"
igd = { version = "0.9", optional = true } igd = "0.9"
siphasher = "0.3" siphasher = "0.3"
daemonize = "0.4" daemonize = "0.4"
ring = "0.16" ring = "0.16"
@ -38,9 +38,8 @@ pkg-config = "0.3"
tempfile = "3" tempfile = "3"
[features] [features]
default = ["nat"] default = []
bench = [] bench = []
nat = ["igd"]
[profile.release] [profile.release]
lto = true lto = true

View File

@ -21,7 +21,7 @@
# Peer timeout in seconds. The peers will exchange information periodically # Peer timeout in seconds. The peers will exchange information periodically
# and drop peers that are silent for this period of time. # and drop peers that are silent for this period of time.
#peer_timeout: 600 #peer_timeout: 1800
# Switch table entry timeout in seconds. This parameter is only used in switch # Switch table entry timeout in seconds. This parameter is only used in switch
# mode. Addresses that have not been seen for the given period of time will # mode. Addresses that have not been seen for the given period of time will

View File

@ -19,7 +19,7 @@ use rand::{prelude::*, random, thread_rng};
use super::{ use super::{
beacon::BeaconSerializer, beacon::BeaconSerializer,
config::{Config, DEFAULT_PEER_TIMEOUT}, config::Config,
crypto::Crypto, crypto::Crypto,
device::Device, device::Device,
net::Socket, net::Socket,
@ -86,7 +86,7 @@ impl<TS: TimeSource> PeerList<TS> {
} }
pub fn min_peer_timeout(&self) -> u16 { pub fn min_peer_timeout(&self) -> u16 {
self.peers.iter().map(|p| p.1.peer_timeout).min().unwrap_or(DEFAULT_PEER_TIMEOUT) self.peers.iter().map(|p| p.1.peer_timeout).min().unwrap_or(1800)
} }
#[inline] #[inline]
@ -257,7 +257,12 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err) Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err)
}; };
let now = TS::now(); let now = TS::now();
let update_freq = config.get_keepalive() as u16; let update_freq = if socket4.detect_nat() && config.get_keepalive() > 120 {
info!("Private IP detected, setting keepalive interval to 120s");
120
} else {
config.get_keepalive() as u16
};
let mut res = GenericCloud { let mut res = GenericCloud {
magic: config.get_magic(), magic: config.get_magic(),
node_id: random(), node_id: random(),

View File

@ -16,7 +16,6 @@ use std::hash::{Hash, Hasher};
const HASH_PREFIX: &str = "hash:"; const HASH_PREFIX: &str = "hash:";
pub const DEFAULT_PEER_TIMEOUT: u16 = 600;
#[derive(Deserialize, Debug, PartialEq, Clone)] #[derive(Deserialize, Debug, PartialEq, Clone)]
@ -60,7 +59,7 @@ impl Default for Config {
magic: None, magic: None,
port: 3210, port: 3210,
peers: vec![], peers: vec![],
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration, peer_timeout: 1800,
keepalive: None, keepalive: None,
beacon_store: None, beacon_store: None,
beacon_load: None, beacon_load: None,
@ -297,7 +296,7 @@ port: 3210
peers: peers:
- remote.machine.foo:3210 - remote.machine.foo:3210
- remote.machine.bar:3210 - remote.machine.bar:3210
peer_timeout: 600 peer_timeout: 1800
keepalive: 840 keepalive: 840
dst_timeout: 300 dst_timeout: 300
beacon_store: /run/vpncloud.beacon.out beacon_store: /run/vpncloud.beacon.out
@ -323,7 +322,7 @@ stats_file: /var/log/vpncloud.stats
magic: Some("0123ABCD".to_string()), magic: Some("0123ABCD".to_string()),
port: Some(3210), port: Some(3210),
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(600), peer_timeout: Some(1800),
keepalive: Some(840), keepalive: Some(840),
beacon_store: Some("/run/vpncloud.beacon.out".to_string()), beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()), beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
@ -353,7 +352,7 @@ fn config_merge() {
magic: Some("0123ABCD".to_string()), magic: Some("0123ABCD".to_string()),
port: Some(3210), port: Some(3210),
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(600), peer_timeout: Some(1800),
keepalive: Some(840), keepalive: Some(840),
beacon_store: Some("/run/vpncloud.beacon.out".to_string()), beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()), beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
@ -378,7 +377,7 @@ fn config_merge() {
shared_key: Some("mysecret".to_string()), shared_key: Some("mysecret".to_string()),
port: 3210, port: 3210,
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()], peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600, peer_timeout: 1800,
keepalive: Some(840), keepalive: Some(840),
dst_timeout: 300, dst_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()), beacon_store: Some("/run/vpncloud.beacon.out".to_string()),

View File

@ -6,7 +6,7 @@ use std::{
sync::atomic::{AtomicBool, Ordering} sync::atomic::{AtomicBool, Ordering}
}; };
use super::util::{MockTimeSource, Time, TimeSource}; use super::util::{get_internal_ip, MockTimeSource, Time, TimeSource};
use net2::UdpBuilder; use net2::UdpBuilder;
@ -17,6 +17,7 @@ pub trait Socket: AsRawFd + Sized {
fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error>; fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error>;
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>; fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
fn address(&self) -> Result<SocketAddr, io::Error>; fn address(&self) -> Result<SocketAddr, io::Error>;
fn detect_nat(&self) -> bool;
} }
impl Socket for UdpSocket { impl Socket for UdpSocket {
@ -39,6 +40,9 @@ impl Socket for UdpSocket {
fn address(&self) -> Result<SocketAddr, io::Error> { fn address(&self) -> Result<SocketAddr, io::Error> {
self.local_addr() self.local_addr()
} }
fn detect_nat(&self) -> bool {
get_internal_ip().is_private()
}
} }
thread_local! { thread_local! {
@ -125,4 +129,7 @@ impl Socket for MockSocket {
fn address(&self) -> Result<SocketAddr, io::Error> { fn address(&self) -> Result<SocketAddr, io::Error> {
Ok(self.address) Ok(self.address)
} }
fn detect_nat(&self) -> bool {
self.nat
}
} }

View File

@ -2,174 +2,131 @@
// Copyright (C) 2015-2019 Dennis Schwerdel // Copyright (C) 2015-2019 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md) // This software is licensed under GPL-3 or newer (see LICENSE.md)
#[cfg(feature = "nat")] use std::{io, net::SocketAddrV4};
mod internal {
use std::{io, net::SocketAddrV4}; use igd::*;
use igd::{search_gateway, AddAnyPortError, AddPortError, Gateway, PortMappingProtocol, SearchError}; use super::util::{get_internal_ip, SystemTimeSource, Time, TimeSource};
use crate::util::{get_internal_ip, SystemTimeSource, Time, TimeSource}; const LEASE_TIME: u32 = 1800;
const DESCRIPTION: &str = "VpnCloud";
const LEASE_TIME: u32 = 1800;
const DESCRIPTION: &str = "VpnCloud"; pub struct PortForwarding {
pub internal_addr: SocketAddrV4,
pub external_addr: SocketAddrV4,
pub gateway: Gateway,
pub next_extension: Option<Time>
}
pub struct PortForwarding { impl PortForwarding {
pub internal_addr: SocketAddrV4, pub fn new(port: u16) -> Option<Self> {
pub external_addr: SocketAddrV4, // Get the gateway
gateway: Gateway, let gateway = match search_gateway(Default::default()) {
pub next_extension: Option<Time> Ok(gateway) => gateway,
Err(err) => {
if let SearchError::IoError(ref err) = err {
if err.kind() == io::ErrorKind::WouldBlock {
// Why this code?
warn!("Port-forwarding: no router found");
return None
}
}
error!("Port-forwarding: failed to find router: {}", err);
return None
}
};
info!("Port-forwarding: found router at {}", gateway.addr);
let internal_addr = SocketAddrV4::new(get_internal_ip(), port);
// Query the external address
let external_ip = match gateway.get_external_ip() {
Ok(ip) => ip,
Err(err) => {
error!("Port-forwarding: failed to obtain external IP: {}", err);
return None
}
};
// Try to activate the port forwarding
// - First with external port = internal port and timeout
// - If the port is used, request any port
// - If timeout is denied, try permanent forwarding
info!("Port-forwarding: external IP is {}", external_ip);
let (external_addr, timeout) = match gateway.add_port(
PortMappingProtocol::UDP,
internal_addr.port(),
internal_addr,
LEASE_TIME,
DESCRIPTION
) {
Ok(()) => (SocketAddrV4::new(external_ip, internal_addr.port()), LEASE_TIME),
Err(AddPortError::PortInUse) => {
match gateway.add_any_port(PortMappingProtocol::UDP, internal_addr, LEASE_TIME, DESCRIPTION) {
Ok(port) => (SocketAddrV4::new(external_ip, port), LEASE_TIME),
Err(AddAnyPortError::OnlyPermanentLeasesSupported) => {
match gateway.add_any_port(PortMappingProtocol::UDP, internal_addr, 0, DESCRIPTION) {
Ok(port) => (SocketAddrV4::new(external_ip, port), 0),
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None
}
}
}
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None
}
}
}
Err(AddPortError::OnlyPermanentLeasesSupported) => {
match gateway.add_port(PortMappingProtocol::UDP, internal_addr.port(), internal_addr, 0, DESCRIPTION) {
Ok(()) => (SocketAddrV4::new(external_ip, internal_addr.port()), 0),
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None
}
}
}
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None
}
};
info!("Port-forwarding: sucessfully activated port forward on {}, timeout: {}", external_addr, timeout);
let next_extension = if timeout > 0 { Some(SystemTimeSource::now() + Time::from(timeout) - 60) } else { None };
Some(PortForwarding { internal_addr, external_addr, gateway, next_extension })
} }
impl PortForwarding { pub fn check_extend(&mut self) {
pub fn new(port: u16) -> Option<Self> { if let Some(deadline) = self.next_extension {
// Get the gateway if deadline > SystemTimeSource::now() {
let gateway = match search_gateway(Default::default()) {
Ok(gateway) => gateway,
Err(err) => {
if let SearchError::IoError(ref err) = err {
if err.kind() == io::ErrorKind::WouldBlock {
// Why this code?
warn!("Port-forwarding: no router found");
return None
}
}
error!("Port-forwarding: failed to find router: {}", err);
return None
}
};
info!("Port-forwarding: found router at {}", gateway.addr);
let internal_addr = SocketAddrV4::new(get_internal_ip(), port);
// Query the external address
let external_ip = match gateway.get_external_ip() {
Ok(ip) => ip,
Err(err) => {
error!("Port-forwarding: failed to obtain external IP: {}", err);
return None
}
};
if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) {
info!("Port-forwarding: external IP is {}", external_ip);
let external_addr = SocketAddrV4::new(external_ip, port);
info!("Port-forwarding: sucessfully activated port forward on {}, timeout: {}", external_addr, timeout);
let next_extension =
if timeout > 0 { Some(SystemTimeSource::now() + Time::from(timeout) - 60) } else { None };
Some(PortForwarding { internal_addr, external_addr, gateway, next_extension })
} else {
None
}
}
fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
if let Ok(a) = Self::get_forwarding(gateway, addr, port) {
return Ok(a)
}
if let Ok(a) = Self::get_forwarding(gateway, addr, 0) {
return Ok(a)
}
for i in 1..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) {
return Ok(a)
}
}
for _ in 0..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) {
return Ok(a)
}
}
Err(())
}
fn get_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
info!("Trying external port {}", port);
if port == 0 {
match gateway.add_any_port(PortMappingProtocol::UDP, addr, LEASE_TIME, DESCRIPTION) {
Ok(port) => Ok((port, LEASE_TIME)),
Err(AddAnyPortError::OnlyPermanentLeasesSupported) => {
match gateway.add_any_port(PortMappingProtocol::UDP, addr, 0, DESCRIPTION) {
Ok(port) => Ok((port, 0)),
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
Err(())
}
}
}
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
Err(())
}
}
} else {
match gateway.add_port(PortMappingProtocol::UDP, port, addr, LEASE_TIME, DESCRIPTION) {
Ok(()) => Ok((port, LEASE_TIME)),
Err(AddPortError::OnlyPermanentLeasesSupported) => {
match gateway.add_port(PortMappingProtocol::UDP, port, addr, 0, DESCRIPTION) {
Ok(()) => Ok((port, 0)),
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
Err(())
}
}
}
Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err);
Err(())
}
}
}
}
pub fn check_extend(&mut self) {
if let Some(deadline) = self.next_extension {
if deadline > SystemTimeSource::now() {
return
}
} else {
return return
} }
match self.gateway.add_port( } else {
PortMappingProtocol::UDP, return
self.external_addr.port(),
self.internal_addr,
LEASE_TIME,
DESCRIPTION
) {
Ok(()) => debug!("Port-forwarding: extended port forwarding"),
Err(err) => error!("Port-forwarding: failed to extend port forwarding: {}", err)
};
self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60);
}
fn deactivate(&self) {
match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) {
Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"),
Err(err) => error!("Port-forwarding: failed to deactivate port forwarding: {}", err)
}
} }
match self.gateway.add_port(
PortMappingProtocol::UDP,
self.external_addr.port(),
self.internal_addr,
LEASE_TIME,
DESCRIPTION
) {
Ok(()) => debug!("Port-forwarding: extended port forwarding"),
Err(err) => error!("Port-forwarding: failed to extend port forwarding: {}", err)
};
self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60);
} }
impl Drop for PortForwarding { fn deactivate(&self) {
fn drop(&mut self) { match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) {
self.deactivate() Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"),
Err(err) => error!("Port-forwarding: failed to deactivate port forwarding: {}", err)
} }
} }
} }
#[cfg(not(feature = "nat"))] impl Drop for PortForwarding {
mod internal { fn drop(&mut self) {
pub struct PortForwarding; self.deactivate()
impl PortForwarding {
pub fn new(_port: u16) -> Option<Self> {
warn!("Compiled without feature 'nat', skipping port forwarding.");
None
}
pub fn check_extend(&mut self) {
unreachable!()
}
} }
} }
pub use internal::*;

View File

@ -18,24 +18,24 @@ fn connect_v4() {
node1.connect("2.3.4.5:6789").unwrap(); node1.connect("2.3.4.5:6789").unwrap();
// Node 1 -> Node 2: Init 0 // Node 1 -> Node 2: Init 0
assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 1800));
assert_clean!(node1); assert_clean!(node1);
assert!(node2.peers().contains_node(&node1.node_id())); assert!(node2.peers().contains_node(&node1.node_id()));
// Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers // Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers
assert_message4!(node2, node2_addr, node1, node1_addr, Message::Init(1, node2.node_id(), vec![], 600)); assert_message4!(node2, node2_addr, node1, node1_addr, Message::Init(1, node2.node_id(), vec![], 1800));
assert!(node1.peers().contains_node(&node2.node_id())); assert!(node1.peers().contains_node(&node2.node_id()));
assert_message4!(node2, node2_addr, node1, node1_addr, Message::Peers(vec![node1_addr])); assert_message4!(node2, node2_addr, node1, node1_addr, Message::Peers(vec![node1_addr]));
assert_clean!(node2); assert_clean!(node2);
// Node 1 -> Node 2: Peers | Node 1 -> Node 1: Init 0 // Node 1 -> Node 2: Peers | Node 1 -> Node 1: Init 0
assert_message4!(node1, node1_addr, node2, node2_addr, Message::Peers(vec![node2_addr])); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Peers(vec![node2_addr]));
assert_message4!(node1, node1_addr, node1, node1_addr, Message::Init(0, node1.node_id(), vec![], 600)); assert_message4!(node1, node1_addr, node1, node1_addr, Message::Init(0, node1.node_id(), vec![], 1800));
assert!(node1.own_addresses().contains(&node1_addr)); assert!(node1.own_addresses().contains(&node1_addr));
assert_clean!(node1); assert_clean!(node1);
// Node 2 -> Node 2: Init 0 // Node 2 -> Node 2: Init 0
assert_message4!(node2, node2_addr, node2, node2_addr, Message::Init(0, node2.node_id(), vec![], 600)); assert_message4!(node2, node2_addr, node2, node2_addr, Message::Init(0, node2.node_id(), vec![], 1800));
assert_clean!(node2); assert_clean!(node2);
assert!(node2.own_addresses().contains(&node2_addr)); assert!(node2.own_addresses().contains(&node2_addr));
@ -158,7 +158,7 @@ fn lost_init1() {
node1.connect("2.3.4.5:6789").unwrap(); node1.connect("2.3.4.5:6789").unwrap();
// Node 1 -> Node 2: Init 0 // Node 1 -> Node 2: Init 0
assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 1800));
assert_clean!(node1); assert_clean!(node1);
// Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers // Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers
@ -179,7 +179,7 @@ fn wrong_magic() {
let node2_addr = addr!("2.3.4.5:6789"); let node2_addr = addr!("2.3.4.5:6789");
node1.connect("2.3.4.5:6789").unwrap(); node1.connect("2.3.4.5:6789").unwrap();
assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 1800));
assert_clean!(node1, node2); assert_clean!(node1, node2);

View File

@ -8,7 +8,6 @@ use std::{
}; };
use super::{ use super::{
config::DEFAULT_PEER_TIMEOUT,
crypto::Crypto, crypto::Crypto,
types::{Error, HeaderMagic, NodeId, Range, NODE_ID_BYTES}, types::{Error, HeaderMagic, NodeId, Range, NODE_ID_BYTES},
util::{bytes_to_hex, Encoder} util::{bytes_to_hex, Encoder}
@ -191,8 +190,7 @@ pub fn decode<'a>(data: &'a mut [u8], magic: HeaderMagic, crypto: &Crypto) -> Re
pos += read; pos += read;
addrs.push(range); addrs.push(range);
} }
let peer_timeout = let peer_timeout = if data.len() >= pos + 2 { Encoder::read_u16(&data[pos..]) } else { 1800 };
if data.len() >= pos + 2 { Encoder::read_u16(&data[pos..]) } else { DEFAULT_PEER_TIMEOUT };
Message::Init(stage, node_id, addrs, peer_timeout) Message::Init(stage, node_id, addrs, peer_timeout)
} }
3 => Message::Close, 3 => Message::Close,

View File

@ -78,7 +78,7 @@ vpncloud(1) -- Peer-to-peer VPN
* `--peer-timeout <secs>`: * `--peer-timeout <secs>`:
Peer timeout in seconds. The peers will exchange information periodically Peer timeout in seconds. The peers will exchange information periodically
and drop peers that are silent for this period of time. [default: `600`] and drop peers that are silent for this period of time. [default: `1800`]
* `--keepalive <secs>`: * `--keepalive <secs>`:
@ -338,7 +338,7 @@ port: 3210
peers: peers:
- remote.machine.foo:3210 - remote.machine.foo:3210
- remote.machine.bar:3210 - remote.machine.bar:3210
peer_timeout: 600 peer_timeout: 1800
mode: normal mode: normal
subnets: subnets:
- 10.0.1.0/24 - 10.0.1.0/24