diff --git a/Kconfig b/Kconfig index 86dbeb7dc..1f0b7a6a9 100644 --- a/Kconfig +++ b/Kconfig @@ -103,6 +103,12 @@ config LUNATIK_XDP help Express Data Path (XDP) support for high-performance packet processing. +config LUNATIK_TC + tristate "Lunatik TC Support" + default m + help + Traffic Controller (TC) support for high-performance packet scheduling. + config LUNATIK_FIFO tristate "Lunatik FIFO Support" default m @@ -161,4 +167,4 @@ config LUNATIK_BYTEORDER Byte order swapping functions. endif - + diff --git a/Makefile b/Makefile index 67b0421a7..18abc0ebc 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ CONFIG_LUNATIK_RUN ?= m # Order matters: modules are loaded left-to-right and unloaded right-to-left (rmmod). # A module must appear AFTER all modules it depends on (e.g. SKB before NETFILTER). -LUNATIK_MODULES := DEVICE LINUX NOTIFIER SOCKET RCU THREAD FIB DATA PROBE SYSCALL XDP FIFO SKB NETFILTER \ +LUNATIK_MODULES := DEVICE LINUX NOTIFIER SOCKET RCU THREAD FIB DATA PROBE SYSCALL XDP TC FIFO SKB NETFILTER \ COMPLETION CRYPTO CPU HID SIGNAL BYTEORDER DARKEN $(foreach c,$(LUNATIK_MODULES),\ @@ -80,6 +80,7 @@ scripts_install: ${MKDIR} ${SCRIPTS_INSTALL_PATH} ${MKDIR} ${SCRIPTS_INSTALL_PATH}/lunatik ${MKDIR} ${SCRIPTS_INSTALL_PATH}/socket + ${MKDIR} ${SCRIPTS_INSTALL_PATH}/skb ${MKDIR} ${SCRIPTS_INSTALL_PATH}/syscall ${MKDIR} ${SCRIPTS_INSTALL_PATH}/crypto ${MKDIR} ${SCRIPTS_INSTALL_PATH}/linux @@ -91,6 +92,7 @@ scripts_install: ${INSTALL} -m 0644 lib/lighten.lua ${SCRIPTS_INSTALL_PATH}/ ${INSTALL} -m 0644 lib/lunatik/*.lua ${SCRIPTS_INSTALL_PATH}/lunatik ${INSTALL} -m 0644 lib/socket/*.lua ${SCRIPTS_INSTALL_PATH}/socket + ${INSTALL} -m 0644 lib/skb/*.lua ${SCRIPTS_INSTALL_PATH}/skb ${INSTALL} -m 0644 lib/syscall/*.lua ${SCRIPTS_INSTALL_PATH}/syscall ${INSTALL} -m 0644 lib/crypto/*.lua ${SCRIPTS_INSTALL_PATH}/crypto # NOTE: `lib/linux/` exists only as LDoc stubs (see doc-stubs); never install it. @@ -115,10 +117,12 @@ scripts_uninstall: ebpf: ${MAKE} -C examples/filter + ${MAKE} -C examples/sniclassify ebpf_install: ${MKDIR} ${LUNATIK_EBPF_INSTALL_PATH} ${INSTALL} -m 0644 examples/filter/https.o ${LUNATIK_EBPF_INSTALL_PATH}/ + ${INSTALL} -m 0644 examples/sniclassify/classify.o ${LUNATIK_EBPF_INSTALL_PATH}/ ebpf_uninstall: ${RM} -r ${LUNATIK_EBPF_INSTALL_PATH} diff --git a/README.md b/README.md index 7e2f5256e..26418cb2a 100644 --- a/README.md +++ b/README.md @@ -465,6 +465,46 @@ ip netns exec tcpreject curl --connect-timeout 2 https://[2001:4860:4860::8888] sudo examples/tcpreject/cleanup.sh ``` +### sniclassify + +[sniclassify](examples/sniclassify) is a kernel extension composed by +a TC/eBPF classifier program attached on egress, +a Lua kernel script to classify [SNI](https://datatracker.ietf.org/doc/html/rfc3546#section-3.1) traffic. +This kernel extension extracts server name and assigns traffic +classes according to a Lua [policy table](examples/sniclassify/sni.lua#18). + +Install and load the classfier: + +```sh +sudo make btf_install # needed to export the 'bpf_luatc_run' kfunc +sudo make examples_install # installs examples +make ebpf # builds the TC/eBPF program +sudo make ebpf_install # installs the TC/eBPF program +sudo lunatik run examples/sniclassify/sni softirq +``` + +Configure HTB classes: +``` +sudo tc qdisc del dev docker0 root 2>/dev/null +sudo tc qdisc add dev docker0 root handle 1: htb default 30 +sudo tc class add dev docker0 parent 1: classid 1:10 htb rate 20mbit +sudo tc class add dev docker0 parent 1: classid 1:20 htb rate 10mbit +``` + +Attach the TC/eBPF classifier on egress: +``` +sudo tc filter add dev docker0 parent 1: bpf da obj examples/sniclassify/classify.o sec classifier +``` + +The classifier inspects outbound TLS ClientHello packets, extracts the SNI +field, and assigns a traffic class according to the Lua policy table. + +Verify and test: +``` +sudo tc filter show dev docker0 +sudo journalctl -ft kernel +``` + ### gesture [gesture](examples/gesture.lua) diff --git a/autogen/specs.lua b/autogen/specs.lua index 7a1a75869..4a810777e 100644 --- a/autogen/specs.lua +++ b/autogen/specs.lua @@ -27,6 +27,8 @@ return { desc = "Network device notifier event types." }, { header = "uapi/linux/bpf.h", prefix = "XDP_", module = "xdp", desc = "XDP verdicts and flags." }, + { header = "uapi/linux/pkt_cls.h", prefix = "TC_", module = "tc", + desc = "TC verdicts and flags." }, { header = "linux/sched.h", prefix = "TASK_", module = "task", desc = "Task state flags." }, { header = "linux/net.h", prefix = "SOCK_", module = "socket.sock", diff --git a/config.ld b/config.ld index e2bcd8629..c674689a6 100644 --- a/config.ld +++ b/config.ld @@ -38,6 +38,7 @@ file = { './lib/net.lua', './lib/luanetfilter.c', './lib/luaskb.c', + './lib/skb/attr.lua', './lib/luanotifier.c', './lib/luaprobe.c', './lib/luarcu.c', diff --git a/examples/sniclassify/Makefile b/examples/sniclassify/Makefile new file mode 100644 index 000000000..373ad3601 --- /dev/null +++ b/examples/sniclassify/Makefile @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal +# SPDX-License-Identifier: MIT OR GPL-2.0-only + +all: vmlinux classify.o + +vmlinux: + bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h + +classify.o: classify.c + clang -target bpf -Wall -O2 -c -g $< + +clean: + rm -f vmlinux.h classify.o + diff --git a/examples/sniclassify/classify.c b/examples/sniclassify/classify.c new file mode 100644 index 000000000..3ece7213b --- /dev/null +++ b/examples/sniclassify/classify.c @@ -0,0 +1,69 @@ +/* +* SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal +* SPDX-License-Identifier: MIT OR GPL-2.0-only +*/ + +#include "vmlinux.h" +#include +#include + +extern int bpf_luatc_run(char *key, size_t key__sz, struct __sk_buff *skb, void *arg, size_t arg__sz) __ksym; + +static char runtime[] = "examples/sniclassify/sni"; + +int const TC_ACT_OK = 0; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 65536); + __type(key, __u32); + __type(value, __u32); +} flow_cache SEC(".maps"); + +struct bpf_luatc_arg { + __u16 offset; +} __attribute__((packed)); + +SEC("classifier") +int classify(struct __sk_buff *skb) +{ + __u32 key = skb->hash; + + __u32 *priority= bpf_map_lookup_elem(&flow_cache, &key); + if (priority) { + skb->priority = *priority; + return TC_ACT_OK; + } + + struct bpf_luatc_arg arg; + void *data_end = (void *)(long)skb->data_end; + void *data = (void *)(long)skb->data; + struct iphdr *ip = data + sizeof(struct ethhdr); + + if (ip + 1 > (struct iphdr *)data_end) + goto pass; + + if (ip->protocol != IPPROTO_TCP) + goto pass; + + struct tcphdr *tcp = (void *)ip + (ip->ihl * 4); + if (tcp + 1 > (struct tcphdr *)data_end) + goto pass; + + if (bpf_ntohs(tcp->dest) != 443 || !tcp->psh) + goto pass; + + void *payload = (void *)tcp + (tcp->doff * 4); + if (payload > data_end) + goto pass; + + arg.offset = bpf_htons((__u16)(payload - data)); + + int action = bpf_luatc_run(runtime, sizeof(runtime), skb, &arg, sizeof(arg)); + return action < 0 ? TC_ACT_OK : action; +pass: + return TC_ACT_OK; +} + +char _license[] SEC("license") = "Dual MIT/GPL"; + diff --git a/examples/sniclassify/sni.lua b/examples/sniclassify/sni.lua new file mode 100644 index 000000000..4e482fdc7 --- /dev/null +++ b/examples/sniclassify/sni.lua @@ -0,0 +1,85 @@ +-- +-- SPDX-FileCopyrightText: (c) 2025-2026 Ashwani Kumar Kamal +-- SPDX-License-Identifier: MIT OR GPL-2.0-only +-- + +local tc = require("tc") +local action = require("linux.tc") +local skbattr = require("skb.attr") + +local TC_H_MAKE = function(maj, min) return (maj << 16) | min end + +local client_hello = 0x01 +local handshake = 0x16 +local server_name = 0x0000 + +local session = 43 +local max_extensions = 17 + +local policy = { + ["netflix%.com"] = TC_H_MAKE(1, 20), + ["zoom%.com"] = TC_H_MAKE(1, 10), +} + +local function log(sni, priority) + print(string.format("sniclassify: %s %s", sni, priority)) +end + +local function unpacker(packet, base) + local byte = function (offset) + return packet:getbyte(base + offset) + end + + local short = function (offset) + local offset = base + offset + return packet:getbyte(offset) << 8 | packet:getbyte(offset + 1) + end + + local str = function (offset, length) + return packet:getstring(base + offset, length) + end + + return byte, short, str +end + +local function offset(argument) + return select(2, unpacker(argument, 0))(0) +end + +local function sniclassify(ctx) + local argument = ctx:argument() + local skb = skbattr(ctx:skb()) + local data = skb:data() + local byte, short, str = unpacker(data, offset(argument)) + + if byte(0) ~= handshake or byte(5) ~= client_hello then + ctx:action(action.ACT_OK) + return + end + + local cipher = (session + 1) + byte(session) + local compression = cipher + 2 + short(cipher) + local extension = compression + 3 + byte(compression) + + for _ = 1, max_extensions do + local data_off = extension + 4 + if short(extension) == server_name then + local sni = str(data_off + 5, short(data_off + 3)) + for pattern, classid in pairs(policy) do + if sni:match(pattern) then + log(sni, classid) + skb.priority = classid + break + end + end + ctx:action(action.ACT_OK) + return + end + extension = data_off + short(extension + 2) + end + + ctx:action(action.ACT_OK) +end + +tc.attach(sniclassify) + diff --git a/lib/Kbuild b/lib/Kbuild index 16d64f35e..ea736c147 100644 --- a/lib/Kbuild +++ b/lib/Kbuild @@ -12,6 +12,7 @@ obj-$(CONFIG_LUNATIK_DATA) += luadata.o obj-$(CONFIG_LUNATIK_PROBE) += luaprobe.o obj-$(CONFIG_LUNATIK_SYSCALL) += luasyscall.o obj-$(CONFIG_LUNATIK_XDP) += luaxdp.o +obj-$(CONFIG_LUNATIK_TC) += luatc.o obj-$(CONFIG_LUNATIK_FIFO) += luafifo.o obj-$(CONFIG_LUNATIK_NETFILTER) += luanetfilter.o obj-$(CONFIG_LUNATIK_COMPLETION) += luacompletion.o diff --git a/lib/luaskb.c b/lib/luaskb.c index 50a6d50d0..c6563adcf 100644 --- a/lib/luaskb.c +++ b/lib/luaskb.c @@ -195,6 +195,55 @@ static int luaskb_forward(lua_State *L) return 0; } +static int luaskb_getmark(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + lua_pushinteger(L, lskb->skb->mark); + return 1; +} + +static int luaskb_setmark(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + lskb->skb->mark = (u32)luaL_checkinteger(L, 2); + return 0; +} + +static int luaskb_getpriority(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + lua_pushinteger(L, lskb->skb->priority); + return 1; +} + +static int luaskb_setpriority(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + lskb->skb->priority = (u32)luaL_checkinteger(L, 2); + return 0; +} + +static int luaskb_protocol(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + lua_pushinteger(L, ntohs(lskb->skb->protocol)); + return 1; +} + +static int luaskb_proto(lua_State *L) +{ + luaskb_t *lskb = luaskb_check(L, 1); + struct sk_buff *skb = lskb->skb; + + if (skb->protocol == htons(ETH_P_IP)) + lua_pushinteger(L, ip_hdr(skb)->protocol); + else if (skb->protocol == htons(ETH_P_IPV6)) + lua_pushinteger(L, ipv6_hdr(skb)->nexthdr); + else + lua_pushnil(L); + return 1; +} + static int luaskb_copy(lua_State *L); static void luaskb_release(void *private) @@ -211,18 +260,25 @@ static const luaL_Reg luaskb_lib[] = { }; static const luaL_Reg luaskb_mt[] = { - {"__gc", lunatik_deleteobject}, - {"__len", luaskb_len}, - {"ifindex", luaskb_ifindex}, - {"vlan", luaskb_vlan}, - {"data", luaskb_data}, - {"resize", luaskb_resize}, - {"checksum", luaskb_checksum}, - {"forward", luaskb_forward}, - {"copy", luaskb_copy}, + {"__gc", lunatik_deleteobject}, + {"__len", luaskb_len}, + {"ifindex", luaskb_ifindex}, + {"vlan", luaskb_vlan}, + {"data", luaskb_data}, + {"resize", luaskb_resize}, + {"checksum", luaskb_checksum}, + {"forward", luaskb_forward}, + {"copy", luaskb_copy}, + {"protocol", luaskb_protocol}, + {"proto", luaskb_proto}, + {"getmark", luaskb_getmark}, + {"getpriority", luaskb_getpriority}, + {"setmark", luaskb_setmark}, + {"setpriority", luaskb_setpriority}, {NULL, NULL} }; + LUNATIK_OPENER(skb); static const lunatik_class_t luaskb_class = { .name = "skb", diff --git a/lib/luatc.c b/lib/luatc.c new file mode 100644 index 000000000..ceb080f9a --- /dev/null +++ b/lib/luatc.c @@ -0,0 +1,332 @@ +/* +* SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal +* SPDX-License-Identifier: MIT OR GPL-2.0-only +*/ + +/*** +* Linux Traffic Controller (TC) integration. +* This library allows Lua scripts to interact with the kernel's TC subsystem. +* It enables TC/eBPF programs to call Lua functions for packet processing, +* traffic shaping, filtering, and policy enforcement, providing a flexible +* way to implement custom networking logic in Lua at the ingress and egress +* layers of network stack. +* +* The primary mechanism involves an TC program calling the `bpf_luatc_run` +* kfunc, which in turn invokes a Lua callback function previously registered +* using `tc.attach()`. +* @module tc +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include + +#include +#include + +#include "luarcu.h" +#include "luadata.h" +#include "luaskb.h" + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)) +__bpf_kfunc_start_defs(); +#else +__diag_push(); +__diag_ignore_all("-Wmissing-prototypes", + "Global kfuncs as their definitions will be in BTF"); +#endif + +static char luatc_env_key; + +static lunatik_object_t *luatc_runtimes = NULL; + +typedef struct luatc_ctx_s { + struct __sk_buff *skb; + void *arg; + size_t arg__sz; + int *action; + lunatik_object_t *argument; + lunatik_object_t *skb_obj; + int callback_ref; +} luatc_ctx_t; + +LUNATIK_PRIVATECHECKER(luatc_ctx_check, luatc_ctx_t *, + luaL_argcheck(L, private->skb != NULL, ix, "ctx is not set"); +); + +/*** +* Returns the packet data buffer for the current XDP context. +* @function skb +* @treturn data +*/ +static int luatc_skb(lua_State *L) +{ + luatc_ctx_t *ctx = luatc_ctx_check(L, 1); + lunatik_getregistry(L, ctx->skb_obj); + return 1; +} + +/*** +* Returns the argument data buffer passed from eBPF. +* @function argument +* @treturn data +*/ +static int luatc_arg(lua_State *L) +{ + luatc_ctx_t *ctx = luatc_ctx_check(L, 1); + lunatik_getregistry(L, ctx->argument); + return 1; +} + +/*** +* Sets the TC verdict action for this packet. +* @function set_action +* @tparam integer action TC action constant (e.g. XDP_PASS, XDP_DROP, ...) +*/ +static int luatc_action(lua_State *L) +{ + luatc_ctx_t *ctx = luatc_ctx_check(L, 1); + *ctx->action = luaL_checkinteger(L, 2); + return 0; +} + +static const luaL_Reg luatc_mt[] = { + {"__gc", lunatik_deleteobject}, + {"skb", luatc_skb}, + {"argument", luatc_arg}, + {"action", luatc_action}, + {NULL, NULL} +}; + +static void luatc_release(void *private) +{ + luatc_ctx_t *lctx = (luatc_ctx_t *)private; + if (lctx->skb_obj) + luaskb_clear(lctx->skb_obj); + if (lctx->argument) + luadata_close(lctx->argument); +} + +LUNATIK_OPENER(tc); +static const lunatik_class_t luatc_class = { + .name = "tc.ctx", + .methods = luatc_mt, + .release = luatc_release, + .opener = luaopen_tc, + .opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_SINGLE, +}; + +static void luatc_handler_cleanup(luatc_ctx_t *lctx) +{ + luaskb_t *lskb = (luaskb_t *)lctx->skb_obj->private; + luadata_clear(lctx->argument); + lskb->skb = NULL; + lctx->action = NULL; +} + +static int luatc_handler(lua_State *L, luatc_ctx_t *ctx) +{ + luatc_ctx_t *lctx; + lunatik_bpf_get_env(L, &luatc_env_key, lctx); + luaskb_t *lskb = (luaskb_t *)lctx->skb_obj->private; + + lskb->skb = (struct sk_buff *)ctx->skb; + + lctx->skb = ctx->skb; + lctx->arg = ctx->arg; + lctx->arg__sz = ctx->arg__sz; + lctx->action = ctx->action; + + luadata_reset(lctx->argument, lctx->arg, lctx->arg__sz, LUADATA_OPT_KEEP); + + lua_rawgeti(L, LUA_REGISTRYINDEX, lctx->callback_ref); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + pr_err("callback_ref is not a function\n"); + luatc_handler_cleanup(lctx); + return -1; + } + + lua_insert(L, -2); + if (lua_pcall(L, 1, 0, 0) != LUA_OK) { + pr_err("%s\n", lua_tostring(L, -1)); + lua_pop(L, 1); + luatc_handler_cleanup(lctx); + return -1; + } + + luatc_handler_cleanup(lctx); + return 0; +} + +__bpf_kfunc int bpf_luatc_run(char *key, size_t key__sz, struct __sk_buff *skb, void *arg, size_t arg__sz) +{ + int action = -1; + + lunatik_object_t *runtime = lunatik_ebpf_lookup(luatc_runtimes, key, key__sz); + if (runtime == NULL) + goto out; + + luatc_ctx_t ctx = { + .skb = skb, + .arg = arg, + .arg__sz = arg__sz, + .action = &action, + }; + + lunatik_run(runtime, luatc_handler, action, &ctx); + lunatik_putobject(runtime); +out: + return action; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)) +__bpf_kfunc_end_defs(); +#else +__diag_pop(); +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)) +BTF_KFUNCS_START(bpf_luatc_set) +BTF_ID_FLAGS(func, bpf_luatc_run) +BTF_KFUNCS_END(bpf_luatc_set) +#else +BTF_SET8_START(bpf_luatc_set) +BTF_ID_FLAGS(func, bpf_luatc_run) +BTF_SET8_END(bpf_luatc_set) +#endif + +static const struct btf_kfunc_id_set bpf_luatc_kfunc_set = { + .owner = THIS_MODULE, + .set = &bpf_luatc_set, +}; + +/*** +* Unregisters the Lua callback function associated with the current Lunatik runtime. +* After calling this, `bpf_luatc_run` calls targeting this runtime will no longer +* invoke a Lua function (they will likely return an error or default action). +* @function detach +* @treturn nil +* @usage +* tc.detach() +* @within tc +*/ +static int luatc_detach(lua_State *L) +{ + luatc_ctx_t *lctx; + lunatik_bpf_get_env(L, &luatc_env_key, lctx); + luaL_unref(L, LUA_REGISTRYINDEX, lctx->callback_ref); + lctx->callback_ref = LUA_NOREF; + lua_pop(L, 1); + lunatik_unregister(L, &luatc_env_key); + return 0; +} + +/*** +* Registers a Lua callback function to be invoked by an XDP/eBPF program. +* When an XDP program calls the `bpf_luatc_run` kfunc, Lunatik will execute +* the registered Lua `callback` associated with the current Lunatik runtime. +* The runtime invoking this function must be non-sleepable. +* +* The `bpf_luatc_run` kfunc is called from an eBPF program with the following signature: +* `int bpf_luatc_run(char *key, size_t key_sz, struct __sk_buff *tc_ctx)` +* +* - `key`: A string identifying the Lunatik runtime (e.g., the script name like "examples/filter/sni"). +* This key is used to look up the runtime in Lunatik's internal table of active runtimes. +* - `key_sz`: Length of the key string (including the null terminator). +* - `tc_ctx`: The XDP metadata context (`struct __sk_buff *`). +* +* @function attach +* @tparam function callback Lua function to call. It receives one argument: +* +* 1. `ctx` (tc.ctx userdata): A context object used to inspect the packet +* and control the XDP verdict via `ctx:action()`. +* +* The callback **must not return a value**. If no action is set, the default +* is `action.PASS`. +* @treturn nil +* @raise Error if the current runtime is sleepable or if internal setup fails. +* @usage +* -- Lua script (e.g., "my_tc_handler.lua" which is run via `lunatik run my_tc_handler.lua`) +* local tc = require("tc") +* local action = require("linux.tc") +* +* local function my_packet_processor(ctx) +* local skb = ctx:packet() +* print("Packet received, size:", #skb) +* ctx:action(action.ACT_OK) +* return nil +* end +* tc.attach(my_packet_processor) +* +* -- In eBPF C code, to call the above Lua function: +* -- char rt_key[] = "my_tc_handler.lua"; // Key matches the script name +* -- int verdict = bpf_luatc_run(rt_key, sizeof(rt_key), ctx); +* @see data +* @within tc +*/ +static int luatc_attach(lua_State *L) +{ + lunatik_checkruntime(L, LUNATIK_OPT_SOFTIRQ); + luaL_checktype(L, 1, LUA_TFUNCTION); /* callback */ + + lunatik_object_t *object = lunatik_newobject(L, &luatc_class, sizeof(luatc_ctx_t), LUNATIK_OPT_NONE); + luatc_ctx_t *ctx = (luatc_ctx_t *)object->private; + + ctx->skb_obj = luaskb_new(L); + lunatik_getobject(ctx->skb_obj); + lunatik_register(L, -1, ctx->skb_obj); + lua_pop(L, 1); + + ctx->argument = luadata_new(L, LUNATIK_OPT_SINGLE); + lunatik_getobject(ctx->argument); + lunatik_register(L, -1, ctx->argument); + lua_pop(L, 1); + + lua_pushvalue(L, 1); + ctx->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lunatik_register(L, -1, &luatc_env_key); + lua_pop(L, 1); + + return 0; +} +#endif + +static const luaL_Reg luatc_lib[] = { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) + {"attach", luatc_attach}, + {"detach", luatc_detach}, +#endif + {NULL, NULL} +}; + +LUNATIK_CLASSES(tc, &luatc_class); +LUNATIK_NEWLIB(tc, luatc_lib, luatc_classes); + +static int __init luatc_init(void) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) + return register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_luatc_kfunc_set); +#else + return 0; +#endif +} + +static void __exit luatc_exit(void) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) + if (luatc_runtimes != NULL) + lunatik_putobject(luatc_runtimes); +#endif +} + +module_init(luatc_init); +module_exit(luatc_exit); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Ashwani Kumar Kamal "); + diff --git a/lib/luaxdp.c b/lib/luaxdp.c index ea3ebcb8a..9a7ea65cc 100644 --- a/lib/luaxdp.c +++ b/lib/luaxdp.c @@ -20,10 +20,10 @@ #include #include +#include #include "luarcu.h" #include "luadata.h" -#include "lunatik_bpf.h" #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) #include @@ -157,31 +157,13 @@ static int luaxdp_handler(lua_State *L, luaxdp_ctx_t *ctx) return 0; } -static inline int luaxdp_checkruntimes(void) -{ - const char *key = "runtimes"; - if (luaxdp_runtimes == NULL && - (luaxdp_runtimes = luarcu_getobject(lunatik_env, key, sizeof(key))) == NULL) - return -1; - return 0; -} - __bpf_kfunc int bpf_luaxdp_run(char *key, size_t key__sz, struct xdp_md *xdp_ctx, void *arg, size_t arg__sz) { - lunatik_object_t *runtime; int action = -1; - size_t keylen = key__sz - 1; - if (unlikely(luaxdp_checkruntimes() != 0)) { - pr_err("couldn't find _ENV.runtimes\n"); + lunatik_object_t *runtime = lunatik_ebpf_lookup(luaxdp_runtimes, key, key__sz); + if (runtime == NULL) goto out; - } - - key[keylen] = '\0'; - if ((runtime = luarcu_getobject(luaxdp_runtimes, key, keylen)) == NULL) { - pr_err("couldn't find runtime '%s'\n", key); - goto out; - } luaxdp_ctx_t ctx = { .xdp = (struct xdp_buff *)xdp_ctx, diff --git a/lib/lunatik_bpf.h b/lib/lunatik_bpf.h deleted file mode 100644 index b0d259503..000000000 --- a/lib/lunatik_bpf.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal - * SPDX-License-Identifier: MIT OR GPL-2.0-only - */ -#ifndef LUNATIK_BPF_H -#define LUNATIK_BPF_H - -#include "lunatik.h" - -/** - * Fetches the environment context from runtime registry - * Sets out_ptr to the objects private pointer - */ -#define lunatik_bpf_get_env(L, env_key, out_ptr) do { \ - if (lunatik_getregistry((L), (env_key)) != LUA_TUSERDATA) { \ - lua_pop((L), 1); \ - pr_err("couldn't find the context object\n"); \ - return -1; \ - } \ - lunatik_object_t *obj = (lunatik_object_t *)lunatik_toobject((L), -1); \ - (out_ptr) = obj->private; \ -} while (0) - -#endif - diff --git a/lunatik_ebpf.h b/lunatik_ebpf.h new file mode 100644 index 000000000..c663c35d2 --- /dev/null +++ b/lunatik_ebpf.h @@ -0,0 +1,49 @@ +/* +* SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal +* SPDX-License-Identifier: MIT OR GPL-2.0-only +*/ + +#ifndef LUNATIK_BPF_H +#define LUNATIK_BPF_H + +#include "lunatik.h" +#include "lib/luarcu.h" + +#define lunatik_ebpf_checkruntimes(runtimes) \ +({ \ + const char *key = "runtimes"; \ + if ((runtimes) == NULL) \ + (runtimes) = luarcu_getobject(lunatik_env, key, sizeof(key)); \ + (runtimes) != NULL ? 0 : -1; \ +}) + + +#define lunatik_ebpf_lookup(runtimes, key, key_sz) \ +({ \ + lunatik_object_t *runtime = NULL; \ + size_t keylen = key_sz - 1; \ + key[keylen] = '\0'; \ + if (unlikely(lunatik_ebpf_checkruntimes(runtimes) != 0)) \ + pr_err("couldn't find _ENV.runtimes\n"); \ + runtime = luarcu_getobject((runtimes), (key), keylen); \ + if (runtime == NULL) \ + pr_err("couldn't find runtime '%s'\n", (key)); \ + runtime; \ +}) + +/** + * Fetches the environment context from runtime registry + * Sets out_ptr to the objects private pointer + */ +#define lunatik_bpf_get_env(L, env_key, out_ptr) do { \ + if (lunatik_getregistry((L), (env_key)) != LUA_TUSERDATA) { \ + lua_pop((L), 1); \ + pr_err("couldn't find the context object\n"); \ + return -1; \ + } \ + lunatik_object_t *obj = (lunatik_object_t *)lunatik_toobject((L), -1); \ + (out_ptr) = obj->private; \ +} while (0) + +#endif +