#!/usr/bin/perl -w
#
# Copyright 2006 VMware, Inc.  All rights reserved.
#-----------------------------------------------------------------------
# $Id: esxnet-support,v 1.29 2006-10-12 17:29:20 cscott Exp $
#-----------------------------------------------------------------------
# This program helps you work with network dumps in ESX 3.0
#-----------------------------------------------------------------------

use lib "/usr/lib/vmware/esx-perl/perl5/site_perl/5.8.0";

use strict;
use Data::Dumper;               # Great debug tool
use Pod::Usage;                 # Convert POD into usage string
use Getopt::Long;               # Processing of CMD line options
use FindBin;                    # Self identification
use Digest::MD5;                # MD5 Calculation
use VMware::Panic qw(Panic);    # VMware Panic() function
use VMware::Log qw(:log);       # VMware logging functions

my ($self_dir, $self) = ($FindBin::RealBin, $FindBin::RealScript);
my $fself = "$self_dir/$self";  # Complete (full) self name

my $tcpdump_opts;
my $trace_item;
my $trace_timeout = 0;
my $trace_type;
my $compress_output = 0;
my $email_output = 0;

# Commands required for the various diagnostics available.
# Checked in "check_dependencies()"
# 'tcpdump' is intentionally not listed here as it's an optional
# component as of 4/18/06 and is checked for within the trace router
# itself.  It will move into this list if/when it becomes a required
# component of ESX
# TODO: Handle multiple versions
my %required_commands = (
    '/usr/sbin/esxcfg-nics' => '334b26aa1708cea185efa4796f6f83c7',
    '/usr/sbin/esxcfg-vswif' => '8d1e946cf8e0874aae97032cf346d69b',
    '/usr/sbin/esxcfg-vswitch' => 'd269e91b3ee0384b4809ae35f1aee532',
    '/bin/netstat' => '46cf84840c1d985568ff85e675f10803', 
    '/bin/ping' => '23cc03559d2c076943d48371c4191db7',
    '/sbin/arping' => '529258de2036a23b6664e9336fec2d3d'
);

#-----------------------------------------------------------------------
# Configurable variables
#-----------------------------------------------------------------------

$|=1;                           # Force flushing of written buffers
my $D = 0;                      # DEBUG flag, 0 - normal,
                                #             # - dbg level
                                #            -1 - silent/quiet
my $version = "1.06.09.26";     # date '+VERSION.%y.%m.%d'

#=======================================================================
# LIST OF ERRORS
#=======================================================================
# 00 - success
# 01 - incorrect usage
# 02 - required command/package not installed
#=======================================================================

#=======================================================================
# MAIN processing
#=======================================================================

# Determine if all of the required commands are available before continuing
my @missing = check_dependencies();
if (scalar(@missing)) {
    my $temp = "$self [ERROR] Required system tools are\nmissing or are the wrong version:\n\n";
    foreach my $miss (@missing) {
        $temp .= "    $miss\n";
    }
    $temp .= "\n";
    die($temp);
}

#-----------------------------------------------------------------------
# 2. process command - type of troubleshooting
#-----------------------------------------------------------------------

my $cmd_type;                  # What is required?

esx3net_help() unless $ARGV[0];

if ( defined($ARGV[0]) && ($ARGV[0] =~ m/^[^-]/) ) {
    # First argument appears to be a command
    $cmd_type = shift;
    debug_print(1, "$self [DEBUG] troubleshooting type = \'$cmd_type\'\n");
}

Getopt::Long::Configure('no_ignore_case');

unless ($cmd_type) {
    # No command specified...

    GetOptions(
        'DEBUG=i'       => \$D,
        'version'       => sub { esx3net_version(); },
        'help|?'        => sub { esx3net_help(); },
        'man'           => sub { esx3net_man(); }
    ) or esx3net_help(1);
} elsif ($cmd_type eq 'diag') {

    GetOptions(
        'DEBUG=i'       => \$D
    ) or esx3net_help(1);

    esx3net_diag();
} elsif ($cmd_type eq 'trace') {

    GetOptions(
        'vswif=s'       => sub {
                                if ($trace_item) {
                                    esx3net_help(1);
                                } else {
                                    $trace_type = 'vswif';
                                    $trace_item = $_[1];
                                }
                            },
        'portgroup=s'   => sub {
                                if ($trace_item) {
                                    esx3net_help(1);
                                } else {
                                    $trace_type = 'pgroup';
                                    $trace_item = $_[1];
                                }
                            },
                                    
        'seconds=i'     => \$trace_timeout,
        'compress|z'    => sub { $compress_output = 1; },
        'email'         => sub { $compress_output = $email_output = 1; },
        'DEBUG=i'       => \$D
    ) or esx3net_help();

    esx3net_trace($trace_type);
} elsif ($cmd_type eq 'rebuild') {
    GetOptions(
        'DEBUG=i'       => \$D
    );

    esx3net_rebuild();
} elsif ($cmd_type eq 'man') {
    esx3net_man();
} elsif ($cmd_type eq 'help') {
    esx3net_help();
} elsif ($cmd_type eq 'version') {
    esx3net_version();
} else {
    die("$self [ERROR] unknown command \'$cmd_type\'.  Try --help.\n");
    exit(1);
}

#-----------------------------------------------------------------------
# Processing command line options
# 1. common functions
#-----------------------------------------------------------------------

sub esx3net_help {
    my $exitval = shift || 0;

    #-------------------------------------------------------------------
    # For peruse: prints short version & usage info and exits with the
    # given return code.
    #-------------------------------------------------------------------
    pod2usage( -msg     => "\n$fself, version $version\n\n" .
                           "For more detailed usage information, ".
                           "try \"$self man\"\n",
               -input   => $fself,
               -verbose => 0,
               -exitval => $exitval )
}

sub esx3net_man {
    my $exitval = shift || 0;
    #-------------------------------------------------------------------
    # Convert entire POD into usage and show it on the screen and exits
    # with the given return code.
    #-------------------------------------------------------------------
    pod2usage( -msg     => "\n$fself, version $version\n",
               -input   => $fself,
               -verbose => 4,
               -exitval => $exitval );
}

#-----------------------------------------------------------------------
# SUBROUTINES
#-----------------------------------------------------------------------
sub debug_print {
    # Handle debugging statements, optionally printing them to the
    # screen based on the setting of $D.  Also uses the VMware
    # Log function to place all messages in the log.
    # Takes $level, returns nothing

    my $level = shift;

    Panic("Invalid level passed to debug_print") unless $level =~ /^\d+$/;

    # Log() doesn't print anything for levels over 0, so we have to
    # duplicate it to the screen...
    print @_ if ($D >= $level);

    # Anything over level 0 is a debug message...
    if ($level > 0) {
        my $temp = '';
        foreach my $item (@_) {
            $temp .= $item;
        }

        LogDebug($temp, $level);
    }
}

sub debug_printf {
    my $level = shift;
    my $format = shift;
    
    my $temp = sprintf($format, @_);
    debug_print($level, $temp);
}

