KMC活動ブログ

京大マイコンクラブの活動の様子を紹介します!!

外部web改造(デザイン編)

こんにちはこんにちは、id:crashrt です。

最近、KMCのサイトが新しくなりました。 デザインから実装まで色々改造しています。 今回は僕が担当したデザイン部分について書いていきたいと思います。 改造後の様子は以下のサイトから確認してみてください。

www.kmc.gr.jp

Image from Gyazo

外部web改造の発端

外部webの改造が始まったのは2年近く前の2022年1月です。 GlassmorphismとかNeumorphismとかについて話してるうちに 「KMCのサイトにNeumorphismとか取り入れたら面白そうじゃね?」 となり、改造が始まりました。

ちょうど実装側も改造したいねという話があったようで、 色々変えていくことになりました。

ページ構造の変更

よりサイトが見やすくなるよう、ページの構造を少し変えました。 大きく変えたのはトップページと活動内容のページです。

トップページ

まずはサイトでアクセスしたときに最初に表示されるトップページを変えました。 外部webの目的は活動内容の公開と入部案内と考えて、

  • 入部案内へのボタンをトップの上の方に設置
  • コンピュータ全般、だけでは活動内容が分かりにくいのでもう少し具体的な例を表示

することにしました。

活動内容

活動内容ページも少し変えています。 活動内容はKMCとしての「主な活動」と部員有志による「プロジェクト」に分けています。 もともとNFやコミケの出展情報のページは活動内容のページと並列だったのですが、出展もKMCとしての活動の一部なので 主な活動としました。

図にするとこんな感じです。

変更前

├ 出展情報
└ 活動内容
  ├ 部誌
  ├ アドベントカレンダー
  ├ エイプリルフール企画
  └ プロジェクト
    └ プロジェクト色々...

変更後

└ 活動内容
  ├ 主な活動
  │ ├ 作品集
  │ ├ 部誌
  │ ├ 出展情報
  │ ├ アドベントカレンダー
  │ └ エイプリルフール企画
  └ プロジェクト
    └ プロジェクト色々...

新しく作品集というものがありますがこれは今作ろうという話になっているサイトです。 そのうち実装・公開されると思います。

デザインの変更

デザインでは過度な装飾は避け、シンプルなデザインにすることを特に意識しました。 すっきりした印象にしたかったので、枠線などはあまり使わず余白を活用しました。 仕事でやっているわけでもないので僕が好きな感じのデザインにしています。

デザインソフトはAdobeのXDを使っています。 XDを使うのは初めてだったので色々調べながらだったのですがなんとかなって良かったです。 URLでデザインの共有ができるのは便利ですね。

配色

色は青を基本とした配色にしています。 使用している色は以下のとおりです。

  • 背景:#F2F2F2
  • ベース:#2D3D61
  • メイン:#4072B3
  • アクセント:#B82E6A

Image from Gyazo 配色は https://color.adobe.com/ja/create/color-contrast-analyzer を使ってコントラストが十分か・カラーの競合が無いか確認しています

Neumorphism

当初の目的のNeumorphismも要所要所で取り入れていきました。

Neumorphism(ニューモーフィズム) はデザインのスタイルの一つで、立体感のあるSkeumophism(スキューモーフィズム)をシンプルにしたものです。 背景と同じ色の要素にハイライトと影をつけて立体感のある要素に斜め上などから光が当たっているように表現しているのが特徴です。 詳しくは以下のサイトなどが参考になると思います。

coliss.com

普通のNeumorphismは光と影だけで要素を表現するため、すこし見づらいという欠点があります。 そこで、見やすくするために色々試してはいたのですが、 他の見出しなどに比べて主張が強くなりすぎたりくどくなったりしたので普通のまま使うことにしました。 が、見づらいのは変わらないので、ハイライトテーマの実装など今後なにか工夫をしていけたらいいなと思います。

色々試していたものたち Image from Gyazo Image from Gyazo

本当は Glassmorphism なども取り入れようとしていたのですが、背景選びが難しかったことや文字が読みづらくなりがちだったことから諦めました。 いいアイデアが思いつけばどこかで使えたら良いなと思っています。

まとめ

外部web改造後のデザインについて書いてみました。 webデザイン初心者が調べながら作ったものでまだまだ改善点はあるので、今後も色々改良していきたいですね。 毎年KMCのサイトを見て来てくれる新入生も結構いるので、より分かりやすいサイトになっているといいなと思います。

AS59128 のフロー情報収集と Amazon Athena での分析

こんにちは、 id:sora_h です。これは KMC Advent Calendar 2023 12 日目の記事です (大遅刻)。

KMC ではインターネット接続手段の 1 つとして AS59128 を 2017 年頃より運用して、部室内のサーバーや一部の部員が利用しています。これまでフロー情報の収集は行ってきませんでしたが、今年、フロー情報の統計を収集して分析を可能にしたため、その実装を軽く紹介します。地味に pmacctd のドキュメントが難解だったので…。

続きを読む

C103で部誌『No Configuration』を頒布しますという宣伝

これはKMC Advent Calendar 2023 - Adventar 18日目の記事です。昨日の記事はcc141さんの会計士的アプローチによるアドベントカレンダー執筆RTA2 - cc141の日記でした。

こんにちは。KMC2年目のtrdrです。

KMCでは年2回、コミケに合わせて部誌を発行しています。もうすぐ始まるC103では、新刊を含む過去数回分の部誌を頒布する予定です。

今回の新刊『No Configuration』

前回までの部誌と同様、内容は大きく分けて

  • 部員のコラム
  • 部内活動の近況

の2つとなっています。主な内容は以下の通りです。

部誌の制作について

部誌を作る際に使われるテンプレートはGitHubで公開されています。今回もこれを使って部誌を作りました。

github.com

このテンプレートからprivate repositoryを作成し、記事を書く部員が各々そこへMarkdownファイルを追加していくことで部誌の制作が行われています。 校正については各記事についてissueを立て、コメントとして指示・提案を残していくという形で進められています。

おわりに

C103は「日曜日 東地区 “T” ブロック 09b」にて部誌の頒布を行う予定です。機会があれば足を運んでいただけると嬉しいです。

