diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..d9b94c1bc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/lua-memory"] + path = deps/lua-memory + url = https://github.com/MatheusNtg/lua-memory diff --git a/Makefile b/Makefile index 9d6b82a3b..d1594d575 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -ccflags-y += -D_LUNATIK -D_KERNEL -I$(src) -D_CONFIG_FULL_PANIC +ccflags-y += -D_LUNATIK -D_KERNEL -I$(src) -D_CONFIG_FULL_PANIC -DLUNATIK_UNUSED -DDEBUG\ + -I$(src)/lua -I$(src)/deps/lua-memory/src asflags-y += -D_LUNATIK -D_KERNEL ifeq ($(ARCH), $(filter $(ARCH),i386 x86)) @@ -18,23 +19,30 @@ ifeq ($(ARCH), mips) endif AFLAGS_setjmp.o += -D_MIPS_ISA_MIPS64 \ -D_MIPS_ISA=_MIPS_ISA_MIPS64 - else + else AFLAGS_setjmp.o += -D__mips_o32 -D_MIPS_ISA_MIPS32 \ -D_MIPS_ISA=_MIPS_ISA_MIPS32 - endif + endif endif obj-$(CONFIG_LUNATIK) += lunatik.o -lunatik-objs += lua/lapi.o lua/lcode.o lua/lctype.o lua/ldebug.o lua/ldo.o \ +lua-objs = lua/lapi.o lua/lcode.o lua/lctype.o lua/ldebug.o lua/ldo.o \ lua/ldump.o lua/lfunc.o lua/lgc.o lua/llex.o lua/lmem.o \ lua/lobject.o lua/lopcodes.o lua/lparser.o lua/lstate.o \ lua/lstring.o lua/ltable.o lua/ltm.o \ lua/lundump.o lua/lvm.o lua/lzio.o lua/lauxlib.o lua/lbaselib.o \ lua/lbitlib.o lua/lcorolib.o lua/ldblib.o lua/lstrlib.o \ lua/ltablib.o lua/lutf8lib.o lua/loslib.o lua/lmathlib.o lua/linit.o \ - lua/loadlib.o \ - arch/$(ARCH)/setjmp.o util/modti3.o lunatik_core.o + lua/loadlib.o luautil.o + +lua_memory-path = deps/lua-memory/src + +lua_memory-objs = $(lua_memory-path)/lmemlib.o $(lua_memory-path)/lmemmod.o + +lunatik-objs += $(lua-objs) \ + arch/$(ARCH)/setjmp.o util/modti3.o lunatik_core.o states.o netlink.o $(lua_memory-objs) \ + luanetlink.o ifeq ($(shell [ "${VERSION}" -lt "4" ] && [ "${VERSION}${PATCHLEVEL}" -lt "312" ] && echo y),y) lunatik-objs += util/div64.o diff --git a/deps/lua-memory b/deps/lua-memory new file mode 160000 index 000000000..3fe2285c2 --- /dev/null +++ b/deps/lua-memory @@ -0,0 +1 @@ +Subproject commit 3fe2285c2c0db27db58d52605d668ce5435f779d diff --git a/doc/lib.md b/doc/lib.md new file mode 100644 index 000000000..ce32b745d --- /dev/null +++ b/doc/lib.md @@ -0,0 +1,122 @@ +# User Space API documentation + +## Introduction + +This document provides the documentation of the user space API of Lunatik. This API is divided in two main features, being the control API and the data API, the first one is responsible to send messages to Lunatik kernel module to perform some operations, whereas the data API is responsible to exchange data between the kernel module and this user space API. + +## Constants + +#### `lunatik.datamaxsize` + +Integer that represents the maximum amount of data that a state can receive or send each time. + +#### `lunatik.defaultmaxallocbytes` + +The default amount of memory that a state will be able to use if no value is passed on creation. + +#### `lunatik.maxstates` + +The maximum number of states that the lunatik module is able to store. + +#### `lunatik.scriptnamemaxsize` + +The maximum length of a script name, this name is used on debug porpuses, for example, show a name for the script on the callback stack trace. + +#### `lunatik.statenamemaxsize` + +The maximum length of a state name. + +## Control API + +Responsable to send messages to Lunatik kernel module perform some operations, these are: + * States creations + * States deletion + * States listing + * Code execution + +To use this API, first you must to include the lunatik module on your lua code, you can do this as follow: + +```lua +local lunatik = require'lunatik' +``` + +After that, you have to create a lunatik session. This session represents a connection between kernel and user space API. To do it, simple store the return of the function `lunatik.session()` for example: + +```lua +local lunatik = require'lunatik' +local session = lunatik.session() +``` + +Now you can use the variable `session` to do all operations related to control API. These operations will be showed next. + +#### `session:newstate(name [, maxalloc])` + +Tells the lunatik kernel module to create a state with the name `name`. If some value is passed to `maxalloc` that you be the maximum amount of memory that the state `name` can use during it execution, if no value is passed, a state with a default `maxalloc` will be created. If the state is succesfully created, the function returns a userdata, such userdata is used to perform all needed operations on that state. You can imagine this userdata as your user space representation of the state created on the kernel side. If the state creation fails, this function returns a `nil` value alongside with a error message. + +#### `session:list()` + +Returns a table with all states present on kernel. Each entry of that table is another table containing the following informations about the state: `name`, `curralloc` and `maxalloc`. `name` represents the state name, `curralloc` the amount of memory that the state `name` is using in that given moment (when the function `session:list()` was called) and `maxalloc` has the same meaning showed at [`session:new`](#sessionnewname--maxalloc). + +#### `session:close()` + +Closes the connection with the kernel, after that, all references for the states will be lost, so it's important to check if you don't have any states in use before close the connection with the kernel to avoid memory leaks. + +#### `session:getstate(name)` + +Gets a user representation of the state with the name `name` which was created on kernel. Returns a userdata as described on [`session:new`](#sessionnewname--maxalloc) if such state is found and `nil` otherwise. + +#### `session:getfd()` + +Returns the session file descriptor of the control socket. + +### State related operations + +As mentioned at [`session:new`](#sessionnewname--maxalloc) function, when you call that function, if the state is succesfully created, it will be returned a userdata. That userdata is used to perform all operations related to that state, for example: + +```lua +local mystate = session:newstate'somename' +``` + +This code will create a state named `somename` on kernel and store the userdata to perform all operations related to the state `somename` on the variable `mystate`. From now on, it will be used `mystate` to explain all operations that can be done at that state. + +#### `mystate:dostring(code [, codename])` + +Runs on the kernel, the code given by `code`, you can give a name for that code, this can be used for debug purposes, if something get wrong on the execution of the code, then the name present on `codename` will be showed at the stack trace. If no value is passed, the default value `'lua in kernel'` will be showed for all states that loaded code without a `codename`. + +#### `mystate:close()` + +Closes on the kernel the state that `mystate` represents. + +#### `mystate:getname()` + +Returns the name of state `mystate` as it is in the kernel. + +#### `mystate:getmaxalloc()` + +Return the maximum amount of memory that `mystate` can use in the kernel. + +#### `mystate:getcurralloc()` + +Return the current memory usage in the kernel of the state represent by `mystate`. + +## Data API + +To transmit data from kernel to user space (and vice versa), we use lua memory (see [lua memory](https://github.com/luainkernel/lua-memory)). + +#### `mystate:send(memory)` + +Sends a [memory](https://github.com/luainkernel/lua-memory/blob/master/doc/manual.md#writable-byte-sequences) `memory` to the kernel state represented by `mystate`. + +In order to receive this memory on the kernel side you must to define a global function called `receive_callback` with one parameter which represents the memory which was sent from the user space. For example: + +```lua +function receive_callback(mem) + -- Here I can do whatever I want with mem +end +``` + +This callback will be called every time that a memory is received by the kernel. It's important to say that the module `memory` from lua memory is loaded by default in this version of Lunatik, thus you can do all supported operations with memory that Lua Memory offers. + +#### `mystate:receive()` + +Receives from the state represented by `mystate` the data sent from kernel and return a [memory](https://github.com/luainkernel/lua-memory/blob/master/doc/manual.md#writable-byte-sequences) to manipulate the data sent by kernel. If no data is available to be received, this function blocks until receive some data. diff --git a/doc/lunatik.md b/doc/lunatik.md new file mode 100644 index 000000000..8c5726ed1 --- /dev/null +++ b/doc/lunatik.md @@ -0,0 +1,122 @@ +# Lunatik API Documentation + +## Introduction + +This document provides a documentation of the Lunatik kernel module, this module is resposible for safe states management. Two main APIs are offered to the user, the first one has correlation with [network namespaces](https://man7.org/linux/man-pages/man8/ip-netns.8.html#DESCRIPTION), and provides a full isolation of the states based on each namespace, the second one provides a state management without relation with namespace, and all operations related to this API will be made based on the global network namespace. + +The API is based on the data structure `lunatik_State` which is used to perform all needed operations. + +## The `lunatik_State` struct + +Defined at `states.h` as: + +```c +typedef struct lunatik_state { + struct hlist_node node; + struct lunatik_instance instance; + struct genl_info usr_state_info; + lua_State *L; + char *code_buffer; + int buffer_offset; + spinlock_t lock; + refcount_t users; + size_t maxalloc; + size_t curralloc; + size_t scriptsize; + bool inuse; + unsigned char name[LUNATIK_NAME_MAXSIZE]; +} lunatik_State; +``` + +The elements in the struct has the following meaning: + +**`struct hlist_node node`** + +Is a variable used by kernel hash table API to storage the `lunatik_State` structure. + +**`struct lunatik_instance instance`** + +The instance of lunatik that some state belongs to. This is used to have access to informations related to the instance of lunatik that that state belongs. As mentioned at the [introduction](#introduction), its possible to have multiples set of states stored based on network namespaces, we know in what set this state belongs through this variable. + +**`struct genl_info usr_state_info`** + +Information related to the user space API. This variable is used to hold information about the user space API and consequently send needed informations through generic netlink. + +**`lua_State L`** + +Is the Lua state used by Lunatik to do all operations related to Lua. + +**`char *code_buffer`** + +A buffer used to store code through multiples function calls and contexts switchs between kernel and user space. + +**`int buffer_offset`** + +The buffer offset, used to hold information about where the cursor of `code_buffer` is at some time. + +**`spinlock_t lock`** + +Is a spinlock variable used to manage concurrency control. + +**`refcount_t users`** + +Represents how many users are referring to a given `lunatik_State`. + +**`size_t maxalloc`** + +Represents the maximum memory that the lua state `L` can use. + +**`size_t curralloc`** + +Represents the current memory that the lua state `L` is using. + +**`bool inuse`** + +Tells if the states is in use for some process on the user space. + +**`unsigned char name[LUNATIK_NAME_MAXSIZE]`** + +Is the unique identifier to `lunatik_State`, used to search it in the kernel hash table, note that this is limited by `LUNATIK_NAME_MAXSIZE`. + +## Namespace independent functions + +"Namespace independent functions" means that you don't have concern about namespaces management, this can be used when your aplication don't need to work isolated of other aplications. If this is not the case, then use the namespace dependent functions. + +#### `lunatik_State *lunatik_statelookup(const char *name)` + +Searches for a state with the name `name`. If a state with that name is found returns a pointer to the `lunatik_State`, returns `NULL` otherwise. + +#### `lunatik_State *lunatik_newstate(const char *name, size_t maxalloc)` + +Creates a `lunatik_State` with the max memory usage defined by `maxalloc` and a unique identifier to acess such state defined by `name`. Return a pointer to `lunatik_State` represeting the lunatik state or `NULL` if any errors occours during the creation. + +#### `int lunatik_close(const char *name)` + +Searchs and close a `lunatik_State` with the name `name`, returns `0` if no errors occours during this operation and `-1` if any error occours. + +#### `bool lunatik_getstate(lunatik_State *s)` + +Tries to get a reference to the state `s`, return `true` if such reference is succesfully acquired and `false` otherwise. +It's important to get a reference to the state before perform any action because this is the way that Lunatik knows if there is someone using the state or not (to close a state for example), so if you don't acquire the state to perform the action, the state can be closed and you will try to perform actions on a memory area that have been freed. + +#### `void lunatik_putstate(lunatik_State *s)` + +Put back the reference of the state. This tells the lunatik that you're are done with that state. + +#### `lunatik_State *lunatik_getenv(lua_State *L)` + +Returns the lunatik state which the lua state `L` is contained into. + +## Namespace depedent functions + +#### `lunatik_State *lunatik_netnewstate(const char *name, size_t maxalloc, struct net *net)` + +Does the same of [`lunatik_newstate`](#lunatik_state-lunatik_newstatesize_t-maxalloc-const-char-name) but stores the state on the lunatik instance present on the network namespace referenced by `net`. + +#### `int lunatik_netclosestate(const char *name, struct net *net)` + +Does the same of [`lunatik_close`](#int-lunatik_closeconst-char-name) but closes the state stored on the lunatik instance present on the namespace referenced by `net`. + +#### `lunatik_State *lunatik_netstatelookup(const char *name, struct net *net)` + +Does the same of [`lunatik_statelookup`](#lunatik_state-lunatik_statelookupconst-char-name) but searching on the lunatik instance present on the namespace referenced by `net`. diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 000000000..fe215227c --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,53 @@ +# Copyright (c) 2020 Matheus Rodrigues +# Copyright (C) 2017-2019 CUJO LLC +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +CC = gcc +CFLAGS = -fPIC -Wall -Wextra -O2 -g -I/usr/include/lua5.3/ -I/usr/include/libnl3 -D_UNUSED \ + -I$(src)../deps/lua-memory/src +LDFLAGS = -shared -lnl-genl-3 -lnl-3 -llua5.3 -L../deps/lua-memory/src -lluamemlib +RM = rm -f + +LIBS = lunatik.so +OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) +DEPS = $(OBJS:.o=.d) + +.PHONY: all +all: lua_memory $(LIBS) clean_objs + +lua_memory: lua_memory_clean + $(MAKE) -C ../deps/lua-memory/src linux CFLAGS="-fPIC -I/usr/include/lua5.3" + +%.so: + $(CC) -o $@ $^ $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +-include $(DEPS) + +.PHONY: clean +clean: lua_memory_clean + $(RM) $(LIBS) $(OBJS) $(DEPS) + +lua_memory_clean: + $(MAKE) -C ../deps/lua-memory/src clean + +clean_objs: + $(MAKE) -C ../deps/lua-memory/src clean_objs + +$(LIBS): $(OBJS) diff --git a/lib/lunatik.c b/lib/lunatik.c new file mode 100644 index 000000000..3a5456683 --- /dev/null +++ b/lib/lunatik.c @@ -0,0 +1,836 @@ +/* + * Copyright (c) 2020 Matheus Rodrigues + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "lunatik.h" + +static int lunatik_family; + +static struct nl_msg *prepare_message(int command, int flags) +{ + struct nl_msg *msg; + + if ((msg = nlmsg_alloc()) == NULL) { + printf("Failed to allocate a new message\n"); + return NULL; + } + + if ((genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, lunatik_family, + 0, 0, command, LUNATIK_NLVERSION)) == NULL) { + printf("Failed to put generic netlink message header\n"); + return NULL; + } + + NLA_PUT_U8(msg, FLAGS, flags); + + return msg; + +nla_put_failure: + printf("Failed to put attributes preparing the message\n"); + return NULL; +} + +static int send_simple_control_msg(struct lunatik_session *session, int command, int flags) +{ + struct nl_msg *msg; + int err = -1; + + if ((msg = prepare_message(command, flags)) == NULL) { + printf("Error preparing message\n"); + goto error; + } + + if ((err = nl_send_auto(session->control_sock, msg)) < 0) { + printf("Failed sending message to kernel\n"); + goto error; + } + + nlmsg_free(msg); + return 0; + +error: + nlmsg_free(msg); + return err; +} + +static int send_fragment(struct lunatik_nl_state *state, const char *original_script, int offset, + const char *script_name, int flags) +{ + struct nl_msg *msg; + char *fragment; + int err = -1; + if ((msg = prepare_message(EXECUTE_CODE, 0)) == NULL){ + nlmsg_free(msg); + return err; + } + + if ((fragment = malloc(sizeof(char) * LUNATIK_FRAGMENT_SIZE)) == NULL) { + printf("Failed to allocate memory to code fragment\n"); + return -ENOMEM; + } + strncpy(fragment, original_script + (offset * LUNATIK_FRAGMENT_SIZE), LUNATIK_FRAGMENT_SIZE); + + NLA_PUT_STRING(msg, STATE_NAME, state->name); + NLA_PUT_STRING(msg, CODE, fragment); + + if (offset == 0) + NLA_PUT_U32(msg, SCRIPT_SIZE, strlen(original_script)); + + if (flags & LUNATIK_DONE) + NLA_PUT_STRING(msg, SCRIPT_NAME, script_name); + + NLA_PUT_U8(msg, FLAGS, flags); + + if ((err = nl_send_auto(state->control_sock, msg)) < 0) { + printf("Failed to send fragment\n %s\n", nl_geterror(err)); + nlmsg_free(msg); + return err; + } + + nlmsg_free(msg); + + return 0; + +nla_put_failure: + printf("Failed putting attributes on the message\n"); + free(fragment); + return err; +} + +static int receive_session_op_result(struct lunatik_session *session){ + int ret; + + if ((ret = nl_recvmsgs_default(session->control_sock))) { + printf("Failed to receive message from kernel: %s\n", nl_geterror(ret)); + return ret; + } + + nl_wait_for_ack(session->control_sock); + + if (session->cb_result == CB_ERROR){ + session->cb_result = CB_EMPTY_RESULT; + return -1; + } + + return 0; +} + +static int receive_state_op_result(struct lunatik_nl_state *state){ + int ret; + + if ((ret = nl_recvmsgs_default(state->control_sock))) { + printf("Failed to receive message from kernel: %s\n", nl_geterror(ret)); + return ret; + } + + nl_wait_for_ack(state->control_sock); + + if (state->cb_result == CB_ERROR){ + state->cb_result = CB_EMPTY_RESULT; + return -1; + } + + return 0; +} + +int init_recv_datasocket_on_kernel(struct lunatik_nl_state *state) +{ + struct nl_msg *msg; + int err = -1; + + if ((msg = prepare_message(DATA_INIT, 0)) == NULL) { + printf("Error preparing message\n"); + goto error; + } + + NLA_PUT_STRING(msg, STATE_NAME, state->name); + + if ((err = nl_send_auto(state->recv_datasock, msg)) < 0) { + printf("Failed sending message to kernel\n"); + goto error; + } + + nl_recvmsgs_default(state->recv_datasock); + nl_wait_for_ack(state->recv_datasock); + + if (state->cb_result == CB_ERROR) { + state->cb_result = CB_EMPTY_RESULT; + goto error; + } + + nlmsg_free(msg); + + return 0; + +error: + nlmsg_free(msg); + return err; + +nla_put_failure: + printf("Failed to put attributes on to initialize data socket on kernel side\n"); + return err; +} + +int lunatikS_newstate(struct lunatik_session *session, struct lunatik_nl_state *cmd) +{ + struct nl_msg *msg; + int ret = -1; + + if ((msg = prepare_message(CREATE_STATE, 0)) == NULL) + return ret; + + NLA_PUT_STRING(msg, STATE_NAME, cmd->name); + NLA_PUT_U32(msg, MAX_ALLOC, cmd->maxalloc); + + if ((ret = nl_send_auto(session->control_sock, msg)) < 0) { + printf("Failed to send message to kernel\n %s\n", nl_geterror(ret)); + return ret; + } + + return receive_session_op_result(session); + +nla_put_failure: + printf("Failed to put attributes on message\n"); + return ret; +} + +int lunatik_closestate(struct lunatik_nl_state *state) +{ + struct nl_msg *msg; + int ret = -1; + + if ((msg = prepare_message(DESTROY_STATE, 0)) == NULL) + return ret; + + NLA_PUT_STRING(msg, STATE_NAME, state->name); + + if ((ret = nl_send_auto(state->control_sock, msg)) < 0) { + printf("Failed to send destroy message:\n\t%s\n", nl_geterror(ret)); + return ret; + } + + ret = receive_state_op_result(state); + + nl_socket_free(state->send_datasock); + nl_socket_free(state->recv_datasock); + nl_socket_free(state->control_sock); + + return ret; + +nla_put_failure: + printf("Failed to put attributes on netlink message\n"); + return ret; +} + +int lunatik_dostring(struct lunatik_nl_state *state, + const char *script, const char *script_name, size_t total_code_size) +{ + int err = -1; + int parts = 0; + + if (total_code_size <= LUNATIK_FRAGMENT_SIZE) { + err = send_fragment(state, script, 0, script_name, LUNATIK_INIT | LUNATIK_DONE); + if (err) + return err; + } else { + parts = (total_code_size % LUNATIK_FRAGMENT_SIZE == 0) ? + total_code_size / LUNATIK_FRAGMENT_SIZE : + (total_code_size / LUNATIK_FRAGMENT_SIZE) + 1; + + for (int i = 0; i < parts - 1; i++) { + if (i == 0) + err = send_fragment(state, script, i, script_name, LUNATIK_INIT | LUNATIK_MULTI); + else + err = send_fragment(state, script, i, script_name, LUNATIK_MULTI); + + nl_wait_for_ack(state->control_sock); + + if (err) + return err; + } + + err = send_fragment(state, script, parts - 1, script_name, LUNATIK_DONE); + if (err) + return err; + } + + return receive_state_op_result(state); +} + +int lunatikS_list(struct lunatik_session *session) +{ + int err = -1; + + if ((err = send_simple_control_msg(session, LIST_STATES, 0))) + return err; + + nl_recvmsgs_default(session->control_sock); + nl_wait_for_ack(session->control_sock); + + if (session->cb_result == CB_ERROR) + return -1; + + while (session->status == SESSION_RECEIVING) { + send_simple_control_msg(session, LIST_STATES, 0); + nl_recvmsgs_default(session->control_sock); + nl_wait_for_ack(session->control_sock); + } + + return 0; +} + +static int parse_states_list(struct lunatik_session *session) +{ + struct lunatik_nl_state *states; + char *ptr; + char *buffer; + int states_cursor = 0; + + buffer = (session->recv_buffer).buffer; + + states = (session->states_list).states; + ptr = strtok(buffer, "#"); + while(ptr != NULL) { + strcpy(states[states_cursor].name, ptr); + ptr = strtok(NULL, "#"); + states[states_cursor].curralloc = atoi(ptr); + ptr = strtok(NULL, "#"); + states[states_cursor].maxalloc = atoi(ptr); + ptr = strtok(NULL, "#"); + states_cursor++; + } + return 0; +} + +static int init_states_list(struct lunatik_session *session, struct nlattr **attrs) +{ + struct states_list *states_list; + int states_count; + + states_list = &session->states_list; + + if (attrs[STATES_COUNT]) { + states_count = nla_get_u32(attrs[STATES_COUNT]); + } else { + printf("Failed to initialize the states list, states count is missing\n"); + return -1; + } + + states_list->states = malloc(sizeof(struct lunatik_nl_state) * states_count); + + if (states_list->states == NULL) { + printf("Failed to allocate memory to store states information\n"); + return -ENOMEM; + } + states_list->list_size = states_count; + return 0; +} + +static int init_recv_buffer(struct lunatik_session *session, struct nlattr **attrs) +{ + struct received_buffer *recv_buffer; + int parts; + + recv_buffer = &session->recv_buffer; + + if (attrs[PARTS]) { + parts = nla_get_u32(attrs[PARTS]); + } else { + printf("Failed to initialize the recv buffer, states count is missing\n"); + return -1; + } + + recv_buffer->buffer = malloc(LUNATIK_FRAGMENT_SIZE * parts); + if (recv_buffer->buffer == NULL) { + printf("Failed to allocate memory to received messages buffer\n"); + return -ENOMEM; + } + + recv_buffer->cursor = 0; + return 0; +} + +static int append_recv_buffer(struct lunatik_session *session, struct nlattr **attrs) +{ + struct received_buffer *recv_buffer; + char *fragment; + + recv_buffer = &session->recv_buffer; + + if (attrs[STATES_LIST]) { + fragment = nla_get_string(attrs[STATES_LIST]); + } else { + printf("Failed to get fragment from states list, attribute is missing\n"); + return -1; + } + + strncpy(recv_buffer->buffer + (LUNATIK_FRAGMENT_SIZE * recv_buffer->cursor), + fragment, LUNATIK_FRAGMENT_SIZE); + + recv_buffer->cursor++; + return 0; +} + +static int send_data_cb_handler(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = genlmsg_hdr(nh); + struct nlattr * attrs_tb[ATTRS_COUNT + 1]; + struct lunatik_nl_state *state = (struct lunatik_nl_state *)arg; + + if (nla_parse(attrs_tb, ATTRS_COUNT, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL)) + { + printf("Error parsing attributes\n"); + state->cb_result = CB_ERROR; + return NL_OK; + } + + if (attrs_tb[OP_SUCESS] && nla_get_u8(attrs_tb[OP_SUCESS])) { + state->cb_result = CB_SUCCESS; + } else if (attrs_tb[OP_ERROR] && nla_get_u8(attrs_tb[OP_ERROR])) { + state->cb_result = CB_ERROR; + } + + return NL_OK; +} + +int init_socket(struct nl_sock**); + +static int get_state_handler(struct lunatik_session *session, struct nlattr **attrs_tb) +{ + char *name; + + if (!(attrs_tb[STATE_NAME] || attrs_tb[CURR_ALLOC] || attrs_tb[MAX_ALLOC])) { + printf("Some attributes are missing\n"); + return -1; + } + + name = nla_get_string(attrs_tb[STATE_NAME]); + + strcpy(session->state_holder.name, name); + session->state_holder.curralloc = nla_get_u32(attrs_tb[CURR_ALLOC]); + session->state_holder.maxalloc = nla_get_u32(attrs_tb[MAX_ALLOC]); + session->state_holder.session = session; + + return 0; +} + +static int response_handler(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = genlmsg_hdr(nh); + struct lunatik_session *session = (struct lunatik_session *) arg; + struct nlattr * attrs_tb[ATTRS_COUNT + 1]; + uint8_t flags = 0; + int err = 0; + + if (nla_parse(attrs_tb, ATTRS_COUNT, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL)) + { + printf("Error parsing attributes\n"); + session->cb_result = CB_ERROR; + return NL_OK; + } + switch (gnlh->cmd) + { + case CREATE_STATE: + case DESTROY_STATE: + if (attrs_tb[OP_SUCESS] && nla_get_u8(attrs_tb[OP_SUCESS])) { + session->cb_result = CB_SUCCESS; + } else if (attrs_tb[OP_ERROR] && nla_get_u8(attrs_tb[OP_ERROR])) { + session->cb_result = CB_ERROR; + } + break; + case LIST_STATES: + + if (attrs_tb[STATES_LIST_EMPTY]) { + session->states_list.list_size = 0; + session->states_list.states = NULL; + session->cb_result = CB_LIST_EMPTY; + return NL_OK; + } + + if (attrs_tb[FLAGS]) { + flags = nla_get_u8(attrs_tb[FLAGS]); + } + + if (flags & LUNATIK_INIT) { + err = init_states_list(session, attrs_tb); + err = init_recv_buffer(session, attrs_tb); + session->status = SESSION_RECEIVING; + } + + if (flags & LUNATIK_DONE) { + err = append_recv_buffer(session, attrs_tb); + err = parse_states_list(session); + session->status = SESSION_FREE; + free(session->recv_buffer.buffer); + session->recv_buffer.cursor = 0; + } + + if (flags & LUNATIK_MULTI) { + err = append_recv_buffer(session, attrs_tb); + } + + if (err) + session->cb_result = CB_ERROR; + + break; + case GET_STATE: + if (attrs_tb[STATE_NOT_FOUND]) { + session->cb_result = CB_STATE_NOT_FOUND; + return NL_OK; + } + + if (attrs_tb[OP_ERROR]) { + session->cb_result = CB_ERROR; + return NL_OK; + } + + if (get_state_handler(session, attrs_tb)) + session->cb_result = CB_ERROR; + + break; + default: + break; + } + + return NL_OK; +} + +static int response_state_handler(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = genlmsg_hdr(nh); + struct nlattr * attrs_tb[ATTRS_COUNT + 1]; + struct lunatik_nl_state *state = (struct lunatik_nl_state *)arg; + + if (nla_parse(attrs_tb, ATTRS_COUNT, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL)) + { + printf("Error parsing attributes\n"); + state->cb_result = CB_ERROR; + return NL_OK; + } + + if (attrs_tb[OP_SUCESS] && nla_get_u8(attrs_tb[OP_SUCESS])) { + state->cb_result = CB_SUCCESS; + return NL_OK; + } else if (attrs_tb[OP_ERROR] && nla_get_u8(attrs_tb[OP_ERROR])) { + state->cb_result = CB_ERROR; + return NL_OK; + } else if (attrs_tb[NOT_IN_USE] && nla_get_u8(attrs_tb[NOT_IN_USE])) { + state->cb_result = CB_ERROR; + return NL_OK; + } + + if (attrs_tb[CURR_ALLOC]) { + state->curralloc = nla_get_u32(attrs_tb[CURR_ALLOC]); + } + + return NL_OK; +} + +static int init_data_buffer(struct data_buffer *data_buffer, size_t size); + +static int data_handler(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = genlmsg_hdr(nh); + struct lunatik_nl_state *state = (struct lunatik_nl_state *) arg; + struct data_buffer *data_buffer = &state->data_buffer; + struct nlattr *attrs_tb[ATTRS_COUNT + 1]; + char *payload; + size_t payload_size; + int err; + + if (nla_parse(attrs_tb, ATTRS_COUNT, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL)) + { + printf("Error parsing attributes\n"); + state->cb_result = CB_ERROR; + return NL_OK; + } + + if (attrs_tb[OP_SUCESS]) { + state->cb_result = CB_SUCCESS; + return NL_OK; + } + + if (attrs_tb[OP_ERROR]) { + state->cb_result = CB_ERROR; + return NL_OK; + } + + if (attrs_tb[LUNATIK_DATA] && attrs_tb[LUNATIK_DATA_LEN]) { + payload = nla_get_string(attrs_tb[LUNATIK_DATA]); + payload_size = nla_get_u32(attrs_tb[LUNATIK_DATA_LEN]); + + err = init_data_buffer(&state->data_buffer, payload_size); + if (err) { + state->cb_result = CB_ERROR; + return NL_OK; + } + + memcpy(data_buffer->buffer, payload, payload_size); + data_buffer->size = payload_size; + } + + return NL_OK; +} + +int init_socket(struct nl_sock **socket) +{ + int err = -1; + if ((*socket = nl_socket_alloc()) == NULL) + return err; + + if ((err = genl_connect(*socket))) + return err; + + if ((lunatik_family = genl_ctrl_resolve(*socket, LUNATIK_FAMILY)) < 0) + return lunatik_family; + + return 0; +} + +int lunatikS_init(struct lunatik_session *session) +{ + int err = -1; + + if (session == NULL) + return -EINVAL; + + if ((err = init_socket(&session->control_sock))) { + printf("Failed to initialize control socket\n"); + nl_socket_free(session->control_sock); + return err; + } + + nl_socket_modify_cb(session->control_sock, NL_CB_MSG_IN, NL_CB_CUSTOM, response_handler, session); + session->control_fd = nl_socket_get_fd(session->control_sock); + + return 0; +} + +void lunatikS_close(struct lunatik_session *session) +{ + if (session != NULL){ + nl_socket_free(session->control_sock); + session->control_fd = -1; + } +} + +int lunatik_datasend(struct lunatik_nl_state *state, const char *payload, size_t len) +{ + struct nl_msg *msg = prepare_message(DATA, 0); + int err = 0; + + if (msg == NULL) + return -1; + + if (state->send_datasock == NULL) { + printf("There is no socket available to send a message from this state, did you initialize the data for this state?\n"); + return -1; + } + + NLA_PUT_STRING(msg, LUNATIK_DATA, payload); + NLA_PUT_STRING(msg, STATE_NAME, state->name); + NLA_PUT_U32(msg, LUNATIK_DATA_LEN, len); + + if ((err = nl_send_auto(state->send_datasock, msg)) < 0) { + printf("Failed sending message to kernel\n"); + nlmsg_free(msg); + return err; + } + + nl_recvmsgs_default(state->send_datasock); + nl_wait_for_ack(state->send_datasock); + nlmsg_free(msg); + + if (state->cb_result == CB_ERROR) { + state->cb_result = CB_EMPTY_RESULT; + return -1; + } + + return 0; + +nla_put_failure: + printf("Failure to put attributes to data send operation\n"); + nlmsg_free(msg); + return -1; +} + +static int init_data_buffer(struct data_buffer *data_buffer, size_t size) +{ + if ((data_buffer->buffer = malloc(size)) == NULL) { + printf("Failed to allocate memory to data buffer\n"); + return -1; + } + memset(data_buffer->buffer, 0, size); + data_buffer->size = size; + return 0; +} + +void release_data_buffer(struct data_buffer *data_buffer) +{ + free(data_buffer->buffer); + data_buffer->size = 0; +} + +int lunatik_receive(struct lunatik_nl_state *state) +{ + int err = 0; + + nl_recvmsgs_default(state->recv_datasock); + + if (state->cb_result == CB_ERROR) { + state->cb_result = CB_EMPTY_RESULT; + err = -1; + } + + return err; +} + +static int lunatik_initdata(struct lunatik_nl_state *state) +{ + int ret = 0; + + if ((ret = init_socket(&state->send_datasock))) { + printf("Failed to initialize the send socket\n"); + nl_socket_free(state->send_datasock); + return ret; + } + + if ((ret = init_socket(&state->recv_datasock))) { + printf("Failed to initialize the recv socket\n"); + nl_socket_free(state->recv_datasock); + return ret; + } + + if ((ret = init_recv_datasocket_on_kernel(state))) { + printf("Failed to initialize receive socket on kernel\n"); + nl_socket_free(state->recv_datasock); + return ret; + } + + nl_socket_modify_cb(state->send_datasock, NL_CB_MSG_IN, NL_CB_CUSTOM, send_data_cb_handler, state); + nl_socket_modify_cb(state->recv_datasock, NL_CB_MSG_IN, NL_CB_CUSTOM, data_handler, state); + nl_socket_disable_seq_check(state->recv_datasock); + nl_socket_disable_auto_ack(state->recv_datasock); + + return 0; +} + +struct lunatik_nl_state *lunatikS_getstate(struct lunatik_session *session, const char *name) +{ + struct nl_msg *msg = prepare_message(GET_STATE, 0); + + NLA_PUT_STRING(msg, STATE_NAME, name); + + if (nl_send_auto(session->control_sock, msg) < 0) { + printf("Failed to send message to kernel\n"); + return NULL; + } + + if (receive_session_op_result(session)) + return NULL; + + if ((session->cb_result == CB_STATE_NOT_FOUND) || (session->cb_result == CB_ERROR)) { + session->cb_result = CB_EMPTY_RESULT; + return NULL; + } + + return &session->state_holder; + +nla_put_failure: + printf("Failed to put attributes on netlink message\n"); + return NULL; +} + +int lunatik_initstate(struct lunatik_nl_state *state) +{ + int err; + + if ((err = lunatik_initdata(state))) { + return err; + } + + if ((err = init_socket(&state->control_sock))) { + printf("Failed to initialize the control socket for state %s\n", state->name); + nl_socket_free(state->control_sock); + return err; + } + + nl_socket_modify_cb(state->control_sock, NL_CB_MSG_IN, NL_CB_CUSTOM, response_state_handler, state); + + return 0; +} + +int lunatik_getcurralloc(struct lunatik_nl_state *state) +{ + struct nl_msg *msg; + int err = -1; + + if ((msg = prepare_message(GET_CURRALLOC, 0)) == NULL) + return err; + + NLA_PUT_STRING(msg, STATE_NAME, state->name); + + if ((err = nl_send_auto(state->control_sock, msg)) < 0) + return err; + + return receive_state_op_result(state); + +nla_put_failure: + printf("Failed to put attribute to get current alloc of state %s\n", state->name); + return -1; +} + +int lunatik_putstate(struct lunatik_nl_state *state) +{ + struct nl_msg *msg; + + int err = -1; + + if ((msg = prepare_message(PUT_STATE, 0)) == NULL) + return err; + + NLA_PUT_STRING(msg, STATE_NAME, state->name); + + if ((err = nl_send_auto(state->control_sock, msg)) < 0) + return err; + + return receive_state_op_result(state); + +nla_put_failure: + printf("Failed to put attribute to put state\n"); + return -1; +} diff --git a/lib/lunatik.h b/lib/lunatik.h new file mode 100644 index 000000000..e9713d7ae --- /dev/null +++ b/lib/lunatik.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LUNATIK_H +#define LUNATIK_H + +#include +#include +#include +#include +#include "../netlink_common.h" +#include "../lunatik_conf.h" + +enum callback_result { + CB_SUCCESS, + CB_ERROR, + CB_EMPTY_RESULT, + CB_LIST_EMPTY, + CB_STATE_NOT_FOUND, +}; + +enum session_status { + SESSION_FREE, + SESSION_RECEIVING, +}; + +struct data_buffer { + char *buffer; + int size; +}; + +struct lunatik_nl_state { + struct lunatik_session *session; + struct nl_sock *send_datasock; + struct nl_sock *recv_datasock; + struct nl_sock *control_sock; + struct data_buffer data_buffer; + enum callback_result cb_result; + uint32_t maxalloc; + uint32_t curralloc; + bool isvalid; + char name[LUNATIK_NAME_MAXSIZE]; +}; + +struct states_list { + struct lunatik_nl_state *states; + size_t list_size; +}; + +struct received_buffer { + char *buffer; + int cursor; +}; + +struct lunatik_session { + struct nl_sock *control_sock; + struct states_list states_list; + struct received_buffer recv_buffer; + struct lunatik_nl_state state_holder; + enum session_status status; + enum callback_result cb_result; + int family; + int control_fd; + int data_fd; + uint32_t pid; +}; + +static inline int lunatikS_getfd(const struct lunatik_session *session) +{ + return session->control_fd; +} + +static inline int lunatikS_isopen(const struct lunatik_session *session) +{ + return session->control_fd >= 0; +} + +int lunatikS_init(struct lunatik_session *session); + +void lunatikS_close(struct lunatik_session *session); + +int lunatikS_newstate(struct lunatik_session *session, struct lunatik_nl_state *s); + +int lunatik_closestate(struct lunatik_nl_state *state); + +int lunatik_dostring(struct lunatik_nl_state *state, + const char *script, const char *script_name, size_t total_code_size); + +int lunatikS_list(struct lunatik_session *session); + +int lunatik_receive(struct lunatik_nl_state *state); + +int lunatik_initstate(struct lunatik_nl_state *state); + +struct lunatik_nl_state *lunatikS_getstate(struct lunatik_session *session, const char *name); + +int lunatik_datasend(struct lunatik_nl_state *state, + const char *payload, size_t len); + +int lunatik_getcurralloc(struct lunatik_nl_state *state); + +int lunatik_putstate(struct lunatik_nl_state *state); + +#endif /* LUNATIK_H */ diff --git a/lib/lunatik_module.c b/lib/lunatik_module.c new file mode 100644 index 000000000..b4651cefb --- /dev/null +++ b/lib/lunatik_module.c @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "lunatik.h" + +#ifndef _UNUSED +extern int luaopen_memory(lua_State *L); +#endif /*_UNUSED*/ + +#define DEFAULT_MAXALLOC_BYTES (32 * 1024) + +extern int init_socket(struct nl_sock **socket); +extern int init_recv_datasocket_on_kernel(struct lunatik_nl_state *state); + +static int pusherrmsg(lua_State *L, const char *msg) +{ + lua_pushnil(L); + lua_pushstring(L, msg); + return 2; +} + +static int pusherrno(lua_State *L, int err) +{ + int r = pusherrmsg(L, strerror(-err)); + lua_pushinteger(L, err); + return r + 1; +} + +#ifndef _UNUSED +static int pushioresult(lua_State *L, int code) +{ + if (code >= 0) { + lua_pushboolean(L, true); + return 1; + } + return pusherrno(L, code); +} +#endif + +static void newclass(lua_State *L, const char *name, + const luaL_Reg mt[]) +{ + luaL_newmetatable(L, name); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, mt, 0); + lua_pop(L, 1); +} + +static int lsession_open(lua_State *L) +{ + int ret; + struct lunatik_session *session = lua_newuserdata(L, sizeof(struct lunatik_session)); + + luaL_setmetatable(L, "lunatik.session"); + ret = lunatikS_init(session); + + return ret < 0 ? pusherrno(L, ret) : 1; +} + +static int lsession_gc(lua_State *L) +{ + struct lunatik_session *session = luaL_checkudata(L, 1, "lunatik.session"); + if (lunatikS_isopen(session)) lunatikS_close(session); + return 0; +} + +static struct lunatik_session *getsession(lua_State *L) +{ + struct lunatik_session *c = luaL_checkudata(L, 1, "lunatik.session"); + if (!lunatikS_isopen(c)) + luaL_argerror(L, 1, "socket closed"); + return c; +} + +static struct lunatik_nl_state *getnlstate(lua_State *L) +{ + struct lunatik_nl_state *s = luaL_checkudata(L, 1, "states.control"); + if (s == NULL) + luaL_argerror(L, 1, "Failed to get state"); + return s; +} + +static int lsession_close(lua_State *L) +{ + struct lunatik_session *session = getsession(L); + lunatikS_close(session); + lua_pushboolean(L, true); + return 1; +} + +static int lsession_getfd(lua_State *L) +{ + struct lunatik_session *session = getsession(L); + lua_pushinteger(L, lunatikS_getfd(session)); + return 1; +} + +static int lsession_newstate(lua_State *L) +{ + struct lunatik_session *session = getsession(L); + size_t len; + const char *name = luaL_checklstring(L, 2, &len); + lua_Integer maxalloc = luaL_optinteger(L, 3, DEFAULT_MAXALLOC_BYTES); + struct lunatik_nl_state *state = lua_newuserdata(L, sizeof(struct lunatik_nl_state)); + + if (len >= LUNATIK_NAME_MAXSIZE) + luaL_argerror(L, 2, "name too long"); + + strcpy(state->name, name); + state->maxalloc = maxalloc; + state->session = session; + state->isvalid = true; + + + if (lunatikS_newstate(session, state)) { + pusherrmsg(L, "Failed to create the state\n"); + return 2; + } + + if (lunatik_initstate(state)) { + pusherrmsg(L, "Failed to initialize state\n"); + return 1; + } + + luaL_setmetatable(L, "states.control"); + + return 1; +} + +static int lstate_close(lua_State *L) +{ + struct lunatik_nl_state *state = getnlstate(L); + + if (!state->isvalid) + return luaL_error(L, "invalid state"); + + if (lunatik_closestate(state)){ + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, true); + return 1; +} + +static int lstate_dostring(lua_State *L) +{ + struct lunatik_nl_state *s = getnlstate(L); + size_t len; + const char *payload = luaL_checklstring(L, 2, &len); + const char *script_name = luaL_optstring(L, 3, "Lunatik"); + + if (strlen(script_name) > LUNATIK_SCRIPTNAME_MAXSIZE) + return luaL_argerror(L, 3, "script name too long"); + + if (!s->isvalid) + return luaL_error(L, "invalid state"); + + int status = lunatik_dostring(s, payload, script_name, len); + + if (status) + goto error; + + lua_pushboolean(L, true); + return 1; + +error: + lua_pushnil(L); + return 1; +} + +static int lstate_getname(lua_State *L) { + struct lunatik_nl_state *s = getnlstate(L); + + if (!s->isvalid) + return luaL_error(L, "invalid state"); + + lua_pushstring(L, s->name); + return 1; +} + +static int lstate_getmaxalloc(lua_State *L) { + struct lunatik_nl_state *s = getnlstate(L); + + if (!s->isvalid) + return luaL_error(L, "invalid state"); + + lua_pushinteger(L, s->maxalloc); + return 1; +} + +static void buildlist(lua_State *L, struct lunatik_nl_state *states, size_t n); + +static int lsession_list(lua_State *L) +{ + struct lunatik_session *session = getsession(L); + struct states_list list; + int status; + + status = lunatikS_list(session); + if (status){ + lua_pushnil(L); + return 1; + } + + if (session->cb_result == CB_LIST_EMPTY) { + buildlist(L, NULL, 0); + } else { + list = session->states_list; + buildlist(L, list.states, list.list_size); + free(list.states); + list.list_size = 0; + } + + return 1; +} + +static int lsession_getstate(lua_State *L) +{ + struct lunatik_session *session = getsession(L); + struct lunatik_nl_state *state = lua_newuserdata(L, sizeof(struct lunatik_nl_state)); + struct lunatik_nl_state *received_state; + const char *name; + + name = luaL_checkstring(L, 2); + + if ((received_state = lunatikS_getstate(session, name)) == NULL) { + lua_pushnil(L); + return 1; + } + + *state = *received_state; + state->isvalid = true; + + if (lunatik_initstate(state)) { + pusherrmsg(L, "Failed to initialize the state\n"); + return 1; + } + + luaL_setmetatable(L, "states.control"); + + return 1; +} + +static void buildlist(lua_State *L, struct lunatik_nl_state *states, size_t n) +{ + size_t i; + + lua_newtable(L); + for (i = 0; i < n; i++) { + lua_newtable(L); + lua_pushstring(L, states[i].name); + lua_setfield(L, -2, "name"); + lua_pushinteger(L, states[i].maxalloc); + lua_setfield(L, -2, "maxalloc"); + lua_pushinteger(L, states[i].curralloc); + lua_setfield(L, -2, "curralloc"); + lua_seti(L, -2, i + 1); + } +} + +static int lstate_datasend(lua_State *L) +{ + struct lunatik_nl_state *state = getnlstate(L); + size_t size; + int err; + const char *buffer = luamem_checkmemory(L, 2, &size); + + if (buffer == NULL) luaL_argerror(L, 2, "expected non NULL memory object"); + + if (!state->isvalid) + return luaL_error(L, "invalid state"); + + + err = lunatik_datasend(state, buffer, size); + err ? lua_pushnil(L) : lua_pushboolean(L, true); + + return 1; +} + +extern void release_data_buffer(struct data_buffer *data_buffer); + +static int lstate_datareceive(lua_State *L) +{ + struct lunatik_nl_state *state = getnlstate(L); + char *memory; + + if (!state->isvalid) + return luaL_error(L, "invalid state"); + + if (lunatik_receive(state)) + goto error; + + memory = luamem_newalloc(L, state->data_buffer.size); + memcpy(memory, state->data_buffer.buffer, state->data_buffer.size); + + release_data_buffer(&state->data_buffer); + + return 1; + +error: + lua_pushnil(L); + return 1; +} + +static int lstate_getcurralloc(lua_State *L) +{ + struct lunatik_nl_state *state = getnlstate(L); + + if (!state->isvalid) + return luaL_error(L, "invalid state"); + + if (lunatik_getcurralloc(state)) { + lua_pushnil(L); + return 1; + } + + lua_pushinteger(L, state->curralloc); + + return 1; +} + +static int lstate_put(lua_State *L) +{ + struct lunatik_nl_state *state = getnlstate(L); + + if (lunatik_putstate(state)) { + lua_pushnil(L); + return 1; + } + + lua_pushboolean(L, true); + state->isvalid = false; + + return 1; +} + +static const luaL_Reg session_mt[] = { + {"close", lsession_close}, + {"getfd", lsession_getfd}, + {"newstate", lsession_newstate}, + {"list", lsession_list}, + {"getstate", lsession_getstate}, + {"__gc", lsession_gc}, + {NULL, NULL} +}; + +static const luaL_Reg state_mt[] = { + {"dostring", lstate_dostring}, + {"getname", lstate_getname}, + {"getmaxalloc", lstate_getmaxalloc}, + {"getcurralloc", lstate_getcurralloc}, + {"close", lstate_close}, + {"send", lstate_datasend}, + {"receive", lstate_datareceive}, + {"put", lstate_put}, + {NULL, NULL} +}; + +static const luaL_Reg lunatik_lib[] = { + {"session", lsession_open}, + {NULL, NULL} +}; + +static void setconst(lua_State *L, const char *name, lua_Integer value) +{ + lua_pushinteger(L, value); + lua_setfield(L, -2, name); +} + +int luaopen_lunatik(lua_State *L) +{ + newclass(L, "lunatik.session", session_mt); + newclass(L, "states.control", state_mt); + + luaL_newlib(L, lunatik_lib); + + setconst(L, "datamaxsize", LUNATIK_FRAGMENT_SIZE); + setconst(L, "defaultmaxallocbytes", DEFAULT_MAXALLOC_BYTES); + setconst(L, "maxstates", LUNATIK_HASH_BUCKETS); + setconst(L, "scriptnamemaxsize", LUNATIK_SCRIPTNAME_MAXSIZE); + setconst(L, "statenamemaxsize", LUNATIK_NAME_MAXSIZE); + + return 1; +} diff --git a/lib/runtests.sh b/lib/runtests.sh new file mode 100755 index 000000000..602848f69 --- /dev/null +++ b/lib/runtests.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +for file in tests/*; do + echo "Running $file" + LD_LIBRARY_PATH=../deps/lua-memory/src LUA_CPATH='../deps/lua-memory/src/?.so;;' lua $file + echo "$file executed with no errors!" +done diff --git a/lib/tests/close.lua b/lib/tests/close.lua new file mode 100644 index 000000000..faf23229b --- /dev/null +++ b/lib/tests/close.lua @@ -0,0 +1,23 @@ +local lunatik = require'lunatik' +local session = lunatik.session() + +-- Normal close +local s1 = session:newstate's1' +assert(s1 ~= nil) + +local err = s1:close() +assert(err ~= nil) + +-- Using state after close session +s1 = session:newstate's1' +assert(s1 ~= nil) + +session:close() +assert(s1:getname() == 's1') + +err = s1:close() +assert(err) + +-- Trying to close nonexistent state +err = s1:close() +assert(err == nil) diff --git a/lib/tests/create.lua b/lib/tests/create.lua new file mode 100644 index 000000000..3765d6e65 --- /dev/null +++ b/lib/tests/create.lua @@ -0,0 +1,38 @@ +local lunatik = require'lunatik' +local session = lunatik.session() + +-- Normal creation +local s1 = session:newstate's1' +assert(s1) +assert(s1:getname() == 's1') +assert(s1:getmaxalloc() == lunatik.defaultmaxallocbytes) +-- assert(s1:getcurralloc() ~= nil) + +-- Max alloc greater than MIN_ALLOC +local s2 = session:newstate('s2', 100000) +assert(s2) + +-- Max alloc less than MIN_ALLOC +local s3 = session:newstate('s3', 1) +assert(s3 == nil) + +-- State already exists +local s4 = session:newstate's1' +assert(s4 == nil) + +-- State creation from another session +local session2 = lunatik.session() + +-- Normal creation +ss1 = session2:newstate'ss1' +assert(ss1) + +-- State created from another session +ss2 = session2:newstate's1' +assert(ss2 == nil) + +s1:close() +s2:close() +ss1:close() +session:close() +session2:close() diff --git a/lib/tests/getstate.lua b/lib/tests/getstate.lua new file mode 100644 index 000000000..1f996d6ed --- /dev/null +++ b/lib/tests/getstate.lua @@ -0,0 +1,26 @@ +local lunatik = require'lunatik' +local memory = require'memory' +local buffer = memory.create(3) +local session = lunatik.session() + +local kscript = [[ + print'olá mundo!' +]] + +-- Testing state normal state creation +local s1 = session:newstate's1' +assert(s1 ~= nil) +local err = s1:dostring(kscript) +assert(err ~= nil) + +-- Trying to get a state created on user space +local state = session:getstate's1' +assert(state == nil) + +-- Try to get a state nonexistent state +local state2 = session:getstate's4' +assert(state2 == nil) + +s1:close() + +session:close() diff --git a/lib/tests/list.lua b/lib/tests/list.lua new file mode 100644 index 000000000..5a7575e51 --- /dev/null +++ b/lib/tests/list.lua @@ -0,0 +1,37 @@ +local lunatik = require'lunatik' +local session = lunatik.session() +local session2 = lunatik.session() + +-- Create states that will be present on the list +local s1 = session:newstate's1' +local s2 = session:newstate's2' +local s3 = session:newstate('s3', 100000) + +local ss1 = session2:newstate'ss1' +local ss2 = session2:newstate'ss2' +local ss3 = session2:newstate('ss3', 100000) + +-- Create states that are not be present on the list +session:newstate('s4', 1) +session2:newstate('ss4', 1) + +-- Get the states information from kernel +local states = session:list() +local states2 = session2:list() + +assert(#states == 6) +assert(#states2 == 6) + +-- Close some states and check if they are gone when list +s1:close() +s2:close() +s3:close() + +states = session:list() +states2 = session2:list() +assert(#states == 3) +assert(#states2 == 3) + +ss1:close() +ss2:close() +ss3:close() diff --git a/lib/tests/receive.lua b/lib/tests/receive.lua new file mode 100644 index 000000000..8c1e659cd --- /dev/null +++ b/lib/tests/receive.lua @@ -0,0 +1,25 @@ +local lunatik = require'lunatik' +local memory = require'memory' +local session = lunatik.session() + +local kscript = [[ + local buffer = memory.create(3) + memory.set(buffer, 1, 65, 66, 67) + netlink.send(buffer) + memory.set(buffer, 1, 68, 69, 70) + netlink.send(buffer) +]] + +local s1 = session:newstate's1' +assert(s1 ~= nil) + +local err = s1:dostring(kscript, 'receive') +assert(err ~= nil) + +local buffer = s1:receive() +assert(memory.tostring(buffer) == 'ABC') + +buffer = s1:receive() +assert(memory.tostring(buffer) == 'DEF') + +s1:close() diff --git a/lib/tests/send.lua b/lib/tests/send.lua new file mode 100644 index 000000000..45db082b0 --- /dev/null +++ b/lib/tests/send.lua @@ -0,0 +1,33 @@ +local lunatik = require'lunatik' +local memory = require'memory' +local session = lunatik.session() + +local buffer = memory.create(3) +memory.set(buffer, 1, 0x1, 0x2, 0x3) + +-- TODO the kscript variable starts with \n, if such variable starts with \t then a kernel panic along with a deadlock will occour +local kscript = [[ + + function receive_callback(mem) + print'A memory has been received' + memory.tostring(mem) + end +]] + +local s1 = session:newstate's1' +assert(s1 ~= nil) + +local s2 = session:newstate's2' +assert(s2 ~= nil) + +local err = s1:dostring(kscript, 'send script') +assert(err ~= nil) + +err = s1:send(buffer) +assert(err ~= nil) + +err = s2:send(buffer) +assert(err == nil) + +s1:close() +s2:close() diff --git a/lib/tests/session.lua b/lib/tests/session.lua new file mode 100644 index 000000000..64371d34c --- /dev/null +++ b/lib/tests/session.lua @@ -0,0 +1,7 @@ +local lunatik = require'lunatik' + +local session = lunatik.session() +local session2 = lunatik.session() + +session:close() +session2:close() diff --git a/luanetlink.c b/luanetlink.c new file mode 100644 index 000000000..fe54ae633 --- /dev/null +++ b/luanetlink.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include + +#include "luautil.h" +#include "lunatik.h" + +extern int lunatikN_send_data(lunatik_State *state, const char *payload, size_t size); + +static int luanetlink_send(lua_State *L) +{ + lunatik_State *s = luaU_getenv(L, lunatik_State); + const char *payload; + size_t size; + int err; + + if (s == NULL) + return luaL_error(L, "invalid lunatik_State"); + + payload = luamem_checkstring(L, 1, &size); + + if ((err = lunatikN_send_data(s, payload, size)) < 0) + return luaL_error(L, "failed to send message. Return code %d", err); + + lua_pushinteger(L, (lua_Integer)size); + + return 1; +} + +static const luaL_Reg luanetlink_lib[] = { + {"send", luanetlink_send}, + {NULL, NULL} +}; + +int luaopen_netlink(lua_State *L) +{ + luaL_newlib(L, luanetlink_lib); + return 1; +} +EXPORT_SYMBOL(luaopen_netlink); diff --git a/luautil.c b/luautil.c new file mode 100644 index 000000000..fb3ea76c4 --- /dev/null +++ b/luautil.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-2019 CUJO LLC. + * Copyright (C) 1994-2018 Lua.org, PUC-Rio. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "luautil.h" + +static int msghandler(lua_State *L) +{ + const char *msg = lua_tostring(L, 1); + if (msg == NULL) { + if (luaL_callmeta(L, 1, "__tostring") && + lua_type(L, -1) == LUA_TSTRING) + return 1; + else + msg = lua_pushfstring(L, "(error object is a %s value)", + luaL_typename(L, 1)); + } + luaL_traceback(L, L, msg, 1); + return 1; +} + +int luaU_pcall(lua_State *L, int nargs, int nresults) +{ + int status; + int base = lua_gettop(L) - nargs; + lua_pushcfunction(L, msghandler); + lua_insert(L, base); + status = lua_pcall(L, nargs, nresults, base); + lua_remove(L, base); + return status; +} diff --git a/luautil.h b/luautil.h new file mode 100644 index 000000000..a44abf95a --- /dev/null +++ b/luautil.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _LUA_UTIL_H +#define _LUA_UTIL_H + +#include "lua/lua.h" +#include "lua/lauxlib.h" + +#ifndef LUNATIK_UNUSED + +typedef const struct {} luaU_id[1]; + +static inline int luaU_pushudata(lua_State *L, void *ud) +{ + return lua_rawgetp(L, LUA_REGISTRYINDEX, ud) == LUA_TUSERDATA; +} + +static inline void luaU_registerudata(lua_State *L, int v) +{ + void *ud = lua_touserdata(L, v); + lua_pushvalue(L, v); + lua_rawsetp(L, LUA_REGISTRYINDEX, ud); +} + +static inline void luaU_unregisterudata(lua_State *L, void *ud) +{ + lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, ud); +} + +static inline void luaU_setregval(lua_State *L, luaU_id id, void *v) +{ + if (v) lua_pushlightuserdata(L, v); + else lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, id); +} + +static inline void *luaU_getregval(lua_State *L, luaU_id id) +{ + void *v; + + lua_rawgetp(L, LUA_REGISTRYINDEX, id); + v = lua_touserdata(L, -1); + lua_pop(L, 1); + + return v; +} +#endif /*LUNATIK_UNUSED*/ + +#define luaU_setenv(L, env, st) { \ + st **penv = (st **)lua_getextraspace(L); \ + *penv = env; } + + +#define luaU_getenv(L, st) (*((st **)lua_getextraspace(L))) + +#ifndef LUNATIK_UNUSED +static inline int luaU_pusherr(lua_State *L, const char *err) +{ + lua_pushnil(L); + lua_pushstring(L, err); + return 2; +} +#endif /* _LUA_UTIL_H */ + +#define luaU_dostring(L, b, s, n) \ + (luaL_loadbufferx(L, b, s, n, "t") || luaU_pcall(L, 0, 0)) + +int luaU_pcall(lua_State *L, int nargs, int nresults); + +#endif /* LUNATIK_UNUSED */ diff --git a/lunatik.h b/lunatik.h new file mode 100644 index 000000000..708b6190d --- /dev/null +++ b/lunatik.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LUNATIK_STATES_H +#define LUNATIK_STATES_H + +#include "lua/lua.h" +#include "lunatik_conf.h" +#include "netlink.h" +#include "luautil.h" + +struct lunatik_instance { + struct hlist_head states_table[LUNATIK_HASH_BUCKETS]; + struct reply_buffer reply_buffer; + struct net namespace; + spinlock_t statestable_lock; + spinlock_t rfcnt_lock; + spinlock_t sendmessage_lock; + atomic_t states_count; +}; + +typedef struct lunatik_state { + struct hlist_node node; + struct lunatik_instance instance; + struct genl_info usr_state_info; + lua_State *L; + char *code_buffer; + int buffer_offset; + spinlock_t lock; + refcount_t users; + size_t maxalloc; + size_t curralloc; + size_t scriptsize; + bool inuse; + unsigned char name[LUNATIK_NAME_MAXSIZE]; +} lunatik_State; + +lunatik_State *lunatik_newstate(const char *name, size_t maxalloc); +int lunatik_close(const char *name); +lunatik_State *lunatik_statelookup(const char *name); + +bool lunatik_getstate(lunatik_State *s); +int lunatik_putstate(lunatik_State *s); + +lunatik_State *lunatik_netnewstate(const char *name, size_t maxalloc, struct net *net); +int lunatik_netclosestate(const char *name, struct net *net); +lunatik_State *lunatik_netstatelookup(const char *name, struct net *net); + +lunatik_State *lunatik_getenv(lua_State *L); + +#endif /* LUNATIK_STATES_H */ diff --git a/lunatik_conf.h b/lunatik_conf.h new file mode 100644 index 000000000..cd800b300 --- /dev/null +++ b/lunatik_conf.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LUNATIK_CONF_H +#define LUNATIK_CONF_H + +#define LUNATIK_NAME_MAXSIZE (64) /* Max length of Lua state name */ + +#define LUNATIK_SCRIPTNAME_MAXSIZE (255) /* Max length of script name */ + +#define LUNATIK_DEFAULT_NS (get_net_ns_by_pid(1)) + +#define LUNATIK_MIN_ALLOC_BYTES (32 * 1024UL) + +#define LUNATIK_HASH_BUCKETS (32) + +#endif /* LUNATIK_CONF_H */ diff --git a/lunatik_core.c b/lunatik_core.c index 4e9cc625d..f06d3381b 100644 --- a/lunatik_core.c +++ b/lunatik_core.c @@ -1,4 +1,5 @@ /* +* Copyright (c) 2020 Matheus Rodrigues * Copyright (c) 2017-2019 CUJO LLC. * * Permission is hereby granted, free of charge, to any person obtaining @@ -22,11 +23,17 @@ */ #ifdef __linux__ #include +#include +#include +#include #include "lua/lua.h" #include "lua/lauxlib.h" #include "lua/lualib.h" +#include "lunatik.h" +#include "netlink_common.h" + EXPORT_SYMBOL(lua_checkstack); EXPORT_SYMBOL(lua_xmove); EXPORT_SYMBOL(lua_atpanic); @@ -165,13 +172,91 @@ EXPORT_SYMBOL(luaopen_string); EXPORT_SYMBOL(luaopen_table); EXPORT_SYMBOL(luaopen_utf8); +EXPORT_SYMBOL(lunatik_newstate); +EXPORT_SYMBOL(lunatik_close); +EXPORT_SYMBOL(lunatik_statelookup); +EXPORT_SYMBOL(lunatik_getstate); +EXPORT_SYMBOL(lunatik_putstate); + +EXPORT_SYMBOL(lunatik_netnewstate); +EXPORT_SYMBOL(lunatik_netclosestate); +EXPORT_SYMBOL(lunatik_netstatelookup); + +EXPORT_SYMBOL(lunatik_getenv); + +extern struct genl_family lunatik_family; +extern void lunatik_statesinit(void); +extern void lunatik_closeall_from_default_ns(void); +extern void state_destroy(lunatik_State *s); + +static int lunatik_netid __read_mostly; + +struct lunatik_instance *lunatik_pernet(struct net *net) +{ + return (struct lunatik_instance *)net_generic(net,lunatik_netid); +} + +static int __net_init lunatik_instancenew(struct net *net) +{ + struct lunatik_instance *instance = lunatik_pernet(net); + + atomic_set(&(instance->states_count), 0); + spin_lock_init(&(instance->statestable_lock)); + spin_lock_init(&(instance->rfcnt_lock)); + hash_init(instance->states_table); + (instance->reply_buffer).status = RB_INIT; + instance->namespace = *net; + return 0; +} + +static void __net_exit lunatik_instanceclose(struct net *net) +{ + struct lunatik_instance *instance; + lunatik_State *s; + int bkt; + + instance = lunatik_pernet(net); + + spin_lock_bh(&(instance->statestable_lock)); + + hash_for_each(instance->states_table, bkt, s, node) { + state_destroy(s); + if (hash_empty(instance->states_table)) + break; + } + + spin_unlock_bh(&(instance->statestable_lock)); +} + +static struct pernet_operations lunatik_net_ops = { + .init = lunatik_instancenew, + .exit = lunatik_instanceclose, + .id = &lunatik_netid, + .size = sizeof(struct lunatik_instance), +}; + static int __init modinit(void) { - return 0; + int err; + + if ((err = register_pernet_subsys(&lunatik_net_ops))) { + pr_err("Failed to register pernet operations\n"); + return err; + } + + if ((err = genl_register_family(&lunatik_family))) { + pr_err("Failed to register generic netlink family\n"); + return err; + } + + return 0; } static void __exit modexit(void) { + lunatik_closeall_from_default_ns(); + unregister_pernet_subsys(&lunatik_net_ops); + genl_unregister_family(&lunatik_family); } module_init(modinit); diff --git a/netlink.c b/netlink.c new file mode 100644 index 000000000..21338c310 --- /dev/null +++ b/netlink.c @@ -0,0 +1,800 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "luautil.h" +#include "lunatik.h" +#include "netlink_common.h" + +#define DATA_RECV_FUNC ("receive_callback") + +struct lunatik_nl_state { + char name[LUNATIK_NAME_MAXSIZE]; + size_t maxalloc; + size_t curralloc; +}; + +extern struct lunatik_instance *lunatik_pernet(struct net *net); + +static int lunatikN_newstate(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_dostring(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_close(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_list(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_data(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_datainit(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_sendstate(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_getcurralloc(struct sk_buff *buff, struct genl_info *info); +static int lunatikN_putstate(struct sk_buff *buff, struct genl_info *info); + +struct nla_policy lunatik_policy[ATTRS_COUNT] = { + [STATE_NAME] = { .type = NLA_STRING }, + [CODE] = { .type = NLA_STRING }, + [SCRIPT_NAME] = { .type = NLA_STRING }, + [STATES_LIST] = { .type = NLA_STRING }, + [LUNATIK_DATA]= { .type = NLA_STRING }, + [LUNATIK_DATA_LEN] = { .type = NLA_U32}, + [SCRIPT_SIZE] = { .type = NLA_U32 }, + [MAX_ALLOC] = { .type = NLA_U32 }, + [CURR_ALLOC] = { .type = NLA_U32}, + [FLAGS] = { .type = NLA_U8 }, + [OP_SUCESS] = { .type = NLA_U8 }, + [OP_ERROR] = { .type = NLA_U8 }, +}; + +static const struct genl_ops l_ops[] = { + { + .cmd = CREATE_STATE, + .doit = lunatikN_newstate, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + /*Before kernel 5.2.0, each operation has its own policy*/ + .policy = lunatik_policy +#endif + }, + { + .cmd = EXECUTE_CODE, + .doit = lunatikN_dostring, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = DESTROY_STATE, + .doit = lunatikN_close, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = LIST_STATES, + .doit = lunatikN_list, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = DATA, + .doit = lunatikN_data, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = DATA_INIT, + .doit = lunatikN_datainit, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = GET_STATE, + .doit = lunatikN_sendstate, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = GET_CURRALLOC, + .doit = lunatikN_getcurralloc, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + }, + { + .cmd = PUT_STATE, + .doit = lunatikN_putstate, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,2,0) + .policy = lunatik_policy +#endif + } + +}; + +struct genl_family lunatik_family = { + .name = LUNATIK_FAMILY, + .version = LUNATIK_NLVERSION, + .maxattr = ATTRS_MAX, + .netnsok = true, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0) + .policy = lunatik_policy, +#endif + .module = THIS_MODULE, + .ops = l_ops, + .n_ops = ARRAY_SIZE(l_ops), +}; + +static void fill_states_list(char *buffer, struct lunatik_instance *instance) +{ + struct lunatik_state *state; + int bucket; + int counter = 0; + int states_count = atomic_read(&instance->states_count); + + hash_for_each_rcu(instance->states_table, bucket, state, node) { + buffer += sprintf(buffer, "%s#", state->name); + buffer += sprintf(buffer, "%ld#", state->curralloc); + if (counter == states_count - 1) + buffer += sprintf(buffer, "%ld", state->maxalloc); + else + buffer += sprintf(buffer, "%ld#", state->maxalloc); + counter++; + } +} + +static void reply_with(int reply, int command, struct genl_info *info) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return; + } + + if ((msg_head = genlmsg_put_reply(obuff, info, &lunatik_family, 0, command)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return; + } + + if (nla_put_u8(obuff, reply, 1)) { + pr_err("Failed to put attributes on socket buffer\n"); + return; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, info) < 0) { + pr_err("Failed to send message to user space\n"); + return; + } + + pr_debug("Message sent to user space\n"); +} + +static void send_states_list(char *buffer, int flags, struct genl_info *info) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return; + } + + if ((msg_head = genlmsg_put_reply(obuff, info, &lunatik_family, 0, LIST_STATES)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return; + } + + if (nla_put_string(obuff, STATES_LIST, buffer)) { + pr_err("Failed to put attributes on socket buffer\n"); + return; + } + + if (nla_put_u8(obuff, FLAGS, flags)) { + pr_err("Failed to put attributes on socket buffer\n"); + return; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, info) < 0) { + pr_err("Failed to send message to user space\n"); + return; + } + + pr_debug("Message sent to user space\n"); +} + +static int lunatikN_newstate(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_instance *instance; + struct lunatik_state *s; + char *state_name; + u32 *max_alloc; + + pr_debug("Received a CREATE_STATE message\n"); + + instance = lunatik_pernet(genl_info_net(info)); + state_name = (char *)nla_data(info->attrs[STATE_NAME]); + max_alloc = (u32 *)nla_data(info->attrs[MAX_ALLOC]); + + s = lunatik_netnewstate(state_name, *max_alloc, genl_info_net(info)); + + if (s == NULL) + goto error; + + if (s->inuse) + goto error; + + s->instance = *instance; + reply_with(OP_SUCESS, CREATE_STATE, info); + s->inuse = true; + + return 0; + +error: + reply_with(OP_ERROR, CREATE_STATE, info); + return 0; +} + +static void init_codebuffer(lunatik_State *s, struct genl_info *info) +{ + s->scriptsize = *((u32*)nla_data(info->attrs[SCRIPT_SIZE])); + + if ((s->code_buffer = kmalloc(s->scriptsize, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating memory to code buffer\n"); + reply_with(OP_ERROR, EXECUTE_CODE, info); + } + + s->buffer_offset = 0; +} + +static void add_fragtostate(char *fragment, lunatik_State *s) +{ + strncpy(s->code_buffer + (s->buffer_offset * LUNATIK_FRAGMENT_SIZE), + fragment, LUNATIK_FRAGMENT_SIZE); + s->buffer_offset++; +} + +static int dostring(char *code, lunatik_State *s, const char *script_name) +{ + int err = 0; + int base; + spin_lock_bh(&s->lock); + + if (!lunatik_getstate(s)) { + pr_err("Failed to get state\n"); + err = -1; + goto out; + } + + base = lua_gettop(s->L); + if ((err = luaU_dostring(s->L, code, s->scriptsize, script_name))) { + pr_err("%s\n", lua_tostring(s->L, -1)); + } + + lunatik_putstate(s); + lua_settop(s->L, base); + +out: + kfree(s->code_buffer); + spin_unlock_bh(&s->lock); + return err; +} + +static int lunatikN_dostring(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_state *s; + const char *script_name; + char *fragment; + char *state_name; + int err; + u8 flags; + + pr_debug("Received a EXECUTE_CODE message\n"); + + state_name = (char *)nla_data(info->attrs[STATE_NAME]); + fragment = (char *)nla_data(info->attrs[CODE]); + flags = *((u8*)nla_data(info->attrs[FLAGS])); + + if ((s = lunatik_netstatelookup(state_name, genl_info_net(info))) == NULL) { + pr_err("Error finding klua state\n"); + reply_with(OP_ERROR, EXECUTE_CODE, info); + return 0; + } + + if (flags & LUNATIK_INIT) { + init_codebuffer(s, info); + } + + if (flags & LUNATIK_MULTI) { + add_fragtostate(fragment, s); + } + + if (flags & LUNATIK_DONE){ + strcpy(s->code_buffer, fragment); + script_name = nla_data(info->attrs[SCRIPT_NAME]); + err = dostring(s->code_buffer, s, script_name); + err ? reply_with(OP_ERROR, EXECUTE_CODE, info) : reply_with(OP_SUCESS, EXECUTE_CODE, info); + } + + return 0; +} + +static int lunatikN_close(struct sk_buff *buff, struct genl_info *info) +{ + char *state_name; + + state_name = (char *)nla_data(info->attrs[STATE_NAME]); + + pr_debug("Received a DESTROY_STATE command\n"); + + if (lunatik_netclosestate(state_name, genl_info_net(info))) + reply_with(OP_ERROR, DESTROY_STATE, info); + else + reply_with(OP_SUCESS, DESTROY_STATE, info); + + return 0; +} + +static void send_init_information(int parts, int states_count, struct genl_info *info) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return; + } + + if ((msg_head = genlmsg_put_reply(obuff, info, &lunatik_family, 0, LIST_STATES)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return; + } + + if (nla_put_u32(obuff, STATES_COUNT, states_count) || nla_put_u32(obuff, PARTS, parts)) { + pr_err("Failed to put attributes on socket buffer\n"); + return; + } + + if (nla_put_u8(obuff, FLAGS, LUNATIK_INIT)) { + pr_err("Failed to put attributes on socket buffer\n"); + return; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, info) < 0) { + pr_err("Failed to send message to user space\n"); + return; + } + + pr_debug("Message sent to user space\n"); +} + +static int init_replybuffer(struct lunatik_instance *instance, size_t size) +{ + struct reply_buffer *reply_buffer = &instance->reply_buffer; + reply_buffer->buffer = kmalloc(size * (sizeof(struct lunatik_nl_state) + DELIMITER), GFP_KERNEL); + + if (reply_buffer->buffer == NULL) { + pr_err("Failed to allocate memory to message buffer\n"); + return -1; + } + + fill_states_list(reply_buffer->buffer, instance); + reply_buffer->curr_pos_to_send = 0; + + reply_buffer->parts = ((strlen(reply_buffer->buffer) % LUNATIK_FRAGMENT_SIZE) == 0) ? + (strlen(reply_buffer->buffer) / LUNATIK_FRAGMENT_SIZE) : + (strlen(reply_buffer->buffer) / LUNATIK_FRAGMENT_SIZE) + 1; + reply_buffer->status = RB_SENDING; + return 0; +} + +static void send_lastfragment(char *fragment, struct reply_buffer *reply_buffer, struct genl_info *info) +{ + strncpy(fragment, reply_buffer->buffer + ((reply_buffer->parts - 1) * LUNATIK_FRAGMENT_SIZE), LUNATIK_FRAGMENT_SIZE); + send_states_list(fragment, LUNATIK_DONE, info); +} + +static void send_fragment(char *fragment, struct reply_buffer *reply_buffer, struct genl_info *info) +{ + strncpy(fragment, reply_buffer->buffer + (reply_buffer->curr_pos_to_send * LUNATIK_FRAGMENT_SIZE), LUNATIK_FRAGMENT_SIZE); + send_states_list(fragment, LUNATIK_MULTI, info); + reply_buffer->curr_pos_to_send++; +} + +static int lunatikN_list(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_instance *instance; + struct reply_buffer *reply_buffer; + int states_count; + char *fragment; + int err = 0; + u8 flags; + + pr_debug("Received a LIST_STATES command\n"); + + instance = lunatik_pernet(genl_info_net(info)); + flags = *((u8 *)nla_data(info->attrs[FLAGS])); + states_count = atomic_read(&instance->states_count); + reply_buffer = &instance->reply_buffer; + + if ((fragment = kmalloc(LUNATIK_FRAGMENT_SIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed to allocate memory to fragment\n"); + return 0; + } + + if (states_count == 0){ + reply_with(STATES_LIST_EMPTY, LIST_STATES, info); + goto out; + } + + if (reply_buffer->status == RB_INIT) { + err = init_replybuffer(instance, states_count); + if (err) + reply_with(OP_ERROR, LIST_STATES, info); + else + send_init_information(reply_buffer->parts, states_count, info); + goto out; + } + + if (reply_buffer->curr_pos_to_send == reply_buffer->parts - 1) { + send_lastfragment(fragment, reply_buffer, info); + goto reset_reply_buffer; + } else { + send_fragment(fragment, reply_buffer, info); + } + +out: + kfree(fragment); + return 0; + +reset_reply_buffer: + reply_buffer->parts = 0; + reply_buffer->status = RB_INIT; + reply_buffer->curr_pos_to_send = 0; + kfree(fragment); + return 0; +} + +static int init_data(struct lunatik_data *data, char *buffer, size_t size) +{ + if ((data->buffer = kmalloc(size, GFP_KERNEL)) == NULL) { + pr_err("Failed to allocate memory to data buffer\n"); + return -1; + } + memcpy(data->buffer, buffer, size); + data->size = size; + return 0; +} + +static int handle_data(lua_State *L); + +static int lunatikN_data(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_data data; + lunatik_State *state; + char *payload; + char *state_name; + u32 payload_len; + int err = 0; + int base; + + state_name = nla_data(info->attrs[STATE_NAME]); + + if ((state = lunatik_netstatelookup(state_name, genl_info_net(info))) == NULL) { + pr_err("State %s not found\n", state_name); + goto error; + } + + payload = nla_data(info->attrs[LUNATIK_DATA]); + payload_len = *((u32 *)nla_data(info->attrs[LUNATIK_DATA_LEN])); + + err = init_data(&data, payload, payload_len); + if (err) + goto error; + + if (!lunatik_getstate(state)) { + pr_err("Failed to get state %s\n", state_name); + goto error; + } + + spin_lock_bh(&state->lock); + + base = lua_gettop(state->L); + lua_pushcfunction(state->L, handle_data); + lua_pushlightuserdata(state->L, &data); + if (luaU_pcall(state->L, 1, 0)) { + pr_err("%s\n", lua_tostring(state->L, -1)); + err = -1; + goto unlock; + } + +unlock: + spin_unlock_bh(&state->lock); + lua_settop(state->L, base); + lunatik_putstate(state); + + err ? reply_with(OP_ERROR, DATA, info) : reply_with(OP_SUCESS, DATA, info); + + return 0; + +error: + reply_with(OP_ERROR, DATA, info); + return 0; +} + +static int lunatikN_datainit(struct sk_buff *buff, struct genl_info *info) +{ + lunatik_State *state; + char *name; + + name = nla_data(info->attrs[STATE_NAME]); + + if ((state = lunatik_netstatelookup(name, genl_info_net(info))) == NULL) { + pr_err("Failed to find the state %s\n", name); + reply_with(OP_ERROR, DATA_INIT, info); + return 0; + } + + state->usr_state_info = *info; + + reply_with(OP_SUCESS, DATA_INIT, info); + + return 0; +} + +int lunatikN_send_data(lunatik_State *state, const char *payload, size_t size) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return 0; + } + + if ((msg_head = genlmsg_put_reply(obuff, &state->usr_state_info, &lunatik_family, 0, DATA)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return 0; + } + + if (nla_put_string(obuff, LUNATIK_DATA, payload) || + nla_put_u32(obuff, LUNATIK_DATA_LEN, size)) { + pr_err("Failed to put attributes on socket buffer\n"); + return 0; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, &state->usr_state_info) < 0) { + pr_err("Failed to send message to user space\n"); + return 0; + } + + pr_debug("Message sent to user space\n"); + return 0; +} + +static int sendstate_msg(lunatik_State *state, struct genl_info *info) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return -1; + } + + if ((msg_head = genlmsg_put_reply(obuff, info, &lunatik_family, 0, GET_STATE)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return -1; + } + + if (nla_put_string(obuff, STATE_NAME, state->name) || + nla_put_u32(obuff, MAX_ALLOC, state->maxalloc) || + nla_put_u32(obuff, CURR_ALLOC, state->curralloc)) { + pr_err("Failed to put attributes on socket buffer\n"); + return -1; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, info) < 0) { + pr_err("Failed to send message to user space\n"); + return -1; + } + + pr_debug("Message sent to user space\n"); + return 0; +} + +static int lunatikN_sendstate(struct sk_buff *buff, struct genl_info *info) +{ + lunatik_State *state; + char *state_name; + + state_name = nla_data(info->attrs[STATE_NAME]); + + if(((state = lunatik_netstatelookup(state_name, genl_info_net(info))) == NULL)) { + pr_err("State %s not found\n", state_name); + reply_with(STATE_NOT_FOUND, GET_STATE, info); + return 0; + } + + if (state->inuse) { + pr_info("State %s is already in use\n", state_name); + reply_with(OP_ERROR, GET_STATE, info); + return 0; + } + + if (sendstate_msg(state, info)) { + pr_err("Failed to send message to user space\n"); + reply_with(OP_ERROR, GET_STATE, info); + } + + state->inuse = true; + + return 0; + +} + +static int send_curralloc(int curralloc, struct genl_info *info) +{ + struct sk_buff *obuff; + void *msg_head; + + if ((obuff = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL)) == NULL) { + pr_err("Failed allocating message to an reply\n"); + return -1; + } + + if ((msg_head = genlmsg_put_reply(obuff, info, &lunatik_family, 0, GET_CURRALLOC)) == NULL) { + pr_err("Failed to put generic netlink header\n"); + return -1; + } + + if (nla_put_u32(obuff, CURR_ALLOC, curralloc)) { + pr_err("Failed to put attributes on socket buffer\n"); + return -1; + } + + genlmsg_end(obuff, msg_head); + + if (genlmsg_reply(obuff, info) < 0) { + pr_err("Failed to send message to user space\n"); + return -1; + } + + pr_debug("Message sent to user space\n"); + return 0; +} + +static int lunatikN_getcurralloc(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_state *s; + char *state_name; + + pr_debug("Received a GET_CURRALLOC message\n"); + + state_name = (char *)nla_data(info->attrs[STATE_NAME]); + + s = lunatik_netstatelookup(state_name, genl_info_net(info)); + + if (s == NULL) + goto error; + + if (send_curralloc(s->curralloc, info)) + goto error; + + return 0; + +error: + reply_with(OP_ERROR, GET_CURRALLOC, info); + return 0; +} + +static int lunatikN_putstate(struct sk_buff *buff, struct genl_info *info) +{ + struct lunatik_state *s; + char *state_name; + + pr_debug("Received a PUT_STATE command\n"); + + state_name = (char*)nla_data(info->attrs[STATE_NAME]); + s = lunatik_netstatelookup(state_name, genl_info_net(info)); + + if (s == NULL) + goto error; + + if (!s->inuse) { + reply_with(NOT_IN_USE, PUT_STATE, info); + return 0; + } + + if (lunatik_putstate(s)) + goto error; + + + s->inuse = false; + reply_with(OP_SUCESS, PUT_STATE, info); + + return 0; + +error: + reply_with(OP_ERROR, PUT_STATE, info); + return 0; +} + +/* Note: Most of the function below is copied from NFLua: https://github.com/cujoai/nflua + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +static int handle_data(lua_State *L) +{ + int error; + struct lunatik_data *req = lua_touserdata(L, 1); + + lua_pop(L, 1); + + luamem_newref(L); + luamem_setref(L, -1, req->buffer, req->size, NULL); + + if (lua_getglobal(L, DATA_RECV_FUNC) != LUA_TFUNCTION) + return luaL_error(L, "couldn't find receive function: %s\n", + DATA_RECV_FUNC); + + lua_pushvalue(L, 1); /* memory */ + + error = lua_pcall(L, 1, 0, 0); + + luamem_setref(L, 1, NULL, 0, NULL); + + if (error) + lua_error(L); + + return 0; +} diff --git a/netlink.h b/netlink.h new file mode 100644 index 000000000..caa286f75 --- /dev/null +++ b/netlink.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef NETLINK_H +#define NETLINK_H +#include + +#include "lunatik.h" + +extern struct genl_family lunatik_family; + +enum reply_buffer_status { + RB_INIT, + RB_SENDING, +}; + +struct reply_buffer { + char *buffer; + int parts; + int curr_pos_to_send; + enum reply_buffer_status status; +}; + +struct lunatik_data { + char *buffer; + size_t size; +}; + +#endif /* NETLINK_H */ diff --git a/netlink_common.h b/netlink_common.h new file mode 100644 index 000000000..396a4dc02 --- /dev/null +++ b/netlink_common.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef NETLINK_COMMOM_H +#define NETLINK_COMMOM_H + +#ifdef _KERNEL +extern struct genl_family lunatik_family; +#include +#endif /* _KERNEL */ + +#define LUNATIK_FRAGMENT_SIZE (3000) /* TODO Find, a more precise size */ +#define DELIMITER (3) /* How many delimiters will be necessary in each part of the message */ + +/*Lunatik generic netlink protocol flags*/ +#define LUNATIK_INIT (0x01) /* Initializes the needed variables for script execution */ +#define LUNATIK_MULTI (0x02) /* A Fragment of a multipart message */ +#define LUNATIK_DONE (0x04) /* Last message of a multipart message */ + +#define LUNATIK_FAMILY ("lunatik_family") +#define LUNATIK_NLVERSION (1) + +enum lunatik_operations { + CREATE_STATE = 1, /* Starts at 1 because 0 is used by generic netlink */ + EXECUTE_CODE, + DESTROY_STATE, + LIST_STATES, + DATA, + DATA_INIT, + GET_STATE, + GET_CURRALLOC, + PUT_STATE, +}; + +enum lunatik_attrs { + STATE_NAME = 1, + MAX_ALLOC, + STATES_LIST, + STATES_COUNT, + PARTS, + CODE, + FLAGS, + SCRIPT_SIZE, + SCRIPT_NAME, + STATES_LIST_EMPTY, + OP_SUCESS, + OP_ERROR, + LUNATIK_DATA, + LUNATIK_DATA_LEN, + CURR_ALLOC, + STATE_NOT_FOUND, + NOT_IN_USE, + ATTRS_COUNT +#define ATTRS_MAX (ATTRS_COUNT - 1) +}; + +#endif /* NETLINK_COMMOM_H */ diff --git a/states.c b/states.c new file mode 100644 index 000000000..404d25e94 --- /dev/null +++ b/states.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2020 Matheus Rodrigues + * Copyright (C) 2017-2019 CUJO LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include + +#include "lua/lualib.h" + +#include "luautil.h" +#include "lunatik.h" + +#ifndef LUNATIK_SETPAUSE +#define LUNATIK_SETPAUSE (100) +#endif /* LUNATIK_SETPAUSE */ + +extern int luaopen_memory(lua_State *); +extern int luaopen_netlink(lua_State *L); +extern struct lunatik_instance *lunatik_pernet(struct net *net); + +static const luaL_Reg libs[] = { + {"memory", luaopen_memory}, + {"netlink", luaopen_netlink}, + {NULL, NULL} +}; + +static inline int name_hash(void *salt, const char *name) +{ + int len = strnlen(name, LUNATIK_NAME_MAXSIZE); + return full_name_hash(salt, name, len) & (LUNATIK_HASH_BUCKETS - 1); +} + +inline lunatik_State *lunatik_statelookup(const char *name) +{ + return lunatik_netstatelookup(name, LUNATIK_DEFAULT_NS); +} + +void state_destroy(lunatik_State *s) +{ + hash_del_rcu(&s->node); + atomic_dec(&(s->instance.states_count)); + + spin_lock_bh(&s->lock); + if (s->L != NULL) { + lua_close(s->L); + s->L = NULL; + } + spin_unlock_bh(&s->lock); + + lunatik_putstate(s); +} + +static void *lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) +{ + lunatik_State *s = ud; + void *nptr = NULL; + + /* osize doesn't represent the object old size if ptr is NULL */ + osize = ptr != NULL ? osize : 0; + + if (nsize == 0) { + s->curralloc -= osize; + kfree(ptr); + } else if (s->curralloc - osize + nsize > s->maxalloc) { + pr_warn_ratelimited("maxalloc limit %zu reached on state %.*s\n", + s->maxalloc, LUNATIK_NAME_MAXSIZE, s->name); + } else if ((nptr = krealloc(ptr, nsize, GFP_ATOMIC)) != NULL) { + s->curralloc += nsize - osize; + } + + return nptr; +} + +static int state_init(lunatik_State *s) +{ + const luaL_Reg *lib; + + s->L = lua_newstate(lua_alloc, s); + if (s->L == NULL) + return -ENOMEM; + + luaU_setenv(s->L, s, lunatik_State); + luaL_openlibs(s->L); + + for (lib = libs; lib->name != NULL; lib++) { + luaL_requiref(s->L, lib->name, lib->func, 1); + lua_pop(s->L, 1); + } + + /* fixes an issue where the Lua's GC enters a vicious cycle. + * more info here: https://marc.info/?l=lua-l&m=155024035605499&w=2 + */ + lua_gc(s->L, LUA_GCSETPAUSE, LUNATIK_SETPAUSE); + + return 0; +} + +inline lunatik_State *lunatik_newstate(const char *name, size_t maxalloc) +{ + return lunatik_netnewstate(name, maxalloc, LUNATIK_DEFAULT_NS); +} + +inline int lunatik_close(const char *name) +{ + return lunatik_netclosestate(name, LUNATIK_DEFAULT_NS); +} + +void lunatik_closeall_from_default_ns(void) +{ + struct lunatik_instance *instance; + struct hlist_node *tmp; + lunatik_State *s; + int bkt; + + instance = lunatik_pernet(LUNATIK_DEFAULT_NS); + + spin_lock_bh(&(instance->statestable_lock)); + hash_for_each_safe (instance->states_table, bkt, tmp, s, node) { + state_destroy(s); + } + spin_unlock_bh(&(instance->statestable_lock)); +} + +inline bool lunatik_getstate(lunatik_State *s) +{ + return refcount_inc_not_zero(&s->users); +} + +int lunatik_putstate(lunatik_State *s) +{ + refcount_t *users = &s->users; + spinlock_t *refcnt_lock = &(s->instance.rfcnt_lock); + + if (WARN_ON(s == NULL)) + return 1; + + if (refcount_dec_not_one(users)) + return 1; + + spin_lock_bh(refcnt_lock); + if (!refcount_dec_and_test(users)) + goto error_and_unlock; + + hash_del_rcu(&s->node); + kfree(s); + spin_unlock_bh(refcnt_lock); + return 0; + +error_and_unlock: + spin_unlock_bh(refcnt_lock); + return 1; +} + +lunatik_State *lunatik_netstatelookup(const char *name, struct net *net) +{ + lunatik_State *state; + struct lunatik_instance *instance; + int key; + + instance = lunatik_pernet(net); + + key = name_hash(instance, name); + + hash_for_each_possible_rcu(instance->states_table, state, node, key) { + if (!strncmp(state->name, name, LUNATIK_NAME_MAXSIZE)) + return state; + } + return NULL; +} + +lunatik_State *lunatik_netnewstate(const char *name, size_t maxalloc, struct net *net) +{ + lunatik_State *s = lunatik_netstatelookup(name, net); + struct lunatik_instance *instance; + + instance = lunatik_pernet(net); + + int namelen = strnlen(name, LUNATIK_NAME_MAXSIZE); + + pr_debug("creating state: %.*s maxalloc: %zd\n", namelen, name, + maxalloc); + + if (s != NULL) { + pr_err("state already exists: %.*s\n", namelen, name); + return NULL; + } + + if (atomic_read(&(instance->states_count)) >= LUNATIK_HASH_BUCKETS) { + pr_err("could not allocate id for state %.*s\n", namelen, name); + pr_err("max states limit reached or out of memory\n"); + return NULL; + } + + if (maxalloc < LUNATIK_MIN_ALLOC_BYTES) { + pr_err("maxalloc %zu should be greater then MIN_ALLOC %zu\n", + maxalloc, LUNATIK_MIN_ALLOC_BYTES); + return NULL; + } + + if ((s = kzalloc(sizeof(lunatik_State), GFP_ATOMIC)) == NULL) { + pr_err("could not allocate nflua state\n"); + return NULL; + } + + spin_lock_init(&s->lock); + s->maxalloc = maxalloc; + s->curralloc = 0; + memcpy(&(s->name), name, namelen); + + if (state_init(s)) { + pr_err("could not allocate a new lua state\n"); + kfree(s); + return NULL; + } + + spin_lock_bh(&(instance->statestable_lock)); + hash_add_rcu(instance->states_table, &(s->node), name_hash(instance,name)); + refcount_inc(&(s->users)); + atomic_inc(&(instance->states_count)); + spin_unlock_bh(&(instance->statestable_lock)); + + pr_debug("new state created: %.*s\n", namelen, name); + return s; +} + +int lunatik_netclosestate(const char *name, struct net *net) +{ + lunatik_State *s = lunatik_netstatelookup(name, net); + struct lunatik_instance *instance; + + instance = lunatik_pernet(net); + + if (s == NULL || refcount_read(&s->users) > 1) + return -1; + + spin_lock_bh(&(instance->statestable_lock)); + + hash_del_rcu(&s->node); + atomic_dec(&(instance->states_count)); + + spin_lock_bh(&s->lock); + if (s->L != NULL) { + lua_close(s->L); + s->L = NULL; + } + spin_unlock_bh(&s->lock); + lunatik_putstate(s); + + spin_unlock_bh(&(instance->statestable_lock)); + + return 0; +} + +lunatik_State *lunatik_getenv(lua_State *L) +{ + return luaU_getenv(L, lunatik_State); +}