Skip to main content

fips_core/upper/
tun_outbound.rs

1use 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}