また、過去に発行した部誌の一部は部誌 — 京大マイコンクラブ (KMC)で公開されています1。興味のある方はぜひご覧ください!

明日の担当はpollenJPさんです。お楽しみに!!


  1. ページの更新が滞っており、この記事を書いている時点ではC100のものが最新となっていますが、C101・C102についても部誌の制作は行われました(更新予定です!)

RubyKaigi 2023でのセキュアなDNSリゾルバの運用 ― DNS-over-HTTPSとDDR

こんにちは,id:hanazukiです.いよいよ明日からRubyKaigi 2023が始まりますね.私は昨日から松本入りして,今日は会場の設営をやっていて,ちょうどいま(17:00),お昼休憩をとっています…….会期が終わったあとにまとめを書こう〜なんて思っているとたいていそのまま忘れてしまうので,今年は準備と並行して記事をしたためている次第です.

私は,RubyKaigiで来場者向けに提供しているWi-Fiの構築・運用を2017年から手伝っています.毎年おなじことをやっていてもおもしろくないというか,だんだんとやることが目減りしてしまいますよね.今年は何か新しいことをできないかと考えて,RubyKaigi会場ネットワークのDNSゾルバをDNS-over-HTTPSに対応させることにしました.

この記事では,

  1. DNS-over-HTTPSが普通のDNSとはどう違うのかを見てから,
  2. RubyKaigi会場ネットワークのDNSゾルバがどのように作られているのか紹介し,
  3. どうすれば今年のRubyKaigiの会場でDNS-over-HTTPSを試せるかを説明したいと思います.

(2023-05-17追記)実際に運用をして分かったことと分からなかったことを記事末尾に追記しました.

1. DNS-over-HTTPSとは

そもそもDNSとは何だったか

まずは,用語の整理も兼ねてDNSをおさらいしましょう.この記事ではDNSに関連する用語はできるだけRFC8499に従いたいと思います.また,JPRSによるRFC8499の日本語訳も参考にしています.

DNSは,ドメイン名(例えばkmc.gr.jp.)をキーとして様々な情報を引ける階層型の分散データベースシステムです.たとえば,kmc.gr.jp.のAレコードをクエリすると,KMC部室のIPv4アドレス192.50.220.129がわかります.みなさんがブラウザでhttps://kmc.gr.jpにアクセスしようとすると,裏でこっそりこのようなクエリが行われています.ドメイン名からIPアドレスを得る手続きなので「名前解決」とも呼ばれます.

kmc.gr.jp.以下のドメインに関するコンテンツはKMCが管理しています.これは,jp.ドメインを管理しているJPRSに利用料を払ってkmc.gr.jp.以下を使う権利を買うことで実現しています.JPRSもルートドメイン.を管理してるICANNから国を代表してjp.を使う権利を取得しています.kmc.gr.jp.jp..といった管理の単位はゾーンと呼ばれています.特定のゾーンに関するコンテンツを保持しているDNSサーバを権威サーバと呼びます.権威サーバはそれぞれのゾーンを管理する組織が責任を持って運用しています.このようにドメインの親子関係によって管理主体がわかれていることから,DNSは階層型かつ分散的であると言えます.この記事では権威サーバは脇役なのであまり登場しません.

一方で,権威サーバに世界中のクライアント端末からクエリが殺到すると大変なので,キャッシュサーバを設置するのが一般的です.キャッシュサーバは,伝統的にはISPやオフィスなど,ある程度まとまった利用者のいるネットワーク管理主体が設置していました.最近はGoogle Public DNSやCloudflare DNSなど,世界中からアクセス可能なキャッシュサーバも増えています.クライアント端末からのクエリを取りまとめて,情報を持っている権威サーバからレコードを取得し,後に備えてキャッシュするDNSサーバのことをフルサービスリゾルバと呼びます.

この記事ではフルサービスリゾルバの意味で「DNSゾルバ」という言葉を用います.RubyKaigiの会場にも1,000人ほどのRubyistが2,000台*1くらいのガジェットを携えてやってくるので,伝統的な流儀に則ってDNSゾルバを設置してRubyistのみなさんに提供しています.この記事ではRubyKaigi会場で提供しているDNSゾルバの話をします.

古き良きインターネット時代のDNS

通信プロトコルとしてのDNSはごく素朴に設計されました.ドメイン名(たとえばkmc.gr.jp.)と欲しい情報の種類(たとえばIPv4アドレスが欲しいときはAレコードを表す整数1)を何ビットかのフラグと一緒に送ると,いくらかの付加情報をともに192.50.220.129が返ってくるだけです.

ハンドシェイクの不要なUDPプロトコルが用いられていましたが,おそらく当時の技術的な制限でメッセージあたり512バイトまでに限られていました.クエリやレスポンスの大きさがこの上限を超えた場合はTCPにフォールバックする決まりでしたが,TCPはハンドシェイクが必要なので余分な時間がかかります.

後にプロトコルを拡張する中でフラグのビット数が足りなくなったり,メッセージが512バイトに収まらないことが増えたりしたことから,拡張規格のEDNS0が考案され,通信経路が許せばUDPで65,535バイトまで送ってもよいことになりました.

このプロトコルが今日まで使われ続けています.当初のインターネットは平和だったので暗号化の機能はありません.

暗号化インターネット時代のDNS

2000年代ごろにもDNSプロトコルを暗号化しようとする試みはあったそうですが,あまり普及はしなかったようです.2010年代にさまざまな事件があり,実は国家規模の権力と動機と資金があればインターネットをまるごと盗聴することも不可能ではないらしいと分かってきました(本当のところはよく知りませんが……).これによってさまざまなインターネットプロトコルを暗号化しようという機運が高まります[RFC7435].

そして,TCP上でTLSを用いて通信を暗号化するDNS-over-TLS (DoT)が考案されました[RFC7858].TCPハンドシェイクに加えて,TLSハンドシェイクのオーバーヘッドがかかるも,背に腹は変えられないというわけです.とはいえ無策ではなく,TCP/TLSコネクションをキープアライブして,クエリをパイプライン化するなどの工夫はされます.現在このプロトコルAndroidやsystemdなどのクライアント環境に実装されています.

DNS-over-HTTPS

