totofugaのブログ

ネットワークとかc言語、perlの話。

ドメインレートリミット(unbound)

ドメインレートリミットとは

/services/cache/infra.cに実装されている

例えば 以下のようなレコードが返ってくる権威サーバーがあったとする

QD:
   www.hoge.co.jp. IN A

NS:
 hoge.co.jp. IN NS ns1.hoge.co.jp.
 hoge.co.jp. IN NS ns2.hoge.co.jp.

このような場合に次にns1.hoge.co.jpまたはns2.hoge.co.jpに問い合わせを行うが、 この問い合わせ数を制限するものである。

水攻め攻撃が行われ

a.hoge.co.jp
b.hoge.co.jp
c.hoge.co.jp
d.hoge.co.jp

のように名前を変えたアクサスをされると、毎回キャッシュに存在しない問い合わせになるので アクセスがあるたびにhoge.co.jpに問い合わせを行ってしまう。

これを防ぐために攻撃を受けているドメインの権威サーバへのアクセスを N秒以内にM回までに制限する。

Nはunboundでは固定で2秒となっている

設定項目

設定項目は下記の6つ

ratelimit: num

ratelimitサイズで、これが0の場合はドメインratelimit無効になる。

ratelimit-size: size

Hashのキーのサイズ(バイト指定)

ratelimit-slabs: num

Hashのスラブサイズ。マルチスレッドの時のアクセス効率を良くするため、ハッシュ自体を何個に分けるかを指定。

ratelimit-factor: num

ratelimitを超えた場合にどうするかを決める。 0だと必ずFAILを返すようになるここに数値を設定するとratelimitを超えた場合には1/数値だけ 成功するようになる。

ratelimit-for-domain: domain num

指定されたドメインのratelimit値を別な値に置き換える 沢山の子を持つTLDドメインや攻撃を受けているドメインのrate-limitを特別扱いしたい場合等に使える

ratelimit-below-domain: domain num

指定されたドメインサブドメインすべてのratelimit値を別な値に置き換える 指定されたドメイン自体のrate-limitは置き換わらない。

テスト

テストがしやすいようにconfの項目の値を下記のように設定しておく。

log-queries: yes         
ratelimit: 3             
do-ip4: yes              
do-ip6: no               
verbosity: 2             
harden-referral-path: no 

ライミングとncad.co.jpのキャッシュを終わらせるために一度www.ncad.co.jpに問い合わせてから次のようなスクリプトを実行する

dig @127.0.0.1 a.ncad.co.jp &
dig @127.0.0.1 b.ncad.co.jp &
dig @127.0.0.1 c.ncad.co.jp &
dig @127.0.0.1 d.ncad.co.jp &
dig @127.0.0.1 e.ncad.co.jp &

実行結果

May 16 08:53:03 unbound[11879:1] info: 127.0.0.1 b.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: resolving b.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: 127.0.0.1 a.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: resolving a.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: 127.0.0.1 c.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: resolving c.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] notice: ratelimit exceeded ncad.co.jp. 3       
May 16 08:53:03 unbound[11879:1] info: response for b.ncad.co.jp. A IN          
May 16 08:53:03 unbound[11879:1] info: reply from <ncad.co.jp.> 182.171.76.42#53
May 16 08:53:03 unbound[11879:1] info: query response was NXDOMAIN ANSWER       
May 16 08:53:03 unbound[11879:1] info: 127.0.0.1 d.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: resolving d.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: 127.0.0.1 e.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: resolving e.ncad.co.jp. A IN             
May 16 08:53:03 unbound[11879:1] info: response for e.ncad.co.jp. A IN          
May 16 08:53:03 unbound[11879:1] info: reply from <ncad.co.jp.> 182.171.76.42#53
May 16 08:53:03 unbound[11879:1] info: query response was NXDOMAIN ANSWER       

3件でratelimitがかかる為、query responseの行が2行しか無いのがわかる。

ratelimitは最初に超えた時しかログが出ないのでragtelimit exceededの行は1行のみである。

内部構造

キャッシュを管理しているデータ

// infra.h

struct infra_cache { 
   ...
    struct slabhash* domain_rate; // keyはrate_key dataはrate_data 対象ドメインに秒間何回のアクセスがあったかを格納する
    rbtree_type domain_limits; // aclと同じくdomainのparentを辿れる構造。for-domainとbelow-domainの情報を格納
    ...
};

struct rate_key {
    struct lruhash_entry entry;
    uint8_t *name;  // ドメイン名のバイナリ表現(圧縮無し)
    size_t name_len; // nameのlen
};

struct rate_data {
    int qps[RATE_WINDOW]; // 対象の秒数に何回アクセスがあったかをカウントする
    time_t timestamp[RATE_WINDOW]; // qpsに対応する時刻(秒単位)
};

// infra.c 
int infra_dp_ratelimit // configで指定されたratelimitの値を格納 0 だと無効

関数

// infra.c 

// 指定された現在時間からRATE_WINDOW以内のアクセスがratelimitを超えていないか調べる
int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, size_t namelen,  time_t timenow) 

// 指定されたドメインに対するratelimitをインクリメントする インクリメントされた結果ratelimitを超えているかを返す
int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow)