それマグで!

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

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

overlay を作るときは、work は upper と同じフォルダ(マウント)内部にある必要がある。

overlay fs が作れないで苦労した

overlay でファイルシステムを保護しながら書き込みを可能にしようとして戸惑ったのでメモ

sudo mount -a 
sudo dmesg | grep overlay

エラーだった

[58517.488953] overlayfs: workdir and upperdir must reside under the same mount

overlay の作り方

volatile=/path/to/tmpfs
upper=/path/to/tmpfs/upper
work=/path/to/tmpfs/work
base=/path/base
merged=/path/meged

mount -t overlay overlay -o lowerdir=$base,upperdir=$upper,workdir=$work $merged

ここの/path/workdir and upperdir must reside と書いてあるとおり、おなじマウント中に作る必要があった。

たぶん、tmpfs を想定してるのだろうが、tmpfs 内部にフォルダを作ってマウントするというのがめんどくさかった。

Bitlocker しないとかありえないんですけど / 多発するUSBメモリ紛失を見ていて思う

Bitlocker しないとかありえないんですけど

USBメモリは「BitLocker」で暗号化して使いましょう。

右クリックして、BitLockerで暗号化。これだけです。

後を絶たないUSBメモリの紛失

中学校教諭が生徒80人分の個人情報入ったUSBメモリー紛失

06月16日 20時07分

埼玉県日高市教育委員会は、中学校の教諭が生徒80人分の氏名や成績、宿泊学習の際の写真など、個人情報が入ったUSBメモリーを紛失したと発表しました。

日高市教育委員会によりますと、USBメモリーを紛失したのは、市立中学校に勤務する60代の教諭です。 https://toyokeizai.net/articles/-/153978

専用USBメモリじゃなくて暗号化を覚えてほしい。

怖がらせるな。正しい知識を教えろ

「教員」がミスをするということは、学生にも「間違って教えている」ということに他ならず。IT能力が必須である現代において、「セキュリティの勘違い」をすることは人生に多大な負債を背負う不事になる。

正しく怖がってほしい。ミスを憎むのではなく、「手段を教えてないこと」を後悔するべきなのです。いつまでも正しい知識を学習しないことを懸念するべきでしょう。

知識を教えるべき教育機関で、「ルールさえ守ればOK」という悪弊が蔓延っていることが問題なのです。

専用ハードを買うな・暗号化を覚えろ

暗号化機能つき専用ハードは「従来の使い方」を維持するために使うのであって、どうしても必要な場面は殆どありません。

ソフトウェア暗号化で十分です。

また、Excelのファイル暗号化は、AESを用いた強力なものなのでExcelのファイル暗号化も合わせて教えておけばいいだろう

セキュリティはルールではなく教育で担保する

セキュリティといえば、すぐにルールを決め打ちしますが、最初にするべきは「教育」です。

暗号化も教えずにルールを押し付けるのは、電子レンジの使い方を教えずに、ボタンの押し方だけ教えるようなものです。

右クリックで暗号化すら。覚える気がない教師は、減給でいいんですよ。

暗号化ファイルは読めません。

後を絶たない紛失ニュースのうち、暗号化ができてるけど、紛失として取り扱ったケースはいくつあるんでしょうか

暗号化したファイルは読めません。読めないから暗号化なんです。

ときどき間違う人がいますが、暗号化ファイルを紛失しても大丈夫です。

暗号化ファイルが「漏洩」しても中身は見れません。このあたりすら認知されてないのが困るんです。

参考URL

USBメモリ紛失は跡を絶ちません。

nginx で WebDAVを有効にする。

nginx で WebDAVを有効にする。

nginx の webdavの速度を試すのはありかも。 WebDAViOSからの唯一のデータ持ち出し手段だし。

webDAVの利点

現在のところ、SFTP/FTPSなどがiOSで手軽に使えなので、WebDAVがすべてのOSで一番手っ取り早いデータの同期方法である。

WebDAVHTTPSであり認証機構もあるので、持っておいて損はないと思われる。

WebDAVを用意する。

nginx のwebDAVを探すと、debianではlibnginx-mod-http-dav-ext が用意されていた。

sudo apt install libnginx-mod-http-dav-ext

インストールされたファイルを見てみる。

takuya@:~$ apt-file show libnginx-mod-http-dav-ext
libnginx-mod-http-dav-ext: /usr/lib/nginx/modules/ngx_http_dav_ext_module.so
libnginx-mod-http-dav-ext: /usr/share/doc/libnginx-mod-http-dav-ext/changelog.Debian.gz
libnginx-mod-http-dav-ext: /usr/share/doc/libnginx-mod-http-dav-ext/copyright
libnginx-mod-http-dav-ext: /usr/share/nginx/modules-available/mod-http-dav-ext.conf

なるほど、モジュールとして動的にロードされるようです。

通常のインデックスを設定する

最初に、nginx でインデックス(ファイル一覧)を表示するように設定

DAVのフォルダを用意する。

sudo mkdir /var/www/dav
sudo chown www-data: /var/www/dav/ -R

BASIC認証をかけておく

index を仕掛けたフォルダに、DAVをメソッドとして追加していく。

location / {
  # 必要であれば設定
  auth_basic "Enter Your ID/PW";
  auth_basic_user_file /etc/nginx/sample.digest;

  # 標準搭載のメソッドと追加モジュール分のメソッドの設定
  dav_methods PUT DELETE MKCOL COPY MOVE;
  dav_ext_methods PROPFIND OPTIONS;

  # ファイル一覧を表示する設定
  autoindex on;
  autoindex_exact_size off;
  autoindex_localtime on;

  # 読み書きの権限設定
  dav_access user:rw group:rw all:r;

  # 一時ファイル・ディレクトリ作成の許可
  client_body_temp_path /var/www/.webdavtmp;
  create_full_put_path on;
}

これで、DAVができるようになる。

認証方法

認証方式はなんでもいい。HTTPSを信用するならBASICでも構わない。

  • BASIC/DIGESTなど
  • lua など
  • auth_request など

Authorization: Bearerなどをつかうには lua サポートでも入れてみるといい。

TLS証明書を書き換えられるようならもう何でもありだし。管理を楽にするために auth_request とかで gitlab とかに投げておいてもいいかもしれない。

BASIC/DIGEST認証の例

とりあえず、簡単にパスワード認証を入れておく。

ユーザ名とパスワードを生成する

apache のdigest auth 互換のパスワードを生成するには次のようにする。

USERNAME=sample
PASSWORD=YOUR_PASSWORD
echo "$USERNAME":$(openssl passwd -apr1 $PASSWORD)

パスワードを生成の機構について

openssl のところで、ハッシュ値を生成し、base64にしている。Apache互換に指定している。

openssl passwd -apr1 YOUR_PASS;

