/* * * ConnMan VPN daemon * * Copyright (C) 2012-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 #include "vpn.h" #include "vpn-rtnl.h" #ifndef ARPHDR_PHONET_PIPE #define ARPHDR_PHONET_PIPE (821) #endif #define print(arg...) do { if (0) connman_info(arg); } while (0) #define debug(arg...) do { if (0) DBG(arg); } while (0) struct watch_data { unsigned int id; int index; vpn_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; }; static GHashTable *interface_list = NULL; static void free_interface(gpointer data) { struct interface_data *interface = data; g_free(interface->ident); g_free(interface); } /** * vpn_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 vpn_rtnl_add_newlink_watch(int index, vpn_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 = __vpn_ipconfig_get_flags_from_index(index); if (flags > 0) callback(flags, 0, user_data); } return watch->id; } /** * vpn_rtnl_remove_watch: * @id: watch identifier * * Remove the RTNL watch for the identifier */ void vpn_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 vpn_rtnl *rtnl = user_data; if (rtnl->newlink) { unsigned short type = __vpn_ipconfig_get_type_from_index(index); unsigned int flags = __vpn_ipconfig_get_flags_from_index(index); rtnl->newlink(type, index, flags, 0); } } static GSList *rtnl_list = NULL; static gint compare_priority(gconstpointer a, gconstpointer b) { const struct vpn_rtnl *rtnl1 = a; const struct vpn_rtnl *rtnl2 = b; return rtnl2->priority - rtnl1->priority; } /** * vpn_rtnl_register: * @rtnl: RTNL module * * Register a new RTNL module * * Returns: %0 on success */ int vpn_rtnl_register(struct vpn_rtnl *rtnl) { DBG("rtnl %p name %s", rtnl, rtnl->name); rtnl_list = g_slist_insert_sorted(rtnl_list, rtnl, compare_priority); __vpn_ipconfig_foreach(trigger_rtnl, rtnl); return 0; } /** * vpn_rtnl_unregister: * @rtnl: RTNL module * * Remove a previously registered RTNL module */ void vpn_rtnl_unregister(struct vpn_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) 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; } } } 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: __vpn_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) { 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); } for (list = rtnl_list; list; list = list->next) { struct vpn_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 vpn_rtnl *rtnl = list->data; if (rtnl->dellink) rtnl->dellink(type, index, flags, change); } switch (type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: case ARPHRD_NONE: __vpn_ipconfig_dellink(index, &stats); break; } g_hash_table_remove(interface_list, GINT_TO_POINTER(index)); } 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)); /* 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)); /* 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 vpn_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)); /* 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)); /* 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 vpn_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); 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_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_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 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; debug("%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; debug("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) { debug("buf %p len %zd", buf, len); while (len > 0) { struct nlmsghdr *hdr = buf; struct nlmsgerr *err; if (!NLMSG_OK(hdr, len)) break; debug("%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: break; case RTM_DELADDR: break; case RTM_NEWROUTE: rtnl_newroute(hdr); break; case RTM_DELROUTE: rtnl_delroute(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; debug(""); 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; debug(""); 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; debug(""); 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) { __vpn_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 __vpn_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); __vpn_rtnl_request_update(); } return update_interval; } unsigned int __vpn_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 __vpn_rtnl_request_update(void) { return send_getlink(); } int __vpn_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); g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, netlink_event, NULL); return 0; } void __vpn_rtnl_start(void) { DBG(""); send_getlink(); send_getaddr(); send_getroute(); } void __vpn_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; debug("%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); }