From: Jo-Philipp Wich Date: Thu, 22 Jan 2015 13:59:13 +0000 (+0100) Subject: libs: add luci-lib-ip X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=commitdiff_plain;h=0ff02e3a13d11466fe8997932a7f9828f53f3ee8 libs: add luci-lib-ip Add new luci.ip library which is an api compatible C reimplementation of ip.lua. It also supports dumping the system routing table and neighbour entry database via netlink. Signed-off-by: Jo-Philipp Wich --- diff --git a/libs/luci-lib-ip/Makefile b/libs/luci-lib-ip/Makefile new file mode 100644 index 000000000..eb80dcb25 --- /dev/null +++ b/libs/luci-lib-ip/Makefile @@ -0,0 +1,14 @@ +# +# Copyright (C) 2015 LuCI Team +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=Lua library for IP calculation and routing information +LUCI_DEPENDS:=+liblua +libnl-tiny + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/libs/luci-lib-ip/src/Makefile b/libs/luci-lib-ip/src/Makefile new file mode 100644 index 000000000..76abd27d2 --- /dev/null +++ b/libs/luci-lib-ip/src/Makefile @@ -0,0 +1,17 @@ +IP_CFLAGS = -std=gnu99 -I$(STAGING_DIR)/usr/include/libnl-tiny/ +IP_LDFLAGS = -llua -lm -lnl-tiny +IP_OBJ = ip.o +IP_LIB = ip.so + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LUA_CFLAGS) $(IP_CFLAGS) $(FPIC) -c -o $@ $< + +compile: $(IP_OBJ) + $(CC) $(LDFLAGS) -shared -o $(IP_LIB) $(IP_OBJ) $(IP_LDFLAGS) + +install: compile + mkdir -p $(DESTDIR)/usr/lib/lua/luci + cp $(IP_LIB) $(DESTDIR)/usr/lib/lua/luci/$(IP_LIB) + +clean: + rm -f *.o *.so diff --git a/libs/luci-lib-ip/src/ip.c b/libs/luci-lib-ip/src/ip.c new file mode 100644 index 000000000..66ecb567e --- /dev/null +++ b/libs/luci-lib-ip/src/ip.c @@ -0,0 +1,1225 @@ +/* +Copyright 2015 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define LUCI_IP "luci.ip" +#define LUCI_IP_CIDR "luci.ip.cidr" + +#define RTA_INT(x) (*(int *)RTA_DATA(x)) +#define RTA_U32(x) (*(uint32_t *)RTA_DATA(x)) + +static int hz = 0; +static struct nl_sock *sock = NULL; + +typedef struct { + union { + struct in_addr v4; + struct in6_addr v6; + } addr; + int len; + int bits; + int family; + bool exact; +} cidr_t; + +struct dump_filter { + bool get; + int family; + int iif; + int oif; + int type; + int scope; + int proto; + int table; + cidr_t gw; + cidr_t from; + cidr_t src; + cidr_t dst; +}; + +struct dump_state { + int index; + int pending; + int callback; + struct lua_State *L; + struct dump_filter *filter; +}; + + +static int _cidr_new(lua_State *L, int index, int family); + +static cidr_t *L_checkcidr (lua_State *L, int index, cidr_t *p) +{ + if (lua_type(L, index) == LUA_TUSERDATA) + return luaL_checkudata(L, index, LUCI_IP_CIDR); + + if (_cidr_new(L, index, p ? p->family : 0)) + return lua_touserdata(L, -1); + + luaL_error(L, "Invalid operand"); + return NULL; +} + +static bool parse_mask(int family, const char *mask, int *bits) +{ + char *e; + struct in_addr m; + struct in6_addr m6; + + if (family == AF_INET && inet_pton(AF_INET, mask, &m)) + { + for (*bits = 0, m.s_addr = ntohl(m.s_addr); + *bits < 32 && (m.s_addr << *bits) & 0x80000000; + ++*bits); + } + else if (family == AF_INET6 && inet_pton(AF_INET6, mask, &m6)) + { + for (*bits = 0; + *bits < 128 && (m6.s6_addr[*bits / 8] << (*bits % 8)) & 128; + ++*bits); + } + else + { + *bits = strtoul(mask, &e, 10); + + if (e == mask || *e != 0 || *bits > ((family == AF_INET) ? 32 : 128)) + return false; + } + + return true; +} + +static bool parse_cidr(const char *dest, cidr_t *pp) +{ + char *p, *a, buf[INET6_ADDRSTRLEN * 2 + 2]; + uint8_t bitlen = 0; + + strncpy(buf, dest, sizeof(buf) - 1); + + a = buf; + p = strchr(buf, '/'); + + if (p) + *p++ = 0; + + if (!strncasecmp(buf, "::ffff:", 7)) + a += 7; + + if (inet_pton(AF_INET, a, &pp->addr.v4)) + { + bitlen = 32; + pp->family = AF_INET; + pp->len = sizeof(struct in_addr); + } + else if (inet_pton(AF_INET6, a, &pp->addr.v6)) + { + bitlen = 128; + pp->family = AF_INET6; + pp->len = sizeof(struct in6_addr); + } + else + return false; + + if (p) + { + if (!parse_mask(pp->family, p, &pp->bits)) + return false; + } + else + { + pp->bits = bitlen; + } + + return true; +} + +static int L_getint(lua_State *L, int index, const char *name) +{ + int rv = 0; + + lua_getfield(L, index, name); + + if (lua_type(L, -1) == LUA_TNUMBER) + rv = lua_tonumber(L, -1); + + lua_pop(L, 1); + + return rv; +} + +static const char * L_getstr(lua_State *L, int index, const char *name) +{ + const char *rv = NULL; + + lua_getfield(L, index, name); + + if (lua_type(L, -1) == LUA_TSTRING) + rv = lua_tostring(L, -1); + + lua_pop(L, 1); + + return rv; +} + +static void L_setint(struct lua_State *L, const char *name, uint32_t n) +{ + lua_pushinteger(L, n); + lua_setfield(L, -2, name); +} + +static void L_setbool(struct lua_State *L, const char *name, bool val) +{ + lua_pushboolean(L, val); + lua_setfield(L, -2, name); +} + +static void L_setaddr(struct lua_State *L, const char *name, + int family, void *addr, int bits) +{ + cidr_t *p; + + if (!addr) + return; + + p = lua_newuserdata(L, sizeof(*p)); + + if (!p) + return; + + if (family == AF_INET) + { + p->family = AF_INET; + p->bits = (bits < 0) ? 32 : bits; + p->len = sizeof(p->addr.v4); + p->addr.v4 = *(struct in_addr *)addr; + } + else + { + p->family = AF_INET6; + p->bits = (bits < 0) ? 128 : bits; + p->len = sizeof(p->addr.v6); + p->addr.v6 = *(struct in6_addr *)addr; + } + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + lua_setfield(L, -2, name); +} + +static void L_setstr(struct lua_State *L, const char *name, const char *val) +{ + lua_pushstring(L, val); + lua_setfield(L, -2, name); +} + +static void L_setdev(struct lua_State *L, const char *name, + struct nlattr *attr) +{ + char buf[32]; + + if (if_indextoname(RTA_INT(attr), buf)) + L_setstr(L, name, buf); +} + +static int L_checkbits(lua_State *L, int index, cidr_t *p) +{ + int bits; + + if (lua_gettop(L) < index || lua_isnil(L, index)) + { + bits = p->bits; + } + else if (lua_type(L, index) == LUA_TNUMBER) + { + bits = lua_tointeger(L, index); + + if (bits < 0 || bits > ((p->family == AF_INET) ? 32 : 128)) + return luaL_error(L, "Invalid prefix size"); + } + else if (lua_type(L, index) == LUA_TSTRING) + { + if (!parse_mask(p->family, lua_tostring(L, index), &bits)) + return luaL_error(L, "Invalid netmask format"); + } + else + { + return luaL_error(L, "Invalid data type"); + } + + return bits; +} + +static int _cidr_new(lua_State *L, int index, int family) +{ + uint32_t n; + const char *addr; + cidr_t cidr = { }, *cidrp; + + if (lua_type(L, index) == LUA_TNUMBER) + { + n = htonl(lua_tointeger(L, index)); + + if (family == AF_INET6) + { + cidr.family = AF_INET6; + cidr.bits = 128; + cidr.len = sizeof(cidr.addr.v6); + cidr.addr.v6.s6_addr[15] = n; + cidr.addr.v6.s6_addr[14] = (n >> 8); + cidr.addr.v6.s6_addr[13] = (n >> 16); + cidr.addr.v6.s6_addr[12] = (n >> 24); + } + else + { + cidr.family = AF_INET; + cidr.bits = 32; + cidr.len = sizeof(cidr.addr.v4); + cidr.addr.v4.s_addr = n; + } + } + else + { + addr = luaL_checkstring(L, index); + + if (!parse_cidr(addr, &cidr)) + return 0; + + if (family && cidr.family != family) + return 0; + } + + if (!(cidrp = lua_newuserdata(L, sizeof(*cidrp)))) + return 0; + + *cidrp = cidr; + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_new(lua_State *L) +{ + return _cidr_new(L, 1, 0); +} + +static int cidr_ipv4(lua_State *L) +{ + return _cidr_new(L, 1, AF_INET); +} + +static int cidr_ipv6(lua_State *L) +{ + return _cidr_new(L, 1, AF_INET6); +} + +static int cidr_is4(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + + lua_pushboolean(L, p->family == AF_INET); + return 1; +} + +static int cidr_is4rfc1918(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + uint32_t a = htonl(p->addr.v4.s_addr); + + lua_pushboolean(L, (p->family == AF_INET && + ((a >= 0x0A000000 && a <= 0x0AFFFFFF) || + (a >= 0xAC100000 && a <= 0xAC1FFFFF) || + (a >= 0xC0A80000 && a <= 0xC0A8FFFF)))); + + return 1; +} + +static int cidr_is4linklocal(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + uint32_t a = htonl(p->addr.v4.s_addr); + + lua_pushboolean(L, (p->family == AF_INET && + a >= 0xA9FE0000 && + a <= 0xA9FEFFFF)); + + return 1; +} + +static int cidr_is6(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + + lua_pushboolean(L, p->family == AF_INET6); + return 1; +} + +static int cidr_is6linklocal(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + + lua_pushboolean(L, (p->family == AF_INET6 && + p->addr.v6.s6_addr[0] == 0xFE && + p->addr.v6.s6_addr[1] >= 0x80 && + p->addr.v6.s6_addr[1] <= 0xBF)); + + return 1; +} + +static int _cidr_cmp(lua_State *L) +{ + cidr_t *a = L_checkcidr(L, 1, NULL); + cidr_t *b = L_checkcidr(L, 2, NULL); + + if (a->family != b->family) + return (a->family - b->family); + + return memcmp(&a->addr.v6, &b->addr.v6, a->len); +} + +static int cidr_lower(lua_State *L) +{ + lua_pushboolean(L, _cidr_cmp(L) < 0); + return 1; +} + +static int cidr_higher(lua_State *L) +{ + lua_pushboolean(L, _cidr_cmp(L) > 0); + return 1; +} + +static int cidr_equal(lua_State *L) +{ + lua_pushboolean(L, _cidr_cmp(L) == 0); + return 1; +} + +static int cidr_lower_equal(lua_State *L) +{ + lua_pushboolean(L, _cidr_cmp(L) <= 0); + return 1; +} + +static int cidr_prefix(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + int bits = L_checkbits(L, 2, p); + + p->bits = bits; + lua_pushinteger(L, p->bits); + return 1; +} + +static void _apply_mask(cidr_t *p, int bits, bool inv) +{ + uint8_t b, i; + + if (bits <= 0) + { + memset(&p->addr.v6, inv * 0xFF, p->len); + } + else if (p->family == AF_INET && bits <= 32) + { + if (inv) + p->addr.v4.s_addr |= ntohl((1 << (32 - bits)) - 1); + else + p->addr.v4.s_addr &= ntohl(~((1 << (32 - bits)) - 1)); + } + else if (p->family == AF_INET6 && bits <= 128) + { + for (i = 0; i < sizeof(p->addr.v6.s6_addr); i++) + { + b = (bits > 8) ? 8 : bits; + if (inv) + p->addr.v6.s6_addr[i] |= ~((uint8_t)(0xFF << (8 - b))); + else + p->addr.v6.s6_addr[i] &= (uint8_t)(0xFF << (8 - b)); + bits -= b; + } + } +} + +static int cidr_network(lua_State *L) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL), *p2; + int bits = L_checkbits(L, 2, p1); + + if (!(p2 = lua_newuserdata(L, sizeof(*p2)))) + return 0; + + *p2 = *p1; + p2->bits = (p1->family == AF_INET) ? 32 : 128; + _apply_mask(p2, bits, false); + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_host(lua_State *L) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL); + cidr_t *p2 = lua_newuserdata(L, sizeof(*p2)); + + if (!p2) + return 0; + + *p2 = *p1; + p2->bits = (p1->family == AF_INET) ? 32 : 128; + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_mask(lua_State *L) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL), *p2; + int bits = L_checkbits(L, 2, p1); + + if (!(p2 = lua_newuserdata(L, sizeof(*p2)))) + return 0; + + p2->bits = (p1->family == AF_INET) ? 32 : 128; + p2->family = p1->family; + + memset(&p2->addr.v6.s6_addr, 0xFF, sizeof(p2->addr.v6.s6_addr)); + _apply_mask(p2, bits, false); + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_broadcast(lua_State *L) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL); + cidr_t *p2; + int bits = L_checkbits(L, 2, p1); + + if (p1->family == AF_INET6) + return 0; + + if (!(p2 = lua_newuserdata(L, sizeof(*p2)))) + return 0; + + *p2 = *p1; + p2->bits = (p1->family == AF_INET) ? 32 : 128; + _apply_mask(p2, bits, true); + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_contains(lua_State *L) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL); + cidr_t *p2 = L_checkcidr(L, 2, NULL); + cidr_t a = *p1, b = *p2; + bool rv = false; + + if (p1->family == p2->family && p1->bits <= p2->bits) + { + _apply_mask(&a, p1->bits, false); + _apply_mask(&b, p1->bits, false); + + rv = !memcmp(&a.addr.v6, &b.addr.v6, a.len); + } + + lua_pushboolean(L, rv); + return 1; +} + +#define S6_BYTE(a, i) \ + (a)->addr.v6.s6_addr[sizeof((a)->addr.v6.s6_addr) - (i) - 1] + +static int _cidr_add_sub(lua_State *L, bool add) +{ + cidr_t *p1 = L_checkcidr(L, 1, NULL); + cidr_t *p2 = L_checkcidr(L, 2, p1); + cidr_t r = *p1; + bool inplace = lua_isboolean(L, 3) ? lua_toboolean(L, 3) : false; + bool ok = true; + uint8_t i, carry; + uint32_t a, b; + + if (p1->family == p2->family) + { + if (p1->family == AF_INET6) + { + for (i = 0, carry = 0; i < sizeof(r); i++) + { + if (add) + { + S6_BYTE(&r, i) = S6_BYTE(p1, i) + S6_BYTE(p2, i) + carry; + carry = (S6_BYTE(p1, i) + S6_BYTE(p2, i) + carry) / 256; + } + else + { + S6_BYTE(&r, i) = (S6_BYTE(p1, i) - S6_BYTE(p2, i) - carry); + carry = (S6_BYTE(p1, i) < (S6_BYTE(p2, i) + carry)); + } + } + + /* would over/underflow */ + if (carry) + { + memset(&r.addr.v6, add * 0xFF, sizeof(r.addr.v6)); + ok = false; + } + } + else + { + a = ntohl(p1->addr.v4.s_addr); + b = ntohl(p2->addr.v4.s_addr); + + /* would over/underflow */ + if ((add && (UINT_MAX - a) < b) || (!add && a < b)) + { + r.addr.v4.s_addr = add * 0xFFFFFFFF; + ok = false; + } + else + { + r.addr.v4.s_addr = add ? htonl(a + b) : htonl(a - b); + } + } + } + else + { + ok = false; + } + + if (inplace) + { + *p1 = r; + lua_pushboolean(L, ok); + return 1; + } + + if (!(p1 = lua_newuserdata(L, sizeof(*p1)))) + return 0; + + *p1 = r; + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_add(lua_State *L) +{ + return _cidr_add_sub(L, true); +} + +static int cidr_sub(lua_State *L) +{ + return _cidr_add_sub(L, false); +} + +static int cidr_minhost(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + cidr_t r = *p; + uint8_t i, rest, carry; + + _apply_mask(&r, r.bits, false); + + if (r.family == AF_INET6 && r.bits < 128) + { + r.bits = 128; + + for (i = 0, carry = 1; i < sizeof(r.addr.v6.s6_addr); i++) + { + rest = (S6_BYTE(&r, i) + carry) > 255; + S6_BYTE(&r, i) += carry; + carry = rest; + } + } + else if (r.family == AF_INET && r.bits < 32) + { + r.bits = 32; + r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) + 1); + } + + if (!(p = lua_newuserdata(L, sizeof(*p)))) + return 0; + + *p = r; + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_maxhost(lua_State *L) +{ + cidr_t *p = L_checkcidr(L, 1, NULL); + cidr_t r = *p; + + _apply_mask(&r, r.bits, true); + + if (r.family == AF_INET && r.bits < 32) + { + r.bits = 32; + r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) - 1); + } + else if (r.family == AF_INET6) + { + r.bits = 128; + } + + if (!(p = lua_newuserdata(L, sizeof(*p)))) + return 0; + + *p = r; + + luaL_getmetatable(L, LUCI_IP_CIDR); + lua_setmetatable(L, -2); + return 1; +} + +static int cidr_gc (lua_State *L) +{ + return 0; +} + +static int cidr_tostring (lua_State *L) +{ + char buf[INET6_ADDRSTRLEN]; + cidr_t *p = L_checkcidr(L, 1, NULL); + + if ((p->family == AF_INET && p->bits < 32) || + (p->family == AF_INET6 && p->bits < 128)) + { + lua_pushfstring(L, "%s/%d", + inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf)), + p->bits); + } + else + { + lua_pushstring(L, inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf))); + } + + return 1; +} + +/* + * route functions + */ + +static bool diff_prefix(int family, void *addr, int bits, cidr_t *p) +{ + uint8_t i, b, r; + uint32_t m; + + if (!p->family) + return false; + + if (!addr || p->family != family || p->bits > bits) + return true; + + if (family == AF_INET6) + { + for (i = 0, r = p->bits; i < sizeof(struct in6_addr); i++) + { + b = r ? (0xFF << (8 - ((r > 8) ? 8 : r))) : 0; + + if ((((struct in6_addr *)addr)->s6_addr[i] & b) != + (p->addr.v6.s6_addr[i] & b)) + return true; + + r -= ((r > 8) ? 8 : r); + } + } + else + { + m = p->bits ? htonl(~((1 << (32 - p->bits)) - 1)) : 0; + + if ((((struct in_addr *)addr)->s_addr & m) != (p->addr.v4.s_addr & m)) + return true; + } + + return (p->exact && p->bits != bits); +} + +static int cb_dump_route(struct nl_msg *msg, void *arg) +{ + struct dump_state *s = arg; + struct dump_filter *f = s->filter; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct rtmsg *rt = NLMSG_DATA(hdr); + struct nlattr *tb[RTA_MAX+1]; + struct in6_addr *src, *dst, *gw, *from, def = { }; + int iif, oif, bitlen; + uint32_t table; + + if (hdr->nlmsg_type != RTM_NEWROUTE || + (rt->rtm_family != AF_INET && rt->rtm_family != AF_INET6)) + return NL_SKIP; + + nlmsg_parse(hdr, sizeof(*rt), tb, RTA_MAX, NULL); + + iif = tb[RTA_IIF] ? RTA_INT(tb[RTA_IIF]) : 0; + oif = tb[RTA_OIF] ? RTA_INT(tb[RTA_OIF]) : 0; + table = tb[RTA_TABLE] ? RTA_U32(tb[RTA_TABLE]) : rt->rtm_table; + from = tb[RTA_SRC] ? RTA_DATA(tb[RTA_SRC]) : NULL; + src = tb[RTA_PREFSRC] ? RTA_DATA(tb[RTA_PREFSRC]) : NULL; + dst = tb[RTA_DST] ? RTA_DATA(tb[RTA_DST]) : &def; + gw = tb[RTA_GATEWAY] ? RTA_DATA(tb[RTA_GATEWAY]) : NULL; + + bitlen = (rt->rtm_family == AF_INET6) ? 128 : 32; + + if ((f->type && rt->rtm_type != f->type) || + (f->family && rt->rtm_family != f->family) || + (f->proto && rt->rtm_protocol != f->proto) || + (f->scope && rt->rtm_scope != f->scope) || + (f->iif && iif != f->iif) || + (f->oif && oif != f->oif) || + (f->table && table != f->table) || + diff_prefix(rt->rtm_family, from, rt->rtm_src_len, &f->from) || + diff_prefix(rt->rtm_family, dst, rt->rtm_dst_len, &f->dst) || + diff_prefix(rt->rtm_family, gw, bitlen, &f->gw) || + diff_prefix(rt->rtm_family, src, bitlen, &f->src)) + goto out; + + if (s->callback) + lua_pushvalue(s->L, 2); + + lua_newtable(s->L); + + L_setint(s->L, "type", rt->rtm_type); + L_setint(s->L, "family", (rt->rtm_family == AF_INET) ? 4 : 6); + + L_setaddr(s->L, "dest", rt->rtm_family, dst, rt->rtm_dst_len); + + if (gw) + L_setaddr(s->L, "gw", rt->rtm_family, gw, -1); + + if (from) + L_setaddr(s->L, "from", rt->rtm_family, from, rt->rtm_src_len); + + if (iif) + L_setdev(s->L, "iif", tb[RTA_IIF]); + + if (oif) + L_setdev(s->L, "dev", tb[RTA_OIF]); + + L_setint(s->L, "table", table); + L_setint(s->L, "proto", rt->rtm_protocol); + L_setint(s->L, "scope", rt->rtm_scope); + + if (src) + L_setaddr(s->L, "src", rt->rtm_family, src, -1); + + if (tb[RTA_PRIORITY]) + L_setint(s->L, "metric", RTA_U32(tb[RTA_PRIORITY])); + + if (rt->rtm_family == AF_INET6 && tb[RTA_CACHEINFO]) + { + struct rta_cacheinfo *ci = RTA_DATA(tb[RTA_CACHEINFO]); + + if (ci->rta_expires) + { + if (ci->rta_expires) + L_setint(s->L, "expires", ci->rta_expires / hz); + + if (ci->rta_error != 0) + L_setint(s->L, "error", ci->rta_error); + } + } + + s->index++; + + if (s->callback) + lua_call(s->L, 1, 0); + else if (hdr->nlmsg_flags & NLM_F_MULTI) + lua_rawseti(s->L, 3, s->index); + +out: + s->pending = !!(hdr->nlmsg_flags & NLM_F_MULTI); + return NL_SKIP; +} + +static int +cb_done(struct nl_msg *msg, void *arg) +{ + struct dump_state *s = arg; + s->pending = 0; + return NL_STOP; +} + +static int +cb_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + struct dump_state *s = arg; + s->pending = 0; + return NL_STOP; +} + +static int _error(lua_State *L, int code, const char *msg) +{ + lua_pushnil(L); + lua_pushnumber(L, code ? code : errno); + lua_pushstring(L, msg ? msg : strerror(errno)); + + return 3; +} + +static int _route_dump(lua_State *L, struct dump_filter *filter) +{ + int flags = NLM_F_REQUEST; + struct dump_state s = { + .L = L, + .pending = 1, + .index = 0, + .callback = lua_isfunction(L, 2), + .filter = filter + }; + + if (!hz) + hz = sysconf(_SC_CLK_TCK); + + if (!sock) + { + sock = nl_socket_alloc(); + if (!sock) + return _error(L, -1, "Out of memory"); + + if (nl_connect(sock, NETLINK_ROUTE)) + return _error(L, 0, NULL); + } + + struct nl_msg *msg; + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct rtmsg rtm = { + .rtm_family = filter->family, + .rtm_dst_len = filter->dst.bits, + .rtm_src_len = filter->src.bits + }; + + if (!filter->get) + flags |= NLM_F_DUMP; + + msg = nlmsg_alloc_simple(RTM_GETROUTE, flags); + if (!msg) + goto out; + + nlmsg_append(msg, &rtm, sizeof(rtm), 0); + + if (filter->get) + nla_put(msg, RTA_DST, filter->dst.len, &filter->dst.addr.v6); + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_dump_route, &s); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &s); + nl_cb_err(cb, NL_CB_CUSTOM, cb_error, &s); + + nl_send_auto_complete(sock, msg); + + if (!filter->get && !s.callback) + lua_newtable(L); + + while (s.pending > 0) + nl_recvmsgs(sock, cb); + + nlmsg_free(msg); + +out: + nl_cb_put(cb); + return (s.index > 0 && s.callback == 0); +} + +static int route_get(lua_State *L) +{ + struct dump_filter filter = { .get = true }; + const char *dest = luaL_checkstring(L, 1); + + if (!parse_cidr(dest, &filter.dst)) + return _error(L, -1, "Invalid destination"); + + filter.family = filter.dst.family; + + return _route_dump(L, &filter); +} + +static int route_dump(lua_State *L) +{ + const char *s; + cidr_t p = { }; + struct dump_filter filter = { }; + + if (lua_type(L, 1) == LUA_TTABLE) + { + filter.family = L_getint(L, 1, "family"); + + if (filter.family == 4) + filter.family = AF_INET; + else if (filter.family == 6) + filter.family = AF_INET6; + else + filter.family = 0; + + if ((s = L_getstr(L, 1, "iif")) != NULL) + filter.iif = if_nametoindex(s); + + if ((s = L_getstr(L, 1, "oif")) != NULL) + filter.oif = if_nametoindex(s); + + filter.type = L_getint(L, 1, "type"); + filter.scope = L_getint(L, 1, "scope"); + filter.proto = L_getint(L, 1, "proto"); + filter.table = L_getint(L, 1, "table"); + + if ((s = L_getstr(L, 1, "gw")) != NULL && parse_cidr(s, &p)) + filter.gw = p; + + if ((s = L_getstr(L, 1, "from")) != NULL && parse_cidr(s, &p)) + filter.from = p; + + if ((s = L_getstr(L, 1, "src")) != NULL && parse_cidr(s, &p)) + filter.src = p; + + if ((s = L_getstr(L, 1, "dest")) != NULL && parse_cidr(s, &p)) + filter.dst = p; + + if ((s = L_getstr(L, 1, "from_exact")) != NULL && parse_cidr(s, &p)) + filter.from = p, filter.from.exact = true; + + if ((s = L_getstr(L, 1, "dest_exact")) != NULL && parse_cidr(s, &p)) + filter.dst = p, filter.dst.exact = true; + } + + return _route_dump(L, &filter); +} + + +static int cb_dump_neigh(struct nl_msg *msg, void *arg) +{ + char buf[32]; + struct ether_addr *addr; + struct dump_state *s = arg; + struct dump_filter *f = s->filter; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct ndmsg *nd = NLMSG_DATA(hdr); + struct nlattr *tb[NDA_MAX+1]; + + if (hdr->nlmsg_type != RTM_NEWNEIGH || + (nd->ndm_family != AF_INET && nd->ndm_family != AF_INET6)) + return NL_SKIP; + + if ((f->family && nd->ndm_family != f->family) || + (f->iif && nd->ndm_ifindex != f->iif) || + (f->type && !(f->type & nd->ndm_state))) + goto out; + + nlmsg_parse(hdr, sizeof(*nd), tb, NDA_MAX, NULL); + + if (s->callback) + lua_pushvalue(s->L, 2); + + lua_newtable(s->L); + + L_setint(s->L, "family", (nd->ndm_family == AF_INET) ? 4 : 6); + L_setstr(s->L, "dev", if_indextoname(nd->ndm_ifindex, buf)); + + L_setbool(s->L, "router", (nd->ndm_flags & NTF_ROUTER)); + L_setbool(s->L, "proxy", (nd->ndm_flags & NTF_PROXY)); + + L_setbool(s->L, "incomplete", (nd->ndm_state & NUD_INCOMPLETE)); + L_setbool(s->L, "reachable", (nd->ndm_state & NUD_REACHABLE)); + L_setbool(s->L, "stale", (nd->ndm_state & NUD_STALE)); + L_setbool(s->L, "delay", (nd->ndm_state & NUD_DELAY)); + L_setbool(s->L, "probe", (nd->ndm_state & NUD_PROBE)); + L_setbool(s->L, "failed", (nd->ndm_state & NUD_FAILED)); + L_setbool(s->L, "noarp", (nd->ndm_state & NUD_NOARP)); + L_setbool(s->L, "permanent", (nd->ndm_state & NUD_PERMANENT)); + + if (tb[NDA_DST]) + L_setaddr(s->L, "dest", nd->ndm_family, RTA_DATA(tb[NDA_DST]), -1); + + if (tb[NDA_LLADDR]) + { + addr = RTA_DATA(tb[NDA_LLADDR]); + snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", + addr->ether_addr_octet[0], addr->ether_addr_octet[1], + addr->ether_addr_octet[2], addr->ether_addr_octet[3], + addr->ether_addr_octet[4], addr->ether_addr_octet[5]); + + lua_pushstring(s->L, buf); + lua_setfield(s->L, -2, "mac"); + } + + s->index++; + + if (s->callback) + lua_call(s->L, 1, 0); + else if (hdr->nlmsg_flags & NLM_F_MULTI) + lua_rawseti(s->L, -2, s->index); + +out: + s->pending = !!(hdr->nlmsg_flags & NLM_F_MULTI); + return NL_SKIP; +} + +static int neighbor_dump(lua_State *L) +{ + const char *s; + struct dump_filter filter = { + .type = 0xFF & ~NUD_NOARP + }; + struct dump_state st = { + .callback = lua_isfunction(L, 2), + .pending = 1, + .filter = &filter, + .L = L + }; + + if (lua_type(L, 1) == LUA_TTABLE) + { + filter.family = L_getint(L, 1, "family"); + + if (filter.family == 4) + filter.family = AF_INET; + else if (filter.family == 6) + filter.family = AF_INET6; + else + filter.family = 0; + + if ((s = L_getstr(L, 1, "dev")) != NULL) + filter.iif = if_nametoindex(s); + } + + if (!sock) + { + sock = nl_socket_alloc(); + if (!sock) + return _error(L, -1, "Out of memory"); + + if (nl_connect(sock, NETLINK_ROUTE)) + return _error(L, 0, NULL); + } + + struct nl_msg *msg; + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct ndmsg ndm = { + .ndm_family = filter.family + }; + + msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP); + if (!msg) + goto out; + + nlmsg_append(msg, &ndm, sizeof(ndm), 0); + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_dump_neigh, &st); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &st); + nl_cb_err(cb, NL_CB_CUSTOM, cb_error, &st); + + nl_send_auto_complete(sock, msg); + + if (!st.callback) + lua_newtable(L); + + while (st.pending > 0) + nl_recvmsgs(sock, cb); + + nlmsg_free(msg); + +out: + nl_cb_put(cb); + return (st.index > 0 && st.callback == 0); +} + + +static const luaL_reg ip_methods[] = { + { "new", cidr_new }, + { "IPv4", cidr_ipv4 }, + { "IPv6", cidr_ipv6 }, + + { "route", route_get }, + { "routes", route_dump }, + + { "neighbors", neighbor_dump }, + + { } +}; + +static const luaL_reg ip_cidr_methods[] = { + { "is4", cidr_is4 }, + { "is4rfc1918", cidr_is4rfc1918 }, + { "is4linklocal", cidr_is4linklocal }, + { "is6", cidr_is6 }, + { "is6linklocal", cidr_is6linklocal }, + { "lower", cidr_lower }, + { "higher", cidr_higher }, + { "equal", cidr_equal }, + { "prefix", cidr_prefix }, + { "network", cidr_network }, + { "host", cidr_host }, + { "mask", cidr_mask }, + { "broadcast", cidr_broadcast }, + { "contains", cidr_contains }, + { "add", cidr_add }, + { "sub", cidr_sub }, + { "minhost", cidr_minhost }, + { "maxhost", cidr_maxhost }, + { "string", cidr_tostring }, + + { "__lt", cidr_lower }, + { "__le", cidr_lower_equal }, + { "__eq", cidr_equal }, + { "__add", cidr_add }, + { "__sub", cidr_sub }, + { "__gc", cidr_gc }, + { "__tostring", cidr_tostring }, + + { } +}; + +int luaopen_luci_ip(lua_State *L) +{ + luaL_register(L, LUCI_IP, ip_methods); + + luaL_newmetatable(L, LUCI_IP_CIDR); + luaL_register(L, NULL, ip_cidr_methods); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); + + return 1; +}