Merge pull request #305 from nmav/compression
[project/luci.git] / libs / luci-lib-ip / src / ip.c
index 66ecb56..b91966c 100644 (file)
@@ -69,6 +69,7 @@ struct dump_filter {
        cidr_t from;
        cidr_t src;
        cidr_t dst;
+       struct ether_addr mac;
 };
 
 struct dump_state {
@@ -80,14 +81,14 @@ 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");
@@ -125,27 +126,23 @@ 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];
+       char *p, 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))
+       if (inet_pton(AF_INET, buf, &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))
+       else if (inet_pton(AF_INET6, buf, &pp->addr.v6))
        {
                bitlen = 128;
                pp->family = AF_INET6;
@@ -283,7 +280,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;
@@ -298,10 +295,10 @@ static int _cidr_new(lua_State *L, int index, int family)
                        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
                {
@@ -320,6 +317,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 +333,17 @@ 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_is4(lua_State *L)
@@ -379,6 +379,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);
@@ -546,6 +571,26 @@ 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 > 32) ? 32 : 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_contains(lua_State *L)
 {
        cidr_t *p1 = L_checkcidr(L, 1, NULL);
@@ -582,7 +627,7 @@ static int _cidr_add_sub(lua_State *L, bool add)
        {
                if (p1->family == AF_INET6)
                {
-                       for (i = 0, carry = 0; i < sizeof(r); i++)
+                       for (i = 0, carry = 0; i < sizeof(r.addr.v6.s6_addr); i++)
                        {
                                if (add)
                                {
@@ -869,7 +914,7 @@ static int cb_dump_route(struct nl_msg *msg, void *arg)
        if (s->callback)
                lua_call(s->L, 1, 0);
        else if (hdr->nlmsg_flags & NLM_F_MULTI)
-               lua_rawseti(s->L, 3, s->index);
+               lua_rawseti(s->L, -2, s->index);
 
 out:
        s->pending = !!(hdr->nlmsg_flags & NLM_F_MULTI);
@@ -961,7 +1006,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)
@@ -1028,27 +1073,49 @@ static int route_dump(lua_State *L)
 }
 
 
+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 = (nd->ndm_family == AF_INET) ? 32 : 128;
+
        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, &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,16 +1136,15 @@ 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])
+       if (mac)
        {
-               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]);
+                        mac->ether_addr_octet[0], mac->ether_addr_octet[1],
+                        mac->ether_addr_octet[2], mac->ether_addr_octet[3],
+                        mac->ether_addr_octet[4], mac->ether_addr_octet[5]);
 
                lua_pushstring(s->L, buf);
                lua_setfield(s->L, -2, "mac");
@@ -1098,10 +1164,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 +1188,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 +1235,97 @@ 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 *p, *addr, 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])
+       {
+               len  = nla_len(tb[IFLA_ADDRESS]);
+               addr = nla_get_string(tb[IFLA_ADDRESS]);
+
+               if ((len * 3) <= sizeof(buf))
+               {
+                       for (p = buf, i = 0; i < len; i++)
+                               p += sprintf(p, "%s%02x", (i ? ":" : ""), (uint8_t)*addr++);
+
+                       L_setstr(s->L, "mac", buf);
+               }
+       }
+
+       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;
 }
 
 
@@ -1176,6 +1339,8 @@ static const luaL_reg ip_methods[] = {
 
        { "neighbors",          neighbor_dump     },
 
+       { "link",           link_get          },
+
        { }
 };
 
@@ -1185,6 +1350,7 @@ static const luaL_reg ip_cidr_methods[] = {
        { "is4linklocal",       cidr_is4linklocal },
        { "is6",                        cidr_is6          },
        { "is6linklocal",       cidr_is6linklocal },
+       { "is6mapped4",         cidr_is6mapped4   },
        { "lower",                      cidr_lower        },
        { "higher",                     cidr_higher       },
        { "equal",                      cidr_equal        },
@@ -1193,6 +1359,7 @@ static const luaL_reg ip_cidr_methods[] = {
        { "host",                       cidr_host         },
        { "mask",                       cidr_mask         },
        { "broadcast",          cidr_broadcast    },
+       { "mapped4",            cidr_mapped4      },
        { "contains",       cidr_contains     },
        { "add",                        cidr_add          },
        { "sub",                        cidr_sub          },