さて,伝統的な平文のDNSプロトコルは53/udpと53/tcpポートを使います(それゆえレトロニムでDo53とも呼ばれています).一方,DoTは853/tcpを利用します.みなれないポート番号なので,環境によってはファイアウォールなどがあやしい通信とみなして遮断してしまうことがありました.

そこで,DNSのリクエストレスポンスをHTTPS上で行うDNS-over-HTTPS (DoH)が考案されました(Google Public DNSなどで実装され,RFC8484で標準化).いわばDNS over HTTP over TLS over TCPということになりますね.443/tcpで通信をするため,外見上は普通のHTTPSと区別がつかず多くの環境で使えるはずという狙いです.前のクエリ・レスポンスが完了するまで次のクエリを送れないHoLブロッキングの問題を回避するために,マルチストリーム通信の可能なHTTP/2 RFC9113が主に利用されます.

HTTPSはおそらく現代で最も開発研究の労力が投じられているインターネットプロトコルの一つで,DoHは自動的にHTTPSの改善の恩恵にあずかれるというメリットもあります.例を挙げると,Google Public DNSとCloudflare DNSにはHTTP/3を使ったDoHがすでに実装されていて,FirefoxChromeはこれを使うことができます(DNS over HTTP over QUIC over UDP).HTTP/3には,HTTP/2と違ってTCP層でのHoLブロッキングを回避できる利点があり,この特徴がDoHにも自動的に導入されるというわけです.

DNS-over-???

その他に,UDP上でDTLSを用いて通信を暗号化するDNS-over-DTLS (DoD)も考案されましたが(DNS over DTLS over UDP)[RFC8094],実験以上の規模で実装されたという話は聞きません.

また,DNS-over-QUIC (DoQ)というプロトコルも提案されています(DNS over QUIC over UDP)[RFC9250].HTTP/3を使うDoHに比べて,HTTP層を省くことでより軽量になるという謳いのようです.まだDoQをサポートするクライアントOS・ブラウザの話は聞かないですが,はやるでしょうか? 一部の公開DNSゾルバには実装が始まっています*2.ちなみにデフォルトでは853/udpポートを使うことになっていて,DoDのポート番号がリサイクルされています.

2. RubyKaigiのDNSゾルバはいかに作られているか

RubyKaigi 2023の物理ネットワークの一部.ケーブルをタップすれば,個人規模の権力と動機と資金でもDNSトラフィックの盗聴ができる(忙しいのでめんどうごとは増やさないでくださいね).

サーバソフトウェア

RubyKaigiのDNSゾルバには,フルサービスリゾルバの機能をもったUnboundを使っています.Unboundの最新版はDo53に加えてDoTとDoH (HTTP/2)をサポートしています.Ubuntu 22.04でパッケージされているバージョン1.13.1ではDoH機能に不具合があり,特定の条件でリクエストに応答しそこねるようでした*3.RubyKaigi程度の規模であれば,通常のチューニングをしておけば問題はないと考えています.DoT/DoH特有の事情として,これらのプロトコルTCPソケットを使うのでincoming-num-tcpを十分に大きく取る必要があります(デフォルトの10では足りません).

UnboundのDoHサーバはHTTP/2を喋りますが,クライアントはすでにHTTP/3を使うものも現れているので(後述),EnvoyHTTPSリバースプロキシとして置いています.DoHだからといって特別な設定は必要なく,信頼できないクライアントからのリクエストを受けるために通常のエッジプロキシとしての設定をしておけばよかろうと思います.UnboundのHTTP/2サーバ機能にまだあまり利用実績はないでしょうからやや心配で,HTTP/2のリクエストも一旦Envoyで受けてプロキシする形にしています.

Unboundのメトリクスを取るためにletsencrypt/unbound_exporterにいくつかパッチを当てたものを利用しています.パッチの一部はプルリクエストにしており,マージされると良いのですが.グラフを眺めるのが好きな人のために会期中限定でGrafanaのダッシュボードを公開しています.公開終了しました.スナップショットをご覧ください.

EKSとNLB

EnvoyやUnboundのような細々としたサーバソフトウェアたちを走らせるために,AWSのマネージドKubernetesサービスのAmazon EKSを利用しています.そして,Kubernetesで管理されるコンテナを束ねて冗長化するため,AWSのNetwork Load Balancer (NLB)を使っています.

伝統的なDo53のクライアントは,UDPデータグラムにメッセージが収まりきらなかった場合,同じIPアドレス宛のTCP通信にフォールバックします.ポート番号は53から変えられません.また,日和見的にDoTにアップグレードするクライアントは,Do53と同じIPアドレスの853/tcpに接続を試みます.これらの要請から,Do53 (UDP/TCP),DoTのリゾルバはすべて同じIPアドレスで提供する必要があります.さらに,今回はDoTとDoHのリゾルバをどちらもresolver.rubykaigi.netというドメイン名で使えるようにしたかったため,DoTとDoH (UDP/TCP)も同じIPアドレスで提供する必要が生まれました.

AWS Load Balancer Controllerを使うと,Kubernetes上のServiceリソースの定義によってAWS上のロードバランサを管理できるようになります.しかしながら,1つのServiceでTCPUDP両方の通信を受けることができない制限があります(これを可能にする機能追加はMixedProtocolLBServiceとして開発中です).さらに,KubernetesのServiceの抽象化では,53番ポートへの通信をunboundのPodに流し,443番ポートへの通信をenvoyのPodへ流すといった構成も取れません.こういった制約から,NLB自体はKubernetesの外部で管理して,Service配下のPod一覧とNLBのtarget groupを同期するTargetGroupBindingという機構を使っています.

ネットワーク

ここまででDNSゾルバを起動するところまでできました.では,松本のRubyKaigi会場と東京のAWS,そして世界はどのように繋がっているのでしょうか.この話をするとかなり長くなってしまうので,この記事では大枠だけを紹介したいと思います.ネットワークのL1/L2の設計,L3/L4部分の構築はWi-Fiスポンサーのクックパッドさんが担当してくれているので,そちらから本格的な解説が出るのを一緒に願いましょう.その代わりに私からは,分かった気持ちになれるかもしれない絵を用意しました.

