$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

ioドメイン障害を理解するため、DNSの仕組みについて勉強した

先日、ioドメインの障害があったのだけど、自分がDNSの仕組みをよく分かっていないせいで、いまいちどういうことが起こっていたのか把握できなかった。そこで、DNSの仕組みについて軽く勉強したので、そのメモを残しておく。内容は間違っているかもしれないので、その場合は指摘してください。

DNSについて学んだこと

Software Design 2015/4DNSの教科書が非常に勉強になった。また、 インターネット10分講座:DNSキャッシュ - JPNICも参考になる。

権威サーバとフルリゾル

まず、DNSサーバには権威サーバとフルリゾルバの二つの種類が存在する。

  • 権威サーバ
    • ドメインの情報を管理し、自分の管理しているゾーンの情報を提供するだけのサーバ
    • 問い合わせたドメインが自分のゾーンの管理下ではない場合、別の権威サーバへ委任するという情報を返す
    • コンテンツサーバとも言われる?
    • 例) comドメインの権威サーバ、Route53の権威サーバなど
  • フルリゾル
    • 名前解決要求を受け取り、権威サーバに問い合わせ、委任情報を受け取りながら再帰的に名前解決を行うサーバ
    • 名前解決結果のキャッシュの役割も同時に担うことが多い
    • 例) Google Public DNS(8.8.8.8)

この二つの役割のサーバが連動して、ドメインの名前解決は行われている。

DNS問い合わせはたらい回しの仕組みで、再帰的に権威サーバに問い合わせしている

DNSの教科書によると、DNS問い合わせは以下のような流れで進むと書いてある。スタブリゾルバというのは、名前解決のための単なるクライアントライブラリととりあえず思っておけば良い。ルートDNSサーバ、TLD DNSサーバ、組織のDNSサーバが上記した権威サーバである。

  • 1. アプリケーションから名前解決要求を受け取ったスタブリゾルバーは、フルリゾルバーにDNS問い合わせを送る
  • 2. フルリゾルバーはルートDNSサーバの情報を事前に設定されているため、スタブリゾルバーからの問い合わせと同じ内容をルートDNSサーバに問い合わせる
  • 3. ルートDNSサーバはTLDへの委任情報だけを知っているため、委任情報を返す
  • 4. フルリゾルバーはルートDNSサーバから得た委任情報に従い、スタブリゾルバーからの問い合わせと同じ内容をTLD DNSサーバに問い合わせる
  • 5. TLD DNSサーバは組織ドメイン名の委任情報だけを知っているため、委任情報を返す
  • 6. フルリゾルバーはTLD DNSサーバから得た委任情報に従い、スタブリゾルバーからの問い合わせと同じ内容を組織のDNSサーバに問い合わせる
  • 7. 組織のDNSサーバは、問い合わせに対応する応答をフルリゾルバーに返す
  • 8. フルリゾルバーは名前解決ができたことを判定し、スタブリゾルバーにその結果を返す

https://www.nic.ad.jp/ja/newsletter/No51/0800.htmlから図を引用すると以下のような感じ。
https://www.nic.ad.jp/ja/newsletter/No51/images/0800_3.gif


つまり、名前解決をしようとすると、一番上位の権威サーバへ問い合わせて、それは自分のゾーン管理下にないからこっちに聞いてねとたらい回しにされて、さらに次に聞きに行って、それはこっちに聞いてねとたらい回しにされて、という風にたらい回しの仕組みによって名前解決をしているということが分かる。

たらい回しにされながら再帰的に聞いていく役割はフルリゾルバが担い、次にどこに行くかの情報や実際に最後の結果を返すのは権威サーバが担うことになる。

digでいろいろ試してみる

ここまで理解してから、digでいろいろ試してみた。

存在するドメインの名前解決をする

まずはこのブログのblog.shibayu36.orgに対してdigを打ってみる。フルリゾルバにはGoogle Public DNSの8.8.8.8を指定してみる。

$ dig @8.8.8.8 blog.shibayu36.org

; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 blog.shibayu36.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43553
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;blog.shibayu36.org.            IN      A

;; ANSWER SECTION:
blog.shibayu36.org.     3599    IN      CNAME   hatenablog.com.
hatenablog.com.         59      IN      A       13.112.5.107

