1use alloc::vec::Vec;
8use core::fmt::Debug;
9use core::ops::Deref as _;
10
11use net_types::ip::Ip;
12use netstack3_base::{
13 BoundInterfaceMatcher, InterfaceProperties, MarkMatchers, Marks, Matcher, MatcherBindingsTypes,
14 SubnetMatcher,
15};
16
17use crate::internal::routing::PacketOrigin;
18use crate::{IpRoutingBindingsTypes, RoutingTableId};
19
20pub struct RulesTable<I: Ip, D, BT: IpRoutingBindingsTypes + MatcherBindingsTypes> {
22 rules: Vec<Rule<I, D, BT>>,
24}
25
26impl<I: Ip, D, BT: IpRoutingBindingsTypes + MatcherBindingsTypes> RulesTable<I, D, BT> {
27 pub(crate) fn new(main_table_id: RoutingTableId<I, D, BT>) -> Self {
28 Self {
31 rules: alloc::vec![Rule {
32 matcher: RuleMatcher::match_all_packets(),
33 action: RuleAction::Lookup(main_table_id)
34 }],
35 }
36 }
37
38 pub(crate) fn iter(&self) -> impl Iterator<Item = &'_ Rule<I, D, BT>> {
39 self.rules.iter()
40 }
41
42 #[cfg(any(test, feature = "testutils"))]
44 pub fn rules_mut(&mut self) -> &mut Vec<Rule<I, D, BT>> {
45 &mut self.rules
46 }
47
48 pub fn replace(&mut self, new_rules: Vec<Rule<I, D, BT>>) {
50 self.rules = new_rules;
51 }
52}
53
54pub struct Rule<I: Ip, D, BT: IpRoutingBindingsTypes + MatcherBindingsTypes> {
56 pub matcher: RuleMatcher<I, BT::DeviceClass>,
58 pub action: RuleAction<RoutingTableId<I, D, BT>>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum RuleAction<Lookup> {
65 Unreachable,
67 Lookup(Lookup),
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum TrafficOriginMatcher<DeviceClass> {
78 Local {
81 bound_device_matcher: Option<BoundInterfaceMatcher<DeviceClass>>,
85 },
86 NonLocal,
88}
89
90impl<'a, I: Ip, D> Matcher<PacketOrigin<I, &'a D>> for SubnetMatcher<I::Addr> {
91 fn matches(&self, actual: &PacketOrigin<I, &'a D>) -> bool {
92 match actual {
93 PacketOrigin::Local { bound_address, bound_device: _ } => {
94 self.required_matches(bound_address.as_deref())
95 }
96 PacketOrigin::NonLocal { source_address, incoming_device: _ } => {
97 self.matches(source_address.deref())
98 }
99 }
100 }
101}
102
103impl<'a, DeviceClass, I: Ip, D: InterfaceProperties<DeviceClass>> Matcher<PacketOrigin<I, &'a D>>
104 for TrafficOriginMatcher<DeviceClass>
105{
106 fn matches(&self, actual: &PacketOrigin<I, &'a D>) -> bool {
107 match (self, actual) {
108 (
109 TrafficOriginMatcher::Local { bound_device_matcher },
110 PacketOrigin::Local { bound_address: _, bound_device },
111 ) => bound_device_matcher.matches(bound_device),
112 (
113 TrafficOriginMatcher::NonLocal,
114 PacketOrigin::NonLocal { source_address: _, incoming_device: _ },
115 ) => true,
116 (TrafficOriginMatcher::Local { .. }, PacketOrigin::NonLocal { .. })
117 | (TrafficOriginMatcher::NonLocal, PacketOrigin::Local { .. }) => false,
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct RuleMatcher<I: Ip, DeviceClass> {
127 pub source_address_matcher: Option<SubnetMatcher<I::Addr>>,
133 pub traffic_origin_matcher: Option<TrafficOriginMatcher<DeviceClass>>,
136 pub mark_matchers: MarkMatchers,
138}
139
140impl<I: Ip, DeviceClass> RuleMatcher<I, DeviceClass> {
141 pub fn match_all_packets() -> Self {
143 RuleMatcher {
144 source_address_matcher: None,
145 traffic_origin_matcher: None,
146 mark_matchers: MarkMatchers::default(),
147 }
148 }
149}
150
151pub struct RuleInput<'a, I: Ip, D> {
153 pub(crate) packet_origin: PacketOrigin<I, &'a D>,
154 pub(crate) marks: &'a Marks,
155}
156
157impl<'a, I: Ip, D: InterfaceProperties<DeviceClass>, DeviceClass> Matcher<RuleInput<'a, I, D>>
158 for RuleMatcher<I, DeviceClass>
159{
160 fn matches(&self, actual: &RuleInput<'a, I, D>) -> bool {
161 let Self { source_address_matcher, traffic_origin_matcher, mark_matchers } = self;
162 let RuleInput { packet_origin, marks } = actual;
163 source_address_matcher.matches(packet_origin)
164 && traffic_origin_matcher.matches(packet_origin)
165 && mark_matchers.matches(marks)
166 }
167}
168
169#[cfg(test)]
170mod test {
171 use ip_test_macro::ip_test;
172 use net_types::SpecifiedAddr;
173 use net_types::ip::Subnet;
174 use netstack3_base::InterfaceMatcher;
175 use netstack3_base::testutil::{FakeDeviceId, MultipleDevicesId, TestIpExt};
176 use test_case::test_case;
177
178 use super::*;
179
180 #[ip_test(I)]
181 #[test_case(None, None => true)]
182 #[test_case(None, Some(MultipleDevicesId::A) => true)]
183 #[test_case(
184 Some(BoundInterfaceMatcher::Unbound),
185 None => true)]
186 #[test_case(
187 Some(BoundInterfaceMatcher::Unbound),
188 Some(MultipleDevicesId::A) => false)]
189 #[test_case(
190 Some(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name("A".into()))),
191 None => false)]
192 #[test_case(
193 Some(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name("A".into()))),
194 Some(MultipleDevicesId::A) => true)]
195 #[test_case(
196 Some(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name("A".into()))),
197 Some(MultipleDevicesId::B) => false)]
198 fn rule_matcher_matches_bound_device<I: TestIpExt>(
199 bound_device_matcher: Option<BoundInterfaceMatcher<()>>,
200 bound_device: Option<MultipleDevicesId>,
201 ) -> bool {
202 let matcher = RuleMatcher::<I, ()> {
203 traffic_origin_matcher: Some(TrafficOriginMatcher::Local { bound_device_matcher }),
204 ..RuleMatcher::match_all_packets()
205 };
206 let input = RuleInput {
207 packet_origin: PacketOrigin::Local {
208 bound_address: None,
209 bound_device: bound_device.as_ref(),
210 },
211 marks: &Default::default(),
212 };
213 matcher.matches(&input)
214 }
215
216 #[ip_test(I)]
217 #[test_case(None, None => true)]
218 #[test_case(None, Some(I::LOOPBACK_ADDRESS) => true)]
219 #[test_case(
220 Some(<I as TestIpExt>::TEST_ADDRS.subnet),
221 None => false)]
222 #[test_case(
223 Some(<I as TestIpExt>::TEST_ADDRS.subnet),
224 Some(<I as TestIpExt>::TEST_ADDRS.local_ip) => true)]
225 #[test_case(
226 Some(<I as TestIpExt>::TEST_ADDRS.subnet),
227 Some(<I as TestIpExt>::get_other_remote_ip_address(1)) => false)]
228 fn rule_matcher_matches_local_addr<I: TestIpExt>(
229 source_address_subnet: Option<Subnet<I::Addr>>,
230 bound_address: Option<SpecifiedAddr<I::Addr>>,
231 ) -> bool {
232 let matcher = RuleMatcher::<I, ()> {
233 source_address_matcher: source_address_subnet.map(SubnetMatcher),
234 ..RuleMatcher::match_all_packets()
235 };
236 let marks = Default::default();
237 let input = RuleInput::<'_, _, FakeDeviceId> {
238 packet_origin: PacketOrigin::Local { bound_address, bound_device: None },
239 marks: &marks,
240 };
241 matcher.matches(&input)
242 }
243
244 #[ip_test(I)]
245 #[test_case(None, PacketOrigin::Local {
246 bound_address: None,
247 bound_device: None
248 } => true)]
249 #[test_case(None, PacketOrigin::NonLocal {
250 source_address: <I as TestIpExt>::TEST_ADDRS.remote_ip,
251 incoming_device: &FakeDeviceId
252 } => true)]
253 #[test_case(Some(TrafficOriginMatcher::Local {
254 bound_device_matcher: None
255 }), PacketOrigin::Local {
256 bound_address: None,
257 bound_device: None
258 } => true)]
259 #[test_case(Some(TrafficOriginMatcher::NonLocal),
260 PacketOrigin::NonLocal {
261 source_address: <I as TestIpExt>::TEST_ADDRS.remote_ip,
262 incoming_device: &FakeDeviceId
263 } => true)]
264 #[test_case(Some(TrafficOriginMatcher::Local { bound_device_matcher: None }),
265 PacketOrigin::NonLocal {
266 source_address: <I as TestIpExt>::TEST_ADDRS.remote_ip,
267 incoming_device: &FakeDeviceId
268 } => false)]
269 #[test_case(Some(TrafficOriginMatcher::NonLocal),
270 PacketOrigin::Local {
271 bound_address: None,
272 bound_device: None
273 } => false)]
274 fn rule_matcher_matches_locally_generated<I: TestIpExt>(
275 traffic_origin_matcher: Option<TrafficOriginMatcher<()>>,
276 packet_origin: PacketOrigin<I, &'static FakeDeviceId>,
277 ) -> bool {
278 let matcher =
279 RuleMatcher::<I, ()> { traffic_origin_matcher, ..RuleMatcher::match_all_packets() };
280 let marks = Default::default();
281 let input = RuleInput::<'_, _, FakeDeviceId> { packet_origin, marks: &marks };
282 matcher.matches(&input)
283 }
284
285 #[ip_test(I)]
286 #[test_case::test_matrix(
287 [
288 None,
289 Some(<I as TestIpExt>::TEST_ADDRS.local_ip),
290 Some(<I as TestIpExt>::get_other_remote_ip_address(1))
291 ],
292 [
293 None,
294 Some(&MultipleDevicesId::A),
295 Some(&MultipleDevicesId::B),
296 Some(&MultipleDevicesId::C),
297 ],
298 [true, false]
299 )]
300 fn rule_matcher_matches_multiple_conditions<I: TestIpExt>(
301 ip: Option<SpecifiedAddr<I::Addr>>,
302 device: Option<&'static MultipleDevicesId>,
303 locally_generated: bool,
304 ) {
305 let matcher = RuleMatcher::<I, ()> {
306 source_address_matcher: Some(SubnetMatcher(I::TEST_ADDRS.subnet)),
307 traffic_origin_matcher: Some(TrafficOriginMatcher::Local {
308 bound_device_matcher: Some(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name(
309 "A".into(),
310 ))),
311 }),
312 ..RuleMatcher::match_all_packets()
313 };
314
315 let packet_origin = if locally_generated {
316 PacketOrigin::Local { bound_address: ip, bound_device: device }
317 } else {
318 let (Some(source_address), Some(incoming_device)) = (ip, device) else {
319 return;
320 };
321 PacketOrigin::NonLocal { source_address, incoming_device }
322 };
323
324 let input = RuleInput { packet_origin, marks: &Default::default() };
325
326 if ip == Some(I::TEST_ADDRS.local_ip)
327 && (device == Some(&MultipleDevicesId::A))
328 && locally_generated
329 {
330 assert!(matcher.matches(&input))
331 } else {
332 assert!(!matcher.matches(&input))
333 }
334 }
335}