右側がAWS東京リージョン,左上が都内DC,左下が松本の会場.会場・DC間はフレッツNGN網内折り返しにより,DC・AWS間はAWS Direct Connectにより接続されている.AWS上にはDNSゾルバがデプロイされている.実際はそれぞれの部分をさまざまな層で冗長化しているが図では省略している.

まず,松本の会場にはNTT東日本フレッツ光ネクストを引いています.これは一般のご家庭で使っているものと同じグレードの光ファイバー回線なのでなじみがあると思います.そして,クックパッドさん提供の都内データセンター(DC)のラックスペースにRubyKaigi用のルータを設置してあります.このラックにもフレッツ回線が引かれていて,会場とこのルータの間はフレッツNGN網内の折返し通信によって接続されています.NGNについては,部員のid:sora_hによる解説記事『NTTフレッツ光における通信速度などの現状について、背景や仕組みから正しく理解する2020*4をおすすめします.

実はKMCもクックパッドさんから同じラックとキャリア回線をお借りして,KMCが運用する自律システムAS59128 / KMC-ASのPOPを開設しています(このプロジェクトについても解説を読めるとうれしいですね!).KMCはこのPOPからRubyKaigiネットワークに対して,IPアドレスと対外接続を提供しています.RubyKaigi会場でRubyistたちがインターネットと通信をする場合,会場からフレッツ網を通って都内DCへ,都内DCからKMCのASを経由してインターネットへと抜けてゆきます.実際,会場Wi-Fiに接続した端末がどのパブリックIPアドレスを使って通信しているか調べると,KMCが保有するIPアドレス192.50.220.0/24を使っていることがわかると思います.

さて,AWSとの接続はどうでしょう.これもクックパッドさんの提供で,AWS Direct Connectの専用線でRubyKaigiのルータとAWS VPCを結んでいます.Wi-Fiに繋がった端末から名前解決すると,フレッツ網を通って都内DCへ,都内DCからDirect Connectを通ってVPCへ向かいます.VPCではNLBによってEnvoyやUnboundのコンテナのどれかへ振り分けられます.

  • 会場のクライアントからインターネットへのトラフィックは,NGNを通って都内DCへゆき,KMC-AS経由でインターネットへ出る(左側の水色矢印).
  • クライアントからのDNS再帰クエリは,Direct Connectを通ってAWSへたどり着き,NLBによってEnvoyコンテナに振り分けられ,EnvoyからUnboundへプロクシされる(右下側の赤色矢印).
  • Unboundからの反復検索クエリは,Private NAT GatewayでNAPTされ,Direct Connectを通って都内DCへ向かい,KMC-AS経由でインターネットへ到達する(上側の黄色矢印).
  • 一部のドメイン名の名前解決は,UnboundからRoute53 Resolverに転送されて,Route53のprivate hosted zoneを参照している(右上側の赤色矢印).

こうしてDNSゾルバがクライアントからのクエリを受けられるようになりました.あとは,リゾルバがインターネットの向こうの権威サーバーへクエリできるようにしたいです.通常,AWS VPCで稼働しているサービスがIPv4インターネットと通信する場合,パブリックサブネットに設置したNAT GatewayでNAPTして,Internet Gateway経由で外部のホストと通信する構成が一般的です.

ところがRubyKaigiでは,Unboundから権威サーバへのクエリは,VPCからDirect Connectで都内DCへ抜けて,そこからKMC-AS経由でインターネットへ出すようにしています.これは,DNSを使った広域負荷分散(GSLB)に悪影響を与えないようにするためです.一部のCDNなどでは,アクセス元の地理的位置・インターネット上の論理的な位置を考慮して,ユーザをできるだけ近いサーバに接続させるために,権威サーバがどのネットワークからクエリが来たかを認識して異なるレスポンスを返し分けるということを行っています.したがって,ユーザのトラフィックはKMC-ASからインターネットへ出ているのに,権威サーバはAWSのネットワークからインターネットに繋がっているのでは都合が悪いのです.たとえば,会場のWi-Fiに接続して,o-o.myaddr.l.google.com.のTXTレコード(このDNSサーバからみてクライアントのIPアドレスを返してくれるおもしろいエンドポイントです)を引いてみると,KMCの保有するIPアドレス192.50.220.0/24になっていると思います.残念ながら,暗号化通信に対応している権威サーバは未だ少なく,リゾルバから権威サーバ間の通信は平文で行われているのが現状です.

また,rubykaigi.orgサブドメインなど一部のドメインについてsplit-horizon DNSを行っています.特定のサービスに会場内からアクセスするのにインターネットを経由せずプライベートなDirect Connect経由でVPCに通信できるよう,プライベートアドレスを返すようにしています.これを実現するために,それらのドメインに関する再帰クエリはUnboundからRoute53 Resolverに転送してRoute53でホストしているプライベートゾーンのコンテンツを返すようにしています.

クックパッド社のid:sora_hがRubyKaigiネットワークの解説を書いてくれました.もっと詳しく知りたい!と思った方はあわせてお読みください.

3. RubyKaigiの会場でDNS-over-HTTPSを試す

クライアントOS・ブラウザの対応状況

PCのChrome,EdgeやFirefoxを利用している場合,ブラウザの設定でカスタムDNSプロバイダを使用するよう選択し,プロバイダとしてhttps://resolver.rubykaigi.net/dns-query{?dns}と入力すると,DoHで名前解決するようにできます.会場外ではこのリゾルバを使えないので,外へ出たら設定を戻し忘れないように気をつけてください.Windows 11のInsider PreviewにもDoHが実装された*5と小耳に挟みましたが,まだ試せていません.

最近AndroidにもDoHが実装され,Android 11までバックポートされているようですが,特定の公開リゾルバに対してしかDoHを使わないそうです*6.残念ですね.そのかわりAndroidは「プライベートDNSモード」が「自動」に設定されていると,DoTを使った日和見暗号化を行います.DoTはAndroid Pieから実装されているそうなので*7,多くの現役スマートフォンは対応しているのではないでしょうか.

iOS/iPadOS 16とmacOS Venturaでは何もする必要がありません.最近リリースされたこれらのOSには,DNSゾルバがDoHに対応していることを自動的に検出してアップグレードする仕組みが実装されています.この機能はDiscovery of Designated Resolvers (DDR)と呼ばれています.AppleのOSのセキュアDNSへの対応は,DNSSECの話と併せてWWDC22でも紹介されていたようです*8

