それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

dockerで終了したubuntuプロセスを再開する。(docker start )

docker でubuntuを起動した場合

なお、実験にはdocker のかわりに互換ソフトであるpodman を用いた。

docker / ubuntubashを起動する

docker  run -it ubuntu bash

起動例

[root@docker-host ~]# podman  run -it ubuntu bash
root@4d6a491bb9ab:/#  cat /etc/issue
Ubuntu 22.04.3 LTS \n \l

root@4d6a491bb9ab:/# exit
exit
[root@docker-host ~]#

docker で ubuntuは起動しているが、bash が終了すると、docker のインスタンスも終了する。

[root@docker-host ~]# podman ps
CONTAINER ID  IMAGE                            COMMAND     CREATED        STATUS                      PORTS       NAMES
4d6a491bb9ab  docker.io/library/ubuntu:latest  bash        9 minutes ago  Exited (130) 6 minutes ago              zen_cohen

STATUS をみると、exited になっているのがわかる。

コンテナを再起動

コンテナを再開するには、次のようにすればいい。

docker start $CONTAINER_ID
## bash の場合はだめである。

ただ、これを再開しようにも、bash を使っているので、単にスタートするだけではだめである。

実際に再開(起動)してみるとbashは起動するが、入力ができない。

[root@docker-host ~]# podman start 72f7e5a16a34
72f7e5a16a34
[root@docker-host ~]# 

bash の場合は tty (STDIO)を再割り当てしてあげないといけないので、コマンドオプションが必要

## bash の場合はこうする
docker start -ia $CONTAINER_ID

実際にやってみると。

[root@docker-host ~]# podman start -ia 72f7e5a16a34
root@72f7e5a16a34:/#
root@72f7e5a16a34:/# tty
/dev/pts/0
root@72f7e5a16a34:/# exit
exit
[root@docker-host ~]#

このように、docker コマンドにオプションを付けて起動すると、ターミナル(tty)が接続されて、bashでも再開できるようになる。

attach を使ってもいいのだが。start -ia をしておけば、起動中でも再接続ができるので、覚えるコマンドは減らすことができる。

debian12 に移行したら mysqldump が Events Schedulerでエラーになる。

debian12 に移行したらエラーがレポートされるようになった

mysqldump: Couldn't execute 'show events': Cannot proceed, because event scheduler is disabled (1577)

そもそも、event scheduler を使ってないと思うのですが。

MariaDB [(none)]> SELECT @@event_scheduler;
+-------------------+
| @@event_scheduler |
+-------------------+
| OFF               |
+-------------------+
1 row in set (0.000 sec)

イベント・スケジューラーをONにしようとすると・・・

MariaDB [(none)]> SET GLOBAL event_scheduler = ON;
ERROR 1408 (HY000): Event Scheduler: An error occurred when initializing system tables. Disabling the Event Scheduler.

有効にすることは出来ない。

この場合は、mariadb の何かがおかしいと思われる。

mysql_upgrade でデータを更新する

ググって見つけたスレッドを参考にする。

sudo mysql_upgrade -u root -h localhost -p --verbose --force

エラーが出なくなった

sudo /usr/sbin/automysqlbackup

実行してもエラーは出なくなった。event scheduler の設定は関係なかった。

過去の記事

https://takuya-1st.hatenablog.jp/entry/2021/11/26/161114

参考資料

https://serverfault.com/questions/551096/couldnt-execute-show-events-on-mysqldump

laravel 5/6/7アプリケーションをphp8.0で動かす。

稼働中のLaravelアプリをバージョンアップ

php8で動かしたいと思ったけど、エラーになるので、対応する。

マニュアルupgrade 5.8 to 6を参考にアップグレードする

composer.json

## require 
-        "php": "^7.1",
+        "php": "^7|^8.0",
-        "laravel/framework": "5.8.*",
+        "laravel/framework": "^6",
-        "laravel/socialite": "^4.1",
+        "laravel/socialite": "^5",
## require-dev
-        "phpunit/phpunit": "^7.5"
+        "phpunit/phpunit": "^8.1"
composer update 

6.x を7.x にアップグレード

マニュアル( Upgrading To 7.0 From 6.x ) を参考にアップグレードする。

composer.json

-        "laravel/framework": "^6.*",
-        "laravel/socialite": "^4.1",
-        "laravel/tinker": "^1.0",
+        "laravel/framework": "^7",
+        "laravel/socialite": "^5",
+        "laravel/tinker": "^2.0",
+        "laravel/ui": "^2.5",
## require-dev
-        "phpunit/phpunit": "^8.1"
+        "phpunit/phpunit": "^8.3"
-        "fzaninotto/faker": "^1.4",
+        "fakerphp/faker": "^1.23",
-        "nunomaduro/collision": "^3.0",
+        "nunomaduro/collision": "^4.0",
composer update 

app/Exceptions/Handler.php がエラーになる。

PHP Fatal error:  Declaration of App\Exceptions\Handler::report(Exception $exception) 
must be compatible with Illuminate\Foundation\Exceptions\Handler::report(Throwable $e) in 

ここではExceptionで書かれている箇所をThrowable に変えてやる。

@@ -3,6 +3,7 @@
 namespace App\Exceptions;

 use Exception;
+use Throwable;
 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

 class Handler extends ExceptionHandler
@@ -29,10 +30,10 @@ class Handler extends ExceptionHandler
     /**
      * Report or log an exception.
      *
-     * @param  \Exception  $exception
+     * @param  \Throwable  $exception
      * @return void
      */
-    public function report(Exception $exception)
+    public function report(Throwable $exception)
     {
         parent::report($exception);
     }
@@ -41,10 +42,10 @@ class Handler extends ExceptionHandler
      * Render an exception into an HTTP response.
      *
      * @param  \Illuminate\Http\Request  $request
-     * @param  \Exception  $exception
+     * @param  \Throwable  $exception
      * @return \Illuminate\Http\Response
      */
-    public function render($request, Exception $exception)
+    public function render($request, Throwable $exception)
     {
         return parent::render($request, $exception);
     }

追加パッケージのアップグレードの方法

依存関係のバージョンを把握するのがめんどくさいので、一回削除してから追加し直すと良い。

どれが、laravel が要求する composer.json で、どれが自分が書いた composer require かがわからなくなるよね。なので、Laravel関連と、Laravelのプラグインと、自分のアプリでパッケージと、区別してどれがどのRequireになるのか、開発時にメモを残しておいてほしい。でなければアップグレードが煩雑になる。

## 例
composer remove "barryvdh/laravel-ide-helper"
composer require "barryvdh/laravel-ide-helper"

laravel/ui が 7.0から出現するので、Auth::XXXが使われてる場合は、次のようにする。

## Auth:: を使っていた場合
composer require "laravel/ui"

php8.1 にすると

 Illuminate\Support\Collection::offsetExists($key) should either be compatible with ArrayAccess::offsetExists(mixed $offset)

LaravelのCollectionというコアなパッケージがphp8.1のoffsetExists の引数(型指定)と合わないので非常にめんどくさい。これは致命的なので、互換モードがちゃんと実装されるまで、php8.1は見送りですかねぇ。

debian 11 -> 12 のアップデートした。pt1_drv が死んだgem が死んだ。openssh-server が死んだ、mysqldumpが死んだ

debian 11 -> 12 にアプグレ

そろそろアップデートしようかなと思った。

nodesource や gitlab や php など apt でインストールしているパッケージ関連も debian 12 に対応したっぽいので特に問題なくアップグレード出来るはず。

準備。

事前に最新版にする

sudo apt update 
sudo apt upgrade 

apt source を書き換える。

