From: "Erich E. Hoover" Subject: [PATCH 2/3] server: Implement an interface change notification object. Message-Id: Date: Tue, 12 Nov 2013 10:50:30 -0700 This patch implements the SIO_ADDRESS_LIST_CHANGE call inside of the server. The implementation uses a per-socket queue so that when a socket is destroyed prior to a notification event then that socket's async operations are all appropriately woken up. Only one interface change notification object exists at a time, this object is destroyed whenever there are no sockets expecting events and is then re-created as necessary. From 2b483c3388d3538dfa69edea44221431979e71da Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Fri, 4 Oct 2013 08:22:17 -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 | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 306 insertions(+), 15 deletions(-) diff --git a/configure.ac b/configure.ac index 98220b2..1b7d9fe 100644 --- a/configure.ac +++ b/configure.ac @@ -430,7 +430,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 1a3a8f7..c617928 100644 --- a/server/sock.c +++ b/server/sock.c @@ -44,11 +44,20 @@ #include #include +#ifdef HAVE_LINUX_NETLINK_H +# include +#endif +#ifdef HAVE_LINUX_RTNETLINK_H +# include +#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" @@ -107,8 +116,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 */ }; +static int 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 ); @@ -117,6 +130,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 ); @@ -151,12 +166,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 @@ -518,6 +536,39 @@ 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 ); + obj_handle_t wait_handle = 0; + async_data_t new_data; + + assert( sock->obj.ops == &sock_ops ); + + if (blocking) + { + if (!(wait_handle = alloc_wait_event( current->process ))) return 0; + new_data = *async_data; + new_data.event = wait_handle; + async_data = &new_data; + } + switch(code) + { + case WS_SIO_ADDRESS_LIST_CHANGE: + if (sock_add_ifchange( sock, async_data )) + { + set_error( STATUS_PENDING ); + return wait_handle; + } + break; + default: + close_handle( current->process, wait_handle ); + return default_fd_ioctl(fd, code, async_data, blocking, data, size); + } + close_handle( current->process, wait_handle ); + return 0; +} + static void sock_queue_async( struct fd *fd, const async_data_t *data, int type, int count ) { struct sock *sock = get_fd_user( fd ); @@ -587,11 +638,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) { @@ -618,6 +675,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) ); } @@ -906,6 +964,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 */ +int 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 FALSE; + 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 ); + return TRUE; + +fail: + free_async_queue( sock->ifchange_q ); + sock->ifchange_q = NULL; + release_object( ifchange ); + return FALSE; +} + +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