<前の日記(2006年07月08日) 次の日記(2006年07月17日)> 最新 編集

高木浩光@自宅の日記

目次 はじめに 連絡先:blog@takagi-hiromitsu.jp
訪問者数 本日: 2612   昨日: 2371

2006年07月16日

Winny稼動コンピュータ数調査の追試をしてみた

Winnyネットワークの規模(同時稼動ノード数)の推計については、古くは3年前のネットアーク社の松本氏によるもの、そしてネットエージェント社の杉浦氏によるもの、さらに最近ではeEye Digital Security社の鵜飼氏らによるものがある。

  • ネットアーク、P2Pノードの自動探索システムを稼働, INTERNET Watch, 2003年7月14日

    ネットアークは14日、P2Pノードの自動探索システム「P2P FINDER」を稼働開始したと発表した。WinMXとWinnyの国内ノードが対象となっており、6月3日から7月14日までに20万5,597ノードが発見されたとしている。

  • ネットアーク、8月1日時点でのWinnyとWinMXの利用者数は56万ノード, INTERNET Watch, 2003年8月4日

    株式会社ネットアークは、同社のP2Pノードの自動探索システム「P2P FINDER」の稼動状況を発表し、8月1日時点で「Winny」と「WinMX」の利用者数が561,459ノードに達したと報告した。

    (略)比率としてはWinMXとWinnyが3対1程度だとしている。

    ネットアークは6月16日に「P2Pネットワーク実態調査2003」を発表しており、その時点ではWinMXが3万2,882ノード、Winnyが3万 2,496ノードで、合計6万5,378ノードだった。わずか1カ月で3倍以上に増加したように見えるが、そうではないようだ。今回稼働を開始したP2P FINDERは、前回の調査に利用したシステムをチューニングしたものにあたり、性能向上によって発見ノードが大幅に増加したという。つまり、「潜在する P2Pノード数は膨大にあり、それをすべて調べきれていない」(松本直人代表取締役)のが実状らしい。

  • 年末年始はWinny接続数増加、ネットエージェントが情報流出調査サービス, INTERNET Watch, 2005年12月26日

    同社がWinny検知システムを開発した当初、2004年5月時点の接続数は27万5,133ノード(ネットエージェント調べ)だった。2005年12月になってもWinnyネットワークには約30万ノードが接続しており、Winny経由の情報流出が相次いでいるにも関わらず、利用者は減少していない状況だ。

  • Winnyのノード数に減少傾向なし、ネットエージェントが調査, INTERNET Watch, 2006年4月25日

    ネットエージェントは25日、P2P型ファイル共有ソフト「Winny」のノード数を発表した。4月10日から23日にかけて独自のWinny検知システムを使って調査したもの。平日で44万〜49万、土日だと50万〜53万以上のノード数を観測したという。

    (略)ネットエージェントでは同社独自の検知システムを11台稼働することで、平日で延べ約350万台のノード情報を取得。IPアドレスなどで重複分を削除してユニークノード数を確定しているという。

  • 「ウイルスの登場は時間の問題、Winnyの使用は今すぐ中止してほしい」〜Winnyの脆弱性を発見した米eEyeの鵜飼裕司氏, INTERNET Watch, 2006年5月25日

    鵜飼氏らは、Winnyのプロトコルや挙動を解析し、Winnyネットワークに参加しているユーザーの一覧を数時間で取得できるシステムも開発した。現在、常時数十万のユーザーがWinnyネットワークに参加していることが観測されており、潜在的には100万人程度のユーザーが存在するのではないかと推測している。

今や、金子勇氏の著書「Winnyの技術」(アスキー)や、日経NETWORK誌の記事「Winnyの通信解読に挑戦!」などによって、Winnyプロトコルの仕様は公知となっており、こうした調査は平均的なプログラマならば誰でも可能な状況にあるといえる。私も、これらの既存の調査の確かさを確認するため、追試を行ってみた。

使用した機材及び環境は以下である。

  • 古いノートPC 1台
    • CPU: Pentium III 900MHz-M
    • メインメモリ: 512MB
  • Windows XP Professional SP1
  • Java Runtime Environment Version 5.0 Update 7
  • 実効最大スループット 7 Mbps 程度の ADSL回線(100BASE-TXで接続) 1本
  • データ記録用LAN接続ハードディスク装置(無線LAN 802.11bで接続) 1台

