ipCidrRouteTableのsnmpwalk結果をPowerShellで処理しテーブル形式で出力する

## 1. はじめに

SNMPエージェントのソフトウェアの開発に利用されるフリーのSNMPパッケージで、
代表的なものとしてはNet-SNMPがあります。

**http://net-snmp.sourceforge.net/**

この記事では、
Net-SNMPに付属しているsnmpwalkコマンドを使って、ルーティング情報が格納されている

**[ipCidrRouteTable(.1.3.6.1.2.1.4.24.4)](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.4.24.4)**
**[ipCidrRouteEntry(.1.3.6.1.2.1.4.24.4.1)](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.4.24.4.1)**

のsnmpwalk結果をPowerShellのスクリプトで処理し、テーブル形式での出力を得る処理の作成例を紹介します。

テーブル形式での出力結果は、下記のような形式です。

### 取得結果例１（テーブル形式）

```:PowerShell
Destination         Netmask             NextHop             ifIndex   Type      Proto     Tos       Age       Info
10.1.15.0           255.255.255.0       0.0.0.0             99        local     local     0         169996    .0.0
10.1.15.254         255.255.255.255     0.0.0.0             99        local     local     0         169996    .0.0
10.15.0.0           255.255.255.0       0.0.0.0             99        local     local     0         169996    .0.0
10.15.0.254         255.255.255.255     0.0.0.0             99        local     local     0         169996    .0.0
10.15.10.0          255.255.255.0       0.0.0.0             99        local     local     0         169996    .0.0
10.15.10.254        255.255.255.255     0.0.0.0             99        local     local     0         169996    .0.0
10.20.25.0          255.255.255.0       0.0.0.0             10501     remote    ospf      0         0         .0.0
10.20.25.254        255.255.255.255     0.0.0.0             20567     local     local     0         170000    .0.0
10.16.0.0           255.255.255.0       0.0.0.0             99        local     local     0         169996    .0.0
10.16.0.254         255.255.255.255     0.0.0.0             99        local     local     0         169996    .0.0
```
### 取得結果例２（csv形式）

```:PowerShell
Destination;Netmask;NextHop;ifIndex;Type;Proto;Tos;Age;Info
10.1.15.0;255.255.255.0;0.0.0.0;99;local;local;0;169996;.0.0
10.1.15.254;255.255.255.255;0.0.0.0;99;local;local;0;169996;.0.0
10.15.0.0;255.255.255.0;0.0.0.0;99;local;local;0;169996;.0.0
10.15.0.254;255.255.255.255;0.0.0.0;99;local;local;0;169996;.0.0
10.15.10.0;255.255.255.0;0.0.0.0;99;local;local;0;169996;.0.0
10.15.10.254;255.255.255.255;0.0.0.0;99;local;local;0;169996;.0.0
10.20.25.0;255.255.255.0;0.0.0.0;10501;remote;ospf;0;0;.0.0
10.20.25.254;255.255.255.255;0.0.0.0;20567;local;local;0;170000;.0.0
10.16.0.0;255.255.255.0;0.0.0.0;99;local;local;0;169996;.0.0
10.16.0.254;255.255.255.255;0.0.0.0;99;local;local;0;169996;.0.0
```

### 完成例

PowerShellスクリプトの完成例は以下にも配置しています。

* ipCidrRouteTable版
**https://github.com/NobuyukiInoue/PS_convert_ipCidrRouteTable**

* ifEntry版
**https://github.com/NobuyukiInoue/PS_convert_ifEntry**

ちなみに、GUI版のほとんどのSNMPマネージャ製品では、簡単にメニュー等から選択するだけで表形式でみることができます。

しかし、SNMPマネージャのデバッグ時や、障害が発生していてSNMPマネージャが利用できない場合などは、
直接、対象のNW機器に対してsnmpwalkしたいときがありますよね。

snmpwalkを利用すれば、引数で指定したOID配下のMIBを再帰的にgetnextするので、下記のように格納順に結果が得られますが、同一レコードをグループ化して見たいときは、表計算ソフトに張り付けたりする作業が必要となるなど、いろいろと不便です。

