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::convert::Infallible as Never;
7use core::fmt::Debug;
8use netstack3_base::{
9    AddressMatcher, InspectableValue, InterfaceMatcher, InterfaceProperties, Matcher,
10    MatcherBindingsTypes, PortMatcher,
11};
12
13use derivative::Derivative;
14use packet_formats::ip::IpExt;
15
16use crate::FilterBindingsTypes;
17use crate::logic::Interfaces;
18use crate::packets::{FilterIpExt, FilterIpPacket, MaybeTransportPacket, TransportPacketData};
19use crate::state::FilterPacketMetadata;
20
21/// A matcher for transport-layer protocol or port numbers.
22#[derive(Debug, Clone)]
23pub struct TransportProtocolMatcher<P> {
24    /// The transport-layer protocol.
25    pub proto: P,
26    /// If set, the matcher for the source port or identifier of the transport
27    /// header.
28    pub src_port: Option<PortMatcher>,
29    /// If set, the matcher for the destination port or identifier of the
30    /// transport header.
31    pub dst_port: Option<PortMatcher>,
32}
33
34impl<P: Debug> InspectableValue for TransportProtocolMatcher<P> {
35    fn record<I: netstack3_base::Inspector>(&self, name: &str, inspector: &mut I) {
36        inspector.record_debug(name, self);
37    }
38}
39
40impl<P: PartialEq, T: MaybeTransportPacket> Matcher<(Option<P>, T)>
41    for TransportProtocolMatcher<P>
42{
43    fn matches(&self, actual: &(Option<P>, T)) -> bool {
44        let Self { proto, src_port, dst_port } = self;
45        let (packet_proto, packet) = actual;
46
47        let Some(packet_proto) = packet_proto else {
48            return false;
49        };
50
51        proto == packet_proto && {
52            let transport_data = packet.transport_packet_data();
53            src_port.required_matches(
54                transport_data.as_ref().map(TransportPacketData::src_port).as_ref(),
55            ) && dst_port.required_matches(
56                transport_data.as_ref().map(TransportPacketData::dst_port).as_ref(),
57            )
58        }
59    }
60}
61
62/// A trait for external matchers supplied by bindings.
63pub trait BindingsPacketMatcher<D> {
64    /// Returns `true` if the packet matches.
65    fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
66        &self,
67        packet: &P,
68        interfaces: Interfaces<'_, D>,
69        meta: &impl FilterPacketMetadata,
70    ) -> bool;
71}
72
73impl<D> BindingsPacketMatcher<D> for Never {
74    fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
75        &self,
76        _packet: &P,
77        _interfaces: Interfaces<'_, D>,
78        _meta: &impl FilterPacketMetadata,
79    ) -> bool {
80        match *self {}
81    }
82}
83
84impl<D, M: BindingsPacketMatcher<D>> BindingsPacketMatcher<D> for Arc<M> {
85    fn matches<I: FilterIpExt, P: FilterIpPacket<I>>(
86        &self,
87        packet: &P,
88        interfaces: Interfaces<'_, D>,
89        meta: &impl FilterPacketMetadata,
90    ) -> bool {
91        self.as_ref().matches(packet, interfaces, meta)
92    }
93}
94
95/// Top-level matcher for IP packets.
96#[derive(Derivative)]
97#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
98pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
99    /// The interface on which the packet entered the stack.
100    ///
101    /// Only available in `INGRESS`, `LOCAL_INGRESS`, and `FORWARDING`.
102    pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
103    /// The interface through which the packet exits the stack.
104    ///
105    /// Only available in `FORWARDING`, `LOCAL_EGRESS`, and `EGRESS`.
106    pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
107    /// Matcher for the source IP address.
108    pub src_address: Option<AddressMatcher<I::Addr>>,
109    /// Matcher for the destination IP address.
110    pub dst_address: Option<AddressMatcher<I::Addr>>,
111    /// Matchers for the transport layer.
112    pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
113    /// A custom matcher to run on the packet.
114    pub external_matcher: Option<BT::BindingsPacketMatcher>,
115}
116
117impl<I, BT> PacketMatcher<I, BT>
118where
119    I: FilterIpExt,
120    BT: FilterBindingsTypes,
121{
122    pub(crate) fn matches<P, D>(
123        &self,
124        packet: &P,
125        interfaces: Interfaces<'_, D>,
126        meta: &impl FilterPacketMetadata,
127    ) -> bool
128    where
129        P: FilterIpPacket<I>,
130        D: InterfaceProperties<BT::DeviceClass>,
131        BT: FilterBindingsTypes<BindingsPacketMatcher: BindingsPacketMatcher<D>>,
132    {
133        let Self {
134            in_interface,
135            out_interface,
136            src_address,
137            dst_address,
138            transport_protocol,
139            external_matcher,
140        } = self;
141        let Interfaces { ingress: in_if, egress: out_if } = interfaces;
142
143        // If no fields are specified, match all traffic by default.
144        in_interface.required_matches(in_if)
145            && out_interface.required_matches(out_if)
146            && src_address.matches(&packet.src_addr())
147            && dst_address.matches(&packet.dst_addr())
148            && transport_protocol.matches(&(packet.protocol(), packet.maybe_transport_packet()))
149            && external_matcher.as_ref().map_or(true, |m| m.matches(packet, interfaces, meta))
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use ip_test_macro::ip_test;
156    use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
157    use packet_formats::ip::{IpProto, Ipv4Proto};
158    use test_case::test_case;
159
160    use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
161    use netstack3_base::{AddressMatcherType, SegmentHeader, SubnetMatcher};
162
163    use super::*;
164    use crate::context::testutil::{FakeBindingsCtx, FakeBindingsPacketMatcher};
165    use crate::packets::testutil::internal::{
166        ArbitraryValue, FakeIcmpEchoRequest, FakeIpPacket, FakeNullPacket, FakeTcpSegment,
167        FakeUdpPacket, TestIpExt, TransportPacketExt,
168    };
169    use crate::state::FakePacketMetadata;
170
171    #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
172    #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
173    #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
174    fn match_on_interface_properties(matcher: InterfaceMatcher<FakeDeviceClass>) {
175        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
176            in_interface: Some(matcher.clone()),
177            out_interface: Some(matcher),
178            ..Default::default()
179        };
180
181        assert_eq!(
182            matcher.matches(
183                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
184                Interfaces {
185                    ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
186                    egress: Some(&FakeMatcherDeviceId::wlan_interface())
187                },
188                &FakePacketMetadata::default(),
189            ),
190            true
191        );
192        assert_eq!(
193            matcher.matches(
194                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
195                Interfaces {
196                    ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
197                    egress: Some(&FakeMatcherDeviceId::ethernet_interface())
198                },
199                &FakePacketMetadata::default(),
200            ),
201            false
202        );
203    }
204
205    #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
206    #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
207    #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
208    fn interface_matcher_specified_but_not_available_in_hook_does_not_match(
209        matcher: InterfaceMatcher<FakeDeviceClass>,
210    ) {
211        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
212            in_interface: Some(matcher.clone()),
213            out_interface: Some(matcher),
214            ..Default::default()
215        };
216
217        assert_eq!(
218            matcher.matches(
219                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
220                Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::wlan_interface()) },
221                &FakePacketMetadata::default(),
222            ),
223            false,
224        );
225        assert_eq!(
226            matcher.matches(
227                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
228                Interfaces { ingress: Some(&FakeMatcherDeviceId::wlan_interface()), egress: None },
229                &FakePacketMetadata::default(),
230            ),
231            false,
232        );
233        assert_eq!(
234            matcher.matches(
235                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
236                Interfaces {
237                    ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
238                    egress: Some(&FakeMatcherDeviceId::wlan_interface())
239                },
240                &FakePacketMetadata::default(),
241            ),
242            true,
243        );
244    }
245
246    enum AddressMatcherTestCase {
247        Subnet,
248        Range,
249    }
250
251    #[ip_test(I)]
252    #[test_case(AddressMatcherTestCase::Subnet, /* invert */ false)]
253    #[test_case(AddressMatcherTestCase::Subnet, /* invert */ true)]
254    #[test_case(AddressMatcherTestCase::Range, /* invert */ false)]
255    #[test_case(AddressMatcherTestCase::Range, /* invert */ 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                    // Generate the inclusive address range that is equivalent to the subnet.
267                    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::<I, FakeBindingsCtx<I>> {
289                src_address: Some(matcher.clone()),
290                ..Default::default()
291            },
292            PacketMatcher::<I, FakeBindingsCtx<I>> {
293                dst_address: Some(matcher),
294                ..Default::default()
295            },
296        ] {
297            assert_ne!(
298                matcher.matches::<_, FakeMatcherDeviceId>(
299                    &FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
300                    Interfaces { ingress: None, egress: None },
301                    &FakePacketMetadata::default(),
302                ),
303                invert,
304            );
305            assert_eq!(
306                matcher.matches::<_, FakeMatcherDeviceId>(
307                    &FakeIpPacket {
308                        src_ip: I::IP_OUTSIDE_SUBNET,
309                        dst_ip: I::IP_OUTSIDE_SUBNET,
310                        body: FakeTcpSegment::arbitrary_value(),
311                    },
312                    Interfaces { ingress: None, egress: None },
313                    &FakePacketMetadata::default(),
314                ),
315                invert,
316            );
317        }
318    }
319
320    enum Protocol {
321        Tcp,
322        Udp,
323        Icmp,
324    }
325
326    impl Protocol {
327        fn ip_proto<I: FilterIpExt>(&self) -> Option<I::Proto> {
328            match self {
329                Self::Tcp => <&FakeTcpSegment as TransportPacketExt<I>>::proto(),
330                Self::Udp => <&FakeUdpPacket as TransportPacketExt<I>>::proto(),
331                Self::Icmp => <&FakeIcmpEchoRequest as TransportPacketExt<I>>::proto(),
332            }
333        }
334    }
335
336    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => true)]
337    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
338    #[test_case(
339        Protocol::Tcp,
340        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
341        => false
342    )]
343    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
344    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => true)]
345    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value()=> false)]
346    #[test_case(
347        Protocol::Udp,
348        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
349        => false
350    )]
351    #[test_case(
352        Protocol::Icmp,
353        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
354        => true
355    )]
356    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
357    #[test_case(
358        Protocol::Icmp,
359        FakeIpPacket::<Ipv6, FakeIcmpEchoRequest>::arbitrary_value()
360        => true
361    )]
362    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => false)]
363    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
364    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
365    fn match_on_transport_protocol<I, P>(protocol: Protocol, packet: P) -> bool
366    where
367        I: TestIpExt,
368        P: FilterIpPacket<I>,
369    {
370        let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
371            transport_protocol: Some(TransportProtocolMatcher {
372                proto: protocol.ip_proto::<I>().unwrap(),
373                src_port: None,
374                dst_port: None,
375            }),
376            ..Default::default()
377        };
378
379        matcher.matches::<_, FakeMatcherDeviceId>(
380            &packet,
381            Interfaces { ingress: None, egress: None },
382            &FakePacketMetadata::default(),
383        )
384    }
385
386    #[test_case(
387        Some(PortMatcher { range: 1024..=65535, invert: false }), None, (11111, 80), true;
388        "matching src port"
389    )]
390    #[test_case(
391        Some(PortMatcher { range: 1024..=65535, invert: true }), None, (11111, 80), false;
392        "invert match src port"
393    )]
394    #[test_case(
395        Some(PortMatcher { range: 1024..=65535, invert: false }), None, (53, 80), false;
396        "non-matching src port"
397    )]
398    #[test_case(
399        None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 22), true;
400        "match dst port"
401    )]
402    #[test_case(
403        None, Some(PortMatcher { range: 22..=22, invert: true }), (11111, 22), false;
404        "invert match dst port"
405    )]
406    #[test_case(
407        None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 80), false;
408        "non-matching dst port"
409    )]
410    fn match_on_port_range(
411        src_port: Option<PortMatcher>,
412        dst_port: Option<PortMatcher>,
413        transport_header: (u16, u16),
414        expect_match: bool,
415    ) {
416        // TCP
417        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
418            transport_protocol: Some(TransportProtocolMatcher {
419                proto: Ipv4Proto::Proto(IpProto::Tcp),
420                src_port: src_port.clone(),
421                dst_port: dst_port.clone(),
422            }),
423            ..Default::default()
424        };
425        let (src, dst) = transport_header;
426        assert_eq!(
427            matcher.matches::<_, FakeMatcherDeviceId>(
428                &FakeIpPacket::<Ipv4, _> {
429                    body: FakeTcpSegment {
430                        src_port: src,
431                        dst_port: dst,
432                        segment: SegmentHeader::arbitrary_value(),
433                        payload_len: 8888,
434                    },
435                    ..ArbitraryValue::arbitrary_value()
436                },
437                Interfaces { ingress: None, egress: None },
438                &FakePacketMetadata::default(),
439            ),
440            expect_match
441        );
442
443        // UDP
444        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
445            transport_protocol: Some(TransportProtocolMatcher {
446                proto: Ipv4Proto::Proto(IpProto::Udp),
447                src_port,
448                dst_port,
449            }),
450            ..Default::default()
451        };
452        let (src, dst) = transport_header;
453        assert_eq!(
454            matcher.matches::<_, FakeMatcherDeviceId>(
455                &FakeIpPacket::<Ipv4, _> {
456                    body: FakeUdpPacket { src_port: src, dst_port: dst },
457                    ..ArbitraryValue::arbitrary_value()
458                },
459                Interfaces { ingress: None, egress: None },
460                &FakePacketMetadata::default(),
461            ),
462            expect_match
463        );
464    }
465
466    #[ip_test(I)]
467    fn packet_must_match_all_provided_matchers<I: TestIpExt>() {
468        let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
469            src_address: Some(AddressMatcher {
470                matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
471                invert: false,
472            }),
473            dst_address: Some(AddressMatcher {
474                matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
475                invert: false,
476            }),
477            ..Default::default()
478        };
479
480        assert_eq!(
481            matcher.matches::<_, FakeMatcherDeviceId>(
482                &FakeIpPacket::<_, FakeTcpSegment> {
483                    src_ip: I::IP_OUTSIDE_SUBNET,
484                    ..ArbitraryValue::arbitrary_value()
485                },
486                Interfaces { ingress: None, egress: None },
487                &FakePacketMetadata::default(),
488            ),
489            false
490        );
491        assert_eq!(
492            matcher.matches::<_, FakeMatcherDeviceId>(
493                &FakeIpPacket::<_, FakeTcpSegment> {
494                    dst_ip: I::IP_OUTSIDE_SUBNET,
495                    ..ArbitraryValue::arbitrary_value()
496                },
497                Interfaces { ingress: None, egress: None },
498                &FakePacketMetadata::default(),
499            ),
500            false
501        );
502        assert_eq!(
503            matcher.matches::<_, FakeMatcherDeviceId>(
504                &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
505                Interfaces { ingress: None, egress: None },
506                &FakePacketMetadata::default(),
507            ),
508            true
509        );
510    }
511
512    #[test]
513    fn match_by_default_if_no_specified_matchers() {
514        assert_eq!(
515            PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>>::default()
516                .matches::<_, FakeMatcherDeviceId>(
517                    &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
518                    Interfaces { ingress: None, egress: None },
519                    &FakePacketMetadata::default(),
520                ),
521            true
522        );
523    }
524
525    #[test_case(true; "yes")]
526    #[test_case(false; "no")]
527    fn match_external_matcher(result: bool) {
528        let external_matcher = FakeBindingsPacketMatcher::new(result);
529        assert_eq!(
530            PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
531                external_matcher: Some(external_matcher.clone()),
532                ..Default::default()
533            }
534            .matches::<_, FakeMatcherDeviceId>(
535                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
536                Interfaces {
537                    ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
538                    egress: None
539                },
540                &FakePacketMetadata::default(),
541            ),
542            result
543        );
544        assert_eq!(external_matcher.num_calls(), 1);
545    }
546}