Merge pull request #1654 from TDT-AG/pr/20180301-luci-several-fixes
[project/luci.git] / libs / luci-lib-ip / src / ip.c
index 403ffbd..854a0c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
+Copyright 2015-2018 Jo-Philipp Wich <jo@mein.io>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -42,6 +42,16 @@ limitations under the License.
 #define RTA_INT(x)     (*(int *)RTA_DATA(x))
 #define RTA_U32(x)     (*(uint32_t *)RTA_DATA(x))
 
+#define AF_BITS(f) \
+       ((f) == AF_INET ? 32 : \
+               ((f) == AF_INET6 ? 128 : \
+                       ((f) == AF_PACKET ? 48 : 0)))
+
+#define AF_BYTES(f) \
+       ((f) == AF_INET ? 4 : \
+               ((f) == AF_INET6 ? 16 : \
+                       ((f) == AF_PACKET ? 6 : 0)))
+
 static int hz = 0;
 static struct nl_sock *sock = NULL;
 
@@ -49,11 +59,11 @@ typedef struct {
        union {
                struct in_addr v4;
                struct in6_addr v6;
+               struct ether_addr mac;
+               uint8_t u8[16];
        } addr;
-       int len;
-       int bits;
-       int family;
-       bool exact;
+       uint16_t family;
+       int16_t bits;
 } cidr_t;
 
 struct dump_filter {
@@ -69,6 +79,9 @@ struct dump_filter {
        cidr_t from;
        cidr_t src;
        cidr_t dst;
+       struct ether_addr mac;
+       bool from_exact;
+       bool dst_exact;
 };
 
 struct dump_state {
@@ -80,43 +93,82 @@ struct dump_state {
 };
 
 
-static int _cidr_new(lua_State *L, int index, int family);
+static int _cidr_new(lua_State *L, int index, int family, bool mask);
 
 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))
+       if (_cidr_new(L, index, p ? p->family : 0, false))
                return lua_touserdata(L, -1);
 
        luaL_error(L, "Invalid operand");
        return NULL;
 }
 
-static bool parse_mask(int family, const char *mask, int *bits)
+static bool parse_mac(const char *mac, struct ether_addr *ea)
+{
+       unsigned long int n;
+       char *e, sep = 0;
+       int i;
+
+       for (i = 0; i < 6; i++)
+       {
+               if (i > 0)
+               {
+                       if (sep == 0 && (mac[0] == ':' || mac[0] == '-'))
+                               sep = mac[0];
+
+                       if (sep == 0 || mac[0] != sep)
+                               return false;
+
+                       mac++;
+               }
+
+               n = strtoul(mac, &e, 16);
+
+               if (n > 0xFF)
+                       return false;
+
+               mac += (e - mac);
+               ea->ether_addr_octet[i] = n;
+       }
+
+       if (mac[0] != 0)
+               return false;
+
+       return true;
+}
+
+static bool parse_mask(int family, const char *mask, int16_t *bits)
 {
        char *e;
-       struct in_addr m;
-       struct in6_addr m6;
+       union {
+               struct in_addr v4;
+               struct in6_addr v6;
+               struct ether_addr mac;
+               uint8_t u8[16];
+       } m;
 
-       if (family == AF_INET && inet_pton(AF_INET, mask, &m))
+       if (family == AF_INET && inet_pton(AF_INET, mask, &m.v4))
        {
-               for (*bits = 0, m.s_addr = ntohl(m.s_addr);
-                        *bits < 32 && (m.s_addr << *bits) & 0x80000000;
+               for (*bits = 0, m.v4.s_addr = ntohl(m.v4.s_addr);
+                        *bits < AF_BITS(AF_INET) && (m.v4.s_addr << *bits) & 0x80000000;
                         ++*bits);
        }
-       else if (family == AF_INET6 && inet_pton(AF_INET6, mask, &m6))
+       else if ((family == AF_INET6 && inet_pton(AF_INET6, mask, &m.v6)) ||
+                (family == AF_PACKET && parse_mac(mask, &m.mac)))
        {
                for (*bits = 0;
-                        *bits < 128 && (m6.s6_addr[*bits / 8] << (*bits % 8)) & 128;
+                        *bits < AF_BITS(family) && (m.u8[*bits / 8] << (*bits % 8)) & 128;
                         ++*bits);
        }
        else
        {
                *bits = strtoul(mask, &e, 10);
 
-               if (e == mask || *e != 0 || *bits > ((family == AF_INET) ? 32 : 128))
+               if (e == mask || *e != 0 || *bits > AF_BITS(family))
                        return false;
        }
 
@@ -125,32 +177,21 @@ static bool parse_mask(int family, const char *mask, int *bits)
 
 static bool parse_cidr(const char *dest, cidr_t *pp)
 {
-       char *p, *a, buf[INET6_ADDRSTRLEN * 2 + 2];
-       uint8_t bitlen = 0;
+       char *p, buf[INET6_ADDRSTRLEN * 2 + 2];
 
        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;
+       if (inet_pton(AF_INET, buf, &pp->addr.v4))
                pp->family = AF_INET;
-               pp->len = sizeof(struct in_addr);
-       }
-       else if (inet_pton(AF_INET6, a, &pp->addr.v6))
-       {
-               bitlen = 128;
+       else if (inet_pton(AF_INET6, buf, &pp->addr.v6))
                pp->family = AF_INET6;
-               pp->len = sizeof(struct in6_addr);
-       }
+       else if (parse_mac(buf, &pp->addr.mac))
+               pp->family = AF_PACKET;
        else
                return false;
 
@@ -161,12 +202,45 @@ static bool parse_cidr(const char *dest, cidr_t *pp)
        }
        else
        {
-               pp->bits = bitlen;
+               pp->bits = AF_BITS(pp->family);
        }
 
        return true;
 }
 
