There's no need to call getpwuid/getpwnam_r 5 times. Do it once before chroot, once...
[util-vserver.git] / src / vip6-autod.c
index f45dce5..1b60aa0 100644 (file)
@@ -1,3 +1,16 @@
+/*
+ * $Id: vip6-autod.c,v 1.4 2007/07/30 14:59:11 dhozac Exp $
+ * Copyright (c) 2007 The Trustees of Princeton University
+ * Author: Daniel Hokka Zakrisson <daniel@hozac.com>
+ *
+ * Licensed under the terms of the GNU General Public License
+ * version 2 or later.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <ctype.h>
 #include <errno.h>
+#include <signal.h>
+#include <syslog.h>
 
 #include <asm/types.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-/*XXX #include <linux/ipv6.h>*/
-struct in6_ifreq {
-       struct in6_addr ifr6_addr;
-       __u32           ifr6_prefixlen;
-       int             ifr6_ifindex;
-};
+/* not defined for gcc -ansi */
+typedef uint64_t __u64;
+typedef int64_t __s64;
+#include <netlink/netlink.h>
+#include <netlink/route/addr.h>
 
-typedef unsigned int nid_t;
-typedef unsigned int xid_t;
 #include <vserver.h>
+#include "pathconfig.h"
 
 #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;
@@ -43,8 +58,11 @@ struct prefix_list {
                int prefix_len;
                time_t valid_until;
        } address;
+       struct nid_list *nids;
 };
 
+struct nl_handle *handle;
+
 /* from linux/include/net/ipv6.h */
 static inline int ipv6_prefix_equal(struct in6_addr *prefix,
                                    struct in6_addr *addr, int prefixlen)