パスワードは固定で、HashのSALTが変更されるので、毎回違う値が出力される。 たとえば、10回実行すると次のようになる。

for i in {1..10}; do openssl passwd -apr1 test; done
$apr1$E6eSjOuq$iAZUUfqiHJ8zv9qG38Shb1
$apr1$K.Wy7sYN$Ix0klhUCQfwcn4UAHSbRz/
$apr1$4DPLNUg0$QVbiHRIY7rAOPt870A3670
$apr1$e3QMPH5C$ua38IaGxo6NGZKIJsWmbZ/
$apr1$DtF4lp2X$/DdNlty6dpU2POQMu5uEL/
$apr1$nahkb2iJ$VZriMtEX.aYqxSbjwuAQF/
$apr1$r2iDLeWL$O.x94JXv81b2VJUu2K1eZ/
$apr1$NYVyiMwU$0MizU07qKCUavuFeKwihD/
$apr1$uwNYEWHO$4XpVQFvyDVcaf6pe5YAZj.
$apr1$xfbKxmrJ$wMEaPWAC63RJdFpvUGFd/1

あとは、このパスワードをnginx に書いておけば共有サーバーの出来上がり

手軽なDAVサーバー

nextCloud など他ソフトウェアを経由しないので、簡単に作れる。Apacheでも良かったのだが、nginx でもできるのを確認した。

既設のnginxを使い回せるので楽ちんですね。

SSHは認証時に利用可能なすべての公開鍵をサーバに送っている。

SSHは認証時にすべての公開鍵を送っている。

SSHは認証時に利用可能なすべての公開鍵をサーバに送っている。そのためGitHubなどでssh鍵を公開している人が知らないサーバにssh接続すると、自分の素性がバレてしまう可能性がある。 https://x.com/mootastic/status/1612413906248699906

つまり、公開鍵が複数あると、全部送っちゃうよっていうもの。

そんなことってあるのかなぁ。デフォルトの公開鍵の場所はわかってるから複数の公開鍵ペアを使うってのはRSAやedcsa などを複数持ってるときかな。

対策は簡単だった。

Hosts * 
  IdentitiesOnly yes

そもそも、知らないサーバーにSSHしちゃだめです。

参考資料

Xbox ゲーミングバーで動画が保存されない。

video フォルダに保存されない。

ビデオ・フォルダに保存されるものだと思いこんでした。

設定-> Gaming -> captiure

ここで示されるフォルダに保存される。

この設定を変えれば、保存先を変えられそうですが。

そこまで頻繁に使うものでもないので、フォルダを開けばいい。

そして、初期設定が ユーザの一時フォルダだから特にに気にしないことにする。

laravel のNotificationをデータベースに入れる

Notificationをデータベースに入れる

Notificationはジョブキュー的に処理されるし、別にメール通知とは限らない。

そもそもイベント・リスナみたいなもの。イベントが発火してリスナが受け取るのと同じように、通知が発生して通知連絡のクラスが起動する。

今回は、Notificationをデータベースにキューイングして構造を見ておこうと思う。

実験環境を準備する。

プロジェクトを作る

composer create-project laravel/laravel database-notification-sample
cd database-notification-sample
sed -i config/app.php -e 's|UTC|Asia/Tokyo|'
sed -i .env -e 's/DB_/#DB_/' -e '/mysql/i DB_CONNECTION=sqlite'
php artisan migrate

通知をやり取りするテーブルを作成する。

php artisan notifications:table
php artisan migrate

クラスを作る。送出側

まずは、通知を送る側クラスを作る。

通知の本体を作る

php artisan make:notification MyNotification

このような感じで作られる。

app/Notifications/MyNotification.php

<?php

namespace App\Notifications;

use App\Models\MyModel;
use Illuminate\Notifications\Notification;

class MyNotification extends Notification {

  public function __construct( protected MyModel $model) { }

  public function via(){
    return ['database'];
  }
  public function toArray(){
    return ['testing' => 'Test'];
  }
}

通知を送る側を作る

通知を送る側は、コマンドとして作り、任意のタイミングで実行できるようにする。

php artisan make:command SendNotification

app/Console/Commands/SendNotification.php

<?php

namespace App\Console\Commands;

use App\Models\MyModel;
use Illuminate\Console\Command;
use App\Notifications\MyNotification;
use Illuminate\Support\Facades\Notification;

class SendNotification extends Command {
  
  protected $signature = 'app:send-notification';
  
  public function handle() {
    $model = MyModel::first();
    Notification::send($model,new MyNotification($model));
  }
}

サンプルのモデルを作る

通知は、モデルに対し行われる。なので、モデルを用意する。

php artisan make:model MyModel -m

app/Models/MyModel.php

use Notifiable; で通知を受け取るフラグを立てる。

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;

class MyModel extends Model {
  use Notifiable;
}

データベースに通知を置く

データベースに通知を置くのが本題なので、通知を設置するテーブルを用意する。

Seederを作る

php artisan make:seeder MyModelSeeder

Seeder を実行する。

php artisan migrate
php artisan db:seed --class MyModelSeeder

database/seeders/MyModelSeeder.php

seeder は次のようにして、いくつかモデルのレコードを用意した。

<?php

namespace Database\Seeders;

use App\Models\MyModel;
use Illuminate\Database\Seeder;

class MyModelSeeder extends Seeder {
  
  public function run():void {
    foreach (range(0, 10) as $idx) {
      ( new MyModel() )->save();
    }
  }
}   

通知を送る。

コマンドからモデルへ通知を送信する。

php artisan app:send-notification

通知を送出後にDBのテーブルを確認する。

id type notifiable_type notifiable_id data read_at created_at updated_at
a3df8a96-f7ae-44ca-bf55-ba94662128f1 App\Notifications\MyNotification App\Models\MyModel 1 {"testing":"Test"} 2024-02-13 14:39:13 2024-02-13 14:39:13

通知を見ればわかるが、どのモデルのID番号へ、通知を送る。そしてReadフラグ(日付)をつけて区別している。これで、データベース通知の仕様が見えてくる

model <- notification 

通知を読み取る。

通知の処理をする起動ポイントを作る。

php artisan make:command ReadNotification

app/Console/Commands/ReadNotification.php

<?php

namespace App\Console\Commands;

use App\Models\MyModel;
use Illuminate\Console\Command;

class ReadNotification extends Command {
  
  protected $signature = 'app:read-notification';
  
  public function handle() {
    /** @var MyModel $model */
    foreach (MyModel::all() as $model) {
      /** @var \Illuminate\Notifications\DatabaseNotification $notification */
      foreach ($model->notifications as $notification) {
        dump($notification->type);
        dump($notification->data);
      }
    }
  }
}

通知の読み取りを実行する

実行結果

