LoginSignup
1
2

More than 5 years have passed since last update.

QUIC のドラフトを読んでみた

Last updated at Posted at 2017-09-07

QUIC: A UDP-Based Multiplexed and Secure Transport 2017年8月15日発行分のメモ

文中に出てくる図については、全て QUIC: A UDP-Based Multiplexed and Secure Transportからコピーした。

所感

  • フロー制御については、未定義な印象を受けた。TCPのRFCを読んだことはないがざっくりとしか書かれていないのかな?
  • 前半は、翻訳かメモかどっちつかずですが、8章辺りからメモらしくなってきた感じがする。

目次
1. はじめに
2. 規定と定義
3. QUICの概要
4. バージョン
5. パケットタイプとフォーマット
6. フレームとフレイムタイプ
7. コネクションのリスト
8. フレームタイプとフォーマット
9. パケット化と確実性
10. ストリーム: QUICのデータ構造抽象(Data Structuring Abstraction)
11. フロー制御
12. エラーハンドリング
13. セキュリティーとプライバシーの検討事項
14. IANAの検討事項
15. 参考文献

1. はじめに

QUIC は、多重化されUDP上で動作する安全な転送プロトコル。QUICは、多様なプリケーション用に多目的を実現する柔軟な機能セットを提供することを目指している。

QUICはTCP、SCTPや他の転送プロトコルの経験を活かして実装している。QUICはUDPを基盤として使っているので、レガシーなクライアントおOSやミドルボックスの更新を必要としない。QUICは自身のヘッダー全て承認し、殆どのデータを暗号化している。これは、プロトコルに依存するミドルボックスのアップグレードなしに進化することが可能となる。
本ドキュメントは、中核をなすQUICプロトコル、コンセプトデザイン、フォーマット、コネクション確立用のQUICプロトコルのメカニズム、ストリームの多重化、ストリームとコネクションレベルフロー制御と、データの信頼性について書いている。

2. 規定と定義

"MUST", "MUST NOT", "SHOULD", "MAYについてはRFC2119を参照

クライアント: QUICコネクションを開始するエンドポイント
サーバー: 入ってきたQUICコネクションを受け入れるエンドポイント
エンドポイント: クライアントまたはサーバの接続末端
ストリーム: 論理的で、QUICコネクション内のバイトが規則正しい双方向チャンネル
コネクション: 多重ストリームの1つの暗号コンテキストでなる 2QUICエンドポイント間の会話
コネクションID: QUICコネクションの識別子
QUICパケット: QUIC受信機で読み取り可能な、定義されたUDPのペイロード。QUICパケットサイズは、UDPのペイロードサイズにしすされている。

2.1

パケット図の読み方は、RFC2360 Section 3.1を参照。以下も付け加える。

[x] xはオプション
{x} xは暗号化されている
x (A) xは Aビット長である
x (A/B/C) ... xはA,B,Cビット長のいずれかである
x (*) ... xは可変長である

3. QUICの概要

QUICの重要な仕組みと利点を軽く紹介

  • 低遅延なコネクション確立
  • ヘッドオブラインブロッキングなしの多重化
  • 承認され暗号化されたヘッダーとペイロード
  • 輻輳制御用の豊富なシグナリングとロス回復
  • ストリームとコネクションフロー制御
  • コネクションの移行とNATの再バインディングを許容
  • バージョンネゴシエーション

3.1 低遅延なコネクション確立

QUIC は、安全な転送接続を準備するために暗号化と転送ハンドシェイクを結合している。飯盒ハンドシェイク専用のストリームID 0 を提供している。ネゴシエーション中のフォーマットとパラメータについては本ドキュメントに書いている。ストリームIDの暗号ハンドシェイクは QUIC-TLCを参照。

3.2 ストリームの多重化

アプリケーションメッセージがTCP上で送られるとき、アプリケーションメッセージはHoLブロッキングに悩まされる。アプリケーションがTCPの1バイトストリーム上の多重ストリームだと、TCPセグメントを損失した結果再送が発生する。
QUICはパケット損失をの問題を解決できる。

3.3 輻輳制御用の豊富なシグナリングとロス回復

QUICのパケットフレームと確認応答は、基本的な方法で、輻輳制御と損失回復の両方を支援する豊富な情報を提供する。
各QUICパケットは、再送データを含む新しいパケット番号を運ぶ。

3.4 ストリームとコネクションフロー制御

QUICは、ストリームとコネクションレベルのフロー制御を実装している。