@@ -67,25 +85,72 @@ static inline int ipv6_prefix_equal(struct in6_addr *prefix,
 
 static int add_address_to_interface(int ifindex, struct in6_addr *address, int prefix)
 {
-       struct in6_ifreq ireq;
-       int sock;
+       int err = -1;
+       struct rtnl_addr *rta;
+       struct nl_addr *nl;
 
-       ireq.ifr6_ifindex = ifindex;
-       ireq.ifr6_prefixlen = prefix;
-       memcpy(&ireq.ifr6_addr, address, sizeof(*address));
+       nl = nl_addr_build(AF_INET6, address, sizeof(struct in6_addr));
+       rta = rtnl_addr_alloc();
 
-       /* XXX should use netlink */
-       sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
-       if (sock == -1)
-               return -1;
+       rtnl_addr_set_family(rta, AF_INET6);
+       rtnl_addr_set_ifindex(rta, ifindex);
+       rtnl_addr_set_local(rta, nl);
+       rtnl_addr_set_prefixlen(rta, prefix);
 
-       if (ioctl(sock, SIOCSIFADDR, &ireq) == -1 && errno != EEXIST) {
-               close(sock);
-               return -1;
+       if (rtnl_addr_add(handle, rta, NLM_F_REPLACE) != -1 || errno == EEXIST)
+               err = 0;
+
+       rtnl_addr_free(rta);
+       nl_addr_destroy(nl);
+       return err;
+}
+
+static int add_nid_to_list(struct nid_list **l, nid_t nid)
+{
+       struct nid_list *n;
+       for (n = *l; n; n = n->next) {
+               if (n->nid == nid)
+                       return 0;
        }
+       n = calloc(1, sizeof(struct nid_list));
+       if (!n)
+               return -1;
+       n->nid = nid;
+       n->next = *l;
+       *l = n;
+       return 1;
+}
 
-       close(sock);
-       return 0;
+static void cleanup_prefix(struct prefix_list *i)
+{
+       struct nid_list *n, *p = NULL;
+
+       for (n = i->nids; n; n = n->next) {
+               struct rtnl_addr *rta;
+               struct nl_addr *nl;
+               struct in6_addr a;
+
+               if (p)
+                       free(p);
+               memcpy(&a, &i->address.addr, sizeof(a));
+               rta = rtnl_addr_alloc();
+               nl = nl_addr_build(AF_INET6, &a, sizeof(a));
+
+               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);
+
+               /* ignore errors */
+               rtnl_addr_delete(handle, rta, 0);
+
+               nl_addr_destroy(nl);
+               rtnl_addr_free(rta);
+
+               p = n;
+       }
+       if (p)
+               free(p);
 }
 
 static void do_slices_autoconf(struct prefix_list *head)
@@ -94,7 +159,8 @@ static void do_slices_autoconf(struct prefix_list *head)
        struct dirent *de;
        nid_t nid;
        struct vc_net_nx addr;
-       struct prefix_list *i, *expired = NULL;
+       struct prefix_list *i;
+       struct nid_list *current = NULL, *n;
 
        if ((dp = opendir("/proc/virtnet")) == NULL)
                return;
@@ -103,21 +169,32 @@ static void do_slices_autoconf(struct prefix_list *head)
                        continue;
 
                nid = strtoul(de->d_name, NULL, 10);
-               addr.type = vcNET_IPV6;
-               addr.count = -1;
-               if (vc_net_remove(nid, &addr) == -1)
+               addr.type = vcNET_IPV6A;
+               addr.count = 0;
+               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);
 
                for (i = head->next; i;) {
                        /* expired */
                        if (i->mask & HAS_PREFIX && i->prefix.valid_until < time(NULL)) {
                                struct prefix_list *tmp;
+                               char buf[64];
+
+                               inet_ntop(AF_INET6, &i->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;
+
+                               cleanup_prefix(i);
+
                                free(i);
                                i = tmp;
                                continue;
@@ -125,18 +202,23 @@ static void do_slices_autoconf(struct prefix_list *head)
                        if (i->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 & 0x7f00) >> 7));
-                       addr.ip[3] = htonl((ntohl(addr.ip[3]) & 0x00ffffff) | ((nid & 0xff) << 25));
+                       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) {
-                               perror("vc_net_add");
-                               exit(1);
+                               syslog(LOG_ERR, "vc_net_add(%u): %s", nid, strerror(errno));
+                               goto next;
                        }
                        if (add_address_to_interface(i->ifindex, (struct in6_addr *) addr.ip, i->prefix.prefix_len) == -1) {
-                               perror("add_address_to_interface");
-                               exit(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));
+                               goto next;
                        }
 next:
                        i = i->next;
