お腹.ヘッタ。

関数型とかセキュリティとか勉強したい。進捗つらぽよ

linux kernelでパケットがなぜかルーティングされないとかドロップされるときにみる記事

本記事は 2020linuxアドベントカレンダーで書こうと思ってた話の供養です。 卒論で書けなかったんですすいません....

本人的にはいい感じに設定を入れてるのにどーーーしてなのか全くいい感じにパケットが通らない時がある。 そんな時にロジカルにトラブルシュートをできるようになりたくありませんか?

ここではパケットが通らない時の実際の事例2つを挙げてそれをどのようにして探し出して解決したかを紹介します。

対象読者は iproute2をググれば使える人たちぐらいを想定してます。簡単な記事です:)

TL;DR

こういう時使えるツール類は以下の通りなので気合で使ってにゃん

  • trace-cmd: なんの関数が呼ばれたかわかる便利な相棒。XDPでもお世話になったりするし便利
  • perf: さっきと同じ感じ。
    • こいつらを眺めるときは kernelshark を使うとグラフィカルで目に優しい
  • ipftrace2: skbbuffにmetadataをつけてやることでパケットがkernelのどこを通ったがわかる便利ツール
  • dropwatch: udpパケットがどこでドロップしてるかわかる。しかしL2L3などのレイアーは共通なので十分に使えるので初手これを使っておくと楽そう。
  • bpftrace: そもそも関数の返り値とか眺めたいときはこれを使うと良い。ただしプローブが書かれてないときは自分で書く必要がある。

    • 今回は取り扱わないが機会があれば書く。
  • どんな事例の前でも共通して見ておく&やっておくといいこと

    • 正しくプロトコルとiproute2などの設定を入れるコマンドを理解してるかを見ておく。RFCやドラフトは読みましたか?manページは見ましたか?
    • tcpdump -i eth1 -vvv とかで期待してるパケットが来てるか
    • ethtool -S eth1 でRX or TXでドロップされてるのかとか。どの辺で死んでそうかをイメージしておく
    • ip n とかでL2が解決してるかを確認

事例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 組み込みのfirewallfedoraにおいて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って思いましたよね?

さらにハマるパターンというのは少ないので大体

  • rpfilter
  • firewalld
  • フォワード忘れてる
  • arpが解決されてない、

とかそう言うレベルなのでとりあえず当てずっぽうで入れてしまえるんですよね。

しかし初見だとそんなkernelパラメーター知らんがなとなってどう解決すればいいか頭に出てこないと思います。なのでロジカルに解決できるというのは非常に重要なことだと自分は思っています。 本記事が皆さんの毎日生き生きlinux networking生活に役立てば幸いです。