#![allow(clippy::arithmetic_side_effects)]
use {
crate::{
netlink::{GreTunnelInfo, MacAddress},
packet::{
ETH_HEADER_SIZE, IP_HEADER_SIZE, UDP_HEADER_SIZE, write_eth_header, write_ip_header,
write_ip_header_for_udp, write_udp_header,
},
},
libc::{ETH_P_IP, IPPROTO_GRE},
std::net::{IpAddr, Ipv4Addr},
thiserror::Error,
};
pub const INNER_PACKET_HEADER_SIZE: usize = IP_HEADER_SIZE + UDP_HEADER_SIZE;
pub const GRE_HEADER_BASE_SIZE: usize = 4;
const GRE_HEADER_FLAGS_VERSION_BASIC: u16 = 0x0000;
pub const fn gre_packet_size(payload_len: usize) -> usize {
(ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE + INNER_PACKET_HEADER_SIZE)
.saturating_add(payload_len)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum PacketError {
#[error("packet buffer too small: need {needed} bytes, have {have} bytes")]
BufferTooSmall { needed: usize, have: usize },
#[error("invalid GRE tunnel endpoints")]
InvalidTunnelEndpoints,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct GreHeader {
pub flags_version: u16,
pub protocol: u16,
}
impl GreHeader {
pub fn new(protocol_type: u16) -> Self {
Self {
flags_version: GRE_HEADER_FLAGS_VERSION_BASIC,
protocol: protocol_type,
}
}
pub fn write_to_packet(&self, packet: &mut [u8]) {
packet[0..2].copy_from_slice(&self.flags_version.to_be_bytes());
packet[2..4].copy_from_slice(&self.protocol.to_be_bytes());
}
}
fn write_gre_outer_headers(
packet: &mut [u8],
gre_src_mac: &MacAddress,
gre_dst_mac: &MacAddress,
inner_packet_len: usize,
gre_header: &GreHeader,
info: &GreTunnelInfo,
) -> Result<(), PacketError> {
write_eth_header(packet, &gre_src_mac.0, &gre_dst_mac.0);
let dont_fragment = info.pmtudisc != 0;
let gre_payload_len = GRE_HEADER_BASE_SIZE + inner_packet_len;
let outer_ttl = (info.ttl != 0).then_some(info.ttl);
let (IpAddr::V4(outer_local), IpAddr::V4(outer_remote)) = (info.local, info.remote) else {
return Err(PacketError::InvalidTunnelEndpoints);
};
write_ip_header(
&mut packet[ETH_HEADER_SIZE..],
&outer_local,
&outer_remote,
gre_payload_len as u16,
IPPROTO_GRE as u8,
dont_fragment,
outer_ttl,
Some(info.tos),
);
gre_header.write_to_packet(&mut packet[ETH_HEADER_SIZE + IP_HEADER_SIZE..]);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn construct_gre_packet(
packet: &mut [u8],
src_mac: &MacAddress,
dst_mac: &MacAddress,
src_ip: &Ipv4Addr,
dst_ip: &Ipv4Addr,
src_port: u16,
dst_port: u16,
payload: &[u8],
info: &GreTunnelInfo,
) -> Result<(), PacketError> {
let payload_len = payload.len();
let gre_header = GreHeader::new(ETH_P_IP as u16);
let inner_packet_len = INNER_PACKET_HEADER_SIZE.saturating_add(payload_len);
let gre_packet_size =
(ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE).saturating_add(inner_packet_len);
if packet.len() < gre_packet_size {
return Err(PacketError::BufferTooSmall {
needed: gre_packet_size,
have: packet.len(),
});
}
write_gre_outer_headers(
packet,
src_mac,
dst_mac,
inner_packet_len,
&gre_header,
info,
)?;
let inner_start = ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE;
write_ip_header_for_udp(
&mut packet[inner_start..inner_start + IP_HEADER_SIZE],
src_ip,
dst_ip,
(UDP_HEADER_SIZE + payload_len) as u16,
);
write_udp_header(
&mut packet[inner_start + IP_HEADER_SIZE..inner_start + INNER_PACKET_HEADER_SIZE],
src_ip,
src_port,
dst_ip,
dst_port,
payload_len as u16,
false, );
packet[inner_start + INNER_PACKET_HEADER_SIZE
..inner_start + INNER_PACKET_HEADER_SIZE + payload_len]
.copy_from_slice(payload);
Ok(())
}