7
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

firewalldのダイレクトルールとフィルター処理について

Posted at

目次

はじめに

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をチェックさせて、
ハッシュテーブルを見る、という作業が面倒なので、
ここではログは割愛します。一応手で試して動作を見ていた限り、
上記の内容はあっていました。

上記以外の内容で、誤差が出るかもしれませんが、、、。
そもそもの実際の計算方法がわかれば、、、という気持ちです。

参考にしたサイト

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?