1pub const VNET_HDR_LEN: usize = 10;
5pub const GSO_NONE: u8 = 0;
6pub const GSO_TCPV4: u8 = 1;
7pub const GSO_TCPV6: u8 = 4;
8pub const GSO_UDP_L4: u8 = 5;
9pub const F_NEEDS_CSUM: u8 = 1;
10pub const F_DATA_VALID: u8 = 2;
11
12#[repr(C)]
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub struct VirtioNetHdr {
15 pub flags: u8,
16 pub gso_type: u8,
17 pub hdr_len: u16,
18 pub gso_size: u16,
19 pub csum_start: u16,
20 pub csum_offset: u16,
21}
22
23pub type VnetHdr = VirtioNetHdr;
24
25pub fn slice_gso_payload(buf: &[u8], mss: usize) -> Vec<&[u8]> {
29 if buf.is_empty() {
30 return Vec::new();
31 }
32 if mss == 0 {
33 return vec![buf];
34 }
35 buf.chunks(mss).collect()
36}
37
38#[inline]
40pub fn slice_gso_packet(buf: &[u8], mss: usize) -> Vec<&[u8]> {
41 slice_gso_payload(buf, mss)
42}
43
44pub fn read_vnet_hdr(buf: &[u8]) -> Option<VirtioNetHdr> {
45 if buf.len() < VNET_HDR_LEN {
46 return None;
47 }
48 let u16h = |a: usize| u16::from_ne_bytes([buf[a], buf[a + 1]]);
49 Some(VirtioNetHdr {
50 flags: buf[0],
51 gso_type: buf[1],
52 hdr_len: u16h(2),
53 gso_size: u16h(4),
54 csum_start: u16h(6),
55 csum_offset: u16h(8),
56 })
57}
58
59pub fn write_vnet_hdr(h: &VirtioNetHdr, out: &mut [u8]) {
60 assert!(out.len() >= VNET_HDR_LEN);
61 out[0] = h.flags;
62 out[1] = h.gso_type;
63 out[2..4].copy_from_slice(&h.hdr_len.to_ne_bytes());
64 out[4..6].copy_from_slice(&h.gso_size.to_ne_bytes());
65 out[6..8].copy_from_slice(&h.csum_start.to_ne_bytes());
66 out[8..10].copy_from_slice(&h.csum_offset.to_ne_bytes());
67}
68
69pub(crate) const TCP_FLAG_FIN: u8 = 0x01;
70pub(crate) const TCP_FLAG_RST: u8 = 0x04;
71pub(crate) const TCP_FLAG_PSH: u8 = 0x08;
72pub(crate) const TCP_FLAG_URG: u8 = 0x20;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub(crate) struct FlowKey {
76 pub src: [u8; 4],
77 pub dst: [u8; 4],
78 pub sport: u16,
79 pub dport: u16,
80}
81
82pub(crate) struct Ipv4Tcp<'a> {
83 pub ip_hdr_len: usize,
84 #[cfg_attr(
85 not(test),
86 expect(
87 dead_code,
88 reason = "read only by parse_ipv4_tcp_extracts_fields; split_gro/Coalescer derive \
89 the segment header length from payload_off instead"
90 )
91 )]
92 pub tcp_hdr_len: usize,
93 pub total_len: usize,
94 pub key: FlowKey,
95 pub seq: u32,
96 pub flags: u8,
97 pub payload_off: usize,
98 pub payload_len: usize,
99 pub bytes: &'a [u8],
100}
101
102pub(crate) fn parse_ipv4_tcp(pkt: &[u8]) -> Option<Ipv4Tcp<'_>> {
103 if pkt.len() < 20 || (pkt[0] >> 4) != 4 {
104 return None;
105 }
106 let ihl = usize::from(pkt[0] & 0x0F) * 4;
107 if ihl < 20 || pkt.len() < ihl {
108 return None;
109 }
110 if pkt[9] != 6 {
111 return None;
112 } let frag = u16::from_be_bytes([pkt[6], pkt[7]]);
115 if (frag & 0x2000) != 0 || (frag & 0x1FFF) != 0 {
116 return None;
117 }
118 let total_len = usize::from(u16::from_be_bytes([pkt[2], pkt[3]]));
119 if total_len < ihl || pkt.len() < total_len {
120 return None;
121 }
122 if total_len < ihl + 20 {
123 return None;
124 }
125 let t = ihl;
126 let data_off = usize::from(pkt[t + 12] >> 4) * 4;
127 if data_off < 20 || total_len < ihl + data_off {
128 return None;
129 }
130 let sport = u16::from_be_bytes([pkt[t], pkt[t + 1]]);
131 let dport = u16::from_be_bytes([pkt[t + 2], pkt[t + 3]]);
132 let seq = u32::from_be_bytes([pkt[t + 4], pkt[t + 5], pkt[t + 6], pkt[t + 7]]);
133 let flags = pkt[t + 13];
134 let payload_off = ihl + data_off;
135 Some(Ipv4Tcp {
136 ip_hdr_len: ihl,
137 tcp_hdr_len: data_off,
138 total_len,
139 key: FlowKey {
140 src: [pkt[12], pkt[13], pkt[14], pkt[15]],
141 dst: [pkt[16], pkt[17], pkt[18], pkt[19]],
142 sport,
143 dport,
144 },
145 seq,
146 flags,
147 payload_off,
148 payload_len: total_len - payload_off,
149 bytes: pkt,
150 })
151}
152
153pub(crate) fn ipv4_checksum(hdr: &mut [u8]) {
154 hdr[10] = 0;
155 hdr[11] = 0;
156 let mut sum: u32 = 0;
157 for c in hdr.chunks(2) {
158 let word = if c.len() == 2 {
159 u16::from_be_bytes([c[0], c[1]])
160 } else {
161 u16::from(c[0]) << 8
162 };
163 sum += u32::from(word);
164 }
165 while (sum >> 16) != 0 {
166 sum = (sum & 0xFFFF) + (sum >> 16);
167 }
168 let ck = !u16::try_from(sum & 0xFFFF).expect("folded sum fits u16");
169 hdr[10..12].copy_from_slice(&ck.to_be_bytes());
170}
171
172pub(crate) fn tcp_checksum(pkt: &mut [u8], ip_hdr_len: usize) {
173 let total = pkt.len();
174 let tcp_off = ip_hdr_len;
175 pkt[tcp_off + 16] = 0;
177 pkt[tcp_off + 17] = 0;
178 let tcp_len = total - tcp_off;
179 let mut sum: u32 = 0;
180 for i in (12..20).step_by(2) {
182 sum += u32::from(u16::from_be_bytes([pkt[i], pkt[i + 1]]));
183 }
184 sum += u32::from(6u16);
185 sum += u32::try_from(tcp_len).expect("tcp_len fits u32") & 0xFFFF;
186 for c in pkt[tcp_off..total].chunks(2) {
187 let w = if c.len() == 2 {
188 u16::from_be_bytes([c[0], c[1]])
189 } else {
190 u16::from(c[0]) << 8
191 };
192 sum += u32::from(w);
193 }
194 while (sum >> 16) != 0 {
195 sum = (sum & 0xFFFF) + (sum >> 16);
196 }
197 let ck = !u16::try_from(sum & 0xFFFF).expect("folded fits u16");
198 pkt[tcp_off + 16..tcp_off + 18].copy_from_slice(&ck.to_be_bytes());
199}
200
201fn complete_partial_csum(pkt: &mut [u8], csum_start: usize, csum_offset: usize) {
208 let field = csum_start + csum_offset;
209 if csum_start >= pkt.len() || field + 2 > pkt.len() {
210 return;
211 }
212 let region = &pkt[csum_start..];
213 let mut sum: u32 = 0;
214 let mut i = 0;
215 while i + 1 < region.len() {
216 sum += u32::from(u16::from_be_bytes([region[i], region[i + 1]]));
217 i += 2;
218 }
219 if i < region.len() {
220 sum += u32::from(u16::from(region[i]) << 8);
221 }
222 while (sum >> 16) != 0 {
223 sum = (sum & 0xFFFF) + (sum >> 16);
224 }
225 let mut ck = !u16::try_from(sum & 0xFFFF).expect("folded sum fits u16");
226 if ck == 0 && pkt.get(9) == Some(&17) {
227 ck = 0xFFFF; }
229 pkt[field..field + 2].copy_from_slice(&ck.to_be_bytes());
230}
231
232pub fn split_gro(frame: &[u8], out: &mut Vec<u8>, offsets: &mut Vec<(usize, usize)>) -> bool {
233 out.clear();
234 offsets.clear();
235 let Some(h) = read_vnet_hdr(frame) else {
236 return false;
237 };
238 let body = &frame[VNET_HDR_LEN..];
239 if h.gso_type == GSO_NONE || h.gso_size == 0 {
240 let start = out.len();
241 out.extend_from_slice(body);
242 if (h.flags & F_NEEDS_CSUM) != 0 {
252 let len = out.len() - start;
253 complete_partial_csum(
254 &mut out[start..start + len],
255 usize::from(h.csum_start),
256 usize::from(h.csum_offset),
257 );
258 }
259 offsets.push((start, body.len()));
260 return true;
261 }
262 let Some(x) = parse_ipv4_tcp(body) else {
263 return false;
264 };
265 let seg = usize::from(h.gso_size);
266 let hdr = x.payload_off; let payload = &body[x.payload_off..x.payload_off + x.payload_len];
268 let mut seq = x.seq;
269 let mut ip_id = u16::from_be_bytes([body[4], body[5]]);
270 let mut off = 0usize;
271 while off < payload.len() {
272 let n = seg.min(payload.len() - off);
273 let start = out.len();
274 out.extend_from_slice(&body[..hdr]); out.extend_from_slice(&payload[off..off + n]);
276 let s = &mut out[start..start + hdr + n];
277 let total = u16::try_from(hdr + n).expect("segment fits u16");
279 s[2..4].copy_from_slice(&total.to_be_bytes());
280 s[4..6].copy_from_slice(&ip_id.to_be_bytes());
281 s[x.ip_hdr_len + 4..x.ip_hdr_len + 8].copy_from_slice(&seq.to_be_bytes());
282 ipv4_checksum(&mut s[..x.ip_hdr_len]);
283 tcp_checksum(s, x.ip_hdr_len);
284 offsets.push((start, hdr + n));
285 seq = seq.wrapping_add(u32::try_from(n).expect("n fits u32"));
286 ip_id = ip_id.wrapping_add(1);
287 off += n;
288 }
289 true
290}
291
292pub const MAX_GSO_SEGMENTS: usize = 64;
293pub const MAX_GSO_PAYLOAD: usize = 65_535;
294
295pub struct Coalescer {
296 pending: Vec<u8>, out: Vec<u8>, has_pending: bool,
299 is_tcp_run: bool, sealed: bool, key: FlowKey,
302 next_seq: u32,
303 gso_size: u16,
304 ip_hdr_len: usize,
305 l3_hdr_len: usize, segs: usize,
307}
308
309impl Coalescer {
310 pub fn new() -> Self {
311 Self {
312 pending: Vec::with_capacity(MAX_GSO_PAYLOAD + 64),
313 out: Vec::with_capacity(MAX_GSO_PAYLOAD + 64),
314 has_pending: false,
315 is_tcp_run: false,
316 sealed: false,
317 key: FlowKey {
318 src: [0; 4],
319 dst: [0; 4],
320 sport: 0,
321 dport: 0,
322 },
323 next_seq: 0,
324 gso_size: 0,
325 ip_hdr_len: 0,
326 l3_hdr_len: 0,
327 segs: 0,
328 }
329 }
330
331 fn take_pending(&mut self) -> Option<&[u8]> {
333 if !self.has_pending {
334 return None;
335 }
336 if self.is_tcp_run {
337 let ip = VNET_HDR_LEN;
339 let l3_len = self.pending.len() - VNET_HDR_LEN;
340 let total = u16::try_from(l3_len).unwrap_or(u16::MAX);
341 self.pending[ip + 2..ip + 4].copy_from_slice(&total.to_be_bytes());
342 ipv4_checksum(&mut self.pending[ip..ip + self.ip_hdr_len]);
343 let h = VnetHdr {
344 flags: F_NEEDS_CSUM,
345 gso_type: GSO_TCPV4,
346 hdr_len: u16::try_from(self.l3_hdr_len).unwrap_or(0),
347 gso_size: self.gso_size,
348 csum_start: u16::try_from(self.ip_hdr_len).unwrap_or(0), csum_offset: 16, };
351 write_vnet_hdr(&h, &mut self.pending[..VNET_HDR_LEN]);
352 }
353 std::mem::swap(&mut self.pending, &mut self.out);
355 self.pending.clear();
356 self.has_pending = false;
357 Some(&self.out)
358 }
359
360 fn start_tcp_run(&mut self, x: &Ipv4Tcp<'_>, sealed: bool) {
361 self.pending.clear();
362 self.pending.resize(VNET_HDR_LEN, 0);
363 self.pending.extend_from_slice(&x.bytes[..x.total_len]); self.has_pending = true;
365 self.is_tcp_run = true;
366 self.sealed = sealed;
367 self.key = x.key;
368 self.ip_hdr_len = x.ip_hdr_len;
369 self.l3_hdr_len = x.payload_off;
370 self.gso_size = u16::try_from(x.payload_len).unwrap_or(0);
371 self.next_seq = x
372 .seq
373 .wrapping_add(u32::try_from(x.payload_len).unwrap_or(0));
374 self.segs = 1;
375 }
376
377 fn start_singleton(&mut self, pkt: &[u8]) {
378 self.pending.clear();
379 self.pending.resize(VNET_HDR_LEN, 0);
380 write_vnet_hdr(
381 &VnetHdr {
382 gso_type: GSO_NONE,
383 ..VnetHdr::default()
384 },
385 &mut self.pending,
386 );
387 self.pending.extend_from_slice(pkt);
388 self.has_pending = true;
389 self.is_tcp_run = false;
390 self.sealed = true;
391 }
392
393 pub fn push(&mut self, pkt: &[u8]) -> Option<&[u8]> {
394 let parsed = parse_ipv4_tcp(pkt);
395 if self.has_pending && self.is_tcp_run && !self.sealed {
397 if let Some(x) = &parsed {
398 let cont = x.payload_len > 0
399 && (x.flags & (TCP_FLAG_PSH | TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_URG)) == 0
400 && x.key == self.key
401 && x.seq == self.next_seq
402 && x.payload_off == self.l3_hdr_len
403 && self.segs < MAX_GSO_SEGMENTS
404 && (self.pending.len() - VNET_HDR_LEN - self.l3_hdr_len) + x.payload_len
405 <= MAX_GSO_PAYLOAD;
406 if cont {
407 self.pending
408 .extend_from_slice(&x.bytes[x.payload_off..x.payload_off + x.payload_len]);
409 self.next_seq = self
410 .next_seq
411 .wrapping_add(u32::try_from(x.payload_len).unwrap_or(0));
412 self.segs += 1;
413 return None;
414 }
415 }
416 }
417 let flushed: Option<Vec<u8>> = self.take_pending().map(<[u8]>::to_vec);
420 match &parsed {
421 Some(x) if x.payload_len > 0 => {
422 let sealed =
423 (x.flags & (TCP_FLAG_PSH | TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_URG)) != 0;
424 self.start_tcp_run(x, sealed);
425 }
426 _ => self.start_singleton(pkt),
427 }
428 match flushed {
429 Some(f) => {
430 self.out = f;
431 Some(&self.out)
432 }
433 None => None,
434 }
435 }
436
437 pub fn flush(&mut self) -> Option<&[u8]> {
438 self.take_pending()
439 }
440}
441
442impl Default for Coalescer {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 fn csum16(data: &[u8]) -> u16 {
454 let mut sum: u32 = 0;
455 let mut i = 0;
456 while i + 1 < data.len() {
457 sum += u32::from(u16::from_be_bytes([data[i], data[i + 1]]));
458 i += 2;
459 }
460 if i < data.len() {
461 sum += u32::from(u16::from(data[i]) << 8);
462 }
463 while (sum >> 16) != 0 {
464 sum = (sum & 0xFFFF) + (sum >> 16);
465 }
466 u16::try_from(sum & 0xFFFF).unwrap()
467 }
468
469 #[test]
470 fn complete_partial_csum_matches_full_udp_checksum() {
471 let payload = [0x11u8, 0x22, 0x33, 0x44, 0x55];
473 let udp_len = 8 + payload.len();
474 let total = 20 + udp_len;
475 let mut p = vec![0u8; total];
476 p[0] = 0x45;
477 p[2..4].copy_from_slice(&u16::try_from(total).unwrap().to_be_bytes());
478 p[8] = 64;
479 p[9] = 17; p[12..16].copy_from_slice(&[10, 0, 0, 1]);
481 p[16..20].copy_from_slice(&[10, 0, 0, 2]);
482 p[20..22].copy_from_slice(&1111u16.to_be_bytes()); p[22..24].copy_from_slice(&2222u16.to_be_bytes()); p[24..26].copy_from_slice(&u16::try_from(udp_len).unwrap().to_be_bytes());
485 p[28..].copy_from_slice(&payload);
486
487 let mut pseudo = vec![0u8; 12];
489 pseudo[..4].copy_from_slice(&[10, 0, 0, 1]);
490 pseudo[4..8].copy_from_slice(&[10, 0, 0, 2]);
491 pseudo[9] = 17;
492 pseudo[10..12].copy_from_slice(&u16::try_from(udp_len).unwrap().to_be_bytes());
493 let pseudo_sum = csum16(&pseudo);
494
495 let good = !{
497 let mut s = u32::from(pseudo_sum) + u32::from(csum16(&p[20..]));
498 while (s >> 16) != 0 {
499 s = (s & 0xFFFF) + (s >> 16);
500 }
501 u16::try_from(s & 0xFFFF).unwrap()
502 };
503
504 p[20 + 6..20 + 8].copy_from_slice(&pseudo_sum.to_be_bytes());
506 complete_partial_csum(&mut p, 20, 6);
507 let completed = u16::from_be_bytes([p[26], p[27]]);
508 assert_eq!(
509 completed, good,
510 "completed checksum must equal the full UDP checksum"
511 );
512 }
513
514 fn mk_tcp(
515 src: [u8; 4],
516 dst: [u8; 4],
517 sport: u16,
518 dport: u16,
519 seq: u32,
520 flags: u8,
521 payload: &[u8],
522 ) -> Vec<u8> {
523 let ihl = 20usize;
524 let thl = 20usize;
525 let total = ihl + thl + payload.len();
526 let mut p = vec![0u8; total];
527 p[0] = 0x45; p[2..4].copy_from_slice(&(u16::try_from(total).unwrap()).to_be_bytes());
529 p[8] = 64;
530 p[9] = 6; p[12..16].copy_from_slice(&src);
532 p[16..20].copy_from_slice(&dst);
533 let t = ihl;
535 p[t..t + 2].copy_from_slice(&sport.to_be_bytes());
536 p[t + 2..t + 4].copy_from_slice(&dport.to_be_bytes());
537 p[t + 4..t + 8].copy_from_slice(&seq.to_be_bytes());
538 p[t + 12] = 0x50; p[t + 13] = flags;
540 p[t + 20..].copy_from_slice(payload);
541 ipv4_checksum(&mut p[..ihl]);
542 p
543 }
544
545 #[test]
546 fn parse_ipv4_tcp_extracts_fields() {
547 let p = mk_tcp(
548 [10, 0, 0, 1],
549 [10, 0, 0, 2],
550 1234,
551 80,
552 1000,
553 TCP_FLAG_PSH,
554 b"hello",
555 );
556 let x = parse_ipv4_tcp(&p).expect("parse");
557 assert_eq!(
558 x.key,
559 FlowKey {
560 src: [10, 0, 0, 1],
561 dst: [10, 0, 0, 2],
562 sport: 1234,
563 dport: 80
564 }
565 );
566 assert_eq!(x.seq, 1000);
567 assert_eq!(x.flags & TCP_FLAG_PSH, TCP_FLAG_PSH);
568 assert_eq!(x.ip_hdr_len, 20);
569 assert_eq!(x.tcp_hdr_len, 20);
570 assert_eq!(x.payload_len, 5);
571 assert_eq!(
572 &x.bytes[x.payload_off..x.payload_off + x.payload_len],
573 b"hello"
574 );
575 }
576
577 #[test]
578 fn parse_rejects_non_tcp_and_fragments() {
579 let mut udp = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 1, 2, 0, 0, b"x");
580 udp[9] = 17; assert!(parse_ipv4_tcp(&udp).is_none());
582 let mut frag = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 1, 2, 0, 0, b"x");
583 frag[6] = 0x20; assert!(parse_ipv4_tcp(&frag).is_none());
585 }
586
587 #[test]
588 fn ipv4_checksum_is_valid() {
589 let p = mk_tcp([1, 2, 3, 4], [5, 6, 7, 8], 1, 2, 0, 0, b"z");
590 let sum: u32 = p[..20]
592 .chunks(2)
593 .map(|c| u32::from(u16::from_be_bytes([c[0], c[1]])))
594 .sum();
595 let folded = u16::try_from((sum & 0xFFFF) + (sum >> 16)).expect("folded sum fits u16");
596 assert_eq!(folded, 0xFFFF);
597 }
598
599 #[test]
600 fn vnet_hdr_roundtrip() {
601 let h = VnetHdr {
602 flags: F_NEEDS_CSUM,
603 gso_type: GSO_TCPV4,
604 hdr_len: 40,
605 gso_size: 1400,
606 csum_start: 20,
607 csum_offset: 16,
608 };
609 let mut buf = [0u8; VNET_HDR_LEN];
610 write_vnet_hdr(&h, &mut buf);
611 let got = read_vnet_hdr(&buf).expect("parse");
612 assert_eq!(got.gso_type, GSO_TCPV4);
613 assert_eq!(got.gso_size, 1400);
614 assert_eq!(got.hdr_len, 40);
615 assert_eq!(got.csum_start, 20);
616 assert_eq!(got.csum_offset, 16);
617 assert_eq!(got.flags, F_NEEDS_CSUM);
618 }
619
620 #[test]
621 fn read_vnet_hdr_rejects_short() {
622 assert!(read_vnet_hdr(&[0u8; VNET_HDR_LEN - 1]).is_none());
623 }
624
625 fn run_coalescer(pkts: &[Vec<u8>]) -> Vec<Vec<u8>> {
628 let mut c = Coalescer::new();
629 let mut out = Vec::new();
630 for p in pkts {
631 if let Some(f) = c.push(p) {
632 out.push(f.to_vec());
633 }
634 }
635 if let Some(f) = c.flush() {
636 out.push(f.to_vec());
637 }
638 out
639 }
640
641 #[test]
642 fn coalesces_contiguous_same_flow() {
643 let p0 = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 0, 0, &[0xAA; 100]);
645 let p1 = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 100, 0, &[0xBB; 100]);
646 let p2 = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 200, 0, &[0xCC; 100]);
647 let out = run_coalescer(&[p0, p1, p2]);
648 assert_eq!(out.len(), 1, "one coalesced super-frame");
649 let h = read_vnet_hdr(&out[0]).unwrap();
650 assert_eq!(h.gso_type, GSO_TCPV4);
651 assert_eq!(h.gso_size, 100);
652 assert_eq!(out[0].len(), VNET_HDR_LEN + 20 + 20 + 300);
654 }
655
656 #[test]
657 fn flushes_on_seq_gap() {
658 let p0 = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 0, 0, &[0; 100]);
659 let gap = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 500, 0, &[0; 100]); assert_eq!(run_coalescer(&[p0, gap]).len(), 2);
661 }
662
663 #[test]
664 fn flushes_on_flow_change() {
665 let a = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 0, 0, &[0; 100]);
666 let b = mk_tcp([10, 0, 0, 1], [10, 0, 0, 3], 9, 80, 0, 0, &[0; 100]); assert_eq!(run_coalescer(&[a, b]).len(), 2);
668 }
669
670 #[test]
671 fn psh_and_fin_force_immediate_flush() {
672 let p0 = mk_tcp(
673 [10, 0, 0, 1],
674 [10, 0, 0, 2],
675 9,
676 80,
677 0,
678 TCP_FLAG_PSH,
679 &[0; 100],
680 );
681 let p1 = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 100, 0, &[0; 100]);
682 assert_eq!(run_coalescer(&[p0, p1]).len(), 2);
684 }
685
686 #[test]
687 fn non_tcp_is_singleton_passthrough() {
688 let mut udp = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 0, 0, &[0; 100]);
689 udp[9] = 17;
690 let out = run_coalescer(&[udp.clone()]);
691 assert_eq!(out.len(), 1);
692 assert_eq!(read_vnet_hdr(&out[0]).unwrap().gso_type, GSO_NONE);
694 assert_eq!(&out[0][VNET_HDR_LEN..], &udp[..]);
695 }
696
697 #[test]
698 fn split_none_passthrough() {
699 let pkt = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 0, TCP_FLAG_PSH, b"hi");
700 let mut frame = vec![0u8; VNET_HDR_LEN];
701 write_vnet_hdr(
702 &VnetHdr {
703 gso_type: GSO_NONE,
704 ..Default::default()
705 },
706 &mut frame,
707 );
708 frame.extend_from_slice(&pkt);
709 let (mut out, mut offs) = (Vec::new(), Vec::new());
710 assert!(split_gro(&frame, &mut out, &mut offs));
711 assert_eq!(offs.len(), 1);
712 assert_eq!(&out[offs[0].0..offs[0].0 + offs[0].1], &pkt[..]);
713 }
714
715 #[test]
716 fn split_gro_segments_and_checksums() {
717 let mut big = mk_tcp(
719 [10, 0, 0, 1],
720 [10, 0, 0, 2],
721 9,
722 80,
723 1000,
724 0,
725 &vec![0xEE; 250],
726 );
727 let mut frame = vec![0u8; VNET_HDR_LEN];
729 write_vnet_hdr(
730 &VnetHdr {
731 flags: F_NEEDS_CSUM,
732 gso_type: GSO_TCPV4,
733 hdr_len: 40,
734 gso_size: 100,
735 csum_start: 20,
736 csum_offset: 16,
737 },
738 &mut frame,
739 );
740 frame.extend_from_slice(&big);
741 let (mut out, mut offs) = (Vec::new(), Vec::new());
742 assert!(split_gro(&frame, &mut out, &mut offs));
743 assert_eq!(offs.len(), 3);
744 let sizes = [100usize, 100, 50];
746 let mut expect_seq = 1000u32;
747 for (i, &(s, l)) in offs.iter().enumerate() {
748 let seg = &out[s..s + l];
749 let x = parse_ipv4_tcp(seg).expect("valid segment");
750 assert_eq!(x.payload_len, sizes[i]);
751 assert_eq!(x.seq, expect_seq);
752 expect_seq += u32::try_from(sizes[i]).unwrap();
753 let sum: u32 = seg[..20]
755 .chunks(2)
756 .map(|c| u32::from(u16::from_be_bytes([c[0], c[1]])))
757 .sum();
758 assert_eq!(
759 u16::try_from((sum & 0xFFFF) + (sum >> 16)).expect("folded sum fits u16"),
760 0xFFFF
761 );
762 }
763 let _ = &mut big;
764 }
765
766 #[test]
767 fn split_then_coalesce_roundtrip() {
768 let payload: Vec<u8> = (0..250usize)
770 .map(|i| u8::try_from(i % 256).expect("fits u8"))
771 .collect();
772 let big = mk_tcp([10, 0, 0, 1], [10, 0, 0, 2], 9, 80, 1000, 0, &payload);
773 let mut frame = vec![0u8; VNET_HDR_LEN];
774 write_vnet_hdr(
775 &VnetHdr {
776 flags: F_NEEDS_CSUM,
777 gso_type: GSO_TCPV4,
778 hdr_len: 40,
779 gso_size: 100,
780 csum_start: 20,
781 csum_offset: 16,
782 },
783 &mut frame,
784 );
785 frame.extend_from_slice(&big);
786
787 let (mut out, mut offs) = (Vec::new(), Vec::new());
788 assert!(split_gro(&frame, &mut out, &mut offs));
789 assert_eq!(offs.len(), 3, "100,100,50-byte segments");
790
791 let mut c = Coalescer::new();
794 let mut frames: Vec<Vec<u8>> = Vec::new();
795 for &(s, l) in &offs {
796 let seg = &out[s..s + l];
797 if let Some(f) = c.push(seg) {
798 frames.push(f.to_vec());
799 }
800 }
801 if let Some(f) = c.flush() {
802 frames.push(f.to_vec());
803 }
804
805 let mut coalesced_payload = Vec::new();
807 for f in &frames {
808 let body = &f[VNET_HDR_LEN..];
809 let x = parse_ipv4_tcp(body).expect("valid coalesced frame");
810 coalesced_payload
811 .extend_from_slice(&body[x.payload_off..x.payload_off + x.payload_len]);
812 }
813 assert_eq!(coalesced_payload, payload);
814 }
815}