Raccoon Tech Blog [株式会社ラクーン 技術戦略部ブログ]

株式会社ラクーン 技術戦略部より、tipsやノウハウなど技術的な話題を発信いたします。

2016年03月

ローカルUnboundによる名前解決の冗長化の検証


こんにちは!はんだです。
今日はDNSサーバの可用性向上についてのエントリです。

DNSサーバの障害時の影響を防ぐ方法として、
単純にDNSサーバを複数設置して/etc/resolv.confに記入するという方法がありますが、
その方法には、タイムアウトの待ち時間という落とし穴があります。

/etc/resolv.conf
nameserver 192.168.0.11  //DNS1のIPアドレス
nameserver 192.168.0.12  //DNS2のIPアドレス

のように設定したとして、
1行目のDNS1が落ちた状態の場合、2行目のDNS2に移行するまでに、設定次第ですが問い合わせの度に毎回1~数秒のタイムアウトが発生します。名前解決の頻度が高いサービスの場合、この僅かな待ち時間がサービス全体へのパフォーマンスに影響してきます(この問題については[24時間365日]サーバ/インフラを支える技術(技術評論社)の 「DNSサーバの冗長化」の項が詳しいです)。

この問題への対策として、LVS+Keepalivedなどを利用して冗長性を高める方法がありますが、それに加え

複数のDNSサーバをローカルのUnboundの参照先として指定することで冗長性を高める

という方法を検証してみました。

(なお、今回の記事ではLVS構成の設定及び検証については言及しませんので、その部分については他の解説記事などをご参照下さい)

検証環境

検証用の環境として以下を準備しました。
sv-web01       192.168.0.1       Webサーバ。Unboundを導入
sv-dns-lvs01     192.168.0.11       node01とnode02でLVS。Keepalivedで死活監視
  sv-dns-node01  192.168.0.101     BIND
  sv-dns-node02  192.168.0.102     BIND
sv-dns-lvs02     192.168.0.12       node03とnode04でLVS。Keepalivedで死活監視
  sv-dns-node03  192.168.0.103     BIND
  sv-dns-node04  192.168.0.104     BIND

Unbound



sv-web01にはUnboundをインストールし、/etc/resolv.confには以下のようにnameserverを設定します。
nameserver 127.0.0.1       //ローカルのUnbound
nameserver 192.168.0.11    //sv-dns-lvs01
nameserver 192.168.0.12    //sv-dns-lvs02

Unboundの設定

Unboundとは、
  • シンプルな設計になっており高速で動作する。BINDより処理性能が良い(2~3倍)
  • 設定ファイルは1つのみ(unbound.conf)で内容もシンプルなため、設定が容易
  • キャッシュ汚染に対する耐性を強化した設計&DNSSECをサポート
  • デフォルト設定で高いセキュリティが確保された状態で動く
という特徴を持った、新定番となっているDNSキャッシュサーバです。

デフォルトの設定でもキャッシュサーバとして問題なく動作しますが、今回は社内の権威DNSサーバへ問い合わせを行いたいため、stub-zoneの設定を行います。
stub-zoneは、特定のドメインの名前解決について、指定したDNS権威サーバに問い合わせを行うための設定です。

/etc/unbound/unbound.confで、参照先DNSサーバとしてsv-dns-lvs01とsv-dns-lvs02を指定します。
 stub-zone:
        name:"example.com"
        stub-addr:192.168.0.11
        stub-addr:192.168.0.12
stub-zone:
        name: "0.168.192.in-addr.arpa"
        stub-addr:192.168.0.11
        stub-addr:192.168.0.12
Unboundはデフォルトではプライベートアドレスの問い合わせを行わないので、以下の設定を追加します。
private-domain: "example.com"
(中略)
local-zone: "0.168.192.in-addr.arpa" transparent

また、特定のドメインについて、指定したDNSキャッシュサーバに再帰問い合わせを行わせるときにはforward-zoneを設定します。
forward-zoneは、nameオプションに "." を指定することで、全ての問い合わせを転送することもできます。
自社ドメイン以外は別のDNSサーバ(外部DNSなど)を指定して直接問い合わせを行わせるような場合は、こちらで設定します。
forward-zone:
  name: "."
  forward-addr: 203.0.113.111