```:PowerShell
PS D:\PS_convert_ipCidrRouteTable> snmpwalk -On -c public -v2c 10.15.10.254 .1.3.6.1.2.1.4.24.4.1
.1.3.6.1.2.1.4.24.4.1.1.10.1.15.0.255.255.255.0.0.0.0.0.0 = IpAddress: 10.1.15.0
.1.3.6.1.2.1.4.24.4.1.1.10.1.15.254.255.255.255.255.0.0.0.0.0 = IpAddress: 10.1.15.254
（中略）
.1.3.6.1.2.1.4.24.4.1.2.10.1.15.0.255.255.255.0.0.0.0.0.0 = IpAddress: 255.255.255.0
.1.3.6.1.2.1.4.24.4.1.2.10.1.15.254.255.255.255.255.0.0.0.0.0 = IpAddress: 255.255.255.255
（中略）
.1.3.6.1.2.1.4.24.4.1.3.10.1.15.0.255.255.255.0.0.0.0.0.0 = INTEGER: 0
.1.3.6.1.2.1.4.24.4.1.3.10.1.15.254.255.255.255.255.0.0.0.0.0 = INTEGER: 0
（中略）
.1.3.6.1.2.1.4.24.4.1.4.10.1.15.0.255.255.255.0.0.0.0.0.0 = IpAddress: 0.0.0.0
.1.3.6.1.2.1.4.24.4.1.4.10.1.15.254.255.255.255.255.0.0.0.0.0 = IpAddress: 0.0.0.0
（中略）
```

そこで、snmpwalkの実行結果をPowerShellのスクリプトで処理し、テーブル形式で出力してみることにします。

pythonやrubyなどでは、snmp用のライブラリが用意されています。

PowerShellでも、外部のDLLやクラスライブラリを利用すれば、もう少し簡単に処理できるかもしれませんが、今回は、snmpwalkの実行結果を利用して、PowerShellスクリプト単体で処理することにしました。

## 2. PowerShellスクリプトの作成

２つのクラスと２つのハッシュテーブルを用意します。

### 2-1. IfValuesクラス

**IfValuesクラス**は、**ipCidrRouteEntry(.1.3.6.1.2.1.4.24.4.1)** 配下のMIBの

* OID
* パターンマッチで使用するためのOIDを正規表現化したもの
* 対象OIDの名前
* 読み込み時の検証で使用するカウンタ
* 初期化用のInitメソッド

などをとりまとめたオブジェクト用のクラスです。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
class IfValues {
    [string] $OID
    [string] $OIDRegular
    [string] $Name
    [int]    $count

    Init($oid, $name) {
        $this.Name = $name
        $this.OID = $oid
        $this.OIDRegular = "^" + $this.OID.Replace("." ,"\.")
        $this.count = 0
    }
}
```

### 2-2. RouteTableクラス

**RouteTableクラス**は、**ipCidrRouteEntry(.1.3.6.1.2.1.4.24.4.1)** 配下のMIBの
各項目と型を定義したクラスです。１つの宛先の情報をとりまとめたもの（１レコード）に相当します。

snmpwalkの実行結果は文字列ですが、値の前に型情報も含まれているので、
INTEGER32はint型、IpAddress型やOID型はstring 型で読み込むことにします。

実行結果は複数の宛先から構成されますので、実行時にはこのオブジェクトの配列を用意することになります。
 
```PowerShell:PS_convert_ipCidrRouteTable.ps1
class RouteTable {
    [string] $ipCidrRouteOID
    [string] $ipCidrRouteDest
    [string] $ipCidrRouteMask
    [int] $ipCidrRouteTos
    [string] $ipCidrRouteNextHop
    [int] $ipCidrRouteIfIndex

    [int] $ipCidrRouteType
    [string] $ipCidrRouteTypeName

    [int] $ipCidrRouteProto
    [string] $ipCidrRouteProtoName