RubyKaigiの参加者はMacBookを使っている割合が比較的たかいと思いますが,せっかくなので他OSのユーザ向けにDDRをどのように実装すればよいか解説してみたいと思います.DDRの仕様draft-ietf-add-ddrIETFで起稿中で,AppleのOSにもおおよそこのドラフト通りに実装されているようでした.draft-ietf-add-ddrは,同じくまだドラフト段階のdraft-ietf-dnsop-svcb-httpsietf-add-svcb-dnsに依存しています.これらの規格は,DNSゾルバがどのようなプロトコルに対応しているかをDNS上で公開・取得する方法を定義しています.

この記事を書いた時点での最新版はそれぞれdraft-ietf-add-ddr-10draft-ietf-dnsop-svcb-https-12draft-ietf-add-svcb-dns-08です.以下ではこれらの版の内容に従います.

DDRを実装する

さて,みなさんの端末がどのようなOSで動いているとしてもRubyはインストールされているでしょうから,ここからはRubyのコードを使って説明をします.

まず,Bundlerで必要なライブラリをインストールするためにGemfileを用意します.

source 'https://rubygems.org'

# DoHは,HTTPS,Base64,URI Templateを知っていれば実装できます.
gem 'net-http'
gem 'base64'
gem 'addressable'

# もちろんDNSの知識も必要でした.
# RubyのDNSライブラリ(resolv)はDDRに必要な機能にまだ対応していないので,パッチを当てたものを使います.
#
# このパッチの詳細については以下のプルリクエストを参照してください:
# - https://github.com/ruby/resolv/pull/32
# - https://github.com/ruby/resolv/pull/33
gem 'resolv', git: 'https://github.com/hanazuki/ruby-resolv', branch: 'svcb-dns'

そして,Rubyスクリプトを書きます.

#!/usr/bin/env ruby
require 'addressable'
require 'base64'
require 'net/http'
require 'resolv'

DoHResolver = Data.define(:hostname, :socket, :dohpath)

# Wi-Fiに接続するとDHCPで端末にIPアドレスが割り振られ,DNSリゾルバ(たち)のIPアドレスを教えてもらえます.
# 従来のDHCPの機能では,これらのリゾルバがどのような暗号化プロトコルに対応しているか伝える方法がありません.
#
# RubyKaigiへバーチャル参加の方は,1.1.1.1や8.8.8.8に書き換えて試してみてください.
RESOLVERS = %w[192.50.220.164 192.50.220.165]

# IPアドレスを与えられたときにDoHをサポートしているリゾルバを探して接続する手続きです.
def connect_doh_resolver(unencrypted_resolvers)

  # DHCPで教えてもらえるDNSリゾルバは優先度順に並んでいるので,前から順番に試します.
  unencrypted_resolvers.each do |unencrypted_resolver|

    # ブートストラップのため,まずは平文で問い合わせをします.
    Resolv::DNS.open(nameserver: [unencrypted_resolver]) do |dns|
      dns.timeouts = 3

      # "resolver.arpa."は,問い合わせ先のDNSリゾルバ自身を表す特殊なドメインです.
      # このリゾルバがDNSのサービス(_dns)を,どのようなプロトコルで提供しているか(SVCB)を問い合わせます.
      svcb_rrset = dns.getresources('_dns.resolver.arpa.', Resolv::DNS::Resource::IN::SVCB)

      # RubyKaigiで運用しているリゾルバは,このようなSVCBレコードを返します:
      #
      #                                   優先度 -- 小さい方が強い
      #                                   |  ターゲット名
      #                                   | /                        ここから後ろはパラメータ
      #                                   | |                       /
      #   _dns.resolver.arpa. 300 IN SVCB 1 resolver.rubykaigi.net. alpn="h3,h2" dohpath="/dns-query{?dns}"
      #                              SVCB 2 resolver.rubykaigi.net. alpn="dot"
      #                              SVCB 9 resolver.rubykaigi.net. alpn="http/1.1" dohpath="/dns-query{?dns}"
      #
      # HTTP/3とHTTP/2でのDoHと,DoT,おまけとしてHTTP/1.1でのDoHをサポートしているとわかります.

      # 優先度順に並べかえます.
      svcb_rrset.sort_by!(&:priority)

      # 今回は簡単のためにAliasMode(優先度0のレコード)への対応を省きます.
      # ざっくりいうと,AliasModeはCNAMEのように別のドメイン名を参照するよう指示するものです.
      fail 'TODO: AliasMode is not supported' if svcb_rrset.any?(:alias_mode?)

      # HTTPファミリーのプロトコルをサポートしていて,dohpathパラメータの指定されているSVCBが,
      # DoHに使うのに適しています.
      #
      # 通常,DoHではHTTP/2またはHTTP/3を使いますが,今回は簡単のためHTTP/1.1を使うことにします.
      # HTTP/1.1と同じくTCPを使うHTTP/2のターゲットにもHTTP/1.1を喋れないか聞いてみることにします.
      svcb_rrset.select! do |svcb|
        svcb.params[:dohpath] &&
          (svcb.params[:alpn]&.protocol_ids&.include?('http/1.1') ||
           svcb.params[:alpn]&.protocol_ids&.include?('h2'))
      end

      svcb_rrset.each do |svcb_doh|

        # ターゲット名からIPアドレスを引きます.
        # IPv6に対応するにはここでAAAAレコードも引きますが,今回は省きます.
        a_rrset = dns.getresources(svcb_doh.target, Resolv::DNS::Resource::IN::A)
        fail "#{svcb_doh.target} does not serve over IPv4" if a_rrset.empty?

        # それぞれのIPアドレスについて,TLSハンドシェイクを試してゆきます.
        a_rrset.shuffle.each do |a|

          # Aレコードで手に入ったIPアドレスに向けたTCPソケットを作ります.
          # SVCBレコードでポート番号が指定されていれば従います.
          socket = TCPSocket.new(a.address.to_s, svcb_doh.params[:port]&.port || 443)

          # ALPNで,サーバがHTTP/1.1に対応しているか訊きます.
          ctx = OpenSSL::SSL::SSLContext.new
          ctx.set_params(alpn_protocols: %w[http/1.1])

          # TLS接続をする際に,実際の接続先サーバのIPアドレスではなく,
          # 最初にDHCP等で設定された平文DNSリゾルバのIPアドレスを使ってサーバ証明書を検証します.
          # これがDDRの肝で,ここまでの平文DNSの通信が改竄されていた場合に気づくことができます.
          #
          # 現在のnet/httpライブラリでは,このような検証方法に対応していないようなので,
          # このコードではローレベルなAPIを使っています.
          ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
          ssl_socket.sync_close = true
          ssl_socket.hostname = unencrypted_resolver  # <- これが最も大切
          Timeout.timeout(3) { ssl_socket.connect }

          return DoHResolver.new(
            hostname: unencrypted_resolver,
            socket: Net::BufferedIO.new(ssl_socket),
            dohpath: Addressable::Template.new(svcb_doh.params[:dohpath].template),
          )
        rescue
          warn $!
        end
      rescue
        warn $!
      end
    end
  rescue
    warn $!
  end

  fail 'No resolver supports DoH'
