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;
18pub const GRE_HEADER_BASE_SIZE: usize = 4;
20const GRE_HEADER_FLAGS_VERSION_BASIC: u16 = 0x0000;
21
22pub 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#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct GreHeader {
46 pub flags_version: u16,
47 pub protocol: u16,
48 }
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 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
67fn 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 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 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 true,
102 outer_ttl,
103 Some(outer_tos),
104 );
105
106 gre_header.write_to_packet(&mut packet[ETH_HEADER_SIZE + IP_HEADER_SIZE..]);
108 Ok(())
109}
110
111#[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 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, );
175
176 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
183fn 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}