    [int] $ipCidrRouteAge
    [string] $ipCidrRouteInfo
    [int] $ipCidrRouteNextHopAS
    [int] $ipCidrRouteMetric1
    [int] $ipCidrRouteMetric2
    [int] $ipCidrRouteMetric3
    [int] $ipCidrRouteMetric4
    [int] $ipCidrRouteMetric5
    [int] $ipCidrRouteStatus
}
```

### 2-3. 各OID用のIfIndexオブジェクトの生成

クラスの定義が終わったので、**ipCidrRouteEntry(.1.3.6.1.2.1.4.24.4.1)** 配下用のオブジェクトを生成します。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
$ipCidrRouteOID = New-Object IfValues
$ipCidrRouteOID.Init(".1.3.6.1.2.1.4.24.4.", "ipCidrRouteOID")

$ipCidrRouteDest = New-Object IfValues
$ipCidrRouteDest.Init(".1.3.6.1.2.1.4.24.4.1.1.", "ipCidrRouteDest")

$ipCidrRouteMask = New-Object IfValues
$ipCidrRouteMask.Init(".1.3.6.1.2.1.4.24.4.1.2.", "ipCidrRouteMask")

$ipCidrRouteTos = New-Object IfValues
$ipCidrRouteTos.Init(".1.3.6.1.2.1.4.24.4.1.3.", "ipCidrRouteTos")

$ipCidrRouteNextHop = New-Object IfValues
$ipCidrRouteNextHop.Init(".1.3.6.1.2.1.4.24.4.1.4.", "ipCidrRouteNextHop")

$ipCidrRouteIfIndex = New-Object IfValues
$ipCidrRouteIfIndex.Init(".1.3.6.1.2.1.4.24.4.1.5.", "ipCidrRouteIfIndex")

$ipCidrRouteType = New-Object IfValues
$ipCidrRouteType.Init(".1.3.6.1.2.1.4.24.4.1.6.", "ipCidrRouteType")

$ipCidrRouteProto = New-Object IfValues
$ipCidrRouteProto.Init(".1.3.6.1.2.1.4.24.4.1.7.", "ipCidrRouteProto")

$ipCidrRouteAge = New-Object IfValues
$ipCidrRouteAge.Init(".1.3.6.1.2.1.4.24.4.1.8.", "ipCidrRouteAge")

$ipCidrRouteInfo = New-Object IfValues
$ipCidrRouteInfo.Init(".1.3.6.1.2.1.4.24.4.1.9.", "ipCidrRouteInfo")
...
...
```

### 2-4. typeとProtocolの定義値

[ipCidrRouteType(1.3.6.1.2.1.4.24.4.1.6)](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.4.24.4.1.6)
[ipCidrRouteProto(1.3.6.1.2.1.4.24.4.1.7)](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.4.24.4.1.7)

については、数値のまま結果を表示すると解りにくいので、

MIB定義ファイルに記載されている値をハッシュテーブルとして用意しておき、
取得した値からプロトコル名に変換できるようにしておきます。

今回のスクリプトでは-Onオプションに合わせて作っていますが、
snmpwalk実行時に、-Onオプションを指定しないという手もありますね。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
$typeTable = @{
    1 = "other"
    2 = "reject"
    3 = "local"
    4 = "remote"
}
```

```PowerShell:PS_convert_ipCidrRouteTable.ps1
$protoTable = @{
    1 = "other"
    2 = "local"
    3 = "netmgmt"
    4 = "icmp"
    5 = "egp"
    6 = "ggp"
    7 = "hello"
    8 = "rip"
    9 = "isIs"
    10 = "esIs"
    11 = "ciscoIgrp"
    12 = "bbnSpfIgp"
    13 = "ospf"
    14 = "bgp"
    15 = "idpr"
    16 = "ciscoEigrp"
}
```

### 2-5.  読み込み処理

**snmpwalk** からパイプで渡された実行結果は、**$input** に格納されていますので、
**foreach** で１行ずつ読み込み、**OID番号** が一致するフィールドに格納していきます。

**\$resultTable** は、結果を格納するための **RouteTable型** の配列です。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
$resultTable = @()

foreach ($line in $input) {
 #  Write-Output $line
    switch -Regex ($line) {
        $ipCidrRouteDest.OIDRegular {
            $line = $line.Replace($ipCidrRouteDest.OID, "")
            $flds = $line -split " = "
            if ($flds.Length -ge 2) {
                $resultTable += New-Object RouteTable
                $resultTable[$resultTable.Lenght - 1].ipCidrRouteOID = $flds[0].Replace($ipCidrRouteDest.OID, "")
                $resultTable[$resultTable.Lenght - 1].ipCidrRouteDest = $flds[1].Replace("IpAddress: ", "")
            }
            else {
                Write-Output "$ipCidrRouteDest.OID Check Error"
                return
            }
            break
        }
        $ipCidrRouteMask.OIDRegular {
            $line = $line.Replace($ipCidrRouteMask.OID, "")
            $flds = $line -split " = "
            if (($flds.Length -ge 2) -And ($flds[0] -eq $resultTable[$ipCidrRouteMask.count].ipCidrRouteOID)) {
                $resultTable[$ipCidrRouteMask.count].ipCidrRouteMask = $flds[1].Replace("IpAddress: ", "")
                $ipCidrRouteMask.count++
            }
            else {
                Write-Output "$ipCidrRouteMask.OID Check Error"
                return
            }
            break
        }
...
...
```

### 2-6. typeとprotocolから定義値を読み込む

**type** と **protocol** については、