end

# ここからは,DoHでクエリを行う手順です.
def doh_query(resolver, qname, qtype)

  # DoHでは,クエリ・レスポンスともに通常のDNSプロトコルで使われているのと同じワイヤフォーマットを用います.
  # そのため,既存のメッセージエンコーダ・デコーダをそのまま使い回せます(RubyではResolv::DNS::Message).
  #
  # 従来のDNSでは,16bitのメッセージIDを使ってクエリとレスポンスのメッセージの対応をとります.
  # HTTPはもともとリクエスト・レスポンス方式のプロトコルで,メッセージを対応づける仕組みが組み込まれているので,
  # DoHではDNS層のメッセージIDとして常に0を使います.
  query = Resolv::DNS::Message.new(0).tap do |m|
    m.rd = 1  # 再帰検索を要求
    m.add_question(qname, qtype)
  end

  # DNSメッセージをBase64でエンコードしたもので,SVCBで指定されたURIテンプレートの変数"dns"を置き換えます.
  # 通例,このURIテンプレートは"/dns-query{?dns}"のような値で,
  # 変数を代入すると"/dns-query?dns=AAABAAABAAAAAAAAA3d3dwNrbWMCZ3ICanAAAAEAAQ"のようなURIになります.
  path = resolver.dohpath.expand(
    dns: Base64.urlsafe_encode64(query.encode, padding: false)
  )

  # HTTP GETリクエストを送ります.
  # コンテントネゴシエーションなど,HTTPの機能を使って自然に拡張する余地があるのはDoHのおもしろいところですね.
  req = Net::HTTP::Get.new(path)
  req['Connection'] = 'keep-alive'
  req['Host'] = resolver.hostname
  req['Accept'] = 'application/dns-message'
  req.exec(resolver.socket, '1.1', req.path)

  # HTTPレスポンスを読みます.
  begin
    res = Net::HTTPResponse.read_new(resolver.socket)
  end while res.is_a?(Net::HTTPInformation)

  res.reading_body(resolver.socket, req.response_body_permitted?) do

    # DNSのレスポンスは,HTTPのレスポンスボディに通常のワイヤフォーマットで書かれています.
    # そのままデコードしてあげればレスポンスを読めます.
    if res.code == '200' && res['Content-Type'] == 'application/dns-message'
      return Resolv::DNS::Message.decode(res.body).answer
    end
  end

  fail "Request failed: #{res.inspect}"
end

# それでは試しに,KMC部室のIPアドレスを引いてみましょう.
doh_resolver = connect_doh_resolver(RESOLVERS)
doh_query(doh_resolver, 'kmc.gr.jp', Resolv::DNS::Resource::IN::A).each do |_, _, a|
  p a.address
end  # => #<Resolv::IPv4 192.50.220.129>

DDRの守備範囲

さて,いま見たようにDDRは,DHCPなどで非暗号化DNSゾルバのIPアドレスが与えられたときに,安全にDoHやDoTなどの暗号化プロトコルにアップグレードを行う枠組みです.この安全性はPKIで検証したTLS証明書によって暗号化DNSゾルバを認証することで担保されています.TLS証明書をうまく使うことによって,非暗号化DNSゾルバと暗号化DNSゾルバを別のIPアドレスで運用することも許されています.

非暗号化DNSゾルバと暗号化DNSゾルバのIPアドレスが一致する場合にはTLS証明書の検査を省略してもよい——とも書いてありますが,そうしないクライアントが存在する限りは正規の証明書を用意する必要があります.Subject Alt Name (SAN)にIPアドレスを載せた証明書を手に入れる必要がありますが,Let's Encryptのような有名どころの認証局はIP SANの証明書を発行してくれないので,すこし手間がかかりますね.RubyKaigiでどの認証局を使ったか気になる方は,会場に来て証明書を確認するか192.220.50.0/24のCTログを覗いてみてください.

また,パブリック認証局はプライベートIPアドレスを使う証明書を発行しないため,DHCPで配布するリゾルバのIPアドレスにはパブリックIPアドレスを使う必要があります(リゾルバがインターネットからアクセス可能である必要はありませんし,深く考えずにそうすべきではありません).IPv4アドレスの調達に難がある場合も多いかもしれません.社内環境など,全てのクライアントにプライベート認証局証明書をインストールできる条件であればこれらの要件は緩和されるでしょう.

一方,DDRの枠組みでは防げない攻撃手段も存在します.たとえば,a)攻撃者がDNSゾルバのIPアドレスへの経路を侵害し,認証局から正規のTLS証明書の発行を受けた場合,b)攻撃者にDHCPパケットを捏造された場合,c)攻撃者にSVCBレコードのクエリ・レスポンスを遮断された場合,d)攻撃者の用意した偽のWi-Fiアクセスポイントにつないでしまった場合などです.これらの攻撃からユーザを守るには,a) RPKIなどによる経路情報の認証・証明書透明性(CT)ログの監視による攻撃検知,b) Wi-FiアクセスポイントでのDHCPスヌーピング,d) IEEE 802.1Xによるネットワーク認証など,多層的な防衛が必要です.