調査に使用したプログラムのアルゴリズムを図2に示す。指定されたノード(ホスト名とポート番号の組)に対してTCP/IPで接続し、Winnyプロトコルにしたがってコマンド10(「拡散クエリ送信要求」)を一個送信しながら、接続先からのコマンドを受信する。受信したコマンド4および13から他のノードについての情報を抽出し、新たに見つかったノードを優先度付きタスク待ち行列に投入して、スレッド数800のスレッドプールを用いて同時並行的にそれらについて同じ処理を繰り返す。優先度は、そのノード情報の作成時刻の新しいものを優先するものとした。タスク待ち行列が空になり、処理中のタスクが終了すると停止する*1。接続については、接続不能エラー発生時は1回で中止するものとし、正常接続時には30秒以上経過すると切断するものとした。

実行中にWindowsのタスクマネージャで観測したところによると、CPUの使用率は 60% 〜 80% 程度、インターネット側ネットワークの通信使用速度は最大 2 Mbps 程度、データ記録側ネットワークは平均 0.8 Mbps程度と、いずれもボトルネックとはなっていないようだった。

このときの実験結果を図1に示す。モニター出力をグラフ化したもので、横軸は経過時間(秒)、縦軸はノード数、赤色(上)のプロットはその時刻における見つかったノード総数(重複除く)であり、緑色(真ん中)は接続を試み終えた数、青色(下)は接続が正常に完了(コマンド97、0、1、2、3のハンドシェイクシーケンスを正常に完了)し終えた数である。

図1: 実行結果(開始時刻 2006年7月16日日曜日午前10時)

このように、3時間20分ほどで39万ノードを発見しそのすべてに接続を試行した。そのうち、57 % のノードが接続に成功した。1秒当たり平均 32.5ノードに対して接続試行できたことになる。

以下はモニター出力の冒頭の部分と最後の部分である。

      0;       0,      0,      0,      0,   NaN%,   NaN%;       0;     2;    NaN,   NaN
     10;    2492,     33,      0,      0,   0.0%,   0.0%;    1659;   802;  3.500, 0.000
     20;   16565,     90,     34,     33,  37.8%,  36.7%;   15676;   802;  4.500, 1.700
     30;   19826,    203,     58,     57,  28.6%,  28.1%;   18823;   802;  6.767, 1.933
     40;   24152,    736,    556,    525,  75.5%,  71.3%;   22797;   802;  18.425, 13.925
     50;   30748,    910,    686,    649,  75.4%,  71.3%;   29038;   802;  18.200, 13.720
     60;   34058,   1086,    794,    757,  73.1%,  69.7%;   32177;   802;  18.100, 13.233
     70;   38849,   1424,   1051,   1009,  73.8%,  70.9%;   36632;   802;  20.343, 15.014
     80;   42730,   1757,   1322,   1273,  75.2%,  72.5%;   40173;   802;  21.962, 16.525
     90;   44970,   1932,   1451,   1399,  75.1%,  72.4%;   42238;   802;  21.467, 16.122
    100;   48513,   2332,   1721,   1654,  73.8%,  70.9%;   45384;   802;  23.320, 17.210
    110;   51283,   2636,   1971,   1896,  74.8%,  71.9%;   47850;   802;  23.964, 17.918
    120;   53646,   2865,   2097,   2019,  73.2%,  70.5%;   49984;   802;  23.875, 17.475
    130;   56392,   3217,   2354,   2268,  73.2%,  70.5%;   52379;   802;  24.746, 18.108
    140;   58784,   3487,   2566,   2477,  73.6%,  71.0%;   54498;   802;  24.907, 18.329
    150;   61268,   3767,   2747,   2655,  72.9%,  70.5%;   56706;   802;  25.113, 18.313
    160;   63774,   4094,   2996,   2899,  73.2%,  70.8%;   58894;   802;  25.587, 18.725
    170;   66206,   4382,   3194,   3093,  72.9%,  70.6%;   61024;   802;  25.776, 18.788
    180;   68231,   4652,   3385,   3277,  72.8%,  70.4%;   62780;   802;  25.844, 18.806
    190;   70664,   4967,   3630,   3518,  73.1%,  70.8%;   64905;   802;  26.142, 19.105
    200;   72863,   5300,   3859,   3743,  72.8%,  70.6%;   66765;   802;  26.500, 19.295
    210;   74593,   5572,   4051,   3932,  72.7%,  70.6%;   68221;   802;  26.533, 19.290
    220;   76438,   5894,   4250,   4128,  72.1%,  70.0%;   69744;   802;  26.791, 19.318