blog.shibayu36.orgはCNAMEで別名としてhatenablog.comを返しているので、ANSWER SECTIONには最終的にhatenablog.comのIPアドレスが返って来ている。

では、これがどのような問い合わせになっているのか、+traceオプションを付けてdigを打ってみる。

$ dig @8.8.8.8 blog.shibayu36.org +trace

; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 blog.shibayu36.org +trace
; (1 server found)
;; global options: +cmd
.                       224784  IN      NS      a.root-servers.net.
.                       224784  IN      NS      i.root-servers.net.
.                       224784  IN      NS      d.root-servers.net.
.                       224784  IN      NS      l.root-servers.net.
.                       224784  IN      NS      f.root-servers.net.
.                       224784  IN      NS      c.root-servers.net.
.                       224784  IN      NS      k.root-servers.net.
.                       224784  IN      NS      j.root-servers.net.
.                       224784  IN      NS      m.root-servers.net.
.                       224784  IN      NS      g.root-servers.net.
.                       224784  IN      NS      e.root-servers.net.
.                       224784  IN      NS      h.root-servers.net.
.                       224784  IN      NS      b.root-servers.net.
;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 359 ms

org.                    172800  IN      NS      a0.org.afilias-nst.info.
org.                    172800  IN      NS      a2.org.afilias-nst.info.
org.                    172800  IN      NS      b0.org.afilias-nst.org.
org.                    172800  IN      NS      b2.org.afilias-nst.org.
org.                    172800  IN      NS      c0.org.afilias-nst.info.
org.                    172800  IN      NS      d0.org.afilias-nst.org.
;; Received 438 bytes from 192.203.230.10#53(192.203.230.10) in 160 ms

shibayu36.org.          86400   IN      NS      ns-351.awsdns-43.com.
shibayu36.org.          86400   IN      NS      ns-591.awsdns-09.net.
shibayu36.org.          86400   IN      NS      ns-1273.awsdns-31.org.
shibayu36.org.          86400   IN      NS      ns-1628.awsdns-11.co.uk.
;; Received 189 bytes from 199.249.120.1#53(199.249.120.1) in 590 ms

blog.shibayu36.org.     3600    IN      CNAME   hatenablog.com.
shibayu36.org.          172800  IN      NS      ns-1273.awsdns-31.org.
shibayu36.org.          172800  IN      NS      ns-1628.awsdns-11.co.uk.
shibayu36.org.          172800  IN      NS      ns-351.awsdns-43.com.
shibayu36.org.          172800  IN      NS      ns-591.awsdns-09.net.
;; Received 198 bytes from 205.251.196.249#53(205.251.196.249) in 26 ms

+traceオプションを付けるとblog.shibayu36.orgを名前解決するために再帰的な呼び出しをしている様子が見て取れて面白い(+traceを付けるとdigが再帰問い合わせしているけど、本当は8.8.8.8が再帰問い合わせすることに注意)。これを見ると

  • まず最初に指定したフルリゾルバである8.8.8.8がルートサーバの位置を教えてくれる
    • ルートサーバは13個あることが分かる
  • 次にdigはこのルートサーバの中から一つ選んで、さらに問い合わせをする。今回はe.root-servers.net.に問い合わせている。これで、今度はorgドメインを管理している権威サーバの位置を教えてくれる。
    • dig -x 192.203.230.10 すると、e.root-servers.netに問い合わせていることが分かる
  • さらにorgドメインを管理しているb2.org.afilias-nst.org(199.249.120.1)に問い合わせる。これによりshibayu36.orgを管理する権威サーバを教えてくれる。shibayu36.orgはRoute53を使っているので、AWSドメインが返っている
  • 最後にns-1273.awsdns-31.org(205.251.196.249)に問い合わせ、CNAMEにhatenablog.comが入っていることを教えてくれる

という風に探索していることが分かる。実際のフルリゾルバはさらにCNAMEにhatenablog.comが入っていることを見つけたらもう一度hatenablog.comを名前解決をして、Aレコードを見つけることになる。これはdig hatenablog.com +traceを実行すれば様子が分かるだろう。

存在しないドメインの名前解決をする

では続いて存在しないドメインの名前解決をしたときの応答を見てみる。

