mirror of
https://github.com/systemd/systemd
synced 2024-10-15 04:24:19 +00:00
Merge pull request #3209 from poettering/nspawn-network-zones
introduce simple "network zones" concept to nspawn
This commit is contained in:
commit
36c9a0728d
|
@ -3598,7 +3598,8 @@ INSTALL_DIRS += \
|
|||
dist_network_DATA = \
|
||||
network/99-default.link \
|
||||
network/80-container-host0.network \
|
||||
network/80-container-ve.network
|
||||
network/80-container-ve.network \
|
||||
network/80-container-vz.network
|
||||
|
||||
dist_udevrules_DATA += \
|
||||
rules/50-udev-default.rules \
|
||||
|
|
5
TODO
5
TODO
|
@ -125,7 +125,8 @@ Features:
|
|||
|
||||
* implement a per-service firewall based on net_cls
|
||||
|
||||
* Port various tools to make use of verbs.[ch], where applicable
|
||||
* Port various tools to make use of verbs.[ch], where applicable: busctl,
|
||||
bootctl, coredumpctl, hostnamectl, localectl, systemd-analyze, timedatectl
|
||||
|
||||
* hostnamectl: show root image uuid
|
||||
|
||||
|
@ -287,8 +288,6 @@ Features:
|
|||
|
||||
* be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1
|
||||
|
||||
* unify dispatch table in systemctl_main() and friends
|
||||
|
||||
* rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it
|
||||
|
||||
* After coming back from hibernation reset hibernation swap partition using the /dev/snapshot ioctl APIs
|
||||
|
|
|
@ -524,15 +524,23 @@
|
|||
<term><option>-n</option></term>
|
||||
<term><option>--network-veth</option></term>
|
||||
|
||||
<listitem><para>Create a virtual Ethernet link
|
||||
(<literal>veth</literal>) between host and container. The host
|
||||
side of the Ethernet link will be available as a network
|
||||
interface named after the container's name (as specified with
|
||||
<option>--machine=</option>), prefixed with
|
||||
<literal>ve-</literal>. The container side of the Ethernet
|
||||
link will be named <literal>host0</literal>. Note that
|
||||
<option>--network-veth</option> implies
|
||||
<option>--private-network</option>.</para></listitem>
|
||||
<listitem><para>Create a virtual Ethernet link (<literal>veth</literal>) between host and container. The host
|
||||
side of the Ethernet link will be available as a network interface named after the container's name (as
|
||||
specified with <option>--machine=</option>), prefixed with <literal>ve-</literal>. The container side of the
|
||||
Ethernet link will be named <literal>host0</literal>. The <option>--network-veth</option> option implies
|
||||
<option>--private-network</option>.</para>
|
||||
|
||||
<para>Note that
|
||||
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
includes by default a network file <filename>/usr/lib/systemd/network/80-container-ve.network</filename>
|
||||
matching the host-side interfaces created this way, which contains settings to enable automatic address
|
||||
provisioning on the created virtual link via DHCP, as well as automatic IP routing onto the host's external
|
||||
network interfaces. It also contains <filename>/usr/lib/systemd/network/80-container-host0.network</filename>
|
||||
matching the container-side interface created this way, containing settings to enable client side address
|
||||
assignment via DHCP. In case <filename>systemd-networkd</filename> is running on both the host and inside the
|
||||
container, automatic IP communication from the container to the host is thus available, with further
|
||||
connectivity to the external network.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -553,15 +561,42 @@
|
|||
<varlistentry>
|
||||
<term><option>--network-bridge=</option></term>
|
||||
|
||||
<listitem><para>Adds the host side of the Ethernet link
|
||||
created with <option>--network-veth</option> to the specified
|
||||
bridge. Note that <option>--network-bridge=</option> implies
|
||||
<option>--network-veth</option>. If this option is used, the
|
||||
host side of the Ethernet link will use the
|
||||
<literal>vb-</literal> prefix instead of
|
||||
<listitem><para>Adds the host side of the Ethernet link created with <option>--network-veth</option> to the
|
||||
specified Ethernet bridge interface. Expects a valid network interface name of a bridge device as
|
||||
argument. Note that <option>--network-bridge=</option> implies <option>--network-veth</option>. If this option
|
||||
is used, the host side of the Ethernet link will use the <literal>vb-</literal> prefix instead of
|
||||
<literal>ve-</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--network-zone=</option></term>
|
||||
|
||||
<listitem><para>Creates a virtual Ethernet link (<literal>veth</literal>) to the container and adds it to an
|
||||
automatically managed Ethernet bridge interface. The bridge interface is named after the passed argument,
|
||||
prefixed with <literal>vz-</literal>. The bridge interface is automatically created when the first container
|
||||
configured for its name is started, and is automatically removed when the last container configured for its
|
||||
name exits. Hence, each bridge interface configured this way exists only as long as there's at least one
|
||||
container referencing it running. This option is very similar to <option>--network-bridge=</option>, besides
|
||||
this automatic creation/removal of the bridge device.</para>
|
||||
|
||||
<para>This setting makes it easy to place multiple related containers on a common, virtual Ethernet-based
|
||||
broadcast domain, here called a "zone". Each container may only be part of one zone, but each zone may contain
|
||||
any number of containers. Each zone is referenced by its name. Names may be chosen freely (as long as they form
|
||||
valid network interface names when prefixed with <literal>vz-</literal>), and it is sufficient to pass the same
|
||||
name to the <option>--network-zones=</option> switch of the various concurrently running containers to join
|
||||
them in one zone.</para>
|
||||
|
||||
<para>Note that
|
||||
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
includes by default a network file <filename>/usr/lib/systemd/network/80-container-vz.network</filename>
|
||||
matching the bridge interfaces created this way, which contains settings to enable automatic address
|
||||
provisioning on the created virtual network via DHCP, as well as automatic IP routing onto the host's external
|
||||
network interfaces. Using <option>--network-zone=</option> is hence in most cases fully automatic and
|
||||
sufficient to connect multiple local containers in a joined broadcast domain to the host, with further
|
||||
connectivity to the external network.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-p</option></term>
|
||||
<term><option>--port=</option></term>
|
||||
|
@ -577,7 +612,7 @@
|
|||
port number and its colon may be omitted, in which case the
|
||||
same port as the host port is implied. This option is only
|
||||
supported if private networking is used, such as with
|
||||
<option>--network-veth</option> or
|
||||
<option>--network-veth</option>, <option>--network-zone=</option>
|
||||
<option>--network-bridge=</option>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
|
|
@ -363,18 +363,26 @@
|
|||
<varlistentry>
|
||||
<term><varname>EmitLLDP=</varname></term>
|
||||
<listitem>
|
||||
<para>Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter and defaults to
|
||||
false. If enabled a short LLDP packet with information about the local system is sent out in regular
|
||||
intervals on the link. The LLDP packet will contain information about the local host name, the local
|
||||
machine ID (as stored in
|
||||
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) and the
|
||||
<para>Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter or the special values
|
||||
<literal>nearest-bridge</literal>, <literal>non-tpmr-bridge</literal> and
|
||||
<literal>customer-bridge</literal>. Defaults to false, which turns off LLDP packet emission. If not false,
|
||||
a short LLDP packet with information about the local system is sent out in regular intervals on the
|
||||
link. The LLDP packet will contain information about the local host name, the local machine ID (as stored
|
||||
in <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) and the
|
||||
local interface name, as well as the pretty hostname of the system (as set in
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>). LLDP
|
||||
emission is only available on Ethernet links. Note that this setting passed data suitable for
|
||||
identification of host to the network and should thus not be used on untrusted networks, where such
|
||||
identification data should not be made available. Use this option to enable other systems to identify on
|
||||
which interface they are connected to this system. See <varname>LLDP=</varname> above for an option to
|
||||
enable LLDP reception.</para>
|
||||
emission is only available on Ethernet links. Note that this setting passes data suitable for
|
||||
identification of host to the network and should thus not be enabled on untrusted networks, where such
|
||||
identification data should not be made available. Use this option to permit other systems to identify on
|
||||
which interfaces they are connected to this system. The three special values control propagation of the
|
||||
LLDP packets. The <literal>nearest-bridge</literal> setting permits propagation only to the nearest
|
||||
connected bridge, <literal>non-tpmr-bridge</literal> permits propagation across Two-Port MAC Relays, but
|
||||
not any other bridges, and <literal>customer-bridge</literal> permits propagation until a customer bridge
|
||||
is reached. For details about these concepts, see <ulink
|
||||
url="http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf">IEEE 802.1AB-2009</ulink>. Note that
|
||||
configuring this setting to true is equivalent to <literal>nearest-bridge</literal>, the recommended and
|
||||
most restricted level of propagation. See <varname>LLDP=</varname> above for an option to enable LLDP
|
||||
reception.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
|
|
|
@ -419,6 +419,16 @@
|
|||
option is privileged (see above).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Zone=</varname></term>
|
||||
|
||||
<listitem><para>Takes a network zone name. This setting implies <varname>VirtualEthernet=yes</varname> and
|
||||
<varname>Private=yes</varname> and has the effect that the host side of the created virtual Ethernet link is
|
||||
connected to an automatically managed bridge interface named after the passed argument, prefixed with
|
||||
<literal>vz-</literal>. This option corresponds to the <option>--network-zone=</option> command line
|
||||
switch. This option is privileged (see above).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Port=</varname></term>
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This network file matches the container-side of the virtual Ethernet link
|
||||
# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for
|
||||
# details.
|
||||
|
||||
[Match]
|
||||
Virtualization=container
|
||||
Name=host0
|
||||
|
@ -13,7 +17,7 @@ Name=host0
|
|||
DHCP=yes
|
||||
LinkLocalAddressing=yes
|
||||
LLDP=yes
|
||||
EmitLLDP=yes
|
||||
EmitLLDP=customer-bridge
|
||||
|
||||
[DHCP]
|
||||
UseTimezone=yes
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This network file matches the host-side of the virtual Ethernet link
|
||||
# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for
|
||||
# details.
|
||||
|
||||
[Match]
|
||||
Name=ve-*
|
||||
Driver=veth
|
||||
|
@ -16,4 +20,4 @@ LinkLocalAddressing=yes
|
|||
DHCPServer=yes
|
||||
IPMasquerade=yes
|
||||
LLDP=yes
|
||||
EmitLLDP=yes
|
||||
EmitLLDP=customer-bridge
|
||||
|
|
22
network/80-container-vz.network
Normal file
22
network/80-container-vz.network
Normal file
|
@ -0,0 +1,22 @@
|
|||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This network file matches the bridge interface created by systemd-nspawn's
|
||||
# --network-zone= switch. See systemd-nspawn(1) for details.
|
||||
|
||||
[Match]
|
||||
Name=vz-*
|
||||
Driver=bridge
|
||||
|
||||
[Network]
|
||||
# Default to using a /24 prefix, giving up to 253 addresses per virtual network.
|
||||
Address=0.0.0.0/24
|
||||
LinkLocalAddressing=yes
|
||||
DHCPServer=yes
|
||||
IPMasquerade=yes
|
||||
LLDP=yes
|
||||
EmitLLDP=customer-bridge
|
|
@ -43,7 +43,9 @@
|
|||
#include "socket-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
#include "util.h"
|
||||
|
||||
int socket_address_parse(SocketAddress *a, const char *s) {
|
||||
|
@ -795,6 +797,42 @@ static const char* const ip_tos_table[] = {
|
|||
|
||||
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
|
||||
|
||||
bool ifname_valid(const char *p) {
|
||||
bool numeric = true;
|
||||
|
||||
/* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources
|
||||
* but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We
|
||||
* also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */
|
||||
|
||||
if (isempty(p))
|
||||
return false;
|
||||
|
||||
if (strlen(p) >= IFNAMSIZ)
|
||||
return false;
|
||||
|
||||
if (STR_IN_SET(p, ".", ".."))
|
||||
return false;
|
||||
|
||||
while (*p) {
|
||||
if ((unsigned char) *p >= 127U)
|
||||
return false;
|
||||
|
||||
if ((unsigned char) *p <= 32U)
|
||||
return false;
|
||||
|
||||
if (*p == ':' || *p == '/')
|
||||
return false;
|
||||
|
||||
numeric = numeric && (*p >= '0' && *p <= '9');
|
||||
p++;
|
||||
}
|
||||
|
||||
if (numeric)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int getpeercred(int fd, struct ucred *ucred) {
|
||||
socklen_t n = sizeof(struct ucred);
|
||||
struct ucred u;
|
||||
|
|
|
@ -123,6 +123,8 @@ int fd_inc_rcvbuf(int fd, size_t n);
|
|||
int ip_tos_to_string_alloc(int i, char **s);
|
||||
int ip_tos_from_string(const char *s);
|
||||
|
||||
bool ifname_valid(const char *p);
|
||||
|
||||
int getpeercred(int fd, struct ucred *ucred);
|
||||
int getpeersec(int fd, char **ret);
|
||||
|
||||
|
|
|
@ -732,16 +732,17 @@ int config_parse_exec(
|
|||
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
|
||||
|
||||
int config_parse_socket_bindtodevice(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_socket_bindtodevice(
|
||||
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) {
|
||||
|
||||
Socket *s = data;
|
||||
char *n;
|
||||
|
@ -752,6 +753,11 @@ int config_parse_socket_bindtodevice(const char* unit,
|
|||
assert(data);
|
||||
|
||||
if (rvalue[0] && !streq(rvalue, "*")) {
|
||||
if (!ifname_valid(rvalue)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = strdup(rvalue);
|
||||
if (!n)
|
||||
return log_oom();
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "network-internal.h"
|
||||
#include "parse-util.h"
|
||||
#include "siphash24.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "utf8.h"
|
||||
|
@ -175,54 +176,17 @@ int config_parse_net_condition(const char *unit,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_ifname(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) {
|
||||
|
||||
char **s = data;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
n = strdup(rvalue);
|
||||
if (!n)
|
||||
return log_oom();
|
||||
|
||||
if (!ascii_is_valid(n) || strlen(n) >= IFNAMSIZ) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(*s);
|
||||
if (*n) {
|
||||
*s = n;
|
||||
n = NULL;
|
||||
} else
|
||||
*s = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_ifnames(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_ifnames(
|
||||
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) {
|
||||
|
||||
char ***sv = data;
|
||||
int r;
|
||||
|
@ -236,13 +200,15 @@ int config_parse_ifnames(const char *unit,
|
|||
_cleanup_free_ char *word = NULL;
|
||||
|
||||
r = extract_first_word(&rvalue, &word, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (!ascii_is_valid(word) || strlen(word) >= IFNAMSIZ) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue);
|
||||
if (!ifname_valid(word)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,10 +50,6 @@ int config_parse_hwaddr(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_ifname(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_ifnames(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);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "networkd.h"
|
||||
#include "parse-util.h"
|
||||
#include "set.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "utf8.h"
|
||||
#include "util.h"
|
||||
|
@ -726,7 +727,8 @@ int config_parse_address(const char *unit,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_label(const char *unit,
|
||||
int config_parse_label(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
|
@ -736,9 +738,9 @@ int config_parse_label(const char *unit,
|
|||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
Network *network = userdata;
|
||||
|
||||
_cleanup_address_free_ Address *n = NULL;
|
||||
char *label;
|
||||
Network *network = userdata;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
|
@ -751,23 +753,14 @@ int config_parse_label(const char *unit,
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
label = strdup(rvalue);
|
||||
if (!label)
|
||||
return log_oom();
|
||||
|
||||
if (!ascii_is_valid(label) || strlen(label) >= IFNAMSIZ) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface label is not ASCII clean or is too long, ignoring assignment: %s", rvalue);
|
||||
free(label);
|
||||
if (!ifname_valid(rvalue)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface label is not valid or too long, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(n->label);
|
||||
if (*label)
|
||||
n->label = label;
|
||||
else {
|
||||
free(label);
|
||||
n->label = NULL;
|
||||
}
|
||||
r = free_and_strdup(&n->label, rvalue);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
n = NULL;
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ static bool link_lldp_rx_enabled(Link *link) {
|
|||
return link->network->lldp_mode != LLDP_MODE_NO;
|
||||
}
|
||||
|
||||
static bool link_lldp_tx_enabled(Link *link) {
|
||||
static bool link_lldp_emit_enabled(Link *link) {
|
||||
assert(link);
|
||||
|
||||
if (link->flags & IFF_LOOPBACK)
|
||||
|
@ -143,7 +143,7 @@ static bool link_lldp_tx_enabled(Link *link) {
|
|||
if (!link->network)
|
||||
return false;
|
||||
|
||||
return link->network->lldp_emit;
|
||||
return link->network->lldp_emit != LLDP_EMIT_NO;
|
||||
}
|
||||
|
||||
static bool link_ipv4_forward_enabled(Link *link) {
|
||||
|
@ -491,7 +491,7 @@ static void link_free(Link *link) {
|
|||
sd_dhcp_client_unref(link->dhcp_client);
|
||||
sd_dhcp_lease_unref(link->dhcp_lease);
|
||||
|
||||
link_lldp_tx_stop(link);
|
||||
link_lldp_emit_stop(link);
|
||||
|
||||
free(link->lease_file);
|
||||
|
||||
|
@ -618,7 +618,7 @@ static int link_stop_clients(Link *link) {
|
|||
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m");
|
||||
}
|
||||
|
||||
link_lldp_tx_stop(link);
|
||||
link_lldp_emit_stop(link);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -1411,12 +1411,12 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n
|
|||
|
||||
(void) link_lldp_save(link);
|
||||
|
||||
if (link_lldp_tx_enabled(link) && event == SD_LLDP_EVENT_ADDED) {
|
||||
if (link_lldp_emit_enabled(link) && event == SD_LLDP_EVENT_ADDED) {
|
||||
/* If we received information about a new neighbor, restart the LLDP "fast" logic */
|
||||
|
||||
log_link_debug(link, "Received LLDP datagram from previously unknown neighbor, restarting 'fast' LLDP transmission.");
|
||||
|
||||
r = link_lldp_tx_start(link);
|
||||
r = link_lldp_emit_start(link);
|
||||
if (r < 0)
|
||||
log_link_warning_errno(link, r, "Failed to restart LLDP transmission: %m");
|
||||
}
|
||||
|
@ -1501,8 +1501,8 @@ static int link_acquire_conf(Link *link) {
|
|||
return r;
|
||||
}
|
||||
|
||||
if (link_lldp_tx_enabled(link)) {
|
||||
r = link_lldp_tx_start(link);
|
||||
if (link_lldp_emit_enabled(link)) {
|
||||
r = link_lldp_emit_start(link);
|
||||
if (r < 0)
|
||||
return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m");
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ typedef struct Link {
|
|||
|
||||
/* This is about LLDP transmission */
|
||||
unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */
|
||||
sd_event_source *lldp_tx_event_source;
|
||||
sd_event_source *lldp_emit_event_source;
|
||||
|
||||
Hashmap *bound_by_links;
|
||||
Hashmap *bound_to_links;
|
||||
|
|
|
@ -25,16 +25,14 @@
|
|||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "hostname-util.h"
|
||||
#include "networkd-lldp-tx.h"
|
||||
#include "networkd.h"
|
||||
#include "parse-util.h"
|
||||
#include "random-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "unaligned.h"
|
||||
|
||||
#include "networkd.h"
|
||||
#include "networkd-lldp-tx.h"
|
||||
|
||||
#define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }
|
||||
|
||||
/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */
|
||||
#define LLDP_TX_FAST_INIT 4U
|
||||
|
||||
|
@ -50,6 +48,12 @@
|
|||
/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */
|
||||
#define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2)
|
||||
|
||||
static const struct ether_addr lldp_multicast_addr[_LLDP_EMIT_MAX] = {
|
||||
[LLDP_EMIT_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }},
|
||||
[LLDP_EMIT_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }},
|
||||
[LLDP_EMIT_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }},
|
||||
};
|
||||
|
||||
static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) {
|
||||
assert(p);
|
||||
|
||||
|
@ -66,6 +70,7 @@ static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) {
|
|||
}
|
||||
|
||||
static int lldp_make_packet(
|
||||
LLDPEmit mode,
|
||||
const struct ether_addr *hwaddr,
|
||||
const char *machine_id,
|
||||
const char *ifname,
|
||||
|
@ -84,6 +89,8 @@ static int lldp_make_packet(
|
|||
size_t l;
|
||||
int r;
|
||||
|
||||
assert(mode > LLDP_EMIT_NO);
|
||||
assert(mode < _LLDP_EMIT_MAX);
|
||||
assert(hwaddr);
|
||||
assert(machine_id);
|
||||
assert(ifname);
|
||||
|
@ -132,7 +139,7 @@ static int lldp_make_packet(
|
|||
|
||||
h = (struct ether_header*) packet;
|
||||
h->ether_type = htobe16(ETHERTYPE_LLDP);
|
||||
memcpy(h->ether_dhost, &(struct ether_addr) { LLDP_MULTICAST_ADDR }, ETH_ALEN);
|
||||
memcpy(h->ether_dhost, lldp_multicast_addr + mode, ETH_ALEN);
|
||||
memcpy(h->ether_shost, hwaddr, ETH_ALEN);
|
||||
|
||||
p = (uint8_t*) packet + sizeof(struct ether_header);
|
||||
|
@ -197,22 +204,28 @@ static int lldp_make_packet(
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int lldp_send_packet(int ifindex, const void *packet, size_t packet_size) {
|
||||
static int lldp_send_packet(
|
||||
int ifindex,
|
||||
const struct ether_addr *address,
|
||||
const void *packet,
|
||||
size_t packet_size) {
|
||||
|
||||
union sockaddr_union sa = {
|
||||
.ll.sll_family = AF_PACKET,
|
||||
.ll.sll_protocol = htobe16(ETHERTYPE_LLDP),
|
||||
.ll.sll_ifindex = ifindex,
|
||||
.ll.sll_halen = ETH_ALEN,
|
||||
.ll.sll_addr = LLDP_MULTICAST_ADDR,
|
||||
};
|
||||
|
||||
_cleanup_close_ int fd = -1;
|
||||
ssize_t l;
|
||||
|
||||
assert(ifindex > 0);
|
||||
assert(address);
|
||||
assert(packet || packet_size <= 0);
|
||||
|
||||
memcpy(sa.ll.sll_addr, address, ETH_ALEN);
|
||||
|
||||
fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
@ -237,6 +250,13 @@ static int link_send_lldp(Link *link) {
|
|||
usec_t ttl;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO)
|
||||
return 0;
|
||||
|
||||
assert(link->network->lldp_emit < _LLDP_EMIT_MAX);
|
||||
|
||||
r = sd_id128_get_machine(&machine_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
@ -251,7 +271,8 @@ static int link_send_lldp(Link *link) {
|
|||
SD_LLDP_SYSTEM_CAPABILITIES_ROUTER :
|
||||
SD_LLDP_SYSTEM_CAPABILITIES_STATION;
|
||||
|
||||
r = lldp_make_packet(&link->mac,
|
||||
r = lldp_make_packet(link->network->lldp_emit,
|
||||
&link->mac,
|
||||
sd_id128_to_string(machine_id, machine_id_string),
|
||||
link->ifname,
|
||||
(uint16_t) ttl,
|
||||
|
@ -264,7 +285,7 @@ static int link_send_lldp(Link *link) {
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return lldp_send_packet(link->ifindex, packet, packet_size);
|
||||
return lldp_send_packet(link->ifindex, lldp_multicast_addr + link->network->lldp_emit, packet, packet_size);
|
||||
}
|
||||
|
||||
static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) {
|
||||
|
@ -300,12 +321,17 @@ static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int link_lldp_tx_start(Link *link) {
|
||||
int link_lldp_emit_start(Link *link) {
|
||||
usec_t next;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) {
|
||||
link_lldp_emit_stop(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */
|
||||
|
||||
link->lldp_tx_fast = LLDP_TX_FAST_INIT;
|
||||
|
@ -313,22 +339,22 @@ int link_lldp_tx_start(Link *link) {
|
|||
next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC),
|
||||
(usec_t) random_u64() % LLDP_JITTER_USEC);
|
||||
|
||||
if (link->lldp_tx_event_source) {
|
||||
if (link->lldp_emit_event_source) {
|
||||
usec_t old;
|
||||
|
||||
/* Lower the timeout, maybe */
|
||||
r = sd_event_source_get_time(link->lldp_tx_event_source, &old);
|
||||
r = sd_event_source_get_time(link->lldp_emit_event_source, &old);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (old <= next)
|
||||
return 0;
|
||||
|
||||
return sd_event_source_set_time(link->lldp_tx_event_source, next);
|
||||
return sd_event_source_set_time(link->lldp_emit_event_source, next);
|
||||
} else {
|
||||
r = sd_event_add_time(
|
||||
link->manager->event,
|
||||
&link->lldp_tx_event_source,
|
||||
&link->lldp_emit_event_source,
|
||||
clock_boottime_or_monotonic(),
|
||||
next,
|
||||
0,
|
||||
|
@ -337,14 +363,54 @@ int link_lldp_tx_start(Link *link) {
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) sd_event_source_set_description(link->lldp_tx_event_source, "lldp-tx");
|
||||
(void) sd_event_source_set_description(link->lldp_emit_event_source, "lldp-tx");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void link_lldp_tx_stop(Link *link) {
|
||||
void link_lldp_emit_stop(Link *link) {
|
||||
assert(link);
|
||||
|
||||
link->lldp_tx_event_source = sd_event_source_unref(link->lldp_tx_event_source);
|
||||
link->lldp_emit_event_source = sd_event_source_unref(link->lldp_emit_event_source);
|
||||
}
|
||||
|
||||
int config_parse_lldp_emit(
|
||||
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) {
|
||||
|
||||
LLDPEmit *emit = data;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
|
||||
if (isempty(rvalue))
|
||||
*emit = LLDP_EMIT_NO;
|
||||
else if (streq(rvalue, "nearest-bridge"))
|
||||
*emit = LLDP_EMIT_NEAREST_BRIDGE;
|
||||
else if (streq(rvalue, "non-tpmr-bridge"))
|
||||
*emit = LLDP_EMIT_NON_TPMR_BRIDGE;
|
||||
else if (streq(rvalue, "customer-bridge"))
|
||||
*emit = LLDP_EMIT_CUSTOMER_BRIDGE;
|
||||
else {
|
||||
r = parse_boolean(rvalue);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse LLDP emission setting, ignoring: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*emit = r ? LLDP_EMIT_NEAREST_BRIDGE : LLDP_EMIT_NO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -21,5 +21,15 @@
|
|||
|
||||
#include "networkd-link.h"
|
||||
|
||||
int link_lldp_tx_start(Link *link);
|
||||
void link_lldp_tx_stop(Link *link);
|
||||
typedef enum LLDPEmit {
|
||||
LLDP_EMIT_NO,
|
||||
LLDP_EMIT_NEAREST_BRIDGE,
|
||||
LLDP_EMIT_NON_TPMR_BRIDGE,
|
||||
LLDP_EMIT_CUSTOMER_BRIDGE,
|
||||
_LLDP_EMIT_MAX,
|
||||
} LLDPEmit;
|
||||
|
||||
int link_lldp_emit_start(Link *link);
|
||||
void link_lldp_emit_stop(Link *link);
|
||||
|
||||
int config_parse_lldp_emit(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);
|
||||
|
|
|
@ -42,7 +42,7 @@ Network.LinkLocalAddressing, config_parse_address_family_boolean,
|
|||
Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
|
||||
Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
|
||||
Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
|
||||
Network.EmitLLDP, config_parse_bool, 0, offsetof(Network, lldp_emit)
|
||||
Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit)
|
||||
Network.Address, config_parse_address, 0, 0
|
||||
Network.Gateway, config_parse_gateway, 0, 0
|
||||
Network.Domains, config_parse_domains, 0, 0
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "networkd-address.h"
|
||||
#include "networkd-fdb.h"
|
||||
#include "networkd-lldp-tx.h"
|
||||
#include "networkd-netdev.h"
|
||||
#include "networkd-route.h"
|
||||
#include "networkd-util.h"
|
||||
|
@ -161,7 +162,7 @@ struct Network {
|
|||
DUID duid;
|
||||
|
||||
LLDPMode lldp_mode; /* LLDP reception */
|
||||
bool lldp_emit; /* LLDP transmission */
|
||||
LLDPEmit lldp_emit; /* LLDP transmission */
|
||||
|
||||
LIST_HEAD(Address, static_addresses);
|
||||
LIST_HEAD(Route, static_routes);
|
||||
|
|
|
@ -39,5 +39,6 @@ Network.MACVLAN, config_parse_strv, 0, offsetof(Settings,
|
|||
Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan)
|
||||
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.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge)
|
||||
Network.Zone, config_parse_network_zone, 0, 0
|
||||
Network.Port, config_parse_expose_port, 0, 0
|
||||
|
|
|
@ -26,9 +26,12 @@
|
|||
|
||||
#include "alloc-util.h"
|
||||
#include "ether-addr-util.h"
|
||||
#include "lockfile-util.h"
|
||||
#include "netlink-util.h"
|
||||
#include "nspawn-network.h"
|
||||
#include "siphash24.h"
|
||||
#include "socket-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "udev-util.h"
|
||||
#include "util.h"
|
||||
|
@ -39,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,
|
||||
|
@ -238,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) {
|
||||
|
@ -515,13 +646,13 @@ int veth_extra_parse(char ***l, const char *p) {
|
|||
r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0 || isempty(a))
|
||||
if (r == 0 || !ifname_valid(a))
|
||||
return -EINVAL;
|
||||
|
||||
r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0 || isempty(b)) {
|
||||
if (r == 0 || !ifname_valid(b)) {
|
||||
free(b);
|
||||
b = strdup(a);
|
||||
if (!b)
|
||||
|
@ -539,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;
|
||||
|
@ -578,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,7 +471,35 @@ 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)) {
|
||||
log_error("Bridge interface name not valid: %s", optarg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
r = free_and_strdup(&arg_network_bridge, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
@ -489,6 +522,12 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
break;
|
||||
|
||||
case ARG_NETWORK_INTERFACE:
|
||||
|
||||
if (!ifname_valid(optarg)) {
|
||||
log_error("Network interface name not valid: %s", optarg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strv_extend(&arg_network_interfaces, optarg) < 0)
|
||||
return log_oom();
|
||||
|
||||
|
@ -497,6 +536,12 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
break;
|
||||
|
||||
case ARG_NETWORK_MACVLAN:
|
||||
|
||||
if (!ifname_valid(optarg)) {
|
||||
log_error("MACVLAN network interface name not valid: %s", optarg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strv_extend(&arg_network_macvlan, optarg) < 0)
|
||||
return log_oom();
|
||||
|
||||
|
@ -505,6 +550,12 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
break;
|
||||
|
||||
case ARG_NETWORK_IPVLAN:
|
||||
|
||||
if (!ifname_valid(optarg)) {
|
||||
log_error("IPVLAN network interface name not valid: %s", optarg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strv_extend(&arg_network_ipvlan, optarg) < 0)
|
||||
return log_oom();
|
||||
|
||||
|
@ -1003,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)
|
||||
|
@ -3271,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 ||
|
||||
|
@ -3301,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3346,7 +3407,7 @@ int main(int argc, char *argv[]) {
|
|||
int ret = EXIT_SUCCESS;
|
||||
union in_addr_union exposed = {};
|
||||
_cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
|
||||
bool interactive;
|
||||
bool interactive, veth_created = false;
|
||||
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
@ -3800,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)
|
||||
|
@ -3819,6 +3889,12 @@ int main(int argc, char *argv[]) {
|
|||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
/* We created the primary and extra veth links now; let's remember this, so that we know to
|
||||
remove them later on. Note that we don't bother with removing veth links that were created
|
||||
here when their setup failed half-way, because in that case the kernel should be able to
|
||||
remove them on its own, since they cannot be referenced by anything yet. */
|
||||
veth_created = true;
|
||||
|
||||
r = setup_macvlan(arg_machine, pid, arg_network_macvlan);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
@ -3981,7 +4057,9 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
expose_port_flush(arg_expose_ports, &exposed);
|
||||
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
veth_created = false;
|
||||
}
|
||||
|
||||
finish:
|
||||
|
@ -4014,7 +4092,10 @@ finish:
|
|||
}
|
||||
|
||||
expose_port_flush(arg_expose_ports, &exposed);
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
|
||||
if (veth_created)
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
(void) remove_bridge(arg_network_zone);
|
||||
|
||||
free(arg_directory);
|
||||
free(arg_template);
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "syslog-util.h"
|
||||
|
@ -873,3 +874,40 @@ int config_parse_personality(
|
|||
*personality = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_ifname(
|
||||
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) {
|
||||
|
||||
char **s = data;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
*s = mfree(*s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ifname_valid(rvalue)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = free_and_strdup(s, rvalue);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ int config_parse_log_facility(const char *unit, const char *filename, unsigned l
|
|||
int config_parse_log_level(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_signal(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_personality(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_ifname(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);
|
||||
|
||||
#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
|
||||
int function(const char *unit, \
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "firewall-util.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "macro.h"
|
||||
#include "socket-util.h"
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free);
|
||||
|
||||
|
@ -59,10 +60,9 @@ static int entry_fill_basics(
|
|||
|
||||
assert(entry);
|
||||
|
||||
if (out_interface && strlen(out_interface) >= IFNAMSIZ)
|
||||
if (out_interface && !ifname_valid(out_interface))
|
||||
return -EINVAL;
|
||||
|
||||
if (in_interface && strlen(in_interface) >= IFNAMSIZ)
|
||||
if (in_interface && !ifname_valid(in_interface))
|
||||
return -EINVAL;
|
||||
|
||||
entry->ip.proto = protocol;
|
||||
|
|
|
@ -27,6 +27,29 @@
|
|||
#include "string-util.h"
|
||||
#include "util.h"
|
||||
|
||||
static void test_ifname_valid(void) {
|
||||
assert(ifname_valid("foo"));
|
||||
assert(ifname_valid("eth0"));
|
||||
|
||||
assert(!ifname_valid("0"));
|
||||
assert(!ifname_valid("99"));
|
||||
assert(ifname_valid("a99"));
|
||||
assert(ifname_valid("99a"));
|
||||
|
||||
assert(!ifname_valid(NULL));
|
||||
assert(!ifname_valid(""));
|
||||
assert(!ifname_valid(" "));
|
||||
assert(!ifname_valid(" foo"));
|
||||
assert(!ifname_valid("bar\n"));
|
||||
assert(!ifname_valid("."));
|
||||
assert(!ifname_valid(".."));
|
||||
assert(ifname_valid("foo.bar"));
|
||||
assert(!ifname_valid("x:y"));
|
||||
|
||||
assert(ifname_valid("xxxxxxxxxxxxxxx"));
|
||||
assert(!ifname_valid("xxxxxxxxxxxxxxxx"));
|
||||
}
|
||||
|
||||
static void test_socket_address_parse(void) {
|
||||
SocketAddress a;
|
||||
|
||||
|
@ -362,6 +385,8 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
|
||||
test_ifname_valid();
|
||||
|
||||
test_socket_address_parse();
|
||||
test_socket_address_parse_netlink();
|
||||
test_socket_address_equal();
|
||||
|
|
Loading…
Reference in a new issue