MacOS Xでdivert socketを使う
divert socketとは,IPパケットの入出力を迂回させて,ユーザランドまで持って行くことが出来る機構です.もともとFreeBSDに実装されたものですが,FreeBSDを元にしているMacOS Xでもdivert socketを利用することが出来ます.
divert socketはipfwと一緒に利用します.ipfwはフィルタルールに基づき,IPパケットの出(入)力をフィルタします.フィルタされたパケットはdivert socketにforwardされ,divert socketを開いているアプリケーションはIPパケットを得ることが出来ます.
サンプルプログラムは以下の通りです.
#include <strings.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> int open_divert(uint16_t port) { struct sockaddr_in bind_port; int fd; fd = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT); if (fd < 0) { perror("socket"); return -1; } bzero(&bind_port, sizeof(bind_port)); bind_port.sin_family = PF_INET; bind_port.sin_port = htons(port); if (bind(fd, (struct sockaddr*)&bind_port, sizeof(bind_port)) < 0) { close(fd); perror("bind"); return -1; } return fd; } void read_loop(int fd) { struct sockaddr_in sin; struct in_addr addr; struct ip *hdr; socklen_t sin_len; ssize_t size; uint8_t buf[1024 * 100]; sin_len = sizeof(sin); for(;;) { size = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_len); if (size < 0) { perror("recvfrom"); exit(-1); } hdr = (struct ip*)buf; printf("recv %d[bytes]\n", size); printf("src: %s\n", inet_ntoa(hdr->ip_src)); printf("dst: %s\n", inet_ntoa(hdr->ip_dst)); printf("if addr: %s\n", inet_ntoa(sin.sin_addr)); printf("protocol: %d\n\n", hdr->ip_p); sendto(fd, buf, size, 0, (struct sockaddr*)&sin, sin_len); } } int main(int argc, char *argv[]) { int fd; if (argc < 2) { printf("usage: ./a.out port"); return -1; } fd = open_divert(atoi(argv[1])); if (fd < 0) return -1; read_loop(fd); return 0; }
socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)として,divert socketを開きます.開いた後は,bind関数で,divert socketを特定のポート番号にバインドします.このポート番号に対して,ipfwでforwardします.
たとえば,divert socketの10000番ポートを開き,UDPパケットの全てをこのポートに迂回させるようにしたいときは,以下のようにipfwを設定します.
$ sudo ipfw add 100 divert 10000 udp from any to any
パケットを開いた後は,recvfrom関数でパケットを読み込みます.
recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sin_len);
とありますが,もしも受信したパケットが出力パケットであるなら,sin.sin_addr == INADDR_ANYとなり,入力パケットであるならパケットを受信したインターフェースのアドレスとなります.bufには,実際のIPパケットが生で入っており,IPヘッダに書かれている情報を知りたい場合は,ip構造体を利用します.
divert socketに書き込むときは,sendtoを利用します.
sendto(fd, buf, size, 0, (struct sockaddr*)&sin, sin_len);
sin.sin_addr == INADDR_ANYの場合は出力とみなされ,IPの出力関数へ渡されます.それ以外の場合は,特定のインターフェースに対する入力パケットとして,IPの入力関数へ渡されます.
上記のサンプルプログラムを実行すると,以下のようになります.
$ sudo ipfw add 100 divert 10000 udp from any to any $ sudo ./a.out 10000 recv 62[bytes] src: 192.168.10.115 dst: 192.168.10.1 if addr: 0.0.0.0 protocol: 17 recv 252[bytes] src: 192.168.10.1 dst: 192.168.10.115 if addr: 192.168.10.115 protocol: 17
上が,何らかのプロセスから出力されたIPパケットを横取りしたもので,下が,インターフェースから入ってきたIPパケットを横取りしたものです.
divert socketへのforwardをやめる場合は,以下のようにします.
$ sudo ipfw delete 100