sub screen_print {
    debug_print(0, @_);
}

sub screen_printf {
    debug_printf(0, @_);
}

sub esx3net_version {
    # Print a short version statement and exit with the given return
    # code.
    my $exitval = shift || 0;
    screen_print "$fself, version $version\n";
    exit($exitval);
}

sub check_file_md5 {
    my $filename = shift;

    open(my $file, "< $filename") || return undef;

    my $md5 = new Digest::MD5();
    $md5->addfile($file);
    return $md5->hexdigest;
}

sub check_dependencies {
    # Takes the globally declared %required_commands and iterates
    # through it verifying each is available.  Returns a list of
    # scalars indicating which are missing or an empty list if
    # everything is present.
    my @missing = ();

    # Diabling MD5 check for now...
    foreach my $command (keys %required_commands) {
    #    unless (-f $command && (check_file_md5($command) eq $required_commands{$command})) {
        unless (-f $command) {
            push @missing, $command;
        }
    }

    return @missing;
}
        
sub get_vmknics {
    # Parses the output of esxcfg-vmknic and returns a pointer to a
    # data structure.
    my %vmk;

    foreach my $line (qx(/usr/sbin/esxcfg-vmknic -l)) {
        next if ($line =~ /^Port Group\s+IP Address/); # Skip header

        # Column info
        #   0 Port Group
        #   1 IP Address
        #   2 Netmask
        #   3 Broadcast
        #   4 MAC Address
        #   5 MTU
        #   6 Enabled

        if ($line =~ # Another long line
            /^(.+\w)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+([\w\:]+)\s+(\d+)\s+(\w+)\s?/) {

            $vmk{$1} = {
                ip => $2,
                mask => $3,
                bcast => $4,
                mac => $5,
                mtu => $6,
                enabled => $7
            };
        }
    }

    debug_print(9, Dumper(%vmk));
    return \%vmk;
}

sub get_vswifs {
    # Parses the output of esxcfg-vswif and returns a pointer to a
    # data structure.
    my %vswifs;

    foreach my $line (qx(/usr/sbin/esxcfg-vswif -l)) {
        next if ($line =~ /^Name\s+Port Group\s+IP Address/);
        # Skip header

        # Column info
        #   0 Name
        #   1 Port Group
        #   2 IP Address
        #   3 Netmask
        #   4 Broadcast
        #   5 Enabled
        #   6 DHCP

        if ($line =~ # I know this is a long line, I'm sorry...
            /^(\w+)\s+(.*\w)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+(\w+)\s+(\w+)/) {
            $vswifs{$1} = {
                pgroup => $2,
                ip => $3,
                mask => $4,
                bcast => $5,
                enabled => $6,
                dhcp => $7
            };
        }
    }

    # esxcfg-vswifs doesn't have gateway info, we have to use route
    my %route;
    foreach my $line ( qx(/bin/netstat -rn) ) {
        next unless ( $line =~ /vswif/ );     # Take only vswif lines
        my @field = split(/\s+/, $line);      # Split them
        if ( ! defined( $route{$field[7]}{'count'} ) ) {
            $route{$field[7]}{'count'} = 0;   # This will count routes
        }
        if ( $field[1] ne '0.0.0.0' ) {       # Only real routes
            push( @{ $route{$field[7]}{'route'} }, $field[1] );
            $route{$field[7]}{'count'}++;     # Count ALL vswif routes
        }
    }

    # Associate the retrieved route with the appropriate vswif
    foreach my $key (keys %vswifs) {
        if ($route{$key}{'route'} && $route{$key}{'route'} ne '') {
            $vswifs{$key}{'gw'} = $route{$key}{'route'}[0];
        } 
    }

    debug_print(9, Dumper(%vswifs));
    return \%vswifs;
}

sub get_vswitches {
    # Parses the output of esxcfg-vswitch and returns a
    # pointer to a data structure.
    my %vswitches;

    my $current_switch = '';

    foreach my $line (qx(/usr/sbin/esxcfg-vswitch -l)) {
        next if ($line =~ /^Switch Name.*Num Ports.*Used Ports/ ||
            $line =~ /^\s+$/ || $line =~ /\s+PortGroup Name.*Internal ID.*/);
            # Skip headers and blank lines

        if ($line =~ /^(.+\w)\s+(\d+)\s+(\d+)\s+(\d+)\s+([\w,]*)/) {
            # We're getting a vswitch entry
            $current_switch = $1;
            my @uplinks = split(/\,/, $5);

            $vswitches{$1} = {
                ports => $2,
                used_ports => $3,
                conf_ports => $4,
                uplinks => \@uplinks
            };

        } elsif ($line =~ /^\s+(.+\w)\s+(\w+)\s+(\d+)\s+(\d+)\s+([\w,]*)/) {
            # This is a portgroup line
            my @uplinks;
            if ($5 eq 'ALL') {
                if ($vswitches{$current_switch}{'uplinks'}) {
                    @uplinks = @{$vswitches{$current_switch}{'uplinks'}};
                } # else leave it empty
            } else {
                @uplinks = split(/\,/, $5);
            }

            $vswitches{$current_switch}{'pgroups'}{$1} = {
                id => $2,
                vlan => $3,
                used_ports => $4,
                uplinks => \@uplinks
            };
        }
    }
    return \%vswitches;
}

sub get_nics {
    # Parses the output of esxcfg-nics and returns a pointer to a
    # data structure.
    my %nics;

    foreach my $line (qx(/usr/sbin/esxcfg-nics -l)) {
        next if ($line =~ /^Name\s+PCI\s+Driver\s+Link\s+Speed/);
            # Skip header

        if ($line =~ /^(\w+)\s+([0-9a-fA-F:.]+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w.+)$/) {
            $nics{$1}{'pci'} = $2;
            $nics{$1}{'driver'} = $3;
            $nics{$1}{'link'} = $4;
            $nics{$1}{'speed'} = $5;
            $nics{$1}{'duplex'} = $6;
            $nics{$1}{'description'} = $7;
        }

    }

    debug_print(9, Dumper(%nics));

    return \%nics;
}

