技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

SNMP trap (coldStart) 送信スクリプト

以前作成した以下の perl スクリプトをエンハンスした。
akrad.hatenablog.com

具体的には、SNMPv1 のcoldStartトラップも SNMPv2cのcoldStartトラップも送信できるようにし、トラップ送信先やSNMPv1トラップの agent-addrフィールドも指定できるようにした。
使い方の例は以下。


1.1.1.1 に、SNMPv2cのcoldStartトラップを送信する。

perl sendTrap.pl -v 2 -d 1.1.1.1


1.1.1.1 に、agent-addrフィールドを 2.2.2.2 として、SNMPv1のcoldStartトラップを送信する。

perl sendTrap.pl -v 1 -d 1.1.1.1 -a 2.2.2.2


作成したperlスクリプトは以下の通り。
sendTrap.pl

use strict;
use warnings;
use Socket;
use Getopt::Long 'GetOptions';

#---------------
# Constant value
#---------------
my $V1_TRAP   = 'a4';
my $V2c_TRAP  = 'a7';
my $SNMP_V1   = 0;
my $SNMP_V2C  = 1;

my $INTEGER      = '02';
my $OCTET_STRING = '04';
my $OBJECT_ID    = '06';
my $SEQUENCE     = '30';
my $IP_ADDRESS   = '40';
my $TIME_TICKS   = '43';

#-------------------
# Set default value
#-------------------
my $destAddr      = 'localhost';
my $destPort      = 162;
my $snmpVersion   = '2';
my $enterpriseOid = '1.2.3.4.5';
my $agentAddr     = '127.0.0.1';

GetOptions(
    'dest=s'      => \$destAddr,
    'version=s'   => \$snmpVersion,
    'agentaddr=s' => \$agentAddr
);

#-------------------
# Check user's input
#-------------------
if ($destAddr =~ /^\d/) { # This is an ip address. Because hostname doesn't start from a digit.
    if (!isIpAddress($agentAddr)) {
        die("$destAddr is not an ip address.");
    }
}

if (($snmpVersion ne '1') and ($snmpVersion ne '2') and ($snmpVersion ne '2c')) {
    die("$snmpVersion must be 1 or 2(2c)");
}

if (!isIpAddress($agentAddr)) {
    die("$agentAddr is not an ip address.");
}


#---------------
# MAIN
#---------------
my $socket;
socket($socket, PF_INET, SOCK_DGRAM, 0) or die "Failed to create socket. $!";

my $addrBin = inet_aton($destAddr);
my $sockAddr = pack_sockaddr_in($destPort, $addrBin);

my $trapData;
if ($snmpVersion eq '1') {
    $trapData = constractV1TrapData();
}
elsif (($snmpVersion eq '2') or ($snmpVersion eq '2c')) {
    $trapData = constractV2cTrapData();
}
else {
    die("Need to specify 1 or 2(2c) for version.");
}

my $trapDataBin = pack("H*", $trapData);
send($socket, $trapDataBin, 0, $sockAddr) or die "Failed to send. $!";