# app/Console/Commands/ReadNotification.php:18
"App\Notifications\MyNotification" 
# app/Console/Commands/ReadNotification.php:19
array:1 [
  "testing" => "Test"
] 

覚えること。

DatabaseNotificationは、notificationsテーブルmodelとモデル・リレーションしている。

mophyを使ってる。

Notificationはモデルへ通知するのが基本。

ReadAtをnull / not null を使ってフラグとして管理している。

マニュアルを一読すると、「イベント」のように見えるけど、イベントとはぜんぜん違う仕組みだとわかる。

どこにもキューが出てこないし、queue:workも出てこない。

通知(notification)を消すには、通常のデータベースと同じ処理になる。

DatabaseNotificationは次のように定義されている

class DatabaseNotification extends Model

xxXXXedと書かれるので、イベントのように見えるがイベントではないし、Notificationのチャンネルがあるわけでもない。よってこれは、ただの1対多のモデル・リレーションである。

モデルに「メッセージ(Data)」を紐づけて送っている。モデルにイベントを通知すると考えると、データベース通知は、やや過剰な仕様であり、Dataにデータを紐付ける程度のものである。

サンプルやドキュメントで「InvoicePaid」「XXXCreated」みたいな通知クラスをみかける。まるでイベント名であるが、イベント・リスナの枠組みではないとわかる。メール・Slackと、データベース通知は、2つはまったく違う。Slack・Mainではキューも使っている。一方でデータベース通知はテーブルに書くだけであり、キューの枠組みではないとわかる。これは初見殺しで、ややこしい。

この違いは、use ShouldQueue;use Queueable; の有無である。データベース通知をキューにいれるかは、コーディングで明確に書く必要がある。ただ、データベース通知ではキューをは必要がないので特に書かれない。

2024-05-30

下書きから更新

Laravelのイベント・リスナをDB利用で非同期に分離動作する。

Laravelのイベント・リスナを分割する。

データベースを使ってイベント・リスナを分割する。

データベース(SQL)を使って、イベントをキューに登録し、queue:work でリスナがイベントを拾って動作させる。

データベースにイベント発生を登録してジョブキューとワーカーにする。イベントハンドラとイベントを動作タイミングを分離してレスポンスを高速化したり、後でいいものは後ろに回す。

プロジェクトを作る

まず、プロジェクトを作成して実験環境を整える。

composer create-project laravel/laravel database-listener-sample
cd database-listener-sample
sed -i config/app.php -e 's|UTC|Asia/Tokyo|'
sed -i .env -e 's/DB_/#DB_/' -e '/mysql/i DB_CONNECTION=sqlite'
php artisan migrate
## キューをDBに入れる。
sed -i .env -e 's/QUEUE_CONNECTION=sync/QUEUE_CONNECTION=database/'

イベントとリスナを作る

ここで作ったリスナが、php artisan queue:work でリスナを受け取って実行する。

また、イベントを発火させるコマンドを作る。

php artisan make:event MyEvent
php artisan make:listener MyListener
php artisan make:command AddJob

コマンドからイベントを発火させる部分

ここでは、手動でイベントを発火させるコードを記入しておく。

<?php

namespace App\Console\Commands;

use App\Events\MyEvent;
use App\Listeners\MyListener;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Event;

class AddJob extends Command {  
  protected $signature = 'app:add-job';
  public function handle() {
    Event::listen(MyEvent::class,MyListener::class);
    Event::dispatch(new MyEvent());
  }
}

イベント

ここでは、特に何も書かない。イベントとしてクラスを定義するだけ

<?php
namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class MyEvent {  
  use Dispatchable, InteractsWithSockets, SerializesModels;
}

リスナ定義

リスナはイベント発火時に動作をする。イベントはデータベースから引き取るためにconnection を入れる。

<?php
namespace App\Listeners;

use App\Events\MyEvent;
use Illuminate\Contracts\Queue\ShouldQueue;

class MyListener implements ShouldQueue {
  
  public $connection = 'database';
  public function __construct() {}
  
  public function handle( MyEvent $event ):void {
    dump([$this, $event]);
  }
}

DBの作成

キューをためておくテーブルを作る。

php artisan queue:table
php artisan migrate
## または php artisan migrate:fresh

準備完了

ここまでで、キューテーブルをつくり、テーブルでイベントの受け渡しをするListernを作り、イベントを発火させるコマンドを作った。

実験

コマンドを実行して、イベント発火する

php artisan app:add-job

ここでは、イベントを発火させるだけ。

ジョブの登録を確認。

イベントはジョブとしてキューに貯まるはずので、それを確認する。

