From 6563640bd59351f7ac0b851b5fb90e728a51a37f Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Fri, 6 Sep 2013 11:28:42 -0600
Subject: server: Implement an interface change notification object.

---
 configure.ac        |    2 +
 server/event.c      |   13 +++
 server/named_pipe.c |   13 ---
 server/object.h     |    1 +
 server/sock.c       |  285 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 299 insertions(+), 15 deletions(-)

diff --git a/configure.ac b/configure.ac
index d8033cf..c0ac04a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -438,7 +438,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/server/event.c b/server/event.c
index b8515af..e8a3888 100644
--- a/server/event.c
+++ b/server/event.c
@@ -124,6 +124,19 @@ struct event *create_event( struct directory *root, const struct unicode_str *na
     return event;
 }
 
+obj_handle_t alloc_wait_event( struct process *process )
+{
+    obj_handle_t handle = 0;
+    struct event *event = create_event( NULL, NULL, 0, 1, 0, NULL );
+
+    if (event)
+    {
+        handle = alloc_handle( process, event, EVENT_ALL_ACCESS, 0 );
+        release_object( event );
+    }
+    return handle;
+}
+
 struct event *get_event_obj( struct process *process, obj_handle_t handle, unsigned int access )
 {
     return (struct event *)get_handle_obj( process, handle, access, &event_ops );
diff --git a/server/named_pipe.c b/server/named_pipe.c
index 4c85104..6ba2145 100644
--- a/server/named_pipe.c
+++ b/server/named_pipe.c
@@ -587,19 +587,6 @@ static enum server_fd_type pipe_client_get_fd_type( struct fd *fd )
     return FD_TYPE_PIPE;
 }
 
-static obj_handle_t alloc_wait_event( struct process *process )
-{
-    obj_handle_t handle = 0;
-    struct event *event = create_event( NULL, NULL, 0, 1, 0, NULL );
-
-    if (event)
-    {
-        handle = alloc_handle( process, event, EVENT_ALL_ACCESS, 0 );
-        release_object( event );
-    }
-    return handle;
-}
-
 static obj_handle_t pipe_server_ioctl( struct fd *fd, ioctl_code_t code, const async_data_t *async_data,
                                        int blocking, const void *data, data_size_t size )
 {
diff --git a/server/object.h b/server/object.h
index bb3ff21..bad162f 100644
--- a/server/object.h
+++ b/server/object.h
@@ -159,6 +159,7 @@ extern struct event *create_event( struct directory *root, const struct unicode_
                                    const struct security_descriptor *sd );
 extern struct keyed_event *create_keyed_event( struct directory *root, const struct unicode_str *name,
                                                unsigned int attr, const struct security_descriptor *sd );
+extern obj_handle_t alloc_wait_event( struct process *process );
 extern struct event *get_event_obj( struct process *process, obj_handle_t handle, unsigned int access );
 extern struct keyed_event *get_keyed_event_obj( struct process *process, obj_handle_t handle, unsigned int access );
 extern void pulse_event( struct event *event );
diff --git a/server/sock.c b/server/sock.c
index 041867e..0d639d9 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -44,11 +44,20 @@
 #include <time.h>
 #include <unistd.h>
 
+#ifdef HAVE_LINUX_NETLINK_H
+# include <linux/netlink.h>
+#endif
+#ifdef HAVE_LINUX_RTNETLINK_H
+# include <linux/rtnetlink.h>
+#endif
+
 #include "ntstatus.h"
 #define WIN32_NO_STATUS
 #include "windef.h"
 #include "winternl.h"
 #include "winerror.h"
+#define USE_WS_PREFIX
+#include "winsock2.h"
 
 #include "process.h"
 #include "file.h"
@@ -106,8 +115,12 @@ struct sock
     struct sock        *deferred;    /* socket that waits for a deferred accept */
     struct async_queue *read_q;      /* queue for asynchronous reads */
     struct async_queue *write_q;     /* queue for asynchronous writes */
+    struct async_queue *ifchange_q;  /* queue for interface change notifications */
+    struct list         ifchange_entry; /* entry in ifchange notification list */
 };
 
