hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

virshコマンドの使い方(snapshot編)

1 virsh snapshotコマンドとは?

スナップショットは仮想マシンのある時点の状態を保存する機能です。保存した状態は、あとで仮想マシンに復元することができます。たとえば、時刻T1でスナップショット(snapshot1)を取得したとします。そして、時刻T2の時にsnapshot1を仮想マシンに戻すと、仮想マシンを時刻T1の状態に戻すことができます。

       T1                                      T2
-------|---------------------------------------|-----------> 時刻
   snapshot1                               

なお、virshコマンドの使い方については、以下を参照してください。
virshコマンドの使い方 - hana_shinのLinux技術ブログ

2 検証環境

KVMホストのAlmaLinux版数は以下のとおりです。

[root@kvm ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@kvm ~]# uname -r
4.18.0-372.9.1.el8.x86_64

virshコマンドの版数は以下のとおりです。

[root@kvm ~]# virsh -v
8.0.0

3 オプション一覧

オプションは以下のとおりです。

[root@kvm ~]# virsh help snapshot
 Snapshot (ヘルプのキーワード 'snapshot'):
    snapshot-create                XML によるスナップショットの作成
    snapshot-create-as             一組の引数からのスナップショットの作成
    snapshot-current               カレントスナップショットの取得・設定
    snapshot-delete                ドメインのスナップショットの削除
    snapshot-dumpxml               ドメインのスナップショットの XML 形式ダンプ
    snapshot-edit                  スナップショットの XML の編集
    snapshot-info                  スナップショット情報
    snapshot-list                  ドメインのスナップショットの一覧表示
    snapshot-parent                スナップショットの親の名前の取得
    snapshot-revert                ドメインのスナップショットへの復帰

4 スナップショットを作成する方法(snapshot-create-as)

スナップショットを取得する仮想マシンを確認します。ここでは、vm1のスナップショットを作成します。

[root@kvm ~]# virsh list
 Id   名前   状態
---------------------
 1    vm1    実行中

vm1のスナップショットをvm1-snap1という名前で作成します。

[root@kvm ~]# virsh snapshot-create-as --domain vm1 --name vm1-snap1 --description "snap1"
ドメインのスナップショット vm1-snap1 が作成されました

スナップショットを確認すると、vm1-snap1という名前のスナップショットが作成されたことがわかります。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前        作成時間                    状態
--------------------------------------------------
 vm1-snap1   2023-01-26 07:29:51 -0500   running

5 スナップショットを削除する方法(snapshot-delete)

スナップショットを確認します。vm1-snap1という名前のスナップショットが存在することがわかります。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前        作成時間                    状態
--------------------------------------------------
 vm1-snap1   2023-01-26 07:29:51 -0500   running

vm1-snap1という名前のスナップショットを削除します。

[root@kvm ~]# virsh snapshot-delete --domain vm1 --snapshotname vm1-snap1
ドメインのスナップショット vm1-snap1 が削除されました

スナップショットを確認します。vm1-snap1という名前のスナップショットが削除されたことがわかります。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前   作成時間   状態
-------------------------

[root@kvm ~]#

6 スナップショットを切り替える方法(snapshot-revert)

スナップショットを切り替えると、仮想マシンの状態を切り替えることができます。

6.1 事前準備

スナップショットを取得する仮想マシンを確認します。ここでは、vm1のスナップショットを2つ作成してみます。

[root@kvm ~]# virsh list
 Id   名前   状態
---------------------
 1    vm1    実行中

vm1-snap1という名前のスナップショットを作成します。

[root@kvm ~]# virsh snapshot-create-as --domain vm1 --name vm1-snap1
ドメインのスナップショット vm1-snap1 が作成されました

vm1-snap2という名前のスナップショットを作成します。

[root@kvm ~]# virsh snapshot-create-as --domain vm1 --name vm1-snap2
ドメインのスナップショット vm1-snap2 が作成されました

スナップショットを確認します。スナップショットが2つ作成されたことがわかります。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前        作成時間                    状態
--------------------------------------------------
 vm1-snap1   2023-01-27 05:58:38 -0500   running
 vm1-snap2   2023-01-27 05:58:47 -0500   running

6.2 スナップショットの切り替え

動作中の仮想マシンが使用しているスナップショットをカレントスナップショットと言います。カレントスナップショットを確認すると、vm1がvm1-snap2を使っていることがわかります。

[root@kvm ~]# virsh snapshot-info --domain vm1 --current
名前:         vm1-snap2
ドメイン:   vm1
カレント:   はい (yes)
状態:         running
場所:         内部
親:            vm1-snap1
子:            0
子孫:         0
メタデータ: はい (yes)

カレントスナップショットをvm1-snap1に切り替えます。

[root@kvm ~]# virsh snapshot-revert --domain vm1 --snapshotname vm1-snap1

カレントスナップショットを確認します。vm1が使用しているスナップショットがvm1-snap1に切り替わったことがわかります。

[root@kvm ~]# virsh snapshot-info --domain vm1 --current
名前:         vm1-snap1
ドメイン:   vm1
カレント:   はい (yes)
状態:         running
場所:         内部
親:            -
子:            1
子孫:         1
メタデータ: はい (yes)

7 スナップショットの実験

スナップショットを利用して、仮想マシンの状態を過去の状態に戻してみます。時刻T2でbcパッケージをインストールしたあと、時刻T3でsnapshot2を作成します。そして、時刻T4でsnapshot1を使って、仮想マシンの状態を時刻T1の状態に戻してみます。

       T1          T2          T3                      T4
-------|-----------------------|-----------------------|---------> 時刻
   snapshot1               snapshot2

スナップショットを取得する仮想マシンを確認します。

[root@kvm ~]# virsh list
 Id   名前   状態
---------------------
 3    vm1    実行中

vm1のスナップショットを作成します。

[root@kvm ~]# virsh snapshot-create-as --domain vm1 --name snapshot1
ドメインのスナップショット snapshot1 が作成されました

作成したスナップショットを確認します。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前        作成時間                    状態
--------------------------------------------------
 snapshot1   2023-01-27 07:46:09 -0500   running

vm1のコンソールに接続します。なお、コンソールを使うためには、あらかじめvm1のカーネルコマンドラインパラメータに"console=ttyS0,115200"を追加して、grub2-mkconfigコマンドを使って、て、/boot/grub2/grub.cfgに設定変更を反映しておく必要があります。

[root@kvm ~]# virsh console vm1
Connected to domain 'vm1'
Escape character is ^] (Ctrl + ])

CentOS Linux 7 (Core)
Kernel 3.10.0-1160.el7.x86_64 on an x86_64

server login: root
パスワード:
最終ログイン: Fri Jan 27 21:36:11  ttyS0 上

bcコマンドがvm1にインストールされているかどうかを確認します。

[root@server ~]# bc
-bash: /bin/bc: そのようなファイルやディレクトリはありません

bcコマンドをインストールします。

[root@server ~]# yum -y install bc

bcコマンドがvm1にインストールされているかどうかを確認します。

[root@server ~]# bc --version
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.

Ctrl+]を押下してkvmホストに戻ります。そして、snapshot2を作成します。snapshot2にはbcパッケージがインストールされています。

[root@kvm ~]# virsh snapshot-create-as --domain vm1 --name snapshot2
ドメインのスナップショット snapshot2 が作成されました

スナップショットの一覧を確認します。

[root@kvm ~]# virsh snapshot-list --domain vm1
 名前        作成時間                    状態
--------------------------------------------------
 snapshot1   2023-01-27 07:46:09 -0500   running
 snapshot2   2023-01-27 07:48:52 -0500   running

スナップショットをsnapshot1に切り替えて、仮想マシンの状態を確認してみます。bcパッケージがインストールされていないことがわかります(期待値)。

[root@kvm ~]# virsh snapshot-revert --domain vm1 --snapshotname snapshot1

カレントのスナップショットを確認すると、snapshot1であることがわかります。

[root@kvm ~]# virsh snapshot-info --domain vm1 --current
名前:         snapshot1
ドメイン:   vm1
カレント:   はい (yes)
状態:         running
場所:         内部
親:            -
子:            1
子孫:         1
メタデータ: はい (yes)

vm1のコンソールに接続します。

[root@kvm ~]# virsh console vm1
Connected to domain 'vm1'
Escape character is ^] (Ctrl + ])

bcコマンドの版数を確認します。bcコマンドがインストールされていないことがわかります。

[root@server ~]# bc --version
-bash: /bin/bc: そのようなファイルやディレクトリはありません

Ctrl+]を押下してkvmホストに戻ります。そして、スナップショットをsnapshot2に切り替えて、仮想マシンの状態を確認してみます。bcパッケージがインストールされていることがわかります

[root@kvm ~]# virsh snapshot-revert --domain vm1 --snapshotname snapshot2

vm1のコンソールに接続します。

[root@kvm ~]# virsh console vm1
Connected to domain 'vm1'
Escape character is ^] (Ctrl + ])

仮想マシンの状態を確認してみます。bcパッケージがインストールされていることがわかります(期待値)

[root@server ~]# bc --version
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

grubbyコマンドの使い方

1 grubbyコマンドとは?

ブートローダ(GRUB2等)のメニューエントリーの表示や変更を行うコマンドです。本コマンドを使うことで、デフォルトカーネル(*)の変更、カーネルコマンドラインパラメータを変更することができます。
(*) システム再起動時に選択されるカーネル

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server  ~]# uname -r
4.18.0-372.9.1.el8.x86_64

grubbyパッケージの版数は以下のとおりです。

[root@server ~]# rpm -qa|grep grubby
grubby-8.40-42.el8.x86_64

3 オプション一覧

grubbyコマンドのオプションは以下のとおりです。

[root@server ~]# grubby --help
Usage: grubby [OPTION...]
      --add-kernel=kernel-path            add an entry for the specified kernel
      --args=args                         default arguments for the new kernel or new arguments for kernel being updated)
      --bad-image-okay                    don't sanity check images in boot entries (for testing only)
  -c, --config-file=path                  path to grub config file to update ("-" for stdin)
      --copy-default                      use the default boot entry as a template for the new entry being added; if the default is not a linux image, or if the kernel referenced by the default image does not exist, the
                                          first linux entry whose kernel does exist is used as the template
      --default-kernel                    display the path of the default kernel
      --default-index                     display the index of the default kernel
      --default-title                     display the title of the default kernel
      --env=path                          path for environment data
      --grub2                             configure grub2 bootloader
      --info=kernel-path                  display boot information for specified kernel
      --initrd=initrd-path                initrd image for the new kernel
  -i, --extra-initrd=initrd-path          auxiliary initrd image for things other than the new kernel
      --make-default                      make the newly added entry the default boot entry
      --remove-args=STRING                remove kernel arguments
      --remove-kernel=kernel-path         remove all entries for the specified kernel
      --set-default=kernel-path           make the first entry referencing the specified kernel the default
      --set-default-index=entry-index     make the given entry index the default entry
      --title=entry-title                 title to use for the new kernel entry
      --update-kernel=kernel-path         updated information for the specified kernel
      --zipl                              configure zipl bootloader
  -b, --bls-directory                     path to directory containing the BootLoaderSpec fragment files
      --no-etc-grub-update                don't update the GRUB_CMDLINE_LINUX variable in /etc/default/grub

Help options:
  -?, --help                              Show this help message

4 事前準備

検証環境としてAlmaLinux8.6を使っていますが、現在(2023/1/21)のAlmaLinux最新版はAlmaLinux8.7です。事前準備として、AlmaLinux8.7をインストールしておきます。

[root@server ~]# dnf install kernel

カーネル版数を確認すると、以下のようになっています。
・kernel-4.18.0-372.9.1.el8.x86_64:AlmaLinux8.6
・kernel-4.18.0-425.10.1.el8_7.x86_64:AlmaLinux8.7(アップデート版)

[root@server  ~]# rpm -qa|grep kernel-4
kernel-4.18.0-372.9.1.el8.x86_64
kernel-4.18.0-425.10.1.el8_7.x86_64

5 エントリを表示する方法

5.1 全エントリを表示する方法(--info=ALL)

--info=ALLは、全てのエントリを表示するオプションです。
実行結果を確認すると、下記4つのエントリがあることがわかります。
・index=0
・index=1
・index=2
・index=3
index=0はAlmaLinux8.7、index=1はAlmaLinux8.6のエントリです。

[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-425.10.1.el8_7.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-425.10.1.el8_7.x86_64) 8.7 (Stone Smilodon)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

5.2 特定のエントリを表示する方法(--info=パス名)

/boot配下にvmlinuz-4.18.0-425.10.1.el8_7.x86_64が存在することを確認します。

