]>
Commit | Line | Data |
---|---|---|
ad1ad5c8 SS |
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 | ||
176c355b | 23 | #include "sd-lldp.h" |
ad1ad5c8 | 24 | |
07630cea LP |
25 | #include "lldp-internal.h" |
26 | ||
ad1ad5c8 SS |
27 | /* We store maximum 1K chassis entries */ |
28 | #define LLDP_MIB_MAX_CHASSIS 1024 | |
29 | ||
30 | /* Maximum Ports can be attached to any chassis */ | |
31 | #define LLDP_MIB_MAX_PORT_PER_CHASSIS 32 | |
32 | ||
ad1ad5c8 SS |
33 | /* 10.5.5.2.2 mibUpdateObjects () |
34 | * The mibUpdateObjects () procedure updates the MIB objects corresponding to | |
35 | * the TLVs contained in the received LLDPDU for the LLDP remote system | |
36 | * indicated by the LLDP remote systems update process defined in 10.3.5 */ | |
37 | ||
38 | int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv) { | |
39 | lldp_neighbour_port *p; | |
40 | uint16_t length, ttl; | |
41 | uint8_t *data; | |
42 | uint8_t type; | |
43 | int r; | |
44 | ||
45 | assert_return(c, -EINVAL); | |
46 | assert_return(tlv, -EINVAL); | |
47 | ||
176c355b | 48 | r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); |
ad1ad5c8 SS |
49 | if (r < 0) |
50 | return r; | |
51 | ||
52 | /* Update the packet if we already have */ | |
53 | LIST_FOREACH(port, p, c->ports) { | |
54 | ||
55 | if ((p->type == type && p->length == length && !memcmp(p->data, data, p->length))) { | |
56 | ||
176c355b | 57 | r = sd_lldp_packet_read_ttl(tlv, &ttl); |
ad1ad5c8 SS |
58 | if (r < 0) |
59 | return r; | |
60 | ||
61 | p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic()); | |
62 | ||
176c355b | 63 | sd_lldp_packet_unref(p->packet); |
ad1ad5c8 SS |
64 | p->packet = tlv; |
65 | ||
66 | prioq_reshuffle(p->c->by_expiry, p, &p->prioq_idx); | |
67 | ||
68 | return 0; | |
69 | } | |
70 | } | |
71 | ||
72 | return -1; | |
73 | } | |
74 | ||
75 | int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv) { | |
76 | lldp_neighbour_port *p, *q; | |
77 | uint8_t *data; | |
78 | uint16_t length; | |
79 | uint8_t type; | |
80 | int r; | |
81 | ||
82 | assert_return(c, -EINVAL); | |
83 | assert_return(tlv, -EINVAL); | |
84 | ||
176c355b | 85 | r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); |
ad1ad5c8 SS |
86 | if (r < 0) |
87 | return r; | |
88 | ||
89 | LIST_FOREACH_SAFE(port, p, q, c->ports) { | |
90 | ||
91 | /* Find the port */ | |
92 | if (p->type == type && p->length == length && !memcmp(p->data, data, p->length)) { | |
93 | lldp_neighbour_port_remove_and_free(p); | |
94 | break; | |
95 | } | |
96 | } | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | int lldp_mib_add_objects(Prioq *by_expiry, | |
102 | Hashmap *neighbour_mib, | |
103 | tlv_packet *tlv) { | |
104 | _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL; | |
105 | _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL; | |
106 | lldp_chassis_id chassis_id; | |
107 | bool new_chassis = false; | |
108 | uint8_t subtype, *data; | |
109 | uint16_t ttl, length; | |
110 | int r; | |
111 | ||
112 | assert_return(by_expiry, -EINVAL); | |
113 | assert_return(neighbour_mib, -EINVAL); | |
114 | assert_return(tlv, -EINVAL); | |
115 | ||
176c355b | 116 | r = sd_lldp_packet_read_chassis_id(tlv, &subtype, &data, &length); |
ad1ad5c8 SS |
117 | if (r < 0) |
118 | goto drop; | |
119 | ||
176c355b | 120 | r = sd_lldp_packet_read_ttl(tlv, &ttl); |
ad1ad5c8 SS |
121 | if (r < 0) |
122 | goto drop; | |
123 | ||
124 | /* Make hash key */ | |
125 | chassis_id.type = subtype; | |
126 | chassis_id.length = length; | |
127 | chassis_id.data = data; | |
128 | ||
129 | /* Try to find the Chassis */ | |
130 | c = hashmap_get(neighbour_mib, &chassis_id); | |
131 | if (!c) { | |
132 | ||
133 | /* Don't create chassis if ttl 0 is received . Silently drop it */ | |
134 | if (ttl == 0) { | |
135 | log_lldp("TTL value 0 received. Skiping Chassis creation."); | |
136 | goto drop; | |
137 | } | |
138 | ||
139 | /* Admission Control: Can we store this packet ? */ | |
140 | if (hashmap_size(neighbour_mib) >= LLDP_MIB_MAX_CHASSIS) { | |
141 | ||
142 | log_lldp("Exceeding number of chassie: %d. Dropping ...", | |
143 | hashmap_size(neighbour_mib)); | |
144 | goto drop; | |
145 | } | |
146 | ||
147 | r = lldp_chassis_new(tlv, by_expiry, neighbour_mib, &c); | |
148 | if (r < 0) | |
149 | goto drop; | |
150 | ||
151 | new_chassis = true; | |
152 | ||
153 | r = hashmap_put(neighbour_mib, &c->chassis_id, c); | |
154 | if (r < 0) | |
155 | goto drop; | |
156 | ||
157 | } else { | |
158 | ||
159 | /* When the TTL field is set to zero, the receiving LLDP agent is notified all | |
160 | * system information associated with the LLDP agent/port is to be deleted */ | |
161 | if (ttl == 0) { | |
162 | log_lldp("TTL value 0 received . Deleting associated Port ..."); | |
163 | ||
164 | lldp_mib_remove_objects(c, tlv); | |
165 | ||
166 | c = NULL; | |
167 | goto drop; | |
168 | } | |
169 | ||
170 | /* if we already have this port just update it */ | |
171 | r = lldp_mib_update_objects(c, tlv); | |
172 | if (r >= 0) { | |
173 | c = NULL; | |
174 | return r; | |
175 | } | |
176 | ||
177 | /* Admission Control: Can this port attached to the existing chassis ? */ | |
9c8e3101 LP |
178 | if (c->n_ref >= LLDP_MIB_MAX_PORT_PER_CHASSIS) { |
179 | log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...", c->n_ref); | |
ad1ad5c8 SS |
180 | |
181 | c = NULL; | |
182 | goto drop; | |
183 | } | |
184 | } | |
185 | ||
186 | /* This is a new port */ | |
187 | r = lldp_neighbour_port_new(c, tlv, &p); | |
188 | if (r < 0) | |
189 | goto drop; | |
190 | ||
191 | r = prioq_put(c->by_expiry, p, &p->prioq_idx); | |
192 | if (r < 0) | |
193 | goto drop; | |
194 | ||
195 | /* Attach new port to chassis */ | |
196 | LIST_PREPEND(port, c->ports, p); | |
9c8e3101 | 197 | c->n_ref ++; |
ad1ad5c8 SS |
198 | |
199 | p = NULL; | |
200 | c = NULL; | |
201 | ||
202 | return 0; | |
203 | ||
204 | drop: | |
176c355b | 205 | sd_lldp_packet_unref(tlv); |
ad1ad5c8 SS |
206 | |
207 | if (new_chassis) | |
208 | hashmap_remove(neighbour_mib, &c->chassis_id); | |
209 | ||
210 | return r; | |
211 | } | |
212 | ||
213 | void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p) { | |
214 | lldp_chassis *c; | |
215 | ||
216 | assert(p); | |
217 | assert(p->c); | |
218 | ||
219 | c = p->c; | |
220 | ||
221 | prioq_remove(c->by_expiry, p, &p->prioq_idx); | |
222 | ||
223 | LIST_REMOVE(port, c->ports, p); | |
224 | lldp_neighbour_port_free(p); | |
225 | ||
226 | /* Drop the Chassis if no port is attached */ | |
9c8e3101 LP |
227 | c->n_ref --; |
228 | if (c->n_ref <= 1) { | |
ad1ad5c8 SS |
229 | hashmap_remove(c->neighbour_mib, &c->chassis_id); |
230 | lldp_chassis_free(c); | |
231 | } | |
232 | } | |
233 | ||
234 | void lldp_neighbour_port_free(lldp_neighbour_port *p) { | |
235 | ||
236 | if(!p) | |
237 | return; | |
238 | ||
176c355b | 239 | sd_lldp_packet_unref(p->packet); |
ad1ad5c8 SS |
240 | |
241 | free(p->data); | |
242 | free(p); | |
243 | } | |
244 | ||
245 | int lldp_neighbour_port_new(lldp_chassis *c, | |
246 | tlv_packet *tlv, | |
247 | lldp_neighbour_port **ret) { | |
e7a2419a | 248 | _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL; |
ad1ad5c8 SS |
249 | uint16_t length, ttl; |
250 | uint8_t *data; | |
251 | uint8_t type; | |
252 | int r; | |
253 | ||
254 | assert(tlv); | |
255 | ||
176c355b | 256 | r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); |
ad1ad5c8 SS |
257 | if (r < 0) |
258 | return r; | |
259 | ||
176c355b | 260 | r = sd_lldp_packet_read_ttl(tlv, &ttl); |
ad1ad5c8 SS |
261 | if (r < 0) |
262 | return r; | |
263 | ||
264 | p = new0(lldp_neighbour_port, 1); | |
265 | if (!p) | |
266 | return -ENOMEM; | |
267 | ||
268 | p->c = c; | |
269 | p->type = type; | |
270 | p->length = length; | |
271 | p->packet = tlv; | |
272 | p->prioq_idx = PRIOQ_IDX_NULL; | |
273 | p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic()); | |
274 | ||
275 | p->data = memdup(data, length); | |
276 | if (!p->data) | |
277 | return -ENOMEM; | |
278 | ||
279 | *ret = p; | |
280 | p = NULL; | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | void lldp_chassis_free(lldp_chassis *c) { | |
286 | ||
287 | if (!c) | |
288 | return; | |
289 | ||
9c8e3101 | 290 | if (c->n_ref > 1) |
ad1ad5c8 SS |
291 | return; |
292 | ||
293 | free(c->chassis_id.data); | |
294 | free(c); | |
295 | } | |
296 | ||
297 | int lldp_chassis_new(tlv_packet *tlv, | |
298 | Prioq *by_expiry, | |
299 | Hashmap *neighbour_mib, | |
300 | lldp_chassis **ret) { | |
7d486654 | 301 | _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL; |
ad1ad5c8 SS |
302 | uint16_t length; |
303 | uint8_t *data; | |
304 | uint8_t type; | |
305 | int r; | |
306 | ||
307 | assert(tlv); | |
308 | ||
176c355b | 309 | r = sd_lldp_packet_read_chassis_id(tlv, &type, &data, &length); |
ad1ad5c8 SS |
310 | if (r < 0) |
311 | return r; | |
312 | ||
313 | c = new0(lldp_chassis, 1); | |
314 | if (!c) | |
315 | return -ENOMEM; | |
316 | ||
9c8e3101 | 317 | c->n_ref = 1; |
ad1ad5c8 SS |
318 | c->chassis_id.type = type; |
319 | c->chassis_id.length = length; | |
320 | ||
321 | c->chassis_id.data = memdup(data, length); | |
322 | if (!c->chassis_id.data) | |
323 | return -ENOMEM; | |
324 | ||
325 | LIST_HEAD_INIT(c->ports); | |
326 | ||
327 | c->by_expiry = by_expiry; | |
328 | c->neighbour_mib = neighbour_mib; | |
329 | ||
330 | *ret = c; | |
331 | c = NULL; | |
332 | ||
333 | return 0; | |
334 | } | |
0037c2dc BG |
335 | |
336 | int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
337 | _cleanup_lldp_packet_unref_ tlv_packet *packet = NULL; | |
338 | tlv_packet *p; | |
339 | uint16_t length; | |
340 | int r; | |
341 | ||
342 | assert(fd); | |
343 | assert(userdata); | |
344 | ||
345 | r = tlv_packet_new(&packet); | |
346 | if (r < 0) | |
347 | return r; | |
348 | ||
349 | length = read(fd, &packet->pdu, sizeof(packet->pdu)); | |
350 | ||
351 | /* Silently drop the packet */ | |
352 | if ((size_t) length > ETHER_MAX_LEN) | |
353 | return 0; | |
354 | ||
355 | packet->userdata = userdata; | |
356 | ||
357 | p = packet; | |
358 | packet = NULL; | |
359 | ||
360 | return lldp_handle_packet(p, (uint16_t) length); | |
361 | } |