Net::SNMPをつかってCisco機器からCDP情報を収集(3)

先日のつづき
Net::SNMPをつかって取得した値で人間が直接読めない形式で返答してきたoidがある。
例えば、

1.3.6.1.4.1.9.9.23.1.2.1.1.3.1.2		 1
1.3.6.1.4.1.9.9.23.1.2.1.1.4.1.2		 0xc0a801fc
1.3.6.1.4.1.9.9.23.1.2.1.1.9.1.2		    )
1.3.6.1.4.1.9.9.23.1.2.1.1.12.1.2		 3

など。これらについてはそれぞれMIBファイルには(一部省略)

CdpCacheEntry ::= SEQUENCE {
    cdpCacheAddressType             CiscoNetworkProtocol,
    cdpCacheAddress                 CiscoNetworkAddress,
    cdpCacheCapabilities            OCTET STRING, 
    cdpCacheDuplex                  INTEGER,
}

CiscoNetworkProtocol,CiscoNetworkAddress,OCTET STRING,INTEGER で値を返すと指定してある。それぞれが何を意味するかは、さらにMIBファイルをたどる必要がある。


  • CiscoNetworkProtocol
  • CiscoNetworkAddress

これらはCISCO-CDP-MIBファイルのヘッダー部に以下記載あるため、さらに別のMIBファイルをたどらなくてはならない。

CISCO-CDP-MIB DEFINITIONS ::= BEGIN

IMPORTS
        MODULE-IDENTITY, OBJECT-TYPE,
        Integer32, Unsigned32
                FROM SNMPv2-SMI
        MODULE-COMPLIANCE, OBJECT-GROUP
                FROM SNMPv2-CONF
        TruthValue, DisplayString, TimeStamp
                FROM SNMPv2-TC
        ciscoMgmt
                FROM CISCO-SMI
        CiscoNetworkProtocol, CiscoNetworkAddress
                FROM CISCO-TC 
        VlanIndex
                FROM CISCO-VTP-MIB
        ifIndex
                FROM IF-MIB
        ;

つまりは、CISCO-TC MIBに書いてあるから、そっちを読めってことになる。
こんどは、CISCO-TC MIBファイルをさらに確認する。(一部省略)

CiscoNetworkProtocol ::= TEXTUAL-CONVENTION
    STATUS          current
    DESCRIPTION
        "Represents the different types of network layer protocols."
    SYNTAX          INTEGER  {
                        ip(1),
                        decnet(2),
                        pup(3),
                        (省略)
                        http(25),
                        unknown(65535)
                    }

CiscoNetworkAddress ::= TEXTUAL-CONVENTION
    STATUS          current
    DESCRIPTION
        "Represents a network layer address.  The length and format of
        the address is protocol dependent as follows:
        ip        4 octets
        decnet    2 octets
        (省略)
    SYNTAX          OCTET STRING

なので

1.3.6.1.4.1.9.9.23.1.2.1.1.3.1.2		 1            =>(1)なのでIP
1.3.6.1.4.1.9.9.23.1.2.1.1.4.1.2		 0xc0a801fc   =>4octets(32ビット)表記のIPアドレス192.168.1.252

0xc0a801fc →
0xc0 = 192 , 0xa8 = 168 , 0x01 = 1 , 0xfc = 252

Net::SNMPをつかってCisco機器からCDP情報を収集(2)

前日からのつづき。
今日はPerlからはちょっと離れてMIBファイルについて

oid 1.3.6.1.4.1.9.9.23.1.2.1(cdpCacheTable)のMIBファイル

cdpCacheTable OBJECT-TYPE
    SYNTAX     SEQUENCE OF CdpCacheEntry
    MAX-ACCESS not-accessible
    STATUS     current
    DESCRIPTION
            "The (conceptual) table containing the cached
            information obtained via receiving CDP messages."
    ::= { cdpCache 1 }

