use crate::{
logging::NetworkSchema,
noise::{stream::NoiseStream, AntiReplayTimestamps, HandshakeAuthMode, NoiseUpgrader},
protocols::{
identity::exchange_handshake,
wire::handshake::v1::{HandshakeMsg, MessagingProtocolVersion, ProtocolIdSet},
},
};
use aptos_config::{
config::{PeerRole, HANDSHAKE_VERSION},
network_id::{NetworkContext, NetworkId},
};
use aptos_crypto::x25519;
use aptos_id_generator::{IdGenerator, U32IdGenerator};
use aptos_logger::prelude::*;
use aptos_time_service::{timeout, TimeService, TimeServiceTrait};
use aptos_types::{
chain_id::ChainId,
network_address::{parse_dns_tcp, parse_ip_tcp, parse_memory, NetworkAddress},
PeerId,
};
use futures::{
future::{Future, FutureExt},
io::{AsyncRead, AsyncWrite},
stream::{Stream, StreamExt, TryStreamExt},
};
use netcore::transport::{proxy_protocol, tcp, ConnectionOrigin, Transport};
use serde::{Deserialize, Serialize};
use short_hex_str::AsShortHexStr;
use std::{collections::BTreeMap, convert::TryFrom, fmt, io, pin::Pin, sync::Arc, time::Duration};
#[cfg(test)]
mod test;
pub const TRANSPORT_TIMEOUT: Duration = Duration::from_secs(30);
pub const SUPPORTED_MESSAGING_PROTOCOL: MessagingProtocolVersion = MessagingProtocolVersion::V1;
static CONNECTION_ID_GENERATOR: ConnectionIdGenerator = ConnectionIdGenerator::new();
pub const APTOS_TCP_TRANSPORT: tcp::TcpTransport = tcp::TcpTransport {
ttl: None,
nodelay: Some(true),
};
pub trait TSocket: AsyncRead + AsyncWrite + Send + fmt::Debug + Unpin + 'static {}
impl<T> TSocket for T where T: AsyncRead + AsyncWrite + Send + fmt::Debug + Unpin + 'static {}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ConnectionId(u32);
impl From<u32> for ConnectionId {
fn from(i: u32) -> ConnectionId {
ConnectionId(i)
}
}
struct ConnectionIdGenerator {
id_generator: U32IdGenerator,
}
impl ConnectionIdGenerator {
const fn new() -> ConnectionIdGenerator {
Self {
id_generator: U32IdGenerator::new(),
}
}
fn next(&self) -> ConnectionId {
ConnectionId::from(self.id_generator.next())
}
}
#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConnectionMetadata {
pub remote_peer_id: PeerId,
pub connection_id: ConnectionId,
pub addr: NetworkAddress,
pub origin: ConnectionOrigin,
pub messaging_protocol: MessagingProtocolVersion,
pub application_protocols: ProtocolIdSet,
pub role: PeerRole,
}
impl ConnectionMetadata {
pub fn new(
remote_peer_id: PeerId,
connection_id: ConnectionId,
addr: NetworkAddress,
origin: ConnectionOrigin,
messaging_protocol: MessagingProtocolVersion,
application_protocols: ProtocolIdSet,
role: PeerRole,
) -> ConnectionMetadata {
ConnectionMetadata {
remote_peer_id,
connection_id,
addr,
origin,
messaging_protocol,
application_protocols,
role,
}
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn mock(remote_peer_id: PeerId) -> ConnectionMetadata {
Self::mock_with_role_and_origin(
remote_peer_id,
PeerRole::Unknown,
ConnectionOrigin::Inbound,
)
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn mock_with_role_and_origin(
remote_peer_id: PeerId,
role: PeerRole,
origin: ConnectionOrigin,
) -> ConnectionMetadata {
ConnectionMetadata {
remote_peer_id,
role,
origin,
connection_id: CONNECTION_ID_GENERATOR.next(),
addr: NetworkAddress::mock(),
messaging_protocol: MessagingProtocolVersion::V1,
application_protocols: ProtocolIdSet::empty(),
}
}
}
impl fmt::Debug for ConnectionMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for ConnectionMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{},{},{},{},{:?},{:?}]",
self.remote_peer_id,
self.addr,
self.origin,
self.messaging_protocol,
self.application_protocols,
self.role
)
}
}
#[derive(Debug)]
pub struct Connection<TSocket> {
pub socket: TSocket,
pub metadata: ConnectionMetadata,
}
async fn timeout_io<F, T>(time_service: TimeService, duration: Duration, fut: F) -> io::Result<T>
where
F: Future<Output = io::Result<T>>,
{
let res = time_service.timeout(duration, fut).await;
match res {
Ok(out) => out,
Err(timeout::Elapsed) => Err(io::Error::new(io::ErrorKind::TimedOut, timeout::Elapsed)),
}
}
pub struct UpgradeContext {
noise: NoiseUpgrader,
handshake_version: u8,
supported_protocols: BTreeMap<MessagingProtocolVersion, ProtocolIdSet>,
chain_id: ChainId,
network_id: NetworkId,
}
impl UpgradeContext {
pub fn new(
noise: NoiseUpgrader,
handshake_version: u8,
supported_protocols: BTreeMap<MessagingProtocolVersion, ProtocolIdSet>,
chain_id: ChainId,
network_id: NetworkId,
) -> Self {
UpgradeContext {
noise,
handshake_version,
supported_protocols,
chain_id,
network_id,
}
}
}
fn add_pp_addr(proxy_protocol_enabled: bool, error: io::Error, addr: &NetworkAddress) -> io::Error {
if proxy_protocol_enabled {
io::Error::new(
error.kind(),
format!("proxied address: {}, error: {}", addr, error),
)
} else {
error
}
}
async fn upgrade_inbound<T: TSocket>(
ctxt: Arc<UpgradeContext>,
fut_socket: impl Future<Output = io::Result<T>>,
addr: NetworkAddress,
proxy_protocol_enabled: bool,
) -> io::Result<Connection<NoiseStream<T>>> {
let origin = ConnectionOrigin::Inbound;
let mut socket = fut_socket.await?;
let addr = if proxy_protocol_enabled {
proxy_protocol::read_header(&addr, &mut socket)
.await
.map_err(|err| {
debug!(
network_address = addr,
error = %err,
"ProxyProtocol: Failed to read header: {}",
err
);
err
})?
} else {
addr
};
let (mut socket, remote_peer_id, peer_role) =
ctxt.noise.upgrade_inbound(socket).await.map_err(|err| {
if err.should_security_log() {
sample!(
SampleRate::Duration(Duration::from_secs(15)),
error!(
SecurityEvent::NoiseHandshake,
NetworkSchema::new(&ctxt.noise.network_context)
.network_address(&addr)
.connection_origin(&origin),
error = %err,
)
);
}
let err = io::Error::new(io::ErrorKind::Other, err);
add_pp_addr(proxy_protocol_enabled, err, &addr)
})?;
let remote_pubkey = socket.get_remote_static();
let addr = addr.append_prod_protos(remote_pubkey, HANDSHAKE_VERSION);
let handshake_msg = HandshakeMsg {
supported_protocols: ctxt.supported_protocols.clone(),
chain_id: ctxt.chain_id,
network_id: ctxt.network_id,
};
let remote_handshake = exchange_handshake(&handshake_msg, &mut socket)
.await
.map_err(|err| add_pp_addr(proxy_protocol_enabled, err, &addr))?;
let (messaging_protocol, application_protocols) = handshake_msg
.perform_handshake(&remote_handshake)
.map_err(|err| {
let err = format!(
"handshake negotiation with peer {} failed: {}",
remote_peer_id.short_str(),
err
);
add_pp_addr(
proxy_protocol_enabled,
io::Error::new(io::ErrorKind::Other, err),
&addr,
)
})?;
Ok(Connection {
socket,
metadata: ConnectionMetadata::new(
remote_peer_id,
CONNECTION_ID_GENERATOR.next(),
addr,
origin,
messaging_protocol,
application_protocols,
peer_role,
),
})
}
pub async fn upgrade_outbound<T: TSocket>(
ctxt: Arc<UpgradeContext>,
fut_socket: impl Future<Output = io::Result<T>>,
addr: NetworkAddress,
remote_peer_id: PeerId,
remote_pubkey: x25519::PublicKey,
) -> io::Result<Connection<NoiseStream<T>>> {
let origin = ConnectionOrigin::Outbound;
let socket = fut_socket.await?;
let mut socket = ctxt
.noise
.upgrade_outbound(socket, remote_pubkey, AntiReplayTimestamps::now)
.await
.map_err(|err| {
if err.should_security_log() {
sample!(
SampleRate::Duration(Duration::from_secs(15)),
error!(
SecurityEvent::NoiseHandshake,
NetworkSchema::new(&ctxt.noise.network_context)
.network_address(&addr)
.connection_origin(&origin),
error = %err,
)
);
}
io::Error::new(io::ErrorKind::Other, err)
})?;
debug_assert_eq!(remote_pubkey, socket.get_remote_static());
let handshake_msg = HandshakeMsg {
supported_protocols: ctxt.supported_protocols.clone(),
chain_id: ctxt.chain_id,
network_id: ctxt.network_id,
};
let remote_handshake = exchange_handshake(&handshake_msg, &mut socket).await?;
let (messaging_protocol, application_protocols) = handshake_msg
.perform_handshake(&remote_handshake)
.map_err(|e| {
let e = format!(
"handshake negotiation with peer {} failed: {}",
remote_peer_id, e
);
io::Error::new(io::ErrorKind::Other, e)
})?;
Ok(Connection {
socket,
metadata: ConnectionMetadata::new(
remote_peer_id,
CONNECTION_ID_GENERATOR.next(),
addr,
origin,
messaging_protocol,
application_protocols,
PeerRole::Unknown,
),
})
}
pub struct AptosNetTransport<TTransport> {
base_transport: TTransport,
ctxt: Arc<UpgradeContext>,
time_service: TimeService,
identity_pubkey: x25519::PublicKey,
enable_proxy_protocol: bool,
}
impl<TTransport> AptosNetTransport<TTransport>
where
TTransport: Transport<Error = io::Error>,
TTransport::Output: TSocket,
TTransport::Outbound: Send + 'static,
TTransport::Inbound: Send + 'static,
TTransport::Listener: Send + 'static,
{
pub fn new(
base_transport: TTransport,
network_context: NetworkContext,
time_service: TimeService,
identity_key: x25519::PrivateKey,
auth_mode: HandshakeAuthMode,
handshake_version: u8,
chain_id: ChainId,
application_protocols: ProtocolIdSet,
enable_proxy_protocol: bool,
) -> Self {
let mut supported_protocols = BTreeMap::new();
supported_protocols.insert(SUPPORTED_MESSAGING_PROTOCOL, application_protocols);
let identity_pubkey = identity_key.public_key();
let upgrade_context = UpgradeContext::new(
NoiseUpgrader::new(network_context, identity_key, auth_mode),
handshake_version,
supported_protocols,
chain_id,
network_context.network_id(),
);
Self {
base_transport,
ctxt: Arc::new(upgrade_context),
time_service,
identity_pubkey,
enable_proxy_protocol,
}
}
fn parse_dial_addr(
addr: &NetworkAddress,
) -> io::Result<(NetworkAddress, x25519::PublicKey, u8)> {
use aptos_types::network_address::Protocol::*;
let protos = addr.as_slice();
let (base_transport_protos, base_transport_suffix) = parse_ip_tcp(protos)
.map(|x| (&protos[..2], x.1))
.or_else(|| parse_dns_tcp(protos).map(|x| (&protos[..2], x.1)))
.or_else(|| parse_memory(protos).map(|x| (&protos[..1], x.1)))
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Unexpected dialing network address: '{}', expected: \
memory, ip+tcp, or dns+tcp",
addr
),
)
})?;
match base_transport_suffix {
[NoiseIK(pubkey), Handshake(version)] => {
let base_addr = NetworkAddress::try_from(base_transport_protos.to_vec())
.expect("base_transport_protos is always non-empty");
Ok((base_addr, *pubkey, *version))
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Unexpected dialing network address: '{}', expected: \
'/../noise-ik/<pubkey>/handshake/<version>'",
addr
),
)),
}
}
pub fn dial(
&self,
peer_id: PeerId,
addr: NetworkAddress,
) -> io::Result<
impl Future<Output = io::Result<Connection<NoiseStream<TTransport::Output>>>> + Send + 'static,
> {
let (base_addr, pubkey, handshake_version) = Self::parse_dial_addr(&addr)?;
if self.ctxt.handshake_version != handshake_version {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Attempting to dial remote with unsupported handshake version: {}, expected: {}",
handshake_version, self.ctxt.handshake_version,
),
));
}
let fut_socket = self.base_transport.dial(peer_id, base_addr)?;
let upgrade_fut = upgrade_outbound(self.ctxt.clone(), fut_socket, addr, peer_id, pubkey);
let upgrade_fut = timeout_io(self.time_service.clone(), TRANSPORT_TIMEOUT, upgrade_fut);
Ok(upgrade_fut)
}
pub fn listen_on(
&self,
addr: NetworkAddress,
) -> io::Result<(
impl Stream<
Item = io::Result<(
impl Future<Output = io::Result<Connection<NoiseStream<TTransport::Output>>>>
+ Send
+ 'static,
NetworkAddress,
)>,
> + Send
+ 'static,
NetworkAddress,
)> {
let (listener, listen_addr) = self.base_transport.listen_on(addr)?;
let listen_addr =
listen_addr.append_prod_protos(self.identity_pubkey, self.ctxt.handshake_version);
let ctxt = self.ctxt.clone();
let time_service = self.time_service.clone();
let enable_proxy_protocol = self.enable_proxy_protocol;
let inbounds = listener.map_ok(move |(fut_socket, addr)| {
let fut_upgrade = upgrade_inbound(
ctxt.clone(),
fut_socket,
addr.clone(),
enable_proxy_protocol,
);
let fut_upgrade = timeout_io(time_service.clone(), TRANSPORT_TIMEOUT, fut_upgrade);
(fut_upgrade, addr)
});
Ok((inbounds, listen_addr))
}
}
impl<TTransport: Transport> Transport for AptosNetTransport<TTransport>
where
TTransport: Transport<Error = io::Error> + Send + 'static,
TTransport::Output: TSocket,
TTransport::Outbound: Send + 'static,
TTransport::Inbound: Send + 'static,
TTransport::Listener: Send + 'static,
{
type Output = Connection<NoiseStream<TTransport::Output>>;
type Error = io::Error;
type Inbound = Pin<Box<dyn Future<Output = io::Result<Self::Output>> + Send + 'static>>;
type Outbound = Pin<Box<dyn Future<Output = io::Result<Self::Output>> + Send + 'static>>;
type Listener =
Pin<Box<dyn Stream<Item = io::Result<(Self::Inbound, NetworkAddress)>> + Send + 'static>>;
fn dial(&self, peer_id: PeerId, addr: NetworkAddress) -> io::Result<Self::Outbound> {
self.dial(peer_id, addr)
.map(|upgrade_fut| upgrade_fut.boxed())
}
fn listen_on(&self, addr: NetworkAddress) -> io::Result<(Self::Listener, NetworkAddress)> {
let (listener, listen_addr) = self.listen_on(addr)?;
let listener = listener
.map_ok(|(upgrade_fut, addr)| (upgrade_fut.boxed(), addr))
.boxed();
Ok((listener, listen_addr))
}
}