From 3d25c088d723eb284041bf3cb2e8e880eb650477 Mon Sep 17 00:00:00 2001
From: Erich Hoover <ehoover@mines.edu>
Date: Fri, 10 May 2013 20:00:41 -0600
Subject: iphlpapi: Implement NotifyAddrChange on Linux.

---
 configure.ac                   |    2 +
 dlls/iphlpapi/iphlpapi_main.c  |  170 ++++++++++++++++++++++++++++++++++++++--
 dlls/iphlpapi/tests/iphlpapi.c |    4 +-
 3 files changed, 169 insertions(+), 7 deletions(-)

diff --git a/configure.ac b/configure.ac
index 6dd2348..f2c61c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -443,7 +443,9 @@ AC_CHECK_HEADERS(\
 	linux/ioctl.h \
 	linux/joystick.h \
 	linux/major.h \
+	linux/netlink.h \
 	linux/param.h \
+	linux/rtnetlink.h \
 	linux/serial.h \
 	linux/types.h \
 	linux/ucdrom.h \
diff --git a/dlls/iphlpapi/iphlpapi_main.c b/dlls/iphlpapi/iphlpapi_main.c
index aeb91c1..564ee48 100644
--- a/dlls/iphlpapi/iphlpapi_main.c
+++ b/dlls/iphlpapi/iphlpapi_main.c
@@ -38,6 +38,8 @@
 
 #define NONAMELESSUNION
 #define NONAMELESSSTRUCT
+#include "ntstatus.h"
+#define WIN32_NO_STATUS
 #include "windef.h"
 #include "winbase.h"
 #include "winreg.h"
@@ -50,9 +52,18 @@
 #include "ipstats.h"
 #include "ipifcons.h"
 #include "fltdefs.h"
+#include "unistd.h"
 
+#include "wine/server.h"
 #include "wine/debug.h"
 
+#ifdef HAVE_LINUX_NETLINK_H
+# include <linux/netlink.h>
+#endif
+#ifdef HAVE_LINUX_RTNETLINK_H
+# include <linux/rtnetlink.h>
+#endif
+
 WINE_DEFAULT_DEBUG_CHANNEL(iphlpapi);
 
 #ifndef IF_NAMESIZE
@@ -2027,6 +2038,118 @@ DWORD WINAPI IpRenewAddress(PIP_ADAPTER_INDEX_MAP AdapterInfo)
 }
 
 