+static int format_cidr(lua_State *L, cidr_t *p)
+{
+       char buf[INET6_ADDRSTRLEN];
+
+       if (p->family == AF_PACKET)
+       {
+               snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X",
+                        p->addr.mac.ether_addr_octet[0],
+                        p->addr.mac.ether_addr_octet[1],
+                        p->addr.mac.ether_addr_octet[2],
+                        p->addr.mac.ether_addr_octet[3],
+                        p->addr.mac.ether_addr_octet[4],
+                        p->addr.mac.ether_addr_octet[5]);
+
+               if (p->bits < AF_BITS(AF_PACKET))
+                       lua_pushfstring(L, "%s/%d", buf, p->bits);
+               else
+                       lua_pushstring(L, buf);
+       }
+       else
+       {
+               if (p->bits < AF_BITS(p->family))
+                       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;
+}
+
 static int L_getint(lua_State *L, int index, const char *name)
 {
        int rv = 0;
@@ -223,17 +297,21 @@ static void L_setaddr(struct lua_State *L, const char *name,
        if (family == AF_INET)
        {
                p->family = AF_INET;
-               p->bits = (bits < 0) ? 32 : bits;
-               p->len = sizeof(p->addr.v4);
+               p->bits = (bits < 0) ? AF_BITS(AF_INET) : bits;
                p->addr.v4 = *(struct in_addr *)addr;
        }
-       else
+       else if (family == AF_INET6)
        {
                p->family = AF_INET6;
-               p->bits = (bits < 0) ? 128 : bits;
-               p->len = sizeof(p->addr.v6);
+               p->bits = (bits < 0) ? AF_BITS(AF_INET6) : bits;
                p->addr.v6 = *(struct in6_addr *)addr;
        }
+       else
+       {
+               p->family = AF_PACKET;
+               p->bits = (bits < 0) ? AF_BITS(AF_PACKET) : bits;
+               p->addr.mac = *(struct ether_addr *)addr;
+       }
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
        lua_setmetatable(L, -2);
@@ -257,6 +335,7 @@ static void L_setdev(struct lua_State *L, const char *name,
 
 static int L_checkbits(lua_State *L, int index, cidr_t *p)
 {
+       int16_t s16;
        int bits;
 
        if (lua_gettop(L) < index || lua_isnil(L, index))
@@ -267,13 +346,15 @@ static int L_checkbits(lua_State *L, int index, cidr_t *p)
        {
                bits = lua_tointeger(L, index);
 
-               if (bits < 0 || bits > ((p->family == AF_INET) ? 32 : 128))
+               if (bits < 0 || bits > AF_BITS(p->family))
                        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))
+               if (!parse_mask(p->family, lua_tostring(L, index), &s16))
                        return luaL_error(L, "Invalid netmask format");
+
+               bits = s16;
        }
        else
        {
@@ -283,7 +364,7 @@ static int L_checkbits(lua_State *L, int index, cidr_t *p)
        return bits;
 }
 
-static int _cidr_new(lua_State *L, int index, int family)
+static int _cidr_new(lua_State *L, int index, int family, bool mask)
 {
        uint32_t n;
        const char *addr;
@@ -296,20 +377,26 @@ static int _cidr_new(lua_State *L, int index, int family)
                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);
+                       cidr.addr.v6.s6_addr[12] = n;
+                       cidr.addr.v6.s6_addr[13] = (n >> 8);
+                       cidr.addr.v6.s6_addr[14] = (n >> 16);
+                       cidr.addr.v6.s6_addr[15] = (n >> 24);
                }
-               else
+               else if (family == AF_INET)
                {
                        cidr.family = AF_INET;
-                       cidr.bits = 32;
-                       cidr.len = sizeof(cidr.addr.v4);
                        cidr.addr.v4.s_addr = n;
                }
+               else
+               {
+                       cidr.family = AF_PACKET;
+                       cidr.addr.mac.ether_addr_octet[2] = n;
+                       cidr.addr.mac.ether_addr_octet[3] = (n >> 8);
+                       cidr.addr.mac.ether_addr_octet[4] = (n >> 16);
+                       cidr.addr.mac.ether_addr_octet[5] = (n >> 24);
+               }
+
+               cidr.bits = AF_BITS(cidr.family);
        }
        else
        {
@@ -320,6 +407,9 @@ static int _cidr_new(lua_State *L, int index, int family)
 
                if (family && cidr.family != family)
                        return 0;
+
+               if (mask)
+                       cidr.bits = L_checkbits(L, index + 1, &cidr);
        }
 
        if (!(cidrp = lua_newuserdata(L, sizeof(*cidrp))))
