[概要]
Unix 系 OS では Raw IP ソケットインターフェースが用意されており、
生の IP パケットをユーザ空間で組み立てて送信する/任意のパケット
を受信することができます。これを利用することによって新しいトラン
スポートプロトコルなどをユーザ空間で実装することが可能になります。
Raw IP ソケットは各 OS毎に実装が若干異なるのでここでは Linux ベー
スで、Raw IP socket を使って UDP/IPパケットを生成し、送信するプ
ログラムを元にして解説します(受信についてはほかで解説されている
ので省略します)。
[解説]
基本的な情報は man 7 ip, man 7 raw にだいたい書かれているので
socket プログラミングの経験があればある程度イメージできると思い
ます。Raw IP socket を利用する場合と通常の socketを利用する場合
の大きな違いは、bind(2) を用いてローカルアドレスとのバインドをし
ません(できません)。パケットを送信する場合には bind(2) しないで
Raw IP ソケット経由でいきなり送信します。ただし setsockopt(2) を
つかってデバイスにバインドすることはできます(man 7 socket 参照の
こと)。
また、Raw IP ソケットは実効 uid が 0 のプロセスもしくは
CAP_NET_RAW のケーパビリティを持つプロセスのみオープン可能です。
以上をふまえるとおおまかな流れは以下のようになります。
- socket(2) を用いて Raw IP socket を生成
- setsockopt(2) を用いて Raw IP ソケットのオプションを設定
- IP パケット生成
- sendto(2) でデータ送信
また Raw IP を使う場合にはカーネルに渡すデータには IP へッダが含
めることができます。setsockopt(2) で IP_HDRINCL を設定す
ることによってカーネルが IP へッダを付加することを抑制できます。
このオプションを使った場合でもいくつかの IP へッダのフィールド値
の設定をカーネルにまかせることができます。内容は以下の通りです。
+------------------------------------------------------------------+
|IP Header fields modified on sending when IP_HDRINCL is specified |
+------------------------------------------------------------------+
| Sending fragments with IP_HDRINCL is not supported currently. |
+--------------------------+---------------------------------------+
|IP Checksum |Always filled in. |
+--------------------------+---------------------------------------+
|Source Address |Filled in when zero. |
+--------------------------+---------------------------------------+
|Packet Id |Filled in when passed as 0. |
+--------------------------+---------------------------------------+
|Total Length |Always filled in. |
+--------------------------+---------------------------------------+
ただし、フラグメントされたパケットにはこのオプションはうまく機能
しません。
まず、手始めとして socket(2) で Raw IP socket を作成しますが、
domain は PF_INET, type は SOCK_RAW, protocol は IPPROTO_RAW を
指定します。
int sockfd, on = 1;
sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
必要に応じて setsockopt(2) を用いて IP_HDRINCL を設定します(自分
で IP へッダをつけない場合には不要です)。
if ((setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))) <0) {
; /* error handling */
}
ここでは IP へッダを自分で生成することにしたので IP へッダを設定
し、次に UDP へッダを設定します。
char packet[ETH_DATA_LEN];
struct iphdr *iph;
struct udphdr *udph;
unsigned long saddr, daddr;
iph = (struct iphdr *)packet;
iph->version = 4;
iph->ihl = 5;
iph->tos = 0;
iph->tot_len = 0;
iph->id = 0;
iph->frag_off = 0;
iph->ttl = 16;
iph->protocol = IPPROTO_UDP;
iph->check = 0;
iph->saddr = saddr;
iph->daddr = daddr;
udph = (struct udphdr *)(packet + sizeof(struct iphdr));
udph->source = htons(dport); /* dummy */
udph->dest = htons(dport);
udph->len = htons(len + sizeof(struct udphdr));
udph->check = 0;
ここまでできれば、パケットのペイロード部分を設定し、あとは通常の
socket を用いたプログラミングと同じです。sendto(2) で送りたいデー
タを送るだけです。
最後に、実際の UDP/IP パケットを送るプログラムをつけておきますので、
参考にしてください。
[ファイル]
|