(略)
  11890;  388941, 386167, 219832, 215572,  56.9%,  55.8%;    1977;   802;  32.478, 18.489
  11900;  388978, 386476, 220028, 215767,  56.9%,  55.8%;    1703;   802;  32.477, 18.490
  11910;  389023, 386790, 220225, 215961,  56.9%,  55.8%;    1439;   802;  32.476, 18.491
  11920;  389071, 387119, 220429, 216157,  56.9%,  55.8%;    1156;   802;  32.476, 18.492
  11930;  389120, 387448, 220622, 216346,  56.9%,  55.8%;     876;   802;  32.477, 18.493
  11940;  389162, 387741, 220787, 216510,  56.9%,  55.8%;     630;   802;  32.474, 18.491
  11950;  389201, 388087, 220994, 216714,  56.9%,  55.8%;     332;   802;  32.476, 18.493
  11960;  389235, 388393, 221188, 216907,  56.9%,  55.8%;      47;   802;  32.474, 18.494
  11970;  389254, 388660, 221363, 217080,  57.0%,  55.9%;       0;   802;  32.470, 18.493
  11980;  389261, 388954, 221565, 217282,  57.0%,  55.9%;       0;   802;  32.467, 18.495
  11990;  389265, 389161, 221748, 217459,  57.0%,  55.9%;       0;   802;  32.457, 18.494
  12000;  389273, 389246, 221828, 217539,  57.0%,  55.9%;       0;   802;  32.437, 18.486
  12010;  389282, 389264, 221844, 217555,  57.0%,  55.9%;       0;   802;  32.412, 18.472
  12020;  389286, 389270, 221847, 217558,  57.0%,  55.9%;       0;   802;  32.385, 18.456
  12030;  389287, 389275, 221851, 217562,  57.0%,  55.9%;       0;   802;  32.359, 18.441
  12040;  389288, 389282, 221857, 217568,  57.0%,  55.9%;       0;   802;  32.332, 18.427
  12050;  389288, 389284, 221859, 217570,  57.0%,  55.9%;       0;   802;  32.306, 18.412
  12060;  389288, 389287, 221862, 217573,  57.0%,  55.9%;       0;   802;  32.279, 18.397
  12070;  389288, 389288, 221863, 217574,  57.0%,  55.9%;       0;   802;  32.253, 18.381
  12080;  389288, 389288, 221863, 217574,  57.0%,  55.9%;       0;   802;  32.226, 18.366
  12090;  389288, 389288, 221863, 217574,  57.0%,  55.9%;       0;   802;  32.199, 18.351

このように、接続に成功するノードの割合は最初のうちは 70 % 程度あるものが、後に減少していた。この原因についてはまだ検討していない。接続に成功しないノードは、「Port0」モードで稼動しているもの(もしくは「Port0」に設定していないがNATルータのポートフォワード設定ができていないもの)と推定できる。

5番目の列は、接続に完了してそこから他のノード情報を取得できたノード数を示しており、接続したノードの 98 % から取得ができたことがわかる。今回は、全体を巡回することを優先して接続を早期に切断しているが、接続を継続することによってこの割合は100 % に近づくと思われる。

今回の巡回(日曜日の昼に実施)で見つかった 38万9288ノードという数は、ネットエージェント社が発表した「平日で44万〜49万、土日だと50万〜53万以上」という値よりいささか少ない。同社の7月3日の発表でも、

2006年7月1日(土)のWinnyノード数は471449、2006年7月2日(日)のWinnyノード数が481359であることが当社Winny調査システムにより観測されました。

となっている。今回のプログラムが全部のノードに到達することができなかった可能性と、7月中旬で利用者が減少している可能性が考えられる。

