/* * Copyright (C) 2013 Martin Willi * Copyright (C) 2013 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * 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. */ /* Windows 7, for some iphlpapi.h functionality */ #define _WIN32_WINNT 0x0601 #include #include #include #include #include #include #include "kernel_iph_net.h" #include #include #include #include /** delay before firing roam events (ms) */ #define ROAM_DELAY 500 typedef struct private_kernel_iph_net_t private_kernel_iph_net_t; /** * Private data of kernel_iph_net implementation. */ struct private_kernel_iph_net_t { /** * Public interface. */ kernel_iph_net_t public; /** * NotifyIpInterfaceChange() handle */ HANDLE changes; /** * EnableRouter() OVERLAPPED */ OVERLAPPED router; /** * Mutex to access interface list */ mutex_t *mutex; /** * Known interfaces, as iface_t */ linked_list_t *ifaces; /** * Earliest time of the next roam event */ timeval_t roam_next; /** * Roam event due to address change? */ bool roam_address; }; /** * Interface entry */ typedef struct { /** interface index */ DWORD ifindex; /** interface name */ char *ifname; /** interface description */ char *ifdesc; /** type of interface */ DWORD iftype; /** interface status */ IF_OPER_STATUS status; /** list of known addresses, as host_t */ linked_list_t *addrs; } iface_t; /** * Clean up an iface_t */ static void iface_destroy(iface_t *this) { this->addrs->destroy_offset(this->addrs, offsetof(host_t, destroy)); free(this->ifname); free(this->ifdesc); free(this); } /** * Enum names for Windows IF_OPER_STATUS */ ENUM(if_oper_names, IfOperStatusUp, IfOperStatusLowerLayerDown, "Up", "Down", "Testing", "Unknown", "Dormant", "NotPresent", "LowerLayerDown", ); /** * Callback function that raises the delayed roam event */ static job_requeue_t roam_event(private_kernel_iph_net_t *this) { bool address; this->mutex->lock(this->mutex); address = this->roam_address; this->roam_address = FALSE; this->mutex->unlock(this->mutex); hydra->kernel_interface->roam(hydra->kernel_interface, address); return JOB_REQUEUE_NONE; } /** * Fire delayed roam event, caller should hold mutex */ static void fire_roam_event(private_kernel_iph_net_t *this, bool address) { timeval_t now; time_monotonic(&now); this->roam_address |= address; if (timercmp(&now, &this->roam_next, >)) { timeval_add_ms(&now, ROAM_DELAY); this->roam_next = now; lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*) callback_job_create((callback_job_cb_t)roam_event, this, NULL, NULL), ROAM_DELAY); } } /** * Update addresses for an iface entry */ static void update_addrs(private_kernel_iph_net_t *this, iface_t *entry, IP_ADAPTER_ADDRESSES *addr, bool log) { IP_ADAPTER_UNICAST_ADDRESS *current; enumerator_t *enumerator; linked_list_t *list; host_t *host, *old; bool changes = FALSE; list = entry->addrs; entry->addrs = linked_list_create(); for (current = addr->FirstUnicastAddress; current; current = current->Next) { if (current->Address.lpSockaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin; sin = (struct sockaddr_in6*)current->Address.lpSockaddr; if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) { continue; } } host = host_create_from_sockaddr(current->Address.lpSockaddr); if (host) { bool found = FALSE; enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &old)) { if (host->ip_equals(host, old)) { list->remove_at(list, enumerator); old->destroy(old); found = TRUE; } } enumerator->destroy(enumerator); entry->addrs->insert_last(entry->addrs, host); if (!found && log) { DBG1(DBG_KNL, "%H appeared on interface %u '%s'", host, entry->ifindex, entry->ifdesc); changes = TRUE; } } } while (list->remove_first(list, (void**)&old) == SUCCESS) { if (log) { DBG1(DBG_KNL, "%H disappeared from interface %u '%s'", old, entry->ifindex, entry->ifdesc); changes = TRUE; } old->destroy(old); } list->destroy(list); if (changes) { fire_roam_event(this, TRUE); } } /** * Add an interface entry */ static void add_interface(private_kernel_iph_net_t *this, IP_ADAPTER_ADDRESSES *addr, bool log) { enumerator_t *enumerator; iface_t *entry; bool exists = FALSE; this->mutex->lock(this->mutex); enumerator = this->ifaces->create_enumerator(this->ifaces); while (enumerator->enumerate(enumerator, &entry)) { if (entry->ifindex == addr->IfIndex) { exists = TRUE; break; } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); if (!exists) { char desc[128] = ""; wcstombs(desc, addr->Description, sizeof(desc)); INIT(entry, .ifindex = addr->IfIndex, .ifname = strdup(addr->AdapterName), .ifdesc = strdup(desc), .iftype = addr->IfType, .status = addr->OperStatus, .addrs = linked_list_create(), ); if (log) { DBG1(DBG_KNL, "interface %u '%s' appeared", entry->ifindex, entry->ifdesc); } this->mutex->lock(this->mutex); update_addrs(this, entry, addr, log); this->ifaces->insert_last(this->ifaces, entry); this->mutex->unlock(this->mutex); } } /** * Remove an interface entry that is gone */ static void remove_interface(private_kernel_iph_net_t *this, NET_IFINDEX index) { enumerator_t *enumerator; iface_t *entry; this->mutex->lock(this->mutex); enumerator = this->ifaces->create_enumerator(this->ifaces); while (enumerator->enumerate(enumerator, &entry)) { if (entry->ifindex == index) { this->ifaces->remove_at(this->ifaces, enumerator); DBG1(DBG_KNL, "interface %u '%s' disappeared", entry->ifindex, entry->ifdesc); iface_destroy(entry); fire_roam_event(this, TRUE); } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); } /** * Update an interface entry changed */ static void update_interface(private_kernel_iph_net_t *this, IP_ADAPTER_ADDRESSES *addr) { enumerator_t *enumerator; iface_t *entry; this->mutex->lock(this->mutex); enumerator = this->ifaces->create_enumerator(this->ifaces); while (enumerator->enumerate(enumerator, &entry)) { if (entry->ifindex == addr->IfIndex) { if (entry->status != addr->OperStatus) { DBG1(DBG_KNL, "interface %u '%s' changed state from %N to %N", entry->ifindex, entry->ifdesc, if_oper_names, entry->status, if_oper_names, addr->OperStatus); entry->status = addr->OperStatus; fire_roam_event(this, TRUE); } update_addrs(this, entry, addr, TRUE); } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); } /** * MinGW gets MIB_IPINTERFACE_ROW wrong, as it packs InterfaceLuid just after * Family. Fix that with our own version of the struct header. */ typedef struct { ADDRESS_FAMILY Family; union { ULONG64 Value; struct { ULONG64 Reserved :24; ULONG64 NetLuidIndex :24; ULONG64 IfType :16; } Info; } InterfaceLuid; NET_IFINDEX InterfaceIndex; /* more would go here if needed */ } MIB_IPINTERFACE_ROW_FIXUP; /** * NotifyIpInterfaceChange() callback */ static void WINAPI change_interface(void *user, PMIB_IPINTERFACE_ROW row_badal, MIB_NOTIFICATION_TYPE type) { private_kernel_iph_net_t *this = user; MIB_IPINTERFACE_ROW_FIXUP* row = (MIB_IPINTERFACE_ROW_FIXUP*)row_badal; IP_ADAPTER_ADDRESSES addrs[64], *current; ULONG res, size = sizeof(addrs); if (row && type == MibDeleteInstance) { remove_interface(this, row->InterfaceIndex); } else { res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, addrs, &size); if (res == NO_ERROR) { current = addrs; while (current) { /* row is NULL only on MibInitialNotification */ if (!row || row->InterfaceIndex == current->IfIndex) { switch (type) { case MibParameterNotification: update_interface(this, current); break; case MibInitialNotification: add_interface(this, current, FALSE); break; case MibAddInstance: add_interface(this, current, TRUE); break; default: break; } } current = current->Next; } } else { DBG1(DBG_KNL, "getting IPH adapter addresses failed: 0x%08lx", res); } } } /** * Get an iface entry for a local address, does no locking */ static iface_t* address2entry(private_kernel_iph_net_t *this, host_t *ip) { enumerator_t *ifaces, *addrs; iface_t *entry, *found = NULL; host_t *host; ifaces = this->ifaces->create_enumerator(this->ifaces); while (!found && ifaces->enumerate(ifaces, &entry)) { addrs = entry->addrs->create_enumerator(entry->addrs); while (!found && addrs->enumerate(addrs, &host)) { if (host->ip_equals(host, ip)) { found = entry; } } addrs->destroy(addrs); } ifaces->destroy(ifaces); return found; } METHOD(kernel_net_t, get_interface_name, bool, private_kernel_iph_net_t *this, host_t* ip, char **name) { iface_t *entry; this->mutex->lock(this->mutex); entry = address2entry(this, ip); if (entry && name) { *name = strdup(entry->ifname); } this->mutex->unlock(this->mutex); return entry != NULL; } /** * Address enumerator */ typedef struct { /** implements enumerator_t */ enumerator_t public; /** what kind of address should we enumerate? */ kernel_address_type_t which; /** enumerator over interfaces */ enumerator_t *ifaces; /** current enumerator over addresses, or NULL */ enumerator_t *addrs; /** mutex to unlock on destruction */ mutex_t *mutex; } addr_enumerator_t; METHOD(enumerator_t, addr_enumerate, bool, addr_enumerator_t *this, host_t **host) { iface_t *entry; while (TRUE) { while (!this->addrs) { if (!this->ifaces->enumerate(this->ifaces, &entry)) { return FALSE; } if (entry->iftype == IF_TYPE_SOFTWARE_LOOPBACK && !(this->which & ADDR_TYPE_LOOPBACK)) { continue; } if (entry->status != IfOperStatusUp && !(this->which & ADDR_TYPE_DOWN)) { continue; } this->addrs = entry->addrs->create_enumerator(entry->addrs); } if (this->addrs->enumerate(this->addrs, host)) { return TRUE; } this->addrs->destroy(this->addrs); this->addrs = NULL; } } METHOD(enumerator_t, addr_destroy, void, addr_enumerator_t *this) { DESTROY_IF(this->addrs); this->ifaces->destroy(this->ifaces); this->mutex->unlock(this->mutex); free(this); } METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, private_kernel_iph_net_t *this, kernel_address_type_t which) { addr_enumerator_t *enumerator; if (!(which & ADDR_TYPE_REGULAR)) { /* we currently have no virtual, but regular IPs only */ return enumerator_create_empty(); } this->mutex->lock(this->mutex); INIT(enumerator, .public = { .enumerate = (void*)_addr_enumerate, .destroy = _addr_destroy, }, .which = which, .ifaces = this->ifaces->create_enumerator(this->ifaces), .mutex = this->mutex, ); return &enumerator->public; } METHOD(kernel_net_t, get_source_addr, host_t*, private_kernel_iph_net_t *this, host_t *dest, host_t *src) { MIB_IPFORWARD_ROW2 route; SOCKADDR_INET best, *sai_dst, *sai_src = NULL; DWORD res, index = 0; res = GetBestInterfaceEx(dest->get_sockaddr(dest), &index); if (res != NO_ERROR) { DBG1(DBG_KNL, "getting interface to %H failed: 0x%08x", dest, res); return NULL; } sai_dst = (SOCKADDR_INET*)dest->get_sockaddr(dest); if (src) { sai_src = (SOCKADDR_INET*)src->get_sockaddr(src); } res = GetBestRoute2(0, index, sai_src, sai_dst, 0, &route, &best); if (res != NO_ERROR) { DBG2(DBG_KNL, "getting src address to %H failed: 0x%08x", dest, res); return NULL; } return host_create_from_sockaddr((struct sockaddr*)&best); } METHOD(kernel_net_t, get_nexthop, host_t*, private_kernel_iph_net_t *this, host_t *dest, host_t *src) { MIB_IPFORWARD_ROW2 route; SOCKADDR_INET best, *sai_dst, *sai_src = NULL; DWORD res, index = 0; host_t *nexthop; res = GetBestInterfaceEx(dest->get_sockaddr(dest), &index); if (res != NO_ERROR) { DBG1(DBG_KNL, "getting interface to %H failed: 0x%08x", dest, res); return NULL; } sai_dst = (SOCKADDR_INET*)dest->get_sockaddr(dest); if (src) { sai_src = (SOCKADDR_INET*)src->get_sockaddr(src); } res = GetBestRoute2(0, index, sai_src, sai_dst, 0, &route, &best); if (res != NO_ERROR) { DBG2(DBG_KNL, "getting nexthop to %H failed: 0x%08x", dest, res); return NULL; } nexthop = host_create_from_sockaddr((struct sockaddr*)&route.NextHop); if (nexthop) { if (!nexthop->is_anyaddr(nexthop)) { return nexthop; } nexthop->destroy(nexthop); } return NULL; } METHOD(kernel_net_t, add_ip, status_t, private_kernel_iph_net_t *this, host_t *virtual_ip, int prefix, char *iface_name) { return NOT_SUPPORTED; } METHOD(kernel_net_t, del_ip, status_t, private_kernel_iph_net_t *this, host_t *virtual_ip, int prefix, bool wait) { return NOT_SUPPORTED; } /** * Add or remove a route */ static status_t manage_route(private_kernel_iph_net_t *this, bool add, chunk_t dst, u_int8_t prefixlen, host_t *gtw, char *name) { MIB_IPFORWARD_ROW2 row = { .DestinationPrefix = { .PrefixLength = prefixlen, }, .SitePrefixLength = prefixlen, .ValidLifetime = INFINITE, .PreferredLifetime = INFINITE, .Metric = 10, .Protocol = MIB_IPPROTO_NETMGMT, }; enumerator_t *enumerator; iface_t *entry; ULONG ret; this->mutex->lock(this->mutex); enumerator = this->ifaces->create_enumerator(this->ifaces); while (enumerator->enumerate(enumerator, &entry)) { if (streq(name, entry->ifname)) { row.InterfaceIndex = entry->ifindex; break; } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); if (!row.InterfaceIndex) { return NOT_FOUND; } switch (dst.len) { case 4: row.DestinationPrefix.Prefix.si_family = AF_INET; memcpy(&row.DestinationPrefix.Prefix.Ipv4.sin_addr, dst.ptr, dst.len); break; case 16: row.DestinationPrefix.Prefix.si_family = AF_INET6; memcpy(&row.DestinationPrefix.Prefix.Ipv6.sin6_addr, dst.ptr, dst.len); break; default: return FAILED; } if (gtw) { memcpy(&row.NextHop, gtw->get_sockaddr(gtw), *gtw->get_sockaddr_len(gtw)); } if (add) { ret = CreateIpForwardEntry2(&row); } else { ret = DeleteIpForwardEntry2(&row); } if (ret != NO_ERROR) { DBG1(DBG_KNL, "%sing route failed: 0x%08lx", add ? "add" : "remov", ret); return FAILED; } if (add) { ret = EnableRouter(NULL, &this->router); if (ret != ERROR_IO_PENDING) { DBG1(DBG_KNL, "EnableRouter router failed: 0x%08lx", ret); } } else { ret = UnenableRouter(&this->router, NULL); if (ret != NO_ERROR) { DBG1(DBG_KNL, "UnenableRouter router failed: 0x%08lx", ret); } } return SUCCESS; } METHOD(kernel_net_t, add_route, status_t, private_kernel_iph_net_t *this, chunk_t dst, u_int8_t prefixlen, host_t *gateway, host_t *src, char *name) { return manage_route(this, TRUE, dst, prefixlen, gateway, name); } METHOD(kernel_net_t, del_route, status_t, private_kernel_iph_net_t *this, chunk_t dst, u_int8_t prefixlen, host_t *gateway, host_t *src, char *name) { return manage_route(this, FALSE, dst, prefixlen, gateway, name); } METHOD(kernel_net_t, destroy, void, private_kernel_iph_net_t *this) { if (this->changes) { CancelMibChangeNotify2(this->changes); } CloseHandle(this->router.hEvent); this->mutex->destroy(this->mutex); this->ifaces->destroy_function(this->ifaces, (void*)iface_destroy); free(this); } /* * Described in header. */ kernel_iph_net_t *kernel_iph_net_create() { private_kernel_iph_net_t *this; ULONG res; INIT(this, .public = { .interface = { .get_interface = _get_interface_name, .create_address_enumerator = _create_address_enumerator, .get_source_addr = _get_source_addr, .get_nexthop = _get_nexthop, .add_ip = _add_ip, .del_ip = _del_ip, .add_route = _add_route, .del_route = _del_route, .destroy = _destroy, }, }, .router = { .hEvent = CreateEvent(NULL, FALSE, FALSE, NULL), }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .ifaces = linked_list_create(), ); /* PIPINTERFACE_CHANGE_CALLBACK is not using WINAPI in MinGW, which seems * to be wrong. Force a cast to our WINAPI call */ res = NotifyIpInterfaceChange(AF_UNSPEC, (void*)change_interface, this, TRUE, &this->changes); if (res != NO_ERROR) { DBG1(DBG_KNL, "registering for IPH interface changes failed: 0x%08lx", res); destroy(this); return NULL; } return &this->public; }