@@ -151,6 +233,9 @@ static int add_prefix(struct prefix_list *head, struct prefixmsg *msg,
        struct prefix_list *i = head;
        if (!msg || !prefix || !cache)
                return -1;
+       /* XXX IF_PREFIX_AUTOCONF == 0x02 */
+       if (!(msg->prefix_flags & 0x02))
+               return -1;
 
        do {
                if (i->next != NULL)
@@ -187,7 +272,7 @@ static inline int add_address(struct prefix_list *head, struct ifaddrmsg *msg,
                return -1;
 
        if (address->s6_addr[11] != 0xFF || address->s6_addr[12] != 0xFE)
-               return 0;
+               return -1;
 
        do {
                if (i->next != NULL)
@@ -215,83 +300,131 @@ static inline int add_address(struct prefix_list *head, struct ifaddrmsg *msg,
        return 1;
 }
 
-int main(int argc, char *argv[])
+static struct nla_policy addr_policy[IFA_MAX+1] = {
+       [IFA_ADDRESS]   = { .minlen = sizeof(struct in6_addr) },
+       [IFA_LABEL]     = { .type = NLA_STRING,
+                           .maxlen = IFNAMSIZ },
+       [IFA_CACHEINFO] = { .minlen = sizeof(struct ifa_cacheinfo) },
+};
+static struct nla_policy prefix_policy[PREFIX_MAX+1] = {
+       [PREFIX_ADDRESS]   = { .minlen = sizeof(struct in6_addr) },
+       [PREFIX_CACHEINFO] = { .minlen = sizeof(struct prefix_cacheinfo) },
+};
+int handle_valid_msg(struct nl_msg *msg, void *arg)
 {
-       int sock;
-       struct sockaddr_nl sa;
-       struct prefix_list head = { .prev = NULL, .next = NULL };
+       struct nlmsghdr *nlh = nlmsg_hdr(msg);
+       int ret = -1;
+       char *payload;
+       struct sockaddr_nl *source = nlmsg_get_src(msg);
+
+       payload = nlmsg_data(nlh);
+       if (source->nl_groups == RTMGRP_IPV6_PREFIX) {
+               struct prefixmsg *prefixmsg;
+               struct in6_addr *prefix = NULL;
+               struct prefix_cacheinfo *cacheinfo = NULL;
+               struct nlattr *tb[PREFIX_MAX+1];
+
+               if (nlmsg_parse(nlh, sizeof(struct prefixmsg), tb, PREFIX_MAX, prefix_policy) < 0) {
+                       syslog(LOG_ERR, "Failed to parse prefixmsg");
+                       return -1;
+               }
 
-       sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
-       if (sock == -1) {
-               perror("socket()");
-               exit(1);
-       }
-       sa.nl_family = AF_NETLINK;
-       sa.nl_groups = RTMGRP_IPV6_PREFIX|RTMGRP_IPV6_IFADDR;
-       if (bind(sock, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
-               perror("bind()");
-               exit(1);
+               prefixmsg = (struct prefixmsg *) payload;
+               if (tb[PREFIX_ADDRESS])
+                       prefix = nl_data_get(nla_get_data(tb[PREFIX_ADDRESS]));
+               if (tb[PREFIX_CACHEINFO])
+                       cacheinfo = nl_data_get(nla_get_data(tb[PREFIX_CACHEINFO]));
+               ret = add_prefix(arg, prefixmsg, prefix, cacheinfo);
+       }       
+       else if (source->nl_groups == RTMGRP_IPV6_IFADDR) {
+               struct ifaddrmsg *ifaddrmsg;
+               struct in6_addr *address = NULL;
+               struct ifa_cacheinfo *cacheinfo = NULL;
+               struct nlattr *tb[IFA_MAX+1];
+
+               if (nlmsg_parse(nlh, sizeof(struct ifaddrmsg), tb, IFA_MAX, addr_policy) < 0) {
+                       syslog(LOG_ERR, "Failed to parse ifaddrmsg");
+                       return -1;
+               }
+
+               ifaddrmsg = (struct ifaddrmsg *) payload;
+               if (tb[IFA_ADDRESS])
+                       address = nl_data_get(nla_get_data(tb[IFA_ADDRESS]));
+               if (tb[IFA_CACHEINFO])
+                       cacheinfo = nl_data_get(nla_get_data(tb[IFA_CACHEINFO]));
+               ret = add_address(arg, ifaddrmsg, address, cacheinfo);
        }
+       if (ret >= 0)
+               do_slices_autoconf(arg);
 
-       while (1) {
-               char buf[4000], *payload;
-               struct nlmsghdr *nlh;
-               ssize_t len, this_len;
-               socklen_t socklen = sizeof(sa);
-
-               if ((len = recvfrom(sock, buf, sizeof(buf), 0, 
-                                   (struct sockaddr *) &sa, &socklen)) <= 0)
-                       break;
-               for (nlh = (struct nlmsghdr *) buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
-                       struct nlattr *nla;
-
-                       if (nlh->nlmsg_type == NLMSG_DONE)
-                               break;
-                       else if (nlh->nlmsg_type == NLMSG_ERROR)
-                               break;
-
-                       this_len = NLMSG_ALIGN((nlh)->nlmsg_len) - NLMSG_LENGTH(0);
-                       payload = NLMSG_DATA(nlh);
-                       if (sa.nl_groups == RTMGRP_IPV6_PREFIX) {
-                               struct prefixmsg *prefixmsg;
-                               struct in6_addr *prefix;
-                               struct prefix_cacheinfo *cacheinfo;
-
-                               prefixmsg = (struct prefixmsg *) payload;
-                               prefix = NULL;
-                               cacheinfo = NULL;
-                               for (nla = (struct nlattr *)(payload + sizeof(*prefixmsg)); nla < payload + this_len; nla = (char *) nla + nla->nla_len) {
-                                       if (nla->nla_type == PREFIX_ADDRESS)
-                                               prefix = (struct in6_addr *)(((char *) nla) + sizeof(*nla));
-                                       else if (nla->nla_type == PREFIX_CACHEINFO)
-                                               cacheinfo = (struct prefix_cacheinfo *)(((char *) nla) + sizeof(*nla));
-                               }
-                               if (add_prefix(&head, prefixmsg, prefix, cacheinfo) == -1) {
-                                       printf("Adding prefix failed!\n");
-                               }
-                       }
-                       else if (sa.nl_groups == RTMGRP_IPV6_IFADDR) {
-                               struct ifaddrmsg *ifaddrmsg;
-                               struct in6_addr *address;
-                               struct ifa_cacheinfo *cacheinfo;
-
-                               ifaddrmsg = (struct ifaddrmsg *) payload;
-                               address = NULL;
-                               cacheinfo = NULL;
-                               for (nla = (struct nlattr *)(payload + sizeof(*ifaddrmsg)); nla < payload + this_len; nla = (char *) nla + nla->nla_len) {
-                                       if (nla->nla_type == IFA_ADDRESS)
-                                               address = (struct in6_addr *)(((char *) nla) + sizeof(*nla));
-                                       else if (nla->nla_type == IFA_CACHEINFO)
-                                               cacheinfo = (struct ifa_cacheinfo *)(((char *) nla) + sizeof(*nla));
-                               }
-                               if (add_address(&head, ifaddrmsg, address, cacheinfo) == -1) {
-                                       printf("Adding address failed!\n");
-                               }
-                       }
-               }
+       return 0;
+}
+
+int handle_error_msg(struct sockaddr_nl *source, struct nlmsgerr *err,
+                    void *arg)
+{
+       syslog(LOG_ERR, "%s", strerror(err->error));
+       return 0;
+}
+
+int handle_no_op(struct nl_msg *msg, void *arg)
+{
+       return 0;
+}
+
+/* only for access in the signal handler */
+struct prefix_list head;
+void signal_handler(int signal)
+{
+       switch (signal) {
+       case SIGUSR1:
                do_slices_autoconf(&head);
+               break;
+       }
+}
+
+static int write_pidfile(const char *filename)
+{
+       FILE *fp;
+       fp = fopen(filename, "w");
+       if (!fp)
+               return -1;
+       fprintf(fp, "%d\n", getpid());
+       fclose(fp);
+       return 0;
+}
+
+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_SEQ_CHECK, NL_CB_CUSTOM, handle_no_op, NULL);
+       nl_cb_err(cbs, NL_CB_CUSTOM, handle_error_msg, &head);
+       nl_disable_sequence_check(handle);
+
+       nl_join_groups(handle, RTMGRP_IPV6_PREFIX|RTMGRP_IPV6_IFADDR);
+       if (nl_connect(handle, NETLINK_ROUTE) == -1) {
+               syslog(LOG_CRIT, "nl_connect: %s", strerror(errno));
+               exit(1);
        }
 
-       close(sock);
+       if (daemon(0, 0) == -1)
+               return -1;
+
+       /* XXX .. here is a hack */
+       write_pidfile(DEFAULT_PKGSTATEDIR "/../vip6-autod.pid");
+
+       signal(SIGUSR1, signal_handler);
+
+       while (nl_recvmsgs(handle, cbs) > 0);
+
+       nl_close(handle);
+       closelog();
        return 0;
 }