全部のノードに到達しない可能性の原因として、Winnyネットワークの「クラスタ化」動作により、いくつかのWinnyノードクラスタに到達できなかったということが考えられる。しかし、Winnyの「クラスタ化」は、ユーザにより指定された3個以下のキーワードの類似性により構成されるものであるため、「A, B」を指定しているユーザと「A, C」を指定しているユーザがいれば、Aのクラスタを経由してBのそれとCのそれはつながっていることになること、また、クラスタ分けは緩やかに構成されているものであることから、完全に分離独立したクラスタというものは存在しないと考えられるし、非常に疎に結合したクラスタの存在も考えにくい。

図1のグラフで、発見ノード数が急激に増加する現象を示すところが2箇所の時刻でみられるが、これは、新たなクラスタが見つかったというタイミングを示しているように見える。今回のアルゴリズムは、ノード情報の新しいものを優先しており、ほぼLIFO(スタック)の順序で実行している*2ため、先に同じクラスタのノードを探し切った後に別のクラスタのノードを探すという傾向の動作をしていることになる。ノードが飽和して新しいノードがほとんど見つからなくなると、古いノード情報を使い始め、古いものをいくらか使っているうちにこのような急激な新規ノード発見増加の現象が生じたようである。

今回の実験では 57 % のノードにしか接続していないのだから、接続できなかった残りのノードに接続していれば到達していたクラスタの存在というものも考えられるが、その可能性は低いように思われる。たしかに、急激にノード数が増加する現象がただ1つのノード(もしくはごくわずか)によってのみ引き起こし得るものだと仮定すれば、そのような可能性も信憑性が出てくるが、これは、最初にそのようなノードが処理された時点で、先にそこから辿られる新しいクラスタのノードを優先して処理することになるため、急激な形で見えているだけで、スタックのすぐ奥には他にも同様のノード(新しいクラスタへリンクしているノード)が多数存在していると考えるのが自然と思われる。そうすると、半分程度のノードを調べないことによって到達できないクラスタが生ずるという可能性は、きわめて小さいのではないか。

今後、優先度の評価方法を変更して実験をすることにより、違うパターンの順序でのノード巡回を試してみたい。

package jp.takagi_hiromitsu.winny.crawler;
import java.io.*;
import java.net.*;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import jp.takagi_hiromitsu.winny.net.WinnyProtocolInterpreter;
import jp.takagi_hiromitsu.winny.net.WinnyProtocolException;
import jp.takagi_hiromitsu.winny.net.WinnyCommandVisitor;
import jp.takagi_hiromitsu.winny.net.WinnyProtocolCommander;
import jp.takagi_hiromitsu.winny.net.Command3;
import jp.takagi_hiromitsu.winny.net.Command4;
import jp.takagi_hiromitsu.winny.net.Command13;
import jp.takagi_hiromitsu.winny.net.Command10;
import jp.takagi_hiromitsu.winny.Node;
import jp.takagi_hiromitsu.winny.WinnyKey;
import jp.takagi_hiromitsu.winny.WinnyFileID;
import jp.takagi_hiromitsu.winny.WinnyConfiguration;

