vimage(8) is a legacy CLI interface for managing jails associated with

network stack instances, which is provided for compatibility with
older applications.  This change brings it back to life in a followup
to the initial conversion of vimage to use the new jail(4)
userland-kernel API:

- when creating vimages via "vimage -c", by default turn on a few
options expected by legacy applications, such as allow operations on
raw sockets, FS mounts etc, and allow jail-related parameters to be
optionally configured.

- introduce the "-m" modifier which allows for configuring jail
parameters of existing vimages / vnet-jails.

- make "vimage name command ..." actually work.

- when reassigning ifnets to vnets using "vimage -i", attempt to rename
the ifnet as "ethXXX" on arrival in the target vnet.  Several legacy
applications are known to depend heavily on such behavior.

- vimage -l lists only jails associated with vnets.  The output is
sorted using vimage / jail names as keys.

- vimage -l by default searches only the current level in the jail
hierarchy.  Recursive listing can be requested via -r switch.

- vimage -l by default prints only jail names on each line, making
such output suitable for pipelining to other commands.  More verbose
output can be obtained via -v switch, and even more jail specific
information will be displayed if -j switch is turned on.

- there's no need to build vimage as statically linked, so update the
Makefile accordingly.

- update the vimage.8 man page.

Approved by:	re (rwatson), julian (mentor)
MFC after:	immediately
This commit is contained in:
Marko Zec 2009-08-20 22:39:20 +00:00
parent 0cef25aeb2
commit ad3764ac1d
3 changed files with 400 additions and 176 deletions

View File

