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

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

###**■■ 使用方法**
####**1. ネットワークインターフェースの取得**
　WinPcapExtensionでキャプチャーするネットワークアダプタを選択します。
![20180422_Network Settings.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/5cdd3a98-8825-02aa-68d3-a897effb5199.jpeg)

**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;
        }

###**■■サンプルソースコード**
https://github.com/ryujimiya/WinPcapSample/tree/master/src/WinPcapSample