Hi!
> This patch introduces helper tools for creating and working
> with namespaces (ltp/testcases/kernel/containers/share):
> 
> * ns_create:
>     Creates a child process in the new specified namespace(s),
>     child is then daemonized and is running in the background.
>     PID of the daemonized child process is printed on the stdout.
>     As the new namespace(s) is(are) maintained by the daemonized
>     child process it(they) can be removed by killing this process.
> * ns_exec:
>     Enters the namespace(s) of a process specified by a PID and
>     then executes the indicated program inside that namespace(s).
> * ns_ifmove:
>     Moves a network interface to the namespace of a process
>     specified by a PID.
> 
> Example usage:
> ==============
> $ myns=$(ns_create net,ipc)
> 
> $ ip link add veth0 type veth peer name veth1
> $ ns_exec $myns ip a
> 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
> 
> $ ns_ifmove veth1 $myns
> $ ns_exec $myns ip a
> 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
> 6: veth1: <BROADCAST> mtu 1500 qdisc noop state DOWN qlen 1000
>     link/ether 6a:0a:45:ed:6e:d0 brd ff:ff:ff:ff:ff:ff
> 
> $ ip link del veth0
> $ kill -9 $myns
> ==============
> 
> The only requirement from kernel side is the support of "setns"
> syscall. There is no need to have util-linux (unshare, nsenter)
> installed. This way test cases utilizing namespaces can be
> executed even on older kernels which do not provide required
> tools for working with namespaces.

Can we add this documentation to some file in doc/ as well?

The test-writing-guidelines.txt are long enough as they are so maybe
start a new document for containers-helpers.txt or similar (and format
it with asciidoc so that we can put it on github wiki as well).

