[connman] Added Tizen Wi-Fi Mesh
[platform/upstream/connman.git] / src / rtnl.c
index ec163a0..d733288 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2007-2013  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
 #include <config.h>
 #endif
 
+#include <errno.h>
+#include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
 #include <arpa/inet.h>
 #include <netinet/ether.h>
-#include <net/if.h>
+#include <netinet/icmp6.h>
 #include <net/if_arp.h>
+#include <linux/if.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
+#include <linux/wireless.h>
 
 #include <glib.h>
 
 #include "connman.h"
 
-#define print(arg...) do { } while (0)
+#ifndef ARPHDR_PHONET_PIPE
+#define ARPHDR_PHONET_PIPE (821)
+#endif
+
+#if defined TIZEN_EXT
+#ifndef ARPHDR_RMNET
+#define ARPHDR_RMNET (530)
+#endif
+#endif
+
+#define print(arg...) do { if (0) connman_info(arg); } while (0)
 //#define print(arg...) connman_info(arg)
 
 struct watch_data {
@@ -50,6 +65,195 @@ struct watch_data {
 static GSList *watch_list = NULL;
 static unsigned int watch_id = 0;
 
+static GSList *update_list = NULL;
+static guint update_interval = G_MAXUINT;
+static guint update_timeout = 0;
+
+struct interface_data {
+       int index;
+       char *ident;
+       enum connman_service_type service_type;
+       enum connman_device_type device_type;
+};
+
+static GHashTable *interface_list = NULL;
+
+static void free_interface(gpointer data)
+{
+       struct interface_data *interface = data;
+
+       __connman_technology_remove_interface(interface->service_type,
+                       interface->index, interface->ident);
+
+       g_free(interface->ident);
+       g_free(interface);
+}
+
+static bool ether_blacklisted(const char *name)
+{
+       if (!name)
+               return true;
+
+       if (__connman_device_isfiltered(name))
+               return true;
+
+       return false;
+}
+
+#if !defined TIZEN_EXT
+static bool wext_interface(char *ifname)
+{
+       struct iwreq wrq;
+       int fd, err;
+
+       fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+       if (fd < 0)
+               return false;
+
+       memset(&wrq, 0, sizeof(wrq));
+       strncpy(wrq.ifr_name, ifname, sizeof(wrq.ifr_name) - 1);
+
+       err = ioctl(fd, SIOCGIWNAME, &wrq);
+
+       close(fd);
+
+       if (err < 0)
+               return false;
+
+       return true;
+}
+#endif
+
+#if defined TIZEN_EXT
+static bool __connman_rtnl_is_cellular_device(const char *name)
+{
+       char **pattern;
+       char **cellular_interfaces;
+
+       cellular_interfaces =
+                       connman_setting_get_string_list(
+                                       "NetworkCellularInterfaceList");
+       if (!cellular_interfaces)
+               return false;
+
+       for (pattern = cellular_interfaces; *pattern; pattern++) {
+               if (g_str_has_prefix(name, *pattern)) {
+                       DBG("Cellular interface: %s", name);
+                       return true;
+               }
+       }
+
+       return false;
+}
+#endif
+
+static void read_uevent(struct interface_data *interface)
+{
+       char *filename, *name, line[128];
+       bool found_devtype;
+       FILE *f;
+
+       name = connman_inet_ifname(interface->index);
+
+#if defined TIZEN_EXT
+       if (__connman_rtnl_is_cellular_device(name)) {
+               interface->service_type = CONNMAN_SERVICE_TYPE_CELLULAR;
+               interface->device_type = CONNMAN_DEVICE_TYPE_CELLULAR;
+               g_free(name);
+               return;
+       }
+#endif
+
+       if (ether_blacklisted(name)) {
+               interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
+               interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
+       } else {
+               interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
+               interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
+       }
+
+       filename = g_strdup_printf("/sys/class/net/%s/uevent", name);
+
+       f = fopen(filename, "re");
+
+       g_free(filename);
+
+       if (!f) {
+               interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
+               interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
+               goto out;
+       }
+
+       found_devtype = false;
+       while (fgets(line, sizeof(line), f)) {
+               char *pos;
+
+               pos = strchr(line, '\n');
+               if (!pos)
+                       continue;
+               pos[0] = '\0';
+
+               if (strncmp(line, "DEVTYPE=", 8) != 0)
+                       continue;
+
+               found_devtype = true;
+
+               if (strcmp(line + 8, "wlan") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_WIFI;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_WIFI;
+               } else if (strcmp(line + 8, "wwan") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_CELLULAR;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_CELLULAR;
+               } else if (strcmp(line + 8, "bluetooth") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_BLUETOOTH;
+               } else if (strcmp(line + 8, "gadget") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_GADGET;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_GADGET;
+               } else if (strcmp(line + 8, "vlan") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
+               } else if (strcmp(line + 8, "bond") == 0) {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
+               } else {
+                       interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
+                       interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
+               }
+       }
+
+       fclose(f);
+
+       if (found_devtype)
+               goto out;
+
+#if !defined TIZEN_EXT
+       /* TIZEN does not use old wext interface */
+       /* We haven't got a DEVTYPE, let's check if it's a wireless device */
+       if (wext_interface(name)) {
+               interface->service_type = CONNMAN_SERVICE_TYPE_WIFI;
+               interface->device_type = CONNMAN_DEVICE_TYPE_WIFI;
+
+               connman_error("%s runs an unsupported 802.11 driver", name);
+       }
+#endif
+
+out:
+       g_free(name);
+}
+
+enum connman_device_type __connman_rtnl_get_device_type(int index)
+{
+       struct interface_data *interface;
+
+       interface = g_hash_table_lookup(interface_list,
+                                       GINT_TO_POINTER(index));
+       if (!interface)
+               return CONNMAN_DEVICE_TYPE_UNKNOWN;
+
+       return interface->device_type;
+}
+
 /**
  * connman_rtnl_add_newlink_watch:
  * @index: network device index
@@ -66,7 +270,7 @@ unsigned int connman_rtnl_add_newlink_watch(int index,
        struct watch_data *watch;
 
        watch = g_try_new0(struct watch_data, 1);
-       if (watch == NULL)
+       if (!watch)
                return 0;
 
        watch->id = ++watch_id;
@@ -80,7 +284,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);
@@ -120,16 +324,18 @@ 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)
+               if (gateway)
                        rtnl->newgateway(index, gateway);
        }
 }
@@ -177,19 +383,196 @@ void connman_rtnl_unregister(struct connman_rtnl *rtnl)
        rtnl_list = g_slist_remove(rtnl_list, rtnl);
 }
 
-static void process_newlink(unsigned short type, int index,
-                                       unsigned flags, unsigned change)
+static const char *operstate2str(unsigned char operstate)
+{
+       switch (operstate) {
+       case IF_OPER_UNKNOWN:
+               return "UNKNOWN";
+       case IF_OPER_NOTPRESENT:
+               return "NOT-PRESENT";
+       case IF_OPER_DOWN:
+               return "DOWN";
+       case IF_OPER_LOWERLAYERDOWN:
+               return "LOWER-LAYER-DOWN";
+       case IF_OPER_TESTING:
+               return "TESTING";
+       case IF_OPER_DORMANT:
+               return "DORMANT";
+       case IF_OPER_UP:
+               return "UP";
+       }
+
+       return "";
+}
+
+static bool extract_link(struct ifinfomsg *msg, int bytes,
+                               struct ether_addr *address, const char **ifname,
+                               unsigned int *mtu, unsigned char *operstate,
+                               struct rtnl_link_stats *stats)
+{
+       struct rtattr *attr;
+
+       for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes);
+                                       attr = RTA_NEXT(attr, bytes)) {
+               switch (attr->rta_type) {
+               case IFLA_ADDRESS:
+                       if (address)
+                               memcpy(address, RTA_DATA(attr), ETH_ALEN);
+                       break;
+               case IFLA_IFNAME:
+                       if (ifname)
+                               *ifname = RTA_DATA(attr);
+                       break;
+               case IFLA_MTU:
+                       if (mtu)
+                               *mtu = *((unsigned int *) RTA_DATA(attr));
+                       break;
+               case IFLA_STATS:
+                       if (stats)
+                               memcpy(stats, RTA_DATA(attr),
+                                       sizeof(struct rtnl_link_stats));
+                       break;
+               case IFLA_OPERSTATE:
+                       if (operstate)
+                               *operstate = *((unsigned char *) RTA_DATA(attr));
+                       break;
+               case IFLA_LINKMODE:
+                       break;
+               case IFLA_WIRELESS:
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static void process_newlink(unsigned short type, int index, unsigned flags,
+                       unsigned change, struct ifinfomsg *msg, int bytes)
 {
+       struct ether_addr address = {{ 0, 0, 0, 0, 0, 0 }};
+       struct rtnl_link_stats stats;
+       unsigned char operstate = 0xff;
+       struct interface_data *interface;
+       const char *ifname = NULL;
+       unsigned int mtu = 0;
+       char ident[13], str[18];
        GSList *list;
 
+       memset(&stats, 0, sizeof(stats));
+       if (!extract_link(msg, bytes, &address, &ifname, &mtu, &operstate, &stats))
+               return;
+
+#if defined TIZEN_EXT_WIFI_MESH
+       /* Do not accept Wi-Fi Mesh interface */
+       if (g_strrstr(ifname, "mesh") != NULL) {
+               DBG("Newlink event for Wi-Fi Mesh interface ignored");
+               return;
+       }
+
+       /* Do not accept Wi-Fi WLAN1 interface "dedicated for softAP */
+       if (!g_strcmp0(ifname, "wlan1")) {
+               DBG("Newlink event for Wi-Fi WLAN1 interface ignored");
+               return;
+       }
+#endif
+
+#if defined TIZEN_EXT
+       /* Do not accept Wi-Fi P2P interface */
+       if (g_strrstr(ifname, "p2p") != NULL) {
+               DBG("Newlink event for Wi-Fi P2P interface ignored");
+               return;
+       }
+#endif
+
+       snprintf(ident, 13, "%02x%02x%02x%02x%02x%02x",
+                                               address.ether_addr_octet[0],
+                                               address.ether_addr_octet[1],
+                                               address.ether_addr_octet[2],
+                                               address.ether_addr_octet[3],
+                                               address.ether_addr_octet[4],
+                                               address.ether_addr_octet[5]);
+
+       snprintf(str, 18, "%02X:%02X:%02X:%02X:%02X:%02X",
+                                               address.ether_addr_octet[0],
+                                               address.ether_addr_octet[1],
+                                               address.ether_addr_octet[2],
+                                               address.ether_addr_octet[3],
+                                               address.ether_addr_octet[4],
+                                               address.ether_addr_octet[5]);
+
+       if (flags & IFF_SLAVE) {
+               connman_info("%s {newlink} ignoring slave, index %d address %s",
+                                               ifname, index, str);
+               return;
+       }
+
+#ifdef TIZEN_EXT
+       if (TIZEN_TV_EXT && g_strcmp0(ident, "eeeeeeeeeeee") == 0) {
+               DBG("Newlink event with Dummy MAC. Ignored!");
+               return;
+       }
+#endif
+
        switch (type) {
        case ARPHRD_ETHER:
        case ARPHRD_LOOPBACK:
+       case ARPHDR_PHONET_PIPE:
+       case ARPHRD_PPP:
        case ARPHRD_NONE:
-               __connman_ipconfig_newlink(index, type, flags);
+#if defined TIZEN_EXT
+/*
+ * Description: ARPHDR_RMNET for QC modem using QMI
+ */
+       case ARPHDR_RMNET:
+#endif
+               __connman_ipconfig_newlink(index, type, flags,
+                                                       str, mtu, &stats);
                break;
        }
 
+       connman_info("%s {newlink} index %d address %s mtu %u",
+                                       ifname, index, str, mtu);
+
+       if (operstate != 0xff)
+               connman_info("%s {newlink} index %d operstate %u <%s>",
+                                               ifname, index, operstate,
+                                               operstate2str(operstate));
+
+       interface = g_hash_table_lookup(interface_list, GINT_TO_POINTER(index));
+       if (!interface) {
+               interface = g_new0(struct interface_data, 1);
+               interface->index = index;
+               interface->ident = g_strdup(ident);
+
+               g_hash_table_insert(interface_list,
+                                       GINT_TO_POINTER(index), interface);
+
+               if (type == ARPHRD_ETHER)
+                       read_uevent(interface);
+#if defined TIZEN_EXT
+               if (type == ARPHRD_PPP || type == ARPHDR_RMNET)
+                       read_uevent(interface);
+
+       } else if (g_strcmp0(interface->ident, ident) != 0) {
+               /* If an original address is built-in physical device,
+                * it's hardly get an address at a initial creation
+                */
+               __connman_technology_remove_interface(interface->service_type,
+                               interface->index, interface->ident);
+
+               g_free(interface->ident);
+               interface->ident = g_strdup(ident);
+
+               __connman_technology_add_interface(interface->service_type,
+                               interface->index, interface->ident);
+
+               interface = NULL;
+#endif
+       } else if (type == ARPHRD_ETHER && interface->device_type == CONNMAN_DEVICE_TYPE_UNKNOWN)
+               read_uevent(interface);
+       else
+               interface = NULL;
+
        for (list = rtnl_list; list; list = list->next) {
                struct connman_rtnl *rtnl = list->data;
 
@@ -197,6 +580,16 @@ static void process_newlink(unsigned short type, int index,
                        rtnl->newlink(type, index, flags, change);
        }
 
+       /*
+        * The interface needs to be added after the newlink call.
+        * The newlink will create the technology when needed and
+        * __connman_technology_add_interface() expects the
+        * technology to be there already.
+        */
+       if (interface)
+               __connman_technology_add_interface(interface->service_type,
+                       interface->index, interface->ident);
+
        for (list = watch_list; list; list = list->next) {
                struct watch_data *watch = list->data;
 
@@ -208,11 +601,23 @@ static void process_newlink(unsigned short type, int index,
        }
 }
 
-static void process_dellink(unsigned short type, int index,
-                                       unsigned flags, unsigned change)
+static void process_dellink(unsigned short type, int index, unsigned flags,
+                       unsigned change, struct ifinfomsg *msg, int bytes)
 {
+       struct rtnl_link_stats stats;
+       unsigned char operstate = 0xff;
+       const char *ifname = NULL;
        GSList *list;
 
+       memset(&stats, 0, sizeof(stats));
+       if (!extract_link(msg, bytes, NULL, &ifname, NULL, &operstate, &stats))
+               return;
+
+       if (operstate != 0xff)
+               connman_info("%s {dellink} index %d operstate %u <%s>",
+                                               ifname, index, operstate,
+                                               operstate2str(operstate));
+
        for (list = rtnl_list; list; list = list->next) {
                struct connman_rtnl *rtnl = list->data;
 
@@ -223,13 +628,24 @@ static void process_dellink(unsigned short type, int index,
        switch (type) {
        case ARPHRD_ETHER:
        case ARPHRD_LOOPBACK:
+       case ARPHDR_PHONET_PIPE:
+       case ARPHRD_PPP:
        case ARPHRD_NONE:
-               __connman_ipconfig_dellink(index);
+#if defined TIZEN_EXT
+       /*
+        * Description: SLP requires ARPHRD_PPP for PPP type device
+        *              ARPHDR_RMNET for QC modem using QMI
+        */
+       case ARPHDR_RMNET:
+#endif
+               __connman_ipconfig_dellink(index, &stats);
                break;
        }
+
+       g_hash_table_remove(interface_list, GINT_TO_POINTER(index));
 }
 
-static void extract_addr(struct ifaddrmsg *msg, int bytes,
+static void extract_ipv4_addr(struct ifaddrmsg *msg, int bytes,
                                                const char **label,
                                                struct in_addr *local,
                                                struct in_addr *address,
@@ -241,56 +657,117 @@ static void extract_addr(struct ifaddrmsg *msg, int bytes,
                                        attr = RTA_NEXT(attr, bytes)) {
                switch (attr->rta_type) {
                case IFA_ADDRESS:
-                       if (address != NULL)
+                       if (address)
                                *address = *((struct in_addr *) RTA_DATA(attr));
                        break;
                case IFA_LOCAL:
-                       if (local != NULL)
+                       if (local)
                                *local = *((struct in_addr *) RTA_DATA(attr));
                        break;
                case IFA_BROADCAST:
-                       if (broadcast != NULL)
+                       if (broadcast)
                                *broadcast = *((struct in_addr *) RTA_DATA(attr));
                        break;
                case IFA_LABEL:
-                       if (label != NULL)
+                       if (label)
                                *label = RTA_DATA(attr);
                        break;
                }
        }
 }
 
+static void extract_ipv6_addr(struct ifaddrmsg *msg, int bytes,
+                                               struct in6_addr *addr,
+                                               struct in6_addr *local)
+{
+       struct rtattr *attr;
+
+       for (attr = IFA_RTA(msg); RTA_OK(attr, bytes);
+                                       attr = RTA_NEXT(attr, bytes)) {
+               switch (attr->rta_type) {
+               case IFA_ADDRESS:
+                       if (addr)
+                               *addr = *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               case IFA_LOCAL:
+                       if (local)
+                               *local = *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               }
+       }
+}
+
 static void process_newaddr(unsigned char family, unsigned char prefixlen,
                                int index, struct ifaddrmsg *msg, int bytes)
 {
-       const char *label;
-       struct in_addr address = { INADDR_ANY };
+       struct in_addr ipv4_addr = { INADDR_ANY };
+       struct in6_addr ipv6_address, ipv6_local;
+       const char *label = NULL;
+       void *src;
+       char ip_string[INET6_ADDRSTRLEN];
+
+       if (family == AF_INET) {
+
+               extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL);
+               src = &ipv4_addr;
+       } else if (family == AF_INET6) {
+               extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local);
+               if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address))
+                       return;
 
-       if (family != AF_INET)
+               src = &ipv6_address;
+       } else {
                return;
+       }
 
-       extract_addr(msg, bytes, &label, &address, NULL, NULL);
+       if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN))
+               return;
 
-       __connman_ipconfig_newaddr(index, label,
-                                       prefixlen, inet_ntoa(address));
+       if (__connman_ipconfig_newaddr(index, family, label,
+                                       prefixlen, ip_string) >= 0) {
+               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.
+                        */
+                       __connman_resolver_redo_servers(index);
+               }
+       }
 }
 
 static void process_deladdr(unsigned char family, unsigned char prefixlen,
                                int index, struct ifaddrmsg *msg, int bytes)
 {
-       const char *label;
-       struct in_addr address = { INADDR_ANY };
+       struct in_addr ipv4_addr = { INADDR_ANY };
+       struct in6_addr ipv6_address, ipv6_local;
+       const char *label = NULL;
+       void *src;
+       char ip_string[INET6_ADDRSTRLEN];
+
+       if (family == AF_INET) {
+               extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL);
+               src = &ipv4_addr;
+       } else if (family == AF_INET6) {
+               extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local);
+               if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address))
+                       return;
 