sqlite3 database/database.sqlite 'select * from jobs;'
-- Loading resources from /Users/takuya/.sqliterc
id  queue    payload                               attempts  reserved_at  available_at  created_at
--  -------  ------------------------------------  ----  -----------  ------------  ----------
1   default  {..."App\\Listeners\\MyListener",...  0                      1707798204    1707798204

リスナを動作させる。

キューに溜まったイベントをリスナで動かす。

php artisan queue:work

コマンド実行すると、ワーカーが、キューを読み込んで、リスナに処理をやらせてくれる。

INFO  Processing jobs from the [default] queue.

  2024-02-13 03:46:10 App\Listeners\MyListener ......... RUNNING
  2024-02-13 03:46:10 App\Listeners\MyListener ......... 8.69ms DONE

あとは、画面を見ながら、イベントを発火せてリスナが処理をするのを眺めていれば、処理機構の枠組みがわかってくる。

覚えるポイント

キューを使ってイベント・リスナの処理を分割するときのポイント。

リスナをデータベースに入れる。

$connection = 'database' を使ってイベント発火し、リスナに割り当てる(dispatch)するとき、実行せずにデータベースに放り込む。

<?php
class MyListener {
    public $connection = 'database';
}

リスナを「動作可能にする」

laravel ではClass::handle()Class::__invoke()を用意すれば、クラスを「実行可能」と定義できる。

<?php
class MyListener implements ShouldQueue {
    public function handle( ...);
}

イベント発火の方法

次はほぼ同じ。

Event::dispatch(new MyEvent());
event(new MyEvent());

イベントとリスナを紐付ける方法

サービスプロバイダで定義してもいいし、Event::listen を使ってもいい。

サービスプロバイダを使うときはEventServiceProviderにかくか、AppServiceProvider#boot()にかく。

Evnet:listen はFacedeに定義されている。\Illuminate\Support\Facades\Event がクラス名である。

キューをデータベースに入れる。

sed -i .env \
  -e 's/QUEUE_/#QUEUE_/' \
  -e '/CONNECTION=sync/i QUEUE_CONNECTION=database'

envに書かずに、動かすには、自分で書くしかない。

データベースに入れ設定を先に書いておかないと、queue:work によるキュー動かないので注意。

2024-05-30

下書きから更新。

SQLiteでcurrent_time

sqlite で current_time

注意点がある。文字列が主体のsqliteなので、暗黙的文字列キャストが入ったり、datetimeにはタイムゾーンが付与されずUTCである点の2点に配慮すること。

current_timestamp がついているカラムがあって。

sqlite3 database/database.sqlite 'select deleted_at from jobs';
-- Loading resources from /home/takuya/.sqliterc
deleted_at
2024-03-16 06:26:50
2024-03-16 07:26:50
2024-03-16 08:26:51
2024-03-16 08:26:51
2024-03-16 02:42:12

datetime を入れた場合

更新していると、ついついそのまま入れたくなるが、これは、UTCである。

update jobs set deleted_at = datetime() where name = "awxlXViQcg"
 ```

## CURRENT_TIMESTAMP はUTC

SELECT CURRENT_TIMESTAMP ; CURRENT_TIMESTAMP 2024-05-30 03:28:33



## localtime を参照する

SELECT time(current_time, 'localtime') local_time, current_time; local_time|current_time 12:27:22|03:27:22

## 現在の標準時帯に変換する。

select datetime(current_timestamp, 'localtime'); datetime(current_timestamp, 'localtime') 2024-05-30 12:31:52

特に指定しないといい感じに、UTCをLocal変換してあげないといけない

SQLite はちゃんとUTC時刻を扱えるので、カラムにローカルタイムを入れないように注意しないといけない。

## 参考資料

- [https://www.sqlite.org/lang_datefunc.html]
- [https://www.sqlitetutorial.net/sqlite-date-functions/sqlite-current_time/]





## 2024-05-30 追記

ローカルタイムについて記載

php でクラスが呼び出す関数をスタブにしたいとき require を工夫すればいいが、laravelでは面倒が起きる

php でクラスが呼び出す関数をスタブにしたいとき

コードテストを書いていて、クラスが呼び出すクラスのメソッドをダミー化してたいとき、スタブを使う。

スタブを作るには、クラスが呼び出すクラスのメソッドを書き換える必要がある。phpでは動的なクラス定義変更ができない。

結論 require_once を弄くればいい。

require_once されているファイルを動的に差し替えればテストが可能。

当たり前なのだけど、気づかないときは気づかない。ちょっと盲点かもしれない。

mockery でやる

また、その実現のために、Mockery が使える。

<?php
// モックでスタブを作る
$mock = Mockery::mock('alias:'.MyChecker::class);
$mock->shouldReceive('is_src_exists')->andReturn(faklse);

たとえば、次のようなクラスがあって。クラスが例外を吐くことをテストしたい。とする

MyChecker::is_src_exists というメソッドを定義して。

<?php
class MyChecker {
  public static function is_src_exists( $path ) {
    return realpath($path) && is_readable($path);
  }
}

MyFileClassクラスの例外送出テストしたいとする。

<?php
class MyFileClass {
  public static function move($src,$dst){
    if (false == MyChecker::is_src_exists($src) ){
      throw new RuntimeException("src not found");
    }
    rename($src,$dst);
  }
}

テスト対象はMyFileClass である。MyChecker::is_src_exists を呼び出すMyFileClassがテスト対象である。

上記のクラスをテストして、例外が起きることをテストしたい

<?php 
class MyFileClassTest extends TestCase{
  public function test_file_move_function_raise_exception(){
    $this->expectException(RuntimeException::class);
    MyFileClass::move('/no_exists','/tmp');
  }
}

php では、動的に関数を上書きできない。runkitを使えばできなくもないが、環境整備も面倒だ。

動的に上書きできないし、フラグや引数やENVをソースコードに書くのも避けたい。 環境変数の切り分けコードをテストのために追記すると、コード全体の見通しが著しく悪化する。

だったら、かわりに、require_once をうまく使えばいい。

<?php 
require_once 'mock_is_src_exists.php';
class MyFileClassTest extends TestCase{ // 以下略

mock_is_src_exists.php

<?php 
// スタブとして代用するクラス定義
class MyChecker {
  
  public static function is_src_exists( $path ) {
    return false;
  }
}

テストしたいモックオブジェクトを作るのではなく、モックオブジェクトの定義を本来の名前でファイルに書き出して、それをrequire してしまえば、解決するのである。

require_onceを使った解決

// mock としてのロードをできるファイルを作る
function create_mock() {
  $temp = tempnam();
  file_put_contents($temp, <<<'EOS'
    <?php 
    class MyChecker{
      publick static function is_src_exists($path){ 
        return false;
      }
    }
  EOS);
  require_once $temp;
  register_shutdown_function(fn()=>@unlink($temp);
}
// テスト前に実行する。
create_mock();

ただ、どうしても煩雑になる。

どうやら、これを快適にやってくれるのが、Mockey 関連であるようで、 phpunit が使っているものらしい。

*1

Mockery を使ったショートカット

require をもっと便利に動的に作れたら便利だろう。それがMockeyらしい。

冒頭で出てきた、mockey のサンプルコードがそれに当たる。

<?php
// モックでスタブを作る
$mock = Mockery::mock('alias:'.MyChecker::class);
$mock->shouldReceive('is_src_exists')->andReturn(faklse);

Mockery で alias:MyClassoverload:MyClass などしたらrequire を変えていい感じにスタブができる。

ただし、php はrequireされたクラスをアンロードできないので。別の箇所でロード済みだと名前が被ってエラーになる。

Could not load mock MyChecker::class, class already exists

そこで、phpunit をprocessIsolation で動かして、テストごとにファイルロードをリセットしてから実行するようにする。

アノテーションを使ってphpunit に指示を出す。

@runTestsInSeparateProcesses@preserveGlobalState disabled を使えばいい。

<?php
use PHPUnit\Framework\TestCase
class MyFileClassTest extends TestCase {
  /**
  * @runTestsInSeparateProcesses
  * @preserveGlobalState disabled
  */
  public function test_file_move_function_raise_exception() {
    // 例外が起きることをテストする
    $this->expectException(RuntimeException::class);
    // モックでスタブを作る
    $mock = Mockery::mock('alias:'.MyChecker::class);
    $mock->shouldReceive('is_src_exists')->andReturn(faklse);
    // メソッドを呼び出すクラスをテストして、例外を送出させる。
    MyFileClass::move('/no_exists', '/tmp');
  }
}

これで無事にモック(スタブ)を使って、クラスが使ってる関数やクラスのメソッドを上書きして、テストコードを実行することができるわけだ。

phpでクラスが呼び出しているクラスの中にある関数やメソッドにダミー値を返却(スタブ)して、作ったクラスの条件判定をすべて網羅してテストしたい。

ネットワークや外部依存が増えた

現代では、プログラミングがオフラインの自己環境で解決しない時代。ネットワークや外部リソースにほぼ必ず依存する。

例えば、DNSHTTPSなど外部のコンテンツを取得する関数に固定値を返却させてテストコードが現実世界に依存しないように記述したい。たとえばDNSのAレコードを変更するツールを作っているとして、コードのテストのために、実際のDNSレコードを書き換えに行くなどは、非現実的である。別の手段として、テスト用にDNSコンテンツサーバーとDNSゾルバを組み込んだDocker環境を作り管理するとか変態的な手間がかかる。どれも正気なテスト実現方法とは言えない。考えるだけでおぞましい。だからモック(スタブ)が必要である。

動的変更は、型付や高速化と相反する

そこで、ある程度メソッドが「固定値」を返すようにスタブを作れば、テストコードのプログラミングが圧倒的に楽になるわけです。

Pythonrubyなど「ゆるい」言語は何でも書き換えられるので、メソッドを上書きしてしまえばいいわけですが。ある程度の「型付」言語はそれを許してくれないし、厳密なコードから離れてしまう。PHPでやろうとすると、クラスを動的に書き換える必要がある。しかし、PHPはそれを許してくれない。高速化の足かせになるだろうし型チェックがききづらくなる。runkitという手もあるがそれもちょっとと思える。緩さと厳密さの板挟みの痛し痒しである。

laravel の場合

laravel の場合、サービスプロバイダでインスタンスを作るという役割を上手に活用することで、モックを作ることができるのだが。

<?php

namespace Tests\Feature\Rules;

use Mockery;
use Tests\TestCase;
use Mockery\MockInterface;

class CheckSrcRuleSeperateTest extends TestCase {
  
  public function test_check_src_rule_has_enough_space(){
    $this->instance(
      MyChecker::class,
      Mockery::mock(MyChecker::class, function (MockInterface $mock) {
        $mock->shouldReceive('is_src_exists')->andReturn(true);
        $mock->shouldReceive('storage_has_enough_space')->andReturn(false);
      })
    );
    // テストコード
    $rule = ['path' => new CheckSrcFile()];
    $v = $this->app['validator']->make(['path' =>test_data('60min.mp4') ], $rule);
  }
}

ただし、「サービスプロバイダ」でインスタンスなので、staticなものをスタブすることはできない。

また、use Tests\TestCase;でテストで使ってるIlluminate\Foundation\Testing\TestCaseuse CreatesApplication している箇所があり、app()でたくさん登録している。

そういう経緯からかphpunit の processIsolation が動かない。

つまり、laravel でh static なものは、スタブ作成が不能

laravel では、static メソッドをスタブにしたテスト不可能

まとめるとこういうことになる。

スタブ作成にはMockery をつかう
-> Mockery は require を使う
-> Mockery は require のために、processIsolation が必要
-> laravel では artisan test から app()を使ってる
-> laravel では app()を使うので、processIsolation はサポート不可能
-> laravel では mock はオブジェクトに限ってサポートされている
-> mockオブジェクトは、サービスプロバイダを経由する必要がある。
-> static::method() はスタブできない。

なので、static::method()をスタブ化したテストはlaravelではかけない。

とくに、laravel 公式で「processIsolationは動かない」と書いてあって絶望するなど。

まぁめんどくさいですよね。phpの限界だろうね。

*1:mockey を使っているとrequire関連のエラーが出まくるので、requireを動的にやっていると想像した。コードは見ていません。

luksをLVMで使うと Devices have inconsistent logical block sizes (512 and 4096).になった

luks のディスクを追加しようとしてエラー

vgextend mydata /edev/mapper/crypt02

エラーが出た

Devices have inconsistent logical block sizes (512 and 4096).

エラーはセクタサイズです。

はて?ファイルシステムでフォーマットしてないのにセクタサイズとは?

調べたらLUKSにはセクタサイズがあった

セクタサイズを調べる。

sudo cryptsetup luksDump /dev/sdd
Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

比べてみると・・・

 sudo cryptsetup luksDump /dev/sde
Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 4096 [bytes]

Keyslots:

ああ、ずっと昔から、luks を使ってるからデフォルト値を変えたの忘れている。

そうか、昔作ったLVMは、gitなど細かいデータのバックアップが多いからセクタサイズを小さくしたんだった。

ブロックデバイスのセクタを見ても解決しない。

luksDumpで見て、セクタサイズが小さいことがわかるが。

sudo cryptsetup luksDump /dev/sde
Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

その他のデバイス経由だとわからない

バイス マッピングのツリー確認

バイスマッピングのツリーは次のようになっているので。

lsblk /dev/sde
NAME                 SIZE FSAVAIL FSUSE% FSTYPE
sde                      7.3T                      crypto_LUKS
└─crypt-disk10  7.3T                       LVM2_member
    └─data           12.7T    1.6T    87% ext4

セクタを確認

それぞれ、デバイスを見ても、ファイルシステムをみても4096が返ってくるだけった。

$ sudo blockdev --getpbsz /dev/sde
4096
$ sudo blockdev --getpbsz /dev/mapper/data
4096

私は、暗号化ディスクを512として、その上のext4 のセクタを4096にするというムダをやっていた。

luksFormatのやり直し

セクタサイズが異なる場合はluksFomat からやり直しです。

sudo cryptsetup luksFormat --sector 512 /dev/sde
```


### ともかくセクタに注意

luksにもセクタサイズがあります。

nft のコマンドの使い方の例。NFTテーブル操作ガイドを作ろう

nft 使い方まとめ

nft の使い方は、本当にややこしい。覚えることが多くてる辛いので、調べたことをまとめ直す。

ルール閲覧

"コマンド" "役割"
nft list ruleset 全部見る
nft- a list ruleset 管理番号を含める

全体を見渡すコマンドがコレ。ruleではなくruleset

テーブル

"コマンド" "役割"
nft list tables テーブル一覧
nft add table TABLE テーブル追加
nft create table TABLE テーブル追加(既存チェック有り)
nft list table TABLE テーブル中身表示

TABLE<?family> <name> で定義される。

<?family> <name>inet mytableのように、アドレスファミリを付ける場合とつけない場合がある。作成時にアドレスファミリをつけたら以降は全部つけて識別する。作成時の省略なら以後は省略して作業する。

<?family> に使えるのは、inet | ip | ip6 | arp | netdev | bridgeであるが、パケット転送に限ればip, ip6, inet である。inetip,ip6の両方を含む。

add と create の違い

$ nft add    table ip mytable 
$ nft add    table ip mytable 
$ nft add    table ip mytable # 何度もaddでも既存があればスキップ
$ nft create table ip mytable # create は既存があればエラー

チェイン

"コマンド" "役割"
nft list chain TABLE CHAIN チェイン一覧
nft insert chain TABLE CHAIN '{ <rule> }' チェインの先頭にルールを追加
nft add chain TABLE CHAIN '{ <rule> }' チェインの末尾にルールを追加

nft -a list chain inet fw4  srcnat_VmNet

チェインにルールを追加する例

nft -a list chain inet fw4  srcnat_VmNet
nft add chain inet fw4 srcnat_VmNet { oifname "eth2" ip saddr 192.168.11.0/0 counter packets 0 bytes 0 masquerade comment "!fw4: my-sample" \; }

ルール

"コマンド" "役割"
nft insert rule TABLE CHAIN <rule> チェインの先頭にルールを追加
nft add rule TABLE CHAIN <rule> チェインの末尾にルールを追加
nft delete rule TABLE CHAIN handle <ID> ハンドルを指定して消す。
nft add table TABLE { flags dormant \; } ルールを一時休止

ルールの削除は、今のところ、ルールの削除は、Handle <ID>をつかって指定する。

指定したルールの後ろに追加する方法は現在発見されていない。ルールを作り直すほうが確実。

ルール削除はHandle ID

ルール削除は次の通り

nft delete rule TABLE CHAIN handle `<ID>` 

コメント活用したIDの探索

書いたルールがどのハンドルになっているか探すのが大変煩わしい、そこでコメントを使う方法を考えた。

コメントを利用した削除例

例えば、こんな感じに頑張る。

## 追加して
nft add rule inet fw4 srcnat oif eth0 accept comment mycomment      
## コメントを探す
nft -a list chain inet fw4 srcnat | grep mycomment
## コメントから handle を取り出し
HNDL_ID=$(nft -a list chain inet fw4 srcnat | grep mycomment | grep -oP '(?<=handle )\d+')
## ID指定で消す
[ -n $HNDL_ID ] && nft delete rule inet fw4 srcnat handle $HNDL_ID

いずれは、次のような記述も可能になると思うが、まだ無理っぽい。

nft delete rule inet fw4 srcnat oif eth0 accept comment mycomment      

コメントを用いたルールの削除と、JSON出力を組み合わせて頑張ろう

テーブルの追加削除

テーブル存在確認と作成と削除

## 作成  ( add または create )
nft add table ip mytable
## 確認
nft list table ip mytable
## 削除
nft delete table ip mytable

追加の失敗例 :既存がある。

nft create table ip mytable
Error: Could not process rule: File exists
create table ip mytable
                ^^^^^^^

追加の失敗例: 文法エラー

nft create table ip mytable chain
Error: syntax error, unexpected chain, expecting end of file or newline or semicolon
create table ip mytable chain
                        ^^^^^

チェインの追加と削除

## 作成 ( add または create )
nft add chain ip mytable srcnat
## 確認
nft list chain ip mytable srcnat
## 削除
nft delete table ip mytable

srcnat は、予約語なので注意

テーブル名は自由に決められるが、予約語がある。詳しくは、man nftCHAINSを参照すること

予約語については公式ページも参考になる。Priority_within_hook

MASQUERADE を作る例

テーブルにチェインを追加

nft add table ip mytable
nft add chain ip mytable srcnat 
nft add chain ip mytable srcnat_vpn
nft add rule ip mytable srcnat  oifname "eth2" jump srcnat_vpn comment jump-to-my-vpn
nft add rule ip mytable srcnat_vpn ip saddr 192.168.11.0/24  masquerade comment '"srcnat to my vpn';

いくつか、ポイントを解説。

meta nfproto ipv4 oifname "eth2" と書きたいところだが、ip mytable と、v4 限定のテーブルを書いているので、不要である。

コメントで、クォートしている。次のようにコメント記入しているcomment '"srcnat to my vpn'、コレは、bashにより、展開されるので、確実に文字列を引き渡す必要があるため。

確認

$ nft list table ip mytable
table ip mytable {
    chain srcnat {
      oifname "eth2" jump srcnat_vpn comment "jump-to-my-vpn"
  }
  chain srcnat_vpn {
    ip saddr 192.168.11.0/24 masquerade comment "srcnat to my vpn"
  }
}

動作チェック方法

counterを入れてマッチしたパケットの個数をカウントすればいい

nft add rule ip mytable srcnat  oifname "eth2" conter jump srcnat_vpn

counter を入れると、バイト数とパケット数がカウントされる。

丸暗記ポイント追加と削除

追加と削除が、ぱっと見てわかりにくい。

発展途上のコマンドのためサブコマンドが混乱を招きやすい体系である。留意しながら丸暗記しないと大変である。もしかしたら手作業による管理がnftに想定されてないのかもしれない。溜息が出るほどとっつきにくい。

追加と削除がわかりにくいので、とくに致命的じゃないかなって思う。

心配なのは、いま苦労して覚えても、数年後にはガラッとコマンド体系が改められそうと懸念している。懸念でるほど煩雑だ。

ルールの追加がわかりにくい

ルールの追加は add chain table chain { <rule> } 、ルールの削除は delete rule table chain handle <ID>

と思いきや、ルール追加は、add rule table chain <rule> でも行ける。

ルールの削除がわかりにくい

そして、削除はdel では無い。deleteである。add と来たら、ついついdelと打ちがち。しかしdelは存在しない。

愚痴をこぼすと、nftablesをnft って省略するのに、delete をdel って省略したらだめってのはひどい。nft は某用語なので検索しづらい。delを認めないのであれば、同じように nft コマンドではなく、nftables コマンドにしてほしい。

[add | insert] , [add | create] のペアがわかりにくい

テーブルとチェインは、add / create で、ルールはadd / insertになる。

初見殺しである。カジュアル利用の私達は、そんな違いをいちいち覚えてられない。めんどくさい。

MWANのためにデフォルトGWを複数にしたらい問題が生じたので単純な冗長化をした

mwan パッケージで冗長化しようとした。

OpenWRT+mwan3でWAN回線を冗長化する

コレを読んでたら、なんだ簡単じゃん。って思ったので試したのですが。

デフォルトGWを複数にしたら問題が生じた

Global IPを取得する機能

こういうのがいくつかの場所で使われている。

. /lib/functions/network.sh
network_flush_cache
network_find_wan WAN_IF
network_get_ipaddr IP_ADDR "${WAN_IF}"

OpenWrt の内部では、WANを探すのにuse defaut gateway ( routing 0.0.0.0/0 )のフラグの解釈結果を探索し、ルートに0.0.0.0/0 を設定しているインターフェイスを探索し、みつかったら探索終了というスクリプトがあり、各所で使われている。つまり2個め以降は探索されない。

そのため、mwanを使うと、どこかのプログラムで齟齬が生じる可能性がある。

このことから、mwan 機能はいくつかのパッケージに影響をあたえ誤作動を起こす。

だから、マルチなデフォルトGWをLuciで作るとなると怖い。

MWANは、何も考えずに使うと、ちょっとやばい気がする。とくにDDNS関連が崩壊する。

そこで、Luciで作らずに、デフォルトGWを増やす

MWANの役割

MWANの役割は、上流GWをクライアントごとに切り替えて、マルチホーミング(死語?)な上流を構成して、負荷軽減を図る

または、上流がダウンしたときに呼び回線に自動的に切り替えるというものだ。

VRRPのようなスジのわるい実装とは違い、ポリシールーティングと ip route add default metric を使っているシンプルな実装。上流GWをpingで監視してダウンしたら、パケットを切り替えるという感じ。

たぶんポリシールーティングでやってると思う。

冗長化だけなら、MWANいらなくね?

冒頭の記事を読んで試して思ったのだが、負荷分散ならともかく、冗長化ならGWだけでよくね。 要は、メインの回線が死んだときに予備回線に切り替わればいいわけだから。

metric 指定してGWを2つ入れればいい。

root@OpenWrt:~# ip route
default via 221.110.204.8 dev pppoe-ybb proto static metric 100
default via 192.168.55.5 dev eth2 proto static metric 200

mwan にある死活監視やセッションの切り替えのような便利なものはなくなってしまうので、ストリーミングは切れてしまうと思うが。ダウンしたときでもちゃんと繋がるのは良い。

pppoe や map-e の再起動が手軽になった。

いままでは、家族に気を使いながら、再起動や、pppoeのIP再取得を行っていたが、全然気にせずにバンバン落とせる。気楽でいいわコレ。

ストリーミングといってもyoutube 程度ならhttpセッションが再開するだけなのでほとんど気にならない。ゲームなどの連続の通信は駄目出し、MWANを使ってても駄目だと思う。ニンテンドースイッチのようなUDPならなおさら無理だと思うが、通常ブラウジング程度ならダウンしたことに気づかない。

設定

主回線のインターフェイスのGWメトリックを指定して

予備回線のインターフェイスのGWメトリックをを指定する

こっちは、uci/ luci で行うとほかのパッケージに影響するのでコマンドから流す。

ip route add default via 192.168.55.5 dev eth2 proto static metric 200

後は、192.168.55.5 宛に送るときにmasquerade するようにする。

masquerade は nft コマンドから流してもいいい、Luciから作ってもいい。

nft の例

nft add rule inet fw4 srcnat meta nfproto ipv4 oifname "eth2" jump srcnat_opnsense comment "takuya:to-opnsense-masq"
nft add rule inet fw4 srcnat_opnsense  oifname "eth2" ip saddr 192.168.10.0/24 counter masquerade comment "takuya:to-opnsense-masq"

後は、起動時に、自動的に予備回線宛のdefault gw 設定を登録するようにする。

/etc/hotplug.d/iface/97-ifup-InnerVM.sh

#!/usr/bin/env sh


function add_default_route(){

  ip route add default via 192.168.55.5 dev eth2 proto static metric 200

}

function google_home_route_to_vm(){
  /etc/config/custom/radiko-route/radiko-route-to-opnsense.sh add
}
function google_home_route_to_default(){
  /etc/config/custom/radiko-route/radiko-route-to-opnsense.sh del
}


function main(){
  ##
  TARGET_ACTION=ifup
  TARGET_INTERFACE=InnerVM
  TARGET_DEVICE=eth2
  ###

  [ "$INTERFACE" = "$TARGET_INTERFACE"  ] && {
    logger "iface $TARGET_INTERFACE / $TARGET_DEVICE $ACTION detected, do hotplug actions."
    case $ACTION in
      ifup)
        add_default_route
        google_home_route_to_vm
        ;;
      ifdown)
        google_home_route_to_default
        ;;
        *)
        ;;
    esac


  }
}


