Skip to main content

netstack3_filter/
matchers.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// A matcher for transport-layer protocol or port numbers.
21#[derive(Debug, Clone)]
22pub struct TransportProtocolMatcher<P> {
23    /// The transport-layer protocol.
24    pub proto: P,
25    /// If set, the matcher for the source port or identifier of the transport
26    /// header.
27    pub src_port: Option<PortMatcher>,
28    /// If set, the matcher for the destination port or identifier of the
29    /// transport header.
30    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
61/// A trait for external matchers supplied by bindings.
62pub trait BindingsPacketMatcher<D> {
63    /// Returns `true` if the packet matches.
64    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/// Top-level matcher for IP packets.
95#[derive(Derivative)]
96#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
97pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
98    /// The interface on which the packet entered the stack.
99    ///
100    /// Only available in `INGRESS`, `LOCAL_INGRESS`, and `FORWARDING`.
101    pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
102    /// The interface through which the packet exits the stack.
103    ///
104    /// Only available in `FORWARDING`, `LOCAL_EGRESS`, and `EGRESS`.
105    pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
106    /// Matcher for the source IP address.
107    pub src_address: Option<AddressMatcher<I::Addr>>,
108    /// Matcher for the destination IP address.
109    pub dst_address: Option<AddressMatcher<I::Addr>>,
110    /// Matchers for the transport layer.
111    pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
112    /// A custom matcher to run on the packet.
113    pub external_matcher: Option<BT::BindingsPacketMatcher>,
114    /// Matchers for packet marks.
115    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        // If no fields are specified, match all traffic by default.
146        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, /* invert */ false)]
259    #[test_case(AddressMatcherTestCase::Subnet, /* invert */ true)]
260    #[test_case(AddressMatcherTestCase::Range, /* invert */ false)]
261    #[test_case(AddressMatcherTestCase::Range, /* invert */ 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                    // Generate the inclusive address range that is equivalent to the subnet.
273                    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        // TCP
423        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        // UDP
450        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}