「Raspberry Piで作るネットワーク遅延模擬ブリッジ」備忘録
はじめに
おことわり
本記事はすべて個人の見解・理解によるもので,所属する組織・団体とは一切の関連を持ちません.
したがって本記事の正確性等は保証できませんが、理解の誤りや、誤植・手順漏れ等、コメント・ご指摘に関しては何でも頂ければ幸いです.
参考
書くこと
- 簡単なネットワーク遅延模擬ブリッジの作成手順メモ
- 動作確認
- できそうなこと・今後の展望
書かないこと
- 各種利用技術の原理詳細
- 各種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に抜ける必要もなかった.
参考:
iproute2
を用いて, bridge1.local
のeth0
とeth1
間を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.local
はeth0
とeth1
間でパケットを素通しする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.local
のeth0
ポートに対して遅延を設定する.
遅延については,tc
(Traffic Control)コマンドを用いて設定する.
詳細な設定方法等は以下を参照されたい.
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