WPP
YAMAHA RTX1210 を Asahiネット IPIP + 固定IPアドレスオプション で使って Synology NAS をインターネットからアクセスできるようにする。

自宅 LAN 環境のルーターを NEC の IX から YAMAHA の RTX に変更したのだがなかなか苦戦したのでその顛末をメモする。 RTX1210 が Hairpin ナットに対応していないことに設定を始めてから気づいたり、自宅の模様替え以降どうにも WiFi が遅かった理由がケーブルだったりといろいろあったがなんとか解消できたので、一旦様子見としよう。

動機

NEC IX だとどうにも IKEv2 リモートアクセスを構築できなそうってことに気づき、なんだかなーと思っていたの思い切ってネット上でも IKEv2 リモートアクセスの設定例が見つかる RTX に変えることにした。

自宅 LAN にリモートアクセスするだけなら他の方式の VPN でも問題はないが、Android OS ネイティブ対応が IKEv2 のみになったのでちょっと困っていた。

回避策として Synology NAS についている OpenVPN サーバーで対応していたが、難しくはないがちょっと面倒に感じていた。

今日の時点ではリモートアクセス VPN は張っていないが、そのうちにやろうと思う。

前提

インターネット回線はタイトルにもあるが以下の通り

  • Asahiネット 光 の 1G のコース 10G だと「固定IPアドレスオプション」が使えない
  • 固定IPアドレスオプション
  • その結果、IPv4 over IPv6接続 (IPIP)
  • ひかり電話なし
  • ONU のみ

が使えるようになるってことらしい。PPPoE の IPv4 セッションも張れるがわざわざ使う必要はないだろう。
にしても ISP 界隈やケータイキャリア界隈ののネーミングセンスは壊滅的にわかりにくいと思う。名前から機能の差異がつかみにくいし、製品群の構成と名前付けルールが部外者からは理解し難く、わざとやっているのか?勘繰ってしまいたくなる。
LAN 内は、

  • Synology NAS
  • PC やスマホなどの端末機器

という構成です。NAS があること以外は至って代わり映えのない環境です。

なのでネットワーク構成は、

  • IPv6 で接続し、
  • その中を IPIP トンネルで流し、
  • DMZ はなし
  • LAN 内に配置した NAS に NAPT (ヤマハだとマスカレード) で外からアクセス
  • LAN 内に広告排除のために PiHole という DNSフィルタ (DNSサーバー)を建てるている。

って形になります。

[ここに図を入れる]

IPアドレス設計

IPアドレスはこんなふうになる。

ルーターの WAN側

IPv6 をあまり理解していないので間違いがあるかもしれない。

IPv6: 自動
IPv4: 157.107.76ZZZ

LAN 側

ルーター: 192.168.1.254

NAS: 192.168.1.244

その他端末: 192.168.1.0/24 のどこか

DNSサーバ: 192.168.1.245 (PiHole を参照するように DHCP で配布)

Asahiネットの固定IP 情報

固定IP が開通すると Asahiネット のマイページで以下のような情報を見ることができる。これらを適宜 RTX に投入する必要がある。

とはいえ、RTX のWeb管理画面でプロバイダー接続の設定はできるので、そこで一旦設定を作成してから手動で微調整した。(ネットワークガチ勢の硬派な方は GUI なんて使わないと思うがそこまでネットワークに興味ないので GUI でいきます。)

  • インターフェースID: 0000:0000:0000:0000:0000:9D6B:ZZZZ:0000 (ZZZZ はダミー)
  • AFTRエンドポイントIPv6アドレス: 2001:0C28:0001:0300:0000:0000:0000:ZZZZ
  • 固定IPv4アドレス: 157.107.76.ZZZ
  • アップデートサーバURL: https://v6update.asahi-net.or.jp/prefix
  • 認証用ユーザーID: P12345
  • 認証用パスワード: XXXYYYZZZ

開通通知の各項目は、設定画面では、(Asahiネット > RTX の Web画面 に対応する)

インターフェイスID > インターフェイスID:
上のインターフェイスID のうち上位の 0000 の並びを取り除き、再下位の 0000 を 0 に正楽略した形を指定する必要がある、要するに 9D6B:ZZZZ:0 とする必要がある。

AFTRエンドポイントIPv6アドレス > IPv6 アドレス:

固定IPv4アドレス > IPv4 アドレス:

アップデートサーバURL > アップデートサーバーの URL:

認証用ユーザーID > ユーザー名:

認証用パスワード > パスワード

にそれぞれ対応する。

RTX の設定した内容

結局こんな感じの大分類になりそう。(分け方とか認識に異論はあると思いますが、現時点の自分の中の整理ではこんなかんじ。)

  1. LAN 設定
  2. IPIP と IPv4 フィルタ
  3. NAT と IPv4 固定アドレス
  4. IPoE (IPv6) と Pv6のフィルタ
  5. DHCP
  6. あと LAN とか NTP アップデートとかもろもも
  7. LUA スクリプト

コンフィグ中に項番を入れコメントします。が基本は設定ウィザードの内容をいくらか修正しただけです。なのでフィルタの内容は十分といえないかもしれないので調整が必要だと思う。

上記の条件でよければ、コメントで開通通知の [項目名] とした部分を変更すればそのままで動くはずです。