@ -10,6 +10,5 @@ CFLAGS+= -I../../../sys
MAN= vimage.8
BINDIR?= /usr/sbin
NO_SHARED?= YES
.include <bsd.prog.mk>

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2002, 2003 Marko Zec <zec@tel.fer.hr>
.\" Copyright (c) 2002, 2003 Marko Zec <zec@fer.hr>
.\" Copyright (c) 2009 University of Zagreb
.\" Copyright (c) 2009 FreeBSD Foundation
.\"
@ -27,7 +27,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 6, 2009
.Dd August 25, 2009
.Dt VIMAGE 8
.Os
.Sh NAME
@ -35,35 +35,46 @@
.Nd manage virtual network stacks
.Sh SYNOPSIS
.Nm
.Ar vi_name
.Op command
.Nm
.Fl c
.Ar vi_name
.Op Fl c | m
.Ar vname
.Op Ar param=value ...
.Nm
.Fl d
.Ar vi_name
.Ar vname
.Nm
.Fl l
.Op Ar vi_name
.Op Fl rvj
.Op Ar vname
.Nm
.Fl i
.Ar vi_name interface
.Sh DESCRIPTION
.Ar vname ifname
.Op Ar newifname
.Nm
command is an interm user interface for controlling the virtual network
stacks in FreeBSD.
.Ar vi_name
.Op command ...
.Sh DESCRIPTION
The
.Nm
utility is an alternative user interface for controlling virtual network
stacks in FreeBSD, aimed primarily at supporting legacy applications
which are not yet converted to using
.Xr jail 8 ,
.Xr jexec 8 ,
and
.Xr jls 8 .
.
.Ss Overview
A virtual image reprepresents an isolated operating environment with its
own independent network stack instance. Every process, socket and network
interface present in the system is always attached to one, and only one,
virtual image i.e. virtual network stack instance.
During the system bootup sequence default virtual image is created to
which all the configured interfaces and user processes are initially
assigned.
Assuming that enough system resources and per virtual image privileges
are provided, the super-user can create and manage a hierarchy of
subordinated virtual images. The
A virtual image or vimage is a jail with its own independent network
stack instance. Every process, socket and network interface present
in the system is always attached to one, and only one, virtual network
stack instance (vnet).
During system bootup sequence a default vnet
is created to which all the configured interfaces and user processes
are initially attached.
Assuming that enough system resources are
are available, a user with sufficient privileges can create and manage
a hierarchy of subordinated virtual images.
The
.Nm
command allows for creation, deletion and monitoring of virtual images,
as well as for execution of arbitrary processes in a targeted virtual
@ -71,59 +82,72 @@ image.
.Ss Invocation
If invoked with no modifiers, the
.Nm
command spawns a new shell process in virtual image
.Ar vi_name .
If provided, the optional arguments following the virtual image name
.Ar vi_name
are interpreted as a standard command line issued at a shell,
otherwise an interactive shell is started in the target virtual image.
command spawns a new interactive shell in virtual image
.Ar vname .
If optional additional arguments following
.Ar vname
are provided, the first of those will be executed in place of the
interactive shell, and the rest of the arguments will be passed as
arguments to the executed command.
.Pp
The following parameters are available:
The following modifiers are available:
.Bl -tag -width indent
.It Fl c
Create a new virtual image named
.So
.Ar vi_name
.Sc .
.Ar vname .
Additional arguments, if provided, may be used to specify operating
parameters different from defaults, in format
.Ar param=value .
See
.Xr jail 8
for an extensive list of available parameters.
.It Fl m
Modify the parameters of a virtual image named
.Ar vname ,
using the same syntax as with the -c form of the command.
.It Fl d
Delete the virtual image
.Ar vi_name .
.Ar vname .
No processes and/or sockets should exist in the target virtual image
in order for the delete request to succeed. Non-loopback interfaces
in order for the delete request to succeed. Non-loopback interfaces
residing in the target virtual image will be reassigned to the virtual
image's parent.
.It Fl l
List the properties and statistics for virtual images one level
below the current one in the hierarchy. If an optional argument
.Ar vi_name
.Ar vname
is provided, only the information regarding the target virtual image
.Ar vi_name
.Ar vname
is displayed.
.It Fl lr
List the properties and statistics for all virtual images in
the hierarchy of subordinated vimages. If an optional argument
.Ar vi_name
is provided, the hierarchy will be traversed at and below the
.Ar vi_name
level.
With the optional
.Op Ar -r
switch enabled the list will include all virtual images below the
current level in the vimage hierarchy.
Enabling the optional
.Op Ar -v
or
.Op Ar -j
switches results in a more detailed output.
.It Fl i
Move the interface
.Ar interface
Move interface
.Ar ifname
to the target virtual image
.Ar vi_name .
If the value of
.Ar vi_name
argument is
.So ..
.Ar vname .
Interfaces will be automatically renamed to
.So
ethXX
.Sc ,
the interface is returned to the parent of the current virtual image.
unless an optional argument specifying the desired interface name
.Op Ar newifname
is provided.
.El
.Sh EXAMPLES
Create a new virtual image named
.So v1
.Sc :
.Sc ,
which is allowed to create and manage an own subhierarchy of vimages:
.Pp
.Dl vimage -c v1
.Dl vimage -c v1 children.max=100
.Pp
Execute the
.So ifconfig
@ -137,28 +161,35 @@ Move the interface
.So vlan0
.Sc to the virtual image
.So v1
.Sc while renaming the interface as
.So
ve0
.Sc :
.Pp
.Dl vimage -i v1 vlan0
.Dl vimage -i v1 vlan0 ve0
.Pp
Show the status information for virtual image
.So v1
.Sc :
.Pp
.Dl vimage -l v1
.Dl vimage -lv v1
.Sh DIAGNOSTICS
The
.Nm
command exits 0 on success, and >0 if an error occurs.
.Sh SEE ALSO
.Xr jail 8
.Xr jexec 8
.Xr jls 8
.Sh BUGS
If memory allocation failure occurs during the vimage creation, it will remain
undetected/ignored in the current implementation, thus latently scheduling
an almost imminent system crash in the future.
Deletion of vimages / vnets is known to leak kernel memory and fail at
stopping various timers, hence may lead to system crashes.
.Sh AUTHOR
.An "Marko Zec" Aq zec@fer.hr
.Sh HISTORY
The
.Nm
facility first appeared as a patch against FreeBSD 4.7-RELEASE in 2002.
Network stack virtualization framework first appeared as a patchset
against the FreeBSD 4.7 kernel in 2002, and was maintained outside
of the main FreeBSD tree.
As a result of a project sponsored by the FreeBSD Foundation and
Stiching NLNet, integrated virtualized network stack first appeared
in FreeBSD 8.0.

View File