検証したいこと 

1. stub-zoneに複数DNSサーバを記述した場合、どのような問い合わせ順序になるか。先に書かれているものが優先なのか、ラウンドロビンなのか。

2. stub-zoneに書いたDNSサーバのうち1つが利用不可になった場合に、どのような挙動になるか。

3. 利用不可になった理由が、サーバ自体がダウンした場合と、DNSサービスが利用不可になった場合では、挙動に違いはあるか。resolv.confでは、サーバ自体のダウンではなくDNSサービスが落ちていた場合にタイムアウトまでに長くかかることがあるが、Unboundではどうか。

検証手順

0.事前準備
テスト用のスクリプトを作成します。sv-web01から他のサーバにnslookupを行い、最後にunboundのキャッシュを削除するというだけの単純なものです。
SERVER_LIST="sv-web02 sv-web03 sv-db01 ・・・・・ sv-db20"
while : ; do

    for SERVER in $SERVER_LIST; do
        echo $(date "+%F %H:%M:%S.%N") $(nslookup $SERVER | head -n 1)
    done

    unbound-control -q flush_zone example.com
    sleep 1

done | tee test.log
このスクリプトを実行しながらサーバやサービスを落として、どのサーバで名前解決が行われているかを確認します。
DNSサーバに何の問題も起こっていない時点では以下のようなログになります。
127.0.0.1、つまりローカルのUnboundで名前解決が行われているのがわかります。
test.log

2016-03-23 10:01:47.710318602 Server: 127.0.0.1
2016-03-23 10:01:47.720166753 Server: 127.0.0.1
2016-03-23 10:01:47.730168114 Server: 127.0.0.1
2016-03-23 10:01:47.739930041 Server: 127.0.0.1
2016-03-23 10:01:47.750394444 Server: 127.0.0.1
2016-03-23 10:01:47.760969879 Server: 127.0.0.1
2016-03-23 10:01:47.770984202 Server: 127.0.0.1
2016-03-23 10:01:47.780918869 Server: 127.0.0.1
2016-03-23 10:01:47.791561248 Server: 127.0.0.1
2016-03-23 10:01:47.802000414 Server: 127.0.0.1
2016-03-23 10:01:47.812778410 Server: 127.0.0.1
2016-03-23 10:01:47.823340386 Server: 127.0.0.1
2016-03-23 10:01:47.833941263 Server: 127.0.0.1
2016-03-23 10:01:47.844795349 Server: 127.0.0.1



検証1. 
問い合わせ順序の確認

各LVSサーバでDNSサービスへの接続状態を確認しながら検証用スクリプトを実行してみます。
[root@i-dns-lvs01 ~]# watch ipvsadm -Ln

IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP 192.168.0.11:53 rr
-> 192.168.0.101    Route     0 0 271 
-> 192.168.0.102     Route    0 0 272
[root@i-dns-lvs02 ~]# watch ipvsadm -Ln

IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP 192.168.0.12:53 rr
-> 192.168.0.103    Route     0 0 269 
-> 192.168.0.104     Route    0 0 271 

InActConn(一定時間以内の接続数)がほぼおなじ数字で推移しています。
結論:stub-zoneに複数のDNSサーバを記入した場合、順番に均等に問い合わせが割り振られる 。


検証2.ローカルのUnboundのstub-addrに登録してあるsv-dns-lvs0xのどちらかが落ちた場合


