3の倍数と3の付くシーケンス番号でアホになるルータ

## これは何ですか？

3の倍数と3がつくシーケンス番号のときだけアホになる（**遅延する**）ルーターをつくった

そのヘンテコなルーターの紹介記事です！
Dockerでつくったので、掲載のソースコードをコピペすれば、誰でも再現可能かと思いますので、ぜひに！

## 完成品はコチラ★

画面左がルーター　／　画面右がクライアント（PINGを打つ係）

ナベアツ式ルールに従って、アホになっている（**１秒遅延している**）
アホじゃないときは、1ミリ秒以下の応答ですね

![Animation1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/32342209-3e73-1d56-d9f2-68c7b30eaa8f.gif)

お待ちかねのゾーン🐥　３０～３９は全部アホになる

![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/01f58404-d7a6-0e1f-a126-c55738fbd0f7.gif)

ちゃんと（？）、１秒遅延してからPINGを転送している

## なぜつくった？

- Rust勉強のため（ネットワークプログラミングを勉強中）
- [ルーター自作でわかるパケットの流れ](https://gihyo.jp/book/2011/978-4-7741-4745-1)を読んで、回線遅延装置をつくってみたくなったため（その足掛かりとして）

## 詳細

- 先にお見せしたルーターやPING打つクライアントは、すべてDockerコンテナ
- ネットワークも物理的な配線などは一切なく、すべてDockerネットワーク内の話

[前回記事](https://qiita.com/t13801206/items/d3b9829c7b5b24adf071)で、すでにDockerネットワークを作成してPINGの疎通確認まで終えている
今回はその続きとして、主にルータをアホにするための細工を施していく

### ネットワーク構成図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/da1b73f8-091b-935a-4352-670ecb022ff9.png)

端末|ネットワーク|MACアドレス
-|-|-
クライアント１号機|172.23.**1.10**/24|02:42:ac:17:**01:0a**
ルータ|172.23.**1.250**/24|02:42:ac:17:**01:fa**
ルータ|172.23.**2.250**/24|02:42:ac:17:**02:fa**
クライアント２号機|172.23.**2.10**/24|02:42:ac:17:**02:0a**

（後述しますが）、、
今回のルーターはインチキ仕様で、ARPをせずにMACアドレスをハードコーディングしているため、ここでMACアドレスをよく調べておく必要がある

:::note info
`:ac:`が:ac:になりますね。MACアドレスに`ac`が含まれると国旗に変換されてしまいますね。
:::

### Dockerfile

- クライアントのDockerfileは、[前回記事](https://qiita.com/t13801206/items/d3b9829c7b5b24adf071#dockerfile)と同じ
- ルーターはDockerfileを使わない。公式のRustイメージを使う。

### entrypoint.sh

ルータは、起動後に`cargo run`でRustのプログラムを実行する
プログラムはルータの役割として常駐し続ける

```sh
#!/bin/bash

cd /mnt
cargo run eth0 eth1
```

ルータは２つのネットワークに所属しており、NICを２つ備える
`eth0`と`eth1`が2つのNICであり、片方から受けたパケットを、もう片方のNICへ転送する
それぞれのNICのIPアドレスとMACアドレスは先述の表のとおり

#### （NIC余談）Windowsの場合

Linuxでは`eth0`であるが、Windowsの場合は`\\Device\\NPF_{A????????-F???-????-????-E??????}`のような長い文字列になる
具体的にどういう文字列になるかは、以下の記事のコードを実行すればわかる

https://qiita.com/m10i/items/f8d3db359f150aafc83b

### docker-compose.yml

ポイント

- ルータマシンはデフォルトでポートフォワードが有効になっているので`sysctls`を`net.ipv4.ip_forward=0`とする


```yml
version: '3.9'

services:
  client1:
    build: ./client1
    container_name: client1
    hostname: client1
    tty: true
    stdin_open: true
    privileged: true
    volumes:
      - ./client1/mnt:/mnt
    networks:
      delay-net1:      
        ipv4_address: 172.23.1.10
    entrypoint: /mnt/entrypoint.sh

  client2:
    build: ./client2
    container_name: client2
    hostname: client2
    tty: true
    stdin_open: true
    privileged: true
    volumes:
      - ./client2/mnt:/mnt
    networks:
      delay-net2:      
        ipv4_address: 172.23.2.10
    entrypoint: /mnt/entrypoint.sh

  router:
    image: rust
    container_name: router
    hostname: router
    volumes:
      - ./:/mnt
    networks:
      delay-net1:
        ipv4_address: 172.23.1.250
      delay-net2:      
        ipv4_address: 172.23.2.250
    sysctls:
      - net.ipv4.ip_forward=0
    entrypoint: /mnt/router/mnt/entrypoint.sh
      
networks:
  delay-net1:
    ipam:
      driver: default
      config:
        - subnet: 172.23.1.0/24
  delay-net2:
    ipam:
      driver: default
      config:
        - subnet: 172.23.2.0/24
```

クライアントの設定ポイントは[前回記事](https://qiita.com/t13801206/items/d3b9829c7b5b24adf071)を参照のこと

### Rustソースコード`main.rs`

[Rustで始めるネットワークプログラミング](https://github.com/teru01/socket-programming)のソースコードを大いに参考にさせていただいた
Rustのスレッドもよくわかっておらず、とりあえず動けばいいコードなので悪しからず

ARPは実装しておらず、Dockerネットワークをつくった段階でMACアドレスを調べて
MACアドレスをハードコーディングするという荒業

```rust
use log::info;
use once_cell::sync::Lazy;
use pnet::datalink::NetworkInterface;
use pnet::datalink::{self, Channel::Ethernet};
use pnet::packet::ip::IpNextHeaderProtocol;
use pnet::packet::{ethernet::EthernetPacket, ipv4::Ipv4Packet, Packet};
use std::{env, net::IpAddr};
use std::{thread, time};

static INTERFACE: Lazy<Vec<NetworkInterface>> = Lazy::new(|| pnet::datalink::interfaces());

fn main() {
    env::set_var("RUST_LOG", "debug");
    env_logger::init();

    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        log::error!("Please specify target interface name");
        std::process::exit(1);
    }
    let interface_name1 = &args[1];
    let interface_name2 = &args[2];

    let interface1 = INTERFACE
        .iter()
        .find(|iface| iface.name == *interface_name1)
        .expect("Failed to get interface1");
    let interface2 = INTERFACE
        .iter()
        .find(|iface| iface.name == *interface_name2)
        .expect("Failed to get interface2");

    let (mut tx, mut rx) = match datalink::channel(interface1, Default::default()) {
        Ok(Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unhandled channel type"),
        Err(e) => panic!(
            "An error occurred when creating the datalink channel: {}",
            e
        ),
    };

    let (mut txx, mut rxx) = match datalink::channel(interface2, Default::default()) {
        Ok(Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unhandled channel type"),
        Err(e) => panic!(
            "An error occurred when creating the datalink channel: {}",
            e
        ),
    };

    let handle1 = thread::spawn(move || {
        let wait_time = time::Duration::from_millis(1000);
        let mut x = 1;
        loop {
            match rx.next() {
                Ok(packet_in) => {
                    let packet = EthernetPacket::new(packet_in).unwrap();
                    let ip_packet = Ipv4Packet::new(packet.payload());

                    if let Some(ip_packet) = ip_packet {
                        println!(
                            "{0} {1} -> {2} {3}\t{4}",
                            IpAddr::V4(ip_packet.get_source()),
                            packet.get_source(),
                            IpAddr::V4(ip_packet.get_destination()),
                            packet.get_destination(),
                            ip_packet.get_next_level_protocol(),
                        );

                        if ip_packet.get_next_level_protocol() == IpNextHeaderProtocol(1) {
                            if x % 3 == 0 || x / 10 % 10 == 3 || x % 10 == 3 {
                                thread::sleep(wait_time);
                                info!("🤪<<「{}」", x);
                            } else {
                                info!("😐<<「{}」", x);
                            }
                            x += 1;
                        }
                    }

                    let mut v1 = vec![2, 66, 172, 23, 2, 10, 2, 66, 172, 23, 2, 250];
                    v1.extend(&packet_in[12..]);
                    txx.send_to(&v1, Some(interface2.to_owned()));
                }
                Err(e) => {
                    panic!("An error occurred while reading: {}", e);
                }
            }
        }
    });

    let handle2 = thread::spawn(move || loop {
        match rxx.next() {
            Ok(packet_in) => {
                let packet = EthernetPacket::new(packet_in).unwrap();
                let ip_packet = Ipv4Packet::new(packet.payload());

                if let Some(ip_packet) = ip_packet {
                    println!(
                        "{0} {1} -> {2} {3}\t{4}",
                        IpAddr::V4(ip_packet.get_source()),
                        packet.get_source(),
                        IpAddr::V4(ip_packet.get_destination()),
                        packet.get_destination(),
                        ip_packet.get_next_level_protocol(),
                    );
                }

                let mut v1 = vec![2, 66, 172, 23, 1, 10, 2, 66, 172, 23, 1, 250];
                v1.extend(&packet_in[12..]);
                tx.send_to(&v1, Some(interface2.to_owned()));
            }
            Err(e) => {
                panic!("An error occurred while reading: {}", e);
            }
        }
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}
```

宛先MACアドレスと送信元MACアドレスは、u8配列の上位12要素目までにある
本来はARPテーブルを使うところを直接書き換えている

```rust
// MACアドレスハードコーディング
let mut v1 = vec![2, 66, 172, 23, 1, 10, 2, 66, 172, 23, 1, 250];
// 入ってきたパケットの上位12バイトを書き換え
v1.extend(&packet_in[12..]);
```

dependenciesは以下の通り

```toml
[dependencies]
default-net = "0.15"
pnet = "0.33.0"
log = "0.4.19"
env_logger = "0.10.0"
once_cell = "1.18.0"
```

## おわりに

Dockerコンテナ上で、Rustイメージをベースとしたルータマシンをつくった

ICMPパケットを受けた回数をカウントして
ナベアツ式ルールによってパケット転送をわざと遅らせてみた

とりあえずやりたいことができたので
今後はRustをきれいに書けるようにしていきつつ
ARPやらNATやらをエセ実装していきたい

## 参考記事

[Rust 練習問題「３がつくとアホになる」](https://rcie.hatenablog.com/entry/2021/09/01/220905)

https://github.com/teru01/socket-programming

## 2024年3月12日追記

もう少し真面目に、現実的に使う場合について

### TCPパケットを遅延させる方法

本文では、ICMPだけが遅延される
なぜならば、下記コードの if 分の右辺が`IpNextHeaderProtocol(1)`となっているから

```rust
if ip_packet.get_next_level_protocol() == IpNextHeaderProtocol(1) {              
    thread::sleep(wait_time);
}     
```

IpNextHeaderProtocolのカッコ内が `6` であれば、それはTCPを表す
ソースコードにそう書いてある

https://docs.rs/pnet_packet/0.34.0/src/pnet_packet/ip.rs.html#493

ちなみ `17` は UDP である

### ハードコーディングしたMACアドレスの見方

先頭の6バイトが宛先MACアドレス、後半の6バイトが送信元MACアドレス
後半はおのずと中継ルーターのMACアドレスになる

```rust
let mut v1 = vec![/*宛先MACアドレス*/2, 66, 172, 23, 2, 10, /*ここから送信元MACアドレス*/2, 66, 172, 23, 2, 250];
```

## 糸冬了！！