3.6 コネクションの移行とNAT理バインディングへの回復

QUICコネクションは、サーバーが生成するランダムな64-bitのコネクションIDで識別される。

3.7 バージョンネゴシエーション

QUICバージョンネゴシエーションは、運用され並行で使用されるプロトコルのバージョンを多重化することを実現する
詳しくは Secion 7.1を参照。

4. バージョン

QUICバージョンは、32ビット値で識別される。

バージョン0x00000000は予約された無効バージョン。この仕様のバージョンは、0x00000001で識別される。

QUICのバージョン0x00000001は、QUIC-TLCに記述された暗号ハンドシェイクプロトコルのTLSに使う。

最上位16ビットのバージョンは、将来IETFコンセンサスドキュメントで使用するために予約されている。

パターン 0x?a?a?a?a に続くバージョンは、強制的にバージョンネゴシエーションを使うために予約されている。これは、全てのオクテットの下位4ビットが1010(2進数)のバージョン番号である。クライアントまたはサーバーは、これらの予約バージョンのサポートを宣言する MAY

予約されたバージョンは、実プロトコルでは、出現しないかもしれない

5. パケットタイプとフォーマット

後のメカニズムで参照されるものがいくつかあるため、はじめにQUICのパケットタイプとフォーマットを説明する。

全ての数値は、ネットワークバイトオーダー(ビッグエンディアン)でエンコードされる。

どのQUICパケットも、ヘッダーフォームビットで示される、長いまたは短いヘッダーのいずれかを持つ。ロングヘッダーは、バージョンネゴシエーションの前と1-RTTキーの確立前で、使われる。ショートヘッダーは、バージョンネゴシエーションの後と1-RTTが確立された後で使われるバージョン指定ヘッダーにある。

5.1 ロングヘッダー

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+
   |1|   Type (7)  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                       Connection ID (64)                      +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Packet Number (32)                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Version (32)                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          Payload (*)                        ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 1: ロングヘッダーフォーマット

ロングヘッダーは、バージョンネゴシエーションと1-RTTキーの確立するよりも先に送られるパケットに使われる。いずれか1つの条件に合致すれば、送信者は、ショートフォームのヘッダー送信に切り替えるべき(SHOULD)。非効率だが、長いヘッダーは、1-RTTキーで暗号化されたパケットに使用しても良い(MAY)。

  • ヘッダーフォーム: オクテット0(最初のオクテット)の最上位ビット(0x80)は、ロングヘッダー用に1を設定する。
  • ロングパケットタイプ: オクテット0の残った7ビットは、パケットタイプ。このフィールドは、128パケットタイプのうちの1つ。このバージョンに指定されたタイプはTalbe 1.に一覧にしている。
  • コネクションID: オクテット1-8にコネクションIDが含まれる。 Section 5.6に詳細が書かれている。
  • パケット番号: オクテット9-12にパケット番号が含まれる。 Section 5.7にパケット番号の使い方が書かれている。
  • バージョン: オクテット13-16は、選択されたプロトコルバージョン。このフィールドは、使用中のQUICバージョンが示され、残りのプロトコルフィールドがどう解釈されるかが記されている。
  • ペイロード; オクテット17以降(QUICパケットの残り)は、パケットのペイロード。
         +------+-------------------------------+---------------+
         | Type | Name                          | Section       |
         +------+-------------------------------+---------------+
         | 0x01 | Version Negotiation           | Section 5.3   |
         |      |                               |               |
         | 0x02 | Client Initial                | Section 5.4.1 |
         |      |                               |               |
         | 0x03 | Server Stateless Retry        | Section 5.4.2 |
         |      |                               |               |
         | 0x04 | Server Cleartext              | Section 5.4.3 |
         |      |                               |               |
         | 0x05 | Client Cleartext              | Section 5.4.4 |
         |      |                               |               |
         | 0x06 | 0-RTT Protected               | Section 5.5   |
         |      |                               |               |
         | 0x07 | 1-RTT Protected (key phase 0) | Section 5.5   |
         |      |                               |               |
         | 0x08 | 1-RTT Protected (key phase 1) | Section 5.5   |
         +------+-------------------------------+---------------+

Table 1: ロングヘッダーパケットタイプ

ロングヘッダーパケットのヘッダーフォーム、パケットタイプ、コネクションID、パケット番号と、バージョンフィールドは、バージョン依存である。Section 5.8にバージョンごとにパケットがどう解釈されるか書かれている。

QUICバージョンが変わるごとに対応が必要なので実装の注意点になりそう。

