メニュー 閉じる

nftablesで特定の接続元からの大量アクセスを遮断する

iptables後継のnftables基本操作もあわせてご覧下さい。

特定の送信元からのDoSのような大量アクセスや、連続したログイン試行を遮断したいと思ったことはないでしょうか。
nftablesではmetersetを使うことでこれを実現することができます。

この記事では、大量アクセスを遮断する方法について紹介します。

注意

nftablesのバージョンやLinux kernelのバージョンで、書き方が異なります。
meterは古い書き方で、nftables 0.9以降(?)、Linux kernel 4.18以降ではsetを利用します。
この記事では、setを中心に、meterについても触れていきます。

nftablesはまだ過渡期のようで、正しい表現のはずなのにバージョンによってはSyntax Errorとなることがあります。
setについては、nftables 0.9.2、Linux kernel 4.19.0で設定投入ができることを確認しました。

設定方針

nftablesの初期状態は以下の様に定義します。

  • input方向はdropするポリシーとします
  • 設定対象のインタフェースはens3です
  • established, relatedなinputは許可しておきます

また、大量アクセスを遮断するために、以下の様な設定を施すことにします。

  • (大量アクセスと判断されない)必要なinputだけacceptします
    • 今回は、SSHのアクセスを許可することにします

初期状態のinputベースチェイン

初期状態のチェインを事前に載せておきます。

# nft list chain inet filter input
table inet filter {
        chain input {
                type filter hook input priority 0; policy drop;
                iifname "ens3" ct state { established, related } accept
        }
}

通信量による制御方法

実際の設定を進める前に、nftablesにおける通信量による制御方法を紹介します。
nftablesではパケット数やバイト数(バイト数はLinux kernel 4.3以降)による条件指定を、limit rateキーワードで行うことができます。
さらに指定したパケット数やバイト数を「超える」場合という条件の場合は、overキーワードを付加することで対応できます。
iptabelsの頃はhashlimitと呼ばれたものに代わる機能です。

具体的には以下の様な書式です。

## パケット数による制御(10パケット/秒までのpingは許可)
# nft add rule inet filter input icmp type echo-request limit rate 10/second accept

## バイト数による制御(10Mbytes/秒を超える(over)通信は破棄)
# nft add rule inet filter input limit rate over 10 mbytes/second drop

これで制御できるじゃない!って思ったそこのあなた。
上記の記述だけでは、残念ながらマッチする全ての通信に対して通信量制限がかかることとなります。
それでは目的を達成することはできませんね!?

特定の接続元からの通信量制御

さて本題に入りますが、通信量制御を送信元単位で行うためにはIPアドレス単位でlimit rateをかけなければなりません。
そのためにはIPアドレス毎に状態を保持する方法が必要です。
元々meterを使うことで実現していましたが、最新のnftablesでは汎用的なsetを使うことで実現することになっています。

では早速、それぞれの方法を紹介していきます。

設定方法(set)

最新のnftables、Linux kernelを利用しているあなたは、setを使いましょう。

setとはナニモノかというと、IPアドレスや何やらの汎用的なリストを作ることができるものです。
例えば初期状態のベースチェインに示したような、{ established, related }setの一部(匿名セット)です。

setの使い方については説明するより実際のルールを見た方が分かりやすいです。
以下のルールを見てみましょう。

# nft add set inet filter sshv4-counter { type ipv4_addr\; timeout 10m\; flags dynamic, timeout\; }
# nft add rule inet filter iifname "ens3" tcp dport 22 ct state new add @sshv4-meter { ip saddr limit rate 1/minute } accept
# nft list ruleset
        set sshv4-meter {
                type ipv4_addr
                size 65535
                flags dynamic,timeout
                timeout 10m
        }

        chain input {
                type filter hook input priority filter; policy drop;
                iifname "ens3" ct state { established, related } accept
                iifname "ens3" tcp dport 22 ct state new add @sshv4-meter { ip saddr limit rate 1/minute } accept
        }