main

google home は、メイン回線のオンオフでストリーミングが止まるので、最初から、予備回線を通すことにした。

コレで、冗長化が終わる。

あとはダウンして試す。

メイン回線をダウンさせて、様子を見る。

ifdown my-main-isp
ping 1.1.1.1

ちゃんと切り替わってるのが面白い。

まとめ

フェイルオーバーなら、mwan3 は機能が多すぎてややこしいが、GUIで完結するので初心者にはいいかも。

ロードバランサーなら、ポリシールーティングを書くのがめっちゃめんどくさいので、mwan3 を使ったら幸せになる。

ただの冗長化なら、linuxではdefault ゲートウェイのmetric 機能で十分だった。

参考資料

openwrt で hotplug dhcp を調べる

openwrt で hotplug dhcp を調べる

DHCP に何が書かれるか、どういうタイミングでホットプラグが呼ばれるのか全然わからないので調べることにした

調査用のファイルを作成

cat << "EOF" > /etc/hotplug.d/dhcp/00-logger
logger -t hotplug $(env)
EOF

調査する

logread -f 

ネットワークを再起動する

service network restart

hotplug のdhcp は公式ページにも記載がない。

公式ページのHotplugの章を読んでみたが、記載がない

OpenWrt Wiki / Hotplug

実行結果が次の通り

