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 "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
  48enum {
  49    ACL_ALLOW = 0,
  50    ACL_ALLOW_ALL,
  51    ACL_DENY,
  52    ACL_DENY_ALL,
  53};
  54
  55typedef struct ACLRule {
  56    int type;
  57    char iface[IFNAMSIZ];
  58    QSIMPLEQ_ENTRY(ACLRule) entry;
  59} ACLRule;
  60
  61typedef QSIMPLEQ_HEAD(ACLList, ACLRule) ACLList;
  62
  63static void usage(void)
  64{
  65    fprintf(stderr,
  66            "Usage: qemu-bridge-helper [--use-vnet] --br=bridge --fd=unixfd\n");
  67}
  68
  69static 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
 152static 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
 167static 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
 173static 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
 201static 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
 221int 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
 402cleanup:
 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}
 411