Docker環境でQUICリバースプロキシを試す

## はじめに

[前回記事](https://qiita.com/t13801206/items/4d1e3b3556960bbf5805)では、[goquic](https://github.com/devsisters/goquic)のリバースプロキシを使ってHTTP/3, QUIC接続を試そうとして失敗した

今回はそのリベンジ！
`curl --http3`コマンドでHTTP/3通信できることを確認した

## 今回のリバースプロキシは・・・

Nginxです！

[WebサーバーのHTTP/3対応をNginxのリバースプロキシでするためのDockerイメージが出来ました](https://qiita.com/nwtgck/items/ff633df298dfd9dc0887)

前回は、goquicをプロキシ、nginxを最終アクセス先としていたが
今回は、**nginxがプロキシ、ghostが最終アクセス先**とする

### （予備知識）[Ghost](https://hub.docker.com/_/ghost/)とは

> Ghost is a free and open source blogging platform written in JavaScript

無料のブログプラットフォーム（らしい）です

- [書くことが大好きな人のためのシンプルすぎるプラットフォーム「Ghost」とは？](https://ferret-plus.com/7929)
- [Ghostで作成されたサイト例](https://ghost.org/explore/?query=Japan)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/7ed3186f-b759-1f58-4870-5342090bf659.png)

最終アクセス先として、Ghostを採用しただけなので、Ghostについてはここまで
ひと昔前は、こういうコンテンツサイト？ブログサイト？といえばWordPressだったけど・・・

## 検証環境をつくる on Docker

[前回記事](https://qiita.com/t13801206/items/4d1e3b3556960bbf5805)と同様に、`docker-compose`で環境をつくる

コンテナ|役割
-|-
`client`|`curl`コマンドを打つ役
`proxy`|HTTP/3アクセスを受け付けるリバースプロキシ
`ghost`|単なるブログサイト。HTTP/3に対応していない想定

クライアント ->(ここはHTTP/3)-> プロキシ -> (ここはHTTP/2以下) -> ブログサイト

### クライアントに証明書をインストールする 

[前回](https://qiita.com/t13801206/items/4d1e3b3556960bbf5805#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%81%AB%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)と同じく、クライアントにはサーバー証明書を入れておく
これでHTTPS通信することができるようになる

何はともあれ、クライアントのコンテナに入って作業する

```bash
docker compose up -d
docker compose exec client ash
```

パッケージマネージャが使えないコンテナなので、`.crt`ファイルに直接書き込む

```bash
cat /nginx/server.crt >>/etc/ssl/certs/ca-certificates.crt
```

:::note warn
パッケージマネージャが使える環境であれば、このような直書きではなく、コマンドで証明書をインストールしましょう
:::

## いざ、接続！

クライアントからHTTP/1.1, HTTP/2, HTTP/3 の3パターンで接続してみる

### HTTP/1.1

HTTP接続

```bash
curl http://proxy -I

HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 01 Aug 2023 13:37:05 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 10255
Connection: keep-alive
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/"280f-LG5sPxcueSIwKUCwqdjGkQDJ338"
Vary: Accept-Encoding
```

### HTTP/2

HTTPS接続

```bash
curl https://proxy -I
HTTP/2 200
server: nginx/1.16.1
date: Tue, 01 Aug 2023 13:39:27 GMT
content-type: text/html; charset=utf-8
content-length: 10255
x-powered-by: Express
cache-control: public, max-age=0
etag: W/"280f-LG5sPxcueSIwKUCwqdjGkQDJ338"
vary: Accept-Encoding
alt-svc: h3-23=":443"; ma=86400
```

### HTTP/3

HTTPSかつ`--http3`オプションを付けると、HTTP/3で接続できる

```bash
curl https://proxy -I --http3

HTTP/3 200
server: nginx/1.16.1
date: Tue, 01 Aug 2023 13:40:46 GMT
content-type: text/html; charset=utf-8
content-length: 10255
x-powered-by: Express
cache-control: public, max-age=0
etag: W/"280f-LG5sPxcueSIwKUCwqdjGkQDJ338"
vary: Accept-Encoding
alt-svc: h3-23=":443"; ma=86400
```

## HTTPのバージョン違いでパケットがどう変わるか

HTTP1.1, 2, 3 のそれぞれの方法で接続できることがわかった
Ghostの初期サイトは軽量すぎて、アクセス速度の差がわからない

ポートスキャンをしてパケットのやり取りの総量を比べてみる

### ポートスキャンの準備

Nginxのコンテナに入り

```bash
docker compose exec proxy bash
```

`tcpdump`をインストールする

```bash
apt update && apt install -y tcpdump

# バージョン確認
tcpdump --version
tcpdump version 4.9.3
libpcap version 1.8.1
OpenSSL 1.1.1  11 Sep 2018

# キャプチャするコマンド
tcpdum -w /mnt/out.pcap
```
### HTTP/1.1

スリーウェイハンドシェイクでかっちり通信

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/21bca175-4c10-ed76-4e35-bd948b92c0bb.png)

### HTTP/2

HTTP/2については、[令和5年春のネットワークスペシャリスト試験午後１問題](https://www.ipa.go.jp/shiken/mondai-kaiotu/ps6vr70000010d6y-att/2023r05h_nw_pm1_qs.pdf)で解説されている

- HTTP1.1はリクエストを受けたのと同じ順序レスポンスを返す必要があるが、HTTP/2にはその制約がない
- HTTP1.1はパイプラインで多重化している。一方、HTTP/2ストリームと呼ばれる仮想の通信路で多重化している

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/f11938fc-d83c-3bb1-a711-daff0abb3a22.png)

#### ヘッダーフィールドの必須フィールド

ヘッダーは[HPACKアルゴリズム](https://kaworu.jpn.org/security/HPACK)でバイナリに圧縮されている

:method, :scheme, :path の3つが必須フィールド

:::note info
ここはもっと書くべきことがありそうだ！後で追記する！
:::

### HTTP/3

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/877804d9-f095-0ab1-2afd-b966b0984829.png)

:::note info
ここはもっと書くべきことがありそうだ！後で追記する！
:::

## おわりに

前回のリベンジで、リバースプロキシを使ってQUICを体感した
HTTP/3に対応していないサーバであっても
このリバースプロキシを使えば、クライアントからはあたかもHTTP/3対応のサーバが動いているように見える

HTTP/2, HTTP/3のパケットキャプチャした結果については
もっともっと中身をよくよんで勉強すべきことがありそう

記事を途中まで書いてから時間がたってしまったので、いったん放流する

## 付録

### docker-compose.yml

```yml
version: "3.8"

services:
  client:
    image: keioni/curl-http3
    container_name: client
    hostname: client
    tty: true
    stdin_open: true
    volumes:
      - './nginx/server.crt:/nginx/server.crt'
    networks:
      net1:      
        ipv4_address: 172.23.230.30
 
  proxy:
    image: nwtgck/nginx-http3
    container_name: proxy
    hostname: proxy
    restart: always
    ports:
      - '80:80'
      - '443:443'
      - '443:443/udp'
    depends_on:
      - ghost
    volumes:
      - ./nginx/nginx.conf:/usr/local/nginx/conf/nginx.conf
      - ./nginx/server.crt:/etc/ssl/certs/server.crt
      - ./nginx/serverinstall.key:/etc/ssl/private/server.key
    networks:
      net1:      
        ipv4_address: 172.23.230.60

  ghost:
    image: ghost
    restart: always
    depends_on:
      - ghost-db
    environment:
      database__client: mysql
      database__connection__host: 172.23.230.80
      database__connection__user: root
      database__connection__password: password
      database__connection__database: ghost
    networks:
      net1:      
        ipv4_address: 172.23.230.70
  
  ghost-db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
    networks:
      net1:      
        ipv4_address: 172.23.230.80

networks:
  net1:
    ipam:
      driver: default
      config:
        - subnet: 172.23.230.0/24
```

## 参考

- [Ghost + MySQLを Dockerで動かす](https://qiita.com/Nunocky/items/d3beb722d35cd7799b49)

## 糸冬了！！