Skip to main content

arcbox_packet/ethernet/
tcp.rs

1use std::net::Ipv4Addr;
2
3use super::{
4    ETH_HEADER_LEN,
5    checksum::{ipv4_header_checksum, tcp_checksum},
6};
7
8/// Parameters for constructing TCP frames on the fast path.
9#[derive(Debug, Clone, Copy)]
10pub struct TcpFrameParams {
11    pub src_ip: Ipv4Addr,
12    pub dst_ip: Ipv4Addr,
13    pub src_port: u16,
14    pub dst_port: u16,
15    pub seq: u32,
16    pub ack: u32,
17    pub window: u16,
18    pub src_mac: [u8; 6],
19    pub dst_mac: [u8; 6],
20}
21
22/// Builds a TCP ACK frame (no payload) to acknowledge data from the guest.
23///
24/// Used by the TCP fast path to ACK guest data segments using the
25/// hand-rolled TCP state machine in `TcpBridge`.
26#[must_use]
27pub fn build_tcp_ack_frame(p: &TcpFrameParams) -> Vec<u8> {
28    let TcpFrameParams {
29        src_ip,
30        dst_ip,
31        src_port,
32        dst_port,
33        seq,
34        ack,
35        window,
36        src_mac,
37        dst_mac,
38    } = *p;
39    // ACK frame: ETH(14) + IP(20) + TCP(20) = 54 bytes, no payload.
40    let tcp_hdr_len = 20;
41    let ip_total_len = 20 + tcp_hdr_len;
42    let frame_len = ETH_HEADER_LEN + ip_total_len;
43    let mut frame = vec![0u8; frame_len];
44
45    // -- Ethernet header --
46    frame[0..6].copy_from_slice(&dst_mac);
47    frame[6..12].copy_from_slice(&src_mac);
48    frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
49
50    // -- IPv4 header --
51    let ip = ETH_HEADER_LEN;
52    frame[ip] = 0x45; // Version 4, IHL 5
53    frame[ip + 2..ip + 4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
54    frame[ip + 6..ip + 8].copy_from_slice(&0x4000u16.to_be_bytes()); // Don't Fragment
55    frame[ip + 8] = 64; // TTL
56    frame[ip + 9] = 6; // Protocol: TCP
57    frame[ip + 12..ip + 16].copy_from_slice(&src_ip.octets());
58    frame[ip + 16..ip + 20].copy_from_slice(&dst_ip.octets());
59    let ip_cksum = ipv4_header_checksum(&frame[ip..ip + 20]);
60    frame[ip + 10..ip + 12].copy_from_slice(&ip_cksum.to_be_bytes());
61
62    // -- TCP header (20 bytes, no options) --
63    let tcp = ip + 20;
64    frame[tcp..tcp + 2].copy_from_slice(&src_port.to_be_bytes());
65    frame[tcp + 2..tcp + 4].copy_from_slice(&dst_port.to_be_bytes());
66    frame[tcp + 4..tcp + 8].copy_from_slice(&seq.to_be_bytes());
67    frame[tcp + 8..tcp + 12].copy_from_slice(&ack.to_be_bytes());
68    frame[tcp + 12] = 0x50; // Data offset: 5 (20 bytes), no options
69    frame[tcp + 13] = 0x10; // Flags: ACK
70    frame[tcp + 14..tcp + 16].copy_from_slice(&window.to_be_bytes());
71    let tcp_cksum = tcp_checksum(src_ip, dst_ip, &frame[tcp..]);
72    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
73
74    frame
75}
76
77/// Builds a TCP data frame carrying payload from the host to the guest.
78///
79/// Used by the TCP fast path to inject host TcpStream data into the guest
80/// via `TcpBridge`'s hand-rolled TCP state machine.
81#[must_use]
82pub fn build_tcp_data_frame(p: &TcpFrameParams, payload: &[u8]) -> Vec<u8> {
83    let TcpFrameParams {
84        src_ip,
85        dst_ip,
86        src_port,
87        dst_port,
88        seq,
89        ack,
90        window,
91        src_mac,
92        dst_mac,
93    } = *p;
94
95    let tcp_hdr_len = 20;
96    let tcp_total_len = tcp_hdr_len + payload.len();
97    let ip_total_len = 20 + tcp_total_len;
98    // IPv4 encodes total length in a 16-bit field; the `as u16` cast below
99    // would silently truncate for oversized payloads. Assert so the failure
100    // is loud if a caller ever exceeds the per-frame MTU budget.
101    assert!(
102        u16::try_from(ip_total_len).is_ok(),
103        "build_tcp_data_frame: ip_total_len={ip_total_len} overflows u16"
104    );
105    let frame_len = ETH_HEADER_LEN + ip_total_len;
106    let mut frame = vec![0u8; frame_len];
107
108    // -- Ethernet header --
109    frame[0..6].copy_from_slice(&dst_mac);
110    frame[6..12].copy_from_slice(&src_mac);
111    frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
112
113    // -- IPv4 header --
114    let ip = ETH_HEADER_LEN;
115    frame[ip] = 0x45;
116    frame[ip + 2..ip + 4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
117    frame[ip + 6..ip + 8].copy_from_slice(&0x4000u16.to_be_bytes()); // DF
118    frame[ip + 8] = 64; // TTL
119    frame[ip + 9] = 6; // TCP
120    frame[ip + 12..ip + 16].copy_from_slice(&src_ip.octets());
121    frame[ip + 16..ip + 20].copy_from_slice(&dst_ip.octets());
122    let ip_cksum = ipv4_header_checksum(&frame[ip..ip + 20]);
123    frame[ip + 10..ip + 12].copy_from_slice(&ip_cksum.to_be_bytes());
124
125    // -- TCP header --
126    let tcp = ip + 20;
127    frame[tcp..tcp + 2].copy_from_slice(&src_port.to_be_bytes());
128    frame[tcp + 2..tcp + 4].copy_from_slice(&dst_port.to_be_bytes());
129    frame[tcp + 4..tcp + 8].copy_from_slice(&seq.to_be_bytes());
130    frame[tcp + 8..tcp + 12].copy_from_slice(&ack.to_be_bytes());
131    frame[tcp + 12] = 0x50; // Data offset: 5
132    frame[tcp + 13] = 0x18; // Flags: ACK | PSH
133    frame[tcp + 14..tcp + 16].copy_from_slice(&window.to_be_bytes());
134
135    // -- Payload --
136    frame[tcp + 20..].copy_from_slice(payload);
137
138    // TCP checksum over header + payload.
139    let tcp_cksum = tcp_checksum(src_ip, dst_ip, &frame[tcp..]);
140    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
141
142    frame
143}
144
145/// Builds a TCP data frame with only a pseudo-header checksum (for GSO).
146///
147/// Identical to [`build_tcp_data_frame`] except the TCP checksum field
148/// contains only the pseudo-header checksum. The guest kernel completes
149/// the checksum per-segment during GSO segmentation.
150#[must_use]
151pub fn build_tcp_data_frame_partial_csum(p: &TcpFrameParams, payload: &[u8]) -> Vec<u8> {
152    let TcpFrameParams {
153        src_ip,
154        dst_ip,
155        src_port,
156        dst_port,
157        seq,
158        ack,
159        window,
160        src_mac,
161        dst_mac,
162    } = *p;
163
164    let tcp_hdr_len = 20;
165    let tcp_total_len = tcp_hdr_len + payload.len();
166    let ip_total_len = 20 + tcp_total_len;
167    // Same IPv4 total_length overflow guard as `build_tcp_data_frame`.
168    // Callers are GSO-oriented and bounded by the RX descriptor budget,
169    // but asserting here keeps us honest if that ever slips.
170    assert!(
171        u16::try_from(ip_total_len).is_ok(),
172        "build_tcp_data_frame_partial_csum: ip_total_len={ip_total_len} overflows u16"
173    );
174    let frame_len = ETH_HEADER_LEN + ip_total_len;
175    let mut frame = vec![0u8; frame_len];
176
177    // -- Ethernet header --
178    frame[0..6].copy_from_slice(&dst_mac);
179    frame[6..12].copy_from_slice(&src_mac);
180    frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
181
182    // -- IPv4 header --
183    let ip = ETH_HEADER_LEN;
184    frame[ip] = 0x45;
185    frame[ip + 2..ip + 4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
186    frame[ip + 6..ip + 8].copy_from_slice(&0x4000u16.to_be_bytes()); // DF
187    frame[ip + 8] = 64; // TTL
188    frame[ip + 9] = 6; // TCP
189    frame[ip + 12..ip + 16].copy_from_slice(&src_ip.octets());
190    frame[ip + 16..ip + 20].copy_from_slice(&dst_ip.octets());
191    let ip_cksum = ipv4_header_checksum(&frame[ip..ip + 20]);
192    frame[ip + 10..ip + 12].copy_from_slice(&ip_cksum.to_be_bytes());
193
194    // -- TCP header --
195    let tcp = ip + 20;
196    frame[tcp..tcp + 2].copy_from_slice(&src_port.to_be_bytes());
197    frame[tcp + 2..tcp + 4].copy_from_slice(&dst_port.to_be_bytes());
198    frame[tcp + 4..tcp + 8].copy_from_slice(&seq.to_be_bytes());
199    frame[tcp + 8..tcp + 12].copy_from_slice(&ack.to_be_bytes());
200    frame[tcp + 12] = 0x50; // Data offset: 5
201    frame[tcp + 13] = 0x18; // Flags: ACK | PSH
202    frame[tcp + 14..tcp + 16].copy_from_slice(&window.to_be_bytes());
203
204    // -- Payload --
205    frame[tcp + 20..].copy_from_slice(payload);
206
207    // Pseudo-header-only TCP checksum. The guest kernel completes it
208    // per-segment during GSO segmentation (VIRTIO_NET_HDR_F_NEEDS_CSUM).
209    let pseudo_cksum = tcp_pseudo_header_checksum(src_ip, dst_ip, tcp_total_len);
210    frame[tcp + 16..tcp + 18].copy_from_slice(&pseudo_cksum.to_be_bytes());
211
212    frame
213}
214
215/// Computes the TCP pseudo-header checksum only (for GSO offload).
216///
217/// The guest kernel adds the TCP header + payload contribution per segment.
218#[must_use]
219pub fn tcp_pseudo_header_checksum(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, tcp_len: usize) -> u16 {
220    let mut sum: u32 = 0;
221    let src = src_ip.octets();
222    let dst = dst_ip.octets();
223    sum += u32::from(u16::from_be_bytes([src[0], src[1]]));
224    sum += u32::from(u16::from_be_bytes([src[2], src[3]]));
225    sum += u32::from(u16::from_be_bytes([dst[0], dst[1]]));
226    sum += u32::from(u16::from_be_bytes([dst[2], dst[3]]));
227    sum += 6u32; // TCP protocol
228    sum += tcp_len as u32;
229    while sum > 0xFFFF {
230        sum = (sum & 0xFFFF) + (sum >> 16);
231    }
232    !sum as u16
233}
234
235/// Builds a TCP FIN+ACK frame for connection teardown.
236#[must_use]
237pub fn build_tcp_fin_frame(p: &TcpFrameParams) -> Vec<u8> {
238    let mut ack_params = *p;
239    ack_params.window = 65535;
240    let mut frame = build_tcp_ack_frame(&ack_params);
241    // Change flags from ACK to FIN|ACK.
242    let tcp = ETH_HEADER_LEN + 20;
243    frame[tcp + 13] = 0x11; // FIN | ACK
244    // Recompute TCP checksum.
245    frame[tcp + 16..tcp + 18].copy_from_slice(&[0, 0]);
246    let tcp_cksum = tcp_checksum(p.src_ip, p.dst_ip, &frame[tcp..]);
247    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
248    frame
249}
250
251/// Peer TCP options observed on an incoming SYN or SYN-ACK frame.
252///
253/// Captured so the handshake synthesizer can mirror the peer's negotiated
254/// options back in its SYN-ACK response (or record them for later use).
255#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
256pub struct TcpSynOptions {
257    /// Maximum Segment Size advertised by the peer. `None` if the option
258    /// was absent (RFC 9293 §3.7.1 says default 536, but we treat absence
259    /// as "use our default").
260    pub mss: Option<u16>,
261    /// Window scale shift count (0–14). `None` if the peer did not advertise it.
262    pub wscale: Option<u8>,
263    /// Whether the peer included SACK-Permitted.
264    pub sack_permitted: bool,
265    /// Whether the peer included Timestamps. We do not echo these back —
266    /// both endpoints must send TSopt in their SYN for it to be used
267    /// (RFC 7323 §3.1), so not echoing disables timestamps cleanly.
268    pub timestamps: bool,
269}
270
271/// Parses the TCP options field of a SYN (or SYN-ACK) segment.
272///
273/// `tcp_segment` is the TCP header + options + (optionally) payload. The
274/// `data_offset` field (upper nibble of byte 12, in 32-bit words) determines
275/// where the options end. Malformed or unknown options cause early
276/// termination without returning an error — the returned struct reflects
277/// only what was parsed successfully.
278#[must_use]
279pub fn parse_tcp_syn_options(tcp_segment: &[u8]) -> TcpSynOptions {
280    let mut opts = TcpSynOptions::default();
281    if tcp_segment.len() < 20 {
282        return opts;
283    }
284    let data_offset = usize::from(tcp_segment[12] >> 4) * 4;
285    if data_offset < 20 || data_offset > tcp_segment.len() {
286        return opts;
287    }
288    let options = &tcp_segment[20..data_offset];
289    let mut i = 0;
290    while i < options.len() {
291        let kind = options[i];
292        match kind {
293            0 => break, // End of option list
294            1 => {
295                i += 1;
296            } // NOP
297            2 => {
298                // MSS
299                if i + 4 > options.len() || options[i + 1] != 4 {
300                    break;
301                }
302                opts.mss = Some(u16::from_be_bytes([options[i + 2], options[i + 3]]));
303                i += 4;
304            }
305            3 => {
306                // Window scale
307                if i + 3 > options.len() || options[i + 1] != 3 {
308                    break;
309                }
310                opts.wscale = Some(options[i + 2]);
311                i += 3;
312            }
313            4 => {
314                // SACK-Permitted
315                if i + 2 > options.len() || options[i + 1] != 2 {
316                    break;
317                }
318                opts.sack_permitted = true;
319                i += 2;
320            }
321            8 => {
322                // Timestamps
323                if i + 10 > options.len() || options[i + 1] != 10 {
324                    break;
325                }
326                opts.timestamps = true;
327                i += 10;
328            }
329            _ => {
330                // Skip using the Length field if present; bail if malformed.
331                if i + 1 >= options.len() {
332                    break;
333                }
334                let len = usize::from(options[i + 1]);
335                if len < 2 || i + len > options.len() {
336                    break;
337                }
338                i += len;
339            }
340        }
341    }
342    opts
343}
344
345/// Parameters for constructing a TCP SYN-ACK frame.
346///
347/// Used by the handshake synthesizer to respond to a guest SYN without
348/// going through a userspace TCP state machine. `mss` defaults to 1460 if
349/// unspecified; `wscale` and `sack_permitted` are conditionally included
350/// based on what the peer advertised.
351#[derive(Debug, Clone, Copy)]
352pub struct SynAckParams {
353    pub src_ip: Ipv4Addr,
354    pub dst_ip: Ipv4Addr,
355    pub src_port: u16,
356    pub dst_port: u16,
357    /// Our chosen ISN (appears as SEQ in the SYN-ACK).
358    pub seq: u32,
359    /// Peer ISN + 1 (appears as ACK in the SYN-ACK).
360    pub ack: u32,
361    pub src_mac: [u8; 6],
362    pub dst_mac: [u8; 6],
363    /// MSS to advertise (typically 1460 for Ethernet with DF bit set).
364    pub mss: u16,
365    /// Window scale to advertise. `None` means don't include the option
366    /// (peer did not send WScale in its SYN, so we must not negotiate it).
367    pub wscale: Option<u8>,
368    /// Whether to include SACK-Permitted.
369    pub sack_permitted: bool,
370}
371
372/// Builds a TCP SYN-ACK frame that accepts a guest SYN.
373///
374/// Options included: MSS (always), WScale (if `Some`), SACK-Permitted (if true).
375/// Timestamps are omitted — not echoing disables TSopt for the connection,
376/// saving 12 bytes/segment of overhead.
377#[must_use]
378pub fn build_tcp_syn_ack_frame(p: &SynAckParams) -> Vec<u8> {
379    // Assemble options. Pad to multiple of 4 bytes with NOPs.
380    let mut options: Vec<u8> = Vec::with_capacity(16);
381
382    // MSS (kind=2, len=4)
383    options.push(2);
384    options.push(4);
385    options.extend_from_slice(&p.mss.to_be_bytes());
386
387    // SACK-Permitted (kind=4, len=2) — placed before WScale so NOP padding
388    // lands naturally after WScale.
389    if p.sack_permitted {
390        options.push(4);
391        options.push(2);
392    }
393
394    // Window scale (kind=3, len=3)
395    if let Some(shift) = p.wscale {
396        options.push(3);
397        options.push(3);
398        options.push(shift);
399    }
400
401    while !options.len().is_multiple_of(4) {
402        options.push(1); // NOP padding
403    }
404
405    let tcp_hdr_len = 20 + options.len();
406    let ip_total_len = 20 + tcp_hdr_len;
407    let frame_len = ETH_HEADER_LEN + ip_total_len;
408    let mut frame = vec![0u8; frame_len];
409
410    // -- Ethernet header --
411    frame[0..6].copy_from_slice(&p.dst_mac);
412    frame[6..12].copy_from_slice(&p.src_mac);
413    frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
414
415    // -- IPv4 header --
416    let ip = ETH_HEADER_LEN;
417    frame[ip] = 0x45;
418    frame[ip + 2..ip + 4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
419    frame[ip + 6..ip + 8].copy_from_slice(&0x4000u16.to_be_bytes()); // DF
420    frame[ip + 8] = 64;
421    frame[ip + 9] = 6; // TCP
422    frame[ip + 12..ip + 16].copy_from_slice(&p.src_ip.octets());
423    frame[ip + 16..ip + 20].copy_from_slice(&p.dst_ip.octets());
424    let ip_cksum = ipv4_header_checksum(&frame[ip..ip + 20]);
425    frame[ip + 10..ip + 12].copy_from_slice(&ip_cksum.to_be_bytes());
426
427    // -- TCP header --
428    let tcp = ip + 20;
429    frame[tcp..tcp + 2].copy_from_slice(&p.src_port.to_be_bytes());
430    frame[tcp + 2..tcp + 4].copy_from_slice(&p.dst_port.to_be_bytes());
431    frame[tcp + 4..tcp + 8].copy_from_slice(&p.seq.to_be_bytes());
432    frame[tcp + 8..tcp + 12].copy_from_slice(&p.ack.to_be_bytes());
433    frame[tcp + 12] = ((tcp_hdr_len / 4) as u8) << 4;
434    frame[tcp + 13] = 0x12; // Flags: SYN | ACK
435    frame[tcp + 14..tcp + 16].copy_from_slice(&65535u16.to_be_bytes());
436    frame[tcp + 20..tcp + 20 + options.len()].copy_from_slice(&options);
437
438    let tcp_cksum = tcp_checksum(p.src_ip, p.dst_ip, &frame[tcp..]);
439    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
440
441    frame
442}
443
444/// Parameters for constructing a TCP SYN frame (active open).
445///
446/// Used when arcbox initiates a TCP connection toward the guest for an
447/// inbound port-forward. Only the MSS option is included — WScale and
448/// SACK-Permitted are negotiated by the peer echoing them in its SYN-ACK.
449#[derive(Debug, Clone, Copy)]
450pub struct SynParams {
451    pub src_ip: Ipv4Addr,
452    pub dst_ip: Ipv4Addr,
453    pub src_port: u16,
454    pub dst_port: u16,
455    pub seq: u32,
456    pub src_mac: [u8; 6],
457    pub dst_mac: [u8; 6],
458    pub mss: u16,
459    pub wscale: Option<u8>,
460}
461
462/// Builds a TCP SYN frame for active open toward the guest.
463#[must_use]
464pub fn build_tcp_syn_frame(p: &SynParams) -> Vec<u8> {
465    let mut options: Vec<u8> = Vec::with_capacity(12);
466
467    // MSS
468    options.push(2);
469    options.push(4);
470    options.extend_from_slice(&p.mss.to_be_bytes());
471
472    // SACK-Permitted — always advertise, we don't care if peer uses it.
473    options.push(4);
474    options.push(2);
475
476    if let Some(shift) = p.wscale {
477        options.push(3);
478        options.push(3);
479        options.push(shift);
480    }
481
482    while !options.len().is_multiple_of(4) {
483        options.push(1);
484    }
485
486    let tcp_hdr_len = 20 + options.len();
487    let ip_total_len = 20 + tcp_hdr_len;
488    let frame_len = ETH_HEADER_LEN + ip_total_len;
489    let mut frame = vec![0u8; frame_len];
490
491    frame[0..6].copy_from_slice(&p.dst_mac);
492    frame[6..12].copy_from_slice(&p.src_mac);
493    frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
494
495    let ip = ETH_HEADER_LEN;
496    frame[ip] = 0x45;
497    frame[ip + 2..ip + 4].copy_from_slice(&(ip_total_len as u16).to_be_bytes());
498    frame[ip + 6..ip + 8].copy_from_slice(&0x4000u16.to_be_bytes());
499    frame[ip + 8] = 64;
500    frame[ip + 9] = 6;
501    frame[ip + 12..ip + 16].copy_from_slice(&p.src_ip.octets());
502    frame[ip + 16..ip + 20].copy_from_slice(&p.dst_ip.octets());
503    let ip_cksum = ipv4_header_checksum(&frame[ip..ip + 20]);
504    frame[ip + 10..ip + 12].copy_from_slice(&ip_cksum.to_be_bytes());
505
506    let tcp = ip + 20;
507    frame[tcp..tcp + 2].copy_from_slice(&p.src_port.to_be_bytes());
508    frame[tcp + 2..tcp + 4].copy_from_slice(&p.dst_port.to_be_bytes());
509    frame[tcp + 4..tcp + 8].copy_from_slice(&p.seq.to_be_bytes());
510    // ack = 0 for pure SYN
511    frame[tcp + 12] = ((tcp_hdr_len / 4) as u8) << 4;
512    frame[tcp + 13] = 0x02; // SYN
513    frame[tcp + 14..tcp + 16].copy_from_slice(&65535u16.to_be_bytes());
514    frame[tcp + 20..tcp + 20 + options.len()].copy_from_slice(&options);
515
516    let tcp_cksum = tcp_checksum(p.src_ip, p.dst_ip, &frame[tcp..]);
517    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
518
519    frame
520}
521
522/// Builds a TCP RST frame for connection abort.
523#[must_use]
524pub fn build_tcp_rst_frame(p: &TcpFrameParams) -> Vec<u8> {
525    let mut rst_params = *p;
526    rst_params.window = 0;
527    let mut frame = build_tcp_ack_frame(&rst_params);
528    // Change flags from ACK to RST|ACK.
529    let tcp = ETH_HEADER_LEN + 20;
530    frame[tcp + 13] = 0x14; // RST | ACK
531    frame[tcp + 16..tcp + 18].copy_from_slice(&[0, 0]);
532    let tcp_cksum = tcp_checksum(p.src_ip, p.dst_ip, &frame[tcp..]);
533    frame[tcp + 16..tcp + 18].copy_from_slice(&tcp_cksum.to_be_bytes());
534    frame
535}