11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事誰得? 私しか得しないニッチな技術で記事投稿!

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

Last updated at Posted at 2023-07-16

これは何ですか?

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

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

完成品はコチラ★

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

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

Animation1.gif

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

Animation2.gif

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

なぜつくった?

詳細

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

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

ネットワーク構成図

image.png

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

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

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

Dockerfile

  • クライアントのDockerfileは、前回記事と同じ
  • ルーターはDockerfileを使わない。公式のRustイメージを使う。

entrypoint.sh

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

#!/bin/bash

cd /mnt
cargo run eth0 eth1

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

(NIC余談)Windowsの場合

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

docker-compose.yml

ポイント

  • ルータマシンはデフォルトでポートフォワードが有効になっているのでsysctlsnet.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がつくとアホになる」

2024年3月12日追記

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

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

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

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

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

ちなみ 17 は UDP である

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

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

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

糸冬了!!

11
6
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
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?