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