# パスワードは encrypted な方がいいのはまちがいない
# このへんは、GUI で適当に
login password XXXXX
administrator password XXXXX
login user admin encrypted XXXXX
user attribute connection=serial,telnet,remote,ssh,sftp,http gui-page=dashboard,lan-map,config login-timer=1800
user attribute admin connection=serial,telnet,remote,ssh,sftp,http gui-page=dashboard,lan-map,config login-timer=1800

# コンソール文字化け対策
console character ja.utf8

# デフォルトで IPIP トンネルに抜ける
ip route default gateway tunnel 1

## 1. LAN設定 
ip lan1 address 192.168.1.254/24
# IPv6 アドレスを上から貰ってくる
ipv6 prefix 1 ra-prefix@lan2::/64

# 開通通知の [インターフェースID]
ipv6 lan1 address ra-prefix@lan2::9d6b:ZZZZ:0/64
ipv6 lan1 prefix change log on
ipv6 lan1 rtadv send 1 o_flag=on
ipv6 lan1 dhcp service server
switch control use lan1 on terminal=on

# これはようわからん、ウィザードが生成してきた。
# 削除するとイニシエーションに失敗するとか理由がありそうなので削らないほうが良さげ
lan linkup send-wait-time lan2 5

description lan2 AsahiNET

## 2. IPIP の設定 と  IPv4 フィルタ
tunnel select 1
 tunnel encapsulation ipip

#『IPv4 over IPv6接続(IPIP)接続設定情報』の
# [AFTRエンドポイントIPv6アドレス] まんま入力するとだめかも
 tunnel endpoint remote address 2001:c28:1:300::ZZZZ
 ip tunnel mtu 1460
 ip tunnel secure filter in 400003 400020 400021 400022 400023 400024 400025 400030 400032 400100 400101 400102 400103 dynamic 400082
 ip tunnel secure filter out 400013 400020 400021 400022 400023 400024 400025 400026 400027 400099 dynamic 400080 400081 400082 400083 400084 400085 400098 400099
 ip tunnel nat descriptor 20000
 tunnel enable 1
ip filter 1001 pass * * icmp * *
ip filter 400003 reject 192.168.1.0/24 * * * *
ip filter 400013 reject * 192.168.1.0/24 * * *
ip filter 400020 reject * * udp,tcp 135 *
ip filter 400021 reject * * udp,tcp * 135
ip filter 400022 reject * * udp,tcp netbios_ns-netbios_ssn *
ip filter 400023 reject * * udp,tcp * netbios_ns-netbios_ssn
ip filter 400024 reject * * udp,tcp 445 *
ip filter 400025 reject * * udp,tcp * 445
ip filter 400026 restrict * * tcpfin * www,21,nntp
ip filter 400027 restrict * * tcprst * www,21,nntp

# ping (icmp) を通しているが、落としてもいいと思う
ip filter 400030 pass * 192.168.1.0/24 icmp * *
ip filter 400032 pass * 192.168.1.0/24 tcp * ident
ip filter 400036 pass * 192.168.1.0/24 udp * ntp
ip filter 400037 pass * 192.168.1.0/24 udp ntp *
ip filter 400099 pass * * * * *

# Synology NAS に対する許可、ip tunnel secure filter in で使ってる 
ip filter 400100 pass * 192.168.1.244 tcp * 5000
ip filter 400101 pass * 192.168.1.244 tcp * 5001
ip filter 400102 pass * 192.168.1.244 tcp * 6690
ip filter 400103 pass * 192.168.1.244 tcp * https
ip filter 500000 restrict * * * * *
ip filter dynamic 400080 * * ftp
ip filter dynamic 400081 * * domain
ip filter dynamic 400082 * * www
ip filter dynamic 400083 * * smtp
ip filter dynamic 400084 * * pop3
ip filter dynamic 400085 * * submission
ip filter dynamic 400098 * * tcp
ip filter dynamic 400099 * * udp

## 3. NAT と IPv4 固定アドレス
nat descriptor type 20000 masquerade

# NAT の抜け先に固定 IP をあてる
nat descriptor address outer 20000 157.107.76.213
nat descriptor masquerade incoming 20000 reject 

# Synology NAS に対するアクセスを NAS の LANアドレスに向ける
nat descriptor masquerade static 20000 1000 192.168.1.244 tcp 5000
nat descriptor masquerade static 20000 1001 192.168.1.244 tcp 5001
nat descriptor masquerade static 20000 1002 192.168.1.244 tcp 6690
nat descriptor masquerade static 20000 1003 192.168.1.244 tcp https

## 4. IPv6 と IPv6フィルタ
ipv6 lan2 secure filter in 101000 101001 101002 101003 101010
ipv6 lan2 secure filter out 101099 dynamic 101080 101081 101082 101083 101084 101085 101097 101098 101099
ipv6 lan2 dhcp service client ir=on
ngn type lan2 ntt
ipv6 filter 101000 pass * * icmp6 * *
ipv6 filter 101001 pass * * tcp * ident
ipv6 filter 101002 pass * * udp * 546

# Synology NAS が使うポートを許可
ipv6 filter 101003 pass * * tcp * 5001

