use std::{
net::SocketAddr,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use futures::{channel::oneshot, future::Either};
use futures_timer::Delay;
use libp2p_identity as identity;
use libp2p_identity::PeerId;
use libp2p_webrtc_utils::{noise, Fingerprint};
use webrtc::{
api::{setting_engine::SettingEngine, APIBuilder},
data::data_channel::DataChannel,
data_channel::data_channel_init::RTCDataChannelInit,
dtls_transport::dtls_role::DTLSRole,
ice::{network_type::NetworkType, udp_mux::UDPMux, udp_network::UDPNetwork},
peer_connection::{configuration::RTCConfiguration, RTCPeerConnection},
};
use crate::tokio::{error::Error, sdp, sdp::random_ufrag, stream::Stream, Connection};
pub(crate) async fn outbound(
addr: SocketAddr,
config: RTCConfiguration,
udp_mux: Arc<dyn UDPMux + Send + Sync>,
client_fingerprint: Fingerprint,
server_fingerprint: Fingerprint,
id_keys: identity::Keypair,
) -> Result<(PeerId, Connection), Error> {
tracing::debug!(address=%addr, "new outbound connection to address");
let (peer_connection, ufrag) = new_outbound_connection(addr, config, udp_mux).await?;
let offer = peer_connection.create_offer(None).await?;
tracing::debug!(offer=%offer.sdp, "created SDP offer for outbound connection");
peer_connection.set_local_description(offer).await?;
let answer = sdp::answer(addr, server_fingerprint, &ufrag);
tracing::debug!(?answer, "calculated SDP answer for outbound connection");
peer_connection.set_remote_description(answer).await?;
let data_channel = create_substream_for_noise_handshake(&peer_connection).await?;
let peer_id = noise::outbound(
id_keys,
data_channel,
server_fingerprint,
client_fingerprint,
)
.await?;
Ok((peer_id, Connection::new(peer_connection).await))
}
pub(crate) async fn inbound(
addr: SocketAddr,
config: RTCConfiguration,
udp_mux: Arc<dyn UDPMux + Send + Sync>,
server_fingerprint: Fingerprint,
remote_ufrag: String,
id_keys: identity::Keypair,
) -> Result<(PeerId, Connection), Error> {
tracing::debug!(address=%addr, ufrag=%remote_ufrag, "new inbound connection from address");
let peer_connection = new_inbound_connection(addr, config, udp_mux, &remote_ufrag).await?;
let offer = sdp::offer(addr, &remote_ufrag);
tracing::debug!(?offer, "calculated SDP offer for inbound connection");
peer_connection.set_remote_description(offer).await?;
let answer = peer_connection.create_answer(None).await?;
tracing::debug!(?answer, "created SDP answer for inbound connection");
peer_connection.set_local_description(answer).await?;
let data_channel = create_substream_for_noise_handshake(&peer_connection).await?;
let client_fingerprint = get_remote_fingerprint(&peer_connection).await;
let peer_id = noise::inbound(
id_keys,
data_channel,
client_fingerprint,
server_fingerprint,
)
.await?;
Ok((peer_id, Connection::new(peer_connection).await))
}
async fn new_outbound_connection(
addr: SocketAddr,
config: RTCConfiguration,
udp_mux: Arc<dyn UDPMux + Send + Sync>,
) -> Result<(RTCPeerConnection, String), Error> {
let ufrag = random_ufrag();
let se = setting_engine(udp_mux, &ufrag, addr);
let connection = APIBuilder::new()
.with_setting_engine(se)
.build()
.new_peer_connection(config)
.await?;
Ok((connection, ufrag))
}
async fn new_inbound_connection(
addr: SocketAddr,
config: RTCConfiguration,
udp_mux: Arc<dyn UDPMux + Send + Sync>,
ufrag: &str,
) -> Result<RTCPeerConnection, Error> {
let mut se = setting_engine(udp_mux, ufrag, addr);
{
se.set_lite(true);
se.disable_certificate_fingerprint_verification(true);
se.set_answering_dtls_role(DTLSRole::Server)?;
}
let connection = APIBuilder::new()
.with_setting_engine(se)
.build()
.new_peer_connection(config)
.await?;
Ok(connection)
}
fn setting_engine(
udp_mux: Arc<dyn UDPMux + Send + Sync>,
ufrag: &str,
addr: SocketAddr,
) -> SettingEngine {
let mut se = SettingEngine::default();
se.set_ice_credentials(ufrag.to_owned(), ufrag.to_owned());
se.set_udp_network(UDPNetwork::Muxed(udp_mux.clone()));
se.detach_data_channels();
let network_type = match addr {
SocketAddr::V4(_) => NetworkType::Udp4,
SocketAddr::V6(_) => NetworkType::Udp6,
};
se.set_network_types(vec![network_type]);
se.set_ip_filter(Box::new({
let once = AtomicBool::new(true);
move |_ip| {
if once.load(Ordering::Relaxed) {
once.store(false, Ordering::Relaxed);
return true;
}
false
}
}));
se
}
async fn get_remote_fingerprint(conn: &RTCPeerConnection) -> Fingerprint {
let cert_bytes = conn.sctp().transport().get_remote_certificate().await;
Fingerprint::from_certificate(&cert_bytes)
}
async fn create_substream_for_noise_handshake(conn: &RTCPeerConnection) -> Result<Stream, Error> {
let data_channel = conn
.create_data_channel(
"",
Some(RTCDataChannelInit {
negotiated: Some(0), ..RTCDataChannelInit::default()
}),
)
.await?;
let (tx, rx) = oneshot::channel::<Arc<DataChannel>>();
crate::tokio::connection::register_data_channel_open_handler(data_channel, tx).await;
let channel = match futures::future::select(rx, Delay::new(Duration::from_secs(10))).await {
Either::Left((Ok(channel), _)) => channel,
Either::Left((Err(_), _)) => {
return Err(Error::Internal("failed to open data channel".to_owned()))
}
Either::Right(((), _)) => {
return Err(Error::Internal(
"data channel opening took longer than 10 seconds (see logs)".into(),
))
}
};
let (substream, drop_listener) = Stream::new(channel);
drop(drop_listener);
Ok(substream)
}