調査してみた結果次のような変数が得られた

Wed May 15 17:21:04 2024 user.notice hotplug: ACTION=update IPADDR=10.0.2.105 SHLVL=1 HOME=/ MACADDR=44:07:0b:5a:bc:b3 TERM=linux BOOT_IMAGE=/boot/vmlinuz PATH=/usr/sbin:/usr/bin:/sbin:/bin PWD=/
Wed May 15 17:21:04 2024 user.notice hotplug: ACTION=add HOSTNAME=Google-Home-Mini IPADDR=10.0.2.103 SHLVL=1 HOME=/ MACADDR=38:8b:59:4a:11:fe TERM=linux BOOT_IMAGE=/boot/vmlinuz PATH=/usr/sbin:/usr/bin:/sbin:/bin PWD=/
Wed May 15 17:21:04 2024 user.notice hotplug: ACTION=update IPADDR=10.0.2.105 SHLVL=1 HOME=/ MACADDR=44:07:0b:5a:bc:b3 TERM=linux BOOT_IMAGE=/boot/vmlinuz PATH=/usr/sbin:/usr/bin:/sbin:/bin PWD=/

つまり、次の変数が取れている。

ACTION=add HOSTNAME=Google-Home-Mini IPADDR=10.0.2.105 SHLVL=1 HOME=/ MACADDR=44:07:0b:5a:bc:b3