例えば、wrong.shibayu36.orgというドメインは存在しない。これに対してdigを打つと

$ dig wrong.shibayu36.org

; <<>> DiG 9.8.3-P1 <<>> wrong.shibayu36.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 59271
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;wrong.shibayu36.org.           IN      A

;; AUTHORITY SECTION:
shibayu36.org.          300     IN      SOA     ns-1628.awsdns-11.co.uk. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400

のように、NXDOMAINという「ドメインが存在しない」という状態をSOAレコードとして返す。この応答のTTLは300になっているので、5分間はこの結果がキャッシュされることも分かる。

これを+trace付きで見ると

$ dig @8.8.8.8 wrong.shibayu36.org +trace

; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 wrong.shibayu36.org +trace
; (1 server found)
;; global options: +cmd
.                       12337   IN      NS      e.root-servers.net.
.                       12337   IN      NS      l.root-servers.net.
.                       12337   IN      NS      m.root-servers.net.
.                       12337   IN      NS      d.root-servers.net.
.                       12337   IN      NS      a.root-servers.net.
.                       12337   IN      NS      b.root-servers.net.
.                       12337   IN      NS      k.root-servers.net.
.                       12337   IN      NS      j.root-servers.net.
.                       12337   IN      NS      i.root-servers.net.
.                       12337   IN      NS      h.root-servers.net.
.                       12337   IN      NS      c.root-servers.net.
.                       12337   IN      NS      f.root-servers.net.
.                       12337   IN      NS      g.root-servers.net.
;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 70 ms

org.                    172800  IN      NS      b2.org.afilias-nst.org.
org.                    172800  IN      NS      c0.org.afilias-nst.info.
org.                    172800  IN      NS      a2.org.afilias-nst.info.
org.                    172800  IN      NS      a0.org.afilias-nst.info.
org.                    172800  IN      NS      b0.org.afilias-nst.org.
org.                    172800  IN      NS      d0.org.afilias-nst.org.
;; Received 439 bytes from 192.228.79.201#53(192.228.79.201) in 142 ms

shibayu36.org.          86400   IN      NS      ns-351.awsdns-43.com.
shibayu36.org.          86400   IN      NS      ns-591.awsdns-09.net.
shibayu36.org.          86400   IN      NS      ns-1273.awsdns-31.org.
shibayu36.org.          86400   IN      NS      ns-1628.awsdns-11.co.uk.
;; Received 190 bytes from 199.249.120.1#53(199.249.120.1) in 384 ms

shibayu36.org.          900     IN      SOA     ns-1628.awsdns-11.co.uk. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
;; Received 124 bytes from 205.251.193.95#53(205.251.193.95) in 99 ms

となり、Route53のサーバがNXDOMAINという結果を返していることが分かる。これはshibayu36.orgまでは存在して、wrong.shibayu36.orgが存在しないので、shibayu36.orgを管理しているRoute53がNXDOMAINを返しているようだ。


では続いて、blog.shibayu36hogehogehoge.orgというドメインを名前解決してみると

$ dig @8.8.8.8 blog.shibayu36hogehogehoge.org +trace

; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 blog.shibayu36hogehogehoge.org +trace
; (1 server found)
;; global options: +cmd
.                       193946  IN      NS      b.root-servers.net.
.                       193946  IN      NS      l.root-servers.net.
.                       193946  IN      NS      m.root-servers.net.
.                       193946  IN      NS      j.root-servers.net.
.                       193946  IN      NS      h.root-servers.net.
.                       193946  IN      NS      k.root-servers.net.
.                       193946  IN      NS      d.root-servers.net.
.                       193946  IN      NS      g.root-servers.net.
.                       193946  IN      NS      c.root-servers.net.
.                       193946  IN      NS      f.root-servers.net.
.                       193946  IN      NS      e.root-servers.net.
.                       193946  IN      NS      a.root-servers.net.
.                       193946  IN      NS      i.root-servers.net.
;; Received 228 bytes from 8.8.8.8#53(8.8.8.8) in 109 ms

