とあるゲームの効率化を求めて補助ツールを書いた。
普通にゲームを遊んでいたら勝手にパケットから欲しいデータを拾って整形するようなヤツだ。
その際に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を見てサンプルコードに修正を加えていく形になる。
今回はTCPパケットだけ欲しかったので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大先生に感謝。
コメント