Jitsi meet(リバースプロキシ構成 + ポート変更)で自分だけのテレビ会議(ビデオ会議)環境を整える

実家に置いてる自宅サーバーをRaspberry Pi 4(8GB)から、IntelのNUCに置き換えた。
Raspberry Pi 3以降から、コンセントの電圧変動ごときでバチバチ落ちるようになってて正直安定化電源かUPSを噛ませないとまともに稼働しない・・・。

NUCで稼働しているとめちゃめちゃ性能的に余裕が出てきたので(いずれ本業でも使えるかもしれないし)調子こいてRocket.Chatを導入。
Rocket.ChatにはいろいろなOSSと連携できる機能があり、その中にビデオ会議(テレビ会議)システムっていうのがある。

そのビデオ会議(テレビ会議)システムを担う部分がJitsi(ジッチ・ジトシって読むらしい。呼びにくい)で、無料で(諸説あり)+誰でも(諸説あり)+どれだけ(諸説あり)でもテレビ会議(ビデオ会議)ができるシステム群とのこと。

これがまぁ思ってるより簡単にインストールできなかったので、おそらくサーバーとかぶっ壊れて同じ環境を再度組みなおす場合のために・・・自分用を兼ねてローコスト多重請負フリーライドなんちゃって虚業の人たちにメモを残します。

このメモは2022年6月時点のものなので、今後の状況によって変動する可能性高。

Jitsiの主な構成

DebianなどのLinux向けに提供されているJitsiのパッケージは、大きく分けて2つの別システムが一体になったもの。

  • Jitsi Meet (WEB側フロントエンド)
    • Prosody (XMPP通信サーバー)
    • Jitsi Conference Focus (WEB会議処理システム)
  • Jitsi Video Bridge (映像・音声双方向通信サーバー)

表向きのポートは、80,443(HTTP,HTTPS)と10000(VideoBridge)が必要で、
もちろん通信内容がインターネット上を通るならSSL証明書は必須。


今回構築する構成

そこらへんのWEB記事には、「そのままインストール~」とか「AWSにインスタンス~」とか書いてあって、たぶん実際そのまま処理すれば動きそうではあるけど、自宅にサーバーを置く日本の一般家庭ではそんな情報は参考にならず

今回は構築にあたって、
外向きデフォルトポート(80,443,10000)の変更と、
Nginxリバースプロキシ構成且つ
すでに取得済みの証明書をJitsiサーバー側から切り離し
動的IPアドレスの場合のJitsi構築を記載。

▲図1:Jitsiサーバーの多重リバースプロキシ構成例

リバースプロキシ側の構成

