link-config.c revision 32bc8adcd836baff68e4d0f53b9a382f358cccf8
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering/***
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering This file is part of systemd.
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering systemd is free software; you can redistribute it and/or modify it
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering under the terms of the GNU Lesser General Public License as published by
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering the Free Software Foundation; either version 2.1 of the License, or
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering (at your option) any later version.
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering systemd is distributed in the hope that it will be useful, but
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering Lesser General Public License for more details.
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering You should have received a copy of the GNU Lesser General Public License
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering***/
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include <netinet/ether.h>
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include <linux/netdevice.h>
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering#include "sd-id128.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "missing.h"
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering#include "link-config.h"
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering#include "ethtool-util.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "libudev-private.h"
3e348b8a6af4bc46562efa44500fe2fa44320aa1Ronny Chevalier#include "sd-rtnl.h"
3e348b8a6af4bc46562efa44500fe2fa44320aa1Ronny Chevalier#include "util.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "log.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "strv.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "path-util.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "conf-parser.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "conf-files.h"
dfd9cf7f0b257d38f5527989dd9315e767fbe41bZbigniew Jędrzejewski-Szmek#include "fileio.h"
dfd9cf7f0b257d38f5527989dd9315e767fbe41bZbigniew Jędrzejewski-Szmek#include "hashmap.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "rtnl-util.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "network-internal.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#include "siphash24.h"
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poetteringstruct link_config_ctx {
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering LIST_HEAD(link_config, links);
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering int ethtool_fd;
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering bool enable_name_policy;
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering sd_rtnl *rtnl;
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering usec_t link_dirs_ts_usec;
0c0cdb06c139b52ff103287f6909b3daa5b2dc54Ronny Chevalier};
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poetteringstatic const char* const link_dirs[] = {
0c0cdb06c139b52ff103287f6909b3daa5b2dc54Ronny Chevalier "/etc/systemd/network",
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering "/run/systemd/network",
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering "/usr/lib/systemd/network",
0c0cdb06c139b52ff103287f6909b3daa5b2dc54Ronny Chevalier#ifdef HAVE_SPLIT_USR
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering "/lib/systemd/network",
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#endif
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering NULL};
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart PoetteringDEFINE_TRIVIAL_CLEANUP_FUNC(link_config_ctx*, link_config_ctx_free);
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering#define _cleanup_link_config_ctx_free_ _cleanup_(link_config_ctx_freep)
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poetteringint link_config_ctx_new(link_config_ctx **ret) {
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering _cleanup_link_config_ctx_free_ link_config_ctx *ctx = NULL;
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering
2678031a179a9b91fc799f8ef951a548c66c4b49Lennart Poettering if (!ret)
dfd9cf7f0b257d38f5527989dd9315e767fbe41bZbigniew Jędrzejewski-Szmek return -EINVAL;
dfd9cf7f0b257d38f5527989dd9315e767fbe41bZbigniew Jędrzejewski-Szmek
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering ctx = new0(link_config_ctx, 1);
d0767ffd08bbb5c069e266710eb0462315e47e6dLennart Poettering if (!ctx)
return -ENOMEM;
LIST_HEAD_INIT(ctx->links);
ctx->ethtool_fd = -1;
ctx->enable_name_policy = true;
*ret = ctx;
ctx = NULL;
return 0;
}
static void link_configs_free(link_config_ctx *ctx) {
link_config *link, *link_next;
if (!ctx)
return;
LIST_FOREACH_SAFE(links, link, link_next, ctx->links) {
free(link->filename);
free(link->name);
free(link->match_path);
free(link->match_driver);
free(link->match_type);
free(link->description);
free(link->alias);
free(link->name_policy);
free(link);
}
}
void link_config_ctx_free(link_config_ctx *ctx) {
if (!ctx)
return;
safe_close(ctx->ethtool_fd);
sd_rtnl_unref(ctx->rtnl);
link_configs_free(ctx);
free(ctx);
return;
}
static int load_link(link_config_ctx *ctx, const char *filename) {
_cleanup_free_ link_config *link = NULL;
_cleanup_fclose_ FILE *file = NULL;
int r;
assert(ctx);
assert(filename);
file = fopen(filename, "re");
if (!file) {
if (errno == ENOENT)
return 0;
else
return -errno;
}
if (null_or_empty_fd(fileno(file))) {
log_debug("Skipping empty file: %s", filename);
return 0;
}
link = new0(link_config, 1);
if (!link)
return log_oom();
link->mac_policy = _MACPOLICY_INVALID;
link->wol = _WOL_INVALID;
link->duplex = _DUP_INVALID;
r = config_parse(NULL, filename, file,
"Match\0Link\0Ethernet\0",
config_item_perf_lookup, link_config_gperf_lookup,
false, false, true, link);
if (r < 0)
return r;
else
log_debug("Parsed configuration file %s", filename);
link->filename = strdup(filename);
LIST_PREPEND(links, ctx->links, link);
link = NULL;
return 0;
}
static bool enable_name_policy(void) {
_cleanup_free_ char *line = NULL;
const char *word, *state;
int r;
size_t l;
r = proc_cmdline(&line);
if (r < 0) {
log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
return true;
}
FOREACH_WORD_QUOTED(word, l, line, state)
if (strneq(word, "net.ifnames=0", l))
return false;
return true;
}
int link_config_load(link_config_ctx *ctx) {
int r;
_cleanup_strv_free_ char **files;
char **f;
link_configs_free(ctx);
if (!enable_name_policy()) {
ctx->enable_name_policy = false;
log_info("Network interface NamePolicy= disabled on kernel command line, ignoring.");
}
/* update timestamp */
paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, true);
r = conf_files_list_strv(&files, ".link", NULL, link_dirs);
if (r < 0)
return log_error_errno(r, "failed to enumerate link files: %m");
STRV_FOREACH_BACKWARDS(f, files) {
r = load_link(ctx, *f);
if (r < 0)
return r;
}
return 0;
}
bool link_config_should_reload(link_config_ctx *ctx) {
return paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, false);
}
int link_config_get(link_config_ctx *ctx, struct udev_device *device,
link_config **ret) {
link_config *link;
LIST_FOREACH(links, link, ctx->links) {
const char* attr_value;
attr_value = udev_device_get_sysattr_value(device, "address");
if (net_match_config(link->match_mac, link->match_path, link->match_driver,
link->match_type, link->match_name, link->match_host,
link->match_virt, link->match_kernel, link->match_arch,
attr_value ? ether_aton(attr_value) : NULL,
udev_device_get_property_value(device, "ID_PATH"),
udev_device_get_driver(udev_device_get_parent(device)),
udev_device_get_property_value(device, "ID_NET_DRIVER"),
udev_device_get_devtype(device),
udev_device_get_sysname(device))) {
if (link->match_name) {
unsigned char name_assign_type = NET_NAME_UNKNOWN;
attr_value = udev_device_get_sysattr_value(device, "name_assign_type");
if (attr_value)
(void)safe_atou8(attr_value, &name_assign_type);
if (name_assign_type == NET_NAME_ENUM) {
log_warning("Config file %s applies to device based on potentially unstable interface name '%s'",
link->filename, udev_device_get_sysname(device));
*ret = link;
return 0;
} else if (name_assign_type == NET_NAME_RENAMED) {
log_warning("Config file %s matches device based on renamed interface name '%s', ignoring",
link->filename, udev_device_get_sysname(device));
} else {
log_debug("Config file %s applies to device %s",
link->filename, udev_device_get_sysname(device));
*ret = link;
return 0;
}
} else {
log_debug("Config file %s applies to device %s",
link->filename, udev_device_get_sysname(device));
*ret = link;
return 0;
}
}
}
*ret = NULL;
return -ENOENT;
}
static bool mac_is_random(struct udev_device *device) {
const char *s;
unsigned type;
int r;
/* if we can't get the assign type, assume it is not random */
s = udev_device_get_sysattr_value(device, "addr_assign_type");
if (!s)
return false;
r = safe_atou(s, &type);
if (r < 0)
return false;
return type == NET_ADDR_RANDOM;
}
static bool should_rename(struct udev_device *device, bool respect_predictable) {
const char *s;
unsigned type;
int r;
/* if we can't get the assgin type, assume we should rename */
s = udev_device_get_sysattr_value(device, "name_assign_type");
if (!s)
return true;
r = safe_atou(s, &type);
if (r < 0)
return true;
switch (type) {
case NET_NAME_USER:
case NET_NAME_RENAMED:
/* these were already named by userspace, do not touch again */
return false;
case NET_NAME_PREDICTABLE:
/* the kernel claims to have given a predictable name */
if (respect_predictable)
return false;
/* fall through */
case NET_NAME_ENUM:
default:
/* the name is known to be bad, or of an unknown type */
return true;
}
}
static int get_mac(struct udev_device *device, bool want_random,
struct ether_addr *mac) {
int r;
if (want_random)
random_bytes(mac->ether_addr_octet, ETH_ALEN);
else {
uint8_t result[8];
r = net_get_unique_predictable_data(device, result);
if (r < 0)
return r;
assert_cc(ETH_ALEN <= sizeof(result));
memcpy(mac->ether_addr_octet, result, ETH_ALEN);
}
/* see eth_random_addr in the kernel */
mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */
mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */
return 0;
}
int link_config_apply(link_config_ctx *ctx, link_config *config,
struct udev_device *device, const char **name) {
const char *old_name;
const char *new_name = NULL;
struct ether_addr generated_mac;
struct ether_addr *mac = NULL;
bool respect_predictable = false;
int r, ifindex;
assert(ctx);
assert(config);
assert(device);
assert(name);
old_name = udev_device_get_sysname(device);
if (!old_name)
return -EINVAL;
r = ethtool_set_speed(&ctx->ethtool_fd, old_name, config->speed / 1024,
config->duplex);
if (r < 0)
log_warning_errno(r, "Could not set speed or duplex of %s to %u Mbps (%s): %m",
old_name, config->speed / 1024,
duplex_to_string(config->duplex));
r = ethtool_set_wol(&ctx->ethtool_fd, old_name, config->wol);
if (r < 0)
log_warning_errno(r, "Could not set WakeOnLan of %s to %s: %m",
old_name, wol_to_string(config->wol));
ifindex = udev_device_get_ifindex(device);
if (ifindex <= 0) {
log_warning("Could not find ifindex");
return -ENODEV;
}
if (ctx->enable_name_policy && config->name_policy) {
NamePolicy *policy;
for (policy = config->name_policy;
!new_name && *policy != _NAMEPOLICY_INVALID; policy++) {
switch (*policy) {
case NAMEPOLICY_KERNEL:
respect_predictable = true;
break;
case NAMEPOLICY_DATABASE:
new_name = udev_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE");
break;
case NAMEPOLICY_ONBOARD:
new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD");
break;
case NAMEPOLICY_SLOT:
new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT");
break;
case NAMEPOLICY_PATH:
new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH");
break;
case NAMEPOLICY_MAC:
new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC");
break;
default:
break;
}
}
}
if (should_rename(device, respect_predictable)) {
/* if not set by policy, fall back manually set name */
if (!new_name)
new_name = config->name;
} else
new_name = NULL;
switch (config->mac_policy) {
case MACPOLICY_PERSISTENT:
if (mac_is_random(device)) {
r = get_mac(device, false, &generated_mac);
if (r == -ENOENT)
break;
else if (r < 0)
return r;
mac = &generated_mac;
}
break;
case MACPOLICY_RANDOM:
if (!mac_is_random(device)) {
r = get_mac(device, true, &generated_mac);
if (r == -ENOENT)
break;
else if (r < 0)
return r;
mac = &generated_mac;
}
break;
default:
mac = config->mac;
}
r = rtnl_set_link_properties(&ctx->rtnl, ifindex, config->alias, mac,
config->mtu);
if (r < 0)
return log_warning_errno(r, "Could not set Alias, MACAddress or MTU on %s: %m", old_name);
*name = new_name;
return 0;
}
int link_get_driver(link_config_ctx *ctx, struct udev_device *device, char **ret) {
const char *name;
char *driver;
int r;
name = udev_device_get_sysname(device);
if (!name)
return -EINVAL;
r = ethtool_get_driver(&ctx->ethtool_fd, name, &driver);
if (r < 0)
return r;
*ret = driver;
return 0;
}
static const char* const mac_policy_table[_MACPOLICY_MAX] = {
[MACPOLICY_PERSISTENT] = "persistent",
[MACPOLICY_RANDOM] = "random"
};
DEFINE_STRING_TABLE_LOOKUP(mac_policy, MACPolicy);
DEFINE_CONFIG_PARSE_ENUM(config_parse_mac_policy, mac_policy, MACPolicy,
"Failed to parse MAC address policy");
static const char* const name_policy_table[_NAMEPOLICY_MAX] = {
[NAMEPOLICY_KERNEL] = "kernel",
[NAMEPOLICY_DATABASE] = "database",
[NAMEPOLICY_ONBOARD] = "onboard",
[NAMEPOLICY_SLOT] = "slot",
[NAMEPOLICY_PATH] = "path",
[NAMEPOLICY_MAC] = "mac"
};
DEFINE_STRING_TABLE_LOOKUP(name_policy, NamePolicy);
DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy,
_NAMEPOLICY_INVALID,
"Failed to parse interface name policy");