パケットモニター (MITM ロガー)
クライアント(Minecraftアプリ)と実際の外部サーバーの間に割り込み、やり取りされる全パケットをリアルタイムにコンソールへダンプ・解析するプロキシツールの実践サンプルです。
完全なコード例
go
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
func main() {
var (
listenAddr = flag.String("listen", "0.0.0.0:19132", "プロキシ待受アドレス")
remoteAddr = flag.String("remote", "127.0.0.1:19133", "転送先サーバーアドレス")
)
flag.Parse()
// 1. プロキシのリスナーを初期化
lc := minecraft.ListenConfig{
AuthenticationDisabled: true,
}
listener, err := lc.Listen("raknet", *listenAddr)
if err != nil {
log.Fatalf("リスナー起動失敗: %v", err)
}
defer listener.Close()
log.Printf("パケットモニター起動: %s -> %s\n", *listenAddr, *remoteAddr)
// Ctrl+C で安全に終了
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
_ = listener.Close()
os.Exit(0)
}()
for {
clientConn, err := listener.Accept()
if err != nil {
log.Printf("Accept 失敗: %v", err)
break
}
go handleSession(clientConn.(*minecraft.Conn), *remoteAddr)
}
}
func handleSession(clientConn *minecraft.Conn, remoteAddr string) {
defer clientConn.Close()
log.Printf("[+] クライアント接続: %s (%s)", clientConn.RemoteAddr(), clientConn.IdentityData().DisplayName)
// 2. リモートサーバーへクライアント情報を使って代理接続
dialer := minecraft.Dialer{
ClientData: clientConn.ClientData(),
IdentityData: clientConn.IdentityData(),
}
serverConn, err := dialer.Dial("raknet", remoteAddr)
if err != nil {
log.Printf("[-] リモート接続失敗: %v", err)
return
}
defer serverConn.Close()
// 3. リモートのゲーム初期状態をクライアントに同期
if err := clientConn.StartGame(serverConn.GameData()); err != nil {
log.Printf("[-] StartGame 同期エラー: %v", err)
return
}
var wg sync.WaitGroup
wg.Add(2)
// Client -> Server
go func() {
defer wg.Done()
for {
pk, err := clientConn.ReadPacket()
if err != nil {
break
}
// パケットのロギング
logPacket("C -> S", pk)
if err := serverConn.WritePacket(pk); err != nil {
break
}
}
}()
// Server -> Client
go func() {
defer wg.Done()
for {
pk, err := serverConn.ReadPacket()
if err != nil {
break
}
// パケットのロギング
logPacket("S -> C", pk)
if err := clientConn.WritePacket(pk); err != nil {
break
}
}
}()
wg.Wait()
log.Printf("[-] セッション切断: %s", clientConn.RemoteAddr())
}
// パケット種別に応じたハイライト表示
func logPacket(direction string, pk packet.Packet) {
switch p := pk.(type) {
case *packet.Text:
fmt.Printf("[%s] [Text] %s: %s\n", direction, p.SourceName, p.Message)
case *packet.MovePlayer:
// 頻繁に流れるため詳細のみ1行表示
fmt.Printf("[%s] [MovePlayer] Pos=%v, Yaw=%.1f, Pitch=%.1f\n", direction, p.Position, p.Yaw, p.Pitch)
case *packet.PlayerAction:
fmt.Printf("[%s] [PlayerAction] Action=%d, Pos=%v\n", direction, p.ActionType, p.BlockPosition)
case *packet.InventoryTransaction:
fmt.Printf("[%s] [InventoryTransaction] Type=%d, ActionsCount=%d\n", direction, p.TransactionData, len(p.Actions))
case *packet.LevelChunk:
fmt.Printf("[%s] [Chunk] X=%d, Z=%d, SubChunks=%d\n", direction, p.Position.X(), p.Position.Z(), p.SubChunkCount)
default:
// その他のパケット
fmt.Printf("[%s] [0x%02x] %T\n", direction, pk.ID(), pk)
}
}