From: Vincent Bernat Date: Thu, 30 Aug 2012 22:59:30 +0000 (+0200) Subject: lldpctl: add a "-w" option to watch neighbor changes X-Git-Tag: 0.6.1~17 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4e90a9e0b465218f65846c08b702f009f10264ee;p=thirdparty%2Flldpd.git lldpctl: add a "-w" option to watch neighbor changes --- diff --git a/NEWS b/NEWS index 98eca5f4..f83d4044 100644 --- 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: diff --git a/man/lldpctl.8 b/man/lldpctl.8 index 941bdc3c..436e7b24 100644 --- a/man/lldpctl.8 +++ b/man/lldpctl.8 @@ -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 , diff --git a/src/client/client.h b/src/client/client.h index 61f55f30..c57b7e11 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -26,10 +26,12 @@ #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); diff --git a/src/client/display.c b/src/client/display.c index 524f57b4..e29bad1e 100644 --- a/src/client/display.c +++ b/src/client/display.c @@ -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); } diff --git a/src/client/lldpctl.c b/src/client/lldpctl.c index ae7a4aa7..a96057fc 100644 --- a/src/client/lldpctl.c +++ b/src/client/lldpctl.c @@ -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; diff --git a/src/ctl.c b/src/ctl.c index 72db53bb..6108741f 100644 --- 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; diff --git a/src/ctl.h b/src/ctl.h index d6e5e210..1cd66c82 100644 --- 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. diff --git a/src/daemon/client.c b/src/daemon/client.c index 8ad5c32c..94fe9a40 100644 --- a/src/daemon/client.c +++ b/src/daemon/client.c @@ -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; diff --git a/src/daemon/event.c b/src/daemon/event.c index b557019a..18464f6b 100644 --- a/src/daemon/event.c +++ b/src/daemon/event.c @@ -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) diff --git a/src/daemon/lldpd.c b/src/daemon/lldpd.c index 8ce719c2..a6eeac63 100644 --- a/src/daemon/lldpd.c +++ b/src/daemon/lldpd.c @@ -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); } } diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index 9d942213..1b52be84 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -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); diff --git a/src/lib/atom-private.c b/src/lib/atom-private.c index 190512f9..73e7e7e1 100644 --- a/src/lib/atom-private.c +++ b/src/lib/atom-private.c @@ -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); diff --git a/src/lib/atom.c b/src/lib/atom.c index 6e008e23..d9fe4936 100644 --- a/src/lib/atom.c +++ b/src/lib/atom.c @@ -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) { diff --git a/src/lib/connection.c b/src/lib/connection.c index 85a11ca1..85536e36 100644 --- a/src/lib/connection.c +++ b/src/lib/connection.c @@ -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 diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h index 823a5fdc..91a84656 100644 --- a/src/lib/lldpctl.h +++ b/src/lib/lldpctl.h @@ -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. * diff --git a/src/lib/private.h b/src/lib/private.h index 755c8705..0065f463 100644 --- a/src/lib/private.h +++ b/src/lib/private.h @@ -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. */ diff --git a/src/lldpd-structs.c b/src/lldpd-structs.c index 8e186c43..e6a0e1ee 100644 --- a/src/lldpd-structs.c +++ b/src/lldpd-structs.c @@ -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 diff --git a/src/lldpd-structs.h b/src/lldpd-structs.h index a0dfc644..d04d3c7b 100644 --- a/src/lldpd-structs.h +++ b/src/lldpd-structs.h @@ -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 *); diff --git a/src/marshal.h b/src/marshal.h index 4bdf39b4..4f6da2ac 100644 --- a/src/marshal.h +++ b/src/marshal.h @@ -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) \