上記スクリプトを実行した状態で、片方のLVS(sv-dns-lvs01)を落としてみます。
[root@sv-dns-lvs01 ~]# service network stop
2016-03-23 11:02:39.654407380 Server: 127.0.0.1
2016-03-23 11:02:39.666387084 Server: 127.0.0.1
2016-03-23 11:02:39.678305469 Server: 127.0.0.1
2016-03-23 11:02:39.690497052 Server: 127.0.0.1
2016-03-23 11:02:39.702769957 Server: 127.0.0.1
2016-03-23 11:02:39.714040967 Server: 127.0.0.1
2016-03-23 11:02:39.725401558 Server: 127.0.0.1
2016-03-23 11:02:39.736123310 Server: 127.0.0.1
2016-03-23 11:02:39.746930229 Server: 127.0.0.1
2016-03-23 11:02:39.758402234 Server: 127.0.0.1
2016-03-23 11:02:39.769120682 Server: 127.0.0.1
2016-03-23 11:02:39.781072673 Server: 127.0.0.1
2016-03-23 11:02:41.296289719 Server: 127.0.0.1
2016-03-23 11:02:41.308759832 Server: 127.0.0.1
2016-03-23 11:02:41.422097623 Server: 127.0.0.1
2016-03-23 11:02:41.434119357 Server: 127.0.0.1
2016-03-23 11:02:42.849200158 Server: 127.0.0.1
2016-03-23 11:02:42.861381999 Server: 127.0.0.1
2016-03-23 11:02:42.873155039 Server: 127.0.0.1
2016-03-23 11:02:42.884901850 Server: 127.0.0.1
2016-03-23 11:02:42.896203445 Server: 127.0.0.1
2016-03-23 11:02:42.906861984 Server: 127.0.0.1
2016-03-23 11:02:42.917331042 Server: 127.0.0.1
2016-03-23 11:02:42.927999125 Server: 127.0.0.1
2016-03-23 11:02:42.938445488 Server: 127.0.0.1
2016-03-23 11:02:42.948876159 Server: 127.0.0.1
2016-03-23 11:02:42.959507049 Server: 127.0.0.1
2016-03-23 11:02:42.970042488 Server: 127.0.0.1

LVSサーバを落としたタイミングで若干処理スピードの低減が発生しましたが、ほぼタイムラグなくsv-dns-lvs01を切り離して引き続きUnboundで名前解決を行ってくれるようです。

結論:stub-addrとして指定した複数のDNSのうち一つが落ちた場合でも、Unbound内で切り離しをして名前解決が続行される。



検証3. sv-dns-lvs0x自体が落ちている場合と、sv-dns-lvs0xにぶらさがっている2つのnodeの両方のDNSサービスが利用不可の場合とで挙動は変わるか


LVS用のサーバが落ちた場合、Unboundがうまく切り離してくれることがわかりました。
では、LVSサーバではなく、node側のDNSサービスが両方落ちた場合はどうでしょうか。

sv-dns-lvs01で利用している、sv-dns-node01とsv-dns-node02の両方で、namedサービスを停止してみます。
[root@sv-dns-node01 ~]# service named stop 
[root@sv-dns-lvs01 ~]# ipvsadm -Ln 

IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP 192.168.0.11:53 rr
-> 192.168.0.101    Route     0 0 0 
-> 192.168.0.102     Route    0 0 0 
2016-03-23 11:26:07.253176732 Server: 127.0.0.1
2016-03-23 11:26:07.263412675 Server: 127.0.0.1
2016-03-23 11:26:07.375477267 Server: 127.0.0.1
2016-03-23 11:26:07.385581955 Server: 127.0.0.1
2016-03-23 11:26:07.395923909 Server: 127.0.0.1
2016-03-23 11:26:07.507452435 Server: 127.0.0.1
2016-03-23 11:26:09.022468808 Server: 127.0.0.1
2016-03-23 11:26:09.635581100 Server: 127.0.0.1
2016-03-23 11:26:09.646748991 Server: 127.0.0.1
2016-03-23 11:26:10.712805036 Server: 127.0.0.1
2016-03-23 11:26:10.725051359 Server: 127.0.0.1
2016-03-23 11:26:10.734853913 Server: 127.0.0.1
2016-03-23 11:26:10.744873105 Server: 127.0.0.1
2016-03-23 11:26:10.754697333 Server: 127.0.0.1
2016-03-23 11:26:10.764829874 Server: 127.0.0.1
2016-03-23 11:26:10.877325395 Server: 127.0.0.1
2016-03-23 11:26:10.887888348 Server: 127.0.0.1
2016-03-23 11:26:10.898024415 Server: 127.0.0.1
2016-03-23 11:26:10.907979399 Server: 127.0.0.1
検証2と同様、若干の処理速度低下はありますが、その後は引き続きUnboundでの名前解決が継続されました。

結論:LVS自体が落ちている場合とサービスが使用不可の場合で挙動は変わらない

まとめ 

