Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "klibc"]
path = klibc
url = https://github.com/luainkernel/klibc.git
[submodule "examples/workload/scx"]
path = examples/workload/scx
url = https://github.com/sched-ext/scx.git
20 changes: 19 additions & 1 deletion Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ config LUNATIK_SET
Compact immutable set of strings: exact membership, plus a
suffix-matched labeled flavor.

config LUNATIK_TASK
tristate "Lunatik Task Support"
default m
help
Kernel task management from Lua.

config LUNATIK_THREAD
tristate "Lunatik Thread Support"
default m
Expand Down Expand Up @@ -111,6 +117,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
Expand Down Expand Up @@ -150,6 +162,12 @@ config LUNATIK_CPU
help
CPU management and information.

config LUNATIK_SCHED
tristate "Lunatik Scheduler Support"
default m
help
Extensible Linux Scheduler support for custom process scheduling.

config LUNATIK_HID
tristate "Lunatik HID (Human Interface Device) Support"
default m
Expand All @@ -176,4 +194,4 @@ config LUNATIK_BPF
Access to pinned eBPF maps from Lua scripts.

endif

12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ 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 NETLINK RCU SET THREAD DATA PROBE SYSCALL XDP FIFO SKB NETFILTER \
COMPLETION CRYPTO CPU HID SIGNAL BYTEORDER DARKEN BPF
LUNATIK_MODULES := DEVICE LINUX NOTIFIER SOCKET NETLINK RCU SET TASK THREAD DATA PROBE SYSCALL XDP FIFO \
SKB TC NETFILTER COMPLETION CRYPTO CPU HID SIGNAL BYTEORDER DARKEN BPF SCHED

$(foreach c,$(LUNATIK_MODULES),\
$(eval CONFIG_LUNATIK_$(c) ?= m))
Expand Down Expand Up @@ -83,6 +83,7 @@ scripts_install:
${MKDIR} ${SCRIPTS_INSTALL_PATH}/netlink
${MKDIR} ${SCRIPTS_INSTALL_PATH}/netlink/rt
${MKDIR} ${SCRIPTS_INSTALL_PATH}/netlink/nl80211
${MKDIR} ${SCRIPTS_INSTALL_PATH}/skb
${MKDIR} ${SCRIPTS_INSTALL_PATH}/syscall
${MKDIR} ${SCRIPTS_INSTALL_PATH}/crypto
${MKDIR} ${SCRIPTS_INSTALL_PATH}/linux
Expand All @@ -100,6 +101,7 @@ scripts_install:
${INSTALL} -m 0644 lib/netlink/*.lua ${SCRIPTS_INSTALL_PATH}/netlink
${INSTALL} -m 0644 lib/netlink/rt/*.lua ${SCRIPTS_INSTALL_PATH}/netlink/rt
${INSTALL} -m 0644 lib/netlink/nl80211/*.lua ${SCRIPTS_INSTALL_PATH}/netlink/nl80211
${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.
Expand Down Expand Up @@ -128,10 +130,16 @@ scripts_uninstall:

ebpf:
${MAKE} -C examples/filter
${MAKE} -C examples/sniclassify
${MAKE} -C examples/workload
${MAKE} -C examples/qos

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}/
${INSTALL} -m 0644 examples/workload/scheduler.o ${LUNATIK_EBPF_INSTALL_PATH}/
${INSTALL} -m 0644 examples/qos/classify.o ${LUNATIK_EBPF_INSTALL_PATH}/

ebpf_uninstall:
${RM} -r ${LUNATIK_EBPF_INSTALL_PATH}
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,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)
Expand Down Expand Up @@ -554,6 +594,37 @@ cpu_usage_idle{cpu="cpu0"} 100.0000000000000000 1764094519529162
...
```

### workload scheduler

[workload](examples/workload) is a scheduler composed by
[sched_ext/scx](https://github.com/sched-ext/scx) framework and eBPF. It includes
and eBPF program which uses a eBPF map to assign queues and slices to tasks and
a Lua kernel script to set dispatch queue and slice to the tasks seen for the first time
according to a Lua [policy table](examples/workload/workload.lua#13).

Install and load the scheduler:

```sh
sudo make btf_install # needed to export the 'bpf_luasched_run' kfunc
sudo make examples_install # installs examples
make ebpf # builds the sched_ext/eBPF program
sudo make ebpf_install # installs the sched_ext/eBPF program
sudo lunatik run examples/workload/workload hardirq
```

Load and attach the struct_ops scheduler:

```sh
sudo bpftool struct_ops register examples/workload/scheduler.o /sys/fs/bpf/luasched
```

Verify and test:

```sh
sudo bpftool struct_ops show
sudo journalctl -ft kernel
```

## References

### Talks and Papers
Expand Down
19 changes: 18 additions & 1 deletion autogen.lua
Original file line number Diff line number Diff line change
Expand Up @@ -375,14 +375,31 @@ local function intermediate_paths(mods)
return util.sorted(needs)
end

local function to_lua_number(val)
-- Negative decimal: assembler sign-extended a u64 with high bit set.
-- Convert to the equivalent hex literal so Lua parses the correct
-- bit pattern without floating point precision loss.
local neg = val:match("^%-(%d+)$")
if neg then
local n = math.tointeger(neg)
if n == nil then
-- Magnitude itself overflows: only -2^63 can do this,
-- which is math.mininteger.
return string.format("0x%016X", math.mininteger)
end
return string.format("0x%016X", -n)
end
return val
end

-- Write one sub-table block: init line (unless this is the top itself)
-- followed by sorted entries.
local function write_submodule(out, mod, top)
out:write("\n")
if mod.name ~= top then out:write(mod.name, " = {}\n") end
table.sort(mod.entries, function(a, b) return a.key < b.key end)
for _, e in ipairs(mod.entries) do
out:write(mod.name, '["', e.key, '"]\t= ', e.value, "\n")
out:write(mod.name, '["', e.key, '"]\t= ', to_lua_number(e.value), "\n")
end
end

Expand Down
4 changes: 4 additions & 0 deletions autogen/specs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ return {
desc = "BPF map types and update flags.",
include = { "ANY", "NOEXIST", "EXIST",
"MAP_TYPE_HASH", "MAP_TYPE_ARRAY", "MAP_TYPE_LRU_HASH", "MAP_TYPE_QUEUE", "MAP_TYPE_STACK" } },
{ 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/sched/ext.h", prefix = "SCX_", module = "scx",
desc = "Extensible scheduler flags." },
{ header = "linux/net.h", prefix = "SOCK_", module = "socket.sock",
desc = "Socket types (SOCK_STREAM, SOCK_DGRAM, ...)." },
{ header = "linux/socket.h", prefix = "AF_", module = "socket.af",
Expand Down
1 change: 1 addition & 0 deletions config.ld
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ file = {
'./lib/luanetfilter.c',
'./lib/luanetlink.c',
'./lib/luaskb.c',
'./lib/skb/attr.lua',
'./lib/luanotifier.c',
'./lib/luaprobe.c',
'./lib/luarcu.c',
Expand Down
12 changes: 8 additions & 4 deletions examples/filter/sni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ local server_name = 0x00
local session = 43
local max_extensions = 17

local function filter_sni(packet, argument)
local function filter_sni(ctx)
local packet = ctx:packet()
local argument = ctx:argument()
local byte, short, str = unpacker(packet, offset(argument))

if byte(0) ~= handshake or byte(5) ~= client_hello then
return action.PASS
ctx:action(action.PASS)
return
end

local cipher = (session + 1) + byte(session)
Expand All @@ -67,12 +70,13 @@ local function filter_sni(packet, argument)

verdict = blacklist[sni] and "DROP" or "PASS"
log(sni, verdict)
return action[verdict]
ctx:action(action[verdict])
return
end
extension = data + short(extension + 2)
end

return action.PASS
ctx:action(action.PASS)
end

xdp.attach(filter_sni)
Expand Down
14 changes: 14 additions & 0 deletions examples/qos/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>
# 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

24 changes: 24 additions & 0 deletions examples/qos/classify.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>
* SPDX-License-Identifier: MIT OR GPL-2.0-only
*/

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

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/qos/tc";

