Code Day's Night

ichikawayのブログ

PHPで自作IPパケットを送受信したい場合

いまPHPでTCPプロトコルを実装中です。TCPはレイヤーとして4層でその下の3層にIPがあります。

PHPで自作TCPパケットを送りたい時はC言語で実装するのと同じように socket_create()関数の引数に、 AF_INETとSOCK_RAWを指定すると実現できます。

SOCK_RAWを使って、sendto()関数に自作TCPパケットを流し込めば良く、IPパケットはOSが勝手に作ってくれます。

では、IPパケットまで自作したい場合はどうするのでしょうか?

 

結論

$socket = socket_create(AF_INET, SOCK_RAW, SOL_TCP);

$IP_HDRINCL = 3;
socket_set_option($socket, IPPROTO_IP,$IP_HDRINCL, 1);

 

RAWソケットを作り、カーネルがIPパケットを自動生成しないように IP_HDRINCL のオプションを有効にします。

PHPのsocket側で IP_HDRINCLの定数がなかったので、Linux側の定数の値 3 を直接していしています。

 

画像は自作IPと自作TCPのパケットを送信してtcpdumpした内容です。自作IPパケットで指定したIdentificationの値 12345 がキャプチャされてるのがわかります。socket_recvfrom()で受信した値の中にもIPヘッダのデータが格納されていました。

 

そもそもできそうになかった

PHP: socket_create - Manual

PHPのsocket_createのマニュアルにはAF_PACKETの指定ができず、AF_INETのみです。これだとイーサネットのフレームからの入出力が操作できないため2層からのアプローチはあきらめました。

PHP拡張のコードを見ると、AF_PACKETなどは指定できそうにありません。

https://github.com/php/php-src/blob/master/ext/sockets/sockets.c#L1097

 

LinuxのRAWソケットのドキュメントを読むとこう書かれています。

Ubuntu Manpage: raw - Linux の IPv4 raw ソケット

IPv4  レイヤは、扱っているソケットで  IP_HDRINCL  ソケットオプションが有効になっていなけれ
       ば、  パケットを送信するときに IP ヘッダーを生成する。 IP_HDRINCL オプションが有効になって
       いるときは、パケットには IP ヘッダーが含まれていなければならない。

TCP/IPを自作する記事でも、IP_HDRINCLオプションを使っているものがあります。

パケットジェネレータのつくり方【C言語】 | InfraPod

 

であれば、PHPのsocket_set_optionでIP_HDRINCLをしていすればできそうだと思うのですが、マニュアルにはそのオプションが記述されておらず、PHPの定数としても定義されていませんでした。

PHP: socket_get_option - Manual

 

PHPはC言語のラッパーみたいなところがあるので、LinuxのIP_HDRINCLの定数値を調べてそのまま渡せばいけるのでは?と思いやってみたところ無事にできました。

定数値はChatGPTに聞きました、便利。