sub ip_is_valid {
    # Figure out if an IP address is valid
    # Return (1, undef) if it is, (0, $reason) otherwise
    # Takes (IP Address, Netmask)

    return (0, "Invalid number of arguments passed to ip_is_valid")
        unless (@_ == 2);

    my ($ip, $nm) = @_;

    # Are the arguments valid?
    return (0, "Not a valid IP address")
        unless ($ip =~ /^\d+\.\d+\.\d+\.\d+$/);
    
    # Verify arguments are valid decimal integers
    foreach my $num (split(/\./, $ip)) {
        return (0, "Not a valid IP address") unless ($num < 256);
    }

    foreach my $num (split(/\./, $nm)) {
        return (0, "Not a valid netmask") unless ($num < 256);
    }

    # Split the addresses into octets
    my ($ip1, $ip2, $ip3, $ip4) = split(/\./, $ip);
    my ($nm1, $nm2, $nm3, $nm4) = split(/\./, $nm);

    # Localhost, multicast, all zero, and full broadcast
    # addresses are not valid...
    return (0, "Localhost reserved range")
        if ($ip1 eq "127");
    return (0, "Multicast reserved range")
        if ($ip1 eq "224");
    return (0, "All zero address")
        if ("$ip1.$ip2.$ip3.$ip4" eq "0.0.0.0");
    return (0, "All network broadcast")
        if ("$ip1.$ip2.$ip3.$ip4" eq "255.255.255.255");

    # If we have a host netmask, we can skip the remaining tests
    return 1 if ($nm eq '255.255.255.255');

    # Network address is invalid for a host...
    my ($na1, $na2, $na3, $na4) = 
        ((0+$ip1 & 0+$nm1), (0+$ip2 & 0+$nm2),
        (0+$ip3 & 0+$nm3), (0+$ip4 & 0+$nm4));

    # Concatenate and compare
    return (0, "Lowest on the network")
        if ("$ip1.$ip2.$ip3.$ip4" eq "$na1.$na2.$na3.$na4");

    # Broadcast address is also invalid for a host...
    my ($bc1, $bc2, $bc3, $bc4) =
        ((0+$na1 ^ (0+$nm1 ^ 255)), (0+$na2 ^ (0+$nm2 ^ 255)),
        (0+$na3 ^ (0+$nm3 ^ 255)), (0+$na4 ^ (0+$nm4 ^ 255)));

    # Concatenate and compare
    return (0, "Highest on the network")
        if ("$ip1.$ip2.$ip3.$ip4" eq "$bc1.$bc2.$bc3.$bc4");

    return (1, undef);
}

sub netmask_is_valid {
    # Determine if a specified netmask is valid
    # Return 1 if it is, 0 otherwise
    # Takes Netmask

    return 0 unless (scalar(@_) == 1);

    my $nm = shift;
    return 0 unless ($nm =~ /^\d+\.\d+\.\d+\.\d+$/);

    foreach my $num (split(/\./, $nm)) {
        return 0 unless ($num < 256);
    }

    my ($nm1, $nm2, $nm3, $nm4) = split(/\./, $nm);

    return 0 if (($nm1 < $nm2) || ($nm2 < $nm3) || ($nm3 < $nm4));

    return 1;
}

sub gateway_is_local {
    # Figure out if a gateway is local
    # Return 1 if it is, 0 otherwise
    # Takes (IP Address, Netmask, Gateway) as arguments

    return 0 unless (scalar(@_) == 3);

    my ($ip, $nm, $gw) = @_;

    # Are the arguments valid?
    return 0 unless
        (($ip =~ /^\d+\.\d+\.\d+\.\d+$/) && 
        ($nm =~ /^\d+\.\d+\.\d+\.\d+$/) &&
        ($gw =~ /^\d+\.\d+\.\d+\.\d+$/));

    # If the gateway is the same as the IP, it's invalid
    return 0 if ($ip eq $gw);

    # Split the addresses into octets
    my ($ip1, $ip2, $ip3, $ip4) = split(/\./, $ip);
    my ($nm1, $nm2, $nm3, $nm4) = split(/\./, $nm);
    my ($gw1, $gw2, $gw3, $gw4) = split(/\./, $gw);

    # If the netmask is host-only, save a lot of trouble...
    return 0 if ("$nm1.$nm2.$nm3.$nm4" eq "255.255.255.255");

    # Figure out the minimum and maximum address space
    # Network address is the minimum...
    my ($na1, $na2, $na3, $na4) =
        ((0+$ip1 & 0+$nm1), (0+$ip2 & 0+$nm2),
        (0+$ip3 & 0+$nm3), (0+$ip4 & 0+$nm4));

    # Broadcast is the maximum...
    my ($bc1, $bc2, $bc3, $bc4) =
        ((0+$na1 ^ (0+$nm1 ^ 255)), (0+$na2 ^ (0+$nm2 ^ 255)),
        (0+$na3 ^ (0+$nm3 ^ 255)), (0+$na4 ^ (0+$nm4 ^ 255)));

    # Is the gateway between these two?
    if ((($gw1 <= $bc1) && ($gw1 >= $na1)) &&
        (($gw2 <= $bc2) && ($gw2 >= $na2)) &&
        (($gw3 <= $bc3) && ($gw3 >= $na3)) &&
        (($gw4 <= ($bc4 - 1)) && ($gw4 >= ($na4 + 1)))) {
        return 1;
    } else {
        return 0;
    }

    # Never reached
    return 0;
}

sub valid_network_hosts {
    # Given an IP and netmask, determine the valid range of hosts
    # on the network.
    # Takes ($ip, $netmask)
    # Returns ($minimum_ip, $maximum_ip)

    my ($ip, $nm) = @_;

    return (undef, undef) unless ($ip && $nm);

    # If the netmask is for a host address, there is no minimum and maximum
    return (undef, undef) if ($nm eq "255.255.255.255");

    my ($ip1, $ip2, $ip3, $ip4) = split(/\./, $ip);
    my ($nm1, $nm2, $nm3, $nm4) = split(/\./, $nm);

    my ($na1, $na2, $na3, $na4) = ((0+$ip1 & 0+$nm1), (0+$ip2 & 0+$nm2),
        (0+$ip3 & 0+$nm3), (0+$ip4 & 0+$nm4));

    my ($bc1, $bc2, $bc3, $bc4) = ((0+$na1 ^ (0+$nm1 ^ 255)),
        (0+$na2 ^ (0+$nm2 ^ 255)), (0+$na3 ^ (0+$nm3 ^ 255)),
        (0+$na4 ^ (0+$nm4 ^ 255)));

    # Minimum is one over the network, maximum is one under the broadcast
    return ("$na1.$na2.$na3.".((0+$na4) + 1), "$bc1.$bc2.$bc3.".((0+$bc4) - 1));
}

sub find_portgroup {
    # Find a portgroup within a vswitches structure
    # Takes ("Port Group", %vswitches), but is smart enough to
    # get %vswitches if not specified
    # Returns ($switch, %portgroup)

    my ($pgroup, $vswitches) = @_;

    return undef unless $pgroup;

    $vswitches = get_vswitches() unless $vswitches;

    foreach my $key (keys %$vswitches) {
        if ($vswitches->{$key}{'pgroups'}) {
            foreach my $vspg (keys %{$vswitches->{$key}{'pgroups'}}) {
                if ($pgroup eq $vspg) {
                    return ($key, $vswitches->{$key}{'pgroups'}{$pgroup});
                }
            }
        }
    }

    return undef;
}