+void sock_add_ifchange( struct sock *sock, const async_data_t *async_data );
+
 static void sock_dump( struct object *obj, int verbose );
 static int sock_signaled( struct object *obj, struct wait_queue_entry *entry );
 static struct fd *sock_get_fd( struct object *obj );
@@ -116,6 +129,8 @@ static void sock_destroy( struct object *obj );
 static int sock_get_poll_events( struct fd *fd );
 static void sock_poll_event( struct fd *fd, int event );
 static enum server_fd_type sock_get_fd_type( struct fd *fd );
+static obj_handle_t sock_ioctl( struct fd *fd, ioctl_code_t code, const async_data_t *async,
+                                int blocking, const void *data, data_size_t size );
 static void sock_queue_async( struct fd *fd, const async_data_t *data, int type, int count );
 static void sock_reselect_async( struct fd *fd, struct async_queue *queue );
 static void sock_cancel_async( struct fd *fd, struct process *process, struct thread *thread, client_ptr_t iosb );
@@ -150,12 +165,15 @@ static const struct fd_ops sock_fd_ops =
     sock_poll_event,              /* poll_event */
     no_flush,                     /* flush */
     sock_get_fd_type,             /* get_fd_type */
-    default_fd_ioctl,             /* ioctl */
+    sock_ioctl,                   /* ioctl */
     sock_queue_async,             /* queue_async */
     sock_reselect_async,          /* reselect_async */
     sock_cancel_async             /* cancel_async */
 };
 
+/* only keep one ifchange object around, all sockets waiting for wakeups will look to it */
+static struct object *ifchange_object = NULL;
+
 
 /* Permutation of 0..FD_MAX_EVENTS - 1 representing the order in which
  * we post messages if there are multiple events.  Used to send
@@ -517,6 +535,32 @@ static enum server_fd_type sock_get_fd_type( struct fd *fd )
     return FD_TYPE_SOCKET;
 }
 
+obj_handle_t sock_ioctl( struct fd *fd, ioctl_code_t code, const async_data_t *async_data,
+                         int blocking, const void *data, data_size_t size )
+{
+    struct sock *sock = get_fd_user( fd );
+    async_data_t new_data = *async_data;
+    obj_handle_t wait_handle = 0;
+
+    assert( sock->obj.ops == &sock_ops );
+
+    if (blocking)
+    {
+        if (!(wait_handle = alloc_wait_event( current->process ))) return 0;
+        new_data.event = wait_handle;
+        async_data = &new_data;
+    }
+    switch(code)
+    {
+    case WS_SIO_ADDRESS_LIST_CHANGE:
+        sock_add_ifchange( sock, async_data );
+        break;
+    default:
+        return default_fd_ioctl(fd, code, async_data, blocking, data, size);
+    }
+    return wait_handle;
+}
+
 static void sock_queue_async( struct fd *fd, const async_data_t *data, int type, int count )
 {
     struct sock *sock = get_fd_user( fd );
@@ -586,11 +630,17 @@ static void sock_destroy( struct object *obj )
 
     /* FIXME: special socket shutdown stuff? */
 
-    if ( sock->deferred )
+    if (sock->deferred)
         release_object( sock->deferred );
 
     free_async_queue( sock->read_q );
     free_async_queue( sock->write_q );
+    if (sock->ifchange_q)
+    {
+        free_async_queue( sock->ifchange_q );
+        list_remove( &sock->ifchange_entry );
+        release_object( ifchange_object );
+    }
     if (sock->event) release_object( sock->event );
     if (sock->fd)
     {
@@ -617,6 +667,7 @@ static void init_sock(struct sock *sock)
     sock->deferred = NULL;
     sock->read_q  = NULL;
     sock->write_q = NULL;
+    sock->ifchange_q = NULL;
     memset( sock->errors, 0, sizeof(sock->errors) );
 }
 
@@ -903,6 +954,236 @@ static void sock_set_error(void)
     set_error( sock_get_ntstatus( errno ) );
 }
 
