use crate::inet::{FromSocketAddr, IntoNetworkInterface, ToSocketAddr};
use crate::service::select::Selectable;
use pnet::datalink::NetworkInterface;
use socket2::{Domain, Protocol, Socket, Type};
use std::fmt::{Display, Formatter};
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::{io, vec};
use url::{ParseError, Url};
pub mod buffer;
pub mod file;
#[cfg(all(target_os = "linux", feature = "ktls"))]
pub mod ktls;
#[cfg(feature = "mio")]
pub mod mio;
pub mod record;
pub mod replay;
pub mod tcp;
#[cfg(any(feature = "rustls", feature = "openssl"))]
pub mod tls;
#[cfg(target_os = "linux")]
const EINPROGRESS: i32 = 115;
#[cfg(target_os = "macos")]
const EINPROGRESS: i32 = 36;
pub trait BindAndConnect {
fn bind_and_connect<A>(addr: A, net_iface: Option<SocketAddr>, cpu: Option<usize>) -> io::Result<TcpStream>
where
A: ToSocketAddrs,
{
Self::bind_and_connect_with_socket_config(addr, net_iface, cpu, |_| Ok(()))
}
fn bind_and_connect_with_socket_config<A, F>(
addr: A,
net_iface: Option<SocketAddr>,
cpu: Option<usize>,
socket_config: F,
) -> io::Result<TcpStream>
where
A: ToSocketAddrs,
F: FnOnce(&Socket) -> io::Result<()>;
}
impl BindAndConnect for TcpStream {
#[allow(unused_variables)]
fn bind_and_connect_with_socket_config<A, F>(
addr: A,
net_iface: Option<SocketAddr>,
cpu: Option<usize>,
socket_config: F,
) -> io::Result<TcpStream>
where
A: ToSocketAddrs,
F: FnOnce(&Socket) -> io::Result<()>,
{
let socket_addr = addr
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::other("unable to resolve socket address"))?;
let socket = Socket::new(
match &socket_addr {
SocketAddr::V4(_) => Domain::IPV4,
SocketAddr::V6(_) => Domain::IPV6,
},
Type::STREAM,
Some(Protocol::TCP),
)?;
socket.set_nonblocking(true)?;
socket.set_nodelay(true)?;
socket.set_keepalive(true)?;
socket_config(&socket)?;
if let Some(addr) = net_iface {
socket.bind(&addr.into())?;
}
#[cfg(target_os = "linux")]
if let Some(cpu_affinity) = cpu {
socket.set_cpu_affinity(cpu_affinity)?;
}
match socket.connect(&socket_addr.into()) {
Ok(()) => Ok(socket.into()),
Err(err) if err.raw_os_error() == Some(EINPROGRESS) => Ok(socket.into()),
Err(err) => Err(err),
}
}
}
impl Selectable for TcpStream {
fn connected(&mut self) -> io::Result<bool> {
Ok(true)
}
fn make_writable(&mut self) -> io::Result<()> {
Ok(())
}
fn make_readable(&mut self) -> io::Result<()> {
Ok(())
}
}
pub trait ConnectionInfoProvider {
fn connection_info(&self) -> &ConnectionInfo;
}
#[derive(Debug, Clone, Default)]
pub struct ConnectionInfo {
host: String,
port: u16,
net_iface: Option<SocketAddr>,
net_iface_name: Option<String>,
cpu: Option<usize>,
socket_config: Option<fn(&Socket) -> io::Result<()>>,
}
impl ToSocketAddrs for ConnectionInfo {
type Iter = vec::IntoIter<SocketAddr>;
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
format!("{}:{}", self.host, self.port).to_socket_addrs()
}
}
impl Display for ConnectionInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.host, self.port)
}
}
impl TryFrom<Url> for ConnectionInfo {
type Error = io::Error;
fn try_from(url: Url) -> Result<Self, Self::Error> {
Ok(ConnectionInfo {
host: url
.host_str()
.ok_or_else(|| io::Error::other("host not present"))?
.to_owned(),
port: url
.port_or_known_default()
.ok_or_else(|| io::Error::other("port not present"))?,
net_iface: None,
net_iface_name: None,
cpu: None,
socket_config: None,
})
}
}
impl TryFrom<Result<Url, ParseError>> for ConnectionInfo {
type Error = io::Error;
fn try_from(result: Result<Url, ParseError>) -> Result<Self, Self::Error> {
match result {
Ok(url) => Ok(url.try_into()?),
Err(err) => Err(io::Error::other(err)),
}
}
}
impl From<(&str, u16)> for ConnectionInfo {
fn from(host_and_port: (&str, u16)) -> Self {
let (host, port) = host_and_port;
Self::new(host, port)
}
}
impl ConnectionInfo {
pub fn new(host: impl AsRef<str>, port: u16) -> Self {
Self {
host: host.as_ref().to_string(),
port,
net_iface: None,
net_iface_name: None,
cpu: None,
socket_config: None,
}
}
pub fn with_net_iface(self, net_iface: SocketAddr) -> Self {
let nif = NetworkInterface::from_socket_addr(net_iface).expect("invalid network interface");
Self {
net_iface: Some(net_iface),
net_iface_name: Some(nif.name),
..self
}
}
pub fn with_net_iface_from_name(self, net_iface_name: &str) -> Self {
let net_iface = net_iface_name
.into_network_interface()
.and_then(|iface| iface.to_socket_addr())
.unwrap_or_else(|| panic!("invalid network interface: {net_iface_name}"));
Self {
net_iface: Some(net_iface),
net_iface_name: Some(net_iface_name.to_owned()),
..self
}
}
pub fn with_cpu(self, cpu: usize) -> Self {
Self { cpu: Some(cpu), ..self }
}
pub fn with_socket_config(self, socket_config: fn(&Socket) -> io::Result<()>) -> Self {
Self {
socket_config: Some(socket_config),
..self
}
}
pub fn host(&self) -> &str {
&self.host
}
pub fn port(&self) -> u16 {
self.port
}
pub fn net_iface(&self) -> Option<SocketAddr> {
self.net_iface
}
pub fn net_iface_name_as_str(&self) -> Option<&str> {
self.net_iface_name.as_deref()
}
pub fn into_tcp_stream(self) -> io::Result<tcp::TcpStream> {
let stream =
TcpStream::bind_and_connect_with_socket_config(&self, self.net_iface, self.cpu, |socket| {
match self.socket_config {
Some(f) => f(socket),
None => Ok(()),
}
})?;
Ok(tcp::TcpStream::new(stream, self))
}
pub fn into_tcp_stream_with_addr(self, addr: SocketAddr) -> io::Result<tcp::TcpStream> {
let stream =
TcpStream::bind_and_connect_with_socket_config(addr, self.net_iface, self.cpu, |socket| {
match self.socket_config {
Some(f) => f(socket),
None => Ok(()),
}
})?;
Ok(tcp::TcpStream::new(stream, self))
}
}