検証により、DNSサーバ障害時のパフォーマンスへの影響をローカルUnboundを導入することによりより減らすことができるだろうという結論が得られました。
ここで一度、Unboundを導入することのメリット・デメリットを整理したいと思います。

ローカルにUnboundを導入するメリット
・サーバ障害時も、キャッシュに保存されている分はTTL内(社内サーバなら1時間)であればローカルで名前解決ができる。
・DNSサーバを冗長化しておいた場合に、1台のみの障害であればresolv.confに列記するだけの場合よりもパフォーマンスへの影響を小さくできる。
・ローカルのネットワーク流量やDNSサーバの負荷を抑制できる。
・ローカルで名前解決ができることによるパフォーマンス向上が期待できる。

ローカルにUnboundを導入するデメリット(懸念事項)
・導入コスト(各サーバへの設定)
・運用コスト(local-dataを使う場合はサーバ追加時のlocal-dataの再配布、ドメイン&セグメント追加時のunbound.conf設定修正)
・キャッシュが新しいトラブルの原因になったり、トラブル時の切り分けがしづらくなる可能性
・サーバ負荷軽減やパフォーマンス向上については、名前解決の問い合わせが少ないサーバや、新しい名前解決が多いサーバの場合には、効果が小さい。

これを踏まえ、弊社では、
・TTL内での名前解決が多く発生し、キャッシュにより問い合わせが大きく減らせる
・DNS障害時のタイムラグがサービスのパフォーマンスに影響を与えることが想定できる
など、Unbound導入によるメリットが大きいサーバから順に導入していくことになりました。
また、導入・運用のコストについては、AnsibleやFabricといった自動化ツールを導入することである程度カバーできると考えています。

参考URL

・5分でわかるUnbound http://gihyo.jp/admin/feature/01/unbound/0001
・Unboundサーバ運用Tips http://gihyo.jp/admin/feature/01/unbound/0004
・キャッシュサーバの設定 http://dnsops.jp/event/20140626/cache-config.pdf

Zipファイルを紙とペンで解凍してみた

こんにちは。ほそかわです。

社内勉強会用に、埋めていくだけでzipファイルの解凍ができるプリントを作りました。

普段、zipファイルの解凍はコンピュータにやってもらっていますが、
この記事を読みながらご自身の手を使って紙とペンでやってみましょう。

アルゴリズムとデータ構造を生で感じるチャンスです。

準備

これから使うプリントをダウンロードして印刷しましょう。

「ZIPローカルファイルヘッダ」、
「ZIPセントラルディレクトリファイルヘッダ」、
「ZIPセントラルディレクトリ終端レコード」

記入してZIPの解凍を進めていくプリントです。

「ZIPファイルデータ」
この記事で解凍を行うzipファイルのバイナリデータです。
左側に書いてあるのは16進数で表されたバイナリデータです。主にこちらを使います。
16進数で書かれている行名と列名を足すと何byte目のアドレスにあるデータかが分かります。
右側に書いてあるのは文字列データが簡単に調べられるASCIIで表されたバイナリデータです。

「16進数表」
計算の手間を省くためのものです。

全て印刷して筆記用具も用意できたら準備はOKです。

今回用意したZIPファイルデータは、データ圧縮が行われていない特殊なzipファイルになってます。
圧縮を行わないzipファイルに意味なんてないと感じるかもしれませんが、圧縮に関する仕様は独立しているのでこの記事を読んだ後に別に勉強できます。

無圧縮のzipファイルはzipコマンドでも作れます。

zip -0 file.zip file.txt

プロトコルのバージョンによって違いがあるかもしれないのでこの記事ではこちらで用意したファイルを使います。

概要

Zipファイルの中にはどんなデータが入っているのか見てみましょう。

ファイル全体の概要を図にしました。

zip_overview_multi_file
Zipファイルはヘッダとファイルエントリ、終端レコードで構成されています。

ヘッダには2種類あります。

  • ZIPローカルファイルヘッダ
  • ZIPセントラルディレクトリファイルヘッダ

ZIPセントラルディレクトリファイルヘッダは、ZIPファイルの最後にあるZIPセントラルディレクトリという領域に入っています。そして、ZIPセントラルディレクトリの一番最後にアーカイブ全体についての情報が書いてある終端レコードが付いていてます。