@@ -333,17 +423,73 @@ static int _cidr_new(lua_State *L, int index, int family)
 
 static int cidr_new(lua_State *L)
 {
-       return _cidr_new(L, 1, 0);
+       return _cidr_new(L, 1, 0, true);
 }
 
 static int cidr_ipv4(lua_State *L)
 {
-       return _cidr_new(L, 1, AF_INET);
+       return _cidr_new(L, 1, AF_INET, true);
 }
 
 static int cidr_ipv6(lua_State *L)
 {
-       return _cidr_new(L, 1, AF_INET6);
+       return _cidr_new(L, 1, AF_INET6, true);
+}
+
+static int cidr_mac(lua_State *L)
+{
+       return _cidr_new(L, 1, AF_PACKET, true);
+}
+
+static int cidr_check(lua_State *L, int family)
+{
+       cidr_t cidr = { }, *cidrp;
+       const char *addr;
+
+       if (lua_type(L, 1) == LUA_TSTRING)
+       {
+               addr = lua_tostring(L, 1);
+
+               if (addr && parse_cidr(addr, &cidr) && cidr.family == family)
+                       return format_cidr(L, &cidr);
+       }
+       else
+       {
+               cidrp = lua_touserdata(L, 1);
+
+               if (cidrp == NULL)
+                       return 0;
+
+               if (!lua_getmetatable(L, 1))
+                       return 0;
+
+               lua_getfield(L, LUA_REGISTRYINDEX, LUCI_IP_CIDR);
+
+               if (!lua_rawequal(L, -1, -2))
+                       cidrp = NULL;
+
+               lua_pop(L, 2);
+
+               if (cidrp != NULL && cidrp->family == family)
+                       return format_cidr(L, cidrp);
+       }
+
+       return 0;
+}
+
+static int cidr_checkip4(lua_State *L)
+{
+       return cidr_check(L, AF_INET);
+}
+
+static int cidr_checkip6(lua_State *L)
+{
+       return cidr_check(L, AF_INET6);
+}
+
+static int cidr_checkmac(lua_State *L)
+{
+       return cidr_check(L, AF_PACKET);
 }
 
 static int cidr_is4(lua_State *L)
@@ -379,6 +525,31 @@ static int cidr_is4linklocal(lua_State *L)
        return 1;
 }
 
+static bool _is_mapped4(cidr_t *p)
+{
+       return (p->family == AF_INET6 &&
+               p->addr.v6.s6_addr[0] == 0 &&
+               p->addr.v6.s6_addr[1] == 0 &&
+               p->addr.v6.s6_addr[2] == 0 &&
+               p->addr.v6.s6_addr[3] == 0 &&
+               p->addr.v6.s6_addr[4] == 0 &&
+               p->addr.v6.s6_addr[5] == 0 &&
+               p->addr.v6.s6_addr[6] == 0 &&
+               p->addr.v6.s6_addr[7] == 0 &&
+               p->addr.v6.s6_addr[8] == 0 &&
+               p->addr.v6.s6_addr[9] == 0 &&
+               p->addr.v6.s6_addr[10] == 0xFF &&
+               p->addr.v6.s6_addr[11] == 0xFF);
+}
+
+static int cidr_is6mapped4(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, _is_mapped4(p));
+       return 1;
+}
+
 static int cidr_is6(lua_State *L)
 {
        cidr_t *p = L_checkcidr(L, 1, NULL);
@@ -399,6 +570,34 @@ static int cidr_is6linklocal(lua_State *L)
        return 1;
 }
 
