いま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のマニュアルには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に聞きました、便利。