本記事は 2020linuxアドベントカレンダーで書こうと思ってた話の供養です。 卒論で書けなかったんですすいません....
本人的にはいい感じに設定を入れてるのにどーーーしてなのか全くいい感じにパケットが通らない時がある。 そんな時にロジカルにトラブルシュートをできるようになりたくありませんか?
ここではパケットが通らない時の実際の事例2つを挙げてそれをどのようにして探し出して解決したかを紹介します。
対象読者は iproute2をググれば使える人たちぐらいを想定してます。簡単な記事です:)
TL;DR
こういう時使えるツール類は以下の通りなので気合で使ってにゃん
- trace-cmd: なんの関数が呼ばれたかわかる便利な相棒。XDPでもお世話になったりするし便利
- perf: さっきと同じ感じ。
- こいつらを眺めるときは kernelshark を使うとグラフィカルで目に優しい
- ipftrace2: skbbuffにmetadataをつけてやることでパケットがkernelのどこを通ったがわかる便利ツール
- dropwatch: udpパケットがどこでドロップしてるかわかる。しかしL2L3などのレイアーは共通なので十分に使えるので初手これを使っておくと楽そう。
bpftrace: そもそも関数の返り値とか眺めたいときはこれを使うと良い。ただしプローブが書かれてないときは自分で書く必要がある。
- 今回は取り扱わないが機会があれば書く。
どんな事例の前でも共通して見ておく&やっておくといいこと
事例1: どうしてか SRv6 End が動いてくれない
EndというのはSRv6のパケットを受けてSRHにあるipv6アドレスのポインター次に進めてそれに合わせたにルーティングする技術のことです。 前提として使ってたのは
- fedora33
- iproute2
となります。また どんな事例の前でも共通して見ておくといいこと
で書いたことは全て行ったあとです。
今回は正しいパケットが来てるのにリダイレクトされないというところが問題でした。
この場合ですととりあえずどんな関数コールをされているのかをあたりをつける必要があります。
なのでそもそもSRv6のEnd動作をするkernelの関数コード (input_action_end
) まで到達してるかをtrace-cmdで確認しました。
具体的にはまず https://github.com/torvalds/linux/blob/master/net/ipv6/seg6_local.cのコードを眺めます。
みるとわかるのですが input_action_end*
のような形で一定のパターンで実装が記述されています。SRv6自体は複数のlocal functionをRFCにて定義されてるため、同じようなノリで書かれてる関数がいくつかあるのではないかと推論されるため、同じようなパターンを持つコードにかかれるのではないかと想定されます。
またトレース可能かどうかを調べるのも良いでしょう。trace-cmdの場合は以下のようにすると所望の関数へのプローブが対応してるかが分かります。
cat /sys/kernel/debug/tracing/available_filter_functions |grep input_action_end input_action_end_dt6 input_action_end_dx6 input_action_end_dx2 input_action_end_dx4 input_action_end input_action_end_x input_action_end_t input_action_end_b6 input_action_end_b6_encap input_action_end_bpf
で、実際やってみるのは以下のようにします。
trace-cmd record -p function_graph -g input_action_end trace-cmd report
しかし実際には動いておらずレコードがされませんでした。
このことから endを行うコードまでは到達してないことがわかります。ということでその前にdropしてるということがわかります。 では次にそもそもどこを通ってどのようなコールが行われるのかを考えたいですよね? 次に使ったのは ipftrace2を利用しました。これはskbというパケットが含まれる構造体にあるmetadataの領域に特定の値を仕込んでおくことでそのパケットがどこを通るのか出力します。
sudo ip6tables -t raw -A PREROUTING -d fc00:2::2 -j MARK --set-mark 0xdeadbeed sudo ipft -m 0xdeadbeed
これを使うと以下のような出力が出ます。
677084806878622 001 __skb_checksum_complete 677084806882860 001 kfree_skb 677084806887163 001 skb_release_head_state 677084806892984 001 skb_release_data 677084806897402 001 kfree_skbmem 677084836856575 001 ip6_route_input 677084836879043 001 ip6_input 677084836881744 001 nf_hook_slow 677084836895052 001 nf_ip6_checksum 677084836899705 001 __skb_checksum_complete 677084836903818 001 kfree_skb 677084836907878 001 skb_release_head_state 677084836913613 001 skb_release_data 677084836918676 001 kfree_skbmem 677084865837153 001 ip6_route_input 677084865860250 001 ip6_input 677084865863010 001 nf_hook_slow 677084865876901 001 nf_ip6_checksum 677084865881458 001 __skb_checksum_complete 677084865885631 001 kfree_skb 677084865889919 001 skb_release_head_state 677084865895676 001 skb_release_data 677084865900353 001 kfree_skbmem ...
大体どこを通ってるかを理解することができました。
ちなみにiptables以外にもtcを利用することでマークをつけることもできます。以下の例はtun1を通るパケットにマーク1, eth1を通るパケットにマーク2を与えている例です。
sudo tc qdisc add dev tun1 ingress sudo tc filter add dev tun1 parent ffff: protocol ip matchall action skbedit mark 1 sudo tc qdisc add dev eth1 ingress sudo tc filter add dev eth1 parent ffff: protocol ip matchall action skbedit mark 2
ではこの辺を起点に調べると良いでしょう。ただこの状態だとiptablesを使ってるのでnfがどこにかかってるのかわかりません。一旦切りましょう ここからは二通りの方法があります。
一つはtrace-cmdやperfで虱潰しに眺めることです。以下のようなコマンドで調べれたりします。
sudo perf record -g -a -e skb:kfree_skb sudo perf script
他にもtrace-cmdで ip6_input
をみるとこんな感じになります
https://gist.github.com/takehaya/8e07656af34bc6b7b132843147f364c5
二つ目は dropwatch を使うことです。 初手これでもいいぐらいだったんですが どこでdropしてるかが即座にわかります。
以下のようにしたらinstallできるはずです
sudo apt install -y libnl-3-dev libnl-genl-3-dev libreadline-dev libpcap-dev binutils-dev make automake libtool m4 autoconf clang pkg-config git clone https://github.com/nhorman/dropwatch.git cd dropwatch ./autogen.sh ./configure make sudo make install
今回はこれで最後に落とし込んでしまいましょう。
sudo dropwatch -l kas dropwatch> start 30 drops at nf_hook_slow ...
さて、先ほどのトレースにも出てきましたが nf_hook_slow
というのが出てきました。
雑にぐぐるとこのような記事がありました。
nfというのはnetfilter系のコードでiptables周りとかをコールすると動くものであると理解できると思います。
あとはちょっとした連想ゲームです。
iptablesとは?=>linux 組み込みのfirewall。fedoraにおいてfirewallを司るものは??
そう、原因は firewalld
でした。
ということで解決方法は以下の通りになります。
systemctl stop firewalld
事例2: どうしてか SRv6 End.DX4 が動いてくれない
End.DX4というのはSRv6のパケットでなおかつinnerがipv4のパケットを受けてouterをdecapした上で任意のNexthopにルーティングする技術のことです。 前提として使ってたのは
- ubuntu18.04
- iproute2
となります。また どんな事例の前でも共通して見ておくといいこと
で書いたことは全て行ったあとです。
今回の問題はなぜかnexthopにパケットが飛ばないでした。
さて先ほどと同じようなノリで呼び出されるべきな関数のトレースから見てみましょう。
trace-cmd record -p function_graph -g input_action_end_dx4 plugin 'function_graph' Hit Ctrl^C to stop recording ^CCPU0 data recorded at offset=0x4b5000 4096 bytes in size trace-cmd report cpus=1 <idle>-0 [000] 4724.772017: funcgraph_entry: | input_action_end_dx4() { <idle>-0 [000] 4724.772043: funcgraph_entry: | decap_and_validate() { <idle>-0 [000] 4724.772043: funcgraph_entry: | get_srh() { <idle>-0 [000] 4724.772044: funcgraph_entry: 0.698 us | ipv6_find_hdr(); <idle>-0 [000] 4724.772045: funcgraph_entry: 0.082 us | seg6_validate_srh(); <idle>-0 [000] 4724.772046: funcgraph_exit: 2.053 us | } <idle>-0 [000] 4724.772046: funcgraph_entry: 0.171 us | seg6_hmac_validate_skb(); <idle>-0 [000] 4724.772046: funcgraph_entry: 0.203 us | ipv6_find_hdr(); <idle>-0 [000] 4724.772047: funcgraph_exit: 3.766 us | } <idle>-0 [000] 4724.772047: funcgraph_entry: 0.058 us | dst_release(); <idle>-0 [000] 4724.772049: funcgraph_entry: | ip_route_input_noref() { <idle>-0 [000] 4724.772049: funcgraph_entry: | ip_route_input_rcu() { <idle>-0 [000] 4724.772051: funcgraph_entry: | make_kuid() { <idle>-0 [000] 4724.772051: funcgraph_entry: 0.472 us | map_id_range_down(); <idle>-0 [000] 4724.772052: funcgraph_exit: 1.000 us | } <idle>-0 [000] 4724.772052: funcgraph_entry: 3.247 us | fib_table_lookup(); <idle>-0 [000] 4724.772056: funcgraph_entry: | fib_validate_source() { <idle>-0 [000] 4724.772056: funcgraph_entry: 0.053 us | l3mdev_master_ifindex_rcu(); <idle>-0 [000] 4724.772057: funcgraph_entry: | make_kuid() { <idle>-0 [000] 4724.772057: funcgraph_entry: 0.055 us | map_id_range_down(); <idle>-0 [000] 4724.772057: funcgraph_exit: 0.457 us | } <idle>-0 [000] 4724.772058: funcgraph_entry: 0.820 us | fib_table_lookup(); <idle>-0 [000] 4724.772059: funcgraph_entry: 0.105 us | l3mdev_master_ifindex_rcu(); <idle>-0 [000] 4724.772059: funcgraph_exit: 3.146 us | } <idle>-0 [000] 4724.772060: funcgraph_entry: 0.066 us | ip_handle_martian_source.isra.40(); <idle>-0 [000] 4724.772060: funcgraph_exit: + 10.579 us | } <idle>-0 [000] 4724.772060: funcgraph_exit: + 11.295 us | } <idle>-0 [000] 4724.772060: funcgraph_entry: | kfree_skb() { <idle>-0 [000] 4724.772061: funcgraph_entry: | skb_release_all() { <idle>-0 [000] 4724.772061: funcgraph_entry: 0.098 us | skb_release_head_state(); <idle>-0 [000] 4724.772061: funcgraph_entry: | skb_release_data() { <idle>-0 [000] 4724.772062: funcgraph_entry: | skb_free_head() { <idle>-0 [000] 4724.772062: funcgraph_entry: 0.665 us | page_frag_free(); <idle>-0 [000] 4724.772063: funcgraph_exit: 1.184 us | } <idle>-0 [000] 4724.772063: funcgraph_exit: 1.788 us | } <idle>-0 [000] 4724.772063: funcgraph_exit: 2.793 us | } <idle>-0 [000] 4724.772064: funcgraph_entry: | kfree_skbmem() { <idle>-0 [000] 4724.772064: funcgraph_entry: 0.240 us | kmem_cache_free(); <idle>-0 [000] 4724.772065: funcgraph_exit: 0.758 us | } <idle>-0 [000] 4724.772065: funcgraph_exit: 4.366 us | } <idle>-0 [000] 4724.772065: funcgraph_exit: + 23.213 us | }
おや?今回は上手いこと到達してるのがわかりますね。
上から順番に調べていくと ip_handle_martian_source.isra.40();
という他と比べると何に使ってるのかわからないのがあることに気がつくと思います。
(残念ながらnetworkに詳しくないとエスパー気味かもしれません)
ちょろっと調べるとMartian packet: https://en.wikipedia.org/wiki/Martian_packet と言うものであると理解できます。
IANAの場合は martian packetを interfaceがそのネットワークを利用してないのに到着するパケットと言ってます。詰まるところサブネットを構成してないのに到着するパケットのことです。 詳しくはぐぐるといろいろ出るのでそこに任せます。これとかみると良いでしょう https://www.thegeekdiary.com/how-to-interpret-linux-martian-source-messages/
さて、以上のことから答えは出ました。RPfilterを無効にしましょう
sysctl net.ipv4.conf.eth0.rp_filter=0
簡単だったでしょう?
まとめ
事例がSRv6だけなのは自分の趣味ですが(すいません)、解決方法を見ればなーーんだwって思いましたよね?
さらにハマるパターンというのは少ないので大体
とかそう言うレベルなのでとりあえず当てずっぽうで入れてしまえるんですよね。
しかし初見だとそんなkernelパラメーター知らんがなとなってどう解決すればいいか頭に出てこないと思います。なのでロジカルに解決できるというのは非常に重要なことだと自分は思っています。 本記事が皆さんの毎日生き生きlinux networking生活に役立てば幸いです。