@ -28,142 +28,294 @@
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/jail.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>
#include <ctype.h>
#include <jail.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define VI_CREATE 0x00000001
#define VI_DESTROY 0x00000002
#define VI_SWITCHTO 0x00000008
#define VI_IFACE 0x00000010
#define VI_GET 0x00000100
#define VI_GETNEXT 0x00000200
typedef enum {
VI_SWITCHTO,
VI_CREATE,
VI_MODIFY,
VI_DESTROY,
VI_IFMOVE,
VI_GET
} vi_cmd_t;
static int getjail(char *name, int lastjid, int *vnet);
typedef struct vimage_status {
char name[MAXPATHLEN]; /* Must be first field for strcmp(). */
char path[MAXPATHLEN];
char hostname[MAXPATHLEN];
char domainname[MAXPATHLEN];
int jid;
int parentjid;
int vnet;
int childcnt;
int childmax;
int cpuset;
int rawsock;
int socket_af;
int mount;
} vstat_t;
#define VST_SIZE_STEP 1024
#define MAXPARAMS 32
static int getjail(vstat_t *, int, int);
static char *invocname;
static void
usage(void)
{
fprintf(stderr,
"usage: %s [-c | -m] vname [param=value ...]\n"
" %s -d vname\n"
" %s -l[rvj] [vname]\n"
" %s -i vname ifname [newifname]\n"
" %s vname [command ...]\n",
invocname, invocname, invocname, invocname, invocname);
exit(1);
}
int
main(int argc, char **argv)
{
int s;
char *shell;
int cmd;
int jid, vnet;
struct jailparam params[MAXPARAMS];
char ifname[IFNAMSIZ];
struct ifreq ifreq;
char name[MAXHOSTNAMELEN];
vi_cmd_t newcmd, cmd;
int recurse = 0;
int verbose = 0;
int jid, i, s, namelen;
int vst_size, vst_last;
vstat_t *vst;
char *str;
char ch;
switch (argc) {
invocname = argv[0];
case 1:
cmd = 0;
break;
case 2:
if (strcmp(argv[1], "-l") == 0)
cmd = VI_GETNEXT;
else if (strcmp(argv[1], "-lr") == 0)
cmd = VI_GETNEXT;
else {
strcpy(name, argv[1]);
cmd = VI_SWITCHTO;
newcmd = cmd = VI_SWITCHTO; /* Default if no modifiers specified. */
while ((ch = getopt(argc, argv, "cdijlmrv")) != -1) {
switch (ch) {
case 'c':
newcmd = VI_CREATE;
break;
case 'm':
newcmd = VI_MODIFY;
break;
case 'd':
newcmd = VI_DESTROY;
break;
case 'l':
newcmd = VI_GET;
break;
case 'i':
newcmd = VI_IFMOVE;
break;
case 'r':
recurse = 1;
break;
case 'v':
verbose++;
break;
case 'j':
verbose = 2;
break;
default:
usage();
}
break;
case 3:
strcpy(name, argv[2]);
if (strcmp(argv[1], "-l") == 0)
cmd = VI_GET;
if (strcmp(argv[1], "-c") == 0)
cmd = VI_CREATE;
if (strcmp(argv[1], "-d") == 0)
cmd = VI_DESTROY;
break;
default:
strcpy(name, argv[2]);
if (strcmp(argv[1], "-c") == 0)
cmd = VI_CREATE;
if (strcmp(argv[1], "-i") == 0)
cmd = VI_IFACE;
if (cmd == VI_SWITCHTO || cmd == newcmd)
cmd = newcmd;
else
usage();
}
argc -= optind;
argv += optind;
if ((cmd != VI_GET && (argc == 0 || recurse != 0 || verbose != 0)) ||
(cmd == VI_IFMOVE && (argc < 2 || argc > 3)) ||
(cmd == VI_MODIFY && argc < 2) || argc >= MAXPARAMS)
usage();
switch (cmd) {
case VI_GET:
jid = getjail(name, -1, &vnet);
if (jid < 0)
goto abort;
printf("%d: %s%s\n", jid, name, vnet ? "" : " (no vnet)");
exit(0);
case VI_GETNEXT:
vst_last = 0;
vst_size = VST_SIZE_STEP;
if ((vst = malloc(vst_size * sizeof(*vst))) == NULL)
break;
if (argc == 1)
namelen = strlen(argv[0]);
else
namelen = 0;
jid = 0;
while ((jid = getjail(name, jid, &vnet)) > 0)
printf("%d: %s%s\n", jid, name,
vnet ? "" : " (no vnet)");
while ((jid = getjail(&vst[vst_last], jid, verbose)) > 0) {
/* Skip jails which do not own vnets. */
if (vst[vst_last].vnet != 1)
continue;
/* Skip non-matching vnames / hierarchies. */
if (namelen &&
((strlen(vst[vst_last].name) < namelen ||
strncmp(vst[vst_last].name, argv[0], namelen) != 0)
|| (strlen(vst[vst_last].name) > namelen &&
vst[vst_last].name[namelen] != '.')))
continue;
/* Skip any sub-trees if -r not requested. */
if (!recurse &&
(strlen(vst[vst_last].name) < namelen ||
strchr(&vst[vst_last].name[namelen], '.') != NULL))
continue;
/* Grow vst table if necessary. */
if (++vst_last == vst_size) {
vst_size += VST_SIZE_STEP;
vst = realloc(vst, vst_size * sizeof(*vst));
if (vst == NULL)
break;
}
}
if (vst == NULL)
break;
/* Sort: the key is the 1st field in *vst, i.e. vimage name. */
qsort(vst, vst_last, sizeof(*vst), (void *) strcmp);
for (i = 0; i < vst_last; i++) {
if (!verbose) {
printf("%s\n", vst[i].name);
continue;
}
printf("%s:\n", vst[i].name);
printf(" Path: %s\n", vst[i].path);
printf(" Hostname: %s\n", vst[i].hostname);
printf(" Domainname: %s\n", vst[i].domainname);
printf(" Children: %d\n", vst[i].childcnt);
if (verbose < 2)
continue;
printf(" Children limit: %d\n", vst[i].childmax);
printf(" CPUsetID: %d\n", vst[i].cpuset);
printf(" JID: %d\n", vst[i].jid);
printf(" PJID: %d\n", vst[i].parentjid);
printf(" Raw sockets allowed: %d\n", vst[i].rawsock);
printf(" All AF allowed: %d\n", vst[i].socket_af);
printf(" Mount allowed: %d\n", vst[i].mount);
}
free(vst);
exit(0);
case VI_IFACE:
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == -1)
goto abort;
jid = jail_getid(name);
if (jid < 0)
goto abort;
case VI_IFMOVE:
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
break;
if ((jid = jail_getid(argv[0])) < 0)
break;
ifreq.ifr_jid = jid;
strncpy(ifreq.ifr_name, argv[3], sizeof(ifreq.ifr_name));
strncpy(ifreq.ifr_name, argv[1], sizeof(ifreq.ifr_name));
if (ioctl(s, SIOCSIFVNET, (caddr_t)&ifreq) < 0)
goto abort;
printf("%s@%s\n", ifreq.ifr_name, name);
break;
close(s);
if (argc == 3)
snprintf(ifname, sizeof(ifname), "%s", argv[2]);
else
snprintf(ifname, sizeof(ifname), "eth0");
ifreq.ifr_data = ifname;
/* Do we need to rename the ifnet? */
if (strcmp(ifreq.ifr_name, ifname) != 0) {
/* Switch to the context of the target vimage. */
if (jail_attach(jid) < 0)
break;
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
break;
for (namelen = 0; isalpha(ifname[namelen]); namelen++);
i = 0;
/* Search for a free ifunit in target vnet. Unsafe. */
while (ioctl(s, SIOCSIFNAME, (caddr_t)&ifreq) < 0) {
snprintf(&ifname[namelen],
sizeof(ifname) - namelen, "%d", i);
/* Emergency brake. */
if (i++ == IF_MAXUNIT)
break;
}
}
if (i < IF_MAXUNIT)
printf("%s@%s\n", ifname, argv[0]);
else
printf("%s@%s\n", ifreq.ifr_name, argv[0]);
exit(0);
case VI_CREATE:
if (jail_setv(JAIL_CREATE, "name", name, "vnet", NULL,
"host", NULL, "persist", NULL, NULL) < 0)
goto abort;
if ((jid = jail_setv(JAIL_CREATE,
"name", argv[0],
"vnet", NULL,
"host", NULL,
"persist", NULL,
"allow.raw_sockets", "true",
"allow.socket_af", "true",
"allow.mount", "true",
NULL)) >= 0)
break;
if (jid < 0)
break;
if (argc == 1)
exit(0);
/* Not done yet, proceed to apply non-default parameters. */
case VI_MODIFY:
jailparam_init(&params[0], "name");
jailparam_import(&params[0], argv[0]);
for (i = 1; i < argc; i++) {
for (str = argv[i]; *str != '=' && *str != 0; str++) {
/* Do nothing - search for '=' delimeter. */
}
if (*str == 0)
break;
*str++ = 0;
if (*str == 0)
break;
jailparam_init(&params[i], argv[i]);
jailparam_import(&params[i], str);
}
if (i != argc)
break;
if (jailparam_set(params, i, JAIL_UPDATE) < 0)
break;
exit(0);
case VI_DESTROY:
if ((jid = jail_getid(argv[0])) < 0)
break;
if (jail_remove(jid) < 0)
break;
exit(0);
case VI_SWITCHTO:
jid = jail_getid(name);
if (jid < 0)
goto abort;
if ((jid = jail_getid(argv[0])) < 0)
break;
if (jail_attach(jid) < 0)
goto abort;
if (argc == 2) {
printf("Switched to jail %s\n", argv[1]);
if ((shell = getenv("SHELL")) == NULL)
execlp("/bin/sh", argv[0], NULL);
break;
if (argc == 1) {
printf("Switched to vimage %s\n", argv[0]);
if ((str = getenv("SHELL")) == NULL)
execlp("/bin/sh", invocname, NULL);
else
execlp(shell, argv[0], NULL);
execlp(str, invocname, NULL);
} else
execvp(argv[2], &argv[2]);
execvp(argv[1], &argv[1]);
break;
case VI_DESTROY:
jid = jail_getid(name);
if (jid < 0)
goto abort;
if (jail_remove(jid) < 0)
goto abort;
exit(0);
default:
fprintf(stderr, "usage: %s [-cdilr] vi_name [args]\n",
argv[0]);
exit(1);
/* Should be unreachable. */
break;
}
abort:
if (jail_errmsg[0])
fprintf(stderr, "Error: %s\n", jail_errmsg);
else
@ -172,27 +324,69 @@ abort:
}
static int
getjail(char *name, int lastjid, int *vnet)
getjail(vstat_t *vs, int lastjid, int verbose)
{
struct jailparam params[3];
int jid;
struct jailparam params[32]; /* Must be > max(psize). */
int psize = 0;
if (lastjid < 0) {
jid = jail_getid(name);
if (jid < 0)
return (jid);
jailparam_init(&params[0], "jid");
jailparam_import_raw(&params[0], &jid, sizeof jid);
} else {
jailparam_init(&params[0], "lastjid");
jailparam_import_raw(&params[0], &lastjid, sizeof lastjid);
}
jailparam_init(&params[1], "name");
jailparam_import_raw(&params[1], name, MAXHOSTNAMELEN);
name[0] = 0;
jailparam_init(&params[2], "vnet");
jailparam_import_raw(&params[2], vnet, sizeof(*vnet));
jid = jailparam_get(params, 3, 0);
jailparam_free(params, 3);
return (jid);
bzero(params, sizeof(params));
bzero(vs, sizeof(*vs));
jailparam_init(&params[psize], "lastjid");
jailparam_import_raw(&params[psize++], &lastjid, sizeof lastjid);
jailparam_init(&params[psize], "vnet");
jailparam_import_raw(&params[psize++], &vs->vnet, sizeof(vs->vnet));
jailparam_init(&params[psize], "name");
jailparam_import_raw(&params[psize++], &vs->name, sizeof(vs->name));
if (verbose == 0)
goto done;
jailparam_init(&params[psize], "path");
jailparam_import_raw(&params[psize++], &vs->path, sizeof(vs->path));
jailparam_init(&params[psize], "host.hostname");
jailparam_import_raw(&params[psize++], &vs->hostname,
sizeof(vs->hostname));
jailparam_init(&params[psize], "host.domainname");
jailparam_import_raw(&params[psize++], &vs->domainname,
sizeof(vs->domainname));
jailparam_init(&params[psize], "children.cur");
jailparam_import_raw(&params[psize++], &vs->childcnt,
sizeof(vs->childcnt));
if (verbose == 1)
goto done;
jailparam_init(&params[psize], "children.max");
jailparam_import_raw(&params[psize++], &vs->childmax,
sizeof(vs->childmax));
jailparam_init(&params[psize], "cpuset.id");
jailparam_import_raw(&params[psize++], &vs->cpuset,
sizeof(vs->cpuset));
jailparam_init(&params[psize], "parent");
jailparam_import_raw(&params[psize++], &vs->parentjid,
sizeof(vs->parentjid));
jailparam_init(&params[psize], "allow.raw_sockets");
jailparam_import_raw(&params[psize++], &vs->rawsock,
sizeof(vs->rawsock));
jailparam_init(&params[psize], "allow.socket_af");
jailparam_import_raw(&params[psize++], &vs->socket_af,
sizeof(vs->socket_af));
jailparam_init(&params[psize], "allow.mount");
jailparam_import_raw(&params[psize++], &vs->mount, sizeof(vs->mount));
done:
vs->jid = jailparam_get(params, psize, 0);
jailparam_free(params, psize);
return (vs->jid);
}