【C#】SharpPcap備忘録

C#

記事をご覧の方へ

現在vivibit.netは旧システムからの移行に伴い修正作業を行っています。
表示上の問題や軽微なエラーが発生する可能性がありますが、ご利用に問題はありません。
また、現在一部ファイルのダウンロードができなくなっています。
順次対応予定ですが、お急ぎの場合や問題を発見された場合はコメント欄でご指摘いただけると助かります。


とあるゲームの効率化を求めて補助ツールを書いた。
普通にゲームを遊んでいたら勝手にパケットから欲しいデータを拾って整形するようなヤツだ。

その際にSharpPcapというC#でWinPcapを扱うためのライブラリを使ったが、絶対に覚えていられないので自分へのメモとして残す。

この記事を読む人は「筆者はSharpPcapはおろかパケットもC#も何もわかってない」ことを理解の上読んで欲しい。
単にこうやったら動いたというものをメモするのでコードは各自奇麗にしてね。

開発環境

Pcap.Netというものもあるようだが、最終リリースが2年新しい事とバグ対応の面からSharpPcapを選択した。

  • VS2019
  • .Net Framework 4.8
  • SharpPcap 5.1

SharpPcap備忘録

[ktms_ads]

SharpPcapのインストール

NuGetからインストールした。

[code]
Install-Package SharpPcap
[/code]

SharpPcapのサンプルコード

検索したら情報が古く使えないものも多くあった。
基本的にはGithubを見てサンプルコードに修正を加えていく形になる。

GitHub - dotpcap/sharppcap: Official repository - Fully managed, cross platform (Windows, Mac, Linux) .NET library for capturing packets
Official repository - Fully managed, cross platform (Windows, Mac, Linux) .NET library for capturing packets - dotpcap/s...

今回はTCPパケットだけ欲しかったのでExample6.DumpTCP.csを参考にした。
(表示は同じだけどページはちゃんと違うよ)

https://github.com/chmorgan/sharppcap/blob/master/Examples/Example6.DumpTCP/Example6.DumpTCP.cs

devicesを自動で設定する

サンプルコードではConsole.ReadLine()でデバイスリストからキャプチャするデバイスを入力しているが、起動ごとに指定するのも面倒なため自動化した。
using System.Net; を忘れずに。

[csharp]
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];
[/csharp]

IPアドレスでフィルタする

いちいちイベントが発生するのでデバッグ中にIPアドレスフィルタが役立った。

[csharp]
string filter = "ip and tcp and dst host IPアドレス";
[/csharp]

IPアドレスやportとのマッチ

IPアドレスフィルタはしていられないが特定のサーバーからのデータがみたい時。

[csharp]
// IPアドレスの場合
string ip = "IPアドレス";
var ipPacket = (PacketDotNet.IPPacket)tcpPacket.ParentPacket;

if (ip == ipPacket.SourceAddress.ToString())
{
}

// Portの場合
ushort port = 8080;
if (port == tcpPacket.SourcePort)
{
}
[/csharp]

接続先の変更(SYNパケット)の確認

新たにサーバーに接続したという判別をしたい場合、tcpPacket.Synchronize がtrueか調べる。
サーバーに接続してからn個めのパケットを~みたいに決め撃つとき便利。

[csharp]
if (tcpPacket.Synchronize == true)
{
}
[/csharp]

特定のバイト列を探す

自分の場合は欲しいデータがzlibだったのでtcpPacket.PayloadDataからヘッダを探す必要があった。
以下のページのベストアンサーが良さそうだったので使わせてもらった。

配列の中から配列を検索する
お世話になります。 主題の通りなのですが、配列から配列を検索する手法、または直接的なメソッドを探しています。 現在はとりあえず以下のようなコードで実現していますが、よりよい方法があればぜひお教

ヘッダまでにゴミがあるときとかにもこの値を活用した。

[csharp]
byte[] pattern = new byte[] { 0x00, 0x00, 0x00, 0x00 };

int headerAddr = searchZlibHeader.SearchBytes(tcpPacket.PayloadData, pattern);

if (headerAddr != -1)
{
}
[/csharp]

分割されて送られるデータを追いかける

必要なデータがサイズ上の問題で分割された場合、結合していきたいが「次のパケットはどれ?」という問題に当たる。
パケットには次のパケットはこれという番号があるようで、tcpPacket.SequenceNumber から計算できる。
AcknowledgmentNumber (次のパケットの番号という理解)は、SequenceNumber に現在の PayloadData.Length を足すことで求められる。

[csharp]
uint acknowledgmentNumber = tcpPacket.SequenceNumber + (uint)tcpPacket.PayloadData.Length;

if (acknowledgmentNumber == tcpPacket.SequenceNumber)
{
}
[/csharp]

フォームにリアルタイムにデータを表示したい

SharpPcapの動作テスト自体は簡単に終わったが、取得したデータをリアルタイムにUIに反映させるには少々厄介だとわかった。
イベントを受けている間フォームが固まっていたからだ。
動いたものを書くが、非同期(やれTask.Runだasync/awaitだ)についてはまるで理解してない。

非同期実行

自分の場合はForm1_ShownイベントのところにSharpPcap周りのコードを雑にコピペしていたので、奇麗にやりたい人は例によって勝手にやってください。
using System.Threading.Tasks; をお忘れなく。

[csharp]
// asyncをつける
private async void Form1_Shown(object sender, EventArgs e)

// キャプチャ開始部分を await Task.Run();
await Task.Run(() =>
{
// キャプチャ開始
device.Capture();
});
[/csharp]

UI更新

UIはSystem.Timers.Timer で1秒に1回更新させる形にした。
Form1_Load に適当にTimerを追加した。

例えばlabelのテキストを書き換えたいとして、別スレッドからのUI更新は普通に書いても動かないらしい。
なのでInvokeとかいうものを使った。

[csharp]
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 = "ほげ"));
}
[/csharp]

あとがき

C#の非同期は意味わからんかったし、もうパケットは見たくないけど、今回色々やって得た知識は今後も便利につかえそうだぜ。

パケットやzlibについて色々教えてくれたxx2zz大先生に感謝。

コメント

タイトルとURLをコピーしました