mirror of
https://github.com/systemd/systemd.git
synced 2025-01-18 10:04:04 +03:00
nspawn: add new --network-zone= switch for automatically managed bridge devices
This adds a new concept of network "zones", which are little more than bridge devices that are automatically managed by nspawn: when the first container referencing a bridge is started, the bridge device is created, when the last container referencing it is removed the bridge device is removed again. Besides this logic --network-zone= is pretty much identical to --network-bridge=. The usecase for this is to make it easy to run multiple related containers (think MySQL in one and Apache in another) in a common, named virtual Ethernet broadcast zone, that only exists as long as one of them is running, and fully automatically managed otherwise.
This commit is contained in:
parent
ef76dff225
commit
22b28dfdc7
@ -40,4 +40,5 @@ Network.IPVLAN, config_parse_strv, 0, offsetof(Settings,
|
||||
Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth)
|
||||
Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0
|
||||
Network.Bridge, config_parse_string, 0, offsetof(Settings, network_bridge)
|
||||
Network.Zone, config_parse_network_zone, 0, 0
|
||||
Network.Port, config_parse_expose_port, 0, 0
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ether-addr-util.h"
|
||||
#include "lockfile-util.h"
|
||||
#include "netlink-util.h"
|
||||
#include "nspawn-network.h"
|
||||
#include "siphash24.h"
|
||||
@ -41,6 +42,30 @@
|
||||
#define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59)
|
||||
#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f)
|
||||
|
||||
static int remove_one_link(sd_netlink *rtnl, const char *name) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
if (isempty(name))
|
||||
return 0;
|
||||
|
||||
r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate netlink message: %m");
|
||||
|
||||
r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add netlink interface name: %m");
|
||||
|
||||
r = sd_netlink_call(rtnl, m, 0, NULL);
|
||||
if (r == -ENODEV) /* Already gone */
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to remove interface %s: %m", name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int generate_mac(
|
||||
const char *machine_name,
|
||||
struct ether_addr *mac,
|
||||
@ -240,43 +265,147 @@ int setup_veth_extra(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setup_bridge(const char *veth_name, const char *bridge_name) {
|
||||
static int join_bridge(sd_netlink *rtnl, const char *veth_name, const char *bridge_name) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
|
||||
int r, bridge_ifi;
|
||||
|
||||
assert(rtnl);
|
||||
assert(veth_name);
|
||||
assert(bridge_name);
|
||||
|
||||
bridge_ifi = (int) if_nametoindex(bridge_name);
|
||||
if (bridge_ifi <= 0)
|
||||
return log_error_errno(errno, "Failed to resolve interface %s: %m", bridge_name);
|
||||
return -errno;
|
||||
|
||||
r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_call(rtnl, m, 0, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return bridge_ifi;
|
||||
}
|
||||
|
||||
static int create_bridge(sd_netlink *rtnl, const char *bridge_name) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_append_string(m, IFLA_IFNAME, bridge_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "bridge");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_call(rtnl, m, 0, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setup_bridge(const char *veth_name, const char *bridge_name, bool create) {
|
||||
_cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT;
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
|
||||
int r, bridge_ifi;
|
||||
unsigned n = 0;
|
||||
|
||||
assert(veth_name);
|
||||
assert(bridge_name);
|
||||
|
||||
r = sd_netlink_open(&rtnl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to netlink: %m");
|
||||
|
||||
r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate netlink message: %m");
|
||||
if (create) {
|
||||
/* We take a system-wide lock here, so that we can safely check whether there's still a member in the
|
||||
* bridge before removing it, without risking interferance from other nspawn instances. */
|
||||
|
||||
r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set IFF_UP flag: %m");
|
||||
r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to take network zone lock: %m");
|
||||
}
|
||||
|
||||
r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add netlink interface name field: %m");
|
||||
for (;;) {
|
||||
bridge_ifi = join_bridge(rtnl, veth_name, bridge_name);
|
||||
if (bridge_ifi >= 0)
|
||||
return bridge_ifi;
|
||||
if (bridge_ifi != -ENODEV || !create || n > 10)
|
||||
return log_error_errno(bridge_ifi, "Failed to add interface %s to bridge %s: %m", veth_name, bridge_name);
|
||||
|
||||
r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add netlink master field: %m");
|
||||
/* Count attempts, so that we don't enter an endless loop here. */
|
||||
n++;
|
||||
|
||||
r = sd_netlink_call(rtnl, m, 0, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add veth interface to bridge: %m");
|
||||
/* The bridge doesn't exist yet. Let's create it */
|
||||
r = create_bridge(rtnl, bridge_name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create bridge interface %s: %m", bridge_name);
|
||||
|
||||
return bridge_ifi;
|
||||
/* Try again, now that the bridge exists */
|
||||
}
|
||||
}
|
||||
|
||||
int remove_bridge(const char *bridge_name) {
|
||||
_cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT;
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
|
||||
const char *path;
|
||||
int r;
|
||||
|
||||
/* Removes the specified bridge, but only if it is currently empty */
|
||||
|
||||
if (isempty(bridge_name))
|
||||
return 0;
|
||||
|
||||
r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to take network zone lock: %m");
|
||||
|
||||
path = strjoina("/sys/class/net/", bridge_name, "/brif");
|
||||
|
||||
r = dir_is_empty(path);
|
||||
if (r == -ENOENT) /* Already gone? */
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Can't detect if bridge %s is empty: %m", bridge_name);
|
||||
if (r == 0) /* Still populated, leave it around */
|
||||
return 0;
|
||||
|
||||
r = sd_netlink_open(&rtnl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to netlink: %m");
|
||||
|
||||
return remove_one_link(rtnl, bridge_name);
|
||||
}
|
||||
|
||||
static int parse_interface(struct udev *udev, const char *name) {
|
||||
@ -541,30 +670,6 @@ int veth_extra_parse(char ***l, const char *p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int remove_one_veth_link(sd_netlink *rtnl, const char *name) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
if (isempty(name))
|
||||
return 0;
|
||||
|
||||
r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate netlink message: %m");
|
||||
|
||||
r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add netlink interface name: %m");
|
||||
|
||||
r = sd_netlink_call(rtnl, m, 0, NULL);
|
||||
if (r == -ENODEV) /* Already gone */
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to remove veth interface %s: %m", name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int remove_veth_links(const char *primary, char **pairs) {
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
|
||||
char **a, **b;
|
||||
@ -580,10 +685,10 @@ int remove_veth_links(const char *primary, char **pairs) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to netlink: %m");
|
||||
|
||||
remove_one_veth_link(rtnl, primary);
|
||||
remove_one_link(rtnl, primary);
|
||||
|
||||
STRV_FOREACH_PAIR(a, b, pairs)
|
||||
remove_one_veth_link(rtnl, *a);
|
||||
remove_one_link(rtnl, *a);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -26,7 +26,8 @@
|
||||
int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge);
|
||||
int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
|
||||
|
||||
int setup_bridge(const char *veth_name, const char *bridge_name);
|
||||
int setup_bridge(const char *veth_name, const char *bridge_name, bool create);
|
||||
int remove_bridge(const char *bridge_name);
|
||||
|
||||
int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces);
|
||||
int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces);
|
||||
|
@ -24,10 +24,11 @@
|
||||
#include "nspawn-settings.h"
|
||||
#include "parse-util.h"
|
||||
#include "process-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
#include "util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int settings_load(FILE *f, const char *path, Settings **ret) {
|
||||
_cleanup_(settings_freep) Settings *s = NULL;
|
||||
@ -96,6 +97,7 @@ Settings* settings_free(Settings *s) {
|
||||
strv_free(s->network_ipvlan);
|
||||
strv_free(s->network_veth_extra);
|
||||
free(s->network_bridge);
|
||||
free(s->network_zone);
|
||||
expose_port_free_all(s->expose_ports);
|
||||
|
||||
custom_mount_free_all(s->custom_mounts, s->n_custom_mounts);
|
||||
@ -111,6 +113,7 @@ bool settings_private_network(Settings *s) {
|
||||
s->private_network > 0 ||
|
||||
s->network_veth > 0 ||
|
||||
s->network_bridge ||
|
||||
s->network_zone ||
|
||||
s->network_interfaces ||
|
||||
s->network_macvlan ||
|
||||
s->network_ipvlan ||
|
||||
@ -122,7 +125,8 @@ bool settings_network_veth(Settings *s) {
|
||||
|
||||
return
|
||||
s->network_veth > 0 ||
|
||||
s->network_bridge;
|
||||
s->network_bridge ||
|
||||
s->network_zone;
|
||||
}
|
||||
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode");
|
||||
@ -319,6 +323,38 @@ int config_parse_veth_extra(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_network_zone(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
Settings *settings = data;
|
||||
_cleanup_free_ char *j = NULL;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
|
||||
j = strappend("vz-", rvalue);
|
||||
if (!ifname_valid(j)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name %s, ignoring: %m", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(settings->network_zone);
|
||||
settings->network_zone = j;
|
||||
j = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_boot(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
|
@ -85,6 +85,7 @@ typedef struct Settings {
|
||||
int private_network;
|
||||
int network_veth;
|
||||
char *network_bridge;
|
||||
char *network_zone;
|
||||
char **network_interfaces;
|
||||
char **network_macvlan;
|
||||
char **network_ipvlan;
|
||||
@ -109,6 +110,7 @@ int config_parse_volatile_mode(const char *unit, const char *filename, unsigned
|
||||
int config_parse_bind(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_tmpfs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_veth_extra(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_network_zone(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_boot(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_pid2(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_private_users(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
|
@ -176,6 +176,7 @@ static char **arg_network_ipvlan = NULL;
|
||||
static bool arg_network_veth = false;
|
||||
static char **arg_network_veth_extra = NULL;
|
||||
static char *arg_network_bridge = NULL;
|
||||
static char *arg_network_zone = NULL;
|
||||
static unsigned long arg_personality = PERSONALITY_INVALID;
|
||||
static char *arg_image = NULL;
|
||||
static VolatileMode arg_volatile_mode = VOLATILE_NO;
|
||||
@ -234,6 +235,8 @@ static void help(void) {
|
||||
" Add a virtual Ethernet connection between host\n"
|
||||
" and container and add it to an existing bridge on\n"
|
||||
" the host\n"
|
||||
" --network-zone=NAME Add a virtual Ethernet connection to the container,\n"
|
||||
" and add it to an automatically managed bridge interface\n"
|
||||
" -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n"
|
||||
" Expose a container IP port on the host\n"
|
||||
" -Z --selinux-context=SECLABEL\n"
|
||||
@ -357,6 +360,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_NETWORK_MACVLAN,
|
||||
ARG_NETWORK_IPVLAN,
|
||||
ARG_NETWORK_BRIDGE,
|
||||
ARG_NETWORK_ZONE,
|
||||
ARG_NETWORK_VETH_EXTRA,
|
||||
ARG_PERSONALITY,
|
||||
ARG_VOLATILE,
|
||||
@ -404,6 +408,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "network-veth", no_argument, NULL, 'n' },
|
||||
{ "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA},
|
||||
{ "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE },
|
||||
{ "network-zone", required_argument, NULL, ARG_NETWORK_ZONE },
|
||||
{ "personality", required_argument, NULL, ARG_PERSONALITY },
|
||||
{ "image", required_argument, NULL, 'i' },
|
||||
{ "volatile", optional_argument, NULL, ARG_VOLATILE },
|
||||
@ -466,6 +471,28 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_settings_mask |= SETTING_USER;
|
||||
break;
|
||||
|
||||
case ARG_NETWORK_ZONE: {
|
||||
char *j;
|
||||
|
||||
j = strappend("vz-", optarg);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
if (!ifname_valid(j)) {
|
||||
log_error("Network zone name not valid: %s", j);
|
||||
free(j);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
free(arg_network_zone);
|
||||
arg_network_zone = j;
|
||||
|
||||
arg_network_veth = true;
|
||||
arg_private_network = true;
|
||||
arg_settings_mask |= SETTING_NETWORK;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_NETWORK_BRIDGE:
|
||||
|
||||
if (!ifname_valid(optarg)) {
|
||||
@ -1027,6 +1054,11 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (arg_network_bridge && arg_network_zone) {
|
||||
log_error("--network-bridge= and --network-zone= may not be combined.");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (argc > optind) {
|
||||
arg_parameters = strv_copy(argv + optind);
|
||||
if (!arg_parameters)
|
||||
@ -3295,6 +3327,7 @@ static int load_settings(void) {
|
||||
(settings->private_network >= 0 ||
|
||||
settings->network_veth >= 0 ||
|
||||
settings->network_bridge ||
|
||||
settings->network_zone ||
|
||||
settings->network_interfaces ||
|
||||
settings->network_macvlan ||
|
||||
settings->network_ipvlan ||
|
||||
@ -3325,6 +3358,10 @@ static int load_settings(void) {
|
||||
free(arg_network_bridge);
|
||||
arg_network_bridge = settings->network_bridge;
|
||||
settings->network_bridge = NULL;
|
||||
|
||||
free(arg_network_zone);
|
||||
arg_network_zone = settings->network_zone;
|
||||
settings->network_zone = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3824,14 +3861,23 @@ int main(int argc, char *argv[]) {
|
||||
goto finish;
|
||||
|
||||
if (arg_network_veth) {
|
||||
r = setup_veth(arg_machine, pid, veth_name, !!arg_network_bridge);
|
||||
r = setup_veth(arg_machine, pid, veth_name,
|
||||
arg_network_bridge || arg_network_zone);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
else if (r > 0)
|
||||
ifi = r;
|
||||
|
||||
if (arg_network_bridge) {
|
||||
r = setup_bridge(veth_name, arg_network_bridge);
|
||||
/* Add the interface to a bridge */
|
||||
r = setup_bridge(veth_name, arg_network_bridge, false);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r > 0)
|
||||
ifi = r;
|
||||
} else if (arg_network_zone) {
|
||||
/* Add the interface to a bridge, possibly creating it */
|
||||
r = setup_bridge(veth_name, arg_network_zone, true);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r > 0)
|
||||
@ -4039,6 +4085,7 @@ finish:
|
||||
|
||||
expose_port_flush(arg_expose_ports, &exposed);
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
(void) remove_bridge(arg_network_zone);
|
||||
|
||||
free(arg_directory);
|
||||
free(arg_template);
|
||||
|
Loading…
x
Reference in New Issue
Block a user