Add --mac filter and rename IpFilter to PacketFilter
A packet is kept if its Ethernet source or destination MAC matches any --mac entry; combined with --ip, both must match. Empty dimensions match everything, so the unfiltered case is still the same code path. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b111ddfdc
commit
a9e283a87c
6 files changed
+189
-53
No files matched your search
+9
-9
@@ -1,4 +1,4 @@
|
||||
use crate::filter::{IpFilter, LINKTYPE_ETHERNET};
|
||||
use crate::filter::{PacketFilter, LINKTYPE_ETHERNET};
|
||||
use anyhow::{Result, bail};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -166,7 +166,7 @@ pub struct RecordAlignedChunker {
|
||||
/// KEPT bytes -- a filter keeping 1% of the traffic still produces
|
||||
/// full-size frames rather than 1%-size ones.
|
||||
kept: Vec<u8>,
|
||||
filter: IpFilter,
|
||||
filter: PacketFilter,
|
||||
/// When the oldest byte currently in `kept` was confirmed, i.e. how
|
||||
/// long the pending output has been waiting. `None` when `kept` is
|
||||
/// empty.
|
||||
@@ -190,20 +190,20 @@ pub struct RecordAlignedChunker {
|
||||
|
||||
impl RecordAlignedChunker {
|
||||
/// Unfiltered: keeps every record. Equivalent to `with_filter` with
|
||||
/// `IpFilter::match_all()` -- which is not a separate code path, just
|
||||
/// `PacketFilter::match_all()` -- which is not a separate code path, just
|
||||
/// a filter that says yes, so this and the filtered case exercise the
|
||||
/// same record-confirmation logic. Production goes through
|
||||
/// `with_filter`; this is the convenience the tests use.
|
||||
#[allow(dead_code)]
|
||||
pub fn new(target: usize, flush_after: Duration, partial: bool) -> Self {
|
||||
Self::with_filter(target, flush_after, partial, IpFilter::match_all())
|
||||
Self::with_filter(target, flush_after, partial, PacketFilter::match_all())
|
||||
}
|
||||
|
||||
pub fn with_filter(
|
||||
target: usize,
|
||||
flush_after: Duration,
|
||||
partial: bool,
|
||||
filter: IpFilter,
|
||||
filter: PacketFilter,
|
||||
) -> Self {
|
||||
Self {
|
||||
header_buf: Vec::with_capacity(GLOBAL_HEADER_LEN),
|
||||
@@ -247,7 +247,7 @@ impl RecordAlignedChunker {
|
||||
// stdout: in --server mode stdout is the wire.
|
||||
if !self.filter.is_match_all() && self.linktype != LINKTYPE_ETHERNET {
|
||||
eprintln!(
|
||||
"scpcap: warning: --ip filtering needs Ethernet (link type {LINKTYPE_ETHERNET}), \
|
||||
"scpcap: warning: --ip/--mac filtering needs Ethernet (link type {LINKTYPE_ETHERNET}), \
|
||||
but this capture is link type {}; no packets will match",
|
||||
self.linktype
|
||||
);
|
||||
@@ -895,8 +895,8 @@ mod tests {
|
||||
record_bytes_with_ts(&pkt, false, 1_700_000_000)
|
||||
}
|
||||
|
||||
fn filter_for(specs: &[&str]) -> IpFilter {
|
||||
IpFilter::parse(&specs.iter().map(|s| s.to_string()).collect::<Vec<_>>()).unwrap()
|
||||
fn filter_for(specs: &[&str]) -> PacketFilter {
|
||||
PacketFilter::parse(&specs.iter().map(|s| s.to_string()).collect::<Vec<_>>(), &[]).unwrap()
|
||||
}
|
||||
|
||||
fn drain_all(c: &mut RecordAlignedChunker) -> Vec<(ChunkKind, Vec<u8>)> {
|
||||
@@ -1026,7 +1026,7 @@ mod tests {
|
||||
stream.extend_from_slice(&ip_record(src, THIRD, 60 + i as usize));
|
||||
}
|
||||
|
||||
let mut all = RecordAlignedChunker::with_filter(1 << 20, DEFAULT_FLUSH, false, IpFilter::match_all());
|
||||
let mut all = RecordAlignedChunker::with_filter(1 << 20, DEFAULT_FLUSH, false, PacketFilter::match_all());
|
||||
let mut some = RecordAlignedChunker::with_filter(1 << 20, DEFAULT_FLUSH, false, filter_for(&["1.1.1.1"]));
|
||||
let mut none = RecordAlignedChunker::with_filter(1 << 20, DEFAULT_FLUSH, false, filter_for(&["203.0.113.7"]));
|
||||
for c in [&mut all, &mut some, &mut none] {
|
||||
|
||||
+13
@@ -48,6 +48,14 @@ pub struct ClientCli {
|
||||
#[arg(long = "ip", value_name = "ADDR")]
|
||||
pub ip: Vec<String>,
|
||||
|
||||
/// Only forward packets whose Ethernet source or destination MAC
|
||||
/// matches, e.g. to pull one switch connection out of a capture by its
|
||||
/// upstream MAC ("aa:bb:cc:dd:ee:ff"). May be repeated; a packet is kept
|
||||
/// if it matches any entry. Combined with --ip, a packet must match
|
||||
/// both. Requires an Ethernet-linktype pcap source.
|
||||
#[arg(long = "mac", value_name = "ADDR")]
|
||||
pub mac: Vec<String>,
|
||||
|
||||
/// Append a diagnostic trace of this (local) process's transfer events
|
||||
/// to this file -- positions, rename detection, finalize -- for
|
||||
/// figuring out where a stalled transfer is stuck.
|
||||
@@ -105,6 +113,11 @@ pub struct ServerCli {
|
||||
#[arg(long = "ip", value_name = "ADDR")]
|
||||
pub ip: Vec<String>,
|
||||
|
||||
/// MAC filter for --send mode; repeated, one per address. See the
|
||||
/// client's --mac.
|
||||
#[arg(long = "mac", value_name = "ADDR")]
|
||||
pub mac: Vec<String>,
|
||||
|
||||
/// Destination final path (--recv mode).
|
||||
#[arg(long)]
|
||||
pub dest_final: Option<String>,
|
||||
|
||||
+8
-4
@@ -1,6 +1,6 @@
|
||||
use crate::channel::{ChannelSink, ChannelSource};
|
||||
use crate::cli::ClientCli;
|
||||
use crate::filter::IpFilter;
|
||||
use crate::filter::PacketFilter;
|
||||
use crate::naming::{self, resolve_dest_paths, strip_name, Target};
|
||||
use crate::pipeline::{run_recv, run_send, RecvOptions, SendOptions};
|
||||
use crate::protocol::{MessageSink, MessageSource, WireSink, WireSource};
|
||||
@@ -43,7 +43,7 @@ pub fn run(cli: ClientCli) -> Result<()> {
|
||||
// remote is (see the Remote->Local branch). It is never passed to a
|
||||
// --recv server: the receiver writes bytes verbatim and never parses
|
||||
// them.
|
||||
let filter = IpFilter::parse(&cli.ip)?;
|
||||
let filter = PacketFilter::parse(&cli.ip, &cli.mac)?;
|
||||
// Filtering means reading pcap records, which an already-compressed
|
||||
// source doesn't expose. Without this the transfer would still pick the
|
||||
// record-aligned chunker (a filter forces it) and abort deep inside with
|
||||
@@ -52,8 +52,8 @@ pub fn run(cli: ClientCli) -> Result<()> {
|
||||
// combination rather than the source.
|
||||
if !filter.is_match_all() && source_stripped.was_zst {
|
||||
bail!(
|
||||
"cannot apply --ip to an already-compressed source ({}): \
|
||||
filtering needs to read pcap records. Copy it without --ip, \
|
||||
"cannot apply --ip/--mac to an already-compressed source ({}): \
|
||||
filtering needs to read pcap records. Copy it without --ip/--mac, \
|
||||
or filter the uncompressed capture.",
|
||||
cli.source
|
||||
);
|
||||
@@ -143,6 +143,10 @@ pub fn run(cli: ClientCli) -> Result<()> {
|
||||
server_args.push("--ip".to_string());
|
||||
server_args.push(spec.clone());
|
||||
}
|
||||
for spec in &cli.mac {
|
||||
server_args.push("--mac".to_string());
|
||||
server_args.push(spec.clone());
|
||||
}
|
||||
if effective_compress {
|
||||
server_args.push("--compress".to_string());
|
||||
server_args.push("zstd".to_string());
|
||||
|
||||
+147
-28
@@ -19,38 +19,54 @@ const MAX_VLAN_TAGS: usize = 2;
|
||||
const IPV4_HEADER_MIN: usize = 20;
|
||||
const IPV6_HEADER_LEN: usize = 40;
|
||||
|
||||
/// A set of IP addresses/prefixes to keep. A packet matches if *either* its
|
||||
/// source or destination address falls in *any* entry.
|
||||
const MAC_LEN: usize = 6;
|
||||
|
||||
/// An Ethernet MAC address.
|
||||
type Mac = [u8; MAC_LEN];
|
||||
|
||||
/// What to keep from a capture: packets whose IP addresses and Ethernet MAC
|
||||
/// addresses match.
|
||||
///
|
||||
/// Sized for the expected 1-4 entries: two plain vectors, split by address
|
||||
/// family so a v4 packet never compares against a v6 entry, scanned
|
||||
/// linearly. No sorting (a prefix match isn't an equality test, so ordering
|
||||
/// buys nothing) and no hashing.
|
||||
/// Each dimension is a set of entries; a packet satisfies a dimension if
|
||||
/// *either* its source or destination falls in *any* entry. An empty
|
||||
/// dimension is satisfied by everything (without looking at the packet), and
|
||||
/// a packet must satisfy every dimension to be kept -- so `--ip` and `--mac`
|
||||
/// together mean "this IP *on* this MAC".
|
||||
///
|
||||
/// Sized for the expected 1-4 entries: plain vectors scanned linearly. IP
|
||||
/// entries are split by address family so a v4 packet never compares against
|
||||
/// a v6 entry. No sorting (a prefix match isn't an equality test, so
|
||||
/// ordering buys nothing) and no hashing.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IpFilter {
|
||||
/// Set when no `--ip` was given: every packet is kept, without looking
|
||||
/// at it at all. This is the default so that the filtering code path is
|
||||
/// the *only* code path -- see the chunker.
|
||||
pub struct PacketFilter {
|
||||
/// Set when neither `--ip` nor `--mac` was given: every packet is kept,
|
||||
/// without looking at it at all. This is the default so that the
|
||||
/// filtering code path is the *only* code path -- see the chunker.
|
||||
match_all: bool,
|
||||
/// (network, mask) pairs, network already masked at parse time.
|
||||
v4: Vec<(u32, u32)>,
|
||||
v6: Vec<(u128, u128)>,
|
||||
macs: Vec<Mac>,
|
||||
}
|
||||
|
||||
impl IpFilter {
|
||||
impl PacketFilter {
|
||||
/// The no-`--ip` default: keeps everything, matches without inspecting.
|
||||
pub fn match_all() -> Self {
|
||||
Self { match_all: true, v4: Vec::new(), v6: Vec::new() }
|
||||
Self { match_all: true, v4: Vec::new(), v6: Vec::new(), macs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Parses `--ip` specs: a bare address (`1.1.1.1`, `2606:4700::1111`)
|
||||
/// or a CIDR prefix (`10.0.0.0/8`). An empty list yields `match_all`.
|
||||
pub fn parse(specs: &[String]) -> Result<Self> {
|
||||
if specs.is_empty() {
|
||||
/// Parses `--ip` specs -- a bare address (`1.1.1.1`, `2606:4700::1111`)
|
||||
/// or a CIDR prefix (`10.0.0.0/8`) -- and `--mac` specs
|
||||
/// (`aa:bb:cc:dd:ee:ff`). Two empty lists yield `match_all`.
|
||||
pub fn parse(ip_specs: &[String], mac_specs: &[String]) -> Result<Self> {
|
||||
if ip_specs.is_empty() && mac_specs.is_empty() {
|
||||
return Ok(Self::match_all());
|
||||
}
|
||||
let mut f = Self { match_all: false, v4: Vec::new(), v6: Vec::new() };
|
||||
for spec in specs {
|
||||
let mut f = Self { match_all: false, v4: Vec::new(), v6: Vec::new(), macs: Vec::new() };
|
||||
for spec in mac_specs {
|
||||
f.macs.push(parse_mac(spec)?);
|
||||
}
|
||||
for spec in ip_specs {
|
||||
let (addr_str, prefix_str) = match spec.split_once('/') {
|
||||
Some((a, p)) => (a, Some(p)),
|
||||
None => (spec.as_str(), None),
|
||||
@@ -82,7 +98,8 @@ impl IpFilter {
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
/// Whether this filter keeps everything, i.e. no `--ip` was given.
|
||||
/// Whether this filter keeps everything, i.e. neither `--ip` nor `--mac`
|
||||
/// was given.
|
||||
/// Callers use it to skip work (and warnings) that only a real filter
|
||||
/// needs.
|
||||
pub fn is_match_all(&self) -> bool {
|
||||
@@ -92,9 +109,10 @@ impl IpFilter {
|
||||
/// Whether to keep `packet` -- the captured bytes of one pcap record,
|
||||
/// starting at the link-layer header.
|
||||
///
|
||||
/// Everything unmatched is dropped: non-IP frames (ARP, LLDP), packets
|
||||
/// truncated by snaplen before the addresses, and any link type other
|
||||
/// than Ethernet.
|
||||
/// Everything unmatched is dropped: packets truncated by snaplen before
|
||||
/// the fields being matched, any link type other than Ethernet, and --
|
||||
/// when `--ip` is set -- non-IP frames (ARP, LLDP). With only `--mac`,
|
||||
/// non-IP frames from a matching MAC are kept.
|
||||
pub fn matches(&self, packet: &[u8], linktype: u32) -> bool {
|
||||
if self.match_all {
|
||||
return true;
|
||||
@@ -102,6 +120,27 @@ impl IpFilter {
|
||||
if linktype != LINKTYPE_ETHERNET || packet.len() < ETH_HEADER_LEN {
|
||||
return false;
|
||||
}
|
||||
self.mac_matches(packet) && self.ip_matches(packet)
|
||||
}
|
||||
|
||||
/// Whether the frame's source or destination MAC is in the set; an empty
|
||||
/// set matches every frame. `packet` is at least an Ethernet header long.
|
||||
fn mac_matches(&self, packet: &[u8]) -> bool {
|
||||
if self.macs.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let dst: Mac = packet[0..MAC_LEN].try_into().unwrap();
|
||||
let src: Mac = packet[MAC_LEN..2 * MAC_LEN].try_into().unwrap();
|
||||
self.macs.iter().any(|&m| m == src || m == dst)
|
||||
}
|
||||
|
||||
/// Whether the frame's source or destination IP is in the set; an empty
|
||||
/// set matches every frame, IP or not. `packet` is at least an Ethernet
|
||||
/// header long.
|
||||
fn ip_matches(&self, packet: &[u8]) -> bool {
|
||||
if self.v4.is_empty() && self.v6.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let mut ethertype = be16(&packet[12..14]);
|
||||
let mut off = ETH_HEADER_LEN;
|
||||
let mut tags = 0;
|
||||
@@ -137,6 +176,25 @@ impl IpFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `aa:bb:cc:dd:ee:ff` (or `-` separated) into a MAC address.
|
||||
fn parse_mac(spec: &str) -> Result<Mac> {
|
||||
let bad = || anyhow::anyhow!("--mac {spec:?}: not a MAC address (expected aa:bb:cc:dd:ee:ff)");
|
||||
let mut mac = [0u8; MAC_LEN];
|
||||
let mut parts = spec.split([':', '-']);
|
||||
for byte in &mut mac {
|
||||
let part = parts.next().ok_or_else(bad)?;
|
||||
// from_str_radix alone would accept a leading '+'.
|
||||
if part.len() != 2 || !part.bytes().all(|b| b.is_ascii_hexdigit()) {
|
||||
return Err(bad());
|
||||
}
|
||||
*byte = u8::from_str_radix(part, 16).map_err(|_| bad())?;
|
||||
}
|
||||
if parts.next().is_some() {
|
||||
return Err(bad());
|
||||
}
|
||||
Ok(mac)
|
||||
}
|
||||
|
||||
fn be16(b: &[u8]) -> u16 {
|
||||
u16::from_be_bytes(b.try_into().unwrap())
|
||||
}
|
||||
@@ -157,8 +215,12 @@ mod tests {
|
||||
list.iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
fn filter(list: &[&str]) -> IpFilter {
|
||||
IpFilter::parse(&specs(list)).unwrap()
|
||||
fn filter(list: &[&str]) -> PacketFilter {
|
||||
PacketFilter::parse(&specs(list), &[]).unwrap()
|
||||
}
|
||||
|
||||
fn mac_filter(ips: &[&str], macs: &[&str]) -> PacketFilter {
|
||||
PacketFilter::parse(&specs(ips), &specs(macs)).unwrap()
|
||||
}
|
||||
|
||||
/// An Ethernet frame with `vlan_tags` 802.1Q tags in front of `ethertype`.
|
||||
@@ -199,12 +261,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn match_all_keeps_everything_including_non_ip_and_odd_linktypes() {
|
||||
let f = IpFilter::match_all();
|
||||
let f = PacketFilter::match_all();
|
||||
assert!(f.is_match_all());
|
||||
assert!(f.matches(&[], 113));
|
||||
assert!(f.matches(ð(0, 0x0806, &[1, 2, 3]), LINKTYPE_ETHERNET));
|
||||
// An empty --ip list is the same thing.
|
||||
assert!(IpFilter::parse(&[]).unwrap().is_match_all());
|
||||
assert!(PacketFilter::parse(&[], &[]).unwrap().is_match_all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -298,12 +360,12 @@ mod tests {
|
||||
fn bad_specs_are_rejected_at_parse() {
|
||||
for spec in ["1.1.1.1/33", "::1/129", "not-an-ip", "1.1.1.1/x", "1.1.1.256"] {
|
||||
assert!(
|
||||
IpFilter::parse(&specs(&[spec])).is_err(),
|
||||
PacketFilter::parse(&specs(&[spec]), &[]).is_err(),
|
||||
"{spec:?} should be rejected"
|
||||
);
|
||||
}
|
||||
// /0 and full-width prefixes are fine.
|
||||
assert!(IpFilter::parse(&specs(&["1.1.1.1/32", "0.0.0.0/0", "::/0", "::1/128"])).is_ok());
|
||||
assert!(PacketFilter::parse(&specs(&["1.1.1.1/32", "0.0.0.0/0", "::/0", "::1/128"]), &[]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -314,4 +376,61 @@ mod tests {
|
||||
assert!(f.matches(ð(0, ETHERTYPE_IPV6, &ipv6("::1", "2606:4700::1")), LINKTYPE_ETHERNET));
|
||||
assert!(!f.matches(&v4_packet("9.9.9.9", "203.0.113.1"), LINKTYPE_ETHERNET));
|
||||
}
|
||||
|
||||
const UPSTREAM: &str = "02:00:00:00:00:aa";
|
||||
const OTHER: &str = "02:00:00:00:00:bb";
|
||||
|
||||
/// An Ethernet frame between two MACs carrying `ethertype`/`payload`.
|
||||
fn eth_between(dst: &str, src: &str, ethertype: u16, payload: &[u8]) -> Vec<u8> {
|
||||
let mut f = parse_mac(dst).unwrap().to_vec();
|
||||
f.extend_from_slice(&parse_mac(src).unwrap());
|
||||
f.extend_from_slice(ðertype.to_be_bytes());
|
||||
f.extend_from_slice(payload);
|
||||
f
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_matches_either_direction_only() {
|
||||
let f = mac_filter(&[], &[UPSTREAM]);
|
||||
assert!(!f.is_match_all());
|
||||
let ip = ipv4("1.1.1.1", "9.9.9.9");
|
||||
assert!(f.matches(ð_between(OTHER, UPSTREAM, ETHERTYPE_IPV4, &ip), LINKTYPE_ETHERNET));
|
||||
assert!(f.matches(ð_between(UPSTREAM, OTHER, ETHERTYPE_IPV4, &ip), LINKTYPE_ETHERNET));
|
||||
assert!(!f.matches(ð_between(OTHER, OTHER, ETHERTYPE_IPV4, &ip), LINKTYPE_ETHERNET));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_only_keeps_non_ip_frames_and_ignores_vlan_tags() {
|
||||
let f = mac_filter(&[], &[UPSTREAM]);
|
||||
assert!(f.matches(ð_between(OTHER, UPSTREAM, 0x0806, &[0u8; 28]), LINKTYPE_ETHERNET));
|
||||
assert!(f.matches(ð_between(OTHER, UPSTREAM, ETHERTYPE_VLAN, &[0u8; 4]), LINKTYPE_ETHERNET));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_and_ip_must_both_match() {
|
||||
let f = mac_filter(&["1.1.1.1"], &[UPSTREAM]);
|
||||
let wanted = ipv4("1.1.1.1", "9.9.9.9");
|
||||
let unwanted = ipv4("8.8.8.8", "9.9.9.9");
|
||||
assert!(f.matches(ð_between(OTHER, UPSTREAM, ETHERTYPE_IPV4, &wanted), LINKTYPE_ETHERNET));
|
||||
assert!(!f.matches(ð_between(OTHER, UPSTREAM, ETHERTYPE_IPV4, &unwanted), LINKTYPE_ETHERNET));
|
||||
assert!(!f.matches(ð_between(OTHER, OTHER, ETHERTYPE_IPV4, &wanted), LINKTYPE_ETHERNET));
|
||||
// ARP on the right MAC still fails the IP dimension.
|
||||
assert!(!f.matches(ð_between(OTHER, UPSTREAM, 0x0806, &[0u8; 28]), LINKTYPE_ETHERNET));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_specs_parse_strictly() {
|
||||
assert_eq!(parse_mac("AA:bb:0c:0D:ee:ff").unwrap(), [0xaa, 0xbb, 0x0c, 0x0d, 0xee, 0xff]);
|
||||
assert_eq!(parse_mac("aa-bb-cc-dd-ee-ff").unwrap(), [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
|
||||
for bad in ["", "aa:bb:cc:dd:ee", "aa:bb:cc:dd:ee:ff:00", "aa:bb:cc:dd:ee:fg", "a:bb:cc:dd:ee:ff", "aabbccddeeff", "+a:bb:cc:dd:ee:ff"] {
|
||||
assert!(parse_mac(bad).is_err(), "{bad:?} should be rejected");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mac_filter_on_non_ethernet_matches_nothing() {
|
||||
let f = mac_filter(&[], &[UPSTREAM]);
|
||||
let pkt = eth_between(OTHER, UPSTREAM, ETHERTYPE_IPV4, &ipv4("1.1.1.1", "9.9.9.9"));
|
||||
assert!(!f.matches(&pkt, 113));
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
use crate::chunker::{Chunker, RawChunker, RecordAlignedChunker};
|
||||
use crate::filter::IpFilter;
|
||||
use crate::filter::PacketFilter;
|
||||
use crate::protocol::{Message, MessageSink, MessageSource};
|
||||
use crate::tail::{run_tail, SourceEvent};
|
||||
use crate::tracelog::Logger;
|
||||
@@ -28,10 +28,10 @@ pub struct SendOptions {
|
||||
/// anyway. See `Chunker::should_flush`.
|
||||
pub flush_after: Duration,
|
||||
pub level: i32,
|
||||
/// Which packets to forward. `IpFilter::match_all()` when no `--ip` was
|
||||
/// Which packets to forward. `PacketFilter::match_all()` when no `--ip`/`--mac` was
|
||||
/// given -- not a disabled filter, just one that says yes, so there is
|
||||
/// only ever one code path through the chunker.
|
||||
pub filter: IpFilter,
|
||||
pub filter: PacketFilter,
|
||||
pub log: Arc<Logger>,
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn run_send(opts: SendOptions, mut sink: Box<dyn MessageSink>) -> Result<()>
|
||||
// structure is the only way to tell real content from a preallocating
|
||||
// writer's not-yet-written tail. Here the chunker is a validator, not
|
||||
// a framer.
|
||||
// * a real `--ip` filter: deciding whether to forward a packet means
|
||||
// * a real `--ip`/`--mac` filter: deciding whether to forward a packet means
|
||||
// finding the packet, and record boundaries are the only way to know
|
||||
// where one starts. Here the chunker is a demultiplexer.
|
||||
// A final, uncompressed copy has neither: `run_recv` writes every Data
|
||||
@@ -273,7 +273,7 @@ mod tests {
|
||||
chunk_target: crate::chunker::DEFAULT_TARGET_BYTES,
|
||||
flush_after: crate::chunker::DEFAULT_FLUSH,
|
||||
level: crate::zstd_frame::DEFAULT_LEVEL,
|
||||
filter: IpFilter::match_all(),
|
||||
filter: PacketFilter::match_all(),
|
||||
log: Arc::new(Logger::none()),
|
||||
};
|
||||
run_pipe(opts, dest_final.clone(), dest_temp.clone()).unwrap();
|
||||
@@ -311,7 +311,7 @@ mod tests {
|
||||
chunk_target: crate::chunker::DEFAULT_TARGET_BYTES,
|
||||
flush_after: crate::chunker::DEFAULT_FLUSH,
|
||||
level: crate::zstd_frame::DEFAULT_LEVEL,
|
||||
filter: IpFilter::match_all(),
|
||||
filter: PacketFilter::match_all(),
|
||||
log: Arc::new(Logger::none()),
|
||||
};
|
||||
|
||||
@@ -360,7 +360,7 @@ mod tests {
|
||||
chunk_target: 512, // small target to force multiple frames
|
||||
flush_after: crate::chunker::DEFAULT_FLUSH,
|
||||
level: crate::zstd_frame::DEFAULT_LEVEL,
|
||||
filter: IpFilter::match_all(),
|
||||
filter: PacketFilter::match_all(),
|
||||
log: Arc::new(Logger::none()),
|
||||
};
|
||||
run_pipe(opts, dest_final.clone(), dest_temp.clone()).unwrap();
|
||||
@@ -398,7 +398,7 @@ mod tests {
|
||||
chunk_target: crate::chunker::DEFAULT_TARGET_BYTES,
|
||||
flush_after: crate::chunker::DEFAULT_FLUSH,
|
||||
level: crate::zstd_frame::DEFAULT_LEVEL,
|
||||
filter: IpFilter::match_all(),
|
||||
filter: PacketFilter::match_all(),
|
||||
log: Arc::new(Logger::none()),
|
||||
};
|
||||
assert!(run_pipe(opts, dest_final.clone(), dest_temp).is_err());
|
||||
@@ -421,8 +421,8 @@ mod tests {
|
||||
record_bytes(&pkt, 1_700_000_000)
|
||||
}
|
||||
|
||||
fn ip_filter(specs: &[&str]) -> IpFilter {
|
||||
IpFilter::parse(&specs.iter().map(|s| s.to_string()).collect::<Vec<_>>()).unwrap()
|
||||
fn ip_filter(specs: &[&str]) -> PacketFilter {
|
||||
PacketFilter::parse(&specs.iter().map(|s| s.to_string()).collect::<Vec<_>>(), &[]).unwrap()
|
||||
}
|
||||
|
||||
/// Source stream plus the exact pcap a `--ip 1.1.1.1` transfer should
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::cli::{ServerCli, ServerRole};
|
||||
use crate::filter::IpFilter;
|
||||
use crate::filter::PacketFilter;
|
||||
use crate::naming;
|
||||
use crate::pipeline::{run_recv, run_send, RecvOptions, SendOptions};
|
||||
use crate::protocol::{MessageSink, MessageSource, WireSink, WireSource};
|
||||
@@ -40,7 +40,7 @@ fn run_send_server(cli: ServerCli) -> Result<()> {
|
||||
// The sending side is the only side that filters -- dropping packets
|
||||
// before they hit the wire is the whole point. `run_recv` never parses
|
||||
// what it writes, so --recv has no --ip.
|
||||
let filter = IpFilter::parse(&cli.ip)?;
|
||||
let filter = PacketFilter::parse(&cli.ip, &cli.mac)?;
|
||||
|
||||
let opts = SendOptions {
|
||||
source_path: PathBuf::from(source),
|
||||
|
||||
Reference in new issue
Block a user