/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::{sync::Arc, time::Duration};
use futures::{
future::{select, Either},
pin_mut,
};
use log::warn;
use url::Url;
use super::{
channel::{Channel, ChannelPtr},
hosts::HostColor,
session::SessionWeakPtr,
settings::Settings,
transport::Dialer,
};
use crate::{system::CondVar, Error, Result};
/// Create outbound socket connections
pub struct Connector {
/// P2P settings
settings: Arc,
/// Weak pointer to the session
pub session: SessionWeakPtr,
/// Stop signal that aborts the connector if received.
stop_signal: CondVar,
}
impl Connector {
/// Create a new connector with given network settings
pub fn new(settings: Arc, session: SessionWeakPtr) -> Self {
Self { settings, session, stop_signal: CondVar::new() }
}
/// Establish an outbound connection
pub async fn connect(&self, url: &Url) -> Result<(Url, ChannelPtr)> {
let hosts = self.session.upgrade().unwrap().p2p().hosts();
if hosts.container.contains(HostColor::Black as usize, url) ||
hosts.block_all_ports(url.host_str().unwrap().to_string())
{
warn!(target: "net::connector::connect", "Peer {} is blacklisted", url);
return Err(Error::ConnectFailed)
}
let mut endpoint = url.clone();
let transports = &self.settings.allowed_transports;
let scheme = endpoint.scheme();
if !transports.contains(&scheme.to_string()) && self.settings.transport_mixing {
if transports.contains(&"tor".to_string()) && scheme == "tcp" {
endpoint.set_scheme("tor")?;
} else if transports.contains(&"tor+tls".to_string()) && scheme == "tcp+tls" {
endpoint.set_scheme("tor+tls")?;
} else if transports.contains(&"nym".to_string()) && scheme == "tcp" {
endpoint.set_scheme("nym")?;
} else if transports.contains(&"nym+tls".to_string()) && scheme == "tcp+tls" {
endpoint.set_scheme("nym+tls")?;
}
}
let dialer = Dialer::new(endpoint.clone(), self.settings.datastore.clone()).await?;
let timeout = Duration::from_secs(self.settings.outbound_connect_timeout);
let stop_fut = async {
self.stop_signal.wait().await;
};
let dial_fut = async { dialer.dial(Some(timeout)).await };
pin_mut!(stop_fut);
pin_mut!(dial_fut);
match select(dial_fut, stop_fut).await {
Either::Left((Ok(ptstream), _)) => {
let channel = Channel::new(
ptstream,
Some(endpoint.clone()),
url.clone(),
self.session.clone(),
)
.await;
Ok((endpoint, channel))
}
Either::Left((Err(e), _)) => {
// If we get ENETUNREACH, we don't have IPv6 connectivity so note it down.
if e.raw_os_error() == Some(libc::ENETUNREACH) {
*self.session.upgrade().unwrap().p2p().hosts().ipv6_available.lock().unwrap() =
false;
}
Err(e.into())
}
Either::Right((_, _)) => Err(Error::ConnectorStopped),
}
}
pub(crate) fn stop(&self) {
self.stop_signal.notify()
}
}