From 67c47e401ba7bc00b0b178588ff7fbfccc4aef01 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Tue, 6 May 2014 08:49:52 -0600 Subject: [PATCH] server: Implement the interface change notification object. --- server/sock.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 5 deletions(-) diff --git a/server/sock.c b/server/sock.c index 748159fbed6..f3bab855280 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 @@ -979,16 +983,208 @@ static int sock_add_ifchange( struct sock *sock, const async_data_t *async_data return 1; } -/* 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; + +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 { - set_error( STATUS_NOT_SUPPORTED ); - return NULL; + 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 ); + fprintf( stderr, "Interface change\n" ); } -/* stub ifchange add socket to list */ +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 ); + + release_object( 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; +} + +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; + } + } + else status = STATUS_CANCELLED; + + 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 +} + +/* 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 */