本記事は第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するヘッダーファイルやマクロも一部異なるので、そこは適宜修正が必要です。それ以外では大きな違いはありません。
この記事は役に立ちましたか?
もし参考になりましたら、下記のボタンで教えてください。
コメント