2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

この記事は

  • libpnetのサンプルコードにあるARPリクエストを使ってみる
  • サンプルコードの中身を読み解く(自分の勉強のため)

libpnetのARPサンプルコードを実行する

まずは環境づくりから。

  • cargo newで作ったまっさらな環境に上記サンプルコードをコピーして、main.rssrcフォルダに入れる
  • Cargo.tomlファイルのdependenciesに1行追加する
  • Packet.libをプロジェクトフォルダ直下に置く
[dependencies]
pnet = "0.33.0"

Packet.libはWinPcapから取得する

\WpdPack_4_1_2\WpdPack\Lib\Packet.libをプロジェクトフォルダにコピーする

4_1_2の数字は'23年6月時点

実行結果

引数にネットワークアダプタとIPアドレスを与える
launch.jsonだとこんな感じ

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            ...
            "args": ["\\Device\\NPF_{????????-????-????-????-????????}", "192.168.10.1"],
            ...
        },

ネットワークアダプタはLinuxであればeth0といったもの。
Windowsの場合は少しややこしく、Device\\NPF...形式の長ったらしい文字列となる。
(詳しい内容は付録に記載する)

試しにWindowsマシンでデフォルトゲートウェイのIPアドレスを調べた結果が下記

Sent ARP request 192.168.10.1
Received reply
Target MAC address: **:**:**:**:**:**

3行目でターゲットのMACアドレスが表示された

※オリジナルとの変更点:ログにIPアドレス追記/MACアドレスは伏字

次章からソースコードを読み解いていく

ARP要求パケットの中身

WireSharkで実際にキャプチャした結果が下図

image.png

全部で42バイトある(あとでバッファを定義するために、この42を使う)

赤枠で区切った単位で意味をもっている

