Compare commits

..

3 Commits

Author SHA1 Message Date
Dennis Schwerdel eb620781a8 Added feature to disable special NAT support 2019-12-22 23:21:47 +01:00
Dennis Schwerdel 54a2240f34 Reduced timeout to 5min to work better with NAT 2019-12-22 23:03:45 +01:00
Dennis Schwerdel 10ebd08dad Improved port forwarding on quirky routers 2019-12-22 22:51:40 +01:00
10 changed files with 165 additions and 124 deletions

View File

@ -2,6 +2,12 @@
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 = "0.9" igd = { version = "0.9", optional = true }
siphasher = "0.3" siphasher = "0.3"
daemonize = "0.4" daemonize = "0.4"
ring = "0.16" ring = "0.16"
@ -38,8 +38,9 @@ pkg-config = "0.3"
tempfile = "3" tempfile = "3"
[features] [features]
default = [] default = ["nat"]
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: 1800 #peer_timeout: 600
# 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, config::{Config, DEFAULT_PEER_TIMEOUT},
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(1800) self.peers.iter().map(|p| p.1.peer_timeout).min().unwrap_or(DEFAULT_PEER_TIMEOUT)
} }
#[inline] #[inline]
@ -257,12 +257,7 @@ 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 = if socket4.detect_nat() && config.get_keepalive() > 120 { let update_freq = config.get_keepalive() as u16;
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,6 +16,7 @@ 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)]
@ -59,7 +60,7 @@ impl Default for Config {
magic: None, magic: None,
port: 3210, port: 3210,
peers: vec![], peers: vec![],
peer_timeout: 1800, peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
keepalive: None, keepalive: None,
beacon_store: None, beacon_store: None,
beacon_load: None, beacon_load: None,
@ -296,7 +297,7 @@ port: 3210
peers: peers:
- remote.machine.foo:3210 - remote.machine.foo:3210
- remote.machine.bar:3210 - remote.machine.bar:3210
peer_timeout: 1800 peer_timeout: 600
keepalive: 840 keepalive: 840
dst_timeout: 300 dst_timeout: 300
beacon_store: /run/vpncloud.beacon.out beacon_store: /run/vpncloud.beacon.out
@ -322,7 +323,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(1800), peer_timeout: Some(600),
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()),
@ -352,7 +353,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(1800), peer_timeout: Some(600),
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()),
@ -377,7 +378,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: 1800, peer_timeout: 600,
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::{get_internal_ip, MockTimeSource, Time, TimeSource}; use super::util::{MockTimeSource, Time, TimeSource};
use net2::UdpBuilder; use net2::UdpBuilder;
@ -17,7 +17,6 @@ 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 {
@ -40,9 +39,6 @@ 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! {
@ -129,7 +125,4 @@ 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,131 +2,174 @@
// 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)
use std::{io, net::SocketAddrV4}; #[cfg(feature = "nat")]
mod internal {
use igd::*; use std::{io, net::SocketAddrV4};
use super::util::{get_internal_ip, SystemTimeSource, Time, TimeSource}; use igd::{search_gateway, AddAnyPortError, AddPortError, Gateway, PortMappingProtocol, SearchError};
const LEASE_TIME: u32 = 1800; use crate::util::{get_internal_ip, SystemTimeSource, Time, TimeSource};
const DESCRIPTION: &str = "VpnCloud";
const LEASE_TIME: u32 = 1800;
pub struct PortForwarding { const DESCRIPTION: &str = "VpnCloud";
pub internal_addr: SocketAddrV4,
pub external_addr: SocketAddrV4,
pub gateway: Gateway,
pub next_extension: Option<Time>
}
impl PortForwarding { pub struct PortForwarding {
pub fn new(port: u16) -> Option<Self> { pub internal_addr: SocketAddrV4,
// Get the gateway pub external_addr: SocketAddrV4,
let gateway = match search_gateway(Default::default()) { gateway: Gateway,
Ok(gateway) => gateway, pub next_extension: Option<Time>
Err(err) => { }
if let SearchError::IoError(ref err) = err {
if err.kind() == io::ErrorKind::WouldBlock { impl PortForwarding {
// Why this code? pub fn new(port: u16) -> Option<Self> {
warn!("Port-forwarding: no router found"); // Get the gateway
return None 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
} }
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
} }
}; }
info!("Port-forwarding: found router at {}", gateway.addr);
let internal_addr = SocketAddrV4::new(get_internal_ip(), port); fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
// Query the external address if let Ok(a) = Self::get_forwarding(gateway, addr, port) {
let external_ip = match gateway.get_external_ip() { return Ok(a)
Ok(ip) => ip,
Err(err) => {
error!("Port-forwarding: failed to obtain external IP: {}", err);
return None
} }
}; if let Ok(a) = Self::get_forwarding(gateway, addr, 0) {
// Try to activate the port forwarding return Ok(a)
// - First with external port = internal port and timeout }
// - If the port is used, request any port for i in 1..5 {
// - If timeout is denied, try permanent forwarding if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) {
info!("Port-forwarding: external IP is {}", external_ip); return Ok(a)
let (external_addr, timeout) = match gateway.add_port( }
PortMappingProtocol::UDP, }
internal_addr.port(), for _ in 0..5 {
internal_addr, if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) {
LEASE_TIME, return Ok(a)
DESCRIPTION }
) { }
Ok(()) => (SocketAddrV4::new(external_ip, internal_addr.port()), LEASE_TIME), Err(())
Err(AddPortError::PortInUse) => { }
match gateway.add_any_port(PortMappingProtocol::UDP, internal_addr, LEASE_TIME, DESCRIPTION) {
Ok(port) => (SocketAddrV4::new(external_ip, port), LEASE_TIME), 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) => { Err(AddAnyPortError::OnlyPermanentLeasesSupported) => {
match gateway.add_any_port(PortMappingProtocol::UDP, internal_addr, 0, DESCRIPTION) { match gateway.add_any_port(PortMappingProtocol::UDP, addr, 0, DESCRIPTION) {
Ok(port) => (SocketAddrV4::new(external_ip, port), 0), Ok(port) => Ok((port, 0)),
Err(err) => { Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err); error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None Err(())
} }
} }
} }
Err(err) => { Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err); error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None Err(())
} }
} }
} } else {
Err(AddPortError::OnlyPermanentLeasesSupported) => { match gateway.add_port(PortMappingProtocol::UDP, port, addr, LEASE_TIME, DESCRIPTION) {
match gateway.add_port(PortMappingProtocol::UDP, internal_addr.port(), internal_addr, 0, DESCRIPTION) { Ok(()) => Ok((port, LEASE_TIME)),
Ok(()) => (SocketAddrV4::new(external_ip, internal_addr.port()), 0), 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) => { Err(err) => {
error!("Port-forwarding: failed to activate port forwarding: {}", err); error!("Port-forwarding: failed to activate port forwarding: {}", err);
return None Err(())
} }
} }
} }
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 })
}
pub fn check_extend(&mut self) { pub fn check_extend(&mut self) {
if let Some(deadline) = self.next_extension { if let Some(deadline) = self.next_extension {
if deadline > SystemTimeSource::now() { if deadline > SystemTimeSource::now() {
return
}
} else {
return return
} }
} else { match self.gateway.add_port(
return 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);
}
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);
} }
fn deactivate(&self) { impl Drop for PortForwarding {
match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) { fn drop(&mut self) {
Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"), self.deactivate()
Err(err) => error!("Port-forwarding: failed to deactivate port forwarding: {}", err)
} }
} }
} }
impl Drop for PortForwarding { #[cfg(not(feature = "nat"))]
fn drop(&mut self) { mod internal {
self.deactivate() pub struct PortForwarding;
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![], 1800)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600));
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![], 1800)); assert_message4!(node2, node2_addr, node1, node1_addr, Message::Init(1, node2.node_id(), vec![], 600));
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![], 1800)); assert_message4!(node1, node1_addr, node1, node1_addr, Message::Init(0, node1.node_id(), vec![], 600));
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![], 1800)); assert_message4!(node2, node2_addr, node2, node2_addr, Message::Init(0, node2.node_id(), vec![], 600));
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![], 1800)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600));
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![], 1800)); assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![], 600));
assert_clean!(node1, node2); assert_clean!(node1, node2);

View File

@ -8,6 +8,7 @@ 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}
@ -190,7 +191,8 @@ 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 = if data.len() >= pos + 2 { Encoder::read_u16(&data[pos..]) } else { 1800 }; let peer_timeout =
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: `1800`] and drop peers that are silent for this period of time. [default: `600`]
* `--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: 1800 peer_timeout: 600
mode: normal mode: normal
subnets: subnets:
- 10.0.1.0/24 - 10.0.1.0/24