diff --git a/aya/src/lib.rs b/aya/src/lib.rs index ece5ebc91..4a066cae5 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -92,4 +92,7 @@ pub use bpf::*; pub use obj::btf::{Btf, BtfError}; pub use object::Endianness; #[doc(hidden)] -pub use sys::netlink_set_link_up; +pub use sys::{ + netlink_add_ip_addr, netlink_add_veth_pair, netlink_delete_link, netlink_set_link_down, + netlink_set_link_up, +}; diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index fddab712f..7d8e4682b 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -15,9 +15,12 @@ pub(crate) use bpf::*; #[cfg(test)] pub(crate) use fake::*; use libc::{pid_t, SYS_bpf, SYS_perf_event_open}; -#[doc(hidden)] -pub use netlink::netlink_set_link_up; pub(crate) use netlink::*; +#[doc(hidden)] +pub use netlink::{ + netlink_add_ip_addr, netlink_add_veth_pair, netlink_delete_link, netlink_set_link_down, + netlink_set_link_up, +}; pub(crate) use perf_event::*; use thiserror::Error; diff --git a/aya/src/sys/netlink.rs b/aya/src/sys/netlink.rs index f10c2839b..25254dd38 100644 --- a/aya/src/sys/netlink.rs +++ b/aya/src/sys/netlink.rs @@ -2,24 +2,28 @@ use std::{ collections::HashMap, ffi::CStr, io, mem, + net::Ipv4Addr, os::fd::{AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd}, ptr, slice, }; use libc::{ - getsockname, nlattr, nlmsgerr, nlmsghdr, recv, send, setsockopt, sockaddr_nl, socket, - AF_NETLINK, AF_UNSPEC, ETH_P_ALL, IFF_UP, IFLA_XDP, NETLINK_EXT_ACK, NETLINK_ROUTE, + getsockname, nlattr, nlmsgerr, nlmsghdr, recv, send, setsockopt, sockaddr_nl, socket, AF_INET, + AF_NETLINK, AF_UNSPEC, ETH_P_ALL, IFA_F_PERMANENT, IFA_LOCAL, IFF_UP, IFLA_IFNAME, + IFLA_INFO_DATA, IFLA_INFO_KIND, IFLA_LINKINFO, IFLA_XDP, NETLINK_EXT_ACK, NETLINK_ROUTE, NLA_ALIGNTO, NLA_F_NESTED, NLA_TYPE_MASK, NLMSG_DONE, NLMSG_ERROR, NLM_F_ACK, NLM_F_CREATE, - NLM_F_DUMP, NLM_F_ECHO, NLM_F_EXCL, NLM_F_MULTI, NLM_F_REQUEST, RTM_DELTFILTER, RTM_GETTFILTER, - RTM_NEWQDISC, RTM_NEWTFILTER, RTM_SETLINK, SOCK_RAW, SOL_NETLINK, + NLM_F_DUMP, NLM_F_ECHO, NLM_F_EXCL, NLM_F_MULTI, NLM_F_REQUEST, RTM_DELLINK, RTM_DELTFILTER, + RTM_GETTFILTER, RTM_NEWADDR, RTM_NEWLINK, RTM_NEWQDISC, RTM_NEWTFILTER, RTM_SETLINK, + RT_SCOPE_UNIVERSE, SOCK_RAW, SOL_NETLINK, }; use thiserror::Error; use crate::{ generated::{ - ifinfomsg, tcmsg, IFLA_XDP_EXPECTED_FD, IFLA_XDP_FD, IFLA_XDP_FLAGS, NLMSG_ALIGNTO, - TCA_BPF_FD, TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT, TCA_BPF_NAME, TCA_KIND, TCA_OPTIONS, - TC_H_CLSACT, TC_H_INGRESS, TC_H_MAJ_MASK, TC_H_UNSPEC, XDP_FLAGS_REPLACE, + ifaddrmsg, ifinfomsg, tcmsg, IFLA_XDP_EXPECTED_FD, IFLA_XDP_FD, IFLA_XDP_FLAGS, + NLMSG_ALIGNTO, TCA_BPF_FD, TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT, TCA_BPF_NAME, TCA_KIND, + TCA_OPTIONS, TC_H_CLSACT, TC_H_INGRESS, TC_H_MAJ_MASK, TC_H_UNSPEC, VETH_INFO_PEER, + XDP_FLAGS_REPLACE, }, programs::TcAttachType, util::tc_handler_make, @@ -266,6 +270,63 @@ pub(crate) unsafe fn netlink_find_filter_with_name( Ok(filter_info) } +#[doc(hidden)] +pub unsafe fn netlink_add_veth_pair(name1: &CStr, name2: &CStr) -> Result<(), io::Error> { + let sock = NetlinkSocket::open()?; + + // Safety: Request is POD so this is safe + let mut req = mem::zeroed::(); + + let nlmsg_len = mem::size_of::() + mem::size_of::(); + req.header = nlmsghdr { + nlmsg_len: nlmsg_len as u32, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE) as u16, + nlmsg_type: RTM_NEWLINK, + nlmsg_pid: 0, + nlmsg_seq: 1, + }; + req.if_info.ifi_family = AF_UNSPEC as u8; + req.if_info.ifi_index = 0; + req.if_info.ifi_flags = 0; + req.if_info.ifi_change = 0; + + let attrs_buf = request_attributes(&mut req, nlmsg_len); + + // add IFLA_IFNAME + let ifname_len = write_attr_bytes(attrs_buf, 0, IFLA_IFNAME, name1.to_bytes_with_nul())?; + + // add IFLA_LINKINFO which includes IFLA_INFO_KIND and IFLA_INFO_DATA + let mut linkinfo = NestedAttrs::new(&mut attrs_buf[ifname_len..], IFLA_LINKINFO); + linkinfo.write_attr_bytes(IFLA_INFO_KIND, b"veth")?; + + linkinfo.write_nested_attrs(IFLA_INFO_DATA, |info_data| { + info_data.write_nested_attrs(VETH_INFO_PEER as u16, |info_peer| { + // Safety: ifinfomsg is POD so this is safe + let mut peer_if_info = mem::zeroed::(); + peer_if_info.ifi_family = AF_UNSPEC as u8; + peer_if_info.ifi_index = 0; + peer_if_info.ifi_flags = 0; + peer_if_info.ifi_change = 0; + + info_peer.write_bytes(bytes_of(&peer_if_info))?; + + // add IFLA_IFNAME + info_peer.write_attr_bytes(IFLA_IFNAME, name2.to_bytes_with_nul())?; + + Ok(()) + })?; + Ok(()) + })?; + + let linkinfo_len = linkinfo.finish()?; + + req.header.nlmsg_len += align_to(ifname_len + linkinfo_len, NLA_ALIGNTO as usize) as u32; + sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; + sock.recv()?; + + Ok(()) +} + #[doc(hidden)] pub unsafe fn netlink_set_link_up(if_index: i32) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; @@ -292,11 +353,100 @@ pub unsafe fn netlink_set_link_up(if_index: i32) -> Result<(), io::Error> { Ok(()) } +#[doc(hidden)] +pub unsafe fn netlink_set_link_down(if_index: i32) -> Result<(), io::Error> { + let sock = NetlinkSocket::open()?; + + // Safety: Request is POD so this is safe + let mut req = mem::zeroed::(); + + let nlmsg_len = mem::size_of::() + mem::size_of::(); + req.header = nlmsghdr { + nlmsg_len: nlmsg_len as u32, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16, + nlmsg_type: RTM_SETLINK, + nlmsg_pid: 0, + nlmsg_seq: 1, + }; + req.if_info.ifi_family = AF_UNSPEC as u8; + req.if_info.ifi_index = if_index; + req.if_info.ifi_flags = 0; + req.if_info.ifi_change = IFF_UP as u32; + + sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; + sock.recv()?; + + Ok(()) +} + +#[doc(hidden)] +pub unsafe fn netlink_delete_link(if_index: i32) -> Result<(), io::Error> { + let sock = NetlinkSocket::open()?; + + // Safety: Request is POD so this is safe + let mut req = mem::zeroed::(); + + let nlmsg_len = mem::size_of::() + mem::size_of::(); + req.header = nlmsghdr { + nlmsg_len: nlmsg_len as u32, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16, + nlmsg_type: RTM_DELLINK, + nlmsg_pid: 0, + nlmsg_seq: 1, + }; + req.if_info.ifi_family = AF_UNSPEC as u8; + req.if_info.ifi_index = if_index; + req.if_info.ifi_flags = 0; + req.if_info.ifi_change = 0; + + sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; + sock.recv()?; + + Ok(()) +} + +#[doc(hidden)] +pub unsafe fn netlink_add_ip_addr( + if_index: u32, + ipv4_addr: Ipv4Addr, + ipv4_prefix: u8, +) -> Result<(), io::Error> { + let sock = NetlinkSocket::open()?; + + // Safety: AddrRequest is POD so this is safe + let mut req = mem::zeroed::(); + + let nlmsg_len = mem::size_of::() + mem::size_of::(); + req.header = nlmsghdr { + nlmsg_len: nlmsg_len as u32, + nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE) as u16, + nlmsg_type: RTM_NEWADDR, + nlmsg_pid: 0, + nlmsg_seq: 1, + }; + req.if_addr.ifa_family = AF_INET as u8; + req.if_addr.ifa_prefixlen = ipv4_prefix; + req.if_addr.ifa_flags = IFA_F_PERMANENT as u8; + req.if_addr.ifa_scope = RT_SCOPE_UNIVERSE; + req.if_addr.ifa_index = if_index; + + let attrs_buf = request_attributes(&mut req, nlmsg_len); + + // add IFA_LOCAL + let local_len = write_attr_bytes(attrs_buf, 0, IFA_LOCAL, &ipv4_addr.octets())?; + + req.header.nlmsg_len += align_to(local_len, NLA_ALIGNTO as usize) as u32; + sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; + sock.recv()?; + + Ok(()) +} + #[repr(C)] struct Request { header: nlmsghdr, if_info: ifinfomsg, - attrs: [u8; 64], + attrs: [u8; 128], } #[repr(C)] @@ -306,6 +456,13 @@ struct TcRequest { attrs: [u8; 64], } +#[repr(C)] +struct AddrRequest { + header: nlmsghdr, + if_addr: ifaddrmsg, + attrs: [u8; 64], +} + struct NetlinkSocket { sock: OwnedFd, _nl_pid: u32, @@ -505,6 +662,23 @@ impl<'a> NestedAttrs<'a> { Ok(size) } + fn write_bytes(&mut self, value: &[u8]) -> Result { + let size = write_bytes(self.buf, self.offset, value)?; + self.offset += size; + Ok(size) + } + + fn write_nested_attrs(&mut self, attr_type: u16, f: F) -> Result + where + F: FnOnce(&mut NestedAttrs<'_>) -> Result<(), io::Error>, + { + let mut nested_attrs = NestedAttrs::new(&mut self.buf[self.offset..], attr_type); + f(&mut nested_attrs)?; + let size = nested_attrs.finish()?; + self.offset += size; + Ok(size) + } + fn finish(self) -> Result { let nla_len = self.offset; let attr = nlattr {