1use smoltcp::wire::{
8 EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, Icmpv4DstUnreachable,
9 Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet, IpProtocol, Ipv4Packet, Ipv6Packet,
10};
11
12const ETH_HDR_LEN: usize = 14;
18
19const IPV4_HDR_LEN: usize = 20;
21
22const IPV6_HDR_LEN: usize = 40;
24
25const ICMP_ERROR_HDR_LEN: usize = 8;
27
28const IPV6_MIN_MTU: usize = 1280;
30
31pub(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
63pub(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
72fn 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
136fn 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
193fn ipv4_next_hop_mtu_field(next_hop_mtu: u32) -> u16 {
195 next_hop_mtu.min(u32::from(u16::MAX)) as u16
196}
197
198#[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}