blob: aedc53a3f5e05022c89ad17550f6bdf65ef75460 [file] [log] [blame] [raw]
/*
* 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_address(list_head_t *ipaddr_h, const char *ipaddr)
{
ip_param *ip;
char *slash, *ip_slash, *ip_only;
int len;
if (list_empty(ipaddr_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, ipaddr_h, list) {
/* Match complete address or address/mask string */
if (!strcmp(ip->val, ipaddr))
return ip->val;
/* Match only address */
if ((ip_only != NULL) && (!strcmp(ip->val, ip_only)))
return ip->val;
/* Match address/ (same address, 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_address);
}
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 *ipaddr_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(ipaddr_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", ipaddr_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 *ipaddr_h, int rollback)
{
int ret = 0;
ip_param *ip;
int inv_op;
list_for_each(ip, ipaddr_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, ipaddr_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 *ipaddr_h = &net->ipaddr;
if ((str = list2str(NULL, ipaddr_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, ipaddr_h, 1)))
return ret;
if ((ret = run_net_script(veid, ADD, ipaddr_h, state, net->skip_arpdetect)))
vps_ip_ctl(h, veid, VE_IP_DEL, ipaddr_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 *ipaddr_h = &net->ipaddr;
if (net->delall) {
/* Add existing VE IP addresses to the list */
if (get_vps_ip(h, veid, ipaddr_h) < 0) return VZ_GET_IP_ERROR;
}
str = list2str(NULL, ipaddr_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, ipaddr_h, 1)))
return ret;
run_net_script(veid, DEL, ipaddr_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->ipaddr;
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->ipaddr) && /* 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 *ipaddr_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(ipaddr_h, token)) {
free_str_param(ipaddr_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 *ipaddr_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(ipaddr_h, buf))) {
break;
}
}
}
out:
free(addr);
return ret;
}
int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ipaddr_h)
{
int ret;
ret = get_vps_ip_ioctl(h, veid, ipaddr_h);
if (ret < 0) {
ret = get_vps_ip_proc(veid, ipaddr_h);
if (ret < 0) free_str_param(ipaddr_h);
}
return ret;
}
#else
int get_vps_ip(vps_handler *h, envid_t veid, list_head_t *ipaddr_h)
{
int ret = get_vps_ip_proc(veid, ipaddr_h);
if(ret < 0) free_str_param(ipaddr_h);
return ret;
}
#endif