1use std::collections::{HashMap, VecDeque};
8use std::io;
9use std::net::{IpAddr, Ipv4Addr, SocketAddr};
10use std::ops::Range;
11#[cfg(target_os = "linux")]
12use std::os::fd::AsRawFd;
13use std::sync::Arc;
14use std::sync::atomic::{AtomicU16, AtomicU32, AtomicUsize, Ordering};
15use std::time::{Duration, Instant};
16
17use bytes::Bytes;
18use smoltcp::wire::{
19 EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, IpProtocol, Ipv4Packet,
20 Ipv6Packet, UdpPacket,
21};
22use socket2::{Domain, Protocol as SocketProtocol, Socket, Type};
23use tokio::io::Interest;
24use tokio::net::UdpSocket;
25use tokio::sync::mpsc;
26
27use crate::icmp_error::{construct_packet_too_big, ethernet_ip_payload};
28use crate::shared::SharedState;
29
30const SESSION_TIMEOUT: Duration = Duration::from_secs(60);
36
37const OUTBOUND_CHANNEL_CAPACITY: usize = 64;
39
40const MAX_UDP_SESSIONS: usize = 256;
42
43const MAX_QUEUED_BYTES_PER_SESSION: usize = 512 * 1024;
45
46const MAX_PMTU_CONTEXTS: usize = OUTBOUND_CHANNEL_CAPACITY;
48
49const MAX_IPV4_UDP_PAYLOAD_LEN: usize = 65_507;
51
52const MAX_IPV6_UDP_PAYLOAD_LEN: usize = 65_527;
54
55const RECV_BUF_SIZE: usize = MAX_IPV6_UDP_PAYLOAD_LEN;
57
58const ETH_HDR_LEN: usize = 14;
60
61const IPV4_HDR_LEN: usize = 20;
63
64const IPV6_HDR_LEN: usize = 40;
66
67const IPV6_FRAGMENT_HDR_LEN: usize = 8;
69
70const UDP_HDR_LEN: usize = 8;
72
73static NEXT_IPV4_RESPONSE_IDENT: AtomicU16 = AtomicU16::new(1);
75
76static NEXT_IPV6_RESPONSE_IDENT: AtomicU32 = AtomicU32::new(1);
78
79pub struct UdpRelay {
92 shared: Arc<SharedState>,
93 sessions: HashMap<(SocketAddr, SocketAddr), UdpSession>,
94 gateway_mac: EthernetAddress,
95 guest_mac: EthernetAddress,
96 mtu: usize,
97 tokio_handle: tokio::runtime::Handle,
98}
99
100struct UdpSession {
102 outbound_tx: mpsc::Sender<OutboundDatagram>,
104 queued_bytes: Arc<AtomicUsize>,
106 last_active: Instant,
108}
109
110struct OutboundDatagram {
112 payload: Bytes,
114 original_ip_packet: Bytes,
116}
117
118impl OutboundDatagram {
119 fn queued_len(&self) -> usize {
121 self.payload.len() + self.original_ip_packet.len()
122 }
123}
124
125impl UdpRelay {
130 pub fn new(
141 shared: Arc<SharedState>,
142 gateway_mac: [u8; 6],
143 guest_mac: [u8; 6],
144 mtu: usize,
145 tokio_handle: tokio::runtime::Handle,
146 ) -> Self {
147 Self {
148 shared,
149 sessions: HashMap::new(),
150 gateway_mac: EthernetAddress(gateway_mac),
151 guest_mac: EthernetAddress(guest_mac),
152 mtu,
153 tokio_handle,
154 }
155 }
156
157 pub fn relay_outbound(
169 &mut self,
170 frame: &[u8],
171 src: SocketAddr,
172 guest_dst: SocketAddr,
173 host_dst: SocketAddr,
174 ) {
175 let key = (src, guest_dst);
176 if !self.ensure_session(key, src, guest_dst, host_dst) {
177 return;
178 }
179
180 let Some(mut datagram) = extract_udp_datagram(frame) else {
183 return;
184 };
185
186 for attempt in 0..2 {
187 let Some(session) = self.sessions.get_mut(&key) else {
188 return;
189 };
190 let queued_len = datagram.queued_len();
191 if !session.try_reserve(queued_len) {
192 tracing::debug!(
193 guest_src = %src,
194 guest_dst = %guest_dst,
195 queued_len,
196 "UDP relay datagram dropped because session queue budget is full",
197 );
198 return;
199 }
200
201 match session.outbound_tx.try_send(datagram) {
202 Ok(()) => {
203 session.last_active = Instant::now();
204 return;
205 }
206 Err(mpsc::error::TrySendError::Full(returned)) => {
207 session.release(queued_len);
208 tracing::debug!(
209 guest_src = %src,
210 guest_dst = %guest_dst,
211 "UDP relay datagram dropped because outbound queue is full",
212 );
213 drop(returned);
214 return;
215 }
216 Err(mpsc::error::TrySendError::Closed(returned)) => {
217 session.release(queued_len);
218 self.sessions.remove(&key);
219 datagram = returned;
220 if attempt == 0 && self.ensure_session(key, src, guest_dst, host_dst) {
221 continue;
222 }
223 tracing::debug!(
224 guest_src = %src,
225 guest_dst = %guest_dst,
226 "UDP relay datagram dropped because session task is closed",
227 );
228 return;
229 }
230 }
231 }
232 }
233
234 pub fn cleanup_expired(&mut self) {
236 self.sessions
237 .retain(|_, session| session.last_active.elapsed() <= SESSION_TIMEOUT);
238 }
239}
240
241impl UdpRelay {
242 fn ensure_session(
244 &mut self,
245 key: (SocketAddr, SocketAddr),
246 guest_src: SocketAddr,
247 guest_dst: SocketAddr,
248 host_dst: SocketAddr,
249 ) -> bool {
250 if self
251 .sessions
252 .get(&key)
253 .is_some_and(|s| s.last_active.elapsed() <= SESSION_TIMEOUT)
254 {
255 return true;
256 }
257
258 self.sessions.remove(&key);
259 if self.sessions.len() >= MAX_UDP_SESSIONS {
260 self.evict_oldest();
261 }
262
263 let Some(session) = self.create_session(guest_src, guest_dst, host_dst) else {
264 return false;
265 };
266 self.sessions.insert(key, session);
267 true
268 }
269
270 fn evict_oldest(&mut self) {
272 let Some(oldest_key) = self
273 .sessions
274 .iter()
275 .min_by_key(|(_, session)| session.last_active)
276 .map(|(key, _)| *key)
277 else {
278 return;
279 };
280 self.sessions.remove(&oldest_key);
281 }
282
283 fn create_session(
285 &self,
286 guest_src: SocketAddr,
287 guest_dst: SocketAddr,
288 host_dst: SocketAddr,
289 ) -> Option<UdpSession> {
290 let (outbound_tx, outbound_rx) = mpsc::channel(OUTBOUND_CHANNEL_CAPACITY);
291 let queued_bytes = Arc::new(AtomicUsize::new(0));
292
293 let shared = self.shared.clone();
294 let gateway_mac = self.gateway_mac;
295 let guest_mac = self.guest_mac;
296 let mtu = self.mtu;
297 let task_queued_bytes = queued_bytes.clone();
298
299 self.tokio_handle.spawn(async move {
300 if let Err(e) = udp_relay_task(
301 outbound_rx,
302 task_queued_bytes,
303 guest_src,
304 guest_dst,
305 host_dst,
306 shared,
307 gateway_mac,
308 guest_mac,
309 mtu,
310 )
311 .await
312 {
313 tracing::debug!(
314 guest_src = %guest_src,
315 guest_dst = %guest_dst,
316 error = %e,
317 "UDP relay task ended",
318 );
319 }
320 });
321
322 Some(UdpSession {
323 outbound_tx,
324 queued_bytes,
325 last_active: Instant::now(),
326 })
327 }
328}
329
330impl UdpSession {
331 fn try_reserve(&self, len: usize) -> bool {
333 let mut current = self.queued_bytes.load(Ordering::Acquire);
334 loop {
335 let Some(next) = current.checked_add(len) else {
336 return false;
337 };
338 if next > MAX_QUEUED_BYTES_PER_SESSION {
339 return false;
340 }
341 match self.queued_bytes.compare_exchange_weak(
342 current,
343 next,
344 Ordering::AcqRel,
345 Ordering::Acquire,
346 ) {
347 Ok(_) => return true,
348 Err(observed) => current = observed,
349 }
350 }
351 }
352
353 fn release(&self, len: usize) {
355 self.queued_bytes.fetch_sub(len, Ordering::AcqRel);
356 }
357}
358
359#[allow(clippy::too_many_arguments)]
393async fn udp_relay_task(
394 mut outbound_rx: mpsc::Receiver<OutboundDatagram>,
395 queued_bytes: Arc<AtomicUsize>,
396 guest_src: SocketAddr,
397 guest_dst: SocketAddr,
398 host_dst: SocketAddr,
399 shared: Arc<SharedState>,
400 gateway_mac: EthernetAddress,
401 guest_mac: EthernetAddress,
402 mtu: usize,
403) -> std::io::Result<()> {
404 let socket = open_udp_socket(host_dst)?;
405 socket.connect(host_dst).await?;
408
409 let mut recv_buf = vec![0u8; RECV_BUF_SIZE];
410 let mut pmtu_contexts = VecDeque::new();
411 let timeout = SESSION_TIMEOUT;
412
413 loop {
414 tokio::select! {
415 data = outbound_rx.recv() => {
417 match data {
418 Some(datagram) => {
419 queued_bytes.fetch_sub(datagram.queued_len(), Ordering::AcqRel);
420 match socket.send(&datagram.payload).await {
421 Ok(_) => {
422 remember_pmtu_context(&mut pmtu_contexts, datagram.original_ip_packet);
423 }
424 Err(e) if is_message_size_error(&e) => {
425 inject_packet_too_big(
426 &shared,
427 datagram.original_ip_packet.as_ref(),
428 socket_path_mtu(&socket, host_dst).ok(),
429 gateway_mac,
430 guest_mac,
431 );
432 }
433 Err(e) => {
434 tracing::debug!(error = %e, "UDP relay send failed");
435 }
436 }
437 }
438 None => break,
440 }
441 }
442
443 ready = socket.ready(Interest::READABLE | Interest::ERROR) => {
445 let ready = ready?;
446
447 #[cfg(target_os = "linux")]
448 if ready.is_error() {
449 match drain_pmtu_errors(&socket) {
450 Ok(updates) => {
451 for mtu in updates {
452 if let Some(original_ip_packet) =
453 take_pmtu_context(&mut pmtu_contexts, mtu)
454 {
455 inject_packet_too_big(
456 &shared,
457 original_ip_packet.as_ref(),
458 Some(mtu),
459 gateway_mac,
460 guest_mac,
461 );
462 }
463 }
464 }
465 Err(e) => tracing::debug!(error = %e, "UDP relay error queue drain failed"),
466 }
467 }
468
469 if ready.is_readable() {
470 match socket.try_recv(&mut recv_buf) {
471 Ok(n) => {
472 if let Some(frames) = construct_udp_response_frames(
473 guest_dst,
474 guest_src,
475 &recv_buf[..n],
476 gateway_mac,
477 guest_mac,
478 mtu,
479 ) {
480 for frame in frames {
481 if !shared.push_rx_frame_and_wake(frame) {
482 tracing::debug!("UDP relay response dropped because rx_ring is full");
483 break;
484 }
485 }
486 }
487 }
488 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
489 Err(e) if is_message_size_error(&e) => {
490 if let Some(original_ip_packet) =
491 take_pmtu_context_without_mtu(&mut pmtu_contexts)
492 {
493 inject_packet_too_big(
494 &shared,
495 original_ip_packet.as_ref(),
496 socket_path_mtu(&socket, host_dst).ok(),
497 gateway_mac,
498 guest_mac,
499 );
500 }
501 }
502 Err(e) => {
503 tracing::debug!(error = %e, "UDP relay recv failed");
504 break;
505 }
506 }
507 }
508 }
509
510 () = tokio::time::sleep(timeout) => {
512 break;
513 }
514 }
515 }
516
517 Ok(())
518}
519
520pub(crate) fn construct_udp_response(
524 src: SocketAddr,
525 dst: SocketAddr,
526 payload: &[u8],
527 gateway_mac: EthernetAddress,
528 guest_mac: EthernetAddress,
529) -> Option<Vec<u8>> {
530 match (src.ip(), dst.ip()) {
531 (IpAddr::V4(src_ip), IpAddr::V4(dst_ip)) => Some(construct_udp_response_v4(
532 src_ip,
533 src.port(),
534 dst_ip,
535 dst.port(),
536 payload,
537 gateway_mac,
538 guest_mac,
539 )?),
540 (IpAddr::V6(src_ip), IpAddr::V6(dst_ip)) => Some(construct_udp_response_v6(
541 src_ip,
542 src.port(),
543 dst_ip,
544 dst.port(),
545 payload,
546 gateway_mac,
547 guest_mac,
548 )?),
549 _ => None, }
551}
552
553fn construct_udp_response_frames(
555 src: SocketAddr,
556 dst: SocketAddr,
557 payload: &[u8],
558 gateway_mac: EthernetAddress,
559 guest_mac: EthernetAddress,
560 mtu: usize,
561) -> Option<Vec<Vec<u8>>> {
562 match (src.ip(), dst.ip()) {
563 (IpAddr::V4(src_ip), IpAddr::V4(dst_ip)) => construct_udp_response_v4_frames(
564 src_ip,
565 src.port(),
566 dst_ip,
567 dst.port(),
568 payload,
569 gateway_mac,
570 guest_mac,
571 mtu,
572 ),
573 (IpAddr::V6(src_ip), IpAddr::V6(dst_ip)) => construct_udp_response_v6_frames(
574 src_ip,
575 src.port(),
576 dst_ip,
577 dst.port(),
578 payload,
579 gateway_mac,
580 guest_mac,
581 mtu,
582 ),
583 _ => None,
584 }
585}
586
587fn construct_udp_response_v4(
589 src_ip: Ipv4Addr,
590 src_port: u16,
591 dst_ip: Ipv4Addr,
592 dst_port: u16,
593 payload: &[u8],
594 gateway_mac: EthernetAddress,
595 guest_mac: EthernetAddress,
596) -> Option<Vec<u8>> {
597 if payload.len() > MAX_IPV4_UDP_PAYLOAD_LEN {
598 return None;
599 }
600
601 let udp_len = UDP_HDR_LEN + payload.len();
602 let ip_total_len = IPV4_HDR_LEN + udp_len;
603 let frame_len = ETH_HDR_LEN + ip_total_len;
604 let mut buf = vec![0u8; frame_len];
605
606 let eth_repr = EthernetRepr {
608 src_addr: gateway_mac,
609 dst_addr: guest_mac,
610 ethertype: EthernetProtocol::Ipv4,
611 };
612 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
613 eth_repr.emit(&mut eth_frame);
614
615 let ip_buf = &mut buf[ETH_HDR_LEN..];
617 let mut ip_pkt = Ipv4Packet::new_unchecked(ip_buf);
618 ip_pkt.set_version(4);
619 ip_pkt.set_header_len(20);
620 ip_pkt.set_total_len(ip_total_len as u16);
621 ip_pkt.clear_flags();
622 ip_pkt.set_dont_frag(true);
623 ip_pkt.set_hop_limit(64);
624 ip_pkt.set_next_header(IpProtocol::Udp);
625 ip_pkt.set_src_addr(src_ip);
626 ip_pkt.set_dst_addr(dst_ip);
627 ip_pkt.fill_checksum();
628
629 let udp_buf = &mut buf[ETH_HDR_LEN + IPV4_HDR_LEN..];
631 let mut udp_pkt = UdpPacket::new_unchecked(udp_buf);
632 udp_pkt.set_src_port(src_port);
633 udp_pkt.set_dst_port(dst_port);
634 udp_pkt.set_len(udp_len as u16);
635 udp_pkt.set_checksum(0); udp_pkt.payload_mut()[..payload.len()].copy_from_slice(payload);
637
638 Some(buf)
639}
640
641#[allow(clippy::too_many_arguments)]
643fn construct_udp_response_v4_frames(
644 src_ip: Ipv4Addr,
645 src_port: u16,
646 dst_ip: Ipv4Addr,
647 dst_port: u16,
648 payload: &[u8],
649 gateway_mac: EthernetAddress,
650 guest_mac: EthernetAddress,
651 mtu: usize,
652) -> Option<Vec<Vec<u8>>> {
653 if payload.len() > MAX_IPV4_UDP_PAYLOAD_LEN {
654 return None;
655 }
656
657 let udp_len = UDP_HDR_LEN.checked_add(payload.len())?;
658 if IPV4_HDR_LEN.checked_add(udp_len)? <= mtu {
659 return construct_udp_response_v4(
660 src_ip,
661 src_port,
662 dst_ip,
663 dst_port,
664 payload,
665 gateway_mac,
666 guest_mac,
667 )
668 .map(|frame| vec![frame]);
669 }
670
671 let max_fragment_payload_len = fragment_payload_limit(mtu, IPV4_HDR_LEN)?;
672 let ident = NEXT_IPV4_RESPONSE_IDENT.fetch_add(1, Ordering::Relaxed);
673 let udp_datagram = build_udp_datagram_v4(src_port, dst_port, payload)?;
674 let mut frames = Vec::new();
675 let mut offset = 0usize;
676
677 while offset < udp_datagram.len() {
678 let remaining = udp_datagram.len() - offset;
679 let take = remaining.min(max_fragment_payload_len);
680 let more_frags = offset + take < udp_datagram.len();
681 frames.push(construct_ipv4_udp_fragment(
682 src_ip,
683 dst_ip,
684 ident,
685 offset,
686 more_frags,
687 &udp_datagram[offset..offset + take],
688 gateway_mac,
689 guest_mac,
690 )?);
691 offset += take;
692 }
693
694 Some(frames)
695}
696
697fn construct_udp_response_v6(
699 src_ip: std::net::Ipv6Addr,
700 src_port: u16,
701 dst_ip: std::net::Ipv6Addr,
702 dst_port: u16,
703 payload: &[u8],
704 gateway_mac: EthernetAddress,
705 guest_mac: EthernetAddress,
706) -> Option<Vec<u8>> {
707 if payload.len() > MAX_IPV6_UDP_PAYLOAD_LEN {
708 return None;
709 }
710
711 let udp_len = UDP_HDR_LEN + payload.len();
712 let ipv6_hdr_len = 40;
713 let frame_len = ETH_HDR_LEN + ipv6_hdr_len + udp_len;
714 let mut buf = vec![0u8; frame_len];
715
716 let eth_repr = EthernetRepr {
718 src_addr: gateway_mac,
719 dst_addr: guest_mac,
720 ethertype: EthernetProtocol::Ipv6,
721 };
722 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
723 eth_repr.emit(&mut eth_frame);
724
725 let ip_buf = &mut buf[ETH_HDR_LEN..];
727 let mut ip_pkt = Ipv6Packet::new_unchecked(ip_buf);
728 ip_pkt.set_version(6);
729 ip_pkt.set_payload_len(udp_len as u16);
730 ip_pkt.set_next_header(IpProtocol::Udp);
731 ip_pkt.set_hop_limit(64);
732 ip_pkt.set_src_addr(src_ip);
733 ip_pkt.set_dst_addr(dst_ip);
734
735 let udp_buf = &mut buf[ETH_HDR_LEN + ipv6_hdr_len..];
737 let mut udp_pkt = UdpPacket::new_unchecked(udp_buf);
738 udp_pkt.set_src_port(src_port);
739 udp_pkt.set_dst_port(dst_port);
740 udp_pkt.set_len(udp_len as u16);
741 udp_pkt.payload_mut()[..payload.len()].copy_from_slice(payload);
744 udp_pkt.fill_checksum(
747 &smoltcp::wire::IpAddress::from(src_ip),
748 &smoltcp::wire::IpAddress::from(dst_ip),
749 );
750
751 Some(buf)
752}
753
754#[allow(clippy::too_many_arguments)]
756fn construct_udp_response_v6_frames(
757 src_ip: std::net::Ipv6Addr,
758 src_port: u16,
759 dst_ip: std::net::Ipv6Addr,
760 dst_port: u16,
761 payload: &[u8],
762 gateway_mac: EthernetAddress,
763 guest_mac: EthernetAddress,
764 mtu: usize,
765) -> Option<Vec<Vec<u8>>> {
766 if payload.len() > MAX_IPV6_UDP_PAYLOAD_LEN {
767 return None;
768 }
769
770 let udp_len = UDP_HDR_LEN.checked_add(payload.len())?;
771 if IPV6_HDR_LEN.checked_add(udp_len)? <= mtu {
772 return construct_udp_response_v6(
773 src_ip,
774 src_port,
775 dst_ip,
776 dst_port,
777 payload,
778 gateway_mac,
779 guest_mac,
780 )
781 .map(|frame| vec![frame]);
782 }
783
784 let max_fragment_payload_len =
785 fragment_payload_limit(mtu, IPV6_HDR_LEN + IPV6_FRAGMENT_HDR_LEN)?;
786 let ident = NEXT_IPV6_RESPONSE_IDENT.fetch_add(1, Ordering::Relaxed);
787 let udp_datagram = build_udp_datagram_v6(src_ip, src_port, dst_ip, dst_port, payload)?;
788 let mut frames = Vec::new();
789 let mut offset = 0usize;
790
791 while offset < udp_datagram.len() {
792 let remaining = udp_datagram.len() - offset;
793 let take = remaining.min(max_fragment_payload_len);
794 let more_frags = offset + take < udp_datagram.len();
795 frames.push(construct_ipv6_udp_fragment(
796 src_ip,
797 dst_ip,
798 ident,
799 offset,
800 more_frags,
801 &udp_datagram[offset..offset + take],
802 gateway_mac,
803 guest_mac,
804 )?);
805 offset += take;
806 }
807
808 Some(frames)
809}
810
811fn build_udp_datagram_v4(src_port: u16, dst_port: u16, payload: &[u8]) -> Option<Vec<u8>> {
813 let udp_len = UDP_HDR_LEN.checked_add(payload.len())?;
814 if udp_len > u16::MAX as usize {
815 return None;
816 }
817
818 let mut buf = vec![0u8; udp_len];
819 let mut udp = UdpPacket::new_unchecked(&mut buf);
820 udp.set_src_port(src_port);
821 udp.set_dst_port(dst_port);
822 udp.set_len(udp_len as u16);
823 udp.set_checksum(0);
824 udp.payload_mut()[..payload.len()].copy_from_slice(payload);
825 Some(buf)
826}
827
828fn build_udp_datagram_v6(
830 src_ip: std::net::Ipv6Addr,
831 src_port: u16,
832 dst_ip: std::net::Ipv6Addr,
833 dst_port: u16,
834 payload: &[u8],
835) -> Option<Vec<u8>> {
836 let udp_len = UDP_HDR_LEN.checked_add(payload.len())?;
837 if udp_len > u16::MAX as usize {
838 return None;
839 }
840
841 let mut buf = vec![0u8; udp_len];
842 let mut udp = UdpPacket::new_unchecked(&mut buf);
843 udp.set_src_port(src_port);
844 udp.set_dst_port(dst_port);
845 udp.set_len(udp_len as u16);
846 udp.payload_mut()[..payload.len()].copy_from_slice(payload);
847 udp.fill_checksum(
848 &smoltcp::wire::IpAddress::from(src_ip),
849 &smoltcp::wire::IpAddress::from(dst_ip),
850 );
851 Some(buf)
852}
853
854fn fragment_payload_limit(mtu: usize, header_len: usize) -> Option<usize> {
856 let available = mtu.checked_sub(header_len)?;
857 let limit = available - (available % 8);
858 (limit > 0).then_some(limit)
859}
860
861#[allow(clippy::too_many_arguments)]
863fn construct_ipv4_udp_fragment(
864 src_ip: Ipv4Addr,
865 dst_ip: Ipv4Addr,
866 ident: u16,
867 fragment_offset: usize,
868 more_frags: bool,
869 fragment_payload: &[u8],
870 gateway_mac: EthernetAddress,
871 guest_mac: EthernetAddress,
872) -> Option<Vec<u8>> {
873 let ip_total_len = IPV4_HDR_LEN.checked_add(fragment_payload.len())?;
874 if ip_total_len > u16::MAX as usize || fragment_offset > u16::MAX as usize {
875 return None;
876 }
877
878 let mut buf = vec![0u8; ETH_HDR_LEN + ip_total_len];
879 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
880 EthernetRepr {
881 src_addr: gateway_mac,
882 dst_addr: guest_mac,
883 ethertype: EthernetProtocol::Ipv4,
884 }
885 .emit(&mut eth_frame);
886
887 let mut ip = Ipv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN..]);
888 ip.set_version(4);
889 ip.set_header_len(IPV4_HDR_LEN as u8);
890 ip.set_total_len(ip_total_len as u16);
891 ip.set_ident(ident);
892 ip.clear_flags();
893 ip.set_more_frags(more_frags);
894 ip.set_frag_offset(fragment_offset as u16);
895 ip.set_hop_limit(64);
896 ip.set_next_header(IpProtocol::Udp);
897 ip.set_src_addr(src_ip);
898 ip.set_dst_addr(dst_ip);
899 ip.payload_mut().copy_from_slice(fragment_payload);
900 ip.fill_checksum();
901
902 Some(buf)
903}
904
905#[allow(clippy::too_many_arguments)]
907fn construct_ipv6_udp_fragment(
908 src_ip: std::net::Ipv6Addr,
909 dst_ip: std::net::Ipv6Addr,
910 ident: u32,
911 fragment_offset: usize,
912 more_frags: bool,
913 fragment_payload: &[u8],
914 gateway_mac: EthernetAddress,
915 guest_mac: EthernetAddress,
916) -> Option<Vec<u8>> {
917 let ipv6_payload_len = IPV6_FRAGMENT_HDR_LEN.checked_add(fragment_payload.len())?;
918 if ipv6_payload_len > u16::MAX as usize || fragment_offset > u16::MAX as usize {
919 return None;
920 }
921
922 let mut buf = vec![0u8; ETH_HDR_LEN + IPV6_HDR_LEN + ipv6_payload_len];
923 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
924 EthernetRepr {
925 src_addr: gateway_mac,
926 dst_addr: guest_mac,
927 ethertype: EthernetProtocol::Ipv6,
928 }
929 .emit(&mut eth_frame);
930
931 {
932 let mut ip = Ipv6Packet::new_unchecked(&mut buf[ETH_HDR_LEN..]);
933 ip.set_version(6);
934 ip.set_payload_len(ipv6_payload_len as u16);
935 ip.set_next_header(IpProtocol::Ipv6Frag);
936 ip.set_hop_limit(64);
937 ip.set_src_addr(src_ip);
938 ip.set_dst_addr(dst_ip);
939 }
940
941 let fragment_start = ETH_HDR_LEN + IPV6_HDR_LEN;
942 let fragment = &mut buf[fragment_start..][..IPV6_FRAGMENT_HDR_LEN];
943 fragment[0] = IpProtocol::Udp.into();
944 fragment[1] = 0;
945 let offset_units = u16::try_from(fragment_offset / 8).ok()?;
946 let raw = (offset_units << 3) | u16::from(more_frags);
947 fragment[2..4].copy_from_slice(&raw.to_be_bytes());
948 fragment[4..8].copy_from_slice(&ident.to_be_bytes());
949 buf[fragment_start + IPV6_FRAGMENT_HDR_LEN..].copy_from_slice(fragment_payload);
950
951 Some(buf)
952}
953
954pub(crate) fn extract_udp_payload(frame: &[u8]) -> Option<&[u8]> {
956 let eth = EthernetFrame::new_checked(frame).ok()?;
957 match eth.ethertype() {
958 EthernetProtocol::Ipv4 => {
959 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
960 let udp = UdpPacket::new_checked(ipv4.payload()).ok()?;
961 Some(udp.payload())
962 }
963 EthernetProtocol::Ipv6 => {
964 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
965 let udp = UdpPacket::new_checked(ipv6.payload()).ok()?;
966 Some(udp.payload())
967 }
968 _ => None,
969 }
970}
971
972fn extract_udp_datagram(frame: &[u8]) -> Option<OutboundDatagram> {
974 let original = ethernet_ip_payload(frame)?;
975 let payload_range = udp_payload_range(original)?;
976 let original_ip_packet = Bytes::copy_from_slice(original);
977 let payload = original_ip_packet.slice(payload_range);
978
979 Some(OutboundDatagram {
980 payload,
981 original_ip_packet,
982 })
983}
984
985fn udp_payload_range(ip_packet: &[u8]) -> Option<Range<usize>> {
987 match ip_packet.first()? >> 4 {
988 4 => {
989 let ipv4 = Ipv4Packet::new_checked(ip_packet).ok()?;
990 if ipv4.next_header() != IpProtocol::Udp {
991 return None;
992 }
993 let udp_offset = ipv4.header_len() as usize;
994 let udp = UdpPacket::new_checked(&ip_packet[udp_offset..]).ok()?;
995 let payload_start = udp_offset + UDP_HDR_LEN;
996 let payload_end = udp_offset + usize::from(udp.len());
997 (payload_start <= payload_end && payload_end <= ip_packet.len())
998 .then_some(payload_start..payload_end)
999 }
1000 6 => {
1001 let ipv6 = Ipv6Packet::new_checked(ip_packet).ok()?;
1002 if ipv6.next_header() != IpProtocol::Udp {
1003 return None;
1004 }
1005 let udp_offset = 40;
1006 let udp = UdpPacket::new_checked(&ip_packet[udp_offset..]).ok()?;
1007 let payload_start = udp_offset + UDP_HDR_LEN;
1008 let payload_end = udp_offset + usize::from(udp.len());
1009 (payload_start <= payload_end && payload_end <= ip_packet.len())
1010 .then_some(payload_start..payload_end)
1011 }
1012 _ => None,
1013 }
1014}
1015
1016fn open_udp_socket(host_dst: SocketAddr) -> io::Result<UdpSocket> {
1018 let domain = match host_dst {
1019 SocketAddr::V4(_) => Domain::IPV4,
1020 SocketAddr::V6(_) => Domain::IPV6,
1021 };
1022 let socket = Socket::new(domain, Type::DGRAM, Some(SocketProtocol::UDP))?;
1023 socket.set_nonblocking(true)?;
1024
1025 let bind_addr: SocketAddr = match host_dst {
1026 SocketAddr::V4(_) => (Ipv4Addr::UNSPECIFIED, 0u16).into(),
1027 SocketAddr::V6(_) => (std::net::Ipv6Addr::UNSPECIFIED, 0u16).into(),
1028 };
1029 socket.bind(&bind_addr.into())?;
1030
1031 #[cfg(target_os = "linux")]
1032 enable_linux_pmtu_errors(&socket, host_dst)?;
1033
1034 UdpSocket::from_std(socket.into())
1035}
1036
1037fn is_message_size_error(error: &io::Error) -> bool {
1039 error.raw_os_error() == Some(libc::EMSGSIZE)
1040}
1041
1042fn inject_packet_too_big(
1044 shared: &SharedState,
1045 original_ip_packet: &[u8],
1046 next_hop_mtu: Option<u32>,
1047 gateway_mac: EthernetAddress,
1048 guest_mac: EthernetAddress,
1049) {
1050 let Some(next_hop_mtu) =
1051 next_hop_mtu.filter(|mtu| valid_packet_too_big_mtu(original_ip_packet, *mtu))
1052 else {
1053 tracing::debug!("UDP relay skipped ICMP too-big because no valid MTU is available");
1054 return;
1055 };
1056
1057 let Some(frame) =
1058 construct_packet_too_big(original_ip_packet, next_hop_mtu, gateway_mac, guest_mac)
1059 else {
1060 return;
1061 };
1062
1063 if !shared.push_rx_frame_and_wake(frame) {
1064 tracing::debug!("UDP relay ICMP too-big response dropped because rx_ring is full");
1065 }
1066}
1067
1068fn valid_packet_too_big_mtu(original_ip_packet: &[u8], mtu: u32) -> bool {
1070 if mtu == 0 {
1071 return false;
1072 }
1073
1074 match original_ip_packet.first().map(|byte| byte >> 4) {
1075 Some(6) => mtu >= 1280,
1078 Some(4) => true,
1079 _ => false,
1080 }
1081}
1082
1083fn remember_pmtu_context(contexts: &mut VecDeque<Bytes>, original_ip_packet: Bytes) {
1085 if contexts.len() >= MAX_PMTU_CONTEXTS {
1086 contexts.pop_front();
1087 }
1088 contexts.push_back(original_ip_packet);
1089}
1090
1091#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
1093fn take_pmtu_context(contexts: &mut VecDeque<Bytes>, mtu: u32) -> Option<Bytes> {
1094 if mtu > 0
1095 && let Some(position) = contexts.iter().position(|packet| {
1096 original_ip_packet_len(packet.as_ref()).is_some_and(|len| len > mtu as usize)
1097 })
1098 {
1099 return contexts.remove(position);
1100 }
1101
1102 contexts.pop_front()
1103}
1104
1105fn take_pmtu_context_without_mtu(contexts: &mut VecDeque<Bytes>) -> Option<Bytes> {
1107 contexts.pop_front()
1108}
1109
1110#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
1112fn original_ip_packet_len(packet: &[u8]) -> Option<usize> {
1113 match packet.first()? >> 4 {
1114 4 => {
1115 let ipv4 = Ipv4Packet::new_checked(packet).ok()?;
1116 Some(usize::from(ipv4.total_len()).min(packet.len()))
1117 }
1118 6 => {
1119 let ipv6 = Ipv6Packet::new_checked(packet).ok()?;
1120 Some((40 + usize::from(ipv6.payload_len())).min(packet.len()))
1121 }
1122 _ => None,
1123 }
1124}
1125
1126#[cfg(target_os = "linux")]
1128fn socket_path_mtu(socket: &UdpSocket, host_dst: SocketAddr) -> io::Result<u32> {
1129 let fd = socket.as_raw_fd();
1130 let (level, optname) = match host_dst {
1131 SocketAddr::V4(_) => (libc::IPPROTO_IP, libc::IP_MTU),
1132 SocketAddr::V6(_) => (libc::IPPROTO_IPV6, libc::IPV6_MTU),
1133 };
1134
1135 let mut mtu: libc::c_int = 0;
1136 let mut len = std::mem::size_of_val(&mtu) as libc::socklen_t;
1137 let rc = unsafe {
1139 libc::getsockopt(
1140 fd,
1141 level,
1142 optname,
1143 (&mut mtu as *mut libc::c_int).cast(),
1144 &mut len,
1145 )
1146 };
1147 if rc == -1 {
1148 return Err(io::Error::last_os_error());
1149 }
1150
1151 if mtu <= 0 {
1152 return Err(io::Error::new(
1153 io::ErrorKind::InvalidData,
1154 "connected socket reported a non-positive path MTU",
1155 ));
1156 }
1157
1158 Ok(mtu as u32)
1159}
1160
1161#[cfg(not(target_os = "linux"))]
1163fn socket_path_mtu(_socket: &UdpSocket, _host_dst: SocketAddr) -> io::Result<u32> {
1164 Err(io::Error::new(
1165 io::ErrorKind::Unsupported,
1166 "path MTU query is not supported on this platform",
1167 ))
1168}
1169
1170#[cfg(target_os = "linux")]
1172fn enable_linux_pmtu_errors(socket: &Socket, host_dst: SocketAddr) -> io::Result<()> {
1173 let fd = socket.as_raw_fd();
1174 match host_dst {
1175 SocketAddr::V4(_) => set_socket_bool(fd, libc::IPPROTO_IP, libc::IP_RECVERR, true),
1176 SocketAddr::V6(_) => set_socket_bool(fd, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, true),
1177 }
1178}
1179
1180#[cfg(target_os = "linux")]
1182fn set_socket_bool(
1183 fd: libc::c_int,
1184 level: libc::c_int,
1185 optname: libc::c_int,
1186 value: bool,
1187) -> io::Result<()> {
1188 let value: libc::c_int = i32::from(value);
1189 let rc = unsafe {
1191 libc::setsockopt(
1192 fd,
1193 level,
1194 optname,
1195 (&value as *const libc::c_int).cast(),
1196 std::mem::size_of_val(&value) as libc::socklen_t,
1197 )
1198 };
1199 if rc == -1 {
1200 return Err(io::Error::last_os_error());
1201 }
1202 Ok(())
1203}
1204
1205#[cfg(target_os = "linux")]
1207fn drain_pmtu_errors(socket: &UdpSocket) -> io::Result<Vec<u32>> {
1208 socket.try_io(Interest::ERROR, || {
1209 drain_pmtu_errors_from_fd(socket.as_raw_fd())
1210 })
1211}
1212
1213#[cfg(target_os = "linux")]
1215fn drain_pmtu_errors_from_fd(fd: libc::c_int) -> io::Result<Vec<u32>> {
1216 let mut mtus = Vec::new();
1217 let mut drained_any = false;
1218
1219 loop {
1220 match recv_one_pmtu_error(fd) {
1221 Ok(mtu) => {
1222 drained_any = true;
1223 if let Some(mtu) = mtu {
1224 mtus.push(mtu);
1225 }
1226 }
1227 Err(e) if e.kind() == io::ErrorKind::WouldBlock && drained_any => return Ok(mtus),
1228 Err(e) => return Err(e),
1229 }
1230 }
1231}
1232
1233#[cfg(target_os = "linux")]
1235fn recv_one_pmtu_error(fd: libc::c_int) -> io::Result<Option<u32>> {
1236 let mut data = [0u8; 1];
1237 let mut iov = libc::iovec {
1238 iov_base: data.as_mut_ptr().cast(),
1239 iov_len: data.len(),
1240 };
1241 let mut control = [0u8; 512];
1242 let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
1244 msg.msg_iov = &mut iov;
1245 msg.msg_iovlen = 1;
1246 msg.msg_control = control.as_mut_ptr().cast();
1247 msg.msg_controllen = control.len();
1248
1249 let rc = unsafe { libc::recvmsg(fd, &mut msg, libc::MSG_ERRQUEUE | libc::MSG_DONTWAIT) };
1251 if rc == -1 {
1252 return Err(io::Error::last_os_error());
1253 }
1254
1255 let control_len = msg.msg_controllen.min(control.len());
1256 Ok(parse_pmtu_from_control_messages(&control[..control_len]))
1257}
1258
1259#[cfg(target_os = "linux")]
1261fn parse_pmtu_from_control_messages(control: &[u8]) -> Option<u32> {
1262 let header_len = std::mem::size_of::<libc::cmsghdr>();
1263 let data_offset = cmsg_align(header_len)?;
1264 let mut offset = 0usize;
1265
1266 while offset.checked_add(header_len)? <= control.len() {
1267 let header = control.get(offset..offset + header_len)?;
1268 let cmsg_len = read_native_usize(header)?;
1269 if cmsg_len < data_offset {
1270 return None;
1271 }
1272
1273 let message_end = offset.checked_add(cmsg_len)?;
1274 if message_end > control.len() {
1275 return None;
1276 }
1277
1278 let cmsg_level = read_native_c_int(header.get(std::mem::size_of::<usize>()..)?)?;
1279 let cmsg_type = read_native_c_int(
1280 header.get(std::mem::size_of::<usize>() + std::mem::size_of::<libc::c_int>()..)?,
1281 )?;
1282 let is_extended_error = (cmsg_level == libc::IPPROTO_IP && cmsg_type == libc::IP_RECVERR)
1283 || (cmsg_level == libc::IPPROTO_IPV6 && cmsg_type == libc::IPV6_RECVERR);
1284
1285 if is_extended_error {
1286 let data_start = offset.checked_add(data_offset)?;
1287 if data_start > message_end {
1288 return None;
1289 }
1290
1291 if let Some(mtu) = parse_sock_extended_err_mtu(control.get(data_start..message_end)?) {
1292 return Some(mtu);
1293 }
1294 }
1295
1296 offset = offset.checked_add(cmsg_align(cmsg_len)?)?;
1297 }
1298
1299 None
1300}
1301
1302#[cfg(target_os = "linux")]
1304fn parse_sock_extended_err_mtu(data: &[u8]) -> Option<u32> {
1305 let error_len = std::mem::size_of::<libc::sock_extended_err>();
1306 if data.len() < error_len {
1307 return None;
1308 }
1309
1310 let ee_errno = read_native_u32(data)?;
1311 let ee_origin = *data.get(4)?;
1312 let ee_info = read_native_u32(data.get(8..)?)?;
1313
1314 if ee_errno == libc::EMSGSIZE as u32
1315 && matches!(
1316 ee_origin,
1317 libc::SO_EE_ORIGIN_ICMP | libc::SO_EE_ORIGIN_ICMP6 | libc::SO_EE_ORIGIN_LOCAL
1318 )
1319 {
1320 return Some(ee_info);
1321 }
1322
1323 None
1324}
1325
1326#[cfg(target_os = "linux")]
1328fn read_native_usize(bytes: &[u8]) -> Option<usize> {
1329 match std::mem::size_of::<usize>() {
1330 4 => Some(u32::from_ne_bytes(bytes.get(..4)?.try_into().ok()?) as usize),
1331 8 => Some(u64::from_ne_bytes(bytes.get(..8)?.try_into().ok()?) as usize),
1332 _ => None,
1333 }
1334}
1335
1336#[cfg(target_os = "linux")]
1338fn read_native_c_int(bytes: &[u8]) -> Option<libc::c_int> {
1339 match std::mem::size_of::<libc::c_int>() {
1340 4 => Some(i32::from_ne_bytes(bytes.get(..4)?.try_into().ok()?) as libc::c_int),
1341 _ => None,
1342 }
1343}
1344
1345#[cfg(target_os = "linux")]
1347fn read_native_u32(bytes: &[u8]) -> Option<u32> {
1348 Some(u32::from_ne_bytes(bytes.get(..4)?.try_into().ok()?))
1349}
1350
1351#[cfg(target_os = "linux")]
1353fn cmsg_align(len: usize) -> Option<usize> {
1354 let align = std::mem::size_of::<usize>();
1355 len.checked_add(align - 1).map(|value| value & !(align - 1))
1356}
1357
1358#[cfg(test)]
1363mod tests {
1364 use super::*;
1365
1366 #[test]
1367 fn construct_v4_response_has_correct_structure() {
1368 let payload = b"hello";
1369 let frame = construct_udp_response_v4(
1370 Ipv4Addr::new(8, 8, 8, 8),
1371 53,
1372 Ipv4Addr::new(100, 96, 0, 2),
1373 12345,
1374 payload,
1375 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1376 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1377 )
1378 .unwrap();
1379
1380 assert_eq!(frame.len(), ETH_HDR_LEN + IPV4_HDR_LEN + UDP_HDR_LEN + 5);
1381
1382 let eth = EthernetFrame::new_checked(&frame).unwrap();
1384 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1385 assert_eq!(
1386 eth.dst_addr(),
1387 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02])
1388 );
1389
1390 let ipv4 = Ipv4Packet::new_checked(eth.payload()).unwrap();
1391 assert_eq!(ipv4.src_addr(), Ipv4Addr::new(8, 8, 8, 8));
1392 assert_eq!(ipv4.dst_addr(), Ipv4Addr::new(100, 96, 0, 2));
1393 assert_eq!(ipv4.next_header(), IpProtocol::Udp);
1394
1395 let udp = UdpPacket::new_checked(ipv4.payload()).unwrap();
1396 assert_eq!(udp.src_port(), 53);
1397 assert_eq!(udp.dst_port(), 12345);
1398 assert_eq!(udp.payload(), b"hello");
1399 }
1400
1401 #[test]
1402 fn construct_v6_response_has_correct_structure() {
1403 let payload = b"hello ipv6";
1404 let src = "2001:db8::1".parse::<std::net::Ipv6Addr>().unwrap();
1405 let dst = "fd42:6d73:62::2".parse::<std::net::Ipv6Addr>().unwrap();
1406 let frame = construct_udp_response_v6(
1407 src,
1408 53,
1409 dst,
1410 12345,
1411 payload,
1412 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1413 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1414 )
1415 .unwrap();
1416
1417 let ipv6_hdr_len = 40;
1418 assert_eq!(
1419 frame.len(),
1420 ETH_HDR_LEN + ipv6_hdr_len + UDP_HDR_LEN + payload.len()
1421 );
1422
1423 let eth = EthernetFrame::new_checked(&frame).unwrap();
1425 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv6);
1426
1427 let ipv6 = Ipv6Packet::new_checked(eth.payload()).unwrap();
1428 assert_eq!(ipv6.next_header(), IpProtocol::Udp);
1429
1430 let udp = UdpPacket::new_checked(ipv6.payload()).unwrap();
1431 assert_eq!(udp.src_port(), 53);
1432 assert_eq!(udp.dst_port(), 12345);
1433 assert_eq!(udp.payload(), b"hello ipv6");
1434 assert_ne!(udp.checksum(), 0, "IPv6 UDP checksum must not be zero");
1436 assert!(
1438 udp.verify_checksum(
1439 &smoltcp::wire::IpAddress::from(src),
1440 &smoltcp::wire::IpAddress::from(dst),
1441 ),
1442 "IPv6 UDP checksum must be valid"
1443 );
1444 }
1445
1446 #[test]
1447 fn extract_payload_from_v6_udp_frame() {
1448 let src = "2001:db8::1".parse::<std::net::Ipv6Addr>().unwrap();
1449 let dst = "fd42:6d73:62::2".parse::<std::net::Ipv6Addr>().unwrap();
1450 let frame = construct_udp_response_v6(
1451 src,
1452 80,
1453 dst,
1454 54321,
1455 b"v6 data",
1456 EthernetAddress([0; 6]),
1457 EthernetAddress([0; 6]),
1458 )
1459 .unwrap();
1460 let payload = extract_udp_payload(&frame).unwrap();
1461 assert_eq!(payload, b"v6 data");
1462 }
1463
1464 #[test]
1465 fn extract_payload_from_v4_udp_frame() {
1466 let frame = construct_udp_response_v4(
1468 Ipv4Addr::new(1, 2, 3, 4),
1469 80,
1470 Ipv4Addr::new(10, 0, 0, 2),
1471 54321,
1472 b"test data",
1473 EthernetAddress([0; 6]),
1474 EthernetAddress([0; 6]),
1475 )
1476 .unwrap();
1477 let payload = extract_udp_payload(&frame).unwrap();
1478 assert_eq!(payload, b"test data");
1479 }
1480
1481 #[test]
1482 fn construct_v4_response_rejects_payload_over_ipv4_limit() {
1483 let payload = vec![0u8; MAX_IPV4_UDP_PAYLOAD_LEN + 1];
1484 assert!(
1485 construct_udp_response_v4(
1486 Ipv4Addr::new(8, 8, 8, 8),
1487 53,
1488 Ipv4Addr::new(100, 96, 0, 2),
1489 12345,
1490 &payload,
1491 EthernetAddress([0; 6]),
1492 EthernetAddress([0; 6]),
1493 )
1494 .is_none()
1495 );
1496 }
1497
1498 #[test]
1499 fn construct_v4_response_frames_fragment_large_payload_to_mtu() {
1500 let payload = vec![b'x'; 2000];
1501 let frames = construct_udp_response_frames(
1502 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 53),
1503 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 2)), 12345),
1504 &payload,
1505 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1506 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1507 1500,
1508 )
1509 .unwrap();
1510
1511 assert_eq!(frames.len(), 2);
1512 assert!(frames.iter().all(|frame| frame.len() <= ETH_HDR_LEN + 1500));
1513
1514 let first_eth = EthernetFrame::new_checked(frames[0].as_slice()).unwrap();
1515 let first_ip = Ipv4Packet::new_checked(first_eth.payload()).unwrap();
1516 assert!(first_ip.more_frags());
1517 assert_eq!(first_ip.frag_offset(), 0);
1518 assert_eq!(&first_ip.payload()[..2], &53u16.to_be_bytes());
1519 assert_eq!(&first_ip.payload()[2..4], &12345u16.to_be_bytes());
1520 assert_eq!(
1521 u16::from_be_bytes([first_ip.payload()[4], first_ip.payload()[5]]) as usize,
1522 UDP_HDR_LEN + payload.len()
1523 );
1524
1525 let second_eth = EthernetFrame::new_checked(frames[1].as_slice()).unwrap();
1526 let second_ip = Ipv4Packet::new_checked(second_eth.payload()).unwrap();
1527 assert!(!second_ip.more_frags());
1528 assert_eq!(second_ip.frag_offset(), 1480);
1529 }
1530
1531 #[test]
1532 fn construct_v6_response_frames_fragment_large_payload_to_mtu() {
1533 let src = "2001:db8::1".parse::<std::net::Ipv6Addr>().unwrap();
1534 let dst = "fd42:6d73:62::2".parse::<std::net::Ipv6Addr>().unwrap();
1535 let payload = vec![b'x'; 2000];
1536 let frames = construct_udp_response_frames(
1537 SocketAddr::new(IpAddr::V6(src), 53),
1538 SocketAddr::new(IpAddr::V6(dst), 12345),
1539 &payload,
1540 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1541 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1542 1500,
1543 )
1544 .unwrap();
1545
1546 assert_eq!(frames.len(), 2);
1547 assert!(frames.iter().all(|frame| frame.len() <= ETH_HDR_LEN + 1500));
1548
1549 let first_eth = EthernetFrame::new_checked(frames[0].as_slice()).unwrap();
1550 let first_ip = Ipv6Packet::new_checked(first_eth.payload()).unwrap();
1551 assert_eq!(first_ip.next_header(), IpProtocol::Ipv6Frag);
1552 let first_fragment = &first_ip.payload()[..IPV6_FRAGMENT_HDR_LEN];
1553 let first_raw = u16::from_be_bytes([first_fragment[2], first_fragment[3]]);
1554 assert_eq!(IpProtocol::from(first_fragment[0]), IpProtocol::Udp);
1555 assert_eq!(first_raw >> 3, 0);
1556 assert_eq!(first_raw & 1, 1);
1557
1558 let second_eth = EthernetFrame::new_checked(frames[1].as_slice()).unwrap();
1559 let second_ip = Ipv6Packet::new_checked(second_eth.payload()).unwrap();
1560 assert_eq!(second_ip.next_header(), IpProtocol::Ipv6Frag);
1561 let second_fragment = &second_ip.payload()[..IPV6_FRAGMENT_HDR_LEN];
1562 let second_raw = u16::from_be_bytes([second_fragment[2], second_fragment[3]]);
1563 assert_eq!(usize::from(second_raw >> 3) * 8, 1448);
1564 assert_eq!(second_raw & 1, 0);
1565 assert_eq!(
1566 &second_ip.payload()[IPV6_FRAGMENT_HDR_LEN..][..2],
1567 &payload[1440..1442]
1568 );
1569 }
1570
1571 #[test]
1572 fn packet_too_big_mtu_validation_rejects_unusable_ipv6_values() {
1573 let frame = construct_udp_response_v6(
1574 "2001:db8::1".parse().unwrap(),
1575 443,
1576 "fd42:6d73:62::2".parse().unwrap(),
1577 12345,
1578 b"payload",
1579 EthernetAddress([0; 6]),
1580 EthernetAddress([0; 6]),
1581 )
1582 .unwrap();
1583 let original = ethernet_ip_payload(&frame).unwrap();
1584
1585 assert!(!valid_packet_too_big_mtu(original, 0));
1586 assert!(!valid_packet_too_big_mtu(original, 1279));
1587 assert!(valid_packet_too_big_mtu(original, 1280));
1588 }
1589
1590 #[test]
1591 fn pmtu_context_prefers_packet_larger_than_reported_mtu() {
1592 let small = Bytes::from(build_ipv4_udp_packet_for_test(100));
1593 let large = Bytes::from(build_ipv4_udp_packet_for_test(1400));
1594 let mut contexts = VecDeque::from([small.clone(), large.clone()]);
1595
1596 let selected = take_pmtu_context(&mut contexts, 1280).unwrap();
1597 assert_eq!(selected, large);
1598 assert_eq!(contexts.pop_front().unwrap(), small);
1599 }
1600
1601 #[cfg(target_os = "linux")]
1602 #[test]
1603 fn parses_linux_extended_error_control_message_without_pointer_walk() {
1604 let mut error = vec![0u8; std::mem::size_of::<libc::sock_extended_err>()];
1605 write_native_u32_for_test(&mut error, libc::EMSGSIZE as u32);
1606 error[4] = libc::SO_EE_ORIGIN_ICMP;
1607 write_native_u32_for_test(&mut error[8..], 1280);
1608
1609 let mut control = Vec::new();
1610 push_control_message_for_test(&mut control, libc::IPPROTO_IP, libc::IP_RECVERR, &error);
1611
1612 assert_eq!(parse_pmtu_from_control_messages(&control), Some(1280));
1613 assert_eq!(parse_pmtu_from_control_messages(&control[..8]), None);
1614 }
1615
1616 fn build_ipv4_udp_packet_for_test(payload_len: usize) -> Vec<u8> {
1617 let payload = vec![0u8; payload_len];
1618 let udp_len = UDP_HDR_LEN + payload.len();
1619 let ip_total_len = IPV4_HDR_LEN + udp_len;
1620 let mut packet = vec![0u8; ip_total_len];
1621
1622 {
1623 let mut ip = Ipv4Packet::new_unchecked(&mut packet);
1624 ip.set_version(4);
1625 ip.set_header_len(IPV4_HDR_LEN as u8);
1626 ip.set_total_len(ip_total_len as u16);
1627 ip.clear_flags();
1628 ip.set_hop_limit(64);
1629 ip.set_next_header(IpProtocol::Udp);
1630 ip.set_src_addr(Ipv4Addr::new(100, 96, 0, 2));
1631 ip.set_dst_addr(Ipv4Addr::new(203, 0, 113, 10));
1632 ip.fill_checksum();
1633 }
1634
1635 let mut udp = UdpPacket::new_unchecked(&mut packet[IPV4_HDR_LEN..]);
1636 udp.set_src_port(12345);
1637 udp.set_dst_port(443);
1638 udp.set_len(udp_len as u16);
1639 udp.payload_mut().copy_from_slice(&payload);
1640
1641 packet
1642 }
1643
1644 #[cfg(target_os = "linux")]
1645 fn push_control_message_for_test(
1646 control: &mut Vec<u8>,
1647 level: libc::c_int,
1648 message_type: libc::c_int,
1649 data: &[u8],
1650 ) {
1651 let header_len = std::mem::size_of::<libc::cmsghdr>();
1652 let data_offset = cmsg_align(header_len).unwrap();
1653 let cmsg_len = data_offset + data.len();
1654 let aligned_len = cmsg_align(cmsg_len).unwrap();
1655 let start = control.len();
1656 control.resize(start + aligned_len, 0);
1657
1658 write_native_usize_for_test(&mut control[start..], cmsg_len);
1659 write_native_c_int_for_test(&mut control[start + std::mem::size_of::<usize>()..], level);
1660 write_native_c_int_for_test(
1661 &mut control
1662 [start + std::mem::size_of::<usize>() + std::mem::size_of::<libc::c_int>()..],
1663 message_type,
1664 );
1665 control[start + data_offset..start + data_offset + data.len()].copy_from_slice(data);
1666 }
1667
1668 #[cfg(target_os = "linux")]
1669 fn write_native_usize_for_test(bytes: &mut [u8], value: usize) {
1670 match std::mem::size_of::<usize>() {
1671 4 => bytes[..4].copy_from_slice(&(value as u32).to_ne_bytes()),
1672 8 => bytes[..8].copy_from_slice(&(value as u64).to_ne_bytes()),
1673 _ => unreachable!("unsupported size_t width"),
1674 }
1675 }
1676
1677 #[cfg(target_os = "linux")]
1678 fn write_native_c_int_for_test(bytes: &mut [u8], value: libc::c_int) {
1679 match std::mem::size_of::<libc::c_int>() {
1680 4 => bytes[..4].copy_from_slice(&(value as i32).to_ne_bytes()),
1681 _ => unreachable!("unsupported c_int width"),
1682 }
1683 }
1684
1685 #[cfg(target_os = "linux")]
1686 fn write_native_u32_for_test(bytes: &mut [u8], value: u32) {
1687 bytes[..4].copy_from_slice(&value.to_ne_bytes());
1688 }
1689}