#include <haproxy/channel.h>
#include <haproxy/cli.h>
#include <haproxy/connection.h>
+#include <haproxy/filters.h>
#include <haproxy/h1.h>
#include <haproxy/hlua.h>
#include <haproxy/hlua_fcn.h>
*/
static lua_State *hlua_states[MAX_THREADS + 1];
+struct hlua_reg_filter {
+ char *name;
+ int flt_ref[MAX_THREADS + 1];
+ int fun_ref[MAX_THREADS + 1];
+ struct list l;
+};
+
+struct hlua_flt_config {
+ struct hlua_reg_filter *reg;
+ int ref[MAX_THREADS + 1];
+ char **args;
+};
+
+struct hlua_flt_ctx {
+ int ref; /* ref to the filter lua object */
+ struct hlua *hlua[2]; /* lua runtime context (0: request, 1: response) */
+ unsigned int cur_off[2]; /* current offset (0: request, 1: response) */
+ unsigned int cur_len[2]; /* current forwardable length (0: request, 1: response) */
+ unsigned int flags; /* HLUA_FLT_CTX_FL_* */
+};
+
+DECLARE_STATIC_POOL(pool_head_hlua_flt_ctx, "hlua_flt_ctx", sizeof(struct hlua_flt_ctx));
+
+/* This is the chained list of struct hlua_flt referenced
+ * for haproxy filters. It is used for a post-initialisation control.
+ */
+static struct list referenced_filters = LIST_HEAD_INIT(referenced_filters);
+
+
/* This is the memory pool containing struct lua for applets
* (including cli).
*/
return 0;
}
+/* Create a new registered filter. Only its name is filled */
+static inline struct hlua_reg_filter *new_hlua_reg_filter(const char *name)
+{
+ struct hlua_reg_filter *reg_flt;
+ int i;
+
+ reg_flt = calloc(1, sizeof(*reg_flt));
+ if (!reg_flt)
+ return NULL;
+ reg_flt->name = strdup(name);
+ if (!reg_flt->name) {
+ free(reg_flt);
+ return NULL;
+ }
+ LIST_APPEND(&referenced_filters, ®_flt->l);
+ for (i = 0; i < MAX_THREADS + 1; i++) {
+ reg_flt->flt_ref[i] = -1;
+ reg_flt->fun_ref[i] = -1;
+ }
+ return reg_flt;
+}
+
+/* Release a registered filter */
+static inline void release_hlua_reg_filter(struct hlua_reg_filter *reg_flt)
+{
+ if (!reg_flt)
+ return;
+ if (reg_flt->name)
+ ha_free(®_flt->name);
+ LIST_DELETE(®_flt->l);
+ ha_free(®_flt);
+}
+
+/* If the common state is set, the stack id is 0, otherwise it is the tid + 1 */
+static inline int reg_flt_to_stack_id(struct hlua_reg_filter *reg_flt)
+{
+ if (reg_flt->fun_ref[0] == -1)
+ return tid + 1;
+ return 0;
+}
+
/* Used to check an Lua function type in the stack. It creates and
* returns a reference of the function. This function throws an
* error if the rgument is not a "function".
return 0; /* Never reached */
}
+static int hlua_filter_init_per_thread(struct proxy *px, struct flt_conf *fconf)
+{
+ struct hlua_flt_config *conf = fconf->conf;
+ lua_State *L;
+ int error, pos, state_id, flt_ref;
+
+ state_id = reg_flt_to_stack_id(conf->reg);
+ L = hlua_states[state_id];
+ pos = lua_gettop(L);
+
+ /* The filter parsing function */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, conf->reg->fun_ref[state_id]);
+
+ /* Push the filter class on the stack and resolve all callbacks */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, conf->reg->flt_ref[state_id]);
+
+ /* Duplicate the filter class so each filter will have its own copy */
+ lua_newtable(L);
+ lua_pushnil(L);
+
+ while (lua_next(L, pos+2)) {
+ lua_pushvalue(L, -2);
+ lua_insert(L, -2);
+ lua_settable(L, -4);
+ }
+ flt_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ /* Remove the original lua filter class from the stack */
+ lua_pop(L, 1);
+
+ /* Push the copy on the stack */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, flt_ref);
+
+ /* extra args are pushed in a table */
+ lua_newtable(L);
+ for (pos = 0; conf->args[pos]; pos++) {
+ /* Check stack available size. */
+ if (!lua_checkstack(L, 1)) {
+ ha_alert("Lua filter '%s' : Lua error : full stack.", conf->reg->name);
+ goto error;
+ }
+ lua_pushstring(L, conf->args[pos]);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ }
+
+ error = lua_pcall(L, 2, LUA_MULTRET, 0);
+ switch (error) {
+ case LUA_OK:
+ /* replace the filter ref */
+ conf->ref[state_id] = flt_ref;
+ break;
+ case LUA_ERRRUN:
+ ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, lua_tostring(L, -1));
+ goto error;
+ case LUA_ERRMEM:
+ ha_alert("Lua filter '%s' : out of memory error", conf->reg->name);
+ goto error;
+ case LUA_ERRERR:
+ ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, lua_tostring(L, -1));
+ goto error;
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
+ case LUA_ERRGCMM:
+ ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, lua_tostring(L, -1));
+ goto error;
+#endif
+ default:
+ ha_alert("Lua filter '%s' : unknonwn error : %s", conf->reg->name, lua_tostring(L, -1));
+ goto error;
+ }
+
+ lua_settop(L, 0);
+ return 0;
+
+ error:
+ lua_settop(L, 0);
+ return -1;
+}
+
+static void hlua_filter_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
+{
+ struct hlua_flt_config *conf = fconf->conf;
+ lua_State *L;
+ int state_id;
+
+ if (!conf)
+ return;
+
+ state_id = reg_flt_to_stack_id(conf->reg);
+ L = hlua_states[state_id];
+ luaL_unref(L, LUA_REGISTRYINDEX, conf->ref[state_id]);
+}
+
+static int hlua_filter_init(struct proxy *px, struct flt_conf *fconf)
+{
+ struct hlua_flt_config *conf = fconf->conf;
+ int state_id = reg_flt_to_stack_id(conf->reg);
+
+ /* Rely on per-thread init for global scripts */
+ if (!state_id)
+ return hlua_filter_init_per_thread(px, fconf);
+ return 0;
+}
+
+static void hlua_filter_deinit(struct proxy *px, struct flt_conf *fconf)
+{
+
+ if (fconf->conf) {
+ struct hlua_flt_config *conf = fconf->conf;
+ int state_id = reg_flt_to_stack_id(conf->reg);
+ int pos;
+
+ /* Rely on per-thread deinit for global scripts */
+ if (!state_id)
+ hlua_filter_deinit_per_thread(px, fconf);
+
+ for (pos = 0; conf->args[pos]; pos++)
+ free(conf->args[pos]);
+ free(conf->args);
+ }
+ ha_free(&fconf->conf);
+ ha_free((char **)&fconf->id);
+ ha_free(&fconf->ops);
+}
+
+static int hlua_filter_new(struct stream *s, struct filter *filter)
+{
+ struct hlua_flt_config *conf = FLT_CONF(filter);
+ struct hlua_flt_ctx *flt_ctx = NULL;
+ int ret = 1;
+
+ /* In the execution wrappers linked with a stream, the
+ * Lua context can be not initialized. This behavior
+ * permits to save performances because a systematic
+ * Lua initialization cause 5% performances loss.
+ */
+ if (!s->hlua) {
+ struct hlua *hlua;
+
+ hlua = pool_alloc(pool_head_hlua);
+ if (!hlua) {
+ SEND_ERR(s->be, "Lua filter '%s': can't initialize Lua context.\n",
+ conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+ HLUA_INIT(hlua);
+ s->hlua = hlua;
+ if (!hlua_ctx_init(s->hlua, reg_flt_to_stack_id(conf->reg), s->task, 0)) {
+ SEND_ERR(s->be, "Lua filter '%s': can't initialize Lua context.\n",
+ conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+ }
+
+ flt_ctx = pool_zalloc(pool_head_hlua_flt_ctx);
+ if (!flt_ctx) {
+ SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
+ conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+ flt_ctx->hlua[0] = pool_alloc(pool_head_hlua);
+ flt_ctx->hlua[1] = pool_alloc(pool_head_hlua);
+ if (!flt_ctx->hlua[0] || !flt_ctx->hlua[1]) {
+ SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
+ conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+ HLUA_INIT(flt_ctx->hlua[0]);
+ HLUA_INIT(flt_ctx->hlua[1]);
+ if (!hlua_ctx_init(flt_ctx->hlua[0], reg_flt_to_stack_id(conf->reg), s->task, 0) ||
+ !hlua_ctx_init(flt_ctx->hlua[1], reg_flt_to_stack_id(conf->reg), s->task, 0)) {
+ SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
+ conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+
+ if (!HLUA_IS_RUNNING(s->hlua)) {
+ /* The following Lua calls can fail. */
+ if (!SET_SAFE_LJMP(s->hlua)) {
+ const char *error;
+
+ if (lua_type(s->hlua->T, -1) == LUA_TSTRING)
+ error = lua_tostring(s->hlua->T, -1);
+ else
+ error = "critical error";
+ SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
+ ret = 0;
+ goto end;
+ }
+
+ /* Check stack size. */
+ if (!lua_checkstack(s->hlua->T, 1)) {
+ SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name);
+ ret = 0;
+ goto end;
+ }
+
+ lua_rawgeti(s->hlua->T, LUA_REGISTRYINDEX, conf->ref[s->hlua->state_id]);
+ if (lua_getfield(s->hlua->T, -1, "new") != LUA_TFUNCTION) {
+ SEND_ERR(s->be, "Lua filter '%s': 'new' field is not a function.\n",
+ conf->reg->name);
+ RESET_SAFE_LJMP(s->hlua);
+ ret = 0;
+ goto end;
+ }
+ lua_insert(s->hlua->T, -2);
+
+ /* Push the copy on the stack */
+ s->hlua->nargs = 1;
+
+ /* We must initialize the execution timeouts. */
+ s->hlua->max_time = hlua_timeout_session;
+
+ /* At this point the execution is safe. */
+ RESET_SAFE_LJMP(s->hlua);
+ }
+
+ switch (hlua_ctx_resume(s->hlua, 0)) {
+ case HLUA_E_OK:
+ /* Nothing returned or not a table, ignore the filter for current stream */
+ if (!lua_gettop(s->hlua->T) || !lua_istable(s->hlua->T, 1)) {
+ ret = 0;
+ goto end;
+ }
+
+ /* Attached the filter pointer to the ctx */
+ lua_pushstring(s->hlua->T, "__filter");
+ lua_pushlightuserdata(s->hlua->T, filter);
+ lua_settable(s->hlua->T, -3);
+
+ /* Save a ref on the filter ctx */
+ lua_pushvalue(s->hlua->T, 1);
+ flt_ctx->ref = luaL_ref(s->hlua->T, LUA_REGISTRYINDEX);
+ filter->ctx = flt_ctx;
+ break;
+ case HLUA_E_ERRMSG:
+ SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(s->hlua->T, -1));
+ ret = -1;
+ goto end;
+ case HLUA_E_ETMOUT:
+ SEND_ERR(s->be, "Lua filter '%s' : 'new' execution timeout.\n", conf->reg->name);
+ ret = 0;
+ goto end;
+ case HLUA_E_NOMEM:
+ SEND_ERR(s->be, "Lua filter '%s' : out of memory error.\n", conf->reg->name);
+ ret = 0;
+ goto end;
+ case HLUA_E_AGAIN:
+ case HLUA_E_YIELD:
+ SEND_ERR(s->be, "Lua filter '%s': yield functions like core.tcp() or core.sleep()"
+ " are not allowed from 'new' function.\n", conf->reg->name);
+ ret = 0;
+ goto end;
+ case HLUA_E_ERR:
+ SEND_ERR(s->be, "Lua filter '%s': 'new' returns an unknown error.\n", conf->reg->name);
+ ret = 0;
+ goto end;
+ default:
+ ret = 0;
+ goto end;
+ }
+
+ end:
+ if (s->hlua)
+ lua_settop(s->hlua->T, 0);
+ if (ret <= 0) {
+ if (flt_ctx) {
+ hlua_ctx_destroy(flt_ctx->hlua[0]);
+ hlua_ctx_destroy(flt_ctx->hlua[1]);
+ pool_free(pool_head_hlua_flt_ctx, flt_ctx);
+ }
+ }
+ return ret;
+}
+
+static void hlua_filter_delete(struct stream *s, struct filter *filter)
+{
+ struct hlua_flt_ctx *flt_ctx = filter->ctx;
+
+ luaL_unref(s->hlua->T, LUA_REGISTRYINDEX, flt_ctx->ref);
+ hlua_ctx_destroy(flt_ctx->hlua[0]);
+ hlua_ctx_destroy(flt_ctx->hlua[1]);
+ pool_free(pool_head_hlua_flt_ctx, flt_ctx);
+ filter->ctx = NULL;
+}
+
+static int hlua_filter_parse_fct(char **args, int *cur_arg, struct proxy *px,
+ struct flt_conf *fconf, char **err, void *private)
+{
+ struct hlua_reg_filter *reg_flt = private;
+ lua_State *L;
+ struct hlua_flt_config *conf = NULL;
+ const char *flt_id = NULL;
+ int state_id, pos, flt_flags = 0;
+ struct flt_ops *hlua_flt_ops = NULL;
+
+ state_id = reg_flt_to_stack_id(reg_flt);
+ L = hlua_states[state_id];
+
+ /* Initialize the filter ops with default callbacks */
+ hlua_flt_ops = calloc(1, sizeof(*hlua_flt_ops));
+ if (!hlua_flt_ops) {
+ memprintf(err, "Lua filter '%s' : Lua out of memory error", reg_flt->name);
+ return -1;
+ }
+ hlua_flt_ops->init = hlua_filter_init;
+ hlua_flt_ops->deinit = hlua_filter_deinit;
+ if (state_id) {
+ /* Set per-thread callback if script is loaded per-thread */
+ hlua_flt_ops->init_per_thread = hlua_filter_init_per_thread;
+ hlua_flt_ops->deinit_per_thread = hlua_filter_deinit_per_thread;
+ }
+ hlua_flt_ops->attach = hlua_filter_new;
+ hlua_flt_ops->detach = hlua_filter_delete;
+
+ /* Push the filter class on the stack and resolve all callbacks */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, reg_flt->flt_ref[state_id]);
+
+ /*
+ * XXX: no callback supported for now
+ */
+
+ /* Get id and flags of the filter class */
+ if (lua_getfield(L, -1, "id") == LUA_TSTRING)
+ flt_id = lua_tostring(L, -1);
+ lua_pop(L, 1);
+ if (lua_getfield(L, -1, "flags") == LUA_TNUMBER)
+ flt_flags = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+
+ /* Create the filter config */
+ conf = calloc(1, sizeof(*conf));
+ if (!conf) {
+ memprintf(err, "Lua filter '%s' : Lua out of memory error", reg_flt->name);
+ goto error;
+ }
+ conf->reg = reg_flt;
+
+ /* duplicate args */
+ for (pos = 0; *args[*cur_arg + 1 + pos]; pos++);
+ conf->args = calloc(pos + 1, sizeof(*conf->args));
+ for (pos = 0; *args[*cur_arg + 1 + pos]; pos++)
+ conf->args[pos] = strdup(args[*cur_arg + 1 + pos]);
+ conf->args[pos] = NULL;
+ *cur_arg += pos + 1;
+
+ fconf->id = strdup(flt_id);
+ fconf->flags = flt_flags;
+ fconf->conf = conf;
+ fconf->ops = hlua_flt_ops;
+
+ lua_settop(L, 0);
+ return 0;
+
+ error:
+ free(hlua_flt_ops);
+ free(conf);
+ lua_settop(L, 0);
+ return -1;
+}
+
+/* This function is an LUA binding used for registering a filter. It expects a
+ * fileter name used in the haproxy configuration file and a LUA function to
+ * parse configuration arguments.
+ */
+__LJMP static int hlua_register_filter(lua_State *L)
+{
+ struct buffer *trash;
+ struct flt_kw_list *fkl;
+ struct flt_kw *fkw;
+ const char *name;
+ struct hlua_reg_filter *reg_flt= NULL;
+ int flt_ref, fun_ref;
+ int len;
+
+ MAY_LJMP(check_args(L, 3, "register_filter"));
+
+ /* First argument : filter name. */
+ name = MAY_LJMP(luaL_checkstring(L, 1));
+
+ /* Second argument : The filter class */
+ flt_ref = MAY_LJMP(hlua_checktable(L, 2));
+
+ /* Third argument : lua function. */
+ fun_ref = MAY_LJMP(hlua_checkfunction(L, 3));
+
+ trash = get_trash_chunk();
+ chunk_printf(trash, "lua.%s", name);
+ fkw = flt_find_kw(trash->area);
+ if (fkw != NULL) {
+ reg_flt = fkw->private;
+ if (reg_flt->flt_ref[hlua_state_id] != -1 || reg_flt->fun_ref[hlua_state_id] != -1) {
+ ha_warning("Trying to register filter 'lua.%s' more than once. "
+ "This will become a hard error in version 2.5.\n", name);
+ }
+ reg_flt->flt_ref[hlua_state_id] = flt_ref;
+ reg_flt->fun_ref[hlua_state_id] = fun_ref;
+ return 0;
+ }
+
+ fkl = calloc(1, sizeof(*fkl) + sizeof(struct flt_kw) * 2);
+ if (!fkl)
+ goto alloc_error;
+ fkl->scope = "HLUA";
+
+ reg_flt = new_hlua_reg_filter(name);
+ if (!reg_flt)
+ goto alloc_error;
+
+ reg_flt->flt_ref[hlua_state_id] = flt_ref;
+ reg_flt->fun_ref[hlua_state_id] = fun_ref;
+
+ /* The filter keyword */
+ len = strlen("lua.") + strlen(name) + 1;
+ fkl->kw[0].kw = calloc(1, len);
+ if (!fkl->kw[0].kw)
+ goto alloc_error;
+
+ snprintf((char *)fkl->kw[0].kw, len, "lua.%s", name);
+
+ fkl->kw[0].parse = hlua_filter_parse_fct;
+ fkl->kw[0].private = reg_flt;
+ memset(&fkl->kw[1], 0, sizeof(*fkl->kw));
+
+ /* Register this new filter */
+ flt_register_keywords(fkl);
+
+ return 0;
+
+ alloc_error:
+ release_hlua_reg_filter(reg_flt);
+ ha_free(&fkl);
+ WILL_LJMP(luaL_error(L, "Lua out of memory error."));
+ return 0; /* Never reached */
+}
+
static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line,
char **err, unsigned int *timeout)
int errors;
char *err = NULL;
struct hlua_function *fcn;
+ struct hlua_reg_filter *reg_flt;
#if USE_OPENSSL
/* Initialize SSL server. */
}
}
+ /* Do the same with registered filters */
+ list_for_each_entry(reg_flt, &referenced_filters, l) {
+ ret = 0;
+ for (i = 1; i < global.nbthread + 1; i++) {
+ if (reg_flt->flt_ref[i] == -1)
+ ret--;
+ else
+ ret++;
+ }
+ if (abs(ret) != global.nbthread) {
+ ha_alert("Lua filter '%s' is not referenced in all thread. "
+ "Expect function in all thread or in none thread.\n", reg_flt->name);
+ errors++;
+ continue;
+ }
+
+ if ((reg_flt->flt_ref[0] == -1) == (ret < 0)) {
+ ha_alert("Lua filter '%s' is referenced both ins shared Lua context (through lua-load) "
+ "and per-thread Lua context (through lua-load-per-thread). these two context "
+ "exclusive.\n", fcn->name);
+ errors++;
+ }
+ }
+
+
if (errors > 0)
return 0;
hlua_class_function(L, "register_action", hlua_register_action);
hlua_class_function(L, "register_service", hlua_register_service);
hlua_class_function(L, "register_cli", hlua_register_cli);
+ hlua_class_function(L, "register_filter", hlua_register_filter);
hlua_class_function(L, "yield", hlua_yield);
hlua_class_function(L, "set_nice", hlua_set_nice);
hlua_class_function(L, "sleep", hlua_sleep);
lua_setglobal(L, "act");
+ /*
+ *
+ * Create "Filter" object.
+ *
+ */
+
+ /* This table entry is the object "filter" base. */
+ lua_newtable(L);
+
+ /* push flags and constants */
+ hlua_class_const_int(L, "CONTINUE", 1);
+ hlua_class_const_int(L, "WAIT", 0);
+ hlua_class_const_int(L, "ERROR", -1);
+
+ hlua_class_const_int(L, "FLT_CFG_FL_HTX", FLT_CFG_FL_HTX);
+
+ lua_setglobal(L, "filter");
+
/*
*
* Register class Map
static void hlua_deinit()
{
int thr;
+ struct hlua_reg_filter *reg_flt, *reg_flt_bck;
+
+ list_for_each_entry_safe(reg_flt, reg_flt_bck, &referenced_filters, l)
+ release_hlua_reg_filter(reg_flt);
for (thr = 0; thr < MAX_THREADS+1; thr++) {
if (hlua_states[thr])