LoginSignup
0
0

More than 5 years have passed since last update.

WinPcapExtension ー ActionScriptからWinPcapを使えるようにする

Posted at

■■ WinPcapExtension

 ActionScriptというかAdobe AIRからWinPcapが使いたかったので、WinPcapのActionScript Native Extensionを書きました。
  WinPcapExtension: https://github.com/ryujimiya/WinPcapExtension/

■■ 使用方法

1. ネットワークインターフェースの取得

 WinPcapExtensionでキャプチャーするネットワークアダプタを選択します。
20180422_Network Settings.jpg

1.1. 初期化
 WinPcapExtensionのインスタンスを new WinPcapExtension()で生成しています。

import flash.utils.ByteArray;
import livefan.packet.PacketUtil;
import livefan.winpcap.PcapDefine;
import livefan.winpcap.PcapHandle;
import livefan.winpcap.PcapIf;
import livefan.winpcap.PcapPktHdr;
import livefan.winpcap.ResultAllDevs;
import livefan.winpcap.ResultGetArrival;
import livefan.winpcap.ResultPcap;
import livefan.winpcap.Timeval;
import livefan.winpcap.WinPcapExtension;
import livefan.winpcap.WinPcapExtensionEvent;


public class NetworkSettingsWin extends NetworkSettingsWinView 
{
    /**
     * WinPcapネイティブ拡張インスタンス
     */
    private var _extension:WinPcapExtension = null;
    /**
     * ネットワークインターフェースインスタンス
     */
    private var _pcapIf:PcapIf = null;

    /**
     * コンストラクタ
     */
    public function NetworkSettingsWin()
    {
        super();
        addEventListener(AIREvent.WINDOW_COMPLETE, initWin);
        addEventListener(Event.CLOSING, closing);
    }

    /**
     * ウィンドウが初期化された
     * @param e
     */
    private function initWin(e:AIREvent):void 
    {
        // WinPcap拡張インスタンスの生成
        _extension = new WinPcapExtension();

        listNetworkIf.addEventListener(IndexChangeEvent.CHANGE, listNetworkIfIndexChange);
        setupNetworkIfList();
    }
}

1.2. ネットワークインターフェースの列挙
 ネットワークインターフェースを取得するにはextension.pcapFindAllDevsEx(source, null);を使います。ここでsourceはPcapDefine.PCAP_SRC_IF_STRINGを指定します。
PCAP_SRC_IF_STRINGは "rpcap://"と定義されています。
pcapFindAllDevsExの戻り値はResultAllDevsで、そのpcapIfsというプロパティでネットワークインターフェースのリスト(の先頭)を取り出せます。あとはpcapIf = pcapIfsとし、pcapIf->nextで列挙していけば一覧を取得できます。

    /**
     * ネットワークインターフェース一覧をセットアップ
     */
    private function setupNetworkIfList():void
    {
        /*
        if (_pcapHandle != null)
        {
            return;
        }
        */
        var source:String = PcapDefine.PCAP_SRC_IF_STRING;

        // ネットワークインターフェースの一覧を取得
        var resAllDevs:ResultAllDevs = _extension.pcapFindAllDevsEx(source, null);
        if (resAllDevs.retVal != 0)
        {
            Alert.show(resAllDevs.errBuf, "", Alert.OK, this);
            return;
        }

        // ネットワークインターフェースをリストに登録
        var dataProvider:ArrayCollection = new ArrayCollection();
        var pcapIfs:PcapIf = resAllDevs.pcapIfs;
        for (var pcapIf:PcapIf = pcapIfs; pcapIf != null; pcapIf = pcapIf.next)
        {
            var name:String = pcapIf.name;
            var description:String = pcapIf.description;
            var addresses:PcapAddr = pcapIf.addresses;
            var labelStr:String = description;
            dataProvider.addItem( { label: labelStr , data:pcapIf } );
        }
        listNetworkIf.dataProvider = dataProvider;

        // インターフェース一覧を解放
        resAllDevs.dispose();
    }

2. パケットキャプチャー

 では実際にパケットをキャプチャーする手順です。
キャプチャーの方法はWinPcapでいうpcap_loopを使う方法とpcap_next_exを使う方法の2種類あり、WinPcapExtensionはどちらにも対応しています。ここではpcap_loopを使う方法について説明します。

