]>
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 | ||
43 | /** | |
0fa2254b VB |
44 | * Connect to netlink. |
45 | * | |
46 | * Open a Netlink socket and connect to it. | |
47 | * | |
48 | * @param protocol Which protocol to use (eg NETLINK_ROUTE). | |
49 | * @param groups Which groups we want to subscribe to | |
50 | * @return The opened socket or -1 on error. | |
e12c2365 | 51 | */ |
0fa2254b VB |
52 | static int |
53 | netlink_connect(int protocol, unsigned groups) | |
e12c2365 | 54 | { |
0fa2254b VB |
55 | int s; |
56 | struct sockaddr_nl local = { | |
57 | .nl_family = AF_NETLINK, | |
58 | .nl_pid = getpid(), | |
59 | .nl_groups = groups | |
60 | }; | |
61 | ||
62 | /* Open Netlink socket */ | |
63 | log_debug("netlink", "opening netlink socket"); | |
64 | s = socket(AF_NETLINK, SOCK_RAW, protocol); | |
65 | if (s == -1) { | |
66 | log_warn("netlink", "unable to open netlink socket"); | |
67 | return -1; | |
13181ede | 68 | } |
0fa2254b VB |
69 | if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { |
70 | log_warn("netlink", "unable to bind netlink socket"); | |
71 | close(s); | |
72 | return -1; | |
73 | } | |
74 | return s; | |
e12c2365 VB |
75 | } |
76 | ||
77 | /** | |
0fa2254b | 78 | * Send a netlink message. |
e12c2365 | 79 | * |
0fa2254b VB |
80 | * The type of the message can be chosen as well the route family. The |
81 | * mesage will always be NLM_F_REQUEST | NLM_F_DUMP. | |
e12c2365 | 82 | * |
0fa2254b VB |
83 | * @param s the netlink socket |
84 | * @param type the request type (eg RTM_GETLINK) | |
85 | * @param family the rt family (eg AF_PACKET) | |
e12c2365 VB |
86 | * @return 0 on success, -1 otherwise |
87 | */ | |
88 | static int | |
0fa2254b | 89 | netlink_send(int s, int type, int family, int seq) |
e12c2365 | 90 | { |
0fa2254b VB |
91 | struct netlink_req req = { |
92 | .hdr = { | |
93 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | |
94 | .nlmsg_type = type, | |
95 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, | |
96 | .nlmsg_seq = seq, | |
97 | .nlmsg_pid = getpid() }, | |
98 | .gen = { .rtgen_family = family } | |
99 | }; | |
100 | struct iovec iov = { | |
101 | .iov_base = &req, | |
102 | .iov_len = req.hdr.nlmsg_len | |
103 | }; | |
104 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; | |
105 | struct msghdr rtnl_msg = { | |
106 | .msg_iov = &iov, | |
107 | .msg_iovlen = 1, | |
108 | .msg_name = &peer, | |
109 | .msg_namelen = sizeof(struct sockaddr_nl) | |
110 | }; | |
111 | ||
112 | /* Send netlink message. This is synchronous but we are guaranteed | |
113 | * to not block. */ | |
114 | log_debug("netlink", "sending netlink message"); | |
115 | if (sendmsg(s, (struct msghdr *)&rtnl_msg, 0) == -1) { | |
116 | log_warn("netlink", "unable to send netlink message"); | |
117 | return -1; | |
16eacc5b VB |
118 | } |
119 | ||
0fa2254b VB |
120 | return 0; |
121 | } | |
16eacc5b | 122 | |
0fa2254b VB |
123 | static void |
124 | netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) | |
125 | { | |
126 | while (RTA_OK(rta, len)) { | |
127 | if ((rta->rta_type <= max) && (!tb[rta->rta_type])) | |
128 | tb[rta->rta_type] = rta; | |
129 | rta = RTA_NEXT(rta,len); | |
16eacc5b | 130 | } |
16eacc5b VB |
131 | } |
132 | ||
e12c2365 | 133 | /** |
0fa2254b VB |
134 | * Parse a `linkinfo` attributes. |
135 | * | |
136 | * @param iff where to put the result | |
137 | * @param rta linkinfo attribute | |
138 | * @param len length of attributes | |
e12c2365 | 139 | */ |
0fa2254b VB |
140 | static void |
141 | netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int len) | |
e12c2365 | 142 | { |
0fa2254b VB |
143 | struct rtattr *link_info_attrs[IFLA_INFO_MAX+1] = {}; |
144 | char *kind = NULL; | |
145 | ||
146 | netlink_parse_rtattr(link_info_attrs, IFLA_INFO_MAX, rta, len); | |
147 | ||
148 | if (link_info_attrs[IFLA_INFO_KIND]) { | |
149 | kind = strdup(RTA_DATA(link_info_attrs[IFLA_INFO_KIND])); | |
150 | if (kind) { | |
151 | if (!strcmp(kind, "vlan")) { | |
152 | log_debug("netlink", "interface %s is a VLAN", | |
153 | iff->name); | |
154 | iff->type |= IFACE_VLAN_T; | |
155 | } else if (!strcmp(kind, "bridge")) { | |
156 | log_debug("netlink", "interface %s is a bridge", | |
157 | iff->name); | |
158 | iff->type |= IFACE_BRIDGE_T; | |
159 | } else if (!strcmp(kind, "bond")) { | |
160 | log_debug("netlink", "interface %s is a bond", | |
161 | iff->name); | |
162 | iff->type |= IFACE_BOND_T; | |
163 | } | |
164 | } | |
165 | } | |
13181ede | 166 | |
0fa2254b VB |
167 | if (kind && !strcmp(kind, "vlan") && link_info_attrs[IFLA_INFO_DATA]) { |
168 | struct rtattr *vlan_link_info_data_attrs[IFLA_VLAN_MAX+1] = {}; | |
169 | netlink_parse_rtattr(vlan_link_info_data_attrs, IFLA_VLAN_MAX, | |
170 | RTA_DATA(link_info_attrs[IFLA_INFO_DATA]), | |
171 | RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA])); | |
172 | ||
173 | if (vlan_link_info_data_attrs[IFLA_VLAN_ID]) { | |
174 | iff->vlanid = *(uint16_t *)RTA_DATA(vlan_link_info_data_attrs[IFLA_VLAN_ID]); | |
175 | log_debug("netlink", "VLAN ID for interface %s is %d", | |
176 | iff->name, iff->vlanid); | |
177 | } | |
178 | } | |
179 | ||
180 | free(kind); | |
e12c2365 VB |
181 | } |
182 | ||
183 | /** | |
13181ede | 184 | * Parse a `link` netlink message. |
e12c2365 | 185 | * |
0fa2254b VB |
186 | * @param msg message to be parsed |
187 | * @param iff where to put the result | |
188 | * return 0 if the interface is worth it, -1 otherwise | |
e12c2365 | 189 | */ |
0fa2254b VB |
190 | static int |
191 | netlink_parse_link(struct nlmsghdr *msg, | |
192 | struct interfaces_device *iff) | |
e12c2365 | 193 | { |
0fa2254b VB |
194 | struct ifinfomsg *ifi; |
195 | struct rtattr *attribute; | |
196 | int len; | |
197 | ifi = NLMSG_DATA(msg); | |
198 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
199 | ||
200 | if (ifi->ifi_type != ARPHRD_ETHER) { | |
201 | log_debug("netlink", "skip non Ethernet interface at index %d", | |
202 | ifi->ifi_index); | |
203 | return -1; | |
13181ede VB |
204 | } |
205 | ||
0fa2254b VB |
206 | iff->index = ifi->ifi_index; |
207 | iff->flags = ifi->ifi_flags; | |
208 | iff->lower_idx = -1; | |
209 | iff->upper_idx = -1; | |
210 | ||
211 | for (attribute = IFLA_RTA(ifi); | |
212 | RTA_OK(attribute, len); | |
213 | attribute = RTA_NEXT(attribute, len)) { | |
214 | switch(attribute->rta_type) { | |
215 | case IFLA_IFNAME: | |
216 | /* Interface name */ | |
217 | iff->name = strdup(RTA_DATA(attribute)); | |
218 | break; | |
219 | case IFLA_IFALIAS: | |
220 | /* Interface alias */ | |
221 | iff->alias = strdup(RTA_DATA(attribute)); | |
222 | break; | |
223 | case IFLA_ADDRESS: | |
224 | /* Interface MAC address */ | |
225 | iff->address = malloc(RTA_PAYLOAD(attribute)); | |
226 | if (iff->address) | |
227 | memcpy(iff->address, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); | |
228 | break; | |
229 | case IFLA_LINK: | |
230 | /* Index of "lower" interface */ | |
231 | iff->lower_idx = *(int*)RTA_DATA(attribute); | |
232 | break; | |
233 | case IFLA_MASTER: | |
234 | /* Index of master interface */ | |
235 | iff->upper_idx = *(int*)RTA_DATA(attribute); | |
236 | break; | |
237 | case IFLA_TXQLEN: | |
238 | /* Transmit queue length */ | |
239 | iff->txqueue = *(int*)RTA_DATA(attribute); | |
240 | break; | |
241 | case IFLA_MTU: | |
242 | /* Maximum Transmission Unit */ | |
243 | iff->mtu = *(int*)RTA_DATA(attribute); | |
244 | break; | |
245 | case IFLA_LINKINFO: | |
246 | netlink_parse_linkinfo(iff, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); | |
247 | break; | |
248 | default: | |
249 | log_debug("netlink", "unhandled link attribute type %d for iface %s", | |
250 | attribute->rta_type, iff->name ? iff->name : "(unknown)"); | |
251 | break; | |
252 | } | |
13181ede | 253 | } |
0fa2254b VB |
254 | if (!iff->name || !iff->address) { |
255 | log_info("netlink", "interface %d does not have a name or an address, skip", | |
13181ede | 256 | iff->index); |
0fa2254b | 257 | return -1; |
13181ede VB |
258 | } |
259 | ||
0fa2254b VB |
260 | log_debug("netlink", "parsed link %d (%s, flags: %d)", |
261 | iff->index, iff->name, iff->flags); | |
262 | return 0; | |
e12c2365 VB |
263 | } |
264 | ||
265 | /** | |
13181ede | 266 | * Parse a `address` netlink message. |
e12c2365 | 267 | * |
0fa2254b VB |
268 | * @param msg message to be parsed |
269 | * @param ifa where to put the result | |
270 | * return 0 if the address is worth it, -1 otherwise | |
e12c2365 | 271 | */ |
0fa2254b VB |
272 | static int |
273 | netlink_parse_address(struct nlmsghdr *msg, | |
274 | struct interfaces_address *ifa) | |
e12c2365 | 275 | { |
0fa2254b VB |
276 | struct ifaddrmsg *ifi; |
277 | struct rtattr *attribute; | |
278 | int len; | |
279 | ifi = NLMSG_DATA(msg); | |
280 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); | |
281 | ||
282 | ifa->index = ifi->ifa_index; | |
283 | ifa->flags = ifi->ifa_flags; | |
284 | switch (ifi->ifa_family) { | |
13181ede VB |
285 | case AF_INET: |
286 | case AF_INET6: break; | |
287 | default: | |
288 | log_debug("netlink", "got a non IP address on if %d (family: %d)", | |
0fa2254b VB |
289 | ifa->index, ifi->ifa_family); |
290 | return -1; | |
13181ede VB |
291 | } |
292 | ||
0fa2254b VB |
293 | for (attribute = IFA_RTA(ifi); |
294 | RTA_OK(attribute, len); | |
295 | attribute = RTA_NEXT(attribute, len)) { | |
296 | switch(attribute->rta_type) { | |
297 | case IFA_ADDRESS: | |
298 | /* Address */ | |
299 | if (ifi->ifa_family == AF_INET) { | |
300 | struct sockaddr_in ip; | |
301 | memset(&ip, 0, sizeof(struct sockaddr_in)); | |
302 | ip.sin_family = AF_INET; | |
303 | memcpy(&ip.sin_addr, RTA_DATA(attribute), | |
304 | sizeof(struct in_addr)); | |
305 | memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in)); | |
306 | } else { | |
307 | struct sockaddr_in6 ip6; | |
308 | memset(&ip6, 0, sizeof(struct sockaddr_in6)); | |
309 | ip6.sin6_family = AF_INET6; | |
310 | memcpy(&ip6.sin6_addr, RTA_DATA(attribute), | |
311 | sizeof(struct in6_addr)); | |
312 | memcpy(&ifa->address, &ip6, sizeof(struct sockaddr_in6)); | |
313 | } | |
314 | break; | |
315 | default: | |
316 | log_debug("netlink", "unhandled address attribute type %d for iface %d", | |
317 | attribute->rta_type, ifa->index); | |
318 | break; | |
319 | } | |
13181ede | 320 | } |
0fa2254b | 321 | if (ifa->address.ss_family == AF_UNSPEC) { |
13181ede VB |
322 | log_debug("netlink", "no IP for interface %d", |
323 | ifa->index); | |
0fa2254b | 324 | return -1; |
13181ede | 325 | } |
0fa2254b | 326 | return 0; |
e12c2365 VB |
327 | } |
328 | ||
329 | /** | |
0fa2254b | 330 | * Receive netlink answer from the kernel. |
e12c2365 | 331 | * |
0fa2254b VB |
332 | * @param s the netlink socket |
333 | * @param ifs list to store interface list or NULL if we don't | |
334 | * @param ifas list to store address list or NULL if we don't | |
335 | * @return 0 on success, -1 on error | |
e12c2365 | 336 | */ |
0fa2254b VB |
337 | static int |
338 | netlink_recv(int s, | |
339 | struct interfaces_device_list *ifs, | |
340 | struct interfaces_address_list *ifas) | |
e12c2365 | 341 | { |
0fa2254b VB |
342 | char reply[NETLINK_BUFFER] __attribute__ ((aligned)); |
343 | int end = 0; | |
344 | int link_update = 0; | |
345 | ||
346 | struct interfaces_device *ifdold; | |
347 | struct interfaces_device *ifdnew; | |
348 | struct interfaces_address *ifaold; | |
349 | struct interfaces_address *ifanew; | |
350 | char addr[INET6_ADDRSTRLEN + 1]; | |
351 | ||
352 | while (!end) { | |
353 | ssize_t len; | |
354 | struct nlmsghdr *msg; | |
355 | struct iovec iov = { | |
356 | .iov_base = reply, | |
357 | .iov_len = NETLINK_BUFFER | |
358 | }; | |
359 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; | |
360 | struct msghdr rtnl_reply = { | |
361 | .msg_iov = &iov, | |
362 | .msg_iovlen = 1, | |
363 | .msg_name = &peer, | |
364 | .msg_namelen = sizeof(struct sockaddr_nl) | |
365 | }; | |
366 | ||
367 | len = recvmsg(s, &rtnl_reply, 0); | |
368 | if (len == -1) { | |
369 | if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
370 | log_debug("netlink", "should have received something, but didn't"); | |
371 | return 0; | |
372 | } | |
373 | log_warnx("netlink", "unable to receive netlink answer"); | |
374 | return -1; | |
375 | } | |
376 | if (!len) return 0; | |
377 | for (msg = (struct nlmsghdr*)(void*)reply; | |
378 | NLMSG_OK(msg, len); | |
379 | msg = NLMSG_NEXT(msg, len)) { | |
380 | if (!(msg->nlmsg_flags & NLM_F_MULTI)) | |
381 | end = 1; | |
382 | switch (msg->nlmsg_type) { | |
383 | case NLMSG_DONE: | |
384 | log_debug("netlink", "received done message"); | |
385 | end = 1; | |
386 | break; | |
387 | case RTM_NEWLINK: | |
388 | case RTM_DELLINK: | |
389 | if (!ifs) break; | |
390 | log_debug("netlink", "received link information"); | |
391 | ifdnew = calloc(1, sizeof(struct interfaces_device)); | |
392 | if (ifdnew == NULL) { | |
393 | log_warn("netlink", "not enough memory for another interface, give up what we have"); | |
394 | goto end; | |
395 | } | |
396 | if (netlink_parse_link(msg, ifdnew) == 0) { | |
397 | /* We need to find if we already have this interface */ | |
398 | TAILQ_FOREACH(ifdold, ifs, next) { | |
399 | if (ifdold->index == ifdnew->index) break; | |
400 | } | |
401 | if (msg->nlmsg_type == RTM_NEWLINK) { | |
402 | if (ifdold == NULL) { | |
403 | log_debug("netlink", "interface %s is new", | |
404 | ifdnew->name); | |
405 | TAILQ_INSERT_TAIL(ifs, ifdnew, next); | |
406 | } else { | |
407 | log_debug("netlink", "interface %s/%s is updated", | |
408 | ifdold->name, ifdnew->name); | |
409 | TAILQ_INSERT_AFTER(ifs, ifdold, ifdnew, next); | |
410 | TAILQ_REMOVE(ifs, ifdold, next); | |
411 | interfaces_free_device(ifdold); | |
412 | } | |
413 | } else { | |
414 | if (ifdold == NULL) { | |
415 | log_warnx("netlink", | |
416 | "removal request for %s, but no knowledge of it", | |
417 | ifdnew->name); | |
418 | } else { | |
419 | log_debug("netlink", "interface %s is to be removed", | |
420 | ifdold->name); | |
421 | TAILQ_REMOVE(ifs, ifdold, next); | |
422 | interfaces_free_device(ifdold); | |
423 | } | |
424 | interfaces_free_device(ifdnew); | |
425 | } | |
426 | link_update = 1; | |
427 | } else { | |
428 | interfaces_free_device(ifdnew); | |
429 | } | |
430 | break; | |
431 | case RTM_NEWADDR: | |
432 | case RTM_DELADDR: | |
433 | if (!ifas) break; | |
434 | log_debug("netlink", "received address information"); | |
435 | ifanew = calloc(1, sizeof(struct interfaces_address)); | |
436 | if (ifanew == NULL) { | |
437 | log_warn("netlink", "not enough memory for another address, give what we have"); | |
438 | goto end; | |
439 | } | |
440 | if (netlink_parse_address(msg, ifanew) == 0) { | |
441 | TAILQ_FOREACH(ifaold, ifas, next) { | |
442 | if ((ifaold->index == ifanew->index) && | |
443 | !memcmp(&ifaold->address, &ifanew->address, | |
444 | sizeof(ifaold->address))) continue; | |
445 | } | |
446 | if (getnameinfo((struct sockaddr *)&ifanew->address, | |
447 | sizeof(ifanew->address), | |
448 | addr, sizeof(addr), | |
449 | NULL, 0, NI_NUMERICHOST) != 0) { | |
450 | strlcpy(addr, "(unknown)", sizeof(addr)); | |
451 | } | |
13181ede | 452 | |
0fa2254b VB |
453 | if (msg->nlmsg_type == RTM_NEWADDR) { |
454 | if (ifaold == NULL) { | |
455 | log_debug("netlink", "new address %s%%%d", | |
456 | addr, ifanew->index); | |
457 | TAILQ_INSERT_TAIL(ifas, ifanew, next); | |
458 | } else { | |
459 | log_debug("netlink", "updated address %s%%%d", | |
460 | addr, ifaold->index); | |
461 | TAILQ_INSERT_AFTER(ifas, ifaold, ifanew, next); | |
462 | TAILQ_REMOVE(ifas, ifaold, next); | |
463 | interfaces_free_address(ifaold); | |
464 | } | |
465 | } else { | |
466 | if (ifaold == NULL) { | |
467 | log_warnx("netlink", | |
468 | "removal request for address of %s%%%d, but no knowledge of it", | |
469 | addr, ifanew->index); | |
470 | } else { | |
471 | log_debug("netlink", "address %s%%%d is to be removed", | |
472 | addr, ifaold->index); | |
473 | TAILQ_REMOVE(ifas, ifaold, next); | |
474 | interfaces_free_address(ifaold); | |
475 | } | |
476 | interfaces_free_address(ifanew); | |
477 | } | |
478 | } else { | |
479 | interfaces_free_address(ifanew); | |
480 | } | |
481 | break; | |
482 | default: | |
483 | log_debug("netlink", | |
484 | "received unhandled message type %d (len: %d)", | |
485 | msg->nlmsg_type, msg->nlmsg_len); | |
486 | } | |
487 | } | |
13181ede | 488 | } |
0fa2254b VB |
489 | end: |
490 | if (link_update) { | |
491 | /* Fill out lower/upper */ | |
492 | struct interfaces_device *iface1, *iface2; | |
493 | TAILQ_FOREACH(iface1, ifs, next) { | |
494 | if (iface1->upper_idx != -1 && iface1->upper_idx != iface1->index) { | |
495 | TAILQ_FOREACH(iface2, ifs, next) { | |
496 | if (iface1->upper_idx == iface2->index) { | |
497 | iface1->upper = iface2; | |
498 | break; | |
499 | } | |
13181ede | 500 | } |
0fa2254b VB |
501 | } else { |
502 | iface1->upper = NULL; | |
13181ede | 503 | } |
0fa2254b VB |
504 | if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) { |
505 | TAILQ_FOREACH(iface2, ifs, next) { | |
506 | if (iface1->lower_idx == iface2->index) { | |
507 | if (iface2->lower_idx == iface1->index) { | |
508 | /* Workaround a bug introduced in Linux 4.1 */ | |
509 | iface2->lower_idx = iface2->index; | |
510 | iface1->lower_idx = iface1->index; | |
511 | } else iface1->lower = iface2; | |
512 | break; | |
7d0a4975 | 513 | } |
13181ede | 514 | } |
0fa2254b VB |
515 | } else { |
516 | iface1->lower = NULL; | |
13181ede | 517 | } |
0fa2254b | 518 | } |
13181ede | 519 | } |
0fa2254b VB |
520 | return 0; |
521 | } | |
13181ede | 522 | |
0fa2254b VB |
523 | static int |
524 | netlink_group_mask(int group) | |
525 | { | |
526 | return group ? (1 << (group - 1)) : 0; | |
e12c2365 VB |
527 | } |
528 | ||
529 | /** | |
0fa2254b | 530 | * Subscribe to link changes. |
e12c2365 | 531 | * |
0fa2254b | 532 | * @return The socket we should listen to for changes. |
e12c2365 | 533 | */ |
0fa2254b VB |
534 | int |
535 | netlink_subscribe_changes() | |
e12c2365 | 536 | { |
0fa2254b VB |
537 | unsigned int groups; |
538 | ||
539 | log_debug("netlink", "listening on interface changes"); | |
540 | ||
541 | groups = netlink_group_mask(RTNLGRP_LINK) | | |
542 | netlink_group_mask(RTNLGRP_IPV4_IFADDR) | | |
543 | netlink_group_mask(RTNLGRP_IPV6_IFADDR); | |
544 | ||
545 | return netlink_connect(NETLINK_ROUTE, groups); | |
546 | } | |
547 | ||
548 | /** | |
549 | * Receive changes from netlink */ | |
550 | static void | |
551 | netlink_change_cb(struct lldpd *cfg) | |
552 | { | |
553 | if (cfg->g_netlink == NULL) | |
554 | return; | |
555 | netlink_recv(cfg->g_netlink->nl_socket, | |
556 | cfg->g_netlink->devices, | |
557 | cfg->g_netlink->addresses); | |
558 | } | |
0484f180 | 559 | |
0fa2254b VB |
560 | /** |
561 | * Initialize netlink subsystem. | |
562 | * | |
563 | * This can be called several times but will have effect only the first time. | |
564 | * | |
565 | * @return 0 on success, -1 otherwise | |
566 | */ | |
567 | static int | |
568 | netlink_initialize(struct lldpd *cfg) | |
569 | { | |
570 | if (cfg->g_netlink) return 0; | |
aca48e4b | 571 | |
0fa2254b VB |
572 | log_debug("netlink", "initialize netlink subsystem"); |
573 | if ((cfg->g_netlink = calloc(sizeof(struct lldpd_netlink), 1)) == NULL) { | |
574 | log_warn("netlink", "unable to allocate memory for netlink subsystem"); | |
575 | goto end; | |
576 | } | |
577 | ||
578 | /* Connect to netlink (by requesting to get notified on updates) and | |
579 | * request updated information right now */ | |
580 | int s = cfg->g_netlink->nl_socket = netlink_subscribe_changes(); | |
581 | ||
582 | struct interfaces_address_list *ifaddrs = cfg->g_netlink->addresses = | |
583 | malloc(sizeof(struct interfaces_address_list)); | |
13181ede VB |
584 | if (ifaddrs == NULL) { |
585 | log_warn("netlink", "not enough memory for address list"); | |
0fa2254b | 586 | goto end; |
13181ede VB |
587 | } |
588 | TAILQ_INIT(ifaddrs); | |
589 | ||
0fa2254b VB |
590 | struct interfaces_device_list *ifs = cfg->g_netlink->devices = |
591 | malloc(sizeof(struct interfaces_device_list)); | |
592 | if (ifs == NULL) { | |
593 | log_warn("netlink", "not enough memory for interface list"); | |
594 | goto end; | |
595 | } | |
596 | TAILQ_INIT(ifs); | |
597 | ||
598 | if (netlink_send(s, RTM_GETADDR, AF_UNSPEC, 1) == -1) | |
599 | goto end; | |
600 | netlink_recv(s, NULL, ifaddrs); | |
601 | if (netlink_send(s, RTM_GETLINK, AF_PACKET, 2) == -1) | |
602 | goto end; | |
603 | netlink_recv(s, ifs, NULL); | |
604 | ||
605 | /* Listen to any future change */ | |
606 | cfg->g_iface_cb = netlink_change_cb; | |
607 | if (levent_iface_subscribe(cfg, s) == -1) { | |
608 | goto end; | |
609 | } | |
610 | ||
611 | return 0; | |
612 | end: | |
613 | netlink_cleanup(cfg); | |
614 | return -1; | |
615 | } | |
616 | ||
617 | /** | |
618 | * Cleanup netlink subsystem. | |
619 | */ | |
620 | void | |
621 | netlink_cleanup(struct lldpd *cfg) | |
622 | { | |
623 | if (cfg->g_netlink == NULL) return; | |
624 | if (cfg->g_netlink->nl_socket != -1) | |
625 | close(cfg->g_netlink->nl_socket); | |
626 | interfaces_free_devices(cfg->g_netlink->devices); | |
627 | interfaces_free_addresses(cfg->g_netlink->addresses); | |
628 | ||
629 | free(cfg->g_netlink); | |
630 | cfg->g_netlink = NULL; | |
631 | } | |
632 | ||
633 | /** | |
634 | * Receive the list of interfaces. | |
635 | * | |
636 | * @return a list of interfaces. | |
637 | */ | |
638 | struct interfaces_device_list* | |
639 | netlink_get_interfaces(struct lldpd *cfg) | |
640 | { | |
641 | if (netlink_initialize(cfg) == -1) return NULL; | |
642 | struct interfaces_device *ifd; | |
643 | TAILQ_FOREACH(ifd, cfg->g_netlink->devices, next) { | |
644 | ifd->ignore = 0; | |
13181ede | 645 | } |
0fa2254b VB |
646 | return cfg->g_netlink->devices; |
647 | } | |
aca48e4b | 648 | |
0fa2254b VB |
649 | /** |
650 | * Receive the list of addresses. | |
651 | * | |
652 | * @return a list of addresses. | |
653 | */ | |
654 | struct interfaces_address_list* | |
655 | netlink_get_addresses(struct lldpd *cfg) | |
656 | { | |
657 | if (netlink_initialize(cfg) == -1) return NULL; | |
658 | return cfg->g_netlink->addresses; | |
0484f180 | 659 | } |