iptables勉強(2)
iptablesのtargetrの拡張
iptablesだいぶわかってきたので最小構成のtargetを書いてみた
構成ファイル
user空間用とkernel空間用の2種類のファイルを用意する必要がある
user空間用ファイル
iptablesコマンドを拡張するために /lib64/xtables/libに共有ライブラリを作成して配置する
名前はlibipt_<target名>.so
エントリポイントは_initという関数を作りxtables_targetを登録する
xtables.hはiptables-develパッケージに含まれている
#include <xtables.h> #include <stdio.h> static void MYLOG_help() { // printf("commandオプションがあればここに記述\n"); // iptables -j MYLOG --helpの時に表示される } static void MYLOG_print(const void *ip, const struct xt_entry_target *target, int numeric) { printf(" MYLOG"); // iptables -Lの時に表示される名前 } static struct xtables_target my_log_target = { .name = "MYLOG", .version = XTABLES_VERSION, .family = NFPROTO_IPV4, .help = MYLOG_help, .print = MYLOG_print }; void _init() { xtables_register_target(&my_log_target); }
作成したら共有ライブラリとしてコンパイルして/lib64/xtables/libに配置
gcc -fpic -shared ./libipt_MYLOG.c -o libipt_MYLOG.so
kernel空間用ファイル
user空間用ファイルに対応するカーネルモジュールを作成する
今回はヒットしたらログを出すだけ。
ユーザ空間のファイルとの対応はxt_targetのnameで行なっている
#include <linux/module.h> #include <linux/netfilter.h> #include <linux/netfilter/x_tables.h> MODULE_LICENSE("GPL"); static unsigned int log_mylog(struct sk_buff *skb, const struct xt_action_param *par) { printk(KERN_ALERT "run mylog\n"); return XT_CONTINUE; } static struct xt_target mylog_target __read_mostly = { .name = "MYLOG", .family = NFPROTO_IPV4, .target = log_mylog, .me = THIS_MODULE, }; static int __init mylog_init(void) { return xt_register_target(&mylog_target); } static void __exit mylog_exit(void) { xt_unregister_target(&mylog_target); } module_init(mylog_init); module_exit(mylog_exit);
作成したらカーネルモジュールをロード
insmod mylog.ko
iptables
user空間とkernel空間のファイルを作成すると、iptablesコマンドの-jオプションにて自分の作成したターゲットを指定できる
# iptables -A INPUT -j MYLOG # iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination MYLOG all -- anywhere anywhere MYLOG Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination
この状態でアクセスがあるとカーネルログにrun mylogが出力される
iptables勉強
ユーザ空間から IPTablesのチェインの取り出し。
とりあえずなんとなくとれるようになったので今日はここまで。
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <net/if.h> #include <getopt.h> #include <stdlib.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter/x_tables.h> int show_ipt_entry(struct ipt_entry * ent, struct ipt_getinfo *info, struct ipt_get_entries *entries) { struct xt_entry_target *xt_target = ipt_get_target(ent); // user定義チェイン if ( strcmp(xt_target->u.user.name, "ERROR") == 0 ) { printf("user定義チェイン(%s)\n", (char*)xt_target->data); return 0; } // builtinチェイン int i; for ( i = 0; i < NF_IP_NUMHOOKS; ++i ) { if ( info->valid_hooks & (1 << i) ) { // 有効か確認 if ( (struct ipt_entry*)((char*)entries->entrytable + info->hook_entry[i]) == ent ) { switch( i ) { case NF_IP_PRE_ROUTING: printf("PREROUTING\n"); break; case NF_IP_POST_ROUTING: printf("POSTROUTING\n"); break; case NF_IP_LOCAL_IN: printf("LOACLIN\n"); break; case NF_IP_LOCAL_OUT: printf("LOCALOUT\n"); break; case NF_IP_FORWARD: printf("FORWARD\n"); break; default: printf("unknown chain\n"); break; } } } } struct xt_standard_target *st = (struct xt_standard_target*)xt_target; printf("#### ipt_verdict %d ####\n", st->verdict); return 0; } void main(int argc, char *argv[]) { char table[256] = {}; strcpy(table, "filter"); int arg; while( (arg = getopt(argc, argv, "t:")) != -1 ) { switch(arg) { case 't': strcpy(table, optarg); break; } } int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); struct ipt_getinfo info; memset(&info, 0, sizeof(info)); strcpy(info.name, table); int len = sizeof(info); if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &len) == -1 ) { perror("IPT_SO_GET_INFO error\n"); return; } printf("#### ipt_getinfo ####\n"); printf("name : %s\n", info.name); printf("valid_hooks : %d\n", info.valid_hooks); printf("underflow: \n"); printf("\tNF_INET_PRE_ROUTING : %d\n", info.underflow[NF_INET_PRE_ROUTING]); printf("\tNF_INET_LOCAL_IN: %d\n", info.underflow[NF_INET_LOCAL_IN]); printf("\tNF_INET_FORWARD: %d\n", info.underflow[NF_INET_FORWARD]); printf("\tNF_INET_LOCAL_OUT: %d\n", info.underflow[NF_INET_LOCAL_OUT]); printf("\tNF_INET_POST_ROUTING: %d\n", info.underflow[NF_INET_POST_ROUTING]); printf("num_entries: %d\n", info.num_entries); printf("size: %d\n", info.size); struct ipt_get_entries *entries; len = sizeof(*entries) + info.size; entries = calloc(1, len); strcpy(entries->name, info.name); entries->size = info.size; if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &len) == -1 ) { perror("IPT_SO_GET_ENTRIES error\n"); return; } int i; for(i = 0; i < NF_INET_NUMHOOKS; ++i) { if ( info.underflow[i] == -1 ) { continue; } struct ipt_entry* ent = (struct ipt_entry*)((char*)entries->entrytable + info.underflow[i]); struct xt_entry_target *t = ipt_get_target(ent); if ( strcmp(t->u.user.name, "") != 0 ) { printf("error\n"); } struct xt_standard_target *st = (struct xt_standard_target*)t; } IPT_ENTRY_ITERATE(entries->entrytable, entries->size, show_ipt_entry, &info, entries); }
linux arpの内部データ
arpの統計情報が欲しかったのでnetlink経由で取得するものを作った NDTA_PARMSはrtattrが入れ子になっている。
#include <asm/types.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/neighbour.h> #include <linux/rtnetlink.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <stdint.h> static int seq; void msg_receive(int sock) { char buf[4096]; int n = read(sock, buf, sizeof(buf)); if ( n <= 0 ) { perror("read error\n"); exit(1); } struct nlmsghdr *nh = (struct nlmsghdr *)buf; // printf("read size: %d\n", n); if ( nh->nlmsg_seq != seq ) { printf("seq error\n"); exit(1); } if ( nh->nlmsg_type == NLMSG_ERROR ) { printf("error\n"); exit(1); } else if ( nh->nlmsg_type == RTM_NEWNEIGHTBL ) { NLMSG_ALIGN(nh->nlmsg_len); struct ndtmsg *ndtm = NLMSG_DATA(nh); if ( ndtm->ndtm_family != AF_INET ) { printf("ndgm_family is not AF_INET\n"); exit(1); } struct rtattr *rta; int len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndtmsg)); for (rta = (struct rtattr*)((char *)ndtm + sizeof(struct ndtmsg)); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch(rta->rta_type) { case NDTA_NAME: printf("NAME: %s\n", (char*)RTA_DATA(rta)); break; case NDTA_THRESH1: printf("THRESH1 %d\n", *(uint32_t*)RTA_DATA(rta)); break; case NDTA_THRESH2: printf("THRESH2 %d\n", *(uint32_t*)RTA_DATA(rta)); break; case NDTA_THRESH3: printf("THRESH3 %d\n", *(uint32_t*)RTA_DATA(rta)); break; case NDTA_CONFIG: { struct ndt_config *config = (struct ndt_config*)RTA_DATA(rta); printf("CONFIG:\n"); printf("\tkey_len: %d\n", config->ndtc_key_len); printf("\tentry_size: %d\n", config->ndtc_entry_size); printf("\tentries: %d\n", config->ndtc_entries); printf("\tlast_flush: %d\n", config->ndtc_last_flush); printf("\tlast_rand: %d\n", config->ndtc_last_rand); printf("\thash_rnd: %d\n", config->ndtc_hash_rnd); printf("\thash_mask: %d\n", config->ndtc_hash_mask); printf("\thash_chain_gc: %d\n", config->ndtc_hash_chain_gc); printf("\tproxy_qlen: %d\n", config->ndtc_proxy_qlen); } break; case NDTA_PARMS: { printf("PARAMS:\n"); struct rtattr *parm_rta; int parm_len = rta->rta_len; for (parm_rta = rta + 1; RTA_OK(parm_rta, parm_len); parm_rta = RTA_NEXT(parm_rta, parm_len)) { switch(parm_rta->rta_type) { case NDTPA_IFINDEX: printf("\tifindex: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_REFCNT: printf("\trefcnt: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_REACHABLE_TIME: printf("\treachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_BASE_REACHABLE_TIME: printf("\tbase_reachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_RETRANS_TIME: printf("\tretrans_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_GC_STALETIME: printf("\tgc_staletime: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_DELAY_PROBE_TIME: printf("\tdelay_probe_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_QUEUE_LEN: printf("\tqueue_len: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_APP_PROBES: printf("\tapp_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_UCAST_PROBES: printf("\tucast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_MCAST_PROBES: printf("\tmcast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_ANYCAST_DELAY: printf("\tanycast_delay: %lld\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_PROXY_DELAY: printf("\tproxy_delay: %d\n", *(uint64_t*)RTA_DATA(parm_rta)); break; case NDTPA_PROXY_QLEN: printf("\tproxy_qlen: %d\n", *(uint32_t*)RTA_DATA(parm_rta)); break; case NDTPA_LOCKTIME: printf("\tlocktime: %d\n", *(uint64_t*)RTA_DATA(parm_rta)); break; } } } break; case NDTA_STATS: { struct ndt_stats *stats = (struct ndt_stats*)RTA_DATA(rta); printf("STATS:\n"); printf("\tallocs: %d\n", stats->ndts_allocs); printf("\tdestorys: %d\n", stats->ndts_destroys); printf("\tgrow: %d\n", stats->ndts_hash_grows); printf("\tres_failed: %d\n", stats->ndts_res_failed); printf("\tlookups %d\n", stats->ndts_lookups); printf("\thits: %d\n", stats->ndts_hits); printf("\trcv_probes_mcast: %d\n", stats->ndts_rcv_probes_mcast); printf("\trcv_probes_ucast: %d\n", stats->ndts_rcv_probes_ucast); printf("\tperiodic_gc_runs: %d\n", stats->ndts_periodic_gc_runs); printf("\tforced_gc_runs: %d\n", stats->ndts_forced_gc_runs); } break; case NDTA_GC_INTERVAL: printf("GC_INTERVAL %lld\n", *(uint64_t*)RTA_DATA(rta)); break; } } } } void main() { srand(time(NULL)); seq = rand(); int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); if ( sock == -1 ) { perror("sock error\n"); exit(1); } struct { struct nlmsghdr nh; struct rtgenmsg rt; } h; struct sockaddr_nl nl; memset(&nl, 0, sizeof(nl)); nl.nl_family = AF_NETLINK; if ( bind(sock, (struct sockaddr*)&nl, sizeof(nl)) == -1 ) { perror("bind error\n"); exit(1); } memset(&h, 0, sizeof(h)); h.nh.nlmsg_type = RTM_GETNEIGHTBL; h.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP; h.nh.nlmsg_len = NLMSG_LENGTH(sizeof(h.rt)); h.nh.nlmsg_seq = seq; h.rt.rtgen_family = AF_INET; int n = write(sock, &h, sizeof(h)); if ( n <= 0 ) { perror("write error\n"); exit(1); } msg_receive(sock); }
出力はこんな感じ
NAME: arp_cache GC_INTERVAL 10000 THRESH1 128 THRESH2 512 THRESH3 1024 CONFIG: key_len: 4 entry_size: 260 entries: 3 last_flush: 27997298 last_rand: 63783 hash_rnd: 398501669 hash_mask: 7 hash_chain_gc: 0 proxy_qlen: 0 STATS: allocs: 49 destorys: 46 grow: 0 res_failed: 25 lookups 61661 hits: 48360 rcv_probes_mcast: 0 rcv_probes_ucast: 0 periodic_gc_runs: 2274 forced_gc_runs: 0 PARAMS: refcnt: 1 queue_len: 3 proxy_qlen: 64 app_probes: 0 ucast_probes: 3 mcast_probes: 3 reachable_time: 18336 base_reachable_time: 20000 gc_staletime: 60000 delay_probe_time: 5000 retrans_time: 1000 anycast_delay: 1000 proxy_delay: 800 locktime: 1000
linuxカーネルのpid管理(取得周り)
カーネルのpid管理と取得周りを調べてみた
PID名前空間
Linuxではpid_namespace(詳しくはman 7参照)に対応しているため、 1つのプロセスに対して複数のプロセスIDを複数持てるようになっている。
pid_namespaceは階層構造になっていて、 子のプロセスIDは同一プロセスでもそれぞれの名前空間で別のプロセスidとなる
親で作成されたのプロセスIDは子からは見えないようになっている
したがってプロセスIDは作成された階層の深度分プロセスIDを持つようになっている
カーネルでの表現
カーネル内部ではPIDはpidとupidという構造で表される
enum pid_type { PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX }; struct upid { int nr; // 実際のID struct pid_namespace *ns; // 属しているネームスペース struct hlist_node pid_chain; }; struct pid { atomic_t count; unsigned int level; // このプロセスが作成された時のname_spaceの深度 struct hlist_head tasks[PIDTYPE_MAX]; // pid構造体のプロセス、プロセスグループ、セッションに属するタスク struct rcu_head rcu; struct upid numbers[1]; // 可変長でlevel分だけupidを持つ };
pid構造体のIDはネームスペース毎にネームスペース毎に持つため、所属している深度分のupidを持つようになっていて、upidに実際の番号を持っている。
pid->tasksメンバ
tasksメンバはそのpidのプロセス、プロセスグループ、セッションに属するtask_structの一覧を管理する
pid構造体に属するPIDTYPE_PIDに結びつくプロセスは1つしかないので、 1つまでしか入らないことが保証されている。
IDからpidへの変換処理
IDからpidへの変換処理はよく行われるので、 pid_hashというIDとネームスペースのポインタをキーにしたハッシュを使い処理を高速化している
関数としてはネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。
extern struct pid *find_pid_ns(int nr, struct pid_namespace *ns); extern struct pid *find_vpid(int nr);
pidからidへの変換処理
ネームスペースのlevelからupidを探し出してnrを返す関数
こちらもネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。
pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns); pid_t pid_vnr(struct pid *pid);
現在実行中のプロセスのネームスペース取得方法
// 呼び出し元 task_active_pid_ns(current->group_leader); // currentはスレッドに対応するのでプロセスのtask_structで呼び出し struct pid_namespace *task_active_pid_ns(struct task_struct *tsk) { return ns_of_pid(task_pid(tsk)); // task_pidでpid構造体を取得してpid構造体からネームスペースを取得する } static inline struct pid *task_pid(struct task_struct *task) { return task->pids[PIDTYPE_PID].pid; // カレントプロセスのPID構造体を取得 } static inline struct pid_namespace *ns_of_pid(struct pid *pid) { struct pid_namespace *ns = NULL; if (pid) ns = pid->numbers[pid->level].ns; // カレントプロセスの最大深度 = カレントプロセスが作成された名前空間となる。 return ns; }
LinuxとFreeBSDの共有ライブラリについて
共有ライブラリについて
Linuxでは共有ライブラリとして.soファイルが使用される。 .soは実行時にリンクする以外でも、実行中にdlopenを使用して読み込むことができる。
実行中の共有ライブラリ読み込みの仕組み
共有ライブラリは.textセクションを共有する為、 静的ライブラリのように外部ライブラリのアドレスを relセクションを作ってリンク時に書き換えということができない。
.textセクションの関数呼び出し先を共有しない書き込み可能な.got(global offset table)という領域に分けて、 そのポインタを参照することにより対処している。
共有ライブラリの遅延ロード
dlopenにはRTLD_LEZYというフラグがある。 RTLD_LEZYは関数の実行時まで参照先のシンボルを解決しないというフラグだが、 それを実現するために.pltという領域を使用している。
.pltには
0x00007fffeb62f568 <+0>: jmpq *0x2022ea(%rip) # 0x7fffeb831858 0x00007fffeb62f56e <+6>: pushq $0x22 0x00007fffeb62f573 <+11>: jmpq 0x7fffeb62f338
のような関数ごとにjmpを2回するようなコードがある。 0x7fffeb831858はGOTのアドレスを指していて、この場合一度も実行していないと0x00007fffeb62f56e(pushdのところ)のアドレスが入っている。
したがって1回目の実行では最初のjmpは何もしないで次の行に進みシンボルをを解決したい関数のID(0x22)を入れて、シンボル解決するコードにジャンプする。 シンボル解決コードでは0x7fffeb831858の中を本来jmpしたいアドレスに書き換えてから、本来jmpしたいアドレスにjmpする。 2回目の呼び出しでは、0x7fffeb831858のGOTが書き換えられているので本来呼びたい関数に直接ジャンプできるという仕組みになっている
LinuxとFreeBSDの挙動の違い
一度解決したリンク先がdlcloseによって参照先がなくなった場合の挙動にlinuxとBSDでは微妙に違うところがある。
first.so
#include <stdio.h> void do_action() { printf("action for first_test\n"); }
second.so
#include <stdio.h> void do_action() { printf("action for second_test\n"); }
test.so
void do_test() { do_action(); }
main.c
void main(){ void (*do_test)(); void *first = dlopen("./libfirst.so", RTLD_LAZY | RTLD_GLOBAL); void *h = dlopen("./libtest.so", RTLD_LAZY | RTLD_LOCAL); *(void **)(&do_test) = dlsym(h, "do_test"); (*do_test)(); dlclose(first); void *second = dlopen("./libsecond.so", RTLD_LAZY | RTLD_GLOBAL); (*do_test)(); }
main => do_test => do_actionの順番に呼ばれているプログラムである。
上記実行するとFreeBSDではセグフォで落ちる どうなっているのかというとdo_testを読んだ時点で、do_actionの.gotがfirst.soのシンボルとして解決され、 その後, first.soをdlcloseにてプロセスから削除している。
次に呼ばれたdo_testでは削除されたアドレスにシンボル解決されたdo_actionを呼ぶので落ちるのである。
Linuxの出力は
action for first_test action for first_test
とfirstが二回出力される。
Linuxではシンボル解決された段階で参照先(first.so)の参照カウントが上がるようで、 dl_closeしても、プロセス空間から削除されないようだ。
まとめ
dl_openで読み込む対象を動的に切り替えるのはうまくいかない。 Linuxではエラーにはならないが最初に読み込まれたもののみが実行される。 FreeBSDではセグフォで落ちる
linuxカーネルのIPフラグメント処理(1)
IPフラグメント処理
第一フラグメント便乗攻撃を調べるためlinuxのIPフラグメント処理を調べてみた。
長くなりそうなのでとりあえずinet_fragmentのメモ
ソースコード
inet_fragmentはプロトコルに依存しない処理を実装して、 ip_fragmentはipプロトコルに関わる処理を実装している
inet_fragment
ネットワーク層のプロトコルに依存しないフラグメント処理をしている。 主にやっていることは下記の3つ
- フラグメント可されたパケットの管理
- タイムアウト処理
- リミット処理
procによるパラメータの設定
ipv4の場合、タイムアウト処理とリミット処理の閾値は下記proc経由で設定可能
- /proc/sys/net/ipv4/ipfrag_time (フラグメント可されたパケットを最初に受信してから有効な秒数)
- /proc/sys/net/ipv4/ipfrag_high_thresh (メモリ使用量がこの値を超えるとフラグメント可されたパケットを受信できなくなる)
- /proc/sys/net/ipv4/ipfrag_low_thresh (メモリ使用量がこの値を超えると値を下回るまで削除が始まる)
カーネル内部でパラメータの格納はnetns_fragsで行われる
struct netns_frags { struct percpu_counter mem; // 現在使用しているメモリ量 int timeout; // フラグメントのタイムアウト int high_thresh; // 削除を開始するメモリ上限 int low_thresh; // 処理を中止するメモリ上限 }
この値はネームスペース毎に保持してあり、プロセスからアクセスするには
task_struct->nsproxy->net_ns->ipv4->frags
となる
フラグメント可されたパケットの管理
フラグメント可されたパケットはinet_fragsのハッシュで管理されており、値はinet_frag_queueとなる プロトコルに関わるところは抽象化されている 主なパラメータは下記の通り
struct inet_frag_queue { struct timer_list timer; // タイムアウトの設定 struct hlist_node list; // ハッシュリスト struct sk_buff *fragments; // 同一パケット内のフラグメント可されたパケットのリスト struct sk_buff *fragments_tail; // fragmentsの最後の要素 int len; // 現在格納されているパケットの最終位置 int meat; // 現在格納されてるパケットのバイト数 (最後のパケットが格納されていてlen == meatになると全て格納されたことになる) __u8 flags; // 下で説明 struct netns_frags *net; // 対象名前空間の総計と制限値へのポインタ }; struct inet_frags { struct inet_frag_bucket hash[INETFRAGS_HASHSZ]; unsigned int (*hashfn)(const struct inet_frag_queue *); // プロトコル毎のハッシュ値の計算 (必須) bool (*match)(const struct inet_frag_queue *q, const void *arg); // プロトコル毎の値比較(必須) void (*destructor)(struct inet_frag_queue *); // プロトコル固有の情報削除用フック(任意) void (*skb_free)(struct sk_buff *); // sk_buff削除時のフック(任意) void (*frag_expire)(unsigned long data); // タイムアウトまたはメモリ上限が超えて削除される時のフック(必須) struct kmem_cache *frags_cachep; // inet_frag_initで設定される名前がfrags_cache_nameでサイズがqsizeのスラブキャッシュ const char *frags_cache_name; // スラブキャッシュに使用される名前(必須) int qsize; // プロトコル固有データとinet_frag_queueデータの合計サイズ(必須) }
構造体自体は使用する側で確保して、必須となっているパラメータの設定を行い、
inet_frag_init
を呼ぶことにより使用可能となる。 inet_frag_init内ではhashの初期化とfrag_cache_name、qsizeからスラブハッシュを構築する
タイムアウト処理
IPフラグメントは各セグメント毎にタイムアウトが設定されるため、 inet_frag_queueにタイマーを持っている。
作成と同時にタイマーが設定され、 タイムアウト時の呼び出しはinet_fragsに設定されたfrag_expire関数が呼ばれる
inet_frag_create inet_frag_alloc setup_timer(&q->timer, f->frag_expire, (unsigned long)q); inet_flag_intern mod_timer(&qp->timer, jiffies + nf->timeout)
リミット処理
制限値を超えた場合は、カーネルのワークキューを使用してメモリの解放処理が行われている。
トリガーとなるのはソフトリミットを超えた場合で、ハードリミットを超えると処理を中断する。 ワーカースレッドが実行する関数は
inet_frag_worker
で、ソフトリミットを超えなくなるまでハッシュの要素を削除する。
expireが呼ばれる時のタイムアウトとリミット処理判断
expireはタイムアウトとリミット処理の両方で、リミットを超えて削除される場合は
INET_FRAG_EVICTED
フラグが設定される。 呼び出し元で判断を行いたい場合はこのフラグを参照する。
参考にさててもらったサイト
ドメインレートリミット(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)