]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/network/networkd-lldp-tx.c
networkd: add basic LLDP transmission support
[thirdparty/systemd.git] / src / network / networkd-lldp-tx.c
CommitLineData
8e1ad1ea
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2016 Lennart Poettering
5
6 systemd 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 systemd 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 systemd; 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
49static 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
64static 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
196static 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
226static 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
267static 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
300int 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
343void 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}