/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. 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 3 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, see . */ #include #include #include #include #include #include "lib/cache/api.h" #include "lib/cache/cdb_api.h" #include "lib/utils.h" #include "daemon/bindings.h" #include "daemon/worker.h" #include "daemon/tls.h" #include "daemon/zimport.h" #define xstr(s) str(s) #define str(s) #s /** @internal Annotate for static checkers. */ KR_NORETURN int lua_error (lua_State *L); /** @internal Prefix error with file:line */ static int format_error(lua_State* L, const char *err) { lua_Debug d; lua_getstack(L, 1, &d); /* error message prefix */ lua_getinfo(L, "Sln", &d); if (strncmp(d.short_src, "[", 1) != 0) { lua_pushstring(L, d.short_src); lua_pushstring(L, ":"); lua_pushnumber(L, d.currentline); lua_pushstring(L, ": error: "); lua_concat(L, 4); } else { lua_pushstring(L, "error: "); } /* error message */ lua_pushstring(L, err); lua_concat(L, 2); return 1; } static inline struct worker_ctx *wrk_luaget(lua_State *L) { lua_getglobal(L, "__worker"); struct worker_ctx *worker = lua_touserdata(L, -1); lua_pop(L, 1); return worker; } /** List loaded modules */ static int mod_list(lua_State *L) { struct engine *engine = engine_luaget(L); lua_newtable(L); for (unsigned i = 0; i < engine->modules.len; ++i) { struct kr_module *module = engine->modules.at[i]; lua_pushstring(L, module->name); lua_rawseti(L, -2, i + 1); } return 1; } /** Load module. */ static int mod_load(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n != 1 || !lua_isstring(L, 1)) { format_error(L, "expected 'load(string name)'"); lua_error(L); } /* Parse precedence declaration */ char *declaration = strdup(lua_tostring(L, 1)); if (!declaration) { return kr_error(ENOMEM); } const char *name = strtok(declaration, " "); const char *precedence = strtok(NULL, " "); const char *ref = strtok(NULL, " "); /* Load engine module */ struct engine *engine = engine_luaget(L); int ret = engine_register(engine, name, precedence, ref); free(declaration); if (ret != 0) { if (ret == kr_error(EIDRM)) { format_error(L, "referenced module not found"); } else { format_error(L, kr_strerror(ret)); } lua_error(L); } lua_pushboolean(L, 1); return 1; } /** Unload module. */ static int mod_unload(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n != 1 || !lua_isstring(L, 1)) { format_error(L, "expected 'unload(string name)'"); lua_error(L); } /* Unload engine module */ struct engine *engine = engine_luaget(L); int ret = engine_unregister(engine, lua_tostring(L, 1)); if (ret != 0) { format_error(L, kr_strerror(ret)); lua_error(L); } lua_pushboolean(L, 1); return 1; } int lib_modules(lua_State *L) { static const luaL_Reg lib[] = { { "list", mod_list }, { "load", mod_load }, { "unload", mod_unload }, { NULL, NULL } }; register_lib(L, "modules", lib); return 1; } /** Append 'addr = {port = int, udp = bool, tcp = bool}' */ static int net_list_add(const char *key, void *val, void *ext) { lua_State *L = (lua_State *)ext; endpoint_array_t *ep_array = val; lua_newtable(L); for (size_t i = ep_array->len; i--;) { struct endpoint *ep = ep_array->at[i]; lua_pushinteger(L, ep->port); lua_setfield(L, -2, "port"); lua_pushboolean(L, ep->flags & NET_UDP); lua_setfield(L, -2, "udp"); lua_pushboolean(L, ep->flags & NET_TCP); lua_setfield(L, -2, "tcp"); lua_pushboolean(L, ep->flags & NET_TLS); lua_setfield(L, -2, "tls"); } lua_setfield(L, -2, key); return kr_ok(); } /** List active endpoints. */ static int net_list(lua_State *L) { struct engine *engine = engine_luaget(L); lua_newtable(L); map_walk(&engine->net.endpoints, net_list_add, L); return 1; } /** Listen on an address list represented by the top of lua stack. */ static int net_listen_addrs(lua_State *L, int port, int flags) { /* Case: table with 'addr' field; only follow that field directly. */ lua_getfield(L, -1, "addr"); if (!lua_isnil(L, -1)) { lua_replace(L, -2); } else { lua_pop(L, 1); } /* Case: string, representing a single address. */ const char *str = lua_tostring(L, -1); if (str != NULL) { struct engine *engine = engine_luaget(L); int ret = network_listen(&engine->net, str, port, flags); if (ret != 0) { kr_log_info("[system] bind to '%s@%d' %s\n", str, port, kr_strerror(ret)); } return ret == 0; } /* Last case: table where all entries are added recursively. */ if (!lua_istable(L, -1)) { format_error(L, "bad type for address"); lua_error(L); return 0; } lua_pushnil(L); while (lua_next(L, -2)) { if (net_listen_addrs(L, port, flags) == 0) return 0; lua_pop(L, 1); } return 1; } static bool table_get_flag(lua_State *L, int index, const char *key, bool def) { bool result = def; lua_getfield(L, index, key); if (lua_isboolean(L, -1)) { result = lua_toboolean(L, -1); } lua_pop(L, 1); return result; } /** Listen on endpoint. */ static int net_listen(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 1 || n > 3) { format_error(L, "expected one to three arguments; usage:\n" "net.listen(addressses, [port = " xstr(KR_DNS_PORT) ", flags = {tls = (port == " xstr(KR_DNS_TLS_PORT) ")}])\n"); lua_error(L); } int port = KR_DNS_PORT; if (n > 1 && lua_isnumber(L, 2)) { port = lua_tointeger(L, 2); } bool tls = (port == KR_DNS_TLS_PORT); if (n > 2 && lua_istable(L, 3)) { tls = table_get_flag(L, 3, "tls", tls); } int flags = tls ? (NET_TCP|NET_TLS) : (NET_TCP|NET_UDP); /* Now focus on the first argument. */ lua_pop(L, n - 1); int res = net_listen_addrs(L, port, flags); lua_pushboolean(L, res); return res; } /** Close endpoint. */ static int net_close(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 2) { format_error(L, "expected 'close(string addr, number port)'"); lua_error(L); } /* Open resolution context cache */ struct engine *engine = engine_luaget(L); int ret = network_close(&engine->net, lua_tostring(L, 1), lua_tointeger(L, 2)); lua_pushboolean(L, ret == 0); return 1; } /** List available interfaces. */ static int net_interfaces(lua_State *L) { /* Retrieve interface list */ int count = 0; char buf[INET6_ADDRSTRLEN]; /* https://tools.ietf.org/html/rfc4291 */ uv_interface_address_t *info = NULL; uv_interface_addresses(&info, &count); lua_newtable(L); for (int i = 0; i < count; ++i) { uv_interface_address_t iface = info[i]; lua_getfield(L, -1, iface.name); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); } /* Address */ lua_getfield(L, -1, "addr"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); } if (iface.address.address4.sin_family == AF_INET) { uv_ip4_name(&iface.address.address4, buf, sizeof(buf)); } else if (iface.address.address4.sin_family == AF_INET6) { uv_ip6_name(&iface.address.address6, buf, sizeof(buf)); } else { buf[0] = '\0'; } lua_pushstring(L, buf); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); lua_setfield(L, -2, "addr"); /* Hardware address. */ char *p = buf; memset(buf, 0, sizeof(buf)); for (unsigned k = 0; k < sizeof(iface.phys_addr); ++k) { sprintf(p, "%s%.2x", k > 0 ? ":" : "", iface.phys_addr[k] & 0xff); p += 3; } lua_pushstring(L, buf); lua_setfield(L, -2, "mac"); /* Push table */ lua_setfield(L, -2, iface.name); } uv_free_interface_addresses(info, count); return 1; } /** Set UDP maximum payload size. */ static int net_bufsize(lua_State *L) { struct engine *engine = engine_luaget(L); knot_rrset_t *opt_rr = engine->resolver.opt_rr; if (!lua_isnumber(L, 1)) { lua_pushnumber(L, knot_edns_get_payload(opt_rr)); return 1; } int bufsize = lua_tointeger(L, 1); if (bufsize < 512 || bufsize > UINT16_MAX) { format_error(L, "bufsize must be within <512, " xstr(UINT16_MAX) ">"); lua_error(L); } knot_edns_set_payload(opt_rr, (uint16_t) bufsize); return 0; } /** Set TCP pipelining size. */ static int net_pipeline(lua_State *L) { struct worker_ctx *worker = wrk_luaget(L); if (!worker) { return 0; } if (!lua_isnumber(L, 1)) { lua_pushnumber(L, worker->tcp_pipeline_max); return 1; } int len = lua_tointeger(L, 1); if (len < 0 || len > UINT16_MAX) { format_error(L, "tcp_pipeline must be within <0, " xstr(UINT16_MAX) ">"); lua_error(L); } worker->tcp_pipeline_max = len; lua_pushnumber(L, len); return 1; } static int net_tls(lua_State *L) { struct engine *engine = engine_luaget(L); if (!engine) { return 0; } struct network *net = &engine->net; if (!net) { return 0; } /* Only return current credentials. */ if (lua_gettop(L) == 0) { /* No credentials configured yet. */ if (!net->tls_credentials) { return 0; } lua_newtable(L); lua_pushstring(L, net->tls_credentials->tls_cert); lua_setfield(L, -2, "cert_file"); lua_pushstring(L, net->tls_credentials->tls_key); lua_setfield(L, -2, "key_file"); return 1; } if ((lua_gettop(L) != 2) || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { lua_pushstring(L, "net.tls takes two parameters: (\"cert_file\", \"key_file\")"); lua_error(L); } int r = tls_certificate_set(net, lua_tostring(L, 1), lua_tostring(L, 2)); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } lua_pushboolean(L, true); return 1; } static int print_tls_param(const char *key, void *val, void *data) { if (!val) { return 0; } struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)val; lua_State *L = (lua_State *)data; lua_createtable(L, 0, 3); lua_createtable(L, entry->pins.len, 0); for (size_t i = 0; i < entry->pins.len; ++i) { lua_pushnumber(L, i + 1); lua_pushstring(L, entry->pins.at[i]); lua_settable(L, -3); } lua_setfield(L, -2, "pins"); lua_createtable(L, entry->ca_files.len, 0); for (size_t i = 0; i < entry->ca_files.len; ++i) { lua_pushnumber(L, i + 1); lua_pushstring(L, entry->ca_files.at[i]); lua_settable(L, -3); } lua_setfield(L, -2, "ca_files"); lua_createtable(L, entry->hostnames.len, 0); for (size_t i = 0; i < entry->hostnames.len; ++i) { lua_pushnumber(L, i + 1); lua_pushstring(L, entry->hostnames.at[i]); lua_settable(L, -3); } lua_setfield(L, -2, "hostnames"); lua_setfield(L, -2, key); return 0; } static int print_tls_client_params(lua_State *L) { struct engine *engine = engine_luaget(L); if (!engine) { return 0; } struct network *net = &engine->net; if (!net) { return 0; } if (net->tls_client_params.root == 0 ) { return 0; } lua_newtable(L); map_walk(&net->tls_client_params, print_tls_param, (void *)L); return 1; } static int net_tls_client(lua_State *L) { struct engine *engine = engine_luaget(L); if (!engine) { return 0; } struct network *net = &engine->net; if (!net) { return 0; } /* Only return current credentials. */ if (lua_gettop(L) == 0) { return print_tls_client_params(L); } const char *full_addr = NULL; bool pin_exists = false; bool hostname_exists = false; if ((lua_gettop(L) == 1) && lua_isstring(L, 1)) { full_addr = lua_tostring(L, 1); } else if ((lua_gettop(L) == 2) && lua_isstring(L, 1) && lua_istable(L, 2)) { full_addr = lua_tostring(L, 1); pin_exists = true; } else if ((lua_gettop(L) == 3) && lua_isstring(L, 1) && lua_istable(L, 2)) { full_addr = lua_tostring(L, 1); hostname_exists = true; } else if ((lua_gettop(L) == 4) && lua_isstring(L, 1) && lua_istable(L, 2) && lua_istable(L, 3)) { full_addr = lua_tostring(L, 1); pin_exists = true; hostname_exists = true; } else { format_error(L, "net.tls_client takes one parameter (\"address\"), two parameters (\"address\",\"pin\"), three parameters (\"address\", \"ca_file\", \"hostname\") or four ones: (\"address\", \"pin\", \"ca_file\", \"hostname\")"); lua_error(L); } char addr[INET6_ADDRSTRLEN]; uint16_t port = 0; if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) { format_error(L, "invalid IP address"); lua_error(L); } if (port == 0) { port = 853; } if (!pin_exists && !hostname_exists) { int r = tls_client_params_set(&net->tls_client_params, addr, port, NULL, TLS_CLIENT_PARAM_NONE); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } lua_pushboolean(L, true); return 1; } if (pin_exists) { /* iterate over table with pins * http://www.lua.org/manual/5.1/manual.html#lua_next */ lua_pushnil(L); /* first key */ while (lua_next(L, 2)) { /* pin table is in stack at index 2 */ /* pin now at index -1, key at index -2*/ const char *pin = lua_tostring(L, -1); int r = tls_client_params_set(&net->tls_client_params, addr, port, pin, TLS_CLIENT_PARAM_PIN); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } lua_pop(L, 1); } } int ca_table_index = 2; int hostname_table_index = 3; if (hostname_exists) { if (pin_exists) { ca_table_index = 3; hostname_table_index = 4; } } else { lua_pushboolean(L, true); return 1; } /* iterate over hostnames, * it must be done before iterating over ca filenames */ lua_pushnil(L); while (lua_next(L, hostname_table_index)) { const char *hostname = lua_tostring(L, -1); int r = tls_client_params_set(&net->tls_client_params, addr, port, hostname, TLS_CLIENT_PARAM_HOSTNAME); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } /* iterate over ca filenames */ lua_pushnil(L); size_t num_of_ca_files = 0; while (lua_next(L, ca_table_index)) { const char *ca_file = lua_tostring(L, -1); int r = tls_client_params_set(&net->tls_client_params, addr, port, ca_file, TLS_CLIENT_PARAM_CA); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } num_of_ca_files += 1; /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } if (num_of_ca_files == 0) { /* No ca files were explicitly configured, so use system CA */ int r = tls_client_params_set(&net->tls_client_params, addr, port, NULL, TLS_CLIENT_PARAM_CA); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } } lua_pushboolean(L, true); return 1; } static int net_tls_client_clear(lua_State *L) { struct engine *engine = engine_luaget(L); if (!engine) { return 0; } struct network *net = &engine->net; if (!net) { return 0; } if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) { format_error(L, "net.tls_client_clear() requires one parameter (\"address\")"); lua_error(L); } const char *full_addr = lua_tostring(L, 1); char addr[INET6_ADDRSTRLEN]; uint16_t port = 0; if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) { format_error(L, "invalid IP address"); lua_error(L); } if (port == 0) { port = 853; } int r = tls_client_params_clear(&net->tls_client_params, addr, port); if (r != 0) { lua_pushstring(L, kr_strerror(r)); lua_error(L); } lua_pushboolean(L, true); return 1; } static int net_tls_padding(lua_State *L) { struct engine *engine = engine_luaget(L); /* Only return current padding. */ if (lua_gettop(L) == 0) { if (engine->resolver.tls_padding < 0) { lua_pushboolean(L, true); return 1; } else if (engine->resolver.tls_padding == 0) { lua_pushboolean(L, false); return 1; } lua_pushinteger(L, engine->resolver.tls_padding); return 1; } if ((lua_gettop(L) != 1)) { lua_pushstring(L, "net.tls_padding takes one parameter: (\"padding\")"); lua_error(L); } if (lua_isboolean(L, 1)) { bool x = lua_toboolean(L, 1); if (x) { engine->resolver.tls_padding = -1; } else { engine->resolver.tls_padding = 0; } } else if (lua_isnumber(L, 1)) { int padding = lua_tointeger(L, 1); if ((padding < 0) || (padding > MAX_TLS_PADDING)) { lua_pushstring(L, "net.tls_padding parameter has to be true, false, or a number between <0, " xstr(MAX_TLS_PADDING) ">"); lua_error(L); } engine->resolver.tls_padding = padding; } else { lua_pushstring(L, "net.tls_padding parameter has to be true, false, or a number between <0, " xstr(MAX_TLS_PADDING) ">"); lua_error(L); } lua_pushboolean(L, true); return 1; } /** Shorter salt can't contain much entropy. */ #define net_tls_sticket_MIN_SECRET_LEN 32 static int net_tls_sticket_secret_string(lua_State *L) { struct network *net = &engine_luaget(L)->net; size_t secret_len; const char *secret; if (lua_gettop(L) == 0) { /* Zero-length secret, implying random key. */ secret_len = 0; secret = NULL; } else { if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) { lua_pushstring(L, "net.tls_sticket_secret takes one parameter: (\"secret string\")"); lua_error(L); } secret = lua_tolstring(L, 1, &secret_len); if (secret_len < net_tls_sticket_MIN_SECRET_LEN || !secret) { lua_pushstring(L, "net.tls_sticket_secret - the secret is shorter than " xstr(net_tls_sticket_MIN_SECRET_LEN) " bytes"); lua_error(L); } } tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx); net->tls_session_ticket_ctx = tls_session_ticket_ctx_create(net->loop, secret, secret_len); if (net->tls_session_ticket_ctx == NULL) { lua_pushstring(L, "net.tls_sticket_secret_string - can't create session ticket context"); lua_error(L); } lua_pushboolean(L, true); return 1; } static int net_tls_sticket_secret_file(lua_State *L) { if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) { lua_pushstring(L, "net.tls_sticket_secret_file takes one parameter: (\"file name\")"); lua_error(L); } const char *file_name = lua_tostring(L, 1); if (strlen(file_name) == 0) { lua_pushstring(L, "net.tls_sticket_secret_file - empty file name"); lua_error(L); } FILE *fp = fopen(file_name, "r"); if (fp == NULL) { lua_pushfstring(L, "net.tls_sticket_secret_file - can't open file '%s': %s", file_name, strerror(errno)); lua_error(L); } char secret_buf[TLS_SESSION_TICKET_SECRET_MAX_LEN]; const size_t secret_len = fread(secret_buf, 1, sizeof(secret_buf), fp); int err = ferror(fp); if (err) { lua_pushfstring(L, "net.tls_sticket_secret_file - error reading from file '%s': %s", file_name, strerror(err)); lua_error(L); } if (secret_len < net_tls_sticket_MIN_SECRET_LEN) { lua_pushfstring(L, "net.tls_sticket_secret_file - file '%s' is shorter than " xstr(net_tls_sticket_MIN_SECRET_LEN) " bytes", file_name); lua_error(L); } fclose(fp); struct network *net = &engine_luaget(L)->net; tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx); net->tls_session_ticket_ctx = tls_session_ticket_ctx_create(net->loop, secret_buf, secret_len); if (net->tls_session_ticket_ctx == NULL) { lua_pushstring(L, "net.tls_sticket_secret_file - can't create session ticket context"); lua_error(L); } lua_pushboolean(L, true); return 1; } static int net_outgoing(lua_State *L, int family) { struct worker_ctx *worker = wrk_luaget(L); union inaddr *addr; if (family == AF_INET) addr = (union inaddr*)&worker->out_addr4; else addr = (union inaddr*)&worker->out_addr6; if (lua_gettop(L) == 0) { /* Return the current value. */ if (addr->ip.sa_family == AF_UNSPEC) { lua_pushnil(L); return 1; } if (addr->ip.sa_family != family) { assert(false); lua_error(L); } char addr_buf[INET6_ADDRSTRLEN]; int err; if (family == AF_INET) err = uv_ip4_name(&addr->ip4, addr_buf, sizeof(addr_buf)); else err = uv_ip6_name(&addr->ip6, addr_buf, sizeof(addr_buf)); if (err) lua_error(L); lua_pushstring(L, addr_buf); return 1; } if ((lua_gettop(L) != 1) || (!lua_isstring(L, 1) && !lua_isnil(L, 1))) { format_error(L, "net.outgoing_vX takes one address string parameter or nil"); lua_error(L); } if (lua_isnil(L, 1)) { addr->ip.sa_family = AF_UNSPEC; return 1; } const char *addr_str = lua_tostring(L, 1); int err; if (family == AF_INET) err = uv_ip4_addr(addr_str, 0, &addr->ip4); else err = uv_ip6_addr(addr_str, 0, &addr->ip6); if (err) { format_error(L, "net.outgoing_vX: failed to parse the address"); lua_error(L); } lua_pushboolean(L, true); return 1; } static int net_outgoing_v4(lua_State *L) { return net_outgoing(L, AF_INET); } static int net_outgoing_v6(lua_State *L) { return net_outgoing(L, AF_INET6); } static int net_update_timeout(lua_State *L, uint64_t *timeout, const char *name) { /* Only return current idle timeout. */ if (lua_gettop(L) == 0) { lua_pushnumber(L, *timeout); return 1; } if ((lua_gettop(L) != 1)) { lua_pushstring(L, name); lua_pushstring(L, " takes one parameter: (\"idle timeout\")"); lua_error(L); } if (lua_isnumber(L, 1)) { int idle_timeout = lua_tointeger(L, 1); if (idle_timeout <= 0) { lua_pushstring(L, name); lua_pushstring(L, " parameter has to be positive number"); lua_error(L); } *timeout = idle_timeout; } else { lua_pushstring(L, name); lua_pushstring(L, " parameter has to be positive number"); lua_error(L); } lua_pushboolean(L, true); return 1; } static int net_tcp_in_idle(lua_State *L) { struct engine *engine = engine_luaget(L); struct network *net = &engine->net; return net_update_timeout(L, &net->tcp.in_idle_timeout, "net.tcp_in_idle"); } static int net_tls_handshake_timeout(lua_State *L) { struct engine *engine = engine_luaget(L); struct network *net = &engine->net; return net_update_timeout(L, &net->tcp.tls_handshake_timeout, "net.tls_handshake_timeout"); } static int net_bpf_set(lua_State *L) { struct engine *engine = engine_luaget(L); struct network *net = &engine->net; if (lua_gettop(L) != 1 || !lua_isnumber(L, 1)) { format_error(L, "net.bpf_set(fd) takes one parameter: the open file descriptor of a loaded BPF program"); lua_error(L); return 0; } #if __linux__ int progfd = lua_tointeger(L, 1); if (progfd == 0) { /* conversion error despite that fact * that lua_isnumber(L, 1) has returned true. * Real or stdin? */ lua_error(L); return 0; } lua_pop(L, 1); if (network_set_bpf(net, progfd) == 0) { char errmsg[256] = { 0 }; snprintf(errmsg, sizeof(errmsg), "failed to attach BPF program to some networks: %s", strerror(errno)); format_error(L, errmsg); lua_error(L); return 0; } lua_pushboolean(L, 1); return 1; #endif format_error(L, "BPF is not supported on this operating system"); lua_error(L); return 0; } static int net_bpf_clear(lua_State *L) { struct engine *engine = engine_luaget(L); struct network *net = &engine->net; if (lua_gettop(L) != 0) { format_error(L, "net.bpf_clear() does not take any parameters"); lua_error(L); return 0; } #if __linux__ network_clear_bpf(net); lua_pushboolean(L, 1); return 1; #endif format_error(L, "BPF is not supported on this operating system"); lua_error(L); return 0; } int lib_net(lua_State *L) { static const luaL_Reg lib[] = { { "list", net_list }, { "listen", net_listen }, { "close", net_close }, { "interfaces", net_interfaces }, { "bufsize", net_bufsize }, { "tcp_pipeline", net_pipeline }, { "tls", net_tls }, { "tls_server", net_tls }, { "tls_client", net_tls_client }, { "tls_client_clear", net_tls_client_clear }, { "tls_padding", net_tls_padding }, { "tls_sticket_secret", net_tls_sticket_secret_string }, { "tls_sticket_secret_file", net_tls_sticket_secret_file }, { "outgoing_v4", net_outgoing_v4 }, { "outgoing_v6", net_outgoing_v6 }, { "tcp_in_idle", net_tcp_in_idle }, { "tls_handshake_timeout", net_tls_handshake_timeout }, { "bpf_set", net_bpf_set }, { "bpf_clear", net_bpf_clear }, { NULL, NULL } }; register_lib(L, "net", lib); return 1; } /** @internal return cache, or throw lua error if not open */ struct kr_cache * cache_assert_open(lua_State *L) { struct engine *engine = engine_luaget(L); struct kr_cache *cache = &engine->resolver.cache; assert(cache); if (!cache || !kr_cache_is_open(cache)) { format_error(L, "no cache is open yet, use cache.open() or cache.size, etc."); lua_error(L); } return cache; } /** Return available cached backends. */ static int cache_backends(lua_State *L) { struct engine *engine = engine_luaget(L); lua_newtable(L); for (unsigned i = 0; i < engine->backends.len; ++i) { const struct kr_cdb_api *api = engine->backends.at[i]; lua_pushboolean(L, api == engine->resolver.cache.api); lua_setfield(L, -2, api->name); } return 1; } /** Return number of cached records. */ static int cache_count(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int count = cache->api->count(cache->db); if (count >= 0) { /* First key is a version counter, omit it if nonempty. */ lua_pushinteger(L, count ? count - 1 : 0); return 1; } return 0; } /** Return time of last checkpoint, or re-set it if passed `true`. */ static int cache_checkpoint(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); if (lua_gettop(L) == 0) { /* Return the current value. */ lua_newtable(L); lua_pushnumber(L, cache->checkpoint_monotime); lua_setfield(L, -2, "monotime"); lua_newtable(L); lua_pushnumber(L, cache->checkpoint_walltime.tv_sec); lua_setfield(L, -2, "sec"); lua_pushnumber(L, cache->checkpoint_walltime.tv_usec); lua_setfield(L, -2, "usec"); lua_setfield(L, -2, "walltime"); return 1; } if (lua_gettop(L) != 1 || !lua_isboolean(L, 1) || !lua_toboolean(L, 1)) { format_error(L, "cache.checkpoint() takes no parameters or a true value"); lua_error(L); } kr_cache_make_checkpoint(cache); return 1; } /** Return cache statistics. */ static int cache_stats(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); lua_newtable(L); lua_pushnumber(L, cache->stats.hit); lua_setfield(L, -2, "hit"); lua_pushnumber(L, cache->stats.miss); lua_setfield(L, -2, "miss"); lua_pushnumber(L, cache->stats.insert); lua_setfield(L, -2, "insert"); lua_pushnumber(L, cache->stats.delete); lua_setfield(L, -2, "delete"); return 1; } static const struct kr_cdb_api *cache_select(struct engine *engine, const char **conf) { /* Return default backend */ if (*conf == NULL || !strstr(*conf, "://")) { return engine->backends.at[0]; } /* Find storage backend from config prefix */ for (unsigned i = 0; i < engine->backends.len; ++i) { const struct kr_cdb_api *api = engine->backends.at[i]; if (strncmp(*conf, api->name, strlen(api->name)) == 0) { *conf += strlen(api->name) + strlen("://"); return api; } } return NULL; } static int cache_max_ttl(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int n = lua_gettop(L); if (n > 0) { if (!lua_isnumber(L, 1)) { format_error(L, "expected 'max_ttl(number ttl)'"); lua_error(L); } uint32_t min = cache->ttl_min; int64_t ttl = lua_tonumber(L, 1); if (ttl < 0 || ttl < min || ttl > UINT32_MAX) { format_error(L, "max_ttl must be larger than minimum TTL, and in range <1, " xstr(UINT32_MAX) ">'"); lua_error(L); } cache->ttl_max = ttl; } lua_pushinteger(L, cache->ttl_max); return 1; } static int cache_min_ttl(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int n = lua_gettop(L); if (n > 0) { if (!lua_isnumber(L, 1)) { format_error(L, "expected 'min_ttl(number ttl)'"); lua_error(L); } uint32_t max = cache->ttl_max; int64_t ttl = lua_tonumber(L, 1); if (ttl < 0 || ttl > max || ttl > UINT32_MAX) { format_error(L, "min_ttl must be smaller than maximum TTL, and in range <0, " xstr(UINT32_MAX) ">'"); lua_error(L); } cache->ttl_min = ttl; } lua_pushinteger(L, cache->ttl_min); return 1; } /** Open cache */ static int cache_open(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 1 || !lua_isnumber(L, 1)) { format_error(L, "expected 'open(number max_size, string config = \"\")'"); lua_error(L); } /* Select cache storage backend */ struct engine *engine = engine_luaget(L); lua_Number csize_lua = lua_tonumber(L, 1); if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */ format_error(L, "invalid cache size specified, it must be in range <8192, " xstr(SIZE_MAX) ">"); lua_error(L); } size_t cache_size = csize_lua; const char *conf = n > 1 ? lua_tostring(L, 2) : NULL; const char *uri = conf; const struct kr_cdb_api *api = cache_select(engine, &conf); if (!api) { format_error(L, "unsupported cache backend"); lua_error(L); } /* Close if already open */ kr_cache_close(&engine->resolver.cache); /* Reopen cache */ struct kr_cdb_opts opts = { (conf && strlen(conf)) ? conf : ".", cache_size }; int ret = kr_cache_open(&engine->resolver.cache, api, &opts, engine->pool); if (ret != 0) { char cwd[PATH_MAX]; if(getcwd(cwd, sizeof(cwd)) == NULL) { const char errprefix[] = ""; strncpy(cwd, errprefix, sizeof(cwd)); } return luaL_error(L, "can't open cache path '%s'; working directory '%s'", opts.path, cwd); } /* Store current configuration */ lua_getglobal(L, "cache"); lua_pushstring(L, "current_size"); lua_pushnumber(L, cache_size); lua_rawset(L, -3); lua_pushstring(L, "current_storage"); lua_pushstring(L, uri); lua_rawset(L, -3); lua_pop(L, 1); lua_pushboolean(L, 1); return 1; } static int cache_close(lua_State *L) { struct engine *engine = engine_luaget(L); struct kr_cache *cache = &engine->resolver.cache; if (!kr_cache_is_open(cache)) { return 0; } kr_cache_close(cache); lua_getglobal(L, "cache"); lua_pushstring(L, "current_size"); lua_pushnumber(L, 0); lua_rawset(L, -3); lua_pop(L, 1); lua_pushboolean(L, 1); return 1; } #if 0 /** @internal Prefix walk. */ static int cache_prefixed(struct kr_cache *cache, const char *prefix, bool exact_name, knot_db_val_t keyval[][2], int maxcount) { /* Convert to domain name */ uint8_t buf[KNOT_DNAME_MAXLEN]; if (!knot_dname_from_str(buf, prefix, sizeof(buf))) { return kr_error(EINVAL); } /* Start prefix search */ return kr_cache_match(cache, buf, exact_name, keyval, maxcount); } #endif /** Prune expired/invalid records. */ static int cache_prune(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); /* Check parameters */ int prune_max = UINT16_MAX; int n = lua_gettop(L); if (n >= 1 && lua_isnumber(L, 1)) { prune_max = lua_tointeger(L, 1); } /* Check if API supports pruning. */ int ret = kr_error(ENOSYS); if (cache->api->prune) { ret = cache->api->prune(cache->db, prune_max); } /* Commit and format result. */ if (ret < 0) { format_error(L, kr_strerror(ret)); lua_error(L); } lua_pushinteger(L, ret); return 1; } /** Clear everything. */ static int cache_clear_everything(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); /* Clear records and packets. */ int ret = kr_cache_clear(cache); if (ret < 0) { format_error(L, kr_strerror(ret)); lua_error(L); } /* Clear reputation tables */ struct engine *engine = engine_luaget(L); lru_reset(engine->resolver.cache_rtt); lru_reset(engine->resolver.cache_rep); lru_reset(engine->resolver.cache_cookie); lua_pushboolean(L, true); return 1; } #if 0 /** @internal Dump cache key into table on Lua stack. */ static void cache_dump(lua_State *L, knot_db_val_t keyval[]) { knot_dname_t dname[KNOT_DNAME_MAXLEN]; char name[KNOT_DNAME_TXT_MAXLEN]; uint16_t type; int ret = kr_unpack_cache_key(keyval[0], dname, &type); if (ret < 0) { return; } ret = !knot_dname_to_str(name, dname, sizeof(name)); assert(!ret); if (ret) return; /* If name typemap doesn't exist yet, create it */ lua_getfield(L, -1, name); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); } /* Append to typemap */ char type_buf[KR_RRTYPE_STR_MAXLEN] = { '\0' }; knot_rrtype_to_string(type, type_buf, sizeof(type_buf)); lua_pushboolean(L, true); lua_setfield(L, -2, type_buf); /* Set name typemap */ lua_setfield(L, -2, name); } /** Query cached records. TODO: fix caveats in ./README.rst documentation? */ static int cache_get(lua_State *L) { //struct kr_cache *cache = cache_assert_open(L); // to be fixed soon /* Check parameters */ int n = lua_gettop(L); if (n < 1 || !lua_isstring(L, 1)) { format_error(L, "expected 'cache.get(string key)'"); lua_error(L); } /* Retrieve set of keys */ const char *prefix = lua_tostring(L, 1); knot_db_val_t keyval[100][2]; int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100); if (ret < 0) { format_error(L, kr_strerror(ret)); lua_error(L); } /* Format output */ lua_newtable(L); for (int i = 0; i < ret; ++i) { cache_dump(L, keyval[i]); } return 1; } #endif static int cache_get(lua_State *L) { int ret = kr_error(ENOSYS); format_error(L, kr_strerror(ret)); lua_error(L); return ret; } /** Set time interval for cleaning rtt cache. * Servers with score >= KR_NS_TIMEOUT will be cleaned after * this interval ended up, so that they will be able to participate * in NS elections again. */ static int cache_ns_tout(lua_State *L) { struct engine *engine = engine_luaget(L); struct kr_context *ctx = &engine->resolver; /* Check parameters */ int n = lua_gettop(L); if (n < 1) { lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); return 1; } if (!lua_isnumber(L, 1)) { format_error(L, "expected 'cache.ns_tout(interval in ms)'"); lua_error(L); } lua_Integer interval_lua = lua_tointeger(L, 1); if (!(interval_lua > 0 && interval_lua < UINT_MAX)) { format_error(L, "invalid interval specified, it must be in range > 0, < " xstr(UINT_MAX)); lua_error(L); } ctx->cache_rtt_tout_retry_interval = interval_lua; lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); return 1; } /** Zone import completion callback. * Deallocates zone import context. */ static void cache_zone_import_cb(int state, void *param) { assert (param); (void)state; struct worker_ctx *worker = (struct worker_ctx *)param; assert (worker->z_import); zi_free(worker->z_import); worker->z_import = NULL; } /** Import zone from file. */ static int cache_zone_import(lua_State *L) { int ret = -1; char msg[128]; struct worker_ctx *worker = wrk_luaget(L); if (!worker) { strncpy(msg, "internal error, empty worker pointer", sizeof(msg)); goto finish; } if (worker->z_import && zi_import_started(worker->z_import)) { strncpy(msg, "import already started", sizeof(msg)); goto finish; } (void)cache_assert_open(L); /* just check it in advance */ /* Check parameters */ int n = lua_gettop(L); if (n < 1 || !lua_isstring(L, 1)) { strncpy(msg, "expected 'cache.zone_import(path to zone file)'", sizeof(msg)); goto finish; } /* Parse zone file */ const char *zone_file = lua_tostring(L, 1); const char *default_origin = NULL; /* TODO */ uint16_t default_rclass = 1; uint32_t default_ttl = 0; if (worker->z_import == NULL) { worker->z_import = zi_allocate(worker, cache_zone_import_cb, worker); if (worker->z_import == NULL) { strncpy(msg, "can't allocate zone import context", sizeof(msg)); goto finish; } } ret = zi_zone_import(worker->z_import, zone_file, default_origin, default_rclass, default_ttl); lua_newtable(L); if (ret == 0) { strncpy(msg, "zone file successfully parsed, import started", sizeof(msg)); } else if (ret == 1) { strncpy(msg, "TA not found", sizeof(msg)); } else { strncpy(msg, "error parsing zone file", sizeof(msg)); } finish: msg[sizeof(msg) - 1] = 0; lua_newtable(L); lua_pushstring(L, msg); lua_setfield(L, -2, "msg"); lua_pushnumber(L, ret); lua_setfield(L, -2, "code"); return 1; } int lib_cache(lua_State *L) { static const luaL_Reg lib[] = { { "backends", cache_backends }, { "count", cache_count }, { "stats", cache_stats }, { "checkpoint", cache_checkpoint }, { "open", cache_open }, { "close", cache_close }, { "prune", cache_prune }, { "clear_everything", cache_clear_everything }, { "get", cache_get }, { "max_ttl", cache_max_ttl }, { "min_ttl", cache_min_ttl }, { "ns_tout", cache_ns_tout }, { "zone_import", cache_zone_import }, { NULL, NULL } }; register_lib(L, "cache", lib); return 1; } static void event_free(uv_timer_t *timer) { struct worker_ctx *worker = timer->loop->data; lua_State *L = worker->engine->L; int ref = (intptr_t) timer->data; luaL_unref(L, LUA_REGISTRYINDEX, ref); free(timer); } static int execute_callback(lua_State *L, int argc) { int ret = engine_pcall(L, argc); if (ret != 0) { fprintf(stderr, "error: %s\n", lua_tostring(L, -1)); } /* Clear the stack, there may be event a/o enything returned */ lua_settop(L, 0); return ret; } static void event_callback(uv_timer_t *timer) { struct worker_ctx *worker = timer->loop->data; lua_State *L = worker->engine->L; /* Retrieve callback and execute */ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) timer->data); lua_rawgeti(L, -1, 1); lua_pushinteger(L, (intptr_t) timer->data); int ret = execute_callback(L, 1); /* Free callback if not recurrent or an error */ if (ret != 0 || (uv_timer_get_repeat(timer) == 0 && uv_is_active((uv_handle_t *)timer) == 0)) { if (!uv_is_closing((uv_handle_t *)timer)) { uv_close((uv_handle_t *)timer, (uv_close_cb) event_free); } } } static void event_fdcallback(uv_poll_t* handle, int status, int events) { struct worker_ctx *worker = handle->loop->data; lua_State *L = worker->engine->L; /* Retrieve callback and execute */ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) handle->data); lua_rawgeti(L, -1, 1); lua_pushinteger(L, (intptr_t) handle->data); lua_pushinteger(L, status); lua_pushinteger(L, events); int ret = execute_callback(L, 3); /* Free callback if not recurrent or an error */ if (ret != 0) { if (!uv_is_closing((uv_handle_t *)handle)) { uv_close((uv_handle_t *)handle, (uv_close_cb) event_free); } } } static int event_sched(lua_State *L, unsigned timeout, unsigned repeat) { uv_timer_t *timer = malloc(sizeof(*timer)); if (!timer) { format_error(L, "out of memory"); lua_error(L); } /* Start timer with the reference */ uv_loop_t *loop = uv_default_loop(); uv_timer_init(loop, timer); int ret = uv_timer_start(timer, event_callback, timeout, repeat); if (ret != 0) { free(timer); format_error(L, "couldn't start the event"); lua_error(L); } /* Save callback and timer in registry */ lua_newtable(L); lua_pushvalue(L, 2); lua_rawseti(L, -2, 1); lua_pushlightuserdata(L, timer); lua_rawseti(L, -2, 2); int ref = luaL_ref(L, LUA_REGISTRYINDEX); /* Save reference to the timer */ timer->data = (void *) (intptr_t)ref; lua_pushinteger(L, ref); return 1; } static int event_after(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) { format_error(L, "expected 'after(number timeout, function)'"); lua_error(L); } return event_sched(L, lua_tonumber(L, 1), 0); } static int event_recurrent(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) { format_error(L, "expected 'recurrent(number interval, function)'"); lua_error(L); } return event_sched(L, 0, lua_tonumber(L, 1)); } static int event_cancel(lua_State *L) { int n = lua_gettop(L); if (n < 1 || !lua_isnumber(L, 1)) { format_error(L, "expected 'cancel(number event)'"); lua_error(L); } /* Fetch event if it exists */ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1)); if (!lua_istable(L, -1)) { lua_pushboolean(L, false); return 1; } /* Close the timer */ lua_rawgeti(L, -1, 2); uv_handle_t *timer = lua_touserdata(L, -1); if (!uv_is_closing(timer)) { uv_close(timer, (uv_close_cb) event_free); } lua_pushboolean(L, true); return 1; } static int event_reschedule(lua_State *L) { int n = lua_gettop(L); if (n < 2 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2)) { format_error(L, "expected 'reschedule(number event, number timeout)'"); lua_error(L); } /* Fetch event if it exists */ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1)); if (!lua_istable(L, -1)) { lua_pushboolean(L, false); return 1; } /* Reschedule the timer */ lua_rawgeti(L, -1, 2); uv_handle_t *timer = lua_touserdata(L, -1); if (!uv_is_closing(timer)) { if (uv_is_active(timer)) { uv_timer_stop((uv_timer_t *)timer); } int ret = uv_timer_start((uv_timer_t *)timer, event_callback, lua_tointeger(L, 2), 0); if (ret != 0) { event_cancel(L); lua_pushboolean(L, false); return 1; } } lua_pushboolean(L, true); return 1; } static int event_fdwatch(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) { format_error(L, "expected 'socket(number fd, function)'"); lua_error(L); } uv_poll_t *handle = malloc(sizeof(*handle)); if (!handle) { format_error(L, "out of memory"); lua_error(L); } /* Start timer with the reference */ int sock = lua_tonumber(L, 1); uv_loop_t *loop = uv_default_loop(); #if defined(__APPLE__) || defined(__FreeBSD__) /* libuv is buggy and fails to create poller for * kqueue sockets as it can't be fcntl'd to non-blocking mode, * so we pass it a copy of standard input and then * switch it with real socket before starting the poller */ int decoy_fd = dup(STDIN_FILENO); int ret = uv_poll_init(loop, handle, decoy_fd); if (ret == 0) { handle->io_watcher.fd = sock; } close(decoy_fd); #else int ret = uv_poll_init(loop, handle, sock); #endif if (ret == 0) { ret = uv_poll_start(handle, UV_READABLE, event_fdcallback); } if (ret != 0) { free(handle); format_error(L, "couldn't start event poller"); lua_error(L); } /* Save callback and timer in registry */ lua_newtable(L); lua_pushvalue(L, 2); lua_rawseti(L, -2, 1); lua_pushlightuserdata(L, handle); lua_rawseti(L, -2, 2); int ref = luaL_ref(L, LUA_REGISTRYINDEX); /* Save reference to the timer */ handle->data = (void *) (intptr_t)ref; lua_pushinteger(L, ref); return 1; } int lib_event(lua_State *L) { static const luaL_Reg lib[] = { { "after", event_after }, { "recurrent", event_recurrent }, { "cancel", event_cancel }, { "socket", event_fdwatch }, { "reschedule", event_reschedule }, { NULL, NULL } }; register_lib(L, "event", lib); return 1; } static int wrk_resolve(lua_State *L) { struct worker_ctx *worker = wrk_luaget(L); if (!worker) { return 0; } uint8_t dname[KNOT_DNAME_MAXLEN]; if (!knot_dname_from_str(dname, lua_tostring(L, 1), sizeof(dname))) { lua_pushstring(L, "invalid qname"); lua_error(L); }; /* Check class and type */ uint16_t rrtype = lua_tointeger(L, 2); if (!lua_isnumber(L, 2)) { lua_pushstring(L, "invalid RR type"); lua_error(L); } uint16_t rrclass = lua_tointeger(L, 3); if (!lua_isnumber(L, 3)) { /* Default class is IN */ rrclass = KNOT_CLASS_IN; } /* Add query options */ const struct kr_qflags *options = lua_topointer(L, 4); if (!options) { /* but we rely on the lua wrapper when dereferencing non-NULL */ lua_pushstring(L, "invalid options"); lua_error(L); } /* Create query packet */ knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_EDNS_MAX_UDP_PAYLOAD, NULL); if (!pkt) { lua_pushstring(L, kr_strerror(ENOMEM)); lua_error(L); } knot_pkt_put_question(pkt, dname, rrclass, rrtype); knot_wire_set_rd(pkt->wire); knot_wire_set_ad(pkt->wire); /* Add OPT RR */ pkt->opt_rr = knot_rrset_copy(worker->engine->resolver.opt_rr, NULL); if (!pkt->opt_rr) { knot_pkt_free(pkt); return kr_error(ENOMEM); } if (options->DNSSEC_WANT) { knot_edns_set_do(pkt->opt_rr); } if (options->DNSSEC_CD) { knot_wire_set_cd(pkt->wire); } /* Create task and start with a first question */ struct qr_task *task = worker_resolve_start(worker, pkt, *options); if (!task) { knot_rrset_free(pkt->opt_rr, NULL); knot_pkt_free(pkt); lua_pushstring(L, "couldn't create a resolution request"); lua_error(L); } /* Add initialisation callback */ if (lua_isfunction(L, 5)) { lua_pushvalue(L, 5); lua_pushlightuserdata(L, worker_task_request(task)); (void) execute_callback(L, 1); } /* Start execution */ int ret = worker_resolve_exec(task, pkt); lua_pushboolean(L, ret == 0); knot_rrset_free(pkt->opt_rr, NULL); knot_pkt_free(pkt); return 1; } static inline double getseconds(uv_timeval_t *tv) { return (double)tv->tv_sec + 0.000001*((double)tv->tv_usec); } /** Return worker statistics. */ static int wrk_stats(lua_State *L) { struct worker_ctx *worker = wrk_luaget(L); if (!worker) { return 0; } lua_newtable(L); lua_pushnumber(L, worker->stats.concurrent); lua_setfield(L, -2, "concurrent"); lua_pushnumber(L, worker->stats.udp); lua_setfield(L, -2, "udp"); lua_pushnumber(L, worker->stats.tcp); lua_setfield(L, -2, "tcp"); lua_pushnumber(L, worker->stats.tls); lua_setfield(L, -2, "tls"); lua_pushnumber(L, worker->stats.ipv6); lua_setfield(L, -2, "ipv6"); lua_pushnumber(L, worker->stats.ipv4); lua_setfield(L, -2, "ipv4"); lua_pushnumber(L, worker->stats.queries); lua_setfield(L, -2, "queries"); lua_pushnumber(L, worker->stats.dropped); lua_setfield(L, -2, "dropped"); lua_pushnumber(L, worker->stats.timeout); lua_setfield(L, -2, "timeout"); /* Add subset of rusage that represents counters. */ uv_rusage_t rusage; if (uv_getrusage(&rusage) == 0) { lua_pushnumber(L, getseconds(&rusage.ru_utime)); lua_setfield(L, -2, "usertime"); lua_pushnumber(L, getseconds(&rusage.ru_stime)); lua_setfield(L, -2, "systime"); lua_pushnumber(L, rusage.ru_majflt); lua_setfield(L, -2, "pagefaults"); lua_pushnumber(L, rusage.ru_nswap); lua_setfield(L, -2, "swaps"); lua_pushnumber(L, rusage.ru_nvcsw + rusage.ru_nivcsw); lua_setfield(L, -2, "csw"); } /* Get RSS */ size_t rss = 0; if (uv_resident_set_memory(&rss) == 0) { lua_pushnumber(L, rss); lua_setfield(L, -2, "rss"); } return 1; } int lib_worker(lua_State *L) { static const luaL_Reg lib[] = { { "resolve_unwrapped", wrk_resolve }, { "stats", wrk_stats }, { NULL, NULL } }; register_lib(L, "worker", lib); return 1; }