内容 説明
ff*6 相手のMACアドレス(ARPリクエストはブロードキャスト)
黒塗り 自分のMACアドレス
08 06 ARPは0806 (TCP/IPは0800
00 01 ハードウェアタイプ。イーサネットは1
08 00 プロトコルタイプ。IPから調べるので0800
06 ハードウェアアドレス長。MACアドレスは6バイト
04 プロトコルアドレス長。IPアドレスは4バイト
00 01 ARPリクエスト要求は1
黒塗り 送信元MACアドレス
c0 a8 0a 65 送信元IPアドレス 192.168.10.101
00*6 送信先MACアドレス
c0 a8 0a 71 送信先IPアドレス

参考にしたサイトは巻末の"参考"に記載

ARP要求をするには、表にある情報を手元に集めればよい
get_mac_through_arp関数を紐解いていく

送信元IPアドレスsource_ip

引数で渡したinterface: NetworkInterfaceをもとに、IPv4アドレスを探している

    let source_ip = interface
        .ips
        .iter()
        .find(|ip| ip.is_ipv4())
        .map(|ip| match ip.ip() {
            IpAddr::V4(ip) => ip,
            _ => unreachable!(),
        })
        .unwrap();

データリンクレイヤの送受信窓口

  • 送信用sender
  • 受信用receiver
    let (mut sender, mut receiver) = match pnet::datalink::channel(&interface, Default::default()) {
        Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unknown channel type"),
        Err(e) => panic!("Error happened {}", e),
    };

Ethernetパケット

表の上3行分のところに該当する

内容 説明
ff*6 相手のMACアドレス(ARPリクエストはブロードキャスト)
黒塗り 自分のMACアドレス
08 06 ARPは0806 (TCP/IPは0800

バッファを42バイトとしているのは、ここで使う14バイトにペイロード分の28バイトを加えるため。
最終的に送り出すパケットの長さまで考慮して、ここであらかじめ42バイト確保している
ゼロで初期化しているが、ちゃんとしたデータで上書きするためにmutキーワードがついている

    let mut ethernet_buffer = [0u8; 42];
    let mut ethernet_packet = MutableEthernetPacket::new(&mut ethernet_buffer).unwrap();

    ethernet_packet.set_destination(MacAddr::broadcast());
    ethernet_packet.set_source(interface.mac.unwrap());
    ethernet_packet.set_ethertype(EtherTypes::Arp);

ethernet.rxの中をみると、ちゃんとイーサタイプは0806で定義されている

pub mod EtherTypes {
    use crate::ethernet::EtherType;

    /// Internet Protocol version 4 (IPv4) \[RFC7042\].
    pub const Ipv4: EtherType = EtherType(0x0800);
    /// Address Resolution Protocol (ARP) \[RFC7042\].
    pub const Arp: EtherType = EtherType(0x0806);
    ...
}

ARPパケット

表の残り後半部分を一気にsetしている
表とコードのどちらとも9行で合致していることがわかる

内容 説明
00 01 ハードウェアタイプ。イーサネットは1
08 00 プロトコルタイプ。IPから調べるので0800
06 ハードウェアアドレス長。MACアドレスは6バイト
04 プロトコルアドレス長。IPアドレスは4バイト
00 01 ARPリクエスト要求は1
黒塗り 送信元MACアドレス
c0 a8 0a 65 送信元IPアドレス 192.168.10.101
00*6 送信先MACアドレス
c0 a8 0a 71 送信先IPアドレス

表のバイト数をすべて足すと28バイトになるので、バッファを28としている

    let mut arp_buffer = [0u8; 28];
    let mut arp_packet = MutableArpPacket::new(&mut arp_buffer).unwrap();

    arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet);
    arp_packet.set_protocol_type(EtherTypes::Ipv4);
    arp_packet.set_hw_addr_len(6);
    arp_packet.set_proto_addr_len(4);
    arp_packet.set_operation(ArpOperations::Request);
    arp_packet.set_sender_hw_addr(interface.mac.unwrap());
    arp_packet.set_sender_proto_addr(source_ip);
    arp_packet.set_target_hw_addr(MacAddr::zero());
    arp_packet.set_target_proto_addr(target_ip);

要求するときのターゲットMACアドレスは、現時点では知りえない(知るためにARP要求を送る)ので、ゼロで埋めてある

ふたたびEthernetパケット

先ほどは宛先/送信元MACアドレスと、タイプだけセットして止まっていた
そのパケットのペイロードにARPパケット部分を埋め込む

    ethernet_packet.set_payload(arp_packet.packet_mut());

パケットを送信する

ペイロードに情報を埋め込んだethernet_packetを送信する
packet()関数によってバイト型の配列を取得しているっぽい

    sender
        .send_to(ethernet_packet.packet(), None)
        .unwrap()
        .unwrap();

libs.rssend_to関数の説明によると、第二引数は無視されるのでNoneを入れておけばよいとのこと

/// This may require an additional copy compared to build_and_send, depending on the
/// operating system being used. The second parameter is currently ignored, however
/// None should be passed.

応答を受信する

next()関数で、チャネルに届いているイーサネットフレームを取り出す
取り出したバイト配列のうち、最初のminimum_packet_size分だけを省いた箇所をARPパケットとしてさらに取り出す
送り元が要求したIPアドレスであり、かつ自分のMACアドレス宛てに届いていたら
それを真正なパケットとみなして、送り元のMACアドレス(=知りたかった情報)を表示する

MACアドレス不明だった端末のIPアドレスから応答をもらえた、ということになる

    while let buf = receiver.next().unwrap() {
        let arp = ArpPacket::new(&buf[MutableEthernetPacket::minimum_packet_size()..]).unwrap();
        if arp.get_sender_proto_addr() == target_ip
            && arp.get_target_hw_addr() == interface.mac.unwrap()
        {
            println!("Received reply");
            return arp.get_sender_hw_addr();
        }
    }

minimum_packet_sizeについて

minimum_packet_size14らしい

println!("{}", MutableEthernetPacket::minimum_packet_size());

// 出力: 14

MACアドレス6バイト×2と、タイプ2バイト分を足した分に等しい
つまりペイロードの部分とイコールとなる

内容 説明
ff*6 相手のMACアドレス(ARPリクエストはブロードキャスト)
黒塗り 自分のMACアドレス
08 06 ARPは0806 (TCP/IPは0800

おわりに

Rustでネットワークプログラミングをする上で欠かせないライブラリlibpnet
サンプルコードにあるARPリクエストを紐解いてみた

参考にリンクを貼っている仕様と比べながら読むと、当たり前ながら、1つ1つ組み立てられているのがわかる

そしてMACアドレスを詐称してARP要求に答えることがいかに容易いかも想像できる

参考

付録:Windowsのインターフェースを調べる方法

上記コードをcargo newしたプロジェクトのdependenciespnet_datalinkを追記する

[dependencies]
pnet_datalink = "0.33.0"

Device\NPFがずらっと5個ぐらい表示された

\Device\NPF_{~~~~~~~~~~~~~~~~~~~~~~~~~~~~}: flags=0
      index: 53
      ether: ~~~~~~~~
       inet: 172.17.80.1/20
...
...

ほかの方法としては、下記記事コードの冒頭部分を実行する
デフォルトで使っているインターフェースが1個だけ表示される

糸冬了!!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?