目次
はじめに
CentOS 7で、firewalldの設定をする機会があったので、調べた内容を書いてみます。
調べた内容は、目次の通りです。
今回、私は、firewalldのダイレクトルールを使ったicmpの制御について、
設定方法と、その動作の確認を行いました。
ダイレクトルールの設定方法は、direct.xmlをエディタで編集しています。
firewall-cmdを使った方法は、試していないため、この記事には出てきません。
また、ルールで使用した拡張モジュール、hashlimitについて興味が出たので、
調べられる限り、調べてみました。
ダイレクトルールの設定
ダイレクトルールは、/etc/firewalld/dirext.xmlを作成、編集します。
デフォルトだと存在していなかったので、エディタで新規作成しました。
以下のような内容で、icmpのEcho Requestの制御が可能なことを確認しています。
<?xml version="1.0" encoding="utf-8"?>
<direct>
<chain table="filter" ipv="ipv4" chain="CHECK_ICMP"/>
<rule priority="0" table="filter" ipv="ipv4" chain="INPUT">-i lo -j ACCEPT</rule>
<rule priority="10" table="filter" ipv="ipv4" chain="INPUT">-m state --state RELATED,ESTABLISHED -j ACCEPT</rule>
<rule priority="20" table="filter" ipv="ipv4" chain="INPUT">-p icmp -m icmp --icmp-type 8 -j CHECK_ICMP</rule>
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-p icmp -j ACCEPT</rule>
<rule priority="100" table="filter" ipv="ipv4" chain="CHECK_ICMP">-p icmp -m icmp --icmp-type 8 -m hashlimit --hashlimit 1/s --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-name check_icmp --hashlimit-htable-expire 300000 -j RETURN</rule>
<rule priority="150" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j LOG --log-level debug --log-prefix 'ping_attack: '</rule>
<rule priority="151" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j DROP</rule>
</direct>
/etc/firewalld/direct.xmlを変更した後は、
systemctl restart firewalldを実行して、設定を反映します。
[root@localhost ~]# systemctl restart firewalld
[root@localhost ~]#
chainについて
先ほどのdirect.xmlの内容では、chainタグを使って、自作のチェイン、CHECK_ICMPを作成しています。
チェインは、処理内容をまとめたものに、名前を付けたもの(関数のようなもの)です。
チェインは、デフォルトで、いくつか存在しています。
filterと呼ばれるtableには、INPUT、FORWARD、OUTPUTの組み込みチェインが存在します。
今回は、受信時のパケットに対してruleを適用したいだけなので、主にINPUTと、自作したchainを使っています。
ipv4のみを見るため、ipv="ipv4"としています。
tableやipvなどの詳細は、firewalldの、ダイレクトルールについてのマニュアルを参照してください。
ruleについて
チェインごとに、ruleを作成できます。
組み込みチェインに対してもルールを追加できます。
filterの組み込みチェインであるINPUTに対して、ruleを追加することで、
受信時のパケットをフィルターする、という設定を行っていきます。
ruleはruleタグで指定します。
タグには、プライオリティと、そのruleの適用先のチェイン(どのtableの、どのipvの、どのチェインか)を指定します。
プライオリティは、0からの番号を指定します。より小さい番号から先に、ルールが処理されていきます。
※同一のプライオリティのruleが存在した場合、その順序は不定のようです。
※チェインごとにプライオリティは独立しているのか?という点は試していません。。。
ruleタグの内容として、パケットのマッチ条件、および動作を指定します。
条件には、そのパケットのプロトコル、送信元、宛先、および拡張モジュールなどがあります。
動作には、ACCEPT、DROP、RETURNなどがあります。
これらの詳細については、iptables、およびiptables-extensionsのマニュアルを見てください。
先のdirect.xmlの内容を例に、ルールの説明を記載します。
# インターフェイスがloかチェックする
# マッチしたら許可する
<rule priority="0" table="filter" ipv="ipv4" chain="INPUT">-i lo -j ACCEPT</rule>
# 拡張モジュールstateを使用して、パケットの状態がRELATED,ESTABLISHEDをチェックする
# マッチしたら許可する
<rule priority="10" table="filter" ipv="ipv4" chain="INPUT">-m state --state RELATED,ESTABLISHED -j ACCEPT</rule>
# icmpかチェックする
# 拡張モジュールicmpを使用して、icmp-typeが8(Echo Request)かチェックする
# マッチしたらCHECK_ICMPに移動する
<rule priority="20" table="filter" ipv="ipv4" chain="INPUT">-p icmp -m icmp --icmp-type 8 -j CHECK_ICMP</rule>
# icmpかチェックする。
# マッチしたら許可する。
# 1つ前の条件と合わせると、icmpのうち、Echo Requestだけ特別なチェックをして、それ以外のicmpは許可する、となる。
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-p icmp -j ACCEPT</rule>
# これはCHECK_ICMPチェインのルール
# icmpかチェックする
# 拡張モジュールicmpを使用して、icmp-typeが8(Echo Request)かチェックする
# 拡張モジュールhashlimitを使用して、秒間パケット数をチェックする
# マッチしたら、元のチェインのルールに戻る(次に実行されるルールは、呼び出し元のルールの、次のルール)
# hashlimitについては後述する
<rule priority="100" table="filter" ipv="ipv4" chain="CHECK_ICMP">-p icmp -m icmp --icmp-type 8 -m hashlimit --hashlimit 1/s --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-name check_icmp --hashlimit-htable-expire 300000 -j RETURN</rule>
# これはCHECK_ICMPチェインのルール
# このルール自体には、マッチ条件は無く、このルールが実行されたら、ログを出力する。
<rule priority="150" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j LOG --log-level debug --log-prefix 'ping_attack: '</rule>
# これはCHECK_ICMPチェインのルール
# このルール自体には、マッチ条件は無く、このルールが実行されたら、パケットを破棄する。
<rule priority="151" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j DROP</rule>
hashlimitについて
拡張モジュール -m hashlimitについて。
この拡張モジュールを使用することで、たとえば、秒間10件のパケットを許可し、
それ以上のパケットが来た場合は、破棄する、というような処理を行うことが出来ます。
-m hashlimitには、以下の引数が必要になります。
--hashlimit 1/s
--hashlimit-burst 3
--hashlimit-mode srcip
--hashlimit-name check_icmp
--hashlimit-htable-expire 300000
-
--hashlimit
受信可能なパケット数の指定をします。
指定方法は、秒間パケット数をそのまま指定するか、もしくは、1/sや1/m、1/h、1/d、というように、指定します。
秒間100件を許可したいなら、100/sとします。分間100件許可するなら、100/mとします。
2秒間に1件としたい、という場合、0.5/sというような指定はできませんが、分間30件(30/m)、
というように指定すれば、同等の結果が得られます。 -
--hashlimit-burst
この項目で指定する値は、倍率です。n倍、というような意味合いを持ちます。
たとえば、大体平均100/sのパケットが来る環境で、稀に最大300/sまで増えることがあり、
しかし、これを正常として許可したい、というような場合に、hashlimit-burst 3とします。
hashlimit-burstを3にすることで、一時的に通常時の3倍までは、許可を許可するようになります。
そして、仮に300/sという通信が継続した場合は、100/sの通信に制限されます。
※後述するハッシュテーブルの内容を見ると、理解が早まるかと思います。 -
--hashlimit-mode
この項目で、何を主キーとして、パケット情報を管理するのかを指定します。
hashlimitでは、送信元IPごとや、宛先ポートごと、というように、
管理情報を複数持つことが出来ます。
管理情報のことを、ハッシュテーブルといいます。
srcipとした場合は、送信元IPアドレス事に、ハッシュテーブルにレコードが記載されます。 -
--hashlimit-name
ハッシュテーブルの名前を指定します。
ここで指定した名前で、/proc/net/ipt_hashlimitに、ハッシュテーブルが作成されます。
たとえば、check_icmpを指定した場合は、
/proc/net/ipt_hashlimit/check_icmp
というハッシュテーブルが作成されます。
このハッシュテーブルは、firewalld起動時に、作成されます。
また、起動前にあった情報は、再起動のタイミングで削除されます。 -
--hashlimit-htable-expire
ハッシュテーブル中にあるレコードの、有効期限をミリ秒で指定します。
--hashlimit-htable-expire 300000とすれば、該当するパケットをチェックしたときに、
そのレコードの残り時間は300秒にセットされます。
該当するパケットが来なければ、指定した時間が経過したタイミングで、
レコードは削除されます。
ハッシュテーブルについて
今回の試したdirect.xmlの内容だと、
/proc/net/ipt_hashlimit/check_icmp
というハッシュテーブルが作成されます。
該当するルールが実行されていない状況だと、中身は空です。
ここで、他のホストから、icmpパケットを送ってみます。
すると、以下のように1行追加されます。
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
296 172.31.34.66:0->0.0.0.0:0 96000 96000 32000
[root@localhost ~]#
ハッシュテーブルには、 --hashlimit-modeで指定した単位で、レコードを持ちます。
この内容は、スペース区切りで、5つの内容を持ちます。
-
残り時間
このレコードが存在する残り秒数です。該当するパケットをチェックしたときに、
--hashlimit-htable-expireで指定した秒数に再度セットされます。
0になった場合、該当するレコードは削除されます。 -
主キー
--hashlimit-modeで指定した主キーです。
srcipを指定した場合は、送信元IPアドレスが172.31.34.66だった場合、
172.31.34.66:0->0.0.0.0:0
というような内容が記載されます。 -
クレジット(現在値)
hashlimitは、該当するパケットをチェックするとき、1パケットに設定されているコストを、
このクレジットから支払えるかどうかで、真偽を返します。
支払えた場合は真を返し、クレジットからコストを引きます。
支払えなかった場合は、偽を返し、結果、そのルールはマッチしなかった、となります。
ここで、クレジット(現在値)は、秒間32000、増加します。
秒間32000、というのは、1秒間隔で32000増える、というわけではなく、線形に増加し続けます。 -
クレジット(最大値)
クレジットの最大値です。これを超えて、クレジット(現在値)が増加することはありません。
クレジットの値は、コスト × hashlimit-burstを計算した値となります。
コストが32000で、hashlimit-burstが3であれば、クレジットの最大値は96000となります。
※場合によって、コスト × hashlimit-burstにならないときもあります。未調査。 -
コスト
1パケットを処理するときに、要求される値です。
この値は1/sを32000として、割り切れる場合は計算可能です。
(割り切れない場合は、この計算式では誤差がでます)
--hashlimit 1/s とした場合は、32000 / 1 = 32000となります。
--hashlimit 2/s とした場合は、32000 / 2 = 16000となります。
--hashlimit 1/m とした場合は、32000 * 60 / 1 = 1920000となります。
上記、クレジットの増え方や、コストの計算は、説明をする上で、わかりやすく
1/s = 32000、として行いました。
実際のコストの決定は、どうやら違うようで、たとえば、123/sというhashlimitを指定した場合、
コストは、上記の計算では約260になりますが、実際の動作を確認してみると、259になります。
一応、疑似的に同じような値を導き出す方法は、確認できたのですが、
なんでこうなるのか、は説明できないです。。。
末尾に、実際に決定するクレジットとコストを求めるコードを置いておきます。
動作確認ログ
以下のdirect.xmlを作成して、firewalldを再起動します。
[root@localhost ~]# cat /etc/firewalld/direct.xml
<?xml version="1.0" encoding="utf-8"?>
<direct>
<chain table="filter" ipv="ipv4" chain="CHECK_ICMP"/>
<rule priority="0" table="filter" ipv="ipv4" chain="INPUT">-i lo -j ACCEPT</rule>
<rule priority="10" table="filter" ipv="ipv4" chain="INPUT">-m state --state RELATED,ESTABLISHED -j ACCEPT</rule>
<rule priority="20" table="filter" ipv="ipv4" chain="INPUT">-p icmp -m icmp --icmp-type 8 -j CHECK_ICMP</rule>
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-p icmp -j ACCEPT</rule>
<rule priority="100" table="filter" ipv="ipv4" chain="CHECK_ICMP">-p icmp -m icmp --icmp-type 8 -m hashlimit --hashlimit 1/s --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-name check_icmp --hashlimit-htable-expire 300000 -j RETURN</rule>
<rule priority="150" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j LOG --log-level debug --log-prefix 'ping_attack: '</rule>
<rule priority="151" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j DROP</rule>
</direct>
[root@localhost ~]#
[root@localhost ~]# systemctl restart firewalld
[root@localhost ~]#
別のホストから、firewalldが動作しているホストに対して、pingを実行します。
ここで1つ、予想と違ったので、詳細にicmpの場合のフィルター動作を記載します。
以下のように、pingコマンドで、-iオプションを使ってインターバルを変えて、
1秒間に5発、icmpパケットを送信します。
# intervalを0.2秒にして、1秒間に5発程度、icmpパケットを送れば、hashlimitが1/s、burstが3だけど、
# それ以上のパケットが到着するし、hashlimitが偽になって、DROPに落ちるはずだ!
ping -i 0.2 192.168.0.1
この結果は、DROPに落ちることはなく、また、このコマンドを動作させ続けたとしても、
hashlimitでDROPすることは無いです。
実際にpingを実行したログです。
# hashlimitが1/s、burstが3なら、seq4のあたりで、DROPに行くんじゃないのか!?
[root@localhost ~]# ping -i 0.2 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.183 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.170 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=0.289 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=0.209 ms
64 bytes from 192.168.0.1: icmp_seq=5 ttl=64 time=0.184 ms
64 bytes from 192.168.0.1: icmp_seq=6 ttl=64 time=0.225 ms
64 bytes from 192.168.0.1: icmp_seq=7 ttl=64 time=0.289 ms
64 bytes from 192.168.0.1: icmp_seq=8 ttl=64 time=0.271 ms
64 bytes from 192.168.0.1: icmp_seq=9 ttl=64 time=0.224 ms
64 bytes from 192.168.0.1: icmp_seq=10 ttl=64 time=0.246 ms
64 bytes from 192.168.0.1: icmp_seq=11 ttl=64 time=0.190 ms
64 bytes from 192.168.0.1: icmp_seq=12 ttl=64 time=0.145 ms
64 bytes from 192.168.0.1: icmp_seq=13 ttl=64 time=0.137 ms
64 bytes from 192.168.0.1: icmp_seq=14 ttl=64 time=0.165 ms
64 bytes from 192.168.0.1: icmp_seq=15 ttl=64 time=0.119 ms
64 bytes from 192.168.0.1: icmp_seq=16 ttl=64 time=0.131 ms
64 bytes from 192.168.0.1: icmp_seq=17 ttl=64 time=0.210 ms
64 bytes from 192.168.0.1: icmp_seq=18 ttl=64 time=0.240 ms
64 bytes from 192.168.0.1: icmp_seq=19 ttl=64 time=0.178 ms
64 bytes from 192.168.0.1: icmp_seq=20 ttl=64 time=0.228 ms
[root@localhost ~]#
# ずっと成功する!!なんで!?
この状況を確認するために、以下のようにdirect.xmlを変更して、
ログを出して確認してみました。
[root@localhost ~]# cat /etc/firewalld/direct.xml
<?xml version="1.0" encoding="utf-8"?>
<direct>
<chain table="filter" ipv="ipv4" chain="CHECK_ICMP"/>
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-j LOG --log-level debug --log-prefix 'check_1: '</rule>
<rule priority="1" table="filter" ipv="ipv4" chain="INPUT">-i lo -j ACCEPT</rule>
<rule priority="10" table="filter" ipv="ipv4" chain="INPUT">-m state --state RELATED,ESTABLISHED -j ACCEPT</rule>
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-j LOG --log-level debug --log-prefix 'check_1: '</rule>
<rule priority="20" table="filter" ipv="ipv4" chain="INPUT">-p icmp -m icmp --icmp-type 8 -j CHECK_ICMP</rule>
<rule priority="21" table="filter" ipv="ipv4" chain="INPUT">-j LOG --log-level debug --log-prefix 'check_2: '</rule>
<rule priority="22" table="filter" ipv="ipv4" chain="INPUT">-p icmp -j ACCEPT</rule>
<rule priority="100" table="filter" ipv="ipv4" chain="CHECK_ICMP">-p icmp -m icmp --icmp-type 8 -m hashlimit --hashlimit 1/s --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-name check_icmp --hashlimit-htable-expire 300000 -j RETURN</rule>
<rule priority="150" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j LOG --log-level debug --log-prefix 'ping_attack: '</rule>
<rule priority="151" table="filter" ipv="ipv4" chain="CHECK_ICMP">-j DROP</rule>
</direct>
[root@localhost ~]#
[root@localhost ~]# systemctl restart firewalld
[root@localhost ~]#
この状態で、先と同じようにpingを実行すると、
kernのログに、以下の内容が出てきます。
# macは、一応隠します。
Dec 4 12:26:35 192.168.0.1 kernel: [1042210.642801] check_0: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=54084 SEQ=1
Dec 4 12:26:35 192.168.0.1 kernel: [1042210.642839] check_1: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=54084 SEQ=1
Dec 4 12:26:35 192.168.0.1 kernel: [1042210.642869] check_2: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=54084 SEQ=1
別のホストから、0.2秒間隔で、icmpパケットを何発か送っていますが、このログしか出てきません。
どうやら、icmpの場合、同じicmp_idのicmpパケットは、最初のパケットのみしか、INPUTチェインに入ってこないようです。
このため、たとえば、hashlimitを1/sにして、burstを1にして、
他のホストからpingコマンドで、ping -i 0.1 というようにして、icmpパケットをたくさん送ったとしても、
icmp_idが変わらない限り、pingは成功し続けます。
手でpingを、別プロセスで実行し続けるのは大変なので、実験用に、
以下のようなスクリプトを使用します。
#! /usr/bin/perl -w
use strict;
use warnings;
$SIG{'CHLD'} = 'IGNORE';
while (1) {
my $pid = fork();
if ($pid == 0) {
system("ping 192.168.0.1 -c 1");
exit(0);
}
select undef, undef, undef, 0.2;
}
このスクリプトを別のホスト上で実行しておき、
firewalldが動作しているホスト上でtcpdumpを使ってみると、
以下のような結果が得られます。
[root@localhost ~]# tcpdump icmp and host 192.168.0.2
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:50:30.916493 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 11340, seq 1, length 64
12:50:30.916553 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 11340, seq 1, length 64
12:50:31.117352 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 11852, seq 1, length 64
12:50:31.117411 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 11852, seq 1, length 64
12:50:31.317484 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 12364, seq 1, length 64
12:50:31.317522 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 12364, seq 1, length 64
12:50:31.518332 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 12876, seq 1, length 64
12:50:31.718706 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 13388, seq 1, length 64
12:50:31.918752 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 13900, seq 1, length 64
12:50:31.918785 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 13900, seq 1, length 64
12:50:32.119558 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 14412, seq 1, length 64
12:50:32.320119 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 14924, seq 1, length 64
12:50:32.520330 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 15436, seq 1, length 64
12:50:32.721219 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 15948, seq 1, length 64
12:50:32.921632 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 16460, seq 1, length 64
12:50:32.921681 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 16460, seq 1, length 64
12:50:33.122178 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 16972, seq 1, length 64
12:50:33.322561 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 17484, seq 1, length 64
12:50:33.523171 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 17996, seq 1, length 64
12:50:33.723931 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 18508, seq 1, length 64
12:50:33.924387 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 19020, seq 1, length 64
12:50:33.924453 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 19020, seq 1, length 64
12:50:34.124944 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 19532, seq 1, length 64
12:50:34.325677 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 20044, seq 1, length 64
12:50:34.526117 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 20556, seq 1, length 64
12:50:34.726403 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 21580, seq 1, length 64
12:50:34.927007 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 22092, seq 1, length 64
echo replyの行に注目してください。
最初の3発分まで、echo replyを返していますが、
その次のecho replyを返すのが、だいたい1秒間隔になっているのが、わかると思います。
この状態の、kernには、hashlimitが偽になることで実行されるLOGの内容が出てきています。
Dec 4 12:50:31 192.168.0.1 kernel: [1043646.212480] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=12876 SEQ=1
Dec 4 12:50:31 192.168.0.1 kernel: [1043646.412856] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=13388 SEQ=1
Dec 4 12:50:32 192.168.0.1 kernel: [1043646.813714] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=14412 SEQ=1
Dec 4 12:50:32 192.168.0.1 kernel: [1043647.014263] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=14924 SEQ=1
Dec 4 12:50:32 192.168.0.1 kernel: [1043647.214480] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=15436 SEQ=1
Dec 4 12:50:32 192.168.0.1 kernel: [1043647.415358] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=15948 SEQ=1
Dec 4 12:50:33 192.168.0.1 kernel: [1043647.816288] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=16972 SEQ=1
Dec 4 12:50:33 192.168.0.1 kernel: [1043648.016702] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=17484 SEQ=1
Dec 4 12:50:33 192.168.0.1 kernel: [1043648.217317] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=17996 SEQ=1
Dec 4 12:50:33 192.168.0.1 kernel: [1043648.418077] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=18508 SEQ=1
Dec 4 12:50:34 192.168.0.1 kernel: [1043648.819083] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=19532 SEQ=1
Dec 4 12:50:34 192.168.0.1 kernel: [1043649.019816] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=20044 SEQ=1
Dec 4 12:50:34 192.168.0.1 kernel: [1043649.220218] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=20556 SEQ=1
Dec 4 12:50:34 192.168.0.1 kernel: [1043649.420528] ping_attack: IN=eth0 OUT= MAC=xxx SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=21580 SEQ=1
また、この状態でハッシュテーブルの中身を、catなどを連打して表示させると、
以下のようになります。
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 25216 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 1184 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 8768 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 16064 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 23936 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 31424 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 8768 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 15840 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 24608 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 32672 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 8672 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 16800 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 25024 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 32288 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 7456 96000 32000
[root@localhost ~]# cat /proc/net/ipt_hashlimit/check_icmp
299 192.168.0.2:0->0.0.0.0:0 14976 96000 32000
最初の要素の、299が変動していないのは、0.2秒間隔で、常にパケットが来ているためです。
クレジット(現在値)に注目してください。
1パケットのコストである32000未満の状態では、徐々に増え続けて、
そして32000以上になったとき、hashlimitの条件は真となり、パケットがACCEPTされます。
そしてクレジット(現在値)はコスト分引かれて小さくなり、これを繰り返しています。
最初の3発分は、クレジットの最大値が3倍分あり、そこから値を引くため、連続で3回までは、
コストを支払うことができるので、hashlimitは真となります。先のtcpdumpの結果からも、
この動作は確認できています。
これまでの内容から、hashlimitがどういう風に動作しているのか、理解できたと思います。
クレジットとコストの計算
hashlimitのコストの計算で、1/s = 32000として説明しましたが、
実際とは異なるという説明をしました。
ここに、実際に使用される内容と、だいたい同じになるように
計算した値を返す、ソースコードを記載します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int parse_hashlimit(char *hashlimit, char *burst, size_t *cost, size_t *credit) {
size_t unit = 1;
size_t v = strtoul(hashlimit, NULL, 10);
size_t b = strtoul(burst, NULL, 10);
if (v == 0 || b == 0) {
return 1;
}
char *c = strchr(hashlimit, '/');
if (c != NULL && c+1 != NULL && *(c+1) != '\0') {
switch (*(c+1)) {
case 's' : unit = 1; break;
case 'm' : unit = 60; break;
case 'h' : unit = 60 * 60; break;
case 'd' : unit = 60 * 60 * 24; break;
default : return 1;
}
}
double tmp = 10000.0 / ((double)v / (double)unit);
double tmp2 = (double)((size_t)tmp) / 10.0 * 32.0;
*cost = (size_t)tmp2;
*credit = (size_t)(tmp2 * (double)b);
return 0;
}
int main(int argc, char** argv) {
if (argc != 3) {
printf("Usage: ./a.out hashlimit hashlimit-burst\n");
exit(1);
}
size_t cost = 0;
size_t credit = 0;
if (parse_hashlimit(argv[1], argv[2], &cost, &credit)) {
printf("hashlimit parse error.\n");
exit(1);
}
printf("credit=%lu, cost=%lu\n", credit, cost);
}
これをコンパイルして、実行した結果です。
[root@localhost ~]# gcc a.c
[root@localhost ~]# ./a.out 1/s 1
credit=32000, cost=32000
[root@localhost ~]# ./a.out 2/s 1
credit=16000, cost=16000
[root@localhost ~]# ./a.out 3/s 1
credit=10665, cost=10665
[root@localhost ~]# ./a.out 4/s 1
credit=8000, cost=8000
[root@localhost ~]# ./a.out 5/s 1
credit=6400, cost=6400
[root@localhost ~]# ./a.out 123/s 1
credit=259, cost=259
[root@localhost ~]# ./a.out 124/s 1
credit=256, cost=256
[root@localhost ~]# ./a.out 123/s 10
credit=2592, cost=259
[root@localhost ~]# ./a.out 124/s 10
credit=2560, cost=256
都度、direct.xmlを書き換えて、hashlimitをチェックさせて、
ハッシュテーブルを見る、という作業が面倒なので、
ここではログは割愛します。一応手で試して動作を見ていた限り、
上記の内容はあっていました。
上記以外の内容で、誤差が出るかもしれませんが、、、。
そもそもの実際の計算方法がわかれば、、、という気持ちです。
参考にしたサイト
-
firewalldのダイレクトルールについてのマニュアル
https://firewalld.org/documentation/man-pages/firewalld.direct.html -
iptablesのマニュアル
https://linuxjm.osdn.jp/html/iptables/man8/iptables.8.html -
iptables-extensions(拡張モジュール)のマニュアル
https://linuxjm.osdn.jp/html/iptables/man8/iptables-extensions.8.html