Statistics
| Branch: | Revision:

root / qemu-bridge-helper.c @ 47e98658

History | View | Annotate | Download (10.1 kB)

1
/*
2
 * QEMU Bridge Helper
3
 *
4
 * Copyright IBM, Corp. 2011
5
 *
6
 * Authors:
7
 * Anthony Liguori   <aliguori@us.ibm.com>
8
 * Richa Marwaha     <rmarwah@linux.vnet.ibm.com>
9
 * Corey Bryant      <coreyb@linux.vnet.ibm.com>
10
 *
11
 * This work is licensed under the terms of the GNU GPL, version 2.  See
12
 * the COPYING file in the top-level directory.
13
 *
14
 */
15

    
16
#include "config-host.h"
17

    
18
#include <stdio.h>
19
#include <errno.h>
20
#include <fcntl.h>
21
#include <unistd.h>
22
#include <string.h>
23
#include <stdlib.h>
24
#include <stdbool.h>
25
#include <ctype.h>
26
#include <glib.h>
27

    
28
#include <sys/types.h>
29
#include <sys/ioctl.h>
30
#include <sys/socket.h>
31
#include <sys/un.h>
32
#include <sys/prctl.h>
33

    
34
#include <net/if.h>
35

    
36
#include <linux/sockios.h>
37

    
38
#include "qemu-queue.h"
39

    
40
#include "net/tap-linux.h"
41

    
42
#ifdef CONFIG_LIBCAP
43
#include <cap-ng.h>
44
#endif
45

    
46
#define DEFAULT_ACL_FILE CONFIG_QEMU_CONFDIR "/bridge.conf"
47

    
48
enum {
49
    ACL_ALLOW = 0,
50
    ACL_ALLOW_ALL,
51
    ACL_DENY,
52
    ACL_DENY_ALL,
53
};
54

    
55
typedef struct ACLRule {
56
    int type;
57
    char iface[IFNAMSIZ];
58
    QSIMPLEQ_ENTRY(ACLRule) entry;
59
} ACLRule;
60

    
61
typedef QSIMPLEQ_HEAD(ACLList, ACLRule) ACLList;
62

    
63
static void usage(void)
64
{
65
    fprintf(stderr,
66
            "Usage: qemu-bridge-helper [--use-vnet] --br=bridge --fd=unixfd\n");
67
}
68

    
69
static int parse_acl_file(const char *filename, ACLList *acl_list)
70
{
71
    FILE *f;
72
    char line[4096];
73
    ACLRule *acl_rule;
74

    
75
    f = fopen(filename, "r");
76
    if (f == NULL) {
77
        return -1;
78
    }
79

    
80
    while (fgets(line, sizeof(line), f) != NULL) {
81
        char *ptr = line;
82
        char *cmd, *arg, *argend;
83

    
84
        while (isspace(*ptr)) {
85
            ptr++;
86
        }
87

    
88
        /* skip comments and empty lines */
89
        if (*ptr == '#' || *ptr == 0) {
90
            continue;
91
        }
92

    
93
        cmd = ptr;
94
        arg = strchr(cmd, ' ');
95
        if (arg == NULL) {
96
            arg = strchr(cmd, '\t');
97
        }
98

    
99
        if (arg == NULL) {
100
            fprintf(stderr, "Invalid config line:\n  %s\n", line);
101
            fclose(f);
102
            errno = EINVAL;
103
            return -1;
104
        }
105

    
106
        *arg = 0;
107
        arg++;
108
        while (isspace(*arg)) {
109
            arg++;
110
        }
111

    
112
        argend = arg + strlen(arg);
113
        while (arg != argend && isspace(*(argend - 1))) {
114
            argend--;
115
        }
116
        *argend = 0;
117

    
118
        if (strcmp(cmd, "deny") == 0) {
119
            acl_rule = g_malloc(sizeof(*acl_rule));
120
            if (strcmp(arg, "all") == 0) {
121
                acl_rule->type = ACL_DENY_ALL;
122
            } else {
123
                acl_rule->type = ACL_DENY;
124
                snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg);
125
            }
126
            QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry);
127
        } else if (strcmp(cmd, "allow") == 0) {
128
            acl_rule = g_malloc(sizeof(*acl_rule));
129
            if (strcmp(arg, "all") == 0) {
130
                acl_rule->type = ACL_ALLOW_ALL;
131
            } else {
132
                acl_rule->type = ACL_ALLOW;
133
                snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg);
134
            }
135
            QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry);
136
        } else if (strcmp(cmd, "include") == 0) {
137
            /* ignore errors */
138
            parse_acl_file(arg, acl_list);
139
        } else {
140
            fprintf(stderr, "Unknown command `%s'\n", cmd);
141
            fclose(f);
142
            errno = EINVAL;
143
            return -1;
144
        }
145
    }
146

    
147
    fclose(f);
