Rust で始める(低レイヤー?)Network Programing

Rustを使ったネットワークプログラミングを書いていきます。
ネットワーク、セキュリティの勉強のために低レイヤー(L2)からパケットを扱うプログラムを書いていきます。
~~webサーバーなどを実装したい人は、portをバインディングする書き方で十分です。~~

## 前提環境
・Rustがインストール済みのLinux
・管理者権限を得られる環境(Network インターフェイスを利用するため)
・L2スイッチ

Network知識の習得のためなので、参考に動かす方は隔離環境を用意して実行してください。


## pnet の使い方について
### 全てのecho Request(ping) に反応するプログラム
実行時の引数にインターフェイス名を指定すると、そのインターフェイスに来たecho request に対するecho replyを返す。
インターネットへ到達不能なネットワークのホストからインターネット上のホストへのpingが通るように見える。

<img width="400" alt="ネットワーク構成図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502095/8f9dce67-4370-f476-8735-c3df461c6534.png">

### About pnet
// 作成中 ...

## プログラム全体
``` rust:DummyReply.src
extern crate pnet;

use pnet::datalink;
use pnet::datalink::Channel::Ethernet;
use pnet::datalink::DataLinkSender;
use pnet::datalink::NetworkInterface;
use pnet::packet::ethernet::EtherTypes;
use pnet::packet::ethernet::EthernetPacket;
use pnet::packet::ethernet::MutableEthernetPacket;
use pnet::packet::icmp;
use pnet::packet::icmp::*;
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::ipv4;
use pnet::packet::ipv4::Ipv4Packet;
use pnet::packet::Packet;
use pnet::util::MacAddr;
use std::env;
use std::net::Ipv4Addr;


fn icmp_packet_reply(
    data: &[u8],
    ipv4packetinfo: IPv4PacketInfo,
    tx: &mut Box<dyn DataLinkSender>,
) {
    // construct icmp packet
    let mut raw_packet: Vec<u8> = vec![0; icmp::MutableIcmpPacket::minimum_packet_size() + data.len()];
    let mut mipacket: MutableIcmpPacket = icmp::MutableIcmpPacket::new(&mut raw_packet).unwrap();
    mipacket.set_payload(data);
    mipacket.set_icmp_type(IcmpTypes::EchoReply);
    mipacket.set_icmp_code(IcmpCode::new(0));
    mipacket.set_checksum(0 as u16);
    mipacket.set_checksum(icmp::checksum(&mipacket.to_immutable()));


    // construct ipv4 packet
    let mut raw_packet: Vec<u8> = vec![0; ipv4::MutableIpv4Packet::minimum_packet_size() + mipacket.packet().len()];
    let mut mi4packet: ipv4::MutableIpv4Packet = ipv4::MutableIpv4Packet::new(&mut raw_packet).unwrap();
    mi4packet.set_version(4); /* version */
    mi4packet.set_header_length(ipv4::Ipv4Packet::minimum_packet_size() as u8); /* Internet Header Length */
    mi4packet.set_header_length(5); /* Internet Header Length */
    mi4packet.set_ecn(24); /* Type of Service */
    mi4packet.set_dscp(24); /* Type of Service */
    mi4packet.set_total_length(mi4packet.packet().len() as u16); /* Total Length */
    mi4packet.set_identification(0); /* Identification */
    mi4packet.set_flags(0); /* Various Control Flags */
    mi4packet.set_fragment_offset(0); /* Fragment Offset */
    mi4packet.set_ttl(115); /* Time to Live */
    mi4packet.set_next_level_protocol(IpNextHeaderProtocols::Icmp); /* Protocol */
    mi4packet.set_checksum(0); /* Header Checksum */
    mi4packet.set_source(ipv4packetinfo.destination); /* Source Address */
    mi4packet.set_destination(ipv4packetinfo.source); /* Destination Address */
    mi4packet.set_checksum(ipv4::checksum(&mi4packet.to_immutable())); /* Header Checksum */
    mi4packet.set_payload(&mut mipacket.packet());


    // construct L2packet
    let mut raw_packet: Vec<u8> = vec![0; MutableEthernetPacket::minimum_packet_size() + mi4packet.packet().len()];
    let mut mepacket = MutableEthernetPacket::new(&mut raw_packet).unwrap();
    mepacket.set_payload(mi4packet.packet());

    mepacket.set_destination(ipv4packetinfo.ehternet_packet_info.as_ref().unwrap().source);
    mepacket.set_source(ipv4packetinfo.ehternet_packet_info.as_ref().unwrap().destination);
    mepacket.set_ethertype(EtherTypes::Ipv4);


    // reply
    tx.send_to(mepacket.packet(), None);
}


// Layer 3
struct IPv4PacketInfo {
    // Layer 3
    source: Ipv4Addr,
    destination: Ipv4Addr,
    ehternet_packet_info: Option<EthernetPacketInfo>
}
// Layer 2
struct EthernetPacketInfo {
    source: MacAddr,
    destination: MacAddr,
}


fn ethernet_packet_check(
    ethernet_packet: &EthernetPacket,
    tx: &mut Box<dyn DataLinkSender>,
){
    if ethernet_packet.get_ethertype() == EtherTypes::Ipv4 {
        let ethernetpacketinfo = EthernetPacketInfo {
            source: ethernet_packet.get_source(),
            destination: ethernet_packet.get_destination(),
        };


        let ipv4packet = Ipv4Packet::new(ethernet_packet.payload()).unwrap();
        if ipv4packet.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
            let ipv4packetinfo: IPv4PacketInfo = IPv4PacketInfo {
                source: ipv4packet.get_source(),
                destination: ipv4packet.get_destination(),
                ehternet_packet_info: Some(ethernetpacketinfo),
            };
            
            
            let icmppacket: IcmpPacket = IcmpPacket::new(ipv4packet.payload()).unwrap();
            if icmppacket.get_icmp_type() == IcmpType(8) {
                println!("catch Echo Request : {} -> {}", ipv4packet.get_source(), ipv4packet.get_destination());
                icmp_packet_reply(
                    icmppacket.payload(),
                    ipv4packetinfo,
                    tx,
                );
            }
        }
    }
}


fn main() {
    // set interface
    let mut interface_name: &str = "interface name";
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 {
        interface_name = args[1].as_str();
    }

    let interfaces: Vec<NetworkInterface> = datalink::interfaces()
        .into_iter()
        .filter(|interface: &NetworkInterface| interface_name == interface.name.as_str() )
        .collect();
    
    if interfaces.len() == 0 {
        println!("Interface is Not Found.");
        return;
    }
    let interface: &NetworkInterface = &interfaces[0];


    let (mut tx, mut rx) = datalink::channel(&interface, Default::default())
        .map(|ch| match ch {
            Ethernet(tx, rx) => (tx, rx),
            _ => panic!("Error occured."),
        })
        .unwrap();

    loop {
        match rx.next() {
            Ok(src) => {
                let ehternet_packet = &EthernetPacket::new(src).unwrap();
                ethernet_packet_check(ehternet_packet, &mut tx);
            }
            Err(_) => {
                println!("Error occured in loop.");
                break;
            }
        }
    }
}
```

pnet のバージョンを0.29.0で固定する。
※最新にするとdatalinkモジュールが削除されるのでインターフェイスを利用するために別のライブラリが必要になるため。
```toml:cargo.toml
[dependencies]
pnet = "0.29.0"
```