-       if (family != AF_INET)
+               src = &ipv6_address;
+       } else {
                return;
+       }
 
-       extract_addr(msg, bytes, &label, &address, NULL, NULL);
+       if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN))
+               return;
 
-       __connman_ipconfig_deladdr(index, label,
-                                       prefixlen, inet_ntoa(address));
+       __connman_ipconfig_deladdr(index, family, label,
+                                       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)
 {
@@ -300,15 +777,41 @@ static void extract_route(struct rtmsg *msg, int bytes, int *index,
                                        attr = RTA_NEXT(attr, bytes)) {
                switch (attr->rta_type) {
                case RTA_DST:
-                       if (dst != NULL)
+                       if (dst)
                                *dst = *((struct in_addr *) RTA_DATA(attr));
                        break;
                case RTA_GATEWAY:
-                       if (gateway != NULL)
+                       if (gateway)
                                *gateway = *((struct in_addr *) RTA_DATA(attr));
                        break;
                case RTA_OIF:
-                       if (index != NULL)
+                       if (index)
+                               *index = *((int *) RTA_DATA(attr));
+                       break;
+               }
+       }
+}
+
+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)
+                               *dst = *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               case RTA_GATEWAY:
+                       if (gateway)
+                               *gateway =
+                                       *((struct in6_addr *) RTA_DATA(attr));
+                       break;
+               case RTA_OIF:
+                       if (index)
                                *index = *((int *) RTA_DATA(attr));
                        break;
                }
