aboutsummaryrefslogtreecommitdiffstats
path: root/src/libcharon/plugins/kernel_iph/kernel_iph_net.c
diff options
context:
space:
mode:
authorMartin Willi <martin@revosec.ch>2014-06-04 16:32:23 +0200
committerMartin Willi <martin@revosec.ch>2014-06-04 16:32:23 +0200
commitd2859f52516b228c27bdba2ea924c6ca74de5ffb (patch)
tree94fa5c78fa1a9456068694c18f9aa4f9086f7a1b /src/libcharon/plugins/kernel_iph/kernel_iph_net.c
parent893e8ceee3acc39693d80c79f6f0953326b5ed74 (diff)
parent4732e29a1d5ed9bd136a804e180cb45d23d24f86 (diff)
downloadstrongswan-d2859f52516b228c27bdba2ea924c6ca74de5ffb.tar.bz2
strongswan-d2859f52516b228c27bdba2ea924c6ca74de5ffb.tar.xz
Merge branch 'win-kernel'
Adds the kernel-iph and kernel-wfp kernel backends for the Windows platform. kernel-iph provides a networking backend using the IP Helper native Windows API, while the kernel-wfp backend implements an interface to the Windows Kernel IPsec layer using the Windows Filtering Platform API.
Diffstat (limited to 'src/libcharon/plugins/kernel_iph/kernel_iph_net.c')
-rw-r--r--src/libcharon/plugins/kernel_iph/kernel_iph_net.c772
1 files changed, 772 insertions, 0 deletions
diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c
new file mode 100644
index 000000000..68b753792
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c
@@ -0,0 +1,772 @@
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <winsock2.h>
+#include <ws2ipdef.h>
+#include <windows.h>
+#include <ntddndis.h>
+#include <naptypes.h>
+#include <iphlpapi.h>
+
+#include "kernel_iph_net.h"
+
+#include <hydra.h>
+#include <threading/mutex.h>
+#include <collections/linked_list.h>
+#include <processing/jobs/callback_job.h>
+
+
+/** 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 change_interface(private_kernel_iph_net_t *this,
+ MIB_IPINTERFACE_ROW_FIXUP *row, MIB_NOTIFICATION_TYPE type)
+{
+ 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(),
+ );
+
+ 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;
+}