Skip to content

プロキシ・MITM (中間者)

gophertunnel の最も強力な活用例のひとつが、クライアントとリモートサーバーの間に立つプロキシ(中間者/MITM) です。

プロキシを使うことで、クライアントとサーバーがやり取りする生のパケットをリアルタイムに監視、フィルタリング、書き換え、インジェクション(差し込み送信)できます。


プロキシの基本構造


透過プロキシのサンプル実装

以下は、クライアントからの接続を受け取り、リモートサーバー(例: 127.0.0.1:19133)にリレーするプロキシの骨組みです。

go
package main

import (
	"errors"
	"log"
	"sync"

	"github.com/sandertv/gophertunnel/minecraft"
	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)

func main() {
	// プロキシの待受設定
	lc := minecraft.ListenConfig{
		AuthenticationDisabled: true,
	}

	listener, err := lc.Listen("raknet", "0.0.0.0:19132")
	if err != nil {
		log.Fatalf("プロキシ起動失敗: %v", err)
	}
	defer listener.Close()

	log.Println("プロキシが待機中: 0.0.0.0:19132")

	for {
		clientConn, err := listener.Accept()
		if err != nil {
			log.Printf("接続受付エラー: %v", err)
			continue
		}

		go handleProxy(clientConn.(*minecraft.Conn))
	}
}

func handleProxy(clientConn *minecraft.Conn) {
	defer clientConn.Close()

	// クライアントの身元・認証情報を引き継いでリモートサーバーへ接続
	dialer := minecraft.Dialer{
		ClientData:   clientConn.ClientData(),
		IdentityData: clientConn.IdentityData(),
	}

	serverConn, err := dialer.Dial("raknet", "127.0.0.1:19133")
	if err != nil {
		log.Printf("リモートサーバー接続失敗: %v", err)
		return
	}
	defer serverConn.Close()

	// リモートサーバーから受け取ったGameDataをクライアント側に渡してゲーム開始
	if err := clientConn.StartGame(serverConn.GameData()); err != nil {
		log.Printf("StartGame転送失敗: %v", err)
		return
	}

	var wg sync.WaitGroup
	wg.Add(2)

	// 1. クライアント -> サーバー のパケット転送
	go func() {
		defer wg.Done()
		for {
			pk, err := clientConn.ReadPacket()
			if err != nil {
				break
			}

			// ここで特定のパケットを検査・改変可能
			// 例: チャットメッセージの傍受
			if textPk, ok := pk.(*packet.Text); ok {
				log.Printf("[Client Chat] %s: %s", textPk.SourceName, textPk.Message)
			}

			if err := serverConn.WritePacket(pk); err != nil {
				break
			}
		}
	}()

	// 2. サーバー -> クライアント のパケット転送
	go func() {
		defer wg.Done()
		for {
			pk, err := serverConn.ReadPacket()
			if err != nil {
				break
			}

			if err := clientConn.WritePacket(pk); err != nil {
				break
			}
		}
	}()

	wg.Wait()
	log.Println("プロキシセッション終了")
}

パケットの書き換えとドロップ

パケット転送ループ内で条件分岐を行うことで、特定のアクションを簡単に阻止または改変できます。

パケットのドロップ(遮断)

転送先の WritePacket を呼ばずに continue することで、パケットが相手に届かなくなります。

go
// クライアントがブロックを壊す操作パケットをキャンセルする例
if actionPk, ok := pk.(*packet.PlayerAction); ok {
    if actionPk.ActionType == packet.PlayerActionStartBreak {
        log.Println("ブロック破壊操作をキャンセルしました")
        continue // サーバーに送らない
    }
}

パケットのインジェクション(割り込み送信)

相手側に独自のパケットを差し込んで送信することも可能です。

go
// サーバーからチャットパケットを受け取った際、クライアント側に偽のシステムメッセージを差し込む
clientConn.WritePacket(&packet.Text{
    TextType:    packet.TextTypeRaw,
    Message:     "§e[Proxy] パケットを検知しました",
    SourceName:  "",
})

Released under the MIT License.