chiark / gitweb /
45881e5f64a1a98ca889caa564f0ae0c7292051a
[elogind.git] / src / libsystemd-network / sd-lldp.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4     This file is part of systemd.
5
6     Copyright (C) 2014 Tom Gundersen
7     Copyright (C) 2014 Susant Sahani
8
9     systemd is free software; you can redistribute it and/or modify it
10     under the terms of the GNU Lesser General Public License as published by
11     the Free Software Foundation; either version 2.1 of the License, or
12     (at your option) any later version.
13
14     systemd is distributed in the hope that it will be useful, but
15     WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17     Lesser General Public License for more details.
18
19     You should have received a copy of the GNU Lesser General Public License
20     along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <arpa/inet.h>
24
25 #include "siphash24.h"
26 #include "hashmap.h"
27 #include "event-util.h"
28
29 #include "lldp-tlv.h"
30 #include "lldp-port.h"
31 #include "sd-lldp.h"
32 #include "prioq.h"
33 #include "strv.h"
34 #include "lldp-internal.h"
35 #include "ether-addr-util.h"
36
37 typedef enum LLDPAgentRXState {
38         LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4,
39         LLDP_AGENT_RX_DELETE_AGED_INFO,
40         LLDP_AGENT_RX_LLDP_INITIALIZE,
41         LLDP_AGENT_RX_WAIT_FOR_FRAME,
42         LLDP_AGENT_RX_RX_FRAME,
43         LLDP_AGENT_RX_DELETE_INFO,
44         LLDP_AGENT_RX_UPDATE_INFO,
45         _LLDP_AGENT_RX_STATE_MAX,
46         _LLDP_AGENT_RX_INVALID = -1,
47 } LLDPAgentRXState;
48
49 /* Section 10.5.2.2 Reception counters */
50 struct lldp_agent_statitics {
51         uint64_t stats_ageouts_total;
52         uint64_t stats_frames_discarded_total;
53         uint64_t stats_frames_in_errors_total;
54         uint64_t stats_frames_in_total;
55         uint64_t stats_tlvs_discarded_total;
56         uint64_t stats_tlvs_unrecognized_total;
57 };
58
59 struct sd_lldp {
60         lldp_port *port;
61
62         Prioq *by_expiry;
63         Hashmap *neighbour_mib;
64
65         sd_lldp_cb_t cb;
66
67         void *userdata;
68
69         LLDPAgentRXState rx_state;
70         lldp_agent_statitics statitics;
71 };
72
73 static unsigned long chassis_id_hash_func(const void *p,
74                                           const uint8_t hash_key[HASH_KEY_SIZE]) {
75         uint64_t u;
76         const lldp_chassis_id *id = p;
77
78         assert(id);
79
80         siphash24((uint8_t *) &u, id->data, id->length, hash_key);
81
82         return (unsigned long) u;
83 }
84
85 static int chassis_id_compare_func(const void *_a, const void *_b) {
86         const lldp_chassis_id *a, *b;
87
88         a = _a;
89         b = _b;
90
91         assert(!a->length || a->data);
92         assert(!b->length || b->data);
93
94         if (a->type != b->type)
95                 return -1;
96
97         if (a->length != b->length)
98                 return a->length < b->length ? -1 : 1;
99
100         return memcmp(a->data, b->data, a->length);
101 }
102
103 static const struct hash_ops chassis_id_hash_ops = {
104         .hash = chassis_id_hash_func,
105         .compare = chassis_id_compare_func
106 };
107
108 static void lldp_mib_delete_objects(sd_lldp *lldp);
109 static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state);
110 static void lldp_run_state_machine(sd_lldp *ll);
111
112 static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
113         int r;
114
115         assert(lldp);
116         assert(tlv);
117
118         /* Remove expired packets */
119         if (prioq_size(lldp->by_expiry) > 0) {
120
121                 lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO);
122
123                 lldp_mib_delete_objects(lldp);
124         }
125
126         r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
127         if (r < 0)
128                 goto out;
129
130         lldp_set_state(lldp, LLDP_AGENT_RX_UPDATE_INFO);
131
132         log_lldp("Packet added. MIB size: %d , PQ size: %d",
133                  hashmap_size(lldp->neighbour_mib),
134                  prioq_size(lldp->by_expiry));
135
136         lldp->statitics.stats_frames_in_total ++;
137
138         return 0;
139
140  out:
141         if (r < 0)
142                 log_lldp("Receive frame failed: %s", strerror(-r));
143
144         lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
145
146         return 0;
147 }
148
149 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
150 int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
151         uint16_t type, len, i, l, t;
152         bool chassis_id = false;
153         bool malformed = false;
154         bool port_id = false;
155         bool ttl = false;
156         bool end = false;
157         lldp_port *port;
158         uint8_t *p, *q;
159         sd_lldp *lldp;
160         int r;
161
162         assert(tlv);
163         assert(length > 0);
164
165         port = (lldp_port *) tlv->userdata;
166         lldp = (sd_lldp *) port->userdata;
167
168         if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
169                 log_lldp("Port is disabled : %s . Dropping ...",
170                          lldp->port->ifname);
171                 goto out;
172         }
173
174         lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
175
176         p = tlv->pdu;
177         p += sizeof(struct ether_header);
178
179         for (i = 1, l = 0; l <= length; i++) {
180
181                 memcpy(&t, p, sizeof(uint16_t));
182
183                 type = ntohs(t) >> 9;
184                 len = ntohs(t) & 0x01ff;
185
186                 if (type == LLDP_TYPE_END) {
187                         if (len != 0) {
188                                 log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
189                                          len);
190
191                                 malformed = true;
192                                 goto out;
193                         }
194
195                         end = true;
196
197                         break;
198                 } else if (type >=_LLDP_TYPE_MAX) {
199                         log_lldp("TLV type not recognized %d . Dropping ...",
200                                  type);
201
202                         malformed = true;
203                         goto out;
204                 }
205
206                 /* skip type and lengh encoding */
207                 p += 2;
208                 q = p;
209
210                 p += len;
211                 l += (len + 2);
212
213                 if (i <= 3) {
214                         if (i != type) {
215                                 log_lldp("TLV missing or out of order. Dropping ...");
216
217                                 malformed = true;
218                                 goto out;
219                         }
220                 }
221
222                 switch(type) {
223                 case LLDP_TYPE_CHASSIS_ID:
224
225                         if (len < 2) {
226                                 log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
227                                          len);
228
229                                 malformed = true;
230                                 goto out;
231                         }
232
233                         if (chassis_id) {
234                                 log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
235
236                                 malformed = true;
237                                 goto out;
238                         }
239
240                         /* Look what subtype it has */
241                         if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
242                             *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
243                                 log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
244                                          *q);
245
246                                 malformed = true;
247                                 goto out;
248
249                         }
250
251                         chassis_id = true;
252
253                         break;
254                 case LLDP_TYPE_PORT_ID:
255
256                         if (len < 2) {
257                                 log_lldp("Received malformed Port ID TLV len = %d. Dropping",
258                                          len);
259
260                                 malformed = true;
261                                 goto out;
262                         }
263
264                         if (port_id) {
265                                 log_lldp("Duplicate Port ID TLV found. Dropping ...");
266
267                                 malformed = true;
268                                 goto out;
269                         }
270
271                         /* Look what subtype it has */
272                         if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
273                             *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
274                                 log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
275                                          *q);
276
277                                 malformed = true;
278                                 goto out;
279
280                         }
281
282                         port_id = true;
283
284                         break;
285                 case LLDP_TYPE_TTL:
286
287                         if(len != 2) {
288                                 log_lldp(
289                                          "Received invalid lenth: %d TTL TLV. Dropping ...",
290                                          len);
291
292                                 malformed = true;
293                                 goto out;
294                         }
295
296                         if (ttl) {
297                                 log_lldp("Duplicate TTL TLV found. Dropping ...");
298
299                                 malformed = true;
300                                 goto out;
301                         }
302
303                         ttl = true;
304
305                         break;
306                 default:
307
308                         if (len == 0) {
309                                 log_lldp("TLV type = %d's, length 0 received . Dropping ...",
310                                          type);
311
312                                 malformed = true;
313                                 goto out;
314                         }
315                         break;
316                 }
317         }
318
319         if(!chassis_id || !port_id || !ttl || !end) {
320                 log_lldp( "One or more mandotory TLV missing . Dropping ...");
321
322                 malformed = true;
323                 goto out;
324
325         }
326
327         r = tlv_packet_parse_pdu(tlv, length);
328         if (r < 0) {
329                 log_lldp( "Failed to parse the TLV. Dropping ...");
330
331                 malformed = true;
332                 goto out;
333         }
334
335         return lldp_receive_frame(lldp, tlv);
336
337  out:
338         lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
339
340         if (malformed) {
341                 lldp->statitics.stats_frames_discarded_total ++;
342                 lldp->statitics.stats_frames_in_errors_total ++;
343         }
344
345         tlv_packet_free(tlv);
346
347         return 0;
348 }
349
350 static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
351         const lldp_neighbour_port *p = a, *q = b;
352
353         if (p->until < q->until)
354                 return -1;
355
356         if (p->until > q->until)
357                 return 1;
358
359         return 0;
360 }
361
362 static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
363
364         assert(lldp);
365         assert(state < _LLDP_AGENT_RX_STATE_MAX);
366
367         lldp->rx_state = state;
368
369         lldp_run_state_machine(lldp);
370 }
371
372 static void lldp_run_state_machine(sd_lldp *lldp) {
373
374         if (lldp->rx_state == LLDP_AGENT_RX_UPDATE_INFO)
375                 if (lldp->cb)
376                         lldp->cb(lldp, LLDP_AGENT_RX_UPDATE_INFO, lldp->userdata);
377 }
378
379 /* 10.5.5.2.1 mibDeleteObjects ()
380  * The mibDeleteObjects () procedure deletes all information in the LLDP remote
381  * systems MIB associated with the MSAP identifier if an LLDPDU is received with
382  * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
383
384 static void lldp_mib_delete_objects(sd_lldp *lldp) {
385         lldp_neighbour_port *p;
386         usec_t t = 0;
387
388         /* Remove all entries that are past their TTL */
389         for (;;) {
390
391                 if (prioq_size(lldp->by_expiry) <= 0)
392                         break;
393
394                 p = prioq_peek(lldp->by_expiry);
395                 if (!p)
396                         break;
397
398                 if (t <= 0)
399                         t = now(CLOCK_BOOTTIME);
400
401                 if (p->until > t)
402                         break;
403
404                 lldp_neighbour_port_remove_and_free(p);
405
406                 lldp->statitics.stats_ageouts_total ++;
407         }
408 }
409
410 static void lldp_mib_objects_flush(sd_lldp *lldp) {
411         lldp_neighbour_port *p, *q;
412         lldp_chassis *c;
413
414         assert(lldp);
415         assert(lldp->neighbour_mib);
416         assert(lldp->by_expiry);
417
418         /* Drop all packets */
419         while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
420
421                 LIST_FOREACH_SAFE(port, p, q, c->ports) {
422                         lldp_neighbour_port_remove_and_free(p);
423                 }
424         }
425
426         assert(hashmap_size(lldp->neighbour_mib) == 0);
427         assert(prioq_size(lldp->by_expiry) == 0);
428 }
429
430 int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
431         _cleanup_free_ char *s = NULL, *t = NULL, *k = NULL;
432         _cleanup_free_ char *temp_path = NULL;
433         _cleanup_fclose_ FILE *f = NULL;
434         uint8_t *mac, *port_id, type;
435         lldp_neighbour_port *p;
436         uint16_t data = 0, length = 0;
437         char buf[LINE_MAX];
438         lldp_chassis *c;
439         usec_t time;
440         Iterator i;
441         int r;
442
443         assert(lldp);
444         assert(lldp_file);
445
446         r = fopen_temporary(lldp_file, &f, &temp_path);
447         if (r < 0)
448                 goto finish;
449
450         fchmod(fileno(f), 0644);
451
452         HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
453                 LIST_FOREACH(port, p, c->ports) {
454
455                         r = lldp_read_chassis_id(p->packet, &type, &length, &mac);
456                         if (r < 0)
457                                 continue;
458
459                         memzero(buf, LINE_MAX);
460
461                         sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
462                                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
463
464                         s = strdup(buf);
465                         if (!s)
466                                 return -ENOMEM;
467
468                         r = lldp_read_port_id(p->packet, &type, &length, &port_id);
469                         if (r < 0) {
470                                 free(s);
471                                 continue;
472                         }
473
474                         memzero(buf, LINE_MAX);
475                         if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
476
477                                 k = strndup((char *) port_id, length -1);
478                                 if (!k)
479                                         return -ENOMEM;
480
481                                 sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
482
483                                 t = strappend(s, buf);
484
485                                 free(k);
486                         } else {
487
488                                 mac = port_id;
489
490                                 sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
491                                         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
492
493                                 t = strappend(s, buf);
494                         }
495
496                         if (!t)
497                                 return -ENOMEM;
498
499                         free(s);
500                         s = t;
501
502                         time = now(CLOCK_BOOTTIME);
503
504                         /* Don't write expired packets */
505                         if(time - p->until <= 0) {
506                                 free(s);
507                                 continue;
508                         }
509
510                         memzero(buf, LINE_MAX);
511                         sprintf(buf, "'_TTL=%lu' ", p->until);
512
513                         t = strappend(s, buf);
514                         if (!t)
515                                 return -ENOMEM;
516
517                         free(s);
518                         s = t;
519
520                         r = lldp_read_system_name(p->packet, &length, &k);
521                         if (r < 0)
522                                 k = strappend(s, "'_NAME=N/A' ");
523                         else {
524                                 t = strndup(k, length);
525                                 if (!t)
526                                         return -ENOMEM;
527
528                                 k = strjoin(s, "'_NAME=", t, "' ", NULL);
529                         }
530
531                         if (!k)
532                                 return -ENOMEM;
533
534                         free(s);
535                         s = k;
536
537                         memzero(buf, LINE_MAX);
538
539                         (void)lldp_read_system_capability(p->packet, &data);
540
541                         sprintf(buf, "'_CAP=%x'", data);
542
543                         t = strappend(s, buf);
544                         if (!t)
545                                 return -ENOMEM;
546
547                         fprintf(f, "%s\n", t);
548
549                         free(s);
550                         free(t);
551                 }
552         }
553         r = 0;
554
555         s = NULL;
556         t = NULL;
557         k = NULL;
558
559         fflush(f);
560
561         if (ferror(f) || rename(temp_path, lldp_file) < 0) {
562                 r = -errno;
563                 unlink(lldp_file);
564                 unlink(temp_path);
565         }
566
567  finish:
568         if (r < 0)
569                 log_error("Failed to save lldp data %s: %s", lldp_file, strerror(-r));
570
571         return r;
572 }
573
574 int sd_lldp_start(sd_lldp *lldp) {
575         int r;
576
577         assert_return(lldp, -EINVAL);
578         assert_return(lldp->port, -EINVAL);
579
580         lldp->port->status = LLDP_PORT_STATUS_ENABLED;
581
582         lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE);
583
584         r = lldp_port_start(lldp->port);
585         if (r < 0) {
586                 log_lldp("Failed to start Port : %s , %s",
587                          lldp->port->ifname,
588                          strerror(-r));
589
590                 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL);
591
592                 return r;
593         }
594
595         lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
596
597         return 0;
598 }
599
600 int sd_lldp_stop(sd_lldp *lldp) {
601         int r;
602
603         assert_return(lldp, -EINVAL);
604         assert_return(lldp->port, -EINVAL);
605
606         lldp->port->status = LLDP_PORT_STATUS_DISABLED;
607
608         r = lldp_port_stop(lldp->port);
609         if (r < 0)
610                 return r;
611
612         lldp_mib_objects_flush(lldp);
613
614         return 0;
615 }
616
617 int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
618         int r;
619
620         assert_return(lldp, -EINVAL);
621         assert_return(!lldp->port->event, -EBUSY);
622
623         if (event)
624                 lldp->port->event = sd_event_ref(event);
625         else {
626                 r = sd_event_default(&lldp->port->event);
627                 if (r < 0)
628                         return r;
629         }
630
631         lldp->port->event_priority = priority;
632
633         return 0;
634 }
635
636 int sd_lldp_detach_event(sd_lldp *lldp) {
637
638         assert_return(lldp, -EINVAL);
639
640         lldp->port->event = sd_event_unref(lldp->port->event);
641
642         return 0;
643 }
644
645 int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) {
646         assert_return(lldp, -EINVAL);
647
648         lldp->cb = cb;
649         lldp->userdata = userdata;
650
651         return 0;
652 }
653
654 void sd_lldp_free(sd_lldp *lldp) {
655
656         if (!lldp)
657                 return;
658
659         /* Drop all packets */
660         lldp_mib_objects_flush(lldp);
661
662         lldp_port_free(lldp->port);
663
664         hashmap_free(lldp->neighbour_mib);
665         prioq_free(lldp->by_expiry);
666
667         free(lldp);
668 }
669
670 int sd_lldp_new(int ifindex,
671                 char *ifname,
672                 struct ether_addr *mac,
673                 sd_lldp **ret) {
674         _cleanup_sd_lldp_free_ sd_lldp *lldp = NULL;
675         int r;
676
677         assert_return(ret, -EINVAL);
678         assert_return(ifindex > 0, -EINVAL);
679         assert_return(ifname, -EINVAL);
680         assert_return(mac, -EINVAL);
681
682         lldp = new0(sd_lldp, 1);
683         if (!lldp)
684                 return -ENOMEM;
685
686         r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
687         if (r < 0)
688                 return r;
689
690         lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
691         if (!lldp->neighbour_mib)
692                 return -ENOMEM;
693
694         r = prioq_ensure_allocated(&lldp->by_expiry,
695                                    ttl_expiry_item_prioq_compare_func);
696         if (r < 0)
697                 return r;
698
699         lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL;
700
701         *ret = lldp;
702         lldp = NULL;
703
704         return 0;
705 }