rtnetlink/traffic_control/
add_filter.rs

1// SPDX-License-Identifier: MIT
2
3use futures_util::stream::StreamExt;
4
5use crate::{
6    packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST},
7    packet_route::{
8        tc::{
9            TcAction, TcActionAttribute, TcActionGeneric, TcActionMirror,
10            TcActionMirrorOption, TcActionOption, TcActionType, TcAttribute,
11            TcFilterU32, TcFilterU32Option, TcHandle, TcHeader, TcMessage,
12            TcMirror, TcMirrorActionType, TcOption, TcU32Key, TcU32Selector,
13            TcU32SelectorFlags,
14        },
15        RouteNetlinkMessage,
16    },
17    try_nl, Error, Handle,
18};
19
20#[derive(Debug, Clone)]
21pub struct TrafficFilterNewRequest {
22    handle: Handle,
23    message: TcMessage,
24    flags: u16,
25}
26
27impl TrafficFilterNewRequest {
28    pub(crate) fn new(handle: Handle, ifindex: i32, flags: u16) -> Self {
29        Self {
30            handle,
31            message: TcMessage::with_index(ifindex),
32            flags: NLM_F_REQUEST | flags,
33        }
34    }
35
36    /// Execute the request
37    pub async fn execute(self) -> Result<(), Error> {
38        let Self {
39            mut handle,
40            message,
41            flags,
42        } = self;
43
44        let mut req = NetlinkMessage::from(
45            RouteNetlinkMessage::NewTrafficFilter(message),
46        );
47        req.header.flags = NLM_F_ACK | flags;
48
49        let mut response = handle.request(req)?;
50        while let Some(message) = response.next().await {
51            try_nl!(message);
52        }
53        Ok(())
54    }
55
56    /// Set interface index.
57    /// Equivalent to `dev STRING`, dev and block are mutually exlusive.
58    pub fn index(mut self, index: i32) -> Self {
59        self.message.header.index = index;
60        self
61    }
62
63    /// Set block index.
64    /// Equivalent to `block BLOCK_INDEX`.
65    pub fn block(mut self, block_index: u32) -> Self {
66        self.message.header.index = TcHeader::TCM_IFINDEX_MAGIC_BLOCK as i32;
67        self.message.header.parent = block_index.into();
68        self
69    }
70
71    /// Set parent.
72    /// Equivalent to `[ root | ingress | egress | parent CLASSID ]`
73    /// command args. They are mutually exclusive.
74    pub fn parent(mut self, parent: u32) -> Self {
75        self.message.header.parent = parent.into();
76        self
77    }
78
79    /// Set parent to root.
80    pub fn root(mut self) -> Self {
81        self.message.header.parent = TcHandle::ROOT;
82        self
83    }
84
85    /// Set parent to ingress.
86    pub fn ingress(mut self) -> Self {
87        self.message.header.parent = TcHandle {
88            major: 0xffff,
89            minor: TcHandle::MIN_INGRESS,
90        };
91        self
92    }
93
94    /// Set parent to egress.
95    pub fn egress(mut self) -> Self {
96        self.message.header.parent = TcHandle {
97            major: 0xffff,
98            minor: TcHandle::MIN_EGRESS,
99        };
100        self
101    }
102
103    /// Set priority.
104    /// Equivalent to `priority PRIO` or `pref PRIO`.
105    pub fn priority(mut self, priority: u16) -> Self {
106        self.message.header.info = u32::from(TcHandle {
107            major: priority,
108            minor: priority,
109        });
110        self
111    }
112
113    /// Set protocol.
114    /// Equivalent to `protocol PROT`.
115    /// Default: ETH_P_ALL 0x0003, see llproto_names at iproute2/lib/ll_proto.c.
116    pub fn protocol(mut self, protocol: u16) -> Self {
117        self.message.header.info = u32::from(TcHandle {
118            major: (self.message.header.info >> 16) as u16,
119            minor: protocol,
120        });
121        self
122    }
123
124    /// The 32bit filter allows to match arbitrary bitfields in the packet.
125    /// Equivalent to `tc filter ... u32`.
126    pub fn u32(mut self, options: &[TcFilterU32Option]) -> Result<Self, Error> {
127        if self
128            .message
129            .attributes
130            .iter()
131            .any(|nla| matches!(nla, TcAttribute::Kind(_)))
132        {
133            return Err(Error::InvalidNla(
134                "message kind has already been set.".to_string(),
135            ));
136        }
137        self.message
138            .attributes
139            .push(TcAttribute::Kind(TcFilterU32::KIND.to_string()));
140        let mut nla_opts = Vec::new();
141        for opt in options {
142            nla_opts.push(TcOption::U32(opt.clone()));
143        }
144        self.message.attributes.push(TcAttribute::Options(nla_opts));
145        Ok(self)
146    }
147
148    /// Use u32 to implement traffic redirect.
149    /// Equivalent to
150    /// `tc filter add [dev source] [parent ffff:] [protocol all] \
151    ///     u32 match u8 0 0 action mirred egress redirect dev dest`
152    /// You need to set the `parent` and `protocol` before call redirect.
153    pub fn redirect(self, dst_index: u32) -> Result<Self, Error> {
154        let mut sel_na = TcU32Selector::default();
155        sel_na.flags = TcU32SelectorFlags::Terminal;
156        sel_na.nkeys = 1;
157        sel_na.keys = vec![TcU32Key::default()];
158        let mut tc_mirror_nla = TcMirror::default();
159        tc_mirror_nla.generic = TcActionGeneric::default();
160        tc_mirror_nla.generic.action = TcActionType::Stolen;
161        tc_mirror_nla.eaction = TcMirrorActionType::EgressRedir;
162        tc_mirror_nla.ifindex = dst_index;
163        let mut action_nla = TcAction::default();
164        action_nla.attributes = vec![
165            TcActionAttribute::Kind(TcActionMirror::KIND.to_string()),
166            TcActionAttribute::Options(vec![TcActionOption::Mirror(
167                TcActionMirrorOption::Parms(tc_mirror_nla),
168            )]),
169        ];
170        let u32_nla = vec![
171            TcFilterU32Option::Selector(sel_na),
172            TcFilterU32Option::Action(vec![action_nla]),
173        ];
174        self.u32(&u32_nla)
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use std::{fs::File, os::fd::AsFd, path::Path};
181
182    use futures_util::stream::TryStreamExt;
183    use nix::sched::{setns, CloneFlags};
184    use tokio::runtime::Runtime;
185
186    use crate::{
187        new_connection,
188        packet_route::{
189            link::LinkMessage,
190            tc::{
191                TcAttribute, TcFilterU32, TcFilterU32Option, TcOption,
192                TcU32Key, TcU32SelectorFlags,
193            },
194        },
195        Handle, LinkVeth, NetworkNamespace, NETNS_PATH, SELF_NS_PATH,
196    };
197
198    const TEST_NS: &str = "netlink_test_filter_ns";
199    const TEST_VETH_1: &str = "test_veth_1";
200    const TEST_VETH_2: &str = "test_veth_2";
201
202    struct Netns {
203        path: String,
204        _cur: File,
205        last: File,
206    }
207
208    impl Netns {
209        async fn new(path: &str) -> Self {
210            // record current ns
211            let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
212
213            // create new ns
214            NetworkNamespace::add(path.to_string()).await.unwrap();
215
216            // entry new ns
217            let ns_path = Path::new(NETNS_PATH);
218            let file = File::open(ns_path.join(path)).unwrap();
219            setns(file.as_fd(), CloneFlags::CLONE_NEWNET).unwrap();
220
221            Self {
222                path: path.to_string(),
223                _cur: file,
224                last,
225            }
226        }
227    }
228    impl Drop for Netns {
229        fn drop(&mut self) {
230            println!("exit ns: {}", self.path);
231            setns(self.last.as_fd(), CloneFlags::CLONE_NEWNET).unwrap();
232
233            let ns_path = Path::new(NETNS_PATH).join(&self.path);
234            nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH)
235                .unwrap();
236            nix::unistd::unlink(&ns_path).unwrap();
237            // _cur File will be closed auto
238            // Since there is no async drop, NetworkNamespace::del cannot be
239            // called here. Dummy interface will be deleted
240            // automatically after netns is deleted.
241        }
242    }
243
244    async fn setup_env() -> (Handle, LinkMessage, LinkMessage, Netns) {
245        let netns = Netns::new(TEST_NS).await;
246
247        // Notice: The Handle can only be created after the setns, so that the
248        // Handle is the connection within the new ns.
249        let (connection, handle, _) = new_connection().unwrap();
250        tokio::spawn(connection);
251        handle
252            .link()
253            .add(LinkVeth::new(TEST_VETH_1, TEST_VETH_2).build())
254            .execute()
255            .await
256            .unwrap();
257
258        let mut links = handle
259            .link()
260            .get()
261            .match_name(TEST_VETH_1.to_string())
262            .execute();
263        let link1 = links.try_next().await.unwrap();
264        links = handle
265            .link()
266            .get()
267            .match_name(TEST_VETH_2.to_string())
268            .execute();
269        let link2 = links.try_next().await.unwrap();
270        (handle, link1.unwrap(), link2.unwrap(), netns)
271    }
272
273    async fn test_async_new_filter() {
274        let (handle, test1, test2, _netns) = setup_env().await;
275        handle
276            .qdisc()
277            .add(test1.header.index as i32)
278            .ingress()
279            .execute()
280            .await
281            .unwrap();
282
283        handle
284            .qdisc()
285            .add(test2.header.index as i32)
286            .ingress()
287            .execute()
288            .await
289            .unwrap();
290
291        handle
292            .traffic_filter(test1.header.index as i32)
293            .add()
294            .parent(0xffff0000)
295            .protocol(0x0003)
296            .redirect(test2.header.index)
297            .unwrap()
298            .execute()
299            .await
300            .unwrap();
301
302        // Verify that attempting to set 2 redirects causes and error
303        assert!(handle
304            .traffic_filter(test1.header.index as i32)
305            .add()
306            .parent(0xffff0000)
307            .protocol(0x0003)
308            .redirect(test2.header.index)
309            .unwrap()
310            .redirect(test1.header.index)
311            .is_err());
312
313        let mut filters_iter = handle
314            .traffic_filter(test1.header.index as i32)
315            .get()
316            .root()
317            .execute();
318
319        let mut found = false;
320        while let Some(nl_msg) = filters_iter.try_next().await.unwrap() {
321            //filters.push(nl_msg.clone());
322            if nl_msg.header.handle == 0x80000800.into() {
323                let mut iter = nl_msg.attributes.iter();
324                assert_eq!(
325                    iter.next().unwrap(),
326                    &TcAttribute::Kind(TcFilterU32::KIND.to_string()),
327                );
328                assert!(matches!(iter.next().unwrap(), &TcAttribute::Chain(_)));
329                // TCA_OPTIONS
330                let nla = iter.next().unwrap();
331                let filter = if let TcAttribute::Options(f) = nla {
332                    f
333                } else {
334                    panic!("expect options nla");
335                };
336                let mut fi = filter.iter();
337                let fa = fi.next().unwrap();
338                let ua = if let TcOption::U32(u) = fa {
339                    u
340                } else {
341                    panic!("expect u32 nla");
342                };
343                // TCA_U32_SEL
344                let sel = if let TcFilterU32Option::Selector(s) = ua {
345                    s
346                } else {
347                    panic!("expect sel nla");
348                };
349                assert_eq!(sel.flags, TcU32SelectorFlags::Terminal);
350                assert_eq!(sel.nkeys, 1);
351                assert_eq!(sel.keys.len(), 1);
352                assert_eq!(sel.keys[0], TcU32Key::default());
353                found = true;
354                break;
355            }
356        }
357        if !found {
358            panic!("not found :{} filter.", test1.header.index);
359        }
360    }
361
362    #[test]
363    fn test_new_filter() {
364        Runtime::new().unwrap().block_on(test_async_new_filter());
365    }
366}