+static int cidr_ismac(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, p->family == AF_PACKET);
+       return 1;
+}
+
+static int cidr_ismacmcast(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, (p->family == AF_PACKET &&
+                           (p->addr.mac.ether_addr_octet[0] & 0x1)));
+
+       return 1;
+}
+
+static int cidr_ismaclocal(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, (p->family == AF_PACKET &&
+                           (p->addr.mac.ether_addr_octet[0] & 0x2)));
+
+       return 1;
+}
+
 static int _cidr_cmp(lua_State *L)
 {
        cidr_t *a = L_checkcidr(L, 1, NULL);
@@ -407,7 +606,7 @@ static int _cidr_cmp(lua_State *L)
        if (a->family != b->family)
                return (a->family - b->family);
 
-       return memcmp(&a->addr.v6, &b->addr.v6, a->len);
+       return memcmp(&a->addr.v6, &b->addr.v6, AF_BYTES(a->family));
 }
 
 static int cidr_lower(lua_State *L)
@@ -450,24 +649,24 @@ static void _apply_mask(cidr_t *p, int bits, bool inv)
 
        if (bits <= 0)
        {
-               memset(&p->addr.v6, inv * 0xFF, p->len);
+               memset(&p->addr.u8, inv * 0xFF, AF_BYTES(p->family));
        }
-       else if (p->family == AF_INET && bits <= 32)
+       else if (p->family == AF_INET && bits <= AF_BITS(AF_INET))
        {
                if (inv)
-                       p->addr.v4.s_addr |= ntohl((1 << (32 - bits)) - 1);
+                       p->addr.v4.s_addr |= ntohl((1 << (AF_BITS(AF_INET) - bits)) - 1);
                else
-                       p->addr.v4.s_addr &= ntohl(~((1 << (32 - bits)) - 1));
+                       p->addr.v4.s_addr &= ntohl(~((1 << (AF_BITS(AF_INET) - bits)) - 1));
        }
-       else if (p->family == AF_INET6 && bits <= 128)
+       else if (bits <= AF_BITS(p->family))
        {
-               for (i = 0; i < sizeof(p->addr.v6.s6_addr); i++)
+               for (i = 0; i < AF_BYTES(p->family); i++)
                {
                        b = (bits > 8) ? 8 : bits;
                        if (inv)
-                               p->addr.v6.s6_addr[i] |= ~((uint8_t)(0xFF << (8 - b)));
+                               p->addr.u8[i] |= ~((uint8_t)(0xFF << (8 - b)));
                        else
-                               p->addr.v6.s6_addr[i] &= (uint8_t)(0xFF << (8 - b));
+                               p->addr.u8[i] &= (uint8_t)(0xFF << (8 - b));
                        bits -= b;
                }
        }
@@ -482,7 +681,7 @@ static int cidr_network(lua_State *L)
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
        _apply_mask(p2, bits, false);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
@@ -499,7 +698,7 @@ static int cidr_host(lua_State *L)
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
        lua_setmetatable(L, -2);
@@ -514,7 +713,7 @@ static int cidr_mask(lua_State *L)
        if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
                return 0;
 
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
        p2->family = p1->family;
 
        memset(&p2->addr.v6.s6_addr, 0xFF, sizeof(p2->addr.v6.s6_addr));
@@ -531,14 +730,14 @@ static int cidr_broadcast(lua_State *L)
        cidr_t *p2;
        int bits = L_checkbits(L, 2, p1);
 
-       if (p1->family == AF_INET6)
+       if (p1->family != AF_INET)
                return 0;
 
        if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(AF_INET);
        _apply_mask(p2, bits, true);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
@@ -546,6 +745,92 @@ static int cidr_broadcast(lua_State *L)
        return 1;
 }
 
