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 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/// A matcher for IP addresses.
19#[derive(Clone, Derivative)]
20#[derivative(Debug)]
21pub enum AddressMatcherType<A: IpAddress> {
22    /// A subnet that must contain the address.
23    #[derivative(Debug = "transparent")]
24    Subnet(SubnetMatcher<A>),
25    /// An inclusive range of IP addresses that must contain the address.
26    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/// A matcher for IP addresses.
39#[derive(Clone, Debug)]
40pub struct AddressMatcher<A: IpAddress> {
41    /// The type of the address matcher.
42    pub matcher: AddressMatcherType<A>,
43    /// Whether to check for an "inverse" or "negative" match (in which case,
44    /// if the matcher criteria do *not* apply, it *is* considered a match, and
45    /// vice versa).
46    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/// A matcher for transport-layer port numbers.
63#[derive(Clone, Debug)]
64pub struct PortMatcher {
65    /// The range of port numbers in which the tested port number must fall.
66    pub range: RangeInclusive<u16>,
67    /// Whether to check for an "inverse" or "negative" match (in which case,
68    /// if the matcher criteria do *not* apply, it *is* considered a match, and
69    /// vice versa).
70    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/// A matcher for transport-layer protocol or port numbers.
81#[derive(Debug, Clone)]
82pub struct TransportProtocolMatcher<P> {
83    /// The transport-layer protocol.
84    pub proto: P,
85    /// If set, the matcher for the source port or identifier of the transport
86    /// header.
87    pub src_port: Option<PortMatcher>,
88    /// If set, the matcher for the destination port or identifier of the
89    /// transport header.
90    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/// Top-level matcher for IP packets.
122#[derive(Derivative, Debug, Clone)]
123#[derivative(Default(bound = ""))]
124pub struct PacketMatcher<I: IpExt, DeviceClass> {
125    /// The interface on which the packet entered the stack.
126    ///
127    /// Only available in `INGRESS`, `LOCAL_INGRESS`, and `FORWARDING`.
128    pub in_interface: Option<InterfaceMatcher<DeviceClass>>,
129    /// The interface through which the packet exits the stack.
130    ///
131    /// Only available in `FORWARDING`, `LOCAL_EGRESS`, and `EGRESS`.
132    pub out_interface: Option<InterfaceMatcher<DeviceClass>>,
133    /// Matcher for the source IP address.
134    pub src_address: Option<AddressMatcher<I::Addr>>,
135    /// Matcher for the destination IP address.
136    pub dst_address: Option<AddressMatcher<I::Addr>>,
137    /// Matchers for the transport layer.
138    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        // If no fields are specified, match all traffic by default.
152        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, /* 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 { 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        // TCP
405        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        // UDP
431        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}