@@ -319,21 +822,49 @@ 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_ipv4_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 && dst.s_addr == INADDR_ANY))
+                       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_route(msg, bytes, &index, &dst, &gateway);
+               extract_ipv6_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, scope, dststr, gatewaystr);
+               __connman_ipconfig_newroute(index, family, scope, dststr,
+                                                               gatewaystr);
 
-       if (scope != RT_SCOPE_UNIVERSE || dst.s_addr != INADDR_ANY)
+               /* 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) {
@@ -348,21 +879,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_ipv4_route(msg, bytes, &index, &dst, &gateway);
+
+               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 && dst.s_addr == INADDR_ANY))
+                       return;
 
-       extract_route(msg, bytes, &index, &dst, &gateway);
+               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);
 
-       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, scope, dststr, 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 (scope != RT_SCOPE_UNIVERSE || dst.s_addr != INADDR_ANY)
+               if (!IN6_IS_ADDR_UNSPECIFIED(&dst))
+                       return;
+       } else
                return;
 
        for (list = rtnl_list; list; list = list->next) {
@@ -504,8 +1063,11 @@ static void rtnl_newlink(struct nlmsghdr *hdr)
 
        rtnl_link(hdr);
 
-       process_newlink(msg->ifi_type, msg->ifi_index,
-                                       msg->ifi_flags, msg->ifi_change);
+       if (hdr->nlmsg_type == IFLA_WIRELESS)
+               connman_warn_once("Obsolete WEXT WiFi driver detected");
+
+       process_newlink(msg->ifi_type, msg->ifi_index, msg->ifi_flags,
+                               msg->ifi_change, msg, IFA_PAYLOAD(hdr));
 }
 
 static void rtnl_dellink(struct nlmsghdr *hdr)
@@ -514,8 +1076,8 @@ static void rtnl_dellink(struct nlmsghdr *hdr)
 
        rtnl_link(hdr);
 
-       process_dellink(msg->ifi_type, msg->ifi_index,
-                                       msg->ifi_flags, msg->ifi_change);
+       process_dellink(msg->ifi_type, msg->ifi_index, msg->ifi_flags,
+                               msg->ifi_change, msg, IFA_PAYLOAD(hdr));
 }
 
 static void rtnl_addr(struct nlmsghdr *hdr)
@@ -631,15 +1193,31 @@ static void rtnl_route(struct nlmsghdr *hdr)
        }
 }
 
+static bool is_route_rtmsg(struct rtmsg *msg)
+{
+       if (msg->rtm_flags & RTM_F_CLONED)
+               return false;
+
+       if (msg->rtm_table != RT_TABLE_MAIN)
+               return false;
+
+       if (msg->rtm_protocol != RTPROT_BOOT &&
+                       msg->rtm_protocol != RTPROT_KERNEL)
+               return false;
+
+       if (msg->rtm_type != RTN_UNICAST)
+               return false;
+
+       return true;
+}
+
 static void rtnl_newroute(struct nlmsghdr *hdr)
 {
        struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr);
 
        rtnl_route(hdr);
 
-       if (msg->rtm_table == RT_TABLE_MAIN &&
-                               msg->rtm_protocol == RTPROT_BOOT &&
-                                               msg->rtm_type == RTN_UNICAST)
+       if (is_route_rtmsg(msg))
                process_newroute(msg->rtm_family, msg->rtm_scope,
                                                msg, RTM_PAYLOAD(hdr));
 }
@@ -650,13 +1228,190 @@ static void rtnl_delroute(struct nlmsghdr *hdr)
 
        rtnl_route(hdr);
 
-       if (msg->rtm_table == RT_TABLE_MAIN &&
-                               msg->rtm_protocol == RTPROT_BOOT &&
-                                               msg->rtm_type == RTN_UNICAST)
+       if (is_route_rtmsg(msg))
                process_delroute(msg->rtm_family, msg->rtm_scope,
                                                msg, RTM_PAYLOAD(hdr));
 }
 
+static void *rtnl_nd_opt_rdnss(struct nd_opt_hdr *opt, guint32 *lifetime,
+                              int *nr_servers)
+{
+       guint32 *optint = (void *)opt;
+
+       if (opt->nd_opt_len < 3)
+               return NULL;
+
+       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;
+
+       /* 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;
+       guint32 lifetime = -1;
+       const char **domains = NULL;
+       struct in6_addr *servers = NULL;
+       int i, nr_servers = 0;
+       int msglen = msg->nduseropt_opts_len;
+       int index;
+
+       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)
+               return;
+
+       index = msg->nduseropt_ifindex;
+       if (index < 0)
+               return;
+
+#if defined TIZEN_EXT
+       struct connman_service *service;
+       enum connman_service_state state;
+       enum connman_dnsconfig_method ipv6_dns_method;
+
+       service = __connman_service_lookup_from_index(index);
+       if (!service) {
+               DBG("Invalid service");
+               return;
+       }
+
+       DBG("service: %p index: %d\n", service, index);
+
+       if (connman_setting_get_bool("SingleConnectedTechnology") == TRUE) {
+               state = __connman_service_ipconfig_get_state(service, CONNMAN_IPCONFIG_TYPE_IPV6);
+               if (state != CONNMAN_SERVICE_STATE_ASSOCIATION &&
+                               state != CONNMAN_SERVICE_STATE_CONFIGURATION &&
+                               state != CONNMAN_SERVICE_STATE_READY &&
+                               state != CONNMAN_SERVICE_STATE_ONLINE) {
+                       DBG("Service state[%d] is not connecting/connected", state);
+                       return;
+               }
+       }
+
+       ipv6_dns_method = connman_service_get_ipv6_dns_method(service);
+       if (ipv6_dns_method != CONNMAN_DNSCONFIG_METHOD_DHCP) {
+               DBG("IPv6 DNS method is not Auto ignore RA!!! [DNS method: %d]", ipv6_dns_method);
+               return;
+       }
+#endif
+
+       for (opt = (void *)&msg[1];
+                       msglen > 0;
+                       msglen -= opt->nd_opt_len * 8,
+                       opt = ((void *)opt) + opt->nd_opt_len*8) {
+
+               DBG("remaining %d nd opt type %d len %d\n",
+                       msglen, opt->nd_opt_type, opt->nd_opt_len);
+
+               if (opt->nd_opt_type == 25) { /* ND_OPT_RDNSS */
+                       char buf[40];
+#if defined TIZEN_EXT
+                       struct connman_service *service;
+
+                       service = __connman_service_lookup_from_index(index);
+                       DBG("service: %p\n",service);
+#endif
+                       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 defined TIZEN_EXT
+                               __connman_service_nameserver_remove(service,
+                                               buf, false,
+                                               CONNMAN_IPCONFIG_TYPE_IPV6);
+                               __connman_service_nameserver_append(service,
+                                               buf, false,
+                                               CONNMAN_IPCONFIG_TYPE_IPV6);
+#endif
+                               connman_resolver_append_lifetime(index,
+                                               NULL, buf, lifetime);
+                       }
+
+               } else if (opt->nd_opt_type == 31) { /* ND_OPT_DNSSL */
+                       g_free(domains);
+
+                       domains = rtnl_nd_opt_dnssl(opt, &lifetime);
+                       for (i = 0; domains && domains[i]; i++)
+                               connman_resolver_append_lifetime(index,
+                                               domains[i], NULL, lifetime);
+               }
+       }
+
+       g_free(domains);
+}
+
 static const char *type2string(uint16_t type)
 {
        switch (type) {
@@ -674,6 +1429,8 @@ static const char *type2string(uint16_t type)
                return "NEWLINK";
        case RTM_DELLINK:
                return "DELLINK";
+       case RTM_GETADDR:
+               return "GETADDR";
        case RTM_NEWADDR:
                return "NEWADDR";
        case RTM_DELADDR:
@@ -684,12 +1441,15 @@ static const char *type2string(uint16_t type)
                return "NEWROUTE";
        case RTM_DELROUTE:
                return "DELROUTE";
+       case RTM_NEWNDUSEROPT:
+               return "NEWNDUSEROPT";
        default:
                return "UNKNOWN";
        }
 }
 
 static GIOChannel *channel = NULL;
