以前、ネットワークまわりをPythonで実装してみたことがありました。
- Python2 + Scapyで、Raspberry Pi 2 Model B をNAT/NAPTルータ化してみた #router_jisaku - メモ的な思考的な
- Mac + Python2 + Scapyで、DHCPサーバを探してみた - メモ的な思考的な
- Scapy + Python2 + I/O多重化・ブロッキングI/O系モジュールにて、Raspberry Pi 2 Model Bをブリッジ化する - メモ的な思考的な
Rustでもネットワークまわりを実装したくなり、以下の書籍を写経したりしました。
書籍「Rustで始めるネットワークプログラミング」を写経した - メモ的な思考的な
次は、Rustでもう少し低レイヤーあたりをさわってみようと考えて調べたところ、Rustと pnet クレートを使うことでデータリンク層の通信も実装できそうでした。
https://github.com/libpnet/libpnet
まず最初に、 pnet クレートのstruct NetworkInterface まわりをさわってみたことから、メモを残します。
目次
環境
- mac
- Rust 1.89.0
- pnet 0.35.0
- RustRover 2025.2.1
環境構築
手元のmacにはRust環境がなかったため、Rustの公式ドキュメントに従い環境構築をしました。
Install Rust - Rust Programming Language
curl でインストールします。
% curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh **info:** downloading installer **Welcome to Rust!** This will download and install the official compiler for the Rust programming language, and its package manager, Cargo. Rustup metadata and toolchains will be installed into the Rustup home directory, located at: /Users/<UserName>/.rustup This can be modified with the RUSTUP_HOME environment variable. The Cargo home directory is located at: /Users/<UserName>/.cargo This can be modified with the CARGO_HOME environment variable. The **cargo**, **rustc**, **rustup** and other commands will be added to Cargo's bin directory, located at: /Users/<UserName>/.cargo/bin This path will then be added to your **PATH** environment variable by modifying the profile files located at: /Users/<UserName>/.profile /Users/<UserName>/.zshenv You can uninstall at any time with **rustup self uninstall** and these changes will be reverted. Current installation options: default host triple: **aarch64-apple-darwin** default toolchain: **stable (default)** profile: **default** modify PATH variable: **yes** 1) Proceed with standard installation (default - just press enter) 2) Customize installation 3) Cancel installation > (1を選択してEnter)
選択した後はインストールが続きます。
**info:** profile set to 'default' **info:** default host triple is aarch64-apple-darwin **info:** syncing channel updates for 'stable-aarch64-apple-darwin' **info:** latest update on 2025-08-07, rust version 1.89.0 (29483883e 2025-08-04) **info:** downloading component 'cargo' **info:** downloading component 'clippy' **info:** downloading component 'rust-docs' **info:** downloading component 'rust-std' **info:** downloading component 'rustc' **info:** downloading component 'rustfmt' **info:** installing component 'cargo' **info:** installing component 'clippy' **info:** installing component 'rust-docs' **info:** installing component 'rust-std' **info:** installing component 'rustc' **info:** installing component 'rustfmt' **info:** default toolchain set to 'stable-aarch64-apple-darwin' **stable-aarch64-apple-darwin installed** - rustc 1.89.0 (29483883e 2025-08-04) **Rust is installed now. Great!** To get started you may need to restart your current shell. This would reload your **PATH** environment variable to include Cargo's bin directory ($HOME/.cargo/bin). To configure your current shell, you need to source the corresponding **env** file under $HOME/.cargo. This is usually done by running one of the following (note the leading DOT): . "$HOME/.cargo/env" # For sh/bash/zsh/ash/dash/pdksh source "$HOME/.cargo/env.fish" # For fish source $"($nu.home-path)/.cargo/env.nu" # For nushell
インストールはできたものの
This is usually done by running one of the following
と言われているため、以下を実行しました。
% . "$HOME/.cargo/env"
続いて、今回のプログラム用に cargo newします。
% cargo new list_network_interfaces **Creating** binary (application) `list_network_interfaces` package **note****:** see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
最後に、 cargo.toml に対し pnet クレートへの依存を追加します。
[dependencies] pnet = "0.35.0"
pnetのinterfaces関数でネットワークインタフェースを列挙
pnetのGitHubリポジトリでは、 examplesディレクトリの中にネットワークインタフェースを列挙する例があります。
https://github.com/libpnet/libpnet/blob/v0.35.0/examples/list_interfaces.rs
例では {}を使っていますが、 interfaces() 関数の戻り値である NetworkInterfaceでは Debug traitも実装していました。
- https://docs.rs/pnet/latest/pnet/datalink/fn.interfaces.html
- https://docs.rs/pnet/latest/pnet/datalink/struct.NetworkInterface.html
そこで、Debug trait用に {?:}も追加し、両者の違いを見てみることにしました。
use pnet::datalink; fn main() { println!("Network Interfaces"); println!("================================>"); println!(); print_interfaces(); print_interface_fields(); } fn print_interfaces() { for interface in datalink::interfaces() { println!("=========================>"); // Displayトレイトでの表示 println!("{}", interface); println!("================"); // Debugトレイトでの表示 println!("{:?}", interface); println!("<========================="); } }
手元のmacで実行してみたところ、結果が表示されました。以下は抜粋 & マスク済です。
Display traitのほうが読みやすいですが、実装する上ではDebug traitのほうが分かりやすく感じました。
Network Interfaces
================================>
=========================>
lo0: flags=8049<UP,LOOPBACK,MULTICAST,RUNNING>
index: 1
ether: 00:00:00:00:00:00
inet: 127.0.0.1/8
inet6: ::1/128
inet6: fe80::1/64
================
NetworkInterface { name: "lo0", description: "", index: 1, mac: Some(00:00:00:00:00:00), ips: [V4(Ipv4Network { addr: 127.0.0.1, prefix: 8 }), V6(Ipv6Network { addr: ::1, prefix: 128 }), V6(Ipv6Network { addr: fe80::1, prefix: 64 })], flags: 32841 }
<=========================
...
=========================>
en1: flags=8863<UP,BROADCAST,MULTICAST,RUNNING>
index: 15
ether: **:**:**:**:**:**
inet6: ****::****:****:****:****/64
inet6: ****::****:****:****:****/64
inet6: ****::****:****:****:****/64
inet: 192.168.***.***/24
================
NetworkInterface { name: "en1", description: "", index: 15, mac: Some(**:**:**:**:**:**), ips: [V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 }), V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 }), V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 }), V4(Ipv4Network { addr: 192.168.***.***, prefix: 24 })], flags: 34915 }
<=========================
上記では省略しましたが、
- gif0
- stf0
- anpi0
- bridge0
- utun1
- ap1
- awdl0
- llw0
などのインタフェースが表示されました。
各インタフェースの詳細を確認する
NetworkInterfaceのドキュメントを読むと、
- is_up()
- is_loopback()
などのメソッドも用意されているようでした。
https://docs.rs/pnet/latest/pnet/datalink/struct.NetworkInterface.html
そこで、どんな値を取得できるか試してみます。
use pnet::datalink; fn main() { println!("Network Interfaces"); println!("================================>"); print_interfaces(); // 追加 print_interface_fields(); } fn print_interface_fields() { for interface in datalink::interfaces() { println!("=========================>"); // インタフェース名 println!("Name: {:?}", interface.name); // インターフェースの説明 println!("Description: {:?}", interface.description); // MACアドレスがあれば、そのMACアドレスを表示 match interface.mac { Some(mac) => println!("MAC: {:?}", mac), None => println!("Macアドレスはありません"), } // IPアドレスがあれば、そのIPアドレスの一覧を表示 if interface.ips.is_empty() { println!("IPアドレスがありません") } else { // 所有権の移動を防ぐため、interfaceに `&` を使って参照だけを使う for ip in &interface.ips { println!("IP: {:?}", ip); } } // インタフェースの状態 println!("Status: {}", if interface.is_up() { "UP" } else { "DOWN" }); // ループバックインタフェースかどうかを確認 println!("Loopback: {}", if interface.is_loopback() { "YES" } else { "NO" }); // ブロードキャストに対応しているか println!("Broadcast: {}", if interface.is_broadcast() { "YES" } else { "NO" }); // マルチキャストに対応しているか println!("Multicast: {}", if interface.is_multicast() { "YES" } else { "NO" }); // P2Pに対応しているか println!("P2P: {}", if interface.is_point_to_point() { "YES" } else { "NO" }); println!("<========================="); } }
実行結果を抜粋します。
Name: "lo0"
Description: ""
MAC: 00:00:00:00:00:00
IP: V4(Ipv4Network { addr: 127.0.0.1, prefix: 8 })
IP: V6(Ipv6Network { addr: ::1, prefix: 128 })
IP: V6(Ipv6Network { addr: fe80::1, prefix: 64 })
Status: UP
Loopback: YES
Broadcast: NO
Multicast: YES
P2P: NO
<=========================
=========================>
Name: "en1"
Description: ""
MAC: **:**:**:**:**:**
IP: V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 })
IP: V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 })
IP: V6(Ipv6Network { addr: ****::****:****:****:****, prefix: 64 })
IP: V4(Ipv4Network { addr: 192.168.***.***, prefix: 24 })
Status: UP
Loopback: NO
Broadcast: YES
Multicast: YES
P2P: NO
他のインタフェースだと、P2PなどがYESとなっているものもありました。
Name: "gif0" Description: "" MAC: 00:00:00:00:00:00 IPアドレスがありません Status: DOWN Loopback: NO Broadcast: NO Multicast: YES P2P: YES
ソースコード
GitHubに上げました。
https://github.com/thinkAmi-sandbox/rust_network_programming-practice
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/rust_network_programming-practice/pull/1