148

    
149
    return 0;
150
}
151

    
152
static bool has_vnet_hdr(int fd)
153
{
154
    unsigned int features = 0;
155

    
156
    if (ioctl(fd, TUNGETFEATURES, &features) == -1) {
157
        return false;
158
    }
159

    
160
    if (!(features & IFF_VNET_HDR)) {
161
        return false;
162
    }
163

    
164
    return true;
165
}
166

    
167
static void prep_ifreq(struct ifreq *ifr, const char *ifname)
168
{
169
    memset(ifr, 0, sizeof(*ifr));
170
    snprintf(ifr->ifr_name, IFNAMSIZ, "%s", ifname);
171
}
172

    
173
static int send_fd(int c, int fd)
174
{
175
    char msgbuf[CMSG_SPACE(sizeof(fd))];
176
    struct msghdr msg = {
177
        .msg_control = msgbuf,
178
        .msg_controllen = sizeof(msgbuf),
179
    };
180
    struct cmsghdr *cmsg;
181
    struct iovec iov;
182
    char req[1] = { 0x00 };
183

    
184
    cmsg = CMSG_FIRSTHDR(&msg);
185
    cmsg->cmsg_level = SOL_SOCKET;
186
    cmsg->cmsg_type = SCM_RIGHTS;
187
    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
188
    msg.msg_controllen = cmsg->cmsg_len;
189

    
190
    iov.iov_base = req;
191
    iov.iov_len = sizeof(req);
192

    
193
    msg.msg_iov = &iov;
194
    msg.msg_iovlen = 1;
195
    memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
196

    
197
    return sendmsg(c, &msg, 0);
198
}
199

    
200
#ifdef CONFIG_LIBCAP
201
static int drop_privileges(void)
202
{
203
    /* clear all capabilities */
204
    capng_clear(CAPNG_SELECT_BOTH);
205

    
206
    if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
207
                     CAP_NET_ADMIN) < 0) {
208
        return -1;
209
    }
210

    
211
    /* change to calling user's real uid and gid, retaining supplemental
212
     * groups and CAP_NET_ADMIN */
213
    if (capng_change_id(getuid(), getgid(), CAPNG_CLEAR_BOUNDING)) {
214
        return -1;
215
    }
216

    
217
    return 0;
