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, IpPacket, MaybeTransportPacket, TransportPacketData};
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).as_ref(),
54            ) && dst_port.required_matches(
55                transport_data.as_ref().map(TransportPacketData::dst_port).as_ref(),
56            )
57        }
58    }
59}
60
61/// A trait for external matchers supplied by bindings.
62pub trait BindingsPacketMatcher {
63    /// Returns `true` if the packet matches.
64    fn matches<I: FilterIpExt, P: IpPacket<I>>(&self, packet: &P) -> bool;
65}
66
67impl BindingsPacketMatcher for Never {
68    fn matches<I: FilterIpExt, P: IpPacket<I>>(&self, _packet: &P) -> bool {
69        match *self {}
70    }
71}
72
73impl<M: BindingsPacketMatcher> BindingsPacketMatcher for Arc<M> {
74    fn matches<I: FilterIpExt, P: IpPacket<I>>(&self, packet: &P) -> bool {
75        self.as_ref().matches(packet)
76    }
77}
78
79/// Top-level matcher for IP packets.
80#[derive(Derivative)]
81#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
82pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
83    /// The interface on which the packet entered the stack.
84    ///
85    /// Only available in `INGRESS`, `LOCAL_INGRESS`, and `FORWARDING`.
86    pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
87    /// The interface through which the packet exits the stack.
88    ///
89    /// Only available in `FORWARDING`, `LOCAL_EGRESS`, and `EGRESS`.
90    pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
91    /// Matcher for the source IP address.
92    pub src_address: Option<AddressMatcher<I::Addr>>,
93    /// Matcher for the destination IP address.
94    pub dst_address: Option<AddressMatcher<I::Addr>>,
95    /// Matchers for the transport layer.
96    pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
97    /// A custom matcher to run on the packet.
98    pub external_matcher: Option<BT::BindingsPacketMatcher>,
99}
100
101impl<I: FilterIpExt, BT: FilterBindingsTypes> PacketMatcher<I, BT> {
102    pub(crate) fn matches<P: IpPacket<I>, D: InterfaceProperties<BT::DeviceClass>>(
103        &self,
104        packet: &P,
105        interfaces: &Interfaces<'_, D>,
106    ) -> bool {
107        let Self {
108            in_interface,
109            out_interface,
110            src_address,
111            dst_address,
112            transport_protocol,
113            external_matcher,
114        } = self;
115        let Interfaces { ingress: in_if, egress: out_if } = interfaces;
116
117        // If no fields are specified, match all traffic by default.
118        in_interface.required_matches(*in_if)
119            && out_interface.required_matches(*out_if)
120            && src_address.matches(&packet.src_addr())
121            && dst_address.matches(&packet.dst_addr())
122            && transport_protocol.matches(&(packet.protocol(), packet.maybe_transport_packet()))
123            && external_matcher.as_ref().map_or(true, |m| m.matches(packet))
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use ip_test_macro::ip_test;
130    use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
131    use packet_formats::ip::{IpProto, Ipv4Proto};
132    use test_case::test_case;
133
134    use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
135    use netstack3_base::{AddressMatcherType, SegmentHeader, SubnetMatcher};
136
137    use super::*;
138    use crate::context::testutil::{FakeBindingsCtx, FakeBindingsPacketMatcher};
139    use crate::packets::testutil::internal::{
140        ArbitraryValue, FakeIcmpEchoRequest, FakeIpPacket, FakeNullPacket, FakeTcpSegment,
141        FakeUdpPacket, TestIpExt, TransportPacketExt,
142    };
143
144    #[test_case(InterfaceMatcher::Id(FakeMatcherDeviceId::wlan_interface().id))]
145    #[test_case(InterfaceMatcher::Name(FakeMatcherDeviceId::wlan_interface().name))]
146    #[test_case(InterfaceMatcher::DeviceClass(FakeMatcherDeviceId::wlan_interface().class))]
147    fn match_on_interface_properties(matcher: InterfaceMatcher<FakeDeviceClass>) {
148        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
149            in_interface: Some(matcher.clone()),
150            out_interface: Some(matcher),
151            ..Default::default()
152        };
153
154        assert_eq!(
155            matcher.matches(
156                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
157                &Interfaces {
158                    ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
159                    egress: Some(&FakeMatcherDeviceId::wlan_interface())
160                },
161            ),
162            true
163        );
164        assert_eq!(
165            matcher.matches(
166                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
167                &Interfaces {
168                    ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
169                    egress: Some(&FakeMatcherDeviceId::ethernet_interface())
170                },
171            ),
172            false
173        );
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 interface_matcher_specified_but_not_available_in_hook_does_not_match(
180        matcher: InterfaceMatcher<FakeDeviceClass>,
181    ) {
182        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
183            in_interface: Some(matcher.clone()),
184            out_interface: Some(matcher),
185            ..Default::default()
186        };
187
188        assert_eq!(
189            matcher.matches(
190                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
191                &Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::wlan_interface()) },
192            ),
193            false
194        );
195        assert_eq!(
196            matcher.matches(
197                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
198                &Interfaces { ingress: Some(&FakeMatcherDeviceId::wlan_interface()), egress: None },
199            ),
200            false
201        );
202        assert_eq!(
203            matcher.matches(
204                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
205                &Interfaces {
206                    ingress: Some(&FakeMatcherDeviceId::wlan_interface()),
207                    egress: Some(&FakeMatcherDeviceId::wlan_interface())
208                },
209            ),
210            true
211        );
212    }
213
214    enum AddressMatcherTestCase {
215        Subnet,
216        Range,
217    }
218
219    #[ip_test(I)]
220    #[test_case(AddressMatcherTestCase::Subnet, /* invert */ false)]
221    #[test_case(AddressMatcherTestCase::Subnet, /* invert */ true)]
222    #[test_case(AddressMatcherTestCase::Range, /* invert */ false)]
223    #[test_case(AddressMatcherTestCase::Range, /* invert */ true)]
224    fn match_on_subnet_or_address_range<I: TestIpExt>(
225        test_case: AddressMatcherTestCase,
226        invert: bool,
227    ) {
228        let matcher = AddressMatcher {
229            matcher: match test_case {
230                AddressMatcherTestCase::Subnet => {
231                    AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET))
232                }
233                AddressMatcherTestCase::Range => {
234                    // Generate the inclusive address range that is equivalent to the subnet.
235                    let start = I::SUBNET.network();
236                    let end = I::map_ip(
237                        start,
238                        |start| {
239                            let range_size = 2_u32.pow(32 - u32::from(I::SUBNET.prefix())) - 1;
240                            let end = u32::from_be_bytes(start.ipv4_bytes()) + range_size;
241                            Ipv4Addr::from(end.to_be_bytes())
242                        },
243                        |start| {
244                            let range_size = 2_u128.pow(128 - u32::from(I::SUBNET.prefix())) - 1;
245                            let end = u128::from_be_bytes(start.ipv6_bytes()) + range_size;
246                            Ipv6Addr::from(end.to_be_bytes())
247                        },
248                    );
249                    AddressMatcherType::Range(start..=end)
250                }
251            },
252            invert,
253        };
254
255        for matcher in [
256            PacketMatcher::<I, FakeBindingsCtx<I>> {
257                src_address: Some(matcher.clone()),
258                ..Default::default()
259            },
260            PacketMatcher::<I, FakeBindingsCtx<I>> {
261                dst_address: Some(matcher),
262                ..Default::default()
263            },
264        ] {
265            assert_ne!(
266                matcher.matches::<_, FakeMatcherDeviceId>(
267                    &FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
268                    &Interfaces { ingress: None, egress: None },
269                ),
270                invert
271            );
272            assert_eq!(
273                matcher.matches::<_, FakeMatcherDeviceId>(
274                    &FakeIpPacket {
275                        src_ip: I::IP_OUTSIDE_SUBNET,
276                        dst_ip: I::IP_OUTSIDE_SUBNET,
277                        body: FakeTcpSegment::arbitrary_value(),
278                    },
279                    &Interfaces { ingress: None, egress: None },
280                ),
281                invert
282            );
283        }
284    }
285
286    enum Protocol {
287        Tcp,
288        Udp,
289        Icmp,
290    }
291
292    impl Protocol {
293        fn ip_proto<I: FilterIpExt>(&self) -> Option<I::Proto> {
294            match self {
295                Self::Tcp => <&FakeTcpSegment as TransportPacketExt<I>>::proto(),
296                Self::Udp => <&FakeUdpPacket as TransportPacketExt<I>>::proto(),
297                Self::Icmp => <&FakeIcmpEchoRequest as TransportPacketExt<I>>::proto(),
298            }
299        }
300    }
301
302    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => true)]
303    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
304    #[test_case(
305        Protocol::Tcp,
306        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
307        => false
308    )]
309    #[test_case(Protocol::Tcp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
310    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => true)]
311    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value()=> false)]
312    #[test_case(
313        Protocol::Udp,
314        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
315        => false
316    )]
317    #[test_case(
318        Protocol::Icmp,
319        FakeIpPacket::<Ipv4, FakeIcmpEchoRequest>::arbitrary_value()
320        => true
321    )]
322    #[test_case(Protocol::Udp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
323    #[test_case(
324        Protocol::Icmp,
325        FakeIpPacket::<Ipv6, FakeIcmpEchoRequest>::arbitrary_value()
326        => true
327    )]
328    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value() => false)]
329    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value() => false)]
330    #[test_case(Protocol::Icmp, FakeIpPacket::<Ipv4, FakeNullPacket>::arbitrary_value() => false)]
331    fn match_on_transport_protocol<I: TestIpExt, P: IpPacket<I>>(
332        protocol: Protocol,
333        packet: P,
334    ) -> bool {
335        let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
336            transport_protocol: Some(TransportProtocolMatcher {
337                proto: protocol.ip_proto::<I>().unwrap(),
338                src_port: None,
339                dst_port: None,
340            }),
341            ..Default::default()
342        };
343
344        matcher
345            .matches::<_, FakeMatcherDeviceId>(&packet, &Interfaces { ingress: None, egress: None })
346    }
347
348    #[test_case(
349        Some(PortMatcher { range: 1024..=65535, invert: false }), None, (11111, 80), true;
350        "matching src port"
351    )]
352    #[test_case(
353        Some(PortMatcher { range: 1024..=65535, invert: true }), None, (11111, 80), false;
354        "invert match src port"
355    )]
356    #[test_case(
357        Some(PortMatcher { range: 1024..=65535, invert: false }), None, (53, 80), false;
358        "non-matching src port"
359    )]
360    #[test_case(
361        None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 22), true;
362        "match dst port"
363    )]
364    #[test_case(
365        None, Some(PortMatcher { range: 22..=22, invert: true }), (11111, 22), false;
366        "invert match dst port"
367    )]
368    #[test_case(
369        None, Some(PortMatcher { range: 22..=22, invert: false }), (11111, 80), false;
370        "non-matching dst port"
371    )]
372    fn match_on_port_range(
373        src_port: Option<PortMatcher>,
374        dst_port: Option<PortMatcher>,
375        transport_header: (u16, u16),
376        expect_match: bool,
377    ) {
378        // TCP
379        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
380            transport_protocol: Some(TransportProtocolMatcher {
381                proto: Ipv4Proto::Proto(IpProto::Tcp),
382                src_port: src_port.clone(),
383                dst_port: dst_port.clone(),
384            }),
385            ..Default::default()
386        };
387        let (src, dst) = transport_header;
388        assert_eq!(
389            matcher.matches::<_, FakeMatcherDeviceId>(
390                &FakeIpPacket::<Ipv4, _> {
391                    body: FakeTcpSegment {
392                        src_port: src,
393                        dst_port: dst,
394                        segment: SegmentHeader::arbitrary_value(),
395                        payload_len: 8888,
396                    },
397                    ..ArbitraryValue::arbitrary_value()
398                },
399                &Interfaces { ingress: None, egress: None },
400            ),
401            expect_match
402        );
403
404        // UDP
405        let matcher = PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
406            transport_protocol: Some(TransportProtocolMatcher {
407                proto: Ipv4Proto::Proto(IpProto::Udp),
408                src_port,
409                dst_port,
410            }),
411            ..Default::default()
412        };
413        let (src, dst) = transport_header;
414        assert_eq!(
415            matcher.matches::<_, FakeMatcherDeviceId>(
416                &FakeIpPacket::<Ipv4, _> {
417                    body: FakeUdpPacket { src_port: src, dst_port: dst },
418                    ..ArbitraryValue::arbitrary_value()
419                },
420                &Interfaces { ingress: None, egress: None },
421            ),
422            expect_match
423        );
424    }
425
426    #[ip_test(I)]
427    fn packet_must_match_all_provided_matchers<I: TestIpExt>() {
428        let matcher = PacketMatcher::<I, FakeBindingsCtx<I>> {
429            src_address: Some(AddressMatcher {
430                matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
431                invert: false,
432            }),
433            dst_address: Some(AddressMatcher {
434                matcher: AddressMatcherType::Subnet(SubnetMatcher(I::SUBNET)),
435                invert: false,
436            }),
437            ..Default::default()
438        };
439
440        assert_eq!(
441            matcher.matches::<_, FakeMatcherDeviceId>(
442                &FakeIpPacket::<_, FakeTcpSegment> {
443                    src_ip: I::IP_OUTSIDE_SUBNET,
444                    ..ArbitraryValue::arbitrary_value()
445                },
446                &Interfaces { ingress: None, egress: None },
447            ),
448            false
449        );
450        assert_eq!(
451            matcher.matches::<_, FakeMatcherDeviceId>(
452                &FakeIpPacket::<_, FakeTcpSegment> {
453                    dst_ip: I::IP_OUTSIDE_SUBNET,
454                    ..ArbitraryValue::arbitrary_value()
455                },
456                &Interfaces { ingress: None, egress: None },
457            ),
458            false
459        );
460        assert_eq!(
461            matcher.matches::<_, FakeMatcherDeviceId>(
462                &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
463                &Interfaces { ingress: None, egress: None },
464            ),
465            true
466        );
467    }
468
469    #[test]
470    fn match_by_default_if_no_specified_matchers() {
471        assert_eq!(
472            PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>>::default()
473                .matches::<_, FakeMatcherDeviceId>(
474                    &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
475                    &Interfaces { ingress: None, egress: None },
476                ),
477            true
478        );
479    }
480
481    #[test_case(true; "yes")]
482    #[test_case(false; "no")]
483    fn match_external_matcher(result: bool) {
484        let external_matcher = FakeBindingsPacketMatcher::new(result);
485        assert_eq!(
486            PacketMatcher::<Ipv4, FakeBindingsCtx<Ipv4>> {
487                external_matcher: Some(external_matcher.clone()),
488                ..Default::default()
489            }
490            .matches::<_, FakeMatcherDeviceId>(
491                &FakeIpPacket::<Ipv4, FakeTcpSegment>::arbitrary_value(),
492                &Interfaces { ingress: None, egress: None },
493            ),
494            result
495        );
496        assert_eq!(external_matcher.num_calls(), 1);
497    }
498}