2.1. 初期化
 WinPcapExtensionインスタンスを生成するところはネットワークインターフェース一覧取得の時と同じです。同じクラスでやるならネットワーク一覧取得のWinPcapExtensionインスタンスをそのまま使えばいいと思います。
次にイベントハンドラを設定します。
  WinPcapExtensionEvent.CAPTURETHREAD_PACKETARRIVAL
  WinPcapExtensionEvent.CAPTURETHREAD_THREADFUNCFINISHED
これらは、pcap_loop使用時に必要なハンドラで、pcap_next_exを利用した場合は必要ありません。
また、networkNameは前項で取得したpcapIf.nameを指定します。

public class CaptureThreadDemoWin extends CaptureThreadDemoWinView
{
    private var _extension:WinPcapExtension = null;
    private var _pcapHandle:PcapHandle = null;
    private var _pcapIf:PcapIf = null;
    private var _networkName:String = "";

    public function CaptureThreadDemoWin(networkName:String)
    {
        super();
        addEventListener(AIREvent.WINDOW_COMPLETE, initWin);
        addEventListener(Event.CLOSING, closing);
        _networkName = networkName;
    }

    private function initWin(e:AIREvent):void 
    {
        // WinPcap拡張インスタンスの生成
        _extension = new WinPcapExtension();
        _extension.addEventListener(WinPcapExtensionEvent.CAPTURETHREAD_PACKETARRIVAL, onCaptureThreadPacketArrival);
        _extension.addEventListener(WinPcapExtensionEvent.CAPTURETHREAD_THREADFUNCFINISHED, onCaptureThreadFinished);

        textAreaDump.editable = false;

        _pcapIf = getNetworkAdapter();
        startCapture(_pcapIf.name);
    }


    private function onCaptureThreadPacketArrival(e:WinPcapExtensionEvent):void 
    {

        // 実装する

    }



    private function onCaptureThreadFinished(e:WinPcapExtensionEvent):void
    {

        // 実装する
    }
}

2.2. キャプチャーの開始、停止
 キャプチャーを開始するにはまず_extension.pcapOpenでpcapHandleを取得する必要があります。
 つぎに_pcapHandle.startCaptureThread()でキャプチャースレッドを起動します。そうすると、先ほど設定したイベントハンドラonCaptureThreadPacketArrivalにパケット到達時に通知されます。
 キャプチャーを終了するには_pcapHandle.stopCaptureThread()を使用します。また、再開の必要がなければ、pcalHandleもクローズします。

    private function startCapture(adapterName:String):Boolean
    {
        var resPcap:ResultPcap = _extension.pcapOpen(adapterName, 65535, PcapDefine.PCAP_OPENFLAG_PROMISCUOUS, 20, null);
        if (resPcap.retVal != 0)
        {
            Alert.show(resPcap.errBuf, "", Alert.OK, this);
            return false;
        }
        this._pcapHandle = resPcap.pcapHandle;

        this._pcapHandle.startCaptureThread(); // CaptureThreadを起動する
        return true;
    }

    private function stopCapture():void
    {
        if (_pcapHandle == null)
        {
            return;
        }
        _pcapHandle.stopCaptureThread(); // CaptureThreadを終了する
        _pcapHandle.pcapClose();
        _pcapHandle.dispose();
        _pcapHandle = null;
    }
}

2.3. フィルタリング
 キャプチャーするパケットにフィルタリングをかけることもできます。
次のようにpcapSetFilterを使います。この設定はstartCaptureThreadを呼び出す前に行います。

        this._pcapHandle.pcapSetFilter("src port >= 80 and src port <= 99", 1, 0xffffff);

3. パケットの取得

 onCaptureThreadPacketArrivalに実装します。
 理想はこのイベントが発生したとき到達パケット数は1であってほしかったのですが、パケット到達数が多いとそうもいかないようなので、まずパケット数を_pcapHandle.getArrivalPacketCount()で取得します。