public class WinnyWalker {
    Set<Node> foundNodes = new HashSet<Node>();
    final int startTime = currentTime();
    int connectionTriedCount = 0;
    int handshakeSucceededCount = 0;
    int extractionSucceededCount = 0;
    static final int MAX_THREADS = 800;
    ThreadPoolExecutor threadPool;
    final Timer timer = new Timer(true);
    WinnyConfiguration conf = new WinnyConfiguration();
    WinnyWalker(Node initialNode) {
        threadPool = new ThreadPoolExecutor(
            MAX_THREADS, MAX_THREADS, 0L, TimeUnit.SECONDS,
            new PriorityBlockingQueue<Runnable>()
        );
        timer.scheduleAtFixedRate(new MonitorTask(), 0L, 10 * 1000L);
        addNode(initialNode, 0);
    }
    boolean addNode(Node node, int priority) {
        if (!node.isValid()) return false;
        boolean added = foundNodes.add(node);
        if (!added) return false;
        int elapsed = currentTime() - startTime;
        threadPool.execute(new Task(node, priority - elapsed));
        return true;
    }
    class Task implements Runnable, Comparable<Task> {
        Node target;
        int priority;
        int taskSubmittedTime;
        int taskStartTime;
        Task(Node target, int priority) {
            this.target = target;
            this.priority = priority;
            taskSubmittedTime = currentTime();
        }
        public int compareTo(Task another) {
            if (this.priority < another.priority) return -1;
            if (this.priority == another.priority) return 0;
            return 1;
        }
        int handshakeCount = 0;
        int extractedNodeCount = 0;
        int newNodeCount = 0;
        Exception lastException;
        String[] clusterWords;
        public void run() {
            taskStartTime = currentTime();
            int errorCount = 0;
            for (int i = 0; i < 4; i++) {
                if (errorCount >= 1) break;
                if (currentTime() - taskStartTime >= 20) break;
                try {
                    connect(30);
                } catch (IOException e) {
                    errorCount++;
                }
            }
            connectionTriedCount++;
            if (handshakeCount > 0) {
                handshakeSucceededCount++;
            }
            if (extractedNodeCount > 0) {
                extractionSucceededCount++;
            }
            printTasklog();
        }
        private void connect(int timeLimit) throws IOException {
            InputStream is = null;
            OutputStream os = null;
            Socket s = null;
            TimerTask closer = null;
            try {
                s = new Socket(target.addr, target.port);
                closer = new SocketCloser(s);
                timer.schedule(closer, timeLimit * 1000L);
                is = new BufferedInputStream(s.getInputStream());
                os = new BufferedOutputStream(s.getOutputStream());
                new WinnyProtocolCommander(os, conf).sendCommand(new Command10());
                WinnyCommandVisitor visitor = new NodeAddingVisitor();
                new WinnyProtocolInterpreter(is, visitor).interpret();
            } catch (WinnyProtocolException e) {
                lastException = e;
            } catch (IOException e) {
                lastException = e;
                throw e;
            } finally {
                if (closer != null) closer.cancel();
                try {
                    if (os != null) os.close();
                    if (is != null) is.close();
                    if (s != null) s.close();
                } catch (IOException e2) {
                }
            }
        }
        class NodeAddingVisitor extends WinnyCommandVisitor {
            public void visit(Command3 c) {
                handshakeCount++;
                clusterWords = c.clusterWords;
            }
            public void visit(Command4 c) {
                extractedNodeCount++;
                boolean added = addNode(new Node(c.addr, c.port), 0);
                if (added) newNodeCount++;
            }
            public void visit(Command13 c) {
                for (int i = 1; i < c.nodePath.length; i++) {
                    Node n = c.nodePath[i];
                    extractedNodeCount++;
                    boolean added = addNode(n, 0);
                    if (added) newNodeCount++;
                }
                Map<Node,Integer> minimum = new HashMap<Node,Integer>();
                for (WinnyKey k: c.keys) {
                    dumpKey(target, k);
                    Node node;
                    if (k.addr.isSiteLocalAddress() || k.addr.isLinkLocalAddress()) {
                        node = target;
                    } else {
                        node = new Node(k.addr, k.port);
                    }
                    int pri = 1500 - k.keyExpirationTimer;
                    if (pri < 0) pri = 0;
                    Integer old = minimum.get(node);
                    if (old == null || old > pri) {
                        minimum.put(node, pri);
                    }
                }
                for (Node n: minimum.keySet()) {
                    extractedNodeCount++;
                    boolean added = addNode(n, minimum.get(n));
                    if (added) newNodeCount++;
                }
            }
        }
        class SocketCloser extends TimerTask {
            Socket socket;
            int lastExtractedNodeCount = 0;
            SocketCloser(Socket socket) {
                this.socket = socket;
            }
            public void run() {
                try {
                    socket.getInputStream().close();
                } catch (IOException e) {
                }
                try {
                    socket.close();
                } catch (IOException e) {
                }
            }
        }

