1use alloc::sync::Arc;
6use core::fmt::Debug;
7use netstack3_base::{
8 AddressMatcher, InspectableValue, InterfaceMatcher, InterfaceProperties, MarkMatchers, Matcher,
9 MatcherBindingsTypes, PortMatcher,
10};
11
12use derivative::Derivative;
13use packet_formats::ip::IpExt;
14
15use crate::FilterBindingsTypes;
16use crate::logic::Interfaces;
17use crate::packets::{FilterIpExt, FilterIpPacket, MaybeTransportPacket, TransportPacketData};
18use crate::state::FilterPacketMetadata;
19
20#[derive(Debug, Clone)]
22pub struct TransportProtocolMatcher<P> {
23 pub proto: P,
25 pub src_port: Option<PortMatcher>,
28 pub dst_port: Option<PortMatcher>,
31}
32
33impl<P: Debug> InspectableValue for TransportProtocolMatcher<P> {
34 fn record<I: netstack3_base::Inspector>(&self, name: &str, inspector: &mut I) {
35 inspector.record_debug(name, self);
36 }
37}
38
39impl<P: PartialEq, T: MaybeTransportPacket> Matcher<(Option<P>, T)>
40 for TransportProtocolMatcher<P>
41{
42 fn matches(&self, actual: &(Option<P>, T)) -> bool {
43 let Self { proto, src_port, dst_port } = self;
44 let (packet_proto, packet) = actual;
45
46 let Some(packet_proto) = packet_proto else {
47 return false;
48 };
49
50 proto == packet_proto && {
51 let transport_data = packet.transport_packet_data();
52 src_port.required_matches(
53 transport_data.as_ref().map(TransportPacketData::src_port_or_id).as_ref(),
54 ) && dst_port.required_matches(
55 transport_data.as_ref().map(TransportPacketData::dst_port_or_id).as_ref(),
56 )
57 }
58 }
59}
60
61pub trait BindingsPacketMatcher<D> {
63 fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
65 &self,
66 packet: &P,
67 interfaces: Interfaces<'_, D>,
68 meta: &impl FilterPacketMetadata,
69 ) -> bool;
70}
71
72impl<D> BindingsPacketMatcher<D> for ! {
73 fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
74 &self,
75 _packet: &P,
76 _interfaces: Interfaces<'_, D>,
77 _meta: &impl FilterPacketMetadata,
78 ) -> bool {
79 match *self {}
80 }
81}
82
83impl<D, M: BindingsPacketMatcher<D>> BindingsPacketMatcher<D> for Arc<M> {
84 fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
85 &self,
86 packet: &P,
87 interfaces: Interfaces<'_, D>,
88 meta: &impl FilterPacketMetadata,
89 ) -> bool {
90 self.as_ref().matches(packet, interfaces, meta)
91 }
92}
93
94#[derive(Derivative)]
96#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
97pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
98 pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
102 pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
106 pub src_address: Option<AddressMatcher<I::Addr>>,
108 pub dst_address: Option<AddressMatcher<I::Addr>>,
110 pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
112 pub external_matcher: Option<BT::BindingsPacketMatcher>,
114 pub mark_matcher: Option<MarkMatchers>,
116}
117
118impl<I, BT> PacketMatcher<I, BT>
119where
120 I: FilterIpExt,
121 BT: FilterBindingsTypes,
122{
123 pub(crate) fn matches<P, D>(
124 &self,
125 packet: &P,
126 interfaces: Interfaces<'_, D>,
127 meta: &impl FilterPacketMetadata,
128 ) -> bool
129 where
130 P: FilterIpPacket<I>,
131 D: InterfaceProperties<BT::DeviceClass>,
132 BT: FilterBindingsTypes<BindingsPacketMatcher: BindingsPacketMatcher<D>>,
133 {
134 let Self {
135 in_interface,
136 out_interface,
137 src_address,
138 dst_address,
139 transport_protocol,
140 external_matcher,
141 mark_matcher,
142 } = self;
143 let Interfaces { ingress: in_if, egress: out_if } = interfaces;
144
145 in_interface.required_matches(in_if)
147 && out_interface.required_matches(out_if)
148 && src_address.matches(&packet.src_addr())
149 && dst_address.matches(&packet.dst_addr())
150 && transport_protocol.matches(&(packet.protocol(), packet.maybe_transport_packet()))
151 && external_matcher.as_ref().map_or(true, |m| m.matches(packet, interfaces, meta))
152 && mark_matcher.matches(meta.marks())
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use ip_test_macro::ip_test;
159 use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
160 use packet_formats::ip::{IpProto, Ipv4Proto};
161 use test_case::test_case;
162
163 use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
164 use netstack3_base::{
165 AddressMatcherType, MarkDomain, MarkMatcher, Marks, PacketType, SegmentHeader,
166 SubnetMatcher,
167 };
168
169 use super::*;
170 use crate::context::testutil::{FakeBindingsCtx, FakeBindingsPacketMatcher};
171 use crate::packets::testutil::internal::{
172 ArbitraryValue, FakeIcmpEchoRequest, FakeIpPacket, FakeNullPacket, FakeTcpSegment,
173 FakeUdpPacket, TestIpExt, TransportPacketExt,
174 };
175 use crate::state::testutil::FakePacketMetadata;
176
177 #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
178 #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
179 #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
180 fn match_on_interface_properties(matcher: InterfaceMatcher<FakeDeviceClass>) {
181 let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
182 in_interface: Some(matcher.clone()),
183 out_interface: Some(matcher),
184 ..Default::default()
185 };
186
187 assert_eq!(
188 matcher.matches(
189 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
190 Interfaces {
191 ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
192 egress: Some(&FakeMatcherDeviceId::wlan_interface())
193 },
194 &FakePacketMetadata::default(),
195 ),
196 true
197 );
198 assert_eq!(
199 matcher.matches(
200 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
201 Interfaces {
202 ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
203 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
204 },
205 &FakePacketMetadata::default(),
206 ),
207 false
208 );
209 }
210
211 #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
212 #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
213 #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
214 fn interface_matcher_specified_but_not_available_in_hook_does_not_match(
215 matcher: InterfaceMatcher<FakeDeviceClass>,
216 ) {
217 let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
218 in_interface: Some(matcher.clone()),
219 out_interface: Some(matcher),
220 ..Default::default()
221 };
222
223 assert_eq!(
224 matcher.matches(
225 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
226 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::wlan_interface()) },
227 &FakePacketMetadata::default(),
228 ),
229 false,
230 );
231 assert_eq!(
232 matcher.matches(
233 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
234 Interfaces { ingress: Some(&FakeMatcherDeviceId::wlan_interface()), egress: None },
235 &FakePacketMetadata::default(),
236 ),
237 false,
238 );
239 assert_eq!(
240 matcher.matches(
241 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
242 Interfaces {
243 ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
244 egress: Some(&FakeMatcherDeviceId::wlan_interface())
245 },
246 &FakePacketMetadata::default(),
247 ),
248 true,
249 );
250 }
251
252 enum AddressMatcherTestCase {
253 Subnet,
254 Range,
255 }
256
257 #[ip_test(I)]
258 #[test_case(AddressMatcherTestCase::Subnet, false)]
259 #[test_case(AddressMatcherTestCase::Subnet, true)]
260 #[test_case(AddressMatcherTestCase::Range, false)]
261 #[test_case(AddressMatcherTestCase::Range, true)]
262 fn match_on_subnet_or_address_range<I: TestIpExt>(
263 test_case: AddressMatcherTestCase,
264 invert: bool,
265 ) {
266 let matcher = AddressMatcher {
267 matcher: match test_case {
268 AddressMatcherTestCase::Subnet => {
269 AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET))
270 }
271 AddressMatcherTestCase::Range => {
272 let start = I::SUBNET.network();
274 let end = I::map_ip(
275 start,
276 |start| {
277 let range_size = 2_u32.pow(32 - u32::from(I::SUBNET.prefix())) - 1;
278 let end = u32::from_be_bytes(start.ipv4_bytes()) + range_size;
279 Ipv4Addr::from(end.to_be_bytes())
280 },
281 |start| {
282 let range_size = 2_u128.pow(128 - u32::from(I::SUBNET.prefix())) - 1;
283 let end = u128::from_be_bytes(start.ipv6_bytes()) + range_size;
284 Ipv6Addr::from(end.to_be_bytes())
285 },
286 );
287 AddressMatcherType::Range(start..=end)
288 }
289 },
290 invert,
291 };
292
293 for matcher in [
294 PacketMatcher::<I, FakeBindingsCtx<I>> {
295 src_address: Some(matcher.clone()),
296 ..Default::default()
297 },
298 PacketMatcher::<I, FakeBindingsCtx<I>> {
299 dst_address: Some(matcher),
300 ..Default::default()
301 },
302 ] {
303 assert_ne!(
304 matcher.matches::<_, FakeMatcherDeviceId>(
305 &FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
306 Interfaces { ingress: None, egress: None },
307 &FakePacketMetadata::default(),
308 ),
309 invert,
310 );
311 assert_eq!(
312 matcher.matches::<_, FakeMatcherDeviceId>(
313 &FakeIpPacket {
314 src_ip: I::IP_OUTSIDE_SUBNET,
315 dst_ip: I::IP_OUTSIDE_SUBNET,
316 body: FakeTcpSegment::arbitrary_value(),
317 },
318 Interfaces { ingress: None, egress: None },
319 &FakePacketMetadata::default(),
320 ),
321 invert,
322 );
323 }
324 }
325
326 enum Protocol {
327 Tcp,
328 Udp,
329 Icmp,
330 }
331
332 impl Protocol {
333 fn ip_proto<I: FilterIpExt>(&self) -> Option<I::Proto> {
334 match self {
335 Self::Tcp => <&FakeTcpSegment as TransportPacketExt<I>>::proto(),
336 Self::Udp => <&FakeUdpPacket as TransportPacketExt<I>>::proto(),
337 Self::Icmp => <&FakeIcmpEchoRequest as TransportPacketExt<I>>::proto(),
338 }
339 }
340 }
341
342 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => true)]
343 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
344 #[test_case(
345 Protocol::Tcp,
346 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
347 => false
348 )]
349 #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
350 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => true)]
351 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value()=> false)]
352 #[test_case(
353 Protocol::Udp,
354 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
355 => false
356 )]
357 #[test_case(
358 Protocol::Icmp,
359 FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
360 => true
361 )]
362 #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
363 #[test_case(
364 Protocol::Icmp,
365 FakeIpPacket::<Ipv6, FakeIcmpEchoRequest>::arbitrary_value()
366 => true
367 )]
368 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => false)]
369 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
370 #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
371 fn match_on_transport_protocol<I, P>(protocol: Protocol, packet: P) -> bool
372 where
373 I: TestIpExt,
374 P: FilterIpPacket<I>,
375 {
376 let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
377 transport_protocol: Some(TransportProtocolMatcher {
378 proto: protocol.ip_proto::<I>().unwrap(),
379 src_port: None,
380 dst_port: None,
381 }),
382 ..Default::default()
383 };
384
385 matcher.matches::<_, FakeMatcherDeviceId>(
386 &packet,
387 Interfaces { ingress: None, egress: None },
388 &FakePacketMetadata::default(),
389 )
390 }
391
392 #[test_case(
393 Some(PortMatcher { range: 1024..=65535, invert: false }), None, (11111, 80), true;
394 "matching src port"
395 )]
396 #[test_case(
397 Some(PortMatcher { range: 1024..=65535, invert: true }), None, (11111, 80), false;
398 "invert match src port"
399 )]
400 #[test_case(
401 Some(PortMatcher { range: 1024..=65535, invert: false }), None, (53, 80), false;
402 "non-matching src port"
403 )]
404 #[test_case(
405 None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 22), true;
406 "match dst port"
407 )]
408 #[test_case(
409 None, Some(PortMatcher { range: 22..=22, invert: true }), (11111, 22), false;
410 "invert match dst port"
411 )]
412 #[test_case(
413 None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 80), false;
414 "non-matching dst port"
415 )]
416 fn match_on_port_range(
417 src_port: Option<PortMatcher>,
418 dst_port: Option<PortMatcher>,
419 transport_header: (u16, u16),
420 expect_match: bool,
421 ) {
422 let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
424 transport_protocol: Some(TransportProtocolMatcher {
425 proto: Ipv4Proto::Proto(IpProto::Tcp),
426 src_port: src_port.clone(),
427 dst_port: dst_port.clone(),
428 }),
429 ..Default::default()
430 };
431 let (src, dst) = transport_header;
432 assert_eq!(
433 matcher.matches::<_, FakeMatcherDeviceId>(
434 &FakeIpPacket::<Ipv4, _> {
435 body: FakeTcpSegment {
436 src_port: src,
437 dst_port: dst,
438 segment: SegmentHeader::arbitrary_value(),
439 payload_len: 8888,
440 },
441 ..ArbitraryValue::arbitrary_value()
442 },
443 Interfaces { ingress: None, egress: None },
444 &FakePacketMetadata::default(),
445 ),
446 expect_match
447 );
448
449 let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
451 transport_protocol: Some(TransportProtocolMatcher {
452 proto: Ipv4Proto::Proto(IpProto::Udp),
453 src_port,
454 dst_port,
455 }),
456 ..Default::default()
457 };
458 let (src, dst) = transport_header;
459 assert_eq!(
460 matcher.matches::<_, FakeMatcherDeviceId>(
461 &FakeIpPacket::<Ipv4, _> {
462 body: FakeUdpPacket { src_port: src, dst_port: dst },
463 ..ArbitraryValue::arbitrary_value()
464 },
465 Interfaces { ingress: None, egress: None },
466 &FakePacketMetadata::default(),
467 ),
468 expect_match
469 );
470 }
471
472 #[ip_test(I)]
473 fn packet_must_match_all_provided_matchers<I: TestIpExt>() {
474 let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
475 src_address: Some(AddressMatcher {
476 matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
477 invert: false,
478 }),
479 dst_address: Some(AddressMatcher {
480 matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
481 invert: false,
482 }),
483 ..Default::default()
484 };
485
486 assert_eq!(
487 matcher.matches::<_, FakeMatcherDeviceId>(
488 &FakeIpPacket::<_, FakeTcpSegment> {
489 src_ip: I::IP_OUTSIDE_SUBNET,
490 ..ArbitraryValue::arbitrary_value()
491 },
492 Interfaces { ingress: None, egress: None },
493 &FakePacketMetadata::default(),
494 ),
495 false
496 );
497 assert_eq!(
498 matcher.matches::<_, FakeMatcherDeviceId>(
499 &FakeIpPacket::<_, FakeTcpSegment> {
500 dst_ip: I::IP_OUTSIDE_SUBNET,
501 ..ArbitraryValue::arbitrary_value()
502 },
503 Interfaces { ingress: None, egress: None },
504 &FakePacketMetadata::default(),
505 ),
506 false
507 );
508 assert_eq!(
509 matcher.matches::<_, FakeMatcherDeviceId>(
510 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
511 Interfaces { ingress: None, egress: None },
512 &FakePacketMetadata::default(),
513 ),
514 true
515 );
516 }
517
518 #[test]
519 fn match_by_default_if_no_specified_matchers() {
520 assert_eq!(
521 PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>>::default()
522 .matches::<_, FakeMatcherDeviceId>(
523 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
524 Interfaces { ingress: None, egress: None },
525 &FakePacketMetadata::default(),
526 ),
527 true
528 );
529 }
530
531 #[test_case(true; "yes")]
532 #[test_case(false; "no")]
533 fn match_external_matcher(result: bool) {
534 let external_matcher = FakeBindingsPacketMatcher::new(result);
535 assert_eq!(
536 PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
537 external_matcher: Some(external_matcher.clone()),
538 ..Default::default()
539 }
540 .matches::<_, FakeMatcherDeviceId>(
541 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
542 Interfaces {
543 ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
544 egress: None
545 },
546 &FakePacketMetadata::default(),
547 ),
548 result
549 );
550 assert_eq!(external_matcher.num_calls(), 1);
551 }
552
553 #[test]
554 fn match_on_mark() {
555 let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
556 mark_matcher: Some(MarkMatchers::new([(
557 MarkDomain::Mark1,
558 MarkMatcher::Marked { mask: !0, start: 100, end: 100, invert: false },
559 )])),
560 ..Default::default()
561 };
562
563 assert_eq!(
564 matcher.matches(
565 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
566 Interfaces { ingress: None, egress: None },
567 &FakePacketMetadata::new(Marks::new([(MarkDomain::Mark1, 100)]), PacketType::Host),
568 ),
569 true
570 );
571 assert_eq!(
572 matcher.matches(
573 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
574 Interfaces { ingress: None, egress: None },
575 &FakePacketMetadata::new(Marks::new([(MarkDomain::Mark1, 200)]), PacketType::Host),
576 ),
577 false
578 );
579 assert_eq!(
580 matcher.matches(
581 &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
582 Interfaces { ingress: None, egress: None },
583 &FakePacketMetadata::default(),
584 ),
585 false
586 );
587 }
588}