【C#】SharpPcap備忘録
投稿日: 投稿者:ktm@s
とあるゲームの効率化を求めて補助ツールを書いた。
普通にゲームを遊んでいたら勝手にパケットから欲しいデータを拾って整形するようなヤツだ。
その際にSharpPcapというC#でWinPcapを扱うためのライブラリを使ったが、絶対に覚えていられないので自分へのメモとして残す。
この記事を読む人は「筆者はSharpPcapはおろかパケットもC#も何もわかってない」ことを理解の上読んで欲しい。
単にこうやったら動いたというものをメモするのでコードは各自奇麗にしてね。
目次
開発環境
Pcap.Netというものもあるようだが、最終リリースが2年新しい事とバグ対応の面からSharpPcapを選択した。
- VS2019
- .Net Framework 4.8
- SharpPcap 5.1
SharpPcap備忘録
スポンサーリンク
SharpPcapのインストール
NuGetからインストールした。
Install-Package SharpPcap
SharpPcapのサンプルコード
検索したら情報が古く使えないものも多くあった。
基本的にはGithubを見てサンプルコードに修正を加えていく形になる。
今回はTCPパケットだけ欲しかったのでExample6.DumpTCP.csを参考にした。
(表示は同じだけどページはちゃんと違うよ)
devicesを自動で設定する
サンプルコードではConsole.ReadLine()でデバイスリストからキャプチャするデバイスを入力しているが、起動ごとに指定するのも面倒なため自動化した。
using System.Net; を忘れずに。
IPHostEntry ipentry = Dns.GetHostEntry(Dns.GetHostName()); string ip = ""; foreach (IPAddress ipaddr in ipentry.AddressList) { if (ipaddr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { ip = ipaddr.ToString(); break; } } // ネットワークアダプタ一覧 var devices = CaptureDeviceList.Instance; if (devices.Count < 1) { MessageBox.Show("ネットワークアダプタが見つかりません"); return; } int i = 0; foreach (var dev in devices) { string str = dev.ToString(); if (str.Contains(ip)) { break; } i++; } var device = devices[i];
IPアドレスでフィルタする
いちいちイベントが発生するのでデバッグ中にIPアドレスフィルタが役立った。
string filter = "ip and tcp and dst host IPアドレス";
IPアドレスやportとのマッチ
IPアドレスフィルタはしていられないが特定のサーバーからのデータがみたい時。
// IPアドレスの場合 string ip = "IPアドレス"; var ipPacket = (PacketDotNet.IPPacket)tcpPacket.ParentPacket; if (ip == ipPacket.SourceAddress.ToString()) { } // Portの場合 ushort port = 8080; if (port == tcpPacket.SourcePort) { }
接続先の変更(SYNパケット)の確認
新たにサーバーに接続したという判別をしたい場合、tcpPacket.Synchronize がtrueか調べる。
サーバーに接続してからn個めのパケットを~みたいに決め撃つとき便利。
if (tcpPacket.Synchronize == true) { }
特定のバイト列を探す
自分の場合は欲しいデータがzlibだったのでtcpPacket.PayloadDataからヘッダを探す必要があった。
以下のページのベストアンサーが良さそうだったので使わせてもらった。
ヘッダまでにゴミがあるときとかにもこの値を活用した。
byte[] pattern = new byte[] { 0x00, 0x00, 0x00, 0x00 }; int headerAddr = searchZlibHeader.SearchBytes(tcpPacket.PayloadData, pattern); if (headerAddr != -1) { }
分割されて送られるデータを追いかける
必要なデータがサイズ上の問題で分割された場合、結合していきたいが「次のパケットはどれ?」という問題に当たる。
パケットには次のパケットはこれという番号があるようで、tcpPacket.SequenceNumber から計算できる。
AcknowledgmentNumber (次のパケットの番号という理解)は、SequenceNumber に現在の PayloadData.Length を足すことで求められる。
uint acknowledgmentNumber = tcpPacket.SequenceNumber + (uint)tcpPacket.PayloadData.Length; if (acknowledgmentNumber == tcpPacket.SequenceNumber) { }
フォームにリアルタイムにデータを表示したい
SharpPcapの動作テスト自体は簡単に終わったが、取得したデータをリアルタイムにUIに反映させるには少々厄介だとわかった。
イベントを受けている間フォームが固まっていたからだ。
動いたものを書くが、非同期(やれTask.Runだasync/awaitだ)についてはまるで理解してない。
非同期実行
自分の場合はForm1_ShownイベントのところにSharpPcap周りのコードを雑にコピペしていたので、奇麗にやりたい人は例によって勝手にやってください。
using System.Threading.Tasks; をお忘れなく。
// asyncをつける private async void Form1_Shown(object sender, EventArgs e) // キャプチャ開始部分を await Task.Run(); await Task.Run(() => { // キャプチャ開始 device.Capture(); });
UI更新
UIはSystem.Timers.Timer で1秒に1回更新させる形にした。
Form1_Load に適当にTimerを追加した。
例えばlabelのテキストを書き換えたいとして、別スレッドからのUI更新は普通に書いても動かないらしい。
なのでInvokeとかいうものを使った。
private void Form1_Load(object sender, EventArgs e) { // Systemからなのはあいまい参照回避 var timer = new System.Timers.Timer(1000); timer.Elapsed += new ElapsedEventHandler(uiTimer); timer.Start(); } // TimerでのUI変更処理(1000ms) public void uiTimer(object sender, ElapsedEventArgs e) { this.Invoke((MethodInvoker)(() => label1.Text = "ほげ")); }
あとがき
C#の非同期は意味わからんかったし、もうパケットは見たくないけど、今回色々やって得た知識は今後も便利につかえそうだぜ。
パケットやzlibについて色々教えてくれたxx2zz大先生に感謝。