また、UPDATEのときは、ホスト名がないことがわかる。

ACTION=update IPADDR=10.0.2.105 SHLVL=1 MACADDR=44:07:0b:5a:bc:b3 

変数まとめ

変数 役割
ACTION update DHCPの更新リクエス
ACTION add DHCPの取得リクエス
IPADDR 0.0.0.0 紐づけたアドレス
MACADDR 00:00:00:00 紐づけたMACアドレス
SHLVL int シェルのネスト

SHLVL はシェルのネスト度合いなので、hotplug がhotplug を呼び出して重複起動しないために重要なので掲載。

DHCPのホットプラグまとめ

コレを使えば、特定のDHCPクライアントが来たときに、ルーティングテーブルを書くような離れ業(荒業)が可能になる。

例えば、未登録のMACADDRはログイン画面に飛ばすとかね。

openwrt のifup / ifdown の使い方

openwrt のifup / ifdown の使い方

使い方の例

ifdown wan86
ifup wan6

インターフェイスがポイント

ifdown <interface name>が書式です。

インターフェイス名はLuciの画面にでてくる名前です。eth0 のようなデバイス名称ではないことに注意する。

インターフェイス名称の取り方

cat /etc/config/network  | grep -oP '(?<=config interface ).+'

uci で確認してもいいけど、grep すれば大丈夫だね