#---------------
# Sub functions
#---------------
sub constractV1TrapData {
    my $snmpVersion  = $SNMP_V1;
    my $commName     = 'public';
    my $pduType      = $V1_TRAP;
    my $requestID    = 10;
    my $errorStatus  = 0;
    my $errorIndex   = 0;
    my $genericTrap  = 0;
    my $specificTrap = 0;
    my $timeStamp    = 0;

    my $snmpVersionBerUnit   = constractBerData($INTEGER, convertDecNumToHexStr($snmpVersion));
    my $commNameBerUnit      = constractBerData($OCTET_STRING, unpack("H*", $commName));
    my $enterpriseOidBerUnit = constractBerData($OBJECT_ID, convertOidStrToHexStr($enterpriseOid));
    my $agentAddrBerUnit     = constractBerData($IP_ADDRESS, convertIpAddrStrToHexStr($agentAddr));
    my $genericTrapBerUnit   = constractBerData($INTEGER, convertDecNumToHexStr($genericTrap));
    my $specificTrapBerUnit  = constractBerData($INTEGER, convertDecNumToHexStr($specificTrap));
    my $timeStampBerUnit     = constractBerData($TIME_TICKS, convertDecNumToHexStr($timeStamp));
    my $emptyVarbindBerUnit  = constractBerData($SEQUENCE, "");
    
    my $trapPduLength = getBinLengthFromHexStr(
                            $enterpriseOidBerUnit,
                            $agentAddrBerUnit,
                            $genericTrapBerUnit,
                            $specificTrapBerUnit,
                            $timeStampBerUnit,
                            $emptyVarbindBerUnit);
    
    my $totalength = getBinLengthFromHexStr(
                        $snmpVersionBerUnit, 
                        $commNameBerUnit, 
                        $pduType,
                        convertDecNumToHexStr($trapPduLength))
                     + $trapPduLength;

    my $trapData = $SEQUENCE . 
                   convertDecNumToHexStr($totalength) .
                   $snmpVersionBerUnit . 
                   $commNameBerUnit . 
                   $pduType .
                   convertDecNumToHexStr($trapPduLength) .
                   $enterpriseOidBerUnit .
                   $agentAddrBerUnit .
                   $genericTrapBerUnit .
                   $specificTrapBerUnit .
                   $timeStampBerUnit .
                   $emptyVarbindBerUnit;
    return $trapData;
}

sub constractV2cTrapData {
    my $snmpVersion = $SNMP_V2C;
    my $commName    = 'public';
    my $pduType     = $V2c_TRAP;
    my $requestID   = 10;
    my $errorStatus = 0;
    my $errorIndex  = 0;

    my $snmpVersionBerUnit = constractBerData($INTEGER, convertDecNumToHexStr($snmpVersion));
    my $commNameBerUnit    = constractBerData($OCTET_STRING, unpack("H*", $commName));
    my $requestIDBerUnit   = constractBerData($INTEGER, convertDecNumToHexStr($requestID));
    my $errorStatusBerUnit = constractBerData($INTEGER, convertDecNumToHexStr($errorStatus));
    my $errorIndexBerUnit  = constractBerData($INTEGER, convertDecNumToHexStr($errorIndex));
    my $varbindData        = constractVarbindData();
    
    my $trapPduLength = getBinLengthFromHexStr(
                            $requestIDBerUnit,
                            $errorStatusBerUnit,
                            $errorIndexBerUnit,
                            $SEQUENCE,
                            convertDecNumToHexStr(getBinLengthFromHexStr($varbindData)),
                            $varbindData);
    
    my $totalength = getBinLengthFromHexStr(
                        $snmpVersionBerUnit, 
                        $commNameBerUnit, 
                        $pduType,
                        convertDecNumToHexStr($trapPduLength))
                     + $trapPduLength;

    my $trapData = $SEQUENCE . 
                   convertDecNumToHexStr($totalength) .
                   $snmpVersionBerUnit . 
                   $commNameBerUnit . 
                   $pduType .
                   convertDecNumToHexStr($trapPduLength) .
                   $requestIDBerUnit .
                   $errorStatusBerUnit .
                   $errorIndexBerUnit .
                   $SEQUENCE .
                   convertDecNumToHexStr(getBinLengthFromHexStr($varbindData)) .
                   $varbindData;
    return $trapData;
}

