⏱️

「Raspberry Piで作るネットワーク遅延模擬ブリッジ」備忘録

2022/03/03に公開

はじめに

おことわり

本記事はすべて個人の見解・理解によるもので,所属する組織・団体とは一切の関連を持ちません.

したがって本記事の正確性等は保証できませんが、理解の誤りや、誤植・手順漏れ等、コメント・ご指摘に関しては何でも頂ければ幸いです.

参考

https://aquasoftware.net/blog/?p=1462
https://qiita.com/taqu/items/3c3dc78a83d05d0d09f6
http://dsas.blog.klab.org/archives/raspi-netem1.html

書くこと

  • 簡単なネットワーク遅延模擬ブリッジの作成手順メモ
  • 動作確認
  • できそうなこと・今後の展望

書かないこと

  • 各種利用技術の原理詳細
  • 各種CUI/GUIの実装

背景・目的

任意のゲームジャンルにおいて,ネットワーク遅延がどの程度まで大きくなると体感に影響を及ぼすのかを調べる上で,遅延量・ジッタ・パケロス率等を自由にいじれるエミュレータが欲しかった.

アプリケーションやアプリケーションサーバー側に遅延の機構を組み込むことも考えたが,アプリケーションがどんなものができるかもわからないし,都度都度その機構を実装・設定するのも面倒で,やはりネットワーク層に直接触れる方法かつ可搬性に優れたRaspberry Piを用いた手法が良いと考えた.

市販のネットワークエミュレータは,個人で買うには非常に高価でなかなか手が出しづらいこともあり,家に転がっているRaspberry Piやサーバーを使って模擬できないかを検討した.

本記事は,今回構築した環境・手順と,今後上記課題を達成する上で必要な機能等のアイデアについての備忘録として記録するものであり,何か新しい知見を含むものではないことにご留意願いたい.

本文

構築環境

今回構築した環境は以下図・写真に示した.
ルーターは以前使っていたBaffalo xxx,Raspberry Piは2台,Client PCはMacBook Proを用いている.

Raspberry Piのうち,一台はbridge1.local,もう一台をpi1.localとして,Client PCとpi1.local間の経路に遅延模擬ブリッジであるbridge1.localを挟むような構成を作る.

ちなみに,全端末は別途Internetへ抜けるLAN(Wi-Fi)に接続されており,セットアップに際してはWi-Fi経由で,各種ICMPパケット等については有線(Ether)経由で行われるようにルーティングしてある.

構成写真:机が狭すぎる

手順

Raspberry Pi x 2台のOSセットアップは,Raspberry Pi Imager v1.7.1を用いて, Raspberry Pi OS (64-bit)を64GByteのMicroSDに焼いたものを用いた.

まずは, bridge1.localにWi-Fi経由でSSH接続し,各種設定を行う.

bridge1.local:~ $ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether dc:a6:32:9d:4b:79 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic noprefixroute wlan0
       valid_lft 86350sec preferred_lft 75550sec
    inet6 240d:1a:bf3:d300:a72b:ac6:28cc:2ab5/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 9334sec preferred_lft 9334sec
    inet6 fe80::a935:85e0:1d80:5d28/64 scope link 
       valid_lft forever preferred_lft forever
6: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether f8:e4:3b:6d:0c:4d brd ff:ff:ff:ff:ff:ff
    inet 192.168.123.3/24 brd 192.168.123.255 scope global dynamic noprefixroute eth1
       valid_lft 172745sec preferred_lft 151145sec
    inet6 fe80::976b:98f:f8a7:5a8c/64 scope link 
       valid_lft forever preferred_lft forever
       
bridge1.local $ ip route list
default via 192.168.123.1 dev eth1 proto dhcp src 192.168.123.3 metric 206 
default via 192.168.1.1 dev wlan0 proto dhcp src 192.168.1.9 metric 303 
192.168.1.0/24 dev wlan0 proto dhcp scope link src 192.168.1.9 metric 303 
192.168.123.0/24 dev eth1 proto dhcp scope link src 192.168.123.3 metric 206 

設定を行うためにInternetへ抜けたいが,default via 192.168.123.1 dev eth1のルートが邪魔でInternetに抜けられない(そもそも間違っている)ため消す.

bridge1.local:~ $ sudo ip route del default dev eth1 
bridge1.local:~ $ sudo ip route del default dev eth1 
bridge1.local:~ $ ip route list
default via 192.168.1.1 dev wlan0 proto dhcp src 192.168.1.9 metric 303 
192.168.1.0/24 dev wlan0 proto dhcp scope link src 192.168.1.9 metric 303 
192.168.123.0/24 dev eth1 proto dhcp scope link src 192.168.123.3 metric 206

bridge1.local:~ $ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=33.9 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=4.49 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=5.22 ms

brctlを入れて設定しようと思ったが,ここ最近ではRaspberry Pi OS標準のiproute2を使うのが主流?らしいので,実際はInternetに抜ける必要もなかった.

参考:
https://takuya-1st.hatenablog.jp/entry/2021/12/13/213814
https://qiita.com/s1061123/items/54c9b4c001877135c4ff