cdpCacheEntry OBJECT-TYPE
    SYNTAX     CdpCacheEntry
    MAX-ACCESS not-accessible
    STATUS     current
    DESCRIPTION
            "An entry (conceptual row) in the cdpCacheTable,
            containing the information received via CDP on one
            interface from one device.  Entries appear when
            a CDP advertisement is received from a neighbor
            device.  Entries disappear when CDP is disabled
            on the interface, or globally."
    INDEX      { cdpCacheIfIndex, cdpCacheDeviceIndex }
    ::= { cdpCacheTable 1 }

CdpCacheEntry ::= SEQUENCE {
    cdpCacheIfIndex                Integer32,
    cdpCacheDeviceIndex            Integer32,
    cdpCacheAddressType             CiscoNetworkProtocol,
    cdpCacheAddress                 CiscoNetworkAddress,
    cdpCacheVersion                 DisplayString,
    cdpCacheDeviceId                DisplayString,
    cdpCacheDevicePort              DisplayString,
    cdpCachePlatform                DisplayString,
    cdpCacheCapabilities            OCTET STRING, 
    cdpCacheVTPMgmtDomain           DisplayString,
    cdpCacheNativeVLAN              VlanIndex, 
    cdpCacheDuplex                  INTEGER,
    cdpCacheApplianceID             Unsigned32,
    cdpCacheVlanID                  Unsigned32,
    cdpCachePowerConsumption        Unsigned32,
    cdpCacheMTU                     Unsigned32,
    cdpCacheSysName                 DisplayString,
    cdpCacheSysObjectID             OBJECT IDENTIFIER,
    cdpCachePrimaryMgmtAddrType     CiscoNetworkProtocol,
    cdpCachePrimaryMgmtAddr         CiscoNetworkAddress,
    cdpCacheSecondaryMgmtAddrType   CiscoNetworkProtocol,
    cdpCacheSecondaryMgmtAddr       CiscoNetworkAddress,
    cdpCachePhysLocation            DisplayString,
    cdpCacheLastChange               TimeStamp
}

先日のPerlの出力結果(見やすくするため一部省略)
同じ色は上記のMIBファイル内の同じEntry項目を示す

1.3.6.1.4.1.9.9.23.1.2.1.1.3.1.2		 1
1.3.6.1.4.1.9.9.23.1.2.1.1.3.3.1		 1
1.3.6.1.4.1.9.9.23.1.2.1.1.4.1.2		 0xc0a801fc
1.3.6.1.4.1.9.9.23.1.2.1.1.4.3.1		 0xc0a802fe
1.3.6.1.4.1.9.9.23.1.2.1.1.5.1.2		 Cisco IOS Software, C181X Soft 
1.3.6.1.4.1.9.9.23.1.2.1.1.5.3.1		 Cisco IOS Software, 7200 Soft 
1.3.6.1.4.1.9.9.23.1.2.1.1.6.1.2		 C1812J
1.3.6.1.4.1.9.9.23.1.2.1.1.6.3.1		 R2
1.3.6.1.4.1.9.9.23.1.2.1.1.7.1.2		 FastEthernet0
1.3.6.1.4.1.9.9.23.1.2.1.1.7.3.1		 FastEthernet1/0
1.3.6.1.4.1.9.9.23.1.2.1.1.8.1.2		 Cisco 1812-J
1.3.6.1.4.1.9.9.23.1.2.1.1.8.3.1		 Cisco 7206VXR
1.3.6.1.4.1.9.9.23.1.2.1.1.9.1.2		    )
1.3.6.1.4.1.9.9.23.1.2.1.1.9.3.1		 0x00000001
1.3.6.1.4.1.9.9.23.1.2.1.1.11.1.2		 0
1.3.6.1.4.1.9.9.23.1.2.1.1.11.3.1		 0
1.3.6.1.4.1.9.9.23.1.2.1.1.12.1.2		 3
1.3.6.1.4.1.9.9.23.1.2.1.1.12.3.1		 3

