1use 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#[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).as_ref(),
54 ) && dst_port.required_matches(
55 transport_data.as_ref().map(TransportPacketData::dst_port).as_ref(),
56 )
57 }
58 }
59}
60
61pub trait BindingsPacketMatcher {
63 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#[derive(Derivative)]
81#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
82pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
83 pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
87 pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
91 pub src_address: Option<AddressMatcher<I::Addr>>,
93 pub dst_address: Option<AddressMatcher<I::Addr>>,
95 pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
97 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 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, false)]
221 #[test_case(AddressMatcherTestCase::Subnet, true)]
222 #[test_case(AddressMatcherTestCase::Range, false)]
223 #[test_case(AddressMatcherTestCase::Range, 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 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 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 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}