org.                    172800  IN      NS      b2.org.afilias-nst.org.
org.                    172800  IN      NS      a0.org.afilias-nst.info.
org.                    172800  IN      NS      d0.org.afilias-nst.org.
org.                    172800  IN      NS      a2.org.afilias-nst.info.
org.                    172800  IN      NS      b0.org.afilias-nst.org.
org.                    172800  IN      NS      c0.org.afilias-nst.info.
;; Received 450 bytes from 192.228.79.201#53(192.228.79.201) in 197 ms

org.                    900     IN      SOA     a0.org.afilias-nst.info. noc.afilias-nst.info. 2012680558 1800 900 604800 86400
;; Received 111 bytes from 199.19.57.1#53(199.19.57.1) in 202 ms

となり、orgドメインを管理している権威サーバがNXDOMAINを返していることが分かる。これはorgドメインはあるけど、shibayu36hogehogehoge.orgはないので、orgドメインを管理している権威サーバがNXDOMAINを返すわけである。


このように、ドメインが存在しないときは、そのゾーンを管理している権威サーバがNXDOMAINを返すということが分かった。

結局ioドメイン障害では何が起こっていたのか

以上を勉強してから、結局ioドメイン障害では何が起こっていたのかを自分なりに整理してみる。

起こっていたことは去年に起こった障害とほぼ近く、状況については .io ドメインがおかしくなったので dig で調べてみた(2016/10/28) - Qiita を見ると大体把握できる。また今回の状況は .IO TLD DNS issues and a day without Cloudflare に載っていたのだけど

The .IO TLD uses 7 different nameservers for its top level domain; a0.nic.io, ns-a3.io, c0.nic.io, ns-a2.io, b0.nic.io, ns-a1.io and ns-a4.io. ​2 of those nameservers, ns-a2.io and ns-a4.io, started misbehaving and instead of returning with a correct set of nameservers for the domain you were requesting, started to reply with an NXDOMAIN result. Essentially declaring that the domain you were requesting, didn’t exist.

と書いていて、7つあるioドメインの権威サーバの内、2台(ns-a2.ioとns-a4.io)が、本当は存在するドメインに対してもNXDOMAINを返してしまっていたということだった。例えばblog.shibayu36.ioというドメインが存在するとした時、

  • ルートサーバがioドメインを管理する権威サーバの位置を教えてくれる
  • ioの権威サーバがshibayu36.ioを管理する権威サーバの位置を教えてくれる
  • shibayu36.ioを管理する権威サーバがblog.shibayu36.ioのAレコードを教えてくれる

となるべきだったのに

  • ルートサーバがioドメインを管理する権威サーバの位置を教えてくれる
  • ioの権威サーバがshibayu36.ioを存在しない(NXDOMAIN)と返してしまう

ということが起こっていたという感じ。


さらに困ったことに、この結果をSOAレコードでTTL 900で返すため、フルリゾルバは900秒間この結果をキャッシュしてしまい、一度ns-a2.ioもしくはns-a4.ioに当たって、変な結果を貰ってしまったら、900秒間は名前解決に失敗し続けるという状況になっていた。


また、Google Public DNSは8.8.8.8の裏側に何台もフルリゾルバを配置している。このため、同じ8.8.8.8で名前解決をしようとすると

  • 変な結果を貰ってしまったフルリゾルバに当たるとドメインが存在しないと返る
  • きちんと結果をもらったやつに当たると正しく名前解決ができる

という風になり、確率的に名前解決に失敗しているように見える、という状況となっていた。


権威サーバが壊れるととにかく怖い!

まとめ

今回は自分がioドメインの障害時に何が起こっているか把握できず、DNSについて理解できていないなーと感じたので、DNSでの名前解決の仕組みについて勉強したことを書いてみた。とりあえず名前解決の様子についてはだいぶ理解が深まったので良かった。

「みんなのGo言語」を読んだ

Go言語の学習のため、A Tour of Goに引き続き、「みんなのGo言語」を読んだ。


「みんなのGo言語」はGo言語を実践的に使うためのTipsがいろいろまとめられていて非常に良かった。Go言語のA Tour of Goをやったけど、次にどうすればよいかわからない、具体的にGoの良い書き方が分からないという人が読むと非常に勉強になりそう。

