‹ hrntknr's blog

tcpdumpのproto/protochainの違い

Feb 28, 2024

TL;DR

  • proto は protocol number でフィルターするが、直下の protocol しか評価しない
    • ip proto \tcpIP/TCPにマッチする。IP/AH/AH/TCPはマッチしない。
  • protochain は protocol number でフィルターするが、ネストされた protocol にも対応している
    • ip protochain \tcpIP/TCPIP/AH/AH/TCPの両方にマッチする
  • protochain は offset を変更するわけではない
    • IP/IP の inner header にマッチさせようと、ip protochain ipv4 and src 10.0.0.1 と書くのは誤り
    • この場合、outer header の src == 10.0.0.1 で評価される。

tcpdump

  • パケットキャプチャをする際によく tcpdump が利用されると思うのだが、引数にパケットを評価する filter を書くことができる。
  • filter はpcap-filterとも呼ばれ、人間の読みやすい形から最終的に bpf の実行コードにコンパイルされ、評価される。
    • tcpdump の-d オプションを使うことで、実際に利用されるアセンブリを見ることが可能。
  • pcap-filter のprotoキーワードを使うことにより、対象パケットのプロトコルによるフィルターをかけることができる。
    • 例:tcpdump -nn -i any "proto \tcp"
  • protochain と呼ばれるキーワードもあり、こちらはプロトコルヘッダチェーン(AH header など)に対応している
  • 今回、ipip の inner field に対してマッチさせたかったが、思うような挙動をとらなかった。
    • iptables bpf module に関しては後ほど記事化する予定。

cbpf/ebpf

ip proto

実際に ip proto でフィルターをかけた際のアセンブリを見ていく。
(コメントは後から付与したもの。出力結果には書いてない。)

; tcpdump -d "ip proto ipv4 and src 10.0.0.1"
(000) ldh      [12]                             ; a = eth.type
(001) jeq      #0x800           jt 2    jf 7    ; if (a == ETHERTYPE_IP) goto 2 else goto 7
(002) ldb      [23]                             ; a = ip.proto
(003) jeq      #0x4             jt 4    jf 7    ; if (a = IPPROTO_TCP) goto 4 else goto 7
(004) ld       [26]                             ; a = ip.saddr
(005) jeq      #0xa000001       jt 6    jf 7    ; if (a == 10.0.0.1) goto 6 else goto 7
(006) ret      #262144
(007) ret      #0

命令コードは少なくシンプル。順番に評価を行って false な条件があったら jump をするようなコードになっている。
見ているフィールドは 3 つ。

  • eth.type == ETHERTYPE_IP
  • ip.proto == IPPROTO_TCP
  • ip.saddr == 10.0.0.1

ip protochain

; tcpdump -d "ip protochain ipv4 and src 10.0.0.1"
(000) ldh      [12]                             ; a = eth.type
(001) jeq      #0x800           jt 2    jf 35   ; if (a == ETHERTYPE_IP) goto 2 else goto 35
(002) ldb      [23]                             ; a = ip.proto
(003) ldxb     4*([14]&0xf)                     ; x = ip.ihl*4
; parse offset loop start (a: protocol, x: nexthdr length)
(004) jeq      #0x4             jt 20   jf 5    ; if (a == IPPROTO_TCP) goto 20 else goto 5
(005) jeq      #0x3b            jt 20   jf 6    ; if (a == IPPROTO_NONE) goto 20 else goto 6
(006) add      #0                               ;
(007) jeq      #0x33            jt 8    jf 20   ; if (a == IPPROTO_AH) goto 8 else goto 20
(008) txa                                       ; a = x
(009) ldb      [x + 14]                         ; a = ah.nexthdr
(010) st       M[0]                             ; M[0] = a
(011) txa                                       ; a = x
(012) add      #1                               ; a = a+1
(013) tax                                       ; x = a
(014) ldb      [x + 14]                         ; a = ah.len
(015) add      #2                               ; a = a+2
(016) mul      #4                               ; a = a*4
(017) tax                                       ; x = a
(018) ld       M[0]                             ; a = M[0]
(019) ja       4                                ; goto 4
; parse offset loop end
(020) add      #0
(021) jeq      #0x4             jt 22   jf 35   ; if (a == IPPROTO_IP) goto 22 else goto 35
(022) ldh      [12]                             ; a = eth.type
(023) jeq      #0x800           jt 24   jf 26   ; if (a == ETHERTYPE_IPV4) goto 24 else goto 26
(024) ld       [26]                             ; a = ip.saddr
(025) jeq      #0xa000001       jt 34   jf 26   ; if (a == 10.0.0.1) goto 34 else goto 26
(026) ldh      [12]                             ; a = eth.type
(027) jeq      #0x806           jt 28   jf 30   ; if (a == ETHERTYPE_ARP) goto 28 else goto 30
(028) ld       [28]                             ; a = arp.spa
(029) jeq      #0xa000001       jt 34   jf 30   ; if (a == 10.0.0.1) goto 34 else goto 30
(030) ldh      [12]                             ; a = eth.type
(031) jeq      #0x8035          jt 32   jf 35   ; if (a == ETHERTYPE_RARP) goto 32 else goto 35
(032) ld       [28]                             ; a = rarp.spa
(033) jeq      #0xa000001       jt 34   jf 35   ; if (a == 10.0.0.1) goto 34 else goto 35
; return
(034) ret      #262144
(035) ret      #0

