]>
Commit | Line | Data |
---|---|---|
4b292b55 | 1 | /* -*- mode: c; c-file-style: "openbsd" -*- */ |
d6e889b6 VB |
2 | /* |
3 | * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx> | |
4 | * | |
5 | * Permission to use, copy, modify, and/or distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include "lldpd.h" | |
bdfe4193 | 19 | #include "trace.h" |
d6e889b6 VB |
20 | |
21 | #include <unistd.h> | |
22 | #include <signal.h> | |
e0478a46 | 23 | #include <errno.h> |
864a7bd5 | 24 | #include <time.h> |
bec75f84 | 25 | #include <fcntl.h> |
d7460a6f ALG |
26 | #if defined(__clang__) |
27 | #pragma clang diagnostic push | |
28 | #pragma clang diagnostic ignored "-Wdocumentation" | |
29 | #endif | |
d6e889b6 | 30 | #include <event2/event.h> |
e0478a46 VB |
31 | #include <event2/bufferevent.h> |
32 | #include <event2/buffer.h> | |
d7460a6f ALG |
33 | #if defined(__clang__) |
34 | #pragma clang diagnostic pop | |
35 | #endif | |
d6e889b6 | 36 | |
b0b8841b ST |
37 | #define EVENT_BUFFER 1024 |
38 | ||
d6e889b6 VB |
39 | static void |
40 | levent_log_cb(int severity, const char *msg) | |
41 | { | |
42 | switch (severity) { | |
26fa5d17 VB |
43 | case _EVENT_LOG_DEBUG: log_debug("libevent", "%s", msg); break; |
44 | case _EVENT_LOG_MSG: log_info ("libevent", "%s", msg); break; | |
45 | case _EVENT_LOG_WARN: log_warnx("libevent", "%s", msg); break; | |
46 | case _EVENT_LOG_ERR: log_warnx("libevent", "%s", msg); break; | |
d6e889b6 VB |
47 | } |
48 | } | |
49 | ||
d6e889b6 VB |
50 | struct lldpd_events { |
51 | TAILQ_ENTRY(lldpd_events) next; | |
52 | struct event *ev; | |
d6e889b6 VB |
53 | }; |
54 | TAILQ_HEAD(ev_l, lldpd_events); | |
55 | ||
56 | #define levent_snmp_fds(cfg) ((struct ev_l*)(cfg)->g_snmp_fds) | |
d6e889b6 VB |
57 | #define levent_hardware_fds(hardware) ((struct ev_l*)(hardware)->h_recv) |
58 | ||
59 | #ifdef USE_SNMP | |
60 | #include <net-snmp/net-snmp-config.h> | |
61 | #include <net-snmp/net-snmp-includes.h> | |
62 | #include <net-snmp/agent/net-snmp-agent-includes.h> | |
63 | #include <net-snmp/agent/snmp_vars.h> | |
64 | ||
59c32cf0 VB |
65 | /* Compatibility with older versions of NetSNMP */ |
66 | #ifndef HAVE_SNMP_SELECT_INFO2 | |
67 | # define netsnmp_large_fd_set fd_set | |
68 | # define snmp_read2 snmp_read | |
69 | # define snmp_select_info2 snmp_select_info | |
70 | # define netsnmp_large_fd_set_init(...) | |
71 | # define netsnmp_large_fd_set_cleanup(...) | |
72 | # define NETSNMP_LARGE_FD_SET FD_SET | |
73 | # define NETSNMP_LARGE_FD_CLR FD_CLR | |
74 | # define NETSNMP_LARGE_FD_ZERO FD_ZERO | |
75 | # define NETSNMP_LARGE_FD_ISSET FD_ISSET | |
76 | #else | |
77 | # include <net-snmp/library/large_fd_set.h> | |
78 | #endif | |
79 | ||
d6e889b6 VB |
80 | static void levent_snmp_update(struct lldpd *); |
81 | ||
82 | /* | |
83 | * Callback function when we have something to read from SNMP. | |
84 | * | |
85 | * This function is called because we have a read event on one SNMP | |
86 | * file descriptor. When need to call snmp_read() on it. | |
87 | */ | |
88 | static void | |
89 | levent_snmp_read(evutil_socket_t fd, short what, void *arg) | |
90 | { | |
d6e889b6 | 91 | struct lldpd *cfg = arg; |
59c32cf0 | 92 | netsnmp_large_fd_set fdset; |
5fd6695c | 93 | (void)what; |
59c32cf0 VB |
94 | netsnmp_large_fd_set_init(&fdset, FD_SETSIZE); |
95 | NETSNMP_LARGE_FD_ZERO(&fdset); | |
96 | NETSNMP_LARGE_FD_SET(fd, &fdset); | |
97 | snmp_read2(&fdset); | |
d6e889b6 VB |
98 | levent_snmp_update(cfg); |
99 | } | |
100 | ||
101 | /* | |
102 | * Callback function for a SNMP timeout. | |
103 | * | |
104 | * A SNMP timeout has occurred. Call `snmp_timeout()` to handle it. | |
105 | */ | |
106 | static void | |
107 | levent_snmp_timeout(evutil_socket_t fd, short what, void *arg) | |
108 | { | |
d6e889b6 | 109 | struct lldpd *cfg = arg; |
5fd6695c | 110 | (void)what; (void)fd; |
d6e889b6 | 111 | snmp_timeout(); |
219c432e | 112 | run_alarms(); |
d6e889b6 VB |
113 | levent_snmp_update(cfg); |
114 | } | |
115 | ||
116 | /* | |
117 | * Watch a new SNMP FD. | |
118 | * | |
119 | * @param base The libevent base we are working on. | |
120 | * @param fd The file descriptor we want to watch. | |
121 | * | |
122 | * The file descriptor is appended to the list of file descriptors we | |
123 | * want to watch. | |
124 | */ | |
125 | static void | |
126 | levent_snmp_add_fd(struct lldpd *cfg, int fd) | |
127 | { | |
128 | struct event_base *base = cfg->g_base; | |
129 | struct lldpd_events *snmpfd = calloc(1, sizeof(struct lldpd_events)); | |
130 | if (!snmpfd) { | |
6f8925be | 131 | log_warn("event", "unable to allocate memory for new SNMP event"); |
d6e889b6 VB |
132 | return; |
133 | } | |
bec75f84 | 134 | levent_make_socket_nonblocking(fd); |
d6e889b6 VB |
135 | if ((snmpfd->ev = event_new(base, fd, |
136 | EV_READ | EV_PERSIST, | |
137 | levent_snmp_read, | |
138 | cfg)) == NULL) { | |
6f8925be | 139 | log_warnx("event", "unable to allocate a new SNMP event for FD %d", fd); |
d6e889b6 VB |
140 | free(snmpfd); |
141 | return; | |
142 | } | |
143 | if (event_add(snmpfd->ev, NULL) == -1) { | |
6f8925be | 144 | log_warnx("event", "unable to schedule new SNMP event for FD %d", fd); |
d6e889b6 VB |
145 | event_free(snmpfd->ev); |
146 | free(snmpfd); | |
147 | return; | |
148 | } | |
149 | TAILQ_INSERT_TAIL(levent_snmp_fds(cfg), snmpfd, next); | |
150 | } | |
151 | ||
152 | /* | |
153 | * Update SNMP event loop. | |
154 | * | |
155 | * New events are added and some other are removed. This function | |
156 | * should be called every time a SNMP event happens: either when | |
157 | * handling a SNMP packet, a SNMP timeout or when sending a SNMP | |
158 | * packet. This function will keep libevent in sync with NetSNMP. | |
159 | * | |
160 | * @param base The libevent base we are working on. | |
161 | */ | |
162 | static void | |
163 | levent_snmp_update(struct lldpd *cfg) | |
164 | { | |
165 | int maxfd = 0; | |
166 | int block = 1; | |
d6e889b6 VB |
167 | struct timeval timeout; |
168 | static int howmany = 0; | |
5fd6695c VB |
169 | int added = 0, removed = 0, current = 0; |
170 | struct lldpd_events *snmpfd, *snmpfd_next; | |
d6e889b6 VB |
171 | |
172 | /* snmp_select_info() can be tricky to understand. We set `block` to | |
173 | 1 to means that we don't request a timeout. snmp_select_info() | |
174 | will reset `block` to 0 if it wants us to setup a timeout. In | |
175 | this timeout, `snmp_timeout()` should be invoked. | |
59c32cf0 | 176 | |
d6e889b6 VB |
177 | Each FD in `fdset` will need to be watched for reading. If one of |
178 | them become active, `snmp_read()` should be called on it. | |
179 | */ | |
59c32cf0 VB |
180 | |
181 | netsnmp_large_fd_set fdset; | |
182 | netsnmp_large_fd_set_init(&fdset, FD_SETSIZE); | |
183 | NETSNMP_LARGE_FD_ZERO(&fdset); | |
184 | snmp_select_info2(&maxfd, &fdset, &timeout, &block); | |
185 | ||
d6e889b6 VB |
186 | /* We need to untrack any event whose FD is not in `fdset` |
187 | anymore */ | |
d6e889b6 VB |
188 | for (snmpfd = TAILQ_FIRST(levent_snmp_fds(cfg)); |
189 | snmpfd; | |
190 | snmpfd = snmpfd_next) { | |
191 | snmpfd_next = TAILQ_NEXT(snmpfd, next); | |
192 | if (event_get_fd(snmpfd->ev) >= maxfd || | |
59c32cf0 | 193 | (!NETSNMP_LARGE_FD_ISSET(event_get_fd(snmpfd->ev), &fdset))) { |
d6e889b6 VB |
194 | event_free(snmpfd->ev); |
195 | TAILQ_REMOVE(levent_snmp_fds(cfg), snmpfd, next); | |
196 | free(snmpfd); | |
197 | removed++; | |
198 | } else { | |
59c32cf0 | 199 | NETSNMP_LARGE_FD_CLR(event_get_fd(snmpfd->ev), &fdset); |
d6e889b6 VB |
200 | current++; |
201 | } | |
202 | } | |
59c32cf0 | 203 | |
d6e889b6 VB |
204 | /* Invariant: FD in `fdset` are not in list of FD */ |
205 | for (int fd = 0; fd < maxfd; fd++) { | |
59c32cf0 | 206 | if (NETSNMP_LARGE_FD_ISSET(fd, &fdset)) { |
d6e889b6 VB |
207 | levent_snmp_add_fd(cfg, fd); |
208 | added++; | |
209 | } | |
210 | } | |
211 | current += added; | |
212 | if (howmany != current) { | |
6f8925be | 213 | log_debug("event", "added %d events, removed %d events, total of %d events", |
d6e889b6 VB |
214 | added, removed, current); |
215 | howmany = current; | |
216 | } | |
217 | ||
218 | /* If needed, handle timeout */ | |
219 | if (evtimer_add(cfg->g_snmp_timeout, block?NULL:&timeout) == -1) | |
6f8925be | 220 | log_warnx("event", "unable to schedule timeout function for SNMP"); |
59c32cf0 VB |
221 | |
222 | netsnmp_large_fd_set_cleanup(&fdset); | |
d6e889b6 VB |
223 | } |
224 | #endif /* USE_SNMP */ | |
225 | ||
257db885 | 226 | struct lldpd_one_client { |
4e90a9e0 | 227 | TAILQ_ENTRY(lldpd_one_client) next; |
257db885 | 228 | struct lldpd *cfg; |
e0478a46 | 229 | struct bufferevent *bev; |
4e90a9e0 | 230 | int subscribed; /* Is this client subscribed to changes? */ |
257db885 | 231 | }; |
4e90a9e0 VB |
232 | TAILQ_HEAD(, lldpd_one_client) lldpd_clients; |
233 | ||
234 | static void | |
235 | levent_ctl_free_client(struct lldpd_one_client *client) | |
236 | { | |
237 | if (client && client->bev) bufferevent_free(client->bev); | |
238 | if (client) { | |
239 | TAILQ_REMOVE(&lldpd_clients, client, next); | |
240 | free(client); | |
241 | } | |
242 | } | |
257db885 | 243 | |
762419b6 VB |
244 | static void |
245 | levent_ctl_close_clients() | |
246 | { | |
247 | struct lldpd_one_client *client, *client_next; | |
248 | for (client = TAILQ_FIRST(&lldpd_clients); | |
249 | client; | |
250 | client = client_next) { | |
251 | client_next = TAILQ_NEXT(client, next); | |
252 | levent_ctl_free_client(client); | |
253 | } | |
254 | } | |
255 | ||
e0478a46 | 256 | static ssize_t |
4e90a9e0 | 257 | levent_ctl_send(struct lldpd_one_client *client, int type, void *data, size_t len) |
e0478a46 | 258 | { |
e0478a46 VB |
259 | struct bufferevent *bev = client->bev; |
260 | struct hmsg_header hdr = { .len = len, .type = type }; | |
261 | bufferevent_disable(bev, EV_WRITE); | |
262 | if (bufferevent_write(bev, &hdr, sizeof(struct hmsg_header)) == -1 || | |
263 | (len > 0 && bufferevent_write(bev, data, len) == -1)) { | |
6f8925be | 264 | log_warnx("event", "unable to create answer to client"); |
4e90a9e0 | 265 | levent_ctl_free_client(client); |
e0478a46 VB |
266 | return -1; |
267 | } | |
268 | bufferevent_enable(bev, EV_WRITE); | |
269 | return len; | |
270 | } | |
271 | ||
4e90a9e0 VB |
272 | void |
273 | levent_ctl_notify(char *ifname, int state, struct lldpd_port *neighbor) | |
274 | { | |
275 | struct lldpd_one_client *client, *client_next; | |
276 | struct lldpd_neighbor_change neigh = { | |
277 | .ifname = ifname, | |
278 | .state = state, | |
279 | .neighbor = neighbor | |
280 | }; | |
281 | void *output = NULL; | |
d79c3de4 | 282 | ssize_t output_len = 0; |
4e90a9e0 | 283 | |
4e90a9e0 | 284 | /* Don't use TAILQ_FOREACH, the client may be deleted in case of errors. */ |
6f8925be | 285 | log_debug("control", "notify clients of neighbor changes"); |
4e90a9e0 VB |
286 | for (client = TAILQ_FIRST(&lldpd_clients); |
287 | client; | |
288 | client = client_next) { | |
289 | client_next = TAILQ_NEXT(client, next); | |
290 | if (!client->subscribed) continue; | |
be969691 VB |
291 | |
292 | if (output == NULL) { | |
293 | /* Ugly hack: we don't want to transmit a list of | |
74e0080e | 294 | * ports. We patch the port to avoid this. */ |
be969691 VB |
295 | TAILQ_ENTRY(lldpd_port) backup_p_entries; |
296 | memcpy(&backup_p_entries, &neighbor->p_entries, | |
297 | sizeof(backup_p_entries)); | |
be969691 VB |
298 | memset(&neighbor->p_entries, 0, |
299 | sizeof(backup_p_entries)); | |
985a4cb5 | 300 | output_len = lldpd_neighbor_change_serialize(&neigh, &output); |
be969691 VB |
301 | memcpy(&neighbor->p_entries, &backup_p_entries, |
302 | sizeof(backup_p_entries)); | |
be969691 VB |
303 | |
304 | if (output_len <= 0) { | |
6f8925be | 305 | log_warnx("event", "unable to serialize changed neighbor"); |
be969691 VB |
306 | return; |
307 | } | |
308 | } | |
309 | ||
4e90a9e0 VB |
310 | levent_ctl_send(client, NOTIFICATION, output, output_len); |
311 | } | |
312 | ||
313 | free(output); | |
314 | } | |
315 | ||
316 | static ssize_t | |
317 | levent_ctl_send_cb(void *out, int type, void *data, size_t len) | |
318 | { | |
319 | struct lldpd_one_client *client = out; | |
320 | return levent_ctl_send(client, type, data, len); | |
321 | } | |
322 | ||
d6e889b6 | 323 | static void |
e0478a46 | 324 | levent_ctl_recv(struct bufferevent *bev, void *ptr) |
d6e889b6 | 325 | { |
e0478a46 VB |
326 | struct lldpd_one_client *client = ptr; |
327 | struct evbuffer *buffer = bufferevent_get_input(bev); | |
328 | size_t buffer_len = evbuffer_get_length(buffer); | |
329 | struct hmsg_header hdr; | |
330 | void *data = NULL; | |
331 | ||
6f8925be | 332 | log_debug("control", "receive data on Unix socket"); |
e0478a46 VB |
333 | if (buffer_len < sizeof(struct hmsg_header)) |
334 | return; /* Not enough data yet */ | |
335 | if (evbuffer_copyout(buffer, &hdr, | |
336 | sizeof(struct hmsg_header)) != sizeof(struct hmsg_header)) { | |
6f8925be | 337 | log_warnx("event", "not able to read header"); |
e0478a46 VB |
338 | return; |
339 | } | |
82374540 | 340 | if (hdr.len > HMSG_MAX_SIZE) { |
6f8925be | 341 | log_warnx("event", "message received is too large"); |
e0478a46 VB |
342 | goto recv_error; |
343 | } | |
d6e889b6 | 344 | |
e0478a46 VB |
345 | if (buffer_len < hdr.len + sizeof(struct hmsg_header)) |
346 | return; /* Not enough data yet */ | |
347 | if (hdr.len > 0 && (data = malloc(hdr.len)) == NULL) { | |
6f8925be | 348 | log_warnx("event", "not enough memory"); |
e0478a46 VB |
349 | goto recv_error; |
350 | } | |
351 | evbuffer_drain(buffer, sizeof(struct hmsg_header)); | |
352 | if (hdr.len > 0) evbuffer_remove(buffer, data, hdr.len); | |
4e90a9e0 VB |
353 | |
354 | /* Currently, we should not receive notification acknowledgment. But if | |
355 | * we receive one, we can discard it. */ | |
356 | if (hdr.len == 0 && hdr.type == NOTIFICATION) return; | |
e0478a46 | 357 | if (client_handle_client(client->cfg, |
4e90a9e0 VB |
358 | levent_ctl_send_cb, client, |
359 | hdr.type, data, hdr.len, | |
360 | &client->subscribed) == -1) goto recv_error; | |
e0478a46 VB |
361 | free(data); |
362 | return; | |
363 | ||
364 | recv_error: | |
365 | free(data); | |
4e90a9e0 | 366 | levent_ctl_free_client(client); |
e0478a46 VB |
367 | } |
368 | ||
369 | static void | |
370 | levent_ctl_event(struct bufferevent *bev, short events, void *ptr) | |
371 | { | |
372 | struct lldpd_one_client *client = ptr; | |
373 | if (events & BEV_EVENT_ERROR) { | |
6f8925be | 374 | log_warnx("event", "an error occurred with client: %s", |
e0478a46 | 375 | evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); |
4e90a9e0 | 376 | levent_ctl_free_client(client); |
e0478a46 | 377 | } else if (events & BEV_EVENT_EOF) { |
6f8925be | 378 | log_debug("event", "client has been disconnected"); |
4e90a9e0 | 379 | levent_ctl_free_client(client); |
d6e889b6 | 380 | } |
d6e889b6 VB |
381 | } |
382 | ||
383 | static void | |
384 | levent_ctl_accept(evutil_socket_t fd, short what, void *arg) | |
385 | { | |
d6e889b6 | 386 | struct lldpd *cfg = arg; |
257db885 | 387 | struct lldpd_one_client *client = NULL; |
d6e889b6 | 388 | int s; |
5fd6695c VB |
389 | (void)what; |
390 | ||
6f8925be | 391 | log_debug("control", "accept a new connection"); |
d6e889b6 | 392 | if ((s = accept(fd, NULL, NULL)) == -1) { |
6f8925be | 393 | log_warn("event", "unable to accept connection from socket"); |
d6e889b6 VB |
394 | return; |
395 | } | |
257db885 VB |
396 | client = calloc(1, sizeof(struct lldpd_one_client)); |
397 | if (!client) { | |
6f8925be | 398 | log_warnx("event", "unable to allocate memory for new client"); |
4e90a9e0 | 399 | close(s); |
257db885 | 400 | goto accept_failed; |
d6e889b6 | 401 | } |
257db885 | 402 | client->cfg = cfg; |
bec75f84 | 403 | levent_make_socket_nonblocking(s); |
506273e9 | 404 | TAILQ_INSERT_TAIL(&lldpd_clients, client, next); |
e0478a46 VB |
405 | if ((client->bev = bufferevent_socket_new(cfg->g_base, s, |
406 | BEV_OPT_CLOSE_ON_FREE)) == NULL) { | |
6f8925be | 407 | log_warnx("event", "unable to allocate a new buffer event for new client"); |
4e90a9e0 | 408 | close(s); |
257db885 | 409 | goto accept_failed; |
d6e889b6 | 410 | } |
e0478a46 VB |
411 | bufferevent_setcb(client->bev, |
412 | levent_ctl_recv, NULL, levent_ctl_event, | |
413 | client); | |
414 | bufferevent_enable(client->bev, EV_READ | EV_WRITE); | |
6f8925be | 415 | log_debug("event", "new client accepted"); |
87dfd175 VB |
416 | /* coverity[leaked_handle] |
417 | s has been saved by bufferevent_socket_new */ | |
257db885 VB |
418 | return; |
419 | accept_failed: | |
4e90a9e0 | 420 | levent_ctl_free_client(client); |
d6e889b6 VB |
421 | } |
422 | ||
327b1d62 VB |
423 | static void |
424 | levent_priv(evutil_socket_t fd, short what, void *arg) | |
425 | { | |
426 | struct event_base *base = arg; | |
427 | ssize_t n; | |
428 | int err; | |
429 | char one; | |
430 | (void)what; | |
431 | /* Check if we have some data available. We need to pass the socket in | |
432 | * non-blocking mode to be able to run the check without disruption. */ | |
433 | levent_make_socket_nonblocking(fd); | |
434 | n = read(fd, &one, 0); err = errno; | |
435 | levent_make_socket_blocking(fd); | |
436 | ||
437 | switch (n) { | |
438 | case -1: | |
439 | if (err == EAGAIN || err == EWOULDBLOCK) | |
440 | /* No data, all good */ | |
441 | return; | |
442 | log_warnx("event", "unable to poll monitor process, exit"); | |
443 | break; | |
444 | case 0: | |
445 | log_warnx("event", "monitor process has terminated, exit"); | |
446 | break; | |
447 | default: | |
448 | /* Unfortunately, dead code, if we have data, we have requested | |
449 | * 0 byte, so we will fall in the previous case. It seems safer | |
450 | * to ask for 0 byte than asking for 1 byte. In the later case, | |
451 | * if we have to speak with the monitor again before exiting, we | |
452 | * would be out of sync. */ | |
453 | log_warnx("event", "received unexpected data from monitor process, exit"); | |
454 | break; | |
455 | } | |
456 | event_base_loopbreak(base); | |
457 | } | |
458 | ||
d6e889b6 VB |
459 | static void |
460 | levent_dump(evutil_socket_t fd, short what, void *arg) | |
461 | { | |
d6e889b6 | 462 | struct event_base *base = arg; |
5fd6695c | 463 | (void)fd; (void)what; |
6f8925be | 464 | log_debug("event", "dumping all events"); |
d6e889b6 VB |
465 | event_base_dump_events(base, stderr); |
466 | } | |
467 | static void | |
468 | levent_stop(evutil_socket_t fd, short what, void *arg) | |
469 | { | |
d6e889b6 | 470 | struct event_base *base = arg; |
5fd6695c | 471 | (void)fd; (void)what; |
d6e889b6 VB |
472 | event_base_loopbreak(base); |
473 | } | |
474 | ||
475 | static void | |
476 | levent_update_and_send(evutil_socket_t fd, short what, void *arg) | |
477 | { | |
d6e889b6 | 478 | struct lldpd *cfg = arg; |
74f55c2e JPT |
479 | struct timeval tv; |
480 | long interval_ms = cfg->g_config.c_tx_interval; | |
481 | ||
5fd6695c | 482 | (void)fd; (void)what; |
d6e889b6 | 483 | lldpd_loop(cfg); |
579bedd5 | 484 | if (cfg->g_iface_event != NULL) |
74f55c2e JPT |
485 | interval_ms *= 20; |
486 | tv.tv_sec = interval_ms / 1000; | |
487 | tv.tv_usec = (interval_ms % 1000) * 1000; | |
d6e889b6 VB |
488 | event_add(cfg->g_main_loop, &tv); |
489 | } | |
490 | ||
e681c859 VB |
491 | void |
492 | levent_update_now(struct lldpd *cfg) | |
493 | { | |
494 | if (cfg->g_main_loop) | |
495 | event_active(cfg->g_main_loop, EV_TIMEOUT, 1); | |
496 | } | |
497 | ||
47287a61 VB |
498 | void |
499 | levent_send_now(struct lldpd *cfg) | |
500 | { | |
959a54d4 | 501 | struct lldpd_hardware *hardware; |
126970da VB |
502 | TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { |
503 | if (hardware->h_timer) | |
504 | event_active(hardware->h_timer, EV_TIMEOUT, 1); | |
505 | else | |
506 | log_warnx("event", "BUG: no timer present for interface %s", | |
507 | hardware->h_ifname); | |
508 | } | |
47287a61 VB |
509 | } |
510 | ||
d6e889b6 VB |
511 | static void |
512 | levent_init(struct lldpd *cfg) | |
513 | { | |
514 | /* Setup libevent */ | |
6f8925be | 515 | log_debug("event", "initialize libevent"); |
d6e889b6 VB |
516 | event_set_log_callback(levent_log_cb); |
517 | if (!(cfg->g_base = event_base_new())) | |
a87db231 | 518 | fatalx("event", "unable to create a new libevent base"); |
6f8925be | 519 | log_info("event", "libevent %s initialized with %s method", |
d6e889b6 VB |
520 | event_get_version(), |
521 | event_base_get_method(cfg->g_base)); | |
522 | ||
523 | /* Setup SNMP */ | |
524 | #ifdef USE_SNMP | |
525 | if (cfg->g_snmp) { | |
526 | agent_init(cfg, cfg->g_snmp_agentx); | |
527 | cfg->g_snmp_timeout = evtimer_new(cfg->g_base, | |
528 | levent_snmp_timeout, | |
529 | cfg); | |
530 | if (!cfg->g_snmp_timeout) | |
a87db231 | 531 | fatalx("event", "unable to setup timeout function for SNMP"); |
d6e889b6 VB |
532 | if ((cfg->g_snmp_fds = |
533 | malloc(sizeof(struct ev_l))) == NULL) | |
a87db231 | 534 | fatalx("event", "unable to allocate memory for SNMP events"); |
d6e889b6 VB |
535 | TAILQ_INIT(levent_snmp_fds(cfg)); |
536 | } | |
537 | #endif | |
6f8925be | 538 | |
579bedd5 | 539 | /* Setup loop that will run every X seconds. */ |
6f8925be | 540 | log_debug("event", "register loop timer"); |
d6e889b6 VB |
541 | if (!(cfg->g_main_loop = event_new(cfg->g_base, -1, 0, |
542 | levent_update_and_send, | |
543 | cfg))) | |
a87db231 | 544 | fatalx("event", "unable to setup main timer"); |
86b5c6fb | 545 | event_active(cfg->g_main_loop, EV_TIMEOUT, 1); |
d6e889b6 VB |
546 | |
547 | /* Setup unix socket */ | |
50efc5f7 | 548 | struct event *ctl_event; |
6f8925be | 549 | log_debug("event", "register Unix socket"); |
4e90a9e0 | 550 | TAILQ_INIT(&lldpd_clients); |
bec75f84 | 551 | levent_make_socket_nonblocking(cfg->g_ctl); |
50efc5f7 | 552 | if ((ctl_event = event_new(cfg->g_base, cfg->g_ctl, |
d6e889b6 | 553 | EV_READ|EV_PERSIST, levent_ctl_accept, cfg)) == NULL) |
a87db231 | 554 | fatalx("event", "unable to setup control socket event"); |
50efc5f7 | 555 | event_add(ctl_event, NULL); |
d6e889b6 | 556 | |
327b1d62 VB |
557 | /* Somehow monitor the monitor process */ |
558 | struct event *monitor_event; | |
559 | log_debug("event", "monitor the monitor process"); | |
560 | if ((monitor_event = event_new(cfg->g_base, priv_fd(PRIV_UNPRIVILEGED), | |
561 | EV_READ|EV_PERSIST, levent_priv, cfg->g_base)) == NULL) | |
562 | fatalx("event", "unable to monitor monitor process"); | |
563 | event_add(monitor_event, NULL); | |
564 | ||
d6e889b6 | 565 | /* Signals */ |
6f8925be | 566 | log_debug("event", "register signals"); |
d6e889b6 VB |
567 | evsignal_add(evsignal_new(cfg->g_base, SIGUSR1, |
568 | levent_dump, cfg->g_base), | |
569 | NULL); | |
d6e889b6 VB |
570 | evsignal_add(evsignal_new(cfg->g_base, SIGINT, |
571 | levent_stop, cfg->g_base), | |
572 | NULL); | |
573 | evsignal_add(evsignal_new(cfg->g_base, SIGTERM, | |
574 | levent_stop, cfg->g_base), | |
575 | NULL); | |
576 | } | |
577 | ||
578 | /* Initialize libevent and start the event loop */ | |
579 | void | |
580 | levent_loop(struct lldpd *cfg) | |
581 | { | |
582 | levent_init(cfg); | |
e4ff3ed5 | 583 | lldpd_loop(cfg); |
dbcb6846 VB |
584 | #ifdef USE_SNMP |
585 | if (cfg->g_snmp) levent_snmp_update(cfg); | |
586 | #endif | |
d6e889b6 VB |
587 | |
588 | /* libevent loop */ | |
589 | do { | |
bdfe4193 | 590 | TRACE(LLDPD_EVENT_LOOP()); |
d6e889b6 VB |
591 | if (event_base_got_break(cfg->g_base) || |
592 | event_base_got_exit(cfg->g_base)) | |
593 | break; | |
d6e889b6 VB |
594 | } while (event_base_loop(cfg->g_base, EVLOOP_ONCE) == 0); |
595 | ||
3744168c VB |
596 | if (cfg->g_iface_timer_event != NULL) |
597 | event_free(cfg->g_iface_timer_event); | |
598 | ||
d6e889b6 VB |
599 | #ifdef USE_SNMP |
600 | if (cfg->g_snmp) | |
601 | agent_shutdown(); | |
602 | #endif /* USE_SNMP */ | |
603 | ||
762419b6 | 604 | levent_ctl_close_clients(); |
f144d837 VB |
605 | } |
606 | ||
607 | /* Release libevent resources */ | |
608 | void | |
609 | levent_shutdown(struct lldpd *cfg) | |
610 | { | |
762419b6 VB |
611 | if (cfg->g_iface_event) |
612 | event_free(cfg->g_iface_event); | |
613 | if (cfg->g_cleanup_timer) | |
614 | event_free(cfg->g_cleanup_timer); | |
f144d837 | 615 | event_base_free(cfg->g_base); |
d6e889b6 VB |
616 | } |
617 | ||
618 | static void | |
619 | levent_hardware_recv(evutil_socket_t fd, short what, void *arg) | |
620 | { | |
d6e889b6 VB |
621 | struct lldpd_hardware *hardware = arg; |
622 | struct lldpd *cfg = hardware->h_cfg; | |
5fd6695c | 623 | (void)what; |
6f8925be VB |
624 | log_debug("event", "received something for %s", |
625 | hardware->h_ifname); | |
d6e889b6 | 626 | lldpd_recv(cfg, hardware, fd); |
3333d2a8 | 627 | levent_schedule_cleanup(cfg); |
d6e889b6 VB |
628 | } |
629 | ||
630 | void | |
631 | levent_hardware_init(struct lldpd_hardware *hardware) | |
632 | { | |
6f8925be | 633 | log_debug("event", "initialize events for %s", hardware->h_ifname); |
d6e889b6 VB |
634 | if ((hardware->h_recv = |
635 | malloc(sizeof(struct ev_l))) == NULL) { | |
6f8925be | 636 | log_warnx("event", "unable to allocate memory for %s", |
d6e889b6 VB |
637 | hardware->h_ifname); |
638 | return; | |
639 | } | |
640 | TAILQ_INIT(levent_hardware_fds(hardware)); | |
641 | } | |
642 | ||
643 | void | |
644 | levent_hardware_add_fd(struct lldpd_hardware *hardware, int fd) | |
645 | { | |
5fd6695c | 646 | struct lldpd_events *hfd = NULL; |
d6e889b6 VB |
647 | if (!hardware->h_recv) return; |
648 | ||
5fd6695c | 649 | hfd = calloc(1, sizeof(struct lldpd_events)); |
d6e889b6 | 650 | if (!hfd) { |
6f8925be | 651 | log_warnx("event", "unable to allocate new event for %s", |
d6e889b6 VB |
652 | hardware->h_ifname); |
653 | return; | |
654 | } | |
bec75f84 | 655 | levent_make_socket_nonblocking(fd); |
d6e889b6 VB |
656 | if ((hfd->ev = event_new(hardware->h_cfg->g_base, fd, |
657 | EV_READ | EV_PERSIST, | |
658 | levent_hardware_recv, | |
659 | hardware)) == NULL) { | |
6f8925be | 660 | log_warnx("event", "unable to allocate a new event for %s", |
d6e889b6 VB |
661 | hardware->h_ifname); |
662 | free(hfd); | |
663 | return; | |
664 | } | |
665 | if (event_add(hfd->ev, NULL) == -1) { | |
6f8925be | 666 | log_warnx("event", "unable to schedule new event for %s", |
d6e889b6 VB |
667 | hardware->h_ifname); |
668 | event_free(hfd->ev); | |
669 | free(hfd); | |
670 | return; | |
671 | } | |
672 | TAILQ_INSERT_TAIL(levent_hardware_fds(hardware), hfd, next); | |
673 | } | |
674 | ||
675 | void | |
676 | levent_hardware_release(struct lldpd_hardware *hardware) | |
677 | { | |
5fd6695c | 678 | struct lldpd_events *ev, *ev_next; |
dbfa89c6 VB |
679 | if (hardware->h_timer) { |
680 | event_free(hardware->h_timer); | |
681 | hardware->h_timer = NULL; | |
682 | } | |
d6e889b6 VB |
683 | if (!hardware->h_recv) return; |
684 | ||
6f8925be | 685 | log_debug("event", "release events for %s", hardware->h_ifname); |
d6e889b6 VB |
686 | for (ev = TAILQ_FIRST(levent_hardware_fds(hardware)); |
687 | ev; | |
688 | ev = ev_next) { | |
689 | ev_next = TAILQ_NEXT(ev, next); | |
690 | /* We may close several time the same FD. This is harmless. */ | |
691 | close(event_get_fd(ev->ev)); | |
692 | event_free(ev->ev); | |
693 | TAILQ_REMOVE(levent_hardware_fds(hardware), ev, next); | |
694 | free(ev); | |
695 | } | |
696 | free(levent_hardware_fds(hardware)); | |
697 | } | |
0484f180 VB |
698 | |
699 | static void | |
700 | levent_iface_trigger(evutil_socket_t fd, short what, void *arg) | |
701 | { | |
702 | struct lldpd *cfg = arg; | |
4f670a1e | 703 | log_debug("event", |
0484f180 VB |
704 | "triggering update of all interfaces"); |
705 | lldpd_update_localports(cfg); | |
706 | } | |
707 | ||
708 | static void | |
709 | levent_iface_recv(evutil_socket_t fd, short what, void *arg) | |
710 | { | |
711 | struct lldpd *cfg = arg; | |
b0b8841b | 712 | char buffer[EVENT_BUFFER]; |
0484f180 VB |
713 | int n; |
714 | ||
13181ede VB |
715 | if (cfg->g_iface_cb == NULL) { |
716 | /* Discard the message */ | |
717 | while (1) { | |
718 | n = read(fd, buffer, sizeof(buffer)); | |
719 | if (n == -1 && | |
720 | (errno == EWOULDBLOCK || | |
721 | errno == EAGAIN)) break; | |
722 | if (n == -1) { | |
723 | log_warn("event", | |
724 | "unable to receive interface change notification message"); | |
725 | return; | |
726 | } | |
727 | if (n == 0) { | |
728 | log_warnx("event", | |
729 | "end of file reached while getting interface change notification message"); | |
730 | return; | |
731 | } | |
0484f180 | 732 | } |
13181ede VB |
733 | } else { |
734 | cfg->g_iface_cb(cfg); | |
0484f180 VB |
735 | } |
736 | ||
737 | /* Schedule local port update. We don't run it right away because we may | |
738 | * receive a batch of events like this. */ | |
739 | struct timeval one_sec = {1, 0}; | |
bdfe4193 | 740 | TRACE(LLDPD_INTERFACES_NOTIFICATION()); |
0484f180 VB |
741 | log_debug("event", |
742 | "received notification change, schedule an update of all interfaces in one second"); | |
743 | if (cfg->g_iface_timer_event == NULL) { | |
744 | if ((cfg->g_iface_timer_event = evtimer_new(cfg->g_base, | |
745 | levent_iface_trigger, cfg)) == NULL) { | |
746 | log_warnx("event", | |
747 | "unable to create a new event to trigger interface update"); | |
748 | return; | |
749 | } | |
750 | } | |
751 | if (evtimer_add(cfg->g_iface_timer_event, &one_sec) == -1) { | |
752 | log_warnx("event", | |
753 | "unable to schedule interface updates"); | |
754 | return; | |
755 | } | |
756 | } | |
757 | ||
aa313f2a | 758 | int |
0484f180 VB |
759 | levent_iface_subscribe(struct lldpd *cfg, int socket) |
760 | { | |
761 | log_debug("event", "subscribe to interface changes from socket %d", | |
762 | socket); | |
66a551ed | 763 | levent_make_socket_nonblocking(socket); |
0484f180 VB |
764 | cfg->g_iface_event = event_new(cfg->g_base, socket, |
765 | EV_READ | EV_PERSIST, levent_iface_recv, cfg); | |
766 | if (cfg->g_iface_event == NULL) { | |
767 | log_warnx("event", | |
768 | "unable to allocate a new event for interface changes"); | |
aa313f2a | 769 | return -1; |
0484f180 VB |
770 | } |
771 | if (event_add(cfg->g_iface_event, NULL) == -1) { | |
772 | log_warnx("event", | |
773 | "unable to schedule new interface changes event"); | |
774 | event_free(cfg->g_iface_event); | |
775 | cfg->g_iface_event = NULL; | |
aa313f2a | 776 | return -1; |
0484f180 | 777 | } |
aa313f2a | 778 | return 0; |
0484f180 | 779 | } |
579bedd5 | 780 | |
3333d2a8 VB |
781 | static void |
782 | levent_trigger_cleanup(evutil_socket_t fd, short what, void *arg) | |
783 | { | |
784 | struct lldpd *cfg = arg; | |
785 | lldpd_cleanup(cfg); | |
786 | } | |
787 | ||
788 | void | |
789 | levent_schedule_cleanup(struct lldpd *cfg) | |
790 | { | |
791 | log_debug("event", "schedule next cleanup"); | |
792 | if (cfg->g_cleanup_timer != NULL) { | |
793 | event_free(cfg->g_cleanup_timer); | |
794 | } | |
795 | cfg->g_cleanup_timer = evtimer_new(cfg->g_base, levent_trigger_cleanup, cfg); | |
796 | if (cfg->g_cleanup_timer == NULL) { | |
797 | log_warnx("event", | |
798 | "unable to allocate a new event for cleanup tasks"); | |
799 | return; | |
800 | } | |
801 | ||
802 | /* Compute the next TTL event */ | |
71b0f981 | 803 | struct timeval tv = { cfg->g_config.c_ttl, 0 }; |
3333d2a8 VB |
804 | time_t now = time(NULL); |
805 | time_t next; | |
806 | struct lldpd_hardware *hardware; | |
807 | struct lldpd_port *port; | |
808 | TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { | |
809 | TAILQ_FOREACH(port, &hardware->h_rports, p_entries) { | |
78346c89 | 810 | if (now >= port->p_lastupdate + port->p_ttl) { |
5a215d4b | 811 | tv.tv_sec = 0; |
408c3e02 VB |
812 | log_debug("event", "immediate cleanup on port %s (%lld, %d, %lld)", |
813 | hardware->h_ifname, | |
814 | (long long)now, | |
78346c89 | 815 | port->p_ttl, |
408c3e02 | 816 | (long long)port->p_lastupdate); |
5a215d4b VB |
817 | break; |
818 | } | |
78346c89 | 819 | next = port->p_ttl - (now - port->p_lastupdate); |
5a215d4b | 820 | if (next < tv.tv_sec) |
3333d2a8 VB |
821 | tv.tv_sec = next; |
822 | } | |
823 | } | |
824 | ||
825 | log_debug("event", "next cleanup in %ld seconds", | |
826 | (long)tv.tv_sec); | |
827 | if (event_add(cfg->g_cleanup_timer, &tv) == -1) { | |
828 | log_warnx("event", | |
07ecfcd1 | 829 | "unable to schedule cleanup task"); |
3333d2a8 VB |
830 | event_free(cfg->g_cleanup_timer); |
831 | cfg->g_cleanup_timer = NULL; | |
832 | return; | |
833 | } | |
834 | } | |
835 | ||
579bedd5 VB |
836 | static void |
837 | levent_send_pdu(evutil_socket_t fd, short what, void *arg) | |
838 | { | |
839 | struct lldpd_hardware *hardware = arg; | |
be511d00 | 840 | int tx_interval = hardware->h_cfg->g_config.c_tx_interval; |
b9de0ca6 | 841 | |
579bedd5 VB |
842 | log_debug("event", "trigger sending PDU for port %s", |
843 | hardware->h_ifname); | |
844 | lldpd_send(hardware); | |
845 | ||
be511d00 | 846 | #ifdef ENABLE_LLDPMED |
b9de0ca6 | 847 | if (hardware->h_tx_fast > 0) |
848 | hardware->h_tx_fast--; | |
849 | ||
850 | if (hardware->h_tx_fast > 0) | |
74f55c2e | 851 | tx_interval = hardware->h_cfg->g_config.c_tx_fast_interval * 1000; |
be511d00 VB |
852 | #endif |
853 | ||
74f55c2e JPT |
854 | struct timeval tv; |
855 | tv.tv_sec = tx_interval / 1000; | |
856 | tv.tv_usec = (tx_interval % 1000) * 1000; | |
579bedd5 VB |
857 | if (event_add(hardware->h_timer, &tv) == -1) { |
858 | log_warnx("event", "unable to re-register timer event for port %s", | |
859 | hardware->h_ifname); | |
860 | event_free(hardware->h_timer); | |
861 | hardware->h_timer = NULL; | |
862 | return; | |
863 | } | |
864 | } | |
865 | ||
866 | void | |
867 | levent_schedule_pdu(struct lldpd_hardware *hardware) | |
868 | { | |
869 | log_debug("event", "schedule sending PDU on %s", | |
870 | hardware->h_ifname); | |
871 | if (hardware->h_timer == NULL) { | |
872 | hardware->h_timer = evtimer_new(hardware->h_cfg->g_base, | |
873 | levent_send_pdu, hardware); | |
874 | if (hardware->h_timer == NULL) { | |
875 | log_warnx("event", "unable to schedule PDU sending for port %s", | |
876 | hardware->h_ifname); | |
877 | return; | |
878 | } | |
879 | } | |
880 | ||
881 | struct timeval tv = { 0, 0 }; | |
882 | if (event_add(hardware->h_timer, &tv) == -1) { | |
883 | log_warnx("event", "unable to register timer event for port %s", | |
884 | hardware->h_ifname); | |
885 | event_free(hardware->h_timer); | |
886 | hardware->h_timer = NULL; | |
887 | return; | |
888 | } | |
889 | } | |
bec75f84 VB |
890 | |
891 | int | |
892 | levent_make_socket_nonblocking(int fd) | |
893 | { | |
894 | int flags; | |
895 | if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) { | |
896 | log_warn("event", "fcntl(%d, F_GETFL)", fd); | |
897 | return -1; | |
898 | } | |
899 | if (flags & O_NONBLOCK) return 0; | |
900 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { | |
901 | log_warn("event", "fcntl(%d, F_SETFL)", fd); | |
902 | return -1; | |
903 | } | |
904 | return 0; | |
905 | } | |
327b1d62 VB |
906 | |
907 | int | |
908 | levent_make_socket_blocking(int fd) | |
909 | { | |
910 | int flags; | |
911 | if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) { | |
912 | log_warn("event", "fcntl(%d, F_GETFL)", fd); | |
913 | return -1; | |
914 | } | |
915 | if (!(flags & O_NONBLOCK)) return 0; | |
916 | if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) { | |
917 | log_warn("event", "fcntl(%d, F_SETFL)", fd); | |
918 | return -1; | |
919 | } | |
920 | return 0; | |
921 | } | |
6c3697f2 VB |
922 | |
923 | #ifdef HOST_OS_LINUX | |
924 | /* Receive and log error from a socket when there is suspicion of an error. */ | |
925 | void | |
926 | levent_recv_error(int fd, const char *source) | |
927 | { | |
928 | do { | |
929 | ssize_t n; | |
930 | char buf[1024] = {}; | |
931 | struct msghdr msg = { | |
932 | .msg_control = buf, | |
933 | .msg_controllen = sizeof(buf) | |
934 | }; | |
935 | if ((n = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT)) <= 0) { | |
936 | return; | |
937 | } | |
938 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); | |
939 | if (cmsg == NULL) | |
940 | log_warnx("event", "received unknown error on %s", | |
941 | source); | |
942 | else | |
943 | log_warnx("event", "received error (level=%d/type=%d) on %s", | |
944 | cmsg->cmsg_level, cmsg->cmsg_type, source); | |
945 | } while (1); | |
946 | } | |
947 | #endif |