sub portgroup_is_up {
    # Determines if the uplinks for a portgroup are all up
    # returns ($up, $down, $downnics)
    # Takes ($pgname, %vswitches, %nics);

    my ($name, $vswitches, $nics) = @_;

    return (0, "No port group name specified") unless ($name);

    $vswitches = get_vswitches() unless $vswitches;
    $nics = get_nics unless $nics;

    # 2) Determine link on portgroup interfaces
    my ($pgswitch, $pgroup) = find_portgroup($name, $vswitches);

    my %pgstatus;
    $pgstatus{'linkup'} = $pgstatus{'linkdown'} = 0;
    $pgstatus{'downnics'} = ();

    foreach my $uplink (sort @{$pgroup->{'uplinks'}}) {
        if ($nics->{$uplink}{'link'} eq 'Up') {
            $pgstatus{'linkup'}++;
        } else {
            $pgstatus{'linkdown'}++;
            push @{$pgstatus{'downnics'}}, $uplink;
        }
    }

    if ($pgstatus{'downnics'}) {
        return ($pgstatus{'linkup'}, $pgstatus{'linkdown'},
            \@{$pgstatus{'downnics'}});
    } else {
        return ($pgstatus{'linkup'}, $pgstatus{'linkdown'},
            undef);
    }
}

sub arping_dupe_check {
    # Check for a duplicate IP using arping
    # Takes ($interface, $ip)
    # Returns (1, $othermac) if there's a duplicate, (0, undef) otherwise

    my ($interface, $ip) = @_;

    return (1, "ERROR") unless ($interface && $ip);

    my @lines = qx(/sbin/arping -c 2 -w 3 -D -I $interface $ip);

    return (0, undef) if not $?;

    foreach my $line (@lines) {
        debug_print(1, "$self [DEBUG] arping_dupe_check: $line");

        if ($line =~ /.*\s+reply from\s+.*\[(.*)\]\s+for.*\[(.*)\]\s+.*/) {
            # TODO: Handle cases of multiple MACs
            return (1, $2);
        }
    }
}

sub ping_connect_check {
    # Ping a host
    # Takes $ip and optionally $interface
    # Returns the loss rate

    my ($host, $interface) = @_;

    return undef unless $host;

    my $cmd = "ping -c 3 -w 3";
    if ($interface) {
        $cmd .= " -I $interface";
    }

    $cmd .= " $host";

    my @lines = qx($cmd);

    my $loss = undef;
    foreach my $line (@lines) {
        debug_print(1, "$self [DEBUG] ping_connect_check: $line");

        $loss = 0+$1 if ($line =~
            /\d+ packets transmitted,.*\s+(\d+)% packet loss/);
    }

    return $loss;
}

sub arping_connect_check {
    # Check for connectivity using arping
    # Takes $ip and optionally $interface
    # returns loss percentage

    my ($ip, $interface) = @_;

    return 100 unless $ip;

    my $cmd = "arping -c 2 -w 3";

    if ($interface) {
        $cmd .= " -I $interface";
    }

    $cmd .= " $ip";

    my @lines = qx($cmd);
    my $sent = 0;
    my $recv = 0;

    foreach my $line (@lines) {
        debug_print(1, "$self [DEBUG] arping_connect_check: $line");
        if ($line =~ /^Sent (\d+) probes.*/) {
            $sent = $1;
        }

        if ($line =~ /^Received (\d+) response.*/) {
            $recv = $1;
        }
    }

    return 100 unless ($sent);

    return (100 - (($recv / $sent) * 100));
}

sub vswif_exists {
    # Determine if the named vswif exists

    my ($interface, $vswifs) = @_;

    return 0 unless $interface;
    $vswifs = get_vswifs() unless $vswifs;

    foreach my $vswif (keys %$vswifs) {
        return 1 if ($interface eq $vswif);
    }

    return 0;
}

sub diag_vswif {
    # Performs diagnostics on a vswif interface
    # Takes ($name, %interface), optionally (%vswitches, %nics)
    # returns an array of text error messages

    my ($name, $interface, $vswitches, $nics) = @_;

    return undef unless ($name && $interface);

    $nics = get_nics() unless $nics;
    $vswitches = get_vswitches() unless $vswitches;

    my @errors;

    # Per Krishna, do these steps in this order...
    # 1) Verify IP configuration
    #   a) Validate IP (not 127, 224, network, or broadcast)

    # The IP check is dependent on the netmask check...
    my $result = netmask_is_valid($interface->{'mask'});

    screen_printf "    %-30s: %s\n",
        "Netmask ($interface->{'mask'})", $result ? "ok" : "INVALID";

    push @errors, "Netmask is invalid" unless $result;

    my $reason;
    if ($result) {
        ($result, $reason) =
            ip_is_valid($interface->{'ip'}, $interface->{'mask'});

        push @errors, "IP address is invalid: $reason" unless $result;

        screen_printf "    %-30s: %s\n", "IP Address ($interface->{'ip'})",
            $result ? "ok" : "INVALID\n        $reason";
    }

    #   b) Validate gateway (within network)
    if ($interface->{'gw'}) {
        $result = gateway_is_local($interface->{'ip'},
            $interface->{'mask'}, $interface->{'gw'});
        push @errors, "Gateway is not on the local network" unless $result;
        screen_printf "    %-30s: %s\n",
            "Gateway ($interface->{'gw'})", $result ? "ok" : "INVALID";
    } else {
        screen_printf "    %-30s: %s\n",
            "Gateway (NONE)", "skipped";
    }

    # 2) Determine link on portgroup interfaces
    my ($up, $down, $downnics) =
        portgroup_is_up($interface->{'pgroup'}, $vswitches, $nics);

    #   a) At least one must have link
    #   b) Warn for physical nics without link
    #   c) Error for no link
    if ($up and not $down) {
        # All of the nics are up
        screen_printf "    %-30s: %s\n",
            "Uplinks", "ok";
    } elsif ($up and $down) {
        # At least one uplink is available
        screen_printf "    %-30s: %s\n",
            "Uplinks", "SOME DOWN";
        screen_print "        Down uplinks: ".join(', ', @$downnics)."\n";
        push @errors, "Some uplinks are down: ".join(', ', @$downnics);
    } elsif ($down and not $up) {
        # All the uplinks are down
        screen_printf "    %-30s: %s\n",
            "Uplinks", "ALL DOWN";
        screen_print "        Down uplinks: ".join(', ', @$downnics)."\n";
        push @errors, "All uplinks are down: ".join(', ', @$downnics);
        return @errors;
    }

    # 3) Look for conflicting IP address on network
    #   a) use arping for this
    screen_print "Checking for duplicate IPs...";

    ($result, $reason) = arping_dupe_check($name, $interface->{'ip'});

    if ($result) {
        screen_printf "\r    %-30s: %s\n",
            "Duplicate IP Check", "FAILED";
        screen_printf "        MAC $reason responds to IP $interface->{'ip'}";
        push @errors, "IP address appears to be a duplicate: ".
            "MAC $reason responds to $interface->{'ip'}";
        return @errors;
    } else {
        screen_printf "\r    %-30s: %s\n",
            "Duplicate IP Check", "ok";
    }

    # 4) Ping gateway, if configured
    if ($interface->{'gw'}) {
        screen_print "Checking ICMP connectivity...";

        $result = ping_connect_check($interface->{'gw'}, $name);

        if (defined $result) {
            screen_printf "\r    %-30s: %s\n", "Gateway Connectivity (ICMP)",
                ($result) ? "FAILED ($result% loss)" : "ok";

            push @errors,
                "Gateway ICMP connectivity problem: $result% packet loss" 
                if $result; 
        } else {
            screen_printf "\r    %-30s: %s\n", "Gateway Connectivity (ICMP)",
                "FAILED (Unknown Failure)";
            push @errors, "Gateway ICMP connectivity problem: unknown failure";
            $result = 100;
        }
    } else {
        screen_printf "    %-30s: %s\n", "Gateway Connectivity (ICMP)", "skipped";
    }

    # 5) If pinging the gateway fails, arping the gateway
    if ($result > 0 && $interface->{'gw'}) {
        screen_print "Checking ARP connectivity...";
        $result = arping_connect_check($interface->{'gw'}, $name);

        if ($result) {
            screen_printf "\r    %-30s: %s\n", "Gateway Connectivity (ARP)",
                "FAILED ($result% loss)";

            push @errors,
                "Gateway ARP connectivity problem: $result% packet loss";
        } else {
            screen_printf "\r    %-30s: %s\n", "Gateway Connectivity (ARP)",
                "ok";
        }
    }
    return @errors;
}

