【第7回】BSDでデータリンク層にフレームを送信するツールをC言語で作成する

本記事は第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するヘッダーファイルやマクロも一部異なるので、そこは適宜修正が必要です。それ以外では大きな違いはありません。

この記事は役に立ちましたか?

もし参考になりましたら、下記のボタンで教えてください。

関連記事

コメント

この記事へのコメントはありません。