+/***********************************************************************
+ *     IPHLPAPI_createmonitorhandle           (INTERNAL)
+ * 
+ * Routine to create the interface monitoring handle for NotifyAddrChange requests.
+ */
+static NTSTATUS IPHLPAPI_createmonitorhandle(HANDLE *h)
+{
+    NTSTATUS status = ERROR_NOT_SUPPORTED;
+
+    *h = INVALID_HANDLE_VALUE;
+#if defined(NETLINK_ROUTE)
+    SERVER_START_REQ( create_socket )
+    {
+        req->family     = PF_NETLINK;
+        req->type       = SOCK_RAW;
+        req->protocol   = NETLINK_ROUTE;
+        req->access     = GENERIC_READ|SYNCHRONIZE;
+        req->attributes = OBJ_INHERIT;
+        req->flags      = WSA_FLAG_OVERLAPPED;
+        status  = wine_server_call( req );
+        *h = wine_server_ptr_handle( reply->handle );
+    }
+    SERVER_END_REQ;
+#else
+    FIXME("Interface monitoring is not currently supported on this platform.\n");
+#endif
+    return status;
+}
+
+
+/***********************************************************************
+ *     IPHLPAPI_initmonitorhandle           (INTERNAL)
+ * 
+ * Routine to setup the interface monitoring handle for NotifyAddrChange requests.
+ */
+static NTSTATUS IPHLPAPI_initmonitorhandle(HANDLE h)
+{
+    NTSTATUS status = ERROR_NOT_SUPPORTED;
+    int fd = -1;
+
+    status = wine_server_handle_to_fd( h, FILE_READ_DATA, &fd, NULL );
+    if (status == STATUS_SUCCESS)
+    {
+#if defined(RTMGRP_IPV4_IFADDR)
+        struct sockaddr_nl addr;
+
+        memset( &addr, 0, sizeof(addr) );
+        addr.nl_family = AF_NETLINK;
+        addr.nl_groups = RTMGRP_IPV4_IFADDR;
+        if (bind( fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1)
+            status = ERROR_NOT_SUPPORTED;
+#endif
+    }
+    wine_server_release_fd( h, fd );
+    return status;
+}
+
+
+/***********************************************************************
+ *     IPHLPAPI_monitorifchange           (INTERNAL)
+ * 
+ * Routine to detect interface changes for NotifyAddrChange requests.
+ */
+static NTSTATUS IPHLPAPI_monitorifchange(HANDLE h)
+{
+    NTSTATUS status = ERROR_NOT_SUPPORTED;
+    char buffer[4096];
+    int fd = -1;
+    size_t len;
+
+    status = wine_server_handle_to_fd( h, FILE_READ_DATA, &fd, NULL );
+    if (status != STATUS_SUCCESS)
+        return status;
+    status = STATUS_PENDING;
+    if ((len = recv( fd, buffer, sizeof(buffer), 0 )) != 0)
+    {
+#if defined(NLMSG_OK)
+        struct nlmsghdr *nlh;
+
+        nlh = (struct nlmsghdr*) buffer;
+        if (NLMSG_OK(nlh, len) && (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR))
+            status = STATUS_SUCCESS;
+#endif
+    }
+    wine_server_release_fd( h, fd );
+    return status;
+}
+
+
+/***********************************************************************
+ *     IPHLPAPI_apc_NotifyAddrChange           (INTERNAL)
+ * 
+ * APC for handling NotifyAddrChange requests.
+ */
+static NTSTATUS IPHLPAPI_apc_NotifyAddrChange(void *arg, IO_STATUS_BLOCK *iosb, NTSTATUS status, void **apc)
+{
+    LPOVERLAPPED overlapped = (LPOVERLAPPED) iosb;
+    HANDLE h = (HANDLE) arg;
+
+    if (status != STATUS_ALERTED)
+        return status;
+    status = IPHLPAPI_monitorifchange( h );
+    if (iosb)
+    {
+        iosb->u.Status = status;
+        if (overlapped->hEvent)
+            SetEvent( overlapped->hEvent );
+    }
+    return status;
+}
+
+
 /******************************************************************
  *    NotifyAddrChange (IPHLPAPI.@)
  *
@@ -2043,12 +2166,49 @@ DWORD WINAPI IpRenewAddress(PIP_ADAPTER_INDEX_MAP AdapterInfo)
  * FIXME
  *  Stub, returns ERROR_NOT_SUPPORTED.
  */
-DWORD WINAPI NotifyAddrChange(PHANDLE Handle, LPOVERLAPPED overlapped)
+DWORD WINAPI NotifyAddrChange(PHANDLE handle, LPOVERLAPPED overlapped)
 {
-  FIXME("(Handle %p, overlapped %p): stub\n", Handle, overlapped);
-  if (Handle) *Handle = INVALID_HANDLE_VALUE;
-  if (overlapped) ((IO_STATUS_BLOCK *) overlapped)->u.Status = STATUS_PENDING;
-  return ERROR_IO_PENDING;
+    IO_STATUS_BLOCK *iosb = (IO_STATUS_BLOCK *) overlapped;
+    NTSTATUS status;
+    HANDLE h;
+
+    TRACE("(handle %p, overlapped %p): stub\n", handle, overlapped);
+
+    status = IPHLPAPI_createmonitorhandle( &h );
+    if (status != STATUS_SUCCESS)
+    {
+        ERR("Could not create interface monitoring socket.\n");
+        goto done;
+    }
+    status = IPHLPAPI_initmonitorhandle( h );
+    if (status != STATUS_SUCCESS)
+    {
+        ERR("Could not initialize interface monitoring socket.\n");
+        goto done;
+    }
+
+    if (!overlapped)
+    {
+        while ((status = IPHLPAPI_monitorifchange( h )) == STATUS_PENDING)
+            Sleep( 0 );
+        goto done;
+    }
+    /* for overlapped operation wait for the server to inform us that it's time to read data */
+    *handle = h;
+    SERVER_START_REQ( register_async )
+    {
+        req->type           = ASYNC_TYPE_READ;
+        req->async.handle   = wine_server_obj_handle( h );
+        req->async.callback = wine_server_client_ptr( IPHLPAPI_apc_NotifyAddrChange );
+        req->async.iosb     = wine_server_client_ptr( iosb );
+        req->async.arg      = wine_server_client_ptr( h );
+        status = wine_server_call( req );
+    }
+    SERVER_END_REQ;
+
+done:
+    if (iosb) iosb->u.Status = status;
+    return RtlNtStatusToDosError( status );
 }
 
 
diff --git a/dlls/iphlpapi/tests/iphlpapi.c b/dlls/iphlpapi/tests/iphlpapi.c
index 26bf26e..684b1ec 100644
--- a/dlls/iphlpapi/tests/iphlpapi.c
+++ b/dlls/iphlpapi/tests/iphlpapi.c
@@ -1065,7 +1065,7 @@ static void testNotifyAddrChange(void)
     overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     ret = pNotifyAddrChange(&handle, &overlapped);
     ok(ret == ERROR_IO_PENDING, "NotifyAddrChange returned %d, expected ERROR_IO_PENDING\n", ret);
-    todo_wine ok(handle != INVALID_HANDLE_VALUE, "NotifyAddrChange returned invalid file handle\n");
+    ok(handle != INVALID_HANDLE_VALUE, "NotifyAddrChange returned invalid file handle\n");
     success = GetOverlappedResult(handle, &overlapped, &bytes, FALSE);
     ok(success == FALSE, "GetOverlappedResult returned TRUE, expected FALSE\n");
     ret = GetLastError();
@@ -1092,7 +1092,7 @@ static void testNotifyAddrChange(void)
         trace("Testing synchronous ipv4 address change notification. Please "
               "change the ipv4 address of one of your network interfaces\n");
         ret = pNotifyAddrChange(NULL, NULL);
-        todo_wine ok(ret == NO_ERROR, "NotifyAddrChange returned %d, expected NO_ERROR\n", ret);
+        ok(ret == NO_ERROR, "NotifyAddrChange returned %d, expected NO_ERROR\n", ret);
     }
 }
 
-- 
1.7.9.5

