/* * * Connection Manager * * 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 * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #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 #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 { 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 *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; 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 { 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 * @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) 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) 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 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 /* 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; } #if defined TIZEN_TV_EXT if (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: #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 interface = NULL; for (list = rtnl_list; list; list = list->next) { struct connman_rtnl *rtnl = list->data; if (rtnl->newlink) 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; 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)); 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; if (rtnl->dellink) rtnl->dellink(type, index, flags, change); } switch (type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: case ARPHDR_PHONET_PIPE: case ARPHRD_PPP: case ARPHRD_NONE: #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_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) *address = *((struct in_addr *) RTA_DATA(attr)); break; case IFA_LOCAL: if (local) *local = *((struct in_addr *) RTA_DATA(attr)); break; case IFA_BROADCAST: if (broadcast) *broadcast = *((struct in_addr *) RTA_DATA(attr)); break; case IFA_LABEL: 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) { 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; src = &ipv6_address; } else { return; } if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN)) return; 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) { 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; src = &ipv6_address; } else { return; } if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN)) 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) *dst = *((struct in_addr *) RTA_DATA(attr)); break; case RTA_GATEWAY: if (gateway) *gateway = *((struct in_addr *) RTA_DATA(attr)); break; case RTA_OIF: 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; } } } 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 }; 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); inet_ntop(family, &dst, dststr, sizeof(dststr)); inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); __connman_ipconfig_delroute(index, family, scope, dststr, gatewaystr); /* skip host specific routes */ if (scope != RT_SCOPE_UNIVERSE && !(scope == RT_SCOPE_LINK && IN6_IS_ADDR_UNSPECIFIED(&dst))) return; if (!IN6_IS_ADDR_UNSPECIFIED(&dst)) return; } else return; for (list = rtnl_list; list; list = list->next) { struct connman_rtnl *rtnl = list->data; if (rtnl->delgateway) rtnl->delgateway(index, gatewaystr); } } static inline void print_ether(struct rtattr *attr, const char *name) { 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)); print(" attr %s (len %d) %s\n", name, len, inet_ntoa(addr)); } else print(" attr %s (len %d)\n", name, len); } static inline void print_string(struct rtattr *attr, const char *name) { 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) { 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) { int len = (int) RTA_PAYLOAD(attr); if (name && len > 0) print(" attr %s (len %d)\n", name, len); else print(" attr %d (len %d)\n", attr->rta_type, len); } static void rtnl_link(struct nlmsghdr *hdr) { struct ifinfomsg *msg; struct rtattr *attr; int bytes; msg = (struct ifinfomsg *) NLMSG_DATA(hdr); bytes = IFLA_PAYLOAD(hdr); 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_ether(attr, "address"); break; case IFLA_BROADCAST: print_ether(attr, "broadcast"); break; case IFLA_IFNAME: print_string(attr, "ifname"); break; case IFLA_MTU: print_integer(attr, "mtu"); break; case IFLA_LINK: print_attr(attr, "link"); break; case IFLA_QDISC: print_attr(attr, "qdisc"); break; case IFLA_STATS: print_attr(attr, "stats"); break; case IFLA_COST: print_attr(attr, "cost"); break; case IFLA_PRIORITY: print_attr(attr, "priority"); break; case IFLA_MASTER: print_attr(attr, "master"); break; case IFLA_WIRELESS: print_attr(attr, "wireless"); break; case IFLA_PROTINFO: print_attr(attr, "protinfo"); break; case IFLA_TXQLEN: print_integer(attr, "txqlen"); break; case IFLA_MAP: print_attr(attr, "map"); break; case IFLA_WEIGHT: print_attr(attr, "weight"); break; case IFLA_OPERSTATE: print_byte(attr, "operstate"); break; case IFLA_LINKMODE: print_byte(attr, "linkmode"); break; default: print_attr(attr, NULL); break; } } } static void rtnl_newlink(struct nlmsghdr *hdr) { struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); rtnl_link(hdr); 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) { 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 ifaddrmsg *msg; struct rtattr *attr; int bytes; msg = (struct ifaddrmsg *) NLMSG_DATA(hdr); bytes = IFA_PAYLOAD(hdr); 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)) { switch (attr->rta_type) { case IFA_ADDRESS: print_inet(attr, "address", msg->ifa_family); break; case IFA_LOCAL: print_inet(attr, "local", msg->ifa_family); break; case IFA_LABEL: print_string(attr, "label"); break; case IFA_BROADCAST: print_inet(attr, "broadcast", msg->ifa_family); break; case IFA_ANYCAST: print_attr(attr, "anycast"); break; case IFA_CACHEINFO: print_attr(attr, "cacheinfo"); break; case IFA_MULTICAST: print_attr(attr, "multicast"); break; default: print_attr(attr, NULL); break; } } } 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; struct rtattr *attr; int bytes; msg = (struct rtmsg *) NLMSG_DATA(hdr); bytes = RTM_PAYLOAD(hdr); 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)) { switch (attr->rta_type) { case RTA_DST: print_inet(attr, "dst", msg->rtm_family); break; case RTA_SRC: print_inet(attr, "src", msg->rtm_family); break; case RTA_IIF: print_string(attr, "iif"); break; case RTA_OIF: print_integer(attr, "oif"); break; case RTA_GATEWAY: print_inet(attr, "gateway", msg->rtm_family); break; case RTA_PRIORITY: print_attr(attr, "priority"); break; case RTA_PREFSRC: print_inet(attr, "prefsrc", msg->rtm_family); break; case RTA_METRICS: print_attr(attr, "metrics"); break; case RTA_TABLE: print_integer(attr, "table"); break; default: print_attr(attr, NULL); break; } } } 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 (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; 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; 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(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) { 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_GETADDR: return "GETADDR"; 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; static guint channel_watch = 0; 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) { request_list = g_slist_remove(request_list, req); g_free(req); } req = g_slist_nth_data(request_list, 0); if (!req) return 0; return send_request(req); } 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; if (!NLMSG_OK(hdr, len)) break; 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_pid); switch (hdr->nlmsg_type) { case NLMSG_NOOP: case NLMSG_OVERRUN: return; case NLMSG_DONE: process_response(hdr->nlmsg_seq); return; case NLMSG_ERROR: err = NLMSG_DATA(hdr); DBG("error %d (%s)", -err->error, strerror(-err->error)); return; case RTM_NEWLINK: rtnl_newlink(hdr); break; case RTM_DELLINK: rtnl_dellink(hdr); break; case RTM_NEWADDR: rtnl_newaddr(hdr); break; case RTM_DELADDR: rtnl_deladdr(hdr); break; case RTM_NEWROUTE: rtnl_newroute(hdr); break; case RTM_DELROUTE: rtnl_delroute(hdr); break; case RTM_NEWNDUSEROPT: rtnl_newnduseropt(hdr); break; } len -= hdr->nlmsg_len; buf += hdr->nlmsg_len; } } static gboolean netlink_event(GIOChannel *chan, GIOCondition cond, gpointer data) { 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)) return FALSE; memset(buf, 0, sizeof(buf)); memset(&nladdr, 0, sizeof(nladdr)); fd = g_io_channel_unix_get_fd(chan); 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, status); return TRUE; } static int send_getlink(void) { struct rtnl_request *req; DBG(""); req = g_try_malloc0(RTNL_REQUEST_SIZE); if (!req) 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); } static int send_getaddr(void) { struct rtnl_request *req; DBG(""); req = g_try_malloc0(RTNL_REQUEST_SIZE); if (!req) return -ENOMEM; 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; return queue_request(req); } static int send_getroute(void) { struct rtnl_request *req; DBG(""); req = g_try_malloc0(RTNL_REQUEST_SIZE); if (!req) 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) 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; int sk; DBG(""); 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 | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE | (1<<(RTNLGRP_ND_USEROPT-1)); if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(sk); return -1; } 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); channel_watch = 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; 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); }