iproute2を用いて, bridge1.localeth0eth1間をbridge接続する.

bridge1.local:~ $ sudo ip link add br0 type bridge
bridge1.local:~ $ ip link show 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DORMANT group default qlen 1000
    link/ether dc:a6:32:9d:4b:79 brd ff:ff:ff:ff:ff:ff
6: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether f8:e4:3b:6d:0c:4d brd ff:ff:ff:ff:ff:ff
7: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether a6:3b:74:2a:59:ac brd ff:ff:ff:ff:ff:ff
    
    
bridge1.local:~ $ sudo ip link set dev eth0 master br0
bridge1.local:~ $ sudo ip link set dev eth1 master br0

bridge1.local:~ $ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DORMANT group default qlen 1000
    link/ether dc:a6:32:9d:4b:79 brd ff:ff:ff:ff:ff:ff
6: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP mode DEFAULT group default qlen 1000
    link/ether f8:e4:3b:6d:0c:4d brd ff:ff:ff:ff:ff:ff
7: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff
    
bridge1.local:~ $ bridge link show
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state disabled priority 32 cost 19 
6: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state disabled priority 32 cost 19 

bridge1.local:~ $ sudo ip link set br0 up 
bridge1.local:~ $ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DORMANT group default qlen 1000
    link/ether dc:a6:32:9d:4b:79 brd ff:ff:ff:ff:ff:ff
6: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP mode DEFAULT group default qlen 1000
    link/ether f8:e4:3b:6d:0c:4d brd ff:ff:ff:ff:ff:ff
7: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:9d:4b:78 brd ff:ff:ff:ff:ff:ff

これで,bridge1.localeth0eth1間でパケットを素通しするbridgeとなった.

ここでbridgeの動作確認のためにClient PCからpi1.local向けにPingを打つと, RTTが数msとWi-Fiを用いた場合より小さく一定なのでおそらく有線を介してそう. 本当はちゃんと経路確認すべき.

client:~ $ ping pi1.local
PING pi1.local (192.168.123.4): 56 data bytes
64 bytes from 192.168.123.4: icmp_seq=0 ttl=64 time=3.034 ms
64 bytes from 192.168.123.4: icmp_seq=1 ttl=64 time=1.011 ms
64 bytes from 192.168.123.4: icmp_seq=2 ttl=64 time=1.056 ms
64 bytes from 192.168.123.4: icmp_seq=3 ttl=64 time=1.109 ms
64 bytes from 192.168.123.4: icmp_seq=4 ttl=64 time=1.131 ms
64 bytes from 192.168.123.4: icmp_seq=5 ttl=64 time=1.148 ms
64 bytes from 192.168.123.4: icmp_seq=6 ttl=64 time=0.931 ms

ここから, bridge1.localeth0ポートに対して遅延を設定する.
遅延については,tc(Traffic Control)コマンドを用いて設定する.
詳細な設定方法等は以下を参照されたい.

https://man7.org/linux/man-pages/man8/tc.8.html
https://qiita.com/hana_shin/items/d9ba818b49aca87b2314
https://zassinojunin.jp/20210815/1651

10ms遅延, 1msジッタ, パケロス0.1%

pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc mq 0: root 
qdisc pfifo_fast 0: parent :5 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

pi@brigde1:~ $ sudo tc qdisc add dev eth0 root netem delay 10ms 1ms loss 0.1%
pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc netem 8001: root refcnt 6 limit 1000 delay 10ms  1ms loss 0.1%

client:~ $ ping pi1.local  
PING pi1.local (192.168.123.4): 56 data bytes
64 bytes from 192.168.123.4: icmp_seq=0 ttl=64 time=23.617 ms
64 bytes from 192.168.123.4: icmp_seq=1 ttl=64 time=11.941 ms
64 bytes from 192.168.123.4: icmp_seq=2 ttl=64 time=11.302 ms
64 bytes from 192.168.123.4: icmp_seq=3 ttl=64 time=12.164 ms
64 bytes from 192.168.123.4: icmp_seq=4 ttl=64 time=10.408 ms

100ms遅延, 1msジッタ, パケロス0.1%

pi@brigde1:~ $ sudo tc qdisc change dev eth0 root netem delay 100ms 1ms loss 0.1%
pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc netem 8001: root refcnt 6 limit 1000 delay 100ms  1ms loss 0.1%

client:~ $ ping pi1.local    
PING pi1.local (192.168.123.4): 56 data bytes
64 bytes from 192.168.123.4: icmp_seq=0 ttl=64 time=202.991 ms
64 bytes from 192.168.123.4: icmp_seq=1 ttl=64 time=101.238 ms
64 bytes from 192.168.123.4: icmp_seq=2 ttl=64 time=101.762 ms
64 bytes from 192.168.123.4: icmp_seq=3 ttl=64 time=100.666 ms
64 bytes from 192.168.123.4: icmp_seq=4 ttl=64 time=101.509 ms
64 bytes from 192.168.123.4: icmp_seq=5 ttl=64 time=102.036 ms
64 bytes from 192.168.123.4: icmp_seq=6 ttl=64 time=100.746 ms