5.2 ショートヘッダー

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+
   |0|C|K| Type (5)|
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                     [Connection ID (64)]                      +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                      Packet Number (8/16/32)                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                     Protected Payload (*)                   ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 2: ショートヘッダーフォーマット

ショートヘッダーは、バージョンと1-RTTキーがネゴった後に使用できる。

  • ヘッダーフォーム: オクテット0(最初のオクテット)の最上位ビット(0x80)は、ショートヘッダー用に0を設定する。
  • コネクションIDフラグ: オクテット0の第2ビット(0x40)は、コネクションIDがあるかどうかを示す。1であれば、コネクションIDはある。0であれば、コネクションIDは、省略される。もしomit_connection_id 転送パラメータ(Section 7.3.1)が意図されたパケット受信者によって指定された場合、コネクションIDフィールドは省略される。
  • キーフレーズビット: オクテット0の第3ビット(0x20) は、キーフレーズである。これは、パケットの受信者に対して、パケット保護目的に使われるパケットプロテクションキーを識別させること許している。QUIC-TLC に詳細あり。
  • ショートパケットタイプ: オクテット0の残り5ビットは、32パケットタイプの内1つを含む。 Table 2は、ショートパケット用のタイプが定義されている。
  • コネクションID(Connection ID): コネクションIDフラグが設定されていれば、コネクションIDがパケットのオクテット1-8を占領する。 Section 5.6に詳細あり。
  • パケット番号(Packet Number): パケット番号フィールドの長さは、パケットタイプに依存する。このフィールドは、ショートパケットタイプに依存する1, 2, 4オクテット長になりうえる。
  • プロテクテッドペイロード(Protected Payload): ショートヘッダーのパケットはいつも1-RTT保護ペイロードが含まれる。

ショートヘッダーに含まれるパケットタイプは、パケット番号フィールドのサイズを特定されるだけに使われる。

+------+--------------------+
| Type | Packet Number Size |
+------+--------------------+
| 0x01 | 1 octet            |
|      |                    |
| 0x02 | 2 octets           |
|      |                    |
| 0x03 | 4 octets           |
+------+--------------------+

Table 2: ショートヘッダーパケットタイプ

ショートヘッダーパケットのヘッダーフォーム、コネクションIDフラグと、コネクションIDは、バージョン依存。残りのフィードは、選択されたQUICバージョンによって指定される。 Section 5.8にQUICのバージョンごとにどうパケットが解釈されるか書かれている。

5.3 バージョンネゴシエーションパケット

バージョンネゴシエーションパケットは、ロングヘッダーで0x01のタイプを持つ。
サーバーからのみ送られる。
バージョンネゴシエーションパケットは、クライアントへのレスポンス。サーバーによってサポートされていないバージョンを含む。

パケット番号、コネクションIDと、バージョンフィールドは、クライアントパケットをトリガーとした値に対して応答する。これは、クライアントが、サーバがパケットを受信したこととバージョンネゴシエーションパケットが偽装された送信元アドレスが運ばれないことを保証する。

