#! /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.

# Thus updates the configuration to add new interfaces.
# It is run on boot after udev and before vyatta configuration.

use strict;
use lib "/opt/vyatta/share/perl5/";
use Sys::Syslog qw(:standard :macros);
use XorpConfigParser;
use File::Copy;

my $TMPFILE  = "/tmp/config.boot.$$";

# These vendors are known to violate the local MAC address assignment convention
my %whitelist = (
    '02:07:01' => 'Interlan',
    '02:60:60' => '3Com',
    '02:60:8c' => '3Com',
    '02:a0:c9' => 'Intel',
    '02:aa:3c' => 'Olivetti',
    '02:cf:1f' => 'CMC',
    '02:e0:3b' => 'Prominet',
    '02:e6:d3' => 'BTI',
    '52:54:00' => 'Realtek',
    '52:54:4c' => 'Novell 2000',
    '52:54:ab' => 'Realtec',
    'e2:0c:0f' => 'Kingston Technologies',
);

# Ignore devices with local assigned or invalid mac address,
# these devices don't have a persistent address
sub persistent_address {
    my $mac = shift;

    return if ($mac eq '00:00:00:00:00:00');  # zero address is reserved

    # get first octet
    return unless ($mac =~ /^([0-9a-f][0-9a-f]):/);
    my $oct0 = hex($1);
    
    return if ($oct0 & 0x1);	   # skip it is a multicast address

    return 1 unless ($oct0 & 0x2); # this is good, not locally assigned

    return 1 if ( -d '/proc/xen' ); # workaround Xen breakage

    # unless it is in whitelist, it is non persistent
    $mac =~ /^([0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f])/;
    return $whitelist{$1};
}

# Map from eth0 to ethernet
# TODO make smarter if more types
sub interface_type {
    my $ifname = shift;

    return "ethernet" if ($ifname =~ /^eth/);
    return "wireless" if ($ifname =~ /^wlan/);

    die "unknown interface name %s\n", $ifname;
}

sub get_hwid {
    my $name = shift;

    open (my $f, '<', $name)
	or die "Can't open $name : $!";

    my $hwaddr = <$f>;
    chomp $hwaddr;
    close $f;

    return $hwaddr;
}

# Determine phy for wlan device
# (This is ugly)
sub get_phy {
    my $wlan = shift;
    my $phypath = "/sys/class/net/$wlan/phy80211";

    # link should be: ../../ieee80211/phy0
    return unless (readlink($phypath) =~ m!../../ieee80211/(phy[0-9]+)$!);

    return $1;
}

# vyatta_net_name leaves files in /dev/.udev/vyatta
#  the filename is the interface and the contents are the hardware id
sub interface_rescan {
    my ($VYATTAUDEV, $BOOTFILE) = @_;

    # parse existing config
    my $xcp = new XorpConfigParser();
    $xcp->parse($BOOTFILE);

    # get list of changed interfaces
    opendir( my $dir, $VYATTAUDEV )
	or die "Can't open $VYATTAUDEV : $!";
    my @interfaces = grep { ! /^\./ } readdir($dir);
    close $dir;

    foreach my $ifname (sort @interfaces) {
	my $hwaddr = get_hwid("$VYATTAUDEV/$ifname");
	unless (persistent_address($hwaddr)) {
	    syslog(LOG_NOTICE, "%s: skipping address %s is not persistent",
		   $ifname, $hwaddr);
	    next;
	}

	# Add new entry to config
	my $ifpath = interface_type($ifname) . " $ifname";

	syslog(LOG_INFO, "add config for %s hw-id %s", $ifname, $hwaddr);
	$xcp->create_node(['interfaces',$ifpath,"hw-id $hwaddr"]);

	# Add existing phy entry for wireless
	if ($ifname =~ /^wlan/) {
	    my $phy = get_phy($ifname);
	    $xcp->create_node(['interfaces',$ifpath,"physical-device $phy"]) if $phy;
	}

    }

    # Rewrite new config file
    open (my $tmp, '>', $TMPFILE)
	or die "Can't open $TMPFILE : $!";

    select $tmp;
    $xcp->output(0);
    select STDOUT;
    close $tmp;

    copy($TMPFILE, $BOOTFILE)
	or die "Can't copy $TMPFILE to $BOOTFILE : $!";

    unlink($TMPFILE);
}

# main
die "vyatta_interface_rescan called with wrong args"
    unless ($#ARGV == 1);

openlog("vyatta-interface-rescan", "", LOG_DAEMON);

interface_rescan(@ARGV);
exit 0;