【ネタアプリ？】トラフィック遅延をブラウザで調節可能なプロキシサーバ（Docker+Squid+tcコマンド）

最近Qiitaを始めた駆け出しエンジニアです。
今回は、以前に作成したネタっぽいアプリを掘り出してきました。その名も「らぐらぐ遅延スタート」。

・・・開発経緯がネタっぽいのですが、使いどころのある内容だったので、駄文にお付き合いください。

# 遅延を生み出したい
筆者は`愉快なパーティゲーム`というジャンルのゲームを友人と遊ぶことが多いのですが、そこでふとこう思いました。
友人との対戦で能動的に遅延行為を調節できたら ~~めちゃくちゃ面白くね？~~ 遅延の多い環境でプレイする時に向けた練習となるのではないか。

# 仕組みの妄想
すこし調べて、下の図のようなものを脳内で考えました。

![イラスト.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/62f54615-c92b-4499-2d43-5e98df4d86ab.jpeg)

1. プロキシ機能を持ったDockerコンテナを用意する
2. そのコンテナのネットワークアダプタに、API経由で遅延をかけられるように設定する
3. ゲーム機のプロキシ設定に1. のIPアドレスとポートを指定する
4. ゲーム機の通信はすべて遅延を付加できる！

といった感じの流れでアプリを作っていこうと思いました。

スマホから遅延の操作をするならば、手っ取り早そうなのはwebアプリですね。APIはGoで作って画面はvueとvuetifyでキレイにしようと思いました。

# 実装
実装において重要だった点を紹介します。

## 遅延を発生させるtcコマンド
Linuxの`tc`コマンド使用すると、パケット送信に遅延を追加したりパケット送信失敗率を設定できるとのこと。

参考:
[hana_shinのLinux技術ブログ tcコマンドの使い方](https://hana-shin.hatenablog.com/entry/2022/03/13/183444)
[tc コマンドでネットワーク遅延やパケットロスを疑似的に発生させるメモ](https://iret.media/19275)

```bash
# キューに遅延を追加
tc qdisc add dev eth0 root netem delay 100ms
# キューの設定を変更
tc qdisc change dev eth0 root netem delay 200ms
```

## 切断
切断機能もつけてみました。
いろいろ考えるよりとりあえず動けばよかったので、操作を受け付けたらファイアウォールでプロキシのポートを拒否します。

```bash
# 切断 3128ポートを拒否
iptables -A INPUT -p tcp --dport 3128 -j DROP
# 切断解除 うーん、ファイアウォールをクリアw
iptables --flush
```

## Goでの実装
APIは以下の通りです。
ネタアプリなので、とくに認証もなければ複雑なバリデーションもなしですべてGETリクエスト。

|URI|クエリパラメータ|説明|
|:-|:-|:-|
|GET /api/set-delay|delay={遅延時間}|ミリ秒単位で遅延時間(整数)を設定する|
|GET /api/get-delay|-|現在設定されている遅延時間を取得する|
|GET /api/check-proxy|-|接続可能な状態になってるか確認する|
|GET /api/disconnect|-|プロキシへの通信を切断する|
|GET /api/connect|-|プロキシへ通信可能な状態に復旧する|

詳しいソースコードはGitHubをご確認ください。
[https://github.com/HoppingGanon/ragudelay](https://github.com/HoppingGanon/ragudelay)

## できたもの
とりあえず完成しているので、DockerHubにアップロードしました。
[のちのセクション](#dockerイメージ)にて、docker-compose.ymlの記述例を載せますので、興味のある方は試してみてください。

コンテナを起動して数秒待つと、以下のような表示で起動します。

![2023-02-13-11-26-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/cbf37d3f-bdee-d3a1-1bfc-eab923049f14.png)

ブラウザで`/menu`にアクセスするとコンソールが表示されます。
遅延の調整スライダーや、虹色の切断ボタン、設定した遅延時間と実際の応答時間がグラフに表示されます。

![2023-02-13-11-31-36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/fdd8ace5-2ad4-d521-6a6f-86a882e2085d.png)

# 使ってみた
## スマホでテスト
まずはスマホにプロキシを設定して応答速度を確認しました。

![Screenshot2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/64c66e92-84bf-af24-da75-1a80035b3a84.jpeg)

プロキシ設定なしの場合は8ミリ秒だったので、設定した遅延時間はしっかり反映されています。

※ ブラウザからの操作もしっかり遅延がかかるので、大きな値を指定すると管理メニューの操作がしんどくなります（でも使えるからヨシ！）。

## ゲーム機でテスト
お・・・遅い！
ping応答時間は出ないけど明らかにラグい・・・！！！

![20230213_165524.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/61895802-4cf3-fa72-3980-cb87527d9fc1.jpeg)

これで ~~見てから魔人拳が避けられない最高の~~ 遅延状況下で練習できる環境が整いましたね・・・。
おや？

## と思いきや
対戦を始めると嘘のように遅延がなくなり、ボコボコにされてしまいました。

![イラスト2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884998/c1d214f0-241b-32a5-ec32-abfc508307b1.png)
※ 画像はイメージです
※ 本記事は特定のゲームやキャラクターを指して執筆されたものではありません

まぁ調べてる最中から気づいていたことなんですが、リアルタイム通信を行うゲームではプロキシを通過する経路で通信してないらしいんですよね。おそらくコンテンツのデータや対戦相手の情報はhttpsを使用したTCPでやり取りしていると思いますが、肝心の対戦はUDPの何かで通信していそうな雰囲気があります。

## 総括
目的は果たせなかったものの、よくよく考えると「Dockerイメージで手軽に起動できて、LAN内多くの端末でwebアプリの遅延環境を再現できるアプリ」というだけでも使い道はある気がしてきました。

そのため、いったんここで完成としました。
何か良いアイデアを思い付いたら更新する可能性はあります。

## Dockerイメージ
というわけで、用途は変わってしまいましたが、無駄ではなかったことにしたのでDockerHubにイメージをアップロードしました。
ご自由にご利用ください。

```docker-compose.yml
version: '3'
services:
  ragudelay:
    image: hoppingganon/ragudelay
    environment:
      RAGUDELAY_BASE_URI: http://localhost:8080
      RAGUDELAY_MIN: 0
      RAGUDELAY_MAX: 10000
    ports:
    - 3128:3128/tcp
    - 8080:80/tcp
    cap_add:
    - NET_ADMIN
    labels:
      io.rancher.container.pull_image: always
```

遅延を設定するにあたって、`NET_ADMIN`は必須です。必ず含めてください。
webアプリのポートは`80`、プロキシのポートは`3128`固定です。必要に応じてポートフォワーディングしてください。
また、既定のタイムアウト60秒だとリクエストが完了しないので、120秒くらいに伸ばしておくといいかもしれません。

```bash
COMPOSE_HTTP_TIMEOUT=120 docker-compose up
```

設定可能な環境変数は以下の通りです。

|環境変数名|説明|規定値|
|:-|:-|:-|
|RAGUDELAY_BASE_URI|APIで使用するBaseURI（ポートフォワーディングしたり、外部からアクセスする場合は変更してください）|http://localhost|
|RAGUDELAY_MIN|設定可能な遅延の最小値|0|
|RAGUDELAY_MAX|設定可能な遅延の最大値|10000|

ちなみに、これはハードの関係なのかOSの関係なのか、windows環境でコンテナを動かした場合は遅延を設定できませんでした。自分はUbuntuのPCを一台所有しているので、そちらで動作させています。原因はよくわかりません。原因がわかる方はコメントを頂けると嬉しいです。

以上です。
駄文にお付き合いいただき、ありがとうございました。