chiark / gitweb /
networkd: add basic LLDP transmission support
[elogind.git] / src / network / networkd-lldp-tx.c
1 /***
2   This file is part of elogind.
3
4   Copyright 2016 Lennart Poettering
5
6   elogind is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   elogind is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with elogind; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <endian.h>
21 #include <inttypes.h>
22 #include <string.h>
23
24 #include "fd-util.h"
25 #include "fileio.h"
26 #include "hostname-util.h"
27 #include "lldp.h"
28 #include "networkd-lldp-tx.h"
29 #include "random-util.h"
30 #include "socket-util.h"
31 #include "string-util.h"
32 #include "unaligned.h"
33
34 /* The LLDP spec calls this "txFastInit", see 9.2.5.19 */
35 #define LLDP_TX_FAST_INIT 4U
36
37 /* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */
38 #define LLDP_TX_HOLD 4U
39
40 /* The jitter range to add, see 9.2.2. */
41 #define LLDP_JITTER_USEC (400U * USEC_PER_MSEC)
42
43 /* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */
44 #define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_JITTER_USEC / 2)
45
46 /* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */
47 #define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2)
48
49 static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) {
50         assert(p);
51
52         if (id > 127)
53                 return -EBADMSG;
54         if (sz > 511)
55                 return -ENOBUFS;
56
57         (*p)[0] = (id << 1) | !!(sz & 256);
58         (*p)[1] = sz & 255;
59
60         *p = *p + 2;
61         return 0;
62 }
63
64 static int lldp_make_packet(
65                 const struct ether_addr *hwaddr,
66                 const char *machine_id,
67                 const char *ifname,
68                 uint16_t ttl,
69                 const char *port_description,
70                 const char *hostname,
71                 const char *pretty_hostname,
72                 uint16_t system_capabilities,
73                 uint16_t enabled_capabilities,
74                 void **ret, size_t *sz) {
75
76         size_t machine_id_length, ifname_length, port_description_length = 0, hostname_length = 0, pretty_hostname_length = 0;
77         _cleanup_free_ void *packet = NULL;
78         struct ether_header *h;
79         uint8_t *p;
80         size_t l;
81         int r;
82
83         assert(hwaddr);
84         assert(machine_id);
85         assert(ifname);
86         assert(ret);
87         assert(sz);
88
89         machine_id_length = strlen(machine_id);
90         ifname_length = strlen(ifname);
91
92         if (port_description)
93                 port_description_length = strlen(port_description);
94
95         if (hostname)
96                 hostname_length = strlen(hostname);
97
98         if (pretty_hostname)
99                 pretty_hostname_length = strlen(pretty_hostname);
100
101         l = sizeof(struct ether_header) +
102                 /* Chassis ID */
103                 2 + 1 + machine_id_length +
104                 /* Port ID */
105                 2 + 1 + ifname_length +
106                 /* TTL */
107                 2 + 2 +
108                 /* System Capabilities */
109                 2 + 4 +
110                 /* End */
111                 2;
112
113         /* Port Description */
114         if (port_description)
115                 l += 2 + port_description_length;
116
117         /* System Name */
118         if (hostname)
119                 l += 2 + hostname_length;
120
121         /* System Description */
122         if (pretty_hostname)
123                 l += 2 + pretty_hostname_length;
124
125         packet = malloc(l);
126         if (!packet)
127                 return -ENOMEM;
128
129         h = (struct ether_header*) packet;
130         h->ether_type = htobe16(ETHERTYPE_LLDP);
131         memcpy(h->ether_dhost, &(struct ether_addr) { LLDP_MULTICAST_ADDR }, ETH_ALEN);
132         memcpy(h->ether_shost, hwaddr, ETH_ALEN);
133
134         p = (uint8_t*) packet + sizeof(struct ether_header);
135
136         r = lldp_write_tlv_header(&p, LLDP_TYPE_CHASSIS_ID, 1 + machine_id_length);
137         if (r < 0)
138                 return r;
139         *(p++) = LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED;
140         p = mempcpy(p, machine_id, machine_id_length);
141
142         r = lldp_write_tlv_header(&p, LLDP_TYPE_PORT_ID, 1 + ifname_length);
143         if (r < 0)
144                 return r;
145         *(p++) = LLDP_PORT_SUBTYPE_INTERFACE_NAME;
146         p = mempcpy(p, ifname, ifname_length);
147
148         r = lldp_write_tlv_header(&p, LLDP_TYPE_TTL, 2);
149         if (r < 0)
150                 return r;
151         unaligned_write_be16(p, ttl);
152         p += 2;
153
154         if (port_description) {
155                 r = lldp_write_tlv_header(&p, LLDP_TYPE_PORT_DESCRIPTION, port_description_length);
156                 if (r < 0)
157                         return r;
158                 p = mempcpy(p, port_description, port_description_length);
159         }
160
161         if (hostname) {
162                 r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_NAME, hostname_length);
163                 if (r < 0)
164                         return r;
165                 p = mempcpy(p, hostname, hostname_length);
166         }
167
168         if (pretty_hostname) {
169                 r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_DESCRIPTION, pretty_hostname_length);
170                 if (r < 0)
171                         return r;
172                 p = mempcpy(p, pretty_hostname, pretty_hostname_length);
173         }
174
175         r = lldp_write_tlv_header(&p, LLDP_TYPE_SYSTEM_CAPABILITIES, 4);
176         if (r < 0)
177                 return r;
178         unaligned_write_be16(p, system_capabilities);
179         p += 2;
180         unaligned_write_be16(p, enabled_capabilities);
181         p += 2;
182
183         r = lldp_write_tlv_header(&p, LLDP_TYPE_END, 0);
184         if (r < 0)
185                 return r;
186
187         assert(p == (uint8_t*) packet + l);
188
189         *ret = packet;
190         *sz = l;
191
192         packet = NULL;
193         return 0;
194 }
195
196 static int lldp_send_packet(int ifindex, const void *packet, size_t packet_size) {
197
198         union sockaddr_union sa = {
199                 .ll.sll_family = AF_PACKET,
200                 .ll.sll_protocol = htobe16(ETHERTYPE_LLDP),
201                 .ll.sll_ifindex = ifindex,
202                 .ll.sll_halen = ETH_ALEN,
203                 .ll.sll_addr = LLDP_MULTICAST_ADDR,
204         };
205
206         _cleanup_close_ int fd = -1;
207         ssize_t l;
208
209         assert(ifindex > 0);
210         assert(packet || packet_size <= 0);
211
212         fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW);
213         if (fd < 0)
214                 return -errno;
215
216         l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll));
217         if (l < 0)
218                 return -errno;
219
220         if ((size_t) l != packet_size)
221                 return -EIO;
222
223         return 0;
224 }
225
226 static int link_send_lldp(Link *link) {
227         char machine_id_string[SD_ID128_STRING_MAX];
228         _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL;
229         _cleanup_free_ void *packet = NULL;
230         size_t packet_size = 0;
231         sd_id128_t machine_id;
232         uint16_t caps;
233         usec_t ttl;
234         int r;
235
236         r = sd_id128_get_machine(&machine_id);
237         if (r < 0)
238                 return r;
239
240         (void) gethostname_strict(&hostname);
241         (void) parse_env_file("/etc/machine-info", NEWLINE, "PRETTY_HOSTNAME", &pretty_hostname, NULL);
242
243         ttl = DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC);
244         if (ttl > (usec_t) UINT16_MAX)
245                 ttl = (usec_t) UINT16_MAX;
246
247         caps = (link->network && link->network->ip_forward != ADDRESS_FAMILY_NO) ?
248                 LLDP_SYSTEM_CAPABILITIES_ROUTER :
249                 LLDP_SYSTEM_CAPABILITIES_STATION;
250
251         r = lldp_make_packet(&link->mac,
252                              sd_id128_to_string(machine_id, machine_id_string),
253                              link->ifname,
254                              (uint16_t) ttl,
255                              link->network ? link->network->description : NULL,
256                              hostname,
257                              pretty_hostname,
258                              LLDP_SYSTEM_CAPABILITIES_STATION|LLDP_SYSTEM_CAPABILITIES_BRIDGE|LLDP_SYSTEM_CAPABILITIES_ROUTER,
259                              caps,
260                              &packet, &packet_size);
261         if (r < 0)
262                 return r;
263
264         return lldp_send_packet(link->ifindex, packet, packet_size);
265 }
266
267 static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) {
268         Link *link = userdata;
269         usec_t current, delay, next;
270         int r;
271
272         assert(s);
273         assert(userdata);
274
275         log_link_debug(link, "Sending LLDP packet...");
276
277         r = link_send_lldp(link);
278         if (r < 0)
279                 log_link_debug_errno(link, r, "Failed to send LLDP packet, ignoring: %m");
280
281         if (link->lldp_tx_fast > 0)
282                 link->lldp_tx_fast--;
283
284         assert_se(sd_event_now(sd_event_source_get_event(s), clock_boottime_or_monotonic(), &current) >= 0);
285
286         delay = link->lldp_tx_fast > 0 ? LLDP_FAST_TX_USEC : LLDP_TX_INTERVAL_USEC;
287         next = usec_add(usec_add(current, delay), (usec_t) random_u64() % LLDP_JITTER_USEC);
288
289         r = sd_event_source_set_time(s, next);
290         if (r < 0)
291                 return log_link_error_errno(link, r, "Failed to restart LLDP timer: %m");
292
293         r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
294         if (r < 0)
295                 return log_link_error_errno(link, r, "Failed to enable LLDP timer: %m");
296
297         return 0;
298 }
299
300 int link_lldp_tx_start(Link *link) {
301         usec_t next;
302         int r;
303
304         assert(link);
305
306         /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */
307
308         link->lldp_tx_fast = LLDP_TX_FAST_INIT;
309
310         next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC),
311                      (usec_t) random_u64() % LLDP_JITTER_USEC);
312
313         if (link->lldp_tx_event_source) {
314                 usec_t old;
315
316                 /* Lower the timeout, maybe */
317                 r = sd_event_source_get_time(link->lldp_tx_event_source, &old);
318                 if (r < 0)
319                         return r;
320
321                 if (old <= next)
322                         return 0;
323
324                 return sd_event_source_set_time(link->lldp_tx_event_source, next);
325         } else {
326                 r = sd_event_add_time(
327                                 link->manager->event,
328                                 &link->lldp_tx_event_source,
329                                 clock_boottime_or_monotonic(),
330                                 next,
331                                 0,
332                                 on_lldp_timer,
333                                 link);
334                 if (r < 0)
335                         return r;
336
337                 (void) sd_event_source_set_description(link->lldp_tx_event_source, "lldp-tx");
338         }
339
340         return 0;
341 }
342
343 void link_lldp_tx_stop(Link *link) {
344         assert(link);
345
346         link->lldp_tx_event_source = sd_event_source_unref(link->lldp_tx_event_source);
347 }