|  | /* | 
|  | *  Copyright (C) 2000-2009, Parallels, Inc. All rights reserved. | 
|  | * | 
|  | *  This program is free software; you can redistribute it and/or modify | 
|  | *  it under the terms of the GNU General Public License as published by | 
|  | *  the Free Software Foundation; either version 2 of the License, or | 
|  | *  (at your option) any later version. | 
|  | * | 
|  | *  This program is distributed in the hope that it will be useful, | 
|  | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | *  GNU General Public License for more details. | 
|  | * | 
|  | *  You should have received a copy of the GNU General Public License | 
|  | *  along with this program; if not, write to the Free Software | 
|  | *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
|  | */ | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <unistd.h> | 
|  | #include <errno.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <string.h> | 
|  | #include <time.h> | 
|  |  | 
|  | #include <linux/vzcalluser.h> | 
|  | #include <linux/vzctl_veth.h> | 
|  |  | 
|  | #include "vzerror.h" | 
|  | #include "util.h" | 
|  | #include "veth.h" | 
|  | #include "env.h" | 
|  | #include "logger.h" | 
|  | #include "script.h" | 
|  |  | 
|  | static int veth_dev_mac_filter(vps_handler *h, envid_t veid, veth_dev *dev) | 
|  | { | 
|  | struct vzctl_ve_hwaddr veth; | 
|  | int ret; | 
|  |  | 
|  | veth.op = dev->mac_filter == YES ? VE_ETH_DENY_MAC_CHANGE : | 
|  | VE_ETH_ALLOW_MAC_CHANGE; | 
|  | veth.veid = veid; | 
|  | memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE); | 
|  | memcpy(veth.dev_name_ve, dev->dev_name_ve, IFNAMSIZE); | 
|  | ret = ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth); | 
|  | if (ret) { | 
|  | if (errno != ENODEV) { | 
|  | logger(-1, errno, "Unable to set mac filter"); | 
|  | ret = VZ_VETH_ERROR; | 
|  | } else | 
|  | ret = 0; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int veth_dev_create(vps_handler *h, envid_t veid, veth_dev *dev) | 
|  | { | 
|  | struct vzctl_ve_hwaddr veth; | 
|  | int ret; | 
|  |  | 
|  | if (!dev->dev_name[0] || dev->addrlen != ETH_ALEN) | 
|  | return EINVAL; | 
|  | if (dev->addrlen_ve != 0 && dev->addrlen_ve != ETH_ALEN) | 
|  | return EINVAL; | 
|  | veth.op = VE_ETH_ADD; | 
|  | veth.veid = veid; | 
|  | veth.addrlen = dev->addrlen; | 
|  | veth.addrlen_ve = dev->addrlen_ve; | 
|  | memcpy(veth.dev_addr, dev->dev_addr, ETH_ALEN); | 
|  | memcpy(veth.dev_addr_ve, dev->dev_addr_ve, ETH_ALEN); | 
|  | memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE); | 
|  | memcpy(veth.dev_name_ve, dev->dev_name_ve, IFNAMSIZE); | 
|  | ret = ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth); | 
|  | if (ret) { | 
|  | if (errno == ENOTTY) { | 
|  | logger(-1, 0, "Error: veth feature is" | 
|  | " not supported by kernel"); | 
|  | } else { | 
|  | logger(-1, errno, "Unable to create veth"); | 
|  | } | 
|  | ret = VZ_VETH_ERROR; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int veth_dev_remove(vps_handler *h, envid_t veid, veth_dev *dev) | 
|  | { | 
|  | struct vzctl_ve_hwaddr veth; | 
|  | int ret; | 
|  |  | 
|  | if (!dev->dev_name[0]) | 
|  | return EINVAL; | 
|  | veth.op = VE_ETH_DEL; | 
|  | veth.veid = veid; | 
|  | memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE); | 
|  | ret = ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth); | 
|  | if (ret) { | 
|  | if (errno != ENODEV) { | 
|  | logger(-1, errno, "Unable to remove veth"); | 
|  | ret = VZ_VETH_ERROR; | 
|  | } else | 
|  | ret = 0; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int run_vznetcfg(envid_t veid, veth_dev *dev) | 
|  | { | 
|  | int ret; | 
|  | char buf[16]; | 
|  | char *argv[] = {VZNETCFG, "init", "veth", NULL, NULL}; | 
|  | char *env[2]; | 
|  |  | 
|  | if (stat_file(VZNETCFG) != 1) | 
|  | return 0; | 
|  | argv[3] = dev->dev_name; | 
|  | snprintf(buf, sizeof(buf), "VEID=%d", veid); | 
|  | env[0] = buf; | 
|  | env[1] = NULL; | 
|  | if ((ret = run_script(VZNETCFG, argv, env, 0))) { | 
|  | logger(-1, 0, VZNETCFG " exited with error"); | 
|  | ret = VZ_VETH_ERROR; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** Create/remove veth devices for CT. | 
|  | * | 
|  | * @param h		CT handler. | 
|  | * @param veid		CT ID. | 
|  | * @param dev		devices list. | 
|  | * @return		0 on success. | 
|  | */ | 
|  | static int veth_ctl(vps_handler *h, envid_t veid, int op, veth_param *list, | 
|  | int rollback) | 
|  | { | 
|  | int ret = 0; | 
|  | char buf[256]; | 
|  | char *p, *ep; | 
|  | veth_dev *tmp; | 
|  | list_head_t *dev_h = &list->dev; | 
|  |  | 
|  | if (list_empty(dev_h)) | 
|  | return 0; | 
|  | if (!vps_is_run(h, veid)) { | 
|  | logger(-1, 0, "Unable to %s veth: container is not running", | 
|  | op == ADD ? "create" : "remove"); | 
|  | return VZ_VE_NOT_RUNNING; | 
|  | } | 
|  | buf[0] = 0; | 
|  | p = buf; | 
|  | ep = buf + sizeof(buf) - 1; | 
|  | list_for_each(tmp, dev_h, list) { | 
|  | p += snprintf(p, ep - p, "%s ", tmp->dev_name); | 
|  | if (p >= ep) | 
|  | break; | 
|  | } | 
|  | logger(0, 0, "%s veth devices: %s", | 
|  | op == ADD ? "Configure" : "Deleting", buf); | 
|  | list_for_each(tmp, dev_h, list) { | 
|  | if (op == ADD) { | 
|  | if (!tmp->active) { | 
|  | if ((ret = veth_dev_create(h, veid, tmp))) | 
|  | break; | 
|  | } | 
|  | tmp->flags = 1; | 
|  | if (tmp->mac_filter) { | 
|  | if ((ret = veth_dev_mac_filter(h, veid, tmp))) | 
|  | break; | 
|  | } | 
|  | if ((ret = run_vznetcfg(veid, tmp))) | 
|  | break; | 
|  | } else { | 
|  | if (!tmp->active) | 
|  | continue; | 
|  | if ((ret = veth_dev_remove(h, veid, tmp))) | 
|  | break; | 
|  | } | 
|  | } | 
|  | /* If operation failed remove added devices. | 
|  | * Remove devices from list to skip saving. | 
|  | */ | 
|  | if (ret && rollback) { | 
|  | list_for_each(tmp, dev_h, list) { | 
|  | if (op == ADD && tmp->flags == 1) | 
|  | veth_dev_remove(h, veid, tmp); | 
|  | } | 
|  | free_veth(dev_h); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int parse_hwaddr(const char *str, char *addr) | 
|  | { | 
|  | int i; | 
|  | char buf[3]; | 
|  | char *endptr; | 
|  |  | 
|  | for (i = 0; i < ETH_ALEN; i++) { | 
|  | buf[0] = str[3*i]; | 
|  | buf[1] = str[3*i+1]; | 
|  | buf[2] = '\0'; | 
|  | addr[i] = strtol(buf, &endptr, 16); | 
|  | if (*endptr != '\0') | 
|  | return ERR_INVAL; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void generate_mac(int veid, char *dev_name, char *mac) | 
|  | { | 
|  | int len, i; | 
|  | unsigned int hash, tmp; | 
|  | char data[128]; | 
|  |  | 
|  | snprintf(data, sizeof(data), "%s:%d:%ld ", dev_name, veid, time(NULL)); | 
|  | hash = veid; | 
|  | len = strlen(data) - 1; | 
|  | for (i = 0; i < len; i++) { | 
|  | hash += data[i]; | 
|  | tmp = (data[i + 1] << 11) ^ hash; | 
|  | hash = (hash << 16) ^ tmp; | 
|  | hash += hash >> 11; | 
|  | } | 
|  | hash ^= hash << 3; | 
|  | hash += hash >> 5; | 
|  | hash ^= hash << 4; | 
|  | hash += hash >> 17; | 
|  | hash ^= hash << 25; | 
|  | hash += hash >> 6; | 
|  | mac[0] = (char) (SW_OUI >> 0xf); | 
|  | mac[1] = (char) (SW_OUI >> 0x8); | 
|  | mac[2] = (char) SW_OUI; | 
|  | mac[3] = (char) hash; | 
|  | mac[4] = (char) (hash >> 0x8); | 
|  | mac[5] = (char) (hash >> 0xf); | 
|  | } | 
|  |  | 
|  | int add_veth_param(veth_param *veth, veth_dev *dev) | 
|  | { | 
|  | veth_dev *tmp; | 
|  |  | 
|  | if (list_is_init(&veth->dev)) | 
|  | list_head_init(&veth->dev); | 
|  | tmp = malloc(sizeof(*tmp)); | 
|  | if (tmp == NULL) | 
|  | return -1; | 
|  | memcpy(tmp, dev, sizeof(*tmp)); | 
|  | list_add_tail(&tmp->list, &veth->dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | veth_dev *find_veth(list_head_t *head, veth_dev *dev) | 
|  | { | 
|  | veth_dev *tmp; | 
|  |  | 
|  | if (list_empty(head)) | 
|  | return NULL; | 
|  | list_for_each(tmp, head, list) { | 
|  | if (!strcmp(tmp->dev_name, dev->dev_name)) | 
|  | return dev; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void free_veth_dev(veth_dev *dev) | 
|  | { | 
|  | } | 
|  |  | 
|  | void free_veth(list_head_t *head) | 
|  | { | 
|  | veth_dev *tmp, *dev_t; | 
|  |  | 
|  | if (list_empty(head)) | 
|  | return; | 
|  | list_for_each_safe(dev_t, tmp, head, list) { | 
|  | free_veth_dev(dev_t); | 
|  | list_del(&dev_t->list); | 
|  | free(dev_t); | 
|  | } | 
|  | list_head_init(head); | 
|  | } | 
|  |  | 
|  | void free_veth_param(veth_param *dev) | 
|  | { | 
|  | free_veth(&dev->dev); | 
|  | } | 
|  |  | 
|  | veth_dev *find_veth_by_ifname(list_head_t *head, char *name) | 
|  | { | 
|  | veth_dev *dev_t; | 
|  |  | 
|  | if (list_empty(head)) | 
|  | return NULL; | 
|  | list_for_each(dev_t, head, list) { | 
|  | if (!strcmp(dev_t->dev_name, name)) | 
|  | return dev_t; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | veth_dev *find_veth_by_ifname_ve(list_head_t *head, char *name) | 
|  | { | 
|  | veth_dev *dev_t; | 
|  |  | 
|  | if (list_empty(head)) | 
|  | return NULL; | 
|  | list_for_each(dev_t, head, list) { | 
|  | if (!strcmp(dev_t->dev_name_ve, name)) | 
|  | return dev_t; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | veth_dev *find_veth_configure(list_head_t *head) | 
|  | { | 
|  | veth_dev *dev_t; | 
|  |  | 
|  | if (list_empty(head)) | 
|  | return NULL; | 
|  | list_for_each(dev_t, head, list) { | 
|  | if (dev_t->configure) | 
|  | return dev_t; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void fill_veth_dev(veth_dev *dst, veth_dev *src) | 
|  | { | 
|  | if (src->dev_name[0] != 0) | 
|  | strcpy(dst->dev_name, src->dev_name); | 
|  | if (src->dev_bridge[0] != 0) | 
|  | strcpy(dst->dev_bridge, src->dev_bridge); | 
|  | if (src->addrlen != 0) { | 
|  | memcpy(dst->dev_addr, src->dev_addr, sizeof(dst->dev_addr)); | 
|  | dst->addrlen = src->addrlen; | 
|  | } | 
|  | if (src->dev_name_ve[0] != 0) | 
|  | strcpy(dst->dev_name_ve, src->dev_name_ve); | 
|  | if (src->addrlen_ve != 0) { | 
|  | memcpy(dst->dev_addr_ve, src->dev_addr_ve, sizeof(dst->dev_addr)); | 
|  | dst->addrlen_ve = src->addrlen_ve; | 
|  | } | 
|  | if (src->mac_filter) { | 
|  | dst->mac_filter = src->mac_filter; | 
|  | } | 
|  | } | 
|  |  | 
|  | int merge_veth_dev(veth_dev *old, veth_dev *new, veth_dev *merged) | 
|  | { | 
|  | memset(merged, 0, sizeof(veth_dev)); | 
|  |  | 
|  | if (old != NULL) | 
|  | fill_veth_dev(merged, old); | 
|  | fill_veth_dev(merged, new); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int merge_veth_list(list_head_t *old, list_head_t *add, list_head_t *del, | 
|  | veth_param *merged) | 
|  | { | 
|  | veth_dev *dev_t; | 
|  | veth_dev dev; | 
|  | list_head_t empty; | 
|  |  | 
|  | list_head_init(&empty); | 
|  | if (old == NULL) | 
|  | old = ∅ | 
|  | if (list_is_init(old)) | 
|  | list_head_init(old); | 
|  | if (add == NULL) | 
|  | add = ∅ | 
|  | if (list_is_init(add)) | 
|  | list_head_init(add); | 
|  | if (del == NULL) | 
|  | del = ∅ | 
|  | if (list_is_init(del)) | 
|  | list_head_init(del); | 
|  |  | 
|  | list_for_each(dev_t, old, list) { | 
|  | veth_dev *tmp; | 
|  | /* Skip old devices that was deleted */ | 
|  | if (find_veth_by_ifname_ve(del, dev_t->dev_name_ve) != NULL) | 
|  | continue; | 
|  | tmp = find_veth_by_ifname_ve(add, dev_t->dev_name_ve); | 
|  | if (tmp != NULL) { | 
|  | merge_veth_dev(dev_t, tmp, &dev); | 
|  | if (add_veth_param(merged, &dev)) | 
|  | return 1; | 
|  | free_veth_dev(&dev); | 
|  | continue; | 
|  | } | 
|  | /* Add old devices */ | 
|  | if (add_veth_param(merged, dev_t)) | 
|  | return 1; | 
|  | } | 
|  | list_for_each(dev_t, add, list) { | 
|  | if (find_veth_by_ifname_ve(old, dev_t->dev_name_ve) == NULL) { | 
|  | if (add_veth_param(merged, dev_t)) | 
|  | return 1; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int copy_veth_param(veth_param *dst, veth_param *src) | 
|  | { | 
|  | veth_dev *dev; | 
|  |  | 
|  | list_for_each(dev, &src->dev, list) { | 
|  | if (add_veth_param(dst, dev)) | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int read_proc_veth(envid_t veid, veth_param *veth) | 
|  | { | 
|  | FILE *fp; | 
|  | char buf[256]; | 
|  | char mac[MAC_SIZE + 1]; | 
|  | char mac_ve[MAC_SIZE + 1]; | 
|  | char dev_name[IFNAMSIZE + 1]; | 
|  | char dev_name_ve[IFNAMSIZE + 1]; | 
|  | envid_t id; | 
|  | veth_dev dev; | 
|  |  | 
|  | fp = fopen(PROC_VETH, "r"); | 
|  | if (fp == NULL) | 
|  | return -1; | 
|  | memset(&dev, 0, sizeof(dev)); | 
|  | while (fgets(buf, sizeof(buf), fp) != NULL) { | 
|  | if (sscanf(buf, "%17s %15s %17s %15s %d", | 
|  | mac, dev_name, mac_ve, dev_name_ve, &id) != 5) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | if (veid != id) | 
|  | continue; | 
|  | parse_hwaddr(mac, dev.dev_addr); | 
|  | parse_hwaddr(mac_ve, dev.dev_addr_ve); | 
|  | strncpy(dev.dev_name, dev_name, IFNAMSIZE); | 
|  | dev.dev_name[IFNAMSIZE - 1] = 0; | 
|  | strncpy(dev.dev_name_ve, dev_name_ve, IFNAMSIZE); | 
|  | dev.dev_name_ve[IFNAMSIZE - 1] = 0; | 
|  | dev.active = 1; | 
|  | add_veth_param(veth, &dev); | 
|  | } | 
|  | fclose(fp); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void fill_veth_dev_name(veth_param *configured, veth_param *new) | 
|  | { | 
|  | veth_dev *it, *dev; | 
|  |  | 
|  | if (list_empty(&configured->dev)) | 
|  | return; | 
|  | list_for_each(it, &new->dev, list) { | 
|  | dev = find_veth_by_ifname_ve(&configured->dev, it->dev_name_ve); | 
|  | if (dev != NULL) { | 
|  | if (*it->dev_name == '\0') | 
|  | strcpy(it->dev_name, dev->dev_name); | 
|  | it->active = 1; | 
|  | } else { | 
|  | logger(-1, 0, "Container does not have " | 
|  | "configured veth: %s, skipped", | 
|  | it->dev_name_ve); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int vps_setup_veth(vps_handler *h, envid_t veid, dist_actions *actions, | 
|  | const char *root, veth_param *veth_add, veth_param *veth_del, | 
|  | int state, int skip) | 
|  | { | 
|  | int ret, dev_num; | 
|  | veth_param veth_old; | 
|  |  | 
|  | if (list_empty(&veth_add->dev) && | 
|  | list_empty(&veth_del->dev) && | 
|  | veth_add->delall != YES) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | ret = 0; | 
|  | memset(&veth_old, 0, sizeof(veth_old)); | 
|  | list_head_init(&veth_old.dev); | 
|  | if (state != STATE_STARTING) | 
|  | read_proc_veth(veid, &veth_old); | 
|  | if (veth_add->delall == YES) { | 
|  | veth_ctl(h, veid, DEL, &veth_old, 0); | 
|  | if (!list_empty(&veth_old.dev)) | 
|  | free_veth_param(&veth_old); | 
|  | } else if (!list_empty(&veth_del->dev)) { | 
|  | dev_num = 0; | 
|  | fill_veth_dev_name(&veth_old, veth_del); | 
|  | veth_ctl(h, veid, DEL, veth_del, 0); | 
|  | } | 
|  | if (!list_empty(&veth_add->dev)) { | 
|  | fill_veth_dev_name(&veth_old, veth_add); | 
|  | ret = veth_ctl(h, veid, ADD, veth_add, 1); | 
|  | } | 
|  | if (!list_empty(&veth_old.dev)) | 
|  | free_veth_param(&veth_old); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int check_veth_param(envid_t veid, veth_param *veth_old, veth_param *veth_new, | 
|  | veth_param *veth_del) | 
|  | { | 
|  | int merge; | 
|  | veth_dev *dev_t, *dev; | 
|  |  | 
|  | /* merge data for --veth_del */ | 
|  | list_for_each(dev, &veth_del->dev, list) { | 
|  | if (dev->dev_name[0] == 0) | 
|  | continue; | 
|  | dev_t = find_veth_by_ifname(&veth_old->dev, dev->dev_name); | 
|  | if (dev_t != NULL) | 
|  | fill_veth_dev(dev, dev_t); | 
|  | } | 
|  |  | 
|  | dev_t = find_veth_configure(&veth_new->dev); | 
|  | if (dev_t == NULL) | 
|  | return 0; | 
|  | if (dev_t->dev_name_ve[0] == 0) { | 
|  | logger(-1, 0, "Invalid usage.  Option --ifname not specified"); | 
|  | return -1; | 
|  | } | 
|  | /* merge --netif_add & --ifname */ | 
|  | merge = 0; | 
|  | list_for_each(dev, &veth_new->dev, list) { | 
|  | if (dev != dev_t && | 
|  | !strcmp(dev->dev_name_ve, dev_t->dev_name_ve)) | 
|  | { | 
|  | fill_veth_dev(dev_t, dev); | 
|  | dev_t->configure = 0; | 
|  | list_del(&dev->list); | 
|  | free_veth_dev(dev); | 
|  | free(dev); | 
|  | merge = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | /* Is corresponding device configured for --ifname <iface> */ | 
|  | if (!merge && | 
|  | (veth_old == NULL || | 
|  | find_veth_by_ifname_ve(&veth_old->dev, dev_t->dev_name_ve) == NULL)) | 
|  | { | 
|  | logger(-1, 0, "Invalid usage: veth device %s is" | 
|  | " not configured, use --netif_add option first", | 
|  | dev_t->dev_name_ve); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } |