]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
lldpctl: add a "-w" option to watch neighbor changes
authorVincent Bernat <bernat@luffy.cx>
Thu, 30 Aug 2012 22:59:30 +0000 (00:59 +0200)
committerVincent Bernat <bernat@luffy.cx>
Thu, 30 Aug 2012 22:59:30 +0000 (00:59 +0200)
19 files changed:
NEWS
man/lldpctl.8
src/client/client.h
src/client/display.c
src/client/lldpctl.c
src/ctl.c
src/ctl.h
src/daemon/client.c
src/daemon/event.c
src/daemon/lldpd.c
src/daemon/lldpd.h
src/lib/atom-private.c
src/lib/atom.c
src/lib/connection.c
src/lib/lldpctl.h
src/lib/private.h
src/lldpd-structs.c
src/lldpd-structs.h
src/marshal.h

diff --git a/NEWS b/NEWS
index 98eca5f4b6ed81902ef00ff5d59d09753e30a777..f83d404495a8616a082e389ec4d9c320dbc0e1eb 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ lldpd (0.6.1)
       documentation is provided through Doxygen. See src/lib/lldpctl.h
       which contains all the exported functions.
     + Make lldpctl uses liblldpctl.so.
+    + Add a "watch" option to lldpctl to monitor neighbor changes.
 
 lldpd (0.6)
   * Features:
index 941bdc3c1486eb17d5cc7d084e518ff0762a9acc..436e7b243c468c85c3b6413109ca50a080268ac1 100644 (file)
@@ -21,7 +21,7 @@
 .Nd control LLDP daemon
 .Sh SYNOPSIS
 .Nm
-.Op Fl adv
+.Op Fl advw
 .Op Fl L Ar location
 .Op Fl P Ar policy
 .Op Fl O Ar poe
@@ -50,6 +50,8 @@ Show
 version.
 .It Fl a
 Display all remote ports, including those hidden by the smart filter.
+.It Fl w
+Monitor neighbor changes. Each changed neighbor will be displayed.
 .It Fl f Ar format
 Choose the output format. Currently
 .Em plain ,
index 61f55f30212373cb3c4b8ab074740185c38fe4ee..c57b7e11190670ea02d7f1645e3e2d7fa5b5b400 100644 (file)
 #include "../lldp-const.h"
 #include "writer.h"
 
-#define LLDPCTL_ARGS "hdvaf:L:P:O:o:"
+#define LLDPCTL_ARGS "hdvaf:L:P:O:o:w"
 
 /* display.c */
-void display_interfaces(lldpctl_conn_t *, const char *, int, int, char **);
+void display_interfaces(lldpctl_conn_t *, struct writer *, int, int, char **);
+void display_interface(lldpctl_conn_t *, struct writer *, int,
+    lldpctl_atom_t *, lldpctl_atom_t *);
 
 /* actions.c */
 void modify_interfaces(lldpctl_conn_t *, int, char **, int);
index 524f57b47168db23c007e4017969b980bfdc68f5..e29bad1e0ce05e06ce1db1d166cf42571788aa55 100644 (file)
@@ -512,11 +512,38 @@ display_age(time_t lastchange)
 }
 
 void