+static void ifchange_dump( struct object *obj, int verbose );
+static struct fd *ifchange_get_fd( struct object *obj );
+static void ifchange_destroy( struct object *obj );
+
+static int ifchange_get_poll_events( struct fd *fd );
+static void ifchange_poll_event( struct fd *fd, int event );
+static void ifchange_reselect_async( struct fd *fd, struct async_queue *queue );
+
+struct ifchange
+{
+    struct object       obj;         /* object header */
+    struct fd          *fd;          /* interface change file descriptor */
+    struct list         sockets;     /* list of sockets to send interface change notifications */
+};
+
+static const struct object_ops ifchange_ops =
+{
+    sizeof(struct ifchange),      /* size */
+    ifchange_dump,                /* dump */
+    no_get_type,                  /* get_type */
+    add_queue,                    /* add_queue */
+    NULL,                         /* remove_queue */
+    NULL,                         /* signaled */
+    no_satisfied,                 /* satisfied */
+    no_signal,                    /* signal */
+    ifchange_get_fd,              /* get_fd */
+    default_fd_map_access,        /* map_access */
+    default_get_sd,               /* get_sd */
+    default_set_sd,               /* set_sd */
+    no_lookup_name,               /* lookup_name */
+    no_open_file,                 /* open_file */
+    no_close_handle,              /* close_handle */
+    ifchange_destroy              /* destroy */
+};
+
+static const struct fd_ops ifchange_fd_ops =
+{
+    ifchange_get_poll_events,     /* get_poll_events */
+    ifchange_poll_event,          /* poll_event */
+    NULL,                         /* flush */
+    NULL,                         /* get_fd_type */
+    NULL,                         /* ioctl */
+    NULL,                         /* queue_async */
+    ifchange_reselect_async,      /* reselect_async */
+    NULL                          /* cancel_async */
+};
+
+static int init_ifchange( struct ifchange *ifchange )
+{
+#if defined(NETLINK_ROUTE)
+    struct sockaddr_nl addr;
+    int unix_fd;
+
+    list_init( &ifchange->sockets );
+    unix_fd = socket( PF_NETLINK, SOCK_RAW, NETLINK_ROUTE );
+    if (unix_fd == -1)
+    {
+        sock_set_error();
+        return 0;
+    }
+    fcntl( unix_fd, F_SETFL, O_NONBLOCK ); /* make socket nonblocking */
+    memset( &addr, 0, sizeof(addr) );
+    addr.nl_family = AF_NETLINK;
+    addr.nl_groups = RTMGRP_IPV4_IFADDR;
+    /* bind the socket to the special netlink kernel interface */
+    if (bind( unix_fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1)
+    {
+        sock_set_error();
+        close( unix_fd );
+        return 0;
+    }
+    if (!(ifchange->fd = create_anonymous_fd( &ifchange_fd_ops, unix_fd, &ifchange->obj, 0 )))
+    {
+        close( unix_fd );
+        return 0;
+    }
+    /* enable read wakeup on the file descriptor */
+    set_fd_events( ifchange->fd, POLLIN );
+    return 1;
+#else
+    fprintf(stderr, "Interface change notification is not supported on this platform.\n");
+    set_error( STATUS_NOT_SUPPORTED );
+    return 0;
+#endif
+}
+
+/* create a new ifchange notifier or, if one already exists, reuse the existing one */
+static struct object *create_ifchange( void )
+{
+    struct ifchange *ifchange;
+
+    /* we only need one of these interface notification objects, all of the sockets dependent upon
+     * it will wake up when a notification event occurs */
+    if (ifchange_object)
+        return grab_object( ifchange_object );
+    if (!(ifchange = alloc_object( &ifchange_ops )))
+        return NULL;
+    if (!init_ifchange( ifchange ))
+    {
+        release_object( ifchange );
+        return NULL;
+    }
+    ifchange_object = &ifchange->obj;
+    return ifchange_object;
+}
+
+/* add a socket to the interface change notification's list of sockets */
+void ifchange_add_sock( struct object *obj, struct sock *sock )
+{
+    struct ifchange *ifchange = (struct ifchange *)obj;
+
+    list_add_tail( &ifchange->sockets, &sock->ifchange_entry );
+}
+
+/* wake up an ifchange notification queue for a socket and decrement the ifchange object refcount */
+void sock_ifchange_wake_up( struct sock *sock, unsigned int status )
+{
+    assert( sock->ifchange_q );
+    async_wake_up( sock->ifchange_q, status );
+    free_async_queue( sock->ifchange_q );
+    sock->ifchange_q = NULL;
+    list_remove( &sock->ifchange_entry );
+    release_object( ifchange_object );
+}
+
+/* add interface change notification to a socket */
+void sock_add_ifchange( struct sock *sock, const async_data_t *async_data )
+{
+    struct object *ifchange = ifchange_object;
+    struct async *async;
+    struct fd *fd;
+
+    if (!sock->ifchange_q)
+    {
+        /* associate this socket with the interface change object */
+        ifchange = create_ifchange();
+        if (!ifchange) return;
+        ifchange_add_sock( ifchange, sock ); /* add this socket to the change notification list */
+        if (!(fd = ifchange_get_fd( ifchange ))) goto fail;
+        sock->ifchange_q = create_async_queue( fd );
+        release_object( fd );
+        if (!sock->ifchange_q) goto fail;
+    }
+    if (!(async = create_async( current, sock->ifchange_q, async_data ))) goto fail;
+    release_object( async );
+    set_error( STATUS_PENDING );
+    return;
+
+fail:
+    free_async_queue( sock->ifchange_q );
+    sock->ifchange_q = NULL;
+    release_object( ifchange );
+}
+
+static void ifchange_dump( struct object *obj, int verbose )
+{
+    assert( obj->ops == &ifchange_ops );
+    printf( "ifchange\n" );
+}
+
+static struct fd *ifchange_get_fd( struct object *obj )
+{
+    struct ifchange *ifchange = (struct ifchange *)obj;
+    return (struct fd *)grab_object( ifchange->fd );
+}
+
+static void ifchange_destroy( struct object *obj )
+{
+    struct ifchange *ifchange = (struct ifchange *)obj;
+    assert( obj->ops == &ifchange_ops );
+
+    /* reset the global ifchange object so that it will be recreated if it is needed again */
+    ifchange_object = NULL;
+    /* shut the socket down to force pending poll() calls in the client to return */
+    shutdown( get_unix_fd(ifchange->fd), SHUT_RDWR );
+    release_object( ifchange->fd );
+}
+
+static int ifchange_get_poll_events( struct fd *fd )
+{
+    return POLLIN;
+}
+
+/* wake up all the sockets waiting for a change notification event */
+static void ifchange_wake_up( struct object *obj, unsigned int status )
+{
+    struct ifchange *ifchange = (struct ifchange *) obj;
+    struct list *ptr, *next;
+
+    assert( obj->ops == &ifchange_ops );
+    LIST_FOR_EACH_SAFE( ptr, next, &ifchange->sockets )
+    {
+        struct sock *sock = LIST_ENTRY( ptr, struct sock, ifchange_entry );
+
+        sock_ifchange_wake_up( sock, status );
+    }
+}
+
+static void ifchange_poll_event( struct fd *fd, int event )
+{
+    struct object *ifchange = get_fd_user( fd );
+    int r, unix_fd, wakeup = FALSE;
+    char buffer[0x1000];
+
+    unix_fd = get_unix_fd( fd );
+    r = recv( unix_fd, buffer, sizeof(buffer), 0 );
+    if (r < 0)
+    {
+        fprintf(stderr,"ifchange_poll_event(): ifchange read failed!\n");
+        return;
+    }
+    else if (r != 0)
+    {
+#if defined(NETLINK_ROUTE)
+        struct nlmsghdr *nlh;
+
+        nlh = (struct nlmsghdr*) buffer;
+        if (NLMSG_OK(nlh, r) && (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR))
+            wakeup = TRUE;
+#endif
+    }
+    if (wakeup)
+        ifchange_wake_up( ifchange, STATUS_SUCCESS );
+}
+
+static void ifchange_reselect_async( struct fd *fd, struct async_queue *queue )
+{
+    /* do nothing, this object is about to disappear */
+}
+
 /* create a socket */
 DECL_HANDLER(create_socket)
 {
-- 
1.7.9.5

