#! /usr/bin/perl # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # 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. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2010 Vyatta, Inc. # All Rights Reserved. use strict; use lib "/opt/vyatta/share/perl5/"; use XorpConfigParser; use Vyatta::Config; use Sys::Syslog qw(:standard :macros); use Fcntl qw(:flock); my $BOOTFILE = "/opt/vyatta/etc/config/config.boot"; my $VYATTACFG = "/opt/vyatta/config/active"; my $UDEVDIR = "/dev/.udev/"; my $VYATTAUDEV = $UDEVDIR . "vyatta"; my $LOCKFILE = $UDEVDIR . ".vyatta-lock"; my $UDEVLOG = $UDEVDIR . "log/"; my $LOGFILE = $UDEVLOG . "vyatta-net-name.coldplug"; # Check if interface name is free to use sub is_available { my ($interfaces, $ifname) = @_; my $count = grep { $_ eq $ifname } values %$interfaces; return ($count == 0); } # Find next available interface name sub find_available { my ($interfaces, $ifprefix) = @_; $ifprefix =~ s/\d+$//; for (my $id = 0; ; $id++) { my $ifname = sprintf("%s%d", $ifprefix, $id); # is it in Vyatta config? return $ifname if (is_available($interfaces, $ifname)); } } # Find the hardware id in the parsed config node for interface sub get_hwid_from_children { my $children = shift; foreach my $attr (@$children) { next unless ($attr->{'name'} =~ /^hw-id ([0-9a-f:]+)/); return $1; } return; # not found } # Leave file for vyatta_interface_rescan sub leave_rescan_hint { my ($ifname, $hwaddr) = @_; my $name = "$VYATTAUDEV/$ifname"; mkdir($VYATTAUDEV); open (my $f, '>', $name) or die "Can't create $name : $!"; print {$f} "$hwaddr\n"; close $f; return 1; } # Use biosdevname program (ethernet only) # to try and find name based on PCI slot and DMI info sub biosdevname { my $ifname = shift; # biosdevname works only on ethernet devices return $ifname unless ($ifname =~ /^eth/); # Let the interface name changes ordered by previous invocations of this # script complete before we call biosdevname. If we don't, biosdevame # may generate incorrect name. sleep 1; my $biosname = `/sbin/biosdevname --policy all_ethN -i $ifname 2>>$UDEVLOG/biosdevname`; chomp $biosname; # if biosdevname has no answer it outputs a nothing return ($biosname eq '') ? $ifname : $biosname; } # parse vyatta config.boot # if file does not then running before off livecd then return empty hash sub parse_config_boot { my $interfaces = {}; if ( -f $BOOTFILE ) { my $xcp = new XorpConfigParser(); $xcp->parse($BOOTFILE); my $inode = $xcp->get_node(['interfaces']); if ($inode) { foreach my $child (@{$inode->{'children'}}) { my $name = $child->{'name'}; next unless ($name =~ /^ethernet (.*)|^wireless (.*)/); my $intf = $1; my $hwid = get_hwid_from_children($child->{'children'}); next unless $hwid; $interfaces->{$hwid} = $intf; } } } return $interfaces; } sub logit { my ($log, $msg) = @_; my $now = localtime; print $log "$now: $msg"; } # Determine network name to use based on Vyatta config during boot sub coldplug { my ($ifname, $hwaddr) = @_; # at this time root directory is read-only so use log file instead mkdir ($UDEVLOG); open (my $log, '>>', $LOGFILE) or die "Can't open $LOGFILE : $!"; logit($log, "lookup $ifname $hwaddr\n"); # parse config file to produce map of existing hw-id values my $interfaces = parse_config_boot(); # is name already in config file my $newname = $interfaces->{$hwaddr}; if ($newname) { logit($log, "use hw-id $hwaddr in config mapped to '$newname'\n"); return $newname; } # add already assigned names if (opendir(my $dir, $VYATTAUDEV)) { foreach my $intf (grep { ! /^\./ } readdir($dir)) { if (open (my $f, '<', "$VYATTAUDEV/$intf")) { my $hwid = <$f>; close $f; chomp $hwid; $interfaces->{$hwid} = $intf; } } } $newname = biosdevname($ifname); logit($log, "biosdevname for $ifname returned '$newname'\n"); unless (is_available($interfaces, $newname)) { $newname = find_available($interfaces, $newname); } logit($log, "new name for '$ifname' is '$newname'\n"); close $log; leave_rescan_hint($newname, $hwaddr); return $newname; } # Determine name from active config sub hotplug { my ($ifname, $hwaddr) = @_; # real filesystem available use real logging openlog("vyatta-net-name", "", LOG_DAEMON); # Parse active config my $cfg = new Vyatta::Config; $cfg->setLevel('interfaces'); my $interfaces = {}; foreach my $type ($cfg->listOrigNodes()) { next unless ($type eq 'ethernet') || ($type eq 'wireless'); foreach my $intf ($cfg->listOrigNodes($type)) { my $hwid = $cfg->returnOrigValue("$type $intf hw-id"); next unless $hwid; # TBD this could be a hash with name and path? $interfaces->{$hwid} = $intf; } } my $newname = $interfaces->{$hwaddr}; if ($newname) { syslog(LOG_DEBUG, "use hw-id %s in config mapped to '%s'", $hwaddr, $newname); return $newname; } $newname = biosdevname($ifname); syslog(LOG_DEBUG, "biosdevname for %s returned '%s'", $ifname, $newname); unless (is_available($interfaces, $newname)) { $newname = find_available($interfaces, $newname); } syslog(LOG_INFO, "new name for '%s' is '%s'", $ifname, $newname); return $newname; } my $LOCKF; sub lock_file { open ($LOCKF, '>', $LOCKFILE) or die "Can't open $LOCKFILE : $!"; flock ($LOCKF, LOCK_EX) or die "Can't lock $LOCKFILE : $!"; } sub unlock_file { close $LOCKF; $LOCKF = undef; } # This script is called from udev with two arguments # it outputs the new name (if any) to stdout if ($#ARGV != 1) { die "vyatta_net_name called with wrong args(%d) : %s", $#ARGV, join(' ', @ARGV); } my $ifname = $ARGV[0]; my $hwaddr = $ARGV[1]; lock_file; my $newname; if ( -d $VYATTACFG ) { $newname = hotplug($ifname, $hwaddr); } else { $newname = coldplug($ifname, $hwaddr); } unlock_file; print "$newname\n" if ($newname); exit 0;