+static guint channel_watch = 0;
 
 struct rtnl_request {
        struct nlmsghdr hdr;
@@ -750,13 +1510,13 @@ static int process_response(guint32 seq)
        DBG("seq %d", seq);
 
        req = find_request(seq);
-       if (req != NULL) {
+       if (req) {
                request_list = g_slist_remove(request_list, req);
                g_free(req);
        }
 
        req = g_slist_nth_data(request_list, 0);
-       if (req == NULL)
+       if (!req)
                return 0;
 
        return send_request(req);
@@ -764,8 +1524,6 @@ static int process_response(guint32 seq)
 
 static void rtnl_message(void *buf, size_t len)
 {
-       DBG("buf %p len %zd", buf, len);
-
        while (len > 0) {
                struct nlmsghdr *hdr = buf;
                struct nlmsgerr *err;
@@ -773,10 +1531,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:
@@ -808,6 +1567,9 @@ static void rtnl_message(void *buf, size_t len)
                case RTM_DELROUTE:
                        rtnl_delroute(hdr);
                        break;
+               case RTM_NEWNDUSEROPT:
+                       rtnl_newnduseropt(hdr);
+                       break;
                }
 
                len -= hdr->nlmsg_len;
@@ -815,26 +1577,40 @@ static void rtnl_message(void *buf, size_t len)
        }
 }
 
-static gboolean netlink_event(GIOChannel *chan,
-                               GIOCondition cond, gpointer data)
+static gboolean netlink_event(GIOChannel *chan, GIOCondition cond, gpointer data)
 {
        unsigned char buf[4096];
-       gsize len;
-       GIOError err;
+       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));
+
+       fd = g_io_channel_unix_get_fd(chan);
 
-       err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
-       if (err) {
-               if (err == G_IO_ERROR_AGAIN)
+       status = recvfrom(fd, buf, sizeof(buf), 0,
+                       (struct sockaddr *) &nladdr, &addr_len);
+       if (status < 0) {
+               if (errno == EINTR || errno == EAGAIN)
                        return TRUE;
+
                return FALSE;
        }
 
-       rtnl_message(buf, len);
+       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, status);
 
        return TRUE;
 }
@@ -846,7 +1622,7 @@ static int send_getlink(void)
        DBG("");
 
        req = g_try_malloc0(RTNL_REQUEST_SIZE);
-       if (req == NULL)
+       if (!req)
                return -ENOMEM;
 
        req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
@@ -866,7 +1642,7 @@ static int send_getaddr(void)
        DBG("");
 
        req = g_try_malloc0(RTNL_REQUEST_SIZE);
-       if (req == NULL)
+       if (!req)
                return -ENOMEM;
 
        req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
@@ -886,7 +1662,7 @@ static int send_getroute(void)
        DBG("");
 
        req = g_try_malloc0(RTNL_REQUEST_SIZE);
-       if (req == NULL)
+       if (!req)
                return -ENOMEM;
 
        req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
@@ -899,6 +1675,78 @@ static int send_getroute(void)
        return queue_request(req);
 }
 
+static gboolean update_timeout_cb(gpointer user_data)
+{
+       __connman_rtnl_request_update();
+
+       return TRUE;
+}
+
+static void update_interval_callback(guint min)
+{
+       if (update_timeout > 0)
+               g_source_remove(update_timeout);
+
+       if (min < G_MAXUINT) {
+               update_interval = min;
+               update_timeout = g_timeout_add_seconds(update_interval,
+                                               update_timeout_cb, NULL);
+       } else {
+               update_timeout = 0;
+               update_interval = G_MAXUINT;
+       }
+}
+
+static gint compare_interval(gconstpointer a, gconstpointer b)
+{
+       guint val_a = GPOINTER_TO_UINT(a);
+       guint val_b = GPOINTER_TO_UINT(b);
+
+       return val_a - val_b;
+}
+
+unsigned int __connman_rtnl_update_interval_add(unsigned int interval)
+{
+       guint min;
+
+       if (interval == 0)
+               return 0;
+
+       update_list = g_slist_insert_sorted(update_list,
+                       GUINT_TO_POINTER(interval), compare_interval);
+
+       min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0));
+       if (min < update_interval) {
+               update_interval_callback(min);
+               __connman_rtnl_request_update();
+       }
+
+       return update_interval;
+}
+
+unsigned int __connman_rtnl_update_interval_remove(unsigned int interval)
+{
+       guint min = G_MAXUINT;
+
+       if (interval == 0)
+               return 0;
+
+       update_list = g_slist_remove(update_list, GINT_TO_POINTER(interval));
+
+       if (update_list)
+               min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0));
+
+       if (min > update_interval)
+               update_interval_callback(min);
+
+       return min;
+}
+
+int __connman_rtnl_request_update(void)
+{
+       return send_getlink();
+}
+
 int __connman_rtnl_init(void)
 {
        struct sockaddr_nl addr;
@@ -906,13 +1754,18 @@ int __connman_rtnl_init(void)
 
        DBG("");
 
-       sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+       interface_list = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                                       NULL, free_interface);
+
+       sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
        if (sk < 0)
                return -1;
 
        memset(&addr, 0, sizeof(addr));
        addr.nl_family = AF_NETLINK;