sed -i '/bullseye/bookworm/g' /etc/apt/sources.list
sed -i '/bullseye/bookworm/g' /etc/apt/sources.list.d/*

debian 11 から non-free が分割されて、 non-free , non-free-firmware になった。参考資料

sed -i '/non-free/non-free non-free-firmware/' /etc/apt/sources.list

更新する

screen 
sudo apt update
sudo apt upgrade --without-new-pkgs
sudo apt full-upgrade
sudo apt update 
sudo apt upgrade

screen で切断時に再接続するようにしておく。

一部がエラーになった。

終わったら再起動

sudo reboot

更新された。

$ cat /etc/debian_version
12.1
$ uname -a 
Linux mine 6.1.0-11-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-4 (2023-08-08) x86_64 GNU/Linux

一部エラーになったもの

libapache2-mod-dnssd がエラーになったのですべてが止まってしまった。

sudo apt purge gnome-user-share libapache2-mod-dnssd

一旦削除して再開した。

中途で停止したため、エラーになったのがgitlab-ce。gitlabは本当によくインストール失敗する。

sed -i '/^/#/' etc/apt/sources.list.d/gitlab_gitlab-ce.list

libapache2-mod-dnssdでアップグレードが中途半端になり、glibcのバージョンが合わずにエラーになった。 エラーになったgitlab-ce は一旦アップグレードから除外してあとまわしにした。

pt1_drv dkms がエラーになった

pt1_drv がエラーになった。どうやら根本的に動かなくなった模様

revh=`hg parents --template '#define DRV_VERSION "r{rev}:{node|short}"\n#define DRV_RELDATE "{date|shortdate}"\n' 2>/dev/null`; \
if [ -n "$revh" ] ; then \
        echo "$revh" > version.h; \
else \
        printf "#define DRV_VERSION \""1.1.0"\"\n#define DRV_RELDATE \""2010-01-27"\"\n" > version.h; \
fi
make -C /lib/modules/`uname -r`/build M=`pwd` V=0 modules
make[1]: ディレクトリ '/usr/src/linux-headers-6.1.0-11-amd64' に入ります
  CC [M]  /home/takuya/pt1-dkms/pt1/driver/pt1_pci.o
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c: In function ‘pt1_release’:
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:407:12: warning: "/*" within comment [-Wcomment]
  407 |         /* /* /* /* mutex_unlock(&channel->ptr->lock); */ */ */ */
      |
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:407:15: warning: "/*" within comment [-Wcomment]
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:407:18: warning: "/*" within comment [-Wcomment]
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:407:60: error: expected expression before ‘/’ token
  407 |         /* /* /* /* mutex_unlock(&channel->ptr->lock); */ */ */ */
      |                                                            ^
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:414:12: warning: "/*" within comment [-Wcomment]
  414 |         /* /* /* mutex_unlock(&channel->ptr->lock); */ */ */
      |
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:414:15: warning: "/*" within comment [-Wcomment]
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:414:57: error: expected expression before ‘/’ token
  414 |         /* /* /* mutex_unlock(&channel->ptr->lock); */ */ */
      |                                                         ^
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:416:12: warning: "/*" within comment [-Wcomment]
  416 |         /* /* mutex_unlock(&channel->ptr->lock); */ */
      |
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c: In function ‘pt1_makering’:
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:666:34: error: implicit declaration of function ‘pci_alloc_consistent’ [-Werror=implicit-function-declaration]
  666 |                         dmaptr = pci_alloc_consistent(pdev, DMA_SIZE, &dmactl->ring_dma[lp2]);
      |                                  ^~~~~~~~~~~~~~~~~~~~
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:666:32: warning: assignment to ‘__u32 *’ {aka ‘unsigned int *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
  666 |                         dmaptr = pci_alloc_consistent(pdev, DMA_SIZE, &dmactl->ring_dma[lp2]);
      |                                ^
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c: In function ‘pt1_dma_init’:
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:688:21: warning: assignment to ‘void *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
  688 |                 ptr = pci_alloc_consistent(pdev, DMA_SIZE, &dev_conf->ring_dma[lp]);
      |                     ^
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c: In function ‘pt1_dma_free’:
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:706:25: error: implicit declaration of function ‘pci_free_consistent’ [-Werror=implicit-function-declaration]
  706 |                         pci_free_consistent(pdev, DMA_SIZE,
      |                         ^~~~~~~~~~~~~~~~~~~
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c: In function ‘pt1_pci_init_one’:
/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:734:14: error: implicit declaration of function ‘pci_set_dma_mask’ [-Werror=implicit-function-declaration]
  734 |         rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
      |              ^~~~~~~~~~~~~~~~
      

コメントのあたりがおかしい

/home/takuya/pt1-dkms/pt1/driver/pt1_pci.c:414:57: error: expected expression before ‘/’ token
  414 |         /* /* /* mutex_unlock(&channel->ptr->lock); */ */ */

関数が見つからなくて、implicit declaration of function になってた

  666 |                         dmaptr = pci_alloc_consistent(pdev, DMA_SIZE, &dmactl->ring_dma[lp2]);
  688 |                 ptr = pci_alloc_consistent(pdev, DMA_SIZE, &dev_conf->ring_dma[lp]);
  706 |                         pci_free_consistent(pdev, DMA_SIZE,
  734 |         rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));

どうやら、pci関連の関数が大幅に変わった模様

pt1_drv は家族が録画を楽しみにしてて、生活の一部になってるので、なんとか方法を考える。

ソースコードを書き換えようかと思ったけど、github にレポジトリをみつけた、カーネル6(debian12)に対応できたソースを見つけたのでそれをつかうことに

既存のdkmsは捨てて新しく作り直した。

if [ ! -e ~/repos/stz2012-recpt1 ]; then 
  cd ~/repos/
  git clone https://github.com/stz2012/recpt1.git stz2012-recpt1       
fi 

cd ~/repos/stz2012-recpt1
git pull 
REVISION=`git rev-parse --short HEAD`
## 
sudo cp -r ./driver/ /usr/src/pt1_drv-$REVISION/
##
cat << EOT | sudo tee /usr/src/pt1_drv-$REVISION/dkms.conf
PACKAGE_NAME="pt1_drv"
PACKAGE_VERSION="$REVISION"
MAKE[0]="make KVER=\${kernelver}"
CLEAN[0]="make clean"
BUILT_MODULE_NAME[0]="pt1_drv"
DEST_MODULE_LOCATION[0]="/kernel/drivers/video"
AUTOINSTALL="YES"
EOT
## 
sudo dkms add -m pt1_drv -v $REVISION
sudo dkms build -m pt1_drv -v $REVISION
sudo dkms install -m pt1_drv -v $REVISION

これで、しばらくpt2/pt1 も動くはずである。良かったね。

rubygem が死んだ。

昔書いたスクリプトで自動化してる系がrubypythonphpがあるが、ruby は システム rubygems を使っていために、動作が止まってしまった。

bundler を使ってない系はgem 直接インストール。

bundler を使っている系は、rubygems をシステム全体にインストールして解決した。

ruby のバージョンを固定するのがめんどくさいですね・・・

pythonphpDebianアップデート後でも簡単に動くのに。

bundleを使ってる箇所を探すには

sudo updatedb
sudo locate Gemfile.lock

とでもやっておけば見つかると思う。

rootで入れたシステム全体のgem はもうやめて、apt で入れたほうが良いかもしれない。

bundle で入れたものはruby のバージョンで依存するので、rbenv をあわせたほうが良いかもしれない。

apt list ruby-*

でapt に登録されている gems を見る限り、ruby-mail ruby-sinatra ruby-zip ruby-pry-byebug ruby-parallel ruby-rspec-core ruby-rmagick ruby-sequel ruby-sqlite3 ruby-daemons とかよく使いそうなものはapt で入るっぽい。OSに任せてロードしたほうがアップデート追従出来て嬉しいかも。

スリープしてしまう。

以前も起きたのですが。アップデート後にスリープしてしまう事象が発生 https://takuya-1st.hatenablog.jp/entry/2021/11/26/172528

sshがdropbear になる。

dropbear は luks 解除のために入れているんですが、SSHDがopenssh-server ではなく、、dropbearになってしまった。

init.d のdropbear が openssh-server より先に起動するためである。

dropbear はモジュールだけがほしいのであるが、自動起動まで入ってしまう。無効にしたのに、また有効になっていた。systemd はマジ、F◯ck だわ。

mysqldumpがエラーを吐くようになった

debian12 に移行したら mysqldump が Events Schedulerでエラーになる。 - それマグで!

openWrt でipsecしてトンネルを経由する。(nft/nftablesに注意する。)

openWrt は nft / nftables になっているので、注意する。

最初にパケットを定期的に送る。(watch を使うのがコツ / ping コマンドで送ると継続パケットになり、パケットがマークされたままになり、経路が変わっても以前の経路を通る)

ssh s0  -- watch -n 2 'ping -c 1  1.0.0.1'

LAN(br-lan)からのForwardをAcceptするには

## src net を書いたり
iptables -I FORWARD -o "${VTI_IF}" -s 172.16.9.0/24 -j ACCEPT
## または
iptables -I FORWARD -o "${VTI_IF}" -i br-lan -j ACCEPT

iptablesでルールを書いて先頭に入れてAcceptしたら良い。ただし。OpenWrtはNFT/nfstable であるので、これを書き換える必要がある。

たとえば次のようになる。

nft insert rule inet fw4 forward iifname $IIF oifname $VTI_IF accept;

ただ、上記の場合だと不要になったあとに削除が面倒なので、ユニークなコメントを入れておく。

nft insert rule inet fw4 forward iifname $IIF oifname $VTI_IF accept comment "\"${UNIQUE_COMMENT}\"";

ただ、これだとルーティングで広めにとると全部転送されてしまうので、もう少し限定する。

nft insert rule inet fw4 forward iifname $IIF ip daddr 1.1.1.1 accept comment "\"${UNIQUE_COMMENT}\"";

これで、削除するときに、コメントをマーク代わり使って削除できる。

HANDLES=$(nft -a list table inet fw4 | grep "${UNIQUE_COMMENT}" | grep -oP '(?<=handle )+\d+')
for h in $HANDLES; do
  nft delete rule inet fw4 forward handle $h
done

マスカレードを入れる。そのままでは応答パケット先が不明になるため、OpenWrtでマスカレードして解決する

nft add table ${TABLE}
nft add chain ${TABLE} prerouting  '{ type nat hook prerouting priority 0; }'
nft add chain ${TABLE} postrouting '{ type nat hook postrouting priority 0; }'
nft add rule  ${TABLE} postrouting oifname "${VTI_IF}" masquerade

これで、通信できた。

あとは、これらを設定にまとめてup-down スクリプトにまとめてvti をつかうようにする。(後述)

最初にパッケージのインストール

opkg install 
strongswan-default  \
strongswan-swanctl  \
strongswan-mod-eap-mschapv2  \
strongswan-mod-eap-identity  \
strongswan-default  \
strongswan-ipsec  \

ipsec の設定を書く

/etc/ipsec.conf

include /etc/config/custom/strongswan/ipsec.conf

/etc/config/custom/strongswan/ipsec.conf

include ./*/ipsec.conf

/etc/config/custom/strongswan/ipsec.secrets

include ./*/ipsec.secrets

/etc/strongswan.conf

## (中略)
## 末尾に追加する。最後の記述で上書きされる。
include /etc/config/custom/strongswan/strongswan.d/vti.conf

/etc/config/custom/strongswan/strongswan.d/vti.conf

# vim: set ft=css et :
## 上書きする設定。
charon { plugins { resolve {
    file = /etc/resolve.ipsec.conf
    load = yes
  }
}}
charon {
   install_routes = no
   install_virtual_ip = no
}

接続設定を書く

/etc/config/custom/strongswan/vpnx.example.com/ipsec.conf

conn myConn

  keyexchange=ikev2

  right=vpnx.example.com
  rightid=vpnx.example.com
  rightauth=pubkey
  rightsubnet=0.0.0.0/0
  type=tunnel

  leftauth=eap
  leftsourceip=%config4
  leftid=takuya001
  eap_identity=takuya001

  auto=add

  leftupdown=/etc/config/custom/strongswan/vpnx.example.com/link-updown.sh
  mark=218

/etc/config/custom/strongswan/vpnx.example.com/ipsec.secrets

# 認証(パスワード)を書く 
# for ikev2 or ms-chap-v2
takuya001       vpnx.example.com : EAP "xxxxxxxxxxxx"

up-down スクリプトを作る

mkdir -p /etc/config/custom/strongswan/vpnx.example.com/
touch /etc/config/custom/strongswan/vpnx.example.com/link-updown.sh
chmod +x /etc/config/custom/strongswan/vpnx.example.com/link-updown.sh
vi /etc/config/custom/strongswan/vpnx.example.com/link-updown.sh

中身

#!/usr/bin/env bash



set -o nounset
#set -o errexit


UNIQUE_NAME=$( basename $( dirname $0 ) )



VTI_IF="vti${PLUTO_UNIQUEID}"
TABLE="inet ${UNIQUE_NAME}"
UNIQUE_COMMENT="takuya:${UNIQUE_NAME}_accept"
SSH_SERVER=xxx.xxx.xxx.xxx
SAMPLE_IP=1.1.1.1

## vpn 先にネットワークがある場合
VPN_GW=${PLUTO_MY_SOURCEIP%.*}.1
VPN_NET=${PLUTO_MY_SOURCEIP%.*}.0/24


add_nft(){
  IIF=br-lan
  ## clear nft
  remove_nft
  ## MASQUERADE
  nft add table ${TABLE}
  nft add chain ${TABLE} prerouting  '{ type nat hook prerouting priority 0; }'
  nft add chain ${TABLE} postrouting '{ type nat hook postrouting priority 0; }'
  nft add rule  ${TABLE} postrouting oifname "${VTI_IF}" masquerade
  ## accept
  nft insert rule inet fw4 forward iifname $IIF ip daddr { ${SSH_SERVER} } counter accept comment "\"${UNIQUE_COMMENT}\""
  nft insert rule inet fw4 forward iifname $IIF ip daddr { ${SAMPLE_IP} } counter accept comment "\"${UNIQUE_COMMENT}\""
}
remove_nft(){
  ## nft
  nft delete table ${TABLE}  2>/dev/null
  HANDLES=$(nft -a list table inet fw4 | grep "${UNIQUE_COMMENT}" | grep -oP '(?<=handle )+\d+')
  for h in $HANDLES; do
    nft delete rule inet fw4 forward handle $h
  done

}
vpn_routing(){
  ## add vpn gw
  if [[ ! -z $VPN_NET ]]; then
    ip route add $VPN_NET dev "${VTI_IF}"
  fi

  ## static routing
  ip route add ${SAMPLE_IP} dev "${VTI_IF}"
  ip route add ${SSH_SERVER} dev "${VTI_IF}"
}

function show_env(){

  env | grep PLUTO
  echo SHELL=$SHELL
}
function link_up_down(){
  case "${PLUTO_VERB}" in
    up-client)
      ## vti tunnel device
      ip tunnel add "${VTI_IF}" mode vti \
        local "${PLUTO_ME}" remote "${PLUTO_PEER}" \
        okey "${PLUTO_MARK_OUT%%/*}" ikey "${PLUTO_MARK_IN%%/*}"
      ip link set "${VTI_IF}" up
      ip addr add ${PLUTO_MY_SOURCEIP} dev "${VTI_IF}"
      ## routing / ルーティングはVIF削除で一緒に消える
      vpn_routing
      # firewall
      add_nft
      ;;
    down-client)
      remove_nft
      ip tunnel del "${VTI_IF}"
      ;;
  esac
}


