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