1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
/*
* Socket Utilities
*/
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "socket.h"
/**
* socket_SIOCGIFNAME() - resolve an ifindex to an ifname
* @socket: socket to operate on
* @ifindex: index of network interface to resolve
* @ifname: buffer to store resolved name
*
* This uses the SIOCGIFNAME ioctl to resolve an ifindex to an ifname. The
* buffer provided in @ifnamep must be at least IFNAMSIZ bytes in size. The
* maximum ifname length is IFNAMSIZ-1, and this function always
* zero-terminates the result.
*
* This function is similar to if_indextoname(3) provided by glibc, but it
* allows to specify the target socket explicitly. This allows the caller to
* control the target network-namespace, rather than relying on the network
* namespace of the running process.
*
* Return: 0 on success, negative kernel error code on failure.
*/
int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]) {
struct ifreq req = { .ifr_ifindex = ifindex };
int r;
r = ioctl(socket, SIOCGIFNAME, &req);
if (r < 0)
return -errno;
/*
* The linux kernel guarantees that an interface name is always
* zero-terminated, and it always fully fits into IFNAMSIZ bytes,
* including the zero-terminator.
*/
memcpy(ifnamep, req.ifr_name, IFNAMSIZ);
return 0;
}
/**
* socket_bind_if() - bind socket to a network interface
* @socket: socket to operate on
* @ifindex: index of network interface to bind to, or 0
*
* This binds the socket given via @socket to the network interface specified
* via @ifindex. It uses the underlying SO_BINDTODEVICE ioctl of the linux
* kernel. However, if available, if prefers the newer SO_BINDTOIFINDEX ioctl,
* which avoids resolving the interface name temporarily, and thus does not
* suffer from a race-condition.
*
* Return: 0 on success, negative error code on failure.
*/
int socket_bind_if(int socket, int ifindex) {
char ifname[IFNAMSIZ] = {};
int r;
c_assert(ifindex >= 0);
/*
* We first try the newer SO_BINDTOIFINDEX. If it is not available on
* the running kernel, we fall back to SO_BINDTODEVICE. This, however,
* requires us to first resolve the ifindex to an ifname. Note that
* this is racy, since the device name might theoretically change
* asynchronously.
*
* Using 0 as ifindex will remove the device-binding. For
* SO_BINDTOIFINDEX we simply pass-through the 0 to the kernel, which
* recognizes this correctly. For SO_BINDTODEVICE we pass the empty
* string, which the kernel recognizes as a request to remove the
* binding.
*
* The commit introducing SO_BINDTOIFINDEX first appeared in linux-5.1:
*
* commit f5dd3d0c9638a9d9a02b5964c4ad636f06cf7e2c
* Author: David Herrmann <dh.herrmann@gmail.com>
* Date: Tue Jan 15 14:42:14 2019 +0100
*
* net: introduce SO_BINDTOIFINDEX sockopt
*
* In older kernels, setsockopt(2) is guaranteed to return ENOPROTOOPT
* for this ioctl.
*/
#ifdef SO_BINDTOIFINDEX
r = setsockopt(socket,
SOL_SOCKET,
SO_BINDTOIFINDEX,
&ifindex,
sizeof(ifindex));
if (r >= 0)
return 0;
else if (errno != ENOPROTOOPT)
return -errno;
#endif /* SO_BINDTOIFINDEX */
if (ifindex > 0) {
r = socket_SIOCGIFNAME(socket, ifindex, &ifname);
if (r)
return r;
}
r = setsockopt(socket,
SOL_SOCKET,
SO_BINDTODEVICE,
ifname,
strlen(ifname));
if (r < 0)
return -errno;
return 0;
}
|