rtnetlink/traffic_control/
add_filter.rs

1// SPDX-License-Identifier: MIT
2
3use futures::stream::StreamExt;
4
5use crate::{
6    packet::{
7        tc::{self, constants::*},
8        NetlinkMessage,
9        RtnlMessage,
10        TcMessage,
11        NLM_F_ACK,
12        NLM_F_REQUEST,
13        TCM_IFINDEX_MAGIC_BLOCK,
14        TC_H_MAKE,
15    },
16    try_nl,
17    Error,
18    Handle,
19};
20
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(RtnlMessage::NewTrafficFilter(message));
45        req.header.flags = NLM_F_ACK | flags;
46
47        let mut response = handle.request(req)?;
48        while let Some(message) = response.next().await {
49            try_nl!(message);
50        }
51        Ok(())
52    }
53
54    /// Set interface index.
55    /// Equivalent to `dev STRING`, dev and block are mutually exlusive.
56    pub fn index(mut self, index: i32) -> Self {
57        assert_eq!(self.message.header.index, 0);
58        self.message.header.index = index;
59        self
60    }
61
62    /// Set block index.
63    /// Equivalent to `block BLOCK_INDEX`.
64    pub fn block(mut self, block_index: u32) -> Self {
65        assert_eq!(self.message.header.index, 0);
66        self.message.header.index = TCM_IFINDEX_MAGIC_BLOCK as i32;
67        self.message.header.parent = block_index;
68        self
69    }
70
71    /// Set parent.
72    /// Equivalent to `[ root | ingress | egress | parent CLASSID ]`
73    /// command args. They are mutually exlusive.
74    pub fn parent(mut self, parent: u32) -> Self {
75        assert_eq!(self.message.header.parent, TC_H_UNSPEC);
76        self.message.header.parent = parent;
77        self
78    }
79
80    /// Set parent to root.
81    pub fn root(mut self) -> Self {
82        assert_eq!(self.message.header.parent, TC_H_UNSPEC);
83        self.message.header.parent = TC_H_ROOT;
84        self
85    }
86
87    /// Set parent to ingress.
88    pub fn ingress(mut self) -> Self {
89        assert_eq!(self.message.header.parent, TC_H_UNSPEC);
90        self.message.header.parent = TC_H_MAKE!(TC_H_CLSACT, TC_H_MIN_INGRESS);
91        self
92    }
93
94    /// Set parent to egress.
95    pub fn egress(mut self) -> Self {
96        assert_eq!(self.message.header.parent, TC_H_UNSPEC);
97        self.message.header.parent = TC_H_MAKE!(TC_H_CLSACT, TC_H_MIN_EGRESS);
98        self
99    }
100
101    /// Set priority.
102    /// Equivalent to `priority PRIO` or `pref PRIO`.
103    pub fn priority(mut self, priority: u16) -> Self {
104        assert_eq!(self.message.header.info & TC_H_MAJ_MASK, 0);
105        self.message.header.info = TC_H_MAKE!((priority as u32) << 16, self.message.header.info);
106        self
107    }
108
109    /// Set protocol.
110    /// Equivalent to `protocol PROT`.
111    /// Default: ETH_P_ALL 0x0003, see llproto_names at iproute2/lib/ll_proto.c.
112    pub fn protocol(mut self, protocol: u16) -> Self {
113        assert_eq!(self.message.header.info & TC_H_MIN_MASK, 0);
114        self.message.header.info = TC_H_MAKE!(self.message.header.info, protocol as u32);
115        self
116    }
117
118    /// The 32bit filter allows to match arbitrary bitfields in the packet.
119    /// Equivalent to `tc filter ... u32`.
120    pub fn u32(mut self, data: Vec<tc::u32::Nla>) -> Self {
121        assert!(!self
122            .message
123            .nlas
124            .iter()
125            .any(|nla| matches!(nla, tc::Nla::Kind(_))));
126        self.message
127            .nlas
128            .push(tc::Nla::Kind(tc::u32::KIND.to_string()));
129        self.message.nlas.push(tc::Nla::Options(
130            data.into_iter().map(tc::TcOpt::U32).collect(),
131        ));
132        self
133    }
134
135    /// Use u32 to implement traffic redirect.
136    /// Equivalent to
137    /// `tc filter add [dev source] [parent ffff:] [protocol all] u32 match u8 0 0 action mirred egress redirect dev dest`
138    /// You need to set the `parent` and `protocol` before call redirect.
139    pub fn redirect(self, dst_index: u32) -> Self {
140        assert_eq!(self.message.nlas.len(), 0);
141        let u32_nla = vec![
142            tc::u32::Nla::Sel(tc::u32::Sel {
143                flags: TC_U32_TERMINAL,
144                nkeys: 1,
145                keys: vec![tc::u32::Key::default()],
146                ..tc::u32::Sel::default()
147            }),
148            tc::u32::Nla::Act(vec![tc::Action {
149                tab: TCA_ACT_TAB,
150                nlas: vec![
151                    tc::ActNla::Kind(tc::mirred::KIND.to_string()),
152                    tc::ActNla::Options(vec![tc::ActOpt::Mirred(tc::mirred::Nla::Parms(
153                        tc::mirred::TcMirred {
154                            action: TC_ACT_STOLEN,
155                            eaction: TCA_EGRESS_REDIR,
156                            ifindex: dst_index,
157                            ..tc::mirred::TcMirred::default()
158                        },
159                    ))]),
160                ],
161            }]),
162        ];
163        self.u32(u32_nla)
164    }
165}
166
167#[cfg(test)]
168mod test {
169    use std::{fs::File, os::unix::io::AsRawFd, path::Path};
170
171    use futures::stream::TryStreamExt;
172    use nix::sched::{setns, CloneFlags};
173    use tokio::runtime::Runtime;
174
175    use super::*;
176    use crate::{new_connection, packet::LinkMessage, NetworkNamespace, NETNS_PATH, SELF_NS_PATH};
177
178    const TEST_NS: &str = "netlink_test_filter_ns";
179    const TEST_VETH_1: &str = "test_veth_1";
180    const TEST_VETH_2: &str = "test_veth_2";
181
182    struct Netns {
183        path: String,
184        _cur: File,
185        last: File,
186    }
187
188    impl Netns {
189        async fn new(path: &str) -> Self {
190            // record current ns
191            let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
192
193            // create new ns
194            NetworkNamespace::add(path.to_string()).await.unwrap();
195
196            // entry new ns
197            let ns_path = Path::new(NETNS_PATH);
198            let file = File::open(ns_path.join(path)).unwrap();
199            setns(file.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap();
200
201            Self {
202                path: path.to_string(),
203                _cur: file,
204                last,
205            }
206        }
207    }
208    impl Drop for Netns {
209        fn drop(&mut self) {
210            println!("exit ns: {}", self.path);
211            setns(self.last.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap();
212
213            let ns_path = Path::new(NETNS_PATH).join(&self.path);
214            nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH).unwrap();
215            nix::unistd::unlink(&ns_path).unwrap();
216            // _cur File will be closed auto
217            // Since there is no async drop, NetworkNamespace::del cannot be called
218            // here. Dummy interface will be deleted automatically after netns is
219            // deleted.
220        }
221    }
222
223    async fn setup_env() -> (Handle, LinkMessage, LinkMessage, Netns) {
224        let netns = Netns::new(TEST_NS).await;
225
226        // Notice: The Handle can only be created after the setns, so that the
227        // Handle is the connection within the new ns.
228        let (connection, handle, _) = new_connection().unwrap();
229        tokio::spawn(connection);
230        handle
231            .link()
232            .add()
233            .veth(TEST_VETH_1.to_string(), TEST_VETH_2.to_string())
234            .execute()
235            .await
236            .unwrap();
237
238        let mut links = handle
239            .link()
240            .get()
241            .match_name(TEST_VETH_1.to_string())
242            .execute();
243        let link1 = links.try_next().await.unwrap();
244        links = handle
245            .link()
246            .get()
247            .match_name(TEST_VETH_2.to_string())
248            .execute();
249        let link2 = links.try_next().await.unwrap();
250        (handle, link1.unwrap(), link2.unwrap(), netns)
251    }
252
253    async fn test_async_new_filter() {
254        let (handle, test1, test2, _netns) = setup_env().await;
255        handle
256            .qdisc()
257            .add(test1.header.index as i32)
258            .ingress()
259            .execute()
260            .await
261            .unwrap();
262
263        handle
264            .qdisc()
265            .add(test2.header.index as i32)
266            .ingress()
267            .execute()
268            .await
269            .unwrap();
270
271        handle
272            .traffic_filter(test1.header.index as i32)
273            .add()
274            .parent(0xffff0000)
275            .protocol(0x0003)
276            .redirect(test2.header.index)
277            .execute()
278            .await
279            .unwrap();
280
281        let mut filters_iter = handle
282            .traffic_filter(test1.header.index as i32)
283            .get()
284            .root()
285            .execute();
286
287        let mut found = false;
288        while let Some(nl_msg) = filters_iter.try_next().await.unwrap() {
289            //filters.push(nl_msg.clone());
290            if nl_msg.header.handle == 0x80000800 {
291                let mut iter = nl_msg.nlas.iter();
292                assert_eq!(
293                    iter.next().unwrap(),
294                    &tc::Nla::Kind(String::from(tc::u32::KIND))
295                );
296                assert!(matches!(iter.next().unwrap(), &tc::Nla::Chain(_)));
297                // TCA_OPTIONS
298                let nla = iter.next().unwrap();
299                let filter = if let tc::Nla::Options(f) = nla {
300                    f
301                } else {
302                    panic!("expect options nla");
303                };
304                let mut fi = filter.iter();
305                let fa = fi.next().unwrap();
306                let ua = if let tc::TcOpt::U32(u) = fa {
307                    u
308                } else {
309                    panic!("expect u32 nla");
310                };
311                // TCA_U32_SEL
312                let sel = if let tc::u32::Nla::Sel(s) = ua {
313                    s
314                } else {
315                    panic!("expect sel nla");
316                };
317                assert_eq!(sel.flags, TC_U32_TERMINAL);
318                assert_eq!(sel.nkeys, 1);
319                assert_eq!(sel.keys.len(), 1);
320                assert_eq!(sel.keys[0], tc::u32::Key::default());
321                found = true;
322                break;
323            }
324        }
325        if !found {
326            panic!("not found :{} filter.", test1.header.index);
327        }
328    }
329
330    #[test]
331    fn test_new_filter() {
332        Runtime::new().unwrap().block_on(test_async_new_filter());
333    }
334}