sub constractVarbindData {
    # ------------------------
    # sysUpTime
    # ------------------------
    my $sysUpTimeOid = '1.3.6.1.2.1.1.3.0';
    my $sysUpTimeOidBerData = constractBerData($OBJECT_ID, convertOidStrToHexStr($sysUpTimeOid));
    my $sysUpTime = 10;
    my $sysUpTimeBerData = constractBerData($TIME_TICKS, convertDecNumToHexStr($sysUpTime));
    my $sysUpTimeVarbindLength = getBinLengthFromHexStr($sysUpTimeOidBerData, $sysUpTimeBerData);
    my $sysUpTimeVarbind = $SEQUENCE . 
                           convertDecNumToHexStr($sysUpTimeVarbindLength) . 
                           $sysUpTimeOidBerData .
                           $sysUpTimeBerData;

    # ------------------------
    # sysTrapOID
    # ------------------------
    my $sysTrapOid = '1.3.6.1.6.3.1.1.4.1.0';
    my $sysTrapOidBerData = constractBerData($OBJECT_ID, convertOidStrToHexStr($sysTrapOid));
    my $sysTrapOidData = '1.3.6.1.6.3.1.1.5.1';
    my $sysTrapBerData = constractBerData($OBJECT_ID, convertOidStrToHexStr($sysTrapOidData));
    my $sysTrapVarbindLength = getBinLengthFromHexStr($sysTrapOidBerData, $sysTrapBerData);
    my $sysTrapVarbind = $SEQUENCE . 
                         convertDecNumToHexStr($sysTrapVarbindLength) . 
                         $sysTrapOidBerData .
                         $sysTrapBerData;
    
    return $sysUpTimeVarbind . $sysTrapVarbind;
}

sub isIpAddress {
    my $ipAddr = shift;
    my @octets = split(/\./, $ipAddr);
    return 0 if (@octets != 4);
    foreach my $octet (@octets) {
        if (!($octet =~ /^\d+$/) or !(0 <= $octet and $octet <= 255)) {
            return 0;
        }
    }
    return 1;
}

sub convertIpAddrStrToHexStr {
    my $ipAddrStr = shift;
    my @octets = split(/\./, $ipAddrStr);
    my $ipAddrHexStr;
    foreach my $octet (@octets) {
        $ipAddrHexStr .= convertDecNumToHexStr($octet);
    }
    return $ipAddrHexStr;
}

sub convertOidStrToHexStr {
    my $oidStr = shift;
    my @oids = split(/\./, $oidStr);
    my $hexStr = convertDecNumToHexStr(40*$oids[0] + $oids[1]);
    for (my $i = 2; $i <= $#oids; $i++) {
        if ($oids[$i] < 128) {
            $hexStr .= convertDecNumToHexStr($oids[$i]);
        }
        else {
            $hexStr .= convertOidNumToHexStrForMultiBytes($oids[$i]);
        }
    }
    return $hexStr;
}

sub convertOidNumToHexStrForMultiBytes {
    my $oidNum = shift;
    my $hexStr = "";
    my $counter = 0;

    my $workVal = $oidNum;
    while (1) {
        my $quotient  = int($workVal / 128);
        my $remainder = $workVal % 128;

        if ($counter != 0) {
            $remainder += 128;
        }
        $hexStr = convertDecNumToHexStr($remainder) . $hexStr;

        if ($quotient == 0) {
            last;
        }
        $workVal = $quotient;
        $counter++;
    }
    return $hexStr;
}

sub getBinLengthFromHexStr {
    my @hexStrs = @_;
    my $binLength = 0;
    foreach my $hexStr (@hexStrs) {
       $binLength += length($hexStr)/2;
    }
    return $binLength;
}

sub constractBerData {
    my ($tag, $data) = @_;
    $data =~ s/\s//g;
    my $binLength = (length($data))/2;
    my $berData = $tag . convertDecNumToHexStr($binLength) . $data;
    return $berData;
}

sub convertDecNumToHexStr {
    my $decNum = shift;
    my $hexStr = sprintf("%02x", $decNum);
    if (length($hexStr)%2 != 0) {
        $hexStr = '0' . $hexStr;
    }
    return $hexStr;
}


上記のスクリプト作成に当たっては、OID を BER(Basic Encoding Rules) 形式でエンコーディングするのがちょっと面倒だった。(convertOidStrToHexStr関数やconvertOidNumToHexStrForMultiBytes関数の辺り・・)
この辺の話については、別途、記事を書きたいと思っている。