あとはこのパケット数分を _pcapHandle.getArrivalPacket()を使ってパケットを取得すればいいです。

    private function onCaptureThreadPacketArrival(e:WinPcapExtensionEvent):void 
    {
        if (_pcapHandle == null)
        {
            return;
        }

        var packetCnt:int = _pcapHandle.getArrivalPacketCount();
        //trace("packet = " + packetCnt);
        for (var i:int = 0; i < packetCnt; i++)
        {
            // 到達パケットを取得する
            var resGetArrival:ResultGetArrival = _pcapHandle.getArrivalPacket();
            var ret:int = resGetArrival.retVal;
            var header:PcapPktHdr = resGetArrival.header as PcapPktHdr;
            var data:ByteArray = resGetArrival.data as ByteArray;
            resGetArrival.dispose(); // 結果を破棄する (取得したheaderはnativeで使用不可となる)
            if (ret != 1)
            {
                continue;
            }
            //trace(PacketUtil.hexDump(data));

            // 表示
            var newlineStr:String = "\r\n";
            var text:String = "";
            if (header != null)
            {
                var ts:Timeval = header.ts;
                var date:Date = new Date(ts.tvSec * 1000);
                text += date.toLocaleString();
                text += " capLen:" + header.capLen.toString() + newlineStr;
            }
            text += PacketUtil.hexDump(data);
            textAreaDump.text = text;
        }
    }

4. パケットの解析

 パケットの解析にはPacketLibを使います。PacketLibはActionScriptのみで書いたパケット解析ライブラリです。
 PacketLib: https://github.com/ryujimiya/PacketLib

 次の関数parsePacketの引数dataには、data:ByteArray = resGetArrival.data as ByteArrayで取得したdataがセットされると思ってください。
 Packet.parsePacketでパケットの生データをパケットオブジェクトに変換します。
以下の例ではlastPacket.payloadByteAry.lengthとペイロード長を取得するためにしか使っていませんが、実際パケット解析するときは、lastPacket.payloadByteAry自体が重要で、パケットのペイロードをByteArrayで取得できます。

    private function parsePacket(data:ByteArray):String 
    {
        var retStr:String = "";
        //var newlineStr:String = "\r\n";

        // パケットデータをパースする
        var dlt:int = _pcapHandle.pcapDataLink();
        var packet:Packet = Packet.parsePacket(dlt, data);
        if (packet == null)
        {
            return retStr;
        }
        // 一番内側のパケットを取得する
        var lastPacket:Packet = packet.getLastPacket();
        // TcpPacketを指定して取得する
        //var lastPacket:Packet = packet.extractPacket(TcpPacket);

        // パケット情報を取得する
        retStr += getQualifiedClassName(lastPacket) + " ";
        if (lastPacket is TcpPacket || lastPacket is UdpPacket)
        {
            // TcpPacketかUdpPacketの場合

            var srcAddrAsString:String = "";
            var dstAddrAsString:String = "";
            var srcPort:uint = 0;
            var dstPort:uint = 0;
            var ipPacket:IpPacket = lastPacket.parentPacket as IpPacket;
            if (ipPacket != null)
            {
                if (ipPacket is Ipv4Packet)
                {
                    var ipv4Packet:Ipv4Packet = ipPacket as Ipv4Packet;
                    srcAddrAsString = _extension.IpAddrByteArrayToString(SockAddr.AF_INET, ipv4Packet.srcAddress);
                    dstAddrAsString = _extension.IpAddrByteArrayToString(SockAddr.AF_INET, ipv4Packet.dstAddress);
                }
                else if (ipPacket is Ipv6Packet)
                {
                    var ipv6Packet:Ipv6Packet = ipPacket as Ipv6Packet;
                    srcAddrAsString = "[" + _extension.IpAddrByteArrayToString(SockAddr.AF_INET6, ipv6Packet.srcAddress) + "]";
                    dstAddrAsString = "[" + _extension.IpAddrByteArrayToString(SockAddr.AF_INET6, ipv6Packet.dstAddress) + "]";
                }
                else
                {
                    // invalid
                }
            }

            if (lastPacket is TcpPacket)
            {
                var tcpPacket:TcpPacket = lastPacket as TcpPacket;
                srcPort = tcpPacket.srcPort;
                dstPort = tcpPacket.dstPort;
            }
            else if (lastPacket is UdpPacket)
            {
                var udpPacket:UdpPacket = lastPacket as UdpPacket;
                srcPort = udpPacket.srcPort;
                dstPort = udpPacket.dstPort;
            }
            retStr += 
                srcAddrAsString
                + ":"
                + srcPort.toString()
                + "->"
                + dstAddrAsString
                + ":"
                + dstPort.toString()
                + " payload length = " + lastPacket.payloadByteAry.length.toString();
        }
        else
        {
            // その他
        }
        return retStr;
    }

■■サンプルソースコード

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0