Created
August 7, 2020 17:24
-
-
Save aspsk/fc145db380aee3c2cd503c36c63b555e to your computer and use it in GitHub Desktop.
A simple C program illustrating how to create, load, and attach a BPF program using maps without the use of libbpf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* SPDX-License-Identifier: BSD-2-Clause | |
* | |
* How to compile and run: | |
* cc nolibbpf.c -o nolibbpf | |
* sudo strace -e bpf ./nolibbpf | |
* | |
* Then look at progs and maps using bpftool: | |
* sudo bpftool prog | |
* sudo bpftool map | |
*/ | |
#define _GNU_SOURCE | |
#include <linux/rtnetlink.h> | |
#include <linux/bpf.h> | |
#include <sys/socket.h> | |
#include <sys/syscall.h> | |
#include <stdbool.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <err.h> | |
static inline __u64 ptr_to_u64(const void *ptr) | |
{ | |
return (__u64) (unsigned long) ptr; | |
} | |
static int map_create(void) | |
{ | |
union bpf_attr attr; | |
memset(&attr, 0, sizeof(attr)); | |
attr.map_type = BPF_MAP_TYPE_ARRAY, | |
attr.key_size = sizeof(__u32), | |
attr.value_size = sizeof(__u64), | |
attr.max_entries = 8, | |
strncpy(attr.map_name, "woo", sizeof(attr.map_name)); | |
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); | |
} | |
static void dump_bpf_instructions(struct bpf_insn *i) | |
{ | |
__u8 *o = (__u8 *) &i->off; | |
__u8 *x = (__u8 *) &i->imm; | |
printf("%02x %01x%01x %02x %02x %02x %02x %02x %02x\n", | |
i->code, i->src_reg, i->dst_reg, o[0], o[1], x[0], x[1], x[2], x[3]); | |
} | |
static int prog_load(int map_fd) | |
{ | |
union bpf_attr attr; | |
char log_buf[40960]; | |
int ret; | |
struct bpf_insn insns[] = { | |
/* 85 00 00 00 08 00 00 00 call 8 */ | |
{ | |
.code = BPF_JMP | BPF_CALL, | |
.imm = 8, | |
}, | |
/* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */ | |
{ | |
.code = BPF_MEM | BPF_STX, | |
.off = -4, | |
.src_reg = BPF_REG_0, | |
.dst_reg = BPF_REG_10, | |
}, | |
/* bf a2 00 00 00 00 00 00 r2 = r10 */ | |
{ | |
.code = BPF_ALU64 | BPF_MOV | BPF_X, | |
.src_reg = BPF_REG_10, | |
.dst_reg = BPF_REG_2, | |
}, | |
/* 07 02 00 00 fc ff ff ff r2 += -4 */ | |
{ | |
.code = BPF_ALU64 | BPF_ADD | BPF_K, | |
.dst_reg = BPF_REG_2, | |
.imm = -4, | |
}, | |
/* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */ | |
{ | |
.code = BPF_LD | BPF_DW | BPF_IMM, | |
.src_reg = BPF_PSEUDO_MAP_FD, | |
.dst_reg = BPF_REG_1, | |
.imm = map_fd, | |
}, | |
{ }, /* placeholder */ | |
/* 85 00 00 00 01 00 00 00 call 1 */ | |
{ | |
.code = BPF_JMP | BPF_CALL, | |
.imm = 1, | |
}, | |
/* b7 01 00 00 00 00 00 00 r1 = 0 */ | |
{ | |
.code = BPF_ALU64 | BPF_MOV | BPF_K, | |
.dst_reg = BPF_REG_1, | |
.imm = 0, | |
}, | |
/* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */ | |
{ | |
.code = BPF_JMP | BPF_JEQ | BPF_K, | |
.off = 4, | |
.src_reg = BPF_REG_0, | |
.imm = 0, | |
}, | |
/* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */ | |
{ | |
.code = BPF_MEM | BPF_LDX, | |
.off = 0, | |
.src_reg = BPF_REG_0, | |
.dst_reg = BPF_REG_1, | |
}, | |
/* 07 01 00 00 01 00 00 00 r1 += 1 */ | |
{ | |
.code = BPF_ALU64 | BPF_ADD | BPF_K, | |
.dst_reg = BPF_REG_1, | |
.imm = 1, | |
}, | |
/* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */ | |
{ | |
.code = BPF_MEM | BPF_STX, | |
.src_reg = BPF_REG_1, | |
.dst_reg = BPF_REG_0, | |
}, | |
/* b7 01 00 00 02 00 00 00 r1 = 2 */ | |
{ | |
.code = BPF_ALU64 | BPF_MOV | BPF_K, | |
.dst_reg = BPF_REG_1, | |
.imm = 2, | |
}, | |
/* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */ | |
{ | |
.code = BPF_ALU64 | BPF_MOV | BPF_X, | |
.src_reg = BPF_REG_1, | |
.dst_reg = BPF_REG_0, | |
}, | |
/* 95 00 00 00 00 00 00 00 exit */ | |
{ | |
.code = BPF_JMP | BPF_EXIT | |
}, | |
}; | |
for (size_t i = 0; i < sizeof(insns)/sizeof(insns[0]); i++) | |
dump_bpf_instructions(&insns[i]); | |
memset(&attr, 0, sizeof(attr)); | |
attr.prog_type = BPF_PROG_TYPE_XDP; | |
attr.insns = ptr_to_u64(insns); | |
attr.insn_cnt = sizeof(insns)/sizeof(insns[0]); | |
attr.license = ptr_to_u64("GPL"); | |
/* enable feedback from the verifier: if anything goes wrong, one can se what */ | |
attr.log_buf = ptr_to_u64(log_buf); | |
attr.log_size = sizeof(log_buf); | |
attr.log_level = 2; | |
strncpy(attr.prog_name, "woo", sizeof(attr.prog_name)); | |
ret = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); | |
if (ret < 0) | |
err(1, "%s\n", log_buf); | |
return ret; | |
} | |
/* this function was initially copied from libbpf */ | |
int netlink_open(__u32 *nl_pid) | |
{ | |
struct sockaddr_nl sa; | |
socklen_t addrlen; | |
int one = 1, ret; | |
int sock; | |
memset(&sa, 0, sizeof(sa)); | |
sa.nl_family = AF_NETLINK; | |
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
if (sock < 0) | |
err(1, "socket"); | |
if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) | |
warnx("netlink error reporting not supported"); | |
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) | |
err(1, "bind"); | |
addrlen = sizeof(sa); | |
if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0) | |
err(1, "getsockname"); | |
*nl_pid = sa.nl_pid; | |
return sock; | |
} | |
/* based on bpf_netlink_recv from libbpf */ | |
static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq) | |
{ | |
bool multipart = true; | |
struct nlmsgerr *errm; | |
struct nlmsghdr *nh; | |
char buf[4096]; | |
int len, ret; | |
while (multipart) { | |
multipart = false; | |
len = recv(sock, buf, sizeof(buf), 0); | |
if (len < 0) | |
err(1, "recv"); | |
if (len == 0) | |
break; | |
for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); | |
nh = NLMSG_NEXT(nh, len)) { | |
if (nh->nlmsg_pid != nl_pid) | |
errx(1, "wrong pid"); | |
if (nh->nlmsg_seq != seq) | |
errx(1, "INVSEQ"); | |
if (nh->nlmsg_flags & NLM_F_MULTI) | |
multipart = true; | |
switch (nh->nlmsg_type) { | |
case NLMSG_ERROR: | |
errm = (struct nlmsgerr *)NLMSG_DATA(nh); | |
if (!errm->error) | |
continue; | |
ret = errm->error; | |
// libbpf_nla_dump_errormsg(nh); too many code to copy... | |
goto done; | |
case NLMSG_DONE: | |
return 0; | |
default: | |
break; | |
} | |
} | |
} | |
ret = 0; | |
done: | |
return ret; | |
} | |
/* based on __bpf_set_link_xdp_fd_replace from libbpf */ | |
static int xdp_attach(int ifindex, int prog_fd) | |
{ | |
int sock, seq = 0, ret; | |
struct nlattr *nla, *nla_xdp; | |
struct { | |
struct nlmsghdr nh; | |
struct ifinfomsg ifinfo; | |
char attrbuf[64]; | |
} req; | |
__u32 nl_pid = 0; | |
sock = netlink_open(&nl_pid); | |
if (sock < 0) | |
return sock; | |
memset(&req, 0, sizeof(req)); | |
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; | |
req.nh.nlmsg_type = RTM_SETLINK; | |
req.nh.nlmsg_pid = 0; | |
req.nh.nlmsg_seq = ++seq; | |
req.ifinfo.ifi_family = AF_UNSPEC; | |
req.ifinfo.ifi_index = ifindex; | |
/* started nested attribute for XDP */ | |
nla = (struct nlattr *)(((char *)&req) | |
+ NLMSG_ALIGN(req.nh.nlmsg_len)); | |
nla->nla_type = NLA_F_NESTED | IFLA_XDP; | |
nla->nla_len = NLA_HDRLEN; | |
/* add XDP fd */ | |
nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len); | |
nla_xdp->nla_type = IFLA_XDP_FD; | |
nla_xdp->nla_len = NLA_HDRLEN + sizeof(int); | |
memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd)); | |
nla->nla_len += nla_xdp->nla_len; | |
/* if user passed in any flags, add those too */ | |
__u32 flags = XDP_FLAGS_SKB_MODE; | |
nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len); | |
nla_xdp->nla_type = IFLA_XDP_FLAGS; | |
nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags); | |
memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags)); | |
nla->nla_len += nla_xdp->nla_len; | |
req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len); | |
if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) | |
err(1, "send"); | |
ret = bpf_netlink_recv(sock, nl_pid, seq); | |
cleanup: | |
close(sock); | |
return ret; | |
} | |
int main(void) | |
{ | |
int map_fd, prog_fd; | |
map_fd = map_create(); | |
if (map_fd < 0) | |
err(1, "bpf: BPF_MAP_CREATE"); | |
prog_fd = prog_load(map_fd); | |
if (prog_fd < 0) | |
err(1, "bpf: BPF_PROG_LOAD"); | |
xdp_attach(1, prog_fd); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment