X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Frtnl.c;h=4e7ef4d5be0c169dfaeae522ba2373f574660c94;hb=f09588ccfac150b9289563276ddfe5f661ee9062;hp=147d2dad363dcae6d7df794a78bbbe288689cbd7;hpb=7e6b36c1c922a2d3f3b7fa025a52bb508415ffc1;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/rtnl.c b/src/rtnl.c index 147d2da..4e7ef4d 100644 --- a/src/rtnl.c +++ b/src/rtnl.c @@ -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,28 +23,35 @@ #include #endif +#include #include #include #include #include +#include #include #include +#include #include #include #include #include +#include #include #include "connman.h" -#define print(arg...) do { } while (0) +#ifndef ARPHDR_PHONET_PIPE +#define ARPHDR_PHONET_PIPE (821) +#endif + +#define print(arg...) do { if (0) connman_info(arg); } while (0) //#define print(arg...) connman_info(arg) struct watch_data { unsigned int id; int index; - connman_rtnl_operstate_cb_t operstate; connman_rtnl_link_cb_t newlink; void *user_data; }; @@ -52,43 +59,150 @@ struct watch_data { static GSList *watch_list = NULL; static unsigned int watch_id = 0; -/** - * connman_rtnl_add_operstate_watch: - * @index: network device index - * @callback: callback function - * @user_data: callback data; - * - * Add a new RTNL watch for operation state events - * - * Returns: %0 on failure and a unique id on success - */ -unsigned int connman_rtnl_add_operstate_watch(int index, - connman_rtnl_operstate_cb_t callback, void *user_data) +static GSList *update_list = NULL; +static guint update_interval = G_MAXUINT; +static guint update_timeout = 0; + +struct interface_data { + int index; + char *name; + 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 watch_data *watch; + struct interface_data *interface = data; - watch = g_try_new0(struct watch_data, 1); - if (watch == NULL) - return 0; + __connman_technology_remove_interface(interface->service_type, + interface->index, interface->name, interface->ident); - watch->id = ++watch_id; - watch->index = index; + g_free(interface->ident); + g_free(interface->name); + g_free(interface); +} - watch->operstate = callback; - watch->user_data = user_data; +static connman_bool_t ether_blacklisted(const char *name) +{ + if (name == NULL) + return TRUE; - watch_list = g_slist_prepend(watch_list, watch); + if (__connman_device_isfiltered(name) == TRUE) + return TRUE; - DBG("id %d", watch->id); + return FALSE; +} - if (callback) { - unsigned char operstate = 0; +static connman_bool_t 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, IFNAMSIZ); - if (operstate > 0) - callback(operstate, user_data); + err = ioctl(fd, SIOCGIWNAME, &wrq); + + close(fd); + + if (err < 0) + return FALSE; + + return TRUE; +} + +static void read_uevent(struct interface_data *interface) +{ + char *filename, line[128]; + connman_bool_t found_devtype; + FILE *f; + + if (ether_blacklisted(interface->name) == TRUE) { + 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; } - return watch->id; + filename = g_strdup_printf("/sys/class/net/%s/uevent", + interface->name); + + f = fopen(filename, "re"); + + g_free(filename); + + if (f == NULL) + return; + + found_devtype = FALSE; + while (fgets(line, sizeof(line), f)) { + char *pos; + + pos = strchr(line, '\n'); + if (pos == NULL) + 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, "wimax") == 0) { + interface->service_type = CONNMAN_SERVICE_TYPE_WIMAX; + interface->device_type = CONNMAN_DEVICE_TYPE_WIMAX; + } else if (strcmp(line + 8, "gadget") == 0) { + interface->service_type = CONNMAN_SERVICE_TYPE_GADGET; + interface->device_type = CONNMAN_DEVICE_TYPE_GADGET; + + } else { + interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN; + interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN; + } + } + + fclose(f); + + if (found_devtype) + return; + + /* We haven't got a DEVTYPE, let's check if it's a wireless device */ + if (wext_interface(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", + interface->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 == NULL) + return CONNMAN_DEVICE_TYPE_UNKNOWN; + + return interface->device_type; } /** @@ -121,7 +235,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); @@ -161,14 +275,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); @@ -284,14 +400,23 @@ static void process_newlink(unsigned short type, int index, unsigned flags, struct ether_addr compare = {{ 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 str[18]; + char ident[13], str[18]; GSList *list; memset(&stats, 0, sizeof(stats)); extract_link(msg, bytes, &address, &ifname, &mtu, &operstate, &stats); + 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], @@ -303,6 +428,7 @@ static void process_newlink(unsigned short type, int index, unsigned flags, switch (type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: + case ARPHDR_PHONET_PIPE: case ARPHRD_NONE: __connman_ipconfig_newlink(index, type, flags, str, mtu, &stats); @@ -318,6 +444,23 @@ static void process_newlink(unsigned short type, int index, unsigned flags, ifname, index, operstate, operstate2str(operstate)); + interface = g_hash_table_lookup(interface_list, GINT_TO_POINTER(index)); + if (interface == NULL) { + interface = g_new0(struct interface_data, 1); + interface->index = index; + interface->name = g_strdup(ifname); + interface->ident = g_strdup(ident); + + g_hash_table_insert(interface_list, + GINT_TO_POINTER(index), interface); + + 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) { struct connman_rtnl *rtnl = list->data; @@ -331,9 +474,6 @@ static void process_newlink(unsigned short type, int index, unsigned flags, if (watch->index != index) continue; - if (operstate != 0xff && watch->operstate) - watch->operstate(operstate, watch->user_data); - if (watch->newlink) watch->newlink(flags, change, watch->user_data); } @@ -355,16 +495,6 @@ static void process_dellink(unsigned short type, int index, unsigned flags, ifname, index, operstate, operstate2str(operstate)); - for (list = watch_list; list; list = list->next) { - struct watch_data *watch = list->data; - - if (watch->index != index) - continue; - - if (operstate != 0xff && watch->operstate) - watch->operstate(operstate, watch->user_data); - } - for (list = rtnl_list; list; list = list->next) { struct connman_rtnl *rtnl = list->data; @@ -379,9 +509,11 @@ static void process_dellink(unsigned short type, int index, unsigned flags, __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, @@ -412,37 +544,104 @@ static void extract_addr(struct ifaddrmsg *msg, int bytes, } } +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 != NULL) + *addr = *((struct in6_addr *) RTA_DATA(attr)); + break; + case IFA_LOCAL: + if (local != NULL) + *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) { - struct in_addr address = { INADDR_ANY }; const char *label = NULL; + void *src; + char ip_string[INET6_ADDRSTRLEN]; + + if (family == AF_INET) { + struct in_addr ipv4_addr = { INADDR_ANY }; + + extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL); + src = &ipv4_addr; + } else if (family == AF_INET6) { + struct in6_addr ipv6_address, ipv6_local; + + extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local); + if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address)) + return; + + src = &ipv6_address; + } else { + return; + } - if (family != AF_INET) + if (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL) return; - extract_addr(msg, bytes, &label, &address, NULL, NULL); + __connman_ipconfig_newaddr(index, family, label, + prefixlen, ip_string); - __connman_ipconfig_newaddr(index, label, - prefixlen, inet_ntoa(address)); + 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, int index, struct ifaddrmsg *msg, int bytes) { - struct in_addr address = { INADDR_ANY }; const char *label = NULL; + void *src; + char ip_string[INET6_ADDRSTRLEN]; + + if (family == AF_INET) { + struct in_addr ipv4_addr = { INADDR_ANY }; - if (family != AF_INET) + extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL); + src = &ipv4_addr; + } else if (family == AF_INET6) { + struct in6_addr ipv6_address, ipv6_local; + + extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local); + if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address)) + return; + + src = &ipv6_address; + } else { return; + } - extract_addr(msg, bytes, &label, &address, NULL, NULL); + if (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL) + 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) { @@ -467,25 +666,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_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); + + /* 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) { @@ -500,21 +753,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; + + 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); - extract_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, 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) { @@ -783,16 +1064,29 @@ static void rtnl_route(struct nlmsghdr *hdr) } } +static connman_bool_t is_route_rtmsg(struct rtmsg *msg) +{ + + 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_protocol == RTPROT_KERNEL) && - msg->rtm_type == RTN_UNICAST) + if (is_route_rtmsg(msg)) process_newroute(msg->rtm_family, msg->rtm_scope, msg, RTM_PAYLOAD(hdr)); } @@ -803,14 +1097,147 @@ static void rtnl_delroute(struct nlmsghdr *hdr) rtnl_route(hdr); - if (msg->rtm_table == RT_TABLE_MAIN && - (msg->rtm_protocol == RTPROT_BOOT || - msg->rtm_protocol == RTPROT_KERNEL) && - 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; + char *interface; + + 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; + + interface = connman_inet_ifname(msg->nduseropt_ifindex); + if (!interface) + return; + + 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]; + + 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; + + connman_resolver_append_lifetime(interface, + 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 != NULL && domains[i] != NULL; i++) + connman_resolver_append_lifetime(interface, + domains[i], NULL, lifetime); + } + } + + g_free(domains); + g_free(interface); +} + static const char *type2string(uint16_t type) { switch (type) { @@ -828,6 +1255,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: @@ -838,6 +1267,8 @@ static const char *type2string(uint16_t type) return "NEWROUTE"; case RTM_DELROUTE: return "DELROUTE"; + case RTM_NEWNDUSEROPT: + return "NEWNDUSEROPT"; default: return "UNKNOWN"; } @@ -927,10 +1358,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: @@ -962,6 +1394,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; @@ -973,22 +1408,37 @@ 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; + } + + 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; } @@ -1053,6 +1503,73 @@ 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 != NULL) + 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(); @@ -1065,13 +1582,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); @@ -1081,6 +1603,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); @@ -1114,6 +1639,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; @@ -1133,4 +1661,6 @@ void __connman_rtnl_cleanup(void) g_io_channel_unref(channel); channel = NULL; + + g_hash_table_destroy(interface_list); }