バージョンネゴシエーションパケットは、クライアントによるACKフレーム内で明示的に承認されることはない。他のクライアントイニシャルパケットを受信することは、暗黙的にバージョンネゴシエーションパケットを認めている。

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Supported Version 1 (32)                 ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version 2 (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                  ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version N (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 3. バージョンネゴシエーションパケット

5.4 平文パケット

平文パケットは、キーネゴシエーション前のハンドシェイク中に送られる。

すべての平分パケットは、バージョンフィールドに現QUICバージョンを含んでいる。
平文パケットのペイロードは、整合性チェックを含んでいる。QUIC-TLSに記述あり

5.4.1 クライアントイニシャルパケット

タイプ値 0x02 を持つロングヘッダー。

5.4.2 サーバーステートレス再送パケット

タイプ値0x03を持つロングヘッダー。
暗号ハンドシェイクメッセージと承認を搬送する。ステートレス再送用にサーバーが使う(Seciont 7.4参照)。

5.4.3 サーバー平文パケット

タイプ値0x04を持つロングヘッダー
サーバーからの承認と暗号ハンドシェイクメッセージを搬送する。

パケットのペイロードは、STREAMフレームが含まれ、PADDINGとACKフレームを含むことができる。

5.4.4 クライアント平文パケット

タイプ値0x05を持つロングヘッダー。
クライアアントがサーバー平文パケットをサーバーから受信しときに送られる。

クライアント平文パケットは、最後に送られたクライアントイニシャル、0-RTT保護または、クライアント平文パケットよりも1つ高い番号が含まれる。

パケットのペイロードは、STREAMフレームが含まれ、PADDINGとACKフレームを含むことができる。

5.5 保護パケット

タイプ値0x06を持つロングヘッダーパケット。0-RTTキー用。

1-RTTキーを持つ保護パケットは、タイプ値0x07でキーフェース0、タイプ値0x08でキーフェース1を使う。

ペイロードは、認められた暗号を使って保護される。

5.6 コネクションID

クライアントがランダムなコネクションIDをクライアントイニシャルパケットと0-RTTパケットに使わないといけない(MUST)。

サーバは、クライアントイニシャルパケットを受取、ハンドシェイクの保護を決定したら、新しいコネクションIDを選び、サーバー平文パケットに入れて送信する。サーバーは、クライアントイニシャルパケットのIDを使うかもしれない(MAY)。

一度クライアントがサーバーが選んだコネクションを受け取ると、ずっと使い続ける。ただし、0-RTTパケットは除く。

5.7 パケット番号

パケット番号は、64-bit符号無し番号で、パケット暗号の暗号ナンスの一部として使われる。

パケット番号は、少なくとも1を増やして送らなければならない(MUST)。他で指定いがない限りは(Section 5.7.1)。

QUICエンドポイントは、同じコネクションでパケット番号を使いまわしてはいけない(MUST NOT)。送信用パケット番号が 2^64-1に到達したら、送信者はCONNECTION_CLOSEフレームまたは他の追加のパケットを送信すること無しにコネクションを切断しないといけない(MUST)。送信者は、受信した追加のパケットに応答する中でパブリックリセットパケットを送信してもよい(MAY)。

5.7.1 イニシャルパケット番号

メモしなかった。

7.6 コネクションマイグレーション

QUICコネクションは、64-bitのコネクションIDで識別される。
QUICの一貫したコネクションIDは、コネクションに、クライアントまたは、サーバーが新たなネットワーク移行によって引き起こされるクライアントのIPとまたはポートが変更にも生存することを許す。コネクションマイグレーションは、クライアントがネットワークを移動したときにコネクションと共有状態を保つことをクライアントに許可する。これは、未完了のリクエストなどの回復し難い状態を含む。そうでなければ、再接続の簡単な方法を失うかもしれません。

7.6.1 コネクションマイグレーションのプライバシーへの影響

多重ネットワーク経路で同じコネクションIDを使うとパッシブオブザーバー(傍観者?)は、それらの経路の活動を結ぶつけること許してしまう。ネットワーク間を移動するクライアントが、サーバー以外のエンティティによって活動を相関されることを望んでないかもしれない。NEW_CONNECTION_IDメッセージは、ネットワーク接続の2点間のリンク可能性を明示的に壊す場合の使用のためにリンク不可能なコネククションIDを提供するためにサーバから送られる。

クライアントは、サーバーから何も応答を受け取らない多重ネットワークでパケットを送る必要があるかもしれない。クライアントがこれらの変更を横切ってリンクできないクライアントを保証するために、新しいコネクションIDとパケット番号のギャップはそれぞれのネットワークに必要とされる。それぞれのNEW_CONNECTION_IDは、シーケンス番号で印付けられる。コネクションIDは、番号付けされた順番に使われないといけない(MUST)。

ネットワーク変更上のリンク可能性を壊すことを望むクライアントは、サーバーが提供するコネクションIDを使わなければならなず、同様にSection 7.6.1.1で書かれている通りの外部の予測不可能な値がパケットシーケンス番号を増加しなければならない(MUST)。パケット番号ギャップは、累積される。クライアントがコネクションIDをスキップするかもしれないが、パケット番号ギャップは、使用するコネクションIDで構成されることに加えて、スキップされたコネクションID用に構成されたパケット番号ギャップを提供することを確保されなければならない(MUST)。

新しいコネクションIDでマークされたパケットを受信するサーバーは、期待されるパケット番号に累積パケット番号ギャップを追加することでをパケット番号をリカバーする。サーバーは、送られてきた小さないギャップを含んだパケットを捨てることを推奨する(SHOULD)。

例えば、サーバーは、新しいコネクションIDに関連付けられたパケット番号ギャップ7を提供したいとする。もし、サーバーが前回のコネクションIDを使うパケット10を受信したら、新しいコネクションIDは18から始まることをパケットに期待しなければならない。新しいコネクションIDのパケットと17のパケット番号は、エラーとして破棄される。

7.6.1.1 パケット番号ギャップ

リンケージを避けるために、パケット番号ギャプは、乱数から外部から判別不可能でなければならない(MUST)。シーケンス番号のコネクションID用パケット番号ギャップは、 ビッグエンディアンフォーマットの32-bit整数としてのシーケンス番号をエンコーディングすることで計算される。以下、計算方法:

Gap = HKDF-Expand-Label(packet_numer_secret, "QUICパケットシーケンスギャップ",シーケンス,4)

HKDF-Expand-Labelの出力は、ビッグエンディアン番号として解釈される。packet_number_secretは、QUIC-TLSSection 5.6に記述されている通りのTLS鍵交換からて依拠される。

8. フレームタイプとフォーマット

フレームのパケット構造が書かれている。実装の注意点もちらほら書いている。
ACkフレームは、他のフレームと比べて長めに書かれている。

9. パケット化と確実性

ICMP& TCPよりもQUICのほうがセキュアだという記述があった。

10. ストリーム: QUICのデータ構造抽象(Data Structuring Abstraction)

ストリームIDの使い回しはダメ。小さい数からストリームIDとする。ストリームID 0x00は、暗号ハンドシェイク用。アプリケーションは0x00以外を使う。

ストリームのライフサイクルの記述がある。
コネクションエラーについては12章を参照。

フレームと状態遷移の記述が多い。

Q. ひょっとしてストリームを作るには、MAX_STREAM_IDフレームを1つ上げるだけでいいのかな?

STREAMフレームでデータのやり取りを行う。

QUICでは、フレームの優先順位の交換を行っておらず、アプリケーションに依存している。
QUICの実装では、フレームの優先順位を決められるようにすること(SHOULD)。
暗号ハンドシェイクに利用されるストリーム0x00は、最も高い優先順位であること(MUST)。

11. フロー制御

QUICのフロー制御について書かれている。HTTP/2のフロー制御と似ている。
エッジーケースなども書かれているので、この辺を考えて実装すると良い実装になりそう。

2段階のフロー制御がある。
1. コネクションフロー制御: コネクションの受信者の許容値を超えないように送信者が制御する
2. ストリームフロー制御: 1つのストリームが受信者のバッファを消費しきらないための制御

受信者は、MAX_DATAフレームまたはMAX_STREAM_DATA フレームを、コネクションまたはストリームで受信したい先頭からのバイト数を通知するために、送信者へ送らないといけない。

考慮すべきストリームとコネクション制御のエッジケースが書かれている。

  • 送信者は、相手がブロックしたと信じているが、相手自身は、もっとデータを受信することを予期している場合にコネクションのデッドロックが発生する。送信者は、送られてこないMAX_DATAまたはMAX_STREAM_DATAを待つ羽目になる。
  • 他…

MAX_DATA と MAX_STREAM_DATA の値自体は実装者に任せている。QUIC実装の性能に大きく影響が出そう。
TCPの制御が参考になるかもしれない。

12. エラーハンドリング

エラーには、コネクションエラーとストリームエラーがある。

コネクションエラーは、CONNECTION_CLOSEでエラーコードが含まれる。
ストリームエラーは、RST_STREAMにエラーコードが含まれる。

エラーコードの範囲

  • 0x00000000-0x3FFFFFFF: アプリケーションが定義できるエラーコード
  • 0x40000000-0x7FFFFFFF: host-localないで用いられるエラーコード。相手に送られない。
  • 0x80000000-0xBFFFFFFF: QUICのトランスポートエラー。パケット保護のエラーも含まれる
  • 0xC0000000-0xFFFFFFFF: 暗号のエラーコード。暗号ハンドシェイクプロトコルで定義される

QUICのトランポートエラーについて0x80000000番台のエラーを13個定義している。

13. セキュリティーとプライバシーの検討事項

攻撃者がIPアドレスのスプーフィングで0-RTTとマイグレーションを利用して攻撃してくる。これに関して2つの攻撃方法が書かれている。

Slowloris攻撃:たくさんのコネクションを出来る限り長く維持し続ける攻撃。
1IPあたりのコネクション数を制限することで対策。

受信したバイトが配置されたメモリーに対して、攻撃するのかな?(理解していない)

14. IANAの検討事項

IANAへの登録する事項が書かれている。7章で述べたパラメータについて触れられている。

15. 参考文献

QUIC-TKS がよく参照されていた印象。
QUIC-RECOVERY時々が参照されていた。

1
2
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
1
2