1264 lines
37 KiB
C
1264 lines
37 KiB
C
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include "daemon/bindings/impl.h"
|
|
|
|
#include "contrib/base64.h"
|
|
#include "contrib/cleanup.h"
|
|
#include "daemon/network.h"
|
|
#include "daemon/tls.h"
|
|
#include "lib/utils.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#define PROXY_DATA_STRLEN (INET6_ADDRSTRLEN + 1 + 3 + 1)
|
|
|
|
/** Table and next index on top of stack -> append entries for given endpoint_array_t. */
|
|
static int net_list_add(const char *b_key, uint32_t key_len, trie_val_t *val, void *ext)
|
|
{
|
|
endpoint_array_t *ep_array = *val;
|
|
lua_State *L = (lua_State *)ext;
|
|
lua_Integer i = lua_tointeger(L, -1);
|
|
for (int j = 0; j < ep_array->len; ++j) {
|
|
struct endpoint *ep = &ep_array->at[j];
|
|
lua_newtable(L); // connection tuple
|
|
|
|
if (ep->flags.kind) {
|
|
lua_pushstring(L, ep->flags.kind);
|
|
} else if (ep->flags.http && ep->flags.tls) {
|
|
lua_pushliteral(L, "doh2");
|
|
} else if (ep->flags.tls) {
|
|
lua_pushliteral(L, "tls");
|
|
} else if (ep->flags.xdp) {
|
|
lua_pushliteral(L, "xdp");
|
|
} else {
|
|
lua_pushliteral(L, "dns");
|
|
}
|
|
lua_setfield(L, -2, "kind");
|
|
|
|
lua_newtable(L); // "transport" table
|
|
|
|
switch (ep->family) {
|
|
case AF_INET:
|
|
lua_pushliteral(L, "inet4");
|
|
break;
|
|
case AF_INET6:
|
|
lua_pushliteral(L, "inet6");
|
|
break;
|
|
case AF_XDP:
|
|
lua_pushliteral(L, "inet4+inet6"); // both UDP ports at once
|
|
break;
|
|
case AF_UNIX:
|
|
lua_pushliteral(L, "unix");
|
|
break;
|
|
default:
|
|
kr_assert(false);
|
|
lua_pushliteral(L, "invalid");
|
|
}
|
|
lua_setfield(L, -2, "family");
|
|
|
|
const char *ip_str_const = network_endpoint_key_str((struct endpoint_key *) b_key);
|
|
kr_require(ip_str_const);
|
|
auto_free char *ip_str = strdup(ip_str_const);
|
|
kr_require(ip_str);
|
|
char *hm = strchr(ip_str, '#');
|
|
if (hm) /* Omit port */
|
|
*hm = '\0';
|
|
lua_pushstring(L, ip_str);
|
|
|
|
if (ep->family == AF_INET || ep->family == AF_INET6) {
|
|
lua_setfield(L, -2, "ip");
|
|
lua_pushboolean(L, ep->flags.freebind);
|
|
lua_setfield(L, -2, "freebind");
|
|
} else if (ep->family == AF_UNIX) {
|
|
lua_setfield(L, -2, "path");
|
|
} else if (ep->family == AF_XDP) {
|
|
lua_setfield(L, -2, "interface");
|
|
lua_pushinteger(L, ep->nic_queue);
|
|
lua_setfield(L, -2, "nic_queue");
|
|
}
|
|
|
|
if (ep->family != AF_UNIX) {
|
|
lua_pushinteger(L, ep->port);
|
|
lua_setfield(L, -2, "port");
|
|
}
|
|
|
|
if (ep->family == AF_UNIX) {
|
|
lua_pushliteral(L, "stream");
|
|
} else if (ep->flags.sock_type == SOCK_STREAM) {
|
|
lua_pushliteral(L, "tcp");
|
|
} else if (ep->flags.sock_type == SOCK_DGRAM) {
|
|
lua_pushliteral(L, "udp");
|
|
} else {
|
|
kr_assert(false);
|
|
lua_pushliteral(L, "invalid");
|
|
}
|
|
lua_setfield(L, -2, "protocol");
|
|
|
|
lua_setfield(L, -2, "transport");
|
|
|
|
lua_settable(L, -3);
|
|
i++;
|
|
lua_pushinteger(L, i);
|
|
}
|
|
return kr_ok();
|
|
}
|
|
|
|
/** List active endpoints. */
|
|
static int net_list(lua_State *L)
|
|
{
|
|
lua_newtable(L);
|
|
lua_pushinteger(L, 1);
|
|
trie_apply_with_key(the_worker->engine->net.endpoints, net_list_add, L);
|
|
lua_pop(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/** Listen on an address list represented by the top of lua stack.
|
|
* \note flags.kind ownership is not transferred, and flags.sock_type doesn't make sense
|
|
* \return success */
|
|
static bool net_listen_addrs(lua_State *L, int port, endpoint_flags_t flags, int16_t nic_queue)
|
|
{
|
|
if (kr_fails_assert(flags.xdp || nic_queue == -1))
|
|
return false;
|
|
|
|
/* 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 network *net = &the_worker->engine->net;
|
|
const bool is_unix = str[0] == '/';
|
|
int ret = 0;
|
|
if (!flags.kind && !flags.tls) { /* normal UDP or XDP */
|
|
flags.sock_type = SOCK_DGRAM;
|
|
ret = network_listen(net, str, port, nic_queue, flags);
|
|
}
|
|
if (!flags.kind && !flags.xdp && ret == 0) { /* common for TCP, DoT and DoH (v2) */
|
|
flags.sock_type = SOCK_STREAM;
|
|
ret = network_listen(net, str, port, nic_queue, flags);
|
|
}
|
|
if (flags.kind) {
|
|
flags.kind = strdup(flags.kind);
|
|
flags.sock_type = SOCK_STREAM; /* TODO: allow to override this? */
|
|
ret = network_listen(net, str, (is_unix ? 0 : port), nic_queue, flags);
|
|
}
|
|
if (ret == 0) return true; /* success */
|
|
|
|
if (is_unix) {
|
|
kr_log_error(NETWORK, "bind to '%s' (UNIX): %s\n",
|
|
str, kr_strerror(ret));
|
|
} else if (flags.xdp) {
|
|
const char *err_str = knot_strerror(ret);
|
|
if (ret == KNOT_ELIMIT) {
|
|
if ((strcmp(str, "::") == 0 || strcmp(str, "0.0.0.0") == 0)) {
|
|
err_str = "wildcard addresses not supported with XDP";
|
|
} else {
|
|
err_str = "address matched multiple network interfaces";
|
|
}
|
|
} else if (ret == kr_error(ENODEV)) {
|
|
err_str = "invalid address or interface name";
|
|
}
|
|
/* Notable OK strerror: KNOT_EPERM Operation not permitted */
|
|
|
|
if (nic_queue == -1) {
|
|
kr_log_error(NETWORK, "failed to initialize XDP for '%s@%d'"
|
|
" (nic_queue = <auto>): %s\n",
|
|
str, port, err_str);
|
|
} else {
|
|
kr_log_error(NETWORK, "failed to initialize XDP for '%s@%d'"
|
|
" (nic_queue = %d): %s\n",
|
|
str, port, nic_queue, err_str);
|
|
}
|
|
|
|
} else {
|
|
const char *stype = flags.sock_type == SOCK_DGRAM ? "UDP" : "TCP";
|
|
kr_log_error(NETWORK, "bind to '%s@%d' (%s): %s\n",
|
|
str, port, stype, kr_strerror(ret));
|
|
}
|
|
return false; /* failure */
|
|
}
|
|
|
|
/* Last case: table where all entries are added recursively. */
|
|
if (!lua_istable(L, -1))
|
|
lua_error_p(L, "bad type for address");
|
|
lua_pushnil(L);
|
|
while (lua_next(L, -2)) {
|
|
if (!net_listen_addrs(L, port, flags, nic_queue))
|
|
return false;
|
|
lua_pop(L, 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
lua_error_p(L, "expected one to three arguments; usage:\n"
|
|
"net.listen(addresses, [port = " STR(KR_DNS_PORT)
|
|
", flags = {tls = (port == " STR(KR_DNS_TLS_PORT) ")}])\n");
|
|
}
|
|
|
|
int port = KR_DNS_PORT;
|
|
if (n > 1) {
|
|
if (lua_isnumber(L, 2)) {
|
|
port = lua_tointeger(L, 2);
|
|
} else
|
|
if (!lua_isnil(L, 2)) {
|
|
lua_error_p(L, "wrong type of second parameter (port number)");
|
|
}
|
|
}
|
|
|
|
endpoint_flags_t flags = { 0 };
|
|
if (port == KR_DNS_TLS_PORT) {
|
|
flags.tls = true;
|
|
} else if (port == KR_DNS_DOH_PORT) {
|
|
flags.http = flags.tls = true;
|
|
}
|
|
|
|
int16_t nic_queue = -1;
|
|
if (n > 2 && !lua_isnil(L, 3)) {
|
|
if (!lua_istable(L, 3))
|
|
lua_error_p(L, "wrong type of third parameter (table expected)");
|
|
flags.tls = table_get_flag(L, 3, "tls", flags.tls);
|
|
flags.freebind = table_get_flag(L, 3, "freebind", false);
|
|
|
|
lua_getfield(L, 3, "kind");
|
|
const char *k = lua_tostring(L, -1);
|
|
if (k && strcasecmp(k, "dns") == 0) {
|
|
flags.tls = flags.http = false;
|
|
} else if (k && strcasecmp(k, "xdp") == 0) {
|
|
flags.tls = flags.http = false;
|
|
flags.xdp = true;
|
|
} else if (k && strcasecmp(k, "tls") == 0) {
|
|
flags.tls = true;
|
|
flags.http = false;
|
|
} else if (k && strcasecmp(k, "doh2") == 0) {
|
|
flags.tls = flags.http = true;
|
|
} else if (k) {
|
|
flags.kind = k;
|
|
if (strcasecmp(k, "doh") == 0) {
|
|
lua_error_p(L, "kind=\"doh\" was renamed to kind=\"doh_legacy\", switch to the new implementation with kind=\"doh2\" or update your config");
|
|
}
|
|
}
|
|
|
|
lua_getfield(L, 3, "nic_queue");
|
|
if (lua_isnumber(L, -1)) {
|
|
if (flags.xdp) {
|
|
nic_queue = lua_tointeger(L, -1);
|
|
} else {
|
|
lua_error_p(L, "nic_queue only supported with kind = 'xdp'");
|
|
}
|
|
} else if (!lua_isnil(L, -1)) {
|
|
lua_error_p(L, "wrong value of nic_queue (integer expected)");
|
|
}
|
|
}
|
|
|
|
/* Memory management of `kind` string is difficult due to longjmp etc.
|
|
* Pop will unreference the lua value, so we store it on C stack instead (!) */
|
|
const int kind_alen = flags.kind ? strlen(flags.kind) + 1 : 1 /* 0 length isn't C standard */;
|
|
char kind_buf[kind_alen];
|
|
if (flags.kind) {
|
|
memcpy(kind_buf, flags.kind, kind_alen);
|
|
flags.kind = kind_buf;
|
|
}
|
|
|
|
/* Now focus on the first argument. */
|
|
lua_settop(L, 1);
|
|
if (!net_listen_addrs(L, port, flags, nic_queue))
|
|
lua_error_p(L, "net.listen() failed to bind");
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
}
|
|
|
|
/** Prints the specified `data` into the specified `dst` buffer. */
|
|
static char *proxy_data_to_string(int af, const struct net_proxy_data *data,
|
|
char *dst, size_t size)
|
|
{
|
|
kr_assert(size >= PROXY_DATA_STRLEN);
|
|
const void *in_addr = (af == AF_INET)
|
|
? (void *) &data->addr.ip4
|
|
: (void *) &data->addr.ip6;
|
|
char *cur = dst;
|
|
|
|
const char *ret = inet_ntop(af, in_addr, cur, size);
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
cur += strlen(cur); /*< advance cursor to after the address */
|
|
*(cur++) = '/';
|
|
int masklen = snprintf(cur, 3 + 1, "%u", data->netmask);
|
|
cur[masklen] = '\0';
|
|
return dst;
|
|
}
|
|
|
|
/** Put all IP addresses from `trie` into the table at the top of the Lua stack.
|
|
* For each address, increment the integer at `i`. All addresses in `trie` must
|
|
* be from the specified `family`. */
|
|
static void net_proxy_addr_put(lua_State *L, int family, trie_t *trie, int *i)
|
|
{
|
|
char addrbuf[PROXY_DATA_STRLEN];
|
|
const char *addr;
|
|
trie_it_t *it;
|
|
for (it = trie_it_begin(trie); !trie_it_finished(it); trie_it_next(it)) {
|
|
lua_pushinteger(L, *i);
|
|
struct net_proxy_data *data = *trie_it_val(it);
|
|
addr = proxy_data_to_string(family, data,
|
|
addrbuf, sizeof(addrbuf));
|
|
lua_pushstring(L, addr);
|
|
lua_settable(L, -3);
|
|
*i += 1;
|
|
}
|
|
trie_it_free(it);
|
|
}
|
|
|
|
/** Allow PROXYv2 headers for IP address. */
|
|
static int net_proxy_allowed(lua_State *L)
|
|
{
|
|
struct network *net = &the_worker->engine->net;
|
|
int n = lua_gettop(L);
|
|
int i = 1;
|
|
const char *addr;
|
|
|
|
/* Return current state */
|
|
if (n == 0) {
|
|
lua_newtable(L);
|
|
i = 1;
|
|
|
|
if (net->proxy_all4) {
|
|
lua_pushinteger(L, i);
|
|
lua_pushstring(L, "0.0.0.0/0");
|
|
lua_settable(L, -3);
|
|
i += 1;
|
|
} else {
|
|
net_proxy_addr_put(L, AF_INET, net->proxy_addrs4, &i);
|
|
}
|
|
|
|
if (net->proxy_all6) {
|
|
lua_pushinteger(L, i);
|
|
lua_pushstring(L, "::/0");
|
|
lua_settable(L, -3);
|
|
i += 1;
|
|
} else {
|
|
net_proxy_addr_put(L, AF_INET6, net->proxy_addrs6, &i);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (n != 1)
|
|
lua_error_p(L, "net.proxy_allowed() takes one parameter (string or table)");
|
|
|
|
if (!lua_istable(L, 1) && !lua_isstring(L, 1))
|
|
lua_error_p(L, "net.proxy_allowed() argument must be string or table");
|
|
|
|
/* Reset allowed proxy addresses */
|
|
network_proxy_reset(net);
|
|
|
|
/* Add new proxy addresses */
|
|
if (lua_istable(L, 1)) {
|
|
for (i = 1; !lua_isnil(L, -1); i++) {
|
|
lua_pushinteger(L, i);
|
|
lua_gettable(L, 1);
|
|
if (lua_isnil(L, -1)) /* missing value - end iteration */
|
|
break;
|
|
if (!lua_isstring(L, -1))
|
|
lua_error_p(L, "net.proxy_allowed() argument may only contain strings");
|
|
addr = lua_tostring(L, -1);
|
|
int ret = network_proxy_allow(net, addr);
|
|
if (ret)
|
|
lua_error_p(L, "invalid argument");
|
|
}
|
|
} else if (lua_isstring(L, 1)) {
|
|
addr = lua_tostring(L, 1);
|
|
int ret = network_proxy_allow(net, addr);
|
|
if (ret)
|
|
lua_error_p(L, "invalid argument");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Close endpoint. */
|
|
static int net_close(lua_State *L)
|
|
{
|
|
/* Check parameters */
|
|
const int n = lua_gettop(L);
|
|
bool ok = (n == 1 || n == 2) && lua_isstring(L, 1);
|
|
const char *addr = lua_tostring(L, 1);
|
|
int port;
|
|
if (ok && (n < 2 || lua_isnil(L, 2))) {
|
|
port = -1;
|
|
} else if (ok) {
|
|
ok = lua_isnumber(L, 2);
|
|
port = lua_tointeger(L, 2);
|
|
ok = ok && port >= 0 && port <= 65535;
|
|
}
|
|
if (!ok)
|
|
lua_error_p(L, "expected 'close(string addr, [number port])'");
|
|
|
|
int ret = network_close(&the_worker->engine->net, addr, port);
|
|
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';
|
|
}
|
|
|
|
if (kr_sockaddr_link_local((struct sockaddr *) &iface.address)) {
|
|
/* Link-local IPv6: add %interface prefix */
|
|
auto_free char *str = NULL;
|
|
int ret = asprintf(&str, "%s%%%s", buf, iface.name);
|
|
kr_assert(ret > 0);
|
|
lua_pushstring(L, str);
|
|
} else {
|
|
lua_pushstring(L, buf);
|
|
}
|
|
|
|
lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
|
|
lua_setfield(L, -2, "addr");
|
|
|
|
/* Hardware address. */
|
|
char *p = buf;
|
|
for (int k = 0; k < sizeof(iface.phys_addr); ++k) {
|
|
(void)sprintf(p, "%.2x:", (uint8_t)iface.phys_addr[k]);
|
|
p += 3;
|
|
}
|
|
p[-1] = '\0';
|
|
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 kr_context *ctx = &the_worker->engine->resolver;
|
|
const int argc = lua_gettop(L);
|
|
if (argc == 0) {
|
|
lua_pushinteger(L, knot_edns_get_payload(ctx->downstream_opt_rr));
|
|
lua_pushinteger(L, knot_edns_get_payload(ctx->upstream_opt_rr));
|
|
return 2;
|
|
}
|
|
|
|
if (argc == 1) {
|
|
int bufsize = lua_tointeger(L, 1);
|
|
if (bufsize < 512 || bufsize > UINT16_MAX)
|
|
lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">");
|
|
knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize);
|
|
knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize);
|
|
} else if (argc == 2) {
|
|
int bufsize_downstream = lua_tointeger(L, 1);
|
|
int bufsize_upstream = lua_tointeger(L, 2);
|
|
if (bufsize_downstream < 512 || bufsize_upstream < 512
|
|
|| bufsize_downstream > UINT16_MAX || bufsize_upstream > UINT16_MAX) {
|
|
lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">");
|
|
}
|
|
knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize_downstream);
|
|
knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize_upstream);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Set TCP pipelining size. */
|
|
static int net_pipeline(lua_State *L)
|
|
{
|
|
struct worker_ctx *worker = the_worker;
|
|
if (!worker) {
|
|
return 0;
|
|
}
|
|
if (!lua_isnumber(L, 1)) {
|
|
lua_pushinteger(L, worker->tcp_pipeline_max);
|
|
return 1;
|
|
}
|
|
int len = lua_tointeger(L, 1);
|
|
if (len < 0 || len > UINT16_MAX)
|
|
lua_error_p(L, "tcp_pipeline must be within <0, " STR(UINT16_MAX) ">");
|
|
worker->tcp_pipeline_max = len;
|
|
lua_pushinteger(L, len);
|
|
return 1;
|
|
}
|
|
|
|
static int net_tls(lua_State *L)
|
|
{
|
|
struct network *net = &the_worker->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_error_p(L, "net.tls takes two parameters: (\"cert_file\", \"key_file\")");
|
|
|
|
int r = tls_certificate_set(net, lua_tostring(L, 1), lua_tostring(L, 2));
|
|
lua_error_maybe(L, r);
|
|
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
}
|
|
|
|
/** Configure HTTP headers for DoH requests. */
|
|
static int net_doh_headers(lua_State *L)
|
|
{
|
|
doh_headerlist_t *headers = &the_worker->doh_qry_headers;
|
|
int i;
|
|
const char *name;
|
|
|
|
/* Only return current configuration. */
|
|
if (lua_gettop(L) == 0) {
|
|
lua_newtable(L);
|
|
for (i = 0; i < headers->len; i++) {
|
|
lua_pushinteger(L, i + 1);
|
|
name = headers->at[i];
|
|
lua_pushlstring(L, name, strlen(name));
|
|
lua_settable(L, -3);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (lua_gettop(L) != 1)
|
|
lua_error_p(L, "net.doh_headers() takes one parameter (string or table)");
|
|
|
|
if (!lua_istable(L, 1) && !lua_isstring(L, 1))
|
|
lua_error_p(L, "net.doh_headers() argument must be string or table");
|
|
|
|
/* Clear existing headers. */
|
|
for (i = 0; i < headers->len; i++)
|
|
free((void *)headers->at[i]);
|
|
array_clear(*headers);
|
|
|
|
if (lua_istable(L, 1)) {
|
|
for (i = 1; !lua_isnil(L, -1); i++) {
|
|
lua_pushinteger(L, i);
|
|
lua_gettable(L, 1);
|
|
if (lua_isnil(L, -1)) /* missing value - end iteration */
|
|
break;
|
|
if (!lua_isstring(L, -1))
|
|
lua_error_p(L, "net.doh_headers() argument table can only contain strings");
|
|
name = lua_tostring(L, -1);
|
|
array_push(*headers, strdup(name));
|
|
}
|
|
} else if (lua_isstring(L, 1)) {
|
|
name = lua_tostring(L, 1);
|
|
array_push(*headers, strdup(name));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Return a lua table with TLS authentication parameters.
|
|
* The format is the same as passed to policy.TLS_FORWARD();
|
|
* more precisely, it's in a compatible canonical form. */
|
|
static int tls_params2lua(lua_State *L, trie_t *params)
|
|
{
|
|
lua_newtable(L);
|
|
if (!params) /* Allowed special case. */
|
|
return 1;
|
|
trie_it_t *it;
|
|
size_t list_index = 0;
|
|
for (it = trie_it_begin(params); !trie_it_finished(it); trie_it_next(it)) {
|
|
/* Prepare table for the current address
|
|
* and its index in the returned list. */
|
|
lua_pushinteger(L, ++list_index);
|
|
lua_createtable(L, 0, 2);
|
|
|
|
/* Get the "addr#port" string... */
|
|
size_t ia_len;
|
|
const char *key = trie_it_key(it, &ia_len);
|
|
int af = AF_UNSPEC;
|
|
if (ia_len == 2 + sizeof(struct in_addr)) {
|
|
af = AF_INET;
|
|
} else if (ia_len == 2 + sizeof(struct in6_addr)) {
|
|
af = AF_INET6;
|
|
}
|
|
if (kr_fails_assert(key && af != AF_UNSPEC))
|
|
lua_error_p(L, "internal error: bad IP address");
|
|
uint16_t port;
|
|
memcpy(&port, key, sizeof(port));
|
|
port = ntohs(port);
|
|
const char *ia = key + sizeof(port);
|
|
char str[INET6_ADDRSTRLEN + 1 + 5 + 1];
|
|
size_t len = sizeof(str);
|
|
if (kr_fails_assert(kr_ntop_str(af, ia, port, str, &len) == kr_ok()))
|
|
lua_error_p(L, "internal error: bad IP address conversion");
|
|
/* ...and push it as [1]. */
|
|
lua_pushinteger(L, 1);
|
|
lua_pushlstring(L, str, len - 1 /* len includes '\0' */);
|
|
lua_settable(L, -3);
|
|
|
|
const tls_client_param_t *e = *trie_it_val(it);
|
|
if (kr_fails_assert(e))
|
|
lua_error_p(L, "internal problem - NULL entry for %s", str);
|
|
|
|
/* .hostname = */
|
|
if (e->hostname) {
|
|
lua_pushstring(L, e->hostname);
|
|
lua_setfield(L, -2, "hostname");
|
|
}
|
|
|
|
/* .ca_files = */
|
|
if (e->ca_files.len) {
|
|
lua_createtable(L, e->ca_files.len, 0);
|
|
for (size_t i = 0; i < e->ca_files.len; ++i) {
|
|
lua_pushinteger(L, i + 1);
|
|
lua_pushstring(L, e->ca_files.at[i]);
|
|
lua_settable(L, -3);
|
|
}
|
|
lua_setfield(L, -2, "ca_files");
|
|
}
|
|
|
|
/* .pin_sha256 = ... ; keep sane indentation via goto. */
|
|
if (!e->pins.len) goto no_pins;
|
|
lua_createtable(L, e->pins.len, 0);
|
|
for (size_t i = 0; i < e->pins.len; ++i) {
|
|
uint8_t pin_base64[TLS_SHA256_BASE64_BUFLEN];
|
|
int err = kr_base64_encode(e->pins.at[i], TLS_SHA256_RAW_LEN,
|
|
pin_base64, sizeof(pin_base64));
|
|
if (kr_fails_assert(err >= 0))
|
|
lua_error_p(L,
|
|
"internal problem when converting pin_sha256: %s",
|
|
kr_strerror(err));
|
|
lua_pushinteger(L, i + 1);
|
|
lua_pushlstring(L, (const char *)pin_base64, err);
|
|
/* pin_base64 isn't 0-terminated ^^^ */
|
|
lua_settable(L, -3);
|
|
}
|
|
lua_setfield(L, -2, "pin_sha256");
|
|
|
|
no_pins:/* .insecure = */
|
|
if (e->insecure) {
|
|
lua_pushboolean(L, true);
|
|
lua_setfield(L, -2, "insecure");
|
|
}
|
|
/* Now the whole table is pushed atop the returned list. */
|
|
lua_settable(L, -3);
|
|
}
|
|
trie_it_free(it);
|
|
return 1;
|
|
}
|
|
|
|
static inline int cmp_sha256(const void *p1, const void *p2)
|
|
{
|
|
return memcmp(*(char * const *)p1, *(char * const *)p2, TLS_SHA256_RAW_LEN);
|
|
}
|
|
static int net_tls_client(lua_State *L)
|
|
{
|
|
/* TODO idea: allow starting the lua table with *multiple* IP targets,
|
|
* meaning the authentication config should be applied to each.
|
|
*/
|
|
struct network *net = &the_worker->engine->net;
|
|
if (lua_gettop(L) == 0)
|
|
return tls_params2lua(L, net->tls_client_params);
|
|
/* Various basic sanity-checking. */
|
|
if (lua_gettop(L) != 1 || !lua_istable(L, 1))
|
|
lua_error_maybe(L, EINVAL);
|
|
/* check that only allowed keys are present */
|
|
{
|
|
const char *bad_key = lua_table_checkindices(L, (const char *[])
|
|
{ "1", "hostname", "ca_file", "pin_sha256", "insecure", NULL });
|
|
if (bad_key)
|
|
lua_error_p(L, "found unexpected key '%s'", bad_key);
|
|
}
|
|
|
|
/**** Phase 1: get the parameter into a C struct, incl. parse of CA files,
|
|
* regardless of the address-pair having an entry already. */
|
|
|
|
tls_client_param_t *newcfg = tls_client_param_new();
|
|
if (!newcfg)
|
|
lua_error_p(L, "out of memory or something like that :-/");
|
|
/* Shortcut for cleanup actions needed from now on. */
|
|
#define ERROR(...) do { \
|
|
free(newcfg); \
|
|
lua_error_p(L, __VA_ARGS__); \
|
|
} while (false)
|
|
|
|
/* .hostname - always accepted. */
|
|
lua_getfield(L, 1, "hostname");
|
|
if (!lua_isnil(L, -1)) {
|
|
const char *hn_str = lua_tostring(L, -1);
|
|
/* Convert to lower-case dname and back, for checking etc. */
|
|
knot_dname_t dname[KNOT_DNAME_MAXLEN];
|
|
if (!hn_str || !knot_dname_from_str(dname, hn_str, sizeof(dname)))
|
|
ERROR("invalid hostname");
|
|
knot_dname_to_lower(dname);
|
|
char *h = knot_dname_to_str_alloc(dname);
|
|
if (!h)
|
|
ERROR("%s", kr_strerror(ENOMEM));
|
|
/* Strip the final dot produced by knot_dname_*() */
|
|
h[strlen(h) - 1] = '\0';
|
|
newcfg->hostname = h;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
/* .ca_file - it can be a list of paths, contrary to the name. */
|
|
bool has_ca_file = false;
|
|
lua_getfield(L, 1, "ca_file");
|
|
if (!lua_isnil(L, -1)) {
|
|
if (!newcfg->hostname)
|
|
ERROR("missing hostname but specifying ca_file");
|
|
lua_listify(L);
|
|
array_init(newcfg->ca_files); /*< placate apparently confused scan-build */
|
|
if (array_reserve(newcfg->ca_files, lua_objlen(L, -1)) != 0) /*< optim. */
|
|
ERROR("%s", kr_strerror(ENOMEM));
|
|
/* Iterate over table at the top of the stack.
|
|
* http://www.lua.org/manual/5.1/manual.html#lua_next */
|
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
|
has_ca_file = true; /* deferred here so that {} -> false */
|
|
const char *ca_file = lua_tostring(L, -1);
|
|
if (!ca_file)
|
|
ERROR("ca_file contains a non-string");
|
|
/* Let gnutls process it immediately, so garbage gets detected. */
|
|
int ret = gnutls_certificate_set_x509_trust_file(
|
|
newcfg->credentials, ca_file, GNUTLS_X509_FMT_PEM);
|
|
if (ret < 0) {
|
|
ERROR("failed to import certificate file '%s': %s - %s\n",
|
|
ca_file, gnutls_strerror_name(ret),
|
|
gnutls_strerror(ret));
|
|
} else {
|
|
kr_log_debug(TLSCLIENT, "imported %d certs from file '%s'\n",
|
|
ret, ca_file);
|
|
}
|
|
|
|
ca_file = strdup(ca_file);
|
|
if (!ca_file || array_push(newcfg->ca_files, ca_file) < 0)
|
|
ERROR("%s", kr_strerror(ENOMEM));
|
|
}
|
|
/* Sort the strings for easier comparison later. */
|
|
if (newcfg->ca_files.len) {
|
|
qsort(&newcfg->ca_files.at[0], newcfg->ca_files.len,
|
|
array_member_size(newcfg->ca_files), strcmp_p);
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
/* .pin_sha256 */
|
|
lua_getfield(L, 1, "pin_sha256");
|
|
if (!lua_isnil(L, -1)) {
|
|
if (has_ca_file)
|
|
ERROR("mixing pin_sha256 with ca_file is not supported");
|
|
lua_listify(L);
|
|
array_init(newcfg->pins); /*< placate apparently confused scan-build */
|
|
if (array_reserve(newcfg->pins, lua_objlen(L, -1)) != 0) /*< optim. */
|
|
ERROR("%s", kr_strerror(ENOMEM));
|
|
/* Iterate over table at the top of the stack. */
|
|
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
|
|
const char *pin = lua_tostring(L, -1);
|
|
if (!pin)
|
|
ERROR("pin_sha256 is not a string");
|
|
uint8_t *pin_raw = malloc(TLS_SHA256_RAW_LEN);
|
|
/* Push the string early to simplify error processing. */
|
|
if (kr_fails_assert(pin_raw && array_push(newcfg->pins, pin_raw) >= 0)) {
|
|
free(pin_raw);
|
|
ERROR("%s", kr_strerror(ENOMEM));
|
|
}
|
|
int ret = kr_base64_decode((const uint8_t *)pin, strlen(pin),
|
|
pin_raw, TLS_SHA256_RAW_LEN + 8);
|
|
if (ret < 0) {
|
|
ERROR("not a valid pin_sha256: '%s' (length %d), %s\n",
|
|
pin, (int)strlen(pin), knot_strerror(ret));
|
|
} else if (ret != TLS_SHA256_RAW_LEN) {
|
|
ERROR("not a valid pin_sha256: '%s', "
|
|
"raw length %d instead of "
|
|
STR(TLS_SHA256_RAW_LEN)"\n",
|
|
pin, ret);
|
|
}
|
|
}
|
|
/* Sort the raw strings for easier comparison later. */
|
|
if (newcfg->pins.len) {
|
|
qsort(&newcfg->pins.at[0], newcfg->pins.len,
|
|
array_member_size(newcfg->pins), cmp_sha256);
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
/* .insecure */
|
|
lua_getfield(L, 1, "insecure");
|
|
if (lua_isnil(L, -1)) {
|
|
if (!newcfg->hostname && !newcfg->pins.len)
|
|
ERROR("no way to authenticate and not set as insecure");
|
|
} else if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) {
|
|
newcfg->insecure = true;
|
|
if (has_ca_file || newcfg->pins.len)
|
|
ERROR("set as insecure but provided authentication config");
|
|
} else {
|
|
ERROR("incorrect value in the 'insecure' field");
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
/* Init CAs from system trust store, if needed. */
|
|
if (!newcfg->insecure && !newcfg->pins.len && !has_ca_file) {
|
|
int ret = gnutls_certificate_set_x509_system_trust(newcfg->credentials);
|
|
if (ret <= 0) {
|
|
ERROR("failed to use system CA certificate store: %s",
|
|
ret ? gnutls_strerror(ret) : kr_strerror(ENOENT));
|
|
} else {
|
|
kr_log_debug(TLSCLIENT, "imported %d certs from system store\n",
|
|
ret);
|
|
}
|
|
}
|
|
#undef ERROR
|
|
|
|
/**** Phase 2: deal with the C authentication "table". */
|
|
/* Parse address and port. */
|
|
lua_pushinteger(L, 1);
|
|
lua_gettable(L, 1);
|
|
const char *addr_str = lua_tostring(L, -1);
|
|
if (!addr_str)
|
|
lua_error_p(L, "address is not a string");
|
|
char buf[INET6_ADDRSTRLEN + 1];
|
|
uint16_t port = 853;
|
|
const struct sockaddr *addr = NULL;
|
|
if (kr_straddr_split(addr_str, buf, &port) == kr_ok())
|
|
addr = kr_straddr_socket(buf, port, NULL);
|
|
/* Add newcfg into the C map, saving the original into oldcfg. */
|
|
if (!addr)
|
|
lua_error_p(L, "address '%s' could not be converted", addr_str);
|
|
tls_client_param_t **oldcfgp = tls_client_param_getptr(
|
|
&net->tls_client_params, addr, true);
|
|
free_const(addr);
|
|
if (!oldcfgp)
|
|
lua_error_p(L, "internal error when extending tls_client_params map");
|
|
tls_client_param_t *oldcfg = *oldcfgp;
|
|
*oldcfgp = newcfg; /* replace old config in trie with the new one */
|
|
/* If there was no original entry, it's easy! */
|
|
if (!oldcfg)
|
|
return 0;
|
|
|
|
/* Check for equality (newcfg vs. oldcfg), and print a warning if not equal.*/
|
|
const bool ok_h = (!newcfg->hostname && !oldcfg->hostname)
|
|
|| (newcfg->hostname && oldcfg->hostname && strcmp(newcfg->hostname, oldcfg->hostname) == 0);
|
|
bool ok_ca = newcfg->ca_files.len == oldcfg->ca_files.len;
|
|
for (int i = 0; ok_ca && i < newcfg->ca_files.len; ++i)
|
|
ok_ca = strcmp(newcfg->ca_files.at[i], oldcfg->ca_files.at[i]) == 0;
|
|
bool ok_pins = newcfg->pins.len == oldcfg->pins.len;
|
|
for (int i = 0; ok_pins && i < newcfg->pins.len; ++i)
|
|
ok_ca = memcmp(newcfg->pins.at[i], oldcfg->pins.at[i], TLS_SHA256_RAW_LEN) == 0;
|
|
const bool ok_insecure = newcfg->insecure == oldcfg->insecure;
|
|
if (!(ok_h && ok_ca && ok_pins && ok_insecure)) {
|
|
kr_log_warning(TLSCLIENT,
|
|
"warning: re-defining TLS authentication parameters for %s\n",
|
|
addr_str);
|
|
}
|
|
tls_client_param_unref(oldcfg);
|
|
return 0;
|
|
}
|
|
|
|
int net_tls_client_clear(lua_State *L)
|
|
{
|
|
/* One parameter: address -> convert it to a struct sockaddr. */
|
|
if (lua_gettop(L) != 1 || !lua_isstring(L, 1))
|
|
lua_error_p(L, "net.tls_client_clear() requires one parameter (\"address\")");
|
|
const char *addr_str = lua_tostring(L, 1);
|
|
char buf[INET6_ADDRSTRLEN + 1];
|
|
uint16_t port = 853;
|
|
const struct sockaddr *addr = NULL;
|
|
if (kr_straddr_split(addr_str, buf, &port) == kr_ok())
|
|
addr = kr_straddr_socket(buf, port, NULL);
|
|
if (!addr)
|
|
lua_error_p(L, "invalid IP address");
|
|
/* Do the actual removal. */
|
|
struct network *net = &the_worker->engine->net;
|
|
int r = tls_client_param_remove(net->tls_client_params, addr);
|
|
free_const(addr);
|
|
lua_error_maybe(L, r);
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
}
|
|
|
|
static int net_tls_padding(lua_State *L)
|
|
{
|
|
struct kr_context *ctx = &the_worker->engine->resolver;
|
|
|
|
/* Only return current padding. */
|
|
if (lua_gettop(L) == 0) {
|
|
if (ctx->tls_padding < 0) {
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
} else if (ctx->tls_padding == 0) {
|
|
lua_pushboolean(L, false);
|
|
return 1;
|
|
}
|
|
lua_pushinteger(L, ctx->tls_padding);
|
|
return 1;
|
|
}
|
|
|
|
const char *errstr = "net.tls_padding parameter has to be true, false,"
|
|
" or a number between <0, " STR(MAX_TLS_PADDING) ">";
|
|
if (lua_gettop(L) != 1)
|
|
lua_error_p(L, "%s", errstr);
|
|
if (lua_isboolean(L, 1)) {
|
|
bool x = lua_toboolean(L, 1);
|
|
if (x) {
|
|
ctx->tls_padding = -1;
|
|
} else {
|
|
ctx->tls_padding = 0;
|
|
}
|
|
} else if (lua_isnumber(L, 1)) {
|
|
int padding = lua_tointeger(L, 1);
|
|
if ((padding < 0) || (padding > MAX_TLS_PADDING))
|
|
lua_error_p(L, "%s", errstr);
|
|
ctx->tls_padding = padding;
|
|
} else {
|
|
lua_error_p(L, "%s", errstr);
|
|
}
|
|
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 = &the_worker->engine->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_error_p(L,
|
|
"net.tls_sticket_secret takes one parameter: (\"secret string\")");
|
|
}
|
|
secret = lua_tolstring(L, 1, &secret_len);
|
|
if (secret_len < net_tls_sticket_MIN_SECRET_LEN || !secret) {
|
|
lua_error_p(L, "net.tls_sticket_secret - the secret is shorter than "
|
|
STR(net_tls_sticket_MIN_SECRET_LEN) " bytes");
|
|
}
|
|
}
|
|
|
|
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_error_p(L,
|
|
"net.tls_sticket_secret_string - can't create session ticket context");
|
|
}
|
|
|
|
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_error_p(L,
|
|
"net.tls_sticket_secret_file takes one parameter: (\"file name\")");
|
|
}
|
|
|
|
const char *file_name = lua_tostring(L, 1);
|
|
if (strlen(file_name) == 0)
|
|
lua_error_p(L, "net.tls_sticket_secret_file - empty file name");
|
|
|
|
FILE *fp = fopen(file_name, "r");
|
|
if (fp == NULL) {
|
|
lua_error_p(L, "net.tls_sticket_secret_file - can't open file '%s': %s",
|
|
file_name, strerror(errno));
|
|
}
|
|
|
|
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_error_p(L,
|
|
"net.tls_sticket_secret_file - error reading from file '%s': %s",
|
|
file_name, strerror(err));
|
|
}
|
|
if (secret_len < net_tls_sticket_MIN_SECRET_LEN) {
|
|
lua_error_p(L,
|
|
"net.tls_sticket_secret_file - file '%s' is shorter than "
|
|
STR(net_tls_sticket_MIN_SECRET_LEN) " bytes",
|
|
file_name);
|
|
}
|
|
if (fclose(fp) == EOF) {
|
|
lua_error_p(L,
|
|
"net.tls_sticket_secret_file - reading of file '%s' failed",
|
|
file_name);
|
|
}
|
|
|
|
struct network *net = &the_worker->engine->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_error_p(L,
|
|
"net.tls_sticket_secret_file - can't create session ticket context");
|
|
}
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
}
|
|
|
|
static int net_outgoing(lua_State *L, int family)
|
|
{
|
|
union kr_sockaddr *addr;
|
|
if (family == AF_INET)
|
|
addr = (union kr_sockaddr*)&the_worker->out_addr4;
|
|
else
|
|
addr = (union kr_sockaddr*)&the_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 (kr_fails_assert(addr->ip.sa_family == family))
|
|
lua_error_p(L, "bad address family");
|
|
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));
|
|
lua_error_maybe(L, err);
|
|
lua_pushstring(L, addr_buf);
|
|
return 1;
|
|
}
|
|
|
|
if ((lua_gettop(L) != 1) || (!lua_isstring(L, 1) && !lua_isnil(L, 1)))
|
|
lua_error_p(L, "net.outgoing_vX takes one address string parameter or nil");
|
|
|
|
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)
|
|
lua_error_p(L, "net.outgoing_vX: failed to parse the address");
|
|
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_pushinteger(L, *timeout);
|
|
return 1;
|
|
}
|
|
|
|
if ((lua_gettop(L) != 1))
|
|
lua_error_p(L, "%s takes one parameter: (\"idle timeout\")", name);
|
|
|
|
if (lua_isnumber(L, 1)) {
|
|
int idle_timeout = lua_tointeger(L, 1);
|
|
if (idle_timeout <= 0)
|
|
lua_error_p(L, "%s parameter has to be positive number", name);
|
|
*timeout = idle_timeout;
|
|
} else {
|
|
lua_error_p(L, "%s parameter has to be positive number", name);
|
|
}
|
|
lua_pushboolean(L, true);
|
|
return 1;
|
|
}
|
|
|
|
static int net_tcp_in_idle(lua_State *L)
|
|
{
|
|
struct network *net = &the_worker->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 network *net = &the_worker->engine->net;
|
|
return net_update_timeout(L, &net->tcp.tls_handshake_timeout, "net.tls_handshake_timeout");
|
|
}
|
|
|
|
static int net_bpf_set(lua_State *L)
|
|
{
|
|
if (lua_gettop(L) != 1 || !lua_isnumber(L, 1)) {
|
|
lua_error_p(L, "net.bpf_set(fd) takes one parameter:"
|
|
" the open file descriptor of a loaded BPF program");
|
|
}
|
|
|
|
#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_p(L, "failed to convert parameter");
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
if (network_set_bpf(&the_worker->engine->net, progfd) == 0) {
|
|
lua_error_p(L, "failed to attach BPF program to some networks: %s",
|
|
kr_strerror(errno));
|
|
}
|
|
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
|
|
#endif
|
|
lua_error_p(L, "BPF is not supported on this operating system");
|
|
}
|
|
|
|
static int net_bpf_clear(lua_State *L)
|
|
{
|
|
if (lua_gettop(L) != 0)
|
|
lua_error_p(L, "net.bpf_clear() does not take any parameters");
|
|
|
|
#if __linux__
|
|
|
|
network_clear_bpf(&the_worker->engine->net);
|
|
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
|
|
#endif
|
|
lua_error_p(L, "BPF is not supported on this operating system");
|
|
}
|
|
|
|
static int net_register_endpoint_kind(lua_State *L)
|
|
{
|
|
const int param_count = lua_gettop(L);
|
|
if (param_count != 1 && param_count != 2)
|
|
lua_error_p(L, "expected one or two parameters");
|
|
if (!lua_isstring(L, 1)) {
|
|
lua_error_p(L, "incorrect kind '%s'", lua_tostring(L, 1));
|
|
}
|
|
size_t kind_len;
|
|
const char *kind = lua_tolstring(L, 1, &kind_len);
|
|
struct network *net = &the_worker->engine->net;
|
|
|
|
/* Unregistering */
|
|
if (param_count == 1) {
|
|
void *val;
|
|
if (trie_del(net->endpoint_kinds, kind, kind_len, &val) == KNOT_EOK) {
|
|
const int fun_id = (char *)val - (char *)NULL;
|
|
luaL_unref(L, LUA_REGISTRYINDEX, fun_id);
|
|
return 0;
|
|
}
|
|
lua_error_p(L, "attempt to unregister unknown kind '%s'\n", kind);
|
|
} /* else -> param_count == 2 */
|
|
|
|
/* Registering */
|
|
if (!lua_isfunction(L, 2)) {
|
|
lua_error_p(L, "second parameter: expected function but got %s\n",
|
|
lua_typename(L, lua_type(L, 2)));
|
|
}
|
|
const int fun_id = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
/* ^^ The function is on top of the stack, incidentally. */
|
|
void **pp = trie_get_ins(net->endpoint_kinds, kind, kind_len);
|
|
if (!pp) lua_error_maybe(L, kr_error(ENOMEM));
|
|
if (*pp != NULL || !strcasecmp(kind, "dns") || !strcasecmp(kind, "tls"))
|
|
lua_error_p(L, "attempt to register known kind '%s'\n", kind);
|
|
*pp = (char *)NULL + fun_id;
|
|
/* We don't attempt to engage corresponding endpoints now.
|
|
* That's the job for network_engage_endpoints() later. */
|
|
return 0;
|
|
}
|
|
|
|
int kr_bindings_net(lua_State *L)
|
|
{
|
|
static const luaL_Reg lib[] = {
|
|
{ "list", net_list },
|
|
{ "listen", net_listen },
|
|
{ "proxy_allowed", net_proxy_allowed },
|
|
{ "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 },
|
|
{ "register_endpoint_kind", net_register_endpoint_kind },
|
|
{ "doh_headers", net_doh_headers },
|
|
{ NULL, NULL }
|
|
};
|
|
luaL_register(L, "net", lib);
|
|
return 1;
|
|
}
|
|
|