diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 6eeae543..cebbe4be 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -152,27 +152,54 @@ impl SchedClassifier { attach_type: TcAttachType, options: TcOptions, ) -> Result { - 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 { + 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 { + 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, diff --git a/aya/src/sys/netlink.rs b/aya/src/sys/netlink.rs index 96c4f01a..f10c2839 100644 --- a/aya/src/sys/netlink.rs +++ b/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::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); + // 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,