Linux の sysctl net.ipv4. icmp_echo_ignore_broadcasts / macOS (XNU) sysctl net.inet.icmp.bmcastecho の実装を読む
Linux の sysctl net.ipv4. icmp_echo_ignore_broadcasts
と、 macOS / XNU の sysctl net.inet.icmp.bmcastecho
の実装を調べたエントリです
何の sysctl ですか?
Linux の sysctl net.ipv4. icmp_echo_ignore_broadcasts
は CIS Benchmarks で Smurf 攻撃対策として取り上げられています。
Smurf 攻撃とは何ですか?
Smurf 攻撃自体の説明は他のサイトで確認してください
SMURF攻撃とは、攻撃者が標的となるサーバーをInternet Control Message Protocol(ICMP)パケットで圧倒させようとする分散サービス妨害(DDoS)攻撃です。1つまたは複数のコンピューターネットワークに対して、標的のデバイスのスプーフィングIPアドレスでリクエストを行うことにより、コンピューターネットワークは標的サーバーに応答し、初期攻撃トラフィックを増幅し、潜在的に標的を圧倒し、アクセス不能にします。この攻撃ベクトルは一般に、解決された脆弱性と見なされており、もはや流行していません。
引用: https://www.cloudflare.com/ja-jp/learning/ddos/smurf-ddos-attack
sysctl net.ipv4. icmp_echo_ignore_broadcasts の詳細
sysctl net.ipv4. icmp_echo_ignore_broadcasts
あるいは /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
の説明を、Red Hat Cusotmer Portal から引用します。
icmp_echo_ignore_all 及び icmp_echo_ignore_broadcasts — 各ホストからの ICMP ECHO パケット、もしくはブロードキャスト及びマルチキャストのアドレスを起点とする ICMP ECHO パケットのみを無視することをそれぞれカーネルに許可します。0 の値は、カーネルによる応答を許可し、1 の値はパケットを無視します。
macOS / XNU にも同様の sysctl 設定はあるのか?
macOS ( XNU ) では、Linux と同様に機能する sysctl として sysctl net.inet.icmp.bmcastecho
が用意されています。なお、bmcastecho
は Broadcast Multicast Echo
の略称のようです。
sysctl の実装を読もう
Smurf 攻撃の sysctl の設定方法を取り扱った記事はたくさんありますが、実装 (= カーネルのソース) まで踏み込んでるエントリは無いようなので、調べて見ましょう。
macOS ( XNU ) のソースも調べてました
Linux の実装
Linux カーネルのソースで grep (ag) icmp_echo_ignore_broadcasts net/
として、 sysctl の定義を見つけることができました。 1️⃣ の部分です。
static struct ctl_table ipv4_net_table[] = { ... { .procname = "icmp_echo_ignore_broadcasts", .data = &init_net.ipv4.sysctl_icmp_echo_ignore_broadcasts, 1️⃣ .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec },
sysctl_icmp_echo_ignore_broadcasts は icmp_recv() で参照されます
icmp_rcv()
IP レイヤから ICMP を受け取って処理するのが icmp_rcv() です。
static const struct net_protocol icmp_protocol = { .handler = icmp_rcv, .err_handler = icmp_err, .no_policy = 1, .netns_ok = 1, };
ネットワークスタックで icmp_rcv() がどのような位置づけなのかは、wiki.bit-hive.com さんの Linuxカーネルメモの図が参考になります。(いつもありがとうございます)
続いて icmp_rcv() と sysctl_icmp_echo_ignore_broadcasts のソースです
int icmp_rcv(struct sk_buff *skb) { struct icmphdr *icmph; struct rtable *rt = skb_rtable(skb); struct net *net = dev_net(rt->dst.dev); bool success; ... /* * Parse the ICMP message */ if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) { 2️⃣ /* * RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be * silently ignored (we let user decide with a sysctl). * RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently * discarded if to broadcast/multicast. */ if ((icmph->type == ICMP_ECHO || icmph->type == ICMP_TIMESTAMP) && net->ipv4.sysctl_icmp_echo_ignore_broadcasts) { 1️⃣ goto error; } ... } success = icmp_pointers[icmph->type].handler(skb); 4️⃣ ... error: __ICMP_INC_STATS(net, ICMP_MIB_INERRORS); 3️⃣ goto drop; }
sysctl が参照されるのは 1️⃣ の部分ですね。 ICMP がマルチキャストあるいはブロードキャストかどうかは 2️⃣ で判定しているようです。
1️⃣ で無視された ICMP は 2️⃣ でエラーとして統計されます。統計は /proc/net/snmp
の InErrors
で確認できます。
ubuntu@primary:~$ grep Icmp: /proc/net/snmp Icmp: InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps Icmp: 40 0 0 40 0 0 0 0 0 0 0 0 0 0 40 0 40 0 0 0 0 0 0 0 0 0 0
4️⃣ を見ると ICMP のタイプに応じてハンドラの関数が呼び出される実装になっています (例 icmph->type == 8 (ICMP_ECHO
なら Echo Request を処理するコードにつながる ) 。ハンドラの詳細はこのエントリでは扱いません。
ここまで読んで sysctl_icmp_echo_ignore_broadcasts がどのように機能するか把握できました。
macOS / XNU
macOS / XNU での sysctl 設定は全くアテがない状態で調べました。 sysctl -a | grep icmp
の中から「これかな?」と調べていきました。
$ sysctl -a | grep icmp net.inet.icmp.maskrepl: 0 net.inet.icmp.icmplim: 250 net.inet.icmp.timestamp: 0 net.inet.icmp.icmplim_random_incr: 250 net.inet.icmp.drop_redirect: 1 net.inet.icmp.log_redirect: 0 net.inet.icmp.bmcastecho: 1 👈 net.inet.tcp.icmp_may_rst: 1
net.inet.icmp.bmcastecho
の名前がそれっぽいので、ググてって調べたところ golang/go の以下の issue コメントがヒントになり、Smurf 攻撃対策の sysctl なのだと絞り込むことができました
https://github.com/golang/go/issues/5811#issuecomment-66081853
Nope, I completely forgot that old RFC says "An ICMP Echo Request destined to an IP broadcast or IP multicast address MAY be silently discarded" and almost all implementations follow that direction. Will fix the test case.
後はソースを探るだけです。 bsd/netinet/ip_icmp.c
に sysctl の定義があります
/* * ICMP broadcast echo sysctl */ static int icmpbmcastecho = 1; SYSCTL_INT(_net_inet_icmp, OID_AUTO, bmcastecho, CTLFLAG_RW | CTLFLAG_LOCKED, &icmpbmcastecho, 0, "");
icmpbmcastecho
が参照されているのは以下の箇所です
/* * Process a received ICMP message. */ void icmp_input(struct mbuf *m, int hlen) { struct sockaddr_in icmpsrc, icmpdst, icmpgw; struct icmp *icp; struct ip *ip = mtod(m, struct ip *); int icmplen; int i; struct in_ifaddr *ia; void (*ctlfunc)(int, struct sockaddr *, void *, struct ifnet *); int code; boolean_t should_log_redirect = false; ... case ICMP_ECHO: if ((m->m_flags & (M_MCAST | M_BCAST))) { if (icmpbmcastecho == 0) { 1️⃣ icmpstat.icps_bmcastecho++; break; } } ... case ICMP_TSTAMP: if (icmptimestamp == 0) { break; } if (!icmpbmcastecho 1️⃣ && (m->m_flags & (M_MCAST | M_BCAST)) != 0) { icmpstat.icps_bmcasttstamp++; break; }
Linux カーネルと細かい差異はあれど、同様に読める感じですね。XNU の実装を読むのはこの辺で終わり。 ( ユーザランドから ICMP の統計を確認する方法がよくわからなかった )
Linux で検証
multipass で起動した 2台の VM (ホスト名は primary
と second
) で検証します。 second で net.ipv4.icmp_echo_ignore_broadcasts=0
に設定します
ubuntu@sencod :~$ sudo sysctl net.ipv4.icmp_echo_ignore_broadcasts=0
primary から ping で -b
をつけてブロードキャストすると応答が返ってきます ( DUP になるのか〜 )
-b Allow pinging a broadcast address.
ubuntu@primary:~$ sudo ping -b 192.168.64.255 WARNING: pinging broadcast address PING 192.168.64.255 (192.168.64.255) 56(84) bytes of data. 64 bytes from 192.168.64.1: icmp_seq=1 ttl=64 time=4.07 ms 64 bytes from 192.168.64.14: icmp_seq=1 ttl=64 time=4.75 ms (DUP!)
なお sysctl_icmp_echo_ignore_broadcasts の初期値は、 Linux カーネルのソースでは 1
です。ディストリビューションや自分で入れた設定で 0
にしない限りは 1
のままですね。
/* Control parameters for ECHO replies. */ net->ipv4.sysctl_icmp_echo_ignore_all = 0; net->ipv4.sysctl_icmp_echo_ignore_broadcasts = 1;