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