ヘッダにはzipファイルに格納したファイルについてファイル名などの情報が書いてあり、ファイルエントリには格納したファイルの中身が入っています。

図のzipファイルにはA,Bの2つのファイルが格納されています。2種類のヘッダとファイルエントリがファイル数だけ繰り返されています。

ネタバレになってしまいますが繰り返しがあると手間が増えるばかりなので、今回使うzipファイルに格納されているファイルは一つです。

バイナリデータ

Zipファイルは普段扱っているテキストデータよりもコンピュータ向けなバイナリデータとして保存されています。

バイナリデータを扱う為に、16進数、2進数の変換方法とエンディアンについて知っておきましょう。

基礎的な知識なのでWikipediaをみてください。

2進法

16進数

エンディアン

エンディアンは様々なことが書いてありますが、zipファイルを扱うのにはリトルエンディアンとビックエンディアンのデータが読めるようになっていれば十分です。

リトルエンディアンでは1byteをかたまりとして並び順を逆にします。

AB CD EF 12

であれば

12 EF CD AB

となります。

例えば、16進数で1B58という数値をリトルエンディアンで表すと

58 1B

となります。

ビックエンディアンは並び順を変えないもので、

AB CD EF 12

であれば、そのまま

AB CD EF 12

となります。

ZIPセントラルディレクトリ終端レコード

これから紙とペンを使ってzipを解凍していきます。

まずは、アーカイブ全体の情報を得るために、セントラルディレクトリの終端レコードに記録してある情報を調べましょう。

使うプリントは、「ZIPセントラルディレクトリ終端レコード」です。

概要で説明したように、終端レコードはzipファイルの最後の領域にあります。

ZIPの仕様として終端レコードの頭には、

50 4B 05 06

というシグネチャをつけることになっています。

終端レコードはファイルの末尾にあるので末尾から開始地点を表すシグネチャを探すことで、終端レコードの領域を知ることができます。
zipファイルデータの末尾からシグネチャを探しましょう。

アドレス0x5Cに見つかりました。

そこからセントラルディレクトリヘッダの終端レコードが始まってます。
次のアドレスから順番にプリントに書き込んでいくと終端レコードの解凍を進められます。

四角い枠一つが1byteに対応してます。

入力欄解説
中には1byteのデータを書き込みます。

シグネチャは例としてリトルエンディアンで埋めてあります。

次の手順で全て埋めてみてください。

  1.  16進数で読み、数値データはリトルエンディアンで、(文字列)と書いてある文字列のデータはビックエンディアンで四角い枠の欄に記入する。

  2.  数値データは、16進数のデータを10進数に直して下線が引いてある欄に記入する。文字列のデータは1byteごとにASCII文字に変換してある右に表の同じ位置の文字を探して記入する。

(厳密には、文字列のデータもリトルエンディアンになっています。並び替えを行わないビックエンディアンとして扱えている理由は、1文字の1byte単位で保存が行われているからです。リトルエンディアンは1byteをかたまりとして並び替えるものなので、1byteのデータでは並び替えが行われず、リトルエンディアンになる並び替えが行われてないように見えています)

最初なので答えを載せます。

ZIPセントラルディレクトリ終端レコード_答え

全ての記入が終わったら細かく見て行きます。

セントラルディレクトリレコードの合計数

セントラルディレクトリレコードの数はファイルの数と同じになっています。
このzipファイルに含まれるファイルは一つだけだと分かります。

セントラルディレクトリの開始位置のオフセット、セントラルディレクトリのサイズについて

セントラルディレクトリの開始位置のオフセットからは、ファイルの先頭から見てどのアドレスでセントラルディレクトリが始まるのか、セントラルディレクトリのサイズからはどこまで続いているのかが分かります。
セントラルディレクトリは、先頭から39byte目のアドレス0x27で始まって53byte後のアドレス0x5Bで終わっているということがわかります。

Zipファイルコメントと長さについて

このzipファイルにはコメントがないため長さには0が入っています。
コメントは可変長のデータなのでここで長さを定義してそのバイト数だけコメントとして扱うという作りになってます。

その他の項目については、今では使われない機能が多いので割愛します。
興味があったら調べてみてください。

ZIPセントラルディレクトリファイルヘッダ

