summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md8
-rw-r--r--mullvad-cli/src/cmds/relay.rs2
-rw-r--r--mullvad-daemon/src/lib.rs234
-rw-r--r--mullvad-daemon/src/relays.rs78
-rw-r--r--mullvad-types/src/custom_tunnel.rs12
-rw-r--r--mullvad-types/src/relay_constraints.rs82
-rw-r--r--mullvad-types/src/settings.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/blocked_state.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs29
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs97
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs41
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs26
-rw-r--r--talpid-types/src/net.rs27
14 files changed, 415 insertions, 231 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49b695afb9..d355213654 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,15 +23,17 @@ Line wrap the file at 100 chars. Th
## [Unreleased]
+### Added
+- Fall back and try to connect over TCP port 443 if protocol is set to automatic and two attempts
+ with UDP fail in a row. If that also fails, alternate between UDP and TCP with random ports.
+
### Fixed
- Place Mssfix setting inside scrollable area
+- Pick new random relay for each reconnect attempt instead of just retrying with the same one.
#### Linux
- The app will have it's window resized correctly when display scaling settings are changed. This
should also fix bad window behaviour on startup.
-
-### Fixed
-#### Linux
- Fixed systemd-resolved DNS management.
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index eda57070fb..e056505069 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -205,7 +205,7 @@ impl Relay {
fn get(&self) -> Result<()> {
let mut rpc = new_rpc_client()?;
let constraints = rpc.get_settings()?.get_relay_settings();
- println!("Current constraints: {:#?}", constraints);
+ println!("Current constraints: {}", constraints);
Ok(())
}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 063e68d56e..36306f1d76 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -44,13 +44,12 @@ mod relays;
mod rpc_uniqueness_check;
use error_chain::ChainedError;
-use futures::sync::mpsc::UnboundedSender;
-use futures::{Future, Sink};
-use jsonrpc_core::futures::sync::oneshot::{self, Sender as OneshotSender};
-
+use futures::{
+ sync::{mpsc::UnboundedSender, oneshot},
+ Future, Sink,
+};
use management_interface::{BoxFuture, ManagementCommand, ManagementInterfaceServer};
use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle};
-
use mullvad_types::{
account::{AccountData, AccountToken},
location::GeoIpLocation,
@@ -60,17 +59,12 @@ use mullvad_types::{
states::TargetState,
version::{AppVersion, AppVersionInfo},
};
-
use std::{mem, path::PathBuf, sync::mpsc, thread, time::Duration};
-
use talpid_core::{
mpsc::IntoSender,
- tunnel_state_machine::{self, TunnelCommand, TunnelParameters},
-};
-use talpid_types::{
- net::TunnelEndpoint,
- tunnel::{BlockReason, TunnelStateTransition},
+ tunnel_state_machine::{self, TunnelCommand, TunnelParameters, TunnelParametersGenerator},
};
+use talpid_types::tunnel::{BlockReason, TunnelStateTransition};
error_chain!{
@@ -97,6 +91,8 @@ type SyncUnboundedSender<T> = ::futures::sink::Wait<UnboundedSender<T>>;
pub enum DaemonEvent {
/// Tunnel has changed state.
TunnelStateTransition(TunnelStateTransition),
+ /// Request from the `MullvadTunnelParametersGenerator` to obtain a new relay.
+ GenerateTunnelParameters(mpsc::Sender<TunnelParameters>, u32),
/// An event coming from the JSONRPC-2.0 management interface.
ManagementInterfaceEvent(ManagementCommand),
/// Triggered if the server hosting the JSONRPC-2.0 management interface dies unexpectedly.
@@ -178,7 +174,7 @@ pub struct Daemon {
https_handle: mullvad_rpc::rest::RequestSender,
tokio_remote: tokio_core::reactor::Remote,
relay_selector: relays::RelaySelector,
- current_relay: Option<Relay>,
+ last_generated_relay: Option<Relay>,
version: String,
}
@@ -214,8 +210,10 @@ impl Daemon {
let settings = Settings::load().chain_err(|| "Unable to read settings")?;
let (tx, rx) = mpsc::channel();
+ let tunnel_parameters_generator = MullvadTunnelParametersGenerator { tx: tx.clone() };
let tunnel_command_tx = tunnel_state_machine::spawn(
settings.get_allow_lan(),
+ tunnel_parameters_generator,
log_dir,
resource_dir,
cache_dir.clone(),
@@ -245,7 +243,7 @@ impl Daemon {
https_handle,
tokio_remote,
relay_selector,
- current_relay: None,
+ last_generated_relay: None,
version,
})
}
@@ -292,11 +290,9 @@ impl Daemon {
/// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a
/// shutdown event is received.
pub fn run(mut self) -> Result<()> {
- if self.settings.get_auto_connect() {
+ if self.settings.get_auto_connect() && self.settings.get_account_token().is_some() {
info!("Automatically connecting since auto-connect is turned on");
- if self.set_target_state(TargetState::Secured).is_err() {
- warn!("Aborting auto-connect since no account token is set");
- }
+ self.set_target_state(TargetState::Secured);
}
while let Ok(event) = self.rx.recv() {
self.handle_event(event)?;
@@ -313,6 +309,9 @@ impl Daemon {
TunnelStateTransition(transition) => {
Ok(self.handle_tunnel_state_transition(transition))
}
+ GenerateTunnelParameters(tunnel_parameters_tx, retry_attempt) => {
+ Ok(self.handle_generate_tunnel_parameters(tunnel_parameters_tx, retry_attempt))
+ }
ManagementInterfaceEvent(event) => Ok(self.handle_management_interface_event(event)),
ManagementInterfaceExited => self.handle_management_interface_exited(),
TriggerShutdown => Ok(self.handle_trigger_shutdown_event()),
@@ -326,10 +325,7 @@ impl Daemon {
debug!("New tunnel state: {:?}", tunnel_state);
match tunnel_state {
- Disconnected => {
- self.state.disconnected();
- self.current_relay = None;
- }
+ Disconnected => self.state.disconnected(),
Blocked(ref reason) => {
info!("Blocking all network connections, reason: {}", reason);
@@ -346,6 +342,44 @@ impl Daemon {
.notify_new_state(tunnel_state);
}
+ fn handle_generate_tunnel_parameters(
+ &mut self,
+ tunnel_parameters_tx: mpsc::Sender<TunnelParameters>,
+ retry_attempt: u32,
+ ) {
+ let result = self
+ .settings
+ .get_account_token()
+ .ok_or(Error::from("No account token configured"))
+ .map(|account_token| {
+ match self.settings.get_relay_settings() {
+ RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay
+ .to_tunnel_endpoint()
+ .chain_err(|| "Custom tunnel endpoint could not be resolved"),
+ RelaySettings::Normal(constraints) => self
+ .relay_selector
+ .get_tunnel_endpoint(&constraints, retry_attempt)
+ .chain_err(|| "No valid relay servers match the current settings")
+ .map(|(relay, endpoint)| {
+ self.last_generated_relay = Some(relay);
+ endpoint
+ }),
+ }
+ .map(|endpoint| {
+ tunnel_parameters_tx
+ .send(TunnelParameters {
+ endpoint,
+ options: self.settings.get_tunnel_options().clone(),
+ username: account_token,
+ })
+ .map_err(|_| Error::from("Tunnel parameters receiver stopped listening"))
+ })
+ });
+ if let Err(error) = result {
+ error!("{}", error.display_chain());
+ }
+ }
+
fn schedule_reconnect(&mut self, delay: Duration) {
let tunnel_command_tx = self.tx.clone();
let (tx, rx) = mpsc::channel();
@@ -393,49 +427,59 @@ impl Daemon {
fn on_set_target_state(
&mut self,
- tx: OneshotSender<::std::result::Result<(), ()>>,
+ tx: oneshot::Sender<::std::result::Result<(), ()>>,
new_target_state: TargetState,
) {
if self.state.is_running() {
- Self::oneshot_send(tx, self.set_target_state(new_target_state), "targe state");
+ self.set_target_state(new_target_state);
} else {
warn!("Ignoring target state change request due to shutdown");
- Self::oneshot_send(tx, Ok(()), "targe state");
}
+ Self::oneshot_send(tx, Ok(()), "targe state");
}
- fn on_get_state(&self, tx: OneshotSender<TunnelStateTransition>) {
+ fn on_get_state(&self, tx: oneshot::Sender<TunnelStateTransition>) {
Self::oneshot_send(tx, self.tunnel_state.clone(), "current state");
}
- fn on_get_current_location(&self, tx: OneshotSender<GeoIpLocation>) {
- if let Some(ref relay) = self.current_relay {
- let location = relay.location.as_ref().cloned().unwrap();
- let hostname = relay.hostname.clone();
- let geo_ip_location = GeoIpLocation {
- country: location.country,
- city: Some(location.city),
- latitude: location.latitude,
- longitude: location.longitude,
- mullvad_exit_ip: true,
- hostname: Some(hostname),
- };
- Self::oneshot_send(tx, geo_ip_location, "current location");
- } else {
- let https_handle = self.https_handle.clone();
- self.tokio_remote.spawn(move |_| {
- geoip::send_location_request(https_handle)
- .map(move |location| Self::oneshot_send(tx, location, "current location"))
- .map_err(|e| {
- warn!("Unable to fetch GeoIP location: {}", e.display_chain());
- })
- });
+ fn on_get_current_location(&self, tx: oneshot::Sender<GeoIpLocation>) {
+ use self::TunnelStateTransition::*;
+ match self.tunnel_state {
+ Disconnected => {
+ let https_handle = self.https_handle.clone();
+ self.tokio_remote.spawn(move |_| {
+ geoip::send_location_request(https_handle)
+ .map(move |location| Self::oneshot_send(tx, location, "current location"))
+ .map_err(|e| {
+ warn!("Unable to fetch GeoIP location: {}", e.display_chain());
+ })
+ });
+ }
+ Connecting | Connected | Disconnecting(..) => {
+ if let Some(ref relay) = self.last_generated_relay {
+ let location = relay.location.as_ref().cloned().unwrap();
+ let hostname = relay.hostname.clone();
+ let geo_ip_location = GeoIpLocation {
+ country: location.country,
+ city: Some(location.city),
+ latitude: location.latitude,
+ longitude: location.longitude,
+ mullvad_exit_ip: true,
+ hostname: Some(hostname),
+ };
+ Self::oneshot_send(tx, geo_ip_location, "current location");
+ }
+ }
+ Blocked(..) => {
+ // We are not online at all at this stage. Return error.
+ mem::drop(tx);
+ }
}
}
fn on_get_account_data(
&mut self,
- tx: OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>,
+ tx: oneshot::Sender<BoxFuture<AccountData, mullvad_rpc::Error>>,
account_token: AccountToken,
) {
let rpc_call = self
@@ -445,12 +489,12 @@ impl Daemon {
Self::oneshot_send(tx, Box::new(rpc_call), "account data")
}
- fn on_get_relay_locations(&mut self, tx: OneshotSender<RelayList>) {
+ fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) {
Self::oneshot_send(tx, self.relay_selector.get_locations(), "relay locations");
}
- fn on_set_account(&mut self, tx: OneshotSender<()>, account_token: Option<String>) {
+ fn on_set_account(&mut self, tx: oneshot::Sender<()>, account_token: Option<String>) {
let account_token_cleared = account_token.is_none();
let save_result = self.settings.set_account_token(account_token);
@@ -462,7 +506,7 @@ impl Daemon {
.notify_settings(&self.settings);
if account_token_cleared {
info!("Disconnecting because account token was cleared");
- let _ = self.set_target_state(TargetState::Unsecured);
+ self.set_target_state(TargetState::Unsecured);
} else {
info!("Initiating tunnel restart because the account token changed");
self.reconnect_tunnel();
@@ -475,7 +519,7 @@ impl Daemon {
fn on_get_version_info(
&mut self,
- tx: OneshotSender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>,
+ tx: oneshot::Sender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>,
) {
let fut = self
.version_proxy
@@ -488,11 +532,11 @@ impl Daemon {
Self::oneshot_send(tx, Box::new(fut), "get_version_info response");
}
- fn on_get_current_version(&mut self, tx: OneshotSender<AppVersion>) {
+ fn on_get_current_version(&mut self, tx: oneshot::Sender<AppVersion>) {
Self::oneshot_send(tx, self.version.clone(), "get_current_version response");
}
- fn on_update_relay_settings(&mut self, tx: OneshotSender<()>, update: RelaySettingsUpdate) {
+ fn on_update_relay_settings(&mut self, tx: oneshot::Sender<()>, update: RelaySettingsUpdate) {
let save_result = self.settings.update_relay_settings(update);
match save_result.chain_err(|| "Unable to save settings") {
Ok(settings_changed) => {
@@ -508,7 +552,7 @@ impl Daemon {
}
}
- fn on_set_allow_lan(&mut self, tx: OneshotSender<()>, allow_lan: bool) {
+ fn on_set_allow_lan(&mut self, tx: oneshot::Sender<()>, allow_lan: bool) {
let save_result = self.settings.set_allow_lan(allow_lan);
match save_result.chain_err(|| "Unable to save settings") {
Ok(settings_changed) => {
@@ -523,7 +567,7 @@ impl Daemon {
}
}
- fn on_set_auto_connect(&mut self, tx: OneshotSender<()>, auto_connect: bool) {
+ fn on_set_auto_connect(&mut self, tx: oneshot::Sender<()>, auto_connect: bool) {
let save_result = self.settings.set_auto_connect(auto_connect);
match save_result.chain_err(|| "Unable to save settings") {
Ok(settings_changed) => {
@@ -537,7 +581,7 @@ impl Daemon {
}
}
- fn on_set_openvpn_mssfix(&mut self, tx: OneshotSender<()>, mssfix_arg: Option<u16>) {
+ fn on_set_openvpn_mssfix(&mut self, tx: oneshot::Sender<()>, mssfix_arg: Option<u16>) {
let save_result = self.settings.set_openvpn_mssfix(mssfix_arg);
match save_result.chain_err(|| "Unable to save settings") {
Ok(settings_changed) => {
@@ -551,7 +595,7 @@ impl Daemon {
}
}
- fn on_set_enable_ipv6(&mut self, tx: OneshotSender<()>, enable_ipv6: bool) {
+ fn on_set_enable_ipv6(&mut self, tx: oneshot::Sender<()>, enable_ipv6: bool) {
let save_result = self.settings.set_enable_ipv6(enable_ipv6);
match save_result.chain_err(|| "Unable to save settings") {
Ok(settings_changed) => {
@@ -567,11 +611,11 @@ impl Daemon {
}
}
- fn on_get_settings(&self, tx: OneshotSender<Settings>) {
+ fn on_get_settings(&self, tx: oneshot::Sender<Settings>) {
Self::oneshot_send(tx, self.settings.clone(), "get_settings response");
}
- fn oneshot_send<T>(tx: OneshotSender<T>, t: T, msg: &'static str) {
+ fn oneshot_send<T>(tx: oneshot::Sender<T>, t: T, msg: &'static str) {
if let Err(_) = tx.send(t) {
warn!("Unable to send {} to management interface client", msg);
}
@@ -589,45 +633,19 @@ impl Daemon {
/// Set the target state of the client. If it changed trigger the operations needed to
/// progress towards that state.
/// Returns an error if trying to set secured state, but no account token is present.
- fn set_target_state(&mut self, new_state: TargetState) -> ::std::result::Result<(), ()> {
+ fn set_target_state(&mut self, new_state: TargetState) {
if new_state != self.target_state || self.tunnel_state.is_blocked() {
debug!("Target state {:?} => {:?}", self.target_state, new_state);
self.target_state = new_state;
match self.target_state {
- TargetState::Secured => match self.settings.get_account_token() {
- Some(account_token) => self.connect_tunnel(account_token),
- None => {
- self.set_target_state(TargetState::Unsecured)?;
- return Err(());
- }
- },
+ TargetState::Secured => self.connect_tunnel(),
TargetState::Unsecured => self.disconnect_tunnel(),
}
}
- Ok(())
}
- fn connect_tunnel(&mut self, account_token: AccountToken) {
- let command = match self.settings.get_relay_settings() {
- RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay
- .to_tunnel_endpoint()
- .chain_err(|| "Custom tunnel endpoint could not be resolved"),
- RelaySettings::Normal(constraints) => self
- .relay_selector
- .get_tunnel_endpoint(&constraints)
- .chain_err(|| "No valid relay servers match the current settings")
- .map(|(relay, endpoint)| {
- self.current_relay = Some(relay);
- endpoint
- }),
- }
- .map(|endpoint| self.build_tunnel_parameters(account_token, endpoint))
- .map(|parameters| TunnelCommand::Connect(parameters))
- .unwrap_or_else(|error| {
- error!("{}", error.display_chain());
- TunnelCommand::Block(BlockReason::NoMatchingRelay)
- });
- self.send_tunnel_command(command);
+ fn connect_tunnel(&mut self) {
+ self.send_tunnel_command(TunnelCommand::Connect);
}
fn disconnect_tunnel(&mut self) {
@@ -636,21 +654,7 @@ impl Daemon {
fn reconnect_tunnel(&mut self) {
if self.target_state == TargetState::Secured {
- if let Some(account_token) = self.settings.get_account_token() {
- self.connect_tunnel(account_token);
- }
- }
- }
-
- fn build_tunnel_parameters(
- &self,
- account_token: AccountToken,
- endpoint: TunnelEndpoint,
- ) -> TunnelParameters {
- TunnelParameters {
- endpoint,
- options: self.settings.get_tunnel_options().clone(),
- username: account_token,
+ self.connect_tunnel();
}
}
@@ -691,3 +695,21 @@ impl Drop for Daemon {
}
}
}
+
+
+struct MullvadTunnelParametersGenerator {
+ tx: mpsc::Sender<DaemonEvent>,
+}
+
+impl TunnelParametersGenerator for MullvadTunnelParametersGenerator {
+ fn generate(&mut self, retry_attempt: u32) -> Option<TunnelParameters> {
+ let (response_tx, response_rx) = mpsc::channel();
+ self.tx
+ .send(DaemonEvent::GenerateTunnelParameters(
+ response_tx,
+ retry_attempt,
+ ))
+ .ok()
+ .and_then(|_| response_rx.recv().ok())
+ }
+}
diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs
index 4fb90695cb..a36ded1628 100644
--- a/mullvad-daemon/src/relays.rs
+++ b/mullvad-daemon/src/relays.rs
@@ -178,40 +178,68 @@ impl RelaySelector {
pub fn get_tunnel_endpoint(
&mut self,
constraints: &RelayConstraints,
+ retry_attempt: u32,
) -> Result<(Relay, TunnelEndpoint)> {
+ let preferred_constraints = Self::preferred_constraints(constraints, retry_attempt);
+ if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(&preferred_constraints) {
+ debug!(
+ "Relay matched on highest preference for retry attempt {}",
+ retry_attempt
+ );
+ Ok((relay, endpoint))
+ } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(constraints) {
+ debug!(
+ "Relay matched on second preference for retry attempt {}",
+ retry_attempt
+ );
+ Ok((relay, endpoint))
+ } else {
+ warn!("No relays matching {}", constraints);
+ bail!(ErrorKind::NoRelay);
+ }
+ }
+
+ fn preferred_constraints(
+ original_constraints: &RelayConstraints,
+ retry_attempt: u32,
+ ) -> RelayConstraints {
+ // Prefer UDP by default. But if that has failed a couple of times, then try TCP port 443,
+ // which works for many with UDP problems. After that, just alternate between protocols.
+ let (preferred_port, preferred_protocol) = match retry_attempt {
+ 0 | 1 => (Constraint::Any, TransportProtocol::Udp),
+ 2 | 3 => (Constraint::Only(443), TransportProtocol::Tcp),
+ attempt if attempt % 2 == 0 => (Constraint::Any, TransportProtocol::Udp),
+ _ => (Constraint::Any, TransportProtocol::Tcp),
+ };
+
// Highest priority preference. Where we prefer OpenVPN using UDP. But without changing
// any constraints that are explicitly specified.
- let tunnel_constraints1 = match constraints.tunnel {
+ let tunnel_constraints = match original_constraints.tunnel {
+ // No constraints, we use our preferred ones.
Constraint::Any => TunnelConstraints::OpenVpn(OpenVpnConstraints {
- port: Constraint::Any,
- protocol: Constraint::Only(TransportProtocol::Udp),
+ port: preferred_port,
+ protocol: Constraint::Only(preferred_protocol),
}),
Constraint::Only(TunnelConstraints::OpenVpn(ref openvpn_constraints)) => {
- TunnelConstraints::OpenVpn(OpenVpnConstraints {
- port: openvpn_constraints.port.clone(),
- protocol: Constraint::Only(
- openvpn_constraints
- .protocol
- .clone()
- .unwrap_or(TransportProtocol::Udp),
- ),
- })
+ match openvpn_constraints {
+ // Constrained to OpenVpn, but port/protocol not constrained. Use our preferred.
+ OpenVpnConstraints {
+ port: Constraint::Any,
+ protocol: Constraint::Any,
+ } => TunnelConstraints::OpenVpn(OpenVpnConstraints {
+ port: preferred_port,
+ protocol: Constraint::Only(preferred_protocol),
+ }),
+ // Other constraints, use the original constraints.
+ openvpn_constraints => TunnelConstraints::OpenVpn(openvpn_constraints.clone()),
+ }
}
+ // Non-OpenVPN constraints. Respect and keep those constraints.
Constraint::Only(ref tunnel_constraints) => tunnel_constraints.clone(),
};
- let relay_constraints1 = RelayConstraints {
- location: constraints.location.clone(),
- tunnel: Constraint::Only(tunnel_constraints1),
- };
-
- if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(&relay_constraints1) {
- debug!("Relay matched on highest preference");
- Ok((relay, endpoint))
- } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(constraints) {
- debug!("Relay matched on second preference");
- Ok((relay, endpoint))
- } else {
- bail!(ErrorKind::NoRelay);
+ RelayConstraints {
+ location: original_constraints.location.clone(),
+ tunnel: Constraint::Only(tunnel_constraints),
}
}
diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs
index 5542ef9e71..9be3ec11f4 100644
--- a/mullvad-types/src/custom_tunnel.rs
+++ b/mullvad-types/src/custom_tunnel.rs
@@ -1,5 +1,7 @@
-use std::net::{IpAddr, ToSocketAddrs};
-
+use std::{
+ fmt,
+ net::{IpAddr, ToSocketAddrs},
+};
use talpid_types::net::{TunnelEndpoint, TunnelEndpointData};
error_chain!{
@@ -26,6 +28,12 @@ impl CustomTunnelEndpoint {
}
}
+impl fmt::Display for CustomTunnelEndpoint {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} over {}", self.host, self.tunnel)
+ }
+}
+
/// Does a DNS lookup if the host isn't an IP.
/// Returns the first IPv4 address if one exists, otherwise the first IPv6 address.
/// Rust only provides means to resolve a socket addr, not just a host, for some reason. So
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index f0a56a9d76..2a2507fa47 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -24,6 +24,13 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> {
Constraint::Only(value) => value,
}
}
+
+ pub fn or(self, other: Constraint<T>) -> Constraint<T> {
+ match self {
+ Constraint::Any => other,
+ Constraint::Only(value) => Constraint::Only(value),
+ }
+ }
}
impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> {
@@ -50,6 +57,17 @@ pub enum RelaySettings {
Normal(RelayConstraints),
}
+impl fmt::Display for RelaySettings {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self {
+ RelaySettings::CustomTunnelEndpoint(endpoint) => {
+ write!(f, "custom endpoint {}", endpoint)
+ }
+ RelaySettings::Normal(constraints) => constraints.fmt(f),
+ }
+ }
+}
+
impl Default for RelaySettings {
fn default() -> Self {
RelaySettings::Normal(RelayConstraints::default())
@@ -88,6 +106,20 @@ impl RelayConstraints {
}
}
+impl fmt::Display for RelayConstraints {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self.tunnel {
+ Constraint::Any => write!(f, "any relay")?,
+ Constraint::Only(ref tunnel_constraint) => tunnel_constraint.fmt(f)?,
+ }
+ write!(f, " in ")?;
+ match self.location {
+ Constraint::Any => write!(f, "any location"),
+ Constraint::Only(ref location_constraint) => location_constraint.fmt(f),
+ }
+ }
+}
+
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
@@ -100,6 +132,18 @@ pub enum LocationConstraint {
Hostname(CountryCode, CityCode, Hostname),
}
+impl fmt::Display for LocationConstraint {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self {
+ LocationConstraint::Country(country) => write!(f, "country {}", country),
+ LocationConstraint::City(country, city) => write!(f, "city {}, {}", city, country),
+ LocationConstraint::Hostname(country, city, hostname) => {
+ write!(f, "city {}, {}, hostname {}", city, country, hostname)
+ }
+ }
+ }
+}
+
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum TunnelConstraints {
@@ -109,6 +153,21 @@ pub enum TunnelConstraints {
Wireguard(WireguardConstraints),
}
+impl fmt::Display for TunnelConstraints {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self {
+ TunnelConstraints::OpenVpn(openvpn_constraints) => {
+ write!(f, "OpenVPN over ")?;
+ openvpn_constraints.fmt(f)
+ }
+ TunnelConstraints::Wireguard(wireguard_constraints) => {
+ write!(f, "Wireguard over ")?;
+ wireguard_constraints.fmt(f)
+ }
+ }
+ }
+}
+
impl Match<OpenVpnEndpointData> for TunnelConstraints {
fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool {
match *self {
@@ -133,6 +192,20 @@ pub struct OpenVpnConstraints {
pub protocol: Constraint<TransportProtocol>,
}
+impl fmt::Display for OpenVpnConstraints {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self.port {
+ Constraint::Any => write!(f, "any port")?,
+ Constraint::Only(port) => write!(f, "port {}", port)?,
+ }
+ write!(f, " over ")?;
+ match self.protocol {
+ Constraint::Any => write!(f, "any protocol"),
+ Constraint::Only(protocol) => write!(f, "{}", protocol),
+ }
+ }
+}
+
impl Match<OpenVpnEndpointData> for OpenVpnConstraints {
fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool {
self.port.matches(&endpoint.port) && self.protocol.matches(&endpoint.protocol)
@@ -144,6 +217,15 @@ pub struct WireguardConstraints {
pub port: Constraint<u16>,
}
+impl fmt::Display for WireguardConstraints {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self.port {
+ Constraint::Any => write!(f, "any port"),
+ Constraint::Only(port) => write!(f, "port {}", port),
+ }
+ }
+}
+
impl Match<WireguardEndpointData> for WireguardConstraints {
fn matches(&self, endpoint: &WireguardEndpointData) -> bool {
self.port.matches(&endpoint.port)
diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings.rs
index 231a1b638d..bca23e1786 100644
--- a/mullvad-types/src/settings.rs
+++ b/mullvad-types/src/settings.rs
@@ -136,7 +136,7 @@ impl Settings {
let new_settings = self.relay_settings.merge(update);
if self.relay_settings != new_settings {
debug!(
- "changing relay settings from {:?} to {:?}",
+ "changing relay settings from {} to {}",
self.relay_settings, new_settings
);
diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs
index 33cdd3a1e1..8c73ccd0c5 100644
--- a/talpid-core/src/tunnel_state_machine/blocked_state.rs
+++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs
@@ -55,9 +55,7 @@ impl TunnelState for BlockedState {
Self::set_security_policy(shared_values);
SameState(self)
}
- Ok(TunnelCommand::Connect(parameters)) => {
- NewState(ConnectingState::enter(shared_values, parameters))
- }
+ Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)),
Ok(TunnelCommand::Disconnect) | Err(_) => {
NewState(DisconnectedState::enter(shared_values, ()))
}
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index 7d04bf0c3b..a969ece39f 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -79,20 +79,14 @@ impl ConnectedState {
}
}
}
- Ok(TunnelCommand::Connect(parameters)) => {
- if parameters != self.tunnel_parameters {
- NewState(DisconnectingState::enter(
- shared_values,
- (
- self.close_handle,
- self.tunnel_close_event,
- AfterDisconnect::Reconnect(parameters),
- ),
- ))
- } else {
- SameState(self)
- }
- }
+ Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter(
+ shared_values,
+ (
+ self.close_handle,
+ self.tunnel_close_event,
+ AfterDisconnect::Reconnect(0),
+ ),
+ )),
Ok(TunnelCommand::Disconnect) | Err(_) => NewState(DisconnectingState::enter(
shared_values,
(
@@ -124,7 +118,7 @@ impl ConnectedState {
(
self.close_handle,
self.tunnel_close_event,
- AfterDisconnect::Reconnect(self.tunnel_parameters),
+ AfterDisconnect::Reconnect(0),
),
)),
Ok(_) => SameState(self),
@@ -144,10 +138,7 @@ impl ConnectedState {
}
info!("Tunnel closed. Reconnecting.");
- NewState(ConnectingState::enter(
- shared_values,
- self.tunnel_parameters,
- ))
+ NewState(ConnectingState::enter(shared_values, 0))
}
}
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index bcf1c422e4..1e0880141e 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -47,6 +47,7 @@ pub struct ConnectingState {
tunnel_parameters: TunnelParameters,
tunnel_close_event: oneshot::Receiver<()>,
close_handle: CloseHandle,
+ retry_attempt: u32,
}
impl ConnectingState {
@@ -68,6 +69,7 @@ impl ConnectingState {
parameters: TunnelParameters,
log_dir: &Option<PathBuf>,
resource_dir: &Path,
+ retry_attempt: u32,
) -> Result<Self> {
let (event_tx, event_rx) = mpsc::unbounded();
let monitor = Self::spawn_tunnel_monitor(&parameters, log_dir, resource_dir, event_tx)?;
@@ -79,6 +81,7 @@ impl ConnectingState {
tunnel_parameters: parameters,
tunnel_close_event,
close_handle,
+ retry_attempt,
})
}
@@ -185,20 +188,14 @@ impl ConnectingState {
}
}
}
- Ok(TunnelCommand::Connect(parameters)) => {
- if parameters != self.tunnel_parameters {
- NewState(DisconnectingState::enter(
- shared_values,
- (
- self.close_handle,
- self.tunnel_close_event,
- AfterDisconnect::Reconnect(parameters),
- ),
- ))
- } else {
- SameState(self)
- }
- }
+ Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter(
+ shared_values,
+ (
+ self.close_handle,
+ self.tunnel_close_event,
+ AfterDisconnect::Reconnect(0),
+ ),
+ )),
Ok(TunnelCommand::Disconnect) | Err(_) => NewState(DisconnectingState::enter(
shared_values,
(
@@ -245,7 +242,7 @@ impl ConnectingState {
(
self.close_handle,
self.tunnel_close_event,
- AfterDisconnect::Reconnect(self.tunnel_parameters),
+ AfterDisconnect::Reconnect(self.retry_attempt + 1),
),
))
}
@@ -262,47 +259,61 @@ impl ConnectingState {
Err(_cancelled) => warn!("Tunnel monitor thread has stopped unexpectedly"),
}
- info!("Tunnel closed. Reconnecting.");
+ info!(
+ "Tunnel closed. Reconnecting, attempt {}.",
+ self.retry_attempt + 1
+ );
EventConsequence::NewState(ConnectingState::enter(
shared_values,
- self.tunnel_parameters,
+ self.retry_attempt + 1,
))
}
}
impl TunnelState for ConnectingState {
- type Bootstrap = TunnelParameters;
+ type Bootstrap = u32;
fn enter(
shared_values: &mut SharedTunnelStateValues,
- parameters: Self::Bootstrap,
+ retry_attempt: u32,
) -> (TunnelStateWrapper, TunnelStateTransition) {
- if let Err(error) = Self::set_security_policy(shared_values, parameters.endpoint) {
- error!("{}", error.display_chain());
- return BlockedState::enter(shared_values, BlockReason::StartTunnelError);
- }
-
- match Self::start_tunnel(
- parameters,
- &shared_values.log_dir,
- &shared_values.resource_dir,
- ) {
- Ok(connecting_state) => (
- TunnelStateWrapper::from(connecting_state),
- TunnelStateTransition::Connecting,
- ),
- Err(error) => {
- let block_reason = match *error.kind() {
- ErrorKind::TunnelMonitorError(tunnel::ErrorKind::EnableIpv6Error) => {
- BlockReason::Ipv6Unavailable
- }
- _ => BlockReason::StartTunnelError,
- };
+ match shared_values
+ .tunnel_parameters_generator
+ .generate(retry_attempt)
+ {
+ None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay),
+ Some(tunnel_parameters) => {
+ if let Err(error) =
+ Self::set_security_policy(shared_values, tunnel_parameters.endpoint)
+ {
+ error!("{}", error.display_chain());
+ BlockedState::enter(shared_values, BlockReason::StartTunnelError)
+ } else {
+ match Self::start_tunnel(
+ tunnel_parameters,
+ &shared_values.log_dir,
+ &shared_values.resource_dir,
+ retry_attempt,
+ ) {
+ Ok(connecting_state) => (
+ TunnelStateWrapper::from(connecting_state),
+ TunnelStateTransition::Connecting,
+ ),
+ Err(error) => {
+ let block_reason = match *error.kind() {
+ ErrorKind::TunnelMonitorError(
+ tunnel::ErrorKind::EnableIpv6Error,
+ ) => BlockReason::Ipv6Unavailable,
+ _ => BlockReason::StartTunnelError,
+ };
- let chained_error = error.chain_err(|| "Failed to start tunnel");
- error!("{}", chained_error.display_chain());
+ let chained_error = error.chain_err(|| "Failed to start tunnel");
+ error!("{}", chained_error.display_chain());
- BlockedState::enter(shared_values, block_reason)
+ BlockedState::enter(shared_values, block_reason)
+ }
+ }
+ }
}
}
}
diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
index 3d4518a050..227e019323 100644
--- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
@@ -46,9 +46,7 @@ impl TunnelState for DisconnectedState {
shared_values.allow_lan = allow_lan;
SameState(self)
}
- Ok(TunnelCommand::Connect(parameters)) => {
- NewState(ConnectingState::enter(shared_values, parameters))
- }
+ Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)),
Ok(TunnelCommand::Block(reason)) => {
NewState(BlockedState::enter(shared_values, reason))
}
diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
index 5028792c11..864b11ab4b 100644
--- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
@@ -8,8 +8,7 @@ use talpid_types::tunnel::{ActionAfterDisconnect, BlockReason};
use super::{
BlockedState, ConnectingState, DisconnectedState, EventConsequence, ResultExt,
- SharedTunnelStateValues, TunnelCommand, TunnelParameters, TunnelState, TunnelStateTransition,
- TunnelStateWrapper,
+ SharedTunnelStateValues, TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper,
};
use tunnel::CloseHandle;
@@ -26,8 +25,6 @@ impl DisconnectingState {
commands: &mut mpsc::UnboundedReceiver<TunnelCommand>,
shared_values: &mut SharedTunnelStateValues,
) -> EventConsequence<Self> {
- use self::AfterDisconnect::*;
-
let event = try_handle_event!(self, commands.poll());
let after_disconnect = self.after_disconnect;
@@ -35,30 +32,30 @@ impl DisconnectingState {
AfterDisconnect::Nothing => match event {
Ok(TunnelCommand::AllowLan(allow_lan)) => {
shared_values.allow_lan = allow_lan;
- Nothing
+ AfterDisconnect::Nothing
}
- Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters),
- Ok(TunnelCommand::Block(reason)) => Block(reason),
- _ => Nothing,
+ Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0),
+ Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason),
+ _ => AfterDisconnect::Nothing,
},
AfterDisconnect::Block(reason) => match event {
Ok(TunnelCommand::AllowLan(allow_lan)) => {
shared_values.allow_lan = allow_lan;
- Block(reason)
+ AfterDisconnect::Block(reason)
}
- Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters),
- Ok(TunnelCommand::Disconnect) => Nothing,
- Ok(TunnelCommand::Block(new_reason)) => Block(new_reason),
- Err(_) => Block(reason),
+ Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0),
+ Ok(TunnelCommand::Disconnect) => AfterDisconnect::Nothing,
+ Ok(TunnelCommand::Block(new_reason)) => AfterDisconnect::Block(new_reason),
+ Err(_) => AfterDisconnect::Block(reason),
},
- AfterDisconnect::Reconnect(tunnel_parameters) => match event {
+ AfterDisconnect::Reconnect(retry_attempt) => match event {
Ok(TunnelCommand::AllowLan(allow_lan)) => {
shared_values.allow_lan = allow_lan;
- Reconnect(tunnel_parameters)
+ AfterDisconnect::Reconnect(retry_attempt)
}
- Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters),
- Ok(TunnelCommand::Disconnect) | Err(_) => Nothing,
- Ok(TunnelCommand::Block(reason)) => Block(reason),
+ Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(retry_attempt),
+ Ok(TunnelCommand::Disconnect) | Err(_) => AfterDisconnect::Nothing,
+ Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason),
},
};
@@ -84,8 +81,8 @@ impl DisconnectingState {
match self.after_disconnect {
AfterDisconnect::Nothing => DisconnectedState::enter(shared_values, ()),
AfterDisconnect::Block(reason) => BlockedState::enter(shared_values, reason),
- AfterDisconnect::Reconnect(tunnel_parameters) => {
- ConnectingState::enter(shared_values, tunnel_parameters)
+ AfterDisconnect::Reconnect(retry_attempt) => {
+ ConnectingState::enter(shared_values, retry_attempt)
}
}
}
@@ -133,7 +130,7 @@ impl TunnelState for DisconnectingState {
pub enum AfterDisconnect {
Nothing,
Block(BlockReason),
- Reconnect(TunnelParameters),
+ Reconnect(u32),
}
impl AfterDisconnect {
@@ -142,7 +139,7 @@ impl AfterDisconnect {
match self {
AfterDisconnect::Nothing => ActionAfterDisconnect::Nothing,
AfterDisconnect::Block(..) => ActionAfterDisconnect::Block,
- AfterDisconnect::Reconnect(_) => ActionAfterDisconnect::Reconnect,
+ AfterDisconnect::Reconnect(..) => ActionAfterDisconnect::Reconnect,
}
}
}
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index 91ca9aa7ad..66e86de9de 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -44,6 +44,7 @@ error_chain! {
/// Spawn the tunnel state machine thread, returning a channel for sending tunnel commands.
pub fn spawn<P, T>(
allow_lan: bool,
+ tunnel_parameters_generator: impl TunnelParametersGenerator,
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: P,
@@ -59,6 +60,7 @@ where
thread::spawn(move || {
match create_event_loop(
allow_lan,
+ tunnel_parameters_generator,
log_dir,
resource_dir,
cache_dir,
@@ -92,6 +94,7 @@ where
fn create_event_loop<T>(
allow_lan: bool,
+ tunnel_parameters_generator: impl TunnelParametersGenerator,
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: impl AsRef<Path>,
@@ -102,8 +105,14 @@ where
T: From<TunnelStateTransition> + Send + 'static,
{
let reactor = Core::new().chain_err(|| ErrorKind::ReactorError)?;
- let state_machine =
- TunnelStateMachine::new(allow_lan, log_dir, resource_dir, cache_dir, commands)?;
+ let state_machine = TunnelStateMachine::new(
+ allow_lan,
+ tunnel_parameters_generator,
+ log_dir,
+ resource_dir,
+ cache_dir,
+ commands,
+ )?;
let future = state_machine.for_each(move |state_change_event| {
state_change_listener
@@ -119,7 +128,7 @@ pub enum TunnelCommand {
/// Enable or disable LAN access in the firewall.
AllowLan(bool),
/// Open tunnel connection.
- Connect(TunnelParameters),
+ Connect,
/// Close tunnel connection.
Disconnect,
/// Disconnect any open tunnel and block all network access
@@ -152,6 +161,7 @@ struct TunnelStateMachine {
impl TunnelStateMachine {
fn new(
allow_lan: bool,
+ tunnel_parameters_generator: impl TunnelParametersGenerator,
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: impl AsRef<Path>,
@@ -162,6 +172,7 @@ impl TunnelStateMachine {
let mut shared_values = SharedTunnelStateValues {
security,
allow_lan,
+ tunnel_parameters_generator: Box::new(tunnel_parameters_generator),
log_dir,
resource_dir,
};
@@ -225,12 +236,21 @@ impl<T: TunnelState> From<EventConsequence<T>> for TunnelStateMachineAction {
}
}
+/// Trait for any type that can provide a stream of `TunnelParameters` to the `TunnelStateMachine`.
+pub trait TunnelParametersGenerator: Send + 'static {
+ /// Given the number of consecutive failed retry attempts, it should yield a `TunnelParameters`
+ /// to establish a tunnel with.
+ /// If this returns `None` then the state machine goes into the `Blocked` state.
+ fn generate(&mut self, retry_attempt: u32) -> Option<TunnelParameters>;
+}
/// Values that are common to all tunnel states.
struct SharedTunnelStateValues {
security: NetworkSecurity,
/// Should LAN access be allowed outside the tunnel.
allow_lan: bool,
+ /// The generator of new `TunnelParameter`s
+ tunnel_parameters_generator: Box<dyn TunnelParametersGenerator>,
/// Directory to store tunnel log file.
log_dir: Option<PathBuf>,
/// Resource directory path.
diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs
index 31edfb7d48..e00d0d9fe4 100644
--- a/talpid-types/src/net.rs
+++ b/talpid-types/src/net.rs
@@ -33,6 +33,21 @@ pub enum TunnelEndpointData {
Wireguard(WireguardEndpointData),
}
+impl fmt::Display for TunnelEndpointData {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self {
+ TunnelEndpointData::OpenVpn(openvpn_data) => {
+ write!(f, "OpenVPN ")?;
+ openvpn_data.fmt(f)
+ }
+ TunnelEndpointData::Wireguard(wireguard_data) => {
+ write!(f, "Wireguard ")?;
+ wireguard_data.fmt(f)
+ }
+ }
+ }
+}
+
impl TunnelEndpointData {
pub fn port(self) -> u16 {
match self {
@@ -55,11 +70,23 @@ pub struct OpenVpnEndpointData {
pub protocol: TransportProtocol,
}
+impl fmt::Display for OpenVpnEndpointData {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(f, "{} port {}", self.protocol, self.port)
+ }
+}
+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub struct WireguardEndpointData {
pub port: u16,
}
+impl fmt::Display for WireguardEndpointData {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(f, "port {}", self.port)
+ }
+}
+
/// Represents a network layer IP address together with the transport layer protocol and port.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]