-display_interfaces(lldpctl_conn_t *conn, const char *fmt, int hidden,
+display_interface(lldpctl_conn_t *conn, struct writer *w, int hidden,
+    lldpctl_atom_t *iface, lldpctl_atom_t *neighbor)
+{
+       if (!hidden &&
+           lldpctl_atom_get_int(neighbor, lldpctl_k_port_hidden))
+               return;
+
+       tag_start(w, "interface", "Interface");
+       tag_attr(w, "name", "",
+           lldpctl_atom_get_str(iface, lldpctl_k_interface_name));
+       tag_attr(w, "via" , "via",
+           lldpctl_atom_get_str(neighbor, lldpctl_k_port_protocol));
+       tag_attr(w, "rid" , "RID",
+           lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_index));
+       tag_attr(w, "age" , "Time",
+           display_age(lldpctl_atom_get_int(neighbor, lldpctl_k_port_age)));
+
+       display_chassis(w, neighbor);
+       display_port(w, neighbor);
+       display_vlans(w, neighbor);
+       display_ppvids(w, neighbor);
+       display_pids(w, neighbor);
+       display_med(w, neighbor);
+
+       tag_end(w);
+}
+
+void
+display_interfaces(lldpctl_conn_t *conn, struct writer *w, int hidden,
     int argc, char *argv[])
 {
        int i;
-       struct writer * w;
        lldpctl_atom_t *iface_list;
        lldpctl_atom_t *iface;
        lldpctl_atom_t *port;
@@ -529,20 +556,6 @@ display_interfaces(lldpctl_conn_t *conn, const char *fmt, int hidden,
                return;
        }
 
-       if (strcmp(fmt, "plain") == 0) {
-               w = txt_init(stdout);
-       } else if (strcmp(fmt, "keyvalue") == 0) {
-               w = kv_init(stdout);
-       }
-#ifdef USE_XML
-       else if (strcmp(fmt,"xml") == 0 ) {
-               w = xml_init(stdout);
-       }
-#endif
-       else {
-               w = txt_init(stdout);
-       }
-
        tag_start(w, "lldp", "LLDP neighbors");
        lldpctl_atom_foreach(iface_list, iface) {
                if (optind < argc) {
@@ -557,33 +570,11 @@ display_interfaces(lldpctl_conn_t *conn, const char *fmt, int hidden,
                port      = lldpctl_get_port(iface);
                neighbors = lldpctl_atom_get(port, lldpctl_k_port_neighbors);
                lldpctl_atom_foreach(neighbors, neighbor) {
-                       if (!hidden &&
-                           lldpctl_atom_get_int(neighbor, lldpctl_k_port_hidden))
-                               continue;
-
-                       tag_start(w, "interface", "Interface");
-                       tag_attr(w, "name", "",
-                           lldpctl_atom_get_str(iface, lldpctl_k_interface_name));
-                       tag_attr(w, "via" , "via",
-                           lldpctl_atom_get_str(neighbor, lldpctl_k_port_protocol));
-                       tag_attr(w, "rid" , "RID",
-                           lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_index));
-                       tag_attr(w, "age" , "Time",
-                           display_age(lldpctl_atom_get_int(neighbor, lldpctl_k_port_age)));
-
-                       display_chassis(w, neighbor);
-                       display_port(w, neighbor);
-                       display_vlans(w, neighbor);
-                       display_ppvids(w, neighbor);
-                       display_pids(w, neighbor);
-                       display_med(w, neighbor);
-
-                       tag_end(w);
+                       display_interface(conn, w, hidden, iface, neighbor);
                }
                lldpctl_atom_dec_ref(neighbors);
                lldpctl_atom_dec_ref(port);
        }
        lldpctl_atom_dec_ref(iface_list);
        tag_end(w);
-       w->finish(w);
 }
index ae7a4aa7bf79b4e3c805495c3eeed4a351119292..a96057fc2bb57fa1368c5c241dbbdeaee00ce3fc 100644 (file)
@@ -48,6 +48,7 @@ usage(void)
 
        fprintf(stderr, "-d          Enable more debugging information.\n");
        fprintf(stderr, "-a          Display all remote ports, including hidden ones.\n");
+       fprintf(stderr, "-w          Watch for changes.\n");
        fprintf(stderr, "-f format   Choose output format (plain, keyvalue or xml).\n");
        fprintf(stderr, "-L location Enable the transmission of LLDP-MED location TLV for the\n");
        fprintf(stderr, "            given interfaces. Can be repeated to enable the transmission\n");
@@ -66,13 +67,56 @@ usage(void)
        exit(1);
 }
 