[root@server ~]# ls /boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64
/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64

grubbyコマンドを実行して、vmlinuz-4.18.0-425.10.1.el8_7.x86_64エントリの情報を表示してみます。

[root@server ~]# grubby --info=/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64
index=0
kernel="/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-425.10.1.el8_7.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-425.10.1.el8_7.x86_64) 8.7 (Stone Smilodon)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64"

次に、/boot配下にvmlinuz-4.18.0-372.9.1.el8.x86_64が存在することを確認します。

[root@server ~]# ls /boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64

grubbyコマンドを実行して、vmlinuz-4.18.0-372.9.1.el8.x86_64エントリの情報を表示してみます。

[root@server ~]# grubby --info=/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"

6 デフォルトカーネルの情報を表示する方法

6.1 インデックス番号を表示する方法(--default-index)

--default-indexは、デフォルトカーネルのインデックス番号を表示するオプションです。実行結果を確認すると、デフォルトカーネルのインデックス番号が0であることがわかります。

[root@server ~]# grubby --default-index
0

6.2 カーネルパスを表示する方法(--default-kernel)

--default-kernelは、デフォルトカーネルのパスを表示するオプションです。実行結果を確認すると、デフォルトカーネルのパスが/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64であることがわかります。

[root@server ~]# grubby --default-kernel
/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64

7 デフォルトカーネルを変更する方法

7.1 インデックスを使う方法(--set-default-index)

--set-default-indexは、デフォルトカーネルのインデックス番号を変更するオプションです。デフォルトカーネルのインデックス番号を確認すると、1であることがわかります。

[root@server ~]# grubby --default-index
1

デフォルトカーネルのインデックス番号を0に変更します。

[root@server ~]# grubby --set-default-index=0
The default is /boot/loader/entries/472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64.conf with index 0 and kernel /boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64

デフォルトカーネルのインデックス番号を確認すると、0に変更されたことがわかります。

[root@server ~]# grubby --default-index
0

システムを再起動します。

[root@server ~]# shutdown -r now

システム再起動後も、デフォルトカーネルのインデックス番号が0であることがわかります。

[root@server ~]# grubby --default-index
0

7.2 カーネルパスを使う方法(--set-default)

デフォルトカーネル変更前の状態を確認します。デフォルトカーネルのパスが、/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64であることがわかります。

[root@server ~]# grubby --default-kernel
/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64

デフォルトカーネルのパスをvmlinuz-4.18.0-372.9.1.el8.x86_64に変更するため、--set-defaultオプションに変更するカーネルパスを指定します。

[root@server ~]# grubby --set-default=/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
The default is /boot/loader/entries/472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64.conf with index 1 and kernel /boot/vmlinuz-4.18.0-372.9.1.el8.x86_64

デフォルトカーネルのパスが/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64に変更されたことがわかります。

[root@server ~]# grubby --default-kernel
/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64

システムを再起動します。

[root@server ~]# shutdown -r now

システム再起動後も、デフォルトカーネルのパスが/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64であることがわかります。

[root@server ~]# grubby --default-kernel
/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64

8 カーネルコマンドラインパラメーターを追加/削除する方法

8.1 削除する方法(--remove-args)

--remove-argsは、カーネルコマンドラインパラメータを削除するオプションです。カーネルコマンドラインパラメーターは、args行に定義されています。

カーネルコマンドラインパラメータ変更前の状態を確認します。

[root@server ~]# grubby --info=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"

カーネルコマンドラインパラメータから、rhgbとquietを削除します。

[root@server ~]# grubby --remove-args="rhgb quiet" --update-kernel=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64

システムを再起動します。

[root@server ~]# shutdown -r now

カーネルコマンドラインパラメータを確認すると、rhgbとquietが削除されたことがわかります。

[root@server ~]# grubby --info=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"

8.2 追加する方法(--args)

カーネルコマンドラインパラメータを変更する前の状態を確認します。

[root@server  ~]# grubby --info=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"

カーネルコマンドラインパラメータに、rhgbとquietを追加します。

[root@server ~]# grubby --args="rhgb quiet" --update-kernel=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x8
6_64

システムを再起動します。

[root@server ~]# shutdown -r now

カーネルコマンドラインパラメータを確認すると、rhgbとquietが追加(args行の末尾)されたことがわかります。

[root@server ~]# grubby --info=/boot/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"

8.3 全エントリのカーネルコマンドラインパラメータを一括で変更する方法(--update-kernel=ALL)

全エントリのカーネルコマンドラインパラメータにconsole=ttyS0と115200を追加してみます。

[root@server ~]# grubby --update-kernel=ALL --args=console=ttyS0,115200

システムを再起動します。

[root@server  ~]# shutdown -r now

エントリを確認すると、全てのエントリにカーネルコマンドラインパラメータにconsole=ttyS0と115200が追加されたことがわかります。

[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-425.10.1.el8_7.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-425.10.1.el8_7.x86_64) 8.7 (Stone Smilodon)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

9 エントリを追加/削除する方法

9.1 エントリを削除する方法(--remove-kernel)

--remove-kernelは、エントリを削除するオプションです。

エントリを削除する前の状態を確認します。全部で3つエントリ(index=0,1,2)があることがわかります。

