fips_core/upper/
tun_outbound.rs1use tokio::sync::mpsc;
2
3#[derive(Debug, Clone)]
4pub struct TunOutboundTx {
5 bulk: mpsc::Sender<QueuedTunOutboundPacket>,
6}
7
8#[derive(Debug)]
9pub struct TunOutboundRx {
10 bulk: mpsc::Receiver<QueuedTunOutboundPacket>,
11 bulk_closed: bool,
12}
13
14#[derive(Debug)]
15struct QueuedTunOutboundPacket {
16 packet: Vec<u8>,
17}
18
19impl QueuedTunOutboundPacket {
20 fn new(packet: Vec<u8>) -> Self {
21 Self { packet }
22 }
23
24 fn into_packet(self) -> Vec<u8> {
25 self.packet
26 }
27}
28
29#[derive(Debug, Clone, Copy, Eq, PartialEq)]
30pub(crate) enum TunOutboundAdmission {
31 Enqueued,
32 BulkDropped,
33}
34
35pub(crate) fn tun_outbound_channel(capacity: usize) -> (TunOutboundTx, TunOutboundRx) {
36 let capacity = capacity.max(1);
37 let (bulk_tx, bulk_rx) = mpsc::channel(capacity);
38 (
39 TunOutboundTx { bulk: bulk_tx },
40 TunOutboundRx {
41 bulk: bulk_rx,
42 bulk_closed: false,
43 },
44 )
45}
46
47impl TunOutboundTx {
48 pub async fn send(&self, packet: Vec<u8>) -> Result<(), mpsc::error::SendError<Vec<u8>>> {
49 self.send_queued(QueuedTunOutboundPacket::new(packet)).await
50 }
51
52 async fn send_queued(
53 &self,
54 queued: QueuedTunOutboundPacket,
55 ) -> Result<(), mpsc::error::SendError<Vec<u8>>> {
56 self.bulk
57 .send(queued)
58 .await
59 .map_err(|error| mpsc::error::SendError(error.0.into_packet()))
60 }
61
62 pub fn blocking_send(&self, packet: Vec<u8>) -> Result<(), mpsc::error::SendError<Vec<u8>>> {
63 self.blocking_send_queued(QueuedTunOutboundPacket::new(packet))
64 }
65
66 fn blocking_send_queued(
67 &self,
68 queued: QueuedTunOutboundPacket,
69 ) -> Result<(), mpsc::error::SendError<Vec<u8>>> {
70 self.bulk
71 .blocking_send(queued)
72 .map_err(|error| mpsc::error::SendError(error.0.into_packet()))
73 }
74
75 pub fn try_send(&self, packet: Vec<u8>) -> Result<(), mpsc::error::TrySendError<Vec<u8>>> {
76 self.try_send_queued(QueuedTunOutboundPacket::new(packet))
77 }
78
79 fn try_send_queued(
80 &self,
81 queued: QueuedTunOutboundPacket,
82 ) -> Result<(), mpsc::error::TrySendError<Vec<u8>>> {
83 self.bulk
84 .try_send(queued)
85 .map_err(map_queued_try_send_error)
86 }
87
88 pub(crate) fn admit_from_tun_reader(
89 &self,
90 packet: Vec<u8>,
91 ) -> Result<TunOutboundAdmission, mpsc::error::SendError<Vec<u8>>> {
92 let queued = QueuedTunOutboundPacket::new(packet);
93 match self.bulk.try_send(queued) {
94 Ok(()) => Ok(TunOutboundAdmission::Enqueued),
95 Err(mpsc::error::TrySendError::Full(queued)) => {
96 crate::perf_profile::record_event(
97 crate::perf_profile::Event::PendingTunPacketDropped,
98 );
99 tracing::debug!(
100 len = queued.packet.len(),
101 "Dropping TUN outbound packet because admission queue is full"
102 );
103 Ok(TunOutboundAdmission::BulkDropped)
104 }
105 Err(mpsc::error::TrySendError::Closed(queued)) => {
106 Err(mpsc::error::SendError(queued.into_packet()))
107 }
108 }
109 }
110}
111
112impl TunOutboundRx {
113 pub(crate) async fn recv(&mut self) -> Option<Vec<u8>> {
114 match self.bulk.recv().await {
115 Some(packet) => Some(packet.into_packet()),
116 None => {
117 self.bulk_closed = true;
118 None
119 }
120 }
121 }
122
123 pub(crate) fn try_recv(&mut self) -> Result<Vec<u8>, mpsc::error::TryRecvError> {
124 match self.bulk.try_recv() {
125 Ok(packet) => Ok(packet.into_packet()),
126 Err(mpsc::error::TryRecvError::Empty) => Err(mpsc::error::TryRecvError::Empty),
127 Err(mpsc::error::TryRecvError::Disconnected) => {
128 self.bulk_closed = true;
129 Err(mpsc::error::TryRecvError::Disconnected)
130 }
131 }
132 }
133}
134
135fn map_queued_try_send_error(
136 error: mpsc::error::TrySendError<QueuedTunOutboundPacket>,
137) -> mpsc::error::TrySendError<Vec<u8>> {
138 match error {
139 mpsc::error::TrySendError::Full(packet) => {
140 mpsc::error::TrySendError::Full(packet.into_packet())
141 }
142 mpsc::error::TrySendError::Closed(packet) => {
143 mpsc::error::TrySendError::Closed(packet.into_packet())
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 fn ipv4_packet(proto: u8, body_len: usize) -> Vec<u8> {
153 let total_len = 20 + body_len;
154 let mut packet = vec![0u8; total_len];
155 packet[0] = 0x45;
156 packet[2..4].copy_from_slice(&(total_len as u16).to_be_bytes());
157 packet[9] = proto;
158 packet
159 }
160
161 fn ipv4_tcp_bulk_packet() -> Vec<u8> {
162 let mut packet = ipv4_packet(6, 20 + 300);
163 let tcp_offset = 20;
164 packet[tcp_offset + 12] = 5 << 4;
165 packet[tcp_offset + 13] = 0x10;
166 packet
167 }
168
169 fn ipv4_icmp_packet() -> Vec<u8> {
170 ipv4_packet(1, 8)
171 }
172
173 #[tokio::test]
174 async fn tun_outbound_recv_preserves_app_packet_order() {
175 let (tx, mut rx) = tun_outbound_channel(4);
176 let bulk = ipv4_tcp_bulk_packet();
177 let icmp = ipv4_icmp_packet();
178
179 tx.try_send(bulk.clone())
180 .expect("first app packet should enqueue");
181 tx.try_send(icmp.clone())
182 .expect("second app packet should enqueue");
183
184 assert_eq!(rx.recv().await, Some(bulk));
185 assert_eq!(rx.recv().await, Some(icmp));
186 }
187
188 #[test]
189 fn tun_outbound_capacity_applies_to_icmp_app_payload() {
190 let (tx, mut rx) = tun_outbound_channel(1);
191 let first_bulk = ipv4_tcp_bulk_packet();
192 let icmp = ipv4_icmp_packet();
193
194 tx.try_send(first_bulk.clone())
195 .expect("first app packet should fit");
196 assert!(
197 tx.try_send(icmp).is_err(),
198 "ICMP app payload should share bounded app packet capacity"
199 );
200
201 assert_eq!(rx.try_recv(), Ok(first_bulk));
202 }
203
204 #[test]
205 fn tun_reader_admission_sheds_icmp_app_payload_when_full() {
206 let (tx, mut rx) = tun_outbound_channel(1);
207 let first_bulk = ipv4_tcp_bulk_packet();
208 let icmp = ipv4_icmp_packet();
209
210 assert!(matches!(
211 tx.admit_from_tun_reader(first_bulk.clone()),
212 Ok(TunOutboundAdmission::Enqueued)
213 ));
214 assert!(matches!(
215 tx.admit_from_tun_reader(icmp),
216 Ok(TunOutboundAdmission::BulkDropped)
217 ));
218
219 assert_eq!(rx.try_recv(), Ok(first_bulk));
220 }
221}