summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2021-05-17 12:58:39 +0200
committerDavid Lönnhager <david.l@mullvad.net>2021-05-17 12:58:39 +0200
commit3dde5e0693dc9b6364bf4207e70d936b45ef3230 (patch)
tree7aee2ef848c4181c1b59c7619843dcb9518c767c
parentaf9f0236984d5e41b2517a3fd52c417290094c82 (diff)
parent113f5bdf8f5d9f5aba3bb4543837ccf9c425d0a6 (diff)
downloadmullvadvpn-3dde5e0693dc9b6364bf4207e70d936b45ef3230.tar.xz
mullvadvpn-3dde5e0693dc9b6364bf4207e70d936b45ef3230.zip
Merge branch 'wg-multihop'
-rw-r--r--CHANGELOG.md1
-rw-r--r--mullvad-cli/src/cmds/bridge.rs2
-rw-r--r--mullvad-cli/src/cmds/relay.rs48
-rw-r--r--mullvad-cli/src/format.rs50
-rw-r--r--mullvad-cli/src/location.rs27
-rw-r--r--mullvad-daemon/src/lib.rs28
-rw-r--r--mullvad-daemon/src/relays.rs99
-rw-r--r--mullvad-management-interface/proto/management_interface.proto7
-rw-r--r--mullvad-management-interface/src/types.rs33
-rw-r--r--mullvad-types/src/relay_constraints.rs12
-rw-r--r--talpid-core/src/firewall/linux.rs18
-rw-r--r--talpid-core/src/firewall/macos.rs14
-rw-r--r--talpid-core/src/firewall/windows.rs13
-rw-r--r--talpid-core/src/tunnel/wireguard/config.rs7
-rw-r--r--talpid-core/src/tunnel/wireguard/mod.rs21
-rw-r--r--talpid-types/src/net/mod.rs40
-rw-r--r--talpid-types/src/net/wireguard.rs10
-rw-r--r--windows/winfw/src/winfw/fwcontext.cpp12
-rw-r--r--windows/winfw/src/winfw/fwcontext.h1
-rw-r--r--windows/winfw/src/winfw/winfw.cpp2
-rw-r--r--windows/winfw/src/winfw/winfw.h1
-rw-r--r--windows/winfw/src/winfw/winfw.vcxproj.filters12
22 files changed, 382 insertions, 76 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cee3e30c77..b57b14d27d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ Line wrap the file at 100 chars. Th
- Support WireGuard over TCP for custom VPN relays in the CLI.
- Make app native on Apple Silicon.
- Add DNS options for ad and tracker blocking to the CLI.
+- Support WireGuard multihop using an entry endpoint constraint.
### Changed
- Upgrade OpenVPN from 2.5.0 to 2.5.1.
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
index 1e2a223817..0599c7aef6 100644
--- a/mullvad-cli/src/cmds/bridge.rs
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -218,7 +218,7 @@ impl Bridge {
}
async fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> {
- Self::update_bridge_settings(Some(location::get_constraint(matches)), None).await
+ Self::update_bridge_settings(Some(location::get_constraint_from_args(matches)), None).await
}
async fn handle_set_bridge_provider(matches: &clap::ArgMatches<'_>) -> Result<()> {
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index d9067c7dbb..245eda9e91 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -2,6 +2,7 @@ use crate::{location, new_rpc_client, Command, Error, Result};
use clap::{value_t, values_t};
use itertools::Itertools;
use std::{
+ fmt::Write,
io::{self, BufRead},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
str::FromStr,
@@ -171,6 +172,17 @@ impl Command for Relay {
.default_value("any")
.possible_values(&["any", "4", "6"]),
)
+ .arg(
+ clap::Arg::with_name("entry location")
+ .help("Entry endpoint to use. This can be 'any', 'none', or \
+ any location that is valid with 'set location', \
+ such as 'se got'.")
+ .default_value("none")
+ .long("entry-location")
+ .multiple(true)
+ .min_values(1)
+ .max_values(3),
+ )
)
)
.subcommand(clap::SubCommand::with_name("tunnel-protocol")
@@ -415,7 +427,7 @@ impl Relay {
}
async fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> {
- let location_constraint = location::get_constraint(matches);
+ let location_constraint = location::get_constraint_from_args(matches);
let mut found = false;
if !location_constraint.country.is_empty() {
@@ -517,6 +529,8 @@ impl Relay {
async fn set_wireguard_constraints(&self, matches: &clap::ArgMatches<'_>) -> Result<()> {
let port = parse_port_constraint(matches.value_of("port").unwrap())?;
let ip_version = parse_ip_version_constraint(matches.value_of("ip version").unwrap());
+ let entry_location =
+ parse_entry_location_constraint(matches.values_of("entry location").unwrap());
self.update_constraints(RelaySettingsUpdate {
r#type: Some(relay_settings_update::Type::Normal(
@@ -526,6 +540,7 @@ impl Relay {
ip_version: ip_version.option().map(|protocol| IpVersionConstraint {
protocol: protocol as i32,
}),
+ entry_location,
}),
..Default::default()
},
@@ -719,7 +734,7 @@ impl Relay {
fn format_wireguard_constraints(constraints: Option<&WireguardConstraints>) -> String {
if let Some(constraints) = constraints {
- format!(
+ let mut out = format!(
"{} over {}",
Self::format_port(constraints.port),
Self::format_ip_version(
@@ -728,7 +743,18 @@ impl Relay {
.clone()
.map(|protocol| IpVersion::from_i32(protocol.protocol).unwrap())
)
- )
+ );
+
+ if let Some(ref entry) = constraints.entry_location {
+ write!(
+ &mut out,
+ " (via {})",
+ location::format_location(Some(entry))
+ )
+ .unwrap();
+ }
+
+ out
} else {
"any port over IPv4 or IPv6".to_string()
}
@@ -800,3 +826,19 @@ fn parse_ip_version_constraint(raw_protocol: &str) -> Constraint<IpVersion> {
_ => unreachable!(),
}
}
+
+fn parse_entry_location_constraint<'a, T: Iterator<Item = &'a str>>(
+ mut location: T,
+) -> Option<RelayLocation> {
+ let country = location.next().unwrap();
+
+ if country == "none" {
+ return None;
+ }
+
+ Some(location::get_constraint(
+ country,
+ location.next(),
+ location.next(),
+ ))
+}
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs
index 250bc70ba7..52014076c3 100644
--- a/mullvad-cli/src/format.rs
+++ b/mullvad-cli/src/format.rs
@@ -57,9 +57,10 @@ pub fn print_state(state: &TunnelState) {
}
fn format_endpoint(endpoint: &TunnelEndpoint) -> String {
+ let tunnel_type = TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel protocol");
let mut out = format!(
"{} {} over {}",
- match TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel protocol") {
+ match tunnel_type {
TunnelType::Wireguard => "WireGuard",
TunnelType::Openvpn => "OpenVPN",
},
@@ -69,20 +70,39 @@ fn format_endpoint(endpoint: &TunnelEndpoint) -> String {
),
);
- if let Some(ref proxy) = endpoint.proxy {
- write!(
- &mut out,
- " via {} {} over {}",
- match ProxyType::from_i32(proxy.proxy_type).expect("invalid proxy type") {
- ProxyType::Shadowsocks => "Shadowsocks",
- ProxyType::Custom => "custom bridge",
- },
- proxy.address,
- format_protocol(
- TransportProtocol::from_i32(proxy.protocol).expect("invalid transport protocol")
- ),
- )
- .unwrap();
+ match tunnel_type {
+ TunnelType::Openvpn => {
+ if let Some(ref proxy) = endpoint.proxy {
+ write!(
+ &mut out,
+ " via {} {} over {}",
+ match ProxyType::from_i32(proxy.proxy_type).expect("invalid proxy type") {
+ ProxyType::Shadowsocks => "Shadowsocks",
+ ProxyType::Custom => "custom bridge",
+ },
+ proxy.address,
+ format_protocol(
+ TransportProtocol::from_i32(proxy.protocol)
+ .expect("invalid transport protocol")
+ ),
+ )
+ .unwrap();
+ }
+ }
+ TunnelType::Wireguard => {
+ if let Some(ref entry_endpoint) = endpoint.entry_endpoint {
+ write!(
+ &mut out,
+ " via {} over {}",
+ entry_endpoint.address,
+ format_protocol(
+ TransportProtocol::from_i32(entry_endpoint.protocol)
+ .expect("invalid transport protocol")
+ )
+ )
+ .unwrap();
+ }
+ }
}
out
diff --git a/mullvad-cli/src/location.rs b/mullvad-cli/src/location.rs
index 09f39720ba..cadeaa24fe 100644
--- a/mullvad-cli/src/location.rs
+++ b/mullvad-cli/src/location.rs
@@ -22,11 +22,22 @@ pub fn get_subcommand() -> clap::App<'static, 'static> {
)
}
-pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> RelayLocation {
- let country_original = matches.value_of("country").unwrap();
+pub fn get_constraint_from_args(matches: &clap::ArgMatches<'_>) -> RelayLocation {
+ let country = matches.value_of("country").unwrap();
+ let city = matches.value_of("city");
+ let hostname = matches.value_of("hostname");
+ get_constraint(country, city, hostname)
+}
+
+pub fn get_constraint<T: AsRef<str>>(
+ country: T,
+ city: Option<T>,
+ hostname: Option<T>,
+) -> RelayLocation {
+ let country_original = country.as_ref();
let country = country_original.to_lowercase();
- let city = matches.value_of("city").map(str::to_lowercase);
- let hostname = matches.value_of("hostname").map(str::to_lowercase);
+ let city = city.map(|s| s.as_ref().to_lowercase());
+ let hostname = hostname.map(|s| s.as_ref().to_lowercase());
match (country_original, city, hostname) {
("any", None, None) => RelayLocation::default(),
@@ -81,16 +92,16 @@ pub fn format_providers(providers: &Vec<String>) -> String {
}
}
-fn country_code_validator(code: String) -> std::result::Result<(), String> {
- if code.len() == 2 || code == "any" {
+pub fn country_code_validator<T: AsRef<str>>(code: T) -> std::result::Result<(), String> {
+ if code.as_ref().len() == 2 || code.as_ref() == "any" {
Ok(())
} else {
Err(String::from("Country codes must be two letters, or 'any'."))
}
}
-fn city_code_validator(code: String) -> std::result::Result<(), String> {
- if code.len() == 3 {
+pub fn city_code_validator<T: AsRef<str>>(code: T) -> std::result::Result<(), String> {
+ if code.as_ref().len() == 3 {
Ok(())
} else {
Err(String::from("City codes must be three letters"))
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index d44ce55153..546f06647b 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -116,6 +116,9 @@ pub enum Error {
#[error(display = "No bridge available")]
NoBridgeAvailable,
+ #[error(display = "No matching entry relay was found")]
+ NoEntryRelayAvailable,
+
#[error(display = "No account token is set")]
NoAccountToken,
@@ -1062,6 +1065,28 @@ where
ipv4_gateway,
ipv6_gateway,
} => {
+ let entry_peer = match self.settings.get_relay_settings() {
+ RelaySettings::Normal(ref relay_constraints)
+ if relay_constraints
+ .wireguard_constraints
+ .entry_location
+ .is_some() =>
+ {
+ Some(
+ self.relay_selector
+ .get_tunnel_entry_endpoint(&peer, relay_constraints, retry_attempt)
+ .and_then(|(_relay, mullvad_endpoint)| match mullvad_endpoint {
+ MullvadEndpoint::Wireguard { peer, .. } => Some(peer),
+ _ => None,
+ })
+ .ok_or(Error::NoEntryRelayAvailable)?,
+ )
+ }
+ _ => None,
+ };
+ let exit_peer = entry_peer.as_ref().map(|_| peer.clone());
+ let entry_peer = entry_peer.unwrap_or(peer);
+
let wg_data = self
.account_history
.get(&account_token)
@@ -1079,7 +1104,8 @@ where
Ok(wireguard::TunnelParameters {
connection: wireguard::ConnectionConfig {
tunnel,
- peer,
+ peer: entry_peer,
+ exit_peer,
ipv4_gateway,
ipv6_gateway: Some(ipv6_gateway),
},
diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs
index 54d664b640..62489255ce 100644
--- a/mullvad-daemon/src/relays.rs
+++ b/mullvad-daemon/src/relays.rs
@@ -7,6 +7,7 @@ use futures::{
future::{Fuse, FusedFuture},
FutureExt, SinkExt, StreamExt,
};
+use ipnetwork::IpNetwork;
use log::{debug, error, info, warn};
use mullvad_rpc::{rest::MullvadRestHandle, RelayListProxy};
use mullvad_types::{
@@ -50,6 +51,13 @@ const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60);
const EXPONENTIAL_BACKOFF_INITIAL: Duration = Duration::from_secs(16);
const EXPONENTIAL_BACKOFF_FACTOR: u32 = 8;
+const DEFAULT_WIREGUARD_PORT: u16 = 51820;
+const WIREGUARD_EXIT_CONSTRAINTS: WireguardConstraints = WireguardConstraints {
+ port: Constraint::Only(DEFAULT_WIREGUARD_PORT),
+ ip_version: Constraint::Only(IpVersion::V4),
+ entry_location: None,
+};
+
#[derive(err_derive::Error, Debug)]
#[error(no_from)]
pub enum Error {
@@ -222,8 +230,16 @@ impl RelaySelector {
retry_attempt: u32,
wg_key_exists: bool,
) -> Result<(Relay, MullvadEndpoint), Error> {
+ let mut relay_constraints = relay_constraints.clone();
+ if relay_constraints
+ .wireguard_constraints
+ .entry_location
+ .is_some()
+ {
+ relay_constraints.wireguard_constraints = WIREGUARD_EXIT_CONSTRAINTS;
+ }
let preferred_constraints = self.preferred_constraints(
- relay_constraints,
+ &relay_constraints,
bridge_state,
retry_attempt,
wg_key_exists,
@@ -234,7 +250,8 @@ impl RelaySelector {
retry_attempt
);
Ok((relay, endpoint))
- } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(relay_constraints)
+ } else if let Some((relay, endpoint)) =
+ self.get_tunnel_endpoint_internal(&relay_constraints)
{
debug!(
"Relay matched on second preference for retry attempt {}",
@@ -242,7 +259,7 @@ impl RelaySelector {
);
Ok((relay, endpoint))
} else {
- warn!("No relays matching {}", relay_constraints);
+ warn!("No relays matching {}", &relay_constraints);
Err(Error::NoRelay)
}
}
@@ -310,7 +327,7 @@ impl RelaySelector {
}
Constraint::Only(TunnelType::Wireguard) => {
relay_constraints.wireguard_constraints =
- original_constraints.wireguard_constraints;
+ original_constraints.wireguard_constraints.clone();
// This ensures that if after the first 2 failed attempts the daemon does not
// connect, then afterwards 2 of each 4 successive attempts will try to connect on
// port 53.
@@ -323,6 +340,66 @@ impl RelaySelector {
relay_constraints
}
+ pub fn get_tunnel_entry_endpoint(
+ &mut self,
+ exit_peer: &wireguard::PeerConfig,
+ relay_constraints: &RelayConstraints,
+ retry_attempt: u32,
+ ) -> Option<(Relay, MullvadEndpoint)> {
+ let entry_location = relay_constraints
+ .wireguard_constraints
+ .entry_location
+ .clone()?;
+ let entry_constraints = RelayConstraints {
+ location: entry_location,
+ tunnel_protocol: Constraint::Only(TunnelType::Wireguard),
+ ..relay_constraints.clone()
+ };
+ let entry_constraints =
+ self.preferred_constraints(&entry_constraints, BridgeState::Off, retry_attempt, true);
+
+ let exit_peer_ip = exit_peer.endpoint.ip();
+ let matching_relays: Vec<Relay> = self
+ .parsed_relays
+ .lock()
+ .relays()
+ .iter()
+ .filter(|relay| {
+ relay.active
+ && exit_peer_ip != IpAddr::V4(relay.ipv4_addr_in)
+ && Some(exit_peer_ip) != relay.ipv6_addr_in.map(IpAddr::V6)
+ })
+ .filter_map(|relay| Self::matching_relay(relay, &entry_constraints))
+ .collect();
+
+ let mut endpoint = self
+ .pick_random_relay(&matching_relays)
+ .and_then(|selected_relay| {
+ let endpoint = self.get_random_tunnel(&selected_relay, &entry_constraints);
+ let addr_in = endpoint
+ .as_ref()
+ .map(|endpoint| endpoint.to_endpoint().address.ip())
+ .unwrap_or(IpAddr::from(selected_relay.ipv4_addr_in));
+ info!(
+ "Selected entry relay {} at {}",
+ selected_relay.hostname, addr_in
+ );
+ endpoint.map(|endpoint| (selected_relay.clone(), endpoint))
+ })?;
+
+ match endpoint.1 {
+ MullvadEndpoint::Wireguard { ref mut peer, .. } => {
+ peer.allowed_ips = vec![IpNetwork::from(exit_peer.endpoint.ip())];
+ }
+ _ => {
+ log::error!("BUG: Endpoint must be WireGuard endpoint");
+ return None;
+ }
+ }
+
+ Some(endpoint)
+ }
+
pub fn get_auto_proxy_settings(
&mut self,
bridge_constraints: &InternalBridgeConstraints,
@@ -493,7 +570,7 @@ impl RelaySelector {
relay.tunnels = RelayTunnels {
wireguard: Self::matching_wireguard_tunnels(
&relay.tunnels,
- constraints.wireguard_constraints,
+ &constraints.wireguard_constraints,
),
openvpn: Self::matching_openvpn_tunnels(
&relay.tunnels,
@@ -507,7 +584,7 @@ impl RelaySelector {
relay.tunnels = RelayTunnels {
wireguard: Self::matching_wireguard_tunnels(
&relay.tunnels,
- constraints.wireguard_constraints,
+ &constraints.wireguard_constraints,
),
openvpn: vec![],
};
@@ -580,7 +657,7 @@ impl RelaySelector {
fn matching_wireguard_tunnels(
tunnels: &RelayTunnels,
- constraints: WireguardConstraints,
+ constraints: &WireguardConstraints,
) -> Vec<WireguardEndpointData> {
tunnels
.wireguard
@@ -660,7 +737,7 @@ impl RelaySelector {
.choose(&mut self.rng)
.cloned()
.and_then(|wg_tunnel| {
- self.wg_data_to_endpoint(relay, wg_tunnel, constraints.wireguard_constraints)
+ self.wg_data_to_endpoint(relay, wg_tunnel, &constraints.wireguard_constraints)
})
};
@@ -689,7 +766,7 @@ impl RelaySelector {
&mut self,
relay: &Relay,
data: WireguardEndpointData,
- constraints: WireguardConstraints,
+ constraints: &WireguardConstraints,
) -> Option<MullvadEndpoint> {
let host = self.get_address_for_wireguard_relay(relay, constraints)?;
let port = self.get_port_for_wireguard_relay(&data, constraints)?;
@@ -709,7 +786,7 @@ impl RelaySelector {
fn get_address_for_wireguard_relay(
&mut self,
relay: &Relay,
- constraints: WireguardConstraints,
+ constraints: &WireguardConstraints,
) -> Option<IpAddr> {
match constraints.ip_version {
Constraint::Any | Constraint::Only(IpVersion::V4) => Some(relay.ipv4_addr_in.into()),
@@ -720,7 +797,7 @@ impl RelaySelector {
fn get_port_for_wireguard_relay(
&mut self,
data: &WireguardEndpointData,
- constraints: WireguardConstraints,
+ constraints: &WireguardConstraints,
) -> Option<u16> {
match constraints.port {
Constraint::Any => {
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 41ba8c96a9..faa5a73ffd 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -170,6 +170,7 @@ message TunnelEndpoint {
TransportProtocol protocol = 2;
TunnelType tunnel_type = 3;
ProxyEndpoint proxy = 4;
+ Endpoint entry_endpoint = 5;
}
enum ProxyType {
@@ -177,6 +178,11 @@ enum ProxyType {
CUSTOM = 1;
}
+message Endpoint {
+ string address = 1;
+ TransportProtocol protocol = 2;
+}
+
message ProxyEndpoint {
string address = 1;
TransportProtocol protocol = 2;
@@ -317,6 +323,7 @@ message WireguardConstraints {
// NOTE: optional
uint32 port = 1;
IpVersionConstraint ip_version = 2;
+ RelayLocation entry_location = 3;
}
message CustomRelaySettings {
diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs
index aff3a30789..7d219e0b4a 100644
--- a/mullvad-management-interface/src/types.rs
+++ b/mullvad-management-interface/src/types.rs
@@ -40,6 +40,10 @@ impl From<talpid_types::net::TunnelEndpoint> for TunnelEndpoint {
net::proxy::ProxyType::Custom => i32::from(ProxyType::Custom),
},
}),
+ entry_endpoint: endpoint.entry_endpoint.map(|entry| Endpoint {
+ address: entry.address.to_string(),
+ protocol: i32::from(TransportProtocol::from(entry.protocol)),
+ }),
}
}
}
@@ -311,6 +315,25 @@ impl From<IpVersion> for IpVersionConstraint {
}
}
+impl
+ From<
+ mullvad_types::relay_constraints::Constraint<
+ mullvad_types::relay_constraints::LocationConstraint,
+ >,
+ > for RelayLocation
+{
+ fn from(
+ location: mullvad_types::relay_constraints::Constraint<
+ mullvad_types::relay_constraints::LocationConstraint,
+ >,
+ ) -> Self {
+ location
+ .option()
+ .map(RelayLocation::from)
+ .unwrap_or_default()
+ }
+}
+
impl From<mullvad_types::relay_constraints::LocationConstraint> for RelayLocation {
fn from(location: mullvad_types::relay_constraints::LocationConstraint) -> Self {
use mullvad_types::relay_constraints::LocationConstraint;
@@ -450,6 +473,10 @@ impl From<mullvad_types::relay_constraints::RelaySettings> for RelaySettings {
.option()
.map(IpVersion::from)
.map(IpVersionConstraint::from),
+ entry_location: constraints
+ .wireguard_constraints
+ .entry_location
+ .map(RelayLocation::from),
}),
openvpn_constraints: Some(OpenvpnConstraints {
@@ -772,6 +799,7 @@ impl TryFrom<RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySet
))?
.into(),
},
+ exit_peer: None,
ipv4_gateway,
ipv6_gateway,
})
@@ -881,6 +909,11 @@ impl TryFrom<RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySet
Constraint::Any
},
ip_version: Constraint::from(ip_version),
+ entry_location: constraints.entry_location.map(
+ Constraint::<
+ mullvad_types::relay_constraints::LocationConstraint,
+ >::from,
+ ),
}
}),
openvpn_constraints: settings.openvpn_constraints.map(|constraints| {
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index d32608124d..075e3b816e 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -434,11 +434,12 @@ impl Match<OpenVpnEndpointData> for OpenVpnConstraints {
}
/// [`Constraint`]s applicable to WireGuard relay servers.
-#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(default)]
pub struct WireguardConstraints {
pub port: Constraint<u16>,
pub ip_version: Constraint<IpVersion>,
+ pub entry_location: Option<Constraint<LocationConstraint>>,
}
impl fmt::Display for WireguardConstraints {
@@ -449,8 +450,13 @@ impl fmt::Display for WireguardConstraints {
}
write!(f, " over ")?;
match self.ip_version {
- Constraint::Any => write!(f, "IPv4 or IPv6"),
- Constraint::Only(protocol) => write!(f, "{}", protocol),
+ Constraint::Any => write!(f, "IPv4 or IPv6")?,
+ Constraint::Only(protocol) => write!(f, "{}", protocol)?,
+ }
+ if let Some(Constraint::Only(ref entry)) = self.entry_location {
+ write!(f, " (via {})", entry)
+ } else {
+ Ok(())
}
}
}
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index a0c767d926..755111a31b 100644
--- a/talpid-core/src/firewall/linux.rs
+++ b/talpid-core/src/firewall/linux.rs
@@ -577,7 +577,7 @@ impl<'a> PolicyBatch<'a> {
let allow_lan = match policy {
FirewallPolicy::Connecting {
peer_endpoint,
- tunnel_interface: _,
+ tunnel_interface,
pingable_hosts,
allow_lan,
allowed_endpoint,
@@ -589,6 +589,10 @@ impl<'a> PolicyBatch<'a> {
// Important to block DNS after allow relay rule (so the relay can operate
// over port 53) but before allow LAN (so DNS does not leak to the LAN)
self.add_drop_dns_rule();
+
+ if let Some(tunnel_interface) = tunnel_interface {
+ self.add_allow_tunnel_rules(tunnel_interface)?;
+ }
*allow_lan
}
FirewallPolicy::Connected {
@@ -603,7 +607,7 @@ impl<'a> PolicyBatch<'a> {
// Important to block DNS *before* we allow the tunnel and allow LAN. So DNS
// can't leak to the wrong IPs in the tunnel or on the LAN.
self.add_drop_dns_rule();
- self.add_allow_tunnel_rules(tunnel)?;
+ self.add_allow_tunnel_rules(&tunnel.interface)?;
if *allow_lan {
self.add_block_cve_2019_14899(tunnel);
}
@@ -820,22 +824,22 @@ impl<'a> PolicyBatch<'a> {
}
}
- fn add_allow_tunnel_rules(&mut self, tunnel: &tunnel::TunnelMetadata) -> Result<()> {
+ fn add_allow_tunnel_rules(&mut self, tunnel_interface: &str) -> Result<()> {
self.batch.add(
- &allow_interface_rule(&self.out_chain, Direction::Out, &tunnel.interface[..])?,
+ &allow_interface_rule(&self.out_chain, Direction::Out, tunnel_interface)?,
nftnl::MsgType::Add,
);
self.batch.add(
- &allow_interface_rule(&self.forward_chain, Direction::Out, &tunnel.interface[..])?,
+ &allow_interface_rule(&self.forward_chain, Direction::Out, tunnel_interface)?,
nftnl::MsgType::Add,
);
self.batch.add(
- &allow_interface_rule(&self.in_chain, Direction::In, &tunnel.interface[..])?,
+ &allow_interface_rule(&self.in_chain, Direction::In, tunnel_interface)?,
nftnl::MsgType::Add,
);
let mut interface_rule = Rule::new(&self.forward_chain);
- check_iface(&mut interface_rule, Direction::In, &tunnel.interface)?;
+ check_iface(&mut interface_rule, Direction::In, tunnel_interface)?;
interface_rule.add_expr(&nft_expr!(ct state));
let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits();
interface_rule.add_expr(&nft_expr!(bitwise mask allowed_states, xor 0u32));
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs
index ddc6815990..f3048d61a8 100644
--- a/talpid-core/src/firewall/macos.rs
+++ b/talpid-core/src/firewall/macos.rs
@@ -97,7 +97,7 @@ impl Firewall {
match policy {
FirewallPolicy::Connecting {
peer_endpoint,
- tunnel_interface: _,
+ tunnel_interface,
allow_lan,
allowed_endpoint,
pingable_hosts,
@@ -105,10 +105,16 @@ impl Firewall {
let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?];
rules.push(self.get_allowed_endpoint_rule(allowed_endpoint)?);
rules.extend(self.get_allow_pingable_hosts(&pingable_hosts)?);
+
+ // Important to block DNS after allow relay rule (so the relay can operate
+ // over port 53) but before allow LAN (so DNS does not leak to the LAN)
+ rules.append(&mut self.get_block_dns_rules()?);
+
+ if let Some(tunnel_interface) = tunnel_interface {
+ rules.push(self.get_allow_tunnel_rule(tunnel_interface.as_str())?);
+ }
+
if allow_lan {
- // Important to block DNS after allow relay rule (so the relay can operate
- // over port 53) but before allow LAN (so DNS does not leak to the LAN)
- rules.append(&mut self.get_block_dns_rules()?);
rules.append(&mut self.get_allow_lan_rules()?);
}
Ok(rules)
diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs
index 34e0481610..6a4fdad186 100644
--- a/talpid-core/src/firewall/windows.rs
+++ b/talpid-core/src/firewall/windows.rs
@@ -154,7 +154,7 @@ impl Firewall {
&mut self,
endpoint: &Endpoint,
winfw_settings: &WinFwSettings,
- _tunnel_iface_alias: &Option<String>,
+ tunnel_interface: &Option<String>,
allowed_endpoint: &Endpoint,
pingable_hosts: &Vec<IpAddr>,
relay_client: &Path,
@@ -196,11 +196,21 @@ impl Firewall {
protocol: WinFwProt::from(allowed_endpoint.protocol),
});
+ let interface_wstr = tunnel_interface
+ .as_ref()
+ .map(|alias| WideCString::new(alias.encode_utf16().collect::<Vec<_>>()).unwrap());
+ let interface_wstr_ptr = if let Some(ref wstr) = interface_wstr {
+ wstr.as_ptr()
+ } else {
+ ptr::null()
+ };
+
unsafe {
WinFw_ApplyPolicyConnecting(
winfw_settings,
&winfw_relay,
relay_client.as_ptr(),
+ interface_wstr_ptr,
pingable_hosts.as_ptr(),
winfw_allowed_endpoint.as_ptr(),
)
@@ -436,6 +446,7 @@ mod winfw {
settings: &WinFwSettings,
relay: &WinFwEndpoint,
relayClient: *const libc::wchar_t,
+ tunnelIfaceAlias: *const libc::wchar_t,
pingable_hosts: *const WinFwPingableHosts,
allowed_endpoint: *const WinFwEndpoint,
) -> WinFwPolicyStatus;
diff --git a/talpid-core/src/tunnel/wireguard/config.rs b/talpid-core/src/tunnel/wireguard/config.rs
index eddc51b25b..ae82483c66 100644
--- a/talpid-core/src/tunnel/wireguard/config.rs
+++ b/talpid-core/src/tunnel/wireguard/config.rs
@@ -47,10 +47,13 @@ impl Config {
/// Constructs a Config from parameters
pub fn from_parameters(params: &wireguard::TunnelParameters) -> Result<Config, Error> {
let tunnel = params.connection.tunnel.clone();
- let peer = vec![params.connection.peer.clone()];
+ let mut peers = vec![params.connection.peer.clone()];
+ if let Some(exit_peer) = &params.connection.exit_peer {
+ peers.push(exit_peer.clone());
+ }
Self::new(
tunnel,
- peer,
+ peers,
&params.connection,
&params.options,
&params.generic_options,
diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs
index c51fdf57f5..7585793a01 100644
--- a/talpid-core/src/tunnel/wireguard/mod.rs
+++ b/talpid-core/src/tunnel/wireguard/mod.rs
@@ -3,6 +3,7 @@ use self::config::Config;
use super::tun_provider;
use super::{tun_provider::TunProvider, TunnelEvent, TunnelMetadata};
use crate::routing::{self, RequiredRoute};
+use cfg_if::cfg_if;
use futures::future::abortable;
#[cfg(target_os = "linux")]
use lazy_static::lazy_static;
@@ -342,9 +343,25 @@ impl WireguardMonitor {
}
fn get_routes(iface_name: &str, config: &Config) -> HashSet<RequiredRoute> {
+ #[cfg(target_os = "linux")]
+ use netlink_packet_route::rtnl::constants::RT_TABLE_MAIN;
+
let node = routing::Node::device(iface_name.to_string());
let mut routes: HashSet<RequiredRoute> = Self::get_tunnel_routes(config)
- .map(|network| RequiredRoute::new(network, node.clone()))
+ .map(|network| {
+ cfg_if! {
+ if #[cfg(target_os = "linux")] {
+ if network.prefix() == 0 {
+ RequiredRoute::new(network, node.clone())
+ } else {
+ RequiredRoute::new(network, node.clone())
+ .table(u32::from(RT_TABLE_MAIN))
+ }
+ } else {
+ RequiredRoute::new(network, node.clone())
+ }
+ }
+ })
.collect();
// route endpoints with specific routes
@@ -360,8 +377,6 @@ impl WireguardMonitor {
// using `mullvad-exclude`
#[cfg(target_os = "linux")]
{
- use netlink_packet_route::rtnl::constants::RT_TABLE_MAIN;
-
routes.insert(
RequiredRoute::new(
ipnetwork::Ipv4Network::from(config.ipv4_gateway).into(),
diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs
index adea4fd512..c168a58f4e 100644
--- a/talpid-types/src/net/mod.rs
+++ b/talpid-types/src/net/mod.rs
@@ -28,11 +28,19 @@ impl TunnelParameters {
tunnel_type: TunnelType::OpenVpn,
endpoint: params.config.endpoint,
proxy: params.proxy.as_ref().map(|proxy| proxy.get_endpoint()),
+ entry_endpoint: None,
},
TunnelParameters::Wireguard(params) => TunnelEndpoint {
tunnel_type: TunnelType::Wireguard,
- endpoint: params.connection.get_endpoint(),
+ endpoint: params
+ .connection
+ .get_exit_endpoint()
+ .unwrap_or(params.connection.get_endpoint()),
proxy: None,
+ entry_endpoint: params
+ .connection
+ .get_exit_endpoint()
+ .map(|_| params.connection.get_endpoint()),
},
}
}
@@ -49,10 +57,11 @@ impl TunnelParameters {
}
}
- pub fn get_proxy_endpoint(&self) -> Option<openvpn::ProxySettings> {
+ // Returns the exit endpoint, if it differs from the next hop endpoint
+ pub fn get_exit_hop_endpoint(&self) -> Option<Endpoint> {
match self {
- TunnelParameters::OpenVpn(params) => params.proxy.clone(),
- _ => None,
+ TunnelParameters::OpenVpn(_params) => None,
+ TunnelParameters::Wireguard(params) => params.connection.get_exit_endpoint(),
}
}
@@ -108,17 +117,28 @@ pub struct TunnelEndpoint {
pub tunnel_type: TunnelType,
#[cfg_attr(target_os = "android", jnix(skip))]
pub proxy: Option<proxy::ProxyEndpoint>,
+ #[cfg_attr(target_os = "android", jnix(skip))]
+ pub entry_endpoint: Option<Endpoint>,
}
impl fmt::Display for TunnelEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{} - {}", self.tunnel_type, self.endpoint)?;
- if let Some(ref proxy) = self.proxy {
- write!(
- f,
- " via {} {} over {}",
- proxy.proxy_type, proxy.endpoint.address, proxy.endpoint.protocol
- )?;
+ match self.tunnel_type {
+ TunnelType::OpenVpn => {
+ if let Some(ref proxy) = self.proxy {
+ write!(
+ f,
+ " via {} {} over {}",
+ proxy.proxy_type, proxy.endpoint.address, proxy.endpoint.protocol
+ )?;
+ }
+ }
+ TunnelType::Wireguard => {
+ if let Some(ref entry_endpoint) = self.entry_endpoint {
+ write!(f, " via {}", entry_endpoint)?;
+ }
+ }
}
Ok(())
}
diff --git a/talpid-types/src/net/wireguard.rs b/talpid-types/src/net/wireguard.rs
index 60a0a29a6d..1c92052762 100644
--- a/talpid-types/src/net/wireguard.rs
+++ b/talpid-types/src/net/wireguard.rs
@@ -25,6 +25,7 @@ pub struct TunnelParameters {
pub struct ConnectionConfig {
pub tunnel: TunnelConfig,
pub peer: PeerConfig,
+ pub exit_peer: Option<PeerConfig>,
/// Gateway used by the tunnel (a private address).
pub ipv4_gateway: Ipv4Addr,
pub ipv6_gateway: Option<Ipv6Addr>,
@@ -37,11 +38,18 @@ impl ConnectionConfig {
protocol: self.peer.protocol,
}
}
+
+ pub fn get_exit_endpoint(&self) -> Option<Endpoint> {
+ self.exit_peer.as_ref().map(|peer| Endpoint {
+ address: peer.endpoint,
+ protocol: peer.protocol,
+ })
+ }
}
#[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug, Hash)]
pub struct PeerConfig {
- /// Public key corresponding to the private key in [`TunnelConfig`].
+ /// Peer's public key.
pub public_key: PublicKey,
/// Addresses that may be routed to the peer. Use `0.0.0.0/0` to route everything.
pub allowed_ips: Vec<IpNetwork>,
diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp
index 793f8c917d..d89437d699 100644
--- a/windows/winfw/src/winfw/fwcontext.cpp
+++ b/windows/winfw/src/winfw/fwcontext.cpp
@@ -178,6 +178,7 @@ bool FwContext::applyPolicyConnecting
const WinFwSettings &settings,
const WinFwEndpoint &relay,
const std::wstring &relayClient,
+ const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<PingableHosts> &pingableHosts,
const std::optional<WinFwEndpoint> &allowedEndpoint
)
@@ -193,6 +194,17 @@ bool FwContext::applyPolicyConnecting
AppendAllowedEndpointRules(ruleset, allowedEndpoint.value());
}
+ if (tunnelInterfaceAlias.has_value())
+ {
+ ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>(
+ *tunnelInterfaceAlias
+ ));
+
+ ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>(
+ *tunnelInterfaceAlias
+ ));
+ }
+
//
// Permit pinging the gateway inside the tunnel.
//
diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h
index bbbb1de485..cff3e3c823 100644
--- a/windows/winfw/src/winfw/fwcontext.h
+++ b/windows/winfw/src/winfw/fwcontext.h
@@ -35,6 +35,7 @@ public:
const WinFwSettings &settings,
const WinFwEndpoint &relay,
const std::wstring &relayClient,
+ const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<PingableHosts> &pingableHosts,
const std::optional<WinFwEndpoint> &allowedEndpoint
);
diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp
index a3ad1737ac..119edc4ca6 100644
--- a/windows/winfw/src/winfw/winfw.cpp
+++ b/windows/winfw/src/winfw/winfw.cpp
@@ -260,6 +260,7 @@ WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
const wchar_t *relayClient,
+ const wchar_t *tunnelInterfaceAlias,
const PingableHosts *pingableHosts,
const WinFwEndpoint *allowedEndpoint
)
@@ -290,6 +291,7 @@ WinFw_ApplyPolicyConnecting(
*settings,
*relay,
relayClient,
+ tunnelInterfaceAlias != nullptr ? std::make_optional(tunnelInterfaceAlias) : std::nullopt,
ConvertPingableHosts(pingableHosts),
MakeOptional(allowedEndpoint)
) ? WINFW_POLICY_STATUS_SUCCESS : WINFW_POLICY_STATUS_GENERAL_FAILURE;
diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h
index 308bb32645..5065582e29 100644
--- a/windows/winfw/src/winfw/winfw.h
+++ b/windows/winfw/src/winfw/winfw.h
@@ -158,6 +158,7 @@ WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
const wchar_t *relayClient,
+ const wchar_t *tunnelInterfaceAlias,
const PingableHosts *pingableHosts,
const WinFwEndpoint *allowedEndpoint
);
diff --git a/windows/winfw/src/winfw/winfw.vcxproj.filters b/windows/winfw/src/winfw/winfw.vcxproj.filters
index 7a2aa85487..bb266aa8ff 100644
--- a/windows/winfw/src/winfw/winfw.vcxproj.filters
+++ b/windows/winfw/src/winfw/winfw.vcxproj.filters
@@ -55,15 +55,15 @@
<ClCompile Include="rules\shared.cpp">
<Filter>rules</Filter>
</ClCompile>
- <ClCompile Include="rules\multi\permitvpnrelay.cpp">
- <Filter>rules\multi</Filter>
- </ClCompile>
<ClCompile Include="rules\persistent\blockall.cpp">
<Filter>rules\persistent</Filter>
</ClCompile>
<ClCompile Include="rules\baseline\permitendpoint.cpp">
<Filter>rules\baseline</Filter>
</ClCompile>
+ <ClCompile Include="rules\multi\permitvpnrelay.cpp">
+ <Filter>rules\multi</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
@@ -129,15 +129,15 @@
<ClInclude Include="rules\shared.h">
<Filter>rules</Filter>
</ClInclude>
- <ClInclude Include="rules\multi\permitvpnrelay.h">
- <Filter>rules\multi</Filter>
- </ClInclude>
<ClInclude Include="rules\persistent\blockall.h">
<Filter>rules\persistent</Filter>
</ClInclude>
<ClInclude Include="rules\baseline\permitendpoint.h">
<Filter>rules\baseline</Filter>
</ClInclude>
+ <ClInclude Include="rules\multi\permitvpnrelay.h">
+ <Filter>rules\multi</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="rules">