[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200 $tuned_params"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-425.10.1.el8_7.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-425.10.1.el8_7.x86_64) 8.7 (Stone Smilodon)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64のエントリを削除します。

[root@server  ~]# grubby --remove-kernel=/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64

/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64のエントリが削除され、エントリが2つに減ったことがわかります。

[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=1
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

9.2 エントリを追加する方法(--add-kernel)

--add-kernelは、エントリを追加するオプションです。全部で2つエントリがあることがわかります。

/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64
[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=1
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64をエントリに追加します。このとき、title,initrd,カーネルパラメータも指定します。カーネルパラメータは、デフォルトカーネルのパラメータ使用(copy-default)を指定してみます。

[root@server  ~]# grubby --add-kernel=/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64 --title="AlmaLinux8.9" --initrd=initramfs-4.18.0-425.10.1.el8_7.x86_64.img --copy-default

/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64が追加され、エントリが3つに増えたことがわかります。

[root@server  ~]# grubby --info=ALL
index=0
kernel="/boot/vmlinuz-4.18.0-425.10.1.el8_7.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-425.10.1.el8_7.x86_64.img"
title="AlmaLinux8.9"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-425.10.1.el8_7.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd biosdevname=0 net.ifnames=0 $tuned_params rhgb quiet console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="AlmaLinux (4.18.0-372.9.1.el8.x86_64) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-4.18.0-372.9.1.el8.x86_64"
index=2
kernel="/boot/vmlinuz-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6"
args="ro crashkernel=auto resume=UUID=707b4445-131d-4800-96e4-c230897e61dd rhgb quiet biosdevname=0 net.ifnames=0 console=ttyS0,115200"
root="UUID=65d68ce4-ac21-4d22-8858-341c473ff5b9"
initrd="/boot/initramfs-0-rescue-472fe4b9a58a49d9bbadb8eab023afb6.img"
title="AlmaLinux (0-rescue-472fe4b9a58a49d9bbadb8eab023afb6) 8.6 (Sky Tiger)"
id="472fe4b9a58a49d9bbadb8eab023afb6-0-rescue"

システムを再起動します。

[root@server  ~]# shutdown -r now

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

41.2. grubby とは Red Hat Enterprise Linux 8 | Red Hat Customer Portal

SystemTapの使い方(User-Space Probing)

1 User-Space Probingとは?

SystemTapカーネルだけでなく、ユーザプログラムに対しても使うことができます。ここでは、ユーザプログラムに対するSystemTapの使い方を説明します。

他にもSystemTapの記事を作成したので参考にしてください。
SystemTapの使い方 - hana_shinのLinux技術ブログ

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

systemtapパッケージをインストールします。

[root@server ~]# dnf -y install systemtap

stapコマンドの版数を確認します。

[root@server ~]# stap -V
Systemtap translator/driver (version 4.7/0.186/0.187, rpm 4.7-1.el8)
Copyright (C) 2005-2022 Red Hat, Inc. and others
This is free software; see the source for copying conditions.
tested kernel versions: 2.6.32 ... 5.18-rc3
enabled features: AVAHI BOOST_STRING_REF DYNINST BPF JAVA PYTHON3 LIBRPM LIBSQLITE3 LIBVIRT LIBXML2 NLS NSS READLINE MONITOR_LIBS

3 debuginfoパッケージのインストール

kernel-debuginfoパッケージをインストールするため、リポジトリを追加します。

[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/debug/x86_64/

kernel-debuginfoパッケージをインストールします。

[root@server ~]# dnf install kernel-debuginfo-4.18.0-372.9.1.el8

kernel-develパッケージをインストールするため、リポジトリを追加します。

[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/x86_64/os/

kernel-develパッケージをインストールします。

[root@server ~]# dnf install kernel-devel-4.18.0-372.9.1.el8.x86_64

4 ユーザプログラムにプローブを設定する方法

4.1 テストプログラムの作成

テストプログラム(以降TP)を作成します。

[root@server ~]# cat tp.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int func1(int x)
{
  int fd;

  fd = open("/tmp/test1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  printf("func1\n");
  close(fd);
  return 1;
}

int func2(int x)
{
  int fd;

  fd = open("/tmp/test2.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  printf("func2\n");
  close(fd);

  return 2;
}

int main(int argc, char *argv[])
{
  int ret;

  for(;;) {
    ret = func1(10);
    sleep(2);

    ret = func2(20);
    sleep(2);
  }
  return ret;
}

-gオプションを付けて、TPをコンパイルします。

[root@server ~]# gcc -Wall -g -o tp tp.c

実行ファイルを確認します。

[root@server ~]# ls -l tp*
-rwxr-xr-x. 1 root root 21080  1月 17 20:26 tp
-rw-r--r--. 1 root root   516  1月 17 20:25 tp.c

4.2 関数の入り口にプローブを設定する方法

func1()の入り口に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。

[root@server ~]# cat tp.stp
#!/usr/bin/stap

probe process("/root/tp").function("func1")
{
  printf("pp=%s\n", pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg tp.stp
Pass 1: parsed user script and 485 library scripts using 295516virt/96596res/18164shr/80232data kb, in 340usr/100sys/455real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 297100virt/99344res/19144shr/81816data kb, in 10usr/0sys/15real ms.
Pass 3: using cached /root/.systemtap/cache/9a/stap_9aac9b9924135443eec26a77b23d2146_1168.c
Pass 4: using cached /root/.systemtap/cache/9a/stap_9aac9b9924135443eec26a77b23d2146_1168.ko
Pass 5: starting run.

もう1つターミナルを開いてTPを実行します。

[root@server ~]# ./tp

SystemTapの実行結果を確認すると、プローブの情報(func1()がtp.cファイルの6行目に定義されている)が表示されていることがわかります。

[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").function("func1@/root/tp.c:6")
pp=process("/root/tp").function("func1@/root/tp.c:6")

ファイルを確認すると、tp.cの6行目にfunc1()が定義されていることがわかります。

[root@server ~]# less -N /root/tp.c
-snip-
      5
      6 int func1(int x)
      7 {

4.3 関数から復帰する場所にプローブを設定する方法

func2()から復帰する場所に設定したプローブが実行されると、pp()の実行とfunc2()の戻り値を表示するスクリプトを作成します。

[root@server ~]# cat tp.stp
#!/usr/bin/stap

probe process("/root/tp").function("func2").return
{
  printf("pp=%s, ret=%d\n", pp(), $return)
}

スクリプトを実行します。

[root@server ~]# stap -vg tp.stp

もう1つターミナルを開いてTPを実行します。

[root@server ~]# ./tp

SystemTapの実行結果を確認すると、プローブの情報(func2()がtp.cファイルの16行目に定義されている)とfunc2()の戻り値(2)が表示されていることがわかります。

[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").function("func2@/root/tp.c:16").return, ret=2
pp=process("/root/tp").function("func2@/root/tp.c:16").return, ret=2

4.4 ファイルの指定した場所にプローブを設定する方法

プローブを設定する場所を確認するため、lessコマンドでtp.cを参照します。ここでは、tp.cの10行目にプローブを設定します。

[root@server ~]# less -N /root/tp.c
-snip-
      9
     10   fd = open("/tmp/test1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
     11   printf("func1\n");

tp.cの10行目に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。

[root@server ~]# cat tp.stp
#!/usr/bin/stap

probe process("/root/tp").statement("func1@/root/tp.c:10")
{
  printf("pp=%s\n", pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg tp.stp

もう1つターミナルを開いてTPを実行します。

[root@server ~]# ./tp

SystemTapの実行結果を確認すると、プローブの情報(設定したプローブがtp.cの10行目)が表示されていることがわかります。

[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").statement("func1@/root/tp.c:10")
pp=process("/root/tp").statement("func1@/root/tp.c:10")

5 httpdにプローブを設定する方法

httpdソースコードにプローブを設定してみます。

5.1 debuginfoパッケージのインストール

httpdパッケージと同じ版数のhttpd-debuginfoパッケージをインストールするため、まず、httpdパッケージの版数を確認します。

[root@server ~]# rpm -qa|grep httpd
almalinux-logos-httpd-84.5-1.el8.noarch
httpd-tools-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
httpd-filesystem-2.4.37-51.module_el8.7.0+3281+01e58653.noarch
httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64

appstream-debuginfoリポジトリを有効にします。

[root@server ~]# dnf config-manager --enable appstream-debuginfo

httpd-debuginfoパッケージをインストールします。

[root@server ~]# dnf install httpd-debuginfo-2.4.37-51.module_el8.7.0+3281+01e58653

httpd-debuginfoパッケージの版数を確認します。

[root@server ~]# rpm -qa|grep httpd
httpd-debuginfo-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
almalinux-logos-httpd-84.5-1.el8.noarch
httpd-tools-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
httpd-filesystem-2.4.37-51.module_el8.7.0+3281+01e58653.noarch
httpd-debugsource-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64

5.2 httpdのmain()にプローブを設定する方法

whichコマンドを実行して、SystemTapスクリプトに記述するhttpdのパスを確認します。

[root@server ~]# which httpd
/usr/sbin/httpd

httpdプログラムのmain()入り口にプローブを設定します。そして、プローブが実行されると、pp()を実行するスクリプトを作成します。

[root@server ~]# cat httpd.stp
#!/usr/bin/stap

probe process("/usr/sbin/httpd").function("main")
{
  printf("pp=%s\n", pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg httpd.stp
Pass 1: parsed user script and 485 library scripts using 295520virt/96560res/18132shr/80236data kb, in 330usr/60sys/394real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 300668virt/103140res/19312shr/85384data kb, in 50usr/0sys/51real ms.
Pass 3: using cached /root/.systemtap/cache/47/stap_47ccd673e95239f6187c55d0d22ba59f_1332.c
Pass 4: using cached /root/.systemtap/cache/47/stap_47ccd673e95239f6187c55d0d22ba59f_1332.ko
Pass 5: starting run.

httpdサービスを起動します。

[root@server ~]# systemctl start httpd

SystemTapの実行結果を確認すると、プローブの情報(main()がmain.cファイルの466行目に定義されている)が表示されていることがわかります。

[root@server ~]# stap -vg httpd.stp
-snip-
pp=process("/usr/sbin/httpd").function("main@/usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/main.c:466")

httpdのソースファイルを確認すると、main()がmain.cファイルの466行目に定義されていることがわかります。

[root@server ~]# less -N /usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/main.c
-snip-
    465
    466 int main(int argc, const char * const argv[])
    467 {

6 動作中のhttpdプロセスにプローブを設定する方法

動作中のhttpdプロセスにプローブを設定する方法について説明します。psコマンドを実行して、プローブを設定するプロセスのPIDを確認します。ここでは、PID=10066 のhttpdに対してプローブポイントを設定してみます。

[root@server ~]# ps -C httpd
    PID TTY          TIME CMD
  10066 ?        00:00:00 httpd
  10067 ?        00:00:00 httpd
  10068 ?        00:00:00 httpd
  10069 ?        00:00:00 httpd
  10070 ?        00:00:00 httpd

プローブを設定する場所を確認するため、lessコマンドでmpm_common.cを参照します。mpm_common.cのap_wait_or_timeout()関数入り口にプローブを設定します。

[root@server ~]# less -N /usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/mpm_common.c
-snip-
    177 AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode,
    178                                     apr_proc_t *ret, apr_pool_t *p,
    179                                     server_rec *s)
    180 {

ap_wait_or_timeout()入り口にプローブを設定します。そして、プローブが実行されるとap_wait_or_timeoutI()への引数の値を表示するスクリプトを作成します。

[root@server ~]# cat httpd.stp
#!/usr/bin/stap

probe process(10066).function("ap_wait_or_timeout")
{
  printf("%s\n", $$parms)
}

SystemTapの実行結果を確認すると、ap_wait_or_timeout()への引数の値が表示されていることがわかります。

[root@server ~]# stap -vg httpd.stp
Pass 1: parsed user script and 485 library scripts using 295516virt/96620res/18192shr/80232data kb, in 260usr/330sys/613real ms.
Pass 2: analyzed script: 1 probe, 5 functions, 0 embeds, 0 globals using 300664virt/103216res/19372shr/85380data kb, in 60usr/0sys/75real ms.
Pass 3: using cached /root/.systemtap/cache/ff/stap_ffe11129161d317f17c1f7beecc71453_1899.c
Pass 4: using cached /root/.systemtap/cache/ff/stap_ffe11129161d317f17c1f7beecc71453_1899.ko
Pass 5: starting run.
status=0x7ffc2c5e3898 exitcode=0x7ffc2c5e389c ret=0x7ffc2c5e38a0 p=0x5651d4e4ba18 s=0x5651d4e74cc0
status=0x7ffc2c5e3898 exitcode=0x7ffc2c5e389c ret=0x7ffc2c5e38a0 p=0x5651d4e4ba18 s=0x5651d4e74cc0
-snip-

7 ライブラリ関数にプローブを設定する方法

glibc等の共有ライブラリ関数にプローブを設定する方法について説明します。

7.1 debuginfoパッケージのインストール

[root@server ~]# dnf install glibc-debuginfo-2.28-189.1.el8.x86_64

7.2 事前準備

mallocを呼び出すテストプログラム(以降TP)を作成します。

[root@server ~]# cat tp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
  char *ptr;

  for(;;) {
    printf("malloc\n");
    ptr = malloc(100);
    free(ptr);
    sleep(2);
  }
  return 0;
}

TPをコンパイルします。

[root@server ~]# gcc -Wall -g -o tp tp.c

lddコマンドを実行してTPがリンクする共有ライブラリのパスを確認します。

[root@server ~]# ldd tp
        linux-vdso.so.1 (0x00007ffef25ac000)
        libc.so.6 => /lib64/libc.so.6 (0x00007ff6b2060000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff6b2425000)

7.3 動作確認

malloc()入り口にプローブを設定します。そして、TPがmalloc()を呼び出すと、pp()を実行するスクリプトを作成します。

[root@server ~]# cat lib.stp
#!/usr/bin/stap

probe process("/lib64/libc.so.6").function("malloc")
{
  if(execname()=="tp"){
    printf("pp=%s\n", pp());
  }
}

SystemTapの使い方

1 SystemTapとは?

SystemTapは、カーネルやアプリの動作を追跡するツールです。SystemTapを使うと、プログラムのバグやパフォーマンス問題の原因を特定することができます。本記事では、SystemTapの基本的な使い方を説明します。

他にもSystemTapの記事を作成したので参考にしてください。
SystemTapの使い方(User-Space Probing) - hana_shinのLinux技術ブログ

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

3 事前準備

systemtapパッケージをインストールします。

[root@server ~]# dnf -y install systemtap

kernel-debuginfoパッケージをインストールするため、リポジトリを追加します。

[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/debug/x86_64/

kernel-debuginfoパッケージをインストールします。

[root@server ~]# dnf install kernel-debuginfo-4.18.0-372.9.1.el8

kernel-develパッケージをインストールするため、リポジトリを追加します。

[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/x86_64/os/

kernel-develパッケージをインストールします。

[root@server ~]# dnf install kernel-devel-4.18.0-372.9.1.el8.x86_64

4 プローブの設定方法

プローブは下記ソースコードに設定することができます。

カーネル
カーネルモジュール(lsmodで表示されるモジュールのことです。たとえばbridge)
・アプリ(Apache等)

ソースコードに設定したプローブが実行されると、あらかじめプローブに定義した処理が実行されます。ここでは、カーネルカーネルモジュールにプローブを設定する方法を説明します。アプリにプローブを設定する方法については、SystemTapの使い方(User-Space Probing) - Qiitaに記事を作成していますが、別途hatenaに記事を作成する予定です。

ソースコードに設定するプローブの書式は以下のとおりです、

書式 概要
kernel.function("関数名@ファイルのパス名") カーネル関数の入り口にプローブを設定
kernel.function("関数名@ファイルのパス名").return カーネル関数から復帰する場所にプローブを設定
kernel.statement("関数名@ファイルのパス名:行数") ファイルの指定した場所にプローブを設定
module.function("関数名@ファイルのパス名") カーネルモジュールで定義されている関数の入り口にプローブを設定
module.function("関数名@ファイルのパス名").return カーネルモジュールで定義されている関数から復帰する場所にプローブを設定
module.statement("関数名@ファイルのパス名") ファイルの指定した場所にプローブを設定
process("アプリの実行ファイルのパス名").function("関数名") アプリの関数入り口にプローブを設定
process("アプリの実行ファイルのパス名").function("関数名").return アプリの関数から復帰する場所にプローブを設定

4.1 カーネルにプローブを設定する方法

4.1.1 カーネル関数の入り口にプローブを設定する方法

カーネル関数にping_v4_sendmsg()があります。pingコマンドを実行すると実行されます。ここでは、ping_v4_sendmsg()の入り口に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。pp()は後述しますが、プローブの情報(関数名、ファイルのパス名)を表示するSystemTapの組み込み関数です。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c")
{
  printf("pp=%s\n",pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp
Pass 1: parsed user script and 485 library scripts using 295520virt/96632res/18204shr/80236data kb, in 360usr/110sys/482real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 487036virt/270636res/19320shr/271752data kb, in 1740usr/480sys/2363real ms.
Pass 3: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.c
Pass 4: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.ko
Pass 5: starting run.

pingコマンドを1回実行します。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果の一番下を確認すると、プローブの情報(ping_v4_sendmsg()がping.cファイルの701行目で定義されている)が表示されていることがわかります。

[root@server ~]# stap -vg ping.stp
Pass 1: parsed user script and 485 library scripts using 295520virt/96632res/18204shr/80236data kb, in 360usr/110sys/482real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 487036virt/270636res/19320shr/271752data kb, in 1740usr/480sys/2363real ms.
Pass 3: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.c
Pass 4: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.ko
Pass 5: starting run.
pp=kernel.function("ping_v4_sendmsg@net/ipv4/ping.c:701")

一方、カーネルのソースファイルを確認すると、ping_v4_sendmsg()がping.cファイルの701行目で定義されていることがわかります。

[root@server ~]# less -N /usr/src/debug/kernel-4.18.0-372.9.1.el8/linux-4.18.0-372.9.1.el8.x86_64/net/ipv4/ping.c
-snip-
    701 static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
    702 {
    703         struct net *net = sock_net(sk);
4.1.2 カーネル関数から復帰する場所にプローブを設定する方法

ping_v4_sendmsg()が復帰する場所に設定したプローブが実行されると、pp()の実行とping_v4_sendmsg()の戻り値を表示するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c").return
{
  printf("pp=%s, ret=%d\n",pp(), $return)
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、プローブの情報とping_v4_sendmsg()の戻り値(64)が表示されていることがわかります。なお、ping_v4_sendmsg()の戻り値はICMP echoパケットのパケット長を表します。

[root@server ~]# stap -vg ping.stp
-snip-
pp=kernel.function("ping_v4_sendmsg@net/ipv4/ping.c:701").return, ret=64
4.1.3 ファイルの指定した場所にプローブを設定する方法

プローブを設定する場所を確認するため、lessコマンドでping.cを参照します。ここでは、ping.cの749行目にプローブを設定します。

[root@server ~]# less -N /usr/src/debug/kernel-4.18.0-372.9.1.el8/linux-4.18.0-372.9.1.el8.x86_64/net/ipv4/ping.c
-snip-
    701 static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
    702 {
    703         struct net *net = sock_net(sk);

    -snip-

    748         ipc.tos = -1;
    749         ipc.sockc.transmit_time = 0;
    750
    751         if (msg->msg_controllen) {

ping.cの749行目に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.statement("ping_v4_sendmsg@net/ipv4/ping.c:749")
{
  printf("pp=%s\n",pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、プローブの情報(設定したプローブがping.cの749行目)が表示されていることがわかります。

[root@server ~]# stap -vg ping.stp
-snip-
pp=kernel.statement("ping_v4_sendmsg@net/ipv4/ping.c:749")

4.2 カーネルモジュールにプローブを設定する方法

4.2.1 モジュール内の特定の関数にプローブを設定する方法

nf_conntrackモジュールにnf_ct_net()という関数が定義されています。ここでは、pingプロセスがnf_ct_net()を実行するとプローブ情報を表示するスクリプトを作成します。

[root@server ~]# cat nf.stp
#!/usr/bin/stap

probe module("nf_conntrack").function("nf_ct_net")
{
  if(execname()=="ping"){
    printf("pp=%s\n", pp())
  }
}

スクリプトを実行します。

[root@server ~]# stap -vg nf.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、プローブの情報(設定したプローブがnf_conntrack.hの138行目)が表示されていることがわかります。

[root@server ~]# stap -vg nf.stp
-snip-
pp=module("nf_conntrack").function("nf_ct_net@./include/net/netfilter/nf_conntrack.h:138")
pp=module("nf_conntrack").function("nf_ct_net@./include/net/netfilter/nf_conntrack.h:138")
pp=module("nf_conntrack").function("nf_ct_net@./include/net/netfilter/nf_conntrack.h:138")
-snip-
4.2.2 モジュール内の全ての関数にプローブを設定する方法

pingプロセスがnf_conntrackモジュールの関数を実行すると、プローブの情報を表示するスクリプトを作成します。

[root@server stap]# cat ping.stp
#!/usr/bin/stap

probe module("nf_conntrack").function("*")
{
  if(execname()=="ping"){
    printf("pp=%s\n", pp())
  }
}

スクリプトを実行します。

[root@server stap]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、nf_conntrackモジュールに定義されている関数が表示されていることがわかります。

[root@server stap]# stap -vg ping
-snip-
pp=module("nf_conntrack").function("ipv4_conntrack_local@net/netfilter/nf_conntrack_proto.c:406")
pp=module("nf_conntrack").function("skb_network_header@./include/linux/skbuff.h:2551")
pp=module("nf_conntrack").function("ip_hdr@./include/linux/ip.h:23")
pp=module("nf_conntrack").function("nf_conntrack_in@net/netfilter/nf_conntrack_core.c:1786")
pp=module("nf_conntrack").function("nf_ct_get@./include/net/netfilter/nf_conntrack.h:157")
pp=module("nf_conntrack").function("skb_network_header@./include/linux/skbuff.h:2551")
-snip-

5 関数の使い方

今までの説明でpp()という関数を使用してきましたが、pp()はSystemTapの組み込み関数です。組み込み関数は、stapコマンドのオプションに--dump-functionsを指定すると、その一覧を表示することができます。

[root@server ~]# stap --dump-functions
AF_INET6:long () /* unprivileged */
AF_INET:long () /* unprivileged */
HZ:long () /* unprivileged */
MAJOR:long (dev:long)
MINOR:long (dev:long)
MKDEV:long (major:long, minor:long)
-snip-

また、関数への引数の変数名、型は次のようにして確認することができます。tcp_v4_rcv()で使える変数名はskb、型はsk_buff構造体へのポインタであることがわかります。

[root@server ~]# stap -L 'kernel.function("tcp_v4_rcv")'
kernel.function("tcp_v4_rcv@net/ipv4/tcp_ipv4.c:1846") $skb:struct sk_buff*

もう1つip_rcv()で使える変数、変数の型を調べてみます。ip_rcv()使える変数名はskb,dev,pt,orig_devの4つであることがわかります。

[root@server ~]# stap -L 'kernel.function("ip_rcv")'
kernel.function("ip_rcv@net/ipv4/ip_input.c:417") $skb:struct sk_buff* $dev:struct net_device* $pt:struct packet_type* $orig_dev:struct net_device*

5.1 プローブの情報を表示する方法(pp)

pp()はプローブの情報を表示する関数です。ping_v4_sendmsg()を実行したら、プローブの情報を表示するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c")
{
  printf("pp=%s\n",pp())
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp
Pass 1: parsed user script and 485 library scripts using 295520virt/96632res/18204shr/80236data kb, in 360usr/110sys/482real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 487036virt/270636res/19320shr/271752data kb, in 1740usr/480sys/2363real ms.
Pass 3: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.c
Pass 4: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.ko
Pass 5: starting run.

pingコマンドを1回実行します。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、プローブの情報(ping_v4_sendmsg()がping.cファイルの701行目で定義されている)が表示されていることがわかります。

[root@server ~]# stap -vg ping.stp
Pass 1: parsed user script and 485 library scripts using 295520virt/96632res/18204shr/80236data kb, in 360usr/110sys/482real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 487036virt/270636res/19320shr/271752data kb, in 1740usr/480sys/2363real ms.
Pass 3: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.c
Pass 4: using cached /root/.systemtap/cache/87/stap_87a3e7f96d3549d2ec26065140f22b46_1174.ko
Pass 5: starting run.
pp=kernel.function("ping_v4_sendmsg@net/ipv4/ping.c:701")

一方、カーネルのソースファイルを確認すると、ping_v4_sendmsg()がping.cファイルの701行目で定義されていることがわかります。

[root@server ~]# less -N /usr/src/debug/kernel-4.18.0-372.9.1.el8/linux-4.18.0-372.9.1.el8.x86_64/net/ipv4/ping.c
-snip-
    701 static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
    702 {
    703         struct net *net = sock_net(sk);

5.2 呼び出し元の関数名を表示する方法(caller)

caller()は関数の呼び出し元の関数名を表示する関数です。ping_v4_sendmsg()の入り口に設定したプローブが実行されると、ping_v4_sendmsgの呼びだし元関数名を表示するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c")
{
  printf("caller=%s\n",caller())
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、ping_v4_sendmsg()の呼び出し元関数がsock_sendmsg()であることがわかります。

[root@server ~]# stap -vg ping.stp
-snip-
caller=sock_sendmsg 0xffffffffb43b752e

5.3 時刻を表示する方法(gettimeofday_s,tz_ctime)

ping_v4_sendmsg()の入り口に設定したプローブが実行されると、ping_v4_sendmsg()実行時の時刻を表示するスクリプトを作成します。なお、gettimeofday_s()は、Unix epoch (1970年1月1日) からの秒数を求める関数です。tz_ctime()は、Unix epoch からの経過時間(秒)をタイムゾーンにもとずいて時刻に変換する関数です。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c")
{
  printf("time=%s\n", tz_ctime(gettimeofday_s()))
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、ping_v4_sendmsg()を実行したときの時刻が、2023年1月13日 22:22:11であることがわかります。

[root@server ~]# stap -vg ping.stp
-snip-
time=Fri Jan 13 22:22:11 2023 JST

5.4 関数を実行するプロセス名を表示する方法(execname)

ping_v4_sendmsg()の入り口に設定したプローブが実行されると、ping_v4_sendmsg()を実行するプロセス名を表示するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ping_v4_sendmsg@net/ipv4/ping.c")
{
  printf("execname=%s\n",execname())
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、ping_v4_sendmsg()を実行したプロセスがpingプロセスであることがわかります。

[root@server ~]# stap -vg ping.stp
-snip-
execname=ping

もう1つexecname()の使い方を紹介します。pingプロセスがip_output()を実行したときの時刻を表示してみます。なお、時刻表示をpingプロセスに限定している理由は、様々なプロセスが外部と通信をするときにip_output()を実行するので、pingプロセスに限定しないと大量にSystemTapの実行結果が表示されるためです。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
  if(execname()=="ping")
  {
    printf("time=%s\n", tz_ctime(gettimeofday_s()))
  }
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、pingプロセスがip_output()を実行したときの時刻が1/13(金) 22:25:59であることがわかります。

[root@server ~]# stap -vg ping.stp
-snip-
time=Fri Jan 13 22:25:59 2023 JST

5.5 バックトレースを表示する方法(print_backtrace)

ip_output()の入り口に設定したプローブをpingプロセスが実行したときだけ、バックトレースを表示するスクリプトを作成します。バックトレースとは、関数の呼び出し元を大元の呼び出しもとに向かって関数名を表示する機能です。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
  if(execname()=="ping")
  {
    printf("backtrace=%s\n", print_backtrace())
  }
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを1回実行します。

[root@server ~]# ping -c 1 192.168.122.1

SystemTapの実行結果を確認すると、ip_output()mの呼び出し元はip_send_skb()、ip_send_skb()の呼び出し元はping_v4_sendmsg()であることがわかります。そして大元の呼び出し元がentry_SYSCALL_64_after_hwframe()であることがわかります。

[root@server ~]# stap -vg ping.stp
Pass 1: parsed user script and 485 library scripts using 295516virt/96608res/18180shr/80232data kb, in 330usr/80sys/425real ms.
Pass 2: analyzed script: 1 probe, 2 functions, 0 embeds, 0 globals using 487044virt/270768res/19296shr/271760data kb, in 1550usr/370sys/1996real ms.
Pass 3: using cached /root/.systemtap/cache/b5/stap_b5b4816c556b1e0b346fac195cb11a8a_1350.c
Pass 4: using cached /root/.systemtap/cache/b5/stap_b5b4816c556b1e0b346fac195cb11a8a_1350.ko
Pass 5: starting run.
 0xffffffffae47c780 : ip_output+0x0/0xe0 [kernel]
 0xffffffffae47d155 : ip_send_skb+0x15/0x40 [kernel]
 0xffffffffae4cdf92 : ping_v4_sendmsg+0x582/0x7b0 [kernel]
 0xffffffffae3b752e : sock_sendmsg+0x3e/0x50 [kernel]
 0xffffffffae3b949e : __sys_sendto+0xee/0x160 [kernel]
 0xffffffffae3b9534 : __x64_sys_sendto+0x24/0x30 [kernel]
 0xffffffffadc0430b : do_syscall_64+0x5b/0x1a0 [kernel]
 0xffffffffae6000ad : entry_SYSCALL_64_after_hwframe+0x65/0xca [kernel]
 0xffffffffae6000ad : entry_SYSCALL_64_after_hwframe+0x65/0xca [kernel] (inexact)
backtrace=

5.6 CPU番号を表示する方法(cpu)

ip_output()の入り口に設定したプローブをpingプロセスが実行したときだけ、CPU番号を表示するスクリプトを作成します。

[root@server ~]# cat ping.stp
#!/usr/bin/stap

probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
  if(execname()=="ping")
  {
    printf("cpu=%d\n", cpu())
  }
}

スクリプトを実行します。

[root@server ~]# stap -vg ping.stp

pingコマンドを3回実行します。

[root@server ~]# ping -c 3 192.168.122.1

実行結果を確認すると、pingプロセスは1回目はip_output()をCPU2で実行しています。そして、2回目,3回目はip_output()をCPU3で実行していることがわかります。

[root@server stap]# stap -vg ping.stp
-snip-
cpu=2
cpu=3
cpu=3

iptables-extensionsの使い方

1 iptables-extensionsとは?

拡張パケットマッチングモジュールのことです。iptables-extensionsを使うと、マッチする条件を詳細に指定することができます。

INPUTチェインでマッチングモジュールを指定する場合、以下のように使います。モジュール名には、mac,icmp等を指定します。ターゲットについては、iptablesコマンドの使い方(ターゲットの使い方) - hana_shinのLinux技術ブログを参照してください。

iptables -A INPUT -m <モジュール名> <モジュールのオプション> <ターゲット>

manは以下のようにして参照します。

[root@server ~]# man iptables-extensions

なお、iptablesコマンドの使い方は、iptablesコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のeth0はNICの名前です。

                          192.168.122.0/24
client(eth0) -------------------------------------------(eth0) server
            .153                                    .128
            52:54:00:8c:76:20                       52:54:00:78:10:73

2.2 版数

サーバとクライアントのCentOS の版数は以下のとおりです。

[root@client ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@client ~]# uname -r
3.10.0-1160.el7.x86_64

3 事前準備

firewall-cmdコマンドとiptablesコマンドは一緒に使うことはできないので、firewalldサービスを停止します。

[root@server ~]# systemctl stop firewalld.service

firewalldサービスの状態を確認します。firewalldサービスが停止したことがわかります。

[root@server ~]# systemctl is-active firewalld.service
inactive

4 macモジュールの使い方

macモジュールは、送信元MACアドレスを指定するモジュールです。REROUTING,FORWARD,INPUTの3つのチェインで使うことができます。

ここでは、INPUTチェインでmacモジュールを使ってみます。macモジュールで、送信元MACアドレスとして52:54:00:8C:76:20を指定します。LOGターゲットを使って、送信元MACアドレスが52:54:00:8C:76:20のパケットを受信したら、ログを出力するルールを作成してみます。

[root@server ~]# iptables -A INPUT -m mac --mac-source 52:54:00:8c:76:20 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            MAC 52:54:00:8C:76:20 LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。なお、journalctlコマンドの使い方は、https://hana-shin.hatenablog.com/entry/2022/02/28/203433を参照してください。

[root@server ~]# journalctl -f

クライアントでpingコマンドを実行します。-cオプションは、ICMP echoパケットの送信回数を指定するオプションです。以下の実行例では、pingコマンドを実行すると、ICMP echoパケットを1つ送信します。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認すると、送信元MACアドレスが52:54:00:8C:76:20のパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 20 20:00:30 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=8685 DF PROTO=ICMP TYPE=8 CODE=0 ID=1515 SEQ=1

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

5 icmpモジュールの使い方

icmpモジュールは、ICMPパケットのタイプ/コードを指定するモジュールです。

5.1 ICMP echoパケットの指定方法(type=8)

ICMP echoパケットを受信したらログを出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 9 packets, 660 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 LOG flags 0 level 4

クライアントでpingコマンドを実行します。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認すると、ICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 21 20:48:35 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=63523 DF PROTO=ICMP TYPE=8 CODE=0 ID=1487 SEQ=1

またルールを確認すると、受信したICMPパケット数が1つ増加したことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 18 packets, 1300 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

5.2 ICMP network unreachableパケットの指定方法(type=3,code=0)

ここではルールの作成だけをします。 ICMP network unreachableパケットを受信して、動作確認をしてみたい場合は、hping3コマンドの使い方 - hana_shinのLinux技術ブログを参照して実験してみてください。

ICMP network unreachableパケットを受信したらログを出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -m icmp --icmp-type 3/0 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 3 code 0 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

6 lengthモジュールの使い方

lengthモジュールは、IPパケット長を指定するモジュールです。IPパケット長は、IPヘッダ(20byte)にIPペイロードを加えた長さになります。

6.1 パケット長を指定する方法

28バイトのIPパケットを受信したらログを出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -m length --length 28 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 13 packets, 972 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 28 LOG flags 0 level 4

クライアントでpingコマンドを実行します。-sは、ICMPパケットのペイロード長を指定するオプションです。以下の実行例は、ペイロード長に0を指定しています。したがって、サーバに送信するICMP echoパケットは、20バイトのIPヘッダ、8バイトのICMPヘッダから構成されています。

[root@client ~]# ping -c 1 -s 0 192.168.122.128

ルールを確認すると、28バイトのICMP echoパケットを受信したので、パケット数が1増加したことがわかります(期待値)。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 123 packets, 8844 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    28 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 28 LOG flags 0 level 4

次は、29バイトのICMP echoパケットを送信してみます。

[root@client ~]# ping -c 1 -s 1 192.168.122.128

ルールを確認すると、29バイトのICMP echoパケットを受信しても、パケット数が増加しないことがわかります(期待値)。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 74 packets, 5370 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    28 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 28 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

6.2 パケット長の範囲を指定する方法

ICMPパケット長が30バイト~31バイトのICMPパケットを受信したらログを出力するルールを作成します。また、他のパケット長のICMPパケットを受信してもログが出力されないことを確認します。パケット長の範囲はコロンで指定します。

[root@server ~]# iptables -A INPUT -p icmp -m length --length 30:31 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 87 packets, 6264 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 30:31 LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

クライアントでpingを実行して、30バイトのICMP echoパケットを送信してみます。

[root@client ~]# ping -c 1 -s 2 192.168.122.128

ログを確認すると、ICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 22 19:56:26 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=30 TOS=0x00 PREC=0x00 TTL=64 ID=38722 DF PROTO=ICMP TYPE=8 CODE=0 ID=1456 SEQ=1

またルールを確認すると、30バイトのICMP echoパケットを受信したので、パケット数が1増加したことがわかります(期待値)。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 186 packets, 13306 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    30 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 30:31 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

6.3 パケット長の範囲(以上、以下)を指定する方法

ICMPのパケット長が31バイト以下のICMPパケットを受信したらログを出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -m length --length :31 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 0:31 LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

31バイトのICMP echoパケットを送信してみます。

[root@client ~]# ping -c 1 -s 3 192.168.122.128

ログを確認すると、ICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 22 20:31:12 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=31 TOS=0x00 PREC=0x00 TTL=64 ID=55728 DF PROTO=ICMP TYPE=8 CODE=0 ID=1547 SEQ=1

またルールを確認すると、31バイトのICMP echoパケットを受信していることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 73 packets, 5255 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    31 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            length 0:31 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

7 multiportモジュールの使い方

multiportモジュールは、複数のポート番号を指定するモジュールです。

7.1 ポート番号を複数指定する方法

宛先ポート番号が11111または11113のパケットを受信したらログに出力するルールを作成します。指定する複数のポート番号はカンマで指定します。

[root@server ~]# iptables -A INPUT -p tcp -m multiport --dports 11111,11113 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 11111,11113 LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

サーバでncコマンドを実行します。ターミナルを3つ開いて、それぞれで下記コマンドを実行します。

[root@server ~]# nc -kl 11111
[root@server ~]# nc -kl 11112
[root@server ~]# nc -kl 11113

クライアントでncコマンドを実行します。クライアントでも、ターミナルを3つ開いて、それぞれで下記コマンドを実行します。

[root@client ~]# nc 192.168.122.128 11111
[root@client ~]# nc 192.168.122.128 11112
[root@client ~]# nc 192.168.122.128 11113

ログを確認すると、宛先ポート番号が11111,11113を受信したログが記録されていることがわかります。宛先ポート番号が11112を受信したログは記録されていません(期待値)。

[root@server ~]# journalctl -f
12月 26 21:36:31 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=36855 DF PROTO=TCP SPT=41724 DPT=11111 WINDOW=29200 RES=0x00 SYN URGP=0
12月 26 21:36:31 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=36856 DF PROTO=TCP SPT=41724 DPT=11111 WINDOW=229 RES=0x00 ACK URGP=0
12月 26 21:36:35 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=14842 DF PROTO=TCP SPT=36130 DPT=11113 WINDOW=29200 RES=0x00 SYN URGP=0
12月 26 21:36:35 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=14843 DF PROTO=TCP SPT=36130 DPT=11113 WINDOW=229 RES=0x00 ACK URGP=0

ルールを確認すると、受信パケット数が4つであることがわかります。これら4つのパケットは、クライアントからサーバへのSYN,SYN+ACKパケットです。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 140 packets, 11574 bytes)
 pkts bytes target     prot opt in     out     source               destination
    4   224 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 11111,11113 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

7.2 ポート番号の範囲を指定する方法

宛先ポート番号が11111から11113のパケットを受信したらログに出力するルールを作成します。指定する範囲はコロンで指定します。

[root@server ~]# iptables -A INPUT -p tcp -m multiport --dports 11111:11113 -j LOG

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 11111:11113 LOG flags 0 level 4

サーバでncコマンドを実行します。ターミナルを3つ開いて、それぞれで下記コマンドを実行します。

[root@server ~]# nc -kl 11111
[root@server ~]# nc -kl 11112
[root@server ~]# nc -kl 11113

クライアントでncコマンドを実行します。クライアントでも、ターミナルを3つ開いて、それぞれで下記コマンドを実行します。

[root@client ~]# nc 192.168.122.128 11111
[root@client ~]# nc 192.168.122.128 11112
[root@client ~]# nc 192.168.122.128 11113

ログを確認すると、宛先ポート番号が11111,11112,11113を受信したログが記録されていることがわかります。

[root@server ~]# journalctl -f
12月 27 19:50:22 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=28814 DF PROTO=TCP SPT=41188 DPT=11111 WINDOW=29200 RES=0x00 SYN URGP=0
12月 27 19:50:22 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=28815 DF PROTO=TCP SPT=41188 DPT=11111 WINDOW=229 RES=0x00 ACK URGP=0
12月 27 19:50:29 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=23525 DF PROTO=TCP SPT=60718 DPT=11112 WINDOW=29200 RES=0x00 SYN URGP=0
12月 27 19:50:29 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=23526 DF PROTO=TCP SPT=60718 DPT=11112 WINDOW=229 RES=0x00 ACK URGP=0
12月 27 19:50:31 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=42542 DF PROTO=TCP SPT=49124 DPT=11113 WINDOW=29200 RES=0x00 SYN URGP=0
12月 27 19:50:31 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=42543 DF PROTO=TCP SPT=49124 DPT=11113 WINDOW=229 RES=0x00 ACK URGP=0

ルールを確認すると、受信パケット数が6つであることがわかります。これら6つのパケットは、クライアントからサーバへのSYN,SYN+ACKパケットです。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 78 packets, 7006 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        6   336 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 11111:11113 LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

8 timeモジュールの使い方

timeモジュールは、日時を指定するモジュールです。なお、timeモジュールはUTCしか使えないようです。UTCは以下のようにして確認できます。

[root@server ~]# date -u
2022年 12月 23日 金曜日 10:44:35 UTC

8.1 日時を指定する方法(--datestart,--datestop)

下記日時にパケットを受信したらログを出力するルールを作成します。
2022年12月23日 10時40分~2022年12月23日 10時55分

[root@server ~]# iptables -A INPUT -p icmp -m time --datestart 2022-12-23T10:40:00 --datestop 2022-12-23T10:55:00 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 19 packets, 1440 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            TIME starting from 2022-12-23 10:40:00 until date 2022-12-23 10:55:00 UTC LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

クライアントでpingコマンドを実行します。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認すると、ICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 23 19:51:38 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=6284 DF PROTO=ICMP TYPE=8 CODE=0 ID=1455 SEQ=1

またルールを確認すると、31バイトのICMP echoパケットを受信していることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 105 packets, 7648 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            TIME starting from 2022-12-23 10:40:00 until date 2022-12-23 10:55:00 UTC LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

8.2 時刻を指定する方法(--timestart,--timestop)

[root@server ~]# date -u
2022年 12月 23日 金曜日 11:01:11 UTC

下記時間帯にパケットを受信したらログに出力するルールを作成します。
11時10分~11時20分

[root@server ~]# iptables -A INPUT -p icmp -m time --timestart 11:10:00 --timestop 11:20:00 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            TIME from 11:10:00 to 11:20:00 UTC LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

クライアントでpingコマンドを実行します。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認すると、ICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 23 20:16:29 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=60099 DF PROTO=ICMP TYPE=8 CODE=0 ID=1520 SEQ=1

またルールを確認すると、31バイトのICMP echoパケットを受信していることがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 78 packets, 5872 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            TIME from 11:10:00 to 11:20:00 UTC LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

9 setモジュールの使い方

setモジュールは、ipsetコマンドで作成したセットを使用するモジュールです。

ok_hostという名前のセットを作成します。

[root@server ~]# ipset n ok_host hash:ip

作成したセットにクライアントのIPアドレス(192.168.122.153)を追加します。

[root@server ~]# ipset a ok_host 192.168.122.153

セットを確認すると、ok_hostという名前のセットが作成されたことがわかります。

[root@server ~]# ipset l
Name: ok_host
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 168
References: 0
Number of entries: 1
Members:
192.168.122.153

作成したセットをINPUTチェインに登録します。ここでは、セットに登録するIPアドレスは1つだけですが、複数のIPアドレスを登録する場合、セットを使うと以下のように簡潔にルールを作成することができます。

[root@server ~]# iptables -A INPUT -m set --match-set ok_host src -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 75 packets, 5232 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set ok_host src LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

クライアントでpingコマンドを実行します。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認すると、クライアントのIPアドレスを送信元とするICMPパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
12月 23 20:44:12 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=177 DF PROTO=ICMP TYPE=8 CODE=0 ID=1573 SEQ=1

またルールを確認すると、31バイトのICMP echoパケットを受信していることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 37 packets, 2592 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set ok_host src LOG flags 0 level 4

あと始末をします。まずルールを削除します。

[root@server ~]# iptables -D INPUT 1

ルールに登録したセットを削除します。

[root@server ~]# ipset x ok_host

10 connlimitモジュールの使い方

connlimitモジュールは、最大同時接続数を指定するモジュールです。

宛先ポート番号が11111番のTCPコネクションの最大同時接続数を1つにするルールを作成します。

[root@server ~]# iptables -A INPUT -p tcp --dport 11111 -m connlimit --connlimit-above 1 -j REJECT

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 9 packets, 660 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 #conn src/32 > 1 reject-with icmp-port-unreachable

サーバでncコマンドを実行します。なお、ncコマンドの使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 11111

lsofコマンドを実行すると、ncプロセスがTCPの11111番ポートでListenしていることがわかります。なお、lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c nc -a -i -a -nP
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      1909 root    3u  IPv6  27620      0t0  TCP *:11111 (LISTEN)
nc      1909 root    4u  IPv4  27621      0t0  TCP *:11111 (LISTEN)

クライアントでncコマンドを実行します。

[root@client ~]# nc 192.168.122.128 11111

もう1つターミナルをひらいて、ncコマンドを実行します。2つ目を実行すると、サーバとTCPコネクション確立ができないため、プロンプトがすぐに復帰します。

[root@client ~]# nc 192.168.122.128 11111
[root@client ~]#

ルールを確認します。2つ目のTCPコネクションが確立できなかったため、廃棄(REJECT)したパケットが1つであることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 58 packets, 3980 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    60 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 #conn src/32 > 1 reject-with icmp-port-unreachable

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

11 commentモジュールの使い方

commentモジュールは、iptablesのルールにコメントをつけるモジュールです。

ICMPパケットを受信したらログを出力するルールにコメントを付けてみます。

[root@server ~]# iptables -A INPUT -p icmp -m comment --comment "This is test comment" -j LOG

作成したルールを確認すると、ルールにコメントが付けられたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 53 packets, 3692 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            /* This is test comment */ LOG flags 0 level 4

後始末をします(作成したルールの削除)。

[root@server ~]# iptables -D INPUT 1

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

iptablesコマンドの使い方(ターゲットの使い方)

1 はじめに

ターゲットはルールにマッチしたら実行する処理のことです。LOGターゲットならログへの出力、DROPターゲットならパケット廃棄といった処理を行います。なお、iptablesコマンドの基本的な使い方は、iptablesコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のeth0はNICの名前です。

                          192.168.122.0/24
client(eth0) -------------------------------------------(eth0) server
            .153                                    .128

2.2 版数

サーバとクライアントのCentOS の版数は以下のとおりです。

[root@client ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@client ~]# uname -r
3.10.0-1160.el7.x86_64

3 事前準備

firewall-cmdコマンドとiptablesコマンドは一緒に使うことはできないので、firewalldサービスを停止します。

[root@server ~]# systemctl stop firewalld.service

firewalldサービスの状態を確認します。firewalldサービスが停止したことがわかります。

[root@server ~]# systemctl is-active firewalld.service
inactive

4 LOGターゲット

LOGターゲットは、ルールにマッチしたら情報(IPアドレス等)をログに出力するターゲットです。ここでは、ICMPパケットを受信したら、ICMPパケットのIPアドレス等の情報をログに出力するルールを作成してみます。

[root@server ~]# iptables -A INPUT -p icmp -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 47 packets, 3272 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

ログを確認するため、journalctlコマンドを実行します。なお、journalctlコマンドの使い方は、journalctlコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# journalctl -f

クライアントで宛先をサーバにしてpingコマンドを1回実行します。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# ping -c 1 192.168.122.128

pingを実行すると、以下のログが出力されることがわかります。

[root@server ~]# journalctl -f
12月 01 21:15:43 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=61606 DF PROTO=ICMP TYPE=8 CODE=0 ID=1456 SEQ=1

5 DROP/REJECTターゲット

5.1 DROPREJECTの違い

DROPは受信したパケットを破棄するだけです。一方、REJECTは受信パケットを廃棄したら、廃棄した旨をパケット送信元に送信します。

DROPの場合は以下のようになります。クライアントからサーバに送信したICMP echo requestパケットは、サーバで廃棄されます。

                          192.168.122.0/24
client(eth0) -------------------------------------------(eth0) server
            .153                                    .128

     ping       ----------- ICMP echo request ---------->  廃棄(DROP)

一方、REJECTの場合は以下のようになります。クライアントからサーバに送信したICMP echo requestパケットはサーバで廃棄されます。そして、廃棄した旨をICMP port unreachableパケットでクライアントに送信します。

                          192.168.122.0/24
client(eth0) -------------------------------------------(eth0) server
            .153                                    .128

     ping       ----------- ICMP echo request ---------->  廃棄(REJECT)
                <---------- ICMP port unreachable ------

5.2 DROPターゲットの動作確認

ICMPパケットを受信したら廃棄するDROPターゲットのルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -j DROP

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 37 packets, 2572 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0

tcpdumpコマンドを実行します。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# tcpdump -i eth0 icmp -nn

クライアントでもう1つターミナルをオープンしてpingコマンドを実行します。-cオプションはpingコマンドを実行する回数です。-wはタイムアウトを指定するオプションです。送信したICMP echo requestパケットに対する応答が1秒以内になければpingコマンドを終了するようにします。

[root@client ~]# ping -c 1 -w 1 192.168.122.128

tcpdumpコマンドの実行結果を確認すると、クライアンからサーバにICMP echoパケットが1つ送信されたことがわかります。

[root@client ~]# tcpdump -i eth0 icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:04:03.773560 IP 192.168.122.153 > 192.168.122.128: ICMP echo request, id 1544, seq 1, length 64

ルールを確認すると、ICMPパケットが廃棄されていることがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 99 packets, 6872 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    84 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0

5.3 REJECTターゲットの動作確認

ICMPパケットを受信したら廃棄します。そして、廃棄した旨を送信元に送信するREJECTターゲットのルールを作成します。

[root@server ~]# iptables -I INPUT -p icmp -j REJECT

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 69 packets, 5588 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 REJECT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable||<

tcpdumpコマンドを実行します。

[root@client ~]# tcpdump -i eth0 -p icmp -nn

クライアントでもう1つターミナルをオープンしてpingコマンドを実行します。

[root@client ~]# ping -c 1 192.168.122.128

tcpdumpの実行結果を確認すると、ICMP port unreachableパケットがICMP echoパケット送信元に送信されていることがわかります。

[root@client ~]# tcpdump -i eth0 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:03:10.907403 IP 192.168.122.153 > 192.168.122.128: ICMP echo request, id 1499, seq 1, length 64
22:03:10.909626 IP 192.168.122.128 > 192.168.122.153: ICMP 192.168.122.128 protocol 1 port 17955 unreachable, length 92

ルールを確認すると、ICMPパケットが廃棄されていることがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 88 packets, 6952 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    84 REJECT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

6 RETURNターゲット

ルールに一致したら、それ以上ルールに一致しているかどうかの処理をしないターゲットです。ルールに一致したら、ルールの呼び出し元に復帰します。

6.1 RETURNターゲットを使用した場合

テスト用のルールを3つ作成します。まず1つ目を作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG --log-prefix "--LOG1--"

2つ目のルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -j RETURN

3つ目のルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG --log-prefix "--LOG2--"

作成したルールを確認します。2番目がRETURN ターゲットのルールです。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 64 packets, 4480 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG1--"
2        0     0 RETURN     icmp --  *      *       0.0.0.0/0            0.0.0.0/0
3        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG2--"

ログを確認するため、journalctlコマンドを実行します。

[root@server ~]# journalctl -f

クライアントでpingコマンドを実行します。宛先はサーバを指定します。

[root@client ~]# ping -c 1 192.168.122.128

ログを確認するとLOG1は表示されていますが、LOG2は表示されていないことがわかります。つまり、1行目のルールを実行したあと、2行目のルールを実行してルールの呼び出し元に復帰していることがわかります。

[root@server ~]# journalctl -f
12月 13 20:21:12 server kernel: --LOG1--IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=27305 DF PROTO=ICMP TYPE=8 CODE=0 ID=1459 SEQ=1

6.2 RETURNターゲットを使用しない場合

テスト用のルールを2つ作成します。まず1つ作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG --log-prefix "--LOG1--"

もう1つルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG --log-prefix "--LOG2--"

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 13 packets, 972 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG1--"
2        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG2--"

クライアントでpingコマンドを実行します。宛先はサーバを指定します。

[root@client ~]# ping -c 1 192.168.122.128

ルールを確認すると、RETURNターゲットのルールを指定した時と違って、2つのルールでICMPパケットを処理していることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 26 packets, 1900 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG1--"
2        1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4 prefix "--LOG2--"

7 NFLOGターゲット

tcpdumpのフックポイントはIP層とドライバの間にありますが、NFLOGターゲットを使うとIP層に位置するチェインにフックポイントを設定することができます。なお、フックポイントとは、パケットをキャプチャする場所のことです。

ここでは、INPUTチェインにtcpdumpのフックポイントを設定してみます。

[root@server ~]# iptables -I INPUT -p tcp --dport 20000 -j NFLOG

作成したルールを確認します。INPUTチェインにNFLOGターゲットが設定されていることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 5 packets, 380 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 NFLOG      tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:20000

tcpdumpを実行します。このとき、インタフェースにnflogを指定します。なお、NFLOGを指定した場合、tcpdumpがキャプチャするパケットは、-wオプションを使って、いったんファイルに保存する必要があります。なお、tcpdumコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i nflog -w nflog.cap
tcpdump: listening on nflog, link-type NFLOG (Linux netfilter log messages), capture size 262144 bytes

サーバでncコマンドを実行します。TCPの20000番ポートでncプロセスがListenするようにします。なお、ncコマンドの使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 20000

クライアントでncコマンドを実行します。宛先はサーバの20000番ポートを指定します。

[root@client ~]# nc 192.168.122.128 20000

Ctrl+Cを押下して、tcpdumpコマンドを終了します。

[root@server ~]# tcpdump -i nflog -w nflog.cap
tcpdump: listening on nflog, link-type NFLOG (Linux netfilter log messages), capture size 262144 bytes
^C2 packets captured
2 packets received by filter
0 packets dropped by kernel

tsharkコマンドの実行結果を確認すると、INPUTチェインで処理するクライアントからサーバへのパケットのみがキャプチャされていることがわかります。サーバからクライアントへのSYN+ACKはINPUTチェインで処理されないので、キャプチャされていません。なお、tsharkコマンドのインストール方法、使い方はtsharkコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tshark -r nflog.cap -nn
Running as user "root" and group "root". This could be dangerous.
  1          0 192.168.122.153 -> 192.168.122.128 TCP 164 53888 > 20000 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=3752814 TSecr=0 WS=128
  2          0 192.168.122.153 -> 192.168.122.128 TCP 156 53888 > 20000 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=3752815 TSecr=3758610

8 REDIRECTターゲット

宛先ポート番号を変更するターゲットです。ここでは、受信パケットの宛先ポート番号を10000から20000に変更してみます。ポート番号をREDIRECTターゲットで変更することで、クライアントからサーバへの10000番ポート宛てのパケットが20000番ポートで待ち受けているプロセス(nc)にパケットが届きます。

[root@server ~]# iptables -t nat -A PREROUTING -p tcp --dport 10000 -j REDIRECT --to-port 20000

PREROUTINGチェインに設定したREDIRECTターゲットを確認します。このターゲットは宛先ポート番号を10000から20000に変更します。

[root@server ~]# iptables -t nat -nvL PREROUTING --line-numbers
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 REDIRECT   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10000 redir ports 20000

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 20000

lsofコマンドを実行すると、ncプロセスがTCPの20000番ポートでListenしていることがわかります。
なお、lsofコマンドのインストール、使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c nc -a -i -a -nP
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      1702 root    3u  IPv6  34357      0t0  TCP *:20000 (LISTEN)
nc      1702 root    4u  IPv4  34358      0t0  TCP *:20000 (LISTEN)

クライアントでncコマンドを実行します。宛先はサーバの10000番ポートを指定します。

[root@client ~]# nc 192.168.122.128 10000
12345

tcpdumpコマンドの実行結果を確認すると、10000番のポート番号を使ってTCPコネクションを確立していることがわかります。tcpdumpのフックポイントは、IP層とドライバの間にあります。PREROUTINGチェインはIP層にあります。したがって、PREROUTINGチェインで宛先ポート番号を10000から20000に変更する前のポート番号が表示されていることがわかります。

[root@server ~]# tcpdump -i eth0 port 10000 or port 20000 -nn
21:42:49.730489 IP 192.168.122.153.39516 > 192.168.122.128.10000: Flags [S], seq 2432710170, win 29200, options [mss 1460,sackOK,TS val 5123058 ecr 0,nop,wscale 7], length 0
21:42:49.730701 IP 192.168.122.128.10000 > 192.168.122.153.39516: Flags [S.], seq 924901017, ack 2432710171, win 28960, options [mss 1460,sackOK,TS val 5128854 ecr 5123058,nop,wscale 7], length 0
21:42:49.732036 IP 192.168.122.153.39516 > 192.168.122.128.10000: Flags [.], ack 1, win 229, options [nop,nop,TS val 5123061 ecr 5128854], length 0

INPUTチェインで採取したパケットを確認してみます。PREROUTINGチェインで宛先ポート番号を10000番から20000番に変更した後なので、ポート番号が20000番になっていることがわかります。

[root@server ~]# tshark -r nflog.cap -nn
Running as user "root" and group "root". This could be dangerous.
  1          0 192.168.122.153 -> 192.168.122.128 TCP 164 39516 > 20000 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=5123058 TSecr=0 WS=128
  2          0 192.168.122.153 -> 192.168.122.128 TCP 156 39516 > 20000 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=5123061 TSecr=5128854

9 MARKターゲット

パケットに印をつけるターゲットです。ここでは、受信したICMPパケット対して、PREROUTINGチェインでMARKターゲットを実行します。MARKターゲットを実行すると、ICMPパケットに100番の印がつけられます。そして、INPUTチェインでは、100番のパケットに対して、LOGターゲットを実行します。比較のため、INPUTチェインで200番のルールも作成しますが、200番のルールでは、ICMPパケットはLOGターゲットで処理されないことを確認します。

+--------------------------+
|                          |
|      INPUT chain         | 100番のパケットに対してLOGターゲットを実行します。
|                          |
+--------------------------+
           A
           |
           |
+--------------------------+
|                          |  ICMPパケットに対してMARKターゲットを実行します。
|      PREROUTING chain    |  MARKターゲットを実行すると、ICMPパケットに100番の印が付けられます。
|                          |
+--------------------------+
           A
           |
           |
       ICMPパケット

PREROUTINGチェインでICMPパケットに100番の印を付けるルールを作成します。

[root@server ~]# iptables -t mangle -I PREROUTING -p icmp -j MARK --set-mark 100

作成したルールを確認します。

[root@server ~]# iptables -t mangle -nvL PREROUTING --line-numbers
Chain PREROUTING (policy ACCEPT 21 packets, 1484 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 MARK       icmp --  *      *       0.0.0.0/0            0.0.0.0/0            MARK set 0x64

100番の印が付いたパケットをLOGターゲットで処理するルールをINPUTチェインに作成します。

[root@server ~]# iptables -I INPUT -m mark --mark 100 -j LOG

200番の印が付いたパケットをLOGターゲットで処理するルールをINPUTチェインに作成します。

[root@server ~]# iptables -I INPUT -m mark --mark 200 -j LOG

作成したルールを確認します。1番目のルールが200番(0xc8)の印が付いたパケットを処理するルール、2番のルールが100番(0x64)の印が付いたパケットを処理するルールです。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 5 packets, 364 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0xc8 LOG flags 0 level 4
2        0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x64 LOG flags 0 level 4

クライアントでpingコマンドを実行します。宛先はサーバを指定します。

[root@client ~]# ping -c 1 192.168.122.128

ルールを確認します。1番目のルールではなく、2番目のルールでパケット数が1増加していることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 35 packets, 2492 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0xc8 LOG flags 0 level 4
2        1    84 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x64 LOG flags 0 level 4

journalctlコマンドを実行します。100番の印が付いたパケットを受信していることがわかります。

[root@server ~]# journalctl -f
-snip-
Dec 14 20:51:49 server kernel: IN=eth0 OUT= MAC=52:54:00:78:10:73:52:54:00:8c:76:20:08:00 SRC=192.168.122.153 DST=192.168.122.128 LEN=84 TOS=0x00 PREC=0
x00 TTL=64 ID=46647 DF PROTO=ICMP TYPE=8 CODE=0 ID=1485 SEQ=1 MARK=0x64

10 TCPMSSターゲット

TCPヘッダに定義されているMSS(Maximum Segment Size)を変更するためのターゲットです。MSSはMTUからTCP/IPヘッダサイズを引いた値になります。通常、Ethernet環境ではMTUは1500(byte)です。また、TCP/IPの各々のヘッダサイズは20(byte)となるので、MSSは1460(byte)になります。しかし、最近ではTCP TimeStampオプション(12byte)が付くことが一般的なので、MSSは1448(byte)になります。

MSSを1000に変更するターゲットをPOSTROUTINGチェイン設定します。

[root@client ~]# iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN SYN -o eth0 -j TCPMSS --set-mss 1000

作成したルールを確認します。POSTROUTINGチェインにTCPMSSターゲットが作成され、MSSが1000に設定されていることがわかります。

[root@client ~]# iptables -t mangle -nvL POSTROUTING --line-numbers
Chain POSTROUTING (policy ACCEPT 7 packets, 968 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 TCPMSS     tcp  --  *      eth0    0.0.0.0/0            0.0.0.0/0            tcp flags:0x02/0x02 TCPMSS set 1000

サーバとクライアントのTCPコネクション確立時のパケットのやり取りを確認するため、tcpdumpコマンドを実行します。

[root@server ~]# tcpdump -i eth0 port 11111 -nn

サーバでncコマンドを実行します。TCPの11111番ポートでncプロセスがListenするようにします。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。宛先はサーバの11111番ポートを指定します。

[root@client ~]# nc 192.168.122.128 11111

tcpdumpコマンドの実行結果を確認します。MSSが1000に設定されたSYNパケットが送信されていることがわかります。

[root@server ~]# tcpdump -i eth0 port 11111 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:37:53.952382 IP 192.168.122.153.54832 > 192.168.122.128.11111: Flags [S], seq 1449643484, win 29200, options [mss 1000,sackOK,TS val 6401001 ecr 0,nop,wscale 7], length 0
21:37:53.952545 IP 192.168.122.128.11111 > 192.168.122.153.54832: Flags [S.], seq 3488042956, ack 1449643485, win 28960, options [mss 1460,sackOK,TS val 6405591 ecr 6401001,nop,wscale 7], length 0
21:37:53.953831 IP 192.168.122.153.54832 > 192.168.122.128.11111: Flags [.], ack 1, win 229, options [nop,nop,TS val 6401003 ecr 6405591], length 0

11 NOTRACKターゲット

コネクショントラッキングを実行しないターゲットです。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

iptablesコマンドの使い方

1 iptablesコマンドとは

iptablesコマンドは、送受信パケットの廃棄/許可等のルールをnetfilterに設定/削除するコマンドです。なお、iptablesコマンドの位置づけは以下になります。

     CentOS          Ubuntu20.04         openSUSE(Leap42.3)
+--------------+    +------------+    +---------------------+   -*-
| firewall-cmd |    |     ufw    |    |    SUSEfirewall2    |    |
+--------------+    +------------+    +---------------------+    |
                                                               ユーザ空間
+-----------------------------------------------------------+    |
|                     iptables  command                     |    |
+-----------------------------------------------------------+   -*-

+-----------------------------------------------------------+   -*-
|                                                           |    |
|                    OS(netfilter)                          |  カーネル空間
|                                                           |    |
+-----------------------------------------------------------+   -*-

firewall-cmdコマンド、ufwコマンドの使い方は以下を参照してください。
firewall-cmdの使い方 - hana_shinのLinux技術ブログ
ufwコマンドの使い方 - hana_shinのLinux技術ブログ

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のeth0はNICの名前です。

                          192.168.122.0/24
client(eth0) -------------------------------------------(eth0) server
            .153                                    .128

2.2 版数

サーバとクライアントのCentOS の版数は以下のとおりです。

[root@client ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@client ~]# uname -r
3.10.0-1160.el7.x86_64

3 事前準備

firewall-cmdコマンドとiptablesコマンドは一緒に使うことはできません。

firewalldサービスを停止します。

[root@server ~]# systemctl stop firewalld.service

firewalldサービスの状態を確認します。firewalldサービスが停止したことがわかります。

[root@server ~]# systemctl is-active firewalld.service
inactive

4 チェインとは?

チェインとは、ルールの集合です。ルールはある条件にマッチしたらパケットの廃棄/通過といった規則のことです。チェインはOS内に5つあります。5つのチェインは、組み込みチェインと呼ばれています。なお、チェインはiptables利用者がユーザ定義チェインとして独自に定義することも可能です。

チェイン 意味
PREROUTING 受信パケットに対してルーティング処理を行う前のパケットに対して実行するチェインです
INPUT 受信パケットのルーティング処理を実行した結果、自分宛てのパケットに対して実行するチェインです
FORWARD 受信パケットのルーティング処理を実行した結果、他サーバ宛てに送信するパケットに対して実行するチェインです
OUTPUT 送信パケットに対してルーティング処理を行う前のパケットに対して実行するチェインです
POSTROUTING 送信パケットに対してルーティング処理を実行した後のパケットに対して実行するチェインです

5 ルールの表示方法(-L)

5.1 INPUTチェインのルールの表示方法

INPUTチェインのルールを表示します。何もルールが登録されていないことがわかります。

[root@server ~]# iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

5.2 OUTPUTチェインのルールの表示方法

OUTPUTチェインのルールを表示します。何もルールが登録されていないことがわかります。

[root@server ~]# iptables -L OUTPUT
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

5.3 FORWARDチェインのルールの表示方法

FORWARDチェインのルールを表示します。何もルールが登録されていないことがわかります。

[root@server ~]# iptables -L FORWARD
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

5.4 PREROUTINGチェインのルールの表示方法

PREROUTINGチェインは、-t natオプションを付けてルールを表示します。PREROUTINGチェインには、何もルールが登録されていないことがわかります。

[root@server ~]# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

5.5 POSTROUTINGチェインのルールの表示方法

POSTROUTINGチェインは、-t natオプションを付けてルールを表示します。POSTROUTINGチェインには、何もルールが登録されていないことがわかります。

[root@server ~]# iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

6 ルールの詳細を表示する方法

6.1 数値で表示する方法(-n)

-nは、IPアドレスやポート番号を数値で表示するオプションです。-nを指定しないと、以下のようIPアドレス(source,destination)が文字列で表示されます。

[root@server ~]# iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
LOG        icmp --  anywhere             anywhere             LOG level warning

-nを指定すると、IPアドレスがanywhereではなく0.0.0.0と表示されていることがわかります。

[root@server ~]# iptables -nL INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
LOG        icmp --  0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

6.2 パケット数やバイト数を表示する方法(-v)

-vは、パケット数(pkts)やバイト数(bytes)を表示するオプションです。-vオプションを付けてiptablesを実行すると、1列目にパケット数(pkts)、2列目にバイト数(bytes)が表示されていることがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 114 packets, 8452 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

クライアントでpingコマンドを1回実行します。宛先はサーバです。pingコマンドを実行すると、クライアントからサーバにICMP echoパケットが1つ送信されます。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# ping -c 1 192.168.122.128

iptablesコマンドの実行結果を確認すると、ICMPパケットを1つ受信していることがわかります。また、受信パケット数が84bytesであることがわかります。なお、84bytesは、IPヘッダを含めたサイズになります。詳細は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 132 packets, 9712 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    84 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

6.3 ルールの番号を表示する方法(--line-numbers)

--line-numbersは、ルールの番号を表示するオプションです。以下の例では、INPUTチェインにルールが1つ登録されていて、番号が1番であることがわかります。

[root@server ~]# iptables -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    LOG        icmp --  anywhere             anywhere             LOG level warning

7 ルールの追加、削除方法(-A,-D)

7.1 ルールの追加(-A)

-Aは既存ルールの末尾に新規ルールを追加するオプションです。

テスト用のルールを1つ作成します。

[root@server ~]#  iptables -A INPUT -p icmp -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 19 packets, 1344 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

作成したルールの末尾に新規ルールを追加してみます。

[root@server ~]# iptables -A INPUT -p tcp --dport 11111

ルールを確認します。既存ルールの末尾に新規ルールが追加されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4
2        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111

7.2 ルールの削除(-D)

-Dはルールを削除するオプションです。書式は、-D <チェイン名> <ルールの番号>となります。

ルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 34 packets, 2404 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4
2        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111

1番目のルールを削除します。

[root@server ~]# iptables -D INPUT 1

ルールを確認します。元の1番目のルールが削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111

8 ルールの挿入、削除方法(-I,-D)

8.1 ルールの挿入(-I)

-Iは指定したルールの前に新規ルールを挿入するオプションです。

テスト用のルールを1つ作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 19 packets, 1376 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

元の1番目の前に新規ルールを挿入します。

[root@server ~]# iptables -I INPUT 1 -p tcp --dport 11111

なお、1は省略することができるので、以下のように実行することもできます。

[root@server ~]# iptables -I INPUT -p tcp --dport 11111

ルールを確認します。元のルールの上に新規ツールが挿入されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111
2        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

8.2 ルールの削除(-D)

-Dはルールを削除するオプションです。

登録されているルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 200 packets, 13964 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111
2        0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

INPUTチェインの2番目のルールを削除してみます。

[root@server ~]# iptables -D INPUT 2

ルールを確認します。2番目のルールが削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 22 packets, 1588 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111

9 ルールを置き換える方法(-R)

テスト用のルールを1つ作成します。

[root@server ~]# iptables -A INPUT -p icmp

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 61 packets, 4232 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            icmp --  *      *       0.0.0.0/0            0.0.0.0/0

次に、元のルールを宛先UDPポート番号が11111番のパケットを受信するルールに置き換えます。

[root@server ~]# iptables -R INPUT 1 -p udp --dport 11111

ルールが置き換えられたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 69 packets, 4812 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:11111

10 カウンタをリセットする方法(-Z)

10.1 事前準備

テスト用のルールを1つ作成します。

[root@server ~]# iptables -A INPUT -p icmp

作成したルールを確認します。ICMPパケットの受信数(pkts)が0であることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 133 packets, 9844 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            icmp --  *      *       0.0.0.0/0            0.0.0.0/0

クライアンで宛先をサーバにしてpingを実行します。なお、pingコマンドの使い方は、pingコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# ping -c 1 192.168.122.128

ルールを確認します。ICMPのパケット受信数が1になったことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 190 packets, 13864 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    84            icmp --  *      *       0.0.0.0/0            0.0.0.0/0

10.2 カウンタのリセット

INPUTチェインのカウンタをリセットします。

[root@server ~]# iptables -Z INPUT

INPUTチェインのルールを確認します。ICMPのパケット受信数が0にクリアされたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            icmp --  *      *       0.0.0.0/0            0.0.0.0/0

11 ユーザ定義チェイン

11.1 ユーザ定義チェインの作成方法(-N)

-Nはユーザ定義チェインを作成するオプションです。ここでは、TESTという名前のユーザ定義チェインを作成します。

[root@server ~]# iptables -N TEST

ユーザ定義チェインに登録されているルールを確認します。ルールが何も登録されていないことがわかります。

[root@server ~]# iptables -L TEST
Chain TEST (0 references)
target     prot opt source               destination

11.2 ユーザ定義チェインの削除方法(-X)

-Xはユーザ定義チェインを削除するオプションです。

TESTという名前のユーザ定義チェインを削除します。

[root@server ~]# iptables -X TEST

チェインを確認します。TESTチェインが削除されたことがわかります。

[root@server ~]# iptables -L TEST
iptables: No chain/target/match by that name.

11.3 ユーザ定義チェインにルールを追加する方法(-A,-I)

TESTという名前のチェインを作成します。

[root@server ~]# iptables -N TEST

作成したユーザ定義チェインにルールを作成します。

[root@server ~]# iptables -A TEST -p icmp -j LOG

ユーザ定義チェインに登録されているルールを確認します。

[root@server ~]# iptables -nvL TEST
Chain TEST (0 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

11.4 組み込みチェインにユーザ定義チェインを登録する方法(-A,-I)

[root@server ~]# iptables -nvL TEST
Chain TEST (0 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

TESTチェインをINPUTチェインに登録します。

[root@server ~]# iptables -A INPUT -j TEST

INPUTチェインに登録されているルールを確認します。TESTチェインがターゲットとして登録されていることがわかります。

[root@server ~]# iptables -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    TEST       all  --  anywhere             anywhere

11.5 ユーザ定義チェインの送信パケット数の確認方法

[root@client ~]# ping -c 1 192.168.122.128

12 ルールの指定方法について

12.1 プロトコルを指定する方法(-p)

-pはプロトコルを指定するオプションです。/etc/protocolsに登録されているプロトコルを指定できます。具体的にはicmp,udp,tcp等があります。

テスト用のルールを1つ作成します。

[root@server ~]# iptables -A INPUT -p icmp -j LOG

さらに、テスト用のルールを1つ作成します。

[root@server ~]# iptables -A INPUT -p udp -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 35 packets, 2432 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4
    0     0 LOG        udp  --  *      *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

あと始末をします。

[root@server ~]# iptables -D INPUT 1

あと始末をします。

[root@server ~]# iptables -D INPUT 1

INPUTチェインのルールを確認します。ルールが全て削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
 pkts bytes target     prot opt in     out     source               destination

12.2 インタフェースを指定する方法

12.2.1 入力インタフェースを指定する方法(-i)

ipコマンドを使ってインタフェースを確認します。loとeth0の2つのインタフェースが存在することがわかります。

[root@server ~]# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:78:10:73 brd ff:ff:ff:ff:ff:ff

eth0でICMPパケットを受信したら、ICMPパケットのIPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -I INPUT -p icmp -i eth0 -j LOG

作成したルールを確認します。受信インタフェースにeth0が指定されていることがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 37 packets, 2572 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  eth0   *       0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4
12.2.2 出力インタフェースを指定する方法(-o)

eth0からICMPパケットを送信したら、ICMPパケットのIPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -A OUTPUT -o eth0 -p icmp -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL OUTPUT
Chain OUTPUT (policy ACCEPT 17 packets, 1880 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      eth0    0.0.0.0/0            0.0.0.0/0            LOG flags 0 level 4

12.3 IPアドレスを指定する方法

12.3.1 送信元IPを指定する方法(-s)

送信元IPアドレスが192.168.122.15のICMPパケットを受信したら、IPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -s 192.168.122.153 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 94 packets, 6920 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       192.168.122.153      0.0.0.0/0            LOG flags 0 level 4

テスト用に作成したルールを削除します。

[root@server ~]# iptables -D INPUT 1
12.3.2 送信先IPを指定する方法(-d)

送信先IPアドレスが192.168.122.128のICMPパケットを受信したら、IPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp -d 192.168.122.128 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        icmp --  *      *       0.0.0.0/0            192.168.122.128      LOG flags 0 level 4

12.4 ポート番号を指定する方法

12.4.1 送信元ポート番号を指定する方法(--sport)

送信元ポート番号が53番のUDPパケットを受信したら、IPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -I INPUT -p udp --sport 53 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp spt:53 LOG flags 0 level 4

digコマンドを実行するとDNSパケットがDNSサーバに送信されます。なお、digコマンドについては、digコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# dig ntp.nict.jp +short
133.243.238.164
133.243.238.163
133.243.238.244
133.243.238.243
61.205.120.130

ルールを確認すると、INPUTチェインに送信元ポート番号が53番のUDPパケット(DNS queryに対する応答)を1つ受信したことが記録されていることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 29 packets, 2132 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1   148 LOG        udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp spt:53 LOG flags 0 level 4

あと始末をします。作成したルールを削除します。

[root@server ~]# iptables -D INPUT 1

ルールを確認すると、作成したルールが削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination
12.4.2 送信先ポート番号を指定する方法(--dport)

送信先ポート番号が53番のUDPパケットを送信したら、IPアドレス等の情報をログに出力するルールを作成します。

[root@server ~]# iptables -I OUTPUT -p udp --dport 53 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 36 packets, 3792 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53 LOG flags 0 level 4

DNSパケットを送信するため、digコマンドを実行します。

[root@server ~]# dig ntp.nict.jp +short
61.205.120.130
133.243.238.164
133.243.238.163
133.243.238.244
133.243.238.243

ルールを確認すると、OUTPUTチェインに送信先ポート番号が53番のUDPパケット(DNS query)を1つ送信したことが記録されていることがわかります。

[root@server ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 56 packets, 6588 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        1    68 LOG        udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53 LOG flags 0 level 4

あと始末をします。作成したルールを削除します。

[root@server ~]# iptables -D OUTPUT 1

ルールを確認します。作成したルールが削除されたことがわかります。

[root@server ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 5 packets, 600 bytes)
num   pkts bytes target     prot opt in     out     source               destination

12.5 TCPの制御フラグを指定する方法

TCPヘッダにSYN,FIN,ACK等の制御ビットを定義するフィールドがあります。ここでは、特定の制御ビットが1になっているパケットをルールに登録する方法について説明します。

12.5.1 SYNパケットをルールに登録する方法

TCPコネクション確立時、以下のパケットをやり取りしてTCPコネクションを確立します。ここでは、SYNパケットを受信したら、SYNパケットのIPアドレス等の情報をログに記録するルールを追加してみます。なお、TCPコネクションの確立/開放の様子は、TCPコネクションの確立、解放シーケンス - hana_shinのLinux技術ブログを参照してください。

client                       server(11111)
  |                             |
  |----------- SYN ------------>| ★このパケットをiptablesのルールに指定する。
  |<---------- SYN +ACK --------|
  |----------- ACK ------------>|
  |                             |

SYNパケットを受信したら、SYNパケットのIPアドレス等の情報をログに記録するルールを追加します。

[root@server ~]# iptables -I INPUT -p tcp --dport 11111 --tcp-flags SYN SYN -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 9 packets, 676 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x02/0x02 LOG flags 0 level 4

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。クライアントでncコマンドを実行すると、クライアントからサーバにSYNパケットが送信されます。ここでは説明しませんが、この時の様子はtcpdumpコマンドを使って確認することができます。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# nc 192.168.122.128 11111

ルールを確認すると、FINパケットを1つ受信(左端のpkts)したことがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 68 packets, 6510 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    60 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x02/0x02 LOG flags 0 level 4

後始末をします。

[root@server ~]# iptables -D INPUT 1

ルールを確認します。作成したルールが削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
 pkts bytes target     prot opt in     out     source               destination
12.5.2 FINパケットをルールに登録する方法

TCPコネクション終了時、以下のパケットをやり取りしてTCPコネクションを解放します。ここでは、FINパケットを受信したら、FINパケットのIPアドレス等の情報をログに記録するルールを追加してみます。

       client                       server(11111)
        |                             |
Ctrl +c |                             |
        |----------- FIN ------------>| ★このパケットをiptablesのルールに指定する。
        |<---------- ACK -------------|
        |                             |
        |<---------- FIN -------------|
        |----------- ACK ------------>|
        |                             |

FINパケットを受信したら、FINパケットのIPアドレス等の情報をログに記録するルールを追加します。

[root@server ~]# iptables -I INPUT -p tcp --dport 11111 --tcp-flags FIN FIN -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 11 packets, 816 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x01/0x01 LOG flags 0 level 4

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。クライアントでncコマンドを実行すると、サーバとクライアント間でTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.128 11111

クライントでCtrl + cを押下して、TCPコネクションを終了します。ncコマンドを終了すると、クライアントからサーバにFINパケットが送信されます。

[root@client ~]# nc 192.168.122.128 11111
^C

ルールを確認すると、FINパケットを1つ受信(左端のpkts)したことがわかります。

[root@server ~]# iptables -nvL INPUT
Chain INPUT (policy ACCEPT 98 packets, 6992 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    52 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x01/0x01 LOG flags 0 level 4

12.6 ワイルドカードの使い方(+)

複数のインタフェース(eth0,eth1,...)を搭載しているマシンでは、1つづつインタフェースを指定するのは面倒です。"+"を使うと複数インタフェースを以下のように指定することができます。

[root@server ~]# iptables -A INPUT -i eth+ -p tcp --dport 11111

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 73 packets, 5104 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0            tcp  --  eth+   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111

あと始末をします。作成したルールを削除します。

[root@server ~]# iptables -D INPUT 1

作成したルールが削除されたことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 7 packets, 504 bytes)
num   pkts bytes target     prot opt in     out     source               destination

12.7 否定を指定する方法(!)

12.7.1 IPアドレスに否定を指定する方法

送信元IPアドレスが192.168.122.153以外のICMPパケットを受信したらログに出力するルールを作成します。

[root@server ~]# iptables -A INPUT -p icmp ! -s 192.168.122.153 -j LOG

作成したルールを確認します。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 59 packets, 4112 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *      !192.168.122.153      0.0.0.0/0            LOG flags 0 level 4

クライアントでpingコマンドを1回実行します。宛先はサーバです。

[root@client ~]# ping -c 1 192.168.122.128

ルールを確認すると、ICMPパケットを受信していないことがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 70 packets, 4876 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 LOG        icmp --  *      *      !192.168.122.153      0.0.0.0/0            LOG flags 0 level 4
12.7.2 他の指定方法

IPアドレス以外に、以下のものを否定の対象に指定できます。
・ポート番号
プロトコル
・インタフェース
・フラグメント(-! -fと指定すると、フラグメントされている場合先頭のパケット、フラグメントされていない場合は、フラグメントされていないパケット自身が対象になります)

13 ターゲット

ターゲットは、iptablesコマンドの使い方(ターゲットの使い方) - hana_shinのLinux技術ブログを参照してください。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