function main(){
  show_env
  link_up_down $@
}




if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

起動と終了

ipsec restart
ipsec up myconn
ipsec status
ipsec down myconn
ipsec status

接続の確認

ping 1.1.1.1
tcpdupm -i vtiX host 1.1.1.1
tcpdupm -i pppoe host vpn.example.com

vtiXのパケットは中身が見えていて、pppoe 経由だとUDP-encap: ESP になっていれば暗号化するトンネルと経由している。

インターリンクのVPNサービスにLinux(ubuntu)のstrongswanから接続する。

strongswan でIKEv2を接続する

UbuntuでStrongswanをインストールして、固定IPサービスを利用する。

MyIP 契約 を用意する

インターリンクMyIPの契約をすると、グローバル・アドレスが直接割り当てられる。

ちなみに、グループ専用VPN では、1つのグローバルIP(私の契約の場合はさくらVPSだった)を複数人で共有する仕様なので、IKEv2でVPNを貼ると、GWとローカルアドレス10.0.0.0が与えられる。

仕様が異なるので注意する。

今回試すのは、個人がつかうMyIPの方である。

インストール

今回はraspi ubuntuを用いた

sudo apt update 
sudo apt install strongswan libstrongswan-extra-plugins

証明書のコピー

sudo cp /etc/ssl/certs/Security_Communication_RootCA2.pem  /etc/ipsec.d/cacerts/

