1use crate::utils::truncate_for_log;
23use serde_json::{json, Value};
24use std::fmt;
25use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
26
27pub const PAYLOAD_PREVIEW_BYTES: usize = 64;
32
33pub const ETHERNET_HEADER_LEN: usize = 14;
35
36pub const PACKET_INFORMATION_LEN: usize = 4;
38
39#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum DecodeError {
49 TooShort {
51 what: &'static str,
53 need: usize,
55 have: usize,
57 },
58 UnsupportedIpVersion(u8),
60 BadPacketInformation {
62 header: [u8; 4],
64 },
65 UnsupportedEtherType(u16),
67 BadIhl(u8),
69}
70
71impl fmt::Display for DecodeError {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 Self::TooShort { what, need, have } => write!(
75 f,
76 "frame too short for {what}: need {need} bytes, have {have}"
77 ),
78 Self::UnsupportedIpVersion(v) => write!(f, "unsupported IP version {v}"),
79 Self::BadPacketInformation { header } => write!(
80 f,
81 "packet-information header {:02x?} does not describe an IP packet",
82 header
83 ),
84 Self::UnsupportedEtherType(t) => write!(f, "unsupported EtherType 0x{t:04x}"),
85 Self::BadIhl(ihl) => write!(f, "IPv4 IHL {ihl} is below the 5-word minimum"),
86 }
87 }
88}
89
90impl std::error::Error for DecodeError {}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct BuildError(pub String);
99
100impl fmt::Display for BuildError {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(&self.0)
103 }
104}
105
106impl std::error::Error for BuildError {}
107
108fn bad(msg: impl Into<String>) -> BuildError {
109 BuildError(msg.into())
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum LinkMode {
122 Tun,
124 Tap,
126}
127
128impl LinkMode {
129 pub fn parse(s: &str) -> Result<Self, String> {
131 match s.trim().to_ascii_lowercase().as_str() {
132 "tun" | "l3" | "layer3" => Ok(Self::Tun),
133 "tap" | "l2" | "layer2" => Ok(Self::Tap),
134 other => Err(format!(
135 "mode must be \"tun\" (layer 3, bare IP) or \"tap\" (layer 2, Ethernet), got {other:?}"
136 )),
137 }
138 }
139
140 pub fn as_str(&self) -> &'static str {
142 match self {
143 Self::Tun => "tun",
144 Self::Tap => "tap",
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum PacketInformation {
163 None,
166 MacOsUtun,
168 LinuxTunPi,
170}
171
172const AF_INET: u32 = 2;
174const AF_INET6_DARWIN: u32 = 30;
176pub const ETHERTYPE_IPV4: u16 = 0x0800;
178pub const ETHERTYPE_IPV6: u16 = 0x86DD;
180
181impl PacketInformation {
182 pub fn parse(s: &str) -> Result<Self, String> {
190 match s.trim().to_ascii_lowercase().as_str() {
191 "auto" | "none" => Ok(Self::None),
192 "macos_utun" | "macos" | "utun" => Ok(Self::MacOsUtun),
193 "linux_pi" | "linux" => Ok(Self::LinuxTunPi),
194 other => Err(format!(
195 "packet_information must be one of \"auto\", \"none\", \"macos_utun\", \
196 \"linux_pi\", got {other:?}"
197 )),
198 }
199 }
200
201 pub fn header_len(&self) -> usize {
203 match self {
204 Self::None => 0,
205 Self::MacOsUtun | Self::LinuxTunPi => PACKET_INFORMATION_LEN,
206 }
207 }
208
209 pub fn strip<'a>(&self, frame: &'a [u8]) -> Result<&'a [u8], DecodeError> {
215 match self {
216 Self::None => Ok(frame),
217 Self::MacOsUtun | Self::LinuxTunPi => {
218 if frame.len() < PACKET_INFORMATION_LEN {
219 return Err(DecodeError::TooShort {
220 what: "packet-information header",
221 need: PACKET_INFORMATION_LEN,
222 have: frame.len(),
223 });
224 }
225 let mut header = [0u8; 4];
226 header.copy_from_slice(&frame[..PACKET_INFORMATION_LEN]);
227 let ok = match self {
228 Self::MacOsUtun => {
229 let af = u32::from_be_bytes(header);
230 af == AF_INET || af == AF_INET6_DARWIN
231 }
232 Self::LinuxTunPi => {
233 let proto = u16::from_be_bytes([header[2], header[3]]);
234 proto == ETHERTYPE_IPV4 || proto == ETHERTYPE_IPV6
235 }
236 Self::None => unreachable!(),
237 };
238 if !ok {
239 return Err(DecodeError::BadPacketInformation { header });
240 }
241 Ok(&frame[PACKET_INFORMATION_LEN..])
242 }
243 }
244 }
245
246 pub fn prefix_for(&self, ipv6: bool) -> Option<[u8; 4]> {
248 match self {
249 Self::None => None,
250 Self::MacOsUtun => Some(if ipv6 {
251 AF_INET6_DARWIN.to_be_bytes()
252 } else {
253 AF_INET.to_be_bytes()
254 }),
255 Self::LinuxTunPi => {
256 let proto = if ipv6 { ETHERTYPE_IPV6 } else { ETHERTYPE_IPV4 };
257 let [a, b] = proto.to_be_bytes();
258 Some([0, 0, a, b])
259 }
260 }
261 }
262
263 pub fn prepend(&self, packet: &mut Vec<u8>) -> Result<(), BuildError> {
265 let ipv6 = match packet.first() {
266 Some(b) => match b >> 4 {
267 4 => false,
268 6 => true,
269 v => return Err(bad(format!("cannot prefix a packet of IP version {v}"))),
270 },
271 None => return Err(bad("cannot prefix an empty packet")),
272 };
273 if let Some(prefix) = self.prefix_for(ipv6) {
274 packet.splice(0..0, prefix);
275 }
276 Ok(())
277 }
278}
279
280pub fn ip_protocol_name(number: u8) -> &'static str {
289 match number {
290 0 => "hopopt",
291 1 => "icmp",
292 2 => "igmp",
293 4 => "ipv4",
294 6 => "tcp",
295 17 => "udp",
296 41 => "ipv6",
297 43 => "ipv6-route",
298 44 => "ipv6-frag",
299 47 => "gre",
300 50 => "esp",
301 51 => "ah",
302 58 => "icmpv6",
303 59 => "ipv6-nonxt",
304 60 => "ipv6-opts",
305 89 => "ospf",
306 112 => "vrrp",
307 132 => "sctp",
308 _ => "unknown",
309 }
310}
311
312pub fn ip_protocol_number(name: &str) -> Option<u8> {
315 Some(match name.trim().to_ascii_lowercase().as_str() {
316 "hopopt" => 0,
317 "icmp" => 1,
318 "igmp" => 2,
319 "tcp" => 6,
320 "udp" => 17,
321 "gre" => 47,
322 "esp" => 50,
323 "ah" => 51,
324 "icmpv6" | "ipv6-icmp" => 58,
325 "ospf" => 89,
326 "vrrp" => 112,
327 "sctp" => 132,
328 _ => return None,
329 })
330}
331
332pub fn icmpv4_type_name(t: u8) -> &'static str {
334 match t {
335 0 => "echo_reply",
336 3 => "destination_unreachable",
337 4 => "source_quench",
338 5 => "redirect",
339 8 => "echo_request",
340 9 => "router_advertisement",
341 10 => "router_solicitation",
342 11 => "time_exceeded",
343 12 => "parameter_problem",
344 13 => "timestamp",
345 14 => "timestamp_reply",
346 _ => "unknown",
347 }
348}
349
350pub fn icmpv6_type_name(t: u8) -> &'static str {
352 match t {
353 1 => "destination_unreachable",
354 2 => "packet_too_big",
355 3 => "time_exceeded",
356 4 => "parameter_problem",
357 128 => "echo_request",
358 129 => "echo_reply",
359 133 => "router_solicitation",
360 134 => "router_advertisement",
361 135 => "neighbor_solicitation",
362 136 => "neighbor_advertisement",
363 _ => "unknown",
364 }
365}
366
367pub fn icmp_type_number(name: &str, ipv6: bool) -> Option<u8> {
369 let name = name.trim().to_ascii_lowercase();
370 let n = if ipv6 {
371 match name.as_str() {
372 "destination_unreachable" => 1,
373 "packet_too_big" => 2,
374 "time_exceeded" => 3,
375 "parameter_problem" => 4,
376 "echo_request" => 128,
377 "echo_reply" => 129,
378 "router_solicitation" => 133,
379 "router_advertisement" => 134,
380 "neighbor_solicitation" => 135,
381 "neighbor_advertisement" => 136,
382 _ => return None,
383 }
384 } else {
385 match name.as_str() {
386 "echo_reply" => 0,
387 "destination_unreachable" => 3,
388 "source_quench" => 4,
389 "redirect" => 5,
390 "echo_request" => 8,
391 "router_advertisement" => 9,
392 "router_solicitation" => 10,
393 "time_exceeded" => 11,
394 "parameter_problem" => 12,
395 "timestamp" => 13,
396 "timestamp_reply" => 14,
397 _ => return None,
398 }
399 };
400 Some(n)
401}
402
403pub const TCP_FLAG_NAMES: [(u8, &str); 8] = [
405 (0x01, "fin"),
406 (0x02, "syn"),
407 (0x04, "rst"),
408 (0x08, "psh"),
409 (0x10, "ack"),
410 (0x20, "urg"),
411 (0x40, "ece"),
412 (0x80, "cwr"),
413];
414
415pub fn tcp_flag_names(bits: u8) -> Vec<&'static str> {
417 TCP_FLAG_NAMES
418 .iter()
419 .filter(|(bit, _)| bits & bit != 0)
420 .map(|(_, name)| *name)
421 .collect()
422}
423
424pub fn tcp_flag_bits(names: &[String]) -> Result<u8, BuildError> {
427 let mut bits = 0u8;
428 for name in names {
429 let lower = name.trim().to_ascii_lowercase();
430 match TCP_FLAG_NAMES.iter().find(|(_, n)| *n == lower) {
431 Some((bit, _)) => bits |= bit,
432 None => {
433 return Err(bad(format!(
434 "unknown TCP flag {name:?}; valid flags are {}",
435 TCP_FLAG_NAMES
436 .iter()
437 .map(|(_, n)| *n)
438 .collect::<Vec<_>>()
439 .join(", ")
440 )))
441 }
442 }
443 }
444 Ok(bits)
445}
446
447#[derive(Debug, Clone, PartialEq, Eq)]
453pub struct EthernetHeader {
454 pub destination_mac: String,
456 pub source_mac: String,
458 pub ethertype: u16,
460}
461
462pub fn format_mac(bytes: &[u8]) -> String {
464 bytes
465 .iter()
466 .map(|b| format!("{b:02x}"))
467 .collect::<Vec<_>>()
468 .join(":")
469}
470
471pub fn parse_mac(s: &str) -> Result<[u8; 6], BuildError> {
473 let parts: Vec<&str> = s.split([':', '-']).collect();
474 if parts.len() != 6 {
475 return Err(bad(format!(
476 "MAC address {s:?} must have six colon-separated octets"
477 )));
478 }
479 let mut out = [0u8; 6];
480 for (i, p) in parts.iter().enumerate() {
481 out[i] = u8::from_str_radix(p, 16)
482 .map_err(|_| bad(format!("MAC address {s:?} has a non-hex octet {p:?}")))?;
483 }
484 Ok(out)
485}
486
487#[derive(Debug, Clone, PartialEq, Eq)]
489pub enum Transport {
490 Tcp {
492 source_port: u16,
494 destination_port: u16,
496 seq: u32,
498 ack: u32,
500 flags: u8,
502 window: u16,
504 },
505 Udp {
507 source_port: u16,
509 destination_port: u16,
511 },
512 Icmp {
514 icmp_type: u8,
516 code: u8,
518 id: Option<u16>,
520 sequence: Option<u16>,
522 },
523 Other,
525}
526
527#[derive(Debug, Clone, PartialEq, Eq)]
529pub struct DecodedPacket {
530 pub ethernet: Option<EthernetHeader>,
532 pub ip_version: u8,
534 pub source: IpAddr,
536 pub destination: IpAddr,
538 pub protocol: u8,
540 pub ttl: u8,
542 pub total_length: usize,
544 pub transport: Transport,
546 pub payload_len: usize,
548 pub payload_preview: String,
550 pub payload_encoding: &'static str,
554}
555
556impl DecodedPacket {
557 pub fn protocol_name(&self) -> &'static str {
559 ip_protocol_name(self.protocol)
560 }
561
562 pub fn is_ipv6(&self) -> bool {
564 self.ip_version == 6
565 }
566
567 pub fn ports(&self) -> Option<(u16, u16)> {
569 match self.transport {
570 Transport::Tcp {
571 source_port,
572 destination_port,
573 ..
574 }
575 | Transport::Udp {
576 source_port,
577 destination_port,
578 } => Some((source_port, destination_port)),
579 _ => None,
580 }
581 }
582
583 pub fn summary(&self) -> String {
585 let base = match &self.transport {
586 Transport::Tcp {
587 source_port,
588 destination_port,
589 flags,
590 seq,
591 ..
592 } => format!(
593 "{}:{} > {}:{} TCP [{}] seq={}",
594 self.source,
595 source_port,
596 self.destination,
597 destination_port,
598 tcp_flag_names(*flags).join(","),
599 seq
600 ),
601 Transport::Udp {
602 source_port,
603 destination_port,
604 } => format!(
605 "{}:{} > {}:{} UDP",
606 self.source, source_port, self.destination, destination_port
607 ),
608 Transport::Icmp {
609 icmp_type,
610 code,
611 id,
612 sequence,
613 } => {
614 let name = if self.is_ipv6() {
615 icmpv6_type_name(*icmp_type)
616 } else {
617 icmpv4_type_name(*icmp_type)
618 };
619 let mut s = format!(
620 "{} > {} {} {} code={}",
621 self.source,
622 self.destination,
623 if self.is_ipv6() { "ICMPv6" } else { "ICMP" },
624 name,
625 code
626 );
627 if let (Some(id), Some(seq)) = (id, sequence) {
628 s.push_str(&format!(" id={id} seq={seq}"));
629 }
630 s
631 }
632 Transport::Other => format!(
633 "{} > {} {}",
634 self.source,
635 self.destination,
636 self.protocol_name()
637 ),
638 };
639 format!("{base} len={}", self.total_length)
640 }
641
642 pub fn to_event_data(&self) -> Value {
647 let mut data = json!({
648 "direction": "inbound",
649 "ip_version": self.ip_version,
650 "source": self.source.to_string(),
651 "destination": self.destination.to_string(),
652 "protocol": self.protocol_name(),
653 "protocol_number": self.protocol,
654 "total_length": self.total_length,
655 "payload_length": self.payload_len,
656 "payload_preview": self.payload_preview,
657 "payload_encoding": self.payload_encoding,
658 "summary": self.summary(),
659 });
660 let obj = data.as_object_mut().expect("json! built an object");
661
662 if self.is_ipv6() {
663 obj.insert("hop_limit".into(), json!(self.ttl));
664 } else {
665 obj.insert("ttl".into(), json!(self.ttl));
666 }
667
668 if let Some(eth) = &self.ethernet {
669 obj.insert(
670 "ethernet".into(),
671 json!({
672 "source_mac": eth.source_mac,
673 "destination_mac": eth.destination_mac,
674 "ethertype": format!("0x{:04x}", eth.ethertype),
675 }),
676 );
677 }
678
679 match &self.transport {
680 Transport::Tcp {
681 source_port,
682 destination_port,
683 seq,
684 ack,
685 flags,
686 window,
687 } => {
688 obj.insert("source_port".into(), json!(source_port));
689 obj.insert("destination_port".into(), json!(destination_port));
690 obj.insert("seq".into(), json!(seq));
691 obj.insert("ack".into(), json!(ack));
692 obj.insert("window".into(), json!(window));
693 obj.insert("tcp_flags".into(), json!(tcp_flag_names(*flags)));
694 }
695 Transport::Udp {
696 source_port,
697 destination_port,
698 } => {
699 obj.insert("source_port".into(), json!(source_port));
700 obj.insert("destination_port".into(), json!(destination_port));
701 }
702 Transport::Icmp {
703 icmp_type,
704 code,
705 id,
706 sequence,
707 } => {
708 let name = if self.is_ipv6() {
709 icmpv6_type_name(*icmp_type)
710 } else {
711 icmpv4_type_name(*icmp_type)
712 };
713 obj.insert("icmp_type".into(), json!(name));
714 obj.insert("icmp_type_number".into(), json!(icmp_type));
715 obj.insert("icmp_code".into(), json!(code));
716 if let Some(id) = id {
717 obj.insert("icmp_id".into(), json!(id));
718 }
719 if let Some(seq) = sequence {
720 obj.insert("icmp_sequence".into(), json!(seq));
721 }
722 }
723 Transport::Other => {}
724 }
725
726 data
727 }
728}
729
730fn need(what: &'static str, need: usize, have: usize) -> DecodeError {
735 DecodeError::TooShort { what, need, have }
736}
737
738pub fn decode(
743 frame: &[u8],
744 pi: PacketInformation,
745 mode: LinkMode,
746) -> Result<DecodedPacket, DecodeError> {
747 let frame = pi.strip(frame)?;
748
749 let (ethernet, ip) = match mode {
750 LinkMode::Tun => (None, frame),
751 LinkMode::Tap => {
752 if frame.len() < ETHERNET_HEADER_LEN {
753 return Err(need("Ethernet header", ETHERNET_HEADER_LEN, frame.len()));
754 }
755 let ethertype = u16::from_be_bytes([frame[12], frame[13]]);
756 if ethertype != ETHERTYPE_IPV4 && ethertype != ETHERTYPE_IPV6 {
757 return Err(DecodeError::UnsupportedEtherType(ethertype));
758 }
759 (
760 Some(EthernetHeader {
761 destination_mac: format_mac(&frame[0..6]),
762 source_mac: format_mac(&frame[6..12]),
763 ethertype,
764 }),
765 &frame[ETHERNET_HEADER_LEN..],
766 )
767 }
768 };
769
770 if ip.is_empty() {
771 return Err(need("IP header", 1, 0));
772 }
773
774 match ip[0] >> 4 {
775 4 => decode_ipv4(ethernet, ip),
776 6 => decode_ipv6(ethernet, ip),
777 v => Err(DecodeError::UnsupportedIpVersion(v)),
778 }
779}
780
781fn decode_ipv4(ethernet: Option<EthernetHeader>, ip: &[u8]) -> Result<DecodedPacket, DecodeError> {
782 if ip.len() < 20 {
783 return Err(need("IPv4 header", 20, ip.len()));
784 }
785 let ihl = ip[0] & 0x0f;
786 if ihl < 5 {
787 return Err(DecodeError::BadIhl(ihl));
788 }
789 let header_len = ihl as usize * 4;
790 if ip.len() < header_len {
791 return Err(need("IPv4 options", header_len, ip.len()));
792 }
793 let total_length = u16::from_be_bytes([ip[2], ip[3]]) as usize;
794 let ttl = ip[8];
795 let protocol = ip[9];
796 let source = IpAddr::V4(ipv4_at(ip, 12));
797 let destination = IpAddr::V4(ipv4_at(ip, 16));
798
799 let rest = &ip[header_len..];
800 let (transport, payload) = decode_transport(protocol, false, rest)?;
801
802 let (payload_preview, payload_encoding) = preview(payload);
803
804 Ok(DecodedPacket {
805 ethernet,
806 ip_version: 4,
807 source,
808 destination,
809 protocol,
810 ttl,
811 total_length,
812 transport,
813 payload_len: payload.len(),
814 payload_preview,
815 payload_encoding,
816 })
817}
818
819fn ipv4_at(buf: &[u8], at: usize) -> Ipv4Addr {
820 Ipv4Addr::new(buf[at], buf[at + 1], buf[at + 2], buf[at + 3])
821}
822
823fn decode_ipv6(ethernet: Option<EthernetHeader>, ip: &[u8]) -> Result<DecodedPacket, DecodeError> {
824 if ip.len() < 40 {
825 return Err(need("IPv6 header", 40, ip.len()));
826 }
827 let payload_length = u16::from_be_bytes([ip[4], ip[5]]) as usize;
828 let protocol = ip[6];
829 let ttl = ip[7];
830 let mut src = [0u8; 16];
831 src.copy_from_slice(&ip[8..24]);
832 let mut dst = [0u8; 16];
833 dst.copy_from_slice(&ip[24..40]);
834
835 let rest = &ip[40..];
836 let (transport, payload) = decode_transport(protocol, true, rest)?;
840 let (payload_preview, payload_encoding) = preview(payload);
841
842 Ok(DecodedPacket {
843 ethernet,
844 ip_version: 6,
845 source: IpAddr::V6(Ipv6Addr::from(src)),
846 destination: IpAddr::V6(Ipv6Addr::from(dst)),
847 protocol,
848 ttl,
849 total_length: 40 + payload_length,
850 transport,
851 payload_len: payload.len(),
852 payload_preview,
853 payload_encoding,
854 })
855}
856
857fn decode_transport(
858 protocol: u8,
859 ipv6: bool,
860 rest: &[u8],
861) -> Result<(Transport, &[u8]), DecodeError> {
862 match protocol {
863 6 => {
864 if rest.len() < 20 {
865 return Err(need("TCP header", 20, rest.len()));
866 }
867 let data_offset = (rest[12] >> 4) as usize * 4;
868 let header_len = data_offset.max(20).min(rest.len());
869 Ok((
870 Transport::Tcp {
871 source_port: u16::from_be_bytes([rest[0], rest[1]]),
872 destination_port: u16::from_be_bytes([rest[2], rest[3]]),
873 seq: u32::from_be_bytes([rest[4], rest[5], rest[6], rest[7]]),
874 ack: u32::from_be_bytes([rest[8], rest[9], rest[10], rest[11]]),
875 flags: rest[13],
876 window: u16::from_be_bytes([rest[14], rest[15]]),
877 },
878 &rest[header_len..],
879 ))
880 }
881 17 => {
882 if rest.len() < 8 {
883 return Err(need("UDP header", 8, rest.len()));
884 }
885 Ok((
886 Transport::Udp {
887 source_port: u16::from_be_bytes([rest[0], rest[1]]),
888 destination_port: u16::from_be_bytes([rest[2], rest[3]]),
889 },
890 &rest[8..],
891 ))
892 }
893 1 | 58 => {
894 if rest.len() < 4 {
895 return Err(need("ICMP header", 4, rest.len()));
896 }
897 let icmp_type = rest[0];
898 let code = rest[1];
899 let echo = if ipv6 {
900 matches!(icmp_type, 128 | 129)
901 } else {
902 matches!(icmp_type, 0 | 8 | 13 | 14)
903 };
904 let (id, sequence, body_at) = if echo && rest.len() >= 8 {
905 (
906 Some(u16::from_be_bytes([rest[4], rest[5]])),
907 Some(u16::from_be_bytes([rest[6], rest[7]])),
908 8,
909 )
910 } else {
911 (None, None, 4.min(rest.len()))
912 };
913 Ok((
914 Transport::Icmp {
915 icmp_type,
916 code,
917 id,
918 sequence,
919 },
920 &rest[body_at..],
921 ))
922 }
923 _ => Ok((Transport::Other, rest)),
924 }
925}
926
927fn preview(payload: &[u8]) -> (String, &'static str) {
934 let head = &payload[..payload.len().min(PAYLOAD_PREVIEW_BYTES)];
935 let printable = !head.is_empty()
936 && head
937 .iter()
938 .all(|b| matches!(b, 0x20..=0x7e | b'\t' | b'\r' | b'\n'));
939 if printable {
940 let text: String = head.iter().map(|b| *b as char).collect();
941 (truncate_for_log(&text, PAYLOAD_PREVIEW_BYTES), "utf8")
944 } else {
945 (
946 truncate_for_log(&hex::encode(head), PAYLOAD_PREVIEW_BYTES * 2),
947 "hex",
948 )
949 }
950}
951
952#[derive(Debug, Clone, PartialEq, Eq)]
958enum Term {
959 V4,
960 V6,
961 Protocol(u8),
962 PortAny(u16),
963 ProtocolPort(u8, u16),
964 TcpSyn,
965 From(IpAddr),
966 To(IpAddr),
967 Host(IpAddr),
968}
969
970impl Term {
971 fn matches(&self, p: &DecodedPacket) -> bool {
972 match self {
973 Self::V4 => p.ip_version == 4,
974 Self::V6 => p.ip_version == 6,
975 Self::Protocol(n) => p.protocol == *n,
976 Self::PortAny(port) => p.ports().is_some_and(|(s, d)| s == *port || d == *port),
977 Self::ProtocolPort(n, port) => {
978 p.protocol == *n && p.ports().is_some_and(|(s, d)| s == *port || d == *port)
979 }
980 Self::TcpSyn => matches!(
981 p.transport,
982 Transport::Tcp { flags, .. } if flags & 0x02 != 0 && flags & 0x10 == 0
983 ),
984 Self::From(addr) => p.source == *addr,
985 Self::To(addr) => p.destination == *addr,
986 Self::Host(addr) => p.source == *addr || p.destination == *addr,
987 }
988 }
989}
990
991#[derive(Debug, Clone, PartialEq, Eq)]
1017pub struct PacketFilter {
1018 source: String,
1020 alternatives: Option<Vec<Vec<Term>>>,
1022}
1023
1024impl PacketFilter {
1025 pub fn parse(expr: &str) -> Result<Self, String> {
1027 let source = expr.trim().to_string();
1028 let lower = source.to_ascii_lowercase();
1029 if lower.is_empty() {
1030 return Err(
1031 "packet_filter must not be empty; use \"none\" to escalate nothing, \
1032 or \"all\" to escalate everything"
1033 .to_string(),
1034 );
1035 }
1036 if lower == "all" {
1037 return Ok(Self {
1038 source,
1039 alternatives: None,
1040 });
1041 }
1042 if lower == "none" {
1043 return Ok(Self {
1044 source,
1045 alternatives: Some(Vec::new()),
1046 });
1047 }
1048
1049 let mut alternatives = Vec::new();
1050 for group in lower.split(',') {
1051 let group = group.trim();
1052 if group.is_empty() {
1053 return Err(format!(
1054 "packet_filter {expr:?} has an empty alternative between commas"
1055 ));
1056 }
1057 let mut terms = Vec::new();
1058 for term in group.split('+') {
1059 terms.push(parse_term(term.trim(), expr)?);
1060 }
1061 alternatives.push(terms);
1062 }
1063 Ok(Self {
1064 source,
1065 alternatives: Some(alternatives),
1066 })
1067 }
1068
1069 pub fn as_str(&self) -> &str {
1071 &self.source
1072 }
1073
1074 pub fn matches_nothing(&self) -> bool {
1076 matches!(&self.alternatives, Some(v) if v.is_empty())
1077 }
1078
1079 pub fn matches(&self, packet: &DecodedPacket) -> bool {
1081 match &self.alternatives {
1082 None => true,
1083 Some(alts) => alts
1084 .iter()
1085 .any(|terms| terms.iter().all(|t| t.matches(packet))),
1086 }
1087 }
1088}
1089
1090fn parse_term(term: &str, whole: &str) -> Result<Term, String> {
1091 let err = |detail: String| format!("packet_filter {whole:?}: {detail}");
1092
1093 if term.is_empty() {
1094 return Err(err("empty term between '+' separators".into()));
1095 }
1096 match term {
1097 "v4" | "ipv4" => return Ok(Term::V4),
1098 "v6" | "ipv6" => return Ok(Term::V6),
1099 "tcp-syn" | "tcp_syn" => return Ok(Term::TcpSyn),
1100 _ => {}
1101 }
1102 if let Some(rest) = term.strip_prefix("ip-proto-") {
1103 let n: u8 = rest
1104 .parse()
1105 .map_err(|_| err(format!("{rest:?} is not an IP protocol number (0-255)")))?;
1106 return Ok(Term::Protocol(n));
1107 }
1108 for (prefix, ctor) in [
1109 ("from:", Term::From as fn(IpAddr) -> Term),
1110 ("to:", Term::To as fn(IpAddr) -> Term),
1111 ("host:", Term::Host as fn(IpAddr) -> Term),
1112 ] {
1113 if let Some(rest) = term.strip_prefix(prefix) {
1114 let addr: IpAddr = rest
1115 .parse()
1116 .map_err(|_| err(format!("{rest:?} is not an IP address")))?;
1117 return Ok(ctor(addr));
1118 }
1119 }
1120 if let Some(rest) = term.strip_prefix("port:") {
1121 let port: u16 = rest
1122 .parse()
1123 .map_err(|_| err(format!("{rest:?} is not a port number (0-65535)")))?;
1124 return Ok(Term::PortAny(port));
1125 }
1126 if let Some((name, port)) = term.split_once(':') {
1127 let number = ip_protocol_number(name)
1128 .ok_or_else(|| err(format!("{name:?} is not a known protocol name")))?;
1129 if number != 6 && number != 17 {
1130 return Err(err(format!("{name:?} has no ports to match on")));
1131 }
1132 let port: u16 = port
1133 .parse()
1134 .map_err(|_| err(format!("{port:?} is not a port number (0-65535)")))?;
1135 return Ok(Term::ProtocolPort(number, port));
1136 }
1137 match ip_protocol_number(term) {
1138 Some(n) => Ok(Term::Protocol(n)),
1139 None if matches!(term, "all" | "none") => Err(err(format!(
1147 "{term:?} is the whole packet_filter, not one term of a list: write \
1148 packet_filter {term:?} on its own rather than combining it with others"
1149 ))),
1150 None => Err(err(format!(
1151 "{term:?} is not a filter term; expected v4, v6, tcp-syn, a protocol name, \
1152 ip-proto-<n>, tcp:<port>, udp:<port>, port:<n>, from:<addr>, to:<addr> or \
1153 host:<addr>. \"all\" and \"none\" are whole expressions, not terms"
1154 ))),
1155 }
1156}
1157
1158pub fn checksum16(bytes: &[u8]) -> u16 {
1164 let mut sum: u32 = 0;
1165 let mut chunks = bytes.chunks_exact(2);
1166 for c in &mut chunks {
1167 sum += u16::from_be_bytes([c[0], c[1]]) as u32;
1168 }
1169 if let [last] = chunks.remainder() {
1170 sum += (*last as u32) << 8;
1171 }
1172 while sum >> 16 != 0 {
1173 sum = (sum & 0xffff) + (sum >> 16);
1174 }
1175 !(sum as u16)
1176}
1177
1178fn pseudo_header(source: IpAddr, destination: IpAddr, protocol: u8, length: usize) -> Vec<u8> {
1179 let mut out = Vec::with_capacity(40);
1180 match (source, destination) {
1181 (IpAddr::V4(s), IpAddr::V4(d)) => {
1182 out.extend_from_slice(&s.octets());
1183 out.extend_from_slice(&d.octets());
1184 out.push(0);
1185 out.push(protocol);
1186 out.extend_from_slice(&(length as u16).to_be_bytes());
1187 }
1188 (IpAddr::V6(s), IpAddr::V6(d)) => {
1189 out.extend_from_slice(&s.octets());
1190 out.extend_from_slice(&d.octets());
1191 out.extend_from_slice(&(length as u32).to_be_bytes());
1192 out.extend_from_slice(&[0, 0, 0, protocol]);
1193 }
1194 _ => unreachable!("caller checked the address families match"),
1195 }
1196 out
1197}
1198
1199fn get_str<'a>(action: &'a Value, key: &str) -> Option<&'a str> {
1200 action.get(key).and_then(|v| v.as_str())
1201}
1202
1203fn get_u64(action: &Value, key: &str) -> Option<u64> {
1204 action.get(key).and_then(|v| v.as_u64())
1205}
1206
1207fn required_addr(action: &Value, key: &str) -> Result<IpAddr, BuildError> {
1208 let raw = get_str(action, key).ok_or_else(|| bad(format!("send_packet requires \"{key}\"")))?;
1209 raw.parse()
1210 .map_err(|_| bad(format!("\"{key}\" is not an IP address: {raw:?}")))
1211}
1212
1213pub fn decode_payload(action: &Value) -> Result<Vec<u8>, BuildError> {
1220 let Some(payload) = action.get("payload") else {
1221 return Ok(Vec::new());
1222 };
1223 if payload.is_null() {
1224 return Ok(Vec::new());
1225 }
1226 let text = payload
1227 .as_str()
1228 .ok_or_else(|| bad("\"payload\" must be a string"))?;
1229 let encoding = get_str(action, "payload_encoding").unwrap_or("utf8");
1230 match encoding.trim().to_ascii_lowercase().as_str() {
1231 "utf8" | "utf-8" | "text" => Ok(text.as_bytes().to_vec()),
1232 "hex" => hex::decode(text.trim()).map_err(|e| {
1233 bad(format!(
1234 "\"payload\" is not valid hex ({e}); payload_encoding said \"hex\""
1235 ))
1236 }),
1237 other => Err(bad(format!(
1238 "payload_encoding must be \"utf8\" or \"hex\", got {other:?}"
1239 ))),
1240 }
1241}
1242
1243pub fn build_packet(action: &Value) -> Result<Vec<u8>, BuildError> {
1250 let source = required_addr(action, "source")?;
1251 let destination = required_addr(action, "destination")?;
1252
1253 let declared_version = get_u64(action, "ip_version");
1254 let ipv6 = match (source, destination) {
1255 (IpAddr::V4(_), IpAddr::V4(_)) => false,
1256 (IpAddr::V6(_), IpAddr::V6(_)) => true,
1257 _ => {
1258 return Err(bad(
1259 "\"source\" and \"destination\" must be the same IP version",
1260 ))
1261 }
1262 };
1263 if let Some(v) = declared_version {
1264 let expected = if ipv6 { 6 } else { 4 };
1265 if v != expected {
1266 return Err(bad(format!(
1267 "ip_version says {v} but the addresses are IPv{expected}"
1268 )));
1269 }
1270 }
1271
1272 let protocol = match action.get("protocol") {
1273 None => return Err(bad("send_packet requires \"protocol\"")),
1274 Some(Value::String(s)) => ip_protocol_number(s).ok_or_else(|| {
1275 bad(format!(
1276 "unknown protocol {s:?}; use a name such as \"icmp\", \"icmpv6\", \"tcp\", \
1277 \"udp\", or a number"
1278 ))
1279 })?,
1280 Some(Value::Number(n)) => {
1281 let v = n
1282 .as_u64()
1283 .ok_or_else(|| bad("\"protocol\" must be a non-negative number"))?;
1284 u8::try_from(v).map_err(|_| bad("\"protocol\" must be 0-255"))?
1285 }
1286 Some(other) => {
1287 return Err(bad(format!(
1288 "\"protocol\" must be a name or a number, got {other}"
1289 )))
1290 }
1291 };
1292
1293 if ipv6 && protocol == 1 {
1294 return Err(bad(
1295 "protocol \"icmp\" is IPv4-only; use \"icmpv6\" with IPv6 addresses",
1296 ));
1297 }
1298 if !ipv6 && protocol == 58 {
1299 return Err(bad(
1300 "protocol \"icmpv6\" is IPv6-only; use \"icmp\" with IPv4 addresses",
1301 ));
1302 }
1303
1304 let payload = decode_payload(action)?;
1305
1306 let transport = match protocol {
1307 1 | 58 => build_icmp(action, ipv6, &payload)?,
1308 6 => build_tcp(action, &payload)?,
1309 17 => build_udp(action, &payload)?,
1310 _ => payload.clone(),
1311 };
1312
1313 let mut transport = transport;
1314 match protocol {
1316 58 => {
1317 let ph = pseudo_header(source, destination, 58, transport.len());
1318 let mut all = ph;
1319 all.extend_from_slice(&transport);
1320 let sum = checksum16(&all);
1321 transport[2..4].copy_from_slice(&sum.to_be_bytes());
1322 }
1323 6 | 17 => {
1324 let ph = pseudo_header(source, destination, protocol, transport.len());
1325 let mut all = ph;
1326 all.extend_from_slice(&transport);
1327 let sum = checksum16(&all);
1328 let at = if protocol == 6 { 16 } else { 6 };
1329 transport[at..at + 2].copy_from_slice(&sum.to_be_bytes());
1330 }
1331 _ => {}
1332 }
1333
1334 let ttl = match (get_u64(action, "ttl"), get_u64(action, "hop_limit")) {
1335 (Some(v), _) | (None, Some(v)) => {
1336 u8::try_from(v).map_err(|_| bad("\"ttl\"/\"hop_limit\" must be 0-255"))?
1337 }
1338 (None, None) => 64,
1339 };
1340
1341 let mut packet = if ipv6 {
1342 build_ipv6_header(source, destination, protocol, ttl, transport.len())?
1343 } else {
1344 build_ipv4_header(action, source, destination, protocol, ttl, transport.len())?
1345 };
1346 packet.extend_from_slice(&transport);
1347
1348 match (
1352 get_str(action, "source_mac"),
1353 get_str(action, "destination_mac"),
1354 ) {
1355 (Some(src), Some(dst)) => {
1356 let mut frame = Vec::with_capacity(ETHERNET_HEADER_LEN + packet.len());
1357 frame.extend_from_slice(&parse_mac(dst)?);
1358 frame.extend_from_slice(&parse_mac(src)?);
1359 frame.extend_from_slice(
1360 &(if ipv6 { ETHERTYPE_IPV6 } else { ETHERTYPE_IPV4 }).to_be_bytes(),
1361 );
1362 frame.extend_from_slice(&packet);
1363 Ok(frame)
1364 }
1365 (None, None) => Ok(packet),
1366 _ => Err(bad(
1367 "an Ethernet frame needs both \"source_mac\" and \"destination_mac\"; supply \
1368 neither for a layer-3 (TUN) packet",
1369 )),
1370 }
1371}
1372
1373fn build_ipv4_header(
1374 action: &Value,
1375 source: IpAddr,
1376 destination: IpAddr,
1377 protocol: u8,
1378 ttl: u8,
1379 payload_len: usize,
1380) -> Result<Vec<u8>, BuildError> {
1381 let (IpAddr::V4(s), IpAddr::V4(d)) = (source, destination) else {
1382 unreachable!("caller checked the family");
1383 };
1384 let total = 20 + payload_len;
1385 if total > u16::MAX as usize {
1386 return Err(bad(format!(
1387 "packet is {total} bytes, larger than IPv4 allows"
1388 )));
1389 }
1390 let identification = get_u64(action, "identification")
1391 .map(|v| u16::try_from(v).map_err(|_| bad("\"identification\" must be 0-65535")))
1392 .transpose()?
1393 .unwrap_or(0);
1394
1395 let mut h = Vec::with_capacity(20);
1396 h.push(0x45); h.push(0); h.extend_from_slice(&(total as u16).to_be_bytes());
1399 h.extend_from_slice(&identification.to_be_bytes());
1400 h.extend_from_slice(&0x4000u16.to_be_bytes()); h.push(ttl);
1402 h.push(protocol);
1403 h.extend_from_slice(&[0, 0]); h.extend_from_slice(&s.octets());
1405 h.extend_from_slice(&d.octets());
1406 let sum = checksum16(&h);
1407 h[10..12].copy_from_slice(&sum.to_be_bytes());
1408 Ok(h)
1409}
1410
1411fn build_ipv6_header(
1412 source: IpAddr,
1413 destination: IpAddr,
1414 protocol: u8,
1415 hop_limit: u8,
1416 payload_len: usize,
1417) -> Result<Vec<u8>, BuildError> {
1418 let (IpAddr::V6(s), IpAddr::V6(d)) = (source, destination) else {
1419 unreachable!("caller checked the family");
1420 };
1421 let declared = u16::try_from(payload_len).map_err(|_| {
1426 bad(format!(
1427 "payload is {payload_len} bytes; IPv6's payload-length field is two bytes, and \
1428 anything larger needs a jumbogram option this builder does not emit"
1429 ))
1430 })?;
1431 let mut h = Vec::with_capacity(40);
1432 h.extend_from_slice(&0x6000_0000u32.to_be_bytes()); h.extend_from_slice(&declared.to_be_bytes());
1434 h.push(protocol);
1435 h.push(hop_limit);
1436 h.extend_from_slice(&s.octets());
1437 h.extend_from_slice(&d.octets());
1438 Ok(h)
1439}
1440
1441fn icmp_type_of(action: &Value, ipv6: bool) -> Result<u8, BuildError> {
1442 match action.get("icmp_type") {
1443 None => Err(bad(
1444 "an ICMP packet requires \"icmp_type\" (a name such as \"echo_reply\", or a number)",
1445 )),
1446 Some(Value::String(s)) => icmp_type_number(s, ipv6).ok_or_else(|| {
1447 bad(format!(
1448 "unknown ICMP{} type {s:?}",
1449 if ipv6 { "v6" } else { "" }
1450 ))
1451 }),
1452 Some(Value::Number(n)) => {
1453 let v = n
1454 .as_u64()
1455 .ok_or_else(|| bad("\"icmp_type\" must be a non-negative number"))?;
1456 u8::try_from(v).map_err(|_| bad("\"icmp_type\" must be 0-255"))
1457 }
1458 Some(other) => Err(bad(format!(
1459 "\"icmp_type\" must be a name or a number, got {other}"
1460 ))),
1461 }
1462}
1463
1464fn build_icmp(action: &Value, ipv6: bool, payload: &[u8]) -> Result<Vec<u8>, BuildError> {
1465 let icmp_type = icmp_type_of(action, ipv6)?;
1466 let code = get_u64(action, "icmp_code")
1467 .map(|v| u8::try_from(v).map_err(|_| bad("\"icmp_code\" must be 0-255")))
1468 .transpose()?
1469 .unwrap_or(0);
1470
1471 let echo = if ipv6 {
1472 matches!(icmp_type, 128 | 129)
1473 } else {
1474 matches!(icmp_type, 0 | 8)
1475 };
1476
1477 let mut msg = Vec::with_capacity(8 + payload.len());
1478 msg.push(icmp_type);
1479 msg.push(code);
1480 msg.extend_from_slice(&[0, 0]); if echo {
1483 let id = get_u64(action, "icmp_id")
1484 .map(|v| u16::try_from(v).map_err(|_| bad("\"icmp_id\" must be 0-65535")))
1485 .transpose()?
1486 .unwrap_or(0);
1487 let seq = get_u64(action, "icmp_sequence")
1488 .map(|v| u16::try_from(v).map_err(|_| bad("\"icmp_sequence\" must be 0-65535")))
1489 .transpose()?
1490 .unwrap_or(0);
1491 msg.extend_from_slice(&id.to_be_bytes());
1492 msg.extend_from_slice(&seq.to_be_bytes());
1493 } else {
1494 msg.extend_from_slice(&[0, 0, 0, 0]); }
1496 msg.extend_from_slice(payload);
1497
1498 if !ipv6 {
1499 let sum = checksum16(&msg);
1502 msg[2..4].copy_from_slice(&sum.to_be_bytes());
1503 }
1504 Ok(msg)
1505}
1506
1507fn port(action: &Value, key: &str) -> Result<u16, BuildError> {
1508 let v = get_u64(action, key)
1509 .ok_or_else(|| bad(format!("this packet requires \"{key}\" (0-65535)")))?;
1510 u16::try_from(v).map_err(|_| bad(format!("\"{key}\" must be 0-65535")))
1511}
1512
1513fn build_udp(action: &Value, payload: &[u8]) -> Result<Vec<u8>, BuildError> {
1514 let length = u16::try_from(8 + payload.len()).map_err(|_| {
1518 bad(format!(
1519 "a UDP datagram is {} bytes including its header, and UDP's length field is two \
1520 bytes (65535 maximum)",
1521 8 + payload.len()
1522 ))
1523 })?;
1524 let mut msg = Vec::with_capacity(8 + payload.len());
1525 msg.extend_from_slice(&port(action, "source_port")?.to_be_bytes());
1526 msg.extend_from_slice(&port(action, "destination_port")?.to_be_bytes());
1527 msg.extend_from_slice(&length.to_be_bytes());
1528 msg.extend_from_slice(&[0, 0]); msg.extend_from_slice(payload);
1530 Ok(msg)
1531}
1532
1533fn build_tcp(action: &Value, payload: &[u8]) -> Result<Vec<u8>, BuildError> {
1534 let flags = match action.get("flags") {
1535 None => 0x10, Some(Value::Array(items)) => {
1537 let names: Vec<String> = items
1538 .iter()
1539 .map(|v| {
1540 v.as_str()
1541 .map(str::to_string)
1542 .ok_or_else(|| bad("\"flags\" entries must be strings"))
1543 })
1544 .collect::<Result<_, _>>()?;
1545 tcp_flag_bits(&names)?
1546 }
1547 Some(other) => {
1548 return Err(bad(format!(
1549 "\"flags\" must be an array of flag names, got {other}"
1550 )))
1551 }
1552 };
1553 let seq = get_u64(action, "seq")
1558 .map(|v| u32::try_from(v).map_err(|_| bad("\"seq\" must be 0-4294967295")))
1559 .transpose()?
1560 .unwrap_or(0);
1561 let ack = get_u64(action, "ack")
1562 .map(|v| u32::try_from(v).map_err(|_| bad("\"ack\" must be 0-4294967295")))
1563 .transpose()?
1564 .unwrap_or(0);
1565 let window = get_u64(action, "window")
1566 .map(|v| u16::try_from(v).map_err(|_| bad("\"window\" must be 0-65535")))
1567 .transpose()?
1568 .unwrap_or(65535);
1569
1570 let mut msg = Vec::with_capacity(20 + payload.len());
1571 msg.extend_from_slice(&port(action, "source_port")?.to_be_bytes());
1572 msg.extend_from_slice(&port(action, "destination_port")?.to_be_bytes());
1573 msg.extend_from_slice(&seq.to_be_bytes());
1574 msg.extend_from_slice(&ack.to_be_bytes());
1575 msg.push(5 << 4); msg.push(flags);
1577 msg.extend_from_slice(&window.to_be_bytes());
1578 msg.extend_from_slice(&[0, 0]); msg.extend_from_slice(&[0, 0]); msg.extend_from_slice(payload);
1581 Ok(msg)
1582}