1
0
mirror of https://github.com/systemd/systemd synced 2026-03-10 07:04:46 +01:00

Compare commits

..

No commits in common. "c83e44110cea3d1542640b954c1d83041a710fe3" and "b332778b30d23193c792d5f5c5dcccd61f4a489c" have entirely different histories.

27 changed files with 186 additions and 381 deletions

View File

@ -226,7 +226,7 @@ appliance-like installations.
### What partitioning tools will create a DPS-compliant partition table?
As of util-linux 2.25.2, the `fdisk` tool provides type codes to create the
root, home, and swap partitions that the DPS expects. By default, `fdisk` will
root, home, and swap partitions that the DPS expects, By default, `fdisk` will
create an old-style MBR, not a GPT, so typing `l` to list partition types will
not show the choices to let you set the correct UUID. Make sure to first create
an empty GPT, then type `l` in order for the DPS-compliant type codes to be

View File

@ -2408,9 +2408,7 @@ SystemCallErrorNumber=EPERM</programlisting>
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more
details about named file descriptors and their ordering.</para>
<para>This setting defaults to <option>null</option>, unless
<varname>StandardInputText=</varname>/<varname>StandardInputData=</varname> are set, in which case it
defaults to <option>data</option>.</para></listitem>
<para>This setting defaults to <option>null</option>.</para></listitem>
</varlistentry>
<varlistentry>
@ -2524,11 +2522,9 @@ SystemCallErrorNumber=EPERM</programlisting>
<term><varname>StandardInputText=</varname></term>
<term><varname>StandardInputData=</varname></term>
<listitem><para>Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to
the executed processes. These settings have no effect unless <varname>StandardInput=</varname> is set
to <option>data</option> (which is the default if <varname>StandardInput=</varname> is not set
otherwise, but <varname>StandardInputText=</varname>/<varname>StandardInputData=</varname> is). Use
this option to embed process input data directly in the unit file.</para>
<listitem><para>Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the
executed processes. These settings have no effect unless <varname>StandardInput=</varname> is set to
<option>data</option>. Use this option to embed process input data directly in the unit file.</para>
<para><varname>StandardInputText=</varname> accepts arbitrary textual data. C-style escapes for special
characters as well as the usual <literal>%</literal>-specifiers are resolved. Each time this setting is used

View File

@ -1349,14 +1349,6 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
<literal>no</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Blackhole=</varname></term>
<listitem>
<para>Takes a boolean. If enabled, packets to the corresponding routes are discarded
silently, and <varname>Gateway=</varname> cannot be specified. Defaults to
<literal>no</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -1063,32 +1063,28 @@
<refsect2>
<title>Conditions and Asserts</title>
<para>Unit files may also include a number of <varname index="false">Condition…=</varname> and <varname
index="false">Assert…=</varname> settings. Before the unit is started, systemd will verify that the
specified conditions and asserts are true. If not, the starting of the unit will be (mostly silently)
skipped (in case of conditions), or aborted with an error message (in case of asserts). Failing
conditions or asserts will not result in the unit being moved into the <literal>failed</literal>
state. The conditions and asserts are checked at the time the queued start job is to be executed. The
ordering dependencies are still respected, so other units are still pulled in and ordered as if this
unit was successfully activated, and the conditions and asserts are executed the precise moment the
unit would normally start and thus can validate system state after the units ordered before completed
initialization. Use condition expressions for skipping units that do not apply to the local system, for
example because the kernel or runtime environment doesn't require their functionality.
<para>Unit files may also include a number of <varname index="false">Condition…=</varname> and
<varname index="false">Assert…=</varname> settings. Before the unit is started, systemd will verify
that the specified conditions are true. If not, the starting of the unit will be (mostly silently)
skipped. Failing conditions will not result in the unit being moved into the <literal>failed</literal>
state. The conditions are checked at the time the queued start job is to be executed. The ordering
dependencies are still respected, so other units are still pulled in and ordered as if this unit was
successfully activated. Use condition expressions in order to skip units that do not apply to the local
system, for example because the kernel or runtime environment doesn't require their functionality.
</para>
<para>If multiple conditions are specified, the unit will be executed if all of them apply (i.e. a
logical AND is applied). Condition checks can use a pipe symbol (<literal>|</literal>) after the equals
sign (<literal>Condition…=|…</literal>), which causes the condition to become a
<emphasis>triggering</emphasis> condition. If at least one triggering condition is defined for a unit,
then the unit will be started if at least one of the triggering conditions of the unit applies and all
of the regular (i.e. non-triggering) conditions apply. If you prefix an argument with the pipe symbol
and an exclamation mark, the pipe symbol must be passed first, the exclamation second. If any of these
options is assigned the empty string, the list of conditions is reset completely, all previous
condition settings (of any kind) will have no effect.</para>
sign (<literal>Condition…=|…</literal>), which causes the condition becomes a triggering condition. If
at least one triggering condition is defined for a unit, then the unit will be executed if at least one
of the triggering conditions apply and all of the non-triggering conditions. If you prefix an argument
with the pipe symbol and an exclamation mark, the pipe symbol must be passed first, the exclamation
second. If any of these options is assigned the empty string, the list of conditions is reset
completely, all previous condition settings (of any kind) will have no effect.</para>
<para>The <varname>AssertArchitecture=</varname>, <varname>AssertVirtualization=</varname>, … options
are similar to conditions but cause the start job to fail (instead of being skipped). The failed check
is logged. Units with failed conditions are considered to be in a clean state and will be garbage
provide a similar mechanism that causes the job to fail (instead of being skipped). The failed check is
logged. Units with failed conditions are considered to be in a clean state and will be garbage
collected if they are not referenced. This means that when queried, the condition failure may or may
not show up in the state of the unit.</para>