10ms遅延, 1msジッタ, パケロス50%

pi@brigde1:~ $ sudo tc qdisc change dev eth0 root netem delay 10ms 1ms loss 50%
pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc netem 8001: root refcnt 6 limit 1000 delay 10ms  1ms loss 50%

client:~ $ ping pi1.local
PING pi1.local (192.168.123.4): 56 data bytes
Request timeout for icmp_seq 0
64 bytes from 192.168.123.4: icmp_seq=1 ttl=64 time=11.513 ms
64 bytes from 192.168.123.4: icmp_seq=2 ttl=64 time=11.370 ms
Request timeout for icmp_seq 3
Request timeout for icmp_seq 4
Request timeout for icmp_seq 5
64 bytes from 192.168.123.4: icmp_seq=6 ttl=64 time=10.318 ms
64 bytes from 192.168.123.4: icmp_seq=7 ttl=64 time=11.292 ms
Request timeout for icmp_seq 8
Request timeout for icmp_seq 9
64 bytes from 192.168.123.4: icmp_seq=10 ttl=64 time=11.793 ms

全てにおいて,およそ設定した遅延通りの値になっていることを確認.

おまけ

元に戻しておく

pi@brigde1:~ $ sudo tc qdisc change dev eth0 root netem delay 0ms 1ms loss 0%
pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc netem 8001: root refcnt 6 limit 1000

計測方法として, netperfも用いる.

client:~ $ ssh pi@pi1.local 

pi@pi1:~ $ sudo netserver -d
check_if_inetd: enter
setup_listens: enter
create_listens: called with host '::0' port '12865' family AF_INET6(10)
getaddrinfo returned the following for host '::0' port '12865'  family AF_INET6
        cannonical name: '(nil)'
        flags: 1 family: AF_INET6: socktype: SOCK_STREAM protocol IPPROTO_TCP addrlen 28
        sa_family: AF_INET6 sadata: 32 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
create_listens: called with host '0.0.0.0' port '12865' family AF_INET(2)
getaddrinfo returned the following for host '0.0.0.0' port '12865'  family AF_INET
        cannonical name: '(nil)'
        flags: 1 family: AF_INET: socktype: SOCK_STREAM protocol IPPROTO_TCP addrlen 16
        sa_family: AF_INET sadata: 50 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0
create_listens: warning: bind or listen call failure: Address already in use (errno 98)
Starting netserver with host 'IN(6)ADDR_ANY' port '12865' and family AF_UNSPEC
daemonize: enter

client:~ $ netperf -H 192.168.123.4 -l -10000 -t TCP_RR -w 10ms -b 1 -v 2 -- -O min_latency,mean_latency,max_latency,stddev_latency,transaction_rate
Packet rate control is not compiled in.
Packet burst size is not compiled in. 
MIGRATED TCP REQUEST/RESPONSE TEST from (null) (0.0.0.0) port 0 AF_INET to (null) () port 0 AF_INET : first burst 0
Minimum      Mean         Maximum      Stddev       Transaction 
Latency      Latency      Latency      Latency      Rate        
Microseconds Microseconds Microseconds Microseconds Tran/s      
                                                                
599          1027.15      5220         336.91       972.674 

tcによるBridgeの遅延を元に戻してもう一回.

pi@brigde1:~ $ sudo tc qdisc delete dev eth0 root netem 
pi@brigde1:~ $ sudo tc qdisc show dev eth0
qdisc mq 0: root 
qdisc pfifo_fast 0: parent :5 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

client:~ $ netperf -H 192.168.123.4 -l -10000 -t TCP_RR -w 10ms -b 1 -v 2 -- -O min_latency,mean_latency,max_latency,stddev_latency,transaction_rate
Packet rate control is not compiled in.
Packet burst size is not compiled in. 
MIGRATED TCP REQUEST/RESPONSE TEST from (null) (0.0.0.0) port 0 AF_INET to (null) () port 0 AF_INET : first burst 0
Minimum      Mean         Maximum      Stddev       Transaction 
Latency      Latency      Latency      Latency      Rate        
Microseconds Microseconds Microseconds Microseconds Tran/s      
                                                                
658          778.13       4846         84.15        1283.256    

おわりに

考察・感想

とりあえず,Raspberry Piを使ったbridgeの作成とネットワーク遅延模擬およびその動作確認を実施できた.
物理NICとかネットワークインターフェースを実際に意識する機会は,最近のクラウド利用の流れの中で減ってきていると個人的に感じており,こういう機会に改めて勉強できるのは良い機会となった.

tcコマンドも, netperfコマンドも,実際まだ使い慣れていないので,もっとよい設定・計測手法がありそうなので引き続き触っていきたい.

また,参考文献のようにGUI/CLIを実装した上で,各種Profileを読み込ませて時間方向のシナリオに沿ったネットワーク遅延を模擬してくれるような仕組みを実装して,色んな実環境上で遅延による体幹の変化を評価できる環境を構築したい.

Discussion