# IPIP を通す
ipv6 filter 101010 pass * * 4
ipv6 filter 101099 pass * * * * *
ipv6 filter dynamic 10197 * * https
ipv6 filter dynamic 101080 * * ftp
ipv6 filter dynamic 101081 * * domain
ipv6 filter dynamic 101082 * * www
ipv6 filter dynamic 101083 * * smtp
ipv6 filter dynamic 101084 * * pop3
ipv6 filter dynamic 101085 * * submission
ipv6 filter dynamic 101098 * * tcp
ipv6 filter dynamic 101099 * * udp

## 5. dhcp
dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.1.1-192.168.1.244/24

# まあこれは趣味、NAS に常時同じIP を降らせてる
dhcp scope bind 1 192.168.1.244 01 90 09 d0 4b 06 cd

# Pihole: 192.168.1.245 と RTX自身と、pubulic な DNS を降らせる
dhcp scope option 1 dns=192.168.1.245,192.168.1.254,9.9.9.9
dns host lan1
dns service fallback on
dns server dhcp lan2 edns=on
dns private address spoof on

## 6. そのたもろもろ
tftp host lan
telnetd host lan1

# スクリプト実行(スクリプト自体は生成されたものを編集して使っている)
schedule at 1 startup * lua emfs:/v6plus_map_e.lua