証明書のコピーはクライアント側から、vpn サーバーがなりすましでないこと(本物)であることを確認し、IKEv2のフェーズ1を完了するため。pubkey(RSA公開鍵とCA署名)を使って検証するために必要になる。これは rightauth=pubkeyrightid=interlink.or.jpの設定で使っている。

ipsec を設定する

/etc/ipsec.conf

# ipsec.conf - strongSwan IPsec configuration file
# basic configuration
config setup

# 接続設定
conn interlink
 # 
 keyexchange=ikev2
 right=myip12345.interlink.or.jp
 rightid=%interlink.or.jp # ここは必ず。
 rightsubnet=0.0.0.0/0
 rightauth=pubkey
 type=tunnel

 leftauth=eap
 #leftauth=eap-mschapv2 # ←動かない
 #leftauth=eap-md5 # ← eap-md5なら動く
 leftsourceip=%config4
 eap_identity=mi123456
 auto=add
 mark=219
 leftupdown=/etc/ipsec.d/updown/interlink-vpn.sh

/etc/ipsec.secrets

# for ikev2 of eap
mi123456 %interlink.or.jp : EAP "MyPassword"

VTI を用意する

strongswanのdocを参考にvti のスクリプトを用意する。

このファイルは、ipsec.confleftupdownの値で指定したPATHのもの。

#!/usr/bin/env bash

set -o nounset
#set -o errexit

VTI_IF="vti${PLUTO_UNIQUEID}"

case "${PLUTO_VERB}" in
  up-client)
    ip tunnel add "${VTI_IF}" mode vti \
      local "${PLUTO_ME}" remote "${PLUTO_PEER}" \
      okey "${PLUTO_MARK_OUT%%/*}" ikey "${PLUTO_MARK_IN%%/*}"
    ip link set "${VTI_IF}" up
    ip addr add ${PLUTO_MY_SOURCEIP} dev "${VTI_IF}"
    ip route add 1.1.1.1 dev "${VTI_IF}" # 確認用サンプル
    #iptables -I FORWARD -o "${VTI_IF}" -s 172.16.9.0/24 -j ACCEPT
    sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1"
    ;;
  down-client)
    # iptables -D FORWARD -o "${VTI_IF}" -s 172.16.9.0/24 -j ACCEPT
    ip tunnel del "${VTI_IF}"
    ;;
esac

/etc/strongswan.conf

