Lunatik

repository·master·Indexed 20 days ago

https://github.com/luainkernel/lunatik

A framework for scripting the Linux kernel using a modified Lua 5.5 interpreter. It provides Lua APIs to interact with kernel facilities including networking, sockets, device drivers, kprobes, and XDP. Supports Linux Kernel versions 5.15 and later. Includes a CLI tool for managing kernel modules and runtime environments (softirq and hardirq contexts).

Tokens
9.8K
Snippets
29
Records
38
Agent score
70%

What's inside Lunatik

  1. Configure Lunatik object behavior with lunatik_opt_t flags

    master

    Behavioral flags for Lunatik objects are set via lunatik_opt_t. These flags can be defined at the class level (inherited by all instances) or at the instance level. They are categorized into constraints (which cannot be overridden) and capabilities.

    Constraints (Always Inherited)

    • LUNATIK_OPT_SOFTIRQ: Instances use spin_lock_bh and GFP_ATOMIC. Use for handlers in softirq context (e.g., netfilter, XDP).
    • LUNATIK_OPT_HARDIRQ: Instances use spin_lock_irqsave. Required for hardirq context (e.g., kprobes).
    • LUNATIK_OPT_SINGLE: Instances are private and non-shareable. This cancels LUNATIK_OPT_MONITOR inheritance.
    • LUNATIK_OPT_EXTERNAL: The object->private pointer is external; Lunatik will not free it on release.

    Capabilities

    • LUNATIK_OPT_MONITOR: Enables a monitored metatable that wraps Lua method calls with the object lock, allowing safe concurrent access from multiple runtimes.
  2. Manage object lifecycles with the Registry pattern

    master

    The registry pattern allows you to keep pre-allocated objects alive across multiple lunatik_run calls without exposing them to the Lua Garbage Collector (GC). This is useful for maintaining state in C between Lua execution cycles.

    Workflow:

    1. Registration (once): Use lunatik_attach to create an object, store it in the LUA_REGISTRYINDEX, and assign it to a field in a C object.
    2. Usage (on each callback): Use lunatik_getregistry to push the stored userdata onto the Lua stack, then convert it to a lunatik_object_t* using lunatik_toobject.
    3. Teardown: Use lunatik_detach to unregister the object and nullify the pointer.
    // Registration (once, at hook setup):
    lunatik_attach(L, obj, field, luafoo_new)   // creates object, stores in registry, sets obj->field
    
    // Use (on each callback):
    lunatik_getregistry(L, obj->field)          // pushes userdata
    lunatik_object_t *o = lunatik_toobject(L, -1);
    luafoo_reset(o, ...);                       // update the wrapped pointer
    
    // Teardown (on unregister):
    lunatik_detach(runtime, obj, field)         // unregisters and nulls obj->field
  3. Understand Lunatik's Lua environment limitations

    master

    Lunatik uses a modified Lua 5.5 kernel interpreter. Because it runs in the kernel, several standard Lua features are unavailable or modified:

    Unsupported Features

    • Floating-point arithmetic: Not supported. The number type only supports integers. Metamethods __div and __pow are unavailable.
    • os library: Not supported.
    • debug.debug: Not supported.
    • math library: Only integer operations are supported; floating-point functions are absent.
    • io library limitations:
      • No default streams (io.stdin, io.stdout, io.stderr).
      • No default I/O (io.read, io.write, io.input, io.output).
      • No process pipes (io.popen), temporary files (io.tmpfile), or buffering control (file:setvbuf).
      • Available: io.open, io.lines, io.type, and file handle methods read, write, lines, flush, seek, and close.
      • Errors always return "I/O error" regardless of the underlying errno.

    Modified Identifiers

    • _VERSION: Defined as "Lua 5.5-kernel".
    • collectgarbage("count"): Returns total memory in bytes (instead of Kbytes).
    • package.path: Set to "/lib/modules/lua/?.lua;/lib/modules/lua/?/init.lua".
    • require: Only supports built-in or already linked C modules. Dynamic loading of kernel modules via require is not supported.
  4. Manage Lunatik object lifecycles

    master

    Lunatik objects are special Lua userdata that manage a reference counter and a lock type.

    Creating Objects

    • lunatik_newobject(L, class, size, opt): Allocates a new object and pushes it onto the Lua stack. object->opt is opt | class->opt.
    • lunatik_createobject(class, size, opt): Creates an object independently of any Lua state. Useful for C-owned objects that will be shared with Lua later.

    Sharing C-owned objects with Lua

    To pass an object created via lunatik_createobject to a Lua runtime, use lunatik_cloneobject inside a Lua handler:

    // 1. Create in C
    obj = lunatik_createobject(&luafoo_class, sizeof(foo_t), LUNATIK_OPT_MONITOR);
    
    // 2. Run a handler to pass it to Lua
    lunatik_run(runtime, my_handler, ret, obj);
    
    // 3. Inside the Lua handler (C side)
    /* inside my_handler: */
    lunatik_cloneobject(L, obj);   // Pushes userdata, increments refcount

    Reference Counting

    • lunatik_getobject(object): Manually increments the reference counter.
    • lunatik_putobject(object): Decrements the reference counter. Returns 1 if the object was already released, 0 otherwise.
    /* Example: C-to-Lua object sharing pattern */
    obj = lunatik_createobject(&luafoo_class, sizeof(foo_t), LUNATIK_OPT_MONITOR);
    lunatik_run(runtime, my_handler, ret, obj);
    
    /* inside my_handler: */
    lunatik_cloneobject(L, obj);   /* pushes userdata, increments refcount */
    lunatik_getobject(obj);
  5. Define a Lunatik module with LUNATIK_CLASSES and LUNATIK_NEWLIB

    master

    To create a Lunatik module, you must define your Lua functions, your object classes, and export the module entry point.

    1. Define Classes with LUNATIK_CLASSES

    Use LUNATIK_CLASSES(name, ...) to create a static const lunatik_class_t * array. This macro automatically appends the required NULL sentinel.

    • name: The module name suffix (must match the libname in LUNATIK_NEWLIB).
    • ...: One or more const lunatik_class_t * pointers.

    Note: If you need conditional compilation (e.g., #if inside the array), you must define the array manually with a NULL terminator instead of using this macro.

    2. Export the Module with LUNATIK_NEWLIB

    Use LUNATIK_NEWLIB(libname, funcs, classes) to define the luaopen_<libname> entry point.

    • libname: The name used for require("<libname>").
    • funcs: A luaL_Reg[] array of the module's functions.
    • classes: The NULL-terminated array created by LUNATIK_CLASSES.

    Context Enforcement

    Modules can expose multiple classes with different execution contexts (e.g., LUNATIK_OPT_HARDIRQ vs process context). require will always succeed, but lunatik_newobject will only allow instantiation of classes that match the current runtime's context.

    // Example: Single class module
    static const luaL_Reg luafoo_lib[] = {
    	{"new", luafoo_new},
    	{NULL, NULL},
    };
    
    LUNATIK_CLASSES(foo, &luafoo_class);
    LUNATIK_NEWLIB(foo, luafoo_lib, luafoo_classes);
  6. Automate Lunatik installation on Debian kernel upgrades

    master

    To ensure Lunatik is automatically installed whenever the kernel is upgraded on a Debian-based system, copy the debian_kernel_postinst_lunatik.sh script into the /etc/kernel/postinst.d/ directory. It is recommended to name the destination file zz-update-lunatik to ensure it runs during the post-installation process.

    sudo cp debian_kernel_postinst_lunatik.sh /etc/kernel/postinst.d/zz-update-lunatik
    sudo chmod +x /etc/kernel/postinst.d/zz-update-lunatik
  7. Run Lunatik tests

    master

    You can run installed test suites using the lunatik test command. Running tests reloads the modules before execution and unloads them afterward to ensure the currently installed kernel code is exercised.

    Commands:

    • Run all suites: sudo lunatik test
    • Run a specific suite: sudo lunatik test <suite_name>

    Available suites: bpf, crypto, io, monitor, netlink, notifier, probe, rcu, runtime, set, skb, socket, struct, thread.

    sudo lunatik test
    sudo lunatik test thread
  8. Install Lunatik

    master

    To install Lunatik, you must first install the necessary system dependencies for your distribution, then clone the repository and compile it.

    Note: Lunatik supports Linux Kernel versions 5.15 and later.

    1. Install Dependencies

    Debian/Ubuntu:

    sudo apt install git build-essential lua5.4 dwarves clang llvm libelf-dev linux-headers-$(uname -r) linux-tools-common linux-tools-$(uname -r) pkg-config libpcap-dev m4

    Arch Linux:

    sudo pacman -S git lua clang llvm m4 libpcap pkg-config build2 linux-tools linux-headers

    Optional: Install lua-readline to enable line editing and command history in the REPL.

    2. Compile and Install

    LUNATIK_DIR=~/lunatik  # Adjust this path as needed
    mkdir "${LUNATIK_DIR}" ; cd "${LUNATIK_DIR}"
    git clone --depth 1 --recurse-submodules https://github.com/luainkernel/lunatik.git
    cd lunatik
    make
    sudo make install

    3. Kernel Upgrades

    To ensure Lunatik and XDP libraries are recompiled during a kernel upgrade, copy the debian_kernel_postinst_lunatik.sh script from the tools/ directory into /etc/kernel/postinst.d/.

    LUNATIK_DIR=~/lunatik
    mkdir "${LUNATIK_DIR}" ; cd "${LUNATIK_DIR}"
    git clone --depth 1 --recurse-submodules https://github.com/luainkernel/lunatik.git
    cd lunatik
    make
    sudo make install
  9. Install and run the filter (XDP/eBPF + Lua) example

    master

    The filter example is a kernel extension composed of an XDP/eBPF program (to filter HTTPS sessions) and a Lua kernel script (to filter SNI TLS extensions). It drops HTTPS requests to blacklisted servers.

    Prerequisites: You must install libbpf, libxdp, and xdp-loader from the xdp-tools repository.

    Setup and Execution:

    1. Build and install xdp-tools components.
    2. In the Lunatik directory, run sudo make btf_install to export the bpf_luaxdp_run kfunc.
    3. Install examples and build the eBPF program using make ebpf and sudo make ebpf_install.
    4. Run the Lua script in softirq context.
    5. Load the XDP program using xdp-loader.
    # 1. Install xdp-tools dependencies
    mkdir -p "${LUNATIK_DIR}" ; cd "${LUNATIK_DIR}"
    git clone --depth 1 --recurse-submodules https://github.com/xdp-project/xdp-tools.git
    cd xdp-tools/lib/libbpf/src
    make
    sudo DESTDIR=/ make install
    cd ../../../
    make libxdp
    cd xdp-loader
    make
    sudo make install
    
    # 2. Install and load filter
    cd ${LUNATIK_DIR}/lunatik
    sudo make btf_install
    sudo make examples_install
    make ebpf
    sudo make ebpf_install
    sudo lunatik run examples/filter/sni softirq
    sudo xdp-loader load -m skb <ifname> https.o
  10. Run the tap network sniffer example

    master

    The tap example implements a network sniffer using an AF_PACKET socket. It prints destination and source MAC addresses, Ethernet type, and frame size to a character device.

    sudo make examples_install    # installs examples
    sudo lunatik run examples/tap # runs tap
    cat /dev/tap
  11. Run the ifquarantine network policy example

    master

    The ifquarantine example demonstrates composing two notifier chains to build an interface-level default-deny policy. It uses a control runtime (process context) that owns notifier.netdevice and a device, which spawns a child softirq runtime containing a netfilter hook. They share a quarantine set via rcu.table through runtime:resume().

    To use it:

    1. Run the control runtime.
    2. Inspect interfaces via /dev/ifquarantine.
    3. Use allow=<name> or deny=<name> to manage quarantine status.
    4. Stop the runtimes with lunatik stop.
    sudo make examples_install                         # installs examples
    sudo lunatik run examples/ifquarantine/control     # starts control+filter
    sudo cat /dev/ifquarantine                         # lists known interfaces and verdict
    sudo sh -c "echo 'allow=eth0' > /dev/ifquarantine" # lift quarantine on eth0
    sudo sh -c "echo 'deny=eth0'  > /dev/ifquarantine" # re-apply quarantine
    sudo lunatik stop examples/ifquarantine/control     # stops both runtimes