X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Frtnl.c;h=4f5021958c52b19557b59401c212b799d1578e1d;hb=18ec478652f819b8837b136ed117d8b882b2b3f6;hp=a8131d08804c8937e0fe970670192317caf8cfed;hpb=f24401eee00df645f2f676381c947487b4ddf6ca;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/rtnl.c b/src/rtnl.c index a8131d0..4f50219 100644 --- a/src/rtnl.c +++ b/src/rtnl.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007 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,55 +23,853 @@ #include #endif +#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connman.h" + +#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_link_cb_t newlink; + void *user_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 *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 interface_data *interface = data; + + __connman_technology_remove_interface(interface->service_type, + interface->index, interface->name, interface->ident); + + g_free(interface->ident); + g_free(interface->name); + g_free(interface); +} + +static connman_bool_t ether_blacklisted(const char *name) +{ + if (name == NULL) + return TRUE; + + /* virtual interface from VMware */ + if (g_str_has_prefix(name, "vmnet") == TRUE) + return TRUE; + + /* virtual interface from VirtualBox */ + if (g_str_has_prefix(name, "vboxnet") == TRUE) + return TRUE; + + /* virtual interface from Virtual Machine Manager */ + if (g_str_has_prefix(name, "virbr") == TRUE) + return TRUE; + + return FALSE; +} + +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); + + 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; + } + + 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; +} + +/** + * connman_rtnl_add_newlink_watch: + * @index: network device index + * @callback: callback function + * @user_data: callback data; + * + * Add a new RTNL watch for newlink events + * + * Returns: %0 on failure and a unique id on success + */ +unsigned int connman_rtnl_add_newlink_watch(int index, + connman_rtnl_link_cb_t callback, void *user_data) +{ + struct watch_data *watch; + + watch = g_try_new0(struct watch_data, 1); + if (watch == NULL) + return 0; + + watch->id = ++watch_id; + watch->index = index; + + watch->newlink = callback; + watch->user_data = user_data; + + watch_list = g_slist_prepend(watch_list, watch); + + DBG("id %d", watch->id); + + if (callback) { + unsigned int flags = __connman_ipconfig_get_flags_from_index(index); + + if (flags > 0) + callback(flags, 0, user_data); + } + + return watch->id; +} + +/** + * connman_rtnl_remove_watch: + * @id: watch identifier + * + * Remove the RTNL watch for the identifier + */ +void connman_rtnl_remove_watch(unsigned int id) +{ + GSList *list; + + DBG("id %d", id); + + if (id == 0) + return; + + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + if (watch->id == id) { + watch_list = g_slist_remove(watch_list, watch); + g_free(watch); + break; + } + } +} + +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_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_from_index(index, + CONNMAN_IPCONFIG_TYPE_ALL); + + if (gateway != NULL) + rtnl->newgateway(index, gateway); + } +} + +static GSList *rtnl_list = NULL; + +static gint compare_priority(gconstpointer a, gconstpointer b) +{ + const struct connman_rtnl *rtnl1 = a; + const struct connman_rtnl *rtnl2 = b; + + return rtnl2->priority - rtnl1->priority; +} + +/** + * connman_rtnl_register: + * @rtnl: RTNL module + * + * Register a new RTNL module + * + * Returns: %0 on success + */ +int connman_rtnl_register(struct connman_rtnl *rtnl) +{ + DBG("rtnl %p name %s", rtnl, rtnl->name); + + rtnl_list = g_slist_insert_sorted(rtnl_list, rtnl, + compare_priority); + + __connman_ipconfig_foreach(trigger_rtnl, rtnl); + + return 0; +} + +/** + * connman_rtnl_unregister: + * @rtnl: RTNL module + * + * Remove a previously registered RTNL module + */ +void connman_rtnl_unregister(struct connman_rtnl *rtnl) +{ + DBG("rtnl %p name %s", rtnl, rtnl->name); + + rtnl_list = g_slist_remove(rtnl_list, rtnl); +} + +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 void 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 != NULL) + memcpy(address, RTA_DATA(attr), ETH_ALEN); + break; + case IFLA_IFNAME: + if (ifname != NULL) + *ifname = RTA_DATA(attr); + break; + case IFLA_MTU: + if (mtu != NULL) + *mtu = *((unsigned int *) RTA_DATA(attr)); + break; + case IFLA_STATS: + if (stats != NULL) + memcpy(stats, RTA_DATA(attr), + sizeof(struct rtnl_link_stats)); + break; + case IFLA_OPERSTATE: + if (operstate != NULL) + *operstate = *((unsigned char *) RTA_DATA(attr)); + break; + case IFLA_LINKMODE: + break; + } + } +} + +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 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 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], + address.ether_addr_octet[2], + address.ether_addr_octet[3], + address.ether_addr_octet[4], + address.ether_addr_octet[5]); + + switch (type) { + case ARPHRD_ETHER: + case ARPHRD_LOOPBACK: + case ARPHDR_PHONET_PIPE: + case ARPHRD_NONE: + __connman_ipconfig_newlink(index, type, flags, + str, mtu, &stats); + break; + } + + if (memcmp(&address, &compare, ETH_ALEN) != 0) + 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 == 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; + + if (rtnl->newlink) + rtnl->newlink(type, index, flags, change); + } + + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + if (watch->index != index) + continue; + + if (watch->newlink) + watch->newlink(flags, change, watch->user_data); + } +} + +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)); + extract_link(msg, bytes, NULL, &ifname, NULL, &operstate, &stats); + + 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; + + if (rtnl->dellink) + rtnl->dellink(type, index, flags, change); + } + + switch (type) { + case ARPHRD_ETHER: + case ARPHRD_LOOPBACK: + case ARPHRD_NONE: + __connman_ipconfig_dellink(index, &stats); + break; + } + + g_hash_table_remove(interface_list, GINT_TO_POINTER(index)); +} + +static void extract_ipv4_addr(struct ifaddrmsg *msg, int bytes, + const char **label, + struct in_addr *local, + struct in_addr *address, + struct in_addr *broadcast) +{ + 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 (address != NULL) + *address = *((struct in_addr *) RTA_DATA(attr)); + break; + case IFA_LOCAL: + if (local != NULL) + *local = *((struct in_addr *) RTA_DATA(attr)); + break; + case IFA_BROADCAST: + if (broadcast != NULL) + *broadcast = *((struct in_addr *) RTA_DATA(attr)); + break; + case IFA_LABEL: + if (label != NULL) + *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 != 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) +{ + 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 (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL) + return; + + __connman_ipconfig_newaddr(index, family, label, + prefixlen, ip_string); + + if (family == AF_INET6) { + /* + * Re-create RDNSS configured servers if there are any + * for this interface. This is done because we might + * have now properly configured interface with proper + * autoconfigured address. + */ + char *interface = connman_inet_ifname(index); + + __connman_resolver_redo_servers(interface); + + g_free(interface); + } +} + +static void process_deladdr(unsigned char family, unsigned char prefixlen, + int index, struct ifaddrmsg *msg, int bytes) +{ + 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 (inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN) == NULL) + return; + + __connman_ipconfig_deladdr(index, family, label, + prefixlen, ip_string); +} + +static void extract_ipv4_route(struct rtmsg *msg, int bytes, int *index, + struct in_addr *dst, + struct in_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 in_addr *) RTA_DATA(attr)); + break; + case RTA_GATEWAY: + if (gateway != NULL) + *gateway = *((struct in_addr *) RTA_DATA(attr)); + break; + case RTA_OIF: + if (index != NULL) + *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 != 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; + char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN]; + int index = -1; + + 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_ipv6_route(msg, bytes, &index, &dst, &gateway); + + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); + + __connman_ipconfig_newroute(index, family, scope, dststr, + gatewaystr); + + /* skip host specific routes */ + if (scope != RT_SCOPE_UNIVERSE && + !(scope == RT_SCOPE_LINK && + IN6_IS_ADDR_UNSPECIFIED(&dst))) + return; + + if (!IN6_IS_ADDR_UNSPECIFIED(&dst)) + return; + } else + return; + + for (list = rtnl_list; list; list = list->next) { + struct connman_rtnl *rtnl = list->data; + + if (rtnl->newgateway) + rtnl->newgateway(index, gatewaystr); + } +} + +static void process_delroute(unsigned char family, unsigned char scope, + struct rtmsg *msg, int bytes) +{ + GSList *list; + char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN]; + int index = -1; + + if (family == AF_INET) { + struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY }; -#include -#include -#include + extract_ipv4_route(msg, bytes, &index, &dst, &gateway); -#include + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); -#include "connman.h" + __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); + + 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; -static inline void print_inet(struct rtattr *attr, const char *name, int family) + if (!IN6_IS_ADDR_UNSPECIFIED(&dst)) + return; + } else + return; + + for (list = rtnl_list; list; list = list->next) { + struct connman_rtnl *rtnl = list->data; + + if (rtnl->delgateway) + rtnl->delgateway(index, gatewaystr); + } +} + +static inline void print_ether(struct rtattr *attr, const char *name) { - if (family == AF_INET) { + int len = (int) RTA_PAYLOAD(attr); + + if (len == ETH_ALEN) { + struct ether_addr eth; + memcpy(ð, RTA_DATA(attr), ETH_ALEN); + print(" attr %s (len %d) %s\n", name, len, ether_ntoa(ð)); + } else + print(" attr %s (len %d)\n", name, len); +} + +static inline void print_inet(struct rtattr *attr, const char *name, + unsigned char family) +{ + int len = (int) RTA_PAYLOAD(attr); + + if (family == AF_INET && len == sizeof(struct in_addr)) { struct in_addr addr; addr = *((struct in_addr *) RTA_DATA(attr)); - printf(" attr %s (len %d) %s\n", - name, RTA_PAYLOAD(attr), inet_ntoa(addr)); + print(" attr %s (len %d) %s\n", name, len, inet_ntoa(addr)); } else - printf(" attr %s (len %d)\n", name, RTA_PAYLOAD(attr)); + print(" attr %s (len %d)\n", name, len); } -static inline void print_char(struct rtattr *attr, const char *name) +static inline void print_string(struct rtattr *attr, const char *name) { - printf(" attr %s (len %d) %s\n", name, RTA_PAYLOAD(attr), + print(" attr %s (len %d) %s\n", name, (int) RTA_PAYLOAD(attr), (char *) RTA_DATA(attr)); } static inline void print_byte(struct rtattr *attr, const char *name) { - printf(" attr %s (len %d) 0x%02x\n", name, RTA_PAYLOAD(attr), + print(" attr %s (len %d) 0x%02x\n", name, (int) RTA_PAYLOAD(attr), *((unsigned char *) RTA_DATA(attr))); } +static inline void print_integer(struct rtattr *attr, const char *name) +{ + print(" attr %s (len %d) %d\n", name, (int) RTA_PAYLOAD(attr), + *((int *) RTA_DATA(attr))); +} + static inline void print_attr(struct rtattr *attr, const char *name) { - if (name) - printf(" attr %s (len %d)\n", name, RTA_PAYLOAD(attr)); + int len = (int) RTA_PAYLOAD(attr); + + if (name && len > 0) + print(" attr %s (len %d)\n", name, len); else - printf(" attr %d (len %d)\n", - attr->rta_type, RTA_PAYLOAD(attr)); + print(" attr %d (len %d)\n", attr->rta_type, len); } static void rtnl_link(struct nlmsghdr *hdr) { - struct connman_iface *iface; struct ifinfomsg *msg; struct rtattr *attr; int bytes; @@ -79,37 +877,22 @@ static void rtnl_link(struct nlmsghdr *hdr) msg = (struct ifinfomsg *) NLMSG_DATA(hdr); bytes = IFLA_PAYLOAD(hdr); - DBG("ifi_index %d ifi_flags 0x%04x", msg->ifi_index, msg->ifi_flags); - - iface = __connman_iface_find(msg->ifi_index); - if (iface == NULL) - return; - - if ((iface->flags & CONNMAN_IFACE_FLAG_RTNL) == 0) - return; - - if (iface->carrier != ((msg->ifi_flags & IFF_RUNNING) != 0)) { - iface->carrier = ((msg->ifi_flags & IFF_RUNNING) != 0); - if (iface->driver->rtnl_carrier) - iface->driver->rtnl_carrier(iface, iface->carrier); - else - connman_iface_indicate_carrier(iface, iface->carrier); - } + print("ifi_index %d ifi_flags 0x%04x", msg->ifi_index, msg->ifi_flags); for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes); attr = RTA_NEXT(attr, bytes)) { switch (attr->rta_type) { case IFLA_ADDRESS: - print_attr(attr, "address"); + print_ether(attr, "address"); break; case IFLA_BROADCAST: - print_attr(attr, "broadcast"); + print_ether(attr, "broadcast"); break; case IFLA_IFNAME: - print_char(attr, "ifname"); + print_string(attr, "ifname"); break; case IFLA_MTU: - print_attr(attr, "mtu"); + print_integer(attr, "mtu"); break; case IFLA_LINK: print_attr(attr, "link"); @@ -130,15 +913,13 @@ static void rtnl_link(struct nlmsghdr *hdr) print_attr(attr, "master"); break; case IFLA_WIRELESS: - if (iface->driver->rtnl_wireless) - iface->driver->rtnl_wireless(iface, - RTA_DATA(attr), RTA_PAYLOAD(attr)); + print_attr(attr, "wireless"); break; case IFLA_PROTINFO: print_attr(attr, "protinfo"); break; case IFLA_TXQLEN: - print_attr(attr, "txqlen"); + print_integer(attr, "txqlen"); break; case IFLA_MAP: print_attr(attr, "map"); @@ -159,9 +940,28 @@ static void rtnl_link(struct nlmsghdr *hdr) } } +static void rtnl_newlink(struct nlmsghdr *hdr) +{ + struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); + + rtnl_link(hdr); + + 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) +{ + struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); + + rtnl_link(hdr); + + 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) { - struct connman_iface *iface; struct ifaddrmsg *msg; struct rtattr *attr; int bytes; @@ -169,14 +969,7 @@ static void rtnl_addr(struct nlmsghdr *hdr) msg = (struct ifaddrmsg *) NLMSG_DATA(hdr); bytes = IFA_PAYLOAD(hdr); - DBG("ifa_family %d ifa_index %d", msg->ifa_family, msg->ifa_index); - - iface = __connman_iface_find(msg->ifa_index); - if (iface == NULL) - return; - - if ((iface->flags & CONNMAN_IFACE_FLAG_RTNL) == 0) - return; + print("ifa_family %d ifa_index %d", msg->ifa_family, msg->ifa_index); for (attr = IFA_RTA(msg); RTA_OK(attr, bytes); attr = RTA_NEXT(attr, bytes)) { @@ -188,7 +981,7 @@ static void rtnl_addr(struct nlmsghdr *hdr) print_inet(attr, "local", msg->ifa_family); break; case IFA_LABEL: - print_char(attr, "label"); + print_string(attr, "label"); break; case IFA_BROADCAST: print_inet(attr, "broadcast", msg->ifa_family); @@ -209,6 +1002,26 @@ static void rtnl_addr(struct nlmsghdr *hdr) } } +static void rtnl_newaddr(struct nlmsghdr *hdr) +{ + struct ifaddrmsg *msg = (struct ifaddrmsg *) NLMSG_DATA(hdr); + + rtnl_addr(hdr); + + process_newaddr(msg->ifa_family, msg->ifa_prefixlen, msg->ifa_index, + msg, IFA_PAYLOAD(hdr)); +} + +static void rtnl_deladdr(struct nlmsghdr *hdr) +{ + struct ifaddrmsg *msg = (struct ifaddrmsg *) NLMSG_DATA(hdr); + + rtnl_addr(hdr); + + process_deladdr(msg->ifa_family, msg->ifa_prefixlen, msg->ifa_index, + msg, IFA_PAYLOAD(hdr)); +} + static void rtnl_route(struct nlmsghdr *hdr) { struct rtmsg *msg; @@ -218,7 +1031,10 @@ static void rtnl_route(struct nlmsghdr *hdr) msg = (struct rtmsg *) NLMSG_DATA(hdr); bytes = RTM_PAYLOAD(hdr); - DBG("rtm_family %d rtm_flags 0x%04x", msg->rtm_family, msg->rtm_flags); + print("rtm_family %d rtm_table %d rtm_protocol %d", + msg->rtm_family, msg->rtm_table, msg->rtm_protocol); + print("rtm_scope %d rtm_type %d rtm_flags 0x%04x", + msg->rtm_scope, msg->rtm_type, msg->rtm_flags); for (attr = RTM_RTA(msg); RTA_OK(attr, bytes); attr = RTA_NEXT(attr, bytes)) { @@ -230,10 +1046,10 @@ static void rtnl_route(struct nlmsghdr *hdr) print_inet(attr, "src", msg->rtm_family); break; case RTA_IIF: - print_char(attr, "iif"); + print_string(attr, "iif"); break; case RTA_OIF: - print_attr(attr, "oif"); + print_integer(attr, "oif"); break; case RTA_GATEWAY: print_inet(attr, "gateway", msg->rtm_family); @@ -248,7 +1064,7 @@ static void rtnl_route(struct nlmsghdr *hdr) print_attr(attr, "metrics"); break; case RTA_TABLE: - print_attr(attr, "table"); + print_integer(attr, "table"); break; default: print_attr(attr, NULL); @@ -257,9 +1073,290 @@ 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 (is_route_rtmsg(msg)) + process_newroute(msg->rtm_family, msg->rtm_scope, + msg, RTM_PAYLOAD(hdr)); +} + +static void rtnl_delroute(struct nlmsghdr *hdr) +{ + struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr); + + rtnl_route(hdr); + + 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) { + case NLMSG_NOOP: + return "NOOP"; + case NLMSG_ERROR: + return "ERROR"; + case NLMSG_DONE: + return "DONE"; + case NLMSG_OVERRUN: + return "OVERRUN"; + case RTM_GETLINK: + return "GETLINK"; + case RTM_NEWLINK: + return "NEWLINK"; + case RTM_DELLINK: + return "DELLINK"; + case RTM_NEWADDR: + return "NEWADDR"; + case RTM_DELADDR: + return "DELADDR"; + case RTM_GETROUTE: + return "GETROUTE"; + case RTM_NEWROUTE: + return "NEWROUTE"; + case RTM_DELROUTE: + return "DELROUTE"; + case RTM_NEWNDUSEROPT: + return "NEWNDUSEROPT"; + default: + return "UNKNOWN"; + } +} + +static GIOChannel *channel = NULL; + +struct rtnl_request { + struct nlmsghdr hdr; + struct rtgenmsg msg; +}; +#define RTNL_REQUEST_SIZE (sizeof(struct nlmsghdr) + sizeof(struct rtgenmsg)) + +static GSList *request_list = NULL; +static guint32 request_seq = 0; + +static struct rtnl_request *find_request(guint32 seq) +{ + GSList *list; + + for (list = request_list; list; list = list->next) { + struct rtnl_request *req = list->data; + + if (req->hdr.nlmsg_seq == seq) + return req; + } + + return NULL; +} + +static int send_request(struct rtnl_request *req) +{ + struct sockaddr_nl addr; + int sk; + + DBG("%s len %d type %d flags 0x%04x seq %d", + type2string(req->hdr.nlmsg_type), + req->hdr.nlmsg_len, req->hdr.nlmsg_type, + req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + + sk = g_io_channel_unix_get_fd(channel); + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + return sendto(sk, req, req->hdr.nlmsg_len, 0, + (struct sockaddr *) &addr, sizeof(addr)); +} + +static int queue_request(struct rtnl_request *req) +{ + request_list = g_slist_append(request_list, req); + + if (g_slist_length(request_list) > 1) + return 0; + + return send_request(req); +} + +static int process_response(guint32 seq) +{ + struct rtnl_request *req; + + DBG("seq %d", seq); + + req = find_request(seq); + if (req != NULL) { + request_list = g_slist_remove(request_list, req); + g_free(req); + } + + req = g_slist_nth_data(request_list, 0); + if (req == NULL) + return 0; + + return send_request(req); +} + static void rtnl_message(void *buf, size_t len) { - DBG("buf %p len %d", buf, len); + DBG("buf %p len %zd", buf, len); while (len > 0) { struct nlmsghdr *hdr = buf; @@ -268,19 +1365,18 @@ static void rtnl_message(void *buf, size_t len) if (!NLMSG_OK(hdr, len)) break; - DBG("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_DONE: - DBG("done"); - return; case NLMSG_NOOP: - DBG("noop"); - return; case NLMSG_OVERRUN: - DBG("overrun"); + return; + case NLMSG_DONE: + process_response(hdr->nlmsg_seq); return; case NLMSG_ERROR: err = NLMSG_DATA(hdr); @@ -288,25 +1384,25 @@ static void rtnl_message(void *buf, size_t len) strerror(-err->error)); return; case RTM_NEWLINK: - rtnl_link(hdr); + rtnl_newlink(hdr); break; case RTM_DELLINK: - rtnl_link(hdr); + rtnl_dellink(hdr); break; case RTM_NEWADDR: - rtnl_addr(hdr); + rtnl_newaddr(hdr); break; case RTM_DELADDR: - rtnl_addr(hdr); + rtnl_deladdr(hdr); break; case RTM_NEWROUTE: - rtnl_route(hdr); + rtnl_newroute(hdr); break; case RTM_DELROUTE: - rtnl_route(hdr); + rtnl_delroute(hdr); break; - default: - DBG("type %d", hdr->nlmsg_type); + case RTM_NEWNDUSEROPT: + rtnl_newnduseropt(hdr); break; } @@ -318,46 +1414,172 @@ static void rtnl_message(void *buf, size_t len) static gboolean netlink_event(GIOChannel *chan, GIOCondition cond, gpointer data) { - unsigned char buf[256]; - gsize len; - GIOError err; + unsigned char buf[4096]; + 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)) { - g_io_channel_unref(chan); + 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; - g_io_channel_unref(chan); + + 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; } -static GIOChannel *channel = NULL; +static int send_getlink(void) +{ + struct rtnl_request *req; + + DBG(""); + + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; + + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETLINK; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; + + return queue_request(req); +} -int __connman_rtnl_send(const void *buf, size_t len) +static int send_getaddr(void) { - struct sockaddr_nl addr; - int sk; + struct rtnl_request *req; + + DBG(""); - DBG("buf %p len %d", buf, len); + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; - sk = g_io_channel_unix_get_fd(channel); + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETADDR; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; + return queue_request(req); +} + +static int send_getroute(void) +{ + struct rtnl_request *req; + + DBG(""); + + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; + + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETROUTE; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; + + 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)); - return sendto(sk, buf, len, 0, - (struct sockaddr *) &addr, sizeof(addr)); + if (min > update_interval) + update_interval_callback(min); + + return min; +} + +int __connman_rtnl_request_update(void) +{ + return send_getlink(); } int __connman_rtnl_init(void) @@ -367,14 +1589,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; - //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); @@ -384,20 +1610,64 @@ 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); - g_io_channel_unref(channel); + g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + netlink_event, NULL); return 0; } +void __connman_rtnl_start(void) +{ + DBG(""); + + send_getlink(); + send_getaddr(); + send_getroute(); +} + void __connman_rtnl_cleanup(void) { + GSList *list; + DBG(""); + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + DBG("removing watch %d", watch->id); + + g_free(watch); + list->data = NULL; + } + + 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; + + DBG("%s len %d type %d flags 0x%04x seq %d", + type2string(req->hdr.nlmsg_type), + req->hdr.nlmsg_len, req->hdr.nlmsg_type, + req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + + g_free(req); + list->data = NULL; + } + + g_slist_free(request_list); + request_list = NULL; + + g_io_channel_shutdown(channel, TRUE, NULL); g_io_channel_unref(channel); channel = NULL; + + g_hash_table_destroy(interface_list); }