次は、セントラルディレクトリファイルヘッダです。
使うプリントは、「ZIPセントラルディレクトリファイルヘッダ」です。

終端レコードの情報から、セントラルディレクトリはアドレス0x27で始まっているとわかりました。
セントラルディレクトリファイルヘッダのシグネチャは、

50 4B 01 02

です。

探してみると、終端レコードで書いてあったようにアドレス0x27にあります。

終端レコードの時と同じようにシグネチャの次のバイトからプリントを埋めていきます。

zipでは、日付、時刻のデータがバイト単位で分けられてません。
2byteの領域に3つのデータが入っているので、
一度、2byteを繋げた2進数に変換してから区切り直して、10進数に変換します。

次の手順で行います。

  1. リトルエンディアンで2byte分を2進数に変換して一桁ずつ下線の上に書く
  2. 縦線で区切られているところで分けて2進数から10進数変換して、次の行に書き込む
  3. 秒は2倍、年は1980を足す

このやり方も初めてなので答えを載せておきます。

日時入力欄解説

これでファイルの最終変更日時がわかります。

これも全ての記入が終わったら細かく見ていきます。

圧縮メソッド
ファイルエントリがどのプロトコルで格納されたかが書いてあります。
今回は無圧縮なので0が入っていますが8のDeflateが一般的です。

Wikipedia - Deflate

ファイルの最終変更時刻、最終変更日付
格納したファイルの最終変更日時です。
年は1980の下駄を履かせるので1980年からが使えます。
そして、なんと、秒はビットが足らず2秒刻みで丸められているので2倍します。

zipで圧縮を行ったファイルは最終変更日時の秒が保障されないことをご存知だったでしょうか。いつも使っているものでも意外な発見があるものなんですね。

CRC-32
格納前のファイルエントリで計算したCRC-32が入っています。

圧縮サイズ
格納後のサイズです。ファイルエントリのサイズが書いてあります。

非圧縮サイズ
格納前のファイルサイズです。

ファイル名の長さ、ファイル名
Zipファイルコメントと同じようにまず長さが定義されていて、その長さ分のファイル名データがあります。

ローカルファイルヘッダの相対オフセット
ファイル一個につき、セントラルディレクトリファイルヘッダとローカルファイルヘッダが一つずつあります。

対応するローカルファイルヘッダのアドレスが書いてあります。
最初のファイルのローカルファイルヘッダのアドレスなので0が入っています。

数が多いので他は割愛します。
調べてみてください。

ZIPローカルファイルヘッダとファイルエントリ

ローカルファイルヘッダはZIPセントラルディレクトリファイルヘッダと同じやり方で埋めていけます。
ローカルファイルヘッダの次のアドレスからファイルサイズのバイト数分だけがファイルエントリと呼ばれるファイルの中身のデータになってます。

圧縮が行われている場合には、ヘッダから得た圧縮プロトコル情報をもとにファイルエントリのデコードを行います。今回は無圧縮なので飛ばせます。

ファイルエントリをこれまでヘッダから得た情報をもとにファイルとして保存すればこのファイルは解凍されます。

今回は、

  • ファイルの最終変更日は「2016年1月26日 13時27分36秒」
  • ファイル名は「raccoon」
  • ファイルの中身は「CA FE」

というファイルを作り、ファイルエントリをそのまま中に書き込めばこのzipファイルの解凍は成功です。

「CA FE」とは16進数で使われるA~Fまでの文字という制約で考えた単語「Cafe」です。

最後に

お疲れ様でした!

Zipファイルが身近に感じられるようになったのではないでしょうか。

この記事は最短ルートでzipファイル概要を理解し解凍を体験できることを目標に書いたので多くの仕様を省略しています。
次のステップとして、圧縮アルゴリズムを勉強すると圧縮が行われている普通のzipファイルも解凍できるようになります。他にもパスワード付きや分割など多くの仕様があります。そして、反対のことを行えば圧縮もできます。

ZIPファイルフォーマットは様々なところで使われています。ZIPの知識を生かすことで仕事の幅が広がるかもしれません。このプリントを使って勉強会を開催するのも面白いでしょう。
これからの活躍につながるといいですね。

参考

wikipedia - ZIP

PK-Ware - APPNOTE

記事検索