**\$resultTable[\$i].ipCidrRouteType**
**\$resultTable[\$i].ipCidrRouteProto**

に格納されている値から、ハッシュテーブルを参照して定義値を取得します。

取得結果は、それぞれ、

**\$resultTable[\$i].ipCidrRouteTypeName**
**\$resultTable[\$i].ipCidrRouteProtoName**

に格納しておきます。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
for ($i = 0; $i -lt $resultTable.Length; $i++) {
    $resultTable[$i].ipCidrRouteTypeName = $typeTable[$resultTable[$i].ipCidrRouteType]
}

for ($i = 0; $i -lt $resultTable.Length; $i++) {
    $resultTable[$i].ipCidrRouteProtoName = $protoTable[$resultTable[$i].ipCidrRouteProto]
}
```


### 2-7. 結果の表示処理

配列**\$resultTable** の全レコード、各レコードを標準出力に書き出します。

**$IFS**の内容によって、**出力幅を固定**して出力したり、
デリミタ文字を指定して**CSV形式**での出力もできるようにしています。

```PowerShell:PS_convert_ipCidrRouteTable.ps1
if ($IFS -eq "") {
    $format = "{0,-20}{1,-20}{2,-20}{3,-10}{4,-10}{5,-10}{6,-10}{7,-10}{8,-10}"
}
else {
    $format = "{0}$IFS{1}$IFS{2}$IFS{3}$IFS{4}$IFS{5}$IFS{6}$IFS{7}$IFS{8}"
}

Write-Output ($format -f `
    "Destination" `
  , "Netmask" `
  , "NextHop" `
  , "ifIndex" `
  , "Type" `
  , "Proto" `
  , "Tos" `
  , "Age" `
  , "Info" `
)

for ($i = 0; $i -lt $resultTable.Length; $i++) {
    Write-Output ($format -f `
      $resultTable[$i].ipCidrRouteDest `
    , $resultTable[$i].ipCidrRouteMask `
    , $resultTable[$i].ipCidrRouteNextHop `
    , $resultTable[$i].ipCidrRouteIfIndex `
    , $resultTable[$i].ipCidrRouteTypeName `
    , $resultTable[$i].ipCidrRouteProtoName `
    , $resultTable[$i].ipCidrRouteTos `
    , $resultTable[$i].ipCidrRouteAge `
    , $resultTable[$i].ipCidrRouteInfo `
    )
}
```

## 3. 実行例

実行する場合は、以下のような書式でsnmpwalkの実行結果を、PowerShellスクリプトに渡します。

### 書式
```:PowerShell
snmpwalk -On -v 2c -c <community> <target_ip> .1.3.6.1.2.1.4.24.4 | .\convert_ipCidrRouteTable.ps1 [IFS(delimiter)]
```

### 実行例１（テーブル形式）
```:PowerShell
snmpwalk -On -v 2c -c public 10.1.15.254 .1.3.6.1.2.1.4.24.4 | .\PS_convert_ipCidrRouteTable.ps1
```

### 実行例２（コンマ区切り（csv形式））
```:PowerShell
snmpwalk -On -v 2c -c public 10.1.15.254 .1.3.6.1.2.1.4.24.4 | .\PS_convert_ipCidrRouteTable.ps1 ";"
```

### 実行例３（タブ区切り）
```:PowerShell
snmpwalk -On -v 2c -c public 10.1.15.254 .1.3.6.1.2.1.4.24.4 | .\PS_convert_ipCidrRouteTable.ps1 "\t"
```

毎回、OIDを指定してsnmpwalkを実行するのが面倒なのであれば、
下記のようなスクリプト内でsnmpwalkとPS_convert_ipCidrRouteTable.ps1を実行すると良いでしょう。

```PowerShell:main.ps1
param($target, $community, $IFS)

if (-Not($target)) {
    Write-Host "Usage :"$MyInvocation.MyCommand.Name"<target> <community>"
    exit
}

if (-Not($community)) {
    $community = "public"
}

if (-Not($IFS)) {
    $IFS = ""
}


$OID = ".1.3.6.1.2.1.4.24.4"
$walkResult = snmpwalk -On -v 2c -c $community $target $OID

if ($walkResult -eq $NULL) {
    Write-Output $walkResult
    exit
}

$walkResult | ./PS_convert_ipCidrRouteTable.ps1 $IFS
```

## 4. 関連記事

**[ifEntryのsnmpwalk結果をPowerShellで処理しテーブル形式で出力する](https://qiita.com/gx3n-inue/items/49f11c497a935d41bb63)**