+struct cbargs {
+       int argc;
+       char **argv;
+       struct writer *w;
+};
+
+void
+watchcb(lldpctl_conn_t *conn,
+    lldpctl_change_t type,
+    lldpctl_atom_t *interface,
+    lldpctl_atom_t *neighbor,
+    void *data)
+{
+       int ch, i;
+       struct cbargs *args = data;
+       optind = 0;
+       while ((ch = getopt(args->argc, args->argv, LLDPCTL_ARGS)) != -1);
+       if (optind < args->argc) {
+               for (i = optind; i < args->argc; i++)
+                       if (strcmp(args->argv[i],
+                               lldpctl_atom_get_str(interface,
+                                   lldpctl_k_interface_name)) == 0)
+                               break;
+               if (i == args->argc)
+                       return;
+       }
+       switch (type) {
+       case lldpctl_c_deleted:
+               tag_start(args->w, "lldp-deleted", "LLDP neighbor deleted");
+               break;
+       case lldpctl_c_updated:
+               tag_start(args->w, "lldp-updated", "LLDP neighbor updated");
+               break;
+       case lldpctl_c_added:
+               tag_start(args->w, "lldp-added", "LLDP neighbor added");
+               break;
+       default: return;
+       }
+       display_interface(conn, args->w, 1, interface, neighbor);
+       tag_end(args->w);
+}
+
 int
 main(int argc, char *argv[])
 {
        int ch, debug = 1;
-       char * fmt = "plain";
-       int action = 0, hidden = 0;
+       char *fmt = "plain";
+       int action = 0, hidden = 0, watch = 0;
        lldpctl_conn_t *conn;
+       struct cbargs args;
 
        /* Get and parse command line options */
        while ((ch = getopt(argc, argv, LLDPCTL_ARGS)) != -1) {
@@ -99,6 +143,9 @@ main(int argc, char *argv[])
                case 'o':
                        action = 1;
                        break;
+               case 'w':
+                       watch = 1;
+                       break;
                default:
                        usage();
                }
@@ -113,8 +160,48 @@ main(int argc, char *argv[])
        conn = lldpctl_new(NULL, NULL, NULL);
        if (conn == NULL) exit(EXIT_FAILURE);
 
-       if (!action) display_interfaces(conn, fmt, hidden, argc, argv);
-       else modify_interfaces(conn, argc, argv, optind);
+       args.argc = argc;
+       args.argv = argv;
+       if (watch) {
+               if (lldpctl_watch_callback(conn, watchcb, &args) < 0) {
+                       LLOG_WARNX("unable to watch for neighbors. %s",
+                           lldpctl_last_strerror(conn));
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       do {
+               if (!action || watch) {
+                       if (strcmp(fmt, "plain") == 0) {
+                               args.w = txt_init(stdout);
+                       } else if (strcmp(fmt, "keyvalue") == 0) {
+                               args.w = kv_init(stdout);
+                       }
+#ifdef USE_XML
+                       else if (strcmp(fmt,"xml") == 0 ) {
+                               args.w = xml_init(stdout);
+                       }
+#endif
+                       else {
+                               args.w = txt_init(stdout);
+                       }
+               }
+
+               if (!watch && !action) {
+                               display_interfaces(conn, args.w,
+                                   hidden, argc, argv);
+                               args.w->finish(args.w);
+               } else if (!watch) {
+                       modify_interfaces(conn, argc, argv, optind);
+               } else {
+                       if (lldpctl_watch(conn) < 0) {
+                               LLOG_WARNX("unable to watch for neighbors. %s",
+                                   lldpctl_last_strerror(conn));
+                               watch = 0;
+                       }
+                       args.w->finish(args.w);
+               }
+       } while (watch);
 
        lldpctl_release(conn);
        return EXIT_SUCCESS;
index 72db53bbe5b427c4b3144187fe47b952d0c55a5a..6108741f08a3085b1222ddda9baf4e0f30b1afa0 100644 (file)
--- a/src/ctl.c
+++ b/src/ctl.c
@@ -180,6 +180,9 @@ ctl_msg_send_unserialized(uint8_t **output_buffer, size_t *output_len,
  *
  * @return -1 in case of error, 0 in case of success and the number of bytes we
  *         request to complete unserialization.
+ *
+ * When requesting a notification, the input buffer is left untouched if we
+ * don't get one and we fail silently.
  */
 size_t
 ctl_msg_recv_unserialized(uint8_t **input_buffer, size_t *input_len,
@@ -208,6 +211,7 @@ ctl_msg_recv_unserialized(uint8_t **input_buffer, size_t *input_len,
                return sizeof(struct hmsg_header) + hdr->len - *input_len;
        }
        if (hdr->type != expected_type) {
+               if (expected_type == NOTIFICATION) return -1;
                LLOG_WARNX("incorrect received message type (expected: %d, received: %d)",
                    expected_type, hdr->type);
                goto end;
index d6e5e210923a1e1e4abc064ef14ccbdbcd2b9695..1cd66c82d30bd9c1334805de26b340357f3dc415 100644 (file)
--- a/src/ctl.h
+++ b/src/ctl.h
@@ -28,6 +28,8 @@ enum hmsg_type {
        GET_INTERFACES,         /* Get list of interfaces */
        GET_INTERFACE,          /* Get all information related to an interface */
        SET_PORT,               /* Set port-related information (location, power, policy) */
+       SUBSCRIBE,              /* Subscribe to neighbor changes */
+       NOTIFICATION,           /* Notification message (sent by lldpd!) */
 };
 
 /** Header for the control protocol.
index 8ad5c32ceadde7ebec9aba28d3ebe964e3222941..94fe9a40b0d094e298a68d637208413d7386052c 100644 (file)
@@ -19,7 +19,7 @@
 
 static int
 client_handle_none(struct lldpd *cfg, enum hmsg_type *type,
-    void *input, int input_len, void **output)
+    void *input, int input_len, void **output, int *subscribed)
 {
        LLOG_INFO("received noop request from client");
        *type = NONE;
@@ -32,7 +32,7 @@ client_handle_none(struct lldpd *cfg, enum hmsg_type *type,
 */
 static int
 client_handle_get_interfaces(struct lldpd *cfg, enum hmsg_type *type,
-    void *input, int input_len, void **output)
+    void *input, int input_len, void **output, int *subscribed)
 {
        struct lldpd_interface *iff, *iff_next;
        struct lldpd_hardware *hardware;
@@ -73,7 +73,7 @@ client_handle_get_interfaces(struct lldpd *cfg, enum hmsg_type *type,
 */
 static int
 client_handle_get_interface(struct lldpd *cfg, enum hmsg_type *type,
-    void *input, int input_len, void **output)
+    void *input, int input_len, void **output, int *subscribed)
 {
        char *name;
        struct lldpd_hardware *hardware;
@@ -109,7 +109,7 @@ client_handle_get_interface(struct lldpd *cfg, enum hmsg_type *type,
 */
 static int
 client_handle_set_port(struct lldpd *cfg, enum hmsg_type *type,
-    void *input, int input_len, void **output)
+    void *input, int input_len, void **output, int *subscribed)
 {
        int ret = 0;
        struct lldpd_port_set *set = NULL;
@@ -201,25 +201,43 @@ set_port_finished:
        return 0;
 }
 
+/* Register subscribtion to neighbor changes */
+static int
+client_handle_subscribe(struct lldpd *cfg, enum hmsg_type *type,
+    void *input, int input_len, void **output, int *subscribed)
+{
+       *subscribed = 1;
+       return 0;
+}
+
+struct client_handle {
+       enum hmsg_type type;
+       int (*handle)(struct lldpd*, enum hmsg_type *,
+           void *, int, void **, int *);
+};
+
 static struct client_handle client_handles[] = {
-       { NONE, client_handle_none },
-       { GET_INTERFACES, client_handle_get_interfaces },
-       { GET_INTERFACE, client_handle_get_interface },
-       { SET_PORT, client_handle_set_port },
-       { 0, NULL } };
+       { NONE,                 client_handle_none },
+       { GET_INTERFACES,       client_handle_get_interfaces },
+       { GET_INTERFACE,        client_handle_get_interface },
+       { SET_PORT,             client_handle_set_port },
+       { SUBSCRIBE,            client_handle_subscribe },
+       { 0,                    NULL } };
 
 int
 client_handle_client(struct lldpd *cfg,
     ssize_t(*send)(void *, int, void *, size_t),
     void *out,
-    enum hmsg_type type, void *buffer, size_t n)
+    enum hmsg_type type, void *buffer, size_t n,
+    int *subscribed)
 {
        struct client_handle *ch;
        void *answer; size_t len, sent;
        for (ch = client_handles; ch->handle != NULL; ch++) {
                if (ch->type == type) {
                        answer = NULL; len = 0;
-                       len  = ch->handle(cfg, &type, buffer, n, &answer);
+                       len  = ch->handle(cfg, &type, buffer, n, &answer,
+                           subscribed);
                        sent = send(out, type, answer, len);
                        free(answer);
                        return sent;
index b557019a6960493cf62ebdf44659463f9f12d98f..18464f6b17e5c2c68abc51cf108baaa2771e900b 100644 (file)
@@ -193,28 +193,92 @@ levent_snmp_update(struct lldpd *cfg)
 #endif /* USE_SNMP */
 
 struct lldpd_one_client {
+       TAILQ_ENTRY(lldpd_one_client) next;
        struct lldpd *cfg;
        struct bufferevent *bev;
+       int    subscribed;      /* Is this client subscribed to changes? */
 };
+TAILQ_HEAD(, lldpd_one_client) lldpd_clients;
+
+static void
+levent_ctl_free_client(struct lldpd_one_client *client)
+{
+       if (client && client->bev) bufferevent_free(client->bev);
+       if (client) {
+               TAILQ_REMOVE(&lldpd_clients, client, next);
+               free(client);
+       }
+}
 
 static ssize_t
-levent_ctl_send(void *out, int type, void *data, size_t len)
+levent_ctl_send(struct lldpd_one_client *client, int type, void *data, size_t len)
 {
-       struct lldpd_one_client *client = out;
        struct bufferevent *bev = client->bev;
        struct hmsg_header hdr = { .len = len, .type = type };
        bufferevent_disable(bev, EV_WRITE);
        if (bufferevent_write(bev, &hdr, sizeof(struct hmsg_header)) == -1 ||
            (len > 0 && bufferevent_write(bev, data, len) == -1)) {
                LLOG_WARNX("unable to create answer to client");
-               bufferevent_free(bev);
-               free(client);
+               levent_ctl_free_client(client);
                return -1;
        }
        bufferevent_enable(bev, EV_WRITE);
        return len;
 }
 
+void
+levent_ctl_notify(char *ifname, int state, struct lldpd_port *neighbor)
+{
+       struct lldpd_one_client *client, *client_next;
+       struct lldpd_neighbor_change neigh = {
+               .ifname = ifname,
+               .state  = state,
+               .neighbor = neighbor
+       };
+       void *output = NULL;
+       ssize_t output_len;
+
+       /* Ugly hack: we don't want to transmit a list of chassis or a list of
+        * ports. We patch the chassis and the port to avoid this. */
+       TAILQ_ENTRY(lldpd_chassis) backup_c_entries;
+       TAILQ_ENTRY(lldpd_port) backup_p_entries;
+       memcpy(&backup_p_entries, &neighbor->p_entries,
+           sizeof(backup_p_entries));
+       memcpy(&backup_c_entries, &neighbor->p_chassis->c_entries,
+           sizeof(backup_c_entries));
+       memset(&neighbor->p_entries, 0, sizeof(backup_p_entries));
+       memset(&neighbor->p_chassis->c_entries, 0, sizeof(backup_c_entries));
+       output_len = marshal_serialize(lldpd_neighbor_change,
+           &neigh, &output);
+       memcpy(&neighbor->p_entries, &backup_p_entries,
+           sizeof(backup_p_entries));
+       memcpy(&neighbor->p_chassis->c_entries, &backup_c_entries,
+           sizeof(backup_c_entries));
+
+       if (output_len <= 0) {
+               LLOG_WARNX("unable to serialize changed neighbor");
+               return;
+       }
+
+       /* Don't use TAILQ_FOREACH, the client may be deleted in case of errors. */
+       for (client = TAILQ_FIRST(&lldpd_clients);
+            client;
+            client = client_next) {
+               client_next = TAILQ_NEXT(client, next);
+               if (!client->subscribed) continue;
+               levent_ctl_send(client, NOTIFICATION, output, output_len);
+       }
+
+       free(output);
+}
+
+static ssize_t
+levent_ctl_send_cb(void *out, int type, void *data, size_t len)
+{
+       struct lldpd_one_client *client = out;
+       return levent_ctl_send(client, type, data, len);
+}
+
 static void
 levent_ctl_recv(struct bufferevent *bev, void *ptr)
 {
@@ -244,16 +308,20 @@ levent_ctl_recv(struct bufferevent *bev, void *ptr)
        }
        evbuffer_drain(buffer, sizeof(struct hmsg_header));
        if (hdr.len > 0) evbuffer_remove(buffer, data, hdr.len);
+
+       /* Currently, we should not receive notification acknowledgment. But if
+        * we receive one, we can discard it. */
+       if (hdr.len == 0 && hdr.type == NOTIFICATION) return;
        if (client_handle_client(client->cfg,
-               levent_ctl_send, client,
-               hdr.type, data, hdr.len) == -1) goto recv_error;
+               levent_ctl_send_cb, client,
+               hdr.type, data, hdr.len,
+               &client->subscribed) == -1) goto recv_error;
        free(data);
        return;
 
 recv_error:
        free(data);
-       bufferevent_free(bev);
-       free(client);
+       levent_ctl_free_client(client);
 }
 
 static void
@@ -263,12 +331,10 @@ levent_ctl_event(struct bufferevent *bev, short events, void *ptr)
        if (events & BEV_EVENT_ERROR) {
                LLOG_WARNX("an error occurred with client: %s",
                    evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
-               bufferevent_free(bev);
-               free(client);
+               levent_ctl_free_client(client);
        } else if (events & BEV_EVENT_EOF) {
                LLOG_DEBUG("client has been disconnected");
-               bufferevent_free(bev);
-               free(client);
+               levent_ctl_free_client(client);
        }
 }
 
@@ -287,6 +353,7 @@ levent_ctl_accept(evutil_socket_t fd, short what, void *arg)
        client = calloc(1, sizeof(struct lldpd_one_client));
        if (!client) {
                LLOG_WARNX("unable to allocate memory for new client");
+               close(s);
                goto accept_failed;
        }
        client->cfg = cfg;
@@ -294,18 +361,18 @@ levent_ctl_accept(evutil_socket_t fd, short what, void *arg)
        if ((client->bev = bufferevent_socket_new(cfg->g_base, s,
                    BEV_OPT_CLOSE_ON_FREE)) == NULL) {
                LLOG_WARNX("unable to allocate a new buffer event for new client");
+               close(s);
                goto accept_failed;
        }
        bufferevent_setcb(client->bev,
            levent_ctl_recv, NULL, levent_ctl_event,
            client);
        bufferevent_enable(client->bev, EV_READ | EV_WRITE);
+       LLOG_DEBUG("new client accepted");
+       TAILQ_INSERT_TAIL(&lldpd_clients, client, next);
        return;
 accept_failed:
-       if (client && client->bev) {
-               bufferevent_free(client->bev);
-       } else close(s);
-       free(client);
+       levent_ctl_free_client(client);
 }
 
 static void
@@ -368,6 +435,7 @@ levent_init(struct lldpd *cfg)
        event_active(cfg->g_main_loop, EV_TIMEOUT, 1);
 
        /* Setup unix socket */
+       TAILQ_INIT(&lldpd_clients);
        evutil_make_socket_nonblocking(cfg->g_ctl);
        if ((cfg->g_ctl_event = event_new(cfg->g_base, cfg->g_ctl,
                    EV_READ|EV_PERSIST, levent_ctl_accept, cfg)) == NULL)
index 8ce719c28e53f32a2dc6571b0f57bb2d844436ed..a6eeac63a5a502615b281eef2acfb61faa3d242e 100644 (file)
@@ -205,6 +205,14 @@ lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
        free(hardware);
 }
 
+static void
+notify_clients_deletion(struct lldpd_hardware *hardware,
+    struct lldpd_port *port)
+{
+       levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_DELETED,
+           port);
+}
+
 static void
 lldpd_cleanup(struct lldpd *cfg)
 {
@@ -216,10 +224,10 @@ lldpd_cleanup(struct lldpd *cfg)
                hardware_next = TAILQ_NEXT(hardware, h_entries);
                if (!hardware->h_flags) {
                        TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries);
-                       lldpd_remote_cleanup(hardware, 1);
+                       lldpd_remote_cleanup(hardware, NULL);
                        lldpd_hardware_cleanup(cfg, hardware);
                } else
-                       lldpd_remote_cleanup(hardware, 0);
+                       lldpd_remote_cleanup(hardware, notify_clients_deletion);
        }
 
        for (chassis = TAILQ_FIRST(&cfg->g_chassis); chassis;
@@ -298,7 +306,7 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
 {
        int i, result;
        struct lldpd_chassis *chassis, *ochassis = NULL;
-       struct lldpd_port *port, *oport = NULL;
+       struct lldpd_port *port, *oport = NULL, *aport;
        int guess = LLDPD_MODE_LLDP;
 
        if (s < sizeof(struct ethhdr) + 4)
@@ -387,7 +395,7 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        /* Add port */
        port->p_lastchange = port->p_lastupdate = time(NULL);
        if ((port->p_lastframe = (struct lldpd_frame *)malloc(s +
-                   sizeof(int))) != NULL) {
+                   sizeof(struct lldpd_frame))) != NULL) {
                port->p_lastframe->size = s;
                memcpy(port->p_lastframe->frame, frame, s);
        }
@@ -407,10 +415,16 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
           freed with lldpd_port_cleanup() and therefore, the refcount
           of the chassis that was attached to it is decreased.
        */
-       i = 0; TAILQ_FOREACH(oport, &hardware->h_rports, p_entries)
+       i = 0; TAILQ_FOREACH(aport, &hardware->h_rports, p_entries)
                i++;
        LLOG_DEBUG("Currently, %s knows %d neighbors",
            hardware->h_ifname, i);
+
+       /* Notify */
+       levent_ctl_notify(hardware->h_ifname,
+           oport?NEIGHBOR_CHANGE_UPDATED:NEIGHBOR_CHANGE_ADDED,
+           port);
+
        return;
 }
 