sub diag_vmknic {
    # Run diagnostics on a vmknic.  This is a duplicate of the above sans
    # the checks we cannot run due to missing data.  At a later date
    # these two tests will hopefully be combined into one.

    my ($name, $interface, $vswitches, $nics) = @_;

    return undef unless ($name && $interface);

    $vswitches = get_vswitches() unless $vswitches;
    $nics = get_nics() unless $nics;

    my @errors;

    # We do not (yet) have easy access to arping via a vmk interface
    # nor gateway information, so those steps are skipped here...

    # Per Krishna, do these steps in this order...
    # 1) Verify IP configuration
    #   a) Validate IP (not 127, 224, network, or broadcast)
    my ($result, $reason) =
        ip_is_valid($interface->{'ip'}, $interface->{'mask'});

    push @errors, "IP address is invalid" unless $result;

    screen_printf "    %-30s: %s\n", "IP Address ($interface->{'ip'})",
        $result ? "ok" : "INVALID\n        $reason\n";
   
    $result = netmask_is_valid($interface->{'mask'});
    push @errors, "Netmask is invalid" unless $result;
 
    screen_printf "    %-30s: %s\n",
        "Netmask ($interface->{'mask'})",
        $result ? "ok" : "INVALID";

    #   b) Validate gateway (within network)
    # TODO: Skipped as we don't have gateway information
    
    # 2) Determine link on portgroup interfaces
    my ($up, $down, $downnics) =
        portgroup_is_up($name, $vswitches, $nics);

    #   a) At least one must have link
    #   b) Warn for physical nics without link
    #   c) Error for no link
    if ($up and not $down) {
        # All of the nics are up
        screen_printf "    %-30s: %s\n",
            "Uplinks", "ok";
    } elsif ($up and $down) {
        # At least one uplink is available
        screen_printf "    %-30s: %s\n",
            "Uplinks", "SOME DOWN";
        screen_print "        Down uplinks: ".join(', ', @$downnics)."\n";
        push @errors, "Some uplinks are down: ".join(', ', @$downnics);
    } elsif ($down and not $up) {
        # All the uplinks are down
        screen_printf "    %-30s: %s\n",
            "Uplinks", "ALL DOWN";
        screen_print "        Down uplinks: ".join(', ', @$downnics)."\n";
        push @errors, "All uplinks are down: ".join(', ', @$downnics);
        return @errors;
    }

    # 3) Look for conflicting IP address on network
    #   a) use arping for this
    # TODO: Skipped as we can't arping over a vmknic yet

    # 4) Ping gateway, if configured
    # TODO: Skipped as we can't ping over a vmknic yet

    # 5) If pinging the gateway fails, arping the gateway
    # TODO: Skipped as we can't arping over a vmknic yet
}

sub esx3net_diag {
    #-------------------------------------------------------------------
    # Perform a diagnostic on various parameters
    #-------------------------------------------------------------------
    my $vswifs = get_vswifs();
    my $vswitches = get_vswitches();
    my $nics = get_nics();
    my $vmknics = get_vmknics();

    # Notify the user there's nothing to do if no interfaces are configured
    unless (scalar(keys %$vswifs) || scalar(keys %$vmknics)) {
        screen_print "\nNo interfaces are configured.\n";
        return;
    }

    # Avoid printing the header if there's nothing to display...
    if (scalar(keys %$vswifs)) {
        screen_print '='x72, "\nConsole OS Interfaces\n", '='x72, "\n";

        foreach my $vswif (sort keys %$vswifs) {
            screen_print "$vswif:\n";
            diag_vswif($vswif, $vswifs->{$vswif}, $vswitches, $nics);
            screen_print "\n";
        }
    }

    if (scalar(keys %$vmknics)) {
        screen_print '='x72, "\nVMkernel Interfaces\n", '='x72, "\n";

        foreach my $vmknic (sort keys %$vmknics) {
            screen_print "$vmknic:\n";
            diag_vmknic($vmknic, $vmknics->{$vmknic}, $vswitches, $nics);
            screen_print "\n";
        }
    }
}

sub gen_unique_name {
    # Returns a unique filename, takes an optional parameter to specify
    # Some useful tag to be included

    my ($tag) = @_;

    $tag = int(rand(65535)) unless $tag;

    my $hostname = `hostname`;
    chomp $hostname;

    my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime;

    $year += 1900;
    $mon++;

    my $unique = "$hostname-$$-$year-$mon-$mday-$yday-$tag";

    return $unique;
}

sub run_command {
    # Run a command silently, displaying output if debugging mode is on
    my ($command) = @_;

    my $cname;

    return undef unless $command;

    if ($command =~ /^([-\w]+)\s+.*/) {
        $cname = $1;
    } else {
        $cname = $command;
    }

    my $pid = open(COMMAND, '-|');

    unless (defined $pid) {
        debug_print(1, "$self [DEBUG] run_command: failed to start \"$command\"\n");
        return undef;
    }

    if ($pid) {
        debug_print(1, "$self [DEBUG] spawned \"$command\" as PID $pid\n");

        while (<COMMAND>) {
            debug_print(1, "$self [DEBUG] $cname: $_");
        }

        # We want to emulate the system routine here, so we'll block
        # waiting for an exit

        close(COMMAND);
        return $?;
    } else {
        # Child
        open STDERR, '>&STDOUT';
        exec($command);
    }
}

