_Much_ more complex vip6-autod, now cleans up after a stopped guest too.
Daniel Hokka Zakrisson [Sat, 24 Nov 2007 20:22:17 +0000 (20:22 +0000)]
src/vip6-autod.c

index 2d48dcd..0c0ee1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: vip6-autod.c,v 1.4 2007/07/30 14:59:11 dhozac Exp $
+ * $Id$
  * Copyright (c) 2007 The Trustees of Princeton University
  * Author: Daniel Hokka Zakrisson <daniel@hozac.com>
  *
@@ -28,9 +28,6 @@
 #include <syslog.h>
 
 #include <asm/types.h>
-/* not defined for gcc -ansi */
-typedef unsigned long long __u64;
-typedef signed long long __s64;
 #include <netlink/netlink.h>
 #include <netlink/route/addr.h>
 
@@ -39,13 +36,12 @@ typedef signed long long __s64;
 
 #define HAS_ADDRESS    0x01
 #define HAS_PREFIX     0x02
+
 struct nid_list {
        nid_t nid;
        struct nid_list *next;
 };
-struct prefix_list {
-       struct prefix_list *prev;
-       struct prefix_list *next;
+struct prefix {
        uint32_t mask;
        int ifindex;
        struct {
@@ -58,7 +54,18 @@ struct prefix_list {
                int prefix_len;
                time_t valid_until;
        } address;
-       struct nid_list *nids;
+};
+struct nid_prefix_map {
+       struct {
+               struct nid_prefix_map *prev;
+               struct nid_prefix_map *next;
+       } n;
+       struct {
+               struct nid_prefix_map *prev;
+               struct nid_prefix_map *next;
+       } p;
+       struct prefix *prefix;
+       nid_t nid;
 };
 
 struct nl_handle *handle;
@@ -83,7 +90,8 @@ static inline int ipv6_prefix_equal(struct in6_addr *prefix,
        return 1;
 }
 
-static int add_address_to_interface(int ifindex, struct in6_addr *address, int prefix)
+static int add_address_to_interface(int ifindex, struct in6_addr *address,
+                                   int prefix)
 {
        int err = -1;
        struct rtnl_addr *rta;
@@ -105,52 +113,171 @@ static int add_address_to_interface(int ifindex, struct in6_addr *address, int p
        return err;
 }
 
-static int add_nid_to_list(struct nid_list **l, nid_t nid)
+static inline int remove_address_from_interface(struct nid_prefix_map *entry)
 {
-       struct nid_list *n;
-       for (n = *l; n; n = n->next) {
-               if (n->nid == nid)
-                       return 0;
+       struct rtnl_addr *rta;
+       struct nl_addr *nl;
+       struct in6_addr a;
+       int ret;
+
+       memcpy(&a, &entry->prefix->address.addr, sizeof(a));
+       if (entry->nid != 0) {
+               a.s6_addr[11] = (entry->nid & 0x7f80) >> 7;
+               a.s6_addr[12] = (entry->nid & 0x7f) << 1;
        }
-       n = calloc(1, sizeof(struct nid_list));
-       if (!n)
+
+       nl = nl_addr_build(AF_INET6, &a, sizeof(a));
+       if (!nl)
                return -1;
-       n->nid = nid;
-       n->next = *l;
-       *l = n;
+       rta = rtnl_addr_alloc();
+       if (!rta)
+               return -1;
+
+       rtnl_addr_set_family(rta, AF_INET6);
+       rtnl_addr_set_ifindex(rta, entry->prefix->ifindex);
+       rtnl_addr_set_local(rta, nl);
+       rtnl_addr_set_prefixlen(rta, entry->prefix->address.prefix_len);
+
+       ret = rtnl_addr_delete(handle, rta, 0);
+
+       rtnl_addr_free(rta);
+       nl_addr_destroy(nl);
+
+       return ret;
+}
+
+static int add_to_map(struct nid_prefix_map *map, struct nid_prefix_map *new)
+{
+       struct nid_prefix_map *i;
+#define PUT_IT_IN_PLACE(node, member, om)                              \
+       /* find the correct location in the list */                     \
+       for (i = map->node.next; i->node.next && i->member <            \
+            new->member; i = i->node.next)                             \
+               ;                                                       \
+       if (i && i->member == new->member && i->om == new->om)          \
+               return 0;                                               \
+       /* first in the list */                                         \
+       if (!i || !i->node.prev) {                                      \
+               new->node.prev = NULL;                                  \
+               new->node.next = i;                                     \
+               map->node.next = new;                                   \
+               if (i)                                                  \
+                       i->node.prev = new;                             \
+       }                                                               \
+       /* last in the list */                                          \
+       else if (i->node.next == NULL) {                                \
+               new->node.prev = i;                                     \
+               new->node.next = NULL;                                  \
+               i->node.next = new;                                     \
+       }                                                               \
+       /* somewhere in the middle */                                   \
+       else {                                                          \
+               new->node.prev = i->node.prev;                          \
+               new->node.next = i;                                     \
+               i->node.prev->node.next = new;                          \
+               i->node.prev = new;                                     \
+       }
+       PUT_IT_IN_PLACE(p, prefix, nid)
+       PUT_IT_IN_PLACE(n, nid, prefix)
        return 1;
 }
 
-static void cleanup_prefix(struct prefix_list *i)
+static inline void remove_from_map(struct nid_prefix_map *map,
+                                  struct nid_prefix_map *entry)
 {
-       struct nid_list *n, *p = NULL;
+       if (map->n.next == entry)
+               map->n.next = entry->n.next;
+       if (map->n.prev == entry)
+               map->n.prev = entry->n.prev;
+       if (map->p.next == entry)
+               map->p.next = entry->p.next;
+       if (map->p.prev == entry)
+               map->p.prev = entry->p.prev;
+}
 
-       for (n = i->nids; n; n = n->next) {
-               struct rtnl_addr *rta;
-               struct nl_addr *nl;
-               struct in6_addr a;
+static inline void remove_from_map_and_free(struct nid_prefix_map *map,
+                                           struct nid_prefix_map *entry)
+{
+       remove_from_map(map, entry);
+       free(entry);
+}
 
-               if (p)
-                       free(p);
-               memcpy(&a, &i->address.addr, sizeof(a));
-               rta = rtnl_addr_alloc();
-               nl = nl_addr_build(AF_INET6, &a, sizeof(a));
+static int add_nid_to_map(struct nid_prefix_map *map, struct prefix *prefix,
+                         nid_t nid)
+{
+       struct nid_prefix_map *new = calloc(1, sizeof(struct nid_prefix_map));
+       int ret;
 
-               rtnl_addr_set_family(rta, AF_INET6);
-               rtnl_addr_set_ifindex(rta, i->ifindex);
-               rtnl_addr_set_local(rta, nl);
-               rtnl_addr_set_prefixlen(rta, i->address.prefix_len);
+       if (!new)
+               return -1;
 
-               /* ignore errors */
-               rtnl_addr_delete(handle, rta, 0);
+       new->prefix = prefix;
+       new->nid = nid;
+       ret = add_to_map(map, new);
+
+       if (ret == 0)
+               free(new);
+
+       return ret;
+}
+
+static int add_prefix_to_map(struct nid_prefix_map *map, struct prefix *prefix)
+{
+       return add_nid_to_map(map, prefix, 0);
+}
+
+static void cleanup_prefix(struct nid_prefix_map *map,
+                          struct nid_prefix_map *first)
+{
+       struct nid_prefix_map *i, *p = NULL;
 
-               nl_addr_destroy(nl);
-               rtnl_addr_free(rta);
+       for (i = first; i && first->prefix == i->prefix; i = i->p.next) {
+               if (p)
+                       remove_from_map_and_free(map, p);
+
+               /* ignore errors */
+               remove_address_from_interface(i);
 
-               p = n;
+               p = i;
        }
        if (p)
-               free(p);
+               remove_from_map_and_free(map, p);
+}
+
+static inline int add_nid_to_list(struct nid_list **head, nid_t nid)
+{
+       struct nid_list *i, *new;
+
+       for (i = *head; i && i->next && i->next->nid < nid; i = i->next)
+               ;
+       /* check if this nid is first in the list */
+       if (i && i->nid == nid)
+               return 0;
+       /* check if it's already in the list */
+       if (i && i->next && i->next->nid == nid)
+               return 0;
+
+       /* add it */
+       new = calloc(1, sizeof(struct nid_list));
+       if (!new)
+               return -1;
+       new->nid = nid;
+
+       /* this is the lowest nid in the list */
+       if (i == *head) {
+               *head = new;
+               new->next = i;
+       }
+       /* in the middle/at the end */
+       else if (i) {
+               new->next = i->next;
+               i->next = new;
+       }
+       /* there was no list */
+       else
+               *head = new;
+
+       return 1;
 }
 
 static inline void free_nid_list(struct nid_list *head)
@@ -165,88 +292,129 @@ static inline void free_nid_list(struct nid_list *head)
                free(p);
 }
 
-static void do_slices_autoconf(struct prefix_list *head)
+static inline void cleanup_nid(struct nid_prefix_map *map,
+                              nid_t nid)
+{
+       struct nid_prefix_map *i, *p = NULL;
+       for (i = map->n.next; i->nid < nid; i = i->n.next)
+               ;
+       /* this nid doesn't have any entries in the map */
+       if (i->nid != nid)
+               return;
+       for (; i->nid == nid; i = i->n.next) {
+               if (p)
+                       remove_from_map_and_free(map, p);
+               remove_address_from_interface(i);
+               p = i;
+       }
+       if (p)
+               remove_from_map_and_free(map, p);
+}
+
+static inline void cleanup_nids(struct nid_prefix_map *map,
+                               struct nid_list *previous,
+                               struct nid_list *current)
+{
+       struct nid_list *p, *pprev = NULL, *c;
+       for (p = previous, c = current; p; pprev = p, p = p->next) {
+               if (pprev)
+                       free(pprev);
+               while (c->nid < p->nid)
+                       c = c->next;
+               if (c->nid == p->nid)
+                       continue;
+               /* this context has disappeared */
+               cleanup_nid(map, p->nid);
+       }
+       if (pprev)
+               free(pprev);
+}
+
+static void do_slices_autoconf(struct nid_prefix_map *map)
 {
        DIR *dp;
        struct dirent *de;
-       nid_t nid;
-       struct vc_net_nx addr;
-       struct prefix_list *i;
+       struct vc_net_addr addr;
+       struct nid_prefix_map *i;
        struct nid_list *current = NULL, *n;
        static struct nid_list *previous = NULL;
 
        if ((dp = opendir("/proc/virtnet")) == NULL)
                return;
        while ((de = readdir(dp)) != NULL) {
+               nid_t nid;
+
                if (!isdigit(de->d_name[0]))
                        continue;
 
                nid = strtoul(de->d_name, NULL, 10);
-               addr.type = vcNET_IPV6A;
-               addr.count = 0;
+               addr.vna_type = VC_NXA_TYPE_IPV6 | VC_NXA_TYPE_ANY;
                if (vc_net_remove(nid, &addr) == -1) {
                        syslog(LOG_ERR, "vc_net_remove(%u): %s", nid, strerror(errno));
                        continue;
                }
 
                add_nid_to_list(&current, nid);
+       }
+       closedir(dp);
 
-               for (i = head->next; i;) {
+       for (n = current; n; n = n->next) {
+               for (i = map->p.next; i && i->nid == 0;) {
                        /* expired */
-                       if (i->mask & HAS_PREFIX && i->prefix.valid_until < time(NULL)) {
-                               struct prefix_list *tmp;
+                       if (i->prefix->mask & HAS_PREFIX && i->prefix->prefix.valid_until < time(NULL)) {
+                               struct nid_prefix_map *tmp;
                                char buf[64];
 
-                               inet_ntop(AF_INET6, &i->address.addr, buf, sizeof(buf));
+                               inet_ntop(AF_INET6, &i->prefix->address.addr, buf, sizeof(buf));
                                syslog(LOG_NOTICE, "Address %s timed out", buf);
 
-                               if (i->next)
-                                       i->next->prev = i->prev;
-                               if (i->prev)
-                                       i->prev->next = i->next;
-                               tmp = i->next;
+                               tmp = i->p.next;
 
-                               cleanup_prefix(i);
+                               cleanup_prefix(map, i);
 
-                               free(i);
                                i = tmp;
                                continue;
                        }
-                       if (i->mask != (HAS_ADDRESS|HAS_PREFIX))
+                       if (i->prefix->mask != (HAS_ADDRESS|HAS_PREFIX))
                                goto next;
 
-                       addr.type = vcNET_IPV6;
-                       addr.count = 1;
-                       addr.mask[0] = i->prefix.prefix_len;
-                       memcpy(addr.ip, &i->address.addr, sizeof(struct in6_addr));
-                       addr.ip[2] = htonl((ntohl(addr.ip[2]) & 0xffffff00) | ((nid & 0x7f80) >> 7));
-                       addr.ip[3] = htonl((ntohl(addr.ip[3]) & 0x00ffffff) | ((nid & 0x7f) << 25));
-                       if (vc_net_add(nid, &addr) == -1) {
-                               syslog(LOG_ERR, "vc_net_add(%u): %s", nid, strerror(errno));
+                       addr.vna_type = VC_NXA_TYPE_IPV6 | VC_NXA_TYPE_ADDR;
+                       memcpy(&addr.vna_v6_ip, &i->prefix->address.addr, sizeof(struct in6_addr));
+                       addr.vna_prefix = i->prefix->prefix.prefix_len;
+                       if (addr.vna_prefix == 64) {
+                               addr.vna_v6_mask.s6_addr32[0] = addr.vna_v6_mask.s6_addr32[1] = 0xffffffff;
+                               addr.vna_v6_mask.s6_addr32[2] = addr.vna_v6_mask.s6_addr32[3] = 0;
+                       }
+                       addr.vna_v6_ip.s6_addr[11] = (n->nid & 0x7f80) >> 7;
+                       addr.vna_v6_ip.s6_addr[12] = (n->nid & 0x007f) << 1;
+                       if (vc_net_add(n->nid, &addr) == -1) {
+                               syslog(LOG_ERR, "vc_net_add(%u): %s", n->nid, strerror(errno));
                                goto next;
                        }
-                       if (add_address_to_interface(i->ifindex, (struct in6_addr *) addr.ip, i->prefix.prefix_len) == -1) {
+                       if (add_address_to_interface(i->prefix->ifindex, &addr.vna_v6_ip, addr.vna_prefix) == -1) {
                                syslog(LOG_ERR, "add_address_to_interface: %s", strerror(errno));
                                goto next;
                        }
-                       if (add_nid_to_list(&i->nids, nid) == -1) {
-                               syslog(LOG_ERR, "add_nid_to_list: %s", strerror(errno));
+                       if (add_nid_to_map(map, i->prefix, n->nid) == -1) {
+                               syslog(LOG_ERR, "add_nid_to_map: %s", strerror(errno));
                                goto next;
                        }
 next:
-                       i = i->next;
+                       i = i->p.next;
                }
        }
-       closedir(dp);
 
-       free_nid_list(previous);
+       cleanup_nids(map, previous, current);
        previous = current;
 }
 
-static int add_prefix(struct prefix_list *head, struct prefixmsg *msg,
+/* XXX These two functions are very similar */
+static int add_prefix(struct nid_prefix_map *map, struct prefixmsg *msg,
                      struct in6_addr *prefix, struct prefix_cacheinfo *cache)
 {
-       struct prefix_list *i = head;
+       struct nid_prefix_map *i = map;
+       struct prefix *new;
+
        if (!msg || !prefix || !cache)
                return -1;
        /* XXX IF_PREFIX_AUTOCONF == 0x02 */
@@ -254,36 +422,39 @@ static int add_prefix(struct prefix_list *head, struct prefixmsg *msg,
                return -1;
 
        do {
-               if (i->next != NULL)
-                       i = i->next;
-               if (ipv6_prefix_equal(prefix, &i->prefix.addr, msg->prefix_len) ||
-                   ipv6_prefix_equal(prefix, &i->address.addr, msg->prefix_len)) {
-                       i->mask |= HAS_PREFIX;
-                       i->ifindex = msg->prefix_ifindex;
-                       memcpy(&i->prefix.addr, prefix, sizeof(*prefix));
-                       i->prefix.prefix_len = msg->prefix_len;
-                       i->prefix.valid_until = time(NULL) + cache->preferred_time;
+               if (i->p.next != NULL)
+                       i = i->p.next;
+               if (ipv6_prefix_equal(prefix, &i->prefix->prefix.addr, msg->prefix_len) ||
+                   ipv6_prefix_equal(prefix, &i->prefix->address.addr, msg->prefix_len)) {
+                       i->prefix->mask |= HAS_PREFIX;
+                       i->prefix->ifindex = msg->prefix_ifindex;
+                       memcpy(&i->prefix->prefix.addr, prefix, sizeof(*prefix));
+                       i->prefix->prefix.prefix_len = msg->prefix_len;
+                       i->prefix->prefix.valid_until = time(NULL) + cache->preferred_time;
                        return 0;
                }
-       } while (i->next);
+       } while (i->p.next && i->nid == 0);
 
-       i->next = calloc(1, sizeof(*i));
-       if (!i->next)
+       /* not yet in the map */
+       new = calloc(1, sizeof(*new));
+       if (!new)
+               return -1;
+       new->mask = HAS_PREFIX;
+       memcpy(&new->prefix.addr, prefix, sizeof(*prefix));
+       new->prefix.prefix_len = msg->prefix_len;
+       new->prefix.valid_until = time(NULL) + cache->preferred_time;
+       if (add_prefix_to_map(map, new) == -1)
                return -1;
-       i->next->prev = i;
-       i = i->next;
-       i->mask = HAS_PREFIX;
-       memcpy(&i->prefix.addr, prefix, sizeof(*prefix));
-       i->prefix.prefix_len = msg->prefix_len;
-       i->prefix.valid_until = time(NULL) + cache->preferred_time;
 
        return 1;
 }
 
-static inline int add_address(struct prefix_list *head, struct ifaddrmsg *msg,
+static inline int add_address(struct nid_prefix_map *map, struct ifaddrmsg *msg,
                              struct in6_addr *address, struct ifa_cacheinfo *cache)
 {
-       struct prefix_list *i = head;
+       struct nid_prefix_map *i = map;
+       struct prefix *new;
+
        if (!msg || !address || !cache)
                return -1;
 
@@ -291,27 +462,27 @@ static inline int add_address(struct prefix_list *head, struct ifaddrmsg *msg,
                return -1;
 
        do {
-               if (i->next != NULL)
-                       i = i->next;
-               if (ipv6_prefix_equal(address, &i->prefix.addr, msg->ifa_prefixlen) ||
-                   ipv6_prefix_equal(address, &i->address.addr, msg->ifa_prefixlen)) {
-                       i->mask |= HAS_ADDRESS;
-                       memcpy(&i->address.addr, address, sizeof(*address));
-                       i->address.prefix_len = msg->ifa_prefixlen;
-                       i->address.valid_until = time(NULL) + cache->ifa_prefered;
+               if (i->p.next != NULL)
+                       i = i->p.next;
+               if (ipv6_prefix_equal(address, &i->prefix->prefix.addr, msg->ifa_prefixlen) ||
+                   ipv6_prefix_equal(address, &i->prefix->address.addr, 128)) {
+                       i->prefix->mask |= HAS_ADDRESS;
+                       memcpy(&i->prefix->address.addr, address, sizeof(*address));
+                       i->prefix->address.prefix_len = msg->ifa_prefixlen;
+                       i->prefix->address.valid_until = time(NULL) + cache->ifa_prefered;
                        return 0;
                }
-       } while (i->next);
+       } while (i->p.next && i->nid == 0);
 
-       i->next = calloc(1, sizeof(*i));
-       if (!i->next)
+       new = calloc(1, sizeof(*new));
+       if (!new)
+               return -1;
+       new->mask = HAS_ADDRESS;
+       memcpy(&new->address.addr, address, sizeof(*address));
+       new->address.prefix_len = msg->ifa_prefixlen;
+       new->address.valid_until = time(NULL) + cache->ifa_prefered;
+       if (add_prefix_to_map(map, new) == -1)
                return -1;
-       i->next->prev = i;
-       i = i->next;
-       i->mask = HAS_ADDRESS;
-       memcpy(&i->address.addr, address, sizeof(*address));
-       i->address.prefix_len = msg->ifa_prefixlen;
-       i->address.valid_until = time(NULL) + cache->ifa_prefered;
 
        return 1;
 }
@@ -389,12 +560,21 @@ int handle_no_op(struct nl_msg *msg, void *arg)
 }
 
 /* only for access in the signal handler */
-struct prefix_list head;
+struct nid_prefix_map map = {
+       .n = {
+               .next = NULL,
+               .prev = NULL,
+       },
+       .p = {
+               .next = NULL,
+               .prev = NULL,
+       },
+};
 void signal_handler(int signal)
 {
        switch (signal) {
        case SIGUSR1:
-               do_slices_autoconf(&head);
+               do_slices_autoconf(&map);
                break;
        }
 }
@@ -413,15 +593,14 @@ static int write_pidfile(const char *filename)
 int main(int argc, char *argv[])
 {
        struct nl_cb *cbs;
-       head.prev = head.next = NULL;
 
        openlog("vip6-autod", LOG_PERROR, LOG_DAEMON);
 
        handle = nl_handle_alloc_nondefault(NL_CB_VERBOSE);
        cbs = nl_handle_get_cb(handle);
-       nl_cb_set(cbs, NL_CB_VALID, NL_CB_CUSTOM, handle_valid_msg, &head);
+       nl_cb_set(cbs, NL_CB_VALID, NL_CB_CUSTOM, handle_valid_msg, &map);
        nl_cb_set(cbs, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, handle_no_op, NULL);
-       nl_cb_err(cbs, NL_CB_CUSTOM, handle_error_msg, &head);
+       nl_cb_err(cbs, NL_CB_CUSTOM, handle_error_msg, &map);
        nl_disable_sequence_check(handle);
 
        nl_join_groups(handle, RTMGRP_IPV6_PREFIX|RTMGRP_IPV6_IFADDR);