notifier: Limit Counter Underflow
[framework/connectivity/connman.git] / src / rtnl.c
index b10d835..6002b5b 100644 (file)
@@ -45,7 +45,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 {
@@ -243,7 +243,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 +283,14 @@ 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);
 
                if (gateway != NULL)
                        rtnl->newgateway(index, gateway);
@@ -462,6 +462,9 @@ static void process_newlink(unsigned short type, int index, unsigned flags,
 
                if (type == ARPHRD_ETHER)
                        read_uevent(interface);
+
+               __connman_technology_add_interface(interface->service_type,
+                       interface->index, interface->name, interface->ident);
        }
 
        for (list = rtnl_list; list; list = list->next) {
@@ -471,9 +474,6 @@ static void process_newlink(unsigned short type, int index, unsigned flags,
                        rtnl->newlink(type, index, flags, change);
        }
 
-       __connman_technology_add_interface(interface->service_type,
-                       interface->index, interface->name, interface->ident);
-
        for (list = watch_list; list; list = list->next) {
                struct watch_data *watch = list->data;
 
@@ -578,9 +578,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 +591,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)
@@ -610,9 +609,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 +622,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 +633,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 +658,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;
+
+               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_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 +745,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) {
@@ -1024,32 +1094,89 @@ static void rtnl_delroute(struct nlmsghdr *hdr)
                                                msg, RTM_PAYLOAD(hdr));
 }
 
-static void rtnl_nd_opt_rdnss(char *interface, struct nd_opt_hdr *opt)
+static void *rtnl_nd_opt_rdnss(struct nd_opt_hdr *opt, guint32 *lifetime,
+                              int *nr_servers)
 {
        guint32 *optint = (void *)opt;
-       guint32 lifetime;
-       char buf[80];
-       int i;
 
        if (opt->nd_opt_len < 3)
-               return;
+               return NULL;
 
-       lifetime = ntohl(optint[1]);
-       if (!lifetime)
-               return;
+       if (*lifetime > ntohl(optint[1]))
+               *lifetime = ntohl(optint[1]);
+
+       /* nd_opt_len is in units of 8 bytes. The header is 1 unit (8 bytes)
+          and each address is another 2 units (16 bytes).
+          So the number of addresses (given rounding) is nd_opt_len/2 */
+       *nr_servers = opt->nd_opt_len / 2;
 
-       optint += 2;
-       for (i = 0; i < opt->nd_opt_len / 2; i++, optint += 4) {
-               if (inet_ntop(AF_INET6, optint, buf, sizeof(buf)))
-                       connman_resolver_append_lifetime(interface, NULL,
-                                                        buf, lifetime);
+       /* And they start 8 bytes into the packet, or two guint32s in. */
+       return optint + 2;
+}
+
+static const char **rtnl_nd_opt_dnssl(struct nd_opt_hdr *opt, guint32 *lifetime)
+{
+       const char **domains = NULL;
+       guint32 *optint = (void *)opt;
+       unsigned char *optc = (void *)&optint[2];
+       int data_len = (opt->nd_opt_len * 8) - 8;
+       int nr_domains = 0;
+       int i, tmp;
+
+       if (*lifetime > ntohl(optint[1]))
+               *lifetime = ntohl(optint[1]);
+
+       /* Turn it into normal strings by converting the length bytes into '.',
+          and count how many search domains there are while we're at it. */
+       i = 0;
+       while (i < data_len) {
+               if (optc[i] > 0x3f) {
+                       DBG("DNSSL contains compressed elements in violation of RFC6106");
+                       return NULL;
+               }
+
+               if (optc[i] == 0) {
+                       nr_domains++;
+                       i++;
+                       /* Check for double zero */
+                       if (i < data_len && optc[i] == 0)
+                               break;
+                       continue;
+               }
+
+               tmp = i;
+               i += optc[i] + 1;
+
+               if (i >= data_len) {
+                       DBG("DNSSL data overflows option length");
+                       return NULL;
+               }
+
+               optc[tmp] = '.';
        }
+
+       domains = g_try_new0(const char *, nr_domains + 1);
+       if (!domains)
+               return NULL;
+
+       /* Now point to the normal strings, missing out the leading '.' that
+          each of them will have now. */
+       for (i = 0; i < nr_domains; i++) {
+               domains[i] = (char *)optc + 1;
+               optc += strlen((char *)optc) + 1;
+       }
+
+       return domains;
 }
 
 static void rtnl_newnduseropt(struct nlmsghdr *hdr)
 {
        struct nduseroptmsg *msg = (struct nduseroptmsg *) NLMSG_DATA(hdr);
        struct nd_opt_hdr *opt = (void *)&msg[1];
+       guint32 lifetime = -1;
+       const char **domains = NULL;
+       struct in6_addr *servers = NULL;
+       int nr_servers = 0;
        int msglen = msg->nduseropt_opts_len;
        char *interface;
 
@@ -1076,8 +1203,33 @@ static void rtnl_newnduseropt(struct nlmsghdr *hdr)
                    opt->nd_opt_type, opt->nd_opt_len);
 
                if (opt->nd_opt_type == 25)
-                       rtnl_nd_opt_rdnss(interface, opt);
+                       servers = rtnl_nd_opt_rdnss(opt, &lifetime,
+                                                   &nr_servers);
+               else if (opt->nd_opt_type == 31)
+                       domains = rtnl_nd_opt_dnssl(opt, &lifetime);
        }
+
+       if (nr_servers) {
+               int i, j;
+               char buf[40];
+
+               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++)
+                               connman_resolver_append_lifetime(interface,
+                                                               domains[j],
+                                                               buf, lifetime);
+               }
+       }
+       g_free(domains);
        g_free(interface);
 }
 
@@ -1249,17 +1401,22 @@ static gboolean netlink_event(GIOChannel *chan,
 {
        unsigned char buf[4096];
        gsize len;
-       GIOError err;
+       GIOStatus status;
 
        if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
                return FALSE;
 
        memset(buf, 0, sizeof(buf));
 
-       err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
-       if (err) {
-               if (err == G_IO_ERROR_AGAIN)
-                       return TRUE;
+       status = g_io_channel_read_chars(chan, (gchar *) buf,
+                                               sizeof(buf), &len, NULL);
+
+       switch (status) {
+       case G_IO_STATUS_NORMAL:
+               break;
+       case G_IO_STATUS_AGAIN:
+               return TRUE;
+       default:
                return FALSE;
        }
 
@@ -1428,6 +1585,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);