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:
.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
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 ,
#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);
}
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;
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) {
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);
}
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");
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) {
case 'o':
action = 1;
break;
+ case 'w':
+ watch = 1;
+ break;
default:
usage();
}
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;
*
* @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,
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;
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.
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;
*/
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;
*/
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;
*/
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;
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;
#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)
{
}
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
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);
}
}
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;
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
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)
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)
{
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;
{
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)
/* 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);
}
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;
}
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);
}
}
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);
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);
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);
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);
}
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)
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)
{
#include "private.h"
#include "../compat/compat.h"
#include "../ctl.h"
+#include "../log.h"
+#include "../lldpd-structs.h"
const char*
lldpctl_get_default_transport(void)
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)
{
}
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
* @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);
* @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.
*
#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
/* 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. */
}
#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;
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
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 *);
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) \