        private void printTasklog() {
            taskOut.printf(
                "%21s pri: %4d life: %3d con: %d new: %5.1f%% (%4d/%4d) %d %d %s %s\n",
                target.toString(),
                priority + (taskStartTime - startTime),
                currentTime() - taskStartTime,
                handshakeCount,
                (float)newNodeCount / extractedNodeCount * 100,
                newNodeCount,
                extractedNodeCount,
                currentTime() - startTime,
                currentTime(),
                clusterWords == null ?
                    "" :
                    clusterWords[0] + "|" + clusterWords[1] + "|" + clusterWords[2],
                lastException == null ? "" : lastException
            );
            taskOut.flush();
        }
    }
    class MonitorTask extends TimerTask {
        public void run() {
            int elapsed = currentTime() - startTime;
            monitorOut.printf(
//                "Elapsed time: %7d; Nodes found: %6d, tried: %6d, " +
//                    "connected: %6d, succeeded: %6d, live: %5.1f%%, " +
//                    "extracted: %5.1f; %Queue lingth: %6d; Threads: %4d;" +
//                    " /sec tried: %5.3f, connected: %5.3f\n",
                "%7d; %6d, %6d, %6d, %6d, %5.1f%%, %5.1f%%; %6d; %4d; %5.3f, %5.3f\n",
                elapsed,
                foundNodes.size(),
                connectionTriedCount,
                handshakeSucceededCount,
                extractionSucceededCount,
                (float)handshakeSucceededCount / connectionTriedCount * 100,
                (float)extractionSucceededCount / connectionTriedCount * 100,
                threadPool.getQueue().size(),
                Thread.activeCount(),
                (float)connectionTriedCount / elapsed,
                (float)handshakeSucceededCount / elapsed
            );
            monitorOut.flush();
        }
    }
    private void dumpKey(Node node, WinnyKey k) {
        keydumpOut.printf(
            "%s %s:%d %s %d %d %d %d \"%s\" %d %s\n",
            node.toString(),
            k.addr.getHostAddress().toString(),
            k.port,
            k.fileid.toString(),
            k.keyExpirationTimer,
            currentTime(),
            k.keyUpdateTime,
            k.referenceCount,
            k.trip,
            k.filesize,
            k.filename
        );
    }
    private static int currentTime() {
        return (int)(System.currentTimeMillis() / 1000);
    }

    static final String usage = "Usage: java WinnyWalker host port [outputdir]";
    static PrintStream keydumpOut, taskOut, monitorOut;
    public static void main(String[] args) throws Exception {
        if (args.length < 2 || args.length > 3) {
            System.err.println(usage);
            System.exit(1);
        }
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        Node initialNode = new Node(InetAddress.getByName(host), port);
        File dir = new File(".");
        if (args.length > 2) {
            dir = new File(args[2]);
        }
        keydumpOut = newPrintStream(dir, "keydump.log");
        taskOut = newPrintStream(dir, "task.log");
        monitorOut = newPrintStream(dir, "monitor.log");
        new WinnyWalker(initialNode);
    }
    static PrintStream newPrintStream(File dir, String file) throws Exception {
        OutputStream os = new FileOutputStream(new File(dir, file));
        return new PrintStream(new BufferedOutputStream(os));
    }
}
図2: 追試に使用したノード巡回アルゴリズムの説明用コード「WinnyWalker」

訂正

上記には誤りがあり、18日の日記で訂正した。

*1 停止させる機能はまだ作っていない。

*2 FIFOの順序としないのは、それぞれのノードの接続試行開始時刻が、ノード情報発見時刻から数時間後となってしまい、接続成功率が低下するのを避けるため。


<前の日記(2006年07月08日) 次の日記(2006年07月17日)> 最新 編集

最近のタイトル

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|05|06|07|08|09|10|11|12|
2012|02|03|04|05|06|07|08|09|
2013|01|02|03|04|05|06|07|
2014|01|04|07|09|11|12|
2015|01|03|06|07|10|11|12|
2016|01|02|03|04|06|07|08|10|11|12|
2017|01|02|03|04|05|06|07|10|12|
2018|03|05|06|10|12|
2019|02|03|05|06|07|08|10|
2020|08|09|
2021|07|08|10|12|
2022|01|04|06|12|
2023|03|
2024|03|04|07|11|12|
2025|01|02|03|04|05|06|10|11|12|
<前の日記(2006年07月08日) 次の日記(2006年07月17日)> 最新 編集