Forward-ported network rule-generator code from eudev-1.10

This commit is contained in:
Ian Stakenvicius 2015-09-22 13:53:41 -04:00
parent 389b9ce228
commit 305f0eef4d
9 changed files with 588 additions and 12 deletions

View File

@ -15,3 +15,7 @@ SUBDIRS += \
hwdb
endif
if ENABLE_RULE_GENERATOR
SUBDIRS += \
rule_generator
endif

View File

@ -268,11 +268,26 @@ AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
AC_ARG_ENABLE([hwdb], AS_HELP_STRING([--enable-hwdb],[install hwdb.d files]),[],[enable_hwdb=yes])
AM_CONDITIONAL(ENABLE_HWDB, [test "x$enable_hwdb" = "xyes"])
# ------------------------------------------------------------------------------
# rule-generator - persistent network and optical device rule generator
# ------------------------------------------------------------------------------
AC_ARG_ENABLE([rule-generator],
AS_HELP_STRING([--enable-rule-generator], [enable legacy persistent network, cdrom support]),
[], [enable_rule_generator=no])
if test "x${enable_rule_generator}" != xno; then
AC_DEFINE([ENABLE_RULE_GENERATOR], [1], [Define if we are enabling rule generator])
fi
AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = xyes])
# ------------------------------------------------------------------------------
AC_CONFIG_FILES([Makefile
hwdb/Makefile
man/Makefile
rule_generator/Makefile
rule_generator/write_net_rules
rules/Makefile
src/Makefile
src/ata_id/Makefile

View File

@ -0,0 +1,100 @@
# do not edit this file, it will be overwritten on update
# these rules generate rules for persistent network device naming
#
# variables used to communicate:
# MATCHADDR MAC address used for the match
# MATCHID bus_id used for the match
# MATCHDRV driver name used for the match
# MATCHIFTYPE interface type match
# COMMENT comment to add to the generated rule
# INTERFACE_NAME requested name supplied by external tool
# INTERFACE_NEW new interface name returned by rule writer
ACTION!="add", GOTO="persistent_net_generator_end"
SUBSYSTEM!="net", GOTO="persistent_net_generator_end"
# ignore the interface if a name has already been set
NAME=="?*", GOTO="persistent_net_generator_end"
# device name whitelist
KERNEL!="eth*|ath*|wlan*[0-9]|msh*|ra*|sta*|ctc*|lcs*|hsi*", GOTO="persistent_net_generator_end"
# ignore Xen virtual interfaces
SUBSYSTEMS=="xen", GOTO="persistent_net_generator_end"
# read MAC address
ENV{MATCHADDR}="$attr{address}"
# match interface type
ENV{MATCHIFTYPE}="$attr{type}"
# ignore KVM virtual interfaces
ENV{MATCHADDR}=="52:54:00:*", GOTO="persistent_net_generator_end"
# ignore VMWare virtual interfaces
ENV{MATCHADDR}=="00:0c:29:*|00:50:56:*", GOTO="persistent_net_generator_end"
# ignore Hyper-V virtual interfaces
ENV{MATCHADDR}=="00:15:5d:*", GOTO="persistent_net_generator_end"
# These vendors are known to violate the local MAC address assignment scheme
# Interlan, DEC (UNIBUS or QBUS), Apollo, Cisco, Racal-Datacom
ENV{MATCHADDR}=="02:07:01:*", GOTO="globally_administered_whitelist"
# 3Com
ENV{MATCHADDR}=="02:60:60:*", GOTO="globally_administered_whitelist"
# 3Com IBM PC; Imagen; Valid; Cisco; Apple
ENV{MATCHADDR}=="02:60:8c:*", GOTO="globally_administered_whitelist"
# Intel
ENV{MATCHADDR}=="02:a0:c9:*", GOTO="globally_administered_whitelist"
# Olivetti
ENV{MATCHADDR}=="02:aa:3c:*", GOTO="globally_administered_whitelist"
# CMC Masscomp; Silicon Graphics; Prime EXL
ENV{MATCHADDR}=="02:cf:1f:*", GOTO="globally_administered_whitelist"
# Prominet Corporation Gigabit Ethernet Switch
ENV{MATCHADDR}=="02:e0:3b:*", GOTO="globally_administered_whitelist"
# BTI (Bus-Tech, Inc.) IBM Mainframes
ENV{MATCHADDR}=="02:e6:d3:*", GOTO="globally_administered_whitelist"
# Realtek
ENV{MATCHADDR}=="52:54:00:*", GOTO="globally_administered_whitelist"
# Novell 2000
ENV{MATCHADDR}=="52:54:4c:*", GOTO="globally_administered_whitelist"
# Realtec
ENV{MATCHADDR}=="52:54:ab:*", GOTO="globally_administered_whitelist"
# Kingston Technologies
ENV{MATCHADDR}=="e2:0c:0f:*", GOTO="globally_administered_whitelist"
# match interface dev_id
ATTR{dev_id}=="?*", ENV{MATCHDEVID}="$attr{dev_id}"
# do not use "locally administered" MAC address
ENV{MATCHADDR}=="?[2367abef]:*", ENV{MATCHADDR}=""
# do not use empty address
ENV{MATCHADDR}=="00:00:00:00:00:00", ENV{MATCHADDR}=""
LABEL="globally_administered_whitelist"
# build comment line for generated rule:
SUBSYSTEMS=="pci", ENV{COMMENT}="PCI device $attr{vendor}:$attr{device} ($driver)"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="?*", ENV{COMMENT}="USB device 0x$attr{idVendor}:0x$attr{idProduct} ($driver)"
SUBSYSTEMS=="pcmcia", ENV{COMMENT}="PCMCIA device $attr{card_id}:$attr{manf_id} ($driver)"
SUBSYSTEMS=="ieee1394", ENV{COMMENT}="Firewire device $attr{host_id})"
# ibmveth likes to use "locally administered" MAC addresses
DRIVERS=="ibmveth", ENV{MATCHADDR}="$attr{address}", ENV{COMMENT}="ibmveth ($id)"
# S/390 uses id matches only, do not use MAC address match
SUBSYSTEMS=="ccwgroup", ENV{COMMENT}="S/390 $driver device at $id", ENV{MATCHID}="$id", ENV{MATCHDRV}="$driver", ENV{MATCHADDR}=""
# see if we got enough data to create a rule
ENV{MATCHADDR}=="", ENV{MATCHID}=="", ENV{INTERFACE_NAME}=="", GOTO="persistent_net_generator_end"
# default comment
ENV{COMMENT}=="", ENV{COMMENT}="net device ($attr{driver})"
# write rule
DRIVERS=="?*", IMPORT{program}="write_net_rules"
# rename interface if needed
ENV{INTERFACE_NEW}=="?*", NAME="$env{INTERFACE_NEW}"
LABEL="persistent_net_generator_end"

View File

@ -0,0 +1,13 @@
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
# ------------------------------------------------------------------------------
# rule_generator - persistent network rule generator
# ------------------------------------------------------------------------------
dist_udevlibexec_SCRIPTS = \
write_net_rules
udevhomedir = $(udevlibexecdir)
dist_udevhome_DATA = rule_generator.functions
dist_udevrules_DATA = \
75-persistent-net-generator.rules

View File

@ -0,0 +1,120 @@
# functions used by the udev rule generator
# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
PATH='/sbin:/bin'
#
PATH='/sbin:/bin'
# Read a single line from file $1 in the $DEVPATH directory.
# The function must not return an error even if the file does not exist.
sysread() {
local file="$1"
[ -e "/sys$DEVPATH/$file" ] || return 0
local value
read value < "/sys$DEVPATH/$file" || return 0
echo "$value"
}
sysreadlink() {
local file="$1"
[ -e "/sys$DEVPATH/$file" ] || return 0
readlink -f /sys$DEVPATH/$file 2> /dev/null || true
}
# Return true if a directory is writeable.
writeable() {
if ln -s test-link $1/.is-writeable 2> /dev/null; then
rm -f $1/.is-writeable
return 0
else
return 1
fi
}
# Create a lock file for the current rules file.
lock_rules_file() {
RUNDIR="/run/udev/"
RULES_LOCK="$RUNDIR/.lock-${RULES_FILE##*/}"
retry=30
while ! mkdir $RULES_LOCK 2> /dev/null; do
if [ $retry -eq 0 ]; then
echo "Cannot lock $RULES_FILE!" >&2
exit 2
fi
sleep 1
retry=$(($retry - 1))
done
}
unlock_rules_file() {
[ "$RULES_LOCK" ] || return 0
rmdir $RULES_LOCK || true
}
# Choose the real rules file if it is writeable or a temporary file if not.
# Both files should be checked later when looking for existing rules.
choose_rules_file() {
RUNDIR="/run/udev/"
local tmp_rules_file="$RUNDIR/tmp-rules--${RULES_FILE##*/}"
[ -e "$RULES_FILE" -o -e "$tmp_rules_file" ] || PRINT_HEADER=1
[ -d "${RULES_FILE%/*}" ] || if writeable ${RULES_FILE%/rules.d/*}; then
mkdir -p "${RULES_FILE%/*}"
fi
if writeable ${RULES_FILE%/*}; then
RO_RULES_FILE='/dev/null'
else
RO_RULES_FILE=$RULES_FILE
RULES_FILE=$tmp_rules_file
fi
}
# Return the name of the first free device.
raw_find_next_available() {
local links="$1"
local basename=${links%%[ 0-9]*}
local max=-1
for name in $links; do
local num=${name#$basename}
[ "$num" ] || num=0
[ $num -gt $max ] && max=$num
done
local max=$(($max + 1))
# "name0" actually is just "name"
[ $max -eq 0 ] && return
echo "$max"
}
# Find all rules matching a key (with action) and a pattern.
find_all_rules() {
local key="$1"
local linkre="$2"
local match="$3"
local search='.*[[:space:],]'"$key"'"('"$linkre"')".*'
echo $(sed -n -r -e 's/^#.*//' -e "${match}s/${search}/\1/p" \
$RO_RULES_FILE \
$([ -e $RULES_FILE ] && echo $RULES_FILE) \
2>/dev/null)
}

View File

@ -0,0 +1,141 @@
#!/bin/sh -e
# This script is run to create persistent network device naming rules
# based on properties of the device.
# If the interface needs to be renamed, INTERFACE_NEW=<name> will be printed
# on stdout to allow udev to IMPORT it.
# variables used to communicate:
# MATCHADDR MAC address used for the match
# MATCHID bus_id used for the match
# MATCHDEVID dev_id used for the match
# MATCHDRV driver name used for the match
# MATCHIFTYPE interface type match
# COMMENT comment to add to the generated rule
# INTERFACE_NAME requested name supplied by external tool
# INTERFACE_NEW new interface name returned by rule writer
# Copyright (C) 2006 Marco d'Itri <md@Linux.IT>
# Copyright (C) 2007 Kay Sievers <kay.sievers@vrfy.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# debug, if UDEV_LOG=<debug>
if [ -n "$UDEV_LOG" ]; then
if [ "$UDEV_LOG" -ge 7 ]; then
set -x
fi
fi
RULES_FILE='@udevconfdir@/rules.d/70-persistent-net.rules'
. @udevlibexecdir@/rule_generator.functions
interface_name_taken() {
local value="$(find_all_rules 'NAME=' $INTERFACE)"
if [ "$value" ]; then
return 0
else
return 1
fi
}
find_next_available() {
raw_find_next_available "$(find_all_rules 'NAME=' "$1")"
}
write_rule() {
local match="$1"
local name="$2"
local comment="$3"
{
if [ "$PRINT_HEADER" ]; then
PRINT_HEADER=
echo "# This file was automatically generated by the $0"
echo "# program, run by the persistent-net-generator.rules rules file."
echo "#"
echo "# You can modify it, as long as you keep each rule on a single"
echo "# line, and change only the value of the NAME= key."
fi
echo ""
[ "$comment" ] && echo "# $comment"
echo "SUBSYSTEM==\"net\", ACTION==\"add\"$match, NAME=\"$name\""
} >> $RULES_FILE
}
if [ -z "$INTERFACE" ]; then
echo "missing \$INTERFACE" >&2
exit 1
fi
# Prevent concurrent processes from modifying the file at the same time.
lock_rules_file
# Check if the rules file is writeable.
choose_rules_file
# the DRIVERS key is needed to not match bridges and VLAN sub-interfaces
if [ "$MATCHADDR" ]; then
match="$match, DRIVERS==\"?*\", ATTR{address}==\"$MATCHADDR\""
fi
if [ "$MATCHDRV" ]; then
match="$match, DRIVERS==\"$MATCHDRV\""
fi
if [ "$MATCHDEVID" ]; then
match="$match, ATTR{dev_id}==\"$MATCHDEVID\""
fi
if [ "$MATCHID" ]; then
match="$match, KERNELS==\"$MATCHID\""
fi
if [ "$MATCHIFTYPE" ]; then
match="$match, ATTR{type}==\"$MATCHIFTYPE\""
fi
if [ -z "$match" ]; then
echo "missing valid match" >&2
unlock_rules_file
exit 1
fi
basename=${INTERFACE%%[0-9]*}
match="$match, KERNEL==\"$basename*\""
if [ "$INTERFACE_NAME" ]; then
# external tools may request a custom name
COMMENT="$COMMENT (custom name provided by external tool)"
if [ "$INTERFACE_NAME" != "$INTERFACE" ]; then
INTERFACE=$INTERFACE_NAME;
echo "INTERFACE_NEW=$INTERFACE"
fi
else
# if a rule using the current name already exists, find a new name
if interface_name_taken; then
INTERFACE="$basename$(find_next_available "$basename[0-9]*")"
# prevent INTERFACE from being "eth" instead of "eth0"
[ "$INTERFACE" = "${INTERFACE%%[ \[\]0-9]*}" ] && INTERFACE=${INTERFACE}0
echo "INTERFACE_NEW=$INTERFACE"
fi
fi
write_rule "$match" "$INTERFACE" "$COMMENT"
unlock_rules_file
exit 0

View File

@ -15,8 +15,12 @@ dist_udevrules_DATA = \
70-mouse.rules \
75-net-description.rules \
75-probe_mtd.rules \
78-sound-card.rules \
78-sound-card.rules
if !ENABLE_RULE_GENERATOR
dist_udevrules_DATA += \
80-net-name-slot.rules
endif
if HAVE_BLKID
dist_udevrules_DATA += \

View File

@ -771,17 +771,17 @@ out:
return err;
}
static int rename_netif(struct udev_event *event) {
struct udev_device *dev = event->dev;
char name[IFNAMSIZ];
const char *oldname;
#ifdef ENABLE_RULE_GENERATOR
/* function to return the count of rules that assign NAME= to a value matching arg#2 , defined in udev-rules.c */
int udev_rules_assigning_name_to(struct udev_rules *rules,const char *match_name);
#endif
static int rename_netif_dev_fromname_toname(struct udev_device *dev,const char *oldname,const char *name) {
int r;
int sk;
struct ifreq ifr;
oldname = udev_device_get_sysname(dev);
strscpy(name, IFNAMSIZ, event->name);
log_debug("changing net interface name from '%s' to '%s'\n",oldname,name);
sk = socket(PF_INET, SOCK_DGRAM, 0);
if (sk < 0)
@ -791,13 +791,52 @@ static int rename_netif(struct udev_event *event) {
strscpy(ifr.ifr_name, IFNAMSIZ, oldname);
strscpy(ifr.ifr_newname, IFNAMSIZ, name);
r = ioctl(sk, SIOCSIFNAME, &ifr);
if (r < 0)
return log_error_errno(-errno, "Error changing net interface name '%s' to '%s': %m", oldname, name);
log_debug("renamed network interface '%s' to '%s'", oldname, name);
#ifdef ENABLE_RULE_GENERATOR
int loop;
if (r == 0) {
log_info("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname);
goto out;
}
/* keep trying if the destination interface name already exists */
log_debug("collision on rename of network interface %s to %s , retrying until timeout\n",
ifr.ifr_name, ifr.ifr_newname);
r = -errno;
if (r != -EEXIST)
goto out;
/* wait a maximum of 90 seconds for our target to become available */
loop = 90 * 20;
while (loop--) {
const struct timespec duration = { 0, 1000 * 1000 * 1000 / 20 };
nanosleep(&duration, NULL);
r = ioctl(sk, SIOCSIFNAME, &ifr);
if (r == 0) {
log_info("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname);
break;
}
r = -errno;
if (r != -EEXIST)
break;
}
out:
#endif
if (r < 0)
log_error_errno(-errno, "Error changing net interface name %s to %s: %m\n", ifr.ifr_name, ifr.ifr_newname);
else
log_debug("renamed network interface '%s' to '%s'", oldname, name);
close(sk);
return 0;
return r;
}
static int rename_netif(struct udev_event *event) {
return rename_netif_dev_fromname_toname(event->dev,udev_device_get_sysname(event->dev),event->name);
}
void udev_event_execute_rules(struct udev_event *event,
@ -843,6 +882,79 @@ void udev_event_execute_rules(struct udev_event *event,
sigmask);
/* rename a new network interface, if needed */
/* ENABLE_RULE_GENERATOR conditional:
* if this is a net iface, and it is an add event,
* and as long as all of the following are FALSE:
* - no NAME target and the current name is not being used
* - there is a NAME target and it is the same as the current name
* - the rules can successfully be searched for the current name (not really part of the conditional)
* the run the rename.
*
* note - udev_rules_assigning_name_to is run when event->name is NULL to ensure renames happen,
* but also on its own to check if a temp-rename is necessary when event->name exists.
*
* A temp-rename is necessary when:
* - there is no rule renaming the current iface but the current name IS used in some other rule
* - there is a rule renaming the current iface,
* the current name IS used AND the target name != the current name
*/
#ifdef ENABLE_RULE_GENERATOR
int r;
if (udev_device_get_ifindex(dev) > 0 && streq(udev_device_get_action(dev), "add") &&
(event->name == NULL && (r=udev_rules_assigning_name_to(rules,udev_device_get_sysname(dev))) > 0 ||
event->name != NULL && !streq(event->name, udev_device_get_sysname(dev)))) {
char syspath[UTIL_PATH_SIZE];
char *pos;
char *finalifname = event->name;
char newifname[IFNAMSIZ];
/* r is the number of rules that assign a device with NAME= this sysname */
if (r > 0 || (r=udev_rules_assigning_name_to(rules,udev_device_get_sysname(dev))) > 0) {
/* have a conflict, rename to a temp name */
char *newpos;
int ifidnum;
/* build the temporary iface name */
strscpy(newifname, IFNAMSIZ, udev_device_get_sysname(dev));
newpos=pos=&newifname[strcspn(newifname,"0123456789")];
ifidnum=(int)strtol(pos,&newpos,10);
*pos='\0';
if (newpos > pos && *newpos == '\0') /* append new iface num to name */
/* use udev_device_get_ifindex(dev) as it is unique to every iface */
snprintf(pos,IFNAMSIZ+(newifname-pos), "%d", 128 - udev_device_get_ifindex(dev));
/* note, r > 0, which will skip the post-rename stuff if no rename occurs */
/* if sysname isn't already the tmpname (ie there is no numeric component), do the rename */
if (!streq(newifname,udev_device_get_sysname(dev))) {
r = rename_netif_dev_fromname_toname(dev,udev_device_get_sysname(dev),newifname);
if (r == 0) {
finalifname = newifname;
log_debug("renamed netif to '%s' for collision avoidance\n", newifname);
} else {
log_error("could not rename netif to '%s' for collision avoidance\n",newifname);
}
}
/* rename it now to its final target if its not already there */
if (event->name != NULL && !streq(event->name, newifname)) {
r = rename_netif_dev_fromname_toname(dev,newifname,event->name);
if (r == 0)
finalifname = event->name;
}
} else { /* no need to rename to a tempname first, do a regular direct rename to event->name */
r = 1; /* skip the post-rename stuff if no rename occurs */
if (!streq(event->name, udev_device_get_sysname(dev)))
r = rename_netif(event);
}
if (r == 0) {
log_debug("renamed netif to '%s'\n", finalifname);
r = udev_device_rename(dev, finalifname);
#else
if (udev_device_get_ifindex(dev) > 0 && streq(udev_device_get_action(dev), "add") &&
event->name != NULL && !streq(event->name, udev_device_get_sysname(dev))) {
int r;
@ -853,6 +965,7 @@ void udev_event_execute_rules(struct udev_event *event,
udev_device_get_sysname(dev), event->name);
else {
r = udev_device_rename(dev, event->name);
#endif
if (r < 0)
log_warning_errno(r, "renamed interface '%d' from '%s' to '%s', but could not update udev_device: %m",
udev_device_get_ifindex(dev), udev_device_get_sysname(dev), event->name);

View File

@ -2755,3 +2755,69 @@ finish:
return r;
}
#ifdef ENABLE_RULE_GENERATOR
/* function to return the count of rules that assign NAME= to a value matching arg#2 - returns 0,1 */
int udev_rules_assigning_name_to(struct udev_rules *rules, const char *match_name)
{
struct token *cur;
struct token *rule;
enum escape_type esc = ESCAPE_UNSET;
bool name_final = false;
if (rules->tokens == NULL)
return 0;
/* loop through token list, match, run actions or forward to next rule */
cur = &rules->tokens[0];
rule = cur;
for (;;) {
dump_token(rules, cur);
switch (cur->type) {
case TK_RULE:
/* current rule */
rule = cur;
if (!rule->rule.can_set_name)
goto nomatch;
break;
case TK_M_SUBSYSTEM:
if (match_key(rules, cur, "net") != 0)
goto nomatch;
break;
case TK_M_ACTION:
if (match_key(rules, cur, "add") != 0)
goto nomatch;
break;
case TK_A_NAME: {
const char *name = rules_str(rules, cur->key.value_off);
char name_str[UTIL_PATH_SIZE];
int count;
strscpy(name_str,UTIL_PATH_SIZE,name);
count = util_replace_chars(name_str, "/");
if (count > 0)
log_debug("%i character(s) replaced\n", count);
if (streq(name_str,match_name)) {
log_debug("found a match! NAME assigns %s in: %s:%u\n",
name,
rules_str(rules, rule->rule.filename_off),
rule->rule.filename_line);
return 1; /* no need to find more than one */
}
/* skip to next rule */
goto nomatch;
}
case TK_END:
return 0;
}
cur++;
continue;
nomatch:
/* fast-forward to next rule */
cur = rule + rule->rule.token_count;
}
}
#endif