# 時刻動機
schedule at 2 */* 05:43:21 * ntpdate ntp.nict.jp syslog
httpd host lan1
alarm usbhost off
alarm sd off
alarm entire off
sshd service on
sshd host key generate 57712 ZZZZZZZ
# any ホストはまずいかも
sftpd host any
statistics traffic on
statistics nat on

embedded file v6plus_map_e.lua <<EOF

#『IPv4 over IPv6接続(IPIP)接続設定情報』の
# [アップデートサーバURL]、[認証用ユーザーID]、[認証用パスワード] を貼り付け
UPD_SV = "https://v6update.asahi-net.or.jp/prefix"
USERNAME = "P1234"
PASSWORD = "XXXYYYZZZ"

# ここはウィザードがやってくれるのでいじらない
WAN_IF = "LAN1"

LOG_PTN = "Add%s+IPv6%s+prefix.+%(Lifetime%:%s+%d+%)%s+via%s+" .. WAN_IF .. "%s+by"
LOG_LEVEL = "info"
LOG_PFX = "[v6plus]"
FAIL_MSG = "Failed to notify IPv6 address to the update server. (remaining retry: %d time(s))"

function logger(msg)
rt.syslog(LOG_LEVEL, string.format("%s %s", LOG_PFX, msg))
end

local rtn, count, log, result
local req_t = {}
local res_t
req_t.url = string.format("%s?user=%s&pass=%s", UPD_SV, USERNAME, PASSWORD)
req_t.method = "GET"
while true do
rtn = rt.syslogwatch(LOG_PTN)
if rtn then
count = 3
while true do
res_t = rt.httprequest(req_t)
if res_t.rtn1 then
logger("Notified IPv6 address to the update server.")
if res_t.code == 200 then
result = "Succeeded"
else
result = "Failed"
end
log = string.format("%s to update IPv6 address. (code=%d, body=%s)", result, res_t.code, res_t.body)
logger(log)
break
end
count = count - 1
if count > 0 then
logger(string.format(FAIL_MSG, count))
rt.sleep(10)
else
logger("Failed to notify IPv6 address to the update server.")
break
end
end
end
end
EOF

LAN内から固定IPあての通信をPiHoleで解決

上が完成形なのだが、当初は DHCP で PiHole のアドレスを配布していなかった。

というのもヘアピンNAT機能が RTX1210 に無いため、LAN内から固定IPあての通信ができないことに気づいた。詳しくいうと独自ドメインに固定IP を紐付けインターネットに公開しているサーバがあり、それにアクセスできなくなっていた。

そのサーバは実際には、 Synology NAS でホストしているのだが、Synology の NAS の画面に接続できていたので、接続 OK と思い込んでいた。

スマホからは、独自ドメインのそのサーバにに、スマホの5G / 4G からはアクセスできていたので、IPv4 で通信はできていることになる。

さいわい、 PiHole をすでに使っていたので DCHP から配布するようにし、
PiHole の ローカル DNS レコードで独自ドメインを内向けに設定した (つまり NASの 192.168.1.244 を独自ドメインの FQDN にした)。

これにより、LAN 側から独自ドメインのサービスへのアクセスも疎通するようになった。

一時は面倒なので RTX830 か 840 あるいは 1220 を買うのかとも思ったが買わずにすんでよかった。

結局のところ NAS の画面には、IPv6 で接続していることなんだろうな。個人で使える固定 IPv6 というのを見たことがないが、それがあれば、単に DNS に登録するだけで済む話なんだろう。

駄話

部屋の模様替えをしてからというもの WiFi が遅いなーとは思っていたのですが、RTX の Web 画面で見てみると 100Base で接続されていました。レイアウト変更時に間に合わせでつないだケーブルを変更するのを忘れていました。

ケーブルをカテ 6A に交換すると常時100Mbps 以下だったところが、いいときは 500Mbps 越えも記録するようになった。物理的に 100Mbps が上限だったので当然といえば当然。世間的には決して早くはないですが、前がひどかったのでだいぶ良くなった。

教訓

事前準備は周到に!

参考

IPv4 over IPv6接続(IPIP) | IPv6接続機能 | オプション | 各種サービス | プロバイダ・インターネット接続は ASAHIネット
Asahiネットの対応機種リスト

IPv4 over IPv6接続(IPIP) 固定IP接続を設定する (Asahi-Net v6プラス) – YAMAHA RTXルーターの操作方法 設定 Tips | iPentec

公式の設定サンプル v6 コネクト接続設定例

introduction for YAMAHA remote router series
RTPro 公式の資料集

コマンドリファレンス
公式のリファレンス

RTX1200のv6コネクト接続設定でハマる – GentooやIoTの覚え書きブログ
RTX1200 でもやれるらしい

RTX830 の設定、IPv6 の話 – 記憶力が無い
さらっとしか見ていないがこれとほぼ同じでできるはず。

RTXルータでパケットフィルタのログを確認する – 3流プログラマのメモ書き
これも参考になる。

【ヘアピンNATができない!】LANからだとアクセスできない問題を解決する。【ローカルDNS】
ヘアピンNATはこのページをヒントに今回は PiHole で回避した

YAMAHAルータでヘアピンNAT(NATループバック)を有効にする – OpenTecMemo / オープンテックメモ
これはよくわからなかった。pp のアドレスと固定IPを別にすると向き先が違うので NAT が効くってことらしいが、IPIP トンネルでどうしたらいいかわからなかった。できなそうな気もする。

YAMAHA RTX1200 – maruko2 Note.
チートシート的なページ

ヤマハルータのコマンド | muchbow
これもチートシート的なページ

どうでもいい話だが、AsahiネットはAasahiNet なのかASAHINETなのか表記がゆれててよくわからん

synology NAS に pi-hole をインストールするも “Failed to set capabilities for pihole-FTL. Cannot run as non-root.” ではまった話

暑いあまりにも暑い。

それはともかく、 iPhone があまりに広告に対し脆弱なので pi-hole をインストールした。

クライアント側にアプリを入れる対策もあるが、正直毎回入れるのは面倒なので、dhcp で参照先 DNS として配布できる pi-hole を入れてみた。

ここまで来るまでには Adguardhome (Adgurad のサーバー版) も試したのだがちょっと自分には機能が豊富すぎる気がしたので pi-hole に一旦落ち着いたところだ。

以下の設定は、docker の動く synology なら動作する。もちろん単独のサーバーでもいい。

docker を bridge モードで動かすと アクセス元のクライアントがみんな一緒になってしまう問題があるが、 fetburner.core の情報を参考にして以下のようになった。

環境

LAN 側の状況

IP: 192.168.1.0/24
デフォルトゲートウェイ: 192.168.1.254

コンテナにも、LAN 側の IP を別個で割り当てる。

IP: 192.168.1.245
まあ、他とかぶらなければなんでもいい。
(当然だが、LAN 内の他のホストと衝突しないように調整する。必要ならルーター dhcp の除外範囲も調整する)

ブラウザでダッシュボードにアクセスするとセキュリテイ警告がでるが、インターネット側からアクセスする必要はないと判断して外部からアクセスするための設定は省略した。

必要なら synology のリバースプロキシとルータのポート開放を設定すること。

docker-compose.yml

実は、公式の docker-compose.yml をコピーしても起動時に ”Failed to set capabilities for pihole-FTL. Cannot run as non-root.” というメッセージを吐いてコケる問題があったが、DNSMASQ_USER を指定してやると回避できた。

# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    volumes:
      - /volume1/docker/pihole/conf:/etc/pihole
      - /volume1/docker/pihole/dnsmasq.d:/etc/dnsmasq.d
    environment:
      TZ: 'Asia/Tokyo'
      FTLCONF_webserver_api_password: 'なんかパスワードをしていすること'
      DNSMASQ_LISTENING: 'all'
      DNSMASQ_USER: 'root'
    networks:
        macvlan:
             ipv4_address: 192.168.1.245
    cap_add:
      - NET_ADMIN
    security_opt:
        - no-new-privileges:true
    restart: unless-stopped

networks:
    macvlan:
        name: macvlan
        driver: macvlan
        driver_opts:
            parent: ovs_eth0
        ipam:
            config:
                - subnet: 192.168.1.0/24
                   gateway: 192.168.1.254

起動後の設定

上記の設定ならブラウザで http://192.168.1.245/admin にアクセスするとダッシュボードが表示される。

左側の settings から DNS に進みお好みの上位 DNS を指定すれば設定は完了。
必要なら、Custom DNS にローカルのルーターを指定してやってもいい。なんにもしなくても動くようになっているので楽ちん。

dhcp から DNS を配布

DNS タイプの広告ブロッカーはインストールしただけでは、クライアントから使うことはできない。クライアントが今までとは別の新しい DNSサーバー を参照しないと意味がない。これは Pi-hole もAdguradhome も同様だ。

手順は割愛するが、iPhone だと手動で DNS を切り替えることができる。

Android12 以降では、手動での切り替えはできない。

何れにしても手動で設定するのは面倒なので、しばらく使ってみて問題がなさそうならルーターの dhcp から pi-hole の ip アドレスを DNS として配布するように設定するつもりだ。

多くの場合、ルーター自身の参照 DNS や dhcp の設定で配布する参照 DNS を決定をすればいい。

補足

実は synology で動かす前に、ROCK64 といラズベリーパイ3B 互換の SBC で試してみた。が明らかにインターネットの参照が遅くなったので、最終的に synology NAS で動かすことにした。

ROCK64 では dietpi という軽量 Linux に直接インストールしてみたがそれでも遅かったのでしかたない。PI4 か PI5 なら違った結果になったのかもしれない。

参考

synology の openVPN に Linux MINT から接続する

synology の NAS には VPN サーバ機能があり openVPN 接続に対応している。

Linux MINT (22.1)を使った家の環境では openVPN 接続を追加する際、デフォルトの設定のままでは接続できなかった。試行錯誤して接続できたので設定画面をメモする。

設定アプリのネットワーク のキャプチャをメモしておく。

openVPN を設定した項目で ギアマークをクリックすると、ダイアログが開く。
Details タブはなにもないのでスルー。

Identity タブ から Advanced… ボタンをクリックする。

Advanced Properties ダイアログの General タブはこんな感じ。多分になにも変えていない。

Security タブは、synology の openVPN と合わせる。

TLS Authentication タブは、 Verify peer (server) certificate usage signature にチェックをいれ Server を選択する。これをしないとネゴシエーションに失敗するようで接続することができない。

再設定する際に、きっと忘れてしまうので記録しておく。

nec ix で dns キャッシュをクリアする方法

先日、 独自ドメインの dns レコードを変更したがしばらくたっても ix が引く dns のキャッシュ情報が反映されなかった。

ix の dns キャッシュ設定はenable か無し (no) しかないらしく。クリアコマンドは存在しないらしい。

そんなときは、以下のように一旦キャッシュの寿命を短くしてクリアすればいいっぽい。

ix# configure
; 一旦キャッシュの寿命を最短にする
ix(config)# dns cache lifetime 1

; キャッシュの寿命を再設定(259200 はデフォルト値なので好きな値にする)
ix(config)# dns cache lifetime 259200

; キャッシュの中身を確認する方法
ix(config)# show dns cache address-database
kill -kill がめんどくさいので alias 設定

小ネタだが、complete コマンドを知らなかったのでメモする。

ごくたまに killall で全部殺しちゃうと不都合なことがあり kill -kill することがあります (もっといい方法があるのかもしれませんが。。。)。

kill はいいとして -kill が地味に面倒なのでエリアスで短縮してみる。

それだけだと kill -kill と異なり PID のタブ補完をしてくれないので complete も追加する。

alias kkill='kill -kill'
complete -F _kill kkill

_kill というのは補完を定義している関数らしく、以下の方法で確認できる。

$ complete -F
や
$ declare -F 
で確認できる。

$ ls /usr/share/bash-completion/completions
あたりに定義ファイルがある

知らないことはたくさんありますね。

Forgejo をアップグレードした

以前も書いたかもですが、自宅 Synology NAS では Forgejo が git サーバとして動作しています。

どうもアタックされていたようで知らないユーザが作成されていました。とはいえ不思議なことにそれ以外の被害はなさそうです。Dockerコンテナで動かしていたので、それ以上何もできなかったのかもしれません。

Forgejo のバージョンも 7 となっていて公式を確認すると随分遅れてしまっています。これを機に復旧がてらアップグレードしました。

復旧作業

もともと永続化データをNASのディレクトリに逃がしているので、復旧にあたっては、作成されたゴミユーザーを消してコンテナを新たに作成すればきれいな状態で再開することができそうです。

  1. NASのコンテナマネージャを開き Forgejo のコンテナを選択する
  2. ターミナルを開き、bash コンソールを起動する
  3. 以下のコマンドで不要なユーザーを削除する
# su - git
$ forgejo admin user list  ; 不要なユーザーの ID を確認する
$ forgejo admin user delete --purge -id 1234

不要な設定の無効化

確認はしていませんが、当初インストールしたときにはコンテナイメージの設定をいじらなかったと思うのですがどうもオンラインサインアップ機能が生きているので攻撃者がユーザを作成できる状態なようです。

設定を変更してオンラインサインアップ機能を無効化します。

設定はファイルはコンテナ内の /data/gitea/conf/app.ini にあります。

NAS 側から編集してもいいですし、コンテナ内にコンソールで入って編集してもいいです。

DISABLE_REGISTRATON が false になっているので true に変更します。

DISABLE_REGISTRATION = true

そうすると、ログイン画面にユーザーサインアップのリンクが消えるはずです。

ユーザーを追加するには、管理者でログインして、管理者が追加すれば OK です。

Forgejo のアップグレード

参考リンクの公式によると、バージョンは1つずつアップグレードしろってことなので 8 > 9 > 10 > 11 と四回アップグレードする必要があるみたいです。

NAS上の dokcker-compose.yml を変更していきます。

  1. コンテナマネージャからプロジェクトを選択します。
  2. プロジェクト名を選択して、YAML Configuration タブにすすみます。

以下のような要領で書き換えます。

container_name は書き換えなくてもいいのですが、コンテナタブで表示されるとすぐに変更されたことがわかるので変えています。

この設定は、sysnlogy NAS に限らず docker-compose 使っていれば同じですね。

version: '3'

networks:
  forgejo:
    external: false

services:
  server:
    # :7 から :8 に変更した
    image: data.forgejo.org/forgejo/forgejo:8

    # forgejo7 を forgejo8 に変更した
    container_name: forgejo8
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - forgejo
    volumes:
      - /volume1/docker/forgejo:/data
      #- /etc/timezone:/etc/timezone:ro
      #- /etc/localtime:/etc/localtime:ro
    ports:
      - '8002:3000'
      - '8003:22'

バージョンを確認するには、コンテナマネージャからコンソールを起動して確認することができます。

# foegejo --version 
 目的のメジャーバージョン番号が表示される

上で書いたようにバージョンを一気に飛ぶことができないので、必要な回数繰返して終了。

参考

Installation with Docker | Forgejo – Beyond coding. We forge.

VSCode で g++ を使うための設定

他のページもちらほら見つかるが自分的最小メモを残しておく。

VSCode には予め CMake Tools のエクステンションをインストールしておく。

そして VSCode 上でステップ実行(デバッグ)するために 2 つの設定ファイルを作成 (編集)する必要があります。

tasks.json でビルド方法を定義し、launch.json でデバック起動方法を定義します。

CMake extensionの設定

CMake がインストールされていると VSCode の左下のほうに CMake .. のような表示が出てくるのでそこをクリックして、コンパイラを選択します。

tasks.json の設定

task.json ではビルドコマンドを定義します。なのでここでは、cmake を使ったビルド環境に合わせて調整します。

プロジェクトのフォルダに .vscode フォルダがあるはずなのでその中の tasks.json を編集します。

CTRL + SHIFT + P を押して tasks: Configure task などを選んで設定を追加します。

{
    "tasks": [
        {
            "label": "CMake: build",
            "type": "shell",
            "command": "/usr/bin/cmake",
            "args": [
                "--build",
                "${workspaceFolder}/build",
                "--config",
                "Debug"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ],
            "detail": "Task generated by CMake Tools."
        }
    ],
    "version": "2.0.0"
}

“label” は表示される名前なので好きな感じにして OK です。
“command” と “args” でコマンドラインを組み立てます。ソースと同じディレクトリで実行するなら
`cmake –build ./build –config Debug` というコマンドになります。
ビルドディレクトリを変えたければ “${workspaceFolder}/build” を変更します。

tasks.json を設定しても、VSCode の Run メニューとかでは実行できないようです。どうしたら実行できるのかは後で調べる。

launch.json の設定

launch.json では VSCode の デバッグタブ? (左端の Run & Debug) で上部に表示できるデバッグ時の起動方法を設定する。

デバッグタブのギヤアイコンをクリックすると設定を追加できる。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "CMake Debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${command:cmake.launchTargetPath}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [
                {
                    "name": "PATH",
                    "value": "S{env:PATH}",
                    // "name": "LD_LIBRARY_PATH",
                    // "value": "/usr/local/cuda-11.8/lib64:/usr/local/cuda-11.8/lib64/stubs:/usr/local/cuda-11.8/targets/x86_64-linux/lib"
                }
            ],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb.",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "CMake: build"
        }
    ]
}

launch.json はきほんそのままで動くみたいだが、
“environment” に PATH や LD_LIBRARY を追加すると
`PATH=xxx LD_LIBRARY [ビルドしたプログラム]`
のように起動することになるらしい。

これで次回からも設定に戸惑わなくなりそう。

bat の小ネタ

bat というシンタックスをハイライトしてくれる cat クローンがあってそれを常用しています。

ネタ的には bat というより less の -X オプションをしらなかったというしょうもない話になりますが忘れないためにメモします。

bat は less のようなページャとしての側面も持っており長いファイルの場合は、一旦画面が止まって入力待ちになるのですが、デフォルトだとこんな時に q で終了しても画面がクリアされてしまい。ファイルの内容が見えなくなってしまいます。これが地味に不便でした。

一方 less では、これを回避するための -X オプションがあり最終画面を残したまま終了することができます。

cat のページャ機能は結局のところ less を使って実現しているようなので -X を強制的に渡すと望んでいることができるようです。

export BAT_PAGER='less -XFR'

これで終了時の画面をクリアしなくなります。

設定を永続化するには、 .bashrc などに上記を追加して source ~/.bashrc で反映します。

また一つ地味な不便さを解消できました。

参考

bat: bat/doc/README-ja.md at master · sharkdp/bat

Gazebo の SDF を分割する

なんとか RGBD_Camara の使い方がわかったGazebo ですが、だんだんファイルが長くなってきて見通しが悪くなってきました。もう少し実践的なシミュレーションにしようとするとオブジェクト(モデル)の数がどんどん増えていくのでちょっと厳しそうです。

SDFromat の仕様をみてみると <include> というタグがありどうやら別ファイルの内容をロードすることができそうなので試してみました。結果からいうと <include><uri> のなかで file:// を使うとファイルシステムからファイルを読み込んでくれました。

分割前のファイル

ファイル名を camera.sdf としているのはいまいち分かりづらいかもしれませんがこれを分割してみます。

地面に相当する面と光源、そしてカメラと球体が配置されています。

<?xml version="1.0" ?>
<sdf version="1.10">
  <world name="rgb_world">
    <plugin
        filename="gz-sim-physics-system"
        name="gz::sim::systems::Physics">
    </plugin>

    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    <gravity>0 0 -9.8</gravity>

    <light type='directional' name='sun'>
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
        <static>true</static>
        <link name="link">
            <collision name="collision">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                </plane>
            </geometry>
            </collision>
            <visual name="visual">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                <size>100 100</size>
                </plane>
            </geometry>
            <material>
                <ambient>0.8 0.8 0.8 1</ambient>
                <diffuse>0.8 0.8 0.8 1</diffuse>
                <specular>0.8 0.8 0.8 1</specular>
            </material>
            </visual>
        </link>
    </model>

    <model name='sphere1'>
      <pose>1.5 1.5 0.5  0 0 0</pose>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </collision>
        <visual name='visual'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </visual>
      </link>
    </model>

    <!-- RGB camera-->
    <model name='rgb_camera'>
      <pose>0 0 0.5  0 0 0.52</pose>
      <link name='link'>
        <inertial>
          <mass>0.1</mass>
          <inertia>
            <ixx>0.000166667</ixx>
            <iyy>0.000166667</iyy>
            <izz>0.000166667</izz>
          </inertia>
        </inertial>

        <collision name='collision'>
          <geometry>
            <box>
              <size>0.1 0.3 0.2</size>
            </box>
          </geometry>
        </collision>

        <visual name='visual'>
          <geometry>
            <box>
              <size>0.1 0.3 0.2</size>
            </box>
          </geometry>
          <material>
            <ambient>0.1 0.1 0.1 1</ambient>
            <diffuse>0.1 0.1 0.1 1</diffuse>
            <specular>0.01 0.01 0.01 1</specular>
          </material>
        </visual>

        <sensor name='camera' type='camera'>
          <always_on>1</always_on>
          <update_rate>2</update_rate>
          <visualize>true</visualize>
          <camera>
            <horizontal_fov>1.047</horizontal_fov>
            <image>
              <width>640</width>
              <height>480</height>
              <format>R8G8B8</format>
            </image>
            <clip>
              <near>0.1</near>
              <far>100</far>
            </clip>
            <save enabled='true'>
              <path>images/</path>
            </save>
          </camera>
          <topic>rgb</topic>
        </sensor>
      </link>
    </model>
    <!-- RGB camea end -->

    <!-- load sensors plugin  -->
    <plugin filename='gz-sim-sensors-system'
      name='gz::sim::systems::Sensors'>
    </plugin>
  </world>

</sdf>

分割後のファイル構成

  • camera-world.sdf ワールド、地面と光源を含む。
  • camera-model.sdf カメラ
  • sphere-model.sdf 球体

こんなふうに分割して、camera-model.sdf と sphere-model.sdf をそれぞれ camera-world.sdf に読み込みます。

camera-world.sdf

まずは、camera-world.sdf です

<?xml version="1.0" ?>
<sdf version="1.10">
  <world name="rgb_world">
    <plugin
        filename="gz-sim-physics-system"
        name="gz::sim::systems::Physics">
    </plugin>

    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    <gravity>0 0 -9.8</gravity>

    <light type='directional' name='sun'>
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
        <static>true</static>
        <link name="link">
            <collision name="collision">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                </plane>
            </geometry>
            </collision>
            <visual name="visual">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                <size>100 100</size>
                </plane>
            </geometry>
            <material>
                <ambient>0.8 0.8 0.8 1</ambient>
                <diffuse>0.8 0.8 0.8 1</diffuse>
                <specular>0.8 0.8 0.8 1</specular>
            </material>
            </visual>
        </link>
    </model>

    <model name='sphere1'>
      <pose>1.5 1.5 0.5  0 0 0</pose>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </collision>
        <visual name='visual'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </visual>
      </link>
    </model>

    <!-- Sphere -->
    <include>
      <uri>file://./sphere-model.sdf</uri>
    </include>

    <!-- RGB camera -->
    <include>
      <uri>file://./camera-model.sdf</uri>
    </include>

    <!-- load sensors plugin  -->
    <plugin filename='gz-sim-sensors-system'
      name='gz::sim::systems::Sensors'>
    </plugin>
  </world>

</sdf>

試していませんが <plugin> も camera-model に追い出せるかもしれません。

camera-model.sdf

<sdf> タグのあと <world> を飛ばして <model> タグを書いて大丈夫でした。これが仕様的に正しいのかちょっと自信はないのですが、問題あったらなおします。

<?xml version="1.0" ?>
<sdf version="1.10">
  <!-- RGB camera-->
  <model name='rgb_camera'>
    <pose>0 0 0.5  0 0 0.52</pose>
    <link name='link'>
      <inertial>
        <mass>0.1</mass>
        <inertia>
          <ixx>0.000166667</ixx>
          <iyy>0.000166667</iyy>
          <izz>0.000166667</izz>
        </inertia>
      </inertial>

      <collision name='collision'>
        <geometry>
          <box>
            <size>0.1 0.3 0.2</size>
          </box>
        </geometry>
      </collision>

      <visual name='visual'>
        <geometry>
          <box>
            <size>0.1 0.3 0.2</size>
          </box>
        </geometry>
        <material>
          <ambient>0.1 0.1 0.1 1</ambient>
          <diffuse>0.1 0.1 0.1 1</diffuse>
          <specular>0.01 0.01 0.01 1</specular>
        </material>
      </visual>

      <sensor name='camera' type='camera'>
        <always_on>1</always_on>
        <update_rate>2</update_rate>
        <visualize>true</visualize>
        <camera>
          <horizontal_fov>1.047</horizontal_fov>
          <image>
            <width>640</width>
            <height>480</height>
            <format>R8G8B8</format>
          </image>
          <clip>
            <near>0.1</near>
            <far>100</far>
          </clip>
          <save enabled='true'>
            <path>images/</path>
          </save>
        </camera>
        <topic>rgb</topic>
      </sensor>
    </link>
  </model>
  <!-- RGB camea end -->
</sdf>

camera-model.sdf

sphere も同じようにします。

<?xml version="1.0" ?>
<sdf version="1.10">
  <model name='sphere1'>
    <pose>1.5 1.5 0.5  0 0 0</pose>
    <link name='link'>
      <collision name='collision'>
        <geometry>
          <sphere>
            <radius>0.5</radius>
          </sphere>
        </geometry>
      </collision>
      <visual name='visual'>
        <geometry>
          <sphere>
            <radius>0.5</radius>
          </sphere>
        </geometry>
      </visual>
    </link>
  </model>
</sdf>

gz sim camera-world.sdf で無事読み込めました。

参考

【ROS】作成したロボット部品を表示してみる【 Gazebo,SDF,XML】 – ふるお〜と!

SDFormat Specification

Gazebo のセンサーが publish しているトピックを調べる方法

前回の記事では camera_sensor と depth_camera を取り上げましたが、その際 rgbd_camera が上手く扱えなかったのですが、どうやら rgbd_camera は / では何も広告していないことが sensors_demo.sdf をみてわかりました。

センサーがどんなトピックを広告するかを調べる方法を調べました。 センサーが広告するトピックがドキュメントで見つけられなかったのですが、その理由の一旦がわかりました。

トピックを確認する方法

まずは、何でもいいので正しく構成された .sdf を読み込んでおきます。

別の端末から以下のコマンドを実行します。ここでは /rgbd が rgbd_camera が publish しているトピックですが、
その下に、camera_info、depth_image などがあることがわかります。

$ gz topic -l
/clock
/gazebo/resource_paths
/gui/camera/pose
/gui/currently_tracked
/gui/track
/rgbd/camera_info
/rgbd/depth_image
/rgbd/image
/rgbd/points
/sensors/marker
/stats
/world/rgbd_world/clock
/world/rgbd_world/dynamic_pose/info
/world/rgbd_world/pose/info
/world/rgbd_world/scene/deletion
/world/rgbd_world/scene/info
/world/rgbd_world/state
/world/rgbd_world/stats
/world/rgbd_world/light_config
/world/rgbd_world/material_color

上記は、この .sdf を読み込んだ結果です

<?xml version="1.0" ?>
<sdf version="1.10">
  <world name="rgbd_world">
    <plugin
        filename="gz-sim-physics-system"
        name="gz::sim::systems::Physics">
    </plugin>

    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    <gravity>0 0 -9.8</gravity>

    <light type='directional' name='sun'>
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
        <static>true</static>
        <link name="link">
            <collision name="collision">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                </plane>
            </geometry>
            </collision>
            <visual name="visual">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                <size>100 100</size>
                </plane>
            </geometry>
            <material>
                <ambient>0.8 0.8 0.8 1</ambient>
                <diffuse>0.8 0.8 0.8 1</diffuse>
                <specular>0.8 0.8 0.8 1</specular>
            </material>
            </visual>
        </link>
    </model>

    <model name='sphere1'>
      <pose>1.5 1.5 0.5  0 0 0</pose>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </collision>
        <visual name='visual'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </visual>
      </link>
    </model>

    <!-- RGB-D camera-->
    <model name='rgbd_camera'>
      <pose>0 0 0.5  0 0 0</pose>
      <link name='link'>
        <inertial>
          <mass>0.1</mass>
          <inertia>
            <ixx>0.000166667</ixx>
            <iyy>0.000166667</iyy>
            <izz>0.000166667</izz>
          </inertia>
        </inertial>

        <collision name='collision'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
        </collision>

        <visual name='visual'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.1 0.1 0.1 1</ambient>
            <diffuse>0.1 0.1 0.1 1</diffuse>
            <specular>0.01 0.01 0.01 1</specular>
          </material>
        </visual>
        
        <sensor name='rgbd_camera' type='rgbd_camera'>
          <always_on>1</always_on>
          <update_rate>1</update_rate>
          <visualize>true</visualize>
          <camera>
            <horizontal_fov>1.047</horizontal_fov>
            <image>
              <width>640</width>
              <height>480</height>
              <!-- <format>R8G8B8</format> -->
            </image>
            <clip>
              <near>0.1</near>
              <far>100</far>
            </clip>
            <save enabled='true'>
              <path>images</path>
            </save>
          </camera>
          <topic>rgbd</topic>
        </sensor>
      </link>
    </model>
    <!-- RGB-D camea end -->

    <!-- load sensors plugin  -->
    <plugin filename='gz-sim-sensors-system'
      name='gz::sim::systems::Sensors'>
    </plugin>
  </world>

</sdf>

ROS / ROS2 を使っている人にとっては、ノードが発信している情報を M2M 的なアプローチで外部から確認すれば OK っていう方式になじみがあるのだろうが、gazebo のドキュメントにはあまり書いていない気がするのはちょっとなーって文句の 1 つもいいたい。