int const TC_ACT_OK = 0;

SEC("classifier")
int classify(struct __sk_buff *skb)
{
int action = bpf_luatc_run(runtime, sizeof(runtime), skb, NULL, 0);
return action < 0 ? TC_ACT_OK : action;
}

char _license[] SEC("license") = "Dual MIT/GPL";

60 changes: 60 additions & 0 deletions examples/qos/tc.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
--
-- SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>
-- SPDX-License-Identifier: MIT OR GPL-2.0-only
--

local tc = require("tc")
local action = require("linux.tc")
local skbattr = require("skb.attr")
local map = require("ebpf.map")

local TC_H_MAKE = function(maj, min) return (maj << 16) | min end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the caps?

@sneaky-potato sneaky-potato Jul 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a defined kernel macro (/include/uapi/linux/pkt_sched.h#L72)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave it as is by now, but I think we should have a better way to export such macros.. perhaps leveraging autogen as well.. it is worth creating an issue at least..


local stats = map.open("/sys/fs/bpf/flow_stats")

-- struct flow_stats {
-- u64 packets;
-- u32 avg_pkt_size;
-- };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now we can have named struct fields on autogen; won't it help here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flow_stats is currently only defined in the example BPF program, not in a kernel header. Are you suggesting to move it to a shared header so autogen can generate a layout for it, or some other mechanism?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't suggesting that, but I think it's a good idea to leverage autogen for this.


local KEY_FMT = "<I4"
local VAL_FMT = "<I8I4"

local function qos(ctx)
local skb = skbattr(ctx:skb())
local hash = skb.hash
if hash == 0 then
ctx:action(action.ACT_OK)
return
end
local key = string.pack(KEY_FMT, hash)
local value = stats:lookup(key)

local packets, avg

if value then
packets, avg = string.unpack(VAL_FMT, value)
else
packets = 0
avg = 0
end

packets = packets + 1
if packets == 1 then
avg = #skb
else
avg = (avg * 7 + #skb) // 8
end

value = string.pack(VAL_FMT, packets, avg)

stats:update(key, value)

local mask = (avg < 256 and 0x10) or (avg < 800 and 0x20) or 0x30
skb.priority = TC_H_MAKE(1, mask)

ctx:action(action.ACT_OK)
end

tc.attach(qos)

14 changes: 14 additions & 0 deletions examples/sniclassify/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>
# 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

Loading