Skip to main content

agave_xdp/gre/
packet.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4    crate::{
5        ecn_codepoint::EcnCodepoint,
6        netlink::{GreTunnelInfo, MacAddress},
7        packet::{
8            ETH_HEADER_SIZE, IP_HEADER_SIZE, UDP_HEADER_SIZE, write_eth_header, write_ip_header,
9            write_ip_header_for_udp, write_udp_header,
10        },
11    },
12    libc::{ETH_P_IP, IPPROTO_GRE},
13    std::net::{IpAddr, Ipv4Addr},
14    thiserror::Error,
15};
16
17pub const INNER_PACKET_HEADER_SIZE: usize = IP_HEADER_SIZE + UDP_HEADER_SIZE;
18/// Minimal GRE header size in bytes without optional fields
19pub const GRE_HEADER_BASE_SIZE: usize = 4;
20const GRE_HEADER_FLAGS_VERSION_BASIC: u16 = 0x0000;
21
22/// Calculate total packet size for GRE encapsulation.
23pub const fn gre_packet_size(payload_len: usize) -> usize {
24    (ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE + INNER_PACKET_HEADER_SIZE)
25        .saturating_add(payload_len)
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
29pub enum PacketError {
30    #[error("packet buffer too small: need {needed} bytes, have {have} bytes")]
31    BufferTooSmall { needed: usize, have: usize },
32    #[error("invalid GRE tunnel endpoints")]
33    InvalidTunnelEndpoints,
34}
35
36/// GRE header structure
37///
38/// Currently only supports basic GRE header format:
39/// - flags_version: Always 0x0000
40/// - protocol: Always 0x0800 (IPv4)
41///
42/// Optional fields (C, K, S flags) are not yet supported but may be added in the future.
43#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct GreHeader {
46    pub flags_version: u16,
47    pub protocol: u16,
48    // Note: Optional fields (key, checksum, sequence) will be added here
49    // when full GRE support is implemented. Keeping struct simple for now.
50}
51
52impl GreHeader {
53    pub fn new(protocol_type: u16) -> Self {
54        Self {
55            flags_version: GRE_HEADER_FLAGS_VERSION_BASIC,
56            protocol: protocol_type,
57        }
58    }
59
60    /// Write the GRE header to a packet buffer
61    pub fn write_to_packet(&self, packet: &mut [u8]) {
62        packet[0..2].copy_from_slice(&self.flags_version.to_be_bytes());
63        packet[2..4].copy_from_slice(&self.protocol.to_be_bytes());
64    }
65}
66
67/// Write outer headers for L3 GRE encapsulation.
68///
69/// This function assumes the buffer has reserved space for the inner packet
70/// (IP + UDP + payload) and for the outer Ethernet + IP + GRE headers. It does
71/// not move or preserve any existing data in `packet`.
72fn write_gre_outer_headers(
73    packet: &mut [u8],
74    gre_src_mac: &MacAddress,
75    gre_dst_mac: &MacAddress,
76    inner_packet_len: usize,
77    gre_header: &GreHeader,
78    ecn: Option<EcnCodepoint>,
79    info: &GreTunnelInfo,
80) -> Result<(), PacketError> {
81    write_eth_header(packet, &gre_src_mac.0, &gre_dst_mac.0);
82
83    // Write outer IP header (protocol = GRE = 47)
84    let gre_payload_len = GRE_HEADER_BASE_SIZE + inner_packet_len;
85    let outer_ttl = (info.ttl != 0).then_some(info.ttl);
86    let (IpAddr::V4(outer_local), IpAddr::V4(outer_remote)) = (info.local, info.remote) else {
87        return Err(PacketError::InvalidTunnelEndpoints);
88    };
89    // We must construct the outer encapsulating IP header by copying the two-bit ECN field of the
90    // incoming IP header, see RFC 6040 Section 4.1.
91    let outer_tos = update_tos_with_ecn(info.tos, ecn);
92
93    write_ip_header(
94        &mut packet[ETH_HEADER_SIZE..],
95        &outer_local,
96        &outer_remote,
97        gre_payload_len as u16,
98        IPPROTO_GRE as u8,
99        // GRE fragmentation is not supported. The outer IP header is always written with the Don't
100        // Fragment (DF) flag set.
101        true,
102        outer_ttl,
103        Some(outer_tos),
104    );
105
106    // Write GRE header
107    gre_header.write_to_packet(&mut packet[ETH_HEADER_SIZE + IP_HEADER_SIZE..]);
108    Ok(())
109}
110
111/// Construct an L3 GRE packet from a UDP payload
112///
113/// This function takes a UDP payload and constructs a complete L3 GRE packet
114/// with the structure: [Ethernet] [Outer IP] [GRE] [Inner IP] [UDP] [Payload]
115///
116/// On success, writes the constructed GRE packet into `packet` and returns `Ok(())`;
117/// returns an error if the buffer is too small or the tunnel info is invalid.
118#[allow(clippy::too_many_arguments)]
119pub fn construct_gre_packet(
120    packet: &mut [u8],
121    src_mac: &MacAddress,
122    dst_mac: &MacAddress,
123    src_ip: &Ipv4Addr,
124    dst_ip: &Ipv4Addr,
125    src_port: u16,
126    dst_port: u16,
127    payload: &[u8],
128    ecn: Option<EcnCodepoint>,
129    info: &GreTunnelInfo,
130) -> Result<(), PacketError> {
131    let payload_len = payload.len();
132
133    let gre_header = GreHeader::new(ETH_P_IP as u16);
134    let inner_packet_len = INNER_PACKET_HEADER_SIZE.saturating_add(payload_len);
135    let gre_packet_size =
136        (ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE).saturating_add(inner_packet_len);
137
138    // Ensure packet buffer is large enough
139    if packet.len() < gre_packet_size {
140        return Err(PacketError::BufferTooSmall {
141            needed: gre_packet_size,
142            have: packet.len(),
143        });
144    }
145
146    write_gre_outer_headers(
147        packet,
148        src_mac,
149        dst_mac,
150        inner_packet_len,
151        &gre_header,
152        ecn,
153        info,
154    )?;
155
156    let inner_start = ETH_HEADER_SIZE + IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE;
157
158    write_ip_header_for_udp(
159        &mut packet[inner_start..inner_start + IP_HEADER_SIZE],
160        src_ip,
161        dst_ip,
162        ecn,
163        (UDP_HEADER_SIZE + payload_len) as u16,
164    );
165
166    write_udp_header(
167        &mut packet[inner_start + IP_HEADER_SIZE..inner_start + INNER_PACKET_HEADER_SIZE],
168        src_ip,
169        src_port,
170        dst_ip,
171        dst_port,
172        payload_len as u16,
173        false, // no checksums
174    );
175
176    // Write payload
177    packet[inner_start + INNER_PACKET_HEADER_SIZE
178        ..inner_start + INNER_PACKET_HEADER_SIZE + payload_len]
179        .copy_from_slice(payload);
180    Ok(())
181}
182
183/// Returns `tos` with ECN (low 2 bits) replaced; DSCP (high 6 bits) is preserved.
184fn update_tos_with_ecn(tos: u8, ecn: Option<EcnCodepoint>) -> u8 {
185    tos & 0b1111_1100 | ecn.map_or(0, |ecn| ecn as u8 & 0b0000_0011)
186}