@@ -849,7 +863,7 @@ lldpd_exit(struct lldpd *cfg)
        for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
             hardware = hardware_next) {
                hardware_next = TAILQ_NEXT(hardware, h_entries);
-               lldpd_remote_cleanup(hardware, 1);
+               lldpd_remote_cleanup(hardware, NULL);
                lldpd_hardware_cleanup(cfg, hardware);
        }
 }
index 9d94221304076d2ae4573cea2f1c692118c38aea..1b52be846368bac2acb1bbdc468d58529d351acc 100644 (file)
@@ -170,6 +170,7 @@ void         levent_loop(struct lldpd *);
 void    levent_hardware_init(struct lldpd_hardware *);
 void    levent_hardware_add_fd(struct lldpd_hardware *, int);
 void    levent_hardware_release(struct lldpd_hardware *);
+void    levent_ctl_notify(char *, int, struct lldpd_port *);
 
 /* lldp.c */
 int     lldp_send(PROTO_SEND_SIG);
@@ -231,17 +232,12 @@ void               agent_init(struct lldpd *, char *);
 void            agent_priv_register_domain(void);
 
 /* client.c */
-struct client_handle {
-       enum hmsg_type type;
-       int (*handle)(struct lldpd*, enum hmsg_type *,
-           void *, int, void **);
-};
-
 int
 client_handle_client(struct lldpd *cfg,
     ssize_t(*send)(void *, int, void *, size_t),
     void *,
-    enum hmsg_type type, void *buffer, size_t n);
+    enum hmsg_type type, void *buffer, size_t n,
+    int*);
 
 /* priv.c */
 void    priv_init(char*, int, uid_t, gid_t);
