1use core::fmt::Debug;
6use core::ops::RangeInclusive;
7use netstack3_base::{
8 InspectableValue, InterfaceMatcher, InterfaceProperties, Matcher, SubnetMatcher,
9};
10
11use derivative::Derivative;
12use net_types::ip::IpAddress;
13use packet_formats::ip::IpExt;
14
15use crate::logic::Interfaces;
16use crate::packets::{FilterIpExt, IpPacket, MaybeTransportPacket, TransportPacketData};
17
18#[derive(Clone, Derivative)]
20#[derivative(Debug)]
21pub enum AddressMatcherType<A: IpAddress> {
22 #[derivative(Debug = "transparent")]
24 Subnet(SubnetMatcher<A>),
25 Range(RangeInclusive<A>),
27}
28
29impl<A: IpAddress> Matcher<A> for AddressMatcherType<A> {
30 fn matches(&self, actual: &A) -> bool {
31 match self {
32 Self::Subnet(subnet_matcher) => subnet_matcher.matches(actual),
33 Self::Range(range) => range.contains(actual),
34 }
35 }
36}
37
38#[derive(Clone, Debug)]
40pub struct AddressMatcher<A: IpAddress> {
41 pub matcher: AddressMatcherType<A>,
43 pub invert: bool,
47}
48
49impl<A: IpAddress> InspectableValue for AddressMatcher<A> {
50 fn record<I: netstack3_base::Inspector>(&self, name: &str, inspector: &mut I) {
51 inspector.record_debug(name, self);
52 }
53}
54
55impl<A: IpAddress> Matcher<A> for AddressMatcher<A> {
56 fn matches(&self, addr: &A) -> bool {
57 let Self { matcher, invert } = self;
58 matcher.matches(addr) ^ *invert
59 }
60}
61
62#[derive(Clone, Debug)]
64pub struct PortMatcher {
65 pub range: RangeInclusive<u16>,
67 pub invert: bool,
71}
72
73impl Matcher<u16> for PortMatcher {
74 fn matches(&self, actual: &u16) -> bool {
75 let Self { range, invert } = self;
76 range.contains(actual) ^ *invert
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct TransportProtocolMatcher<P> {
83 pub proto: P,
85 pub src_port: Option<PortMatcher>,
88 pub dst_port: Option<PortMatcher>,
91}
92
93impl<P: Debug> InspectableValue for TransportProtocolMatcher<P> {
94 fn record<I: netstack3_base::Inspector>(&self, name: &str, inspector: &mut I) {
95 inspector.record_debug(name, self);
96 }
97}
98
99impl<P: PartialEq, T: MaybeTransportPacket> Matcher<(Option<P>, T)>
100 for TransportProtocolMatcher<P>
101{
102 fn matches(&self, actual: &(Option<P>, T)) -> bool {
103 let Self { proto, src_port, dst_port } = self;
104 let (packet_proto, packet) = actual;
105
106 let Some(packet_proto) = packet_proto else {
107 return false;
108 };
109
110 proto == packet_proto && {
111 let transport_data = packet.transport_packet_data();
112 src_port.required_matches(
113 transport_data.as_ref().map(TransportPacketData::src_port).as_ref(),
114 ) && dst_port.required_matches(
115 transport_data.as_ref().map(TransportPacketData::dst_port).as_ref(),
116 )
117 }
118 }
119}
120
121#[derive(Derivative, Debug, Clone)]
123#[derivative(Default(bound = ""))]
124pub struct PacketMatcher<I: IpExt, DeviceClass> {
125 pub in_interface: Option<InterfaceMatcher<DeviceClass>>,
129 pub out_interface: Option<InterfaceMatcher<DeviceClass>>,
133 pub src_address: Option<AddressMatcher<I::Addr>>,
135 pub dst_address: Option<AddressMatcher<I::Addr>>,
137 pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
139}
140
141impl<I: FilterIpExt, DeviceClass> PacketMatcher<I, DeviceClass> {
142 pub(crate) fn matches<P: IpPacket<I>, D: InterfaceProperties<DeviceClass>>(
143 &self,
144 packet: &P,
145 interfaces: &Interfaces<'_, D>,
146 ) -> bool {
147 let Self { in_interface, out_interface, src_address, dst_address, transport_protocol } =
148 self;
149 let Interfaces { ingress: in_if, egress: out_if } = interfaces;
150
151 in_interface.required_matches(*in_if)
153 && out_interface.required_matches(*out_if)
154 && src_address.matches(&packet.src_addr())
155 && dst_address.matches(&packet.dst_addr())
156 && transport_protocol.matches(&(packet.protocol(), packet.maybe_transport_packet()))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use ip_test_macro::ip_test;
163 use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
164 use packet_formats::ip::{IpProto, Ipv4Proto};
165 use test_case::test_case;
166
167 use netstack3_base::SegmentHeader;
168 use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
169
170 use super::*;
171 use crate::packets::testutil::internal::{
172 ArbitraryValue, FakeIcmpEchoRequest, FakeIpPacket, FakeNullPacket, FakeTcpSegment,
173 FakeUdpPacket, TestIpExt, TransportPacketExt,
174 };
175
176 #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
177 #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
178 #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
179 fn match_on_interface_properties(matcher: InterfaceMatcher<FakeDeviceClass>) {
180 let matcher = PacketMatcher {
181 in_interface: Some(matcher.clone()),
182 out_interface: Some(matcher),
183 ..Default::default()
184 };
185
186 assert_eq!(
187 matcher.matches(
188 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
189 &Interfaces {
190 ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
191 egress: Some(&FakeMatcherDeviceId::wlan_interface())
192 },
193 ),
194 true
195 );
196 assert_eq!(
197 matcher.matches(
198 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
199 &Interfaces {
200 ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
201 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
202 },
203 ),
204 false
205 );
206 }
207
208 #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
209 #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
210 #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
211 fn interface_matcher_specified_but_not_available_in_hook_does_not_match(
212 matcher: InterfaceMatcher<FakeDeviceClass>,
213 ) {
214 let matcher = PacketMatcher {
215 in_interface: Some(matcher.clone()),
216 out_interface: Some(matcher),
217 ..Default::default()
218 };
219
220 assert_eq!(
221 matcher.matches(
222 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
223 &Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::wlan_interface()) },
224 ),
225 false
226 );
227 assert_eq!(
228 matcher.matches(
229 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
230 &Interfaces { ingress: Some(&FakeMatcherDeviceId::wlan_interface()), egress: None },
231 ),
232 false
233 );
234 assert_eq!(
235 matcher.matches(
236 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
237 &Interfaces {
238 ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
239 egress: Some(&FakeMatcherDeviceId::wlan_interface())
240 },
241 ),
242 true
243 );
244 }
245
246 enum AddressMatcherTestCase {
247 Subnet,
248 Range,
249 }
250
251 #[ip_test(I)]
252 #[test_case(AddressMatcherTestCase::Subnet, false)]
253 #[test_case(AddressMatcherTestCase::Subnet, true)]
254 #[test_case(AddressMatcherTestCase::Range, false)]
255 #[test_case(AddressMatcherTestCase::Range, true)]
256 fn match_on_subnet_or_address_range<I: TestIpExt>(
257 test_case: AddressMatcherTestCase,
258 invert: bool,
259 ) {
260 let matcher = AddressMatcher {
261 matcher: match test_case {
262 AddressMatcherTestCase::Subnet => {
263 AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET))
264 }
265 AddressMatcherTestCase::Range => {
266 let start = I::SUBNET.network();
268 let end = I::map_ip(
269 start,
270 |start| {
271 let range_size = 2_u32.pow(32 - u32::from(I::SUBNET.prefix())) - 1;
272 let end = u32::from_be_bytes(start.ipv4_bytes()) + range_size;
273 Ipv4Addr::from(end.to_be_bytes())
274 },
275 |start| {
276 let range_size = 2_u128.pow(128 - u32::from(I::SUBNET.prefix())) - 1;
277 let end = u128::from_be_bytes(start.ipv6_bytes()) + range_size;
278 Ipv6Addr::from(end.to_be_bytes())
279 },
280 );
281 AddressMatcherType::Range(start..=end)
282 }
283 },
284 invert,
285 };
286
287 for matcher in [
288 PacketMatcher { src_address: Some(matcher.clone()), ..Default::default() },
289 PacketMatcher { dst_address: Some(matcher), ..Default::default() },
290 ] {
291 assert_ne!(
292 matcher.matches::<_, FakeMatcherDeviceId>(
293 &FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
294 &Interfaces { ingress: None, egress: None },
295 ),
296 invert
297 );
298 assert_eq!(
299 matcher.matches::<_, FakeMatcherDeviceId>(
300 &FakeIpPacket {
301 src_ip: I::IP_OUTSIDE_SUBNET,
302 dst_ip: I::IP_OUTSIDE_SUBNET,
303 body: FakeTcpSegment::arbitrary_value(),
304 },
305 &Interfaces { ingress: None, egress: None },
306 ),
307 invert
308 );
309 }
310 }
311
312 enum Protocol {
313 Tcp,
314 Udp,
315 Icmp,
316 }
317
318 impl Protocol {
319 fn ip_proto<I: FilterIpExt>(&self) -> Option<I::Proto> {
320 match self {
321 Self::Tcp => <&FakeTcpSegment as TransportPacketExt<I>>::proto(),
322 Self::Udp => <&FakeUdpPacket as TransportPacketExt<I>>::proto(),
323 Self::Icmp => <&FakeIcmpEchoRequest as TransportPacketExt<I>>::proto(),
324 }
325 }
326 }
327
328 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => true)]
329 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
330 #[test_case(
331 Protocol::Tcp,
332 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
333 => false
334 )]
335 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
336 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => true)]
337 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value()=> false)]
338 #[test_case(
339 Protocol::Udp,
340 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
341 => false
342 )]
343 #[test_case(
344 Protocol::Icmp,
345 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
346 => true
347 )]
348 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
349 #[test_case(
350 Protocol::Icmp,
351 FakeIpPacket::<Ipv6, FakeIcmpEchoRequest>::arbitrary_value()
352 => true
353 )]
354 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => false)]
355 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
356 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
357 fn match_on_transport_protocol<I: TestIpExt, P: IpPacket<I>>(
358 protocol: Protocol,
359 packet: P,
360 ) -> bool {
361 let matcher = PacketMatcher {
362 transport_protocol: Some(TransportProtocolMatcher {
363 proto: protocol.ip_proto::<I>().unwrap(),
364 src_port: None,
365 dst_port: None,
366 }),
367 ..Default::default()
368 };
369
370 matcher
371 .matches::<_, FakeMatcherDeviceId>(&packet, &Interfaces { ingress: None, egress: None })
372 }
373
374 #[test_case(
375 Some(PortMatcher { range: 1024..=65535, invert: false }), None, (11111, 80), true;
376 "matching src port"
377 )]
378 #[test_case(
379 Some(PortMatcher { range: 1024..=65535, invert: true }), None, (11111, 80), false;
380 "invert match src port"
381 )]
382 #[test_case(
383 Some(PortMatcher { range: 1024..=65535, invert: false }), None, (53, 80), false;
384 "non-matching src port"
385 )]
386 #[test_case(
387 None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 22), true;
388 "match dst port"
389 )]
390 #[test_case(
391 None, Some(PortMatcher { range: 22..=22, invert: true }), (11111, 22), false;
392 "invert match dst port"
393 )]
394 #[test_case(
395 None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 80), false;
396 "non-matching dst port"
397 )]
398 fn match_on_port_range(
399 src_port: Option<PortMatcher>,
400 dst_port: Option<PortMatcher>,
401 transport_header: (u16, u16),
402 expect_match: bool,
403 ) {
404 let matcher = PacketMatcher {
406 transport_protocol: Some(TransportProtocolMatcher {
407 proto: Ipv4Proto::Proto(IpProto::Tcp),
408 src_port: src_port.clone(),
409 dst_port: dst_port.clone(),
410 }),
411 ..Default::default()
412 };
413 let (src, dst) = transport_header;
414 assert_eq!(
415 matcher.matches::<_, FakeMatcherDeviceId>(
416 &FakeIpPacket::<Ipv4, _> {
417 body: FakeTcpSegment {
418 src_port: src,
419 dst_port: dst,
420 segment: SegmentHeader::arbitrary_value(),
421 payload_len: 8888,
422 },
423 ..ArbitraryValue::arbitrary_value()
424 },
425 &Interfaces { ingress: None, egress: None },
426 ),
427 expect_match
428 );
429
430 let matcher = PacketMatcher {
432 transport_protocol: Some(TransportProtocolMatcher {
433 proto: Ipv4Proto::Proto(IpProto::Udp),
434 src_port,
435 dst_port,
436 }),
437 ..Default::default()
438 };
439 let (src, dst) = transport_header;
440 assert_eq!(
441 matcher.matches::<_, FakeMatcherDeviceId>(
442 &FakeIpPacket::<Ipv4, _> {
443 body: FakeUdpPacket { src_port: src, dst_port: dst },
444 ..ArbitraryValue::arbitrary_value()
445 },
446 &Interfaces { ingress: None, egress: None },
447 ),
448 expect_match
449 );
450 }
451
452 #[ip_test(I)]
453 fn packet_must_match_all_provided_matchers<I: TestIpExt>() {
454 let matcher = PacketMatcher::<I, FakeDeviceClass> {
455 src_address: Some(AddressMatcher {
456 matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
457 invert: false,
458 }),
459 dst_address: Some(AddressMatcher {
460 matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
461 invert: false,
462 }),
463 ..Default::default()
464 };
465
466 assert_eq!(
467 matcher.matches::<_, FakeMatcherDeviceId>(
468 &FakeIpPacket::<_, FakeTcpSegment> {
469 src_ip: I::IP_OUTSIDE_SUBNET,
470 ..ArbitraryValue::arbitrary_value()
471 },
472 &Interfaces { ingress: None, egress: None },
473 ),
474 false
475 );
476 assert_eq!(
477 matcher.matches::<_, FakeMatcherDeviceId>(
478 &FakeIpPacket::<_, FakeTcpSegment> {
479 dst_ip: I::IP_OUTSIDE_SUBNET,
480 ..ArbitraryValue::arbitrary_value()
481 },
482 &Interfaces { ingress: None, egress: None },
483 ),
484 false
485 );
486 assert_eq!(
487 matcher.matches::<_, FakeMatcherDeviceId>(
488 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
489 &Interfaces { ingress: None, egress: None },
490 ),
491 true
492 );
493 }
494
495 #[test]
496 fn match_by_default_if_no_specified_matchers() {
497 assert_eq!(
498 PacketMatcher::<_, FakeDeviceClass>::default().matches::<_, FakeMatcherDeviceId>(
499 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
500 &Interfaces { ingress: None, egress: None },
501 ),
502 true
503 );
504 }
505}