debファイルの中身を見る

単なるメモ。

apt download [パッケージ名]

debファイルをダウンロードする。

dpkg -X [debファイル] [展開先ディレクトリ名]

でファイルを展開できる。


また、debファイルはar形式なので、専用のdpkgコマンドを使わなくても展開できる。

ar x [debファイル]

とすると、data.tar.xz、control.tar.xz、、debian-binaryの3つのファイルが展開される。
「data.tar.xz」はパッケージの本体(実際にインストールされるファイル)で、dpkg -Xで展開される中身と同じ。
「control.tar.xz」にはシェルスクリプト、md5sum、その他のテキストファイルなどが入っている。インストール時にダウンロードマネージャが使用するファイルだと思われる。
debian-binary」はdebファイルのバージョン(2.0)が書いてあるだけのテキストファイル。

参考:
https://wiki.debian.org/SimplePackagingTutorial

torrentファイルを作成する: mktorrentコマンド

torrentファイルを作成できるツールはいくつかあるが、その中でもシンプルなのがmktorrentコマンド。
UbuntuDebianArch Linuxなどのtorrentファイルもmktorrentコマンドで作成されているようだ。
(ちなみに、torrentファイルのメタデータはtransmission-showコマンドで確認できる)

使い方

mktorrent [共有したいファイル、もしくはディレクトリ]

基本はこれだけでよい(この場合、トラッカーなしでDHTのみを使ってピアを見つけることになる)。拡張子が.torrentのファイルが作成されるので、torrentファイルをBitTorrentクライアントに渡せばファイル共有が始まるはず。
トラッカーを指定したいときは-a、web seedを指定したいときは-wをオプションで使う。



(なお、mktorrent --helpの説明だと、トラッカーが最低一つは必要と書いてあるが、これは古い情報のようで、実際はトラッカーなしでもエラーは出力されない。このあたりのコミットで修正されている
https://github.com/pobrn/mktorrent/commit/a6e476272ce797eea804265ecc07c90674c44bc3
https://github.com/pobrn/mktorrent/commit/77bc7d745bf53075989c804cc6434123201102dc
)

libtorrentでBitTorrentクライアントを作ってみる

libtorrentBitTorrentプロトコルを実装したライブラリ。qBittorrentのような有名なBitTorrentソフトでも使われている。

※ちなみに「libtorrent」という名前のライブラリは二種類あるので注意が必要(もうひとつの「libtorrent」はrTorrentというBitTorrentクライアントで使われている)。ここで取り上げる「libtorrent」は、多くのLinuxのパッケージでは「libtorrent-rasterbar」という名前になっているはず。

