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,20 +2,23 @@
// 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")]
mod internal {
use std::{io, net::SocketAddrV4}; 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 LEASE_TIME: u32 = 1800;
const DESCRIPTION: &str = "VpnCloud";
const DESCRIPTION: &str = "VpnCloud";
pub struct PortForwarding { pub struct PortForwarding {
pub internal_addr: SocketAddrV4, pub internal_addr: SocketAddrV4,
pub external_addr: SocketAddrV4, pub external_addr: SocketAddrV4,
pub gateway: Gateway, gateway: Gateway,
pub next_extension: Option<Time> pub next_extension: Option<Time>
} }
@ -46,54 +49,75 @@ impl PortForwarding {
return None return None
} }
}; };
// Try to activate the port forwarding if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) {
// - 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); info!("Port-forwarding: external IP is {}", external_ip);
let (external_addr, timeout) = match gateway.add_port( let external_addr = SocketAddrV4::new(external_ip, 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); 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 }; let next_extension =
if timeout > 0 { Some(SystemTimeSource::now() + Time::from(timeout) - 60) } else { None };
Some(PortForwarding { internal_addr, external_addr, gateway, next_extension }) 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) { pub fn check_extend(&mut self) {
@ -130,3 +154,22 @@ impl Drop for PortForwarding {
self.deactivate() self.deactivate()
} }
} }
}
#[cfg(not(feature = "nat"))]
mod internal {
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