このうちc)に関しては,DNRという別の規格が考えられている途中です[draft-ietf-add-dnr].おおまかには,DDRで平文のクエリによって取得していた暗号化DNSゾルバの情報を,最初からDHCPなどのオプションに詰め込んで渡してしまおうという作戦です.

DDRDNRをはじめとした,クライアントがネットワーク環境に応じて適切なDNSゾルバやプロトコルを選ぶための技術が,IETFAdaptive DNS Discovery (ADD)ワーキンググループで開発されています.この分野のRFCドラフトの著者の所属を見ると,クライアントOS・ブラウザの開発元,公開DNSゾルバの運営元,セキュリティアプライアンスの開発元などの企業が揃っていますね.近いうちに一般の環境にも対応が広がるのではないかと期待しています.RubyKaigi 2023のDNSゾルバには,安全なWi-Fiを提供するためと言いつつおもしろ半分でDoT/DoH/DDRを実装したので,みなさんもぜひ遊んでみてください.

RubyKaigi 2023を終えて(Day 7追記)

RubyKaigiはあっという間に終わってしまいましたね.私はDay 5に会場で機材整理をしたあと,名残惜しさから予定外に1泊増やして松本の街を楽しみました.3日間の会期は短いですが(かといって,それ以上の期間Wi-Fiを運用し続ける体力はありませんが),ひさしぶりのオフライン・フルスケールでのRubyKaigiは濃密な時間に感じられました.Kaigi Wi-FiがRubyistsのコラボレーションに役立っていたなら幸いです.

会期中にメトリクスを眺めていたところ,いくつか分かったことと分からなかったことがありました.

  • Wi-Fiの同時接続数1300くらいに対して,クエリレートは300–400 qps程度でした.このくらいであればなにも考えずに運用できますね.UnboundもEnvoyもたいへん安定しています.
  • タイプ別のクエリ数では,A/AAAAがほぼ同数でトップで,HTTPSがその7割程度でした.AAAAHTTPSへのアンサーが存在した割合のメトリクスも取れていればもう少しおもしろいデータになったかもしれないですね.PTRのリクエストもそこそこありました.これはmacOSが自機のホスト名を調べるために逆引きを行っているようです.
  • 1/3くらいのクエリがDoHを使っていて,午後休憩の時間帯ではこの割合が1/2近くに達していた瞬間もありました.みんなApple製品を大好きなことがわかりますね.実際のところは,iOS/macOSのスタブリゾルバはDoHリクエストのヘッダでUser-Agentを名乗らないようで,どのくらいの割合がAppleのOSなのか,その他の実装でDoHに対応しているものがあるのかないのかはよくわからないのです.User-Agentがフィンガープリンティングに悪用されがちな問題があるのはわかりますが,相互運用性が気になる立場ではもどかしい気持ちです.
  • DoHクエリのうち10%程度がHTTP/3を使っていて,残りはHTTP/2でした.自宅でドッグフーディングをしていたときにも,iPadOSが極稀にHTTP/3を喋っていたのですが発動条件がよくわかりませんでした.
  • 正しくDNSSEC署名されていたドメインはクエリに対する割合で5%弱でした.まだまだ普及しませんね.

RubyのKaigiでネットワークの記事を書いてもあまり読まれないかと思っていましたが,現地で感想を伝えてくださったRubyistがいたのも大変うれしかったです.Kaigi Wi-FiはあくまでRubyKaigiを支えるインフラですが,せっかくコミュニティメンバーで手作りしているので,Rubyistsにおもしろがってもらえるコンテンツも提供してゆけたらと思っています.その点で,今年は現地で手を動かせるおもちゃを用意できて,刺さる人には刺さっていたようでよかったです.半ばNOCメンバーが好き勝手あそんでいる風にもなっていますが,今年のWi-Fiは落ちなかったのでよしとしてもらいましょう.実は会期明けすぐにKMCのご近所にDDoSが着弾していて,ヒヤッとしたりしました.

RubyKaigi 2024開催地の沖縄は,インターネット上のコンテンツが集まる東京から地理的に遠く,ややチャレンジングな立地ですが,安定したインターネットを提供できるようにがんばりたいと思います.インターネット標準も引き続きウォッチしてゆくつもりなので,また来年もなにかおもしろいものを試せるとよいですね.

それでは沖縄でお会いしましょう!

おまけ

いい話すぎない(???)

*1:最近のW-Fi端末は,MACアドレスをランダム化するなどトラッキングを回避するための努力をするので,実際に接続された延べ台数はよくわかりません.それゆえ,数に根拠はありません.

*2:AdGuard - DNS over QUIC

*3:詳しく追っていませんが,おそらくNLnetLabs/unbound#420のパッチが必要

*4:サイトが落ちていることが多いので,Internet Archiveも探してみてください.

*5:Windows Insiders gain new DNS over HTTPS controls

*6:DNS-over-HTTP/3 in Android — Google Security Blog

*7:DNS over TLS support in Android P Developer Preview — Android Developers Blog

*8:Improve DNS security for apps and servers - WWDC22

部内 k8s クラスタで使う CSI Driver を Longhorn に移行しました

あたーっちゅ!最近は動いてないのに暑くて大変です。

部内のサーバーには Kubernetes クラスタが立っており、 Persistent Volume (PV) を提供する CSI Driver には TopoLVM を使っていました。

blog.kmc.gr.jp

TopoLVM 自体は非常に安定しておりノートラブルだったのですが(issue にもすぐ対応してもらって助かってます)、 1年運用していると単独で Dynamic Volume Provisioner として使うには足りない面が目立ってきていました。

主な問題は PV の Node 間の移動が簡単にできないことです。 TopoLVM は Node に LVM Logical Volume を切り出して PV として提供するものなので、レプリケーション機能はありません。そのため、 PVC を紐つけた Pod は必ず Logical Volume がある特定の Node に立つようになります。

PVC が紐ついた Pod を Node 間で移動するには PV が移動先の Node で使える必要があります。TopoLVM 単体で PV の移動を行うには手作業のマイグレーションが必要であり、アップグレードなどのメンテナンス時は Pod の移動を諦めて単に止める状態になっていました。

