chiark / gitweb /
7085a02491c08372fe13411815e6999e83cf89a8
[elogind.git] / src / libsystemd-network / lldp-internal.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 "lldp-internal.h"
24
25 /* We store maximum 1K chassis entries */
26 #define LLDP_MIB_MAX_CHASSIS 1024
27
28 /* Maximum Ports can be attached to any chassis */
29 #define LLDP_MIB_MAX_PORT_PER_CHASSIS 32
30
31 int lldp_read_chassis_id(tlv_packet *tlv,
32                          uint8_t *type,
33                          uint16_t *length,
34                          uint8_t **data) {
35         uint8_t subtype;
36         int r;
37
38         assert_return(tlv, -EINVAL);
39
40         r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID);
41         if (r < 0)
42                 goto out2;
43
44         r = tlv_packet_read_u8(tlv, &subtype);
45         if (r < 0)
46                 goto out1;
47
48         switch (subtype) {
49         case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
50
51                 r = tlv_packet_read_bytes(tlv, data, length);
52                 if (r < 0)
53                         goto out1;
54
55                 break;
56         default:
57                 r = -ENOTSUP;
58                 break;
59         }
60
61         *type = subtype;
62
63  out1:
64         (void)lldp_tlv_packet_exit_container(tlv);
65
66  out2:
67         return r;
68 }
69
70 int lldp_read_port_id(tlv_packet *tlv,
71                       uint8_t *type,
72                       uint16_t *length,
73                       uint8_t **data) {
74         uint8_t subtype;
75         char *s;
76         int r;
77
78         assert_return(tlv, -EINVAL);
79
80         r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID);
81         if (r < 0)
82                 goto out2;
83
84         r = tlv_packet_read_u8(tlv, &subtype);
85         if (r < 0)
86                 goto out1;
87
88         switch (subtype) {
89         case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
90
91                 r = tlv_packet_read_string(tlv, &s, length);
92                 if (r < 0)
93                         goto out1;
94
95                 *data = (uint8_t *) s;
96
97                 break;
98         case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
99
100                 r = tlv_packet_read_bytes(tlv, data, length);
101                 if (r < 0)
102                         goto out1;
103
104                 break;
105         default:
106                 r = -ENOTSUP;
107                 break;
108         }
109
110         *type = subtype;
111
112  out1:
113         (void)lldp_tlv_packet_exit_container(tlv);
114
115  out2:
116         return r;
117 }
118
119 int lldp_read_ttl(tlv_packet *tlv, uint16_t *ttl) {
120         int r;
121
122         assert_return(tlv, -EINVAL);
123
124         r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_TTL);
125         if (r < 0)
126                 goto out;
127
128         r = tlv_packet_read_u16(tlv, ttl);
129
130         (void) lldp_tlv_packet_exit_container(tlv);
131
132  out:
133         return r;
134 }
135
136 /* 10.5.5.2.2 mibUpdateObjects ()
137  * The mibUpdateObjects () procedure updates the MIB objects corresponding to
138  * the TLVs contained in the received LLDPDU for the LLDP remote system
139  * indicated by the LLDP remote systems update process defined in 10.3.5 */
140
141 int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv) {
142         lldp_neighbour_port *p;
143         uint16_t length, ttl;
144         uint8_t *data;
145         uint8_t type;
146         int r;
147
148         assert_return(c, -EINVAL);
149         assert_return(tlv, -EINVAL);
150
151         r = lldp_read_port_id(tlv, &type, &length, &data);
152         if (r < 0)
153                 return r;
154
155         /* Update the packet if we already have */
156         LIST_FOREACH(port, p, c->ports) {
157
158                 if ((p->type == type && p->length == length && !memcmp(p->data, data, p->length))) {
159
160                         r = lldp_read_ttl(tlv, &ttl);
161                         if (r < 0)
162                                 return r;
163
164                         p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
165
166                         tlv_packet_free(p->packet);
167                         p->packet = tlv;
168
169                         prioq_reshuffle(p->c->by_expiry, p, &p->prioq_idx);
170
171                         return 0;
172                 }
173         }
174
175         return -1;
176 }
177
178 int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv) {
179         lldp_neighbour_port *p, *q;
180         uint8_t *data;
181         uint16_t length;
182         uint8_t type;
183         int r;
184
185         assert_return(c, -EINVAL);
186         assert_return(tlv, -EINVAL);
187
188         r = lldp_read_port_id(tlv, &type, &length, &data);
189         if (r < 0)
190                 return r;
191
192         LIST_FOREACH_SAFE(port, p, q, c->ports) {
193
194                 /* Find the port */
195                 if (p->type == type && p->length == length && !memcmp(p->data, data, p->length)) {
196                         lldp_neighbour_port_remove_and_free(p);
197                         break;
198                 }
199         }
200
201         return 0;
202 }
203
204 int lldp_mib_add_objects(Prioq *by_expiry,
205                          Hashmap *neighbour_mib,
206                          tlv_packet *tlv) {
207         _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL;
208         _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL;
209         lldp_chassis_id chassis_id;
210         bool new_chassis = false;
211         uint8_t subtype, *data;
212         uint16_t ttl, length;
213         int r;
214
215         assert_return(by_expiry, -EINVAL);
216         assert_return(neighbour_mib, -EINVAL);
217         assert_return(tlv, -EINVAL);
218
219         r = lldp_read_chassis_id(tlv, &subtype, &length, &data);
220         if (r < 0)
221                 goto drop;
222
223         r = lldp_read_ttl(tlv, &ttl);
224         if (r < 0)
225                 goto drop;
226
227         /* Make hash key */
228         chassis_id.type = subtype;
229         chassis_id.length = length;
230         chassis_id.data = data;
231
232         /* Try to find the Chassis */
233         c = hashmap_get(neighbour_mib, &chassis_id);
234         if (!c) {
235
236                 /* Don't create chassis if ttl 0 is received . Silently drop it */
237                 if (ttl == 0) {
238                         log_lldp("TTL value 0 received. Skiping Chassis creation.");
239                         goto drop;
240                 }
241
242                 /* Admission Control: Can we store this packet ? */
243                 if (hashmap_size(neighbour_mib) >= LLDP_MIB_MAX_CHASSIS) {
244
245                         log_lldp("Exceeding number of chassie: %d. Dropping ...",
246                                  hashmap_size(neighbour_mib));
247                         goto drop;
248                 }
249
250                 r = lldp_chassis_new(tlv, by_expiry, neighbour_mib, &c);
251                 if (r < 0)
252                         goto drop;
253
254                 new_chassis = true;
255
256                 r = hashmap_put(neighbour_mib, &c->chassis_id, c);
257                 if (r < 0)
258                         goto drop;
259
260         } else {
261
262                 /* When the TTL field is set to zero, the receiving LLDP agent is notified all
263                  * system information associated with the LLDP agent/port is to be deleted */
264                 if (ttl == 0) {
265                         log_lldp("TTL value 0 received . Deleting associated Port ...");
266
267                         lldp_mib_remove_objects(c, tlv);
268
269                         c = NULL;
270                         goto drop;
271                 }
272
273                 /* if we already have this port just update it */
274                 r = lldp_mib_update_objects(c, tlv);
275                 if (r >= 0) {
276                         c = NULL;
277                         return r;
278                 }
279
280                 /* Admission Control: Can this port attached to the existing chassis ? */
281                 if (REFCNT_GET(c->n_ref) >= LLDP_MIB_MAX_PORT_PER_CHASSIS) {
282                         log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...",
283                                  REFCNT_GET(c->n_ref));
284
285                         c = NULL;
286                         goto drop;
287                 }
288         }
289
290         /* This is a new port */
291         r = lldp_neighbour_port_new(c, tlv, &p);
292         if (r < 0)
293                 goto drop;
294
295         r = prioq_put(c->by_expiry, p, &p->prioq_idx);
296         if (r < 0)
297                 goto drop;
298
299         /* Attach new port to chassis */
300         LIST_PREPEND(port, c->ports, p);
301         REFCNT_INC(c->n_ref);
302
303         p = NULL;
304         c = NULL;
305
306         return 0;
307
308  drop:
309         tlv_packet_free(tlv);
310
311         if (new_chassis)
312                 hashmap_remove(neighbour_mib, &c->chassis_id);
313
314         return r;
315 }
316
317 void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p) {
318         lldp_chassis *c;
319
320         assert(p);
321         assert(p->c);
322
323         c = p->c;
324
325         prioq_remove(c->by_expiry, p, &p->prioq_idx);
326
327         LIST_REMOVE(port, c->ports, p);
328         lldp_neighbour_port_free(p);
329
330         /* Drop the Chassis if no port is attached  */
331         if (REFCNT_DEC(c->n_ref) <= 1) {
332                 hashmap_remove(c->neighbour_mib, &c->chassis_id);
333                 lldp_chassis_free(c);
334         }
335 }
336
337 void lldp_neighbour_port_free(lldp_neighbour_port *p) {
338
339         if(!p)
340                 return;
341
342         tlv_packet_free(p->packet);
343
344         free(p->data);
345         free(p);
346 }
347
348 int lldp_neighbour_port_new(lldp_chassis *c,
349                             tlv_packet *tlv,
350                             lldp_neighbour_port **ret) {
351         _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p;
352         uint16_t length, ttl;
353         uint8_t *data;
354         uint8_t type;
355         int r;
356
357         assert(tlv);
358
359         r = lldp_read_port_id(tlv, &type, &length, &data);
360         if (r < 0)
361                 return r;
362
363         r = lldp_read_ttl(tlv, &ttl);
364         if (r < 0)
365                 return r;
366
367         p = new0(lldp_neighbour_port, 1);
368         if (!p)
369                 return -ENOMEM;
370
371         p->c = c;
372         p->type = type;
373         p->length = length;
374         p->packet = tlv;
375         p->prioq_idx = PRIOQ_IDX_NULL;
376         p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
377
378         p->data = memdup(data, length);
379         if (!p->data)
380                 return -ENOMEM;
381
382         *ret = p;
383         p = NULL;
384
385         return 0;
386 }
387
388 void lldp_chassis_free(lldp_chassis *c) {
389
390         if (!c)
391                 return;
392
393         if (REFCNT_GET(c->n_ref) > 1)
394                 return;
395
396         free(c->chassis_id.data);
397         free(c);
398 }
399
400 int lldp_chassis_new(tlv_packet *tlv,
401                      Prioq *by_expiry,
402                      Hashmap *neighbour_mib,
403                      lldp_chassis **ret) {
404         _cleanup_lldp_chassis_free_ lldp_chassis *c;
405         uint16_t length;
406         uint8_t *data;
407         uint8_t type;
408         int r;
409
410         assert(tlv);
411
412         r = lldp_read_chassis_id(tlv, &type, &length, &data);
413         if (r < 0)
414                 return r;
415
416         c = new0(lldp_chassis, 1);
417         if (!c)
418                 return -ENOMEM;
419
420         c->n_ref = REFCNT_INIT;
421         c->chassis_id.type = type;
422         c->chassis_id.length = length;
423
424         c->chassis_id.data = memdup(data, length);
425         if (!c->chassis_id.data)
426                 return -ENOMEM;
427
428         LIST_HEAD_INIT(c->ports);
429
430         c->by_expiry = by_expiry;
431         c->neighbour_mib = neighbour_mib;
432
433         *ret = c;
434         c = NULL;
435
436         return 0;
437 }