Skip to content

Commit

Permalink
aya: Add netlink functions
Browse files Browse the repository at this point in the history
`netlink_add_veth_pair`, `netlink_delete_link`,
`netlink_set_link_down`, and `netlink_add_ip_addr` are used in setting
up and tearing down a veth pair for integration tests.
  • Loading branch information
kevinji committed Apr 2, 2024
1 parent b9f171d commit dd029bf
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 11 deletions.
5 changes: 4 additions & 1 deletion aya/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
7 changes: 5 additions & 2 deletions aya/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
190 changes: 182 additions & 8 deletions aya/src/sys/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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::<Request>();

let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifinfomsg>();
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::<ifinfomsg>();
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()?;
Expand All @@ -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::<Request>();

let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifinfomsg>();
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::<Request>();

let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifinfomsg>();
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::<AddrRequest>();

let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifaddrmsg>();
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(attrs_buf, 0, IFA_LOCAL, u32::from(ipv4_addr))?;

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)]
Expand All @@ -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,
Expand Down Expand Up @@ -505,6 +662,23 @@ impl<'a> NestedAttrs<'a> {
Ok(size)
}

fn write_bytes(&mut self, value: &[u8]) -> Result<usize, io::Error> {
let size = write_bytes(self.buf, self.offset, value)?;
self.offset += size;
Ok(size)
}

fn write_nested_attrs<F>(&mut self, attr_type: u16, f: F) -> Result<usize, io::Error>
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<usize, io::Error> {
let nla_len = self.offset;
let attr = nlattr {
Expand Down

0 comments on commit dd029bf

Please sign in to comment.