リバースプロキシ側(図のserver#1)のNginxにて、Jitsi側(図のserver#2)で使う証明書と転送の設定を実施。

Websocketを使用できるようにする

シームレスな通信実現のためにNginx側でwebsocketのサポートが必要なため、Nginxのデフォルト設定(/etc/nginx/nginx.conf)のhttp{…}の中にUpgradeヘッダー関連を有効にする設定を記述しておく。
(もしくは、http{…}の中に読み込まれる設定内(/etc/nginx/conf.d/*.confなど)に記載)

http {
  # ... ごちゃごちゃと他のデフォルト設定の記述
  # ...
  
  # websocket (HTTP/1.1 Upgradeヘッダー)
  map $http_upgrade $connection_upgrade {
      default upgrade;
      '' close;
  }
  
  # ... ごちゃごちゃと他のデフォルト設定の記述
  # ...
}

もちろんこの設定はJitsi側(図のserver#2)のNginxでも設定が必要なはず。
(Debianで標準インストールされるNginxにはUpgradeヘッダー関連の記述がなかった)

リバースプロキシ設定の記述

リバースプロキシ側(図のserver#1)のNginxにて、すでに取得している証明書(jitsi.example.jp)を処理したあと、Jitsi側(図のserver#2)の8443ポートに転送する設定。
既存の設定ファイルに書き足してもいいし、専用でconfファイル作ってもいい。

証明書の取得・更新方法がわからん・・?
LetsEncryptでもZeroSSLでも山ほど情報転がってるのでここでは割愛。

server {
    listen 80;
    server_name jitsi.example.jp;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name jitsi.example.jp;

      return 502;
    }

    # なんかよくわからんがデカい転送発生しそうなのでヨシ!
    client_max_body_size 2G;

    # 転送ログ
    access_log /var/log/nginx/jitsi.access.log;
    error_log /var/log/nginx/jitsi.err.log;

    # 実際に使用するjitsi側のSSL証明書例
    ssl_certificate /etc/letsencrypt/live/jitsi.example.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/jitsi.example.jp/privkey.pem;

    # その他SSL関連設定(お好みで)
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_prefer_server_ciphers on;

    ssl_dhparam /etc/letsencrypt/live/jitsi.example.jp/dhparam.pem;
    add_header Strict-Transport-Security max-age=15768000;

    # ここからが転送の設定
    location / {
        proxy_pass http://192.168.10.20:8443/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forward-Proto http;
        proxy_set_header X-Nginx-Proxy true;
        proxy_redirect off;
    }
}

80ポートに来た接続を443に自動転送する設定はお好みで。最悪80番ポートは開けなくてもいい。

設定などが終わったらsudo nginx -t などでconfファイル全体にエラーがないことを確認し、
Nginxをリロードしておくことを忘れずに。


Jitsi側サーバー準備(インストールから)

リバースプロキシ側に小難しいSSL関連おいてきたので、あとはフツーにJitsiをインストールして設定ファイルをごちゃごちゃいじるだけで完成する。

今回行うポート変更は前述のとおり。

  • jitsi受付ポート (80,443)→リバースプロキシがHTTPS関連受けもってくれるので8443に変更
  • jitsi VideoBridgeポート (10000)→他サーバーで10000が占拠されているため10101に変更

まずはフツーにJitsiインストール

DebianやUbuntuの場合はほぼほぼ公式ドキュメントに書いてあるとおり。

  1. Prosodyリポジトリの鍵入れてリスト見に行くようにする
  2. Jitsiリポジトリの鍵入れてリスト見に行くようにする
  3. Jitsiをインストール
    1. hostname聞いてくるので、ホスト名(図の例であればjitsi.example.jp)を入れる
    2. SSL鍵を新規取得するか聞いてくるので、「I want to use my own certificate」選択
    3. 鍵の場所聞いてくるので、デフォルトの場所のまま次へ(使わない)
    4. 証明書の場所聞いてくるので、デフォルトの場所のまま次へ(使わない)

ここまでの段階でNginxの設定ファイルにJitsiのものが勝手に入っていて、Prosody等が勝手に起動しているはず。(もしくは鍵がないとか言われて起動失敗しているはず。その場合は放置でいい。)


動的(Dynamic)IPアドレス対策

Jitsiの映像・音声通信は、送受信側それぞれに直接送受信される形をとり、Jitsi VideoBridge(以下JVB)がその仲介を担うようになっている。要するにJVBは、今ビデオ会議に参加している端末それぞれとP2Pでつながっている(たぶん)。

そういう接続形態である都合上、ビデオ会議に参加する端末は「サーバーのグローバル(パブリック)IPアドレス」と、「ローカル(内側)IPアドレス」を知っておき、そこに直接映像などを送らなければならないため、JVBの設定にてそれらを予め構成しておく必要がある。

でも待ってほしい・・・。

日本の一般的なプロバイダは特別な契約でない限り、IPv4グローバルIPアドレスは動的に提供されるし、IPv6の場合もあくまで半固定であり絶対に変動しないとは限らない。

そのため、グローバルIPがコロコロ変わる前提で対策を打っておかないといけない

対策スクリプトの作成とCronへの登録

要するにグローバルIPアドレスがコロコロ変更されるということは、JVBにて読み取るグローバルIPとローカルIPが記述されたファイルの内容を都度変更して再読み込みするようにすればいいという話であり、下記のようなスクリプトを用意しておいて、cronにて適宜呼び出せば大丈夫なはず。

発案した神→https://community.jitsi.org/t/jitsi-meet-with-nat-port-mapping-and-dynamic-ip/23890/6

sudo vi /etc/jitsi/pubipaddr_auto_replace.sh

でファイル編集画面(新規作成)を開き、下記内容を記述

ThisFQDN="jitsi.example.jp"

# get the actual IP from the Internet
IPnow=$(host -tA $ThisFQDN | grep address | cut -d " " -f4 )

# get the configured IP of Jitsi
IPbefore=$(grep "NAT_HARVESTER_PUBLIC_ADDRESS" /etc/jitsi/videobridge/sip-communicator.properties |  grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")

if [ "$IPbefore" == "$IPnow" ]
then
        exit 0
fi

#clear config
sed -i -e "s/HARVESTER_PUBLIC_ADDRESS\=$IPbefore/HARVESTER_PUBLIC_ADDRESS\=$IPnow/g" /etc/jitsi/videobridge/sip-communicator.properties

#restart services
service jicofo restart
#service jigasi restart
service jitsi-videobridge2 restart

内容(特に1行目のThisFQDN)を適宜変更して上書き保存し、実行可能フラグを割り当てする。

chmod 655 /etc/jitsi/pubipaddr_auto_replace.sh

あとはこれをcronに登録し、お好みの間隔で実行するようにしておけばOK。
今の日本の環境でADSLとかモバイルルーターとかそんなクソ雑魚回線使ってない限りは10~30分間隔で見るようにしておけば大丈夫かと思われる。
(IPが前回と同じであればexitで終了するので無駄なリロードはかからない。天才。)

crontab -e

MAILTO=""

#Jitsi動的IP対策 15分間隔で実行
*/15 * * * * /etc/jitsi/pubipaddr_auto_replace.sh

Jitsi VideoBridgeの設定ファイル編集

前述のスクリプトで自動的に書き換えるJVBの設定ファイルは、デフォルトでは
/etc/jitsi/videobridge/sip-communicator.properties
になっている。

ここに、前述のローカルIPアドレスとグローバルIPアドレス、JVBの対外ポート番号を記述する。
(あと、不要な設定をコメントアウトする)

sudo vi /etc/jitsi/videobridge/sip-communicator.properties

などでファイルを編集し、下記項目をそれぞれ書き換え

org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES=meet-jit-si-turnrelay.jitsi.net:443 ←コメントアウトする
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=192.168.1.20 ←jitsiサーバーのローカルIPを記述(リバースプロキシ側のIPではない)
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=127.0.0.1 ←グローバルIPを記述するが、どうせあとでスクリプトで置き換わるので、127.0.0.1とか書いておけばOK
org.jitsi.videobridge.SINGLE_PORT_HARVESTER_PORT=10101 ←JVBのポート番号を記述する。デフォルトではこの行は存在しないし、なんならマニュアルにも書いてない

上記項目を反映したファイルとしては以下のようになっているはず。

org.ice4j.ice.harvest.DISABLE_AWS_HARVESTER=true
#org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES=meet-jit-si-turnrelay.jitsi.net:443
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=192.168.1.20
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=127.0.0.1
org.jitsi.videobridge.ENABLE_STATISTICS=true
org.jitsi.videobridge.SINGLE_PORT_HARVESTER_PORT=10101
org.jitsi.videobridge.STATISTICS_TRANSPORT=muc
org.jitsi.videobridge.xmpp.user.shard.HOSTNAME=localhost
org.jitsi.videobridge.xmpp.user.shard.DOMAIN=auth.jitsi.example.jp
org.jitsi.videobridge.xmpp.user.shard.USERNAME=*********
org.jitsi.videobridge.xmpp.user.shard.PASSWORD=*********
org.jitsi.videobridge.xmpp.user.shard.MUC_JIDS=***************@internal.auth.jitsi.example.jp
org.jitsi.videobridge.xmpp.user.shard.MUC_NICKNAME=*****************************

内容が自身の環境に合致していることを確認し、設定ファイルを上書き保存。
JVBの再起動の前に、スクリプトがきちんと動作するかを確かめる。

bash /etc/jitsi/pubipaddr_auto_replace.sh

スクリプトを実行すると、hostコマンドで自分自身の正引きIPをネームサーバーに問い合わせ、
返ってきたグローバルIPアドレスが自動的に「org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=」の部分に設定されるハズ。

実行したあと、もしIPアドレスが前回と変わっているならJVBも自動的に再起動しているはず。
本当にファイルが書き換わってるかどうか確認することを忘れずに。

JitsiのNginx設定ファイルの編集

jitsiをインストールした段階でNginxのconfファイルが自動生成されているはずなので、そのファイルを編集して証明書関連の記述をゴッソリ削除し、受付ポートを変更する。

Ubuntu/Debianデフォルトでは、
/etc/nginx/sites-enabled/[ホスト名].confになっているはず。(図1であればホスト名はjitsi.example.jp)

sudo vi /etc/nginx/sites-enabled/[ホスト名].conf

で編集し、SSL関連の記述をコメントアウト(もしくは削除)していく。

server_names_hash_bucket_size 64;

types {
# nginx's default mime.types doesn't include a mapping for wasm
    application/wasm     wasm;
}
upstream prosody {
    zone upstreams 64K;
    server 127.0.0.1:5280;
    keepalive 2;
}
upstream jvb1 {
    zone upstreams 64K;
    server 127.0.0.1:9090;
    keepalive 2;
}

#このへんの記述いらない(フロントエンド側で処理済み)
#server {
#    listen 80;
#    listen [::]:80;
#    server_name jitsi.example.jp;
#
#    location ^~ /.well-known/acme-challenge/ {
#        default_type "text/plain";
#        root         /usr/share/jitsi-meet;
#    }
#    location = /.well-known/acme-challenge/ {
#        return 404;
#    }
#    location / {
#        return 301 https://$host$request_uri;
#    }
#}
#ここらへんまでいらない

server {
    #受付ポート変更 デフォルトは443
    listen 8443;
    listen [::]:8443;
  
    server_name jitsi.example.jp;

#このへんの記述いらない(フロントエンド側で処理済み)
    # Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration
    #ssl_protocols TLSv1.2 TLSv1.3;
    #ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    #ssl_prefer_server_ciphers off;

    #ssl_session_timeout 1d;
    #ssl_session_cache shared:SSL:10m;  # about 40000 sessions
    #ssl_session_tickets off;

    #add_header Strict-Transport-Security "max-age=63072000" always;
    #set $prefix "";

    #ssl_certificate /etc/ssl/jitsi.example.jp.crt;
    #ssl_certificate_key /etc/ssl/jitsi.example.jp.key;
#ここらへんまでいらない

    root /usr/share/jitsi-meet;

    # ssi on with javascript for multidomain variables in config.js
    ssi on;
    ssi_types application/x-javascript application/javascript;

    index index.html index.htm;
    error_page 404 /static/404.html;

    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
    gzip_vary on;
    gzip_proxied no-cache no-store private expired auth;
    gzip_min_length 512;

    location = /config.js {
        alias /etc/jitsi/meet/jitsi.example.jp-config.js;
    }

    location = /external_api.js {
        alias /usr/share/jitsi-meet/libs/external_api.min.js;
    }

    # ensure all static content can always be found first
    location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
    {
        add_header 'Access-Control-Allow-Origin' '*';
        alias /usr/share/jitsi-meet/$1/$2;

        # cache all versioned files
        if ($arg_v) {
            expires 1y;
        }
    }

    # BOSH
    location = /http-bind {
        proxy_pass http://prosody/http-bind?prefix=$prefix&$args;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $http_host;
        proxy_set_header Connection "";
    }

    # xmpp websockets
    location = /xmpp-websocket {
        proxy_pass http://prosody/xmpp-websocket?prefix=$prefix&$args;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        tcp_nodelay on;
    }

    # colibri (JVB) websockets for jvb1
    location ~ ^/colibri-ws/default-id/(.*) {
        proxy_pass http://jvb1/colibri-ws/default-id/$1$is_args$args;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        tcp_nodelay on;
    }

    # load test minimal client, uncomment when used
    #location ~ ^/_load-test/([^/?&:'"]+)$ {
    #    rewrite ^/_load-test/(.*)$ /load-test/index.html break;
    #}
    #location ~ ^/_load-test/libs/(.*)$ {
    #    add_header 'Access-Control-Allow-Origin' '*';
    #    alias /usr/share/jitsi-meet/load-test/libs/$1;
    #}

    location ~ ^/([^/?&:'"]+)$ {
        try_files $uri @root_path;
    }

    location @root_path {
        rewrite ^/(.*)$ / break;
    }

    location ~ ^/([^/?&:'"]+)/config.js$
    {
        set $subdomain "$1.";
        set $subdir "$1/";

        alias /etc/jitsi/meet/jitsi.example.jp-config.js;
    }

    # BOSH for subdomains
    location ~ ^/([^/?&:'"]+)/http-bind {
        set $subdomain "$1.";
        set $subdir "$1/";
        set $prefix "$1";

        rewrite ^/(.*)$ /http-bind;
    }

    # websockets for subdomains
    location ~ ^/([^/?&:'"]+)/xmpp-websocket {
        set $subdomain "$1.";
        set $subdir "$1/";
        set $prefix "$1";

        rewrite ^/(.*)$ /xmpp-websocket;
    }

    # Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
    location ~ ^/([^/?&:'"]+)/(.*)$ {
        set $subdomain "$1.";
        set $subdir "$1/";
        rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
    }
}

編集がおわったら上書き保存。

sudo nginx -t などで記述が正しいかを確認。
もし「$http_upgradeがない」と怒られる場合は、
リバースプロキシ側の構成>Websocketを使用できるようにする であったように
Nginx側でWebsocketを処理できるようにする記述が追記必要かもしれない。

問題なさそうであればNginxを起動(もしくは再起動)しておく。

sudo systemctl restart nginx

※適宜リバースプロキシ側の起動状態も確認すること

ポートの解放とファイアウォール設定

DebianやUbuntuの場合はufwコマンドで簡単にファイアウォールが設定できるので便利。
OpenSUSEとかの場合もYastとか使えば比較的簡単にファイアウォール設定ができる。
(iptables・・・・?いえ。聞いたことありませんね・・・)

ここまで図1の内容に基づいて設定してきたので、対外に解放しておくポートとしては2つ。8443と10101のポート(IPv4とIPv6両方)のファイアウォールを除外。

sudo ufw allow 8443
sudo ufw allow 10101

もし、サーバーより上の階層(たとえばネットワークルーターなど)でファイアウォールが設定されていれば、そちらも解除する必要がある。(Jitsi Meet側ポート[8443]はリバースプロキシとの通信用なので対外ポートとしてルーター等で除外する必要はない。JVBポートは除外必須。)

サービス再起動と自動起動設定確認

いままでの設定でとりあえず動作するようになっているハズなので、関連サービスを全部再起動する。

sudo systemctl restart nginx
sudo systemctl restart prosody
sudo systemctl restart jicofo
sudo systemctl restart jitsi-videobridge2

ここで起動不全が出ているようであれば、sudo systemctl status [サービス名] でログが出てるはず。

何も問題なければ、サーバー起動時に自動起動するように設定しておく。
(おそらくNginx以外はデフォで自動起動に入ってる)

#自動起動設定
sudo systemctl enable nginx
#自動起動確認
systemctl is-enabled nginx
systemctl is-enabled prosody
systemctl is-enabled jicofo
systemctl is-enabled jitsi-videobridge2

※再度動的IPスクリプトが正しく稼働していることを確認したほうがいい

接続テスト

あとは外側ネットワークからhttp://[ホスト名]にアクセスして、jitsiの画面が出てくるかとかビデオ通話できるかとかを確認する。

firefoxだと最新のバージョンでもマイク音声が途切れる(?)など不具合あり。
chromium系ブラウザじゃないと接続テストできなさそう。


あぁ・・・言わなくてもわかってる。 わかってるよ。 もう何度も見た光景だ。
キミ(俺)たちはいつもそうだよな・・・。 

どうせなにかイキって独自設定しててエラーとかでてビデオ通話できないんでしょ?

見た感じうまくいってるように見えてSSL暗号化できてないんでしょ?


頑張ってログみて解決して!!

接続テストが終わったら、ログの片づけを

デフォルトの設定では、JicofoとJVBのログ出力がALLに設定されてるのでログファイルのサイズがめちゃめちゃ肥大化する。

デバッグが終わったら、正式稼働の前にログをALLからSEVEREに変えておくのを忘れずに。

sudo vi /etc/jitsi/jicofo/logging.properties

java.util.logging.ConsoleHandler.level = ALL ← SEVEREに


sudo vi /etc/jitsi/videobridge/logging.properties

java.util.logging.ConsoleHandler.level = ALL ← SEVEREに

書き換えたらサービスリスタートでエラー出ないことを再確認。

使ってみた感想

Rocket.Chatと組み合わせても、Jitsi単体でも結構使える印象がある。
ただ、操作にクセがあるのでいろいろ実験して覚える必要あり。
グローバルIPを3つ以上使用したテストなどもする必要があり、小規模のインフラエンジニアは泣かされることになると思う。

遠隔面接などで使用する場合などぶっつけ本番で利用するのはちょっと怖い。
その点はやはりGoogle meetとかZoomのほうが配慮されているかんじ。

今のところは身内で通話する用。

参考にした記事など

https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart
https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-manual

https://remoteroom.jp/diary/2020-05-31/

https://remoteroom.jp/diary/2020-06-09/

https://rohhie.net/reduce-jitsis-log-output/

https://community.jitsi.org/t/adjust-port-10000-udp/41895
https://community.jitsi.org/t/how-is-the-videobridge-connected/16524
https://community.jitsi.org/t/jitsi-meet-with-nat-port-mapping-and-dynamic-ip/23890

コメントを残す