]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/daemon/event.c
dist: provide a complete changelog
[thirdparty/lldpd.git] / src / daemon / event.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
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"
19
20 #include <unistd.h>
21 #include <signal.h>
22 #include <event2/event.h>
23
24 static void
25 levent_log_cb(int severity, const char *msg)
26 {
27 switch (severity) {
28 case _EVENT_LOG_DEBUG: log_debug("libevent[debug]: %s", msg); break;
29 case _EVENT_LOG_MSG: log_info ("libevent[info]: %s", msg); break;
30 case _EVENT_LOG_WARN: log_warnx("libevent[warn]: %s", msg); break;
31 case _EVENT_LOG_ERR: log_warnx("libevent[error]: %s", msg); break;
32 }
33 }
34
35 struct lldpd_events {
36 TAILQ_ENTRY(lldpd_events) next;
37 struct event *ev;
38 };
39 TAILQ_HEAD(ev_l, lldpd_events);
40
41 #define levent_snmp_fds(cfg) ((struct ev_l*)(cfg)->g_snmp_fds)
42 #define levent_hardware_fds(hardware) ((struct ev_l*)(hardware)->h_recv)
43
44 #ifdef USE_SNMP
45 #include <net-snmp/net-snmp-config.h>
46 #include <net-snmp/net-snmp-includes.h>
47 #include <net-snmp/agent/net-snmp-agent-includes.h>
48 #include <net-snmp/agent/snmp_vars.h>
49
50 static void levent_snmp_update(struct lldpd *);
51
52 /*
53 * Callback function when we have something to read from SNMP.
54 *
55 * This function is called because we have a read event on one SNMP
56 * file descriptor. When need to call snmp_read() on it.
57 */
58 static void
59 levent_snmp_read(evutil_socket_t fd, short what, void *arg)
60 {
61 struct lldpd *cfg = arg;
62 fd_set fdset;
63 (void)what;
64 FD_ZERO(&fdset);
65 FD_SET(fd, &fdset);
66 snmp_read(&fdset);
67 levent_snmp_update(cfg);
68 }
69
70 /*
71 * Callback function for a SNMP timeout.
72 *
73 * A SNMP timeout has occurred. Call `snmp_timeout()` to handle it.
74 */
75 static void
76 levent_snmp_timeout(evutil_socket_t fd, short what, void *arg)
77 {
78 struct lldpd *cfg = arg;
79 (void)what; (void)fd;
80 snmp_timeout();
81 run_alarms();
82 levent_snmp_update(cfg);
83 }
84
85 /*
86 * Watch a new SNMP FD.
87 *
88 * @param base The libevent base we are working on.
89 * @param fd The file descriptor we want to watch.
90 *
91 * The file descriptor is appended to the list of file descriptors we
92 * want to watch.
93 */
94 static void
95 levent_snmp_add_fd(struct lldpd *cfg, int fd)
96 {
97 struct event_base *base = cfg->g_base;
98 struct lldpd_events *snmpfd = calloc(1, sizeof(struct lldpd_events));
99 if (!snmpfd) {
100 LLOG_WARN("unable to allocate memory for new SNMP event");
101 return;
102 }
103 evutil_make_socket_nonblocking(fd);
104 if ((snmpfd->ev = event_new(base, fd,
105 EV_READ | EV_PERSIST,
106 levent_snmp_read,
107 cfg)) == NULL) {
108 LLOG_WARNX("unable to allocate a new SNMP event for FD %d", fd);
109 free(snmpfd);
110 return;
111 }
112 if (event_add(snmpfd->ev, NULL) == -1) {
113 LLOG_WARNX("unable to schedule new SNMP event for FD %d", fd);
114 event_free(snmpfd->ev);
115 free(snmpfd);
116 return;
117 }
118 TAILQ_INSERT_TAIL(levent_snmp_fds(cfg), snmpfd, next);
119 }
120
121 /*
122 * Update SNMP event loop.
123 *
124 * New events are added and some other are removed. This function
125 * should be called every time a SNMP event happens: either when
126 * handling a SNMP packet, a SNMP timeout or when sending a SNMP
127 * packet. This function will keep libevent in sync with NetSNMP.
128 *
129 * @param base The libevent base we are working on.
130 */
131 static void
132 levent_snmp_update(struct lldpd *cfg)
133 {
134 int maxfd = 0;
135 int block = 1;
136 fd_set fdset;
137 struct timeval timeout;
138 static int howmany = 0;
139 int added = 0, removed = 0, current = 0;
140 struct lldpd_events *snmpfd, *snmpfd_next;
141
142 /* snmp_select_info() can be tricky to understand. We set `block` to
143 1 to means that we don't request a timeout. snmp_select_info()
144 will reset `block` to 0 if it wants us to setup a timeout. In
145 this timeout, `snmp_timeout()` should be invoked.
146
147 Each FD in `fdset` will need to be watched for reading. If one of
148 them become active, `snmp_read()` should be called on it.
149 */
150
151 FD_ZERO(&fdset);
152 snmp_select_info(&maxfd, &fdset, &timeout, &block);
153
154 /* We need to untrack any event whose FD is not in `fdset`
155 anymore */
156 for (snmpfd = TAILQ_FIRST(levent_snmp_fds(cfg));
157 snmpfd;
158 snmpfd = snmpfd_next) {
159 snmpfd_next = TAILQ_NEXT(snmpfd, next);
160 if (event_get_fd(snmpfd->ev) >= maxfd ||
161 (!FD_ISSET(event_get_fd(snmpfd->ev), &fdset))) {
162 event_free(snmpfd->ev);
163 TAILQ_REMOVE(levent_snmp_fds(cfg), snmpfd, next);
164 free(snmpfd);
165 removed++;
166 } else {
167 FD_CLR(event_get_fd(snmpfd->ev), &fdset);
168 current++;
169 }
170 }
171
172 /* Invariant: FD in `fdset` are not in list of FD */
173 for (int fd = 0; fd < maxfd; fd++) {
174 if (FD_ISSET(fd, &fdset)) {
175 levent_snmp_add_fd(cfg, fd);
176 added++;
177 }
178 }
179 current += added;
180 if (howmany != current) {
181 LLOG_DEBUG("added %d events, removed %d events, total of %d events",
182 added, removed, current);
183 howmany = current;
184 }
185
186 /* If needed, handle timeout */
187 if (evtimer_add(cfg->g_snmp_timeout, block?NULL:&timeout) == -1)
188 LLOG_WARNX("unable to schedule timeout function for SNMP");
189 }
190 #endif /* USE_SNMP */
191
192 struct lldpd_one_client {
193 struct lldpd *cfg;
194 struct event *ev;
195 };
196
197 static void
198 levent_ctl_recv(evutil_socket_t fd, short what, void *arg)
199 {
200 struct lldpd_one_client *client = arg;
201 enum hmsg_type type;
202 void *buffer = NULL;
203 int n;
204 (void)what;
205
206 if ((n = ctl_msg_recv(fd, &type, &buffer)) == -1 ||
207 client_handle_client(client->cfg, fd, type, buffer, n) == -1) {
208 close(fd);
209 event_free(client->ev);
210 free(client);
211 }
212 free(buffer);
213 }
214
215 static void
216 levent_ctl_accept(evutil_socket_t fd, short what, void *arg)
217 {
218 struct lldpd *cfg = arg;
219 struct lldpd_one_client *client = NULL;
220 int s;
221 (void)what;
222
223 if ((s = accept(fd, NULL, NULL)) == -1) {
224 LLOG_WARN("unable to accept connection from socket");
225 return;
226 }
227 client = calloc(1, sizeof(struct lldpd_one_client));
228 if (!client) {
229 LLOG_WARNX("unable to allocate memory for new client");
230 goto accept_failed;
231 }
232 client->cfg = cfg;
233 evutil_make_socket_nonblocking(s);
234 if ((client->ev = event_new(cfg->g_base, s,
235 EV_READ | EV_PERSIST,
236 levent_ctl_recv,
237 client)) == NULL) {
238 LLOG_WARNX("unable to allocate a new event for new client");
239 goto accept_failed;
240 }
241 if (event_add(client->ev, NULL) == -1) {
242 LLOG_WARNX("unable to schedule new event for new client");
243 goto accept_failed;
244 }
245 return;
246 accept_failed:
247 if (client && client->ev) event_free(client->ev);
248 free(client);
249 close(s);
250 }
251
252 static void
253 levent_dump(evutil_socket_t fd, short what, void *arg)
254 {
255 struct event_base *base = arg;
256 (void)fd; (void)what;
257 event_base_dump_events(base, stderr);
258 }
259 static void
260 levent_stop(evutil_socket_t fd, short what, void *arg)
261 {
262 struct event_base *base = arg;
263 (void)fd; (void)what;
264 event_base_loopbreak(base);
265 }
266
267 static void
268 levent_update_and_send(evutil_socket_t fd, short what, void *arg)
269 {
270 struct lldpd *cfg = arg;
271 struct timeval tv = {cfg->g_delay, 0};
272 (void)fd; (void)what;
273 lldpd_loop(cfg);
274 event_add(cfg->g_main_loop, &tv);
275 }
276
277 static void
278 levent_init(struct lldpd *cfg)
279 {
280 /* Setup libevent */
281 event_set_log_callback(levent_log_cb);
282 if (!(cfg->g_base = event_base_new()))
283 fatalx("unable to create a new libevent base");
284 LLOG_INFO("libevent %s initialized with %s method",
285 event_get_version(),
286 event_base_get_method(cfg->g_base));
287
288 /* Setup SNMP */
289 #ifdef USE_SNMP
290 if (cfg->g_snmp) {
291 agent_init(cfg, cfg->g_snmp_agentx);
292 cfg->g_snmp_timeout = evtimer_new(cfg->g_base,
293 levent_snmp_timeout,
294 cfg);
295 if (!cfg->g_snmp_timeout)
296 fatalx("unable to setup timeout function for SNMP");
297 if ((cfg->g_snmp_fds =
298 malloc(sizeof(struct ev_l))) == NULL)
299 fatalx("unable to allocate memory for SNMP events");
300 TAILQ_INIT(levent_snmp_fds(cfg));
301 }
302 #endif
303
304 /* Setup loop that will run every 30 seconds. */
305 if (!(cfg->g_main_loop = event_new(cfg->g_base, -1, 0,
306 levent_update_and_send,
307 cfg)))
308 fatalx("unable to setup main timer");
309 event_active(cfg->g_main_loop, EV_TIMEOUT, 1);
310
311 /* Setup unix socket */
312 evutil_make_socket_nonblocking(cfg->g_ctl);
313 if ((cfg->g_ctl_event = event_new(cfg->g_base, cfg->g_ctl,
314 EV_READ|EV_PERSIST, levent_ctl_accept, cfg)) == NULL)
315 fatalx("unable to setup control socket event");
316 event_add(cfg->g_ctl_event, NULL);
317
318 /* Signals */
319 evsignal_add(evsignal_new(cfg->g_base, SIGUSR1,
320 levent_dump, cfg->g_base),
321 NULL);
322 evsignal_add(evsignal_new(cfg->g_base, SIGHUP,
323 levent_stop, cfg->g_base),
324 NULL);
325 evsignal_add(evsignal_new(cfg->g_base, SIGINT,
326 levent_stop, cfg->g_base),
327 NULL);
328 evsignal_add(evsignal_new(cfg->g_base, SIGTERM,
329 levent_stop, cfg->g_base),
330 NULL);
331 }
332
333 /* Initialize libevent and start the event loop */
334 void
335 levent_loop(struct lldpd *cfg)
336 {
337 levent_init(cfg);
338
339 /* libevent loop */
340 do {
341 if (event_base_got_break(cfg->g_base) ||
342 event_base_got_exit(cfg->g_base))
343 break;
344 #ifdef USE_SNMP
345 if (cfg->g_snmp) {
346 /* We don't use delegated requests (request
347 whose answer is delayed). However, we keep
348 the call here in case we use it some
349 day. We don't call run_alarms() here. We do
350 it on timeout only. */
351 netsnmp_check_outstanding_agent_requests();
352 levent_snmp_update(cfg);
353 }
354 #endif
355 } while (event_base_loop(cfg->g_base, EVLOOP_ONCE) == 0);
356
357 #ifdef USE_SNMP
358 if (cfg->g_snmp)
359 agent_shutdown();
360 #endif /* USE_SNMP */
361
362 }
363
364 static void
365 levent_hardware_recv(evutil_socket_t fd, short what, void *arg)
366 {
367 struct lldpd_hardware *hardware = arg;
368 struct lldpd *cfg = hardware->h_cfg;
369 (void)what;
370 lldpd_recv(cfg, hardware, fd);
371 }
372
373 void
374 levent_hardware_init(struct lldpd_hardware *hardware)
375 {
376 if ((hardware->h_recv =
377 malloc(sizeof(struct ev_l))) == NULL) {
378 LLOG_WARNX("unable to allocate memory for %s",
379 hardware->h_ifname);
380 return;
381 }
382 TAILQ_INIT(levent_hardware_fds(hardware));
383 }
384
385 void
386 levent_hardware_add_fd(struct lldpd_hardware *hardware, int fd)
387 {
388 struct lldpd_events *hfd = NULL;
389 if (!hardware->h_recv) return;
390
391 hfd = calloc(1, sizeof(struct lldpd_events));
392 if (!hfd) {
393 LLOG_WARNX("unable to allocate new event for %s",
394 hardware->h_ifname);
395 return;
396 }
397 evutil_make_socket_nonblocking(fd);
398 if ((hfd->ev = event_new(hardware->h_cfg->g_base, fd,
399 EV_READ | EV_PERSIST,
400 levent_hardware_recv,
401 hardware)) == NULL) {
402 LLOG_WARNX("unable to allocate a new event for %s",
403 hardware->h_ifname);
404 free(hfd);
405 return;
406 }
407 if (event_add(hfd->ev, NULL) == -1) {
408 LLOG_WARNX("unable to schedule new event for %s",
409 hardware->h_ifname);
410 event_free(hfd->ev);
411 free(hfd);
412 return;
413 }
414 TAILQ_INSERT_TAIL(levent_hardware_fds(hardware), hfd, next);
415 }
416
417 void
418 levent_hardware_release(struct lldpd_hardware *hardware)
419 {
420 struct lldpd_events *ev, *ev_next;
421 if (!hardware->h_recv) return;
422
423 for (ev = TAILQ_FIRST(levent_hardware_fds(hardware));
424 ev;
425 ev = ev_next) {
426 ev_next = TAILQ_NEXT(ev, next);
427 /* We may close several time the same FD. This is harmless. */
428 close(event_get_fd(ev->ev));
429 event_free(ev->ev);
430 TAILQ_REMOVE(levent_hardware_fds(hardware), ev, next);
431 free(ev);
432 }
433 free(levent_hardware_fds(hardware));
434 }