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, FilterIpPacket, MaybeTransportPacket, TransportPacketData};
19use crate::state::FilterPacketMetadata;
20
21#[derive(Debug, Clone)]
23pub struct TransportProtocolMatcher<P> {
24 pub proto: P,
26 pub src_port: Option<PortMatcher>,
29 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
62pub trait BindingsPacketMatcher<D> {
64 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#[derive(Derivative)]
97#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
98pub struct PacketMatcher<I: IpExt, BT: MatcherBindingsTypes> {
99 pub in_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
103 pub out_interface: Option<InterfaceMatcher<BT::DeviceClass>>,
107 pub src_address: Option<AddressMatcher<I::Addr>>,
109 pub dst_address: Option<AddressMatcher<I::Addr>>,
111 pub transport_protocol: Option<TransportProtocolMatcher<I::Proto>>,
113 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 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, false)]
253 #[test_case(AddressMatcherTestCase::Subnet, true)]
254 #[test_case(AddressMatcherTestCase::Range, false)]
255 #[test_case(AddressMatcherTestCase::Range, 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 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 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 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}