1年たってノードが増えたこともあり、 TopoLVM を卒業して分散ストレージに手を出すことにしました。

  • クラウドネイティブな分散ストレージを検討しよう
  • Longhorn
    • 仕組み
      • Replica
      • instance-manager-r,e 詳細
      • iSCSI や Node 周り詳細
    • Drain 時の挙動
  • OpenEBS (ざっくり)
  • 移行
続きを読む

ISUCON本を読んだ感想

こんにちは、KMC-ID: karakasaです。

今回は「達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践」、通称「ISUCON本」をご恵贈いただきましたので、早速読ませていただいた感想を書かせていただきたいと思います。

gihyo.jp

そもそもISUCONとは

ISUCONとは「Iikanjini Speed Up Contest」の略で、LINE株式会社が主催する、Webサービスのパフォーマンスチューニングコンテストです。

7/23に開催を控えているISUCON12オンライン予選の参加登録では、わずか数分で参加枠が埋まるほどの人気ぶりを見せています。

本書について

そんなISUCONで数々の実績を誇り、現場でも活躍する達人たちが、Webサービスを高速化する手法について解説するのが本書です。

タイトルにISUCONを冠してはいますが、単にISUCONの対策に限らず、実務で作成されるWebサービスに対しても役に立つような知見が詰まっています。

本書の内容について

本書で扱われている内容は多岐に渡ります。 モニタリングの方法やDBのチューニング、キャッシュの活用やリバースプロキシの利用など、ISUCONでも必要となる基本的なトピックについては一通り網羅されていると言えるでしょう。

本書の良いところの一つは、これらの話題についてprivate-isuという、pixiv株式会社で過去に開催された社内ISUCONにおける問題を題材に、種々の改善手法を具体的に適用して学ぶスタイルを取ってるところです。 実際にログを出力してボトルネックを解析する手順であったり、各種改善手法を適用した時に、単にスコアが上がる以外にどのような変化が見られるのか、といった内容を手を動かして学べるので、より理解が深まる形式になっているかと思います。

github.com

もう一つの良いところは、コラムなども使用して細かいチューニングの方法まで、広く浅く記載してあるところだと思います。 ISUCONにおいて、インデックスの作成やN+1問題の解消などは初心者にとってもとっつきやすい内容であり、多くの方が認知している内容だと思います。 しかし、例えばMySQLの設定(innodb_buffer_pool_size や innodb_flush_log_at_trx_commit など)について多くを知っている方々は、かなりISUCONに慣れた層になってくるでしょう。

これらのパラメータはそもそも存在を知らなければ、チューニングのしようもなければ、検索して認知することも難しいでしょう。 本書を読むことで、このようなパフォーマンスチューニングに役に立つが、そもそもその知識について知らないということすら知らないレベルの話題にも触れることができます。

結果として、パフォーマンスチューニングにおける選択肢が増えます。 N+1問題などのわかりやすい問題に対処した後に、何もすることがなくなった、次に何をすればいいのかわからない、といったことにならず、とりあえずこれを試してみよう、という動きが取れるようになるかと思います。

先日、練習のためにISUCON11の予選問題に取り組んでみたのですが、そこでも「そういえばISUCON本にこんなことが書いてあったけど、今まさに試してみたらスコアが上がるのではないか?」といった場面が多々ありました。

他にも、ISUCONの参加者側には直接的な関わりはないかと思いますが、負荷試験の実施方法や、ISUCONでの採点にも使われるようなベンチマーカーの作成法といった話題もあります。 特にベンチマーカーの作成に関しては、個人的に読み物として面白かったです。

まとめ

ISUCONに初めて参加する方の中には、スコアを伸ばすために何をしたらいいのかわからない、という方が多いかと思います。 そういった初学者がまず何をしたらいいのか、その第一歩を踏み出す参考書として本書はおすすめです。

またISUCONへの参加経験がある方にも本書はおすすめです。 先に述べたように、この書籍を読むことでパフォーマンスチューニングでとれる選択肢がかなり広がると思います。

皆さんもISUCON本を読んで、ISUCON本番に向けて頑張っていきましょう。

Rustを完全に理解する2022 第8回~第10回

こんにちは、たまろん (@tamaroning) といいます。

私は2022年4月から6月に開催されていた「Rustを知ろう2022」の後釜として、「Rustを完全に理解する2022」の第8回~第10回の担当をしていました。「Rustを知ろう2022」については以下の記事をごらんください。

hsjoihs.hatenablog.com

さて、私の担当回ではRustを書いて実際に動くものを作るハンズオンを中心に行いました。

第8回 Rustのソースコードを解析しよう

この回ではMinippyを改造することを通して、簡単なLintを作成しました。MinippyはRust公式のLinterであるClippyの規模を縮小して作ったプログラムです。

参加者の方には、構文木を検査して 1+023*1 といった演算を警告する機能を追加してもらいました。

github.com

drive.google.com

第9回 Rustコンパイラについて知ろう

この回では、Rustコンパイラの概要について勉強しました。Rustのコンパイルがどのような過程を経て行われるかという質問が挙がっており、一般的なコンパイラの構成をふまえながら、borrow checkerなどのRustコンパイラ特有の仕組みについても説明しました。

また、ClippyとRustcのソースコードを読む時間も設けました。

drive.google.com

第10回 Actix-rsでWebアプリを作ろう

前回と前々回では似たような内容が続いていたため、第10回ではテーマを大きく変えて、Actix-rsを使って、タスク管理アプリを作りました。

Webサーバーを立てたことがない参加者も多く、苦戦したようでした。 最終的には、参加者の全員がAPIを作成して、タスク一覧を表示すことができ、多くの人が、アプリを完成させることが出来ました。

また、mutexやJSON形式といったRustとは直接関係のないことについても説明をしました。

github.com

drive.google.com

まとめ

三回という短い回数でしたが、この勉強会では、Rustを含め様々な話題について勉強しました。スライドやリポジトリは公開しているので、この記事を見てくださった方は、活用していただけるとうれしいです。

宣伝

KMCでは、このような勉強会を毎週開催しています。年齢や大学を問わず参加可能ですので、興味を持っていただければ、ぜひ入部を検討してみてください!

www.kmc.gr.jp