-       addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE;
+       addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
+                               RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE |
+                               (1<<(RTNLGRP_ND_USEROPT-1));
 
        if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
                close(sk);
@@ -922,8 +1775,12 @@ int __connman_rtnl_init(void)
        channel = g_io_channel_unix_new(sk);
        g_io_channel_set_close_on_unref(channel, TRUE);
 
-       g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
-                                                       netlink_event, NULL);
+       g_io_channel_set_encoding(channel, NULL, NULL);
+       g_io_channel_set_buffered(channel, FALSE);
+
+       channel_watch = g_io_add_watch(channel,
+                               G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+                               netlink_event, NULL);
 
        return 0;
 }
@@ -955,6 +1812,9 @@ void __connman_rtnl_cleanup(void)
        g_slist_free(watch_list);
        watch_list = NULL;
 
+       g_slist_free(update_list);
+       update_list = NULL;
+
        for (list = request_list; list; list = list->next) {
                struct rtnl_request *req = list->data;
 
@@ -970,8 +1830,15 @@ void __connman_rtnl_cleanup(void)
        g_slist_free(request_list);
        request_list = NULL;
 
+       if (channel_watch) {
+               g_source_remove(channel_watch);
+               channel_watch = 0;
+       }
+
        g_io_channel_shutdown(channel, TRUE, NULL);
        g_io_channel_unref(channel);
 
        channel = NULL;
+
+       g_hash_table_destroy(interface_list);
 }