const std = @import("../../std.zig");
const errno = getErrno;
const unexpectedErrno = std.os.unexpectedErrno;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const expect = std.testing.expect;
const linux = std.os.linux;
const fd_t = linux.fd_t;
const pid_t = linux.pid_t;
const getErrno = linux.getErrno;
pub const btf = @import("bpf/btf.zig");
pub const kern = @import("bpf/kern.zig");
pub const LD = 0x00;
pub const LDX = 0x01;
pub const ST = 0x02;
pub const STX = 0x03;
pub const ALU = 0x04;
pub const JMP = 0x05;
pub const RET = 0x06;
pub const MISC = 0x07;
pub const W = 0x00;
pub const H = 0x08;
pub const B = 0x10;
pub const DW = 0x18;
pub const IMM = 0x00;
pub const ABS = 0x20;
pub const IND = 0x40;
pub const MEM = 0x60;
pub const LEN = 0x80;
pub const MSH = 0xa0;
pub const ADD = 0x00;
pub const SUB = 0x10;
pub const MUL = 0x20;
pub const DIV = 0x30;
pub const OR = 0x40;
pub const AND = 0x50;
pub const LSH = 0x60;
pub const RSH = 0x70;
pub const NEG = 0x80;
pub const MOD = 0x90;
pub const XOR = 0xa0;
pub const JA = 0x00;
pub const JEQ = 0x10;
pub const JGT = 0x20;
pub const JGE = 0x30;
pub const JSET = 0x40;
pub const K = 0x00;
pub const X = 0x08;
pub const MAXINSNS = 4096;
pub const JMP32 = 0x06;
pub const ALU64 = 0x07;
pub const XADD = 0xc0;
pub const MOV = 0xb0;
pub const ARSH = 0xc0;
pub const END = 0xd0;
pub const TO_LE = 0x00;
pub const TO_BE = 0x08;
pub const FROM_LE = TO_LE;
pub const FROM_BE = TO_BE;
pub const JNE = 0x50;
pub const JLT = 0xa0;
pub const JLE = 0xb0;
pub const JSGT = 0x60;
pub const JSGE = 0x70;
pub const JSLT = 0xc0;
pub const JSLE = 0xd0;
pub const CALL = 0x80;
pub const EXIT = 0x90;
pub const F_ALLOW_OVERRIDE = 0x1;
pub const F_ALLOW_MULTI = 0x2;
pub const F_REPLACE = 0x4;
pub const F_STRICT_ALIGNMENT = 0x1;
pub const F_ANY_ALIGNMENT = 0x2;
pub const F_TEST_RND_HI32 = 0x4;
pub const PSEUDO_MAP_FD = 1;
pub const PSEUDO_MAP_VALUE = 2;
pub const PSEUDO_CALL = 1;
pub const ANY = 0;
pub const NOEXIST = 1;
pub const EXIST = 2;
pub const F_LOCK = 4;
pub const BPF_F_NO_PREALLOC = 0x1;
pub const BPF_F_NO_COMMON_LRU = 0x2;
pub const BPF_F_NUMA_NODE = 0x4;
pub const BPF_F_RDONLY = 0x8;
pub const BPF_F_WRONLY = 0x10;
pub const BPF_F_STACK_BUILD_ID = 0x20;
pub const BPF_F_ZERO_SEED = 0x40;
pub const BPF_F_RDONLY_PROG = 0x80;
pub const BPF_F_WRONLY_PROG = 0x100;
pub const BPF_F_CLONE = 0x200;
pub const BPF_F_MMAPABLE = 0x400;
pub const Helper = enum(i32) {
unspec,
map_lookup_elem,
map_update_elem,
map_delete_elem,
probe_read,
ktime_get_ns,
trace_printk,
get_prandom_u32,
get_smp_processor_id,
skb_store_bytes,
l3_csum_replace,
l4_csum_replace,
tail_call,
clone_redirect,
get_current_pid_tgid,
get_current_uid_gid,
get_current_comm,
get_cgroup_classid,
skb_vlan_push,
skb_vlan_pop,
skb_get_tunnel_key,
skb_set_tunnel_key,
perf_event_read,
redirect,
get_route_realm,
perf_event_output,
skb_load_bytes,
get_stackid,
csum_diff,
skb_get_tunnel_opt,
skb_set_tunnel_opt,
skb_change_proto,
skb_change_type,
skb_under_cgroup,
get_hash_recalc,
get_current_task,
probe_write_user,
current_task_under_cgroup,
skb_change_tail,
skb_pull_data,
csum_update,
set_hash_invalid,
get_numa_node_id,
skb_change_head,
xdp_adjust_head,
probe_read_str,
get_socket_cookie,
get_socket_uid,
set_hash,
setsockopt,
skb_adjust_room,
redirect_map,
sk_redirect_map,
sock_map_update,
xdp_adjust_meta,
perf_event_read_value,
perf_prog_read_value,
getsockopt,
override_return,
sock_ops_cb_flags_set,
msg_redirect_map,
msg_apply_bytes,
msg_cork_bytes,
msg_pull_data,
bind,
xdp_adjust_tail,
skb_get_xfrm_state,
get_stack,
skb_load_bytes_relative,
fib_lookup,
sock_hash_update,
msg_redirect_hash,
sk_redirect_hash,
lwt_push_encap,
lwt_seg6_store_bytes,
lwt_seg6_adjust_srh,
lwt_seg6_action,
rc_repeat,
rc_keydown,
skb_cgroup_id,
get_current_cgroup_id,
get_local_storage,
sk_select_reuseport,
skb_ancestor_cgroup_id,
sk_lookup_tcp,
sk_lookup_udp,
sk_release,
map_push_elem,
map_pop_elem,
map_peek_elem,
msg_push_data,
msg_pop_data,
rc_pointer_rel,
spin_lock,
spin_unlock,
sk_fullsock,
tcp_sock,
skb_ecn_set_ce,
get_listener_sock,
skc_lookup_tcp,
tcp_check_syncookie,
sysctl_get_name,
sysctl_get_current_value,
sysctl_get_new_value,
sysctl_set_new_value,
strtol,
strtoul,
sk_storage_get,
sk_storage_delete,
send_signal,
tcp_gen_syncookie,
skb_output,
probe_read_user,
probe_read_kernel,
probe_read_user_str,
probe_read_kernel_str,
tcp_send_ack,
send_signal_thread,
jiffies64,
read_branch_records,
get_ns_current_pid_tgid,
xdp_output,
get_netns_cookie,
get_current_ancestor_cgroup_id,
sk_assign,
ktime_get_boot_ns,
seq_printf,
seq_write,
sk_cgroup_id,
sk_ancestor_cgroup_id,
ringbuf_output,
ringbuf_reserve,
ringbuf_submit,
ringbuf_discard,
ringbuf_query,
csum_level,
skc_to_tcp6_sock,
skc_to_tcp_sock,
skc_to_tcp_timewait_sock,
skc_to_tcp_request_sock,
skc_to_udp6_sock,
get_task_stack,
_,
};
pub const Insn = packed struct {
code: u8,
dst: u4,
src: u4,
off: i16,
imm: i32,
pub const Reg = enum(u4) { r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10 };
const Source = enum(u1) { reg, imm };
const Mode = enum(u8) {
imm = IMM,
abs = ABS,
ind = IND,
mem = MEM,
len = LEN,
msh = MSH,
};
pub const AluOp = enum(u8) {
add = ADD,
sub = SUB,
mul = MUL,
div = DIV,
alu_or = OR,
alu_and = AND,
lsh = LSH,
rsh = RSH,
neg = NEG,
mod = MOD,
xor = XOR,
mov = MOV,
arsh = ARSH,
};
pub const Size = enum(u8) {
byte = B,
half_word = H,
word = W,
double_word = DW,
};
pub const JmpOp = enum(u8) {
ja = JA,
jeq = JEQ,
jgt = JGT,
jge = JGE,
jset = JSET,
jlt = JLT,
jle = JLE,
jne = JNE,
jsgt = JSGT,
jsge = JSGE,
jslt = JSLT,
jsle = JSLE,
};
const ImmOrReg = union(Source) {
imm: i32,
reg: Reg,
};
fn imm_reg(code: u8, dst: Reg, src: anytype, off: i16) Insn {
const imm_or_reg = if (@TypeOf(src) == Reg or @typeInfo(@TypeOf(src)) == .EnumLiteral)
ImmOrReg{ .reg = @as(Reg, src) }
else
ImmOrReg{ .imm = src };
const src_type: u8 = switch (imm_or_reg) {
.imm => K,
.reg => X,
};
return Insn{
.code = code | src_type,
.dst = @enumToInt(dst),
.src = switch (imm_or_reg) {
.imm => 0,
.reg => |r| @enumToInt(r),
},
.off = off,
.imm = switch (imm_or_reg) {
.imm => |i| i,
.reg => 0,
},
};
}
pub fn alu(comptime width: comptime_int, op: AluOp, dst: Reg, src: anytype) Insn {
const width_bitfield = switch (width) {
32 => ALU,
64 => ALU64,
else => @compileError("width must be 32 or 64"),
};
return imm_reg(width_bitfield | @enumToInt(op), dst, src, 0);
}
pub fn mov(dst: Reg, src: anytype) Insn {
return alu(64, .mov, dst, src);
}
pub fn add(dst: Reg, src: anytype) Insn {
return alu(64, .add, dst, src);
}
pub fn sub(dst: Reg, src: anytype) Insn {
return alu(64, .sub, dst, src);
}
pub fn mul(dst: Reg, src: anytype) Insn {
return alu(64, .mul, dst, src);
}
pub fn div(dst: Reg, src: anytype) Insn {
return alu(64, .div, dst, src);
}
pub fn alu_or(dst: Reg, src: anytype) Insn {
return alu(64, .alu_or, dst, src);
}
pub fn alu_and(dst: Reg, src: anytype) Insn {
return alu(64, .alu_and, dst, src);
}
pub fn lsh(dst: Reg, src: anytype) Insn {
return alu(64, .lsh, dst, src);
}
pub fn rsh(dst: Reg, src: anytype) Insn {
return alu(64, .rsh, dst, src);
}
pub fn neg(dst: Reg) Insn {
return alu(64, .neg, dst, 0);
}
pub fn mod(dst: Reg, src: anytype) Insn {
return alu(64, .mod, dst, src);
}
pub fn xor(dst: Reg, src: anytype) Insn {
return alu(64, .xor, dst, src);
}
pub fn arsh(dst: Reg, src: anytype) Insn {
return alu(64, .arsh, dst, src);
}
pub fn jmp(op: JmpOp, dst: Reg, src: anytype, off: i16) Insn {
return imm_reg(JMP | @enumToInt(op), dst, src, off);
}
pub fn ja(off: i16) Insn {
return jmp(.ja, .r0, 0, off);
}
pub fn jeq(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jeq, dst, src, off);
}
pub fn jgt(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jgt, dst, src, off);
}
pub fn jge(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jge, dst, src, off);
}
pub fn jlt(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jlt, dst, src, off);
}
pub fn jle(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jle, dst, src, off);
}
pub fn jset(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jset, dst, src, off);
}
pub fn jne(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jne, dst, src, off);
}
pub fn jsgt(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jsgt, dst, src, off);
}
pub fn jsge(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jsge, dst, src, off);
}
pub fn jslt(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jslt, dst, src, off);
}
pub fn jsle(dst: Reg, src: anytype, off: i16) Insn {
return jmp(.jsle, dst, src, off);
}
pub fn xadd(dst: Reg, src: Reg) Insn {
return Insn{
.code = STX | XADD | DW,
.dst = @enumToInt(dst),
.src = @enumToInt(src),
.off = 0,
.imm = 0,
};
}
fn ld(mode: Mode, size: Size, dst: Reg, src: Reg, imm: i32) Insn {
return Insn{
.code = @enumToInt(mode) | @enumToInt(size) | LD,
.dst = @enumToInt(dst),
.src = @enumToInt(src),
.off = 0,
.imm = imm,
};
}
pub fn ld_abs(size: Size, dst: Reg, src: Reg, imm: i32) Insn {
return ld(.abs, size, dst, src, imm);
}
pub fn ld_ind(size: Size, dst: Reg, src: Reg, imm: i32) Insn {
return ld(.ind, size, dst, src, imm);
}
pub fn ldx(size: Size, dst: Reg, src: Reg, off: i16) Insn {
return Insn{
.code = MEM | @enumToInt(size) | LDX,
.dst = @enumToInt(dst),
.src = @enumToInt(src),
.off = off,
.imm = 0,
};
}
fn ld_imm_impl1(dst: Reg, src: Reg, imm: u64) Insn {
return Insn{
.code = LD | DW | IMM,
.dst = @enumToInt(dst),
.src = @enumToInt(src),
.off = 0,
.imm = @intCast(i32, @truncate(u32, imm)),
};
}
fn ld_imm_impl2(imm: u64) Insn {
return Insn{
.code = 0,
.dst = 0,
.src = 0,
.off = 0,
.imm = @intCast(i32, @truncate(u32, imm >> 32)),
};
}
pub fn ld_dw1(dst: Reg, imm: u64) Insn {
return ld_imm_impl1(dst, .r0, imm);
}
pub fn ld_dw2(imm: u64) Insn {
return ld_imm_impl2(imm);
}
pub fn ld_map_fd1(dst: Reg, map_fd: fd_t) Insn {
return ld_imm_impl1(dst, @intToEnum(Reg, PSEUDO_MAP_FD), @intCast(u64, map_fd));
}
pub fn ld_map_fd2(map_fd: fd_t) Insn {
return ld_imm_impl2(@intCast(u64, map_fd));
}
pub fn st(comptime size: Size, dst: Reg, off: i16, imm: i32) Insn {
return Insn{
.code = MEM | @enumToInt(size) | ST,
.dst = @enumToInt(dst),
.src = 0,
.off = off,
.imm = imm,
};
}
pub fn stx(size: Size, dst: Reg, off: i16, src: Reg) Insn {
return Insn{
.code = MEM | @enumToInt(size) | STX,
.dst = @enumToInt(dst),
.src = @enumToInt(src),
.off = off,
.imm = 0,
};
}
fn endian_swap(endian: std.builtin.Endian, comptime size: Size, dst: Reg) Insn {
return Insn{
.code = switch (endian) {
.Big => 0xdc,
.Little => 0xd4,
},
.dst = @enumToInt(dst),
.src = 0,
.off = 0,
.imm = switch (size) {
.byte => @compileError("can't swap a single byte"),
.half_word => 16,
.word => 32,
.double_word => 64,
},
};
}
pub fn le(comptime size: Size, dst: Reg) Insn {
return endian_swap(.Little, size, dst);
}
pub fn be(comptime size: Size, dst: Reg) Insn {
return endian_swap(.Big, size, dst);
}
pub fn call(helper: Helper) Insn {
return Insn{
.code = JMP | CALL,
.dst = 0,
.src = 0,
.off = 0,
.imm = @enumToInt(helper),
};
}
pub fn exit() Insn {
return Insn{
.code = JMP | EXIT,
.dst = 0,
.src = 0,
.off = 0,
.imm = 0,
};
}
};
test "insn bitsize" {
try expectEqual(@bitSizeOf(Insn), 64);
}
fn expect_opcode(code: u8, insn: Insn) !void {
try expectEqual(code, insn.code);
}
test "opcodes" {
try expect_opcode(0x07, Insn.add(.r1, 0));
try expect_opcode(0x0f, Insn.add(.r1, .r2));
try expect_opcode(0x17, Insn.sub(.r1, 0));
try expect_opcode(0x1f, Insn.sub(.r1, .r2));
try expect_opcode(0x27, Insn.mul(.r1, 0));
try expect_opcode(0x2f, Insn.mul(.r1, .r2));
try expect_opcode(0x37, Insn.div(.r1, 0));
try expect_opcode(0x3f, Insn.div(.r1, .r2));
try expect_opcode(0x47, Insn.alu_or(.r1, 0));
try expect_opcode(0x4f, Insn.alu_or(.r1, .r2));
try expect_opcode(0x57, Insn.alu_and(.r1, 0));
try expect_opcode(0x5f, Insn.alu_and(.r1, .r2));
try expect_opcode(0x67, Insn.lsh(.r1, 0));
try expect_opcode(0x6f, Insn.lsh(.r1, .r2));
try expect_opcode(0x77, Insn.rsh(.r1, 0));
try expect_opcode(0x7f, Insn.rsh(.r1, .r2));
try expect_opcode(0x87, Insn.neg(.r1));
try expect_opcode(0x97, Insn.mod(.r1, 0));
try expect_opcode(0x9f, Insn.mod(.r1, .r2));
try expect_opcode(0xa7, Insn.xor(.r1, 0));
try expect_opcode(0xaf, Insn.xor(.r1, .r2));
try expect_opcode(0xb7, Insn.mov(.r1, 0));
try expect_opcode(0xbf, Insn.mov(.r1, .r2));
try expect_opcode(0xc7, Insn.arsh(.r1, 0));
try expect_opcode(0xcf, Insn.arsh(.r1, .r2));
try expect_opcode(0xdb, Insn.xadd(.r1, .r2));
try expect_opcode(0xd4, Insn.le(.half_word, .r1));
try expectEqual(@intCast(i32, 16), Insn.le(.half_word, .r1).imm);
try expect_opcode(0xd4, Insn.le(.word, .r1));
try expectEqual(@intCast(i32, 32), Insn.le(.word, .r1).imm);
try expect_opcode(0xd4, Insn.le(.double_word, .r1));
try expectEqual(@intCast(i32, 64), Insn.le(.double_word, .r1).imm);
try expect_opcode(0xdc, Insn.be(.half_word, .r1));
try expectEqual(@intCast(i32, 16), Insn.be(.half_word, .r1).imm);
try expect_opcode(0xdc, Insn.be(.word, .r1));
try expectEqual(@intCast(i32, 32), Insn.be(.word, .r1).imm);
try expect_opcode(0xdc, Insn.be(.double_word, .r1));
try expectEqual(@intCast(i32, 64), Insn.be(.double_word, .r1).imm);
try expect_opcode(0x18, Insn.ld_dw1(.r1, 0));
try expect_opcode(0x00, Insn.ld_dw2(0));
try expect_opcode(0x18, Insn.ld_map_fd1(.r1, 0));
try expectEqual(@intCast(u4, PSEUDO_MAP_FD), Insn.ld_map_fd1(.r1, 0).src);
try expect_opcode(0x00, Insn.ld_map_fd2(0));
try expect_opcode(0x38, Insn.ld_abs(.double_word, .r1, .r2, 0));
try expect_opcode(0x20, Insn.ld_abs(.word, .r1, .r2, 0));
try expect_opcode(0x28, Insn.ld_abs(.half_word, .r1, .r2, 0));
try expect_opcode(0x30, Insn.ld_abs(.byte, .r1, .r2, 0));
try expect_opcode(0x58, Insn.ld_ind(.double_word, .r1, .r2, 0));
try expect_opcode(0x40, Insn.ld_ind(.word, .r1, .r2, 0));
try expect_opcode(0x48, Insn.ld_ind(.half_word, .r1, .r2, 0));
try expect_opcode(0x50, Insn.ld_ind(.byte, .r1, .r2, 0));
try expect_opcode(0x79, Insn.ldx(.double_word, .r1, .r2, 0));
try expect_opcode(0x61, Insn.ldx(.word, .r1, .r2, 0));
try expect_opcode(0x69, Insn.ldx(.half_word, .r1, .r2, 0));
try expect_opcode(0x71, Insn.ldx(.byte, .r1, .r2, 0));
try expect_opcode(0x62, Insn.st(.word, .r1, 0, 0));
try expect_opcode(0x6a, Insn.st(.half_word, .r1, 0, 0));
try expect_opcode(0x72, Insn.st(.byte, .r1, 0, 0));
try expect_opcode(0x63, Insn.stx(.word, .r1, 0, .r2));
try expect_opcode(0x6b, Insn.stx(.half_word, .r1, 0, .r2));
try expect_opcode(0x73, Insn.stx(.byte, .r1, 0, .r2));
try expect_opcode(0x7b, Insn.stx(.double_word, .r1, 0, .r2));
try expect_opcode(0x05, Insn.ja(0));
try expect_opcode(0x15, Insn.jeq(.r1, 0, 0));
try expect_opcode(0x1d, Insn.jeq(.r1, .r2, 0));
try expect_opcode(0x25, Insn.jgt(.r1, 0, 0));
try expect_opcode(0x2d, Insn.jgt(.r1, .r2, 0));
try expect_opcode(0x35, Insn.jge(.r1, 0, 0));
try expect_opcode(0x3d, Insn.jge(.r1, .r2, 0));
try expect_opcode(0xa5, Insn.jlt(.r1, 0, 0));
try expect_opcode(0xad, Insn.jlt(.r1, .r2, 0));
try expect_opcode(0xb5, Insn.jle(.r1, 0, 0));
try expect_opcode(0xbd, Insn.jle(.r1, .r2, 0));
try expect_opcode(0x45, Insn.jset(.r1, 0, 0));
try expect_opcode(0x4d, Insn.jset(.r1, .r2, 0));
try expect_opcode(0x55, Insn.jne(.r1, 0, 0));
try expect_opcode(0x5d, Insn.jne(.r1, .r2, 0));
try expect_opcode(0x65, Insn.jsgt(.r1, 0, 0));
try expect_opcode(0x6d, Insn.jsgt(.r1, .r2, 0));
try expect_opcode(0x75, Insn.jsge(.r1, 0, 0));
try expect_opcode(0x7d, Insn.jsge(.r1, .r2, 0));
try expect_opcode(0xc5, Insn.jslt(.r1, 0, 0));
try expect_opcode(0xcd, Insn.jslt(.r1, .r2, 0));
try expect_opcode(0xd5, Insn.jsle(.r1, 0, 0));
try expect_opcode(0xdd, Insn.jsle(.r1, .r2, 0));
try expect_opcode(0x85, Insn.call(.unspec));
try expect_opcode(0x95, Insn.exit());
}
pub const Cmd = enum(usize) {
map_create,
map_lookup_elem,
map_update_elem,
map_delete_elem,
map_get_next_key,
prog_load,
obj_pin,
obj_get,
prog_attach,
prog_detach,
prog_test_run,
prog_get_next_id,
map_get_next_id,
prog_get_fd_by_id,
map_get_fd_by_id,
obj_get_info_by_fd,
prog_query,
raw_tracepoint_open,
btf_load,
btf_get_fd_by_id,
task_fd_query,
map_lookup_and_delete_elem,
map_freeze,
btf_get_next_id,
map_lookup_batch,
map_lookup_and_delete_batch,
map_update_batch,
map_delete_batch,
link_create,
link_update,
link_get_fd_by_id,
link_get_next_id,
enable_stats,
iter_create,
link_detach,
_,
};
pub const MapType = enum(u32) {
unspec,
hash,
array,
prog_array,
perf_event_array,
percpu_hash,
percpu_array,
stack_trace,
cgroup_array,
lru_hash,
lru_percpu_hash,
lpm_trie,
array_of_maps,
hash_of_maps,
devmap,
sockmap,
cpumap,
xskmap,
sockhash,
cgroup_storage,
reuseport_sockarray,
percpu_cgroup_storage,
queue,
stack,
sk_storage,
devmap_hash,
struct_ops,
ringbuf,
_,
};
pub const ProgType = enum(u32) {
unspec,
socket_filter,
kprobe,
sched_cls,
sched_act,
tracepoint,
xdp,
perf_event,
cgroup_skb,
cgroup_sock,
lwt_in,
lwt_out,
lwt_xmit,
sock_ops,
sk_skb,
cgroup_device,
sk_msg,
raw_tracepoint,
cgroup_sock_addr,
lwt_seg6local,
lirc_mode2,
sk_reuseport,
flow_dissector,
cgroup_sysctl,
raw_tracepoint_writable,
cgroup_sockopt,
tracing,
struct_ops,
ext,
lsm,
sk_lookup,
_,
};
pub const AttachType = enum(u32) {
cgroup_inet_ingress,
cgroup_inet_egress,
cgroup_inet_sock_create,
cgroup_sock_ops,
sk_skb_stream_parser,
sk_skb_stream_verdict,
cgroup_device,
sk_msg_verdict,
cgroup_inet4_bind,
cgroup_inet6_bind,
cgroup_inet4_connect,
cgroup_inet6_connect,
cgroup_inet4_post_bind,
cgroup_inet6_post_bind,
cgroup_udp4_sendmsg,
cgroup_udp6_sendmsg,
lirc_mode2,
flow_dissector,
cgroup_sysctl,
cgroup_udp4_recvmsg,
cgroup_udp6_recvmsg,
cgroup_getsockopt,
cgroup_setsockopt,
trace_raw_tp,
trace_fentry,
trace_fexit,
modify_return,
lsm_mac,
trace_iter,
cgroup_inet4_getpeername,
cgroup_inet6_getpeername,
cgroup_inet4_getsockname,
cgroup_inet6_getsockname,
xdp_devmap,
cgroup_inet_sock_release,
xdp_cpumap,
sk_lookup,
xdp,
_,
};
const obj_name_len = 16;
pub const MapCreateAttr = extern struct {
map_type: u32,
key_size: u32,
value_size: u32,
max_entries: u32,
map_flags: u32,
inner_map_fd: fd_t,
numa_node: u32,
map_name: [obj_name_len]u8,
map_ifindex: u32,
btf_fd: fd_t,
btf_key_type_id: u32,
bpf_value_type_id: u32,
btf_vmlinux_value_type_id: u32,
};
pub const MapElemAttr = extern struct {
map_fd: fd_t,
key: u64,
result: extern union {
value: u64,
next_key: u64,
},
flags: u64,
};
pub const MapBatchAttr = extern struct {
in_batch: u64,
out_batch: u64,
keys: u64,
values: u64,
count: u32,
map_fd: fd_t,
elem_flags: u64,
flags: u64,
};
pub const ProgLoadAttr = extern struct {
prog_type: u32,
insn_cnt: u32,
insns: u64,
license: u64,
log_level: u32,
log_size: u32,
log_buf: u64,
kern_version: u32,
prog_flags: u32,
prog_name: [obj_name_len]u8,
prog_ifindex: u32,
expected_attach_type: u32,
prog_btf_fd: fd_t,
func_info_rec_size: u32,
func_info: u64,
func_info_cnt: u32,
line_info_rec_size: u32,
line_info: u64,
line_info_cnt: u32,
attact_btf_id: u32,
attach_prog_id: u32,
};
pub const ObjAttr = extern struct {
pathname: u64,
bpf_fd: fd_t,
file_flags: u32,
};
pub const ProgAttachAttr = extern struct {
target_fd: fd_t,
attach_bpf_fd: fd_t,
attach_type: u32,
attach_flags: u32,
replace_bpf_fd: fd_t,
};
pub const TestRunAttr = extern struct {
prog_fd: fd_t,
retval: u32,
data_size_in: u32,
data_size_out: u32,
data_in: u64,
data_out: u64,
repeat: u32,
duration: u32,
ctx_size_in: u32,
ctx_size_out: u32,
ctx_in: u64,
ctx_out: u64,
};
pub const GetIdAttr = extern struct {
id: extern union {
start_id: u32,
prog_id: u32,
map_id: u32,
btf_id: u32,
link_id: u32,
},
next_id: u32,
open_flags: u32,
};
pub const InfoAttr = extern struct {
bpf_fd: fd_t,
info_len: u32,
info: u64,
};
pub const QueryAttr = extern struct {
target_fd: fd_t,
attach_type: u32,
query_flags: u32,
attach_flags: u32,
prog_ids: u64,
prog_cnt: u32,
};
pub const RawTracepointAttr = extern struct {
name: u64,
prog_fd: fd_t,
};
pub const BtfLoadAttr = extern struct {
btf: u64,
btf_log_buf: u64,
btf_size: u32,
btf_log_size: u32,
btf_log_level: u32,
};
pub const TaskFdQueryAttr = extern struct {
pid: pid_t,
fd: fd_t,
flags: u32,
buf_len: u32,
buf: u64,
prog_id: u32,
fd_type: u32,
probe_offset: u64,
probe_addr: u64,
};
pub const LinkCreateAttr = extern struct {
prog_fd: fd_t,
target_fd: fd_t,
attach_type: u32,
flags: u32,
};
pub const LinkUpdateAttr = extern struct {
link_fd: fd_t,
new_prog_fd: fd_t,
flags: u32,
old_prog_fd: fd_t,
};
pub const EnableStatsAttr = extern struct {
type: u32,
};
pub const IterCreateAttr = extern struct {
link_fd: fd_t,
flags: u32,
};
pub const Attr = extern union {
map_create: MapCreateAttr,
map_elem: MapElemAttr,
map_batch: MapBatchAttr,
prog_load: ProgLoadAttr,
obj: ObjAttr,
prog_attach: ProgAttachAttr,
test_run: TestRunAttr,
get_id: GetIdAttr,
info: InfoAttr,
query: QueryAttr,
raw_tracepoint: RawTracepointAttr,
btf_load: BtfLoadAttr,
task_fd_query: TaskFdQueryAttr,
link_create: LinkCreateAttr,
link_update: LinkUpdateAttr,
enable_stats: EnableStatsAttr,
iter_create: IterCreateAttr,
};
pub const Log = struct {
level: u32,
buf: []u8,
};
pub fn map_create(map_type: MapType, key_size: u32, value_size: u32, max_entries: u32) !fd_t {
var attr = Attr{
.map_create = std.mem.zeroes(MapCreateAttr),
};
attr.map_create.map_type = @enumToInt(map_type);
attr.map_create.key_size = key_size;
attr.map_create.value_size = value_size;
attr.map_create.max_entries = max_entries;
const rc = linux.bpf(.map_create, &attr, @sizeOf(MapCreateAttr));
switch (errno(rc)) {
.SUCCESS => return @intCast(fd_t, rc),
.INVAL => return error.MapTypeOrAttrInvalid,
.NOMEM => return error.SystemResources,
.PERM => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
test "map_create" {
const map = try map_create(.hash, 4, 4, 32);
defer std.os.close(map);
}
pub fn map_lookup_elem(fd: fd_t, key: []const u8, value: []u8) !void {
var attr = Attr{
.map_elem = std.mem.zeroes(MapElemAttr),
};
attr.map_elem.map_fd = fd;
attr.map_elem.key = @ptrToInt(key.ptr);
attr.map_elem.result.value = @ptrToInt(value.ptr);
const rc = linux.bpf(.map_lookup_elem, &attr, @sizeOf(MapElemAttr));
switch (errno(rc)) {
.SUCCESS => return,
.BADF => return error.BadFd,
.FAULT => unreachable,
.INVAL => return error.FieldInAttrNeedsZeroing,
.NOENT => return error.NotFound,
.PERM => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn map_update_elem(fd: fd_t, key: []const u8, value: []const u8, flags: u64) !void {
var attr = Attr{
.map_elem = std.mem.zeroes(MapElemAttr),
};
attr.map_elem.map_fd = fd;
attr.map_elem.key = @ptrToInt(key.ptr);
attr.map_elem.result = .{ .value = @ptrToInt(value.ptr) };
attr.map_elem.flags = flags;
const rc = linux.bpf(.map_update_elem, &attr, @sizeOf(MapElemAttr));
switch (errno(rc)) {
.SUCCESS => return,
.@"2BIG" => return error.ReachedMaxEntries,
.BADF => return error.BadFd,
.FAULT => unreachable,
.INVAL => return error.FieldInAttrNeedsZeroing,
.NOMEM => return error.SystemResources,
.PERM => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn map_delete_elem(fd: fd_t, key: []const u8) !void {
var attr = Attr{
.map_elem = std.mem.zeroes(MapElemAttr),
};
attr.map_elem.map_fd = fd;
attr.map_elem.key = @ptrToInt(key.ptr);
const rc = linux.bpf(.map_delete_elem, &attr, @sizeOf(MapElemAttr));
switch (errno(rc)) {
.SUCCESS => return,
.BADF => return error.BadFd,
.FAULT => unreachable,
.INVAL => return error.FieldInAttrNeedsZeroing,
.NOENT => return error.NotFound,
.PERM => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
pub fn map_get_next_key(fd: fd_t, key: []const u8, next_key: []u8) !bool {
var attr = Attr{
.map_elem = std.mem.zeroes(MapElemAttr),
};
attr.map_elem.map_fd = fd;
attr.map_elem.key = @ptrToInt(key.ptr);
attr.map_elem.result.next_key = @ptrToInt(next_key.ptr);
const rc = linux.bpf(.map_get_next_key, &attr, @sizeOf(MapElemAttr));
switch (errno(rc)) {
.SUCCESS => return true,
.BADF => return error.BadFd,
.FAULT => unreachable,
.INVAL => return error.FieldInAttrNeedsZeroing,
.NOENT => return false,
.PERM => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
test "map lookup, update, and delete" {
const key_size = 4;
const value_size = 4;
const map = try map_create(.hash, key_size, value_size, 1);
defer std.os.close(map);
const key = std.mem.zeroes([key_size]u8);
var value = std.mem.zeroes([value_size]u8);
try expectError(error.NotFound, map_lookup_elem(map, &key, &value));
try map_update_elem(map, &key, &value, 0);
try map_lookup_elem(map, &key, &value);
const second_key = [key_size]u8{ 0, 0, 0, 1 };
try expectError(error.ReachedMaxEntries, map_update_elem(map, &second_key, &value, 0));
var lookup_key = [_]u8{ 1, 0, 0, 0 };
var next_key = [_]u8{ 2, 3, 4, 5 };
const status = try map_get_next_key(map, &lookup_key, &next_key);
try expectEqual(status, true);
try expectEqual(next_key, key);
std.mem.copy(u8, &lookup_key, &next_key);
const status2 = try map_get_next_key(map, &lookup_key, &next_key);
try expectEqual(status2, false);
try map_delete_elem(map, &key);
try expectError(error.NotFound, map_lookup_elem(map, &key, &value));
try expectError(error.NotFound, map_delete_elem(map, &key));
}
pub fn prog_load(
prog_type: ProgType,
insns: []const Insn,
log: ?*Log,
license: []const u8,
kern_version: u32,
) !fd_t {
var attr = Attr{
.prog_load = std.mem.zeroes(ProgLoadAttr),
};
attr.prog_load.prog_type = @enumToInt(prog_type);
attr.prog_load.insns = @ptrToInt(insns.ptr);
attr.prog_load.insn_cnt = @intCast(u32, insns.len);
attr.prog_load.license = @ptrToInt(license.ptr);
attr.prog_load.kern_version = kern_version;
if (log) |l| {
attr.prog_load.log_buf = @ptrToInt(l.buf.ptr);
attr.prog_load.log_size = @intCast(u32, l.buf.len);
attr.prog_load.log_level = l.level;
}
const rc = linux.bpf(.prog_load, &attr, @sizeOf(ProgLoadAttr));
return switch (errno(rc)) {
.SUCCESS => @intCast(fd_t, rc),
.ACCES => error.UnsafeProgram,
.FAULT => unreachable,
.INVAL => error.InvalidProgram,
.PERM => error.AccessDenied,
else => |err| unexpectedErrno(err),
};
}
test "prog_load" {
const bad_prog = [_]Insn{
Insn.exit(),
};
const good_prog = [_]Insn{
Insn.mov(.r0, 0),
Insn.exit(),
};
const prog = try prog_load(.socket_filter, &good_prog, null, "MIT", 0);
defer std.os.close(prog);
try expectError(error.UnsafeProgram, prog_load(.socket_filter, &bad_prog, null, "MIT", 0));
}