From: "Erich E. Hoover" Subject: [PATCH 4/5] server: Implement the interface change notification object (resend 3). Message-Id: Date: Wed, 20 Aug 2014 23:47:20 -0600 This patch is the meat of the interface change notification object, which uses RTNETLINK to create events when an interface is added or removed. The patch has been updated with this revision to set errors locally, rather than passing the error up to the caller. From c82a0973b59832ec5f9b5e7c992d19b18d63cec1 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Tue, 6 May 2014 08:49:52 -0600 Subject: server: Implement the interface change notification object. --- server/sock.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 3 deletions(-) diff --git a/server/sock.c b/server/sock.c index f9d0a52..5ce3d36 100644 --- a/server/sock.c +++ b/server/sock.c @@ -46,6 +46,10 @@ #endif #include #include +#include +#ifdef HAVE_LINUX_RTNETLINK_H +# include +#endif #include "ntstatus.h" #define WIN32_NO_STATUS @@ -975,16 +979,212 @@ static int sock_add_ifchange( struct sock *sock, const async_data_t *async_data return TRUE; } -/* stub ifchange object */ -static struct object *get_ifchange( void ) +#ifdef HAVE_LINUX_RTNETLINK_H + +/* only keep one ifchange object around, all sockets waiting for wakeups will look to it */ +static struct object *ifchange_object = NULL; + +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 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 ); + + if (ifchange->fd) + { + /* reset the global ifchange object so that it will be recreated if it is needed again */ + assert( obj == ifchange_object ); + 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 ); + assert( obj == ifchange_object ); + + LIST_FOR_EACH_SAFE( ptr, next, &ifchange->sockets ) + { + struct sock *sock = LIST_ENTRY( ptr, struct sock, ifchange_entry ); + + assert( sock->ifchange_q ); + async_wake_up( sock->ifchange_q, status ); /* issue ifchange notification for the socket */ + sock_destroy_ifchange_q( sock ); /* remove socket from list and decrement ifchange refcount */ + } +} + +static void ifchange_poll_event( struct fd *fd, int event ) +{ + struct object *ifchange = get_fd_user( fd ); + unsigned int status = STATUS_PENDING; + char buffer[PIPE_BUF]; + int r; + + r = recv( get_unix_fd(fd), buffer, sizeof(buffer), MSG_DONTWAIT ); + if (r < 0) + { + if (errno == EWOULDBLOCK || errno == EAGAIN) + return; /* retry when poll() says the socket is ready */ + status = sock_get_ntstatus( errno ); + } + else if (r > 0) + { + struct nlmsghdr *nlh; + + for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, r); nlh = NLMSG_NEXT(nlh, r)) + { + if (nlh->nlmsg_type == NLMSG_DONE) + break; + if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR) + status = STATUS_SUCCESS; + } + } + if (status != STATUS_PENDING) + ifchange_wake_up( ifchange, status ); +} + +static void ifchange_reselect_async( struct fd *fd, struct async_queue *queue ) { + /* do nothing, this object is about to disappear */ +} + +#endif + +/* we only need one of these interface notification objects, all of the sockets dependent upon + * it will wake up when a notification event occurs */ + static struct object *get_ifchange( void ) + { +#ifdef HAVE_LINUX_RTNETLINK_H + struct ifchange *ifchange; + struct sockaddr_nl addr; + int unix_fd; + + if (ifchange_object) + { + /* increment the refcount for each socket that uses the ifchange object */ + return grab_object( ifchange_object ); + } + + /* create the socket we need for processing interface change notifications */ + unix_fd = socket( PF_NETLINK, SOCK_RAW, NETLINK_ROUTE ); + if (unix_fd == -1) + { + sock_set_error(); + return NULL; + } + 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) + { + close( unix_fd ); + sock_set_error(); + return NULL; + } + if (!(ifchange = alloc_object( &ifchange_ops ))) + { + close( unix_fd ); + set_error( STATUS_NO_MEMORY ); + return NULL; + } + list_init( &ifchange->sockets ); + if (!(ifchange->fd = create_anonymous_fd( &ifchange_fd_ops, unix_fd, &ifchange->obj, 0 ))) + { + release_object( ifchange ); + set_error( STATUS_NO_MEMORY ); + return NULL; + } + set_fd_events( ifchange->fd, POLLIN ); /* enable read wakeup on the file descriptor */ + + /* the ifchange object is now successfully configured */ + ifchange_object = &ifchange->obj; + return &ifchange->obj; +#else set_error( STATUS_NOT_SUPPORTED ); return NULL; +#endif } -/* stub ifchange add socket to list */ +/* add the socket to the interface change notification list */ static void ifchange_add_sock( struct object *obj, struct sock *sock ) { +#ifdef HAVE_LINUX_RTNETLINK_H + struct ifchange *ifchange = (struct ifchange *)obj; + + list_add_tail( &ifchange->sockets, &sock->ifchange_entry ); +#endif } /* create a new ifchange queue for a specific socket or, if one already exists, reuse the existing one */ -- 1.7.9.5