index 190512f9c94f6ec88f6405b5e07db10662bf8f9d..73e7e7e140f18074bcd543e1e57c46610b399c61 100644 (file)
@@ -464,6 +464,14 @@ _lldpctl_atom_free_port(lldpctl_atom_t *atom)
        TAILQ_INIT(&chassis_list);
 
        if (port->parent) lldpctl_atom_dec_ref((lldpctl_atom_t*)port->parent);
+       else if (!hardware) {
+               /* No parent, no hardware, we assume a single neighbor: one
+                * port, one chassis. */
+               lldpd_chassis_cleanup(port->port->p_chassis, 1);
+               port->port->p_chassis = NULL;
+               lldpd_port_cleanup(port->port, 1);
+               free(port->port);
+       }
        if (!hardware) return;
 
        add_chassis(&chassis_list, port->port->p_chassis);
@@ -471,7 +479,7 @@ _lldpctl_atom_free_port(lldpctl_atom_t *atom)
                add_chassis(&chassis_list, one_port->p_chassis);
 
        /* Free hardware port */
-       lldpd_remote_cleanup(hardware, 1);
+       lldpd_remote_cleanup(hardware, NULL);
        lldpd_port_cleanup(port->port, 1);
        free(port->hardware);
 
index 6e008e23e921c8ea522b8bc7705ea6056d40d81b..d9fe49363cfb3a3917c95158fa2a4f01c0e6c43e 100644 (file)
@@ -324,9 +324,9 @@ _lldpctl_do_something(lldpctl_conn_t *conn,
        }
        if (conn->state == state_recv && conn->state_data == state_data) {
                /* We need to receive the answer */
-               while ((rc = ctl_msg_recv_unserialized(&conn->input_buffer, &conn->input_buffer_len,
-                           type,
-                           to_recv, mi_recv)) > 0) {
+               while ((rc = ctl_msg_recv_unserialized(&conn->input_buffer,
+                           &conn->input_buffer_len,
+                           type, to_recv, mi_recv)) > 0) {
                        /* We need more bytes */
                        rc = _lldpctl_needs(conn, rc);
                        if (rc < 0)
@@ -342,6 +342,50 @@ _lldpctl_do_something(lldpctl_conn_t *conn,
                return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE);
 }
 
+
+int
+lldpctl_watch_callback(lldpctl_conn_t *conn,
+    lldpctl_change_callback cb,
+    void *data)
+{
+       int rc;
+
+       RESET_ERROR(conn);
+
+       rc = _lldpctl_do_something(conn,
+           CONN_STATE_SET_WATCH_SEND, CONN_STATE_SET_WATCH_RECV, NULL,
+           SUBSCRIBE, NULL, NULL, NULL, NULL);
+       if (rc == 0) {
+               conn->watch_cb = cb;
+               conn->watch_data = data;
+       }
+       return rc;
+}
+
+int
+lldpctl_watch(lldpctl_conn_t *conn)
+{
+       int rc;
+       size_t much;
+
+       RESET_ERROR(conn);
+
+       if (conn->state != CONN_STATE_IDLE)
+               return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE);
+
+       conn->watch_triggered = 0;
+       much = 512;
+       while (!conn->watch_triggered) {
+               rc = _lldpctl_needs(conn, much);
+               much += 512;
+               if (rc < 0)
+                       return SET_ERROR(conn, rc);
+       }
+
+       RESET_ERROR(conn);
+       return 0;
+}
+
 lldpctl_atom_t*
 lldpctl_get_interfaces(lldpctl_conn_t *conn)
 {
index 85a11ca19233f977c5ccd8213752ea7aa4feccc2..85536e36079905a7d53dc93b441c77122fe6501f 100644 (file)
@@ -24,6 +24,8 @@
 #include "private.h"
 #include "../compat/compat.h"
 #include "../ctl.h"
+#include "../log.h"
+#include "../lldpd-structs.h"
 
 const char*
 lldpctl_get_default_transport(void)
@@ -160,6 +162,54 @@ _lldpctl_needs(lldpctl_conn_t *conn, size_t length)
        return rc;
 }
 
+static void
+check_for_notification(lldpctl_conn_t *conn)
+{
+       struct lldpd_neighbor_change *change;
+       int rc;
+       lldpctl_change_t type;
+       lldpctl_atom_t *interface = NULL, *neighbor = NULL;
+       rc = ctl_msg_recv_unserialized(&conn->input_buffer,
+           &conn->input_buffer_len,
+           NOTIFICATION,
+           (void**)&change,
+           &MARSHAL_INFO(lldpd_neighbor_change));
+       if (rc != 0) return;
+
+       /* We have a notification, call the callback */
+       if (conn->watch_cb) {
+               switch (change->state) {
+               case NEIGHBOR_CHANGE_DELETED: type = lldpctl_c_deleted; break;
+               case NEIGHBOR_CHANGE_ADDED: type = lldpctl_c_added; break;
+               case NEIGHBOR_CHANGE_UPDATED: type = lldpctl_c_updated; break;
+               default:
+                       LLOG_WARNX("unknown notification type (%d)",
+                           change->state);
+                       goto end;
+               }
+               interface = _lldpctl_new_atom(conn, atom_interface,
+                   change->ifname);
+               if (interface == NULL) goto end;
+               neighbor = _lldpctl_new_atom(conn, atom_port,
+                   NULL, change->neighbor, NULL);
+               if (neighbor == NULL) goto end;
+               conn->watch_cb(conn, type, interface, neighbor, conn->watch_data);
+               conn->watch_triggered = 1;
+               goto end;
+       }
+
+end:
+       if (interface) lldpctl_atom_dec_ref(interface);
+       if (neighbor) lldpctl_atom_dec_ref(neighbor);
+       else {
+               lldpd_chassis_cleanup(change->neighbor->p_chassis, 1);
+               lldpd_port_cleanup(change->neighbor, 1);
+               free(change->neighbor);
+       }
+       free(change->ifname);
+       free(change);
+}
+
 ssize_t
 lldpctl_recv(lldpctl_conn_t *conn, const uint8_t *data, size_t length)
 {
@@ -181,8 +231,13 @@ lldpctl_recv(lldpctl_conn_t *conn, const uint8_t *data, size_t length)
        }
        memcpy(conn->input_buffer + conn->input_buffer_len, data, length);
        conn->input_buffer_len += length;
+
+       /* Is it a notification? */
+       check_for_notification(conn);
+
        RESET_ERROR(conn);
-       return length;
+
+       return conn->input_buffer_len;
 }
 
 ssize_t