sub tcpdump {
    # Run a background tcpdump process
    # Takes the options to tcpdump
    # returns (FILEHANDLE, $pid)

    my ($options) = @_;

    $options = '' unless $options;

    my $pid = open(my $fh, '-|');
    Panic("failed to start tcpdump") unless (defined $pid);

    if ($pid) {
        debug_print(1, "$self [DEBUG] spawned \"tcpdump $options\" as PID $pid\n");
        return ($fh, $pid);
    } else {
        # Run as the child...
        open STDERR, '>&STDOUT';
        exec("tcpdump $options");
    }
}

sub esx3net_vswif_trace {
    # Simplest trace scenario:
    #   User simply wants to watch for all traffic on an existing vswif

    my ($interface, $timeout, $options) = @_;
    return unless ($interface);

    $timeout = 0 unless $timeout;

    # First, let's make sure the vswif we want to trace even exists
    my $vswifs = get_vswifs();
    unless (vswif_exists($interface, $vswifs)) {
        die("$self [ERROR] Interface $interface not valid\n");
        return;
    };

    my $name = gen_unique_name($interface).".tcpdump";

    debug_print(1, "$self [DEBUG] Selected $name for destination...\n");

    screen_printf "Dumping network traffic data from $interface%s...\n",
        ($timeout > 0) ? " for $timeout second(s)" : '';

    my $dumpopts = "-i $interface -n -w \"$name\"";
    
    debug_print(1, "$self [DEBUG] Using options $dumpopts for tcpdump...\n");

    my ($fh, $pid) = tcpdump($dumpopts);

    Panic("unable to start tcpdump") unless $pid;

    # Install a signal handler for CTRL-C
    $SIG{'INT'} = sub { kill('INT', $pid); };

    if ($timeout > 0) {
        sleep $timeout;
        kill('INT', $pid);
    }

    while(<$fh>) {
        debug_print(1, "$self [DEBUG] tcpdump: $_");
    }

    close($fh);
    
    # Take out our handler
    $SIG{'INT'} = 'DEFAULT'; 

    if ($compress_output) {
        screen_print "Compressing output, please wait...";
        $name = create_gzip_archive($name);
        screen_print "done.\n";
    }

    if ($email_output) {
        screen_print "Encoding output, please wait...";
        $name = base64_encode($name);
        screen_print "done.\n";
    }

    screen_print "Dumping complete.\nData saved to $name\n";
}

sub create_archive {

    return undef unless @_;

    if (scalar(@_) == 1) {
        # There's only one item in the list, so no archive is needed
        return $_[0];
    }

    my $command = 'tar --remove-files -cvf ';
    my $tar_name = gen_unique_name('archive').'.tar';

    $command .= $tar_name.' ';

    debug_print(1, "$self [DEBUG] using $tar_name for archive...\n");

    foreach my $file (@_) {
        $command .= $file;
    }

    my $result = run_command($command);

    Panic("error creating archive") unless $result;

    return $tar_name;
}

sub create_gzip_archive {

    return undef unless @_;

    if (scalar(@_) == 1) {
        # There's only one item in the list, so just compress instead
        run_command("gzip -f \"$_[0]\"");
        return $_[0].'.gz';
    }

    my $command = 'tar --remove-files -czvf ';
    my $tar_name = gen_unique_name('archive').'.tgz';

    $command .= $tar_name.' ';

    debug_print(1, "$self [DEBUG] using $tar_name for archive...\n");

    foreach my $file (@_) {
        $command .= $file;
    }

    my $result = run_command($command);

    Panic("error creating archive") unless $result;

    return $tar_name;
}

sub find_empty_vswif {
    my ($vswifs) = @_;

    $vswifs = get_vswifs() unless $vswifs;

    # Find an available vswif for temporary use and return it
    my $lastnum = 0;

    foreach my $vswif (sort keys %$vswifs) {
        if ($vswif =~ /.*(\d+)$/) {
            $lastnum = $1 if ($1 > $lastnum);
        }
    }

    # Add one to the highest interface.  If it exceeds 125, return nothing.
    return 'vswif'.($lastnum + 1) unless (($lastnum + 1) > 125);

    return undef;
}

sub esx3net_pgroup_trace {
    # Trace a particular port group, may require creation of a vswif
    my ($portgroup, $timeout, $options) = @_;

    Panic("no port group specified to esx3net_pgroup_trace")
        unless ($portgroup);
    $timeout = 0 unless $timeout;
    $options = '' unless $options;

    # Figure out if we've already got a vswif connected to this portgroup
    my $pgvswif;
    my $temporary = 0;
    my $vswifs = get_vswifs();

    foreach my $vswif (keys %$vswifs) {
        if ($vswifs->{$vswif}{'pgroup'} eq $portgroup) {
            # Bingo
            $pgvswif = $vswif;
            last;
        }
    }

    unless ($pgvswif) {
        # We didn't find one, so we need to create one
        $temporary = 1;

        # Does the port group we've been asked to trace even exist?
        my $vswitches = get_vswitches();
        Panic("failed to get vswitches") unless $vswitches;

        unless (find_portgroup($portgroup, $vswitches)) {
            die("$self [ERROR] port group \"$portgroup\" does not exist\n");
            return;
        };
       
        # Find a slot for our port group 
        $pgvswif = find_empty_vswif($vswifs);
    }

    unless($pgvswif) {
        die("$self [ERROR] no vswif available for temporary connection\n");
        return;
    }

    debug_print(1, "$self [DEBUG] Using $pgvswif for temporary connection\n");

    # FIXME: Ideally, we would be able to create a vswif without IP
    #        information.  For now, we just make up an IP address and
    #        NOARP it.

    if ($temporary) {
        # Create the vswif and then NOARP it... there's a very, very
        # slim chance that a rogue arp response could fly out while
        # we're doing this, but there's not much that can be done
        # about that.

        # We need a random IP address... we pick something in
        # 192.168 and use a host netmask to minimize the chance of a
        # rogue arp response ruining someone's day.

        my $tempip = "192.168.".int(rand(255)).".".int(rand(255));

        Panic("Failed to properly create vswif") if 
            run_command("esxcfg-vswif -a $pgvswif -i $tempip -n 255.255.255.255 -p \"$portgroup\"");

        if(run_command("ifconfig $pgvswif -arp")) {
            # ifconfig had some sort of problem... pull out the interface
            # we just created and die

            run_command("esxcfg-vswif -d $pgvswif");
            Panic("ifconfig failed to NOARP $pgvswif");
        }
    }

    my $name = gen_unique_name($portgroup).".tcpdump";

    debug_print(1, "$self [DEBUG] Selected $name for destination...\n");

    screen_printf "Dumping network traffic data from \"$portgroup\"%s...\n",
        ($timeout > 0) ? " for $timeout second(s)" : '';

    my $dumpopts = "-i $pgvswif -n -w \"$name\"";

    debug_print(1, "$self [DEBUG] Using options $dumpopts for tcpdump...\n");

    my ($fh, $pid) = tcpdump($dumpopts);

    Panic("unable to start tcpdump") unless $pid;

    # Install a signal handler for CTRL-C
    $SIG{'INT'} = sub { kill('INT', $pid); };

    if ($timeout > 0) {
        sleep $timeout;
        kill('INT', $pid);
    }

    while(<$fh>) {
        debug_print(1, "$self [DEBUG] tcpdump: $_");
    }

    # Take out our handler
    $SIG{'INT'} = 'DEFAULT';

    close($fh);

    if ($compress_output) {
        screen_print "Compressing output, please wait...";
        $name = create_gzip_archive($name);
        screen_print "done.\n";
    }

    if ($email_output) {
        screen_print "Encoding output, please wait...";
        $name = base64_encode($name);
        screen_print "done.\n";
    }

    screen_print "Dumping complete.\nData saved to $name\n";

    if ($temporary) {
        # Pull out our temporary interface
        run_command("esxcfg-vswif -d $pgvswif");
    }
}