View File

@ -71,9 +71,11 @@ static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) {
return -ENODATA;
c += strspn(c, DIGITS);
if (*c == '-' && !STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-"))
if (*c == '-') {
/* A connector DRM device, let's ignore all but LVDS and eDP! */
return -EOPNOTSUPP;
if (!STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-"))
return -EOPNOTSUPP;
}
} else if (streq(subsystem, "pci") &&
sd_device_get_sysattr_value(parent, "class", &value) >= 0) {
@ -135,14 +137,19 @@ static int validate_device(sd_device *device) {
assert(device);
/* Verify whether we should actually care for a specific backlight device. For backlight devices
* there might be multiple ways to access the same control: "firmware" (i.e. ACPI), "platform"
* (i.e. via the machine's EC) and "raw" (via the graphics card). In general we should prefer
* "firmware" (i.e. ACPI) or "platform" access over "raw" access, in order not to confuse the
* BIOS/EC, and compatibility with possible low-level hotkey handling of screen brightness. The
* kernel will already make sure to expose only one of "firmware" and "platform" for the same
* device to userspace. However, we still need to make sure that we use "raw" only if no
* "firmware" or "platform" device for the same device exists. */
/* Verify whether we should actually care for a specific
* backlight device. For backlight devices there might be
* multiple ways to access the same control: "firmware"
* (i.e. ACPI), "platform" (i.e. via the machine's EC) and
* "raw" (via the graphics card). In general we should prefer
* "firmware" (i.e. ACPI) or "platform" access over "raw"
* access, in order not to confuse the BIOS/EC, and
* compatibility with possible low-level hotkey handling of
* screen brightness. The kernel will already make sure to
* expose only one of "firmware" and "platform" for the same
* device to userspace. However, we still need to make sure
* that we use "raw" only if no "firmware" or "platform"
* device for the same device exists. */
r = sd_device_get_subsystem(device, &subsystem);
if (r < 0)
@ -187,12 +194,13 @@ static int validate_device(sd_device *device) {
!STR_IN_SET(v, "platform", "firmware"))
continue;
/* OK, so there's another backlight device, and it's a platform or firmware device.
* Let's see if we can verify it belongs to the same device as ours. */
/* OK, so there's another backlight device, and it's a
* platform or firmware device, so, let's see if we
* can verify it belongs to the same device as ours. */
if (find_pci_or_platform_parent(other, &other_parent) < 0)
continue;
if (same_device(parent, other_parent) > 0) {
if (same_device(parent, other_parent)) {
const char *device_sysname = NULL, *other_sysname = NULL;
/* Both have the same PCI parent, that means we are out. */
@ -249,6 +257,11 @@ static int get_max_brightness(sd_device *device, unsigned *ret) {
return 0;
}
/* Some systems turn the backlight all the way off at the lowest levels.
* clamp_brightness clamps the saved brightness to at least 1 or 5% of
* max_brightness in case of 'backlight' subsystem. This avoids preserving
* an unreadably dim screen, which would otherwise force the user to
* disable state restoration. */
static int clamp_brightness(sd_device *device, bool saved, unsigned max_brightness, unsigned *brightness) {
unsigned new_brightness, min_brightness;
const char *subsystem;
@ -257,11 +270,6 @@ static int clamp_brightness(sd_device *device, bool saved, unsigned max_brightne
assert(device);
assert(brightness);
/* Some systems turn the backlight all the way off at the lowest levels. This clamps the saved
* brightness to at least 1 or 5% of max_brightness in case of 'backlight' subsystem. This
* avoids preserving an unreadably dim screen, which would otherwise force the user to disable
* state restoration. */
r = sd_device_get_subsystem(device, &subsystem);
if (r < 0)
return log_device_warning_errno(device, r, "Failed to get device subsystem: %m");
@ -380,9 +388,6 @@ static int run(int argc, char *argv[]) {
if (argc != 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires two arguments.");
if (!STR_IN_SET(argv[1], "load", "save"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", argv[1]);
umask(0022);
r = mkdir_p("/var/lib/systemd/backlight", 0755);
@ -404,8 +409,9 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
/* If max_brightness is 0, then there is no actual backlight device. This happens on desktops
* with Asus mainboards that load the eeepc-wmi module. */
/* If max_brightness is 0, then there is no actual backlight
* device. This happens on desktops with Asus mainboards
* that load the eeepc-wmi module. */
if (get_max_brightness(device, &max_brightness) < 0)
return 0;
@ -426,11 +432,14 @@ static int run(int argc, char *argv[]) {
} else
saved = strjoina("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname);
/* If there are multiple conflicting backlight devices, then their probing at boot-time might
* happen in any order. This means the validity checking of the device then is not reliable,
* since it might not see other devices conflicting with a specific backlight. To deal with
* this, we will actively delete backlight state files at shutdown (where device probing should
* be complete), so that the validity check at boot time doesn't have to be reliable. */
/* If there are multiple conflicting backlight devices, then
* their probing at boot-time might happen in any order. This
* means the validity checking of the device then is not
* reliable, since it might not see other devices conflicting
* with a specific backlight. To deal with this, we will
* actively delete backlight state files at shutdown (where
* device probing should be complete), so that the validity
* check at boot time doesn't have to be reliable. */
if (streq(argv[1], "load")) {
_cleanup_free_ char *value = NULL;
@ -494,7 +503,7 @@ static int run(int argc, char *argv[]) {
return log_device_error_errno(device, r, "Failed to write %s: %m", saved);
} else
assert_not_reached("Unknown verb.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", argv[1]);
return 0;
}

View File

@ -5648,14 +5648,15 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
for (size_t i = 0; i < c->n_mount_images; i++) {
MountOptions *o;
fprintf(f, "%sMountImages: %s%s:%s", prefix,
fprintf(f, "%sMountImages: %s%s:%s%s", prefix,
c->mount_images[i].ignore_enoent ? "-": "",
c->mount_images[i].source,
c->mount_images[i].destination);
c->mount_images[i].destination,
LIST_IS_EMPTY(c->mount_images[i].mount_options) ? "": ":");
LIST_FOREACH(mount_options, o, c->mount_images[i].mount_options)
fprintf(f, ":%s:%s",
fprintf(f, "%s:%s",
partition_designator_to_string(o->partition_designator),
strempty(o->options));
o->options);
fprintf(f, "\n");
}
}

View File

@ -419,6 +419,7 @@ static int parse_options(const char *options) {
static char* disk_description(const char *path) {
static const char name_fields[] =
"ID_PART_ENTRY_NAME\0"
"DM_NAME\0"
"ID_MODEL_FROM_DATABASE\0"
"ID_MODEL\0";
@ -426,7 +427,6 @@ static char* disk_description(const char *path) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
const char *i, *name;
struct stat st;
int r;
assert(path);
@ -436,27 +436,9 @@ static char* disk_description(const char *path) {
if (!S_ISBLK(st.st_mode))
return NULL;
if (sd_device_new_from_stat_rdev(&device, &st) < 0)
if (sd_device_new_from_devnum(&device, 'b', st.st_rdev) < 0)
return NULL;
if (sd_device_get_property_value(device, "ID_PART_ENTRY_NAME", &name) >= 0) {
_cleanup_free_ char *unescaped = NULL;
/* ID_PART_ENTRY_NAME uses \x style escaping, using libblkid's blkid_encode_string(). Let's
* reverse this here to make the string more human friendly in case people embed spaces or
* other weird stuff. */
r = cunescape(name, UNESCAPE_RELAX, &unescaped);
if (r < 0) {
log_debug_errno(r, "Failed to unescape ID_PART_ENTRY_NAME, skipping device: %m");
return NULL;
}
if (!isempty(unescaped) && !string_has_cc(unescaped, NULL))
return TAKE_PTR(unescaped);
}
/* These need no unescaping. */
NULSTR_FOREACH(i, name_fields)
if (sd_device_get_property_value(device, i, &name) >= 0 &&
!isempty(name))

View File

@ -287,7 +287,7 @@ static int run(int argc, char *argv[]) {
"%s is not a block device.",
device);
r = sd_device_new_from_stat_rdev(&dev, &st);
r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev);
if (r < 0)
return log_error_errno(r, "Failed to detect device %s: %m", device);

View File

@ -2711,7 +2711,7 @@ static int home_get_image_path_seat(Home *h, char **ret) {
if (!S_ISBLK(st.st_mode))
return -ENOTBLK;
r = sd_device_new_from_stat_rdev(&d, &st);
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return r;

View File

@ -929,7 +929,7 @@ static int umount_by_device(sd_bus *bus, const char *what) {
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK),
"Not a block device: %s", what);
r = sd_device_new_from_stat_rdev(&d, &st);
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
@ -1270,7 +1270,7 @@ static int discover_loop_backing_file(void) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid file type: %s", loop_dev);
r = sd_device_new_from_stat_rdev(&d, &st);
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");
@ -1314,7 +1314,7 @@ static int discover_device(void) {
"Invalid file type: %s",
arg_mount_what);
r = sd_device_new_from_stat_rdev(&d, &st);
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return log_error_errno(r, "Failed to get device from device number: %m");

View File

@ -894,8 +894,6 @@ Manager* manager_free(Manager *m) {
m->routes = set_free(m->routes);
m->routes_foreign = set_free(m->routes_foreign);
m->nexthops = set_free(m->nexthops);
m->nexthops_foreign = set_free(m->nexthops_foreign);
m->nexthops_by_id = hashmap_free(m->nexthops_by_id);
sd_event_source_unref(m->speed_meter_event_source);

View File

@ -64,10 +64,6 @@ struct Manager {
/* Manage nexthops by id. */
Hashmap *nexthops_by_id;
/* Manager stores nexthops without RTA_OIF attribute. */
Set *nexthops;
Set *nexthops_foreign;
/* Manager stores routes without RTA_OIF attribute. */
Set *routes;
Set *routes_foreign;

View File

@ -190,7 +190,6 @@ NextHop.Id, config_parse_nexthop_id,
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
NextHop.Family, config_parse_nexthop_family, 0, 0
NextHop.OnLink, config_parse_nexthop_onlink, 0, 0
NextHop.Blackhole, config_parse_nexthop_blackhole, 0, 0
DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
DHCPv4.UseDNS, config_parse_dhcp_use_dns, 0, 0
DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns)

View File

@ -33,14 +33,6 @@ NextHop *nexthop_free(NextHop *nexthop) {
hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
}
if (nexthop->manager) {
set_remove(nexthop->manager->nexthops, nexthop);
set_remove(nexthop->manager->nexthops_foreign, nexthop);
if (nexthop->id > 0)
hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
}
return mfree(nexthop);
}
@ -103,7 +95,6 @@ static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) {
assert(nexthop);
siphash24_compress(&nexthop->id, sizeof(nexthop->id), state);
siphash24_compress(&nexthop->blackhole, sizeof(nexthop->blackhole), state);
siphash24_compress(&nexthop->family, sizeof(nexthop->family), state);
switch (nexthop->family) {
@ -125,10 +116,6 @@ static int nexthop_compare_func(const NextHop *a, const NextHop *b) {
if (r != 0)
return r;
r = CMP(a->blackhole, b->blackhole);
if (r != 0)
return r;
r = CMP(a->family, b->family);
if (r != 0)
return r;
@ -146,18 +133,6 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
nexthop_compare_func,
nexthop_free);
static void nexthop_copy(NextHop *dest, const NextHop *src) {
assert(dest);
assert(src);
/* This only copies entries used in the above hash and compare functions. */
dest->id = src->id;
dest->blackhole = src->blackhole;
dest->family = src->family;
dest->gw = src->gw;
}
int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
NextHop *nh;
@ -175,20 +150,20 @@ int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
return 0;
}
static int nexthop_get(Manager *manager, Link *link, const NextHop *in, NextHop **ret) {
static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) {
NextHop *existing;
assert(manager || link);
assert(link);
assert(in);
existing = set_get(link ? link->nexthops : manager->nexthops, in);
existing = set_get(link->nexthops, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
existing = set_get(link ? link->nexthops_foreign : manager->nexthops_foreign, in);
existing = set_get(link->nexthops_foreign, in);
if (existing) {
if (ret)
*ret = existing;
@ -198,11 +173,11 @@ static int nexthop_get(Manager *manager, Link *link, const NextHop *in, NextHop
return -ENOENT;
}
static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, const NextHop *in, NextHop **ret) {
static int nexthop_add_internal(Link *link, Set **nexthops, const NextHop *in, NextHop **ret) {
_cleanup_(nexthop_freep) NextHop *nexthop = NULL;
int r;
assert(manager || link);
assert(link);
assert(nexthops);
assert(in);
@ -210,7 +185,9 @@ static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, co
if (r < 0)
return r;
nexthop_copy(nexthop, in);
nexthop->id = in->id;
nexthop->family = in->family;
nexthop->gw = in->gw;
r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop);
if (r < 0)
@ -219,7 +196,6 @@ static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, co
return -EEXIST;
nexthop->link = link;
nexthop->manager = manager;
if (ret)
*ret = nexthop;
@ -228,9 +204,8 @@ static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, co
return 0;
}
static int nexthop_add_foreign(Manager *manager, Link *link, const NextHop *in, NextHop **ret) {
assert(manager || link);
return nexthop_add_internal(manager, link, link ? &link->nexthops_foreign : &manager->nexthops_foreign, in, ret);
static int nexthop_add_foreign(Link *link, const NextHop *in, NextHop **ret) {
return nexthop_add_internal(link, &link->nexthops_foreign, in, ret);
}
static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
@ -238,30 +213,20 @@ static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
NextHop *nexthop;
int r;
assert(link);
assert(in);
if (in->blackhole)
r = nexthop_get(link->manager, NULL, in, &nexthop);
else
r = nexthop_get(NULL, link, in, &nexthop);
r = nexthop_get(link, in, &nexthop);
if (r == -ENOENT) {
/* NextHop does not exist, create a new one */
r = nexthop_add_internal(link->manager,
in->blackhole ? NULL : link,
in->blackhole ? &link->manager->nexthops : &link->nexthops,
in, &nexthop);
r = nexthop_add_internal(link, &link->nexthops, in, &nexthop);
if (r < 0)
return r;
is_new = true;
} else if (r == 0) {
/* Take over a foreign nexthop */
r = set_ensure_put(in->blackhole ? &link->manager->nexthops : &link->nexthops,
&nexthop_hash_ops, nexthop);
r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop);
if (r < 0)
return r;
set_remove(in->blackhole ? link->manager->nexthops_foreign : link->nexthops_foreign, nexthop);
set_remove(link->nexthops_foreign, nexthop);
} else if (r == 1) {
/* NextHop exists, do nothing */
;
@ -273,13 +238,11 @@ static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
return is_new;
}
static int nexthop_update(Manager *manager, Link *link, NextHop *nexthop, const NextHop *in) {
Set *nexthops;
static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
int r;
/* link may be NULL. */
assert(manager);
assert(link);
assert(link->manager);
assert(nexthop);
assert(in);
assert(in->id > 0);
@ -292,21 +255,19 @@ static int nexthop_update(Manager *manager, Link *link, NextHop *nexthop, const
return -EINVAL;
}
nexthops = link ? link->nexthops : manager->nexthops;
nexthop = set_remove(nexthops, nexthop);
nexthop = set_remove(link->nexthops, nexthop);
if (!nexthop)
return -ENOENT;
nexthop->id = in->id;
r = set_put(nexthops, nexthop);
r = set_put(link->nexthops, nexthop);
if (r <= 0) {
int k;
/* On failure, revert the change. */
nexthop->id = 0;
k = set_put(nexthops, nexthop);
k = set_put(link->nexthops, nexthop);
if (k <= 0) {
nexthop_free(nexthop);
return k < 0 ? k : -EEXIST;
@ -316,14 +277,13 @@ static int nexthop_update(Manager *manager, Link *link, NextHop *nexthop, const
}
set_manager:
return hashmap_ensure_put(&manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop);
return hashmap_ensure_put(&link->manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop);
}
static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *str, const Link *link) {
assert(nexthop);
assert(str);
/* link may be NULL. */
assert(link);
if (DEBUG_LOGGING) {
_cleanup_free_ char *gw = NULL;
@ -331,11 +291,11 @@ static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *s
(void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw);
if (nexthop->id == id)
log_link_debug(link, "%s nexthop: id: %"PRIu32", gw: %s, blackhole: %s",
str, nexthop->id, strna(gw), yes_no(nexthop->blackhole));
log_link_debug(link, "%s nexthop: id: %"PRIu32", gw: %s",
str, nexthop->id, strna(gw));
else
log_link_debug(link, "%s nexthop: id: %"PRIu32"→%"PRIu32", gw: %s, blackhole: %s",
str, nexthop->id, id, strna(gw), yes_no(nexthop->blackhole));
log_link_debug(link, "%s nexthop: id: %"PRIu32"→%"PRIu32", gw: %s",
str, nexthop->id, id, strna(gw));
}
}
@ -393,25 +353,19 @@ static int nexthop_configure(const NextHop *nexthop, Link *link) {
return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
}
if (nexthop->blackhole) {
r = sd_netlink_message_append_flag(req, NHA_BLACKHOLE);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_BLACKHOLE attribute: %m");
} else {
r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m");
r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m");
if (in_addr_is_set(nexthop->family, &nexthop->gw)) {
r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw);
if (in_addr_is_set(nexthop->family, &nexthop->gw)) {
r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m");
if (nexthop->onlink > 0) {
r = sd_rtnl_message_nexthop_set_flags(req, RTNH_F_ONLINK);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m");
if (nexthop->onlink > 0) {
r = sd_rtnl_message_nexthop_set_flags(req, RTNH_F_ONLINK);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set RTNH_F_ONLINK flag: %m");
}
return log_link_error_errno(link, r, "Failed to set RTNH_F_ONLINK flag: %m");
}
}
@ -477,7 +431,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
NextHop *nexthop = NULL;
uint32_t ifindex;
uint16_t type;
Link *link = NULL;
Link *link;
int r;
assert(rtnl);
@ -502,21 +456,22 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
}
r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex);
if (r < 0 && r != -ENODATA) {
if (r == -ENODATA) {
log_warning_errno(r, "rtnl: received nexthop message without NHA_OIF attribute, ignoring: %m");
return 0;
} else if (r < 0) {
log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m");
return 0;
} else if (r >= 0) {
if (ifindex <= 0) {
log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
return 0;
}
} else if (ifindex <= 0) {
log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
return 0;
}
r = link_get(m, ifindex, &link);
if (r < 0 || !link) {
if (!m->enumerating)
log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
return 0;
}
r = link_get(m, ifindex, &link);
if (r < 0 || !link) {
if (!m->enumerating)
log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
return 0;
}
r = nexthop_new(&tmp);
@ -536,13 +491,6 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
return 0;
}
r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m");
return 0;
}
tmp->blackhole = r;
r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id);
if (r == -ENODATA) {
log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m");
@ -555,12 +503,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
return 0;
}
/* All blackhole nexthops are managed by Manager. Note that the linux kernel does not set
* NHA_OID attribute when NHA_BLACKHOLE is set. Just for safety. */
if (tmp->blackhole)
link = NULL;
r = nexthop_get(m, link, tmp, &nexthop);
r = nexthop_get(link, tmp, &nexthop);
if (r < 0) {
uint32_t id;
@ -569,7 +512,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
id = tmp->id;
tmp->id = 0;
(void) nexthop_get(m, link, tmp, &nexthop);
(void) nexthop_get(link, tmp, &nexthop);
tmp->id = id;
}
@ -580,14 +523,14 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
log_nexthop_debug(nexthop, tmp->id, "Received remembered", link);
else {
log_nexthop_debug(tmp, tmp->id, "Remembering foreign", link);
r = nexthop_add_foreign(m, link, tmp, &nexthop);
r = nexthop_add_foreign(link, tmp, &nexthop);
if (r < 0) {
log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m");
return 0;
}
}
r = nexthop_update(m, link, nexthop, tmp);
r = nexthop_update(link, nexthop, tmp);
if (r < 0) {
log_link_warning_errno(link, r, "Could not update nexthop, ignoring: %m");
return 0;
@ -613,12 +556,6 @@ static int nexthop_section_verify(NextHop *nh) {
/* When no Gateway= is specified, assume IPv4. */
nh->family = AF_INET;
if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: blackhole nexthop cannot have gateway address. "
"Ignoring [NextHop] section from line %u.",
nh->section->filename, nh->section->line);
if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) &&
ordered_hashmap_isempty(nh->network->addresses_by_section)) {
/* If no address is configured, in most cases the gateway cannot be reachable.
@ -847,42 +784,3 @@ int config_parse_nexthop_onlink(
TAKE_PTR(n);
return 0;
}
int config_parse_nexthop_blackhole(
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) {
_cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = nexthop_new_static(network, filename, section_line, &n);
if (r < 0)
return log_oom();
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
return 0;
}
n->blackhole = r;
TAKE_PTR(n);
return 0;
}

View File

@ -20,13 +20,11 @@ typedef struct NextHop {
Network *network;
NetworkConfigSection *section;
Manager *manager;
Link *link;
unsigned char protocol;
uint32_t id;
bool blackhole;
int family;
union in_addr_union gw;
int onlink;
@ -45,4 +43,3 @@ CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_id);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_family);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_onlink);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_blackhole);

View File

@ -451,18 +451,34 @@ static int route_get(const Manager *manager, const Link *link, const Route *in,
assert(manager || link);
assert(in);
existing = set_get(link ? link->routes : manager->routes, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
if (link) {
existing = set_get(link->routes, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
existing = set_get(link ? link->routes_foreign : manager->routes_foreign, in);
if (existing) {
if (ret)
*ret = existing;
return 0;
existing = set_get(link->routes_foreign, in);
if (existing) {
if (ret)
*ret = existing;
return 0;
}
} else {
existing = set_get(manager->routes, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
existing = set_get(manager->routes_foreign, in);
if (existing) {
if (ret)
*ret = existing;
return 0;
}
}
return -ENOENT;
@ -472,8 +488,6 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, c
assert(dest);
assert(src);
/* This only copies entries used by the above hash and compare functions. */
dest->family = src->family;
dest->src = src->src;
dest->src_prefixlen = src->src_prefixlen;
@ -482,10 +496,7 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, c
dest->prefsrc = src->prefsrc;
dest->scope = src->scope;
dest->protocol = src->protocol;
if (nh && nh->blackhole)
dest->type = RTN_BLACKHOLE;
else
dest->type = src->type;
dest->type = src->type;
dest->tos = src->tos;
dest->priority = src->priority;
dest->table = src->table;
@ -582,11 +593,19 @@ static int route_add(Manager *manager, Link *link, const Route *in, const Multip
is_new = true;
} else if (r == 0) {
/* Take over a foreign route */
r = set_ensure_put(link ? &link->routes : &manager->routes, &route_hash_ops, route);
if (r < 0)
return r;
if (link) {
r = set_ensure_put(&link->routes, &route_hash_ops, route);
if (r < 0)
return r;
set_remove(link ? link->routes_foreign : manager->routes_foreign, route);
set_remove(link->routes_foreign, route);
} else {
r = set_ensure_put(&manager->routes, &route_hash_ops, route);
if (r < 0)
return r;
set_remove(manager->routes_foreign, route);
}
} else if (r == 1) {
/* Route exists, do nothing */
;
@ -962,8 +981,8 @@ static int route_add_and_setup_timer(Link *link, const Route *route, const Multi
(void) manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh);
if (route_type_is_reject(route) || (nh && nh->blackhole))
k = route_add(link->manager, NULL, route, NULL, nh, &nr);
if (route_type_is_reject(route))
k = route_add(link->manager, NULL, route, NULL, NULL, &nr);
else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex)
k = route_add(NULL, link, route, m, nh, &nr);
else {

View File

@ -532,7 +532,7 @@ int dissect_image(
if (!S_ISBLK(st.st_mode))
return -ENOTBLK;
r = sd_device_new_from_stat_rdev(&d, &st);
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return r;

View File

@ -136,7 +136,7 @@ static int device_new_from_dev_path(const char *devlink, sd_device **ret_device)
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK),
"%s does not point to a block device: %m", devlink);
r = sd_device_new_from_stat_rdev(ret_device, &st);
r = sd_device_new_from_devnum(ret_device, 'b', st.st_rdev);
if (r < 0)
return log_error_errno(r, "Failed to initialize device from %s: %m", devlink);

View File

@ -353,7 +353,6 @@ Id=
Gateway=
Family=
OnLink=
Blackhole=
[QDisc]
Parent=
Handle=

View File

@ -28,27 +28,9 @@ Id=5
Gateway=192.168.10.1
OnLink=yes
[NextHop]
Id=6
Family=ipv4
Blackhole=yes
[NextHop]
Id=7
Family=ipv6
Blackhole=yes
[NextHop]
Gateway=192.168.5.2
[NextHop]
Family=ipv4
Blackhole=yes
[NextHop]
Family=ipv6
Blackhole=yes
[Route]
NextHop=1
Destination=10.10.10.10
@ -64,11 +46,3 @@ Destination=2001:1234:5:8f62::1
[Route]
NextHop=5
Destination=10.10.10.12
[Route]
NextHop=6
Destination=10.10.10.13
[Route]
NextHop=7
Destination=2001:1234:5:8f62::2

View File

@ -3,6 +3,7 @@ Name=wg99
Kind=wireguard
[WireGuard]
PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
ListenPort=51820
FwMark=1234

View File

@ -1,5 +0,0 @@
[WireGuardPeer]
PublicKey=TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=
PresharedKey=it7nd33chCT/tKT2ZZWfYyp43Zs+6oif72hexnSNMqA=
AllowedIPs=192.168.124.2

View File

@ -1,5 +0,0 @@
[WireGuardPeer]
PublicKey=9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=
PresharedKey=6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
AllowedIPs=192.168.124.3

View File

@ -1,2 +0,0 @@
[WireGuard]
PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=

View File

@ -1,5 +1,2 @@
[Match]
Name=wg99
[Network]
Address=192.168.124.1/24

View File

@ -404,13 +404,6 @@ def remove_routes(routes):
for route_type, addr in routes:
call('ip route del', route_type, addr, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def remove_blackhole_nexthops():
ret = run('ip nexthop show dev lo', stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
if ret.returncode == 0:
for line in ret.stdout.rstrip().splitlines():
id = line.split()[1]
call(f'ip nexthop del id {id}')
def remove_l2tp_tunnels(tunnel_ids):
output = check_output('ip l2tp show tunnel')
for tid in tunnel_ids:
@ -1225,11 +1218,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',
'25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network')
start_networkd()
self.wait_online(['wg99:routable', 'wg98:routable', 'wg97:carrier'])
output = check_output('ip -4 address show dev wg99')
print(output)
self.assertIn('inet 192.168.124.1/24 scope global wg99', output)
self.wait_online(['wg99:carrier', 'wg98:routable', 'wg97:carrier'])
output = check_output('ip -4 address show dev wg98')
print(output)
@ -1243,39 +1232,29 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
call('wg')
output = check_output('wg show wg99 listen-port')
self.assertEqual(output, '51820')
self.assertRegex(output, '51820')
output = check_output('wg show wg99 fwmark')
self.assertEqual(output, '0x4d2')
output = check_output('wg show wg99 private-key')
self.assertEqual(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=')
self.assertRegex(output, '0x4d2')
output = check_output('wg show wg99 allowed-ips')
self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t192.168.124.3/32', output)
self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\t192.168.124.2/32', output)
self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tfdbc:bae2:7871:e1fe:793:8636::/96 fdbc:bae2:7871:500:e1fe:793:8636:dad1/128', output)
self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.26.0/24 fd31:bf08:57cb::/48', output)
self.assertRegex(output, r'RDf\+LSpeEre7YEIKaxg\+wbpsNV7du\+ktR99uBEtIiCA=\t192.168.26.0/24 fd31:bf08:57cb::/48')
self.assertRegex(output, r'lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tfdbc:bae2:7871:e1fe:793:8636::/96 fdbc:bae2:7871:500:e1fe:793:8636:dad1/128')
output = check_output('wg show wg99 persistent-keepalive')
self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\toff', output)
self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\toff', output)
self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\toff', output)
self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t20', output)
self.assertRegex(output, r'RDf\+LSpeEre7YEIKaxg\+wbpsNV7du\+ktR99uBEtIiCA=\t20')
output = check_output('wg show wg99 endpoints')
self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t(none)', output)
self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\t(none)', output)
self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\t(none)', output)
self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.27.3:51820', output)
self.assertRegex(output, r'RDf\+LSpeEre7YEIKaxg\+wbpsNV7du\+ktR99uBEtIiCA=\t192.168.27.3:51820')
output = check_output('wg show wg99 private-key')
self.assertRegex(output, r'EEGlnEPYJV//kbvvIqxKkQwOiS\+UENyPncC4bF46ong=')
output = check_output('wg show wg99 preshared-keys')
self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=', output)
self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\tit7nd33chCT/tKT2ZZWfYyp43Zs+6oif72hexnSNMqA=', output)
self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tcPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=', output)
self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\tIIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=', output)
self.assertRegex(output, r'RDf\+LSpeEre7YEIKaxg\+wbpsNV7du\+ktR99uBEtIiCA= IIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=')
self.assertRegex(output, r'lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc= cPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=')
output = check_output('wg show wg98 private-key')
self.assertEqual(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=')
self.assertRegex(output, r'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr\+WHtZLZ90FU=')
output = check_output('wg show wg97 listen-port')
self.assertEqual(output, '51821')
self.assertRegex(output, '51821')
output = check_output('wg show wg97 fwmark')
self.assertEqual(output, '0x4d3')
self.assertRegex(output, '0x4d3')
def test_geneve(self):
copy_unit_to_networkd_unit_path('25-geneve.netdev', 'netdev-link-local-addressing-yes.network')
@ -1838,14 +1817,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
routes = [['blackhole', '202.54.1.2'], ['unreachable', '202.54.1.3'], ['prohibit', '202.54.1.4']]
def setUp(self):
remove_blackhole_nexthops()
remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
remove_routes(self.routes)
remove_links(self.links)
stop_networkd(show_logs=False)
def tearDown(self):
remove_blackhole_nexthops()
remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
remove_routes(self.routes)
remove_links(self.links)
@ -2838,12 +2815,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99')
# kernel manages blackhole nexthops on lo
output = check_output('ip nexthop list dev lo')
print(output)
self.assertIn('id 6 blackhole', output)
self.assertIn('id 7 blackhole', output)
output = check_output('ip route show dev veth99 10.10.10.10')
print(output)
self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
@ -2860,14 +2831,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
print(output)
self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
output = check_output('ip route show 10.10.10.13')
print(output)
self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
output = check_output('ip -6 route show 2001:1234:5:8f62::2')
print(output)
self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output)
def test_qdisc(self):
copy_unit_to_networkd_unit_path('25-qdisc-clsact-and-htb.network', '12-dummy.netdev',
'25-qdisc-ingress-netem-compat.network', '11-dummy.netdev')