index 823a5fdcddf2314720d02234223426cc503a08b6..91a84656cce164bb8e882420e0dbfc06921debea 100644 (file)
@@ -98,8 +98,9 @@ typedef ssize_t (*lldpctl_recv_callback)(lldpctl_conn_t *lldpctl,
  * @param  lldpctl   Handle to the connection to lldpd.
  * @param  data      Data received from lldpd.
  * @param  length    Length of data received.
- * @return The number of bytes processed or a negative integer if an error has
- *         occurred.
+ * @return The number of bytes available or a negative integer if an error has
+ *         occurred. 0 is not an error. It usually means that a notification has
+ *         been processed.
  */
 ssize_t lldpctl_recv(lldpctl_conn_t *lldpctl, const uint8_t *data, size_t length);
 
@@ -294,10 +295,65 @@ void lldpctl_atom_inc_ref(lldpctl_atom_t *atom);
  * @param atom Atom we want to decrease reference count. Can be @c NULL. In this
  *             case, nothing happens.
  *
- * When the reference count becomes 0, the atom is freed. 
+ * When the reference count becomes 0, the atom is freed.
  */
 void lldpctl_atom_dec_ref(lldpctl_atom_t *atom);
 
+/**
+ * Possible events for a change.
+ */
+typedef enum {
+       lldpctl_c_deleted,      /**< The neighbor has been deleted */
+       lldpctl_c_updated,      /**< The neighbor has been updated */
+       lldpctl_c_added,        /**< This is a new neighbor */
+} lldpctl_change_t;
+
+/**
+ * Callback function invoked when a change is detected.
+ *
+ * @param conn      Connection with lldpd.
+ * @param type      Type of change detected.
+ * @param interface Physical interface on which the change has happened.
+ * @param neighbor  Changed neighbor.
+ * @param data      Data provided when registering the callback.
+ *
+ * The provided interface and neighbor atoms will have their reference count
+ * decremented when the callback ends. If you want to keep a reference to it, be
+ * sure to increment the reference count in the callback.
+ */
+typedef void (*lldpctl_change_callback)(lldpctl_conn_t *conn,
+    lldpctl_change_t type,
+    lldpctl_atom_t *interface,
+    lldpctl_atom_t *neighbor,
+    void *data);
+
+/**
+ * Register a callback to be called on changes.
+ *
+ * @param conn Connection with lldpd.
+ * @param cb   Replace the current callback with the provided one.
+ * @param data Data that will be passed to the callback.
+ * @return 0 in case of success or -1 in case of errors.
+ *
+ * This function will register the necessity to push neighbor changes to lldpd
+ * and therefore will issue IO operations. The error code could then be @c
+ * LLDPCTL_ERR_WOULDBLOCK.
+ */
+int lldpctl_watch_callback(lldpctl_conn_t *conn,
+    lldpctl_change_callback cb,
+    void *data);
+
+/**
+ * Wait for the next change.
+ *
+ * @param conn Connection with lldpd.
+ * @return 0 on success or a negative integer in case of error.
+ *
+ * This function will return once a change has been detected. It is only useful
+ * as a main loop when using the builtin blocking IO mechanism.
+ */
+int lldpctl_watch(lldpctl_conn_t *conn);
+
 /**
  * Retrieve the list of available interfaces.
  *
index 755c8705edec8a0e6f56fb162301136a73494d2c..0065f46303ddb0bc6b8ece2ebb289ec54315570a 100644 (file)
@@ -40,6 +40,8 @@ struct lldpctl_conn_t {
 #define CONN_STATE_GET_PORT_RECV       4
 #define CONN_STATE_SET_PORT_SEND       5
 #define CONN_STATE_SET_PORT_RECV       6
+#define CONN_STATE_SET_WATCH_SEND      7
+#define CONN_STATE_SET_WATCH_RECV      8
        int state;              /* Current state */
        void *state_data;       /* Data attached to the state. It is used to
                                 * check that we are using the same data as a
@@ -48,6 +50,11 @@ struct lldpctl_conn_t {
 
        /* Error handling */
        lldpctl_error_t error;  /* Last error */
+
+       /* Handling notifications */
+       lldpctl_change_callback watch_cb;
+       void *watch_data;
+       int watch_triggered;
 };
 
 /* User data for synchronous callbacks. */