公式サイトのチュートリアル(https://libtorrent.org/tutorial-ref.html)やoverview(https://libtorrent.org/manual-ref.html)、リファレンス(https://libtorrent.org/reference.html)には詳しい説明がある。

1.インストール

Ubuntuなどの場合、以下のコマンドでインストールできる。(コンパイルするのでdev付きのパッケージを選ぶ)

$ sudo apt install libtorrent-rasterbar-dev

2.基本的な概念

torrent_handle

一つのトレントファイルやマグネットリンクにつき、一つのハンドルを持つ。
後述のsessionが自動で管理する。

session

sessionは複数のtorrent_handleを管理し、通信やデータの書き込みなど、トレントに必要な操作をほとんど自動で行ってくれる。
一つのプログラムにつき、基本的にsessionは一つでよい。

settings_pack

sessionの設定を格納し、sessionに渡すためのクラス。設定はsession内のすべてのtorrent_handleに影響する。

add_torrent_params

個々のtorrent_handleの設定を格納するためのクラス。
これを設定し、sessionに渡すことで、sessionがtorrent_handleを生成し、ファイル共有が開始される。

alert

sessionは別スレッドで通信を行うので、実行中のsessionから現在の状態を取得する方法が必要になる。libtorrentでは、alertを使ってsessionの様々な情報を取得できる。

3.BitTorrentクライアントのサンプルプログラム

BitTorrentの通信部分は、基本的にsessionが別スレッドですべて行ってくれるので、ループを回してsessionの状態確認・ダウンロード完了待機をするだけでよい。

#include <iostream>
#include <thread>
#include <chrono>
#include <filesystem>

#include <libtorrent/session.hpp>
#include <libtorrent/session_params.hpp>
#include <libtorrent/add_torrent_params.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/load_torrent.hpp>
#include <libtorrent/alert.hpp>

int main(int argc, char const* argv[]) try
{
  if (argc < 2) {
    return 1;
  }

  // セッションの設定
  lt::settings_pack p;
  // 取得するアラートの種類を指定する。
  // 以下の設定ではやや冗長な出力となる。error、status、storage以外は指定しなくても動作に支障はない
  p.set_int(lt::settings_pack::alert_mask
        , lt::alert_category::error
        | lt::alert_category::storage
        | lt::alert_category::status
        | lt::alert_category::peer
        | lt::alert_category::dht
        | lt::alert_category::file_progress
        | lt::alert_category::piece_progress
        | lt::alert_category::upload
        | lt::alert_category::incoming_request
  );

  // セッションを作成する
  lt::session ses(p);

  // トレントハンドル用のパラメータを設定する
  lt::add_torrent_params atp;
  // 引数がトレントファイルの場合と、マグネットリンクの場合で処理を分岐する。
  // パスが存在する場合はトレントファイルと判断する
  if(std::filesystem::exists(argv[1])){
    // トレントファイルを読み込む
    atp = lt::load_torrent_file(argv[1]);
  }
  else{
    // マグネットリンクを読み込む
    atp = lt::parse_magnet_uri(argv[1]);
  }
  // 保存先のディレクトリを設定する
  // ここではカレントディレクトリに保存する
  atp.save_path = ".";
 
  // セッションにトレントの情報を追加する。
  ses.async_add_torrent(std::move(atp));

  for (;;) {
    // alert格納用の変数を宣言する
    std::vector<lt::alert*> alerts;

    // pop_alerts()を使って、alertsに新しいalertを格納する
    ses.pop_alerts(&alerts);

    // 格納されたalertのそれぞれに対して処理を行う
    for (lt::alert const* a : alerts) {

      // alertのメッセージを出力する
      std::cout << a->message() << "\n";

      // alert_cast<>()を使って、alertの種類を判定する
      // torrent_finished_alertを受け取ったら、ダウンロード完了
      if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
        goto done;
      }
      
      // エラー発生
      if (lt::alert_cast<lt::torrent_error_alert>(a)) {
        throw std::runtime_error(a->message());
      }
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
  }
  done:
  std::cout << "ダウンロードが完了しました" << std::endl;
}
catch (std::exception& e)
{
  std::cerr << "Error: " << e.what() << std::endl;
}

上のコードは、

$ g++ -o simple_torrent_client simple_torrent_client.cpp `pkg-config --cflags --libs libtorrent-rasterbar`

このようなコマンドでコンパイルして、

$ ./simple_torrent_client [マグネットリンク]

もしくは

$ ./simple_torrent_client [トレントファイル]

トレントのダウンロードができるはず。
100行以下のコードだが、これでも十分に実用的なBitTorrentクライアントになる。

参考リンク

https://libtorrent.org/tutorial-ref.html
https://libtorrent.org/manual-ref.html
https://libtorrent.org/reference.html

ライセンス

記事内のBitTorrentクライアントのサンプルプログラムは、libtorrentのドキュメント(https://libtorrent.org/tutorial-ref.htmlhttps://github.com/arvidn/libtorrent/blob/RC_2_0/docs/tutorial.rst)内のコードの改変なので、以下にライセンスを明示する。

Copyright (c) 2003-2020, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of Rasterbar Software nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

sqlite3コマンドでデータベース

sqliteはファイル単体をデータベースとして扱うことができるシンプルさが魅力。
sqlite3コマンドをスクリプトなどから呼び出して使う想定(そのため対話的なコマンドは使っていない)で、使用方法をまとめてみた。

1. 基本的なSQL

データベース(ファイル名)とSQLコマンドを並べるだけでSQLを実行できる。SQLを複数実行するときは;で区切る。
指定したファイルが存在しない場合は新規作成される。

1.1 データベースとテーブルの作成

$ sqlite3 sample.db "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"

ちなみにAUTOINCREMENTは公式ではパフォーマンスが落ちるため基本的には非推奨とされている(https://www.sqlite.org/autoinc.html)。
AUTOINCREMENTを使わなくても、INTEGER PRIMARY KEYを設定した場合、INSERTのときに値を設定しなければ自動でユニークな数字が割り振られる。
データ型はTEXT(文字列)、INTEGER(整数)、REAL(小数)などを使う(これについては後述)。

1.2 データの挿入、テーブル表示、データ取得

SELECT文のテーブル表示はデフォルトだと少し読みにくい。オプションで-header-columnを使うと見やすくなる。

$ sqlite3 sample.db \
"INSERT INTO users (name , age ) VALUES ( 'Watanabe', 35); \
INSERT INTO users (name , age ) VALUES ( 'Shimamura', 23 ); \
INSERT INTO users (name , age ) VALUES ('Aoki' , 49 );"
$ sqlite3 sample.db "SELECT * FROM users;"
1|Watanabe|35
2|Shimamura|23
3|Aoki|49
$ sqlite3 -header -column sample.db "SELECT * FROM users;"
id  name       age
--  ---------  ---
1   Watanabe   35 
2   Shimamura  23 
3   Aoki       49
$ sqlite3  sample.db "SELECT age FROM users WHERE name = 'Shimamura';"
23

-json-csvのような別のフォーマットで出力するオプションもある。

$ sqlite3 -json sample.db "SELECT * FROM users;"
[{"id":1,"name":"Watanabe","age":35},
{"id":2,"name":"Shimamura","age":23},
{"id":3,"name":"Aoki","age":49}]

1-3. UPDATE、DELETE

$ sqlite3 sample.db "UPDATE users SET age = 36 WHERE id = 1;"
$ sqlite3 -header -column sample.db "SELECT * FROM users;"
id  name       age
--  ---------  ---
1   Watanabe   36
2   Shimamura  23
3   Aoki       49
$ sqlite3 sample.db "DELETE FROM users WHERE name = 'Aoki'"
$ sqlite3 -header -column sample.db "SELECT * FROM users;"
id  name       age
--  ---------  ---
1   Watanabe   36
2   Shimamura  23

2. ドットコマンド

sqliteでは.(ドット)から始まるコマンドを使うことができる。ここでは仮にドットコマンドと呼ぶ。

2.1 テーブル一覧を表示: .tables

$ sqlite3 sample.db ".tables"
users

2.2 スキーマを表示: .schema

$ sqlite3 sample.db ".schema users"
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);

2.3 ダンプ: .dump

$ sqlite3 sample.db ".dump"
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
INSERT INTO users VALUES(1,'Watanabe',36);
INSERT INTO users VALUES(2,'Shimamura',23);
COMMIT;

2.4 ダンプを読み込む: .read

$ sqlite3 samole.db ".dump" > dump.sql
$ sqlite3 new.db ".read dump.sql"

3. 型について

sqliteのデータ型はあまり厳格ではなく、指定した型と異なるデータでもエラーは起こらない(たとえば、INTEGER型の列に文字列を挿入しようとすると、数字に変換できるときは変換され、できないときはそのまま文字列が挿入される。エラーは発生しない)。細かい動作については公式サイトの解説→https://sqlite.org/datatype3.htmlを参照のこと。

par2コマンドで誤り訂正

データの一部が破損しても破損を検出・修復できる、「誤り訂正」という技術がある。有名なものだと、rarのリカバリレコードやQRコードで誤り訂正の技術が使用されている。
par2コマンドを使用すると、任意のファイルに対して、誤り訂正用のリカバリファイルを作成することができる。

1. インストール

LinuxのほとんどのディストリビューションMacでは、パッケージマネージャでインストールできるはず。
DebianUbuntuなら、

sudo apt install par2

(なお、パッケージ名はDebianUbuntu系では「par2」だが、RHEL系、Arch系、Macなどでは「par2cmdline」となっているらしい)

2. リカバリファイルの作成(基本)

以下では仮に、対象ファイルの名前を「file」としておく。

par2 create file

このコマンドを実行すると、拡張子が「.par2」のファイルがfileと同じディレクトリに作成される(.par2ファイルは複数作成されるが、規定の動作なので心配いらない)

3. リカバリファイルによるファイルの検証、修復

作成したpar2ファイルと、検証・修復対象ファイルを同じディレクトリに置く。ファイル名はもとと同じにしておく。

3.1 検証

par2 verify *.par2

検証対象が元ファイルと同一なら、「All files are correct, repair is not required.」のような結果が出力されるはず。
破損している場合、「Repair is possible.」もしくは「Repair is not possible.」のような結果が出力される。前者の場合は修復が可能。

3.2 修復

par2 repair *.par2

修復可能な場合、これでファイルが修復される。修復後のファイルの名前はもとのファイル名と同じになる。修復前の破損したファイルは「(ファイル名).1」のようなファイル名に変更される。

4. リカバリファイル作成時のオプション

  • -r(数字)で、元データに対するリカバリファイルのサイズを指定できる(単位は%、デフォルトでは5%)。たとえば5%の場合、ファイルのおよそ5%以下までなら破損しても修復できる。10%ならファイルの10%が破損しても修復できる。
par2 create -r10 file
  • -n(数字)で、作成するリカバリファイルの数が指定できる。(「インデックスファイル」は必ず作成されるため、指定した数字+1だけの.par2ファイルが作成されることになる)
par2 create -r10 -n1 file

ちなみに、リカバリファイルを複数に分けるのは、配布するさいに通信量を減らすためである(破損が小さい場合、リカバリファイルをすべて使わなくても修復できるような仕組み)。おそらく帯域幅が非常に狭かった時代に考えられた工夫なので、現代なら特に理由がなければ-n1で問題ないと思う。

参考リンク

https://github.com/Parchive/par2cmdline
https://wiki.archlinux.jp/index.php/Parchive
https://man.archlinux.org/man/par2.1.en

Linuxでnftablesを使用してレートリミットを設定する

レートリミットとは通信量の制限のこと。ここではアプリケーションレベルのレートリミットではなく、OS全体のレートリミットについて扱う。
Linuxにおけるレートリミットは、1.ファイアウォールiptablesやnftables)か、2.トラフィック制御(tcコマンド)を用いて設定することができる。
ここでは1のうち、nftablesを用いたレートリミットの設定を扱う。

1.nftablesによるレートリミットの設定方法

参考リンク:https://wiki.nftables.org/wiki-nftables/index.php/Rate_limiting_matchings
当然だが、ファイアウォールに他のルールを設定している場合は、ルールの順番を間違えたりすると意味がなくなるので注意すること。
nftablesの他の機能と組み合わせれば、送信先ポートや送信元アドレスごとのリミットレートも設定できる。

1.1 基本

nft add rule inet filter input limit rate 10 mbytes/second accept
nft add rule inet filter input drop

limit rateのキーワードを使うと、指定した通信量以下のパケットのみにマッチさせることができる。上のルールで、1秒につき10メガバイトまではacceptし、その基準を超えたパケットは下のルールによってdropされる。
(ちなみに、パケットを廃棄してしまうと確立した接続が途中で突然切れないか心配になるかもしれない。しかし、TCPの仕様でパケットはすぐに再送されるので、接続がいきなりブツッと切れるみたいなことはそんなにないはず)
パケット数で指定することもできる。

nft add rule inet filter input limit rate 100/second accept
nft add rule inet filter input drop

1.2 burst

burstを使うと、一時的な通信量の増加を受け入れることもできる。

nft add rule inet filter input limit rate 10 mbytes/second burst 9000 kbytes accept
nft add rule inet filter input drop

1秒につき10メガバイトの通信量を超えたときも、あと9000キロバイトまでなら一時的に許可される(あくまで一時的なので、常時この通信量が許可されるわけではない)

1.3 limit rate over

単にlimit rateの場合は指定した通信量以下の場合にマッチするルールになったが、limit rate overの場合は指定した通信量を超えたときにマッチするルールになる。

nft add rule inet filter input limit rate over 10 mbytes/second drop

こちらを使ってdropしたほうがシンプルで、使い勝手が良いかもしれない。
burstも設定できる。

nft add rule inet filter input limit rate over 10 mbytes/second burst 9000 kbytes drop

1.4 速度計測

設定したルールが上手く動いているか確認したいときは、https://www.speedtest.netなどのサイトで手軽に速度を確認することができる。
仮想OS等を使って、iperf3などで計測してみるのも良い。
注意点として、通信速度によく使われる単位である「Mbps」(bが小文字)は、一秒あたりの通信量をメガビット(メガバイトではなく)で表したものであることには気をつけること。

1.5 IPアドレスごとの通信量制限

参考リンク:
https://wiki.nftables.org/wiki-nftables/index.php/Meters
https://www2.filewo.net/wordpress/2019/10/19/2206/

setを用いることでIPアドレスごとのレートリミットの設定が可能になる。

nft add set inet filter rate_limit_ip { type ipv4_addr \; flags dynamic \; timeout 10m \;}
nft add rule inet filter input update @rate_limit_ip { ip saddr limit rate over 10 mbytes/second } drop

まずIPアドレスのsetを作成する。このときflags dynamic \; を指定するのがポイントで、動的にアドレスを管理できるようになる。
(timeoutはsetが溢れないために設定しておく)
@をつけてsetを呼び出し、随時updateすることでIPアドレスごとのレートリミットが設定できる。
IPv6も同様に設定できる。

nft add set inet filter rate_limit_ipv6 { type ipv6_addr \; flags dynamic \; timeout 10m \;}
nft add rule inet filter input update @rate_limit_ipv6 { ip6 saddr limit rate over 10 mbytes/second } drop

2.ルールセットの例

レートリミットを使用したnftablesルールセットをいくつか書いておく。

2.1 SSHサーバへの新規接続に制限を設けた例

table inet filter {
	chain input {
		type filter hook input priority filter; policy drop;
		ct state established,related accept
		ct state invalid drop
		iif "lo" accept
		meta l4proto icmp accept
		meta l4proto ipv6-icmp accept
		tcp dport 22 ct state new limit rate over 20/minute drop
		tcp dport 22 accept
	}

	chain forward {
		type filter hook forward priority filter; policy drop;
	}

	chain output {
		type filter hook output priority filter; policy accept;
	}
}

2.2 HTTP(HTTPS)サーバで同一アドレスからの通信量と新規接続を制限した例

table inet filter {
	set rate_limit_ip {
		type ipv4_addr
		size 65535
		flags dynamic,timeout
		timeout 10m
	}

	set rate_limit_ipv6 {
		type ipv6_addr
		size 65535
		flags dynamic,timeout
		timeout 10m
	}


	set rate_limit_newconnection_ip {
		type ipv4_addr
		size 65535
		flags dynamic,timeout
		timeout 10m
	}

	set rate_limit_newconnection_ipv6 {
		type ipv6_addr
		size 65535
		flags dynamic,timeout
		timeout 10m
	}
	chain input {
		type filter hook input priority filter; policy drop;
		ct state established,related accept
		ct state invalid drop
		iif "lo" accept
		meta l4proto icmp accept
		meta l4proto ipv6-icmp accept
		tcp dport { 80, 443 } update @rate_limit_ip { ip saddr limit rate over 20 mbytes/second } drop
		tcp dport { 80, 443 } update @rate_limit_ipv6 { ip6 saddr limit rate over 20 mbytes/second } drop
		tcp dport { 80, 443 } ct state new update @rate_limit_newconnection_ip { ip saddr limit rate over 20/second } drop
		tcp dport { 80, 443 } ct state new update @rate_limit_newconnection_ipv6 { ip6 saddr limit rate over 20/second } drop
		tcp dport { 80, 443 } accept
	}



	chain forward {
		type filter hook forward priority filter; policy drop;
	}

	chain output {
		type filter hook output priority filter; policy accept;
	}
}

3. 参考リンク

https://wiki.nftables.org/wiki-nftables/index.php/Main_Page
https://netfilter.org/projects/nftables/manpage.html
https://wiki.archlinux.jp/index.php/Nftables

steghideでjpg画像に情報を埋め込む

ステガノグラフィーとは情報を埋め込んで存在を隠す技術のことである。たとえば画像の場合、ピクセルの色などを視認できないレベルで僅かに変化させることで情報を埋め込み、専用のツールのみで情報を復元できるようにする。
steghideステガノグラフィー用のコマンドで、 jpegの他にはbmp、wav、auに対応している。開発が長らく止まっているため対応フォーマットが少なく、ステガノグラフィーの手法も古いが、今でもこの分野ではメジャーなツールらしい。

使い方

情報を埋め込む

steghide embed -cf example.jpg -ef secret.txt -p pass

-cfで画像ファイルを指定し、-efで埋め込む情報のファイルを指定、-pでパスフレーズを指定する。
画像に対して埋め込む情報が大きすぎると失敗する。
標準入力から埋め込む情報を読み込むなら、

echo secretinfo | steghide embed -cf example.jpg -ef - -p pass

このように-ef -と指定する。
また、画像はデフォルトでは上書きされる。上書きしたくないなら、

steghide embed -cf example.jpg -ef secret.txt -p pass -sf output.jpg

このように-sfで出力ファイル名を指定する。

情報を取り出す

steghide extract -sf example.jpg -p pass

-sfで画像ファイル、-pでパスフレーズを指定する。デフォルトでは、埋め込んだときと同じファイル名のファイルが作成される。
情報を標準出力に出力したいなら、

steghide extract -sf example.jpg -xf - -p pass

pngに対して使う

pngのような可逆圧縮フォーマットに対しては、いったんbmpに変換すれば一応使うことができる(bmpは無圧縮なので、pngから変換しても劣化しない)。ただし、透明度の情報が含まれている場合は変換時に消失する。

埋め込む

変換にはImagemagickを使用した。

convert example.png bmp3:example.bmp
steghide embed -cf example.bmp -ef secret.txt -p pass
convert example.bmp output.png

steghideはbmp4に対応していないので、変換時にbmp3:を指定する必要がある。

取り出す

convert output.png bmp3:output.bmp
steghide extract -sf output.bmp -xf - -p pass

参考リンク

https://manpages.debian.org/bookworm/steghide/steghide.1.en.html