大きく分けて 2 つの部分に分けられる。(and の前と後)

前半

ここは ip protochain ipv4 に当たる内容の部分になっている。
ip.proto == IPPROTO_IP(IPIP のばあい)、ip.proto == IPPROTO_NONE の場合は次のセクション(後半)へ、IPPROTO_AH の場合はループをしながら IPPROTO_IP か IPPROTO_NONE が存在しないかを探している。

(000) ldh      [12]                             ; a = eth.type
(001) jeq      #0x800           jt 2    jf 35   ; if (a == ETHERTYPE_IP) goto 2 else goto 35
(002) ldb      [23]                             ; a = ip.proto
(003) ldxb     4*([14]&0xf)                     ; x = ip.ihl*4
; parse offset loop start (a: protocol, x: nexthdr length)
(004) jeq      #0x4             jt 20   jf 5    ; if (a == IPPROTO_IP) goto 20 else goto 5
(005) jeq      #0x3b            jt 20   jf 6    ; if (a == IPPROTO_NONE) goto 20 else goto 6
(006) add      #0                               ;
(007) jeq      #0x33            jt 8    jf 20   ; if (a == IPPROTO_AH) goto 8 else goto 20
(008) txa                                       ; a = x
(009) ldb      [x + 14]                         ; a = ah.nexthdr
(010) st       M[0]                             ; M[0] = a
(011) txa                                       ; a = x
(012) add      #1                               ; a = a+1
(013) tax                                       ; x = a
(014) ldb      [x + 14]                         ; a = ah.len
(015) add      #2                               ; a = a+2
(016) mul      #4                               ; a = a*4
(017) tax                                       ; x = a
(018) ld       M[0]                             ; a = M[0]
(019) ja       4                                ; goto 4
; parse offset loop end

後半

後半は至ってシンプル。 src 10.0.0.1 に当たる評価式になる。 前半部分に関わらず、ip.saddr や、arp.spa、rarp.spa などを評価している。

(020) add      #0
(021) jeq      #0x4             jt 22   jf 35   ; if (a == IPPROTO_IP) goto 22 else goto 35
(022) ldh      [12]                             ; a = eth.type
(023) jeq      #0x800           jt 24   jf 26   ; if (a == ETHERTYPE_IPV4) goto 24 else goto 26
(024) ld       [26]                             ; a = ip.saddr
(025) jeq      #0xa000001       jt 34   jf 26   ; if (a == 10.0.0.1) goto 34 else goto 26
(026) ldh      [12]                             ; a = eth.type
(027) jeq      #0x806           jt 28   jf 30   ; if (a == ETHERTYPE_ARP) goto 28 else goto 30
(028) ld       [28]                             ; a = arp.spa
(029) jeq      #0xa000001       jt 34   jf 30   ; if (a == 10.0.0.1) goto 34 else goto 30
(030) ldh      [12]                             ; a = eth.type
(031) jeq      #0x8035          jt 32   jf 35   ; if (a == ETHERTYPE_RARP) goto 32 else goto 35
(032) ld       [28]                             ; a = rarp.spa
(033) jeq      #0xa000001       jt 34   jf 35   ; if (a == 10.0.0.1) goto 34 else goto 35

ここで重要なのはパケットの固定位置からロードしている、ということ。 例えば、ip.saddr を読み取るときは ld [26] のように、パケット頭から 26byte 目を読み取っている。
これは前半部分の protochain の部分は考慮されずに、評価されていることになる。 そのため、ip protochain ipv4 and src 10.0.0.1 では outer src ip が 10.0.0.1 である、ということを表している。

おまけ

🏷