+static int cidr_mapped4(lua_State *L)
+{
+       cidr_t *p1 = L_checkcidr(L, 1, NULL);
+       cidr_t *p2;
+
+       if (!_is_mapped4(p1))
+               return 0;
+
+       if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
+               return 0;
+
+       p2->family = AF_INET;
+       p2->bits = (p1->bits > AF_BITS(AF_INET)) ? AF_BITS(AF_INET) : p1->bits;
+       memcpy(&p2->addr.v4, p1->addr.v6.s6_addr + 12, sizeof(p2->addr.v4));
+
+       luaL_getmetatable(L, LUCI_IP_CIDR);
+       lua_setmetatable(L, -2);
+       return 1;
+}
+
+static int cidr_tolinklocal(lua_State *L)
+{
+       cidr_t *p1 = L_checkcidr(L, 1, NULL);
+       cidr_t *p2;
+       int i;
+
+       if (p1->family != AF_PACKET)
+               return 0;
+
+       if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
+               return 0;
+
+       p2->family = AF_INET6;
+       p2->bits = AF_BITS(AF_INET6);
+       p2->addr.u8[0] = 0xFE;
+       p2->addr.u8[1] = 0x80;
+       p2->addr.u8[8] = p1->addr.u8[0] ^ 0x02;
+       p2->addr.u8[9] = p1->addr.u8[1];
+       p2->addr.u8[10] = p1->addr.u8[2];
+       p2->addr.u8[11] = 0xFF;
+       p2->addr.u8[12] = 0xFE;
+       p2->addr.u8[13] = p1->addr.u8[3];
+       p2->addr.u8[14] = p1->addr.u8[4];
+       p2->addr.u8[15] = p1->addr.u8[5];
+
+       luaL_getmetatable(L, LUCI_IP_CIDR);
+       lua_setmetatable(L, -2);
+       return 1;
+}
+
+static int cidr_tomac(lua_State *L)
+{
+       cidr_t *p1 = L_checkcidr(L, 1, NULL);
+       cidr_t *p2;
+       int i;
+
+       if (p1->family != AF_INET6 ||
+           p1->addr.u8[0] != 0xFE ||
+           p1->addr.u8[1] != 0x80 ||
+           p1->addr.u8[2] != 0x00 ||
+           p1->addr.u8[3] != 0x00 ||
+           p1->addr.u8[4] != 0x00 ||
+           p1->addr.u8[5] != 0x00 ||
+           p1->addr.u8[6] != 0x00 ||
+           p1->addr.u8[7] != 0x00 ||
+           p1->addr.u8[11] != 0xFF ||
+           p1->addr.u8[12] != 0xFE)
+           return 0;
+
+       if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
+               return 0;
+
+       p2->family = AF_PACKET;
+       p2->bits = AF_BITS(AF_PACKET);
+       p2->addr.u8[0] = p1->addr.u8[8] ^ 0x02;
+       p2->addr.u8[1] = p1->addr.u8[9];
+       p2->addr.u8[2] = p1->addr.u8[10];
+       p2->addr.u8[3] = p1->addr.u8[13];
+       p2->addr.u8[4] = p1->addr.u8[14];
+       p2->addr.u8[5] = p1->addr.u8[15];
+
+       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);
@@ -558,15 +843,15 @@ static int cidr_contains(lua_State *L)
                _apply_mask(&a, p1->bits, false);
                _apply_mask(&b, p1->bits, false);
 
-               rv = !memcmp(&a.addr.v6, &b.addr.v6, a.len);
+               rv = !memcmp(&a.addr.v6, &b.addr.v6, AF_BYTES(a.family));
        }
 
        lua_pushboolean(L, rv);
        return 1;
 }
 
