/src/samba/lib/socket/interfaces.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | return a list of network interfaces |
4 | | Copyright (C) Andrew Tridgell 1998 |
5 | | Copyright (C) Jeremy Allison 2007 |
6 | | Copyright (C) Jelmer Vernooij 2007 |
7 | | |
8 | | This program is free software; you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation; either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | */ |
21 | | |
22 | | |
23 | | #include "includes.h" |
24 | | #include "system/network.h" |
25 | | #include "interfaces.h" |
26 | | #include "lib/util/tsort.h" |
27 | | #include "librpc/gen_ndr/ioctl.h" |
28 | | |
29 | | #ifdef HAVE_ETHTOOL |
30 | | #include "linux/sockios.h" |
31 | | #include "linux/ethtool.h" |
32 | | #endif |
33 | | |
34 | | /**************************************************************************** |
35 | | Create a struct sockaddr_storage with the netmask bits set to 1. |
36 | | ****************************************************************************/ |
37 | | |
38 | | bool make_netmask(struct sockaddr_storage *pss_out, |
39 | | const struct sockaddr_storage *pss_in, |
40 | | unsigned long masklen) |
41 | 0 | { |
42 | 0 | *pss_out = *pss_in; |
43 | | /* Now apply masklen bits of mask. */ |
44 | 0 | #if defined(HAVE_IPV6) |
45 | 0 | if (pss_in->ss_family == AF_INET6) { |
46 | 0 | char *p = (char *)&((struct sockaddr_in6 *)pss_out)->sin6_addr; |
47 | 0 | unsigned int i; |
48 | |
|
49 | 0 | if (masklen > 128) { |
50 | 0 | return false; |
51 | 0 | } |
52 | 0 | for (i = 0; masklen >= 8; masklen -= 8, i++) { |
53 | 0 | *p++ = 0xff; |
54 | 0 | } |
55 | | /* Deal with the partial byte. */ |
56 | 0 | *p++ &= (0xff & ~(0xff>>masklen)); |
57 | 0 | i++; |
58 | 0 | for (;i < sizeof(struct in6_addr); i++) { |
59 | 0 | *p++ = '\0'; |
60 | 0 | } |
61 | 0 | return true; |
62 | 0 | } |
63 | 0 | #endif |
64 | 0 | if (pss_in->ss_family == AF_INET) { |
65 | 0 | if (masklen > 32) { |
66 | 0 | return false; |
67 | 0 | } |
68 | 0 | ((struct sockaddr_in *)pss_out)->sin_addr.s_addr = |
69 | 0 | htonl(((0xFFFFFFFFL >> masklen) ^ 0xFFFFFFFFL)); |
70 | 0 | return true; |
71 | 0 | } |
72 | 0 | return false; |
73 | 0 | } |
74 | | |
75 | | /**************************************************************************** |
76 | | Create a struct sockaddr_storage set to the broadcast or network address from |
77 | | an incoming sockaddr_storage. |
78 | | ****************************************************************************/ |
79 | | |
80 | | static void make_bcast_or_net(struct sockaddr_storage *pss_out, |
81 | | const struct sockaddr_storage *pss_in, |
82 | | const struct sockaddr_storage *nmask, |
83 | | bool make_bcast_p) |
84 | 0 | { |
85 | 0 | unsigned int i = 0, len = 0; |
86 | 0 | const char *pmask = NULL; |
87 | 0 | char *p = NULL; |
88 | 0 | *pss_out = *pss_in; |
89 | | |
90 | | /* Set all zero netmask bits to 1. */ |
91 | 0 | #if defined(HAVE_IPV6) |
92 | 0 | if (pss_in->ss_family == AF_INET6) { |
93 | 0 | p = (char *)&((struct sockaddr_in6 *)pss_out)->sin6_addr; |
94 | 0 | pmask = (const char *)&((const struct sockaddr_in6 *)nmask)->sin6_addr; |
95 | 0 | len = 16; |
96 | 0 | } |
97 | 0 | #endif |
98 | 0 | if (pss_in->ss_family == AF_INET) { |
99 | 0 | p = (char *)&((struct sockaddr_in *)pss_out)->sin_addr; |
100 | 0 | pmask = (const char *)&((const struct sockaddr_in *)nmask)->sin_addr; |
101 | 0 | len = 4; |
102 | 0 | } |
103 | |
|
104 | 0 | for (i = 0; i < len; i++, p++, pmask++) { |
105 | 0 | if (make_bcast_p) { |
106 | 0 | *p = (*p & *pmask) | (*pmask ^ 0xff); |
107 | 0 | } else { |
108 | | /* make_net */ |
109 | 0 | *p = (*p & *pmask); |
110 | 0 | } |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | void make_bcast(struct sockaddr_storage *pss_out, |
115 | | const struct sockaddr_storage *pss_in, |
116 | | const struct sockaddr_storage *nmask) |
117 | 0 | { |
118 | 0 | make_bcast_or_net(pss_out, pss_in, nmask, true); |
119 | 0 | } |
120 | | |
121 | | void make_net(struct sockaddr_storage *pss_out, |
122 | | const struct sockaddr_storage *pss_in, |
123 | | const struct sockaddr_storage *nmask) |
124 | 0 | { |
125 | 0 | make_bcast_or_net(pss_out, pss_in, nmask, false); |
126 | 0 | } |
127 | | |
128 | | #ifdef HAVE_ETHTOOL |
129 | | static void query_iface_speed_from_name(const char *name, uint64_t *speed) |
130 | 0 | { |
131 | 0 | int ret = 0; |
132 | 0 | struct ethtool_cmd ecmd; |
133 | 0 | struct ethtool_value edata; |
134 | 0 | struct ifreq ifr; |
135 | 0 | int fd; |
136 | |
|
137 | 0 | fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); |
138 | 0 | if (fd == -1) { |
139 | 0 | DBG_ERR("Failed to open socket.\n"); |
140 | 0 | return; |
141 | 0 | } |
142 | | |
143 | 0 | if (strlen(name) >= IF_NAMESIZE) { |
144 | 0 | DBG_ERR("Interface name too long.\n"); |
145 | 0 | goto done; |
146 | 0 | } |
147 | | |
148 | 0 | ZERO_STRUCT(ifr); |
149 | 0 | strlcpy(ifr.ifr_name, name, IF_NAMESIZE); |
150 | |
|
151 | 0 | ifr.ifr_data = (void *)&edata; |
152 | 0 | ZERO_STRUCT(edata); |
153 | 0 | edata.cmd = ETHTOOL_GLINK; |
154 | 0 | ret = ioctl(fd, SIOCETHTOOL, &ifr); |
155 | 0 | if (ret == -1) { |
156 | 0 | goto done; |
157 | 0 | } |
158 | 0 | if (edata.data == 0) { |
159 | | /* no link detected */ |
160 | 0 | *speed = 0; |
161 | 0 | goto done; |
162 | 0 | } |
163 | | |
164 | 0 | ifr.ifr_data = (void *)&ecmd; |
165 | 0 | ZERO_STRUCT(ecmd); |
166 | 0 | ecmd.cmd = ETHTOOL_GSET; |
167 | 0 | ret = ioctl(fd, SIOCETHTOOL, &ifr); |
168 | 0 | if (ret == -1) { |
169 | 0 | goto done; |
170 | 0 | } |
171 | 0 | *speed = ((uint64_t)ethtool_cmd_speed(&ecmd)) * 1000 * 1000; |
172 | |
|
173 | 0 | done: |
174 | 0 | (void)close(fd); |
175 | 0 | } |
176 | | |
177 | | static void query_iface_rx_queues_from_name(const char *name, |
178 | | uint64_t *rx_queues) |
179 | 0 | { |
180 | 0 | int ret = 0; |
181 | 0 | struct ethtool_rxnfc rxcmd; |
182 | 0 | struct ifreq ifr; |
183 | 0 | int fd; |
184 | |
|
185 | 0 | fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); |
186 | 0 | if (fd == -1) { |
187 | 0 | DBG_ERR("Failed to open socket.\n"); |
188 | 0 | return; |
189 | 0 | } |
190 | | |
191 | 0 | if (strlen(name) >= IF_NAMESIZE) { |
192 | 0 | DBG_ERR("Interface name too long.\n"); |
193 | 0 | goto done; |
194 | 0 | } |
195 | | |
196 | 0 | ZERO_STRUCT(ifr); |
197 | 0 | strlcpy(ifr.ifr_name, name, IF_NAMESIZE); |
198 | |
|
199 | 0 | ifr.ifr_data = (void *)&rxcmd; |
200 | 0 | ZERO_STRUCT(rxcmd); |
201 | 0 | rxcmd.cmd = ETHTOOL_GRXRINGS; |
202 | 0 | ret = ioctl(fd, SIOCETHTOOL, &ifr); |
203 | 0 | if (ret == -1) { |
204 | 0 | goto done; |
205 | 0 | } |
206 | | |
207 | 0 | *rx_queues = rxcmd.data; |
208 | |
|
209 | 0 | done: |
210 | 0 | (void)close(fd); |
211 | 0 | } |
212 | | #endif |
213 | | |
214 | | /**************************************************************************** |
215 | | Try the "standard" getifaddrs/freeifaddrs interfaces. |
216 | | Also gets IPv6 interfaces. |
217 | | ****************************************************************************/ |
218 | | |
219 | | /**************************************************************************** |
220 | | Get the netmask address for a local interface. |
221 | | ****************************************************************************/ |
222 | | |
223 | | static int _get_interfaces(TALLOC_CTX *mem_ctx, struct iface_struct **pifaces) |
224 | 0 | { |
225 | 0 | struct iface_struct *ifaces; |
226 | 0 | struct ifaddrs *iflist = NULL; |
227 | 0 | struct ifaddrs *ifptr = NULL; |
228 | 0 | int count; |
229 | 0 | int total = 0; |
230 | 0 | size_t copy_size; |
231 | |
|
232 | 0 | if (getifaddrs(&iflist) < 0) { |
233 | 0 | return -1; |
234 | 0 | } |
235 | | |
236 | 0 | count = 0; |
237 | 0 | for (ifptr = iflist; ifptr != NULL; ifptr = ifptr->ifa_next) { |
238 | 0 | if (!ifptr->ifa_addr || !ifptr->ifa_netmask) { |
239 | 0 | continue; |
240 | 0 | } |
241 | 0 | if (!(ifptr->ifa_flags & IFF_UP)) { |
242 | 0 | continue; |
243 | 0 | } |
244 | 0 | count += 1; |
245 | 0 | } |
246 | |
|
247 | 0 | ifaces = talloc_array(mem_ctx, struct iface_struct, count); |
248 | 0 | if (ifaces == NULL) { |
249 | 0 | errno = ENOMEM; |
250 | 0 | return -1; |
251 | 0 | } |
252 | | |
253 | | /* Loop through interfaces, looking for given IP address */ |
254 | 0 | for (ifptr = iflist; ifptr != NULL; ifptr = ifptr->ifa_next) { |
255 | 0 | uint64_t if_speed = 1000 * 1000 * 1000; /* 1Gbps */ |
256 | 0 | uint64_t rx_queues = 1; |
257 | |
|
258 | 0 | if (!ifptr->ifa_addr || !ifptr->ifa_netmask) { |
259 | 0 | continue; |
260 | 0 | } |
261 | | |
262 | | /* Check the interface is up. */ |
263 | 0 | if (!(ifptr->ifa_flags & IFF_UP)) { |
264 | 0 | continue; |
265 | 0 | } |
266 | | |
267 | 0 | memset(&ifaces[total], '\0', sizeof(ifaces[total])); |
268 | |
|
269 | 0 | copy_size = sizeof(struct sockaddr_in); |
270 | |
|
271 | 0 | ifaces[total].flags = ifptr->ifa_flags; |
272 | |
|
273 | 0 | #if defined(HAVE_IPV6) |
274 | 0 | if (ifptr->ifa_addr->sa_family == AF_INET6) { |
275 | 0 | copy_size = sizeof(struct sockaddr_in6); |
276 | 0 | } |
277 | 0 | #endif |
278 | |
|
279 | 0 | memcpy(&ifaces[total].ip, ifptr->ifa_addr, copy_size); |
280 | 0 | memcpy(&ifaces[total].netmask, ifptr->ifa_netmask, copy_size); |
281 | | |
282 | | /* calculate broadcast address */ |
283 | 0 | #if defined(HAVE_IPV6) |
284 | 0 | if (ifptr->ifa_addr->sa_family == AF_INET6) { |
285 | 0 | struct sockaddr_in6 *sin6 = |
286 | 0 | (struct sockaddr_in6 *)ifptr->ifa_addr; |
287 | 0 | struct in6_addr *in6 = |
288 | 0 | (struct in6_addr *)&sin6->sin6_addr; |
289 | |
|
290 | 0 | if (IN6_IS_ADDR_LINKLOCAL(in6) || IN6_IS_ADDR_V4COMPAT(in6)) { |
291 | 0 | continue; |
292 | 0 | } |
293 | | /* IPv6 does not have broadcast it uses multicast. */ |
294 | 0 | memset(&ifaces[total].bcast, '\0', copy_size); |
295 | 0 | } else |
296 | 0 | #endif |
297 | 0 | if (ifaces[total].flags & (IFF_BROADCAST|IFF_LOOPBACK)) { |
298 | 0 | make_bcast(&ifaces[total].bcast, |
299 | 0 | &ifaces[total].ip, |
300 | 0 | &ifaces[total].netmask); |
301 | 0 | } else if ((ifaces[total].flags & IFF_POINTOPOINT) && |
302 | 0 | ifptr->ifa_dstaddr ) { |
303 | 0 | memcpy(&ifaces[total].bcast, |
304 | 0 | ifptr->ifa_dstaddr, |
305 | 0 | copy_size); |
306 | 0 | } else { |
307 | 0 | continue; |
308 | 0 | } |
309 | | |
310 | 0 | ifaces[total].if_index = if_nametoindex(ifptr->ifa_name); |
311 | 0 | if (ifaces[total].if_index == 0) { |
312 | 0 | DBG_ERR("Failed to retrieve interface index for '%s': " |
313 | 0 | "%s\n", ifptr->ifa_name, strerror(errno)); |
314 | 0 | } |
315 | |
|
316 | 0 | #ifdef HAVE_ETHTOOL |
317 | 0 | query_iface_speed_from_name(ifptr->ifa_name, &if_speed); |
318 | 0 | query_iface_rx_queues_from_name(ifptr->ifa_name, &rx_queues); |
319 | 0 | #endif |
320 | 0 | ifaces[total].linkspeed = if_speed; |
321 | 0 | ifaces[total].capability = FSCTL_NET_IFACE_NONE_CAPABLE; |
322 | 0 | if (rx_queues > 1) { |
323 | 0 | ifaces[total].capability |= FSCTL_NET_IFACE_RSS_CAPABLE; |
324 | 0 | } |
325 | |
|
326 | 0 | if (strlcpy(ifaces[total].name, ifptr->ifa_name, |
327 | 0 | sizeof(ifaces[total].name)) >= |
328 | 0 | sizeof(ifaces[total].name)) { |
329 | | /* Truncation ! Ignore. */ |
330 | 0 | continue; |
331 | 0 | } |
332 | 0 | total++; |
333 | 0 | } |
334 | |
|
335 | 0 | freeifaddrs(iflist); |
336 | |
|
337 | 0 | *pifaces = ifaces; |
338 | 0 | return total; |
339 | 0 | } |
340 | | |
341 | | static int iface_comp(struct iface_struct *i1, struct iface_struct *i2) |
342 | 0 | { |
343 | 0 | int r; |
344 | |
|
345 | 0 | #if defined(HAVE_IPV6) |
346 | | /* |
347 | | * If we have IPv6 - sort these interfaces lower |
348 | | * than any IPv4 ones. |
349 | | */ |
350 | 0 | if (i1->ip.ss_family == AF_INET6 && |
351 | 0 | i2->ip.ss_family == AF_INET) { |
352 | 0 | return -1; |
353 | 0 | } else if (i1->ip.ss_family == AF_INET && |
354 | 0 | i2->ip.ss_family == AF_INET6) { |
355 | 0 | return 1; |
356 | 0 | } |
357 | | |
358 | 0 | if (i1->ip.ss_family == AF_INET6) { |
359 | 0 | struct sockaddr_in6 *s1 = (struct sockaddr_in6 *)&i1->ip; |
360 | 0 | struct sockaddr_in6 *s2 = (struct sockaddr_in6 *)&i2->ip; |
361 | |
|
362 | 0 | r = memcmp(&s1->sin6_addr, |
363 | 0 | &s2->sin6_addr, |
364 | 0 | sizeof(struct in6_addr)); |
365 | 0 | if (r) { |
366 | 0 | return r; |
367 | 0 | } |
368 | | |
369 | 0 | s1 = (struct sockaddr_in6 *)&i1->netmask; |
370 | 0 | s2 = (struct sockaddr_in6 *)&i2->netmask; |
371 | |
|
372 | 0 | r = memcmp(&s1->sin6_addr, |
373 | 0 | &s2->sin6_addr, |
374 | 0 | sizeof(struct in6_addr)); |
375 | 0 | if (r) { |
376 | 0 | return r; |
377 | 0 | } |
378 | 0 | } |
379 | 0 | #endif |
380 | | |
381 | | /* AIX uses __ss_family instead of ss_family inside of |
382 | | sockaddr_storage. Instead of trying to figure out which field to |
383 | | use, we can just cast it to a sockaddr. |
384 | | */ |
385 | | |
386 | 0 | if (((struct sockaddr *)&i1->ip)->sa_family == AF_INET) { |
387 | 0 | struct sockaddr_in *s1 = (struct sockaddr_in *)&i1->ip; |
388 | 0 | struct sockaddr_in *s2 = (struct sockaddr_in *)&i2->ip; |
389 | 0 | uint32_t a1 = ntohl(s1->sin_addr.s_addr); |
390 | 0 | uint32_t a2 = ntohl(s2->sin_addr.s_addr); |
391 | 0 | r = NUMERIC_CMP(a1, a2); |
392 | 0 | if (r == 0) { |
393 | | /* compare netmasks as a tiebreaker */ |
394 | 0 | s1 = (struct sockaddr_in *)&i1->netmask; |
395 | 0 | s2 = (struct sockaddr_in *)&i2->netmask; |
396 | 0 | a1 = ntohl(s1->sin_addr.s_addr); |
397 | 0 | a2 = ntohl(s2->sin_addr.s_addr); |
398 | 0 | r = NUMERIC_CMP(a1, a2); |
399 | 0 | } |
400 | 0 | return r; |
401 | 0 | } |
402 | 0 | return 0; |
403 | 0 | } |
404 | | |
405 | | /* this wrapper is used to remove duplicates from the interface list generated |
406 | | above */ |
407 | | int get_interfaces(TALLOC_CTX *mem_ctx, struct iface_struct **pifaces) |
408 | 0 | { |
409 | 0 | struct iface_struct *ifaces = NULL; |
410 | 0 | int total, i, j; |
411 | |
|
412 | 0 | total = _get_interfaces(mem_ctx, &ifaces); |
413 | | /* If we have an error, no interface or just one we can leave */ |
414 | 0 | if (total <= 1) { |
415 | 0 | *pifaces = ifaces; |
416 | 0 | return total; |
417 | 0 | } |
418 | | |
419 | | /* now we need to remove duplicates */ |
420 | 0 | TYPESAFE_QSORT(ifaces, total, iface_comp); |
421 | |
|
422 | 0 | for (i=1;i<total;) { |
423 | 0 | if (iface_comp(&ifaces[i-1], &ifaces[i]) == 0) { |
424 | 0 | for (j=i-1;j<total-1;j++) { |
425 | 0 | ifaces[j] = ifaces[j+1]; |
426 | 0 | } |
427 | 0 | total--; |
428 | 0 | } else { |
429 | 0 | i++; |
430 | 0 | } |
431 | 0 | } |
432 | |
|
433 | 0 | *pifaces = ifaces; |
434 | 0 | return total; |
435 | 0 | } |