chiark / gitweb /
networkd: Introduce Link Layer Discovery Protocol (LLDP)
[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 "lldp-internal.h"
34
35 /* Section 10.5.2.2 Reception counters */
36 struct lldp_agent_statitics {
37         uint64_t stats_ageouts_total;
38         uint64_t stats_frames_discarded_total;
39         uint64_t stats_frames_in_errors_total;
40         uint64_t stats_frames_in_total;
41         uint64_t stats_tlvs_discarded_total;
42         uint64_t stats_tlvs_unrecognized_total;
43 };
44
45 struct sd_lldp {
46         lldp_port *port;
47
48         Prioq *by_expiry;
49         Hashmap *neighbour_mib;
50
51         lldp_agent_statitics statitics;
52 };
53
54 static unsigned long chassis_id_hash_func(const void *p,
55                                           const uint8_t hash_key[HASH_KEY_SIZE]) {
56         uint64_t u;
57         const lldp_chassis_id *id = p;
58
59         assert(id);
60
61         siphash24((uint8_t *) &u, id->data, id->length, hash_key);
62
63         return (unsigned long) u;
64 }
65
66 static int chassis_id_compare_func(const void *_a, const void *_b) {
67         const lldp_chassis_id *a, *b;
68
69         a = _a;
70         b = _b;
71
72         assert(!a->length || a->data);
73         assert(!b->length || b->data);
74
75         if (a->type != b->type)
76                 return -1;
77
78         if (a->length != b->length)
79                 return a->length < b->length ? -1 : 1;
80
81         return memcmp(a->data, b->data, a->length);
82 }
83
84 static const struct hash_ops chassis_id_hash_ops = {
85         .hash = chassis_id_hash_func,
86         .compare = chassis_id_compare_func
87 };
88
89 static void lldp_mib_delete_objects(sd_lldp *lldp);
90
91 static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
92         int r;
93
94         assert(lldp);
95         assert(tlv);
96
97         /* Remove expired packets */
98         if (prioq_size(lldp->by_expiry) > 0)
99                 lldp_mib_delete_objects(lldp);
100
101         r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
102         if (r < 0)
103                 goto out;
104
105         log_lldp("Packet added. MIB size: %d , PQ size: %d",
106                  hashmap_size(lldp->neighbour_mib),
107                  prioq_size(lldp->by_expiry));
108
109         lldp->statitics.stats_frames_in_total ++;
110
111         return 0;
112
113  out:
114         if (r < 0)
115                 log_lldp("Receive frame failed: %s", strerror(-r));
116
117         return 0;
118 }
119
120 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
121 int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
122         uint16_t type, len, i, l, t;
123         bool chassis_id = false;
124         bool malformed = false;
125         bool port_id = false;
126         bool ttl = false;
127         bool end = false;
128         lldp_port *port;
129         uint8_t *p, *q;
130         sd_lldp *lldp;
131         int r;
132
133         assert(tlv);
134         assert(length > 0);
135
136         port = (lldp_port *) tlv->userdata;
137         lldp = (sd_lldp *) port->userdata;
138
139         if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
140                 log_lldp("Port is disabled : %s . Dropping ...",
141                          lldp->port->ifname);
142                 goto out;
143         }
144
145         p = tlv->pdu;
146         p += sizeof(struct ether_header);
147
148         for (i = 1, l = 0; l <= length; i++) {
149
150                 memcpy(&t, p, sizeof(uint16_t));
151
152                 type = ntohs(t) >> 9;
153                 len = ntohs(t) & 0x01ff;
154
155                 if (type == LLDP_TYPE_END) {
156                         if (len != 0) {
157                                 log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
158                                          len);
159
160                                 malformed = true;
161                                 goto out;
162                         }
163
164                         end = true;
165
166                         break;
167                 } else if (type >=_LLDP_TYPE_MAX) {
168                         log_lldp("TLV type not recognized %d . Dropping ...",
169                                  type);
170
171                         malformed = true;
172                         goto out;
173                 }
174
175                 /* skip type and lengh encoding */
176                 p += 2;
177                 q = p;
178
179                 p += len;
180                 l += (len + 2);
181
182                 if (i <= 3) {
183                         if (i != type) {
184                                 log_lldp("TLV missing or out of order. Dropping ...");
185
186                                 malformed = true;
187                                 goto out;
188                         }
189                 }
190
191                 switch(type) {
192                 case LLDP_TYPE_CHASSIS_ID:
193
194                         if (len < 2) {
195                                 log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
196                                          len);
197
198                                 malformed = true;
199                                 goto out;
200                         }
201
202                         if (chassis_id) {
203                                 log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
204
205                                 malformed = true;
206                                 goto out;
207                         }
208
209                         /* Look what subtype it has */
210                         if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
211                             *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
212                                 log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
213                                          *q);
214
215                                 malformed = true;
216                                 goto out;
217
218                         }
219
220                         chassis_id = true;
221
222                         break;
223                 case LLDP_TYPE_PORT_ID:
224
225                         if (len < 2) {
226                                 log_lldp("Received malformed Port ID TLV len = %d. Dropping",
227                                          len);
228
229                                 malformed = true;
230                                 goto out;
231                         }
232
233                         if (port_id) {
234                                 log_lldp("Duplicate Port ID TLV found. Dropping ...");
235
236                                 malformed = true;
237                                 goto out;
238                         }
239
240                         /* Look what subtype it has */
241                         if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
242                             *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
243                                 log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
244                                          *q);
245
246                                 malformed = true;
247                                 goto out;
248
249                         }
250
251                         port_id = true;
252
253                         break;
254                 case LLDP_TYPE_TTL:
255
256                         if(len != 2) {
257                                 log_lldp(
258                                          "Received invalid lenth: %d TTL TLV. Dropping ...",
259                                          len);
260
261                                 malformed = true;
262                                 goto out;
263                         }
264
265                         if (ttl) {
266                                 log_lldp("Duplicate TTL TLV found. Dropping ...");
267
268                                 malformed = true;
269                                 goto out;
270                         }
271
272                         ttl = true;
273
274                         break;
275                 default:
276
277                         if (len == 0) {
278                                 log_lldp("TLV type = %d's, length 0 received . Dropping ...",
279                                          type);
280
281                                 malformed = true;
282                                 goto out;
283                         }
284                         break;
285                 }
286         }
287
288         if(!chassis_id || !port_id || !ttl || !end) {
289                 log_lldp( "One or more mandotory TLV missing . Dropping ...");
290
291                 malformed = true;
292                 goto out;
293
294         }
295
296         r = tlv_packet_parse_pdu(tlv, length);
297         if (r < 0) {
298                 log_lldp( "Failed to parse the TLV. Dropping ...");
299
300                 malformed = true;
301                 goto out;
302         }
303
304         return lldp_receive_frame(lldp, tlv);
305
306  out:
307         if (malformed) {
308                 lldp->statitics.stats_frames_discarded_total ++;
309                 lldp->statitics.stats_frames_in_errors_total ++;
310         }
311
312         tlv_packet_free(tlv);
313
314         return 0;
315 }
316
317 static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
318         const lldp_neighbour_port *p = a, *q = b;
319
320         if (p->until < q->until)
321                 return -1;
322
323         if (p->until > q->until)
324                 return 1;
325
326         return 0;
327 }
328
329 /* 10.5.5.2.1 mibDeleteObjects ()
330  * The mibDeleteObjects () procedure deletes all information in the LLDP remote
331  * systems MIB associated with the MSAP identifier if an LLDPDU is received with
332  * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
333
334 static void lldp_mib_delete_objects(sd_lldp *lldp) {
335         lldp_neighbour_port *p;
336         usec_t t = 0;
337
338         /* Remove all entries that are past their TTL */
339         for (;;) {
340
341                 if (prioq_size(lldp->by_expiry) <= 0)
342                         break;
343
344                 p = prioq_peek(lldp->by_expiry);
345                 if (!p)
346                         break;
347
348                 if (t <= 0)
349                         t = now(CLOCK_BOOTTIME);
350
351                 if (p->until > t)
352                         break;
353
354                 lldp_neighbour_port_remove_and_free(p);
355
356                 lldp->statitics.stats_ageouts_total ++;
357         }
358 }
359
360 static void lldp_mib_objects_flush(sd_lldp *lldp) {
361         lldp_neighbour_port *p, *q;
362         lldp_chassis *c;
363
364         assert(lldp);
365         assert(lldp->neighbour_mib);
366         assert(lldp->by_expiry);
367
368         /* Drop all packets */
369         while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
370
371                 LIST_FOREACH_SAFE(port, p, q, c->ports) {
372                         lldp_neighbour_port_remove_and_free(p);
373                 }
374         }
375
376         assert(hashmap_size(lldp->neighbour_mib) == 0);
377         assert(prioq_size(lldp->by_expiry) == 0);
378 }
379
380 int sd_lldp_start(sd_lldp *lldp) {
381         int r;
382
383         assert_return(lldp, -EINVAL);
384         assert_return(lldp->port, -EINVAL);
385
386         lldp->port->status = LLDP_PORT_STATUS_ENABLED;
387
388         r = lldp_port_start(lldp->port);
389         if (r < 0) {
390                 log_lldp("Failed to start Port : %s , %s",
391                          lldp->port->ifname,
392                          strerror(-r));
393                 return r;
394         }
395
396         return 0;
397 }
398
399 int sd_lldp_stop(sd_lldp *lldp) {
400         int r;
401
402         assert_return(lldp, -EINVAL);
403         assert_return(lldp->port, -EINVAL);
404
405         lldp->port->status = LLDP_PORT_STATUS_DISABLED;
406
407         r = lldp_port_stop(lldp->port);
408         if (r < 0)
409                 return r;
410
411         lldp_mib_objects_flush(lldp);
412
413         return 0;
414 }
415
416 int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
417         int r;
418
419         assert_return(lldp, -EINVAL);
420         assert_return(!lldp->port->event, -EBUSY);
421
422         if (event)
423                 lldp->port->event = sd_event_ref(event);
424         else {
425                 r = sd_event_default(&lldp->port->event);
426                 if (r < 0)
427                         return r;
428         }
429
430         lldp->port->event_priority = priority;
431
432         return 0;
433 }
434
435 int sd_lldp_detach_event(sd_lldp *lldp) {
436
437         assert_return(lldp, -EINVAL);
438
439         lldp->port->event = sd_event_unref(lldp->port->event);
440
441         return 0;
442 }
443
444 void sd_lldp_free(sd_lldp *lldp) {
445
446         if (!lldp)
447                 return;
448
449         /* Drop all packets */
450         lldp_mib_objects_flush(lldp);
451
452         lldp_port_free(lldp->port);
453
454         hashmap_free(lldp->neighbour_mib);
455         prioq_free(lldp->by_expiry);
456
457         free(lldp);
458 }
459
460 int sd_lldp_new(int ifindex,
461                 char *ifname,
462                 struct ether_addr *mac,
463                 sd_lldp **ret) {
464         _cleanup_sd_lldp_free_ sd_lldp *lldp = NULL;
465         int r;
466
467         assert_return(ret, -EINVAL);
468         assert_return(ifindex > 0, -EINVAL);
469         assert_return(ifname, -EINVAL);
470         assert_return(mac, -EINVAL);
471
472         lldp = new0(sd_lldp, 1);
473         if (!lldp)
474                 return -ENOMEM;
475
476         r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
477         if (r < 0)
478                 return r;
479
480         lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
481         if (!lldp->neighbour_mib)
482                 return -ENOMEM;
483
484         r = prioq_ensure_allocated(&lldp->by_expiry,
485                                    ttl_expiry_item_prioq_compare_func);
486         if (r < 0)
487                 return r;
488
489         *ret = lldp;
490         lldp = NULL;
491
492         return 0;
493 }