-#define S6_BYTE(a, i) \
-       (a)->addr.v6.s6_addr[sizeof((a)->addr.v6.s6_addr) - (i) - 1]
+#define BYTE(a, i) \
+       (a)->addr.u8[AF_BYTES((a)->family) - (i) - 1]
 
 static int _cidr_add_sub(lua_State *L, bool add)
 {
@@ -580,45 +865,45 @@ static int _cidr_add_sub(lua_State *L, bool add)
 
        if (p1->family == p2->family)
        {
-               if (p1->family == AF_INET6)
+               if (p1->family == AF_INET)
+               {
+                       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
                {
-                       for (i = 0, carry = 0; i < sizeof(r); i++)
+                       for (i = 0, carry = 0; i < AF_BYTES(p1->family); 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;
+                                       BYTE(&r, i) = BYTE(p1, i) + BYTE(p2, i) + carry;
+                                       carry = (BYTE(p1, i) + 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));
+                                       BYTE(&r, i) = (BYTE(p1, i) - BYTE(p2, i) - carry);
+                                       carry = (BYTE(p1, i) < (BYTE(p2, i) + carry));
                                }
                        }
 
                        /* would over/underflow */
                        if (carry)
                        {
-                               memset(&r.addr.v6, add * 0xFF, sizeof(r.addr.v6));
+                               memset(&r.addr.u8, add * 0xFF, AF_BYTES(r.family));
                                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
        {
@@ -660,22 +945,22 @@ static int cidr_minhost(lua_State *L)
 
        _apply_mask(&r, r.bits, false);
 
-       if (r.family == AF_INET6 && r.bits < 128)
+       if (r.family == AF_INET && r.bits < AF_BITS(AF_INET))
        {
-               r.bits = 128;
+               r.bits = AF_BITS(AF_INET);
+               r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) + 1);
+       }
+       else if (r.bits < AF_BITS(r.family))
+       {
+               r.bits = AF_BITS(r.family);
 
-               for (i = 0, carry = 1; i < sizeof(r.addr.v6.s6_addr); i++)
+               for (i = 0, carry = 1; i < AF_BYTES(r.family); i++)
                {
-                       rest = (S6_BYTE(&r, i) + carry) > 255;
-                       S6_BYTE(&r, i) += carry;
+                       rest = (BYTE(&r, i) + carry) > 255;
+                       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;
@@ -694,14 +979,14 @@ static int cidr_maxhost(lua_State *L)
 
        _apply_mask(&r, r.bits, true);
 
-       if (r.family == AF_INET && r.bits < 32)
+       if (r.family == AF_INET && r.bits < AF_BITS(AF_INET))
        {
-               r.bits = 32;
+               r.bits = AF_BITS(AF_INET);
                r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) - 1);
        }
-       else if (r.family == AF_INET6)
+       else
        {
-               r.bits = 128;
+               r.bits = AF_BITS(r.family);
        }
 
        if (!(p = lua_newuserdata(L, sizeof(*p))))
@@ -721,31 +1006,17 @@ static int cidr_gc (lua_State *L)
 
 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;
+       return format_cidr(L, p);
 }
 
 /*
  * route functions
  */
 
-static bool diff_prefix(int family, void *addr, int bits, cidr_t *p)
+static bool diff_prefix(int family, void *addr, int bits, bool exact, cidr_t *p)
 {
-       uint8_t i, b, r;
+       uint8_t i, b, r, *a;
        uint32_t m;
 
        if (!p->family)
@@ -754,28 +1025,27 @@ static bool diff_prefix(int family, void *addr, int bits, cidr_t *p)
        if (!addr || p->family != family || p->bits > bits)
                return true;
 
-       if (family == AF_INET6)
+       if (family == AF_INET)
+       {
+               m = p->bits ? htonl(~((1 << (AF_BITS(AF_INET) - p->bits)) - 1)) : 0;
+
+               if ((((struct in_addr *)addr)->s_addr & m) != (p->addr.v4.s_addr & m))
+                       return true;
+       }
+       else
        {
-               for (i = 0, r = p->bits; i < sizeof(struct in6_addr); i++)
+               for (i = 0, a = addr, r = p->bits; i < AF_BYTES(p->family); 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))
+                       if ((a[i] & b) != (p->addr.u8[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);
+       return (exact && p->bits != bits);
 }
 
 static int cb_dump_route(struct nl_msg *msg, void *arg)
@@ -803,7 +1073,7 @@ static int cb_dump_route(struct nl_msg *msg, void *arg)
        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;
+       bitlen = AF_BITS(rt->rtm_family);
 
        if ((f->type   && rt->rtm_type     != f->type)   ||
            (f->family && rt->rtm_family   != f->family) ||
@@ -812,10 +1082,14 @@ static int cb_dump_route(struct nl_msg *msg, void *arg)
                (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))
+           diff_prefix(rt->rtm_family, from, rt->rtm_src_len,
+                       f->from_exact, &f->from)         ||
+           diff_prefix(rt->rtm_family, dst,  rt->rtm_dst_len,
+                       f->dst_exact, &f->dst)           ||
+           diff_prefix(rt->rtm_family, gw,   bitlen,
+                       false, &f->gw)                   ||
+           diff_prefix(rt->rtm_family, src,  bitlen,
+                       false, &f->src))
                goto out;
 
        if (s->callback)
@@ -943,7 +1217,8 @@ static int _route_dump(lua_State *L, struct dump_filter *filter)
        nlmsg_append(msg, &rtm, sizeof(rtm), 0);
 
        if (filter->get)
-               nla_put(msg, RTA_DST, filter->dst.len, &filter->dst.addr.v6);
+               nla_put(msg, RTA_DST, AF_BYTES(filter->dst.family),
+                       &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);
@@ -961,7 +1236,7 @@ static int _route_dump(lua_State *L, struct dump_filter *filter)
 
 out:
        nl_cb_put(cb);
-       return (s.index > 0 && s.callback == 0);
+       return (s.callback == 0);
 }
 
 static int route_get(lua_State *L)
@@ -1018,37 +1293,59 @@ static int route_dump(lua_State *L)
                        filter.dst = p;
 
                if ((s = L_getstr(L, 1, "from_exact")) != NULL && parse_cidr(s, &p))
-                       filter.from = p, filter.from.exact = true;
+                       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;
+                       filter.dst = p, filter.dst_exact = true;
        }
 
        return _route_dump(L, &filter);
 }
 
 