> Signed-off-by: Matus Marhefka <mmarh...@redhat.com>
> ---
>  testcases/kernel/containers/share/.gitignore  |   3 +
>  testcases/kernel/containers/share/Makefile    |  22 ++++
>  testcases/kernel/containers/share/ns_create.c | 100 ++++++++++++++++
>  testcases/kernel/containers/share/ns_exec.c   | 157 
> ++++++++++++++++++++++++++
>  testcases/kernel/containers/share/ns_ifmove.c | 123 ++++++++++++++++++++
>  testcases/kernel/containers/share/ns_utils.h  |  42 +++++++
>  6 files changed, 447 insertions(+)
>  create mode 100644 testcases/kernel/containers/share/.gitignore
>  create mode 100644 testcases/kernel/containers/share/Makefile
>  create mode 100644 testcases/kernel/containers/share/ns_create.c
>  create mode 100644 testcases/kernel/containers/share/ns_exec.c
>  create mode 100644 testcases/kernel/containers/share/ns_ifmove.c
>  create mode 100644 testcases/kernel/containers/share/ns_utils.h
> 
> diff --git a/testcases/kernel/containers/share/.gitignore 
> b/testcases/kernel/containers/share/.gitignore
> new file mode 100644
> index 0000000..0d5ecf0
> --- /dev/null
> +++ b/testcases/kernel/containers/share/.gitignore
> @@ -0,0 +1,3 @@
> +/ns_ifmove
> +/ns_create
> +/ns_exec
> diff --git a/testcases/kernel/containers/share/Makefile 
> b/testcases/kernel/containers/share/Makefile
> new file mode 100644
> index 0000000..962d688
> --- /dev/null
> +++ b/testcases/kernel/containers/share/Makefile
> @@ -0,0 +1,22 @@
> +# Copyright (c) 2015 Red Hat, Inc.
> +#
> +# This program is free software: you can redistribute it and/or modify
> +# it under the terms of version 2 the GNU General Public License as
> +# published by the Free Software Foundation.
> +#
> +# 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, see <http://www.gnu.org/licenses/>.
> +##############################################################################
> +top_srcdir              ?= ../../../..
> +
> +include $(top_srcdir)/include/mk/testcases.mk
> +include $(abs_srcdir)/../Makefile.inc
> +
> +LDLIBS                  := -lltp
> +
> +include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/kernel/containers/share/ns_create.c 
> b/testcases/kernel/containers/share/ns_create.c
> new file mode 100644
> index 0000000..8e18fab
> --- /dev/null
> +++ b/testcases/kernel/containers/share/ns_create.c
> @@ -0,0 +1,100 @@
> +/* Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of version 2 the GNU General Public License as
> + * published by the Free Software Foundation.
> + *
> + * 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, see <http://www.gnu.org/licenses/>.
> + *
> + * Written by Matus Marhefka <mmarh...@redhat.com>
> + *
> + ***********************************************************************
> + * File: ns_create.c

I always wondered why people add the File: tag here. Is this useful for
something?

> + * Creates a child process in the new specified namespace(s), child is then
> + * daemonized and is running in the background. PID of the daemonized child
> + * process is printed on the stdout. As the new namespace(s) is(are) 
> maintained
> + * by the daemonized child process it(they) can be removed by killing this
> + * process.

Hmm, I miss the code where the child gets detached from the control
terminal (i.e. daemonized). Did I miss something?

> + */
> +
> +#define _GNU_SOURCE
> +#include <sched.h>
> +#include <sys/syscall.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <errno.h>
> +#include "test.h"
> +#include "ns_utils.h"
> +
> +char *TCID = "ns_create";
> +
> +
> +static int child_fn(void *arg)
> +{
> +     int i;
> +
> +     if (chdir("/") == -1) {
> +             tst_resm(TINFO | TERRNO, "chdir");
> +             exit(1);
> +     }
> +
> +     /* close all inherrited file descriptors */
> +     for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
> +             close(i);
> +
> +     pause();
> +     return 0;
> +}
> +
> +/*
> + * ./ns_create <comma separated namespaces list>
> + * where full list is: ipc,mnt,net,pid,user,uts.
> + */
> +int main(int argc, char *argv[])
> +{
> +     int pid, flags;
> +
> +     if (argc != 2) {
> +             tst_resm(TINFO, "%s  <comma separated namespaces list> "
> +                             "(full list: ipc,mnt,net,pid,user,uts "
> +                             "-- do not use whitespaces in list)", argv[0]);
> +             return 1;
> +     }
> +
> +     flags = 0;
> +     if (strstr(argv[1], "ipc") != NULL)
> +             flags |= CLONE_NEWIPC;
> +     if (strstr(argv[1], "mnt") != NULL)
> +             flags |= CLONE_NEWNS;
> +     if (strstr(argv[1], "net") != NULL)
> +             flags |= CLONE_NEWNET;
> +     if (strstr(argv[1], "pid") != NULL)
> +             flags |= CLONE_NEWPID;
> +     if (strstr(argv[1], "user") != NULL)
> +             flags |= CLONE_NEWUSER;
> +     if (strstr(argv[1], "uts") != NULL)
> +             flags |= CLONE_NEWUTS;
> +     if (flags == 0) {
> +             tst_resm(TINFO, "unknown namespace: %s", argv[1]);
> +             return 1;
> +     }

Hmm, this will still start without any error if I pass ipc,nonexistent
as parameters right?

Can't we do it more robust and pass namespace names as separate
parameters? Something like:

struct param {
        const char *name;
        int flag;
}

struct param params[] = {
        {"ipc", CLONE_NEWIPC},
        ...
        {NULL, 0},
};

struct param *get_param(const char *name)
{
        int i;

        for (i = 0; params[i]; i++) {
                if (!strcasecmp(params[i], name))
                        return params + i;
        }

        return NULL;
}

void print_help(void)
{
        int i;

        printf("usage: ns_create [%s", params[0].name);

        for (i = 1; params[i]; i++)
                printf("|%s", params[i].name);

        printf("]\n");
}

for (i = 1; argv[i]; i++) {
        struct param *p = get_param(argv[i]);

        if (!p) {
                tst_resm(TINFO, "Unknown parameter: %s", argv[i]);
                print_help();
                return 1;
        }

        flags |= p->flag;
}

> +     pid = ltp_clone_quick(flags | SIGCHLD, (void *)child_fn, NULL);
> +     if (pid == -1) {
> +             tst_resm(TINFO | TERRNO, "ltp_clone_quick");
> +             return 1;
> +     }
> +
> +     printf("%d", pid);
> +     return 0;
> +}
> diff --git a/testcases/kernel/containers/share/ns_exec.c 
> b/testcases/kernel/containers/share/ns_exec.c
> new file mode 100644
> index 0000000..8c350d3
> --- /dev/null
> +++ b/testcases/kernel/containers/share/ns_exec.c
> @@ -0,0 +1,157 @@
> +/* Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of version 2 the GNU General Public License as
> + * published by the Free Software Foundation.
> + *
> + * 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, see <http://www.gnu.org/licenses/>.
> + *
> + * Written by Matus Marhefka <mmarh...@redhat.com>
> + *
> + ***********************************************************************
> + * File: ns_exec.c
> + *
> + * Enters the namespace(s) of a process specified by a PID and then executes
> + * the indicated program inside that namespace(s).
> + *
> + */
> +
> +#define _GNU_SOURCE
> +#include <sched.h>
> +#include <sys/syscall.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <errno.h>
> +#include "test.h"
> +#include "linux_syscall_numbers.h"
> +#include "ns_utils.h"
> +
> +#define PROC_PATH "/proc"
> +#define NS_TOTAL 6
> +
> +char *TCID = "ns_exec";
> +int ns_fd[NS_TOTAL];
> +int ns_fd_index;
> +
> +struct argst {
> +     char **argv;
> +     int argc;
> +};
> +
> +
> +static int open_ns_fd(const char *pid, const char *ns)
> +{
> +     int fd;
> +     char file_buf[30];
> +
> +     sprintf(file_buf, "%s/%s/ns/%s", PROC_PATH, pid, ns);
> +
> +     fd = open(file_buf, O_RDONLY);
> +     if (fd > 0) {
> +             ns_fd[ns_fd_index] = fd;
> +             ++ns_fd_index;
> +     } else if (fd == -1 && errno != ENOENT) {
> +             tst_resm(TINFO | TERRNO, "open");
> +             return -1;
> +     }

I found the else if combinations a bit confusing. Why can't you just
return 0; at the end of the first instead?

> +     return 0;
> +}
> +
> +static void close_ns_fd(void)
> +{
> +     int i;
> +
> +     for (i = 0; i < ns_fd_index; i++)
> +             close(ns_fd[i]);
> +}
> +
> +static int child_fn(void *arg)
> +{
> +     struct argst *args = arg;
> +     int i;
> +
> +     for (i = 1; i < args->argc-1; i++)
> +             args->argv[i] = args->argv[i+1];
> +     args->argv[i] = NULL;

The argv array from main is NULL terminated allready so we can just pass
argv[2], argv + 2 to the execvp below.

> +     execvp(args->argv[1], args->argv+1);
> +     tst_resm(TINFO | TERRNO, "execvp");
> +     return 1;
> +}
> +
> +/*
> + * ./ns_exec <NS_PID> <PROGRAM> [ARGS]
> + */
> +int main(int argc, char *argv[])
> +{
> +     int i, rv, pid;
> +     struct argst args;
> +
> +     rv = syscall(__NR_setns, -1, 0);
> +     if (rv == -1 && errno == ENOSYS) {
> +             tst_resm(TINFO, "setns is not supported in the kernel");
> +             return 1;
> +     }
> +
> +     if (argc < 3) {
> +             tst_resm(TINFO, "%s <NS_PID> <PROGRAM> [ARGS]\n", argv[0]);
> +             return 1;
> +     }
> +
> +     rv = 0;
> +     memset(ns_fd, 0, sizeof(ns_fd));
> +     rv |= open_ns_fd(argv[1], "ipc");
> +     rv |= open_ns_fd(argv[1], "mnt");
> +     rv |= open_ns_fd(argv[1], "net");
> +     rv |= open_ns_fd(argv[1], "pid");
> +     rv |= open_ns_fd(argv[1], "user");
> +     rv |= open_ns_fd(argv[1], "uts");
> +     if (rv != 0)
> +             return 1;
> +
> +     if (ns_fd_index == 0) {
> +             tst_resm(TINFO, "no namespace entries in /proc/%s/ns/",
> +                      argv[1]);
> +             close_ns_fd();
> +             return 1;
> +     }
> +
> +     for (i = 0; i < ns_fd_index; i++) {
> +             if (syscall(__NR_setns, ns_fd[i], 0) == -1) {
> +                     tst_resm(TINFO | TERRNO, "setns");
> +                     close_ns_fd();
> +                     return 1;
> +             }
> +     }
> +
> +     args.argv = argv;
> +     args.argc = argc;
> +     pid = ltp_clone_quick(SIGCHLD, (void *)child_fn, (void *)&args);
> +     if (pid == -1) {
> +             tst_resm(TINFO | TERRNO, "ltp_clone_quick");
> +             close_ns_fd();
> +             return 1;
> +     }
> +
> +     if (waitpid(pid, &rv, 0) == -1) {
> +             tst_resm(TINFO | TERRNO, "waitpid");
> +             return 1;
> +     }
> +
> +     close_ns_fd();
> +
> +     if (WIFEXITED(rv))
> +             return WEXITSTATUS(rv);
> +
> +     return 0;
> +}
> diff --git a/testcases/kernel/containers/share/ns_ifmove.c 
> b/testcases/kernel/containers/share/ns_ifmove.c
> new file mode 100644
> index 0000000..728863b
> --- /dev/null
> +++ b/testcases/kernel/containers/share/ns_ifmove.c
> @@ -0,0 +1,123 @@
> +/* Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of version 2 the GNU General Public License as
> + * published by the Free Software Foundation.
> + *
> + * 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, see <http://www.gnu.org/licenses/>.
> + *
> + * Written by Matus Marhefka <mmarh...@redhat.com>
> + *
> + ***********************************************************************
> + * File: ns_ifmove.c
> + *
> + * Moves a network interface to the namespace of a process specified by a 
> PID.
> + *
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <unistd.h>
> +#include <asm/types.h>
> +#include <sys/socket.h>
> +#include <linux/rtnetlink.h>
> +#include <sys/ioctl.h>
> +#include <linux/if.h>
> +#include <net/ethernet.h>
> +#include <arpa/inet.h>
> +#include "test.h"
> +
> +char *TCID = "ns_linkset";
> +
> +struct {
> +     struct nlmsghdr nh;
> +     struct ifinfomsg ifi;
> +     char attrbuf[512];
> +} req;
> +
> +
> +int get_intf_index_from_name(const char *intf_name)
> +{
> +     struct ifreq ifr;
> +     int sock_fd;
> +
> +     memset(&ifr, 0, sizeof(ifr));
> +     strncpy(ifr.ifr_name, intf_name, sizeof(ifr.ifr_name) - 1);
> +     ifr.ifr_name[sizeof(ifr.ifr_name)-1] = '\0';
> +
> +     sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
> +     if (sock_fd == -1) {
> +             tst_resm(TINFO | TERRNO, "socket");
> +             return -1;
> +     }
> +
> +     /* gets interface index */
> +     if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1) {
> +             close(sock_fd);
> +             tst_resm(TINFO | TERRNO, "ioctl");
> +             return -1;
> +     }
> +
> +     close(sock_fd);
> +     return ifr.ifr_ifindex;
> +}
> +
> +/*
> + * ./ns_ifmove <INTERFACE_NAME> <NAMESPACE_PID>
> + */
> +int main(int argc, char **argv)
> +{
> +     struct rtattr *rta;
> +     int intf_index, pid, rtnetlink_socket;
> +
> +     if (argc != 3) {
> +             tst_resm(TINFO, "%s <INTERFACE_NAME> <NAMESPACE_PID>\n",
> +                      argv[0]);
> +             return 1;
> +     }
> +
> +     intf_index = get_intf_index_from_name(argv[1]);
> +     if (intf_index == -1) {
> +             tst_resm(TINFO , "unable to get interface index");
> +             return 1;
> +     }
> +
> +     pid = atoi(argv[2]);
> +
> +     rtnetlink_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
> +     if (rtnetlink_socket == -1) {
> +             tst_resm(TINFO | TERRNO, "socket");
> +             return 1;
> +     }
> +
> +     memset(&req, 0, sizeof(req));
> +     req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
> +     req.nh.nlmsg_flags = NLM_F_REQUEST;
> +     req.nh.nlmsg_type = RTM_NEWLINK;
> +     req.ifi.ifi_family = AF_UNSPEC;
> +     req.ifi.ifi_index = intf_index;
> +     req.ifi.ifi_change = 0xffffffff;
> +     rta = (struct rtattr *)(((char *) &req) +
> +             NLMSG_ALIGN(req.nh.nlmsg_len));
> +     rta->rta_type = IFLA_NET_NS_PID;
> +     rta->rta_len = RTA_LENGTH(sizeof(int));
> +     req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) +
> +             RTA_LENGTH(sizeof(pid));
> +     memcpy(RTA_DATA(rta), &pid, sizeof(pid));
> +
> +     if (send(rtnetlink_socket, &req, req.nh.nlmsg_len, 0) == -1) {
> +             tst_resm(TINFO | TERRNO, "send");
> +             return 1;
> +     }
> +
> +     close(rtnetlink_socket);
> +     return 0;
> +}
> diff --git a/testcases/kernel/containers/share/ns_utils.h 
> b/testcases/kernel/containers/share/ns_utils.h
> new file mode 100644
> index 0000000..fe11a6b
> --- /dev/null
> +++ b/testcases/kernel/containers/share/ns_utils.h
> @@ -0,0 +1,42 @@
> +/* Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of version 2 the GNU General Public License as
> + * published by the Free Software Foundation.
> + *
> + * 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, see <http://www.gnu.org/licenses/>.
> + ***********************************************************************
> + * File: ns_utils.h
> + *
> + */
> +
> +#ifndef NS_UTILS_H
> +#define NS_UTILS_H
> +
> +#ifndef CLONE_NEWIPC
> +#  define CLONE_NEWIPC       0x08000000
> +#endif
> +#ifndef CLONE_NEWNS
> +#  define CLONE_NEWNS        0x00020000
> +#endif
> +#ifndef CLONE_NEWNET
> +#  define CLONE_NEWNET       0x40000000
> +#endif
> +#ifndef CLONE_NEWPID
> +#  define CLONE_NEWPID       0x20000000
> +#endif
> +#ifndef CLONE_NEWUSER
> +#  define CLONE_NEWUSER      0x10000000
> +#endif
> +#ifndef CLONE_NEWUTS
> +#  define CLONE_NEWUTS       0x04000000
> +#endif

We allready have these in:

testcases/kernel/containers/libclone/libclone.h

It would be good to move these to some common location, perhaps header
in lapi/ and include it in both places.

-- 
Cyril Hrubis
chru...@suse.cz

------------------------------------------------------------------------------
Don't Limit Your Business. Reach for the Cloud.
GigeNET's Cloud Solutions provide you with the tools and support that
you need to offload your IT needs and focus on growing your business.
Configured For All Businesses. Start Your Cloud Today.
https://www.gigenetcloud.com/
_______________________________________________
Ltp-list mailing list
Ltp-list@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ltp-list

Reply via email to