From d34efcea2debdfe525b6b15fa4c90895ef1a2420 Mon Sep 17 00:00:00 2001 From: Mohammad Shehar Yaar Tausif Date: Mon, 29 Jul 2024 18:13:28 +0530 Subject: [PATCH 1/3] add userspace library skeleton Signed-off-by: Mohammad Shehar Yaar Tausif --- README.md | 53 +++++++++++ usr/lib/xtable/Makefile | 29 ++++++ usr/lib/xtable/luaxt.c | 206 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 usr/lib/xtable/Makefile create mode 100644 usr/lib/xtable/luaxt.c diff --git a/README.md b/README.md index 37c2d21c..9f631e28 100644 --- a/README.md +++ b/README.md @@ -1208,6 +1208,59 @@ netfilter hooks to Lua. * `"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. +### luaxt + +The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/). The user can modify the generated lua code to implement the userspace handlers for the corresponding xtable extension. + +To generate the library, the following steps are required: + +1. Go to `usr/lib/xtable` and create a `libxt_.lua` file. +2. Register your callbacks for the xtable extension by importing the library (`luaxt`) in the created file. +3. Run `LUAXTABLE_MODULE= make` to build the extension and `LUAXTABLE_MODULE= 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](https://inai.de/projects/xtables-addons/) 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.family](https://github.com/luainkernel/lunatik#luaxtfamily) + * `help`: function to be called for displaying help message for the extension. + * `init`: function to be called for initializing the extension. + * `print`: function to be called for printing the arguments. + * `save`: function to be called for saving the arguments. + * `parse`: function to be called for parsing the command line arguments. + * `final_check`: function to be called for final checking of the arguments. + +#### `luaxt.target(opts)` + +_luaxt.target()_ returns a new [luaxt](https://inai.de/projects/xtables-addons/) 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.family](https://github.com/luainkernel/lunatik#luaxtfamily) + * `help`: function to be called for displaying help message for the extension. + * `init`: function to be called for initializing the extension. + * `print`: function to be called for printing the arguments. + * `save`: function to be called for saving the arguments. + * `parse`: function to be called for parsing the command line arguments. + * `final_check`: function to be called for final checking of the arguments. + +#### `luaxt.family` + +_luaxt.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. + # Examples ### spyglass diff --git a/usr/lib/xtable/Makefile b/usr/lib/xtable/Makefile new file mode 100644 index 00000000..8e20c13c --- /dev/null +++ b/usr/lib/xtable/Makefile @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +# SPDX-License-Identifier: MIT OR GPL-2.0-only + +ifndef LUAXTABLE_MODULE +$(error LUAXTABLE_MODULE is not defined. Run LUAXTABLE_MODULE= make) +endif + +CFLAGS = -DLUAXTABLE_MODULE=\"${LUAXTABLE_MODULE}\" -O2 -Wall -I../../../lib +XTABLES_SO_DIR = $(shell pkg-config xtables --variable xtlibdir) +LUA_FLAGS = -llua -lm + +all: + make libxt_${LUAXTABLE_MODULE}.so + +install: + sudo cp libxt_*.so ${XTABLES_SO_DIR} + +uninstall: + sudo rm -f ${XTABLES_SO_DIR}/libxt_${LUAXTABLE_MODULE}.so + +clean: + rm -f libxt_*.so libxt_*.o + +lib%.so: lib%.o + gcc -shared -fPIC -o libxt_${LUAXTABLE_MODULE}.so libxt_${LUAXTABLE_MODULE}.o ${LUA_FLAGS}; + +lib%.o: luaxt.c + gcc ${CFLAGS} ${LUA_FLAGS} -D_INIT=lib$*_init -fPIC -c -o libxt_${LUAXTABLE_MODULE}.o $<; + diff --git a/usr/lib/xtable/luaxt.c b/usr/lib/xtable/luaxt.c new file mode 100644 index 00000000..2e1713c0 --- /dev/null +++ b/usr/lib/xtable/luaxt.c @@ -0,0 +1,206 @@ +/* +* SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +* SPDX-License-Identifier: MIT OR GPL-2.0-only +*/ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "luaxtable.h" + +#ifndef LUAXTABLE_MODULE +#error "LUAXTABLE_MODULE is not defined" +#endif + +#define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) + +typedef struct luaxt_flags_s { + const char *name; + lua_Integer value; +} luaxt_flags_t; + +static lua_State *L = NULL; +static int luaopen_luaxt(lua_State *L); +static int luaxt_match(lua_State *L); +static int luaxt_target(lua_State *L); + +static inline int luaxt_run(lua_State *L, const char *func_name, const char *key, int nargs, int nresults) +{ + int ret = 0; + int base = lua_gettop(L) - nargs; + lua_rawgetp(L, LUA_REGISTRYINDEX, key); + + if (lua_getfield(L, -1, func_name) != LUA_TFUNCTION) { + pr_err("Function %s not found\n", func_name); + goto restore; + } + + lua_insert(L, base + 1); /* func */ + lua_pop(L, 1); /* table */ + + if (lua_pcall(L, nargs, nresults, 0) != LUA_OK) { + pr_err("Failed to call Lua function %s: %s\n", func_name, + lua_tostring(L, -1)); + goto restore; + } + + if (nresults == 1) + ret = lua_toboolean(L, -1); + +restore: + lua_settop(L, base); + return ret; +} + +#define LUAXT_HELPER_CB(hook) \ +static void luaxt_##hook##_help(void) \ +{ \ + luaxt_run(L, "help", (void *)luaxt_##hook, 0, 0); \ +} + +#define LUAXT_INITER_CB(hook) \ +static void luaxt_##hook##_init(struct xt_entry_##hook *hook) \ +{ \ + luaxt_run(L, "init", (void *)luaxt_##hook, 1, 0); \ +} + +#define LUAXT_PARSER_CB(hook) \ +static int luaxt_##hook##_parse(int c, char **argv, int invert, unsigned int *flags, \ + const void *entry, struct xt_entry_##hook **hook) \ +{ \ + return luaxt_run(L, "parse", (void *)luaxt_##hook, 0, 1); \ +} + +#define LUAXT_FINALCHECKER_CB(hook) \ +static void luaxt_##hook##_finalcheck(unsigned int flags) \ +{ \ + luaxt_run(L, "final_check", (void *)luaxt_##hook, 0, 0); \ +} + +#define LUAXT_PRINTER_CB(hook) \ +static void luaxt_##hook##_print(const void *entry, const struct xt_entry_##hook *hook, int numeric) \ +{ \ + luaxt_run(L, "print", (void *)luaxt_##hook, 0, 0); \ +} + +#define LUAXT_SAVER_CB(hook) \ +static void luaxt_##hook##_save(const void *entry, const struct xt_entry_##hook *hook) \ +{ \ + luaxt_run(L, "save", (void *)luaxt_##hook, 0, 0); \ +} + +#define LUAXT_NEWCB(hook) \ + LUAXT_HELPER_CB(hook) \ + LUAXT_INITER_CB(hook) \ + LUAXT_PARSER_CB(hook) \ + LUAXT_FINALCHECKER_CB(hook) \ + LUAXT_PRINTER_CB(hook) \ + LUAXT_SAVER_CB(hook) + +LUAXT_NEWCB(match); +LUAXT_NEWCB(target); + +#define LUAXT_NEWREG(hook) \ +static struct xtables_##hook luaxt_##hook##_reg = { \ + .version = XTABLES_VERSION, \ + .name = LUAXTABLE_MODULE, \ + .size = XT_ALIGN(sizeof(luaxtable_info_t)), \ + .userspacesize = 0, \ + .help = luaxt_##hook##_help, \ + .init = luaxt_##hook##_init, \ + .parse = luaxt_##hook##_parse, \ + .final_check = luaxt_##hook##_finalcheck, \ + .print = luaxt_##hook##_print, \ + .save = luaxt_##hook##_save \ +} + +LUAXT_NEWREG(match); +LUAXT_NEWREG(target); + +static inline int luaxt_checkint(lua_State *L, int idx, const char *key) +{ + if (lua_getfield(L, idx, key) != LUA_TNUMBER) { + pr_err("invalid \'%s\' in ops\n", key); + return 0; + } + int ret = lua_tointeger(L, -1); + lua_pop(L, 1); + return ret; +} + +#define LUAXT_LIB_CB(hook) \ +static int luaxt_##hook(lua_State *L) \ +{ \ + luaL_checktype(L, 1, LUA_TTABLE); \ + \ + luaxt_##hook##_reg.revision = luaxt_checkint(L, 1, "revision"); \ + luaxt_##hook##_reg.family = luaxt_checkint(L, 1, "family"); \ + xtables_register_##hook(&luaxt_##hook##_reg); \ + lua_pushvalue(L, 1); \ + lua_rawsetp(L, LUA_REGISTRYINDEX, luaxt_##hook); \ + return 0; \ +} + +LUAXT_LIB_CB(match); +LUAXT_LIB_CB(target); + +static const luaL_Reg luaxt_lib[] = { + {"match", luaxt_match}, + {"target", luaxt_target}, + {NULL, NULL} +}; + +static const luaxt_flags_t luaxt_family[] = { + {"UNSPEC", NFPROTO_UNSPEC}, + {"INET", NFPROTO_INET}, + {"IPV4", NFPROTO_IPV4}, + {"IPV6", NFPROTO_IPV6}, + {"ARP", NFPROTO_ARP}, + {"NETDEV", NFPROTO_NETDEV}, + {"BRIDGE", NFPROTO_BRIDGE}, + {NULL, 0} +}; + +static int luaopen_luaxt(lua_State *L) +{ + const luaxt_flags_t *flag; + luaL_newlib(L, luaxt_lib); + lua_newtable(L); + for (flag = luaxt_family; flag->name; flag++) { + lua_pushinteger(L, flag->value); + lua_setfield(L, -2, flag->name); /* namespace[name] = value */ + } + lua_setfield(L, -2, "family"); /* lib.namespace = namespace */ + return 1; +} + +static int __attribute__((constructor)) _init(void) +{ + if ((L = luaL_newstate()) == NULL) { + pr_err("Failed to create Lua state\n"); + return -ENOMEM; + } + luaL_openlibs(L); + luaL_requiref(L, "luaxt", luaopen_luaxt, 1); + + if (luaL_dofile(L, "libxt_"LUAXTABLE_MODULE".lua") != LUA_OK) { + pr_err("Failed to load Lua script: %s\n", lua_tostring(L, -1)); + return -ENOENT; + } + return 0; +} + +static void __attribute__((destructor)) _fini(void) +{ + if (L != NULL) + lua_close(L); +} + From e1de16f3da9631236845793397830c2bb8e37a0d Mon Sep 17 00:00:00 2001 From: Mohammad Shehar Yaar Tausif Date: Fri, 9 Aug 2024 17:06:54 +0530 Subject: [PATCH 2/3] add userdata passing from userspace to kernel mod Signed-off-by: Mohammad Shehar Yaar Tausif --- README.md | 30 +++++++++++++------------ lib/luaxtable.c | 17 +++++++------- lib/luaxtable.h | 3 +++ usr/lib/xtable/luaxt.c | 50 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9f631e28..d1de738e 100644 --- a/README.md +++ b/README.md @@ -1149,9 +1149,10 @@ This function receives the following arguments: * `match` : function to be called for matching packets. It receives the following arguments: * `skb` (readonly): a `data` object representing the socket buffer. * `par`: a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields. + * `userdata` : a lua string passed from the userspace xtable module. * The function must return `true` if the packet matches the extension; otherwise, it must return `false`. - * `checkentry`: function to be called for checking the entry. - * `destroy`: function to be called for destroying the xtable extension. + * `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument. + * `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument. #### `xtable.target(opts)` @@ -1166,9 +1167,10 @@ This function receives the following arguments: * `target` : function to be called for targeting packets. It receives the following arguments: * `skb`: a `data` object representing the socket buffer. * `par` (readonly): a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields. + * `userdata` : a lua string passed from the userspace xtable module. * The function must return one of the values defined by the [xtable.action](https://github.com/luainkernel/lunatik#xtableaction) table. - * `checkentry`: function to be called for checking the entry. - * `destroy`: function to be called for destroying the xtable extension. + * `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument. + * `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument. #### `xtable.family` @@ -1228,11 +1230,11 @@ This function receives the following arguments: * `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension). * `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily) * `help`: function to be called for displaying help message for the extension. - * `init`: function to be called for initializing the extension. - * `print`: function to be called for printing the arguments. - * `save`: function to be called for saving the arguments. - * `parse`: function to be called for parsing the command line arguments. - * `final_check`: function to be called for final checking of the arguments. + * `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userdata`. (`par.userdata = "mydata"`) + * `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function. + * `save`: function to be called for saving the arguments. This function recevies `userdata` 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 `userdata` and `flags`. (`par.userdata = "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)` @@ -1242,11 +1244,11 @@ This function receives the following arguments: * `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension). * `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily) * `help`: function to be called for displaying help message for the extension. - * `init`: function to be called for initializing the extension. - * `print`: function to be called for printing the arguments. - * `save`: function to be called for saving the arguments. - * `parse`: function to be called for parsing the command line arguments. - * `final_check`: function to be called for final checking of the arguments. + * `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userdata`. (`par.userdata = "mydata"`) + * `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function. + * `save`: function to be called for saving the arguments. This function recevies `userdata` 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 `userdata` and `flags`. (`par.userdata = "mydata"`) + * `final_check`: function to be called for final checking of the arguments. This function receives `flags` set by the `parse` function. #### `luaxt.family` diff --git a/lib/luaxtable.c b/lib/luaxtable.c index 8c0e23eb..9c6649f6 100644 --- a/lib/luaxtable.c +++ b/lib/luaxtable.c @@ -40,7 +40,7 @@ static struct { unsigned int target_fallback; } luaxtable_hooks = {NULL, NULL, false, XT_CONTINUE}; -static int luaxtable_docall(lua_State *L, luaxtable_t *xtable, const char *op, int nargs, int nret) +static int luaxtable_docall(lua_State *L, luaxtable_t *xtable, luaxtable_info_t *info, const char *op, int nargs, int nret) { int base = lua_gettop(L) - nargs; @@ -56,8 +56,9 @@ static int luaxtable_docall(lua_State *L, luaxtable_t *xtable, const char *op, i lua_insert(L, base + 1); /* op */ lua_pop(L, 1); /* table */ + lua_pushstring(L, info->userdata); /* userdata */ - if (lua_pcall(L, nargs, nret, 0) != LUA_OK) { + if (lua_pcall(L, nargs + 1, nret, 0) != LUA_OK) { pr_err("%s error: %s\n", op, lua_tostring(L, -1)); goto err; } @@ -101,12 +102,12 @@ static int luaxtable_pushparams(lua_State *L, const struct xt_action_param *par, return 0; } -#define luaxtable_call(L, op, xtable, skb, par, opt) \ - ((luaxtable_pushparams(L, par, xtable, skb, opt) == -1) || (luaxtable_docall(L, xtable, op, 2, 1) == -1)) +#define luaxtable_call(L, op, xtable, skb, par, info, opt) \ + ((luaxtable_pushparams(L, par, xtable, skb, opt) == -1) || (luaxtable_docall(L, xtable, info, op, 2, 1) == -1)) static int luaxtable_domatch(lua_State *L, luaxtable_t *xtable, const struct sk_buff *skb, struct xt_action_param *par, int fallback) { - if (luaxtable_call(L, "match", xtable, (struct sk_buff *)skb, par, LUADATA_OPT_READONLY) != 0) + if (luaxtable_call(L, "match", xtable, (struct sk_buff *)skb, par, (luaxtable_info_t *)par->matchinfo, LUADATA_OPT_READONLY) != 0) return fallback; int ret = lua_toboolean(L, -1); @@ -117,7 +118,7 @@ static int luaxtable_domatch(lua_State *L, luaxtable_t *xtable, const struct sk_ static int luaxtable_dotarget(lua_State *L, luaxtable_t *xtable, struct sk_buff *skb, const struct xt_action_param *par, int fallback) { - if (luaxtable_call(L, "target", xtable, skb, par, LUADATA_OPT_NONE) != 0) + if (luaxtable_call(L, "target", xtable, skb, par, (luaxtable_info_t *)par->targinfo, LUADATA_OPT_NONE) != 0) return fallback; int ret = lua_tointeger(L, -1); @@ -149,7 +150,7 @@ static int luaxtable_##hook##_check(const struct xt_##hk##chk_param *par) \ luaxtable_info_t *info = (luaxtable_info_t *)par->huk##info; \ info->data = xtable; \ \ - lunatik_run(xtable->runtime, luaxtable_docall, ret, xtable, "checkentry", 0, 1); \ + lunatik_run(xtable->runtime, luaxtable_docall, ret, xtable, info, "checkentry", 0, 1); \ return ret != 0 ? -EINVAL : 0; \ } @@ -160,7 +161,7 @@ static void luaxtable_##hook##_destroy(const struct xt_##hk##dtor_param *par) \ luaxtable_info_t *info = (luaxtable_info_t *)par->huk##info; \ luaxtable_t *xtable = (luaxtable_t *)info->data; \ \ - lunatik_run(xtable->runtime, luaxtable_docall, ret, xtable, "destroy", 0, 0); \ + lunatik_run(xtable->runtime, luaxtable_docall, ret, xtable, info, "destroy", 0, 0); \ } LUAXTABLE_HOOK_CB(match, match, const struct sk_buff *, struct xt_action_param *, bool); diff --git a/lib/luaxtable.h b/lib/luaxtable.h index b2bbe0fa..5ed92b82 100644 --- a/lib/luaxtable.h +++ b/lib/luaxtable.h @@ -9,6 +9,9 @@ struct luaxtable_s; typedef struct luaxtable_info_s { + char userdata[256]; + + /* used internally by the luaxtable kernel module */ struct luaxtable_s *data __attribute__((aligned(8))); } luaxtable_info_t; diff --git a/usr/lib/xtable/luaxt.c b/usr/lib/xtable/luaxt.c index 2e1713c0..11471451 100644 --- a/usr/lib/xtable/luaxt.c +++ b/usr/lib/xtable/luaxt.c @@ -21,6 +21,7 @@ #endif #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#define MIN(a,b) (((a)<(b))?(a):(b)) typedef struct luaxt_flags_s { const char *name; @@ -32,9 +33,9 @@ static int luaopen_luaxt(lua_State *L); static int luaxt_match(lua_State *L); static int luaxt_target(lua_State *L); -static inline int luaxt_run(lua_State *L, const char *func_name, const char *key, int nargs, int nresults) +static int luaxt_run(lua_State *L, const char *func_name, const char *key, int nargs, int nresults) { - int ret = 0; + int ret = -1; int base = lua_gettop(L) - nargs; lua_rawgetp(L, LUA_REGISTRYINDEX, key); @@ -54,12 +55,38 @@ static inline int luaxt_run(lua_State *L, const char *func_name, const char *key if (nresults == 1) ret = lua_toboolean(L, -1); - restore: lua_settop(L, base); return ret; } +static int luaxt_doparams(lua_State *L, const char *op, const char *key, unsigned int *flags, luaxtable_info_t *info) +{ + int ret; + lua_newtable(L); + lua_pushvalue(L, -1); /* stack : param param */ + + ret = luaxt_run(L, op, key, 1, 1); + if (ret == -1) + return 0; + + if (flags && (lua_getfield(L, -1, "flags") == LUA_TNUMBER)) { + *flags = lua_tointeger(L, -1); + lua_pop(L, 1); + } + + if (lua_getfield(L, -1, "userdata") == LUA_TSTRING) { + size_t len = 0; + const char *ldata = lua_tolstring(L, -1, &len); + memset(info->userdata, 0, 256); + memcpy(info->userdata, ldata, MIN(len, 256)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + return ret; +} + #define LUAXT_HELPER_CB(hook) \ static void luaxt_##hook##_help(void) \ { \ @@ -69,32 +96,37 @@ static void luaxt_##hook##_help(void) \ #define LUAXT_INITER_CB(hook) \ static void luaxt_##hook##_init(struct xt_entry_##hook *hook) \ { \ - luaxt_run(L, "init", (void *)luaxt_##hook, 1, 0); \ + luaxt_doparams(L, "init", (void *)luaxt_##hook, NULL, (luaxtable_info_t *)(hook->data)); \ } #define LUAXT_PARSER_CB(hook) \ static int luaxt_##hook##_parse(int c, char **argv, int invert, unsigned int *flags, \ const void *entry, struct xt_entry_##hook **hook) \ { \ - return luaxt_run(L, "parse", (void *)luaxt_##hook, 0, 1); \ + return luaxt_doparams(L, "parse", (void *)luaxt_##hook, flags, (luaxtable_info_t *)((*hook)->data)); \ } #define LUAXT_FINALCHECKER_CB(hook) \ static void luaxt_##hook##_finalcheck(unsigned int flags) \ { \ - luaxt_run(L, "final_check", (void *)luaxt_##hook, 0, 0); \ + lua_pushnumber(L, flags); \ + luaxt_run(L, "final_check", (void *)luaxt_##hook, 1, 0); \ } #define LUAXT_PRINTER_CB(hook) \ static void luaxt_##hook##_print(const void *entry, const struct xt_entry_##hook *hook, int numeric) \ { \ - luaxt_run(L, "print", (void *)luaxt_##hook, 0, 0); \ + luaxtable_info_t *info = (luaxtable_info_t *)hook->data; \ + lua_pushstring(L, info->userdata); \ + luaxt_run(L, "print", (void *)luaxt_##hook, 1, 0); \ } #define LUAXT_SAVER_CB(hook) \ static void luaxt_##hook##_save(const void *entry, const struct xt_entry_##hook *hook) \ -{ \ - luaxt_run(L, "save", (void *)luaxt_##hook, 0, 0); \ +{ \ + luaxtable_info_t *info = (luaxtable_info_t *)hook->data; \ + lua_pushstring(L, info->userdata); \ + luaxt_run(L, "save", (void *)luaxt_##hook, 1, 0); \ } #define LUAXT_NEWCB(hook) \ From aa69b3c600b88c899dad181f0f9eaf195f954844 Mon Sep 17 00:00:00 2001 From: Mohammad Shehar Yaar Tausif Date: Sun, 11 Aug 2024 17:51:52 +0530 Subject: [PATCH 3/3] add dns doctoring example Signed-off-by: Mohammad Shehar Yaar Tausif --- Makefile | 2 + README.md | 72 ++++++++++++++++++++------ examples/dnsdoctor/Makefile | 15 ++++++ examples/dnsdoctor/cleanup.sh | 36 +++++++++++++ examples/dnsdoctor/dnsdoctor.lua | 68 ++++++++++++++++++++++++ examples/dnsdoctor/libxt_dnsdoctor.lua | 28 ++++++++++ examples/dnsdoctor/setup.sh | 61 ++++++++++++++++++++++ lib/luaxtable.c | 2 +- lib/luaxtable.h | 4 +- usr/lib/xtable/luaxt.c | 35 +++++++------ 10 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 examples/dnsdoctor/Makefile create mode 100755 examples/dnsdoctor/cleanup.sh create mode 100644 examples/dnsdoctor/dnsdoctor.lua create mode 100644 examples/dnsdoctor/libxt_dnsdoctor.lua create mode 100755 examples/dnsdoctor/setup.sh diff --git a/Makefile b/Makefile index 28dd6fdb..46c87063 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,8 @@ examples_install: ${INSTALL} -m 0644 examples/filter/*.lua ${SCRIPTS_INSTALL_PATH}/examples/filter ${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsblock ${INSTALL} -m 0644 examples/dnsblock/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsblock + ${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor + ${INSTALL} -m 0644 examples/dnsdoctor/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor examples_uninstall: ${RM} -r ${SCRIPTS_INSTALL_PATH}/examples diff --git a/README.md b/README.md index d1de738e..10c651ce 100644 --- a/README.md +++ b/README.md @@ -1149,10 +1149,10 @@ This function receives the following arguments: * `match` : function to be called for matching packets. It receives the following arguments: * `skb` (readonly): a `data` object representing the socket buffer. * `par`: a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields. - * `userdata` : a lua string passed from the userspace xtable module. + * `userargs` : a lua string passed from the userspace xtable module. * The function must return `true` if the packet matches the extension; otherwise, it must return `false`. - * `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument. - * `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument. + * `checkentry`: function to be called for checking the entry. This function receives `userargs` as its argument. + * `destroy`: function to be called for destroying the xtable extension. This function receives `userargs` as its argument. #### `xtable.target(opts)` @@ -1167,10 +1167,10 @@ This function receives the following arguments: * `target` : function to be called for targeting packets. It receives the following arguments: * `skb`: a `data` object representing the socket buffer. * `par` (readonly): a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields. - * `userdata` : a lua string passed from the userspace xtable module. + * `userargs` : a lua string passed from the userspace xtable module. * The function must return one of the values defined by the [xtable.action](https://github.com/luainkernel/lunatik#xtableaction) table. - * `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument. - * `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument. + * `checkentry`: function to be called for checking the entry. This function receives `userargs` as its argument. + * `destroy`: function to be called for destroying the xtable extension. This function receives `userargs` as its argument. #### `xtable.family` @@ -1212,9 +1212,9 @@ netfilter hooks to Lua. ### luaxt -The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/). The user can modify the generated lua code to implement the userspace handlers for the corresponding xtable extension. +The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/). -To generate the library, the following steps are required: +To build the library, the following steps are required: 1. Go to `usr/lib/xtable` and create a `libxt_.lua` file. 2. Register your callbacks for the xtable extension by importing the library (`luaxt`) in the created file. @@ -1230,10 +1230,10 @@ This function receives the following arguments: * `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension). * `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily) * `help`: 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 `userdata`. (`par.userdata = "mydata"`) - * `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function. - * `save`: function to be called for saving the arguments. This function recevies `userdata` 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 `userdata` and `flags`. (`par.userdata = "mydata"`) + * `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)` @@ -1244,10 +1244,10 @@ This function receives the following arguments: * `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension). * `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily) * `help`: 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 `userdata`. (`par.userdata = "mydata"`) - * `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function. - * `save`: function to be called for saving the arguments. This function recevies `userdata` 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 `userdata` and `flags`. (`par.userdata = "mydata"`) + * `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.family` @@ -1411,6 +1411,46 @@ 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 ``` +### dnsdoctor + +[dnsdoctor](examples/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. + +#### Usage + +``` +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 + +# copy the userspace extension to luaxt directory +cp libxt_dnsdoctor.lua ../../usr/lib/xtable/ +cd ../../usr/lib/xtable + +# build and install the userspace extension for netfilter +LUAXTABLE_MODULE=dnsdoctor make +sudo LUAXTABLE_MODULE=dnsdoctor 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 +cd ../../../examples/dnsdoctor +cleanup.sh +``` + ## References * [Scripting the Linux Routing Table with Lua](https://netdevconf.info/0x17/sessions/talk/scripting-the-linux-routing-table-with-lua.html) diff --git a/examples/dnsdoctor/Makefile b/examples/dnsdoctor/Makefile new file mode 100644 index 00000000..e3ef173b --- /dev/null +++ b/examples/dnsdoctor/Makefile @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +# SPDX-License-Identifier: MIT OR GPL-2.0-only + +all: + LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable + +install: + sudo LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable install + +uninstall: + sudo rm -f ${XTABLES_SO_DIR}/libxt_${LUAXTABLE_MODULE}.so + +clean: + LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable clean + diff --git a/examples/dnsdoctor/cleanup.sh b/examples/dnsdoctor/cleanup.sh new file mode 100755 index 00000000..367129b8 --- /dev/null +++ b/examples/dnsdoctor/cleanup.sh @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +# SPDX-License-Identifier: MIT OR GPL-2.0-only + +#!/bin/bash + +set -eux + +rm dnstest -rf + +# backup resolv config +if [[ -f /etc/resolv.conf.lunatik ]] then + echo "Restoring dns config from resolv.conf.lunatik" + sudo rm /etc/resolv.conf + sudo cp /etc/resolv.conf.lunatik /etc/resolv.conf + sudo rm /etc/resolv.conf.lunatik +fi + +# down the interfaces +sudo ip -n ns1 link set veth1 down +sudo ip -n ns2 link set veth3 down +sudo ip link set veth2 down +sudo ip link set veth4 down + +sudo ip addr delete 10.1.1.2/24 dev veth2 +sudo ip -n ns1 addr delete 10.1.1.3/24 dev veth1 +sudo ip addr delete 10.1.2.2/24 dev veth4 +sudo ip -n ns2 addr delete 10.1.2.3/24 dev veth3 + +# delete link between host and the namespaces +sudo ip -n ns1 link delete veth1 +sudo ip -n ns2 link delete veth3 + +# delete namespaces ns1 for dns server ns2 for server +sudo ip netns delete ns1 +sudo ip netns delete ns2 + diff --git a/examples/dnsdoctor/dnsdoctor.lua b/examples/dnsdoctor/dnsdoctor.lua new file mode 100644 index 00000000..dda1b2a5 --- /dev/null +++ b/examples/dnsdoctor/dnsdoctor.lua @@ -0,0 +1,68 @@ +-- +-- SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +-- SPDX-License-Identifier: MIT OR GPL-2.0-only +-- + +-- DNS Doctoring : Rewrite DNS type A record to a private address for local clients + +local xt = require("xtable") +local linux = require("linux") +local string = require("string") +local action = xt.action +local family = xt.family + +local udp = 0x11 +local dns = 0x35 + +local function nop() end + +local function get_domain(skb, off) + local _, nameoff, name = skb:getstring(off):find("([^\0]*)") + return name, nameoff + 1 +end + +local function dnsdoctor_tg(skb, par, userargs) + local target_dns, dst_ip, target_ip = string.unpack(">s4I4I4", userargs) + local thoff = par.thoff + + local packetdst = skb:getuint32(16) + if packetdst ~= linux.hton32(dst_ip) then + return action.ACCEPT + end + + local srcport = linux.ntoh16(skb:getuint16(thoff)) + if srcport == dns then + local dnsoff = thoff + 8 + local nanswers = linux.ntoh16(skb:getuint16(dnsoff + 6)) + + -- check the domain name + dnsoff = dnsoff + 12 + local domainname, nameoff = get_domain(skb, dnsoff) + + if domainname == target_dns then + dnsoff = dnsoff + nameoff + 4 -- skip over type, label fields + -- iterate over answers + for i = 1, nanswers do + local atype = linux.hton16(skb:getuint16(dnsoff + 2)) + if atype == 1 then + skb:setuint32(dnsoff + 12, linux.hton32(target_ip)) + end + dnsoff = dnsoff + 16 + end + end + end + + return action.ACCEPT +end + +xt.target{ + name = "dnsdoctor", + revision = 0, + family = family.UNSPEC, + proto = 0, + target = dnsdoctor_tg, + checkentry = nop, + destroy = nop, + hooks = 0, +} + diff --git a/examples/dnsdoctor/libxt_dnsdoctor.lua b/examples/dnsdoctor/libxt_dnsdoctor.lua new file mode 100644 index 00000000..70986904 --- /dev/null +++ b/examples/dnsdoctor/libxt_dnsdoctor.lua @@ -0,0 +1,28 @@ +local luaxt = require("luaxt") +local family = luaxt.family + +local function nop() end + +local function dnsdoctor_init(par) + local target_ip = "10.1.2.3" + local target = 0 + target_ip:gsub("%d+", function(s) target = target * 256 + tonumber(s) end) + + local src_ip = "10.1.1.2" + local src = 0 + src_ip:gsub("%d+", function(s) src = src * 256 + tonumber(s) end) + + par.userargs = string.pack(">s4I4I4", "\x07lunatik\x03com", src, target) +end + +luaxt.target{ + revision = 0, + family = family.UNSPEC, + help = nop, + init = dnsdoctor_init, + print = nop, + save = nop, + parse = nop, + final_check = nop +} + diff --git a/examples/dnsdoctor/setup.sh b/examples/dnsdoctor/setup.sh new file mode 100755 index 00000000..9c800d55 --- /dev/null +++ b/examples/dnsdoctor/setup.sh @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif +# SPDX-License-Identifier: MIT OR GPL-2.0-only + +#!/bin/bash + +set -eux + +# add namespaces ns1 for dns server ns2 for server +sudo ip netns add ns1 +sudo ip netns add ns2 + +# add link between host and the namespaces +sudo ip link add veth1 netns ns1 type veth peer name veth2 +sudo ip link add veth3 netns ns2 type veth peer name veth4 + +# add ip address to the links +# DNS IP : 10.1.1.3 +# Server IP : 10.1.2.3 +sudo ip addr add 10.1.1.2/24 dev veth2 +sudo ip -n ns1 addr add 10.1.1.3/24 dev veth1 +sudo ip addr add 10.1.2.2/24 dev veth4 +sudo ip -n ns2 addr add 10.1.2.3/24 dev veth3 + +# up the interfaces +sudo ip -n ns1 link set veth1 up +sudo ip -n ns2 link set veth3 up +sudo ip link set veth2 up +sudo ip link set veth4 up + +# make a directory to setup dns server +mkdir dnstest +cd dnstest +python -m venv .venv +source .venv/bin/activate +pip install dnserver + +# backup resolv config +echo "Backing up resolver config to /etc/resolver.conf.lunatik" +sudo cp -f /etc/resolv.conf /etc/resolv.conf.lunatik && \ +sudo sed -i 's/nameserver/#nameserver/g' /etc/resolv.conf && \ +echo "nameserver 10.1.1.3" | sudo tee -a /etc/resolv.conf && \ + +# add zone info and run dns server in ns1 +echo """ +[[zones]] +host = 'lunatik.com' +type = 'A' +answer = '192.168.10.1' + +[[zones]] +host = 'lunatik.com' +type = 'NS' +answer = 'ns1.lunatik.com.' + +[[zones]] +host = 'lunatik.com' +type = 'NS' +answer = 'ns2.lunatik.com.' +""" > zones.toml +sudo ip netns exec ns1 .venv/bin/dnserver --no-upstream zones.toml + diff --git a/lib/luaxtable.c b/lib/luaxtable.c index 9c6649f6..35eb801a 100644 --- a/lib/luaxtable.c +++ b/lib/luaxtable.c @@ -56,7 +56,7 @@ static int luaxtable_docall(lua_State *L, luaxtable_t *xtable, luaxtable_info_t lua_insert(L, base + 1); /* op */ lua_pop(L, 1); /* table */ - lua_pushstring(L, info->userdata); /* userdata */ + lua_pushlstring(L, info->userargs, LUAXTABLE_USERDATA_SIZE); /* userargs */ if (lua_pcall(L, nargs + 1, nret, 0) != LUA_OK) { pr_err("%s error: %s\n", op, lua_tostring(L, -1)); diff --git a/lib/luaxtable.h b/lib/luaxtable.h index 5ed92b82..b32821e4 100644 --- a/lib/luaxtable.h +++ b/lib/luaxtable.h @@ -6,10 +6,12 @@ #ifndef luaxtable_h #define luaxtable_h +#define LUAXTABLE_USERDATA_SIZE 256 + struct luaxtable_s; typedef struct luaxtable_info_s { - char userdata[256]; + char userargs[LUAXTABLE_USERDATA_SIZE]; /* used internally by the luaxtable kernel module */ struct luaxtable_s *data __attribute__((aligned(8))); diff --git a/usr/lib/xtable/luaxt.c b/usr/lib/xtable/luaxt.c index 11471451..ce942837 100644 --- a/usr/lib/xtable/luaxt.c +++ b/usr/lib/xtable/luaxt.c @@ -21,7 +21,7 @@ #endif #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) -#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) typedef struct luaxt_flags_s { const char *name; @@ -48,7 +48,7 @@ static int luaxt_run(lua_State *L, const char *func_name, const char *key, int n lua_pop(L, 1); /* table */ if (lua_pcall(L, nargs, nresults, 0) != LUA_OK) { - pr_err("Failed to call Lua function %s: %s\n", func_name, + pr_err("failed to call Lua function %s: %s\n", func_name, lua_tostring(L, -1)); goto restore; } @@ -60,14 +60,14 @@ static int luaxt_run(lua_State *L, const char *func_name, const char *key, int n return ret; } -static int luaxt_doparams(lua_State *L, const char *op, const char *key, unsigned int *flags, luaxtable_info_t *info) +static int luaxt_runwithuserargs(lua_State *L, const char *op, const char *key, unsigned int *flags, luaxtable_info_t *info) { int ret; + lua_newtable(L); - lua_pushvalue(L, -1); /* stack : param param */ + lua_pushvalue(L, -1); /* stack: param, param */ - ret = luaxt_run(L, op, key, 1, 1); - if (ret == -1) + if ((ret = luaxt_run(L, op, key, 1, 1)) == -1) return 0; if (flags && (lua_getfield(L, -1, "flags") == LUA_TNUMBER)) { @@ -75,14 +75,14 @@ static int luaxt_doparams(lua_State *L, const char *op, const char *key, unsigne lua_pop(L, 1); } - if (lua_getfield(L, -1, "userdata") == LUA_TSTRING) { + if (lua_getfield(L, -1, "userargs") == LUA_TSTRING) { size_t len = 0; const char *ldata = lua_tolstring(L, -1, &len); - memset(info->userdata, 0, 256); - memcpy(info->userdata, ldata, MIN(len, 256)); + memset(info->userargs, 0, LUAXTABLE_USERDATA_SIZE); + memcpy(info->userargs, ldata, MIN(len, LUAXTABLE_USERDATA_SIZE)); lua_pop(L, 1); } - + lua_pop(L, 1); return ret; } @@ -96,14 +96,14 @@ static void luaxt_##hook##_help(void) \ #define LUAXT_INITER_CB(hook) \ static void luaxt_##hook##_init(struct xt_entry_##hook *hook) \ { \ - luaxt_doparams(L, "init", (void *)luaxt_##hook, NULL, (luaxtable_info_t *)(hook->data)); \ + luaxt_runwithuserargs(L, "init", (void *)luaxt_##hook, NULL, (luaxtable_info_t *)(hook->data)); \ } #define LUAXT_PARSER_CB(hook) \ static int luaxt_##hook##_parse(int c, char **argv, int invert, unsigned int *flags, \ const void *entry, struct xt_entry_##hook **hook) \ { \ - return luaxt_doparams(L, "parse", (void *)luaxt_##hook, flags, (luaxtable_info_t *)((*hook)->data)); \ + return luaxt_runwithuserargs(L, "parse", (void *)luaxt_##hook, flags, (luaxtable_info_t *)((*hook)->data)); \ } #define LUAXT_FINALCHECKER_CB(hook) \ @@ -117,7 +117,7 @@ static void luaxt_##hook##_finalcheck(unsigned int flags) \ static void luaxt_##hook##_print(const void *entry, const struct xt_entry_##hook *hook, int numeric) \ { \ luaxtable_info_t *info = (luaxtable_info_t *)hook->data; \ - lua_pushstring(L, info->userdata); \ + lua_pushstring(L, info->userargs); \ luaxt_run(L, "print", (void *)luaxt_##hook, 1, 0); \ } @@ -125,7 +125,7 @@ static void luaxt_##hook##_print(const void *entry, const struct xt_entry_##hook static void luaxt_##hook##_save(const void *entry, const struct xt_entry_##hook *hook) \ { \ luaxtable_info_t *info = (luaxtable_info_t *)hook->data; \ - lua_pushstring(L, info->userdata); \ + lua_pushstring(L, info->userargs); \ luaxt_run(L, "save", (void *)luaxt_##hook, 1, 0); \ } @@ -204,6 +204,7 @@ static const luaxt_flags_t luaxt_family[] = { static int luaopen_luaxt(lua_State *L) { const luaxt_flags_t *flag; + luaL_newlib(L, luaxt_lib); lua_newtable(L); for (flag = luaxt_family; flag->name; flag++) { @@ -217,14 +218,14 @@ static int luaopen_luaxt(lua_State *L) static int __attribute__((constructor)) _init(void) { if ((L = luaL_newstate()) == NULL) { - pr_err("Failed to create Lua state\n"); + pr_err("failed to create Lua state\n"); return -ENOMEM; } luaL_openlibs(L); luaL_requiref(L, "luaxt", luaopen_luaxt, 1); - if (luaL_dofile(L, "libxt_"LUAXTABLE_MODULE".lua") != LUA_OK) { - pr_err("Failed to load Lua script: %s\n", lua_tostring(L, -1)); + if (luaL_dofile(L, "libxt_" LUAXTABLE_MODULE ".lua") != LUA_OK) { + pr_err("failed to load Lua script: %s\n", lua_tostring(L, -1)); return -ENOENT; } return 0;