+static bool diff_macaddr(struct ether_addr *mac1, struct ether_addr *mac2)
+{
+       struct ether_addr empty = { };
+
+       if (!memcmp(mac2, &empty, sizeof(empty)))
+               return false;
+
+       if (!mac1 || memcmp(mac1, mac2, sizeof(empty)))
+               return true;
+
+       return false;
+}
+
 static int cb_dump_neigh(struct nl_msg *msg, void *arg)
 {
        char buf[32];
-       struct ether_addr *addr;
+       struct ether_addr *mac;
+       struct in6_addr *dst;
        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];
+       int bitlen;
 
        if (hdr->nlmsg_type != RTM_NEWNEIGH ||
            (nd->ndm_family != AF_INET && nd->ndm_family != AF_INET6))
                return NL_SKIP;
 
+       nlmsg_parse(hdr, sizeof(*nd), tb, NDA_MAX, NULL);
+
+       mac = tb[NDA_LLADDR] ? RTA_DATA(tb[NDA_LLADDR]) : NULL;
+       dst = tb[NDA_DST]    ? RTA_DATA(tb[NDA_DST])    : NULL;
+
+       bitlen = AF_BITS(nd->ndm_family);
+
        if ((f->family && nd->ndm_family  != f->family) ||
            (f->iif    && nd->ndm_ifindex != f->iif) ||
-               (f->type   && !(f->type & nd->ndm_state)))
+               (f->type   && !(f->type & nd->ndm_state)) ||
+           diff_prefix(nd->ndm_family, dst, bitlen, false, &f->dst) ||
+           diff_macaddr(mac, &f->mac))
                goto out;
 
-       nlmsg_parse(hdr, sizeof(*nd), tb, NDA_MAX, NULL);
-
        if (s->callback)
                lua_pushvalue(s->L, 2);
 
@@ -1069,20 +1366,11 @@ static int cb_dump_neigh(struct nl_msg *msg, void *arg)
        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 (dst)
+               L_setaddr(s->L, "dest", nd->ndm_family, 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");
-       }
+       if (mac)
+               L_setaddr(s->L, "mac", AF_PACKET, mac, -1);
 
        s->index++;
 
