qemu/qemu-bridge-helper.c
<<
>>
Prefs
   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 "qemu/osdep.h"
  17
  18#include <glib.h>
  19
  20#include <sys/ioctl.h>
  21#include <sys/socket.h>
  22#include <sys/un.h>
  23#include <sys/prctl.h>
  24
  25#include <net/if.h>
  26
  27#include <linux/sockios.h>
  28
  29#ifndef SIOCBRADDIF
  30#include <linux/if_bridge.h>
  31#endif
  32
  33#include "qemu/queue.h"
  34
  35#include "net/tap-linux.h"
  36
  37#ifdef CONFIG_LIBCAP
  38#include <cap-ng.h>
  39#endif
  40
  41#define DEFAULT_ACL_FILE CONFIG_QEMU_CONFDIR "/bridge.conf"
  42
  43enum {
  44    ACL_ALLOW = 0,
  45    ACL_ALLOW_ALL,
  46    ACL_DENY,
  47    ACL_DENY_ALL,
  48};
  49
  50typedef struct ACLRule {
  51    int type;
  52    char iface[IFNAMSIZ];
  53    QSIMPLEQ_ENTRY(ACLRule) entry;
  54} ACLRule;
  55
  56typedef QSIMPLEQ_HEAD(ACLList, ACLRule) ACLList;
  57
  58static void usage(void)
  59{
  60    fprintf(stderr,
  61            "Usage: qemu-bridge-helper [--use-vnet] --br=bridge --fd=unixfd\n");
  62}
  63
  64static int parse_acl_file(const char *filename, ACLList *acl_list)
  65{
  66    FILE *f;
  67    char line[4096];
  68    ACLRule *acl_rule;
  69
  70    f = fopen(filename, "r");
  71    if (f == NULL) {
  72        return -1;
  73    }
  74
  75    while (fgets(line, sizeof(line), f) != NULL) {
  76        char *ptr = line;
  77        char *cmd, *arg, *argend;
  78
  79        while (isspace(*ptr)) {
  80            ptr++;
  81        }
  82
  83        /* skip comments and empty lines */
  84        if (*ptr == '#' || *ptr == 0) {
  85            continue;
  86        }
  87
  88        cmd = ptr;
  89        arg = strchr(cmd, ' ');
  90        if (arg == NULL) {
  91            arg = strchr(cmd, '\t');
  92        }
  93
  94        if (arg == NULL) {
  95            fprintf(stderr, "Invalid config line:\n  %s\n", line);
  96            fclose(f);
  97            errno = EINVAL;
  98            return -1;
  99        }
 100
 101        *arg = 0;
 102        arg++;
 103        while (isspace(*arg)) {
 104            arg++;
 105        }
 106
 107        argend = arg + strlen(arg);
 108        while (arg != argend && isspace(*(argend - 1))) {
 109            argend--;
 110        }
 111        *argend = 0;
 112
 113        if (strcmp(cmd, "deny") == 0) {
 114            acl_rule = g_malloc(sizeof(*acl_rule));
 115            if (strcmp(arg, "all") == 0) {
 116                acl_rule->type = ACL_DENY_ALL;
 117            } else {
 118                acl_rule->type = ACL_DENY;
 119                snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg);
 120            }
 121            QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry);
 122        } else if (strcmp(cmd, "allow") == 0) {
 123            acl_rule = g_malloc(sizeof(*acl_rule));
 124            if (strcmp(arg, "all") == 0) {
 125                acl_rule->type = ACL_ALLOW_ALL;
 126            } else {
 127                acl_rule->type = ACL_ALLOW;
 128                snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg);
 129            }
 130            QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry);
 131        } else if (strcmp(cmd, "include") == 0) {
 132            /* ignore errors */
 133            parse_acl_file(arg, acl_list);
 134        } else {
 135            fprintf(stderr, "Unknown command `%s'\n", cmd);
 136            fclose(f);
 137            errno = EINVAL;
 138            return -1;
 139        }
 140    }
 141
 142    fclose(f);
 143
 144    return 0;
 145}
 146
 147static bool has_vnet_hdr(int fd)
 148{
 149    unsigned int features = 0;
 150
 151    if (ioctl(fd, TUNGETFEATURES, &features) == -1) {
 152        return false;
 153    }
 154
 155    if (!(features & IFF_VNET_HDR)) {
 156        return false;
 157    }
 158
 159    return true;
 160}
 161
 162static void prep_ifreq(struct ifreq *ifr, const char *ifname)
 163{
 164    memset(ifr, 0, sizeof(*ifr));
 165    snprintf(ifr->ifr_name, IFNAMSIZ, "%s", ifname);
 166}
 167
 168static int send_fd(int c, int fd)
 169{
 170    char msgbuf[CMSG_SPACE(sizeof(fd))];
 171    struct msghdr msg = {
 172        .msg_control = msgbuf,
 173        .msg_controllen = sizeof(msgbuf),
 174    };
 175    struct cmsghdr *cmsg;
 176    struct iovec iov;
 177    char req[1] = { 0x00 };
 178
 179    cmsg = CMSG_FIRSTHDR(&msg);
 180    cmsg->cmsg_level = SOL_SOCKET;
 181    cmsg->cmsg_type = SCM_RIGHTS;
 182    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
 183    msg.msg_controllen = cmsg->cmsg_len;
 184
 185    iov.iov_base = req;
 186    iov.iov_len = sizeof(req);
 187
 188    msg.msg_iov = &iov;
 189    msg.msg_iovlen = 1;
 190    memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
 191
 192    return sendmsg(c, &msg, 0);
 193}
 194
 195#ifdef CONFIG_LIBCAP
 196static int drop_privileges(void)
 197{
 198    /* clear all capabilities */
 199    capng_clear(CAPNG_SELECT_BOTH);
 200
 201    if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
 202                     CAP_NET_ADMIN) < 0) {
 203        return -1;
 204    }
 205
 206    /* change to calling user's real uid and gid, retaining supplemental
 207     * groups and CAP_NET_ADMIN */
 208    if (capng_change_id(getuid(), getgid(), CAPNG_CLEAR_BOUNDING)) {
 209        return -1;
 210    }
 211
 212    return 0;
 213}
 214#endif
 215
 216int main(int argc, char **argv)
 217{
 218    struct ifreq ifr;
 219#ifndef SIOCBRADDIF
 220    unsigned long ifargs[4];
 221#endif
 222    int ifindex;
 223    int fd = -1, ctlfd = -1, unixfd = -1;
 224    int use_vnet = 0;
 225    int mtu;
 226    const char *bridge = NULL;
 227    char iface[IFNAMSIZ];
 228    int index;
 229    ACLRule *acl_rule;
 230    ACLList acl_list;
 231    int access_allowed, access_denied;
 232    int ret = EXIT_SUCCESS;
 233
 234#ifdef CONFIG_LIBCAP
 235    /* if we're run from an suid binary, immediately drop privileges preserving
 236     * cap_net_admin */
 237    if (geteuid() == 0 && getuid() != geteuid()) {
 238        if (drop_privileges() == -1) {
 239            fprintf(stderr, "failed to drop privileges\n");
 240            return 1;
 241        }
 242    }
 243#endif
 244
 245    /* parse arguments */
 246    for (index = 1; index < argc; index++) {
 247        if (strcmp(argv[index], "--use-vnet") == 0) {
 248            use_vnet = 1;
 249        } else if (strncmp(argv[index], "--br=", 5) == 0) {
 250            bridge = &argv[index][5];
 251        } else if (strncmp(argv[index], "--fd=", 5) == 0) {
 252            unixfd = atoi(&argv[index][5]);
 253        } else {
 254            usage();
 255            return EXIT_FAILURE;
 256        }
 257    }
 258
 259    if (bridge == NULL || unixfd == -1) {
 260        usage();
 261        return EXIT_FAILURE;
 262    }
 263
 264    /* parse default acl file */
 265    QSIMPLEQ_INIT(&acl_list);
 266    if (parse_acl_file(DEFAULT_ACL_FILE, &acl_list) == -1) {
 267        fprintf(stderr, "failed to parse default acl file `%s'\n",
 268                DEFAULT_ACL_FILE);
 269        ret = EXIT_FAILURE;
 270        goto cleanup;
 271    }
 272
 273    /* validate bridge against acl -- default policy is to deny
 274     * according acl policy if we have a deny and allow both
 275     * then deny should always win over allow
 276     */
 277    access_allowed = 0;
 278    access_denied = 0;
 279    QSIMPLEQ_FOREACH(acl_rule, &acl_list, entry) {
 280        switch (acl_rule->type) {
 281        case ACL_ALLOW_ALL:
 282            access_allowed = 1;
 283            break;
 284        case ACL_ALLOW:
 285            if (strcmp(bridge, acl_rule->iface) == 0) {
 286                access_allowed = 1;
 287            }
 288            break;
 289        case ACL_DENY_ALL:
 290            access_denied = 1;
 291            break;
 292        case ACL_DENY:
 293            if (strcmp(bridge, acl_rule->iface) == 0) {
 294                access_denied = 1;
 295            }
 296            break;
 297        }
 298    }
 299
 300    if ((access_allowed == 0) || (access_denied == 1)) {
 301        fprintf(stderr, "access denied by acl file\n");
 302        ret = EXIT_FAILURE;
 303        goto cleanup;
 304    }
 305
 306    /* open a socket to use to control the network interfaces */
 307    ctlfd = socket(AF_INET, SOCK_STREAM, 0);
 308    if (ctlfd == -1) {
 309        fprintf(stderr, "failed to open control socket: %s\n", strerror(errno));
 310        ret = EXIT_FAILURE;
 311        goto cleanup;
 312    }
 313
 314    /* open the tap device */
 315    fd = open("/dev/net/tun", O_RDWR);
 316    if (fd == -1) {
 317        fprintf(stderr, "failed to open /dev/net/tun: %s\n", strerror(errno));
 318        ret = EXIT_FAILURE;
 319        goto cleanup;
 320    }
 321
 322    /* request a tap device, disable PI, and add vnet header support if
 323     * requested and it's available. */
 324    prep_ifreq(&ifr, "tap%d");
 325    ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
 326    if (use_vnet && has_vnet_hdr(fd)) {
 327        ifr.ifr_flags |= IFF_VNET_HDR;
 328    }
 329
 330    if (ioctl(fd, TUNSETIFF, &ifr) == -1) {
 331        fprintf(stderr, "failed to create tun device: %s\n", strerror(errno));
 332        ret = EXIT_FAILURE;
 333        goto cleanup;
 334    }
 335
 336    /* save tap device name */
 337    snprintf(iface, sizeof(iface), "%s", ifr.ifr_name);
 338
 339    /* get the mtu of the bridge */
 340    prep_ifreq(&ifr, bridge);
 341    if (ioctl(ctlfd, SIOCGIFMTU, &ifr) == -1) {
 342        fprintf(stderr, "failed to get mtu of bridge `%s': %s\n",
 343                bridge, strerror(errno));
 344        ret = EXIT_FAILURE;
 345        goto cleanup;
 346    }
 347
 348    /* save mtu */
 349    mtu = ifr.ifr_mtu;
 350
 351    /* set the mtu of the interface based on the bridge */
 352    prep_ifreq(&ifr, iface);
 353    ifr.ifr_mtu = mtu;
 354    if (ioctl(ctlfd, SIOCSIFMTU, &ifr) == -1) {
 355        fprintf(stderr, "failed to set mtu of device `%s' to %d: %s\n",
 356                iface, mtu, strerror(errno));
 357        ret = EXIT_FAILURE;
 358        goto cleanup;
 359    }
 360
 361    /* Linux uses the lowest enslaved MAC address as the MAC address of
 362     * the bridge.  Set MAC address to a high value so that it doesn't
 363     * affect the MAC address of the bridge.
 364     */
 365    if (ioctl(ctlfd, SIOCGIFHWADDR, &ifr) < 0) {
 366        fprintf(stderr, "failed to get MAC address of device `%s': %s\n",
 367                iface, strerror(errno));
 368        ret = EXIT_FAILURE;
 369        goto cleanup;
 370    }
 371    ifr.ifr_hwaddr.sa_data[0] = 0xFE;
 372    if (ioctl(ctlfd, SIOCSIFHWADDR, &ifr) < 0) {
 373        fprintf(stderr, "failed to set MAC address of device `%s': %s\n",
 374                iface, strerror(errno));
 375        ret = EXIT_FAILURE;
 376        goto cleanup;
 377    }
 378
 379    /* add the interface to the bridge */
 380    prep_ifreq(&ifr, bridge);
 381    ifindex = if_nametoindex(iface);
 382#ifndef SIOCBRADDIF
 383    ifargs[0] = BRCTL_ADD_IF;
 384    ifargs[1] = ifindex;
 385    ifargs[2] = 0;
 386    ifargs[3] = 0;
 387    ifr.ifr_data = (void *)ifargs;
 388    ret = ioctl(ctlfd, SIOCDEVPRIVATE, &ifr);
 389#else
 390    ifr.ifr_ifindex = ifindex;
 391    ret = ioctl(ctlfd, SIOCBRADDIF, &ifr);
 392#endif
 393    if (ret == -1) {
 394        fprintf(stderr, "failed to add interface `%s' to bridge `%s': %s\n",
 395                iface, bridge, strerror(errno));
 396        ret = EXIT_FAILURE;
 397        goto cleanup;
 398    }
 399
 400    /* bring the interface up */
 401    prep_ifreq(&ifr, iface);
 402    if (ioctl(ctlfd, SIOCGIFFLAGS, &ifr) == -1) {
 403        fprintf(stderr, "failed to get interface flags for `%s': %s\n",
 404                iface, strerror(errno));
 405        ret = EXIT_FAILURE;
 406        goto cleanup;
 407    }
 408
 409    ifr.ifr_flags |= IFF_UP;
 410    if (ioctl(ctlfd, SIOCSIFFLAGS, &ifr) == -1) {
 411        fprintf(stderr, "failed to bring up interface `%s': %s\n",
 412                iface, strerror(errno));
 413        ret = EXIT_FAILURE;
 414        goto cleanup;
 415    }
 416
 417    /* write fd to the domain socket */
 418    if (send_fd(unixfd, fd) == -1) {
 419        fprintf(stderr, "failed to write fd to unix socket: %s\n",
 420                strerror(errno));
 421        ret = EXIT_FAILURE;
 422        goto cleanup;
 423    }
 424
 425    /* ... */
 426
 427    /* profit! */
 428
 429cleanup:
 430    if (fd >= 0) {
 431        close(fd);
 432    }
 433    if (ctlfd >= 0) {
 434        close(ctlfd);
 435    }
 436    while ((acl_rule = QSIMPLEQ_FIRST(&acl_list)) != NULL) {
 437        QSIMPLEQ_REMOVE_HEAD(&acl_list, entry);
 438        g_free(acl_rule);
 439    }
 440
 441    return ret;
 442}
 443