Home > Windows にまつわる e.t.c.

PowerShell で ICMPv6 のチェックサムの計算をする


今まで、TCPとUDPの PowerShell Class は書いたことあったので、じゃ ICMPv6 を PowerShell Class で書くとどうなるのか挑戦してみました(現在進行形)

すると、.NET には ICMPv6 Client が無いので、自前で ICMPv6 を書く必要があるんですよね...

System.Net.Sockets.TcpClient とか、System.Net.Sockets.UdpClient ではチェックサムがクラス内で実装されているので、チェックサムを気にする必要はないのですが、ICMPv6 Client が無いので、チェックサムから自分で書く必要があります。

ということで、最初のハードルであるチェックサムの説明からです。

 

What is チェックサム

チェックサムとは、通信で送られてきたデータが破損していないかを確認する仕組みです。

仕組みは意外とシンプルで、送られてきたデータの Uint16 Sum とオーバーフローを加算したものが 0xffff になっていれば OK って仕組みです。

通信の世界では、データは Byte 配列と考え操作します。

例えば以下の byte 列データが送られてきたとします。

[0xab][0xcd][0xef][0xaa][0xff][0xbb][0x64][0xcb]

 

この Byte 配列から 2 byte ずつ取り出して、Uint16 に変換し、Sum します。

[0xabce][0xefaa][0xffbb][0x64cb]

 

Sum はオーバーフローするので、Uint32 にしておきます。
Sum を 16進にすると...

[System.UInt32]$Sum = 0xabcd + 0xefaa + 0xffbb + 0x64cb
$Sum.ToString("x")

0x2fffd ですね。

 

0x2fffd を Uint16 に分解すると、0x2 と 0xfffd なので、2回オーバーフローした 0xfffd ということがわかります。

これを加算して、16進にすると...
(加算した時に更にオーバーフローする可能性があるので、Uint32)

[System.UInt32]$Checksum = 0x2 + 0xfffd
$Checksum.ToString("x")

 

0xffff になったので、データは破損していないということになります。

 

チェックサムの算出方法

チェックサムの確認は簡単でしたね。

では、Sum すると 0xffff になる値はどのようにし算出するのでしょうか

言葉で表現すると、

1) Uint16のチェックサム領域に 0x0000 をセットした Uint16 の Sum を計算
2) オーバーフローと Uint16 を加算
3) 1の補数にする

この手順で計算します。

先のチェックサムを例に計算してみましょう。

 

先ほどのデータは、末尾がチェックサムでしたので、チェックサム領域を 0x0000 とします

[0xabce][0xefaa][0xffbb][0x0000]

ちなみに、チェックサム領域は Uint16 で Sum も Uint16 計算なので、データのどこにチェックサム領域があっても構わないようになっています。

 

それでは、末尾をチェックサムとした Uint16 の Sum を求めます。

[System.UInt32]$Sum = 0xabcd + 0xefaa + 0xffbb + 0x0000
$Sum.ToString("x")

 

0x29b32 を Uint16 で分解すると、0x2 オーバーフローな 0x9b32 なので、これを加算します。

[System.UInt32]$Sum2 = 0x2 + 0x9b32

 

これを1の補数にします。

1の補数とは、加えるとビット列が ALL 1 になる値です。

これを求めるのは 0xffff から減算するか、XOR をってビット反転するいずれかの方法で算出できます。

 

・0xffff から減算する方法

[System.UInt16]$Checksum = 0xffff - $Sum2
$Checksum.ToString("x")

 

・XOR をとる方法

[System.UInt16]$Checksum = $Sum2 -bxor 0xffff
$Checksum.ToString("x")

 

いずれの場合も 0x64cb と、先のチェックサム値と等しくなります。

実際にチェックサムとしてセットする値は、後述のネットワークオーダーにする必要があります。

 

ネットワークオーダー

インターネット上には様々なアーキテクチャーや OS のコンピューターが存在しており、これらが相互に問題なく通信できる必要があります、

実は1バイト以上の数値の内部形式は、CPU アーキテクチャー依存で「ビッグエンディアン」と「リトルエンディアン」の 2 種類あります。

Uint32 の数値で、0x01234567 という値があった場合、「ビッグエンディアン」と「リトルエンディアン」では以下のような内部形式になっています。

違いは、数値を byte 単位で考えた場合、並びが逆転しているのです。

 

インターネット上で使用するネットワークオーダーの数値形式は、「ビッグエンディアン」と定められているので、「リトルエンディアン」の場合は数値形式を「ビッグエンディアン」変換する必要があります。

使用している環境のエンディアンを確認するには「[System.BitConverter]::IsLittleEndian」で確認できます。

 

僕が使用している Intel CPU の Windows 10 の場合は、「リトルエンディアン」なので、「ビッグエンディアン」に変換が必要なのが分かりす。

これは、「[System.BitConverter]::ToUInt16()」「[System.BitConverter]::ToUInt32()」等で数値を byte 配列にした際に、並び順が逆転してしまう事を意味しています。

BitConverter クラス (System) | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/api/system.bitconverter?view=netframework-4.8

 

サンプルを見ると、リトルエンディアンの時は、Array.Reverse を使って、byte 配列を逆転しろとあります。
(僕の使い方が悪いのか、期待動作をしなかったので、自前で書きましたけど w)

実は、ネットワークオーダーに対応する機能は、Windows socket(*1) や .NET(*2) でも提供されているのですが、僕はうまく使えなかったので、ここも自前で実装しました。

 

 

(*1) Windows socket

Windows socket で提供されいるのは 「Htons」「Htonl」「ntohs」「ntohl」ですが、PowerShell では使えなさそうです。

