これは何ですか?

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

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

完成品はコチラ★

画面左がルーター / 画面右がクライアント(PINGを打つ係)

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

Animation1.gif

お待ちかねのゾーン? 30~39は全部アホになる

Animation2.gif

ちゃんと(?)、1秒遅延してからPINGを転送している

なぜつくった?

    • Rust勉強のため(ネットワークプログラミングを勉強中)

ルーター自作でわかるパケットの流れを読んで、回線遅延装置をつくってみたくなったため(その足掛かりとして)

詳細

    • 先にお見せしたルーターやPING打つクライアントは、すべてDockerコンテナ

 

    ネットワークも物理的な配線などは一切なく、すべてDockerネットワーク内の話

前回記事で、すでにDockerネットワークを作成してPINGの疎通確認まで終えている
今回はその続きとして、主にルータをアホにするための細工を施していく

ネットワーク構成図

image.png
端末ネットワークMACアドレスクライアント1号機172.23.1.10/2402:42:flag_ac:17:01:0aルータ172.23.1.250/2402:42:flag_ac:17:01:faルータ172.23.2.250/2402:42:flag_ac:17:02:faクライアント2号機172.23.2.10/2402:42:flag_ac:17:02:0a

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

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

Dockerfile

    • クライアントのDockerfileは、前回記事と同じ

 

    ルーターはDockerfileを使わない。公式のRustイメージを使う。

entrypoint.sh

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

#!/bin/bash

cd /mnt
cargo run eth0 eth1

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

(NIC余談)Windowsの場合

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

 

docker-compose.yml

ポイント

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

クライアントの設定ポイントは前回記事を参照のこと

Rustソースコードmain.rs

Rustで始めるネットワークプログラミングのソースコードを大いに参考にさせていただいた
Rustのスレッドもよくわかっておらず、とりあえず動けばいいコードなので悪しからず

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

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テーブルを使うところを直接書き換えている

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

dependenciesは以下の通り

[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 練習問題「3がつくとアホになる」

 

糸冬了!!

广告
将在 10 秒后关闭
bannerAds