僕はまだGoをそこまで書いていないので、「第1章Goによるチーム開発のはじめ方とコードを書く上での心得」と「第4章コマンドラインツールを作る」が非常に参考になった。例えば以下のようなものが参考になった(数字はKindleのロケーション番号)。

  • Goプロジェクトのプロジェクト構成やディレクトリ構成 519, 2389
    • バイナリをメインの成果物とする場合と、ライブラリをメインの成果物にする場合の二種類
    • 第1章、第4章両方で言及されている
  • 依存管理をするためのglideというツールの使い方 563
  • Makefileの具体例 609
  • Goらしいコード 656
    • panicを使わず、errorをちゃんと返す
    • 正規表現を避けてstringsを使う
    • mapではなくちゃんとstructを定義する
  • Build Constraintsやビルドタグを使って、ビルド対象のソースコードを切り替える手法 776
  • CLIを作るためのライブラリの使い方
    • flagパッケージ 2432
    • mitchellh/cliの使い方 2771
  • main関数に書くような主な処理はRun関数を定義してそこに記述し、Run関数はExitを呼ぶのではなく、そのステータスコードを返す 2926
  • CLIステータスコードのテスト手法 3016
  • CLIの出力メッセージのテスト手法 3034


この本を読んで、なんとなくGo言語に対する素地が出来たように思うので、次は 「みんなのGo言語」が良かったので、自分のためだけのCLIツールを作ってみた - えいのうにっき を見習って、簡単なCLIツールを作って学習を進めていきたい。

読書ノート

1章 Goによるチーム開発のはじめ方とコードを書く上での心得

  • goimportsを高速化するためのdragon-imports 407
  • Goはディレクトリ単位でパッケージが切られるため、mainパッケージを除き、ディレクトリ名はソースコード内に記述されるパッケージ名と同一であることが強く推奨されている 512 ※
  • Goプロジェクトのディレクトリ構成の代表的な一例 519
    • トップレベルはGoのパッケージとして利用される
    • cmd/以下にバイナリビルド用のmainパッケージが配置される
    • サブパッケージが必要な場合はディレクトリを掘る
    • Makefileをビルドのほかタスクランナー的に使う
    • 他の構成が2371にもある
  • ファイル分割は任意で良いが、typeを定義する場合、typeごとに1つのファイルに切り出すという良いプラクティスがある 527
  • 依存管理ができるglide 563 ※
    • govendorというツールもある
  • Makefileの参考になる例 609
  • Goらしいコード 656
    • panicを使わず、errorをちゃんと返す
    • 正規表現を避けてstringsを使う
    • mapではなくちゃんとstructを定義する
  • goのbuildの方法 776
  • Build Constraintsやビルドタグを使って、ビルド対象のソースコードを切り替える手法 776

基本として押さえておきたい

2章 マルチプラットフォームで動作する社内ツールのつくり方

  • runtime.GOOSに実行されるOS名が格納されている 1130
  • Build Constraintsの指定方法いろいろ 1142
    • ファイル名による指定
    • +buildコメントによる指定

3章実用的なアプリケーションを作るために

  • バージョン番号をバイナリに埋め込む 1534 ※
    • go build -ldflags
  • goでのIOのバッファリング 1602
  • 複数の出力先に一気に書き込む 1798
  • Goからos/execパッケージを利用して外部コマンドを実行する 1961
  • タイムアウトの実装 2048
  • シグナルを扱う 2109
  • goroutineを外部から停止する 2200

ツールを作る時にこれどうやるんだろうと迷った時に参考になりそう

4章コマンドラインツールを作る

かなり参考になった。とりあえずGoを学ぼうとすると簡単なCLIを作ってクロスビルドしてみるのが手っ取り早い気がするので、この章は参考になる。

  • リポジトリ構成 2389 ※
    • バイナリをメインの成果物とする場合
    • ライブラリをメインの成果物とする場合
  • flagパッケージ 2432
  • mitchellh/cliの使い方 2771
  • main関数に書くような主な処理はRun関数を定義してそこに記述し、Run関数はExitを呼ぶのではなく、そのステータスコードを返す 2926 ※
    • mainではRunを起動してos.Exitするだけ
    • os.Exitが呼ばれて後処理が行われないなどの問題が避けられる
    • テストもしやすい
  • ステータスコードのテスト 3016
  • 出力メッセージのテスト 3034

5章 The Dark Arts Of Reflection