# strongswan.conf - strongSwan configuration file
# (略)
charon {
    load_modular = yes
    plugins {
        include strongswan.d/charon/*.conf
    }
}

include strongswan.d/*.conf

## 2023-08-30
##   include の結果を上書き
charon {
  install_routes = no
  install_virtual_ip = no
}

設定を反映

sudo ipsec restart

接続と切断

sudo ipsec up interlink
sudo ipsec down interlink

経路の確認

ルートがVTI経由になってることを確認

ip route get 1.1.1.1

vti 経由になっている、

1.1.1.1 dev vti1 src xxx.xxx.xxx.xxx uid 1000
    cache

通信の確認

ping送信して、vtiでは生パケットが見えて、eth0ではESP(暗号化)パケットになることを確認

ping -t 2 1.1.1.1

vtiX では「普通」のパケットが見えることを確認

sudo tcpdump -n -i vti1  host 1.1.1.1
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on vti1, link-type RAW (Raw IP), snapshot length 262144 bytes
04:20:00.968755 IP xxx.xxx.xxx.xxx > 1.1.1.1: ICMP echo request, id 20, seq 13, length 64
04:20:00.982322 IP 1.1.1.1 > xxx.xxx.xxx.xxx: ICMP echo reply, id 20, seq 13, length 64

xxx.xxx.xxx.xxxVPNで割当られた固定IPアドレスvti1 は作成された VTIデバイス。ただし、vti1,vti2,vti3 のように接続切断で数字が変化する。ipsec restart で1 に戻る。

eth0 上ではパケットが「カプセル化(暗号化)」されてトンネルに入ってることを確認。

sudo tcpdump -n -i eth0 host yyy.yyy.yyy.yyy
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on macvlan0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
04:22:13.107454 IP 192.168.0.222.4500 > yyy.yyy.yyy.yyy.4500: UDP-encap: ESP(spi=0xc2bd1f42,seq=0x29), length 136
04:22:13.121220 IP yyy.yyy.yyy.yyy.4500 > 192.168.0.222.4500: UDP-encap: ESP(spi=0xc8a1a75f,seq=0x38), length 136
04:22:17.109161 IP 192.168.0.222.4500 > yyy.yyy.yyy.yyy.4500: UDP-encap: ESP(spi=0xc2bd1f42,seq=0x2a), length 136
04:22:17.122883 IP yyy.yyy.yyy.yyy.4500 > 192.168.0.222.4500: UDP-encap: ESP(spi=0xc8a1a75f,seq=0x39), length 136

yyy.yyy.yyy.yyy は myXXX.interlink.or.jp のAレコード(IKEv2 の接続先)

192.168.0.222ipsec クライアントの eth0 アドレス。

暗号化対象(xfrm)の確認

linuxipsecではXFRMで暗号化されるので、xfrm サブコマンドで暗号化される経路と、本当に通信でつかう(暗号化パケットが通る)経路を確認する。

sudo ip xfrm policy

コマンド実行例

src xxx.xxx.xxx.xxx/32 dst 0.0.0.0/0
        dir out priority 383615
        mark 0xdb/0xffffffff
        tmpl src 192.168.0.222 dst yyy.yyy.yyy.yyy
                proto esp spi 0xc2bd1f42 reqid 1 mode tunnel
src 0.0.0.0/0 dst xxx.xxx.xxx.xxx/32
        dir fwd priority 383615
        mark 0xdb/0xffffffff
        tmpl src yyy.yyy.yyy.yyy dst 192.168.0.222
                proto esp reqid 1 mode tunnel
src 0.0.0.0/0 dst xxx.xxx.xxx.xxx/32
        dir in priority 383615
        mark 0xdb/0xffffffff
        tmpl src yyy.yyy.yyy.yyy dst 192.168.0.222
                proto esp reqid 1 mode tunnel
                

このコマンドの結果では、クライアントからの送信(src=x.x.x.x)と、相手側からの応答(src=0.0.0.0/0)がにマッチしたパケットが実際に通る暗号化経路(y.y.y.y ⇔ 192.168.0.222) が示されている。

ただし、今回は、updown スクリプトsysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1"をしているので、vtiを通るパケットは上記から除外される。vtiを出たときに暗号化経路に置き換えられる。

vtiで使いやすい。

ipsec はESPとxfrm があって、経路が暗黙的(透過的)に暗号化されるのであるが、vtiをつかうことでppptpやpppoeデバイスと同じ使い方になるので、扱いやすい。ファイアウォールなども書きやすい。

明示的に接続してつかう場合や、特定IPアドレス宛だけを暗号化トンネルを通したいときに便利だと思う。ipsecを使っていると、暗黙的な暗号化なので、どの経路を通ったら暗号化されるのかが分かりづらく、実際につかうときはクライアント全部をトンネルを通る使い方(スマホやPCが全部トンネルを通る)になりがちである。

それと比べるとvtiを使えば、指定IPアドレス(IPネットワークアドレス)をvti経由にすることで、暗号化と非暗号化を共存できるし、サイト間の通信を確実に暗号化出来て便利だと思った。

今回の設定により、MyIPサービスとipsec,vtiを組み合わせて、自分のVPSサーバーに接続する際にだけ、VPNを通るように設定することが出来て、VPSサーバは固定アドレスを対象にポート開放が可能になった。

インターリンクのサービスでの違い

インターリンクのMyIPサービスグループ専用VPNではサービス提供の形式が違うのと、認証が異なるので注意する。

leftauth=eapで認証され利用可能になる。ただ、MyIPサービスは eap-md5 で認証接続する。一方でが グループ専用VPNサービスは eap-mschapv2 で認証していた。

また、MyIPサービスはPPP的にグローバルIPが割り当てられるのにたいし、グループ専用VPNサービスはローカルアドレス(10.x.x.x)が割り当てられGW(10.x.x.1)が存在しグローバルIPはGW経由で外部に接続したときに使われる。

また、MyIPサービスはPPP的だがDNS割当がない。グループ専用VPNサービスはDNS割当がある。

また、どちらもOP25Bについては対象外になっているようです。

上記のような違いがあった。

VPNサービスと価格で考えた場合。

さくらVPSに、自分でWireguardやOpenVPNを構成できるなら、月額課金より自分で構成しちゃったほうが楽ちんだと思ったり。課金するだけでVPNと固定IPが誰でも使えるのが魅力的ですけどね。

メール関連の運用には

固定IPでメール関連を運用しようと思うと大変ですよね。

参考資料

nft によるMSQUERADE(iptables MASQUERADEからの移行)

nft によるMSQUERADE(iptables MASQUERADEからの移行)

iptablesでマスカレード(またはSNAT)は古くから行われている枯れた手法。これをnft(nftables)に書き換える場合のメモ

次のような、マスカレード(NAT)をiptablesで追記しているとする。

iptables -I FORWARD -o ${interface} -j ACCEPT
iptables -t nat -A POSTROUTING -o ${interface} -j MASQUERADE

たとえば、このように。ルーティングと組み合わせて使ってる。

DEST_NET=10.1.1.0/24
interface=vpn0
router=10.1.1.1.1

ip route add ${DEST_NET} via ${router} dev ${interface}
##
iptables -I FORWARD -o ${interface} -j ACCEPT
iptables -t nat -A POSTROUTING -o ${interface} -j MASQUERADE

これをnft(nftables)に書き換えてみると。

nft コマンドでは次のようになる。 テーブル追加とチェインの追加、そして、ルールの追加である。

interface=vpn0
router=10.1.1.1.1

TABLE=my_vpn_nat
nft add table ip ${TABLE};
# 冗長じゃない?
nft add chain ${TABLE} prerouting { type nat hook postrouting priority srcnat \; }
nft add chain ${TABLE} postrouting type nat hook postrouting priority srcnat accept;
nft add rule  ${TABLE} postrouting oifname ${interface} masquerade 
nft add rule  ${TABLE} postrouting oifname ${interface} masquerade 

テーブルの追加

nft add table ip ${TABLE};

一般形式は次のようになっていて。

nft add table <?family> <name>

<?family> にはip, ip6, inet ,arp, bridge が使える。省略時は ip である。ipはv4 アドレスを意味する。inet はinet = ip + ip6であり、v6/v4に無関係(双方)を指す。

テーブルを削除する場合 add を delete にかえるだけである。(2023-08-29 現在 del ではエラーになる)

nft delete table <?family> <name>

サンプル

nft add table my_sample
nft list tables # チェック
nft delete table my_sample
nft list tables # チェック

チェインの追加

チェインは、テーブル内部に作られる。チェインの中にはパケットのルールが入る。ルールをグループにして一括りにする。

nft add chain <?family> <table> <chain_name> { \
  type <type> hook <hook> priority <priority> \; \
  policy <policy> \;
}

hook はチェインが実行されるタイミング。type は何をするか。

{ ... } で括られた部分が、チェインに入るruleである。

ip/ip6/inetにおいては、

  • hook={prerouting, input, forward, output, postrouting} から一つ.
  • type={filter,nat,route} から一つを選ぶ。
  • policy ={accept,drop} から一つを選ぶ。
  • priority は、他のチェイン・ルールとの優先度±1 を書く。基本は0を書いておく。

ベースチェインの候補は、テーブル名やチェイン名と混同するような類似キーワードが多く初見殺しである。

以下をよく覚えておく。

type <type_name> hook <hook_name> priority <num|name>

例えばよくあるサンプルでは次のようになっている

nft add chain vpn_nat postrouting { type nat hook postrouting priority srcnat; policy accept; }   

この場合、ベースチェインの名前=postrouting となり、hook = postrouting であり、type=nat であり、priority=srcnatである。postroutingnatが、名前にもキーワードにも登場し、ややこしくなる。これが初見殺しである。

ベースチェインとはiptablesでも使っていた、mangle や nat のような特定パケットに一致する機能である。

ベースチェインのタイプ未指定にしたら、単なる関数になる。独立したチェインとして登録され、他チェインから呼び出されるために存在できる。

## ベースチェイン(タイプあり)の例
nft add chain ip MyMangle prerouting { type filter hook prerouting priority mangle \;}
## 単純チェイン(ベースタイプなし)の例
nft add chain ip MyMangle my_chain

詳しくは、公式wikiを参考にする。

ベースチェインは「パケットが条件一致したら呼び出される手順」、関数は「他チェインのルールから使われる手順」という違いがある。

マスカレード対象のフォワードをAcceptする。

iptable の場合は次のように先頭に書けば良い。

iptables -I FORWARD -o $TARGET_IF -i br0 -j ACCEPT

nftables の場合は次のように、対象のチェインに追記すると良い

nft insert rule inet fw4 forward iifname br0 oifname $TARGET_IF accept;

nftablesの場合は、同じチェイン内でaccept したら打ち止めになるが、ほかテーブルを参照している場合、他テーブルでDROPされる可能性もあるので注意する。デフォルトがDROPなどであれば、add は使わずに、insert でチェイン先頭にacceptを追記するようにする。

tailwindcss でチェックボックスやラジオボタンと連携させる。

tailwindcss でチェックボックスと連動させる

チェックボックスラジオボタンのチェック状態(疑似セレクタ:check)と連動させることができる。

.peer/{$name}:checked ~ .peer-checked/{$name}:{$tailwindcss-class}

tailwindcss で「実数とネガティブ」(マイナス値)なマージンを入れる。

参考資料によると、ネガティブな値を使えるようです。

実数には、正数とネガティブ値(マイナス値・負数)がが使える。

translate-x-[-2rem]
translate-x-[2rem]
pl-[32px]
py-[10%]

など、具体的な数字をはめ込む事ができる。

まので、マイナスマージンなども作ったりできる。

チェックボックスを改良してみてみる。

これを使って、チェックボックスを使ってラベルを一体化させて、ボタンっぽいものを作ってみる。

      <label for="status" class="cursor-pointer ml-[-1rem] select-none">
        <input class="peer/status inline-block translate-x-[2rem]" id="status" type="checkbox" name="status"  />
        <div class="
          w-[8rem]
          pl-[3rem]
          hidden
          peer-checked/status:inline-block
          peer-checked/status:bg-blue-500
          peer-checked/status:hover:bg-blue-700
          text-white font-bold py-2 px-4 rounded "
        >Enabled</div>
        <div class="
          inline-block
          w-[8rem]
          pl-[3rem]
          peer-checked/status:hidden
          bg-gray-500 hover:bg-gray-70
          text-white font-bold py-2 px-4 rounded "
        >Disabled</div>
      </label>

UIコンポーネントにありがちな、チェックボックスとラベルが一体化したボタンもこの通り、それっぽいものが作れた。

なるほど、tailwindcss は完成されている。

これ、transiionと組み合わせて、ラジオボタンに変えれば、トグルスイッチも作れそうですよね。

オープンソースのWebDAVサーバー

WebDAVオープンソースなサーバは無いのか

WebDAVサーバーを設置しておけば、HTTPSでファイルがアップロードが出来て便利だと思う。

ファイルの管理や一覧が便利なので、ある程度扱いやすいと思う。

いまさら、Apache+mod_dav も不便だよなぁ。と考えてたら、PHPwebdav 機能があり、もしかしたらと思った。

nextcloud が対応してた。

Nextcloudが、webDAV機能を持っていた。Nextcloudで完結した。

クライアントはWinSCPでも行けた

WebDAVサーバーの今後

HTTPSしか通してくれない公衆WiFiが乱立しているいま、443ポートがプロトコルの坩堝になっているので、ファイル転送もSCP・SFTPではなく、443を通さないと駄目だよなぁ。TCP/443 でSSHdをリッスンしておくしかないんだろうな。

sqlite3 を使っていると遭遇するロック・エラー database is locked

sqlite3 を使っていると遭遇するロック・エラー

sqlite3 を使っていると、トランザクションでデータベースがロックされて、書き込みに失敗することがある。phppythonruby でもなんでも良いけど、トランザクションを使ってるときに、ロックが行われてdatabase is locked という無情なエラーになる。

これは、SQLite3のエラーであり、プロセス間でもロックすることがある。

たとえば、次のエラーはruby で再現したものである。

sqlite3/database.rb:281:in `step': database is locked (SQLite3::BusyException)

再現コードを作る

サクッとruby で再現したものである

mkdir lock-test
cd lock-test
##
bundle init
bundle config set --local path 'vendor/bundle'
bundle config set --local disable_shared_gems true
bundle add sqlite3

ソースを書く lock-error-sample.rb

require 'sqlite3'
require 'openssl'
require "thread"

DSN = 'lock-test.db'

def create_table
  db = SQLite3::Database.new(DSN)
  ct = <<~SQL
  DROP TABLE IF EXISTS log;
  CREATE TABLE IF not Exists log (
      id integer primary key autoincrement,
      name string unique
  );
  INSERT INTO  log ( name ) values( 'aaaa' );
  SQL
  db.execute_batch(ct)
end

def insert_test_data
  ## sqlite3/database.rb:281:in `step': database is locked (SQLite3::BusyException)
  db = SQLite3::Database.new(DSN)
  db.transaction
  a = [OpenSSL::Random.random_bytes(12)].pack("m*").strip
  ct = <<~SQL
    INSERT INTO  log ( name ) values( '#{a}' );
  SQL
  db.execute_batch(ct)
  Thread.pass
  sleep 1
  db.commit
end

create_table
threads = []
threads << Thread.new{|t|insert_test_data }
threads << Thread.new{|t|insert_test_data }
# SQLite3::BusyExceptionになる。
threads.each{|t| t.join  }

実行すると。

bundle exec ruby lock-error-sample.rb

エラーになる。

 `step': cannot commit - no transaction is active (SQLite3::SQLException)

デッドロックが起きる。

スレッドを使って、Thread.passするタイミングでロックしたまま、別スレッドに実行を切り替えている。

  db.transaction
  db.execute_batch(ct)
  sleep 1 #   Thread.passが起きる
  db.commit

sleep 時には Thread.passが実行されるので、確実にロックしたまま別のトランザクションに入ることになる。別のトランザクションでもロックしたままなので双方がデッドロックする。これにより、双方でエラーが起きて実行が終了する。

ロックが開放されるのを待つ。

sqlite3 を使う場合は、ロック解放を待つことができる。

db.busy_timeout = 1000

初期値では0らしいので、ロック解放を待たずに、ロック取得に失敗したらイコール即エラーになる。

リトライを入れる。

エラーになる箇所をはロックしたままになる箇所なので。

  db.transaction
  sleep 1.0
  db.execute_batch(ct)
  sleep 1.0
  db.commit

排他ロックが取れるまで、再起動すればいいと思われる。

  db = SQLite3::Database.new(DSN)
  db.busy_timeout = 1000*3
  db.transaction
  begin
    sleep 1.0
    db.execute_batch(ct)
    sleep 1.0
  rescue  SQLite3::BusyException => e
    retry
  ensure
    db.commit
  end

ruby の場合は、retry という便利すぎるエラー処理があるので、処理は楽かもしれない。phpだとwhile でロック状態を認識して再起動しないと駄目だろうね。

解決策1

ロック待ちをしつつリトライもする。

db.busy_timeout = 1000

解決先2

そもそも、トランザクションを使わない。

実行順番が確実であるひつようがないのであれば(たとえばログなど)、念のためのトランザクションなど不要であり、SQLiteの実装に任せれば良いのである。とてもいい加減な解決と思うかもしれないが、案外良い解決方法です。

解決策3

EXCLUSIVE / IMMEDIATE / DEFERRED を変える。

トランザクションに引数を与えればロックレベルを変えられる。

db.transaction :immediate ... 
db.transaction :deffered ... 
db.transaction :exclusive ...

ただ、sqltie のトランザクションEXCLUSIVEで別プロセスからもロックしたりできる。とあるが、sqlite3 sample.db を複数起動してもとくにロックされなかった。

(上下でユニーク違反になるに同一レコードを登録したが、どちらも正常にCommitできてしまった。)

細かいところを調べるのは時間がないので後回しにする。

余計なトランザクションを書かない。

SQLiteをつかう限りにおいてログなど単なる追記に、保険なトランザクションを書かないのが良いかもしれない。

参考資料

nvmeの大きめヒートシンクを導入してその効果を検証する。

昨年・一昨年、nvmeが死にまくったので、ヒートシンク導入した

nvme故障連発した。夏場のPCのnvmeが高温で死んでるかもしれないので、今年から大きめのヒートシンクを導入している。

導入したヒートシンクは、ヒートパイプつきでファンは無いけど、CPUファンの風が当たるのでOKとする。

GRAUGEAR G-M2HS06

ポイントは、商品寸法 (長さx幅x高さ) 8.6 x 2.5 x 3.4 cm である。高さが低いのでCPU用の巨大ヒートシンクと干渉しないと思われる。

これに近い類似商品も購入したが、この商品が明らかに加工精度が良かった。

実際に取り付けたところ

ヒートシンクSSDの間に、サーマルパッドを挟んでいる。

SSD本体を包み込むようにし、先に覆ってしまってからPCへ取り付ける。なので、SSDを取り付けたあとにヒートシンクを取り付けるようなことはしない。

梅雨明け真夏の気温測定

1年で一番暑いと思われる、7.25-8.10 で、その中間である、8/1 日の温度記録である。PCの設置場所は変えてない。

2022 年(令和四年)の梅雨明け後

2023 年(令和五年)の梅雨明け後

ヒートシンク有無で、昨年と今年でnvme の温度は低下している。確実に低下している。今年こそ故障無しで乗り切りたい。

購入したヒートシンク

mattermost でファイルのアップロードができない。

ファイルアップロードで失敗する

mattermostでファイルをアップロードしようとすると、アップロードで止まってしまう。

{
"create_post.fileProcessing": "処理しています...",
}

処理していますは、とmattermostのソースコードで日本語化ファイルをみる"create_post.fileProcessing": が相当することがわかる。"create_post.fileProcessing" でmattermostのフォーラムを探し回ると、mattermost側の設定とnginx側の設定の話が出てくる。

ログを見てみると、nginx がエラー

Too Larget なので制限超過ですね。

ファイルアップロードといえば、nginx の client_max_body_size だと思うんだけど。どこだろう。

nginx の構成

うちは、nginx は多段構成になっている。

gw -> nginx (main) -> nginx(gitlab-mattermost)

gitlab 内蔵のnginxではなく、その手前において仕分けてる nginx 側に問題がありそう。

after

以前は、location /api の内部に client_max_body_size を記入していて問題なかったが、最近のアップデートで、api/*/websocket 以外でアップロードしている気がする。公式の設定サンプルを見ると、location /location /api両方max size が記入されている。なるほど。だったら共通なので外側に出せばいいですよね。

server {
  ## 略
  ## これらを location 外部へ
  client_max_body_size 50M;
  client_body_timeout 60;
  server_name mm.example.tld;
  ## 略
  location ~ /api/v[0-9]+/(users/)?websocket$ {
     ## 略
  }
  location / {
     ## 略
  }
}

再起動

sudo gitlab-ctl restart nginx
sudo gitlab-ctl restart mattermost

アップロードできた

無事にアップロードできた。

NTT フレッツ HGW(RP-500MI)のvpnサーバーに iOS/iPhoneからつなぐ

NTT のホームゲートウェイVPNサーバー設定

NTTのホームゲートウェイにはVPNサーバ機能がついている機種があり(PR-500系)、L2TP/IPsecで接続することができる。

ユーザを作って、共有鍵を取得しておく

iOSVPN画面から

接続先・ユーザ名・パスワード・IPSec共有鍵 を設定すると接続ができる。

接続は時間がかかる。

接続は、少々時間がかかる。体感で数十秒位かかってるイメージ。

自動接続

自動接続はiOSプロファイルを使えば可能になるが、NTTのHGW相手VPNは接続の立ち上がりが遅い(IPSecがどうもおそい)ので、自動接続で使うのは向いてないと思う。

iOSの構成プロファイルのOnDemandEnableを使えば、CellularやSSIDごとに接続ができて良いのだが。

<key>OnDemandEnabled</key>
<integer>1</integer>
<key>OnDemandRules</key>

接続確立までに時間がかかるので、どうもうまく行かないし、速度も出ないので手動で使うほうが良いと思った。

L2TP/ipsecのvpnサーバーに OpenWrt からつなぐ

OpenWrt からL2TP/IPsecにつなぐ

接続相手は、NTT フレッツ HGW(RP-500MI)のVPNサーバー。

基本的にはNTTのHGWにUbuntuでL2TP/IPSecで繋いだのと同じ法

インストール

opkg install xlt2ptd strongswan

動作確認したときのインストール状態

strongswanは次のパッケージを入れている。(IKEv2とかも使える状態)

strongswan strongswan-charon strongswan-default strongswan-ipsec strongswan-mod-aes strongswan-mod-attr strongswan-mod-connmark strongswan-mod-constraints strongswan-mod-des strongswan-mod-dnskey strongswan-mod-eap-identity strongswan-mod-eap-mschapv2 strongswan-mod-fips-prf strongswan-mod-gcm strongswan-mod-gmp strongswan-mod-hmac strongswan-mod-kernel-netlink strongswan-mod-md4 strongswan-mod-md5 strongswan-mod-nonce strongswan-mod-pem strongswan-mod-pgp strongswan-mod-pkcs1 strongswan-mod-pubkey strongswan-mod-random strongswan-mod-rc2 strongswan-mod-resolve strongswan-mod-revocation strongswan-mod-sha1 strongswan-mod-sha2 strongswan-mod-socket-default strongswan-mod-sshkey strongswan-mod-stroke strongswan-mod-updown strongswan-mod-vici strongswan-mod-x509 strongswan-mod-xauth-generic strongswan-mod-xcbc strongswan-swanctl

l2tp 関連は次のパッケージを入れている

kmod-l2tp kmod-pppol2tp ppp-mod-pppol2tp xl2tpd

ppp 関連は次のパッケージを入れている

kmod-ppp kmod-pppoe kmod-pppol2tp kmod-pppox luci-proto-ppp ppp ppp-mod-pppoe ppp-mod-pppol2tp

ipsec の接続

/etc/ipsec.conf にinclude で次のファイルを読むように指示

include /etc/config/custom/strongswan/ipsec.conf

/etc/config に設置するようにした。

/etc/config/custom/strongswan/ipsec.conf で接続先毎にフォルダを作って格納することにした。

include ./www.example.tld/ipsec.conf
include ./interlink-vpn/ipsec.conf
include ./server/ipsec.conf

相対パスは、include時点のファイル自身(/etc/config/custom/strongswan/ipsec.conf)から見た相対パスになる。

./www.example.tld/ipsec.conf    (/etc/config/custom/strongswan/www.example.tld/ipsec.conf)

conn www.example.tld
  type=transport
  authby=secret
  rekey=yes
  keyingtries=1
  keyexchange=ikev1
  ike=aes256-sha-modp1024
  esp=aes256-sha1
  left=%any4
  leftid=@my1
  leftprotoport=udp/l2tp
  right=www.example.tld
  rightid=%any
  rightprotoport=udp/l2tp
  dpdaction=restart
  closeaction=restart
  auto=add

left にドメインを指定するとき、%any4 をつけないと、AAAAが優先されIPv6で繋いでしまうので注意。

今回は、NTTのホームゲートウェイ(HGW)を相手に接続するのでV4アドレスが必須である。

共有鍵も同じように、接続ごとにフォルダに分ける。

/etc/ipsec.secrets

# include
include /etc/config/custom/strongswan/ipsec.secrets

/etc/config/custom/strongswan/ipsec.secrets

include ./interlink-vpn/ipsec.secrets
include ./server/ipsec.secrets
include ./www.example.tld/ipsec.secrets

include の相対パスは、conf と同じく、include記載ファイルからの相対パスで解釈される。

./www.example.tld/ipsec.secrets    ( = /etc/config/custom/strongswan/www.example.tld/ipsec.secrets)

@my1 : PSK 3vpWkK7K

leftid=@my1と記載したので 共有鍵のIDに同じ名前を付与する。

接続する

ipsec reload  # conf を再読み込み
ipsec rereadsecrets  # secrets を再読み込み
ipsec up www.example.tld # 接続
ipsec status  www.example.tld  # 状況表示

no shared key found になる場合は、secrets の PSK の記載方法が間違っているので、strongswan のマニュアルにあるIDSelector について確認すること

xl2tpd の接続

/etc/xl2tpd/xl2tpd.conf

[lac] を追加

[lac www.example.tld ]
lns = www.example.tld 
require authentication = yes
pppoptfile = /etc/ppp/options.l2tpd.www.example.tld 
length bit = yes
require chap= yes
refuse pap = yes
redial = yes
redial timeout = 10
max redials = 6

/etc/ppp/options.l2tpd.www.example.tld

パスワードとnoauthを追加

name takuya
password  myPassword
mtu 1280 # この値は適当、ちゃんと計算するべき
noauth

再起動

service xl2tpd stop
service xl2tpd start

接続

xl2tpd-control -d connect-lac www.example.tld 

うまく行かないときは、ログを見ながら( logread -f )設定を見直す。

ppp の確認

接続確立したら ppp が生えている。

ip a s ppp1
23: ppp1: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 3
    link/ppp
    inet 192.168.10.249 peer 192.168.10.1/32 scope global ppp1
       valid_lft forever preferred_lft forever

luci でファイアウォールの設定

luci ではアンマネージドにしておくと、存在だけがわかるので良いかもしれない。

パケットの経路に関してはこのデバイスをターゲットに色々と書けば良い。

luci webui で接続管理

luci で接続管理ができるようなWEB-UIパッケージを探したけど、見つからない。

opkg find luci-proto* | cut -d ' ' -f 1
luci-proto-3g
luci-proto-autoip
luci-proto-bonding
luci-proto-gre
luci-proto-hnet
luci-proto-ipip
luci-proto-ipv6
luci-proto-modemmanager
luci-proto-ncm
luci-proto-nebula
luci-proto-openconnect
luci-proto-openfortivpn
luci-proto-ppp
luci-proto-pppossh
luci-proto-qmi
luci-proto-relay
luci-proto-sstp
luci-proto-vpnc
luci-proto-vxlan
luci-proto-wireguard
luci-proto-xfrm

luci-proto-ppp はそれっぽい気もするが、PPPoE かPPP(ttyX)が対象で使えなかった。これと言った管理はできないので、アンマネージドで存在だけ見ておいて、あとはスクリプトで設定したほうが良さそうだった。

自動接続

自動接続に関しては、スクリプトとhotplugi.d を駆使して行えばいいだろう。

PPPoEが接続されたタイミングで、L2TP/IPSecを繋げば良い。スクリプトを追加すれば良い

ipsecに関しては、auto=addauto=start にしてもいい。

わたしは、必要虹つなげばいいとおもったので、今回は自動化をしない。

その他細かいipsec/xl2tpd/pppの設定 で切断時の自動的な再接続を制御できる。

手動接続

ipsecを繋いでから、pppをつなぐ。

接続

ipsec up www.example.tld
xl2tpd-control -d connect-lac www.example.tld

切断

xl2tpd-control -d disconnect-lac www.example.tld
ipsec down www.example.tld

手動で接続し切断できれば十分であるはずである。

ルーティング

接続後にルーティングを入れたりDNS参照を変えたりする必要がある場合はip route などを使って制御する。

xl2tpd/ppp のオプションでpppXデバイスが作られたタイミングで、IP割当やルーティングを行うスクリプトを指定して毎回同じ設定をロードしてもいい

関連記事

OpenWRT で繋ぐ前に、Ubuntuで使い方を調べたときの記事

IPsecでの暗号化の確認。xl2tpd で接続する方法とデバッグ起動などを調べてある。

参考資料

NTT フレッツ HGW(RP-500MI)のvpnサーバーに ubuntu からつなぐ

NTT フレッツ HGW(RP-500MI)のvpnサーバーに ubuntu からつなぐ

NTT が提供する ホームゲートウェイ・光コンバータ一・ひかり電話の一体型機器にはVPNサーバー機能がある。

NTT 東

NTT西

次のようにVPNサーバーのメニューが提供される

このVPNサーバー(L2TP/IPsec)にUbuntuから接続してみる。

準備

ホスト名のかわりに/etc/hsotsを使う。

cat <<EOF >> /etc/hosts
10.2.10.10 pr500.mi
EOF

ipsc の準備と接続

インストール

sudo apt install strongswan

ipsec 設定 - /etc/ipsec.conf - /etc/ipsec.secrets

コマンドから流し込む

## 設定
cat <<EOF >> /etc/ipsec.conf
conn vpn
  type=transport
  authby=secret
  rekey=yes
  keyingtries=1
  keyexchange=ikev1
  ike=aes256-sha-modp1024
  esp=aes256-sha1
  left=%any
  leftprotoport=udp/l2tp
  right=pr500.mi
  rightid=%any
  rightprotoport=udp/l2tp
  dpdaction=restart
  closeaction=restart
  auto=add ## 手動の場合
  # auto=start ## 自動接続をする場合

EOF
## 共有鍵
cat <<EOF >> /etc/ipsec.secrets
: PSK 3vBhtdpWkK7Ku4PxQnnj

ipsec の接続テスト

systemctl reload ipsec
ipsec status vpn
ipsec up vpn
ipsec down vpn
ipsec up vpn

接続の確認

Security Associations (1 up, 0 connecting):
         vpn[60]: ESTABLISHED 54 minutes ago,        

接続が確率(Established)すればOK。

これで SRC/UDP/1701 から DST/UDP/1701への通信が双方向で暗号化される。

細かく確認したいときは 過去記事:パケットがトンネルに入るか否かを確認する。(l2tp/ipsc)を参考にする

L2TP を接続する

インストール

sudo apt install xl2tpd 

xlt2pd の設定

cat <<EOF >> /etc/xl2tpd/xl2tpd.conf  

[lac pr500mi]
lns = pr500.mi
require authentication = yes
pppoptfile = /etc/ppp/options.l2tpd.pr500.mi
length bit = yes
require chap= yes
refuse pap = yes
ppp debug = yes
redial = yes
redial timeout = 10
max redials = 6
EOF

LACで使うPPP設定

cat <<EOF > /etc/ppp/options.l2tpd.pr500.mi
name my-user
password  my-password
mtu 1280
noauth
EOF

接続テスト

CTL=/var/run/xl2tpd/l2tp-control
sudo xl2tpd-control -d -c $CTL  available
sudo xl2tpd-control -d -c $CTL  connect-lac pr500mi
sudo xl2tpd-control -d -c $CTL  disconnect-lac pr500mi
sudo xl2tpd-control -d -c $CTL  status-lac pr500mi

うまく接続できないときは、自動起動を使わずに手動で起動する

## 自動起動を止めて
systemctl stop xl2tpd
systemctl disable xl2tpd
## 手動で起動する
/usr/sbin/xl2tpd -D \
   -c /etc/xl2tpd/xl2tpd.conf  \
   -C /var/run/xl2tpd/xl2tpd-control
## ログを見ながら試す
CTL=/var/run/xl2tpd/xl2tpd-control
xl2tpd-control -d -c $CTL  available
xl2tpd-control -d -c $CTL  connect-lac pr500m

無事に接続できた。

接続済みになるはずである。

IPアドレスがわからなくなる。

NTTのHGWのPPPoEのIPアドレスがわからなくなるので、通知をSMTPですることが可能だが。メールでIPを通知させることができる。

ただこれ見てる限り、SMTPSには未対応っぽいので、現在ではまるで使えないと思う。

v6 アドレスについて

NTTの仕様を確認したが、v6アドレス(フレッツ網v6・IPoEアドレス)でVPNを張ることはできない。

ただし、IPv6・PPPoEであれば接続が可能かと思われる。

raspi などでddns

ddns(mydns.jp など) を使うなり、ドメインを何処かホスティング(Cloudflare・Route53)するなりして、IPアドレスがわかるようにAレコードを更新するとか、Raspiで定期的に書き込みに行くしかしかない。

IPoE/IPv6環境であれば、v6アドレスは変わらないので、v6アドレス経由でSSHできるようにして、SSHしてからPPPoEのアドレスをコマンドで確認するしかない。

v6アドレスであれば、VPNなどしなくてもSSH環境が用意に作れるので、VPN環境をわざわざ作る必要もないが、SSH踏み台としてのRaspiがダウンしたときとか予備回線として有能である。

バージョン

利用したソフトウェアのバージョンについて。

apt で導入された一覧

strongswan/jammy-updates,jammy-security,now 5.9.5-2ubuntu2.1 all [installed]
xl2tpd/jammy-updates,now 1.3.16-1ubuntu0.1 arm64 [installed]
Ubuntu 22.04.2 LTS # /etc/lsb-release 
xl2tpd version:  xl2tpd-1.3.16 # xl2tpd -v 
Linux strongSwan U5.9.5/K5.15.0-1034-raspi # ipsec --version

速度テスト

簡易な速度テストの結果です。 VPNありだと、VPNなし(サーバクライアント側双方は60-80Mbps程度)に比べて15-20%程度に速度低下しました。MTUなどで改善の余地はあるかもしれないけど、NATやパケット化やカプセル化によるスループットの低下は相当大きいと思われる。

速度についてはISPのアップロード制限(PPPoE同士)やHGWの性能の上限にぶち当たる。といういくつかの要因が考えられる。

常用するVPNであれば、Wireguardや単純なIPSecやikev2のほうが良いかもしれない。

関連記事

参考資料