例として
1.3.6.1.4.1.9.9.23.1.2.1.1.8.3.1 は
cdpCacheTable(1.3.6.1.4.1.9.9.23.1.2.1)
cdpCacheEntry(.1)
cdpCachePlatform(.8)
cdpCacheIfIndex(.3)
cdpCacheDeviceIndex(.1)
と読み解く
後ろの(.3.1)がINDEX{ cdpCacheIfIndex, cdpCacheDeviceIndex }に該当する
この行はINDEX(.3.1)cdpCachePlatform(.8)Cisco 7206VXRであることを示す。


MIBファイル上はcdpCacheTableには24項目のデータが指定されているが、実際に機器が返答しているのはindexを除くと9項目だけとわかる。古いIOSからしかたがないのかも。最新のIOSを使えばもっと情報が取得できるのかもしれない。

機器へTelnetアクセスしてCLIにて取得したCDP情報

R1#show cdp neighbors
Capability Codes: R - Router, T - Trans Bridge, B - Source Route Bridge
                  S - Switch, H - Host, I - IGMP, r - Repeater

Device ID        Local Intrfce     Holdtme    Capability  Platform  Port ID
C1812J           Fas 0/0            121        R S I      1812-J    Fas 0
R2               Fas 1/0            179          R        7206VXR   Fas 1/0
R1#

Local Intrfce→cdpCacheIfIndex
Holdtme→cdpCacheTableでは取得できない様子

Net::SNMPをつかってCisco機器からCDP情報を収集(1)

Net::SNMP のサンプルスクリプトを参考に実行してみた。
CDP情報はoid 1.3.6.1.4.1.9.9.23.1.2.1(cdpCacheTable)にて取得できる。
oid検索にはCisco社のWebサイトSNMP Object Navigator内の「SEARCH」とかで検索。
ついてにCISCO-CDP-MIBファイルもダウンロード。
あとでどのoidが何の値を示しているか確認につかう。

use strict;
use warnings;
#use utf8;
#use Encode;
use Net::SNMP qw(:snmp);  # oid_lex_sort()を使うためにInport指定
use Data::Dumper;

my $OID_cdpCacheTable = '1.3.6.1.4.1.9.9.23.1.2.1';

my ( $session, $error ) = Net::SNMP->session(
    -hostname  => '192.168.1.253',   #実際のIPアドレスにあわせる
    -community => 'public',          #実際のCommunity名にあわせる
);

if ( !defined $session ) {
    printf "ERROR: %s.\n", $error;
    exit 1;
}

#結果は oid => 値 のハッシュリファレンスが返る
my $result = $session->get_table( -baseoid => $OID_cdpCacheTable, );

if ( !defined $result ) {
    printf "ERROR: %s\n", $session->error();
    $session->close();
    exit 1;
}

#print Dumper($result);
#print "cdpCacheTable\n$OID_cdpCacheTable\n";


#oid_lex_sort()にてoid値を数字順にソートしてハッシュ内容を表示
foreach my $get_oid ( oid_lex_sort( keys %$result ) ) {
    print "$get_oid\t\t $result->{$get_oid}\n";
}

$session->close();

出力結果

1.3.6.1.4.1.9.9.23.1.2.1.1.3.1.2		 1
1.3.6.1.4.1.9.9.23.1.2.1.1.3.3.1		 1
1.3.6.1.4.1.9.9.23.1.2.1.1.4.1.2		 0xc0a801fc
1.3.6.1.4.1.9.9.23.1.2.1.1.4.3.1		 0xc0a802fe
1.3.6.1.4.1.9.9.23.1.2.1.1.5.1.2		 Cisco IOS Software, C181X Software (C181X-ADVENTERPRISEK9-M), Version 12.4(22)T, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2008 by Cisco Systems, Inc.
Compiled Thu 09-Oct-08 20:27 by prod_rel_team
1.3.6.1.4.1.9.9.23.1.2.1.1.5.3.1		 Cisco IOS Software, 7200 Software (C7200-IS-M), Version 12.4(21), RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2008 by Cisco Systems, Inc.
Compiled Thu 10-Jul-08 11:36 by prod_rel_team
1.3.6.1.4.1.9.9.23.1.2.1.1.6.1.2		 C1812J
1.3.6.1.4.1.9.9.23.1.2.1.1.6.3.1		 R2
1.3.6.1.4.1.9.9.23.1.2.1.1.7.1.2		 FastEthernet0
1.3.6.1.4.1.9.9.23.1.2.1.1.7.3.1		 FastEthernet1/0
1.3.6.1.4.1.9.9.23.1.2.1.1.8.1.2		 Cisco 1812-J
1.3.6.1.4.1.9.9.23.1.2.1.1.8.3.1		 Cisco 7206VXR
1.3.6.1.4.1.9.9.23.1.2.1.1.9.1.2		    )
1.3.6.1.4.1.9.9.23.1.2.1.1.9.3.1		 0x00000001
1.3.6.1.4.1.9.9.23.1.2.1.1.11.1.2		 0
1.3.6.1.4.1.9.9.23.1.2.1.1.11.3.1		 0
1.3.6.1.4.1.9.9.23.1.2.1.1.12.1.2		 3
1.3.6.1.4.1.9.9.23.1.2.1.1.12.3.1		 3