Windows ソケット:バイトの順序付け | Microsoft Docs
https://docs.microsoft.com/ja-jp/cpp/mfc/windows-sockets-byte-ordering?view=vs-2019

 

(*2) .NET

.NET では、「[System.Net.IPAddress]::HostToNetworkOrder()」「[System.Net.IPAddress]::NetworkToHostOrder()」が同等の機能を提供している情報を MS MVP 仲間から得ましたが、僕はうまく使えず... 結局自前で書きました。

IPAddress クラス (System.Net) | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/api/system.net.ipaddress?view=netframework-4.8

 

IPv6 アドレスの扱い

IPv6 アドレスは、16 byte(128 bit) が1つの値として扱われているわけではなく、byte 配列や Uint の構造体として扱われています。

PowerShell で扱う場合は、System.Net.IPAddress として扱えば、GetAddressBytes() を使って byte 配列として扱うことが出来ます。

C++ とかでは Uint の構造体として扱うので、Uint 単位をネットワークオーダーにする必要があります。

これ以外も、チェックサム等の2バイト以上の数値もネットワークオーダーにする必要があります。

 

IPv6 のチェックサム

IPv6 のチェックサムは、疑似ヘッダーに値をセットし、上位層を含めたチェックサムを求めます。

RFC 8200 - Internet Protocol, Version 6 (IPv6) Specification - 8.1. Upper-Layer Checksums
https://tools.ietf.org/html/rfc8200

 

ICMPv6(ping) の場合は、以下がチェックサムの計算対象になります。

 

計算は、先に説明したようにチェックサムに 0x0000 をセットし、チェックサムを算出しセットします。

 

PowerShell でのチェックサム算出実装例

以上を踏まえ、PowerShell での実装サンプルです。

Class のメソッドなので、$this 変数とかコンストラクタとかは省略しています。

完成したら、リポジトリ公開します。


##########################################################################
# ホストバイトオーダー/ネットワークバイトオーダー(ビッグエンディアン)変換
##########################################################################
[byte[]]HostNetwork([byte[]]$Data){

    $Max = $Data.Length
    [byte[]]$ReturnData = New-Object byte[] $Max

    # CPU アーキテクチャが IsLittleEndian か
    if( [System.BitConverter]::IsLittleEndian ){
        # バイト配列逆転
        for( $i = 0; $i -lt $Max; $i++ ){
            $ReturnData[$Max - $i -1] = $Data[$i]
        }
    }
    else{
        # 変換無し
        for( $i = 0; $i -lt $Max; $i++ ){
            $ReturnData[$i] = $Data[$i]
        }
    }

    Return $ReturnData
}

##########################################################################
# ICMPv6 チェックサム用データ作成
##########################################################################
[byte[]]MakeICMPv6ChecksumData(){

    [byte[]]$Body = @()

    ### 疑似ヘッダ

    # 送信元 IPv6 アドレス(128)
    $Body += $this.SrcIPv6Address.GetAddressBytes()

    # 宛先 IPv6 アドレス(128)
    $Body += $this.DstIPv6Address.GetAddressBytes()

    # 上位レイヤー プロトコル パケット長(32)
    [System.UInt32]$Length = $this.CV_ICMPv6Data.Length
    #$Length = [System.Net.IPAddress]::HostToNetworkOrder( $Length )
    [byte[]]$ByteLength = [System.BitConverter]::GetBytes($Length)
    $ByteLength = $this.HostNetwork($ByteLength)
    $Body += $ByteLength

    # zero(24)
    $Zero = New-Object byte[] 3
    $Zero = @(0x00, 0x00, 0x00)
    $Body += $Zero

    # プロトコル(8)
    [byte]$Protocol = 58    # ICMPv6
    $Body += $Protocol

    ### 上位レイヤー データ
    $Body += $this.CV_ICMPv6Data

    Return $Body
}

##########################################################################
# チェックサム計算
##########################################################################
[byte[]] ComputeChecksum([byte[]]$Body){

    # 2バイトずつ取り出し、Sum を求める
    [System.UInt32]$Sum = 0
    $Max = $Body.Length
    $Bytes = New-Object byte[] 2
    for( $i = 0; $i -lt $Max; $i += 2 ){
        $Bytes = @($Body[$i], $Body[$i +1])
        $Bytes = $this.HostNetwork( $Bytes )
        [System.UInt16]$Data = [System.BitConverter]::ToUInt16($Bytes, 0)

        $Sum += $Data
    }

    # オーバーフロー部分取り出し($Sum >> 16)
    [System.UInt16]$OverFlow = $Sum -shr 16

    # Sum の Uint16 部分($Sum << 16 >> 16)
    [System.UInt32]$Sum32 = ($Sum -shl 16) -shr 16

    # オーバーフロー加算
    $Sum32 += $OverFlow

    # Uint16 部分のみ取り出す
    [System.UInt16]$Sum16 = ($Sum32 -shl 16) -shr 16

    # 1の補数(ビット反転)する
    [System.UInt16]$Checksum = $Sum16 -bxor 0xffff

    # ネットワークオーダーにする
    [byte[]]$ByteChecksum = [System.BitConverter]::GetBytes($Checksum)
    $ByteChecksum = $this.HostNetwork($ByteChecksum)

    Return $ByteChecksum
}

 

ICMPv6 クラスは GitHub で公開しています

https://github.com/MuraAtVwnet/ICMPv6_Class
git@github.com:MuraAtVwnet/ICMPv6_Class.git

 

back.gif (1980 バイト)

home.gif (1907 バイト)

Copyright © MURA All rights reserved.