]>
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 | ||
22 | #include <stdio.h> | |
23 | #include <unistd.h> | |
24 | #include <sys/types.h> | |
25 | #include <sys/socket.h> | |
adbb6e54 | 26 | #include <net/if_arp.h> |
e12c2365 VB |
27 | #include <linux/netlink.h> |
28 | #include <linux/rtnetlink.h> | |
29 | ||
30 | #define NETLINK_BUFFER 4096 | |
31 | ||
32 | struct netlink_req { | |
33 | struct nlmsghdr hdr; | |
34 | struct rtgenmsg gen; | |
35 | }; | |
36 | ||
37 | /** | |
38 | * Connect to netlink. | |
39 | * | |
40 | * Open a Netlink socket and connect to it. | |
41 | * | |
42 | * @param protocol Which protocol to use (eg NETLINK_ROUTE). | |
0484f180 | 43 | * @param groups Which groups we want to subscribe to |
e12c2365 VB |
44 | * @return The opened socket or -1 on error. |
45 | */ | |
46 | static int | |
0484f180 | 47 | netlink_connect(int protocol, unsigned groups) |
e12c2365 | 48 | { |
281a5cd4 | 49 | int s; |
e12c2365 VB |
50 | struct sockaddr_nl local = { |
51 | .nl_family = AF_NETLINK, | |
52 | .nl_pid = getpid(), | |
0484f180 | 53 | .nl_groups = groups |
e12c2365 VB |
54 | }; |
55 | ||
56 | /* Open Netlink socket */ | |
57 | log_debug("netlink", "opening netlink socket"); | |
58 | s = socket(AF_NETLINK, SOCK_RAW, protocol); | |
59 | if (s == -1) { | |
60 | log_warn("netlink", "unable to open netlink socket"); | |
61 | return -1; | |
62 | } | |
0484f180 | 63 | if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { |
e12c2365 VB |
64 | log_warn("netlink", "unable to bind netlink socket"); |
65 | close(s); | |
66 | return -1; | |
67 | } | |
68 | return s; | |
69 | } | |
70 | ||
71 | /** | |
72 | * Send a netlink message. | |
73 | * | |
74 | * The type of the message can be chosen as well the route family. The | |
75 | * mesage will always be NLM_F_REQUEST | NLM_F_DUMP. | |
76 | * | |
77 | * @param s the netlink socket | |
78 | * @param type the request type (eg RTM_GETLINK) | |
79 | * @param family the rt family (eg AF_PACKET) | |
80 | * @return 0 on success, -1 otherwise | |
81 | */ | |
82 | static int | |
83 | netlink_send(int s, int type, int family) | |
84 | { | |
85 | struct netlink_req req = { | |
86 | .hdr = { | |
87 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | |
de461f15 | 88 | .nlmsg_type = type, |
e12c2365 VB |
89 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, |
90 | .nlmsg_seq = 1, | |
91 | .nlmsg_pid = getpid() }, | |
de461f15 | 92 | .gen = { .rtgen_family = family } |
e12c2365 VB |
93 | }; |
94 | struct iovec iov = { | |
95 | .iov_base = &req, | |
96 | .iov_len = req.hdr.nlmsg_len | |
97 | }; | |
98 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; | |
99 | struct msghdr rtnl_msg = { | |
100 | .msg_iov = &iov, | |
101 | .msg_iovlen = 1, | |
102 | .msg_name = &peer, | |
103 | .msg_namelen = sizeof(struct sockaddr_nl) | |
104 | }; | |
105 | ||
106 | /* Send netlink message. This is synchronous but we are guaranteed | |
107 | * to not block. */ | |
108 | log_debug("netlink", "sending netlink message"); | |
109 | if (sendmsg(s, (struct msghdr *)&rtnl_msg, 0) == -1) { | |
110 | log_warn("netlink", "unable to send netlink message"); | |
111 | return -1; | |
112 | } | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
e12c2365 VB |
117 | /** |
118 | * Parse a `link` netlink message. | |
119 | * | |
120 | * @param msg message to be parsed | |
121 | * @param iff where to put the result | |
122 | * return 0 if the interface is worth it, -1 otherwise | |
123 | */ | |
124 | static int | |
125 | netlink_parse_link(struct nlmsghdr *msg, | |
adbb6e54 | 126 | struct interfaces_device *iff) |
e12c2365 | 127 | { |
2fafbd30 | 128 | struct ifinfomsg *ifi; |
e12c2365 VB |
129 | struct rtattr *attribute; |
130 | int len; | |
2fafbd30 | 131 | ifi = NLMSG_DATA(msg); |
e12c2365 VB |
132 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); |
133 | ||
c258a053 | 134 | if (!((ifi->ifi_flags & IFF_UP) && (ifi->ifi_flags & IFF_RUNNING))) { |
adbb6e54 VB |
135 | log_debug("netlink", "skip down interface at index %d", |
136 | ifi->ifi_index); | |
137 | return -1; | |
138 | } | |
139 | if (ifi->ifi_type != ARPHRD_ETHER) { | |
140 | log_debug("netlink", "skip non Ethernet interface at index %d", | |
141 | ifi->ifi_index); | |
142 | return -1; | |
143 | } | |
144 | ||
2fafbd30 | 145 | iff->index = ifi->ifi_index; |
2fafbd30 | 146 | iff->flags = ifi->ifi_flags; |
adbb6e54 VB |
147 | iff->lower_idx = -1; |
148 | iff->upper_idx = -1; | |
e12c2365 | 149 | |
2fafbd30 | 150 | for (attribute = IFLA_RTA(ifi); |
e12c2365 VB |
151 | RTA_OK(attribute, len); |
152 | attribute = RTA_NEXT(attribute, len)) { | |
153 | switch(attribute->rta_type) { | |
154 | case IFLA_IFNAME: | |
155 | /* Interface name */ | |
156 | iff->name = strdup(RTA_DATA(attribute)); | |
157 | break; | |
158 | case IFLA_IFALIAS: | |
159 | /* Interface alias */ | |
160 | iff->alias = strdup(RTA_DATA(attribute)); | |
161 | break; | |
162 | case IFLA_ADDRESS: | |
163 | /* Interface MAC address */ | |
164 | iff->address = malloc(RTA_PAYLOAD(attribute)); | |
165 | if (iff->address) | |
166 | memcpy(iff->address, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); | |
167 | break; | |
168 | case IFLA_LINK: | |
adbb6e54 VB |
169 | /* Index of "lower" interface */ |
170 | iff->lower_idx = *(int*)RTA_DATA(attribute); | |
e12c2365 VB |
171 | break; |
172 | case IFLA_MASTER: | |
173 | /* Index of master interface */ | |
adbb6e54 | 174 | iff->upper_idx = *(int*)RTA_DATA(attribute); |
e12c2365 VB |
175 | break; |
176 | case IFLA_TXQLEN: | |
177 | /* Transmit queue length */ | |
178 | iff->txqueue = *(int*)RTA_DATA(attribute); | |
179 | break; | |
180 | case IFLA_MTU: | |
181 | /* Maximum Transmission Unit */ | |
182 | iff->mtu = *(int*)RTA_DATA(attribute); | |
183 | break; | |
184 | default: | |
adbb6e54 | 185 | log_debug("netlink", "unhandled link attribute type %d for iface %s", |
e12c2365 VB |
186 | attribute->rta_type, iff->name ? iff->name : "(unknown)"); |
187 | break; | |
188 | } | |
189 | } | |
190 | if (!iff->name || !iff->address) { | |
191 | log_info("netlink", "interface %d does not have a name or an address, skip", | |
192 | iff->index); | |
193 | return -1; | |
194 | } | |
195 | return 0; | |
196 | } | |
197 | ||
198 | /** | |
199 | * Parse a `address` netlink message. | |
200 | * | |
201 | * @param msg message to be parsed | |
202 | * @param ifa where to put the result | |
203 | * return 0 if the address is worth it, -1 otherwise | |
204 | */ | |
205 | static int | |
206 | netlink_parse_address(struct nlmsghdr *msg, | |
adbb6e54 | 207 | struct interfaces_address *ifa) |
e12c2365 VB |
208 | { |
209 | struct ifaddrmsg *ifi; | |
210 | struct rtattr *attribute; | |
211 | int len; | |
212 | ifi = NLMSG_DATA(msg); | |
213 | len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); | |
214 | ||
215 | ifa->index = ifi->ifa_index; | |
216 | ifa->flags = ifi->ifa_flags; | |
217 | switch (ifi->ifa_family) { | |
218 | case AF_INET: | |
219 | case AF_INET6: break; | |
220 | default: | |
221 | log_debug("netlink", "got a non IP address on if %d (family: %d)", | |
222 | ifa->index, ifi->ifa_family); | |
223 | return -1; | |
224 | } | |
225 | ||
226 | for (attribute = IFA_RTA(ifi); | |
227 | RTA_OK(attribute, len); | |
228 | attribute = RTA_NEXT(attribute, len)) { | |
229 | switch(attribute->rta_type) { | |
230 | case IFA_ADDRESS: | |
231 | /* Address */ | |
232 | if (ifi->ifa_family == AF_INET) { | |
233 | struct sockaddr_in ip = { .sin_family = AF_INET }; | |
234 | memcpy(&ip.sin_addr, RTA_DATA(attribute), | |
235 | sizeof(struct in_addr)); | |
236 | memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in)); | |
237 | } else { | |
238 | struct sockaddr_in6 ip6 = { .sin6_family = AF_INET6 }; | |
239 | memcpy(&ip6.sin6_addr, RTA_DATA(attribute), | |
240 | sizeof(struct in6_addr)); | |
241 | memcpy(&ifa->address, &ip6, sizeof(struct sockaddr_in6)); | |
242 | } | |
243 | break; | |
244 | default: | |
adbb6e54 | 245 | log_debug("netlink", "unhandled address attribute type %d for iface %d", |
e12c2365 VB |
246 | attribute->rta_type, ifa->index); |
247 | break; | |
248 | } | |
249 | } | |
250 | if (ifa->address.ss_family == AF_UNSPEC) { | |
251 | log_debug("netlink", "no IP for interface %d", | |
252 | ifa->index); | |
253 | return -1; | |
254 | } | |
255 | return 0; | |
256 | } | |
257 | ||
258 | /** | |
259 | * Receive netlink answer from the kernel. | |
260 | * | |
261 | * @param s the netlink socket | |
262 | * @param ifs list to store interface list or NULL if we don't | |
263 | * @param ifas list to store address list or NULL if we don't | |
264 | * @return 0 on success, -1 on error | |
265 | */ | |
266 | static int | |
267 | netlink_recv(int s, | |
adbb6e54 VB |
268 | struct interfaces_device_list *ifs, |
269 | struct interfaces_address_list *ifas) | |
e12c2365 | 270 | { |
2fafbd30 | 271 | char reply[NETLINK_BUFFER] __attribute__ ((aligned)); |
e12c2365 VB |
272 | int end = 0; |
273 | ||
adbb6e54 VB |
274 | struct interfaces_device *iff; |
275 | struct interfaces_address *ifa; | |
e12c2365 VB |
276 | |
277 | while (!end) { | |
278 | int len; | |
279 | struct nlmsghdr *msg; | |
280 | struct iovec iov = { | |
281 | .iov_base = reply, | |
282 | .iov_len = NETLINK_BUFFER | |
283 | }; | |
284 | struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; | |
285 | struct msghdr rtnl_reply = { | |
286 | .msg_iov = &iov, | |
287 | .msg_iovlen = 1, | |
288 | .msg_name = &peer, | |
289 | .msg_namelen = sizeof(struct sockaddr_nl) | |
290 | }; | |
291 | ||
292 | len = recvmsg(s, &rtnl_reply, 0); | |
293 | if (len == -1) { | |
294 | log_warnx("netlink", "unable to receive netlink answer"); | |
295 | return -1; | |
296 | } | |
297 | if (!len) return 0; | |
2fafbd30 | 298 | for (msg = (struct nlmsghdr*)(void*)reply; |
e12c2365 VB |
299 | NLMSG_OK(msg, len); |
300 | msg = NLMSG_NEXT(msg, len)) { | |
301 | switch (msg->nlmsg_type) { | |
302 | case NLMSG_DONE: | |
303 | log_debug("netlink", "received end of dump message"); | |
304 | end = 1; | |
305 | break; | |
306 | case RTM_NEWLINK: | |
307 | if (!ifs) break; | |
308 | log_debug("netlink", "received link information"); | |
adbb6e54 | 309 | iff = calloc(1, sizeof(struct interfaces_device)); |
e12c2365 VB |
310 | if (iff == NULL) { |
311 | log_warn("netlink", "not enough memory for another interface, give what we have"); | |
312 | return 0; | |
313 | } | |
314 | if (netlink_parse_link(msg, iff) == 0) | |
315 | TAILQ_INSERT_TAIL(ifs, iff, next); | |
316 | else | |
adbb6e54 | 317 | interfaces_free_device(iff); |
e12c2365 VB |
318 | break; |
319 | case RTM_NEWADDR: | |
320 | if (!ifas) break; | |
321 | log_debug("netlink", "received address information"); | |
adbb6e54 | 322 | ifa = calloc(1, sizeof(struct interfaces_address)); |
e12c2365 VB |
323 | if (ifa == NULL) { |
324 | log_warn("netlink", "not enough memory for another address, give what we have"); | |
325 | return 0; | |
326 | } | |
327 | if (netlink_parse_address(msg, ifa) == 0) | |
328 | TAILQ_INSERT_TAIL(ifas, ifa, next); | |
329 | else | |
adbb6e54 | 330 | interfaces_free_address(ifa); |
e12c2365 VB |
331 | break; |
332 | default: | |
333 | log_debug("netlink", | |
334 | "received unhandled message type %d (len: %d)", | |
335 | msg->nlmsg_type, msg->nlmsg_len); | |
336 | } | |
337 | } | |
338 | } | |
339 | return 0; | |
340 | } | |
341 | ||
342 | /** | |
343 | * Receive the list of interfaces. | |
344 | * | |
345 | * @return a list of interfaces. | |
346 | */ | |
adbb6e54 | 347 | struct interfaces_device_list* |
e12c2365 VB |
348 | netlink_get_interfaces() |
349 | { | |
350 | int s; | |
adbb6e54 VB |
351 | struct interfaces_device_list *ifs; |
352 | struct interfaces_device *iface1, *iface2; | |
e12c2365 | 353 | |
0484f180 | 354 | if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1) |
e12c2365 VB |
355 | return NULL; |
356 | if (netlink_send(s, RTM_GETLINK, AF_PACKET) == -1) { | |
357 | close(s); | |
358 | return NULL; | |
359 | } | |
360 | ||
361 | log_debug("netlink", "get the list of available interfaces"); | |
adbb6e54 | 362 | ifs = malloc(sizeof(struct interfaces_device_list)); |
e12c2365 VB |
363 | if (ifs == NULL) { |
364 | log_warn("netlink", "not enough memory for interface list"); | |
3ee3f25d | 365 | close(s); |
e12c2365 VB |
366 | return NULL; |
367 | } | |
368 | TAILQ_INIT(ifs); | |
369 | netlink_recv(s, ifs, NULL); | |
370 | ||
adbb6e54 VB |
371 | /* Fill out lower/upper */ |
372 | TAILQ_FOREACH(iface1, ifs, next) { | |
373 | if (iface1->upper_idx != -1 && iface1->upper_idx != iface1->index) | |
374 | TAILQ_FOREACH(iface2, ifs, next) { | |
375 | if (iface1->upper_idx == iface2->index) { | |
376 | iface1->upper = iface2; | |
377 | break; | |
378 | } | |
379 | } | |
380 | if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) | |
381 | TAILQ_FOREACH(iface2, ifs, next) { | |
382 | if (iface1->lower_idx == iface2->index) { | |
383 | iface1->lower = iface2; | |
384 | break; | |
385 | } | |
386 | } | |
387 | } | |
388 | ||
e12c2365 VB |
389 | close(s); |
390 | return ifs; | |
391 | } | |
392 | ||
393 | /** | |
394 | * Receive the list of addresses. | |
395 | * | |
396 | * @return a list of addresses. | |
397 | */ | |
adbb6e54 | 398 | struct interfaces_address_list* |
e12c2365 VB |
399 | netlink_get_addresses() |
400 | { | |
401 | int s; | |
adbb6e54 | 402 | struct interfaces_address_list *ifaddrs; |
e12c2365 | 403 | |
0484f180 | 404 | if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1) |
e12c2365 VB |
405 | return NULL; |
406 | if (netlink_send(s, RTM_GETADDR, AF_UNSPEC) == -1) { | |
407 | close(s); | |
408 | return NULL; | |
409 | } | |
410 | ||
411 | log_debug("netlink", "get the list of available addresses"); | |
adbb6e54 | 412 | ifaddrs = malloc(sizeof(struct interfaces_address_list)); |
e12c2365 VB |
413 | if (ifaddrs == NULL) { |
414 | log_warn("netlink", "not enough memory for address list"); | |
3ee3f25d | 415 | close(s); |
e12c2365 VB |
416 | return NULL; |
417 | } | |
418 | TAILQ_INIT(ifaddrs); | |
419 | netlink_recv(s, NULL, ifaddrs); | |
420 | ||
421 | close(s); | |
422 | return ifaddrs; | |
423 | } | |
0484f180 VB |
424 | |
425 | /** | |
426 | * Subscribe to link changes. | |
427 | * | |
428 | * @return The socket we should listen to for changes. | |
429 | */ | |
430 | int | |
431 | netlink_subscribe_changes() | |
432 | { | |
433 | log_debug("netlink", "listening on interface changes"); | |
434 | return netlink_connect(NETLINK_ROUTE, RTMGRP_LINK); | |
435 | } |