本記事は第6回の記事のBSD版です。OpenBSDを使い、ARPリプライを送信するプログラムを作成します。
参考:【第6回】Linuxでデータリンク層にフレームを送信するツールをC言語で作成する
イーサネットヘッダーやARPに関する解説は第6回の記事でおこなっていますので、本記事ではBSD特有の箇所について解説します。
データリンク層でフレームを送信するサンプルプログラム
最初にサンプルプログラム掲載します。細かい解説はその後におこなっています。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <net/bpf.h> #include <net/if.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <sys/ioctl.h> #include <arpa/inet.h> struct sendhdr { struct ether_header ether; struct arphdr arp; unsigned char srcmac[ETHER_ADDR_LEN]; unsigned char srcip[4]; unsigned char targetmac[ETHER_ADDR_LEN]; unsigned char targetip[4]; }; int main(int argc, char *argv[]) { unsigned char buf[BUFSIZ]; char bpfname[BUFSIZ]; int d; int spoof = 1; char *ifname, *srcmac, *dstmac, *srcip, *targetmac, *targetip; struct ifreq ifr; struct sendhdr *s; if (argc != 6) { printf("Usage %s <interface> <src mac> <dst mac> <src ip> <target ip>\n", argv[0]); exit(1); } ifname = argv[1]; srcmac = argv[2]; dstmac = argv[3]; srcip = argv[4]; targetip = argv[5]; printf("interface: %s\n", ifname); printf("ether src mac: %s\n", srcmac); printf("ether dst mac: %s\n", dstmac); printf("arp src mac: %s\n", srcmac); printf("arp src ip: %s\n", srcip); printf("arp target mac: %s\n", dstmac); printf("arp target ip: %s\n", targetip); /* BPFを開く */ for (int i = 0; i < 10; i++) { snprintf(bpfname, sizeof(bpfname), "/dev/bpf%d", i); if ((d = open(bpfname, O_RDWR, 0)) > 0) break; } if (d < 0) { perror("bpf"); exit(1); } strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* 送信インタフェースを指定する */ if (ioctl(d, BIOCSETIF, &ifr) < 0) { perror("ioctl"); exit(1); } /* 送信元MACアドレスを設定できるようにする */ if (ioctl(d, BIOCSHDRCMPLT, &spoof) < 0) { perror("ioctl"); exit(1); } /* Ether Header */ s = (struct sendhdr *) buf; memcpy(&s->ether.ether_shost, ether_aton(srcmac), ETHER_ADDR_LEN); memcpy(&s->ether.ether_dhost, ether_aton(dstmac), ETHER_ADDR_LEN); s->ether.ether_type = htons(ETHERTYPE_ARP); /* ARP Header */ s->arp.ar_hrd = htons(ARPHRD_ETHER); s->arp.ar_pro = htons(ETHERTYPE_IP); s->arp.ar_hln = ETHER_ADDR_LEN; s->arp.ar_pln = 4; s->arp.ar_op = htons(ARPOP_REPLY); /* ARPリプライのMACアドレスとIPアドレス */ memcpy(&s->srcmac, ether_aton(srcmac), ETHER_ADDR_LEN); inet_pton(AF_INET, srcip, &s->srcip); memcpy(&s->targetmac, ether_aton(dstmac), ETHER_ADDR_LEN); inet_pton(AF_INET, targetip, &s->targetip); /* フレーム送信 */ if (write(d, buf, sizeof(struct sendhdr)) < 0) perror("write"); close(d); return 0; }
Linuxではsocket(2)を使ってフレームを送信していましたがBSDではBPF(BSD Packet Filter)デバイスを使います。このデバイスは/dev/bpf
にあります。
openbsd# ls /dev/bpf* /dev/bpf /dev/bpf0 openbsd#
本記事のサンプルコードでは/dev/bpf0~/dev/bpf9まで順にopenして、成功したデバイスファイルを使っています。
/* BPFを開く */ for (int i = 0; i < 10; i++) { snprintf(bpfname, sizeof(bpfname), "/dev/bpf%d", i); if ((d = open(bpfname, O_RDWR, 0)) > 0) break; }
BPFデバイスのopenに成功したら送信するインタフェースを指定します。ioctl(2)の引数にBIOCSETIF
を渡してインタフェースを指定します。
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* 送信インタフェースを指定する */ if (ioctl(d, BIOCSETIF, &ifr) < 0) { perror("ioctl"); exit(1); }
つぎにioctl(2)の引数にBIOCSHDRCMPLTを渡して送信元MACアドレスを設定できるようにします。この設定をしないと指定したMACアドレスで送信できません。
/* 送信元MACアドレスを設定できるようにする */ if (ioctl(d, BIOCSHDRCMPLT, &spoof) < 0) { perror("ioctl"); exit(1); }
そしてフレームの送信はwrite(2)を使います。
/* フレーム送信 */ if (write(d, buf, sizeof(struct sendhdr)) < 0) perror("write");
まとめ
Linuxとの主な違いはsocket(2)を使わずBPFデバイスを使うこと、送信はsendto(2)ではなくwrite(2)を使うことです。また、includeするヘッダーファイルやマクロも一部異なるので、そこは適宜修正が必要です。それ以外では大きな違いはありません。
この記事は役に立ちましたか?
もし参考になりましたら、下記のボタンで教えてください。
コメント