動的に型情報を使うためのTips。必要になったらちゃんと読む。

6章 Goのテストに関するツールセット

  • testingパッケージ入門 3746
  • ベンチマーク入門 3853
  • reflect.DeepEqual 3973
  • テストにおける変数または手続きの置き換え 4105
    • モックの手法

A Tour of Goをやって演習問題を解いてみた

Goをやることになったので、とりあえずA Tour of Goをやって、演習問題を解いてみた。直近ではgoroutineは使わなそうだったので、ひとまずそれ以外の演習問題をした。

回答は https://github.com/shibayu36/golang-playground/tree/master/go-tour に置いている。

Exercise: Loops and Functions

for文の使い方を学べる。for文の初期化ステートメントと条件式を使えば解ける。

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	for diff := 1.0; math.Abs(diff) > 1e-10; {
		diff = ((z * z) - x) / (2 * z)
		z = z - diff
	}
	return z
}

func main() {
	fmt.Println(Sqrt(3))
	fmt.Println(math.Sqrt(3))
}

Exercise: Slices

  • rangeをうまく使うと綺麗にかけた
  • 多次元配列だと、ループでmakeしなければならない
  • uint8()でキャストしないといけない
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	pic := make([][]uint8, dy)

	for y := range pic {
		pic[y] = make([]uint8, dx)
		for x := range pic[y] {
			pic[y][x] = uint8((x + y) / 2)
		}
	}

	return pic
}

func main() {
	pic.Show(Pic)
}

Exercise: Maps

  • string.Fieldsを使ってワード分割
  • mapの値を増やしていくだけで良い
package main

import (
	"strings"
	"golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
	words := strings.Fields(s)
	wordToCount := make(map[string]int)
	for _, word := range words {
		wordToCount[word]++
	}
	return wordToCount
}

func main() {
	wc.Test(WordCount)
}

Exercise: Fibonacci closure

クロージャで変数を二つ持っておくと綺麗に書ける。

package main

import "fmt"

func fibonacci() func() int {
	a1, a2 := 0, 1
	return func() int {
		ret := a1
		a1, a2 = a2, a1 + a2
		return ret
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

Exercise: Stringers

typeにメソッドを生やす練習

package main

import "fmt"

type IPAddr [4]byte

func (ipAddr IPAddr) String() string {
	return fmt.Sprintf("%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}

Exercise: Errors

Errorメソッドを生やすだけで簡単に独自エラーを定義できる。

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}

	z := 1.0
	for diff := 1.0; math.Abs(diff) > 1e-10; {
		diff = ((z * z) - x) / (2 * z)
		z = z - diff
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

Exercise: Readers

インターフェース定義に合わせて実装する練習。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (r MyReader) Read(buf []byte) (int, error) {
	for i := range buf {
		buf[i] = 'A'
	}
	return len(buf), nil
}

func main() {
	reader.Validate(MyReader{})
}

Exercise: rot13Reader

非公開メソッドで変換を書きつつ、Readを実装。

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader *rot13Reader) Read(buf []byte) (int, error) {
	n, err := reader.r.Read(buf)
	if err != nil {
		return 0, err
	}

	for i := 0; i < n; i++ {
		buf[i] = reader.rot13(buf[i])
	}
	return n, nil
}

func (reader *rot13Reader) rot13(b byte) (byte) {
	if ('a' <= b && b <= 'm') || ('A' <= b && b <= 'M') {
		return b + 13
	}
	if ('n' <= b && b <= 'z') || ('N' <= b && b <= 'Z') {
		return b - 13
	}
	return b
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

Exercise: Images

自分でImage typeを定義しつつ、インターフェース仕様を満たすように実装。

package main

import (
	"image"
	"image/color"

	"golang.org/x/tour/pic"
)

type Image struct {
	width  int
	height int
}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.width, i.height)
}

func (i Image) At(x, y int) color.Color {
	v := uint8((x + y) / 2)
	return color.RGBA{v, v, 255, 255}
}

func main() {
	m := Image{256, 256}
	pic.ShowImage(m)
}

まとめ

今回はA Tour of Goをして演習問題を解いてみたので、その回答を載せてみた。続いてみんなのGo言語を読んでいきたいと思う。