]>
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; | |
5f658dac VB |
214 | } else if (!strcmp(kind, "team")) { |
215 | log_debug("netlink", "interface %s is a team", | |
216 | iff->name); | |
217 | iff->type |= IFACE_BOND_T; | |
0fa2254b VB |
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 | 317 | } |
92c6dcfe VB |
318 | if (iff->upper_idx == -1) { |
319 | /* No upper interface, we cannot be enslaved. We need to clear | |
320 | * the flag because the appropriate information may come later | |
321 | * and we don't want to miss it. */ | |
322 | iff->flags &= ~IFF_SLAVE; | |
323 | } | |
13181ede | 324 | |
2b9ecd2a VB |
325 | if (ifi->ifi_family == AF_BRIDGE && msg->nlmsg_type == RTM_DELLINK && iff->upper_idx != -1) { |
326 | log_debug("netlink", "removal of %s from bridge %d", | |
327 | iff->name, iff->upper_idx); | |
328 | msg->nlmsg_type = RTM_NEWLINK; | |
329 | iff->upper_idx = -1; | |
330 | } else if (ifi->ifi_family != 0) { | |
331 | log_debug("netlink", "skip non-generic message update %d at index %d", | |
332 | ifi->ifi_type, ifi->ifi_index); | |
333 | return -1; | |
334 | } | |
335 | ||
0fa2254b VB |
336 | log_debug("netlink", "parsed link %d (%s, flags: %d)", |
337 | iff->index, iff->name, iff->flags); | |
338 | return 0; | |
e12c2365 VB |
339 | } |
340 | ||
341 | /** | |
13181ede | 342 | * Parse a `address` netlink message. |
e12c2365 | 343 | * |
0fa2254b VB |
344 | * @param msg message to be parsed |
345 | * @param ifa where to put the result | |
346 | * return 0 if the address is worth it, -1 otherwise | |
e12c2365 | 347 | */ |
0fa2254b VB |
348 | static int |
349 | netlink_parse_address(struct nlmsghdr *msg, | |
350 | struct interfaces_address *ifa) | |
e12c2365 | 351 | { |
0fa2254b VB |
352 | struct ifaddrmsg *ifi; |
353 | struct rtattr *attribute; | |
354 | int len; | |
355 | ifi = NLMSG_DATA(msg); | |
356 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); | |
357 | ||
358 | ifa->index = ifi->ifa_index; | |
359 | ifa->flags = ifi->ifa_flags; | |
360 | switch (ifi->ifa_family) { | |
13181ede VB |
361 | case AF_INET: |
362 | case AF_INET6: break; | |
363 | default: | |
364 | log_debug("netlink", "got a non IP address on if %d (family: %d)", | |
0fa2254b VB |
365 | ifa->index, ifi->ifa_family); |
366 | return -1; | |
13181ede VB |
367 | } |
368 | ||
0fa2254b VB |
369 | for (attribute = IFA_RTA(ifi); |
370 | RTA_OK(attribute, len); | |
371 | attribute = RTA_NEXT(attribute, len)) { | |
372 | switch(attribute->rta_type) { | |
373 | case IFA_ADDRESS: | |
374 | /* Address */ | |
375 | if (ifi->ifa_family == AF_INET) { | |
376 | struct sockaddr_in ip; | |
377 | memset(&ip, 0, sizeof(struct sockaddr_in)); | |
378 | ip.sin_family = AF_INET; | |
379 | memcpy(&ip.sin_addr, RTA_DATA(attribute), | |
380 | sizeof(struct in_addr)); | |
381 | memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in)); | |
382 | } else { | |
383 | struct sockaddr_in6 ip6; | |
384 | memset(&ip6, 0, sizeof(struct sockaddr_in6)); | |
385 | ip6.sin6_family = AF_INET6; | |
386 | memcpy(&ip6.sin6_addr, RTA_DATA(attribute), | |
387 | sizeof(struct in6_addr)); | |
388 | memcpy(&ifa->address, &ip6, sizeof(struct sockaddr_in6)); | |
389 | } | |
390 | break; | |
391 | default: | |
392 | log_debug("netlink", "unhandled address attribute type %d for iface %d", | |
393 | attribute->rta_type, ifa->index); | |
394 | break; | |
395 | } | |
13181ede | 396 | } |
0fa2254b | 397 | if (ifa->address.ss_family == AF_UNSPEC) { |
13181ede VB |
398 | log_debug("netlink", "no IP for interface %d", |
399 | ifa->index); | |
0fa2254b | 400 | return -1; |
13181ede | 401 | } |
0fa2254b | 402 | return 0; |
e12c2365 VB |
403 | } |
404 | ||
58e30b5a VB |
405 | /** |
406 | * Merge an old interface with a new one. | |
407 | * | |
408 | * Some properties may be absent in the new interface that should be copied over | |
409 | * from the old one. | |
410 | */ | |
411 | void | |
412 | netlink_merge(struct interfaces_device *old, struct interfaces_device *new) | |
413 | { | |
414 | if (new->alias == NULL) { | |
415 | new->alias = old->alias; | |
416 | old->alias = NULL; | |
417 | } | |
418 | if (new->address == NULL) { | |
419 | new->address = old->address; | |
420 | old->address = NULL; | |
421 | } | |
422 | if (new->mtu == 0) | |
423 | new->mtu = old->mtu; | |
424 | if (new->type == 0) | |
425 | new->type = old->type; | |
426 | if (new->vlanid == 0) | |
427 | new->vlanid = old->vlanid; | |
d2e6f750 VB |
428 | |
429 | /* It's not possible for lower link to change */ | |
430 | new->lower_idx = old->lower_idx; | |
58e30b5a VB |
431 | } |
432 | ||
e12c2365 | 433 | /** |
0fa2254b | 434 | * Receive netlink answer from the kernel. |
e12c2365 | 435 | * |
0fa2254b VB |
436 | * @param ifs list to store interface list or NULL if we don't |
437 | * @param ifas list to store address list or NULL if we don't | |
438 | * @return 0 on success, -1 on error | |
e12c2365 | 439 | */ |
0fa2254b | 440 | static int |
960541eb | 441 | netlink_recv(struct lldpd *cfg, |
0fa2254b | 442 | struct interfaces_device_list *ifs, |
960541eb | 443 | struct interfaces_address_list *ifas) |
e12c2365 | 444 | { |
85b72fe0 DM |
445 | int end = 0, ret = 0; |
446 | int flags = MSG_PEEK | MSG_TRUNC; | |
447 | struct iovec iov; | |
0fa2254b | 448 | int link_update = 0; |
960541eb | 449 | int s = cfg->g_netlink->nl_socket; |
0fa2254b VB |
450 | |
451 | struct interfaces_device *ifdold; | |
452 | struct interfaces_device *ifdnew; | |
453 | struct interfaces_address *ifaold; | |
454 | struct interfaces_address *ifanew; | |
455 | char addr[INET6_ADDRSTRLEN + 1]; | |
456 | ||
85b72fe0 DM |
457 | iov.iov_len = NETLINK_BUFFER; |
458 | iov.iov_base = malloc(iov.iov_len); | |
459 | if (!iov.iov_base) { | |
460 | log_warn("netlink", "not enough memory"); | |
461 | return -1; | |
462 | } | |
463 | ||
0fa2254b VB |
464 | while (!end) { |
465 | ssize_t len; | |
466 | struct nlmsghdr *msg; | |
0fa2254b VB |
467 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; |
468 | struct msghdr rtnl_reply = { | |
469 | .msg_iov = &iov, | |
470 | .msg_iovlen = 1, | |
471 | .msg_name = &peer, | |
472 | .msg_namelen = sizeof(struct sockaddr_nl) | |
473 | }; | |
474 | ||
85b72fe0 DM |
475 | retry: |
476 | len = recvmsg(s, &rtnl_reply, flags); | |
0fa2254b VB |
477 | if (len == -1) { |
478 | if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
479 | log_debug("netlink", "should have received something, but didn't"); | |
85b72fe0 DM |
480 | ret = 0; |
481 | goto out; | |
0fa2254b | 482 | } |
960541eb VB |
483 | int rsize = cfg->g_netlink->nl_socket_recv_size; |
484 | if (errno == ENOBUFS && | |
485 | rsize > 0 && rsize < NETLINK_MAX_RECEIVE_BUFSIZE) { | |
486 | /* Try to increase buffer size */ | |
487 | rsize *= 2; | |
488 | if (rsize > NETLINK_MAX_RECEIVE_BUFSIZE) { | |
489 | rsize = NETLINK_MAX_RECEIVE_BUFSIZE; | |
490 | } | |
491 | int rc = netlink_socket_set_buffer_size(s, | |
492 | SO_RCVBUF, "SO_RCVBUF", | |
493 | rsize); | |
494 | if (rc < 0) | |
495 | cfg->g_netlink->nl_socket_recv_size = 0; | |
496 | else | |
497 | cfg->g_netlink->nl_socket_recv_size = rsize; | |
498 | if (rc > 0 || rc == -2) { | |
499 | log_info("netlink", | |
500 | "netlink receive buffer too small, retry with larger one (%d)", | |
501 | rsize); | |
0cb979c7 VB |
502 | flags = 0; |
503 | goto retry; | |
0cb979c7 VB |
504 | } |
505 | } | |
644cd42d | 506 | log_warn("netlink", "unable to receive netlink answer"); |
85b72fe0 DM |
507 | ret = -1; |
508 | goto out; | |
0fa2254b | 509 | } |
85b72fe0 DM |
510 | if (!len) { |
511 | ret = 0; | |
512 | goto out; | |
513 | } | |
514 | ||
515 | if (iov.iov_len < len || (rtnl_reply.msg_flags & MSG_TRUNC)) { | |
516 | void *tmp; | |
517 | ||
518 | /* Provided buffer is not large enough, enlarge it | |
519 | * to size of len (which should be total length of the message) | |
520 | * and try again. */ | |
521 | iov.iov_len = len; | |
522 | tmp = realloc(iov.iov_base, iov.iov_len); | |
523 | if (!tmp) { | |
524 | log_warn("netlink", "not enough memory"); | |
525 | ret = -1; | |
526 | goto out; | |
527 | } | |
528 | log_debug("netlink", "enlarge message size to %zu bytes", len); | |
529 | iov.iov_base = tmp; | |
530 | flags = 0; | |
531 | goto retry; | |
532 | } | |
533 | ||
534 | if (flags != 0) { | |
535 | /* Buffer is big enough, do the actual reading */ | |
536 | flags = 0; | |
537 | goto retry; | |
538 | } | |
539 | ||
540 | for (msg = (struct nlmsghdr*)(void*)(iov.iov_base); | |
0fa2254b VB |
541 | NLMSG_OK(msg, len); |
542 | msg = NLMSG_NEXT(msg, len)) { | |
543 | if (!(msg->nlmsg_flags & NLM_F_MULTI)) | |
544 | end = 1; | |
545 | switch (msg->nlmsg_type) { | |
546 | case NLMSG_DONE: | |
547 | log_debug("netlink", "received done message"); | |
548 | end = 1; | |
549 | break; | |
550 | case RTM_NEWLINK: | |
551 | case RTM_DELLINK: | |
552 | if (!ifs) break; | |
553 | log_debug("netlink", "received link information"); | |
554 | ifdnew = calloc(1, sizeof(struct interfaces_device)); | |
555 | if (ifdnew == NULL) { | |
556 | log_warn("netlink", "not enough memory for another interface, give up what we have"); | |
557 | goto end; | |
558 | } | |
559 | if (netlink_parse_link(msg, ifdnew) == 0) { | |
560 | /* We need to find if we already have this interface */ | |
561 | TAILQ_FOREACH(ifdold, ifs, next) { | |
562 | if (ifdold->index == ifdnew->index) break; | |
563 | } | |
703f6a0d | 564 | |
0fa2254b VB |
565 | if (msg->nlmsg_type == RTM_NEWLINK) { |
566 | if (ifdold == NULL) { | |
567 | log_debug("netlink", "interface %s is new", | |
568 | ifdnew->name); | |
569 | TAILQ_INSERT_TAIL(ifs, ifdnew, next); | |
570 | } else { | |
571 | log_debug("netlink", "interface %s/%s is updated", | |
572 | ifdold->name, ifdnew->name); | |
58e30b5a | 573 | netlink_merge(ifdold, ifdnew); |
0fa2254b VB |
574 | TAILQ_INSERT_AFTER(ifs, ifdold, ifdnew, next); |
575 | TAILQ_REMOVE(ifs, ifdold, next); | |
576 | interfaces_free_device(ifdold); | |
577 | } | |
578 | } else { | |
579 | if (ifdold == NULL) { | |
580 | log_warnx("netlink", | |
581 | "removal request for %s, but no knowledge of it", | |
703f6a0d | 582 | ifdnew->name); |
0fa2254b VB |
583 | } else { |
584 | log_debug("netlink", "interface %s is to be removed", | |
585 | ifdold->name); | |
586 | TAILQ_REMOVE(ifs, ifdold, next); | |
587 | interfaces_free_device(ifdold); | |
588 | } | |
589 | interfaces_free_device(ifdnew); | |
590 | } | |
591 | link_update = 1; | |
592 | } else { | |
593 | interfaces_free_device(ifdnew); | |
594 | } | |
595 | break; | |
596 | case RTM_NEWADDR: | |
597 | case RTM_DELADDR: | |
598 | if (!ifas) break; | |
599 | log_debug("netlink", "received address information"); | |
600 | ifanew = calloc(1, sizeof(struct interfaces_address)); | |
601 | if (ifanew == NULL) { | |
602 | log_warn("netlink", "not enough memory for another address, give what we have"); | |
603 | goto end; | |
604 | } | |
605 | if (netlink_parse_address(msg, ifanew) == 0) { | |
606 | TAILQ_FOREACH(ifaold, ifas, next) { | |
607 | if ((ifaold->index == ifanew->index) && | |
608 | !memcmp(&ifaold->address, &ifanew->address, | |
878441b2 | 609 | sizeof(ifaold->address))) break; |
0fa2254b VB |
610 | } |
611 | if (getnameinfo((struct sockaddr *)&ifanew->address, | |
612 | sizeof(ifanew->address), | |
613 | addr, sizeof(addr), | |
614 | NULL, 0, NI_NUMERICHOST) != 0) { | |
615 | strlcpy(addr, "(unknown)", sizeof(addr)); | |
616 | } | |
13181ede | 617 | |
0fa2254b VB |
618 | if (msg->nlmsg_type == RTM_NEWADDR) { |
619 | if (ifaold == NULL) { | |
620 | log_debug("netlink", "new address %s%%%d", | |
621 | addr, ifanew->index); | |
622 | TAILQ_INSERT_TAIL(ifas, ifanew, next); | |
623 | } else { | |
624 | log_debug("netlink", "updated address %s%%%d", | |
625 | addr, ifaold->index); | |
626 | TAILQ_INSERT_AFTER(ifas, ifaold, ifanew, next); | |
627 | TAILQ_REMOVE(ifas, ifaold, next); | |
628 | interfaces_free_address(ifaold); | |
629 | } | |
630 | } else { | |
631 | if (ifaold == NULL) { | |
8027d907 | 632 | log_info("netlink", |
0fa2254b VB |
633 | "removal request for address of %s%%%d, but no knowledge of it", |
634 | addr, ifanew->index); | |
635 | } else { | |
636 | log_debug("netlink", "address %s%%%d is to be removed", | |
637 | addr, ifaold->index); | |
638 | TAILQ_REMOVE(ifas, ifaold, next); | |
639 | interfaces_free_address(ifaold); | |
640 | } | |
641 | interfaces_free_address(ifanew); | |
642 | } | |
643 | } else { | |
644 | interfaces_free_address(ifanew); | |
645 | } | |
646 | break; | |
647 | default: | |
648 | log_debug("netlink", | |
649 | "received unhandled message type %d (len: %d)", | |
650 | msg->nlmsg_type, msg->nlmsg_len); | |
651 | } | |
652 | } | |
85b72fe0 | 653 | flags = MSG_PEEK | MSG_TRUNC; |
13181ede | 654 | } |
0fa2254b VB |
655 | end: |
656 | if (link_update) { | |
657 | /* Fill out lower/upper */ | |
658 | struct interfaces_device *iface1, *iface2; | |
659 | TAILQ_FOREACH(iface1, ifs, next) { | |
660 | if (iface1->upper_idx != -1 && iface1->upper_idx != iface1->index) { | |
661 | TAILQ_FOREACH(iface2, ifs, next) { | |
662 | if (iface1->upper_idx == iface2->index) { | |
c04fafa7 VB |
663 | log_debug("netlink", |
664 | "upper interface for %s is %s", | |
665 | iface1->name, iface2->name); | |
0fa2254b VB |
666 | iface1->upper = iface2; |
667 | break; | |
668 | } | |
13181ede | 669 | } |
100266c1 VB |
670 | if (iface2 == NULL) |
671 | iface1->upper = NULL; | |
0fa2254b VB |
672 | } else { |
673 | iface1->upper = NULL; | |
13181ede | 674 | } |
0fa2254b VB |
675 | if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) { |
676 | TAILQ_FOREACH(iface2, ifs, next) { | |
677 | if (iface1->lower_idx == iface2->index) { | |
100266c1 VB |
678 | /* Workaround a bug introduced |
679 | * in Linux 4.1: a pair of veth | |
680 | * will be lower interface of | |
681 | * each other. Do not modify | |
682 | * index as if one of them is | |
683 | * updated, we will loose the | |
684 | * information about the | |
685 | * loop. */ | |
0fa2254b | 686 | if (iface2->lower_idx == iface1->index) { |
100266c1 | 687 | iface1->lower = NULL; |
c04fafa7 VB |
688 | log_debug("netlink", |
689 | "link loop detected between %s and %s", | |
690 | iface1->name, iface2->name); | |
691 | } else { | |
692 | log_debug("netlink", | |
693 | "lower interface for %s is %s", | |
694 | iface1->name, iface2->name); | |
695 | iface1->lower = iface2; | |
696 | } | |
0fa2254b | 697 | break; |
7d0a4975 | 698 | } |
100266c1 VB |
699 | if (iface2 == NULL) |
700 | iface1->lower = NULL; | |
13181ede | 701 | } |
0fa2254b VB |
702 | } else { |
703 | iface1->lower = NULL; | |
13181ede | 704 | } |
0fa2254b | 705 | } |
13181ede | 706 | } |
85b72fe0 DM |
707 | |
708 | out: | |
709 | free(iov.iov_base); | |
710 | return ret; | |
0fa2254b | 711 | } |
13181ede | 712 | |
0fa2254b VB |
713 | static int |
714 | netlink_group_mask(int group) | |
715 | { | |
716 | return group ? (1 << (group - 1)) : 0; | |
e12c2365 VB |
717 | } |
718 | ||
719 | /** | |
0fa2254b | 720 | * Subscribe to link changes. |
e12c2365 | 721 | * |
960541eb | 722 | * @return 0 on success, -1 otherwise |
e12c2365 | 723 | */ |
960541eb VB |
724 | static int |
725 | netlink_subscribe_changes(struct lldpd *cfg) | |
e12c2365 | 726 | { |
0fa2254b VB |
727 | unsigned int groups; |
728 | ||
729 | log_debug("netlink", "listening on interface changes"); | |
730 | ||
731 | groups = netlink_group_mask(RTNLGRP_LINK) | | |
732 | netlink_group_mask(RTNLGRP_IPV4_IFADDR) | | |
733 | netlink_group_mask(RTNLGRP_IPV6_IFADDR); | |
734 | ||
960541eb | 735 | return netlink_connect(cfg, NETLINK_ROUTE, groups); |
0fa2254b VB |
736 | } |
737 | ||
738 | /** | |
739 | * Receive changes from netlink */ | |
740 | static void | |
741 | netlink_change_cb(struct lldpd *cfg) | |
742 | { | |
743 | if (cfg->g_netlink == NULL) | |
744 | return; | |
960541eb | 745 | netlink_recv(cfg, |
0fa2254b | 746 | cfg->g_netlink->devices, |
960541eb | 747 | cfg->g_netlink->addresses); |
0fa2254b | 748 | } |
0484f180 | 749 | |
0fa2254b VB |
750 | /** |
751 | * Initialize netlink subsystem. | |
752 | * | |
753 | * This can be called several times but will have effect only the first time. | |
754 | * | |
755 | * @return 0 on success, -1 otherwise | |
756 | */ | |
757 | static int | |
758 | netlink_initialize(struct lldpd *cfg) | |
759 | { | |
760 | if (cfg->g_netlink) return 0; | |
aca48e4b | 761 | |
0fa2254b VB |
762 | log_debug("netlink", "initialize netlink subsystem"); |
763 | if ((cfg->g_netlink = calloc(sizeof(struct lldpd_netlink), 1)) == NULL) { | |
764 | log_warn("netlink", "unable to allocate memory for netlink subsystem"); | |
765 | goto end; | |
766 | } | |
767 | ||
768 | /* Connect to netlink (by requesting to get notified on updates) and | |
769 | * request updated information right now */ | |
960541eb VB |
770 | if (netlink_subscribe_changes(cfg) == -1) |
771 | goto end; | |
0fa2254b VB |
772 | |
773 | struct interfaces_address_list *ifaddrs = cfg->g_netlink->addresses = | |
774 | malloc(sizeof(struct interfaces_address_list)); | |
13181ede VB |
775 | if (ifaddrs == NULL) { |
776 | log_warn("netlink", "not enough memory for address list"); | |
0fa2254b | 777 | goto end; |
13181ede VB |
778 | } |
779 | TAILQ_INIT(ifaddrs); | |
780 | ||
0fa2254b VB |
781 | struct interfaces_device_list *ifs = cfg->g_netlink->devices = |
782 | malloc(sizeof(struct interfaces_device_list)); | |
783 | if (ifs == NULL) { | |
784 | log_warn("netlink", "not enough memory for interface list"); | |
785 | goto end; | |
786 | } | |
787 | TAILQ_INIT(ifs); | |
788 | ||
960541eb | 789 | if (netlink_send(cfg->g_netlink->nl_socket, RTM_GETADDR, AF_UNSPEC, 1) == -1) |
0fa2254b | 790 | goto end; |
960541eb VB |
791 | netlink_recv(cfg, NULL, ifaddrs); |
792 | if (netlink_send(cfg->g_netlink->nl_socket, RTM_GETLINK, AF_PACKET, 2) == -1) | |
0fa2254b | 793 | goto end; |
960541eb | 794 | netlink_recv(cfg, ifs, NULL); |
0fa2254b VB |
795 | |
796 | /* Listen to any future change */ | |
797 | cfg->g_iface_cb = netlink_change_cb; | |
960541eb | 798 | if (levent_iface_subscribe(cfg, cfg->g_netlink->nl_socket) == -1) { |
0fa2254b VB |
799 | goto end; |
800 | } | |
801 | ||
802 | return 0; | |
803 | end: | |
804 | netlink_cleanup(cfg); | |
805 | return -1; | |
806 | } | |
807 | ||
808 | /** | |
809 | * Cleanup netlink subsystem. | |
810 | */ | |
811 | void | |
812 | netlink_cleanup(struct lldpd *cfg) | |
813 | { | |
814 | if (cfg->g_netlink == NULL) return; | |
815 | if (cfg->g_netlink->nl_socket != -1) | |
816 | close(cfg->g_netlink->nl_socket); | |
817 | interfaces_free_devices(cfg->g_netlink->devices); | |
818 | interfaces_free_addresses(cfg->g_netlink->addresses); | |
819 | ||
820 | free(cfg->g_netlink); | |
821 | cfg->g_netlink = NULL; | |
822 | } | |
823 | ||
824 | /** | |
825 | * Receive the list of interfaces. | |
826 | * | |
827 | * @return a list of interfaces. | |
828 | */ | |
829 | struct interfaces_device_list* | |
830 | netlink_get_interfaces(struct lldpd *cfg) | |
831 | { | |
832 | if (netlink_initialize(cfg) == -1) return NULL; | |
833 | struct interfaces_device *ifd; | |
834 | TAILQ_FOREACH(ifd, cfg->g_netlink->devices, next) { | |
835 | ifd->ignore = 0; | |
13181ede | 836 | } |
0fa2254b VB |
837 | return cfg->g_netlink->devices; |
838 | } | |
aca48e4b | 839 | |
0fa2254b VB |
840 | /** |
841 | * Receive the list of addresses. | |
842 | * | |
843 | * @return a list of addresses. | |
844 | */ | |
845 | struct interfaces_address_list* | |
846 | netlink_get_addresses(struct lldpd *cfg) | |
847 | { | |
848 | if (netlink_initialize(cfg) == -1) return NULL; | |
849 | return cfg->g_netlink->addresses; | |
0484f180 | 850 | } |