218
}
219
#endif
220

    
221
int main(int argc, char **argv)
222
{
223
    struct ifreq ifr;
224
    int fd, ctlfd, unixfd = -1;
225
    int use_vnet = 0;
226
    int mtu;
227
    const char *bridge = NULL;
228
    char iface[IFNAMSIZ];
229
    int index;
230
    ACLRule *acl_rule;
231
    ACLList acl_list;
232
    int access_allowed, access_denied;
233
    int ret = EXIT_SUCCESS;
234

    
235
#ifdef CONFIG_LIBCAP
236
    /* if we're run from an suid binary, immediately drop privileges preserving
237
     * cap_net_admin */
238
    if (geteuid() == 0 && getuid() != geteuid()) {
239
        if (drop_privileges() == -1) {
240
            fprintf(stderr, "failed to drop privileges\n");
241
            return 1;
242
        }
243
    }
244
#endif
245

    
246
    /* parse arguments */
247
    for (index = 1; index < argc; index++) {
248
        if (strcmp(argv[index], "--use-vnet") == 0) {
249
            use_vnet = 1;
250
        } else if (strncmp(argv[index], "--br=", 5) == 0) {
251
            bridge = &argv[index][5];
252
        } else if (strncmp(argv[index], "--fd=", 5) == 0) {
253
            unixfd = atoi(&argv[index][5]);
254
        } else {
255
            usage();
256
            return EXIT_FAILURE;
257
        }
258
    }
259

    
260
    if (bridge == NULL || unixfd == -1) {
261
        usage();
262
        return EXIT_FAILURE;
263
    }
264

    
265
    /* parse default acl file */
266
    QSIMPLEQ_INIT(&acl_list);
267
    if (parse_acl_file(DEFAULT_ACL_FILE, &acl_list) == -1) {
268
        fprintf(stderr, "failed to parse default acl file `%s'\n",
269
                DEFAULT_ACL_FILE);
270
        ret = EXIT_FAILURE;
271
        goto cleanup;
272
    }
273

    
274
    /* validate bridge against acl -- default policy is to deny
275
     * according acl policy if we have a deny and allow both
276
     * then deny should always win over allow
277
     */
278
    access_allowed = 0;
279
    access_denied = 0;
280
    QSIMPLEQ_FOREACH(acl_rule, &acl_list, entry) {
281
        switch (acl_rule->type) {
282
        case ACL_ALLOW_ALL:
283
            access_allowed = 1;
284
            break;
285
        case ACL_ALLOW:
286
            if (strcmp(bridge, acl_rule->iface) == 0) {
287
                access_allowed = 1;
288
            }
289
            break;
290
        case ACL_DENY_ALL:
291
            access_denied = 1;
292
            break;
293
        case ACL_DENY:
294
            if (strcmp(bridge, acl_rule->iface) == 0) {
295
                access_denied = 1;
296
            }
297
            break;
298
        }
299
    }
300

    
301
    if ((access_allowed == 0) || (access_denied == 1)) {
302
        fprintf(stderr, "access denied by acl file\n");
303
        ret = EXIT_FAILURE;
304
        goto cleanup;
305
    }
306

    
307
    /* open a socket to use to control the network interfaces */
308
    ctlfd = socket(AF_INET, SOCK_STREAM, 0);
309
    if (ctlfd == -1) {
310
        fprintf(stderr, "failed to open control socket: %s\n", strerror(errno));
311
        ret = EXIT_FAILURE;
312
        goto cleanup;
313
    }
314

    
315
    /* open the tap device */
316
    fd = open("/dev/net/tun", O_RDWR);
317
    if (fd == -1) {
318
        fprintf(stderr, "failed to open /dev/net/tun: %s\n", strerror(errno));
319
        ret = EXIT_FAILURE;
320
        goto cleanup;
321
    }
322

    
323
    /* request a tap device, disable PI, and add vnet header support if
324
     * requested and it's available. */
325
    prep_ifreq(&ifr, "tap%d");
326
    ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
327
    if (use_vnet && has_vnet_hdr(fd)) {
328
        ifr.ifr_flags |= IFF_VNET_HDR;
329
    }
330

    
331
    if (ioctl(fd, TUNSETIFF, &ifr) == -1) {
332
        fprintf(stderr, "failed to create tun device: %s\n", strerror(errno));
333
        ret = EXIT_FAILURE;
334
        goto cleanup;
335
    }
336

    
337
    /* save tap device name */
338
    snprintf(iface, sizeof(iface), "%s", ifr.ifr_name);
339

    
340
    /* get the mtu of the bridge */
341
    prep_ifreq(&ifr, bridge);
342
    if (ioctl(ctlfd, SIOCGIFMTU, &ifr) == -1) {
343
        fprintf(stderr, "failed to get mtu of bridge `%s': %s\n",
344
                bridge, strerror(errno));
345
        ret = EXIT_FAILURE;
346
        goto cleanup;
347
    }
348

    
349
    /* save mtu */
350
    mtu = ifr.ifr_mtu;
351

    
352
    /* set the mtu of the interface based on the bridge */
353
    prep_ifreq(&ifr, iface);
354
    ifr.ifr_mtu = mtu;
355
    if (ioctl(ctlfd, SIOCSIFMTU, &ifr) == -1) {
356
        fprintf(stderr, "failed to set mtu of device `%s' to %d: %s\n",
357
                iface, mtu, strerror(errno));
358
        ret = EXIT_FAILURE;
359
        goto cleanup;
360
    }
361

    
362
    /* add the interface to the bridge */
363
    prep_ifreq(&ifr, bridge);
364
    ifr.ifr_ifindex = if_nametoindex(iface);
365

    
366
    if (ioctl(ctlfd, SIOCBRADDIF, &ifr) == -1) {
367
        fprintf(stderr, "failed to add interface `%s' to bridge `%s': %s\n",
368
                iface, bridge, strerror(errno));
369
        ret = EXIT_FAILURE;
370
        goto cleanup;
371
    }
372

    
373
    /* bring the interface up */
374
    prep_ifreq(&ifr, iface);
375
    if (ioctl(ctlfd, SIOCGIFFLAGS, &ifr) == -1) {
376
        fprintf(stderr, "failed to get interface flags for `%s': %s\n",
377
                iface, strerror(errno));
378
        ret = EXIT_FAILURE;
379
        goto cleanup;
380
    }
381

    
382
    ifr.ifr_flags |= IFF_UP;
383
    if (ioctl(ctlfd, SIOCSIFFLAGS, &ifr) == -1) {
384
        fprintf(stderr, "failed to bring up interface `%s': %s\n",
385
                iface, strerror(errno));
386
        ret = EXIT_FAILURE;
387
        goto cleanup;
388
    }
389

    
390
    /* write fd to the domain socket */
391
    if (send_fd(unixfd, fd) == -1) {
392
        fprintf(stderr, "failed to write fd to unix socket: %s\n",
393
                strerror(errno));
394
        ret = EXIT_FAILURE;
395
        goto cleanup;
396
    }
397

    
398
    /* ... */
399

    
400
    /* profit! */
401

    
402
cleanup:
403

    
404
    while ((acl_rule = QSIMPLEQ_FIRST(&acl_list)) != NULL) {
405
        QSIMPLEQ_REMOVE_HEAD(&acl_list, entry);
406
        g_free(acl_rule);
407
    }
408

    
409
    return ret;
410
}