sub base64_encode {
    # TODO: More robust error handling here
    use MIME::Base64 ();

    return undef unless (scalar(@_) == 1);
    my $file = shift;

    open(my $ifh, "< $file") or return undef;

    open(my $ofh, "> $file.uue") or return undef;

    binmode($ifh);

    print $ofh "begin-base64 0644 $file\n";

    my $buffer;
    while (read($ifh, $buffer, 1024*57)) {
        print $ofh MIME::Base64::encode($buffer);
    }

    print $ofh "====\n";

    close $ifh;
    close $ofh;

    unlink $file;

    return "$file.uue";
}

sub esx3net_trace {
    # A router for the various types of traces that are possible

    # We need tcpdump installed... error out if it's not
    unless (-f '/usr/sbin/tcpdump') {
        screen_print("$self [ERROR] The 'tcpdump' package does not appear to be installed.\n");
        screen_print("The package is required for the 'trace' command.\n");
        screen_print("Please install the package and retry.\n");
        exit(2);
    }

    esx3net_help() unless $trace_type;

    if ($trace_type eq 'vswif') {
        esx3net_vswif_trace($trace_item, $trace_timeout, $tcpdump_opts);
    } elsif ($trace_type eq 'pgroup') {
        esx3net_pgroup_trace($trace_item, $trace_timeout, $tcpdump_opts);
    }
}

