]>
Commit | Line | Data |
---|---|---|
e12c2365 VB |
1 | /* -*- mode: c; c-file-style: "openbsd" -*- */ |
2 | /* | |
3 | * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx> | |
4 | * | |
5 | * Permission to use, copy, modify, and/or distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
adbb6e54 VB |
18 | /* Grabbing interfaces information with netlink only. */ |
19 | ||
e12c2365 VB |
20 | #include "lldpd.h" |
21 | ||
0fa2254b | 22 | #include <errno.h> |
e12c2365 | 23 | #include <sys/socket.h> |
0fa2254b | 24 | #include <netdb.h> |
13181ede | 25 | #include <net/if_arp.h> |
0fa2254b VB |
26 | #include <linux/netlink.h> |
27 | #include <linux/rtnetlink.h> | |
28 | ||
29 | #define NETLINK_BUFFER 4096 | |
02c91f0a | 30 | |
0fa2254b VB |
31 | struct netlink_req { |
32 | struct nlmsghdr hdr; | |
33 | struct rtgenmsg gen; | |
34 | }; | |
13181ede VB |
35 | |
36 | struct lldpd_netlink { | |
0fa2254b | 37 | int nl_socket; |
0cb979c7 | 38 | int can_increase; /* Can we increase the netlink socket size? */ |
0fa2254b VB |
39 | /* Cache */ |
40 | struct interfaces_device_list *devices; | |
41 | struct interfaces_address_list *addresses; | |
e12c2365 VB |
42 | }; |
43 | ||
c9f0ee58 AA |
44 | |
45 | static int | |
e8ee1d9d | 46 | netlink_socket_set_buffer_sizes(int s, int sndbuf, int rcvbuf) |
c9f0ee58 | 47 | { |
e8ee1d9d | 48 | socklen_t size = sizeof(int); |
c9f0ee58 AA |
49 | int got = 0; |
50 | ||
93051bc2 | 51 | if (sndbuf > 0 && setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { |
c9f0ee58 AA |
52 | log_warn("netlink", "unable to set SO_SNDBUF to '%d'", sndbuf); |
53 | return -1; | |
54 | } | |
55 | ||
93051bc2 | 56 | if (rcvbuf > 0 && setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { |
c9f0ee58 AA |
57 | log_warn("netlink", "unable to set SO_RCVBUF to '%d'", rcvbuf); |
58 | return -1; | |
59 | } | |
60 | ||
61 | /* Now read them back from kernel. | |
62 | * SO_SNDBUF & SO_RCVBUF are cap-ed at sysctl `net.core.rmem_max` & | |
63 | * `net.core.wmem_max`. This it the easiest [probably sanest too] | |
64 | * to validate that our socket buffers were set properly. | |
65 | */ | |
93051bc2 VB |
66 | if (sndbuf > 0) { |
67 | if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &got, &size) < 0) { | |
68 | log_warn("netlink", "unable to get SO_SNDBUF"); | |
69 | } else { | |
93051bc2 | 70 | if (got != sndbuf) |
e8ee1d9d | 71 | log_warnx("netlink", "tried to set SO_SNDBUF to '%d' " |
93051bc2 VB |
72 | "but got '%d'", sndbuf, got); |
73 | } | |
c9f0ee58 AA |
74 | } |
75 | ||
93051bc2 VB |
76 | if (rcvbuf > 0) { |
77 | if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &got, &size) < 0) { | |
78 | log_warn("netlink", "unable to get SO_RCVBUF"); | |
79 | } else { | |
93051bc2 | 80 | if (got != rcvbuf) |
e8ee1d9d | 81 | log_warnx("netlink", "tried to set SO_RCVBUF to '%d' " |
93051bc2 VB |
82 | "but got '%d'", rcvbuf, got); |
83 | } | |
c9f0ee58 AA |
84 | } |
85 | ||
86 | return 0; | |
87 | } | |
88 | ||
0cb979c7 VB |
89 | /** |
90 | * Double the size of the receive netlink send buffer. | |
91 | * | |
92 | * Returns 0 on success, -1 on system error, 1 when we were unable to grow the | |
93 | * buffer more. | |
94 | */ | |
95 | static int | |
96 | netlink_socket_double_buffer_sizes(int s, int max) | |
97 | { | |
98 | socklen_t size = sizeof(int); | |
99 | int current = 0, got = 0; | |
100 | if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, ¤t, &size) < 0) { | |
101 | log_warn("netlink", "unable to get SO_RCVBUF"); | |
102 | return -1; | |
103 | } | |
104 | if (current < max) { | |
105 | current *= 2; | |
106 | if (current > max) | |
107 | current = max; | |
108 | if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, ¤t, sizeof(current)) < 0) { | |
109 | log_warn("netlink", "unable to set SO_RCVBUF to '%d'", current); | |
110 | return -1; | |
111 | } | |
112 | if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &got, &size) < 0) { | |
113 | log_warn("netlink", "unable to get SO_RCVBUF"); | |
114 | return -1; | |
115 | } | |
116 | if (got != current) { | |
117 | log_warn("netlink", "tried to set SO_RCVBUF to '%d' " | |
118 | "but got '%d'", current, got); | |
119 | return 1; /* Assume we got the maximum */ | |
120 | } | |
121 | log_debug("netlink", "increased SO_RCVBUF to %d", current); | |
122 | return 0; | |
123 | } | |
124 | return 1; /* Maximum alredy reached */ | |
125 | } | |
126 | ||
e12c2365 | 127 | /** |
0fa2254b VB |
128 | * Connect to netlink. |
129 | * | |
130 | * Open a Netlink socket and connect to it. | |
131 | * | |
132 | * @param protocol Which protocol to use (eg NETLINK_ROUTE). | |
133 | * @param groups Which groups we want to subscribe to | |
134 | * @return The opened socket or -1 on error. | |
e12c2365 | 135 | */ |
0fa2254b VB |
136 | static int |
137 | netlink_connect(int protocol, unsigned groups) | |
e12c2365 | 138 | { |
0fa2254b VB |
139 | int s; |
140 | struct sockaddr_nl local = { | |
141 | .nl_family = AF_NETLINK, | |
142 | .nl_pid = getpid(), | |
143 | .nl_groups = groups | |
144 | }; | |
145 | ||
146 | /* Open Netlink socket */ | |
147 | log_debug("netlink", "opening netlink socket"); | |
148 | s = socket(AF_NETLINK, SOCK_RAW, protocol); | |
149 | if (s == -1) { | |
150 | log_warn("netlink", "unable to open netlink socket"); | |
151 | return -1; | |
13181ede | 152 | } |
e8ee1d9d VB |
153 | if (netlink_socket_set_buffer_sizes(s, |
154 | NETLINK_SEND_BUFSIZE, NETLINK_RECEIVE_BUFSIZE) < 0) | |
c9f0ee58 | 155 | return -1; |
0fa2254b VB |
156 | if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { |
157 | log_warn("netlink", "unable to bind netlink socket"); | |
158 | close(s); | |
159 | return -1; | |
160 | } | |
161 | return s; | |
e12c2365 VB |
162 | } |
163 | ||
164 | /** | |
0fa2254b | 165 | * Send a netlink message. |
e12c2365 | 166 | * |
0fa2254b VB |
167 | * The type of the message can be chosen as well the route family. The |
168 | * mesage will always be NLM_F_REQUEST | NLM_F_DUMP. | |
e12c2365 | 169 | * |
0fa2254b VB |
170 | * @param s the netlink socket |
171 | * @param type the request type (eg RTM_GETLINK) | |
172 | * @param family the rt family (eg AF_PACKET) | |
e12c2365 VB |
173 | * @return 0 on success, -1 otherwise |
174 | */ | |
175 | static int | |
0fa2254b | 176 | netlink_send(int s, int type, int family, int seq) |
e12c2365 | 177 | { |
0fa2254b VB |
178 | struct netlink_req req = { |
179 | .hdr = { | |
180 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | |
181 | .nlmsg_type = type, | |
182 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, | |
183 | .nlmsg_seq = seq, | |
184 | .nlmsg_pid = getpid() }, | |
185 | .gen = { .rtgen_family = family } | |
186 | }; | |
187 | struct iovec iov = { | |
188 | .iov_base = &req, | |
189 | .iov_len = req.hdr.nlmsg_len | |
190 | }; | |
191 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; | |
192 | struct msghdr rtnl_msg = { | |
193 | .msg_iov = &iov, | |
194 | .msg_iovlen = 1, | |
195 | .msg_name = &peer, | |
196 | .msg_namelen = sizeof(struct sockaddr_nl) | |
197 | }; | |
198 | ||
199 | /* Send netlink message. This is synchronous but we are guaranteed | |
200 | * to not block. */ | |
201 | log_debug("netlink", "sending netlink message"); | |
202 | if (sendmsg(s, (struct msghdr *)&rtnl_msg, 0) == -1) { | |
203 | log_warn("netlink", "unable to send netlink message"); | |
204 | return -1; | |
16eacc5b VB |
205 | } |
206 | ||
0fa2254b VB |
207 | return 0; |
208 | } | |
16eacc5b | 209 | |
0fa2254b VB |
210 | static void |
211 | netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) | |
212 | { | |
213 | while (RTA_OK(rta, len)) { | |
214 | if ((rta->rta_type <= max) && (!tb[rta->rta_type])) | |
215 | tb[rta->rta_type] = rta; | |
216 | rta = RTA_NEXT(rta,len); | |
16eacc5b | 217 | } |
16eacc5b VB |
218 | } |
219 | ||
e12c2365 | 220 | /** |
0fa2254b VB |
221 | * Parse a `linkinfo` attributes. |
222 | * | |
223 | * @param iff where to put the result | |
224 | * @param rta linkinfo attribute | |
225 | * @param len length of attributes | |
e12c2365 | 226 | */ |
0fa2254b VB |
227 | static void |
228 | netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int len) | |
e12c2365 | 229 | { |
0fa2254b VB |
230 | struct rtattr *link_info_attrs[IFLA_INFO_MAX+1] = {}; |
231 | char *kind = NULL; | |
232 | ||
233 | netlink_parse_rtattr(link_info_attrs, IFLA_INFO_MAX, rta, len); | |
234 | ||
235 | if (link_info_attrs[IFLA_INFO_KIND]) { | |
236 | kind = strdup(RTA_DATA(link_info_attrs[IFLA_INFO_KIND])); | |
237 | if (kind) { | |
238 | if (!strcmp(kind, "vlan")) { | |
239 | log_debug("netlink", "interface %s is a VLAN", | |
240 | iff->name); | |
241 | iff->type |= IFACE_VLAN_T; | |
242 | } else if (!strcmp(kind, "bridge")) { | |
243 | log_debug("netlink", "interface %s is a bridge", | |
244 | iff->name); | |
245 | iff->type |= IFACE_BRIDGE_T; | |
246 | } else if (!strcmp(kind, "bond")) { | |
247 | log_debug("netlink", "interface %s is a bond", | |
248 | iff->name); | |
249 | iff->type |= IFACE_BOND_T; | |
250 | } | |
251 | } | |
252 | } | |
13181ede | 253 | |
0fa2254b VB |
254 | if (kind && !strcmp(kind, "vlan") && link_info_attrs[IFLA_INFO_DATA]) { |
255 | struct rtattr *vlan_link_info_data_attrs[IFLA_VLAN_MAX+1] = {}; | |
256 | netlink_parse_rtattr(vlan_link_info_data_attrs, IFLA_VLAN_MAX, | |
257 | RTA_DATA(link_info_attrs[IFLA_INFO_DATA]), | |
258 | RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA])); | |
259 | ||
260 | if (vlan_link_info_data_attrs[IFLA_VLAN_ID]) { | |
261 | iff->vlanid = *(uint16_t *)RTA_DATA(vlan_link_info_data_attrs[IFLA_VLAN_ID]); | |
262 | log_debug("netlink", "VLAN ID for interface %s is %d", | |
263 | iff->name, iff->vlanid); | |
264 | } | |
265 | } | |
266 | ||
267 | free(kind); | |
e12c2365 VB |
268 | } |
269 | ||
270 | /** | |
13181ede | 271 | * Parse a `link` netlink message. |
e12c2365 | 272 | * |
0fa2254b VB |
273 | * @param msg message to be parsed |
274 | * @param iff where to put the result | |
275 | * return 0 if the interface is worth it, -1 otherwise | |
e12c2365 | 276 | */ |
0fa2254b VB |
277 | static int |
278 | netlink_parse_link(struct nlmsghdr *msg, | |
279 | struct interfaces_device *iff) | |
e12c2365 | 280 | { |
0fa2254b VB |
281 | struct ifinfomsg *ifi; |
282 | struct rtattr *attribute; | |
283 | int len; | |
284 | ifi = NLMSG_DATA(msg); | |
285 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
286 | ||
287 | if (ifi->ifi_type != ARPHRD_ETHER) { | |
288 | log_debug("netlink", "skip non Ethernet interface at index %d", | |
289 | ifi->ifi_index); | |
290 | return -1; | |
13181ede VB |
291 | } |
292 | ||
0fa2254b VB |
293 | iff->index = ifi->ifi_index; |
294 | iff->flags = ifi->ifi_flags; | |
295 | iff->lower_idx = -1; | |
296 | iff->upper_idx = -1; | |
297 | ||
298 | for (attribute = IFLA_RTA(ifi); | |
299 | RTA_OK(attribute, len); | |
300 | attribute = RTA_NEXT(attribute, len)) { | |
301 | switch(attribute->rta_type) { | |
302 | case IFLA_IFNAME: | |
303 | /* Interface name */ | |
304 | iff->name = strdup(RTA_DATA(attribute)); | |
305 | break; | |
306 | case IFLA_IFALIAS: | |
307 | /* Interface alias */ | |
308 | iff->alias = strdup(RTA_DATA(attribute)); | |
309 | break; | |
310 | case IFLA_ADDRESS: | |
311 | /* Interface MAC address */ | |
312 | iff->address = malloc(RTA_PAYLOAD(attribute)); | |
313 | if (iff->address) | |
314 | memcpy(iff->address, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); | |
315 | break; | |
316 | case IFLA_LINK: | |
317 | /* Index of "lower" interface */ | |
318 | iff->lower_idx = *(int*)RTA_DATA(attribute); | |
d2e6f750 VB |
319 | log_debug("netlink", "attribute IFLA_LINK for %s: %d", |
320 | iff->name ? iff->name : "(unknown)", iff->lower_idx); | |
0fa2254b | 321 | break; |
c04fafa7 VB |
322 | case IFLA_LINK_NETNSID: |
323 | /* Is the lower interface into another namesapce? */ | |
324 | iff->lower_idx = -1; | |
d2e6f750 VB |
325 | log_debug("netlink", "attribute IFLA_LINK_NETNSID received for %s", |
326 | iff->name ? iff->name : "(unknown)"); | |
c04fafa7 | 327 | break; |
0fa2254b VB |
328 | case IFLA_MASTER: |
329 | /* Index of master interface */ | |
330 | iff->upper_idx = *(int*)RTA_DATA(attribute); | |
331 | break; | |
0fa2254b VB |
332 | case IFLA_MTU: |
333 | /* Maximum Transmission Unit */ | |
334 | iff->mtu = *(int*)RTA_DATA(attribute); | |
335 | break; | |
336 | case IFLA_LINKINFO: | |
337 | netlink_parse_linkinfo(iff, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); | |
338 | break; | |
339 | default: | |
340 | log_debug("netlink", "unhandled link attribute type %d for iface %s", | |
341 | attribute->rta_type, iff->name ? iff->name : "(unknown)"); | |
342 | break; | |
343 | } | |
13181ede | 344 | } |
0fa2254b VB |
345 | if (!iff->name || !iff->address) { |
346 | log_info("netlink", "interface %d does not have a name or an address, skip", | |
13181ede | 347 | iff->index); |
0fa2254b | 348 | return -1; |
13181ede VB |
349 | } |
350 | ||
0fa2254b VB |
351 | log_debug("netlink", "parsed link %d (%s, flags: %d)", |
352 | iff->index, iff->name, iff->flags); | |
353 | return 0; | |
e12c2365 VB |
354 | } |
355 | ||
356 | /** | |
13181ede | 357 | * Parse a `address` netlink message. |
e12c2365 | 358 | * |
0fa2254b VB |
359 | * @param msg message to be parsed |
360 | * @param ifa where to put the result | |
361 | * return 0 if the address is worth it, -1 otherwise | |
e12c2365 | 362 | */ |
0fa2254b VB |
363 | static int |
364 | netlink_parse_address(struct nlmsghdr *msg, | |
365 | struct interfaces_address *ifa) | |
e12c2365 | 366 | { |
0fa2254b VB |
367 | struct ifaddrmsg *ifi; |
368 | struct rtattr *attribute; | |
369 | int len; | |
370 | ifi = NLMSG_DATA(msg); | |
371 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); | |
372 | ||
373 | ifa->index = ifi->ifa_index; | |
374 | ifa->flags = ifi->ifa_flags; | |
375 | switch (ifi->ifa_family) { | |
13181ede VB |
376 | case AF_INET: |
377 | case AF_INET6: break; | |
378 | default: | |
379 | log_debug("netlink", "got a non IP address on if %d (family: %d)", | |
0fa2254b VB |
380 | ifa->index, ifi->ifa_family); |
381 | return -1; | |
13181ede VB |
382 | } |
383 | ||
0fa2254b VB |
384 | for (attribute = IFA_RTA(ifi); |
385 | RTA_OK(attribute, len); | |
386 | attribute = RTA_NEXT(attribute, len)) { | |
387 | switch(attribute->rta_type) { | |
388 | case IFA_ADDRESS: | |
389 | /* Address */ | |
390 | if (ifi->ifa_family == AF_INET) { | |
391 | struct sockaddr_in ip; | |
392 | memset(&ip, 0, sizeof(struct sockaddr_in)); | |
393 | ip.sin_family = AF_INET; | |
394 | memcpy(&ip.sin_addr, RTA_DATA(attribute), | |
395 | sizeof(struct in_addr)); | |
396 | memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in)); | |
397 | } else { | |
398 | struct sockaddr_in6 ip6; | |
399 | memset(&ip6, 0, sizeof(struct sockaddr_in6)); | |
400 | ip6.sin6_family = AF_INET6; | |
401 | memcpy(&ip6.sin6_addr, RTA_DATA(attribute), | |
402 | sizeof(struct in6_addr)); | |
403 | memcpy(&ifa->address, &ip6, sizeof(struct sockaddr_in6)); | |
404 | } | |
405 | break; | |
406 | default: | |
407 | log_debug("netlink", "unhandled address attribute type %d for iface %d", | |
408 | attribute->rta_type, ifa->index); | |
409 | break; | |
410 | } | |
13181ede | 411 | } |
0fa2254b | 412 | if (ifa->address.ss_family == AF_UNSPEC) { |
13181ede VB |
413 | log_debug("netlink", "no IP for interface %d", |
414 | ifa->index); | |
0fa2254b | 415 | return -1; |
13181ede | 416 | } |
0fa2254b | 417 | return 0; |
e12c2365 VB |
418 | } |
419 | ||
58e30b5a VB |
420 | /** |
421 | * Merge an old interface with a new one. | |
422 | * | |
423 | * Some properties may be absent in the new interface that should be copied over | |
424 | * from the old one. | |
425 | */ | |
426 | void | |
427 | netlink_merge(struct interfaces_device *old, struct interfaces_device *new) | |
428 | { | |
429 | if (new->alias == NULL) { | |
430 | new->alias = old->alias; | |
431 | old->alias = NULL; | |
432 | } | |
433 | if (new->address == NULL) { | |
434 | new->address = old->address; | |
435 | old->address = NULL; | |
436 | } | |
437 | if (new->mtu == 0) | |
438 | new->mtu = old->mtu; | |
439 | if (new->type == 0) | |
440 | new->type = old->type; | |
441 | if (new->vlanid == 0) | |
442 | new->vlanid = old->vlanid; | |
d2e6f750 VB |
443 | |
444 | /* It's not possible for lower link to change */ | |
445 | new->lower_idx = old->lower_idx; | |
58e30b5a VB |
446 | } |
447 | ||
e12c2365 | 448 | /** |
0fa2254b | 449 | * Receive netlink answer from the kernel. |
e12c2365 | 450 | * |
0fa2254b VB |
451 | * @param s the netlink socket |
452 | * @param ifs list to store interface list or NULL if we don't | |
453 | * @param ifas list to store address list or NULL if we don't | |
454 | * @return 0 on success, -1 on error | |
e12c2365 | 455 | */ |
0fa2254b VB |
456 | static int |
457 | netlink_recv(int s, | |
458 | struct interfaces_device_list *ifs, | |
0cb979c7 VB |
459 | struct interfaces_address_list *ifas, |
460 | int *can_increase) | |
e12c2365 | 461 | { |
85b72fe0 DM |
462 | int end = 0, ret = 0; |
463 | int flags = MSG_PEEK | MSG_TRUNC; | |
464 | struct iovec iov; | |
0fa2254b VB |
465 | int link_update = 0; |
466 | ||
467 | struct interfaces_device *ifdold; | |
468 | struct interfaces_device *ifdnew; | |
469 | struct interfaces_address *ifaold; | |
470 | struct interfaces_address *ifanew; | |
471 | char addr[INET6_ADDRSTRLEN + 1]; | |
472 | ||
85b72fe0 DM |
473 | iov.iov_len = NETLINK_BUFFER; |
474 | iov.iov_base = malloc(iov.iov_len); | |
475 | if (!iov.iov_base) { | |
476 | log_warn("netlink", "not enough memory"); | |
477 | return -1; | |
478 | } | |
479 | ||
0fa2254b VB |
480 | while (!end) { |
481 | ssize_t len; | |
482 | struct nlmsghdr *msg; | |
0fa2254b VB |
483 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; |
484 | struct msghdr rtnl_reply = { | |
485 | .msg_iov = &iov, | |
486 | .msg_iovlen = 1, | |
487 | .msg_name = &peer, | |
488 | .msg_namelen = sizeof(struct sockaddr_nl) | |
489 | }; | |
490 | ||
85b72fe0 DM |
491 | retry: |
492 | len = recvmsg(s, &rtnl_reply, flags); | |
0fa2254b VB |
493 | if (len == -1) { |
494 | if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
495 | log_debug("netlink", "should have received something, but didn't"); | |
85b72fe0 DM |
496 | ret = 0; |
497 | goto out; | |
0fa2254b | 498 | } |
0cb979c7 VB |
499 | if (errno == ENOBUFS && *can_increase) { |
500 | switch (netlink_socket_double_buffer_sizes(s, | |
501 | NETLINK_MAX_RECEIVE_BUFSIZE)) { | |
502 | case -1: break; | |
503 | case 0: | |
504 | log_info("netlink", "netlink receive buffer too small, retry with larger one"); | |
505 | flags = 0; | |
506 | goto retry; | |
507 | case 1: | |
508 | *can_increase = 0; | |
509 | break; | |
510 | } | |
511 | } | |
644cd42d | 512 | log_warn("netlink", "unable to receive netlink answer"); |
85b72fe0 DM |
513 | ret = -1; |
514 | goto out; | |
0fa2254b | 515 | } |
85b72fe0 DM |
516 | if (!len) { |
517 | ret = 0; | |
518 | goto out; | |
519 | } | |
520 | ||
521 | if (iov.iov_len < len || (rtnl_reply.msg_flags & MSG_TRUNC)) { | |
522 | void *tmp; | |
523 | ||
524 | /* Provided buffer is not large enough, enlarge it | |
525 | * to size of len (which should be total length of the message) | |
526 | * and try again. */ | |
527 | iov.iov_len = len; | |
528 | tmp = realloc(iov.iov_base, iov.iov_len); | |
529 | if (!tmp) { | |
530 | log_warn("netlink", "not enough memory"); | |
531 | ret = -1; | |
532 | goto out; | |
533 | } | |
534 | log_debug("netlink", "enlarge message size to %zu bytes", len); | |
535 | iov.iov_base = tmp; | |
536 | flags = 0; | |
537 | goto retry; | |
538 | } | |
539 | ||
540 | if (flags != 0) { | |
541 | /* Buffer is big enough, do the actual reading */ | |
542 | flags = 0; | |
543 | goto retry; | |
544 | } | |
545 | ||
546 | for (msg = (struct nlmsghdr*)(void*)(iov.iov_base); | |
0fa2254b VB |
547 | NLMSG_OK(msg, len); |
548 | msg = NLMSG_NEXT(msg, len)) { | |
549 | if (!(msg->nlmsg_flags & NLM_F_MULTI)) | |
550 | end = 1; | |
551 | switch (msg->nlmsg_type) { | |
552 | case NLMSG_DONE: | |
553 | log_debug("netlink", "received done message"); | |
554 | end = 1; | |
555 | break; | |
556 | case RTM_NEWLINK: | |
557 | case RTM_DELLINK: | |
558 | if (!ifs) break; | |
559 | log_debug("netlink", "received link information"); | |
560 | ifdnew = calloc(1, sizeof(struct interfaces_device)); | |
561 | if (ifdnew == NULL) { | |
562 | log_warn("netlink", "not enough memory for another interface, give up what we have"); | |
563 | goto end; | |
564 | } | |
565 | if (netlink_parse_link(msg, ifdnew) == 0) { | |
566 | /* We need to find if we already have this interface */ | |
567 | TAILQ_FOREACH(ifdold, ifs, next) { | |
568 | if (ifdold->index == ifdnew->index) break; | |
569 | } | |
570 | if (msg->nlmsg_type == RTM_NEWLINK) { | |
571 | if (ifdold == NULL) { | |
572 | log_debug("netlink", "interface %s is new", | |
573 | ifdnew->name); | |
574 | TAILQ_INSERT_TAIL(ifs, ifdnew, next); | |
575 | } else { | |
576 | log_debug("netlink", "interface %s/%s is updated", | |
577 | ifdold->name, ifdnew->name); | |
58e30b5a | 578 | netlink_merge(ifdold, ifdnew); |
0fa2254b VB |
579 | TAILQ_INSERT_AFTER(ifs, ifdold, ifdnew, next); |
580 | TAILQ_REMOVE(ifs, ifdold, next); | |
581 | interfaces_free_device(ifdold); | |
582 | } | |
583 | } else { | |
584 | if (ifdold == NULL) { | |
585 | log_warnx("netlink", | |
586 | "removal request for %s, but no knowledge of it", | |
587 | ifdnew->name); | |
588 | } else { | |
589 | log_debug("netlink", "interface %s is to be removed", | |
590 | ifdold->name); | |
591 | TAILQ_REMOVE(ifs, ifdold, next); | |
592 | interfaces_free_device(ifdold); | |
593 | } | |
594 | interfaces_free_device(ifdnew); | |
595 | } | |
596 | link_update = 1; | |
597 | } else { | |
598 | interfaces_free_device(ifdnew); | |
599 | } | |
600 | break; | |
601 | case RTM_NEWADDR: | |
602 | case RTM_DELADDR: | |
603 | if (!ifas) break; | |
604 | log_debug("netlink", "received address information"); | |
605 | ifanew = calloc(1, sizeof(struct interfaces_address)); | |
606 | if (ifanew == NULL) { | |
607 | log_warn("netlink", "not enough memory for another address, give what we have"); | |
608 | goto end; | |
609 | } | |
610 | if (netlink_parse_address(msg, ifanew) == 0) { | |
611 | TAILQ_FOREACH(ifaold, ifas, next) { | |
612 | if ((ifaold->index == ifanew->index) && | |
613 | !memcmp(&ifaold->address, &ifanew->address, | |
614 | sizeof(ifaold->address))) continue; | |
615 | } | |
616 | if (getnameinfo((struct sockaddr *)&ifanew->address, | |
617 | sizeof(ifanew->address), | |
618 | addr, sizeof(addr), | |
619 | NULL, 0, NI_NUMERICHOST) != 0) { | |
620 | strlcpy(addr, "(unknown)", sizeof(addr)); | |
621 | } | |
13181ede | 622 | |
0fa2254b VB |
623 | if (msg->nlmsg_type == RTM_NEWADDR) { |
624 | if (ifaold == NULL) { | |
625 | log_debug("netlink", "new address %s%%%d", | |
626 | addr, ifanew->index); | |
627 | TAILQ_INSERT_TAIL(ifas, ifanew, next); | |
628 | } else { | |
629 | log_debug("netlink", "updated address %s%%%d", | |
630 | addr, ifaold->index); | |
631 | TAILQ_INSERT_AFTER(ifas, ifaold, ifanew, next); | |
632 | TAILQ_REMOVE(ifas, ifaold, next); | |
633 | interfaces_free_address(ifaold); | |
634 | } | |
635 | } else { | |
636 | if (ifaold == NULL) { | |
8027d907 | 637 | log_info("netlink", |
0fa2254b VB |
638 | "removal request for address of %s%%%d, but no knowledge of it", |
639 | addr, ifanew->index); | |
640 | } else { | |
641 | log_debug("netlink", "address %s%%%d is to be removed", | |
642 | addr, ifaold->index); | |
643 | TAILQ_REMOVE(ifas, ifaold, next); | |
644 | interfaces_free_address(ifaold); | |
645 | } | |
646 | interfaces_free_address(ifanew); | |
647 | } | |
648 | } else { | |
649 | interfaces_free_address(ifanew); | |
650 | } | |
651 | break; | |
652 | default: | |
653 | log_debug("netlink", | |
654 | "received unhandled message type %d (len: %d)", | |
655 | msg->nlmsg_type, msg->nlmsg_len); | |
656 | } | |
657 | } | |
85b72fe0 | 658 | flags = MSG_PEEK | MSG_TRUNC; |
13181ede | 659 | } |
0fa2254b VB |
660 | end: |
661 | if (link_update) { | |
662 | /* Fill out lower/upper */ | |
663 | struct interfaces_device *iface1, *iface2; | |
664 | TAILQ_FOREACH(iface1, ifs, next) { | |
665 | if (iface1->upper_idx != -1 && iface1->upper_idx != iface1->index) { | |
666 | TAILQ_FOREACH(iface2, ifs, next) { | |
667 | if (iface1->upper_idx == iface2->index) { | |
c04fafa7 VB |
668 | log_debug("netlink", |
669 | "upper interface for %s is %s", | |
670 | iface1->name, iface2->name); | |
0fa2254b VB |
671 | iface1->upper = iface2; |
672 | break; | |
673 | } | |
13181ede | 674 | } |
100266c1 VB |
675 | if (iface2 == NULL) |
676 | iface1->upper = NULL; | |
0fa2254b VB |
677 | } else { |
678 | iface1->upper = NULL; | |
13181ede | 679 | } |
0fa2254b VB |
680 | if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) { |
681 | TAILQ_FOREACH(iface2, ifs, next) { | |
682 | if (iface1->lower_idx == iface2->index) { | |
100266c1 VB |
683 | /* Workaround a bug introduced |
684 | * in Linux 4.1: a pair of veth | |
685 | * will be lower interface of | |
686 | * each other. Do not modify | |
687 | * index as if one of them is | |
688 | * updated, we will loose the | |
689 | * information about the | |
690 | * loop. */ | |
0fa2254b | 691 | if (iface2->lower_idx == iface1->index) { |
100266c1 | 692 | iface1->lower = NULL; |
c04fafa7 VB |
693 | log_debug("netlink", |
694 | "link loop detected between %s and %s", | |
695 | iface1->name, iface2->name); | |
696 | } else { | |
697 | log_debug("netlink", | |
698 | "lower interface for %s is %s", | |
699 | iface1->name, iface2->name); | |
700 | iface1->lower = iface2; | |
701 | } | |
0fa2254b | 702 | break; |
7d0a4975 | 703 | } |
100266c1 VB |
704 | if (iface2 == NULL) |
705 | iface1->lower = NULL; | |
13181ede | 706 | } |
0fa2254b VB |
707 | } else { |
708 | iface1->lower = NULL; | |
13181ede | 709 | } |
0fa2254b | 710 | } |
13181ede | 711 | } |
85b72fe0 DM |
712 | |
713 | out: | |
714 | free(iov.iov_base); | |
715 | return ret; | |
0fa2254b | 716 | } |
13181ede | 717 | |
0fa2254b VB |
718 | static int |
719 | netlink_group_mask(int group) | |
720 | { | |
721 | return group ? (1 << (group - 1)) : 0; | |
e12c2365 VB |
722 | } |
723 | ||
724 | /** | |
0fa2254b | 725 | * Subscribe to link changes. |
e12c2365 | 726 | * |
0fa2254b | 727 | * @return The socket we should listen to for changes. |
e12c2365 | 728 | */ |
0fa2254b VB |
729 | int |
730 | netlink_subscribe_changes() | |
e12c2365 | 731 | { |
0fa2254b VB |
732 | unsigned int groups; |
733 | ||
734 | log_debug("netlink", "listening on interface changes"); | |
735 | ||
736 | groups = netlink_group_mask(RTNLGRP_LINK) | | |
737 | netlink_group_mask(RTNLGRP_IPV4_IFADDR) | | |
738 | netlink_group_mask(RTNLGRP_IPV6_IFADDR); | |
739 | ||
740 | return netlink_connect(NETLINK_ROUTE, groups); | |
741 | } | |
742 | ||
743 | /** | |
744 | * Receive changes from netlink */ | |
745 | static void | |
746 | netlink_change_cb(struct lldpd *cfg) | |
747 | { | |
748 | if (cfg->g_netlink == NULL) | |
749 | return; | |
750 | netlink_recv(cfg->g_netlink->nl_socket, | |
751 | cfg->g_netlink->devices, | |
0cb979c7 VB |
752 | cfg->g_netlink->addresses, |
753 | &cfg->g_netlink->can_increase); | |
0fa2254b | 754 | } |
0484f180 | 755 | |
0fa2254b VB |
756 | /** |
757 | * Initialize netlink subsystem. | |
758 | * | |
759 | * This can be called several times but will have effect only the first time. | |
760 | * | |
761 | * @return 0 on success, -1 otherwise | |
762 | */ | |
763 | static int | |
764 | netlink_initialize(struct lldpd *cfg) | |
765 | { | |
766 | if (cfg->g_netlink) return 0; | |
aca48e4b | 767 | |
0fa2254b VB |
768 | log_debug("netlink", "initialize netlink subsystem"); |
769 | if ((cfg->g_netlink = calloc(sizeof(struct lldpd_netlink), 1)) == NULL) { | |
770 | log_warn("netlink", "unable to allocate memory for netlink subsystem"); | |
771 | goto end; | |
772 | } | |
773 | ||
774 | /* Connect to netlink (by requesting to get notified on updates) and | |
775 | * request updated information right now */ | |
776 | int s = cfg->g_netlink->nl_socket = netlink_subscribe_changes(); | |
777 | ||
778 | struct interfaces_address_list *ifaddrs = cfg->g_netlink->addresses = | |
779 | malloc(sizeof(struct interfaces_address_list)); | |
13181ede VB |
780 | if (ifaddrs == NULL) { |
781 | log_warn("netlink", "not enough memory for address list"); | |
0fa2254b | 782 | goto end; |
13181ede VB |
783 | } |
784 | TAILQ_INIT(ifaddrs); | |
785 | ||
0fa2254b VB |
786 | struct interfaces_device_list *ifs = cfg->g_netlink->devices = |
787 | malloc(sizeof(struct interfaces_device_list)); | |
788 | if (ifs == NULL) { | |
789 | log_warn("netlink", "not enough memory for interface list"); | |
790 | goto end; | |
791 | } | |
792 | TAILQ_INIT(ifs); | |
793 | ||
0cb979c7 | 794 | cfg->g_netlink->can_increase = 1; |
0fa2254b VB |
795 | if (netlink_send(s, RTM_GETADDR, AF_UNSPEC, 1) == -1) |
796 | goto end; | |
0cb979c7 | 797 | netlink_recv(s, NULL, ifaddrs, &cfg->g_netlink->can_increase); |
0fa2254b VB |
798 | if (netlink_send(s, RTM_GETLINK, AF_PACKET, 2) == -1) |
799 | goto end; | |
0cb979c7 | 800 | netlink_recv(s, ifs, NULL, &cfg->g_netlink->can_increase); |
0fa2254b VB |
801 | |
802 | /* Listen to any future change */ | |
803 | cfg->g_iface_cb = netlink_change_cb; | |
804 | if (levent_iface_subscribe(cfg, s) == -1) { | |
805 | goto end; | |
806 | } | |
807 | ||
808 | return 0; | |
809 | end: | |
810 | netlink_cleanup(cfg); | |
811 | return -1; | |
812 | } | |
813 | ||
814 | /** | |
815 | * Cleanup netlink subsystem. | |
816 | */ | |
817 | void | |
818 | netlink_cleanup(struct lldpd *cfg) | |
819 | { | |
820 | if (cfg->g_netlink == NULL) return; | |
821 | if (cfg->g_netlink->nl_socket != -1) | |
822 | close(cfg->g_netlink->nl_socket); | |
823 | interfaces_free_devices(cfg->g_netlink->devices); | |
824 | interfaces_free_addresses(cfg->g_netlink->addresses); | |
825 | ||
826 | free(cfg->g_netlink); | |
827 | cfg->g_netlink = NULL; | |
828 | } | |
829 | ||
830 | /** | |
831 | * Receive the list of interfaces. | |
832 | * | |
833 | * @return a list of interfaces. | |
834 | */ | |
835 | struct interfaces_device_list* | |
836 | netlink_get_interfaces(struct lldpd *cfg) | |
837 | { | |
838 | if (netlink_initialize(cfg) == -1) return NULL; | |
839 | struct interfaces_device *ifd; | |
840 | TAILQ_FOREACH(ifd, cfg->g_netlink->devices, next) { | |
841 | ifd->ignore = 0; | |
13181ede | 842 | } |
0fa2254b VB |
843 | return cfg->g_netlink->devices; |
844 | } | |
aca48e4b | 845 | |
0fa2254b VB |
846 | /** |
847 | * Receive the list of addresses. | |
848 | * | |
849 | * @return a list of addresses. | |
850 | */ | |
851 | struct interfaces_address_list* | |
852 | netlink_get_addresses(struct lldpd *cfg) | |
853 | { | |
854 | if (netlink_initialize(cfg) == -1) return NULL; | |
855 | return cfg->g_netlink->addresses; | |
0484f180 | 856 | } |