Skip to main content

microsandbox_network/
icmp_error.rs

1//! Synthetic ICMP error frame construction for relay-owned IP edge behavior.
2//!
3//! The UDP relay strips guest IP framing before sending host UDP datagrams.
4//! When the host reports a path-MTU problem, the relay must therefore act as
5//! the IP edge and send the guest an ICMP too-big error itself.
6
7use smoltcp::wire::{
8    EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, Icmpv4DstUnreachable,
9    Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet, IpProtocol, Ipv4Packet, Ipv6Packet,
10};
11
12//--------------------------------------------------------------------------------------------------
13// Constants
14//--------------------------------------------------------------------------------------------------
15
16/// Ethernet header length.
17const ETH_HDR_LEN: usize = 14;
18
19/// IPv4 header length used for synthesized outer ICMP packets.
20const IPV4_HDR_LEN: usize = 20;
21
22/// IPv6 header length.
23const IPV6_HDR_LEN: usize = 40;
24
25/// ICMP error header length before the quoted offending packet.
26const ICMP_ERROR_HDR_LEN: usize = 8;
27
28/// IPv6 error packets must fit within the IPv6 minimum MTU.
29const IPV6_MIN_MTU: usize = 1280;
30
31//--------------------------------------------------------------------------------------------------
32// Functions
33//--------------------------------------------------------------------------------------------------
34
35/// Construct an ICMP Packet Too Big / Fragmentation Needed frame for the guest.
36///
37/// `original_ip_packet` is the guest packet as seen on the virtual wire, without the Ethernet
38/// header. The returned frame is addressed from the gateway MAC to the guest MAC, while the IP
39/// source is the original destination so the guest attributes the PMTU to the peer it dialed.
40pub(crate) fn construct_packet_too_big(
41    original_ip_packet: &[u8],
42    next_hop_mtu: u32,
43    gateway_mac: EthernetAddress,
44    guest_mac: EthernetAddress,
45) -> Option<Vec<u8>> {
46    match original_ip_packet.first()? >> 4 {
47        4 => construct_icmpv4_fragmentation_needed(
48            original_ip_packet,
49            next_hop_mtu,
50            gateway_mac,
51            guest_mac,
52        ),
53        6 => construct_icmpv6_packet_too_big(
54            original_ip_packet,
55            next_hop_mtu,
56            gateway_mac,
57            guest_mac,
58        ),
59        _ => None,
60    }
61}
62
63/// Return the IP packet payload from an Ethernet frame.
64pub(crate) fn ethernet_ip_payload(frame: &[u8]) -> Option<&[u8]> {
65    let eth = EthernetFrame::new_checked(frame).ok()?;
66    match eth.ethertype() {
67        EthernetProtocol::Ipv4 | EthernetProtocol::Ipv6 => Some(eth.payload()),
68        _ => None,
69    }
70}
71
72/// Construct an IPv4 Destination Unreachable / Fragmentation Needed frame.
73fn construct_icmpv4_fragmentation_needed(
74    original_ip_packet: &[u8],
75    next_hop_mtu: u32,
76    gateway_mac: EthernetAddress,
77    guest_mac: EthernetAddress,
78) -> Option<Vec<u8>> {
79    let original = Ipv4Packet::new_checked(original_ip_packet).ok()?;
80    let original_header_len = original.header_len() as usize;
81    if original_ip_packet.len() < original_header_len || original_header_len < IPV4_HDR_LEN {
82        return None;
83    }
84
85    let original_total_len = usize::from(original.total_len());
86    let quoted_len = original_ip_packet
87        .len()
88        .min(original_total_len)
89        .min(original_header_len + 8);
90    if quoted_len < original_header_len {
91        return None;
92    }
93
94    let icmp_len = ICMP_ERROR_HDR_LEN + quoted_len;
95    let ip_total_len = IPV4_HDR_LEN + icmp_len;
96    let frame_len = ETH_HDR_LEN + ip_total_len;
97    let mut buf = vec![0u8; frame_len];
98
99    let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
100    EthernetRepr {
101        src_addr: gateway_mac,
102        dst_addr: guest_mac,
103        ethertype: EthernetProtocol::Ipv4,
104    }
105    .emit(&mut eth_frame);
106
107    {
108        let mut ip = Ipv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN..]);
109        ip.set_version(4);
110        ip.set_header_len(IPV4_HDR_LEN as u8);
111        ip.set_total_len(ip_total_len as u16);
112        ip.clear_flags();
113        ip.set_hop_limit(64);
114        ip.set_next_header(IpProtocol::Icmp);
115        ip.set_src_addr(original.dst_addr());
116        ip.set_dst_addr(original.src_addr());
117        ip.fill_checksum();
118    }
119
120    let icmp_buf = &mut buf[ETH_HDR_LEN + IPV4_HDR_LEN..];
121    {
122        let mut icmp = Icmpv4Packet::new_unchecked(&mut *icmp_buf);
123        icmp.set_msg_type(Icmpv4Message::DstUnreachable);
124        icmp.set_msg_code(Icmpv4DstUnreachable::FragRequired.into());
125        icmp.set_checksum(0);
126    }
127    icmp_buf[4..6].copy_from_slice(&0u16.to_be_bytes());
128    icmp_buf[6..8].copy_from_slice(&ipv4_next_hop_mtu_field(next_hop_mtu).to_be_bytes());
129    icmp_buf[ICMP_ERROR_HDR_LEN..].copy_from_slice(&original_ip_packet[..quoted_len]);
130
131    Icmpv4Packet::new_unchecked(icmp_buf).fill_checksum();
132
133    Some(buf)
134}
135
136/// Construct an IPv6 ICMPv6 Packet Too Big frame.
137fn construct_icmpv6_packet_too_big(
138    original_ip_packet: &[u8],
139    next_hop_mtu: u32,
140    gateway_mac: EthernetAddress,
141    guest_mac: EthernetAddress,
142) -> Option<Vec<u8>> {
143    if original_ip_packet.len() < IPV6_HDR_LEN {
144        return None;
145    }
146
147    let original = Ipv6Packet::new_checked(original_ip_packet).ok()?;
148    let max_quote_len = IPV6_MIN_MTU - IPV6_HDR_LEN - ICMP_ERROR_HDR_LEN;
149    let original_total_len = IPV6_HDR_LEN + usize::from(original.payload_len());
150    let quoted_len = original_ip_packet
151        .len()
152        .min(original_total_len)
153        .min(max_quote_len);
154
155    let icmp_len = ICMP_ERROR_HDR_LEN + quoted_len;
156    let frame_len = ETH_HDR_LEN + IPV6_HDR_LEN + icmp_len;
157    let mut buf = vec![0u8; frame_len];
158
159    let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
160    EthernetRepr {
161        src_addr: gateway_mac,
162        dst_addr: guest_mac,
163        ethertype: EthernetProtocol::Ipv6,
164    }
165    .emit(&mut eth_frame);
166
167    {
168        let mut ip = Ipv6Packet::new_unchecked(&mut buf[ETH_HDR_LEN..]);
169        ip.set_version(6);
170        ip.set_payload_len(icmp_len as u16);
171        ip.set_next_header(IpProtocol::Icmpv6);
172        ip.set_hop_limit(64);
173        ip.set_src_addr(original.dst_addr());
174        ip.set_dst_addr(original.src_addr());
175    }
176
177    let src_addr = original.dst_addr();
178    let dst_addr = original.src_addr();
179    let icmp_buf = &mut buf[ETH_HDR_LEN + IPV6_HDR_LEN..];
180    {
181        let mut icmp = Icmpv6Packet::new_unchecked(&mut *icmp_buf);
182        icmp.set_msg_type(Icmpv6Message::PktTooBig);
183        icmp.set_msg_code(0);
184        icmp.set_pkt_too_big_mtu(next_hop_mtu);
185        icmp.set_checksum(0);
186    }
187    icmp_buf[ICMP_ERROR_HDR_LEN..].copy_from_slice(&original_ip_packet[..quoted_len]);
188    Icmpv6Packet::new_unchecked(icmp_buf).fill_checksum(&src_addr, &dst_addr);
189
190    Some(buf)
191}
192
193/// Encode the RFC 1191 IPv4 next-hop MTU field.
194fn ipv4_next_hop_mtu_field(next_hop_mtu: u32) -> u16 {
195    next_hop_mtu.min(u32::from(u16::MAX)) as u16
196}
197
198//--------------------------------------------------------------------------------------------------
199// Tests
200//--------------------------------------------------------------------------------------------------
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    use std::net::{Ipv4Addr, Ipv6Addr};
207
208    use smoltcp::wire::{
209        Icmpv4DstUnreachable, Icmpv4Message, Icmpv6Message, IpProtocol, Ipv4Packet, Ipv6Packet,
210        UdpPacket,
211    };
212
213    fn gateway_mac() -> EthernetAddress {
214        EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])
215    }
216
217    fn guest_mac() -> EthernetAddress {
218        EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02])
219    }
220
221    fn build_ipv4_udp_packet(payload: &[u8]) -> Vec<u8> {
222        let udp_len = 8 + payload.len();
223        let ip_total_len = IPV4_HDR_LEN + udp_len;
224        let mut buf = vec![0u8; ip_total_len];
225
226        {
227            let mut ip = Ipv4Packet::new_unchecked(&mut buf);
228            ip.set_version(4);
229            ip.set_header_len(IPV4_HDR_LEN as u8);
230            ip.set_total_len(ip_total_len as u16);
231            ip.clear_flags();
232            ip.set_dont_frag(true);
233            ip.set_hop_limit(64);
234            ip.set_next_header(IpProtocol::Udp);
235            ip.set_src_addr(Ipv4Addr::new(100, 96, 0, 2));
236            ip.set_dst_addr(Ipv4Addr::new(203, 0, 113, 10));
237            ip.fill_checksum();
238        }
239
240        let mut udp = UdpPacket::new_unchecked(&mut buf[IPV4_HDR_LEN..]);
241        udp.set_src_port(12345);
242        udp.set_dst_port(443);
243        udp.set_len(udp_len as u16);
244        udp.set_checksum(0);
245        udp.payload_mut().copy_from_slice(payload);
246
247        buf
248    }
249
250    fn build_ipv6_udp_packet(payload: &[u8]) -> Vec<u8> {
251        let udp_len = 8 + payload.len();
252        let mut buf = vec![0u8; IPV6_HDR_LEN + udp_len];
253        let src = "fd42:6d73:62::2".parse::<Ipv6Addr>().unwrap();
254        let dst = "2001:db8::10".parse::<Ipv6Addr>().unwrap();
255
256        {
257            let mut ip = Ipv6Packet::new_unchecked(&mut buf);
258            ip.set_version(6);
259            ip.set_payload_len(udp_len as u16);
260            ip.set_next_header(IpProtocol::Udp);
261            ip.set_hop_limit(64);
262            ip.set_src_addr(src);
263            ip.set_dst_addr(dst);
264        }
265
266        let mut udp = UdpPacket::new_unchecked(&mut buf[IPV6_HDR_LEN..]);
267        udp.set_src_port(12345);
268        udp.set_dst_port(443);
269        udp.set_len(udp_len as u16);
270        udp.payload_mut().copy_from_slice(payload);
271        udp.fill_checksum(
272            &smoltcp::wire::IpAddress::from(src),
273            &smoltcp::wire::IpAddress::from(dst),
274        );
275
276        buf
277    }
278
279    #[test]
280    fn constructs_ipv4_fragmentation_needed_with_next_hop_mtu() {
281        let original = build_ipv4_udp_packet(b"hello world");
282        let frame = construct_packet_too_big(&original, 1280, gateway_mac(), guest_mac()).unwrap();
283
284        let eth = EthernetFrame::new_checked(&frame).unwrap();
285        assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
286        assert_eq!(eth.src_addr(), gateway_mac());
287        assert_eq!(eth.dst_addr(), guest_mac());
288
289        let ip = Ipv4Packet::new_checked(eth.payload()).unwrap();
290        assert_eq!(ip.src_addr(), Ipv4Addr::new(203, 0, 113, 10));
291        assert_eq!(ip.dst_addr(), Ipv4Addr::new(100, 96, 0, 2));
292        assert_eq!(ip.next_header(), IpProtocol::Icmp);
293
294        let icmp = Icmpv4Packet::new_checked(ip.payload()).unwrap();
295        assert_eq!(icmp.msg_type(), Icmpv4Message::DstUnreachable);
296        assert_eq!(
297            icmp.msg_code(),
298            u8::from(Icmpv4DstUnreachable::FragRequired)
299        );
300        assert!(icmp.verify_checksum());
301        assert_eq!(u16::from_be_bytes([ip.payload()[6], ip.payload()[7]]), 1280);
302        assert_eq!(
303            &ip.payload()[ICMP_ERROR_HDR_LEN..],
304            &original[..IPV4_HDR_LEN + 8]
305        );
306    }
307
308    #[test]
309    fn constructs_ipv6_packet_too_big_with_mtu() {
310        let original = build_ipv6_udp_packet(b"hello ipv6");
311        let frame = construct_packet_too_big(&original, 1280, gateway_mac(), guest_mac()).unwrap();
312
313        let eth = EthernetFrame::new_checked(&frame).unwrap();
314        assert_eq!(eth.ethertype(), EthernetProtocol::Ipv6);
315        assert_eq!(eth.src_addr(), gateway_mac());
316        assert_eq!(eth.dst_addr(), guest_mac());
317
318        let ip = Ipv6Packet::new_checked(eth.payload()).unwrap();
319        assert_eq!(ip.src_addr(), "2001:db8::10".parse::<Ipv6Addr>().unwrap());
320        assert_eq!(
321            ip.dst_addr(),
322            "fd42:6d73:62::2".parse::<Ipv6Addr>().unwrap()
323        );
324        assert_eq!(ip.next_header(), IpProtocol::Icmpv6);
325
326        let icmp = Icmpv6Packet::new_checked(ip.payload()).unwrap();
327        assert_eq!(icmp.msg_type(), Icmpv6Message::PktTooBig);
328        assert_eq!(icmp.pkt_too_big_mtu(), 1280);
329        assert!(icmp.verify_checksum(&ip.src_addr(), &ip.dst_addr()));
330        assert_eq!(&icmp.payload()[..original.len()], original.as_slice());
331    }
332}