PHPでパケットキャプチャ
単なる実験ですが、PHPでパケットキャプチャするようなものを作ってみました。
元々PHPは、ウェブアプリケーション用の言語なのでOSに依存するリンクレイヤを扱うようなAPIは用意されていません。
無いなら作ってしまえ!ということで、Linux専用ですが extension を作りました。(実験なので超適当です)
Debian 5 での例
# su
# apt-get source php5
# exit
% cd php5-5.2.6.dfsg.1/ext
% ./ext_skel --extname=エクステンション名 エクステンション名には好きな名前をつけます
% cd エクステンション名
% vi config.m4
dnl PHP_ARG_ENABLE(エクステンション名, whether to enable エクステンション名 support,
dnl Make sure that the comment is aligned:
dnl [ --enable-エクステンション名 Enable エクステンション名 support])
この3行のdnlを消して保存する。
% phpize (このコマンドが見つからないときは、apt-get install php5-dev をしておく。)
% ./configure
% vi php_エクステンション名.h
PHP_FUNCTION(confirm_エクステンション名_compiled); /* For testing, remove later. */
の下に、これから作る関数名を追加する。
PHP_FUNCTION(raw_socket);
PHP_FUNCTION(raw_close);
PHP_FUNCTION(raw_recv);
% vi エクステンション名.c
#include "php_エクステンション名.h"
の下に、必要なヘッダファイルを書いておく。
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/if.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>
#include <ifaddrs.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
(とりあえず、自分の著書からコピーしたのでいらないのも含まれています・・・)
zend_function_entry エクステンション名_functions[] = {
PHP_FE(confirm_エクステンション名_compiled, NULL) /* For testing, remove later. */
と、
{NULL, NULL, NULL} /* Must be the last line in エクステンション名_functions[] */
}
の間に、これから作る関数名を追加しておきます。
PHP_FE(raw_socket, NULL)
PHP_FE(raw_close, NULL)
PHP_FE(raw_recv, NULL)
ファイルの一番末尾に関数の実態を書きます。
PHP_FUNCTION(raw_socket)
{
struct ifreq if_req;
struct sockaddr_ll sa;
int soc;
char *str;
int str_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"s",
&str,
&str_len)==FAILURE){
return;
}
/* ソケットの生成 */
if ((soc = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("socket");
return;
}
/* インタフェース情報の取得 */
(void) snprintf(if_req.ifr_name, sizeof(if_req.ifr_name), "%s", str);
if (ioctl(soc, SIOCGIFINDEX, &if_req) == -1) {
perror("ioctl");
(void) close(soc);
return;
}
/* インタフェースをbind() */
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_req.ifr_ifindex;
if (bind(soc, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
perror("bind");
(void) close(soc);
return;
}
/* インタフェースのフラグ取得 */
if (ioctl(soc, SIOCGIFFLAGS, &if_req) == -1) {
perror("ioctl");
(void) close(soc);
return;
}
/* インタフェースの*/
if_req.ifr_flags = if_req.ifr_flags|IFF_PROMISC|IFF_UP;
if (ioctl(soc, SIOCSIFFLAGS, &if_req) == -1) {
perror("ioctl");
(void) close(soc);
return (-1);
}
RETURN_LONG(soc);
}
PHP_FUNCTION(raw_close)
{
long l;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l",
&l)==FAILURE){
return;
}
RETURN_LONG(close(l));
}
PHP_FUNCTION(raw_recv)
{
long l,len,i;
char buf[1500];
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l",
&l)==FAILURE){
return;
}
array_init(return_value);
len=recv(l,buf,1500,0);
for(i=0;i<len;i++){
add_index_long(return_value,i,buf[i]);
}
}
ここまで来たら、エクステンションをビルドします。
% make
# su
# cp modules/エクステンション名.so /usr/lib/php5/20060613+lfs
# vi pdump.php
<?php
dl('エクステンション名.so');
$soc=raw_socket("eth0");
$buf=raw_recv($soc);
foreach($buf as $c){
printf("%x",$c);
}
echo "\n";
raw_close($soc);
?>
# php ./pdump.php
エクステンション名 => enabled
01b21e17ffffff840ffffffa0ffffffde34ffffffb9228045100345affffffb64003f65effffff97ffffffc0ffffffa80ffffffdcffffffc0ffffffa803affffffc7e016ffffffc36effffffb3ffffffa0affffffe1957ffffff8010ffffffffffffffffffffffd0ffffffea00118a1134ffffffb814d7ffffffffa37
無事、1個のパケットが出力されました!
以上、本日の実験でした。