@@ -1098,10 +1386,10 @@ out:
 
 static int neighbor_dump(lua_State *L)
 {
+       cidr_t p = { };
        const char *s;
-       struct dump_filter filter = {
-               .type = 0xFF & ~NUD_NOARP
-       };
+       struct ether_addr *mac;
+       struct dump_filter filter = { .type = 0xFF & ~NUD_NOARP };
        struct dump_state st = {
                .callback = lua_isfunction(L, 2),
                .pending = 1,
@@ -1122,6 +1410,13 @@ static int neighbor_dump(lua_State *L)
 
                if ((s = L_getstr(L, 1, "dev")) != NULL)
                        filter.iif = if_nametoindex(s);
+
+               if ((s = L_getstr(L, 1, "dest")) != NULL && parse_cidr(s, &p))
+                       filter.dst = p;
+
+               if ((s = L_getstr(L, 1, "mac")) != NULL &&
+                   (mac = ether_aton(s)) != NULL)
+                       filter.mac = *mac;
        }
 
        if (!sock)
@@ -1162,7 +1457,86 @@ static int neighbor_dump(lua_State *L)
 
 out:
        nl_cb_put(cb);
-       return (st.index > 0 && st.callback == 0);
+       return (st.callback == 0);
+}
+
+
+static int cb_dump_link(struct nl_msg *msg, void *arg)
+{
+       char buf[48];
+       struct dump_state *s = arg;
+       struct nlmsghdr *hdr = nlmsg_hdr(msg);
+       struct ifinfomsg *ifm = NLMSG_DATA(hdr);
+       struct nlattr *tb[IFLA_MAX+1];
+       int i, len;
+
+       if (hdr->nlmsg_type != RTM_NEWLINK)
+               return NL_SKIP;
+
+       nlmsg_parse(hdr, sizeof(*ifm), tb, IFLA_MAX, NULL);
+
+       L_setbool(s->L, "up", (ifm->ifi_flags & IFF_RUNNING));
+       L_setint(s->L, "type", ifm->ifi_type);
+       L_setstr(s->L, "name", if_indextoname(ifm->ifi_index, buf));
+
+       if (tb[IFLA_MTU])
+               L_setint(s->L, "mtu", RTA_U32(tb[IFLA_MTU]));
+
+       if (tb[IFLA_TXQLEN])
+               L_setint(s->L, "qlen", RTA_U32(tb[IFLA_TXQLEN]));
+
+       if (tb[IFLA_MASTER])
+               L_setdev(s->L, "master", tb[IFLA_MASTER]);
+
+       if (tb[IFLA_ADDRESS] && nla_len(tb[IFLA_ADDRESS]) == AF_BYTES(AF_PACKET))
+               L_setaddr(s->L, "mac", AF_PACKET, nla_get_string(tb[IFLA_ADDRESS]), -1);
+
+       s->pending = 0;
+       return NL_SKIP;
+}
+
+static int link_get(lua_State *L)
+{
+       const char *dev = luaL_checkstring(L, 1);
+       struct dump_state st = {
+               .pending = 1,
+               .L = L
+       };
+
+       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 = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST);
+       struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+       struct ifinfomsg ifm = { .ifi_index = if_nametoindex(dev) };
+
+       if (!msg || !cb)
+               return 0;
+
+       nlmsg_append(msg, &ifm, sizeof(ifm), 0);
+
+       nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_dump_link, &st);
+       nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &st);
+       nl_cb_err(cb, NL_CB_CUSTOM, cb_error, &st);
+
+       lua_newtable(L);
+
+       nl_send_auto_complete(sock, msg);
+
+       while (st.pending > 0)
+               nl_recvmsgs(sock, cb);
+
+       nlmsg_free(msg);
+       nl_cb_put(cb);
+
+       return 1;
 }
 
 
@@ -1170,12 +1544,19 @@ static const luaL_reg ip_methods[] = {
        { "new",                        cidr_new          },
        { "IPv4",                       cidr_ipv4         },
        { "IPv6",                       cidr_ipv6         },
+       { "MAC",                        cidr_mac          },
+
+       { "checkip4",                   cidr_checkip4     },
+       { "checkip6",                   cidr_checkip6     },
+       { "checkmac",                   cidr_checkmac     },
 
        { "route",                      route_get         },
        { "routes",                     route_dump        },
 
        { "neighbors",          neighbor_dump     },
 
+       { "link",                       link_get          },
+
        { }
 };
 
@@ -1185,6 +1566,10 @@ static const luaL_reg ip_cidr_methods[] = {
        { "is4linklocal",       cidr_is4linklocal },
        { "is6",                        cidr_is6          },
        { "is6linklocal",       cidr_is6linklocal },
+       { "is6mapped4",         cidr_is6mapped4   },
+       { "ismac",                      cidr_ismac        },
+       { "ismaclocal",         cidr_ismaclocal   },
+       { "ismacmcast",         cidr_ismacmcast   },
        { "lower",                      cidr_lower        },
        { "higher",                     cidr_higher       },
        { "equal",                      cidr_equal        },
@@ -1193,7 +1578,10 @@ static const luaL_reg ip_cidr_methods[] = {
        { "host",                       cidr_host         },
        { "mask",                       cidr_mask         },
        { "broadcast",          cidr_broadcast    },
-       { "contains",       cidr_contains     },
+       { "mapped4",            cidr_mapped4      },
+       { "tomac",                      cidr_tomac        },
+       { "tolinklocal",        cidr_tolinklocal  },
+       { "contains",           cidr_contains     },
        { "add",                        cidr_add          },
        { "sub",                        cidr_sub          },
        { "minhost",            cidr_minhost      },