Lunatik是用Lua脚本脚本脚本的框架。它是由修改以在内核中运行的LUA解释器组成的;设备驱动程序(用LUA =)和一个命令行工具加载和运行脚本并从用户空间管理运行时环境; C API加载和运行脚本并从内核管理运行时环境;和LUA API将内核设施绑定到LUA脚本。
这是使用lua使用lunatik编写的字符设备驱动程序的示例,以生成随机的ASCII可打印字符:
-- /lib/modules/lua/passwd.lua
--
-- implements /dev/passwd for generate passwords
-- usage: $ sudo lunatik run passwd
-- $ head -c <width> /dev/passwd
local device = require ( " device " )
local linux = require ( " linux " )
local function nop () end -- do nothing
local s = linux . stat
local driver = { name = " passwd " , open = nop , release = nop , mode = s . IRUGO }
function driver : read () -- read(2) callback
-- generate random ASCII printable characters
return string.char ( linux . random ( 32 , 126 ))
end
-- creates a new character device
device . new ( driver )安装依赖项(在这里为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编译并安装lunatik :
LUNATIK_DIR= ~ /lunatik # to be adapted
mkdir " ${LUNATIK_DIR} " ; cd " ${LUNATIK_DIR} "
git clone --depth 1 --recurse-submodules https://github.com/luainkernel/lunatik.git
cd lunatik
make
sudo make install Once done, the debian_kernel_postinst_lunatik.sh script from tools/ may be copied into /etc/kernel/postinst.d/ : this ensures lunatik (and also the xdp needed libs) will get compiled on kernel upgrade.
sudo lunatik # execute Lunatik REPL
Lunatik 3.5 Copyright (C) 2023-2024 ring-0 Ltda.
> return 42 -- execute this line in the kernel
42
usage: lunatik [load | unload | reload | status | list] [run | spawn | stop < script > ]load :加载Lunatik内核模块unload :卸载Lunatik内核模块reload :重新加载Lunatik内核模块status :显示当前加载了哪些Lunatik内核模块list :显示当前正在运行的运行时环境run :创建一个新的运行时环境来运行脚本/lib/modules/lua/<script>.lua lua/<script> .luaspawn :创建一个新的运行时环境,并产生线程以运行脚本/lib/modules/lua/<script>.lua lua/<script> .luastop :停止创建的运行时环境来运行脚本<script>default :启动一个repl(read – eval – Print循环) Lunatik 3.4基于LUA 5.4适用于内核运行。
Lunatik does not support floating-point arithmetic, thus it does not support __div nor __pow metamethods and the type number has only the subtype integer .
Lunatik不支持IO和OS库,以及以下库中给定的标识符:
Lunatik修改以下标识符:
"Lua 5.4-kernel" 。"/lib/modules/lua/?.lua;/lib/modules/lua/?/init.lua" 。lunatik不支持lual_stream,lual_execresult,lual_fileresult,luaopen_io和luaopen_os。
lunatik修改了lual_openlibs以删除luaopen_io和luaopen_os。
#include <lunatik.h> int lunatik_runtime ( lunatik_object_t * * pruntime , const char * script , bool sleep ); lunatik_runtime() creates a new runtime environment then loads and runs the script /lib/modules/lua/<script>.lua as the entry point for this environment.必须仅从过程上下文中调用它。 runtime环境是持有LUA状态的Lunatik对象。 Lunatik objects are special Lua userdata which also hold a lock type and a reference counter. If sleep is true , lunatik_runtime() will use a mutex for locking the runtime environment and the GFP_KERNEL flag for allocating new memory later on on lunatik_run() calls. Otherwise, it will use a spinlock and GFP_ATOMIC. lunatik_runtime()打开Lua标准库。 If successful, lunatik_runtime() sets the address pointed by pruntime and Lua's extra space with a pointer for the new created runtime environment, sets the reference counter to 1 and then returns 0 . Otherwise, it returns -ENOMEM , if insufficient memory is available;或-EINVAL ,如果未能加载或运行script 。
-- /lib/modules/lua/mydevice.lua
function myread ( len , off )
return " 42 "
end static lunatik_object_t * runtime ;
static int __init mydevice_init ( void )
{
return lunatik_runtime ( & runtime , "mydevice" , true);
} int lunatik_stop ( lunatik_object_t * runtime ); lunatik_stop() closes the Lua state created for this runtime environment and decrements the reference counter.一旦将参考计数器降低至零,则会发布为runtime环境分配的锁定类型和内存。如果发布了runtime环境,则返回1 ;否则,它将返回0 。
void lunatik_run ( lunatik_object_t * runtime , < inttype > ( * handler )(...), < inttype > & ret , ...); lunatik_run() locks the runtime environment and calls the handler passing the associated Lua state as the first argument followed by the variadic arguments.如果LUA状态已关闭,则将ret设置为-ENXIO ;否则, ret设置为handler(L, ...)调用的结果。然后,它还原Lua堆栈并解锁runtime环境。 It is defined as a macro.
static int l_read ( lua_State * L , char * buf , size_t len , loff_t * off )
{
size_t llen ;
const char * lbuf ;
lua_getglobal ( L , "myread" );
lua_pushinteger ( L , len );
lua_pushinteger ( L , * off );
if ( lua_pcall ( L , 2 , 2 , 0 ) != LUA_OK ) { /* calls myread(len, off) */
pr_err ( "%sn" , lua_tostring ( L , -1 ));
return - ECANCELED ;
}
lbuf = lua_tolstring ( L , -2 , & llen );
llen = min ( len , llen );
if ( copy_to_user ( buf , lbuf , llen ) != 0 )
return - EFAULT ;
* off = ( loff_t ) luaL_optinteger ( L , -1 , * off + llen );
return ( ssize_t ) llen ;
}
static ssize_t mydevice_read ( struct file * f , char * buf , size_t len , loff_t * off )
{
ssize_t ret ;
lunatik_object_t * runtime = ( lunatik_object_t * ) f -> private_data ;
lunatik_run ( runtime , l_read , ret , buf , len , off );
return ret ;
} void lunatik_getobject ( lunatik_object_t * object ); lunatik_getObject()增加了此object的参考计数器(例如, runtime环境)。
int lunatik_putobject ( lunatik_object_t * object ); lunatik_putobject()降低了此object的参考计数器(例如, runtime环境)。如果object已释放,则返回1 ;否则,它将返回0 。
lunatik_object_t * lunatik_toruntime ( lua_State * L ); lunatik_toruntime()返回L的额外空间引用的runtime环境。
lunatik库为加载和运行脚本并管理LUA的运行时环境提供了支持。
lunatik.runtime(script [, sleep]) lunatik.runtime() creates a new runtime environment then loads and runs the script /lib/modules/lua/<script>.lua as the entry point for this environment. It returns a Lunatik object representing the runtime environment. If sleep is true or omitted, it will use a mutex and GFP_KERNEL; otherwise, it will use a spinlock and GFP_ATOMIC. Lunatik.runtime()打开Lua标准库在Lunatik上。
runtime:stop() runtime:stop() stops the runtime environment and clear its reference from the runtime object.
runtime:resume([obj1, ...])运行时:简历()恢复执行runtime 。值obj1, ...作为参数传递给runtime创建返回的函数。 If the runtime has yielded, resume() restarts it;值obj1, ...值作为产量的结果传递。
device库为LUA中的字符设备驱动程序提供支持。
device.new(driver) device.new()返回一个新的device对象并将其driver安装在系统中。 driver必须定义为包含以下字段的表:
name : string defining the device name; it is used for creating the device file (eg, /dev/<name> ). driver表可能会选择包含以下字段:
read : callback function to handle the read operation on the device file.它接收driver表作为第一个参数,然后是两个整数,要读取的length和文件offset 。它应该返回字符串,并选择updated offset 。如果返回的字符串的长度大于请求的length ,则将校正该字符串为该length 。如果未返回updated offset ,则将使用offset + length更新offset 。write : callback function to handle the write operation on the device file.它接收driver表作为第一个参数,然后是要编写的字符串和整数作为文件offset 。它可能会选择返回书面length ,然后再返回updated offset 。如果返回的长度大于请求的length ,则将纠正返回的长度。如果未返回updated offset ,则将使用offset + length更新offset 。open : callback function to handle the open operation on the device file.它收到driver表,预计什么也不会返回。release : callback function to handle the release operation on the device file.它收到driver表,预计什么也不会返回。mode :指定设备文件模式的整数。 If an operation callback is not defined, the device returns -ENXIO to VFS on its access.
device.stop(dev) , dev:stop() device.stop() removes a device driver specified by the dev object from the system.
linux库为某些Linux内核设施提供了支持。
linux.random([m [, n]])linux.random()模仿Math.random的行为,但绑定<linux/andural.h>的get_random_u32()和get_random_u64()apis。
当无参数调用时,会随机产生一个具有所有位(伪)的整数。当使用两个整数m和n调用时, Linux.random()将返回一个伪随机整数,范围内具有均匀分布[m, n] 。对于正n ,呼叫math.random(n)等同于math.random(1, n) 。
linux.statLinux.Stat是一张表,将<linux/stat.h>整数标志导出到LUA。
"IRWXUGO" : permission to read , write and execute for user , group and other ."IRUGO" : permission only to read for user , group and other ."IWUGO" : permission only to write for user , group and other ."IXUGO" :仅对用户,组和其他人执行的权限。 linux.schedule([timeout [, state]]) linux.schedule() sets the current task state and makes the it sleep until timeout milliseconds have elapsed. If timeout is omitted, it uses MAX_SCHEDULE_TIMEOUT .如果省略state ,它使用task.INTERRUPTIBLE 。
linux.taskLinux.Task是一张将任务状态标志导出到LUA的表。
"RUNNING" :任务是在CPU上执行或等待执行。"INTERRUPTIBLE" :任务正在等待信号或资源(睡眠)。"UNINTERRUPTIBLE" :表现为“可中断”,但信号不会唤醒任务。"KILLABLE" :表现为“不间断”,但致命信号将唤醒任务。"IDLE" :表现为“不间断”,但它避免了LoadAvg会计。 linux.time()Linux.Time()自时代以来返回纳秒秒的当前时间。
linux.errnolinux.errno是一张表,该表导出<uapi/asm-generic/errno-base.h> flags flags。
"PERM" :不允许操作。"NOENT" :没有此类文件或目录。"SRCH" :没有这样的过程。"INTR" :中断的系统调用。"IO" :I/O错误。"NXIO" :没有这样的设备或地址。"2BIG" :,参数列表太长。"NOEXEC" :exec格式错误。"BADF" :坏文件编号。"CHILD" :没有孩子的过程。"AGAIN" :重试。"NOMEM" :不记忆。"ACCES" :拒绝许可。"FAULT" :不良地址。"NOTBLK" :需要块设备。"BUSY" :设备或资源忙。"EXIST" :文件存在。"XDEV" :跨设备链接。"NODEV" :没有这样的设备。"NOTDIR" :不是目录。"ISDIR" :是一个目录。"INVAL" :无效的参数。"NFILE" :文件表溢出。"MFILE" :太多的打开文件。"NOTTY" :不是打字机。"TXTBSY" :文本文件忙。"FBIG" :文件太大。"NOSPC" :设备上没有剩余的空间。"SPIPE" :非法寻求。"ROFS" :仅读取文件系统。"MLINK" :链接太多。"PIPE" :折断的管道。"DOM" :来自Func领域的数学论点。"RANGE" :数学结果不可代表。 linux.hton16(num)Linux.hton16()将主体字节订单转换为16位整数的网络字节订单。
linux.hton32(num)Linux.hton32()将主体字节订单转换为32位整数的网络字节订单。
linux.hton64(num)Linux.hton64()将主体字节订单转换为64位整数的网络字节订单。
linux.ntoh16(num)linux.ntoh16()将网络字节订单转换为托管字节订单16位整数。
linux.ntoh32(num)Linux.NTOH32()将网络字节订单转换为托管32位整数的字节订单。
linux.ntoh64(num)Linux.NTOH64()将网络字节订单转换为托管字节订单64位整数。
linux.htobe16(num)linux.htobe16()将主体字节订单转换为16位整数的大型字节订单。
linux.htobe32(num)linux.htobe32()将主体字节顺序转换为32位整数的大型字节订单。
linux.htobe64(num)Linux.htobe64()将主体字节订单转换为64位整数的大型字节订单。
linux.be16toh(num)linux.be16toh()将大端字节订单转换为16位整数的主字节订单。
linux.be32toh(num)linux.be32toh()将大型字节订单转换为32位整数的主字节订单。
linux.be64toh(num)linux.be64toh()将大型字节订单转换为64位整数托管字节订单。
linux.htole16(num)linux.htole16()将主体字节订单转换为16位整数的Little-Endian字节订单。
linux.htole32(num)linux.htole32()将主体字节订单转换为32位整数的Little-Endian字节订单。
linux.htole64(num)linux.htole64()将主体字节订单转换为64位整数的Little-Endian字节订单。
linux.le16toh(num)linux.le16toh()将小型字节订单转换为16位整数的主字节订单。
linux.le32toh(num)linux.le32toh()将小型字节订单转换为32位整数托管字节订单。
linux.le64toh(num)linux.le64toh()将小型字节订单转换为64位整数托管字节订单。
notifier库为内核通知链提供了支持。
notifier.keyboard(callback) notifier.keyboard()返回一个新的键盘notifier对象并将其安装在系统中。每当发生控制台键盘事件时(例如,已按下或释放键)时,调用callback函数。此callback收到以下参数:
event :可用事件由notifier.kbd表定义。down : true ,如果按下键; false ,如果发布。shift : true ,如果持有换档密钥; false ,否则。key :根据event的不同,密钥代码或密钥。 callback函数可能会返回notifier.notify表所定义的值。
notifier.kbdnotifier.kbd是一张将KBD标志导出到LUA的表。
"KEYCODE" :键盘键代码,在其他任何内容之前。"UNBOUND_KEYCODE" :键盘键盘未绑定到任何其他键盘。"UNICODE" :键盘Unicode。"KEYSYM" :键盘键。"POST_KEYSYM" :在键盘键键解释后调用。 notifier.netdevice(callback) notifier.netdevice()返回新的NetDevice notifier对象并将其安装在系统中。每当发生游戏机NetDevice事件发生时,都会调用callback函数(例如,已连接或断开网络接口)。此callback收到以下参数:
event :可用事件由notifier.netdev表定义。name :设备名称。 callback函数可能会返回notifier.notify表所定义的值。
notifier.netdevnotifier.netdev是一张将NetDev标志出口到LUA的表。
notifier.notifynotifier.notify是一张将通知标志导出到lua的表。
"DONE" :不在乎。"OK" :适合我。"BAD" :坏/否决动作。"STOP" :从通知者返回并停止进一步呼叫的清洁方法。 notfr:delete() notfr:delete()从系统中删除了notfr对象指定的notifier 。
socket库为内核网络处理提供了支持。该图书馆的灵感来自Chengzhi Tan的GSOC项目。
socket.new(family, type, protocol) socket.new()创建一个新的socket对象。此功能会收到以下参数:
family :可用的地址系列由套接字定义。sock :可用类型存在于插座表上。protocol :可用协议由socket.ipproto表定义。 socket.afsocket.af.af是一张桌子,该表将家族(AF)出口到LUA。
"UNSPEC" :未指定。"UNIX" :unix域插座。"LOCAL" :af_unix的POSIX名称。"INET" :Internet IP协议。"AX25" :业余无线电AX.25。"IPX" :Novell IPX。"APPLETALK" :appletalk ddp。"NETROM" :业余无线电网/ROM。"BRIDGE" :多协议桥。"ATMPVC" :ATM PVC。"X25" :保留用于X.25项目。"INET6" :IP版本6。"ROSE" :业余无线电X.25 PLP。"DEC" :保留用于DECNET项目。"NETBEUI" :保留给802.2LLC项目。"SECURITY" :安全回调伪AF。"KEY" :PF_KEY密钥管理API。"NETLINK" :Netlink。"ROUTE" :模仿4.4BSD的别名。"PACKET" :数据包家庭。"ASH" :灰。"ECONET" :Acorn Econet。"ATMSVC" :ATM SVCS。"RDS" :RDS插座。"SNA" :Linux SNA项目(Nutters!)。"IRDA" :IRDA插座。"PPPOX" :PPPOX插座。"WANPIPE" :wanpipe api插座。"LLC" :Linux LLC。"IB" :本地Infiniband地址。"MPLS" :MPLS。"CAN" :控制器区域网络。"TIPC" :TIPC插座。"BLUETOOTH" :蓝牙插座。"IUCV" :IUCV插座。"RXRPC" :RXRPC插座。"ISDN" :MISDN插座。"PHONET" :Phonet插座。"IEEE802154" :IEEE802154插座。"CAIF" :CAIF插座。"ALG" :算法插座。"NFC" :NFC插座。"VSOCK" :vsockets。"KCM" :内核连接多路复用器。"QIPCRTR" :高通IPC路由器。"SMC" :PF_SMC协议家族的储备编号,该家族重用AF_INET地址家庭。"XDP" :XDP插座。"MCTP" :管理组件传输协议。"MAX" :最大值。 socket.sockSocket.Sock是一张表格类型(袜子)的表:
"STREAM" :流(连接)插座。"DGRAM" : datagram (conn.less) socket."RAW" :原始插座。"RDM" :可靠交付的消息。"SEQPACKET" :顺序数据包套接字。"DCCP" :数据报拥塞控制协议套接字。"PACKET" :Linux在DEV级别获取数据包的特定方式。和标志(袜子):
"CLOEXEC" : n/a."NONBLOCK" : n/a. socket.ipprotosocket.ipproto是一张将IP协议(IPPROTO)导出到LUA的表。
"IP" :TCP的虚拟协议。"ICMP" :Internet控制消息协议。"IGMP" :Internet组管理协议。"IPIP" :IPIP隧道(较旧的KA9Q隧道使用94)。"TCP" :传输控制协议。"EGP" : Exterior Gateway Protocol."PUP" :PUP协议。"UDP" :用户数据报协议。"IDP" : XNS IDP protocol."TP" :因此运输协议第4类。"DCCP" : Datagram Congestion Control Protocol."IPV6" :IPv6-IN-IPV4隧道。"RSVP" :RSVP协议。"GRE" : Cisco GRE tunnels (rfc 1701,1702)."ESP" :封装安全有效负载协议。"AH" :身份验证标头协议。"MTP" :多播运输协议。"BEETPH" :甜菜的IP选项伪标头。"ENCAP" : Encapsulation Header."PIM" : Protocol Independent Multicast."COMP" : Compression Header Protocol."SCTP" :流控制传输协议。"UDPLITE" :udp-lite(RFC 3828)。"MPLS" :IP中的MPLS(RFC 4023)。"ETHERNET" : Ethernet-within-IPv6 Encapsulation."RAW" : Raw IP packets."MPTCP" :Multipath TCP连接。 sock:close() sock:close() removes sock object from the system.
sock:send(message, [addr [, port]]) sock:send() sends a string message through the socket sock .如果sock地址家族是af.INET ,那么它会期望以下论点:
addr : integer describing the destination IPv4 address.port :描述目标IPv4端口的integer 。否则:
addr : packed string describing the destination address. sock:receive(length, [flags [, from]])袜子:接收()通过插座sock接收一个具有length字节的字符串。 The available message flags are defined by the socket.msg table. If from is true , it returns the received message followed by the peer's address. Otherwise, it returns only the received message.
socket.msgsocket.msg is a table that exports message flags to Lua.
"OOB" :n/a。"PEEK" : n/a."DONTROUTE" : n/a."TRYHARD" :Decnet的"DONTROUTE"的同义词。"CTRUNC" : n/a."PROBE" :不要发送。 Only probe path fe for MTU."TRUNC" : n/a."DONTWAIT" : Nonblocking io."EOR" :记录结束。"WAITALL" : Wait for a full request."FIN" : n/a."SYN" : n/a."CONFIRM" : Confirm path validity."RST" : n/a."ERRQUEUE" :从错误队列中获取消息。"NOSIGNAL" :请勿生成sigpipe。"MORE" :发件人将发送更多。"WAITFORONE" : recvmmsg(): block until 1+ packets avail."SENDPAGE_NOPOLICY" : sendpage() internal: do no apply policy."SENDPAGE_NOTLAST" : sendpage() internal: not the last page."BATCH" :sendmmsg():更多消息即将到来。"EOF" :n/a。"NO_SHARED_FRAGS" : sendpage() internal: page frags are not shared."SENDPAGE_DECRYPTED" :sendpage()内部:页面可能携带纯文本并需要加密。"ZEROCOPY" : Use user data in kernel path."FASTOPEN" : Send data in TCP SYN."CMSG_CLOEXEC" : Set close_on_exec for file descriptor received through SCM_RIGHTS. sock:bind(addr [, port]) sock:bind() binds the socket sock to a given address.如果sock地址家族是af.INET ,那么它会期望以下论点:
addr :描述主机IPv4地址的integer 。port :描述主机IPv4端口的integer 。否则:
addr :描述主机地址的包装字符串。 sock:listen([backlog]) sock:listen() moves the socket sock to listening state.
backlog :等待连接队列大小。如果省略,它将SomaxConn用作默认值。 sock:accept([flags]) sock:accept() accepts a connection on socket sock . It returns a new socket object.可用标志存在于插座表上。
sock:connect(addr [, port] [, flags])袜子:Connect()将套筒sock连接到地址addr 。如果sock地址家族是af.INET ,那么它会期望以下论点:
addr : integer describing the destination IPv4 address.port :描述目标IPv4端口的integer 。否则:
addr : packed string describing the destination address.可用标志存在于插座表上。
对于数据报插座, addr是默认情况下发送数据报的地址,也是收到数据报的唯一地址。 For stream sockets, attempts to connect to addr .
sock:getsockname() sock:getsockname() get the address which the socket sock is bound.如果sock地址家族是af.INET ,则返回以下内容:
addr :描述有限的IPv4地址的integer 。port : integer describing the bounded IPv4 port.否则:
addr :描述有界地址的包装字符串。 sock:getpeername()袜子:getPeername()获取插座sock连接的地址。 If the sock address family is af.INET , then it returns the following:
addr :描述对等的IPv4地址的integer 。port :描述对等的IPv4端口的integer 。否则:
addr :描述对等地址的包装字符串。socket.inet库提供了对高级IPv4插座的支持。
inet.tcp() inet.tcp() creates a new socket using af.INET address family, sock.STREAM type and ipproto.TCP protocol.它覆盖了将地址用作数字和点符号(例如"127.0.0.1" )的socket方法,而不是整数。
inet.udp() inet.udp() creates a new socket using af.INET address family, sock.DGRAM type and ipproto.UDP protocol. It overrides socket methods to use addresses as numbers-and-dots notation (eg, "127.0.0.1" ), instead of integers.
udp:receivefrom(length [, flags]) udp:receerfrom()只是sock:receive(length, flags, true) 。
The rcu library provides support for the kernel Read-copy update (RCU) synchronization mechanism. This library was inspired by Caio Messias' GSoC project.
rcu.table([size]) rcu.table() creates a new rcu.table object which binds the kernel generic hash table.此函数作为参数接收到的框架数量达到2的下一个功率。默认大小为1024 。钥匙必须是字符串,值必须是lunatik对象或零。
The thread library provides support for the kernel thread primitives.
thread.run(runtime, name) thread.run() creates a new thread object and wakes it up. This function receives the following arguments:
runtime :用于在创建内核线程中运行任务的运行时环境。必须通过在runtime环境中加载的脚本上返回函数来指定任务。name :表示线程名称的字符串(例如,如ps所示)。 thread.shouldstop()如果thread.stop()调用thread.shouldstop(),则返回true ;否则,它返回false 。
thread.current() thread.current() returns a thread object representing the current task.
thrd:stop() thrd:stop()在螺纹上设置thread.shouldstop()thr thr thrd true,wakes thrd ,然后等待它退出。
thrd:task() thrd:task()返回包含此thread任务信息的表(例如,“ CPU”,“命令”,“ PID”和“ TGID”)。
fib库为内核转发信息库提供了支持。
fib.newrule(table, priority)fib.newrule()绑定内核fib_nl_newrule api;它创建了一个新的FIB规则,该规则将指定的路由表与指定的PrivimiTy匹配。此功能类似于用户空间命令IP规则添加IPROUTE2提供的添加。
fib.delrule(table, priority)fib.delrule()绑定内核fib_nl_delrule api;它删除了将指定路由表与指定的优先属性匹配的FIB规则。此功能类似于IPROUTE2提供的用户空间命令IP规则DEL。
data提供了将系统内存与LUA结合的支持。
data.new(size) data.new()创建一个新的data对象,该数据对象分配size字节。
d:getnumber(offset) D:getNumber()从data对象offset的内存中提取lua_integer,从零开始。
d:setnumber(offset, number) D:setNumber()将lua_integer number插入data对象引用的内存和字节offset ,从零开始。
d:getbyte(offset) D:getByte()从data对象引用的内存中提取字节和一个字节offset ,从零开始。
d:setbyte(offset, byte) D:setByte()将一个字节插入data对象引用的内存和一个字节offset ,从零开始。
d:getstring(offset[, length]) D:getString()从data对象offset的内存中提取带有length字节的字符串,从零开始。如果省略了length ,它将从data offset提取所有字节。
d:setstring(offset, s) D:setString()将字符串s插入data对象引用的内存和一个字节offset ,从零开始。
d:getint8(offset) D:getInt8(d,偏移)从data对象引用的内存和字节offset中提取一个符号的8位整数,从零开始。
d:setint8(offset, number) D:setInt8()将签名的8位编号插入data对象引用的内存和一个字节offset ,从零开始。
d:getuint8(offset) D:getUint8()从data对象引用的内存和字节offset中提取一个无符号的8位整数,从零开始。
d:setuint8(offset, number) D:setUint8()将一个未符号的8位编号插入data对象引用的内存和一个字节offset ,从零开始。
d:getint16(offset) D:GetInt16()从data对象引用的内存和字节offset从零开始提取签名的16位整数。
d:setint16(offset, number) D:setInt16()将签名的16位数字插入data对象引用的内存和一个字节offset ,从零开始。
d:getuint16(offset) D:getUint16()从data对象引用的内存和字节offset中提取一个未签名的16位整数,从零开始。
d:setuint16(offset, number) D:setUint16()将一个未签名的16位编号插入data对象引用的内存和一个字节offset ,从零开始。
d:getint32(offset) D:GetInt32()从data对象引用的内存和字节offset从零开始,从零开始提取一个符号的32位整数。
d:setint32(offset, number) D:setInt32()将签名的32位编号插入data对象引用的内存和一个字节offset ,从零开始。
d:getuint32(offset) D:getUint32()从data对象引用的内存和字节offset中提取一个无符号的32位整数,从零开始。
d:setuint32(offset, number) D:setUint32()将一个未签名的32位编号插入data对象引用的内存和一个字节offset ,从零开始。
d:getint64(offset) D:GetInt64()从data对象引用的内存和字节offset从零开始提取符号的64位整数。
d:setint64(offset, number) D:setInt64()将签名的64位编号插入data对象引用的内存和一个字节offset ,从零开始。
probe库提供了对内核探针的支持。
probe.new(symbol|address, handlers) probe.new() returns a new probe object for monitoring a kernel symbol (string) or address (light userdata) and installs its handlers in the system. handler必须定义为包含以下字段的表:
pre :在探测指令之前要调用功能。它接收symbol或address ,然后收到封闭式,可以打电话给CPU寄存器和堆叠系统日志中。post :探测指令后要调用的功能。它接收symbol或address ,然后收到封闭式,可以打电话给CPU寄存器和堆叠系统日志中。 p:stop() P:stop()删除系统中的probe处理程序。
p:enable(bool) P:Enable()启用或禁用probe处理程序,相应地bool 。
syscall库为系统呼叫地址和数字提供了支持。
syscall.address(number) syscall.address()返回给定number引用的系统呼叫地址(LIGHT USERDATA)。
syscall.number(name) syscall.number()返回给定name引用的系统呼叫号码。
syscall.table库提供了将系统呼叫名称转换为地址(Light userData)的支持。
xdp库为内核Express数据路径(XDP)子系统提供了支持。该图书馆的灵感来自Victor Nogueira的GSOC项目。
xdp.attach(callback) XDP.ATTACH()每当调用BPF_LUAXDP_RUN KFUNC时,将callback函数登录到当前runtime ,要从XDP/EBPF程序调用。此callback收到以下参数:
buffer :代表网络缓冲区的data对象。argument :包含XDP/EBPF程序传递的参数的data对象。 callback函数可能会返回XDP.Action表所定义的值。
xdp.detach() xdp.detach()取消注册与当前runtime相关联的callback (如果有)。
xdp.actionXDP.Action是将XDP_Action标志导出到LUA的表。
"ABORTED" :表明XDP程序流产,通常是由于错误。"DROP" :指定应该删除数据包,将其完全丢弃。"PASS" :允许数据包通过Linux网络堆栈。"TX" :将数据包在接收到的相同接口上传输。"REDIRECT" :将数据包重定向到另一个接口或处理上下文。 xtable库为开发NetFilter Xtable Extensions提供了支持。
xtable.match(opts)Xtable.match()返回一个新的Xtable对象,以进行匹配扩展。此功能会收到以下参数:
opts :包含以下字段的表:name :表示Xtable扩展名称的字符串。revision :代表Xtable扩展修订的整数。family :地址家庭,netfilter。proto :协议编号,socket.ipproto之一。hooks : hook to attach the extension to, one value from either of the hooks table - netfilter.inet_hooks, netfilter.bridge_hooks and netfilter.arp_hooks (Note: netfilter.netdev_hooks is not available for legacy x_tables). (例如1 << inet_hooks.LOCAL_OUT )。match :匹配数据包的功能。它收到以下论点:skb (ReadOnly):代表套接字缓冲区的data对象。par :包含hotdrop , thoff (传输标头偏移)和fragoff (片段偏移)字段的表。userargs :从用户空间Xtable模块传递的LUA字符串。true ;否则,它必须返回false 。checkentry :呼叫要检查条目的功能。此功能接收userargs作为参数。destroy :被要求销毁Xtable扩展的功能。此功能接收userargs作为参数。 xtable.target(opts)Xtable.target()返回一个新的Xtable对象以进行目标扩展。此功能会收到以下参数:
opts :包含以下字段的表:name :表示Xtable扩展名称的字符串。revision :代表Xtable扩展修订的整数。family :地址家庭,netfilter。proto :协议编号,socket.ipproto之一。hooks : hook to attach the extension to, one value from either of the hooks table - netfilter.inet_hooks, netfilter.bridge_hooks and netfilter.arp_hooks (Note: netfilter.netdev_hooks is not available for legacy x_tables). (例如1 << inet_hooks.LOCAL_OUT )。target :要调用定位数据包的功能。它收到以下论点:skb :代表套接字缓冲区的data对象。par (ROADONLY):包含hotdrop , thoff (传输标头偏移)和fragoff (片段偏移)字段的表。userargs :从用户空间Xtable模块传递的LUA字符串。checkentry :呼叫要检查条目的功能。此功能接收userargs作为参数。destroy :被要求销毁Xtable扩展的功能。此功能接收userargs作为参数。 netfilter库为新的NetFilter钩系统提供了支持。
netfilter.register(ops) NetFilter.Register()在给定的ops表中注册了一个新的NetFilter钩子。 This function receives the following arguments:
ops : a table containing the following fields:pf : protocol family, one of netfilter.familyhooknum : hook to attach the filter to, one value from either of the hooks table - netfilter.inet_hooks, netfilter.bridge_hooks, netfilter.arp_hooks and netfilter.netdev_hooks. (Eg - inet_hooks.LOCAL_OUT + 11 ).priority :挂钩的优先级。 One of the values from the netfilter.ip_priority or netfilter.bridge_priority tables.hook :要调用钩子的函数。它收到以下论点:skb :代表套接字缓冲区的data对象。netfilter.familynetfilter.family is a table that exports address families to Lua.
"UNSPEC" : Unspecified."INET" : Internet Protocol version 4."IPV4" : Internet Protocol version 4."IPV6" : Internet Protocol version 6."ARP" : Address Resolution Protocol."NETDEV" : Device ingress and egress path"BRIDGE" : Ethernet Bridge. netfilter.actionnetfilter.action is a table that exports netfilter actions to Lua.
"DROP" : NF_DROP . The packet is dropped. It is not forwarded, processed, or seen by any other network layer."ACCEPT" : NF_ACCEPT . The packet is accepted and passed to the next step in the network processing chain."STOLEN" : NF_STOLEN . The packet is taken by the handler, and processing stops."QUEUE" : NF_QUEUE . The packet is queued for user-space processing."REPEAT" : NF_REPEAT . The packet is sent through the hook chain again."STOP" : NF_STOP . Processing of the packet stops."CONTINUE" : XT_CONTINUE . Return the packet should continue traversing the rules within the same table."RETURN" : XT_RETURN . Return the packet to the previous chain. netfilter.inet_hooksnetfilter.inet_hooks is a table that exports inet netfilter hooks to Lua.
"PRE_ROUTING" : NF_INET_PRE_ROUTING . The packet is received by the network stack."LOCAL_IN" : NF_INET_LOCAL_IN . The packet is destined for the local system."FORWARD" : NF_INET_FORWARD . The packet is to be forwarded to another host."LOCAL_OUT" : NF_INET_LOCAL_OUT . The packet is generated by the local system."POST_ROUTING" : NF_INET_POST_ROUTING . The packet is about to be sent out. netfilter.bridge_hooksnetfilter.bridge_hooks is a table that exports bridge netfilter hooks to Lua.
"PRE_ROUTING" : NF_BR_PRE_ROUTING . First hook invoked, runs before forward database is consulted."LOCAL_IN" : NF_BR_LOCAL_IN . Invoked for packets destined for the machine where the bridge was configured on."FORWARD" : NF_BR_FORWARD . Called for frames that are bridged to a different port of the same logical bridge device."LOCAL_OUT" : NF_BR_LOCAL_OUT . Called for locally originating packets that will be transmitted via the bridge."POST_ROUTING" : NF_BR_POST_ROUTING . Called for all locally generated packets and all bridged packets netfilter.arp_hooksnetfilter.arp_hooks is a table that exports arp netfilter hooks to Lua.
"IN" : NF_ARP_IN . The packet is received by the network stack."OUT" : NF_ARP_OUT . The packet is generated by the local system."FORWARD" : NF_ARP_FORWARD . The packet is to be forwarded to another host. netfilter.netdev_hooksnetfilter.netdev_hooks is a table that exports netdev netfilter hooks to Lua.
"INGRESS" : NF_NETDEV_INGRESS . The packet is received by the network stack."EGRESS" : NF_NETDEV_EGRESS . The packet is generated by the local system. netfilter.ip_prioritynetfilter.ip_priority is a table that exports netfilter IPv4/IPv6 priority levels to Lua.
"FIRST" : NF_IP_PRI_FIRST"RAW_BEFORE_DEFRAG" : NF_IP_PRI_RAW_BEFORE_DEFRAG"CONNTRACK_DEFRAG" : NF_IP_PRI_CONNTRACK_DEFRAG"RAW" : NF_IP_PRI_RAW"SELINUX_FIRST" : NF_IP_PRI_SELINUX_FIRST"CONNTRACK" : NF_IP_PRI_CONNTRACK"MANGLE" : NF_IP_PRI_MANGLE"NAT_DST" : NF_IP_PRI_NAT_DST"FILTER" : NF_IP_PRI_FILTER"SECURITY" : NF_IP_PRI_SECURITY"NAT_SRC" : NF_IP_PRI_NAT_SRC"SELINUX_LAST" : NF_IP_PRI_SELINUX_LAST"CONNTRACK_HELPER" : NF_IP_PRI_CONNTRACK_HELPER"LAST" : NF_IP_PRI_LAST netfilter.bridge_prioritynetfilter.bridge_priority is a table that exports netfilter bridge priority levels to Lua.
"FIRST" : NF_BR_PRI_FIRST"NAT_DST_BRIDGED" : NF_BR_PRI_NAT_DST_BRIDGED"FILTER_BRIDGED" : NF_BR_PRI_FILTER_BRIDGED"BRNF" : NF_BR_PRI_BRNF"NAT_DST_OTHER" : NF_BR_PRI_NAT_DST_OTHER"FILTER_OTHER" : NF_BR_PRI_FILTER_OTHER"NAT_SRC" : NF_BR_PRI_NAT_SRC"LAST" : NF_BR_PRI_LAST The luaxt userspace library provides support for generating userspace code for xtable extensions.
To build the library, the following steps are required:
usr/lib/xtable and create a libxt_<ext_name>.lua file.luaxt ) in the created file.LUAXTABLE_MODULE=<ext_name> make to build the extension and LUAXTABLE_MODULE=<ext_name> make install (as root) to install the userspace plugin to the system. Now load the extension normally using iptables .
luaxt.match(opts)luaxt.match() returns a new luaxt object for match extensions. This function receives the following arguments:
opts : a table containing the following fields:revision : integer representing the xtable extension revision ( must be same as used in corresponding kernel extension).family : address family, one of luaxt.familyhelp : function to be called for displaying help message for the extension.init : function to be called for initializing the extension. This function receives an par table that can be used to set userargs . ( par.userargs = "mydata" )print : function to be called for printing the arguments. This function recevies userargs set by the init or parse function.save : function to be called for saving the arguments. This function recevies userargs set by the init or parse function.parse : function to be called for parsing the command line arguments. This function receives an par table that can be used to set userargs and flags . ( par.userargs = "mydata" )final_check : function to be called for final checking of the arguments. This function receives flags set by the parse function. luaxt.target(opts)luaxt.target() returns a new luaxt object for target extensions. This function receives the following arguments:
opts : a table containing the following fields:revision : integer representing the xtable extension revision ( must be same as used in corresponding kernel extension).family : address family, one of luaxt.familyhelp : function to be called for displaying help message for the extension.init : function to be called for initializing the extension. This function receives an par table that can be used to set userargs . ( par.userargs = "mydata" )print : function to be called for printing the arguments. This function recevies userargs set by the init or parse function.save : function to be called for saving the arguments. This function recevies userargs set by the init or parse function.parse : function to be called for parsing the command line arguments. This function receives an par table that can be used to set userargs and flags . ( par.userargs = "mydata" )final_check : function to be called for final checking of the arguments. This function receives flags set by the parse function. luaxt.familyluaxt.family is a table that exports address families to Lua.
"UNSPEC" : Unspecified."INET" : Internet Protocol version 4."IPV4" : Internet Protocol version 4."IPV6" : Internet Protocol version 6."ARP" : Address Resolution Protocol."NETDEV" : Device ingress and egress path"BRIDGE" : Ethernet Bridge.completion The completion library provides support for the kernel completion primitives.
Task completion is a synchronization mechanism used to coordinate the execution of multiple threads, similar to pthread_barrier , it allows threads to wait for a specific event to occur before proceeding, ensuring certain tasks are complete in a race-free manner.
completion.new() completion.new() creates a new completion object.
c:complete()c:complete() signals a single thread waiting on this completion.
c:wait([timeout]) c:wait() waits for completion of a task until the specified timeout expires. The timeout is specified in milliseconds. If the timeout parameter is omitted, it waits indefinitely. Passing a timeout value less than zero results in undefined behavior. Threads waiting for events can be interrupted by signals, for example, such as when thread.stop is invoked. Therefore, this function can return in three ways:
truenil, "timeout"nil, "interrupt"spyglass is a kernel script that implements a keylogger inspired by the spy kernel module. This kernel script logs the keysym of the pressed keys in a device ( /dev/spyglass ). If the keysym is a printable character, spyglass logs the keysym itself; otherwise, it logs a mnemonic of the ASCII code, (eg, <del> stands for 127 ).
sudo make examples_install # installs examples
sudo lunatik run examples/spyglass # runs spyglass
sudo tail -f /dev/spyglass # prints the key log
sudo sh -c "echo 'enable=false' > /dev/spyglass" # disable the key logging
sudo sh -c "echo 'enable=true' > /dev/spyglass" # enable the key logging
sudo sh -c "echo 'net=127.0.0.1:1337' > /dev/spyglass" # enable network support
nc -lu 127.0.0.1 1337 & # listen to UDP 127.0.0.1:1337
sudo tail -f /dev/spyglass # sends the key log through the network
keylocker is a kernel script that implements Konami Code for locking and unlocking the console keyboard. When the user types ↑ ↑ ↓ ↓ ← → ← → LCTRL LALT , the keyboard will be locked ; that is, the system will stop processing any key pressed until the user types the same key sequence again.
sudo make examples_install # installs examples
sudo lunatik run examples/keylocker # runs keylocker
<↑> <↑> <↓> <↓> <←> <→> <←> <→> <LCTRL> <LALT> # locks keyboard
<↑> <↑> <↓> <↓> <←> <→> <←> <→> <LCTRL> <LALT> # unlocks keyboard
tap is a kernel script that implements a sniffer using AF_PACKET socket. It prints destination and source MAC addresses followed by Ethernet type and the frame size.
sudo make examples_install # installs examples
sudo lunatik run examples/tap # runs tap
cat /dev/tap
shared is a kernel script that implements an in-memory key-value store using rcu, data, socket and thread.
sudo make examples_install # installs examples
sudo lunatik spawn examples/shared # spawns shared
nc 127.0.0.1 90 # connects to shared
foo=bar # assigns "bar" to foo
foo # retrieves foo
bar
^C # finishes the connection
echod is an echo server implemented as kernel scripts.
sudo make examples_install # installs examples
sudo lunatik spawn examples/echod/daemon # runs echod
nc 127.0.0.1 1337
hello kernel!
hello kernel!
systrack is a kernel script that implements a device driver to monitor system calls. It prints the amount of times each system call was called since the driver has been installed.
sudo make examples_install # installs examples
sudo lunatik run examples/systrack # runs systracker
cat /dev/systrack
writev: 0
close: 1927
write: 1085
openat: 2036
read: 4131
readv: 0
filter is a kernel extension composed by a XDP/eBPF program to filter HTTPS sessions and a Lua kernel script to filter SNI TLS extension. This kernel extension drops any HTTPS request destinated to a blacklisted server.
Compile and install libbpf , libxdp and xdp-loader :
mkdir -p " ${LUNATIK_DIR} " ; cd " ${LUNATIK_DIR} " # LUNATIK_DIR must be set to the same value as above (Setup section)
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 installCome back to this repository, install and load the filter:
cd ${LUNATIK_DIR} /lunatik # cf. above
sudo make btf_install # needed to export the 'bpf_luaxdp_run' kfunc
sudo make examples_install # installs examples
make ebpf # builds the XDP/eBPF program
sudo make ebpf_install # installs the XDP/eBPF program
sudo lunatik run examples/filter/sni false # runs the Lua kernel script
sudo xdp-loader load -m skb < ifname > https.o # loads the XDP/eBPF programFor example, testing is easy thanks to docker. Assuming docker is installed and running:
sudo xdp-loader load -m skb docker0 https.o
sudo journalctl -ft kerneldocker run --rm -it alpine/curl https://ebpf.io The system logs (in the first terminal) should display filter_sni: ebpf.io DROP , and the docker run… should return curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to ebpf.io:443 .
This other sni filter uses netfilter api.
dnsblock is a kernel script that uses the lunatik xtable library to filter DNS packets. This script drops any outbound DNS packet with question matching the blacklist provided by the user.
sudo make examples_install # installs examples
cd examples/dnsblock
make # builds the userspace extension for netfilter
sudo make install # installs the extension to Xtables directory
sudo lunatik run examples/dnsblock/dnsblock false # runs the Lua kernel script
sudo iptables -A OUTPUT -m dnsblock -j DROP # this initiates the netfilter framework to load our extension
sudo make examples_install # installs examples
sudo lunatik run examples/dnsblock/nf_dnsblock false # runs the Lua kernel script
dnsdoctor is a kernel script that uses the lunatik xtable library to change the DNS response from Public IP to a Private IP if the destination IP matches the one provided by the user. For example, if the user wants to change the DNS response from 192.168.10.1 to 10.1.2.3 for the domain lunatik.com if the query is being sent to 10.1.1.2 (a private client), this script can be used.
sudo make examples_install # installs examples
cd examples/dnsdoctor
setup.sh # sets up the environment
# test the setup, a response with IP 192.168.10.1 should be returned
dig lunatik.com
# run the Lua kernel script
sudo lunatik run examples/dnsdoctor/dnsdoctor false
# build and install the userspace extension for netfilter
make
sudo make install
# add rule to the mangle table
sudo iptables -t mangle -A PREROUTING -p udp --sport 53 -j dnsdoctor
# test the setup, a response with IP 10.1.2.3 should be returned
dig lunatik.com
# cleanup
sudo iptables -t mangle -D PREROUTING -p udp --sport 53 -j dnsdoctor # remove the rule
sudo lunatik unload
cleanup.sh
sudo make examples_install # installs examples
examples/dnsdoctor/setup.sh # sets up the environment
# test the setup, a response with IP 192.168.10.1 should be returned
dig lunatik.com
# run the Lua kernel script
sudo lunatik run examples/dnsdoctor/nf_dnsdoctor false
# test the setup, a response with IP 10.1.2.3 should be returned
dig lunatik.com
# cleanup
sudo lunatik unload
examples/dnsdoctor/cleanup.sh
Lunatik is dual-licensed under MIT or GPL-2.0-only.
Lua submodule is licensed under MIT. For more details, see its Copyright Notice.
Klibc submodule is dual-licensed under BSD 3-Clause or GPL-2.0-only. For more details, see its LICENCE file.