| /* |
| * Copyright (C) 2000-2011, 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 <stdio.h> |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <linux/vzcalluser.h> |
| #include <linux/vzctl_venet.h> |
| |
| #include "types.h" |
| #include "net.h" |
| #include "vzerror.h" |
| #include "logger.h" |
| #include "list.h" |
| #include "dist.h" |
| #include "exec.h" |
| #include "env.h" |
| #include "script.h" |
| #include "util.h" |
| #include "vps_configure.h" |
| |
| |
| char *find_ip(list_head_t *ip_h, const char *ipaddr) |
| { |
| ip_param *ip; |
| char *slash, *ip_slash, *ip_only; |
| int len; |
| |
| if (list_empty(ip_h)) |
| return NULL; |
| |
| slash = strchr(ipaddr, '/'); |
| if (slash) { |
| len = slash - ipaddr + 1; |
| ip_slash = strndupa(ipaddr, len); |
| ip_only = strndupa(ipaddr, len - 1); |
| } |
| else { |
| ip_only = NULL; |
| len = asprintf(&ip_slash, "%s/", ipaddr); |
| } |
| |
| list_for_each(ip, ip_h, list) { |
| /* Match complete IP or IP/mask string */ |
| if (!strcmp(ip->val, ipaddr)) |
| return ip->val; |
| /* Match only IP */ |
| if ((ip_only != NULL) && (!strcmp(ip->val, ip_only))) |
| return ip->val; |
| /* Match IP/ (same IP, different mask) */ |
| if (!strncmp(ip->val, ip_slash, len)) |
| return ip->val; |
| } |
| return NULL; |
| } |
| |
| int merge_ip_list(int delall, list_head_t *old, list_head_t *add, |
| list_head_t *del, list_head_t *merged) |
| { |
| return __merge_str_list(delall, old, add, del, merged, find_ip); |
| } |
| |
| static int ip_ctl(vps_handler *h, envid_t veid, int op, char *str) |
| { |
| const char *ip, *mask; |
| |
| mask = strchr(str, '/'); |
| if (mask) { |
| ip = strndupa(str, mask - str); |
| mask++; |
| } |
| else { |
| ip = str; |
| } |
| |
| return h->ip_ctl(h, veid, op, ip); |
| } |
| |
| int run_net_script(envid_t veid, int op, list_head_t *ip_h, int state, |
| int skip_arpdetect) |
| { |
| char *argv[3]; |
| char *envp[10]; |
| char *script; |
| int ret; |
| char buf[STR_SIZE]; |
| int i = 0; |
| char *skip_str = "SKIP_ARPDETECT=yes"; |
| |
| if (list_empty(ip_h)) |
| return 0; |
| snprintf(buf, sizeof(buf), "VEID=%d", veid); |
| envp[i++] = strdup(buf); |
| snprintf(buf, sizeof(buf), "VE_STATE=%s", state2str(state)); |
| envp[i++] = strdup(buf); |
| envp[i++] = list2str("IP_ADDR", ip_h); |
| envp[i++] = strdup(ENV_PATH); |
| if (skip_arpdetect) |
| envp[i++] = strdup(skip_str); |
| envp[i] = NULL; |
| switch (op) { |
| case ADD: |
| script = VPS_NET_ADD; |
| break; |
| case DEL: |
| script = VPS_NET_DEL; |
| break; |
| default: |
| return 0; |
| } |
| argv[0] = script; |
| argv[1] = NULL; |
| ret = run_script(script, argv, envp, 0); |
| free_arg(envp); |
| |
| return ret; |
| } |
| |
| static inline int invert_ip_op(int op) |
| { |
| if (op == VE_IP_ADD) |
| return VE_IP_DEL; |
| else |
| return VE_IP_ADD; |
| } |
| |
| static int vps_ip_ctl(vps_handler *h, envid_t veid, int op, |
| list_head_t *ip_h, int rollback) |
| { |
| int ret = 0; |
| ip_param *ip; |
| int inv_op; |
| |
| list_for_each(ip, ip_h, list) { |
| if ((ret = ip_ctl(h, veid, op, ip->val))) |
| break; |
| } |
| if (ret && rollback) { |
| /* restore original ip state op of error */ |
| inv_op = invert_ip_op(op); |
| list_for_each_prev(ip, ip_h, list) { |
| ip_ctl(h, veid, inv_op, ip->val); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int vps_add_ip(vps_handler *h, envid_t veid, |
| net_param *net, int state) |
| { |
| char *str; |
| int ret; |
| list_head_t *ip_h = &net->ip; |
| |
| if ((str = list2str(NULL, ip_h)) != NULL) { |
| if (str[0] != '\0') |
| logger(0, 0, "Adding IP address(es): %s", str); |
| free(str); |
| } |
| if ((ret = vps_ip_ctl(h, veid, VE_IP_ADD, ip_h, 1))) |
| return ret; |
| if ((ret = run_net_script(veid, ADD, ip_h, state, net->skip_arpdetect))) |
| vps_ip_ctl(h, veid, VE_IP_DEL, ip_h, 0); |
| |
| return ret; |
| } |
| |
| static int vps_del_ip(vps_handler *h, envid_t veid, |
| net_param *net, int state) |
| { |
| char *str; |
| int ret; |
| list_head_t *ip_h = &net->ip; |
| |
| if (net->delall) |
| /* Add existing VE IPs to the list */ |
| if (get_vps_ip(h, veid, ip_h) < 0) |
| return VZ_GET_IP_ERROR; |
| |
| str = list2str(NULL, ip_h); |
| if ((str != NULL) || (net->delall)) { |
| logger(0, 0, "Deleting %sIP address(es): %s", |
| (net->delall) ? "all " : "", |
| str ? str : ""); |
| free(str); |
| } |
| if ((ret = vps_ip_ctl(h, veid, VE_IP_DEL, ip_h, 1))) |
| return ret; |
| run_net_script(veid, DEL, ip_h, state, net->skip_arpdetect); |
| |
| return ret; |
| } |
| |
| static int set_netdev(vps_handler *h, envid_t veid, int cmd, net_param *net) |
| { |
| int ret = 0; |
| list_head_t *dev_h = &net->dev; |
| net_dev_param *dev; |
| |
| if (list_empty(dev_h)) |
| return 0; |
| list_for_each(dev, dev_h, list) { |
| if ((ret = h->netdev_ctl(h, veid, cmd, dev->val))) { |
| logger(-1, errno, "Unable to %s netdev %s", |
| (cmd == VE_NETDEV_ADD ) ? "add": "del", |
| dev->val); |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| int vps_set_netdev(vps_handler *h, envid_t veid, ub_param *ub, |
| net_param *net_add, net_param *net_del) |
| { |
| int ret, pid, pid1, status; |
| |
| if (list_empty(&net_add->dev) && list_empty(&net_del->dev)) |
| return 0; |
| if (!vps_is_run(h, veid)) { |
| logger(-1, 0, "Unable to setup network devices: " |
| "container is not running"); |
| return VZ_VE_NOT_RUNNING; |
| } |
| |
| if ((ret = set_netdev(h, veid, VE_NETDEV_DEL, net_del))) |
| return ret; |
| |
| /* Adding device(s) to CT should be done under setluid() */ |
| if ((pid1 = fork()) < 0) { |
| logger(-1, errno, "Can't fork"); |
| return VZ_RESOURCE_ERROR; |
| } else if (pid1 == 0) { |
| |
| if ((ret = h->setcontext(veid))) |
| exit(ret); |
| exit(set_netdev(h, veid, VE_NETDEV_ADD, net_add)); |
| } |
| while ((pid = waitpid(pid1, &status, 0)) == -1) |
| if (errno != EINTR) { |
| logger(-1, errno, "Error in waitpid()"); |
| break; |
| } |
| ret = VZ_SYSTEM_ERROR; |
| if (pid == pid1) { |
| if (WIFEXITED(status)) |
| ret = WEXITSTATUS(status); |
| else if (WIFSIGNALED(status)) |
| logger(-1, 0, "Got signal %d", WTERMSIG(status)); |
| } else if (pid < 0) |
| logger(-1, errno, "Error in waitpid()"); |
| |
| return ret; |
| } |
| |
| static int remove_ipv6_addr(net_param *net) |
| { |
| list_head_t *head = &net->ip; |
| ip_param *ip, *tmp; |
| int cnt; |
| |
| cnt = 0; |
| list_for_each_safe(ip, tmp, head, list) { |
| if (get_addr_family(ip->val) == AF_INET6) { |
| free(ip->val); |
| list_del(&ip->list); |
| free(ip); |
| cnt++; |
| } |
| } |
| return cnt; |
| } |
| |
| int vps_net_ctl(vps_handler *h, envid_t veid, int op, net_param *net, |
| dist_actions *actions, const char *root, int state, int skip) |
| { |
| int ret = 0; |
| |
| if (list_empty(&net->ip) && /* Skip if no IPs in list, */ |
| /* except for these cases: |
| * (1) starting, do always run ADD */ |
| !(state == STATE_STARTING && op == ADD) && |
| /* (2) deleting all, do delete */ |
| !(op == DEL && net->delall)) |
| return 0; |
| |
| if (!vps_is_run(h, veid)) { |
| logger(-1, 0, "Unable to apply network parameters: " |
| "container is not running"); |
| return VZ_VE_NOT_RUNNING; |
| } |
| if (net->ipv6_net != YES) { |
| if (remove_ipv6_addr(net)) |
| logger(0, 0, "WARNING: IPv6 support is disabled"); |
| } |
| if (op == ADD) { |
| ret = vps_add_ip(h, veid, net, state); |
| } else if (op == DEL) { |
| ret = vps_del_ip(h, veid, net, state); |
| } |
| if (!ret && !(skip & SKIP_CONFIGURE)) |
| vps_ip_configure(h, veid, actions, root, op, net, state); |
| return ret; |
| } |
| |
| #define PROC_VEINFO "/proc/vz/veinfo" |
| static int get_vps_ip_proc(envid_t veid, list_head_t *ip_h) |
| { |
| FILE *fd; |
| char str[16384]; |
| char data[16]; |
| char *token; |
| int id, cnt = 0; |
| |
| if ((fd = fopen(PROC_VEINFO, "r")) == NULL) { |
| logger(-1, errno, "Unable to open %s", PROC_VEINFO); |
| return -1; |
| } |
| while (!feof(fd)) { |
| if (fgets(str, sizeof(str), fd) == NULL) |
| break; |
| token = strtok(str, " "); |
| if (token == NULL) |
| continue; |
| if (parse_int(token, &id)) |
| continue; |
| if (veid != (envid_t)id) |
| continue; |
| if ((token = strtok(NULL, " ")) != NULL) |
| token = strtok(NULL, " "); |
| if (token == NULL) |
| break; |
| while ((token = strtok(NULL, " \t\n")) != NULL) { |
| /* Canonicalize IPv6 addresses */ |
| if ((get_addr_family(token) == AF_INET6) && |
| inet_pton(AF_INET6, token, data) > 0 && |
| !inet_ntop(AF_INET6, data, token, strlen(token)+1)) |
| break; |
| |
| if (add_str_param(ip_h, token)) { |
| free_str_param(ip_h); |
| cnt = -1; |
| break; |
| } |
| cnt++; |
| } |
| break; |
| } |
| fclose(fd); |
| return cnt; |
| } |
| |
| #if HAVE_VZLIST_IOCTL |
| #ifndef NIPQUAD |
| #define NIPQUAD(addr) \ |
| ((unsigned char *)&addr)[0], \ |
| ((unsigned char *)&addr)[1], \ |
| ((unsigned char *)&addr)[2], \ |
| ((unsigned char *)&addr)[3] |
| #endif |
| |
| static int get_vps_ip_ioctl(vps_handler *h, envid_t veid, |
| list_head_t *ip_h) |
| { |
| int ret = -1; |
| struct vzlist_veipv4ctl veip; |
| uint32_t *addr, *tmp; |
| char buf[64]; |
| int i; |
| |
| veip.veid = veid; |
| veip.num = 256; |
| addr = malloc(veip.num * sizeof(*veip.ip)); |
| if (addr == NULL) |
| return -1; |
| for (;;) { |
| veip.ip = addr; |
| ret = ioctl(h->vzfd, VZCTL_GET_VEIPS, &veip); |
| if (ret < 0) |
| goto out; |
| else if (ret <= veip.num) |
| break; |
| veip.num = ret; |
| tmp = realloc(addr, veip.num * sizeof(*veip.ip)); |
| if (tmp == NULL) { |
| ret = -1; |
| goto out; |
| } |
| addr = tmp; |
| } |
| if (ret > 0) { |
| for (i = ret - 1; i >= 0; i--) { |
| snprintf(buf, sizeof(buf), "%d.%d.%d.%d", |
| NIPQUAD(addr[i])); |
| if ((ret = add_str_param(ip_h, buf))) |
| break; |
| } |
| } |
| out: |
| free(addr); |
| return ret; |
| } |
| |
| int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ip_h) |
| { |
| int ret; |
| ret = get_vps_ip_ioctl(h, veid, ip_h); |
| if (ret < 0) |
| ret = get_vps_ip_proc(veid, ip_h); |
| if (ret < 0) |
| free_str_param(ip_h); |
| return ret; |
| } |
| #else |
| |
| int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ip_h) |
| { |
| int ret; |
| |
| if ((ret = get_vps_ip_proc(veid, ip_h)) < 0) |
| free_str_param(ip_h); |
| return ret; |
| } |
| #endif |