機器へTelnetアクセスしてCLIにて取得したCDP情報

R1#show cdp neighbors
Capability Codes: R - Router, T - Trans Bridge, B - Source Route Bridge
                  S - Switch, H - Host, I - IGMP, r - Repeater

Device ID        Local Intrfce     Holdtme    Capability  Platform  Port ID
C1812J           Fas 0/0            121        R S I      1812-J    Fas 0
R2               Fas 1/0            179          R        7206VXR   Fas 1/0
R1#

CLIの結果やMIBファイルそしてNet::SNMPでの取得結果を見比べてスクリプトを修正していく。(つづく)

Win32::GUI::GetOpenFileName(ファイルダイアログ)をためす(2)

Win32::GUI::GetOpenFileNameをつかわない例
CLIアプリにも関わらずファイル選択はGUIで行いたい時に使用した。
当時はutf-8フラグなど意識せずshifjisにてファイル保存している。

  • Win32::OLEを使用したパターン
use strict;
use warnings;
use Win32;
use Win32::OLE;

Win32::MsgBox("使用した\nアドレスリストを選択", 0 | MB_ICONINFORMATION, "Check");

my $filter = 'IPアドレス リスト|*.txt|全てのファイル (*.*)|*.*';
my $IntDir = 'C:\\test';
my $objFD = Win32::OLE->new("UserAccounts.CommonDialog");
  $objFD->{'Filter'} = $filter;
  $objFD->{'InitialDir'} = $IntDir;
  $objFD->ShowOpen();
my $filename = $objFD->{FileName};

Win32::MsgBox($filename, 0 | MB_ICONINFORMATION, "チェック");
  • Tkxを使用したパターン。最近のActive Perlは最初からこのモジュールが入っている
use warnings;
use strict;
use Tkx;
use Encode qw(decode encode);
use Win32;

BEGIN {
	if ( $^O eq 'MSWin32' ) {
		require Win32::Console;
		Win32::Console::Free();
	}
}

my $jp_sjis = 'テキスト Files';

## TkxはUTF8でないと文字化けする。
my $jp_utf8 = decode ( 'shiftjis' , $jp_sjis); 
my $jp_title = decode ( 'shiftjis' , 'チェック'); 

my $types = [
     [$jp_utf8,       ['.txt', '.text']],
     ['MP3 Files',        ['.mp3', '.m4a']  ],
     ['All Files',        '*',             ],
 ];


my $mw = Tkx::widget->new(".");
$mw->g_wm_withdraw;


my $filename = Tkx::tk___getOpenFile(-filetypes=>$types,-parent=>$mw);
Tkx::tk___messageBox(-message=> $filename,-title => $jp_title ,-parent => $mw);

## $filenameはUTF8 で戻ってくるので sjisへ変換
$filename = encode ( 'cp932' , $filename ); 
Win32::MsgBox($filename, 0 | MB_ICONINFORMATION, "チェック");

Tkxは記述方法がよくわからない...

