rtnetlink/traffic_control/
add_filter.rs

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