eth1 / eth2 とかの名前は、netdev で、その上にIP空間を載せてるのが interface って感じですね。

通常のBSD/Linuxのifdownに比べ、より階層が浅い。 ドメインが微妙に違う。

public dns のv6アドレスを通信拒否する。

exgress な udp/53 をブロックすれば、通信を改竄詐称盗聴できる時代は終わった。従来どおりやるにはdot / doh / dos を止めないと・・・それには打つ手はない。

v6 の public DNSIPアドレスをブロックする。

ただ、public dnsを止めるだけでも相当の効果はあると考えられる。

v6時代のpublic dns をブロックしてDNSをある程度制御することにする。

public dns のv6アドレスの一覧をURLで用意した

https://github.com/takuya/public_dns_list/raw/master/public_dns_ipv6_list.txt

GitHub - takuya/public_dns_list: known public dns list ips and domains

これをルータに仕込む

#!/usr/bin/env bash


function v6_addrs(){

map=working_$RANDOM
ipset destroy $map &> /dev/null
ipset create  $map hash:ip family inet6

addrs=(
https://github.com/takuya/public_dns_list/raw/master/public_dns_ipv6_list.txt
)

list=()
for url in ${addrs[@]} ; do
  echo $url;
  a=$(curl -sL $url )
  list+=( $a )
done
ips=$(
  for i in "${list[@]}"; do  echo $i;  done | sort -n | uniq  \
    | sed -E 's|$|/128|' \
    | sort -n | uniq

)
for i in $ips ; do
  ipset add $map $i 2>/dev/null;
done ;
ipset list $map  | grep -E '^[0-9]' | sort -n  > /etc/config/custom/public-dns/ip6-list.txt
ipset destroy $map
}

function v4_addrs(){

map=working_$RANDOM
#iptables -D FORWARD -o pppoe-ybb -m set --match-set public_dns dst -j DROP
ipset destroy $map &> /dev/null
ipset create  $map hash:ip

addrs=(
https://github.com/takuya/public_dns_list/raw/master/dns-list-ipv4.txt
https://github.com/takuya/public_dns_list/raw/master/dns-list-doh-as-ip.txt
https://github.com/takuya/public_dns_list/raw/master/dns-list-dot-as-ip.txt
)

list=()
for url in ${addrs[@]} ; do
  echo $url;
  a=$(curl -sL $url )
  list+=( $a )
done
ips=$(
  for i in "${list[@]}"; do  echo $i;  done | sort -n | uniq  \
    | sed -E 's|:[0-9]+||' \
    | sed -E 's|$|/32|' \
    | sort -n \
    | uniq

)
for i in $ips ; do
  ipset add $map $i ;
done ;
ipset list $map  | grep -E '^[0-9]' | sort -n  > /etc/config/custom/public-dns/ip-list.txt
ipset destroy $map
#
#ipset list public_dns
#iptables -I FORWARD -o pppoe-ybb -m set --match-set public_dns dst -j DROP


}
function main(){
  v4_addrs
  v6_addrs
}


###
main;

firewall 設定

config ipset
    option name 'public_dns'
    option match 'dest_ip'
    option family 'ipv4'
    option loadfile '/etc/config/custom/public-dns/ip-list.txt'

config ipset
    option name 'public_dns_v6'
    option match 'dest_ip'
    option family 'ipv6'
    option loadfile '/etc/config/custom/public-dns/ip6-list.txt'
### 特定のネットワーク・アドレス空間だけ v6を許可する。
config rule
        option ipset 'public_dns_v6'
        option src 'lan6'
        option dest '*'
        option target 'ACCEPT'
        option name 'Allow v6 global from pc '
        list src_ip 'fd03:xxx:xxx:xxx::/64'
## public_dns(v6)への通信は全部拒否
config rule
        option src 'lan6'
        option dest '*'
        option ipset 'public_dns_v6'
        option target 'REJECT'
        option family 'ipv6'
        option name 'reject public dns:ipset'
        list proto 'all'
## public dns (v4)の通信は全部拒否
config rule
    option src 'lan'
    option dest 'wan'
    option ipset 'public_dns'
    option target 'REJECT'
    option device 'pppoe-xxx'
    option direction 'out'
    option family 'ipv4'
    option name 'reject public dns:ipset'
    list proto 'icmp'
    list proto 'any'
        

基本的には打つ手なし

DoHがChromeにはいったので、野良でDoHを起動されると、本当に打つ手はない。

証明書の書換えによる、通信監視しか手段はない。

証明書の書換えは、リスクが高すぎるし、なにより、通信の秘密に関わる部分である。日本国と国民は憲法により、通信の秘密は保持すると決めている。国民間の労働契約の場合においても雇用条件通知書や雇用契約書で明記しない限り、リーガルリスクを伴う。学生・社員が逃れたとしてもそれの責任追及をする根拠としての「通知・契約」である。不文律というわけには行かない。せいぜい漏洩した被害にたいする責任追及に留まることになる。なので、野良DoHは打つ手はない。契約で縛るしかない。でも「悪意の広告」「フィッシング」「悪質な全面広告」をブロックするには、DNS書き換えが強い力を発揮するので使わない手はない。ライトユーザ相手にはとても良い選択肢だと思う。

こっそり通信を監視するような横暴は不可能になっているので、改めて本当に必要な監視かどうか考え直してほしい。

また証明書のインストールをするのであれば、ちゃんと契約に明示したほうがいいと思う。そして証明書のインストールが「レアキャラ」であり信用できない証明書のインストールをしてはいけないことを合わせて教えておく必要がある。でなければ、「証明書のインストールをやって良いんだ」という誤った解釈を学習することにになる。

いまは、Google Chromeのインストールを「Windowsで構成」すれば、DoHを使わせないことができる。いつまできることやら。

大いなる力には大いなる責任が伴う。