TrustTunnel/lib/src/net_utils.rs
Sergei Gunchenko 60c57ee112 Pull request #13: Turn the project structure inside out
Squashed commit of the following:

commit 3e8836cf2ac92ad22291d83b31e9ab83cbdb0330
Author: Sergei Gunchenko <s.gunchenko@adguard.com>
Date:   Mon May 23 19:34:51 2022 +0300

    add comment

commit f08a48bd6c7c386814417f89b6b818d1bfae8613
Author: Sergei Gunchenko <s.gunchenko@adguard.com>
Date:   Mon May 23 19:28:36 2022 +0300

    Fix readmes

commit a83825713de051cdc407a3885b0cab50739d9876
Author: Sergei Gunchenko <s.gunchenko@adguard.com>
Date:   Mon May 23 19:23:54 2022 +0300

    Fix merge artifact

commit fae5b022123a664b34ec3a5104f90d7f6405196f
Author: Sergei Gunchenko <s.gunchenko@adguard.com>
Date:   Mon May 23 19:23:27 2022 +0300

    Turn the project structure inside out
2022-05-24 12:49:06 +03:00

293 lines
9.7 KiB
Rust

extern {
#[cfg(target_os = "macos")]
fn bind_to_interface_by_index(fd: libc::c_int, family: libc::c_int, idx: libc::c_uint) -> libc::c_int;
}
use std::io;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket};
use bytes::{Buf, BufMut, Bytes, BytesMut};
pub(crate) const MIN_LINK_MTU: usize = 1280;
pub(crate) const MIN_IPV4_HEADER_SIZE: usize = 20;
pub(crate) const MIN_IPV6_HEADER_SIZE: usize = 40;
pub(crate) const MAX_DATAGRAM_SIZE: usize = 64 * 1024;
pub(crate) const PLAIN_DNS_PORT_NUMBER: u16 = 53;
pub(crate) const PLAIN_HTTP_PORT_NUMBER: u16 = 80;
pub(crate) const IPV4_WIRE_LENGTH: usize = 4;
pub(crate) const IPV6_WIRE_LENGTH: usize = 16;
const FIXED_LENGTH_IP_WIRE_LENGTH: usize = IPV6_WIRE_LENGTH;
const IPV4_PADDING_WIRE_LENGTH: usize = FIXED_LENGTH_IP_WIRE_LENGTH - IPV4_WIRE_LENGTH;
pub(crate) const HTTP1_ALPN: &str = "http/1.1";
pub(crate) const HTTP2_ALPN: &str = "h2";
pub(crate) const HTTP3_ALPN: &str = "h3";
pub(crate) const QUIC_DATA_FRAME_ID_WIRE_LENGTH: usize = varint_len(0);
/// The minimum value of a stream capacity which allows to send a data chunk.
/// Consists of 1 byte for frame ID, 1 byte for the shortest frame length, and
/// 1 byte for the chunk itself.
pub(crate) const MIN_USABLE_QUIC_STREAM_CAPACITY: usize = QUIC_DATA_FRAME_ID_WIRE_LENGTH + 1 + 1;
pub(crate) type HostnamePort = (String, u16);
#[derive(Debug)]
pub(crate) enum TcpDestination {
Address(SocketAddr),
HostName(HostnamePort),
}
pub(crate) fn make_udp_socket(is_v4: bool) -> io::Result<UdpSocket> {
if is_v4 {
UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)))
} else {
UdpSocket::bind(SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)))
}
}
/// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
pub(crate) const fn varint_len(x: usize) -> usize {
if x <= 63 {
1
} else if x <= 16_383 {
2
} else if x <= 1_073_741_823 {
4
} else if x <= 4_611_686_018_427_387_903 {
8
} else {
unreachable!()
}
}
pub(crate) fn get_fixed_size_ip(bytes: &mut Bytes) -> IpAddr {
let ip = bytes.split_to(IPV6_WIRE_LENGTH);
if ip[..IPV4_PADDING_WIRE_LENGTH].iter().all(|x| *x == 0) {
let address: [u8; IPV4_WIRE_LENGTH] = ip[IPV4_PADDING_WIRE_LENGTH..].try_into().unwrap();
IpAddr::from(address)
} else {
let address: [u8; FIXED_LENGTH_IP_WIRE_LENGTH] = ip[..].try_into().unwrap();
IpAddr::from(address)
}
}
pub(crate) fn put_fixed_size_ip(bytes: &mut BytesMut, ip: &IpAddr) {
match ip {
IpAddr::V4(ip) => {
bytes.put_slice(&[0; IPV4_PADDING_WIRE_LENGTH]);
bytes.put_slice(&ip.octets());
},
IpAddr::V6(ip) => bytes.put_slice(&ip.octets()),
}
}
#[cfg(target_os = "linux")]
pub(crate) fn bind_to_interface(fd: libc::c_int, _family: libc::c_int, name: &str) -> io::Result<()> {
unsafe {
let r = libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_BINDTODEVICE,
name.as_bytes().as_ptr() as *const libc::c_void,
name.len() as libc::socklen_t,
);
if r == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
#[cfg(target_os = "macos")]
pub(crate) fn bind_to_interface(fd: libc::c_int, family: libc::c_int, name: &str) -> io::Result<()> {
unsafe {
let idx = libc::if_nametoindex(name.as_ptr() as *const libc::c_char);
if idx == 0 {
return Err(io::Error::last_os_error());
}
if 0 != bind_to_interface_by_index(fd, family, idx) {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
pub(crate) fn set_socket_ttl(fd: libc::c_int, is_ipv4: bool, ttl: u8) -> io::Result<()> {
unsafe {
let (level, name) = if is_ipv4 {
(libc::IPPROTO_IP, libc::IP_TTL)
} else {
(libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS)
};
let ttl = ttl as libc::c_int;
let r = libc::setsockopt(
fd,
level,
name,
&ttl as *const _ as *const libc::c_void,
std::mem::size_of_val(&ttl) as _,
);
if r < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub(crate) fn socket_addr_to_libc(addr: &SocketAddr) -> (libc::sockaddr_storage, libc::socklen_t) {
unsafe {
let mut storage = std::mem::zeroed();
let len = match addr {
SocketAddr::V4(addr) => {
let storage = &mut storage as *mut _ as *mut libc::sockaddr_in;
(*storage).sin_family = libc::AF_INET as libc::sa_family_t;
(*storage).sin_port = addr.port().to_be();
(*storage).sin_addr.s_addr = u32::from_ne_bytes((*addr).ip().octets());
std::mem::size_of::<libc::sockaddr_in>()
}
SocketAddr::V6(addr) => {
let storage = &mut storage as *mut _ as *mut libc::sockaddr_in6;
(*storage).sin6_family = libc::AF_INET6 as libc::sa_family_t;
(*storage).sin6_port = addr.port().to_be();
(*storage).sin6_flowinfo = addr.flowinfo();
(*storage).sin6_addr.s6_addr = addr.ip().octets();
(*storage).sin6_scope_id = addr.scope_id();
std::mem::size_of::<libc::sockaddr_in6>()
}
};
(storage, len as libc::socklen_t)
}
}
pub(crate) fn libc_to_socket_addr(addr: &libc::sockaddr_storage) -> SocketAddr {
match addr.ss_family as libc::c_int {
libc::AF_INET => unsafe {
let addr = &*(addr as *const _ as *const libc::sockaddr_in);
SocketAddrV4::new(Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), addr.sin_port).into()
}
libc::AF_INET6 => unsafe {
let addr = &*(addr as *const _ as *const libc::sockaddr_in6);
SocketAddrV6::new(
Ipv6Addr::from(addr.sin6_addr.s6_addr),
addr.sin6_port,
addr.sin6_flowinfo,
addr.sin6_scope_id,
).into()
}
_ => unreachable!()
}
}
/// Do [`libc::recvfrom`] over `fd` in a buffer of `buffer_size` size.
/// If [`None`], `buffer_size` defaults to [`MIN_LINK_MTU`].
pub(crate) fn recv_from(fd: libc::c_int, buffer_size: Option<usize>) -> io::Result<(IpAddr, Bytes)> {
let mut buffer = BytesMut::new();
buffer.resize(buffer_size.unwrap_or(MIN_LINK_MTU), 0);
unsafe {
let mut peer = std::mem::zeroed::<libc::sockaddr_storage>();
let mut peer_len = std::mem::size_of_val(&peer) as libc::socklen_t;
let flags = libc::MSG_DONTWAIT;
let r = libc::recvfrom(
fd,
buffer.as_mut_ptr() as *mut libc::c_void,
buffer.len(),
flags,
&mut peer as *mut libc::sockaddr_storage as *mut libc::sockaddr,
&mut peer_len as *mut _,
);
if r < 0 {
return Err(io::Error::last_os_error());
}
buffer.truncate(r as usize);
Ok((
libc_to_socket_addr(&peer).ip(),
buffer.freeze(),
))
}
}
/// # Return
///
/// [`None`] in case of packet is invalid, or
/// the next header protocol ID ([`libc::IPPROTO_*`]) and IP packet payload otherwise.
pub(crate) fn skip_ipv4_header(mut packet: Bytes) -> Option<(libc::c_int, Bytes)> {
if packet.len() < MIN_IPV4_HEADER_SIZE {
return None;
}
let x = packet.get_u8(); // Version + Header length
let header_length = ((x & 0x0f) * 4) as usize;
if header_length < 20 || header_length > packet.len() + 1 {
return None;
}
packet.advance(1 + 2 + 2 + 2 + 1); // DSCP + ECN + Total length + ID + Flags + Frag. Offset + TTL
let next_protocol = packet.get_u8() as libc::c_int;
packet.advance(2 + 4 + 4 + (header_length - MIN_IPV4_HEADER_SIZE)); // Checksum + Source + Destination + Options
Some((next_protocol, packet))
}
/// # Return
///
/// [`None`] in case of packet is invalid, or
/// the next header protocol ID ([`libc::IPPROTO_*`]) and IP packet payload otherwise.
pub(crate) fn skip_ipv6_header(mut packet: Bytes) -> Option<(libc::c_int, Bytes)> {
if packet.len() < MIN_IPV6_HEADER_SIZE {
return None;
}
packet.advance(4 + 2); // Version + Traffic class + Flow label + Payload length
let mut next_protocol = packet.get_u8() as libc::c_int;
packet.advance(1 + 16 + 16); // Hop limit + Source + Destination
loop {
match next_protocol {
libc ::IPPROTO_HOPOPTS | libc::IPPROTO_ROUTING | libc::IPPROTO_DSTOPTS => {
if packet.len() < 2 {
return None;
}
next_protocol = packet.get_u8() as libc::c_int;
let header_ext_length = packet.get_u8() as usize;
packet.advance(header_ext_length);
}
libc::IPPROTO_FRAGMENT => {
const IPV6_FRAGMENT_EXT_LENGTH: usize = 8;
if packet.len() < IPV6_FRAGMENT_EXT_LENGTH {
return None;
}
next_protocol = packet.get_u8() as libc::c_int;
packet.advance(IPV6_FRAGMENT_EXT_LENGTH - 1);
}
_ => break,
}
}
Some((next_protocol, packet))
}
/// Calculates the checksum for the provided byte array
/// in accordance with https://datatracker.ietf.org/doc/html/rfc1071
pub(crate) fn rfc1071_checksum(bytes: &[u8]) -> u16 {
let mut sum = 0_u32;
let is_even = bytes.len() % 2 == 0;
for i in (0..bytes.len()).step_by(2) {
sum += (bytes[i] as u32) << 8;
if is_even || i + 1 < bytes.len() - 1 {
sum += bytes[i + 1] as u32;
}
}
!((sum >> 16) + sum) as u16
}