sub esx3net_rebuild {
    # A function to get vswif0 into a usable state
    # Takes %vswifs, %vswitches, %nics optionally
    # Returns nothing

    my ($vswifs, $vswitches, $nics) = @_;
    $vswifs = get_vswifs() unless $vswifs;
    $vswitches = get_vswitches() unless $vswitches;
    $nics = get_nics() unless $nics;

    my $create = 0;

    if ($vswifs->{'vswif0'}) {
        # The interface exists, so presumably there's a configuration
        # problem... check the obvious items and give the user an
        # opportunity to fix them.

        screen_print "Analyzing interface vswif0:\n";
        my @errors = diag_vswif('vswif0', $vswifs->{'vswif0'}, $vswitches, $nics);

        # Add the port group to the display...
        screen_printf "    %-30s: %s\n", 'Port Group', $vswifs->{'vswif0'}{'pgroup'};

        # Check to see if there are any NICs assigned to the portgroup
        # vswif0 is bound to.  This check is not done in the diag.
        my ($pgswitch, $portgroup) = find_portgroup($vswifs->{'vswif0'}{'pgroup'}, $vswitches);

        screen_printf "    %-30s: %s\n", 'VLAN', $portgroup->{'vlan'};

        unless (scalar($portgroup->{'uplinks'})) {
            # There are no nics...
            screen_printf "    %-30s: %s\n", 'Uplink NICs', 'NONE';
            push @errors, "No NICs are bound to the port group ".$vswifs->{'vswif0'}{'pgroup'};
        } else {
            screen_printf "    %-30s: %s\n", 'Uplink NICs',
                join(', ', @{$portgroup->{'uplinks'}});
        }

        if (scalar(@errors)) {
            screen_print "\nSome errors were found:\n    ";
            screen_print join("\n    ", @errors);
            screen_print "\n";
        }

        screen_printf "\nThis interface %s currently using DHCP\n",
            ($vswifs->{'vswif0'}{'dhcp'} eq 'true') ? 'IS' : 'is not';

        screen_print "\n";
    } else {
        $create = 1;
    }

    # Warn the user about the automatic nature of this script...
    screen_print "WARNING: This utility will change the network\n";
    screen_print "configuration of this ESX server automatically.\n";
    screen_print "Any existing configuration will be permanently\n";
    screen_print "lost and unrecoverable.\n\n";

    screen_print "Do you wish to update the current configuration [y/N]? ";

    my $result = <STDIN>;
        
    unless ($result =~ /^(\s*[Yy]\w+\s*|\s*[Yy]\s*)$/) {
        # We got something other than yes...
        screen_print "\nAborted.\n";
        return;
    }

    my $masterokay = 0;
    until ($masterokay) {

        # Would you like DHCP with that, sir?
        screen_print "\nUse DHCP to auto-configure vswif0 [y/N]? ";
    
        my $dhcp = 0;
        $result = <STDIN>;
    
        if ($result =~ /^(\s*[Yy]\w+\s*|\s*[Yy]\s*)$/) {
            screen_print "Using DHCP to auto-configure.\n\n";
            $dhcp = 1;
        }
    
        my ($ip, $mask, $gw, $vlan);
    
        unless ($dhcp) {
            my $okay = 0;
    
            screen_print "\nEnter the IP configuration to use:\n";
    
            until ($okay) {
                until ($ip) {
                    screen_print "    IP Address?      : ";
        
                    $ip = <STDIN>;
                    chomp $ip;
                }
    
                until ($mask) {
                    screen_print "    Netmask?         : ";
                    $mask = <STDIN>;
                    chomp $mask;
                }
    
                ($result, my $reason) = ip_is_valid($ip, $mask);
                my $netresult = netmask_is_valid($mask);
    
    
                unless ($result && $netresult) {
                    screen_print "\n";
                    screen_print "IP $ip is not valid: $reason\n" unless $result;
                    screen_print "Netmask $mask is not valid\n" unless $netresult;
                    screen_print "\n";
                } else {
                    $okay = 1;
                }
    
                $ip = '' unless $result;
                $mask = '' unless $netresult;
            }
    
            $okay = 0;
            until ($okay) {
                until ($gw) {
                    screen_print "    Gateway?         : ";
                    $gw = <STDIN>;
                    chomp $gw;
                }
    
                $result = gateway_is_local($ip, $mask, $gw);
                unless ($result) {
                    screen_print "\nGateway $gw is not valid\n";
                    my ($min, $max) = valid_network_hosts($ip, $mask);
                    screen_print "Valid range is $min - $max\n";
                    $gw = '';
                } else {
                    $okay = 1;
                }
            }
        }

        my $okay = 0;
        until ($okay) {
            screen_print "    VLAN? (Optional) : ";

            $vlan = <STDIN>;
            chomp $vlan;

            $vlan = 0 if ($vlan eq '');

            unless ($vlan =~ /^\d$/) {
                screen_print "\nVLAN is not valid.  Enter a blank value for none.\n";
            } else {
                $okay = 1;
            }
        }

        $okay = 0;
        my $usenic = undef;
        until ($okay) {
            screen_print "\n    Please choose a NIC to attach to vswif0:\n";
            my %menu;
            my $count = 1;
            foreach my $nic (sort keys %$nics) {
                screen_printf "        %2.2s. %-7.7s [%-4.4s] (%.48s)\n", $count,
                    $nic, $nics->{$nic}{'link'}, $nics->{$nic}{'description'};
                $menu{$count} = $nic;
                $count++;
            }
            screen_print "\n    Enter a number [1-".($count-1)."]? ";
            $result = <STDIN>;
            chomp $result;
            unless ($result =~ /^\d+$/) {
                screen_print "\nInvalid entry\n";
            } else {
                if ($result > ($count - 1) || $result < 1) {
                    screen_print "\nEntry is out of range\n";
                } else {
                    $usenic = $menu{$result};
                    $okay = 1;
                }
            }
        }
    
        screen_print "\nAre you sure you want to proceed [y/N]? ";
        $result = <STDIN>;
        unless ($result =~  /^(\s*[Yy]\w+\s*|\s*[Yy]\s*)$/) {
            screen_print "\nAborted.\n";   
            return;
        }
    
        unless ($vswitches->{'vSwitch0'}) {
            # Create vSwitch0 if it doesn't exist
            run_command('esxcfg-vswitch -a vSwitch0');
        } else {
            # Remove all uplinks from vSwitch0
            foreach my $uplink (@{$vswitches->{'vSwitch0'}{'uplinks'}}) {
                run_command("esxcfg-vswitch -U $uplink vSwitch0");
            }
        }

        # Unlink the nic from all other switches 
        foreach my $vswitch (keys %$vswitches) {
            next if ($vswitch eq 'vSwitch0');
            run_command("esxcfg-vswitch -U $usenic $vswitch");
        }

        # Link the nic to vSwitch0 
        run_command("esxcfg-vswitch -L $usenic vSwitch0");
    
        # Find port group "Service Console", creating it if needed
        my ($pgswitch, $portgroup) = find_portgroup('Service Console', $vswitches);
    
        unless ($portgroup) {
            # It must not exist
            run_command("esxcfg-vswitch -A \"Service Console\" vSwitch0");
        }

        # Now that it exists... set it to the VLAN
        run_command("esxcfg-vswitch -v $vlan -p \"Service Console\" vSwitch0");
    
        # Update the gateway information
        open(my $netconf, "< /etc/sysconfig/network");
        my @netconflines;
    
        while (<$netconf>) {
            push @netconflines, $_;
        }
    
        close($netconf);
    
        open($netconf, "> /etc/sysconfig/network");
    
        my $gwline = 0;
        my $gwdevline = 0;
        foreach my $line (@netconflines) {
            if ($line =~ /^GATEWAY=/) {
                if ($dhcp) {
                    next;
                } else { 
                    # Write out our own GATEWAY line
                    print $netconf "GATEWAY=$gw\n";
                    $gwline = 1;
                }
            } elsif ($line =~ /^GATEWAYDEV=/) {
                if ($dhcp) {
                    next;
                } else {
                    # Write out our own GATEWAYDEV line
                    print $netconf "GATEWAYDEV=vswif0\n";
                    $gwdevline = 1;
                }
            } else {
                print $netconf $line;
            }
        }

        if (!$dhcp) {
            print $netconf "GATEWAY=$gw\n" if (!$gwline);
            print $netconf "GATEWAYDEV=vswif0\n" if (!$gwdevline);
        }
    
        close ($netconf);

        # Okay, create/reconfigure vswif0
        my $command = 'esxcfg-vswif ';
    
        $command .= '-a ' if ($create);
    
        if ($dhcp) {
            $command .= '-i DHCP ';
        } else {
            $command .= "-i $ip -n $mask ";
        }
    
        $command .= '-p "Service Console" vswif0';
    
        run_command($command);

        # Wait a moment so everything can catch up
        sleep 1;
        
        screen_print "\nChecking connectivity with new settings...\n";

        # Verify gateway connectivity
        if ($dhcp) {
            $vswifs = get_vswifs();
            
            $gw = $vswifs->{'vswif0'}{'gw'};

            if (!defined($gw)) {
                screen_print "DHCP either failed or did not assign us a gateway.\n";
            }
        }

        if (defined($gw) && $gw) {
            $result = ping_connect_check($gw, 'vswif0');
            if ($result > 0) {
                screen_print "WARNING: ICMP connectivity check fails to gateway $gw.\n";
                $result = arping_connect_check($gw, 'vswif0');
    
                if ($result > 0) {
                    screen_print "WARNING: ARP connectivity checks fail to gateway $gw.\n";
                } else {
                    screen_print "Although the ICMP check fails, the gateway appears reachable via ARP.\n";
                }
    
            } else {
                screen_print "\nConnectivity check succeded.\n";
                $masterokay = 0;
            }
        } else {
            screen_print "Gateway connectivity check skipped.\n";
        }

        screen_print "\nDo you want to try a different configuration [y/N]? ";
        $result = <STDIN>;
        unless ($result =~ /^(\s*[Yy]\w+\s*|\s*[Yy]\s*)$/) {
            screen_print "\n";
            $masterokay = 1;
        }
    }
}

#-----------------------------------------------------------------------

__END__

=head1 NAME

esxnet-support - perform network troubleshooting in ESX 3.0

=head1 SYNOPSIS

esxnet-support diag [B<-D> <debug level>]

esxnet-support rebuild [B<-D> <debug level>]

=head1 DESCRIPTION

The esxnet-support script provides troubleshooting assistance for the ESX 3.0 networking stack by automating tasks such as network tracing, configuration sanity checking, and emergency rebuilds.

=head1 OPTIONS

Options are context-sensitive for each command available.

=over

=item diag [B<-D> <debug level>]:

Verifies network configuration and connectivity

=over

=item B<-D> <numeric level>:

Turns on debugging at the level specified

=back 

=item rebuild

Restores the default networking configuration to restore network access.

=over

=item B<-D> <numeric level>:

Turns on debugging at the level specified

=back

=item man

Displays the man page for esxnet-support

=item help

Displays the usage page for esxnet-support

=item version

Displays version information.

=back

=head1 COMMANDS

=over

=item B<diag>

Verifies the IP configuration on all configured vswif interfaces and verifies network connectivity to the outside world.  Virtual switch uplinks are also checked for link-level connectivity.

=item B<rebuild>

Rebuilds the default networking configuration to restore network connectivity.  This command is primarily used to erase the networking configuration after a bad configuration is saved either by the installer or a restored backup.  Once connectivity is restored, the Virtual Infrastructure Client can be used to manage the system.

=item B<man>

Displays a detailed troff-formatted man page for esxnet-support

=item B<help>

Displays an abbreviated usage page describing the options available for esxnet-support 

=item B<version>

Displays version information.

=back

=cut
