service: Simplify nameserver route adding and removing
[framework/connectivity/connman.git] / src / rtnl.c
index f486a18..4f50219 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
@@ -23,6 +23,7 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
@@ -45,7 +46,7 @@
 #define ARPHDR_PHONET_PIPE (821)
 #endif
 
-#define print(arg...) do { } while (0)
+#define print(arg...) do { if (0) connman_info(arg); } while (0)
 //#define print(arg...) connman_info(arg)
 
 struct watch_data {
@@ -109,7 +110,7 @@ static connman_bool_t wext_interface(char *ifname)
        struct iwreq wrq;
        int fd, err;
 
-       fd = socket(PF_INET, SOCK_DGRAM, 0);
+       fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        if (fd < 0)
                return FALSE;
 
@@ -243,7 +244,7 @@ unsigned int connman_rtnl_add_newlink_watch(int index,
        DBG("id %d", watch->id);
 
        if (callback) {
-               unsigned int flags = __connman_ipconfig_get_flags(index);
+               unsigned int flags = __connman_ipconfig_get_flags_from_index(index);
 
                if (flags > 0)
                        callback(flags, 0, user_data);
@@ -283,14 +284,16 @@ static void trigger_rtnl(int index, void *user_data)
        struct connman_rtnl *rtnl = user_data;
 
        if (rtnl->newlink) {
-               unsigned short type = __connman_ipconfig_get_type(index);
-               unsigned int flags = __connman_ipconfig_get_flags(index);
+               unsigned short type = __connman_ipconfig_get_type_from_index(index);
+               unsigned int flags = __connman_ipconfig_get_flags_from_index(index);
 
                rtnl->newlink(type, index, flags, 0);
        }
 
        if (rtnl->newgateway) {
-               const char *gateway = __connman_ipconfig_get_gateway(index);
+               const char *gateway =
+                       __connman_ipconfig_get_gateway_from_index(index,
+                                       CONNMAN_IPCONFIG_TYPE_ALL);
 
                if (gateway != NULL)
                        rtnl->newgateway(index, gateway);
@@ -578,9 +581,6 @@ static void process_newaddr(unsigned char family, unsigned char prefixlen,
        void *src;
        char ip_string[INET6_ADDRSTRLEN];
 
-       if (family != AF_INET && family != AF_INET6)
-               return;
-
        if (family == AF_INET) {
                struct in_addr ipv4_addr = { INADDR_ANY };
 
@@ -594,6 +594,8 @@ static void process_newaddr(unsigned char family, unsigned char prefixlen,
                        return;
 
                src = &ipv6_address;
+       } else {
+               return;
        }
 
        if (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL)
@@ -601,6 +603,20 @@ static void process_newaddr(unsigned char family, unsigned char prefixlen,
 
        __connman_ipconfig_newaddr(index, family, label,
                                        prefixlen, ip_string);
+
+       if (family == AF_INET6) {
+               /*
+                * Re-create RDNSS configured servers if there are any
+                * for this interface. This is done because we might
+                * have now properly configured interface with proper
+                * autoconfigured address.
+                */
+               char *interface = connman_inet_ifname(index);
+
+               __connman_resolver_redo_servers(interface);
+
+               g_free(interface);
+       }
 }
 
 static void process_deladdr(unsigned char family, unsigned char prefixlen,
@@ -610,9 +626,6 @@ static void process_deladdr(unsigned char family, unsigned char prefixlen,
        void *src;
        char ip_string[INET6_ADDRSTRLEN];
 
-       if (family != AF_INET && family != AF_INET6)
-               return;
-
        if (family == AF_INET) {
                struct in_addr ipv4_addr = { INADDR_ANY };
 
@@ -626,6 +639,8 @@ static void process_deladdr(unsigned char family, unsigned char prefixlen,
                        return;
 
                src = &ipv6_address;
+       } else {
+               return;
        }
 
        if (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL)
@@ -635,7 +650,7 @@ static void process_deladdr(unsigned char family, unsigned char prefixlen,
                                        prefixlen, ip_string);
 }
 
-static void extract_route(struct rtmsg *msg, int bytes, int *index,
+static void extract_ipv4_route(struct rtmsg *msg, int bytes, int *index,
                                                struct in_addr *dst,
                                                struct in_addr *gateway)
 {
@@ -660,30 +675,79 @@ static void extract_route(struct rtmsg *msg, int bytes, int *index,
        }
 }
 
+static void extract_ipv6_route(struct rtmsg *msg, int bytes, int *index,
+                                               struct in6_addr *dst,
+                                               struct in6_addr *gateway)
+{
+       struct rtattr *attr;
+
+       for (attr = RTM_RTA(msg); RTA_OK(attr, bytes);
+                                       attr = RTA_NEXT(attr, bytes)) {
+               switch (attr->rta_type) {
+               case RTA_DST:
+                       if (dst != NULL)
+                               *dst = *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               case RTA_GATEWAY:
+                       if (gateway != NULL)
+                               *gateway =
+                                       *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               case RTA_OIF:
+                       if (index != NULL)
+                               *index = *((int *) RTA_DATA(attr));
+                       break;
+               }
+       }
+}
+
 static void process_newroute(unsigned char family, unsigned char scope,
                                                struct rtmsg *msg, int bytes)
 {
        GSList *list;
-       struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
-       char dststr[16], gatewaystr[16];
+       char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN];
        int index = -1;
 
-       if (family != AF_INET)
-               return;
+       if (family == AF_INET) {
+               struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
 
-       extract_route(msg, bytes, &index, &dst, &gateway);
+               extract_ipv4_route(msg, bytes, &index, &dst, &gateway);
 
-       inet_ntop(family, &dst, dststr, sizeof(dststr));
-       inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
+               inet_ntop(family, &dst, dststr, sizeof(dststr));
+               inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
 
-       __connman_ipconfig_newroute(index, family, scope, dststr, gatewaystr);
+               __connman_ipconfig_newroute(index, family, scope, dststr,
+                                                               gatewaystr);
 
-       /* skip host specific routes */
-       if (scope != RT_SCOPE_UNIVERSE &&
+               /* skip host specific routes */
+               if (scope != RT_SCOPE_UNIVERSE &&
                        !(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY))
-               return;
+                       return;
+
+               if (dst.s_addr != INADDR_ANY)
+                       return;
+
+       } else if (family == AF_INET6) {
+               struct in6_addr dst = IN6ADDR_ANY_INIT,
+                               gateway = IN6ADDR_ANY_INIT;
 
-       if (dst.s_addr != INADDR_ANY)
+               extract_ipv6_route(msg, bytes, &index, &dst, &gateway);
+
+               inet_ntop(family, &dst, dststr, sizeof(dststr));
+               inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
+
+               __connman_ipconfig_newroute(index, family, scope, dststr,
+                                                               gatewaystr);
+
+               /* skip host specific routes */
+               if (scope != RT_SCOPE_UNIVERSE &&
+                       !(scope == RT_SCOPE_LINK &&
+                               IN6_IS_ADDR_UNSPECIFIED(&dst)))
+                       return;
+
+               if (!IN6_IS_ADDR_UNSPECIFIED(&dst))
+                       return;
+       } else
                return;
 
        for (list = rtnl_list; list; list = list->next) {
@@ -698,26 +762,49 @@ static void process_delroute(unsigned char family, unsigned char scope,
                                                struct rtmsg *msg, int bytes)
 {
        GSList *list;
-       struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
-       char dststr[16], gatewaystr[16];
+       char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN];
        int index = -1;
 
-       if (family != AF_INET)
-               return;
+       if (family == AF_INET) {
+               struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
 
-       extract_route(msg, bytes, &index, &dst, &gateway);
+               extract_ipv4_route(msg, bytes, &index, &dst, &gateway);
 
-       inet_ntop(family, &dst, dststr, sizeof(dststr));
-       inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
+               inet_ntop(family, &dst, dststr, sizeof(dststr));
+               inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
 
-       __connman_ipconfig_delroute(index, family, scope, dststr, gatewaystr);
+               __connman_ipconfig_delroute(index, family, scope, dststr,
+                                                               gatewaystr);
 
-       /* skip host specific routes */
-       if (scope != RT_SCOPE_UNIVERSE &&
+               /* skip host specific routes */
+               if (scope != RT_SCOPE_UNIVERSE &&
                        !(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY))
-               return;
+                       return;
+
+               if (dst.s_addr != INADDR_ANY)
+                       return;
+
+       }  else if (family == AF_INET6) {
+               struct in6_addr dst = IN6ADDR_ANY_INIT,
+                               gateway = IN6ADDR_ANY_INIT;
+
+               extract_ipv6_route(msg, bytes, &index, &dst, &gateway);
 
-       if (dst.s_addr != INADDR_ANY)
+               inet_ntop(family, &dst, dststr, sizeof(dststr));
+               inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
+
+               __connman_ipconfig_delroute(index, family, scope, dststr,
+                                               gatewaystr);
+
+               /* skip host specific routes */
+               if (scope != RT_SCOPE_UNIVERSE &&
+                       !(scope == RT_SCOPE_LINK &&
+                               IN6_IS_ADDR_UNSPECIFIED(&dst)))
+                       return;
+
+               if (!IN6_IS_ADDR_UNSPECIFIED(&dst))
+                       return;
+       } else
                return;
 
        for (list = rtnl_list; list; list = list->next) {
@@ -1102,22 +1189,22 @@ static const char **rtnl_nd_opt_dnssl(struct nd_opt_hdr *opt, guint32 *lifetime)
 static void rtnl_newnduseropt(struct nlmsghdr *hdr)
 {
        struct nduseroptmsg *msg = (struct nduseroptmsg *) NLMSG_DATA(hdr);
-       struct nd_opt_hdr *opt = (void *)&msg[1];
+       struct nd_opt_hdr *opt;
        guint32 lifetime = -1;
        const char **domains = NULL;
-       struct in6_addr *servers;
-       int nr_servers = 0;
+       struct in6_addr *servers = NULL;
+       int i, nr_servers = 0;
        int msglen = msg->nduseropt_opts_len;
        char *interface;
 
-       DBG("family %02x index %x len %04x type %02x code %02x",
-           msg->nduseropt_family, msg->nduseropt_ifindex,
-           msg->nduseropt_opts_len, msg->nduseropt_icmp_type,
-           msg->nduseropt_icmp_code);
+       DBG("family %d index %d len %d type %d code %d",
+               msg->nduseropt_family, msg->nduseropt_ifindex,
+               msg->nduseropt_opts_len, msg->nduseropt_icmp_type,
+               msg->nduseropt_icmp_code);
 
        if (msg->nduseropt_family != AF_INET6 ||
-           msg->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
-           msg->nduseropt_icmp_code != 0)
+                       msg->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
+                       msg->nduseropt_icmp_code != 0)
                return;
 
        interface = connman_inet_ifname(msg->nduseropt_ifindex);
@@ -1125,40 +1212,37 @@ static void rtnl_newnduseropt(struct nlmsghdr *hdr)
                return;
 
        for (opt = (void *)&msg[1];
-            msglen >= 2 && msglen >= opt->nd_opt_len && opt->nd_opt_len;
-            msglen -= opt->nd_opt_len,
-                    opt = ((void *)opt) + opt->nd_opt_len*8) {
-
-               DBG("nd opt type %d len %d\n",
-                   opt->nd_opt_type, opt->nd_opt_len);
+                       msglen > 0;
+                       msglen -= opt->nd_opt_len * 8,
+                       opt = ((void *)opt) + opt->nd_opt_len*8) {
 
-               if (opt->nd_opt_type == 25)
-                       servers = rtnl_nd_opt_rdnss(opt, &lifetime,
-                                                   &nr_servers);
-               else if (opt->nd_opt_type == 31)
-                       domains = rtnl_nd_opt_dnssl(opt, &lifetime);
-       }
+               DBG("remaining %d nd opt type %d len %d\n",
+                       msglen, opt->nd_opt_type, opt->nd_opt_len);
 
-       if (nr_servers) {
-               int i, j;
-               char buf[40];
+               if (opt->nd_opt_type == 25) { /* ND_OPT_RDNSS */
+                       char buf[40];
 
-               for (i = 0; i < nr_servers; i++) {
-                       if (!inet_ntop(AF_INET6, servers + i, buf, sizeof(buf)))
-                               continue;
+                       servers = rtnl_nd_opt_rdnss(opt, &lifetime,
+                                                               &nr_servers);
+                       for (i = 0; i < nr_servers; i++) {
+                               if (!inet_ntop(AF_INET6, servers + i, buf,
+                                                               sizeof(buf)))
+                                       continue;
 
-                       if (domains == NULL || domains[0] == NULL) {
                                connman_resolver_append_lifetime(interface,
                                                        NULL, buf, lifetime);
-                               continue;
                        }
 
-                       for (j = 0; domains[j]; j++)
+               } else if (opt->nd_opt_type == 31) { /* ND_OPT_DNSSL */
+                       g_free(domains);
+
+                       domains = rtnl_nd_opt_dnssl(opt, &lifetime);
+                       for (i = 0; domains != NULL && domains[i] != NULL; i++)
                                connman_resolver_append_lifetime(interface,
-                                                               domains[j],
-                                                               buf, lifetime);
+                                               domains[i], NULL, lifetime);
                }
        }
+
        g_free(domains);
        g_free(interface);
 }
@@ -1281,10 +1365,11 @@ static void rtnl_message(void *buf, size_t len)
                if (!NLMSG_OK(hdr, len))
                        break;
 
-               DBG("%s len %d type %d flags 0x%04x seq %d",
+               DBG("%s len %d type %d flags 0x%04x seq %d pid %d",
                                        type2string(hdr->nlmsg_type),
                                        hdr->nlmsg_len, hdr->nlmsg_type,
-                                       hdr->nlmsg_flags, hdr->nlmsg_seq);
+                                       hdr->nlmsg_flags, hdr->nlmsg_seq,
+                                       hdr->nlmsg_pid);
 
                switch (hdr->nlmsg_type) {
                case NLMSG_NOOP:
@@ -1330,27 +1415,37 @@ static gboolean netlink_event(GIOChannel *chan,
                                GIOCondition cond, gpointer data)
 {
        unsigned char buf[4096];
-       gsize len;
-       GIOStatus status;
+       struct sockaddr_nl nladdr;
+       socklen_t addr_len = sizeof(nladdr);
+       ssize_t status;
+       int fd;
 
        if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
                return FALSE;
 
        memset(buf, 0, sizeof(buf));
+       memset(&nladdr, 0, sizeof(nladdr));
 
-       status = g_io_channel_read_chars(chan, (gchar *) buf,
-                                               sizeof(buf), &len, NULL);
+       fd = g_io_channel_unix_get_fd(chan);
 
-       switch (status) {
-       case G_IO_STATUS_NORMAL:
-               break;
-       case G_IO_STATUS_AGAIN:
-               return TRUE;
-       default:
+       status = recvfrom(fd, buf, sizeof(buf), 0,
+                       (struct sockaddr *) &nladdr, &addr_len);
+       if (status < 0) {
+               if (errno == EINTR || errno == EAGAIN)
+                       return TRUE;
+
+               return FALSE;
+       }
+
+       if (status == 0)
                return FALSE;
+
+       if (nladdr.nl_pid != 0) { /* not sent by kernel, ignore */
+               DBG("Received msg from %u, ignoring it", nladdr.nl_pid);
+               return TRUE;
        }
 
-       rtnl_message(buf, len);
+       rtnl_message(buf, status);
 
        return TRUE;
 }
@@ -1497,7 +1592,7 @@ int __connman_rtnl_init(void)
        interface_list = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                                        NULL, free_interface);
 
-       sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+       sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
        if (sk < 0)
                return -1;
 
@@ -1515,6 +1610,9 @@ int __connman_rtnl_init(void)
        channel = g_io_channel_unix_new(sk);
        g_io_channel_set_close_on_unref(channel, TRUE);
 
+       g_io_channel_set_encoding(channel, NULL, NULL);
+       g_io_channel_set_buffered(channel, FALSE);
+
        g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
                                                        netlink_event, NULL);