index 8e186c43f796a1ed5b91cf0f9557bdcfa40a575b..e6a0e1eec808d3d4343f0181a8a743781c537f6a 100644 (file)
@@ -96,8 +96,12 @@ lldpd_pi_cleanup(struct lldpd_port *port)
 }
 #endif
 
+/* Cleanup a remote port. The last argument, `expire` is a function that should
+ * be called when expiration happens. If it is NULL, all remote ports are
+ * removed. */
 void
-lldpd_remote_cleanup(struct lldpd_hardware *hardware, int all)
+lldpd_remote_cleanup(struct lldpd_hardware *hardware,
+    void(*expire)(struct lldpd_hardware *, struct lldpd_port *))
 {
        struct lldpd_port *port, *port_next;
        int del;
@@ -105,20 +109,21 @@ lldpd_remote_cleanup(struct lldpd_hardware *hardware, int all)
             port != NULL;
             port = port_next) {
                port_next = TAILQ_NEXT(port, p_entries);
-               del = all;
-               if (!all &&
+               del = (expire == NULL);
+               if (expire &&
                    (time(NULL) - port->p_lastupdate > port->p_chassis->c_ttl)) {
                        hardware->h_rx_ageout_cnt++;
+                       expire(hardware, port);
                        del = 1;
                }
                if (del) {
-                       if (!all)
+                       if (expire)
                                TAILQ_REMOVE(&hardware->h_rports, port, p_entries);
                        lldpd_port_cleanup(port, 1);
                        free(port);
                }
        }
-       if (all) TAILQ_INIT(&hardware->h_rports);
+       if (!expire) TAILQ_INIT(&hardware->h_rports);
 }
 
 /* If `all' is true, clear all information, including information that
index a0dfc6443de1874fc2edd6ddfa83b8e0de4a824f..d04d3c7b47e4219bfce26d5f32727ea4f2fbc11a 100644 (file)
@@ -357,10 +357,24 @@ MARSHAL_END;
 TAILQ_HEAD(lldpd_interface_list, lldpd_interface);
 MARSHAL_TQ(lldpd_interface_list, lldpd_interface);
 
+struct lldpd_neighbor_change {
+       char *ifname;
+#define NEIGHBOR_CHANGE_DELETED -1
+#define NEIGHBOR_CHANGE_ADDED    1
+#define NEIGHBOR_CHANGE_UPDATED  0
+       int state;
+       struct lldpd_port *neighbor;
+};
+MARSHAL_BEGIN(lldpd_neighbor_change)
+MARSHAL_STR(lldpd_neighbor_change, ifname)
+MARSHAL_POINTER(lldpd_neighbor_change, lldpd_port, neighbor)
+MARSHAL_END;
+
 /* Cleanup functions */
 void    lldpd_chassis_mgmt_cleanup(struct lldpd_chassis *);
 void    lldpd_chassis_cleanup(struct lldpd_chassis *, int);
-void    lldpd_remote_cleanup(struct lldpd_hardware *, int);
+void    lldpd_remote_cleanup(struct lldpd_hardware *,
+    void(*expire)(struct lldpd_hardware *, struct lldpd_port *));
 void    lldpd_port_cleanup(struct lldpd_port *, int);
 #ifdef ENABLE_DOT1
 void    lldpd_ppvid_cleanup(struct lldpd_port *);
index 4bdf39b4fe4d8cfac94cda3382c8cd4e5899c75b..4f6da2acfbd59dd01bd42277a933759d8b69e509 100644 (file)
@@ -89,7 +89,7 @@ extern struct marshal_info marshal_info_ignore;
        MARSHAL_IGNORE(type, field.tqe_prev)
 /* Support for TAILQ list is partial. Access to last and previous
    elements is not available. Some operations are therefore not
-   possible. However, TAILQ_FOREACH and TAILQ_REMOVE are still
+   possible. However, TAILQ_FOREACH is still
    available. */
 #define MARSHAL_TQH(type, subtype)                      \
        MARSHAL_POINTER(type, subtype, tqh_first)        \