1行目で、sshv4-counterというsetを作成しています。
;はエスケープが必要なことに注意してください。
このsetは次のような設定になっています。

  • IPv4アドレスのリスト(type ipv4_addr
  • 追加された値は10分で削除(timeout 10m
  • 動的に追加でき、タイムアウトが設定(flags dynamic, timeout

なおflagsについては後ほど紹介します。

2行目では、少し細かいルール指定をしています。
具体的には次のようなルールです。

  • 22/tcp宛(tcp dport 22)の新規のコネクション(ct state new)が来たとき
  • 送信元IPアドレス毎(ip saddr)に1分あたり1回以内(limit rate 1/minute)であれば
  • sshv4-meterというsetに追加(add @sshv4-meter)して
  • 通信を許可(accept)する

この状態で条件にマッチする通信を発生させ、sshv4-meterを表示させると以下の様になります。

# nft list set inet filter sshv4-meter
table inet filter {
        set sshv4-meter {
                type ipv4_addr
                size 65535
                flags dynamic,timeout
                elements = { 192.168.0.1 expires 9m53s208ms limit rate 1/minute, 192.168.0.2 expires 2m5s501ms limit rate 1/minute }
        }
}

elementsが表示され、IPアドレスと状態(limit rate1/minute)が紐付いて、リストになっていることが分かります。

ちなみに、もし1分あたり1回を超えるような通信があった場合はこのルールにマッチしないため、ポリシーdropすることで結果的に通信が遮断されることになるわけですね。

setのパラメータ

add setで指定できる主要なパラメータを紹介しておきます。

パラメータ 値のタイプ 説明
type ipv4_addr, ipv6_addr,
ether_addr, inet_proto,
inet_service, mark
要素のデータ種別
flags constant, dynamic,
interval, timeout
フラグ
dynamicは、ルールで動的に追加する際に指定
timeoutは、タイムアウトを設定する際に指定
パラメータのtimeoutを指定した場合は、flagsに自動的にtimeoutが付く
timeout string
(利用可能な単位:d, h, m, s
要素の保持時間
例:1h30m(1時間30分)
size unsigned integer (64bit) 要素の保持できる最大数

設定方法(meter)

ちょっと古いnftables、Linux kernelを使っている方は、meterコマンドを使う必要があります。

meterの使い方についても説明するより実際のルールを見た方が分かりやすいです。
以下のルールを見てみましょう。

# nft add rule inet filter iifname "ens3" tcp dport 22 ct state new meter sshv4-meter { ip saddr limit timeout 10m rate 1/minute } accept
# nft list ruleset
        chain input {
                type filter hook input priority filter; policy drop;
                iifname "ens3" ct state { established, related } accept
                iifname "ens3" tcp dport 22 ct state new meter sshv4-meter { ip saddr timeout 10m limit rate 1/minute } accept
        }

meterの場合は1行だけでルールが完結します。
1行目は、具体的には次のようなルールです。

  • 22/tcp宛(tcp dport 22)の新規のコネクション(ct state new)が来たとき
  • 送信元IPアドレス毎(ip saddr)に1分あたり1回以内(limit rate 1/minute)であれば
  • sshv4-meterというmeterに追加(meter sshv4-meter)して
  • 通信を許可(accept)する

この状態で条件にマッチする通信を発生させ、sshv4-meterを表示させると以下の様になります。
なお、meterは明示的に作成せずにlist meterすることができます。

# nft list meter inet filter sshv4-meter
table inet filter {
        meter sshv4-meter {
                type ipv4_addr
                flags timeout
                elements = { 192.168.0.1 expires 9m53s208ms : limit rate 1/minute, 192.168.0.2 expires 2m5s501ms : limit rate 1/minute }
        }
}

elementsが表示され、IPアドレスと状態(limit rate1/minute)が紐付いて、リストになっていることが分かります。

setmeterもほとんど変わらない上に若干meterの方がシンプルな気がしますね。

おまけ

ちなみに、ポリシーをacceptにしている場合は追加するルールをlimit rate overの表現にしつつdropするルールにすれば、一定の通信量を超えたものを破棄するように書くこともできます。

コメントを残す