cgroupのnet_clsを使って、特定のプロセス(ツリー)に対するiptables/nftablesルールを設定する

概要

Linuxのcgroup機能とは、プロセスをグループに分けて管理し、その単位ごとにCPU/メモリの割り当て等の設定を可能にするものです。

cgroupのサブシステムにはcpu, memoryなどの他にnet_clsというものがあり、これを使うと指定したグループからのパケットに対してタグ(classid)を付加することができ、これを条件にnetfilter(iptables/nftablesコマンド)でフィルタリングなどを行うことができます。

筆者自身深く理解してはいないので言葉遣いは間違っている可能性があります。日本語情報だと他にはLinux 3.14 で net_cls cgroup に追加された netfilter 対応 - TenForwardがあります。

net_clsサブシステムが使えるか確認する

まず、cat /proc/cgroupsを実行します。すると手元(Linux Mint 21.3, Ubuntu 22.04ベース)では以下のような出力が返ってきました。

#subsys_name    hierarchy    num_cgroups    enabled
cpuset    0    170    1
cpu    0    170    1
cpuacct    0    170    1
blkio    0    170    1
memory    0    170    1
devices    0    170    1
freezer    0    170    1
net_cls    0    170    1
perf_event    0    170    1
net_prio    0    170    1
hugetlb    0    170    1
pids    0    170    1
rdma    0    170    1
misc    0    170    1

このようにnet_clsが含まれていればOKです。含まれていなかったらsudo modprobe cls_cgroupとかをすると出てくるかもしれないです(参考:第4回 Linuxカーネルのコンテナ機能[3] ─cgroupとは?(その2) | gihyo.jp

マウントする

では、次に、ls /sys/fs/cgroupを実行します。手元では以下のようになりました。

cgroup.controllers      cpuset.mems.effective  memory.pressure
cgroup.max.depth        dev-hugepages.mount    memory.stat
cgroup.max.descendants  dev-mqueue.mount       misc.capacity
cgroup.procs            init.scope             proc-sys-fs-binfmt_misc.mount
cgroup.stat             io.cost.model          sys-fs-fuse-connections.mount
cgroup.subtree_control  io.cost.qos            sys-kernel-config.mount
cgroup.threads          io.pressure            sys-kernel-debug.mount
cpu.pressure            io.prio.class          sys-kernel-tracing.mount
cpu.stat                io.stat                system.slice
cpuset.cpus.effective   memory.numa_stat       user.slice

(フォルダとファイルが見分けられなくてすみません)

本当はここの中にnet_cls(というフォルダ)が含まれているべきなのですが、ありません。どうやら、マウントする必要があるようです。これは、2.3. 既存の階層へのサブシステムの接続と接続解除 Red Hat Enterprise Linux 6 | Red Hat Customer Portalみたいな感じでmountコマンドを打つと追加できます。しかし自分で打つのは面倒です。

実は、cgroupfs-mountというコマンドをsudoで実行するとこの部分を勝手にやってくれるということがわかりました。これはcgroupfs-mountというパッケージに入っています。実は、cgroups-mountという非常によく似た名前のコマンドもあり、こちらはcgroup-liteというパッケージに入っています。挙動もほぼ同じです。中身はシェルスクリプトで、見た感じ前者のほうが内容が多いので新しそうです。

というわけでcgroupfs-mountを実行するとls /sys/fs/cgroupは以下のようになります。

cgroup.controllers      cpuacct        io.prio.class     proc-sys-fs-binfmt_misc.mount
cgroup.max.depth        cpuset         io.stat           rdma
cgroup.max.descendants  devices        memory.numa_stat  sys-fs-fuse-connections.mount
cgroup.procs            freezer        memory.pressure   sys-kernel-config.mount
cgroup.stat             hugetlb        memory.stat       sys-kernel-debug.mount
cgroup.subtree_control  init.scope     misc              sys-kernel-tracing.mount
cgroup.threads          io.cost.model  net_cls           system.slice
cpu.pressure            io.cost.qos    net_prio          systemd
cpu.stat                io.pressure    perf_event        user.slice

このようにnet_clsなどいろいろ追加されています。

cgroupの作成・設定

cgroupを作成するには、先ほど存在が確認できた/sys/fs/cgroup/net_clsに、testというフォルダを作成します。testの中に移動してみると、cgroup.procsとかnet_cls.classidといったファイルが勝手に作成されていることがわかります。これを編集することでcgroupに関する設定ができます。

ここではnet_cls.classidに1と書き込んでみます。(数字は何でもいいです)

# echo 1 > net_cls.classid

こうするとiptablesで -m cgroup --cgroup 1などと指定できるようになります。

次にプロセスをcgroupに追加します。適当なシェルを起動してecho $$と打ってPIDを取得し、これをcgroup.procsに書き込みます。(ここでは5008とします)

# echo 5008 > cgroup.procs

これで、該当のシェルの子プロセス(シェルで実行したコマンド)はclassidが1として扱われるようになります。あとはiptablesのルールで好き勝手にやるだけです。

cgroup指定は原則的にINPUTチェインでは機能しません(一応特殊な条件下では機能する?詳しくはhttps://ipset.netfilter.org/iptables-extensions.man.html)。

cgroup-tools

cgroupの作成や各種設定はcgcreateやcgsetといったコマンドで行うこともできます。これらはcgroup-toolsというパッケージに入っています。

今回作成したtestというcgroupで特定プロセスを実行するには以下のようにします。

sudo cgexec -g net_cls:test command args

勝手にcgroup.procsのところにcommandのPIDが追加されていることがわかります。

PIDでの指定について

Linux Netfilter iptables — Re: module owner does not work

これによると、Linux 2.6あたりの一時期には、--pid-owner、--sid-owner、および --cmd-ownerといったPIDなどを直接指定するiptablesルールがサポートされていたようですが、なにか問題があったようで現在は利用できません。--uid-ownerと--gid-ownerは引き続き利用可能です。これらはINPUTチェインでは使えません。