Win32::GUI::GetOpenFileName(ファイルダイアログ)をためす(1) 

ファイル名を取得するダイアログ表示のためにWin32::GUI::GetOpenFileNameを使用する

参考にした情報

ファイルダイアログ部分のみ抽出

use Cwd;
my $dir_name = cwd;       # ファイルパスが '/'で取得される
$dir_name =~ s/\//\\/g;   # ファイルパスが '/' だとダメなので '\'に変更

my $file = Win32::GUI::GetOpenFileName(
    -directory => $dir_name,
    -title     => encode( cp932 => "ログファイル選択" ),
    -filter    => [ encode( cp932 => "ログ(*.txt;*.log)" ) => '*.txt;*.log',
                   'All files'                             => '*.*',
                  ],
    -owner => $main,
);

以下全部

use strict;
use warnings;
use utf8;
use Encode;
use Win32::GUI();
use Cwd;

my $DOS = Win32::GUI::GetPerlWindow();
Win32::GUI::Hide($DOS);

my $con = 1;

my $main = Win32::GUI::Window->new(
    -name   => 'Main',
    -width  => 240,
    -height => 100,
    -text   => encode( cp932 => "☆ほげほげ" ),
);

my $label1 = $main->AddLabel(
    -name  => 'LB1',
    -text  => " ",
    -width => 240,
);

my $label2 = $main->AddLabel(
    -name  => 'LB2',
    -text  => encode( cp932 => "出力" ),
    -width => 240,
    -pos   => [ 0, 60 ],
);

$main->AddButton(
    -name  => "Button1",
    -text  => encode( cp932 => "表示変更" ),
    -width => 80,
    -pos   => [ 0, 15 ],
);

$main->AddButton(
    -name  => "Button2",
    -text  => encode( cp932 => "Log表示" ),
    -width => 80,
    -pos   => [ 80, 15 ],
);

$main->Show();
Win32::GUI::Dialog();

sub Main_Terminate {
    -1;
}

sub Button1_Click {

    if ($con) {
        $label1->Change( -text => encode( cp932 => "こんばんわ" ) );
        $con = 0;
    }
    else {
        $label1->Change( -text => encode( cp932 => "こんにちわ" ) );
        $con = 1;
    }
    return 0;
}

#Log読み込み処理
sub Button2_Click {
    my $dir_name = cwd;       # ファイルパスが '/'で取得される
    $dir_name =~ s/\//\\/g;   # ファイルパスが '/' だとダメなので '\'に変更

    my $file = Win32::GUI::GetOpenFileName(
        -directory => $dir_name,
        -title     => encode( cp932 => "ログファイル選択" ),
        -filter    => [ encode( cp932 => "ログ(*.txt;*.log)" ) => '*.txt;*.log',
                       'All files'                             => '*.*',
                      ],
        -owner => $main,
    );

    #ファイルがなにも選択されなかった場合
    if ( !$file ) {
        return 0;
    }

    #ファイル読み込み&表示
    open my $fh, '<', $file or die qq/Can't open file "$file" : $!/;
    while ( my $line = <$fh> ) {
        $label2->Change( -text => $line );
        Win32::GUI::DoEvents(); #重い処理中にウィンドーが固まらないため
    }

    $label2->Change( -text => encode( cp932 => "終了" ) );
    return 0;
}

Win32::GUI::ThreadUtilsコード入力をためす(3)

ThreadUtilsを使っていない例。
The Win32::GUI F.A.Q.には以下記載がある。

Why does my window seem to freeze when my program is in a loop?
Put a call to DoEvents() inside the loop. This will ensure that all queued messages are processed before going on with the loop:

何故か今日はちゃんと動いた。これがうまく動かなかったから、Win32::GUI::ThreadUtilsを使ったのだけど...

use strict;
use warnings;
use utf8;
use Encode;
use Win32::GUI();

my $DOS = Win32::GUI::GetPerlWindow();
Win32::GUI::Hide($DOS);

my $con = 1;

my $main = Win32::GUI::Window->new(
    -name   => 'Main',
    -width  => 240,
    -height => 100,
    -text   => encode( cp932 => "☆ほげほげ" ),
);

my $label1 = $main->AddLabel(
    -name => 'LB1',
    -text  => " ",
    -width => 240,
);

my $label2 = $main->AddLabel(
    -name  => 'LB2',
    -text  => encode( cp932 => "出力" ),
    -width => 240,
    -pos   => [ 0, 60 ],
);

$main->AddButton(
    -name  => "Button1",
    -text  => encode( cp932 => "表示変更" ),
    -width => 80,
    -pos   => [ 0, 15 ],
);

$main->AddButton(
    -name  => "Button2",
    -text  => encode( cp932 => "実行" ),
    -width => 80,
    -pos   => [ 80, 15 ],
);

$main->Show();
Win32::GUI::Dialog();


sub Main_Terminate {
    -1;
}

#重い処理中もボタンがクリックできる.
sub Button1_Click {

    if ($con) {
        $label1->Change( -text => encode( cp932 => "こんばんわ" ) );
        $con = 0;
    }
    else {
        $label1->Change( -text => encode( cp932 => "こんにちわ" ) );
        $con = 1;
    }
    return 0;
}

##重い処理_開始
sub Button2_Click {
    
    #重い処理
    my $file        = 'test.log';
    open my $fh, '<', $file or die qq/Can't open file "$file" : $!/;
    while ( my $line = <$fh> ) {
        $label2->Change( -text => $line );
        
        #FAQにしたがって以下追加
        Win32::GUI::DoEvents();
    }
    
    $label2->Change( -text => encode( cp932 => "終了" ) );
    return 0;
}

Win32::GUI::ThreadUtilsコード入力をためす(2)

別スレッド処理終了後に、親スレッドへ通知するパターン

use strict;
use warnings;
use utf8;
use threads;
use Encode;
use Win32::GUI();
use Win32::GUI::ThreadUtils;

my $DOS = Win32::GUI::GetPerlWindow();
Win32::GUI::Hide($DOS);

my $con = 1;

my $main = Win32::GUI::Window->new(
    -name   => 'Main',
    -width  => 240,
    -height => 100,
    -text   => encode( cp932 => "☆ほげほげ" ),
);

my $label1 = $main->AddLabel(
    -name => 'LB1',
    -text  => " ",
    -width => 240,
);

my $label2 = $main->AddLabel(
    -name  => 'LB2',
    -text  => encode( cp932 => "出力" ),
    -width => 240,
    -pos   => [ 0, 60 ],
);

$main->AddButton(
    -name  => "Button1",
    -text  => encode( cp932 => "表示変更" ),
    -width => 80,
    -pos   => [ 0, 15 ],
);

$main->AddButton(
    -name  => "Button2",
    -text  => encode( cp932 => "実行" ),
    -width => 80,
    -pos   => [ 80, 15 ],
);

$main->Show();
Win32::GUI::Dialog();

sub Main_Terminate {
    -1;
}

#重い処理中にも「Button1(表示変更)」が可能
sub Button1_Click {

    if ($con) {
        $label1->Change( -text => encode( cp932 => "こんばんわ" ) );
        $con = 0;
    }
    else {
        $label1->Change( -text => encode( cp932 => "こんにちわ" ) );
        $con = 1;
    }
    return 0;
}

##重い処理_開始
sub Button2_Click {

    #処理終了後に実施されるサブルーチンを登録
    my $comms = $main->AttachComms( \&boss );

    #別スレッド(重い処理)の設定及び実行
    my $thr = threads->create( \&worker, $comms );
    $thr->detach();
    return 0;
}

#重い(時間掛かる)処理の終了後に呼び出される
sub boss {
    my $self = shift;
    $self->LB2->Change( -text => encode( cp932 => "終了" ) );
}

#重い(時間掛かる)処理(別スレッドにて実施)
sub worker {
    my $SetProgress = shift;

#   実際の処理(省略)

    #処理終了を親スレッドへ通知してる?
    $SetProgress->Call();

}