2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-22 09:58:09 +00:00
criu/net.c

624 lines
14 KiB
C
Raw Normal View History

#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <string.h>
#include <net/if_arp.h>
#include <sys/wait.h>
#include <sched.h>
#include <sys/mount.h>
#include "fdset.h"
#include "syscall-types.h"
#include "namespaces.h"
#include "net.h"
#include "libnetlink.h"
#include "cr_options.h"
#include "sk-inet.h"
#include "tun.h"
#include "util-pie.h"
#include "plugin.h"
#include "protobuf.h"
#include "protobuf/netdev.pb-c.h"
static int ns_fd = -1;
static int ns_sysfs_fd = -1;
int read_ns_sys_file(char *path, char *buf, int len)
{
int fd, rlen;
BUG_ON(ns_sysfs_fd == -1);
fd = openat(ns_sysfs_fd, path, O_RDONLY, 0);
if (fd < 0) {
pr_perror("Can't open ns' %s", path);
return -1;
}
rlen = read(fd, buf, len);
close(fd);
if (rlen >= 0)
buf[rlen] = '\0';
return rlen;
}
int write_netdev_img(NetDeviceEntry *nde, struct cr_fdset *fds)
{
return pb_write_one(fdset_fd(fds, CR_FD_NETDEV), nde, PB_NETDEV);
}
static int dump_one_netdev(int type, struct ifinfomsg *ifi,
struct rtattr **tb, struct cr_fdset *fds,
int (*dump)(NetDeviceEntry *, struct cr_fdset *))
{
NetDeviceEntry netdev = NET_DEVICE_ENTRY__INIT;
if (!tb[IFLA_IFNAME]) {
pr_err("No name for link %d\n", ifi->ifi_index);
return -1;
}
netdev.type = type;
netdev.ifindex = ifi->ifi_index;
netdev.mtu = *(int *)RTA_DATA(tb[IFLA_MTU]);
netdev.flags = ifi->ifi_flags;
netdev.name = RTA_DATA(tb[IFLA_IFNAME]);
if (tb[IFLA_ADDRESS] && (type != ND_TYPE__LOOPBACK)) {
netdev.has_address = true;
netdev.address.data = RTA_DATA(tb[IFLA_ADDRESS]);
netdev.address.len = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
pr_info("Found ll addr (%02x:../%d) for %s\n",
(int)netdev.address.data[0],
(int)netdev.address.len, netdev.name);
}
if (!dump)
dump = write_netdev_img;
return dump(&netdev, fds);
}
static char *link_kind(struct ifinfomsg *ifi, struct rtattr **tb)
{
struct rtattr *linkinfo[IFLA_INFO_MAX + 1];
if (!tb[IFLA_LINKINFO]) {
pr_err("No linkinfo for eth link %d\n", ifi->ifi_index);
return NULL;
}
parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
if (!linkinfo[IFLA_INFO_KIND]) {
pr_err("No kind for eth link %d\n", ifi->ifi_index);
return NULL;
}
return RTA_DATA(linkinfo[IFLA_INFO_KIND]);
}
static int dump_unknown_device(struct ifinfomsg *ifi, char *kind,
struct rtattr **tb, struct cr_fdset *fds)
{
int ret;
ret = cr_plugin_dump_ext_link(ifi->ifi_index, ifi->ifi_type, kind);
if (ret == 0)
return dump_one_netdev(ND_TYPE__EXTLINK, ifi, tb, fds, NULL);
if (ret == -ENOTSUP)
pr_err("Unsupported link %d (type %d kind %s)\n",
ifi->ifi_index, ifi->ifi_type, kind);
return -1;
}
static int dump_one_ethernet(struct ifinfomsg *ifi, char *kind,
struct rtattr **tb, struct cr_fdset *fds)
{
if (!strcmp(kind, "veth"))
/*
* This is not correct. The peer of the veth device may
* be either outside or inside the netns we're working
* on, but there's currently no way of finding this out.
*
* Sigh... we have to assume, that the veth device is a
* connection to the outer world and just dump this end :(
*/
return dump_one_netdev(ND_TYPE__VETH, ifi, tb, fds, NULL);
if (!strcmp(kind, "tun"))
return dump_one_netdev(ND_TYPE__TUN, ifi, tb, fds, dump_tun_link);
return dump_unknown_device(ifi, kind, tb, fds);
}
static int dump_one_gendev(struct ifinfomsg *ifi, char *kind,
struct rtattr **tb, struct cr_fdset *fds)
{
if (!strcmp(kind, "tun"))
return dump_one_netdev(ND_TYPE__TUN, ifi, tb, fds, dump_tun_link);
return dump_unknown_device(ifi, kind, tb, fds);
}
static int dump_one_voiddev(struct ifinfomsg *ifi, char *kind,
struct rtattr **tb, struct cr_fdset *fds)
{
if (!strcmp(kind, "venet"))
/*
* If we meet a link we know about, such as
* OpenVZ's venet, save general parameters of
* it as external link.
*/
return dump_one_netdev(ND_TYPE__EXTLINK, ifi, tb, fds, NULL);
return dump_unknown_device(ifi, kind, tb, fds);
}
static int dump_one_link(struct nlmsghdr *hdr, void *arg)
{
struct cr_fdset *fds = arg;
struct ifinfomsg *ifi;
int ret = 0, len = hdr->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
struct rtattr *tb[IFLA_MAX + 1];
char *kind;
ifi = NLMSG_DATA(hdr);
if (len < 0) {
pr_err("No iflas for link %d\n", ifi->ifi_index);
return -1;
}
parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
pr_info("\tLD: Got link %d, type %d\n", ifi->ifi_index, ifi->ifi_type);
if (ifi->ifi_type == ARPHRD_LOOPBACK)
return dump_one_netdev(ND_TYPE__LOOPBACK, ifi, tb, fds, NULL);
kind = link_kind(ifi, tb);
if (!kind)
goto unk;
switch (ifi->ifi_type) {
case ARPHRD_ETHER:
ret = dump_one_ethernet(ifi, kind, tb, fds);
break;
case ARPHRD_NONE:
ret = dump_one_gendev(ifi, kind, tb, fds);
break;
case ARPHRD_VOID:
ret = dump_one_voiddev(ifi, kind, tb, fds);
break;
default:
unk:
ret = dump_unknown_device(ifi, kind, tb, fds);
break;
}
return ret;
}
static int dump_links(struct cr_fdset *fds)
{
int sk, ret;
struct {
struct nlmsghdr nlh;
struct rtgenmsg g;
} req;
pr_info("Dumping netns links\n");
ret = sk = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sk < 0) {
pr_perror("Can't open rtnl sock for net dump");
goto out;
}
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = RTM_GETLINK;
req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
req.nlh.nlmsg_pid = 0;
req.nlh.nlmsg_seq = CR_NLMSG_SEQ;
req.g.rtgen_family = AF_PACKET;
ret = do_rtnl_req(sk, &req, sizeof(req), dump_one_link, fds);
close(sk);
out:
return ret;
}
static int restore_link_cb(struct nlmsghdr *hdr, void *arg)
{
pr_info("Got response on SETLINK =)\n");
return 0;
}
struct newlink_req {
struct nlmsghdr h;
struct ifinfomsg i;
char buf[1024];
};
static int do_rtm_link_req(int msg_type, NetDeviceEntry *nde, int nlsk,
int (*link_info)(NetDeviceEntry *, struct newlink_req *))
{
struct newlink_req req;
memset(&req, 0, sizeof(req));
req.h.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.h.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE;
req.h.nlmsg_type = msg_type;
req.h.nlmsg_seq = CR_NLMSG_SEQ;
req.i.ifi_family = AF_PACKET;
/*
* SETLINK is called for external devices which may
* have ifindex changed. Thus configure them by their
* name only.
*/
if (msg_type == RTM_NEWLINK)
req.i.ifi_index = nde->ifindex;
req.i.ifi_flags = nde->flags;
addattr_l(&req.h, sizeof(req), IFLA_IFNAME, nde->name, strlen(nde->name));
addattr_l(&req.h, sizeof(req), IFLA_MTU, &nde->mtu, sizeof(nde->mtu));
if (nde->has_address) {
pr_debug("Restore ll addr (%02x:../%d) for device\n",
(int)nde->address.data[0], (int)nde->address.len);
addattr_l(&req.h, sizeof(req), IFLA_ADDRESS,
nde->address.data, nde->address.len);
}
if (link_info) {
struct rtattr *linkinfo;
int ret;
linkinfo = NLMSG_TAIL(&req.h);
addattr_l(&req.h, sizeof(req), IFLA_LINKINFO, NULL, 0);
ret = link_info(nde, &req);
if (ret < 0)
return ret;
linkinfo->rta_len = (void *)NLMSG_TAIL(&req.h) - (void *)linkinfo;
}
return do_rtnl_req(nlsk, &req, req.h.nlmsg_len, restore_link_cb, NULL);
}
int restore_link_parms(NetDeviceEntry *nde, int nlsk)
{
return do_rtm_link_req(RTM_SETLINK, nde, nlsk, NULL);
}
static int restore_one_link(NetDeviceEntry *nde, int nlsk,
int (*link_info)(NetDeviceEntry *, struct newlink_req *))
{
pr_info("Restoring netdev %s idx %d\n", nde->name, nde->ifindex);
return do_rtm_link_req(RTM_NEWLINK, nde, nlsk, link_info);
}
#ifndef VETH_INFO_MAX
enum {
VETH_INFO_UNSPEC,
VETH_INFO_PEER,
__VETH_INFO_MAX
#define VETH_INFO_MAX (__VETH_INFO_MAX - 1)
};
#endif
#if IFLA_MAX <= 28
#define IFLA_NET_NS_FD 28
#endif
static int veth_link_info(NetDeviceEntry *nde, struct newlink_req *req)
{
struct rtattr *veth_data, *peer_data;
struct ifinfomsg ifm;
struct veth_pair *n;
BUG_ON(ns_fd < 0);
addattr_l(&req->h, sizeof(*req), IFLA_INFO_KIND, "veth", 4);
veth_data = NLMSG_TAIL(&req->h);
addattr_l(&req->h, sizeof(*req), IFLA_INFO_DATA, NULL, 0);
peer_data = NLMSG_TAIL(&req->h);
memset(&ifm, 0, sizeof(ifm));
addattr_l(&req->h, sizeof(*req), VETH_INFO_PEER, &ifm, sizeof(ifm));
list_for_each_entry(n, &opts.veth_pairs, node) {
if (!strcmp(nde->name, n->inside))
break;
}
if (&n->node != &opts.veth_pairs)
addattr_l(&req->h, sizeof(*req), IFLA_IFNAME, n->outside, strlen(n->outside));
addattr_l(&req->h, sizeof(*req), IFLA_NET_NS_FD, &ns_fd, sizeof(ns_fd));
peer_data->rta_len = (void *)NLMSG_TAIL(&req->h) - (void *)peer_data;
veth_data->rta_len = (void *)NLMSG_TAIL(&req->h) - (void *)veth_data;
return 0;
}
static int restore_link(NetDeviceEntry *nde, int nlsk)
{
pr_info("Restoring link %s type %d\n", nde->name, nde->type);
switch (nde->type) {
case ND_TYPE__LOOPBACK: /* fallthrough */
case ND_TYPE__EXTLINK: /* see comment in protobuf/netdev.proto */
return restore_link_parms(nde, nlsk);
case ND_TYPE__VETH:
return restore_one_link(nde, nlsk, veth_link_info);
case ND_TYPE__TUN:
return restore_one_tun(nde, nlsk);
default:
pr_err("Unsupported link type %d\n", nde->type);
break;
}
return -1;
}
static int restore_links(int pid)
{
int fd, nlsk, ret;
NetDeviceEntry *nde;
fd = open_image(CR_FD_NETDEV, O_RSTR, pid);
if (fd < 0)
return -1;
nlsk = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (nlsk < 0) {
pr_perror("Can't create nlk socket");
close_safe(&fd);
return -1;
}
while (1) {
ret = pb_read_one_eof(fd, &nde, PB_NETDEV);
if (ret <= 0)
break;
ret = restore_link(nde, nlsk);
net_device_entry__free_unpacked(nde, NULL);
if (ret)
break;
}
close(nlsk);
close(fd);
return ret;
}
static int run_ip_tool(char *arg1, char *arg2, int fdin, int fdout)
{
char *ip_tool_cmd;
int ret;
pr_debug("\tRunning ip %s %s\n", arg1, arg2);
ip_tool_cmd = getenv("CR_IP_TOOL");
if (!ip_tool_cmd)
ip_tool_cmd = "ip";
ret = cr_system(fdin, fdout, -1, ip_tool_cmd,
(char *[]) { "ip", arg1, arg2, NULL });
if (ret) {
pr_err("IP tool failed on %s %s\n", arg1, arg2);
return -1;
}
return 0;
}
static int run_iptables_tool(char *def_cmd, int fdin, int fdout)
{
int ret;
char *cmd;
cmd = getenv("CR_IPTABLES");
if (!cmd)
cmd = def_cmd;
pr_debug("\tRunning %s for %s\n", cmd, def_cmd);
ret = cr_system(fdin, fdout, -1, "sh", (char *[]) { "sh", "-c", cmd, NULL });
if (ret)
pr_err("%s failed\n", def_cmd);
return ret;
}
static inline int dump_ifaddr(struct cr_fdset *fds)
{
return run_ip_tool("addr", "save", -1, fdset_fd(fds, CR_FD_IFADDR));
}
static inline int dump_route(struct cr_fdset *fds)
{
return run_ip_tool("route", "save", -1, fdset_fd(fds, CR_FD_ROUTE));
}
static inline int dump_iptables(struct cr_fdset *fds)
{
return run_iptables_tool("iptables-save", -1, fdset_fd(fds, CR_FD_IPTABLES));
}
static int restore_ip_dump(int type, int pid, char *cmd)
{
int fd, ret;
ret = fd = open_image(type, O_RSTR, pid);
if (fd >= 0) {
ret = run_ip_tool(cmd, "restore", fd, -1);
close(fd);
}
return ret;
}
static inline int restore_ifaddr(int pid)
{
return restore_ip_dump(CR_FD_IFADDR, pid, "addr");
}
static inline int restore_route(int pid)
{
return restore_ip_dump(CR_FD_ROUTE, pid, "route");
}
static inline int restore_iptables(int pid)
{
int ret, fd;
ret = fd = open_image(CR_FD_IPTABLES, O_RSTR, pid);
if (fd >= 0) {
ret = run_iptables_tool("iptables-restore", fd, -1);
close(fd);
}
return ret;
}
static int mount_ns_sysfs(void)
{
char sys_mount[] = "crtools-sys.XXXXXX";
BUG_ON(ns_sysfs_fd != -1);
/*
* A new mntns is required to avoid the race between
* open_detach_mount and creating mntns.
*/
if (unshare(CLONE_NEWNS)) {
pr_perror("Can't create new mount namespace");
return -1;
}
if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL)) {
pr_perror("Can't mark the root mount as private");
return -1;
}
if (mkdtemp(sys_mount) == NULL) {
pr_perror("mkdtemp failed %s", sys_mount);
return -1;
}
/*
* The setns() is called, so we're in proper context,
* no need in pulling the mountpoint from parasite.
*/
pr_info("Mount ns' sysfs in %s\n", sys_mount);
if (mount("sysfs", sys_mount, "sysfs", MS_MGC_VAL, NULL)) {
pr_perror("mount failed");
rmdir(sys_mount);
return -1;
}
ns_sysfs_fd = open_detach_mount(sys_mount);
return ns_sysfs_fd >= 0 ? 0 : -1;
}
int dump_net_ns(int pid, int ns_id)
{
struct cr_fdset *fds;
int ret;
fds = cr_fdset_open(ns_id, NETNS, O_DUMP);
if (fds == NULL)
return -1;
ret = switch_ns(pid, &net_ns_desc, NULL);
if (!ret)
ret = mount_ns_sysfs();
if (!ret)
ret = dump_links(fds);
if (!ret)
ret = dump_ifaddr(fds);
if (!ret)
ret = dump_route(fds);
if (!ret)
ret = dump_iptables(fds);
close(ns_sysfs_fd);
ns_sysfs_fd = -1;
close_cr_fdset(&fds);
return ret;
}
int prepare_net_ns(int pid)
{
int ret;
ret = restore_links(pid);
if (!ret)
ret = restore_ifaddr(pid);
if (!ret)
ret = restore_route(pid);
if (!ret)
ret = restore_iptables(pid);
close(ns_fd);
return ret;
}
int netns_pre_create(void)
{
criu: Add exec-cmd option (v3) The --exec-cmd option specifies a command that will be execvp()-ed on successful restore. This way the command specified here will become the parent process of the restored process tree. Waiting for the restored processes to finish is responsibility of this command. All service FDs are closed before we call execvp(). Standad output and error of the command are redirected to the log file when we are restoring through the RPC service. This option will be used when restoring LinuX Containers and it seems helpful for perf or other use cases when restored processes must be supervised by a parent. Two directions were researched in order to integrate CRIU and LXC: 1. We tell to CRIU, that after restoring container is should execve() lxc properly explaining to it that there's a new container hanging around. 2. We make LXC set himself as child subreaper, then fork() criu and ask it to detach (-d) from restore container afterwards. Being a subreaper, it should get the container's init into his child list after it. The main reason for choosing the first option is that the second one can't work with the RPC service. If we call restore via the service then criu service will be the top-most task in the hierarchy and will not be able to reparent the restore trees to any other task in the system. Calling execve from service worker sub-task (and daemonizing it) should solve this. Signed-off-by: Deyan Doychev <deyandoichev@gmail.com> Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
2014-03-22 20:14:00 +04:00
ns_fd = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
if (ns_fd < 0) {
pr_perror("Can't cache net fd");
return -1;
}
pr_info("Saved netns fd for links restore\n");
return 0;
}
int network_lock(void)
{
pr_info("Lock network\n");
/* Each connection will be locked on dump */
if (!(root_ns_mask & CLONE_NEWNET))
return 0;
return run_scripts("network-lock");
}
void network_unlock(void)
{
pr_info("Unlock network\n");
cpt_unlock_tcp_connections();
rst_unlock_tcp_connections();
if (root_ns_mask & CLONE_NEWNET)
run_scripts("network-unlock");
}
int veth_pair_add(char *in, char *out)
{
struct veth_pair *n;
n = xmalloc(sizeof(*n));
if (n == NULL)
return -1;
n->inside = in;
n->outside = out;
list_add(&n->node, &opts.veth_pairs);
pr_debug("Added %s:%s veth map\n", in, out);
return 0;
}
struct ns_desc net_ns_desc = NS_DESC_ENTRY(CLONE_NEWNET, "net");