Browse Source

tc: add SchedClassifier::attach_to_link

Similar to Xdp::attach_to_link, can be used to replace/upgrade the
program attached to a link.
Alessandro Decina 1 year ago
parent
commit
2257cbeccb
2 changed files with 46 additions and 5 deletions
  1. 31 4
      aya/src/programs/tc.rs
  2. 15 1
      aya/src/sys/netlink.rs

+ 31 - 4
aya/src/programs/tc.rs

@@ -152,27 +152,54 @@ impl SchedClassifier {
         attach_type: TcAttachType,
         options: TcOptions,
     ) -> Result<SchedClassifierLinkId, ProgramError> {
-        let prog_fd = self.fd()?;
-        let prog_fd = prog_fd.as_fd();
         let if_index = ifindex_from_ifname(interface)
             .map_err(|io_error| TcError::NetlinkError { io_error })?;
+        self.do_attach(if_index as i32, attach_type, options, true)
+    }
+
+    /// Atomically replaces the program referenced by the provided link.
+    ///
+    /// Ownership of the link will transfer to this program.
+    pub fn attach_to_link(
+        &mut self,
+        link: SchedClassifierLink,
+    ) -> Result<SchedClassifierLinkId, ProgramError> {
+        let TcLink {
+            if_index,
+            attach_type,
+            priority,
+            handle,
+        } = link.into_inner();
+        self.do_attach(if_index, attach_type, TcOptions { priority, handle }, false)
+    }
+
+    fn do_attach(
+        &mut self,
+        if_index: i32,
+        attach_type: TcAttachType,
+        options: TcOptions,
+        create: bool,
+    ) -> Result<SchedClassifierLinkId, ProgramError> {
+        let prog_fd = self.fd()?;
+        let prog_fd = prog_fd.as_fd();
         let name = self.data.name.as_deref().unwrap_or_default();
         // TODO: avoid this unwrap by adding a new error variant.
         let name = CString::new(name).unwrap();
         let (priority, handle) = unsafe {
             netlink_qdisc_attach(
-                if_index as i32,
+                if_index,
                 &attach_type,
                 prog_fd,
                 &name,
                 options.priority,
                 options.handle,
+                create,
             )
         }
         .map_err(|io_error| TcError::NetlinkError { io_error })?;
 
         self.data.links.insert(SchedClassifierLink::new(TcLink {
-            if_index: if_index as i32,
+            if_index,
             attach_type,
             priority,
             handle,

+ 15 - 1
aya/src/sys/netlink.rs

@@ -117,14 +117,28 @@ pub(crate) unsafe fn netlink_qdisc_attach(
     prog_name: &CStr,
     priority: u16,
     handle: u32,
+    create: bool,
 ) -> Result<(u16, u32), io::Error> {
     let sock = NetlinkSocket::open()?;
     let mut req = mem::zeroed::<TcRequest>();
 
     let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<tcmsg>();
+    // When create=true, we're creating a new attachment so we must set NLM_F_CREATE. Then we also
+    // set NLM_F_EXCL so that attaching fails if there's already a program attached to the given
+    // handle.
+    //
+    // When create=false we're replacing an existing attachment so we must not set either flags.
+    //
+    // See https://github.com/torvalds/linux/blob/3a87498/net/sched/cls_api.c#L2304
+    let request_flags = if create {
+        NLM_F_CREATE | NLM_F_EXCL
+    } else {
+        // NLM_F_REPLACE exists, but seems unused by cls_bpf
+        0
+    };
     req.header = nlmsghdr {
         nlmsg_len: nlmsg_len as u32,
-        nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ECHO) as u16,
+        nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_ECHO | request_flags) as u16,
         nlmsg_type: RTM_NEWTFILTER,
         nlmsg_pid: 0,
         nlmsg_seq: 1,