Antrea が OVS に書き込んでる Flow Table を読んでみよう (苦行)

皆様こんにちは。
TUNA-JP Advent Calendar 11日目の投稿になります。
今回は、Tanzu Community Edition に含まれる CNI の 1つ Antrea が、OpenFlow の仮想スイッチである Open vSwitch に書き込んでいる Flow Table を読んでみようと思います。
雰囲気を知っている人からすると「苦行過ぎない？マゾなの？」と言われる内容です。

# 導入

どういうことかと申しますと、まず、Antrea のご紹介から。

<img width="400" alt="Antrea" src="https://github.com/antrea-io/antrea/raw/main/docs/assets/logo/antrea_logo.svg">

Antrea は、VMware がオープンソース (OSS) として開発しているコンテナネットワーク (CNI) プラグインです。VMware ベースの Kubernetes プラットフォームである Tanzu だけでなく、主要なマネージド Kubernetes サービス (EKS, AKS, GKE, etc) や、本来比較される製品である OpenShift や Rancher、さらには Windows 上でも (!!) 動くよう、「あらゆる Kubernetes 環境向け」を目指して開発されてます。

この Antrea が、あらゆる Kubernetes 環境をサポートするために、採用している仮想スイッチが、Open vSwitch (OVS) になります。

<img width="150" alt="Open vSwitch" src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/Open_vSwitch_Logo.svg/1200px-Open_vSwitch_Logo.svg.png">

「最近だと、Windows 上で Linux も動くし、その Linux が提供する iptables でも良くない？」と思われる方もいらっしゃるかと思いますが、Linux もルーターや NAT などの NW 機器の用途向け専用に開発されている訳ではないので、広く普及している iptables と言えど、ネットワーキング用途では限界があります。
そこで、あらゆる環境でも動き、かつ性能を犠牲にすることなく、柔軟なネットワーキングを提供するための Data Plane として、OVS が採用されています。

この OVS についても少し紹介しますと、OpenFlow というプロトコルと切っても切り離せない関係にあります。
OpenFlow は、ネットワーク機器を Control Plane と Data Plane を分離し、Controller から一元的にネットワークを管理しようという、まさに SDN の先駆け的なプロトコルで、OVS は、この OpenFlow プロトコルを実装している仮想スイッチであり、歴史も非常に長く、今でも活発に OSS として開発されています。

![OpenFlow 概要図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/f74e0aa9-b5f1-ea2e-8d3d-400935dce08e.png)

OpenFlow の仕組みを少し紹介しますと、コンセプトとしてまず、"こういう条件のパケットが来たら" "こういう処理をしなさい" というルールを記述するという特徴があります。このルールを OpenFlow Controller から OpenFlow Switch に送り、OpenFlow Switch は受け取ったルールに基づいてルーティングをすることで、普通の NW 機器では実現できないようなルーティングを実現したり、仮想スイッチ上で様々な NW 機能 (ルーターや、ファイアウォール、NAT など) を実現できたりします。

![OpenFlow Switch 内部構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/6c47fe6f-4c3a-a749-8f39-c0f475860e67.png)

この書き込まれるルール 1つ 1つを Flow (Flow Entry)、"こういう条件" という部分を Match、"こういう処理" という部分を Actions (*1)、そして、そのルールを溜め込むデータベースのようなものを Flow Table と呼びます。

(*1) 正確には、Actions の部分の構造が、もう少し複雑だったりしますが、ここでは割愛します。

OpenFlow Switch では、この Flow Table を多段に持つことができ、パケットが到着した際は、まず id=0 の Flow Table から順番に評価していきます。そして、条件に合う Flow が見つかれば、そこに記載の Actions を実行、もし Actions の中身に "id=n の Flow Table に行け" であれば、次の Flow Table に行きますし、"パケットを Port XX から出せ" であれば、指定の Port からパケットを転送します。
このような仕組みを OpenFlow では Pipeline と読んでいます。

ちょっと長くなりましたが、今回は、この Flow Table に対して、Antrea が実際に書き込んだルール (Flow) を、ざぁーっくりと読むことで、Antrea の設計を感じてみたいというのが本記事の目的になります。
(そこまでいければ、ですが...)

# 環境

まず今回、実物の Antrea を動かしている環境ですが、せっかく TUNA-JP の Advent Calendar ということで、Tanzu Community Edition (TCE) を Docker の上で動かしたものを使おうと思います。

```bash
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"

$ docker version
Client: Docker Engine - Community
 Version:           20.10.11
 API version:       1.41
 Go version:        go1.16.9
 Git commit:        dea9396
 Built:             Thu Nov 18 00:37:06 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.11
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.9
  Git commit:       847da18
  Built:            Thu Nov 18 00:35:15 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.12
  GitCommit:        7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ tanzu version
version: v0.2.1
buildDate: 2021-09-29
sha: ceaa474
```

ダウンロードした TCE のバイナリとしては `tce-linux-amd64-v0.9.1.tar.gz` を使ってます。
構築が終わるとこんな感じ。

```bash
$ kubectl get nodes
NAME                              STATUS   ROLES                  AGE   VERSION
management-control-plane-5ctvm    Ready    control-plane,master   70m   v1.21.2+vmware.1-360497810732255795
management-md-0-94f7fb956-rrnnb   Ready    <none>                 68m   v1.21.2+vmware.1-360497810732255795
```

# 実践

まず、実際に覗き見にいく Flow Table を書き込んでいる Antrea の Pod を探します。

Antrea のアーキテクチャは下記のようになっており、OVS に OpenFlow プロトコルを使って、Flow を書き込んでいるのは Antrea Agent のようですので、こいつを見つけに行きます。

<img width="700" alt="Antrea アーキテクチャー" src="https://github.com/antrea-io/antrea/blob/main/docs/assets/antrea_overview.svg.png?raw=true">

`kubectl` を使って、Pod の一覧を取得します。

```bash
$ kubectl get pod -A -o wide
NAMESPACE                           NAME                                                             READY   STATUS    RESTARTS   AGE   IP            NODE                              NOMINATED NODE   READINESS GATES
...(snip)...
kube-system                         antrea-agent-gmc8d                                               2/2     Running   0          63m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         antrea-agent-tmzn5                                               2/2     Running   1          63m   172.18.0.5    management-md-0-94f7fb956-rrnnb   <none>           <none>
kube-system                         antrea-controller-69549f95ff-8kwlk                               1/1     Running   0          63m   172.18.0.5    management-md-0-94f7fb956-rrnnb   <none>           <none>
kube-system                         coredns-8dcb5c56b-vvmqc                                          1/1     Running   0          70m   100.96.1.8    management-md-0-94f7fb956-rrnnb   <none>           <none>
kube-system                         coredns-8dcb5c56b-wllvj                                          1/1     Running   0          70m   100.96.1.7    management-md-0-94f7fb956-rrnnb   <none>           <none>
kube-system                         etcd-management-control-plane-5ctvm                              1/1     Running   0          70m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         kube-apiserver-management-control-plane-5ctvm                    1/1     Running   0          70m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         kube-controller-manager-management-control-plane-5ctvm           1/1     Running   0          70m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         kube-proxy-4rv5j                                                 1/1     Running   0          70m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         kube-proxy-qc8h8                                                 1/1     Running   0          69m   172.18.0.5    management-md-0-94f7fb956-rrnnb   <none>           <none>
kube-system                         kube-scheduler-management-control-plane-5ctvm                    1/1     Running   0          70m   172.18.0.4    management-control-plane-5ctvm    <none>           <none>
kube-system                         metrics-server-55fbcdd8f6-qbp5f                                  1/1     Running   1          63m   100.96.1.12   management-md-0-94f7fb956-rrnnb   <none>           <none>
...(snip)...
```

この結果から、ここでは、`antrea-agent-gmc8d` という Pod が、Master Node `management-control-plane-5ctvm` 上で動いており、`antrea-agent-tmzn5` という Pod が、Worker Node `management-md-0-94f7fb956-rrnnb` 上で動いていることが確認できます。
(ただの感覚ですが) Master Node 上の Flow Table の方が簡単そうなので、Master Node 上の OVS にどんなルールが入っているのか、見ていくことにします。

まず、OVS の仮想スイッチがどんな感じに構成されているのか確認します。

```bash
$ kubectl exec -it antrea-agent-gmc8d -n kube-system -- ovs-vsctl show
Defaulted container "antrea-agent" out of: antrea-agent, antrea-ovs, install-cni (init)
6fc0dcda-bd2b-4e63-b39b-0a7cb1b27c8f
    Bridge br-phy
        fail_mode: standalone
        datapath_type: netdev
        Port eth0
            Interface eth0
        Port br-phy
            Interface br-phy
                type: internal
    Bridge br-int
        datapath_type: netdev
        Port antrea-tun0
            Interface antrea-tun0
                type: geneve
                options: {csum="true", key=flow, remote_ip=flow}
        Port capi-con-10ca3e
            Interface capi-con-10ca3e
        Port capi-kub-5e7cce
            Interface capi-kub-5e7cce
        Port antrea-gw0
            Interface antrea-gw0
                type: internal
        Port capi-kub-38a927
            Interface capi-kub-38a927
        Port capi-kub-573f13
            Interface capi-kub-573f13
    ovs_version: "2.14.0"
```

正確なアーキテクチャの深堀りは割愛しますが、`br-phy` (名前からして物理 = 外側) と `br-int` (名前からして内側) の 2つが存在していることが分かります。
この 2つの仮想スイッチのうち、`antrea-tun0` というインタフェースで `type: geneve` とあることから、Antrea が CNI として提供する Geneve プロトコルによる Overlay NW は、br-int 側でごにょごにょしていそう、とあたりをつけます。

そして、実際に書き込まれているルール (Flow) を見るべく、仮想スイッチ `br-int` の Flow Table を取得してみます。

```bash
$ kubectl exec -it antrea-agent-gmc8d -n kube-system -c antrea-ovs -- ovs-ofctl dump-flows br-int --no-stats
 cookie=0x2000000000000, priority=200,in_port="antrea-gw0" actions=load:0x1->NXM_NX_REG0[0..15],resubmit(,10)
 cookie=0x2000000000000, priority=200,in_port="antrea-tun0" actions=load:0->NXM_NX_REG0[0..15],load:0x1->NXM_NX_REG0[19],resubmit(,30)
 cookie=0x2030000000000, priority=190,in_port="capi-kub-38a927" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
 cookie=0x2030000000000, priority=190,in_port="capi-kub-573f13" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
 cookie=0x2030000000000, priority=190,in_port="capi-con-10ca3e" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
 cookie=0x2030000000000, priority=190,in_port="capi-kub-5e7cce" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
 cookie=0x2000000000000, priority=0 actions=drop
 cookie=0x2000000000000, table=10, priority=200,ipv6,ipv6_src=fe80::/10 actions=resubmit(,21)
 cookie=0x2000000000000, table=10, priority=200,ip,in_port="antrea-gw0" actions=resubmit(,29)
 cookie=0x2000000000000, table=10, priority=200,arp,in_port="antrea-gw0",arp_spa=100.96.0.1,arp_sha=62:71:14:27:89:7b actions=resubmit(,20)
 cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-38a927",arp_spa=100.96.0.2,arp_sha=9a:1c:f6:e0:ef:d2 actions=resubmit(,20)
 cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-573f13",arp_spa=100.96.0.3,arp_sha=56:57:02:3a:33:b2 actions=resubmit(,20)
 cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-con-10ca3e",arp_spa=100.96.0.4,arp_sha=d6:e4:80:34:4f:11 actions=resubmit(,20)
 cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-5e7cce",arp_spa=100.96.0.5,arp_sha=c2:6b:be:8d:ee:77 actions=resubmit(,20)
 cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-38a927",dl_src=9a:1c:f6:e0:ef:d2,nw_src=100.96.0.2 actions=resubmit(,29)
 cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-573f13",dl_src=56:57:02:3a:33:b2,nw_src=100.96.0.3 actions=resubmit(,29)
 cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-con-10ca3e",dl_src=d6:e4:80:34:4f:11,nw_src=100.96.0.4 actions=resubmit(,29)
 cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-5e7cce",dl_src=c2:6b:be:8d:ee:77,nw_src=100.96.0.5 actions=resubmit(,29)
 cookie=0x2000000000000, table=10, priority=0 actions=drop
 cookie=0x2020000000000, table=20, priority=200,arp,arp_tpa=100.96.1.1,arp_op=1 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:aa:bb:cc:dd:ee:ff,load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],load:0xaabbccddeeff->NXM_NX_ARP_SHA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],load:0x64600101->NXM_OF_ARP_SPA[],IN_PORT
 cookie=0x2000000000000, table=20, priority=190,arp actions=NORMAL
 cookie=0x2000000000000, table=20, priority=0 actions=drop
 cookie=0x2000000000000, table=21, priority=200,icmp6,icmp_type=135,icmp_code=0 actions=NORMAL
 cookie=0x2000000000000, table=21, priority=200,icmp6,icmp_type=136,icmp_code=0 actions=NORMAL
 cookie=0x2000000000000, table=21, priority=200,ipv6,ipv6_dst=ff00::/8 actions=NORMAL
 cookie=0x2000000000000, table=21, priority=0 actions=resubmit(,29)
 cookie=0x2040000000000, table=29, priority=200,ip,nw_dst=169.254.169.252 actions=move:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:0x1->NXM_NX_REG0[18],resubmit(,30)
 cookie=0x2000000000000, table=29, priority=0 actions=resubmit(,30)
 cookie=0x2000000000000, table=30, priority=200,ip actions=ct(table=31,zone=65520,nat)
 cookie=0x2000000000000, table=30, priority=200,ipv6 actions=ct(table=31,zone=65510,nat)
 cookie=0x2040000000000, table=31, priority=200,ct_state=-new+trk,ct_mark=0x21,ip actions=load:0x1->NXM_NX_REG0[19],resubmit(,50)
 cookie=0x2000000000000, table=31, priority=190,ct_state=+inv+trk,ip actions=drop
 cookie=0x2040000000000, table=31, priority=190,ct_state=-new+trk,ip actions=resubmit(,50)
 cookie=0x2000000000000, table=31, priority=0 actions=resubmit(,40),resubmit(,41)
 cookie=0x2040000000000, table=40, priority=0 actions=load:0x1->NXM_NX_REG4[16..18]
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.64.0.1,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:1
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.69.81.207,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:2
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.70.79.226,tp_dst=8443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:3
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.69.211.60,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:4
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.67.139.185,tp_dst=9402 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:5
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.69.97.198,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:6
 cookie=0x2040000000000, table=41, priority=200,udp,reg4=0x10000/0x70000,nw_dst=100.64.0.10,tp_dst=53 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:7
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.64.0.10,tp_dst=53 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:8
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.64.0.10,tp_dst=9153 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:9
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.69.149.181,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:10
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.70.238.45,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:11
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.69.62.239,tp_dst=8443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:12
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.68.165.182,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:13
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.64.180.199,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:14
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.66.238.184,tp_dst=8443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:15
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.65.178.75,tp_dst=8443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:16
 cookie=0x2040000000000, table=41, priority=200,tcp,reg4=0x10000/0x70000,nw_dst=100.66.37.68,tp_dst=443 actions=load:0x2->NXM_NX_REG4[16..18],load:0x1->NXM_NX_REG0[19],group:17
 cookie=0x2000000000000, table=41, priority=0 actions=resubmit(,42)
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0xac120004,reg4=0x2192b/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=172.18.0.4:6443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600003,reg4=0x224e3/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.0.3:9443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600005,reg4=0x224e3/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.0.5:9443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,udp,reg3=0x64600108,reg4=0x20035/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.8:53),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,udp,reg3=0x64600107,reg4=0x20035/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.7:53),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600108,reg4=0x20035/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.8:53),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600107,reg4=0x20035/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.7:53),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600107,reg4=0x223c1/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.7:9153),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600108,reg4=0x223c1/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.8:9153),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600004,reg4=0x224e3/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.0.4:9443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0xac120004,reg4=0x22774/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=172.18.0.4:10100),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600002,reg4=0x220fb/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.0.2:8443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0xac120005,reg4=0x2286d/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=172.18.0.5:10349),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x6460010a,reg4=0x220fb/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.10:8443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600102,reg4=0x2280a/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.2:10250),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x6460010c,reg4=0x2115b/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.12:4443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x6460010b,reg4=0x220fb/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.11:8443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x6460010b,reg4=0x224e3/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.11:9443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600109,reg4=0x220fb/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.9:8443),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=200,tcp,reg3=0x64600103,reg4=0x224ba/0x7ffff actions=ct(commit,table=45,zone=65520,nat(dst=100.96.1.3:9402),exec(load:0x21->NXM_NX_CT_MARK[]))
 cookie=0x2040000000000, table=42, priority=190,reg4=0x20000/0x70000 actions=load:0x1->NXM_NX_REG4[16..18],resubmit(,41)
 cookie=0x2000000000000, table=42, priority=0 actions=resubmit(,45)
 cookie=0x2000000000000, table=45, priority=64990,ct_state=-new+est,ip actions=resubmit(,61)
 cookie=0x2000000000000, table=45, priority=0 actions=resubmit(,50)
 cookie=0x2000000000000, table=50, priority=210,ct_state=-new+est,ip actions=resubmit(,61)
 cookie=0x2000000000000, table=50, priority=0 actions=resubmit(,60)
 cookie=0x2000000000000, table=60, priority=64990,ct_state=-new+est,ip actions=resubmit(,61)
 cookie=0x2000000000000, table=60, priority=0 actions=resubmit(,61)
 cookie=0x2000000000000, table=61, priority=0 actions=resubmit(,70)
 cookie=0x2000000000000, table=70, priority=210,ct_state=+rpl+trk,ct_mark=0x20,ip actions=mod_dl_dst:62:71:14:27:89:7b,resubmit(,80)
 cookie=0x2000000000000, table=70, priority=200,ip,reg0=0x80000/0x80000,nw_dst=100.96.0.1 actions=mod_dl_dst:62:71:14:27:89:7b,resubmit(,80)
 cookie=0x2030000000000, table=70, priority=200,ip,reg0=0x80000/0x80000,nw_dst=100.96.0.2 actions=mod_dl_src:62:71:14:27:89:7b,mod_dl_dst:9a:1c:f6:e0:ef:d2,resubmit(,71)
 cookie=0x2030000000000, table=70, priority=200,ip,reg0=0x80000/0x80000,nw_dst=100.96.0.3 actions=mod_dl_src:62:71:14:27:89:7b,mod_dl_dst:56:57:02:3a:33:b2,resubmit(,71)
 cookie=0x2030000000000, table=70, priority=200,ip,reg0=0x80000/0x80000,nw_dst=100.96.0.4 actions=mod_dl_src:62:71:14:27:89:7b,mod_dl_dst:d6:e4:80:34:4f:11,resubmit(,71)
 cookie=0x2030000000000, table=70, priority=200,ip,reg0=0x80000/0x80000,nw_dst=100.96.0.5 actions=mod_dl_src:62:71:14:27:89:7b,mod_dl_dst:c2:6b:be:8d:ee:77,resubmit(,71)
 cookie=0x2020000000000, table=70, priority=200,ip,nw_dst=100.96.1.0/24 actions=mod_dl_src:62:71:14:27:89:7b,mod_dl_dst:aa:bb:cc:dd:ee:ff,load:0xac120005->NXM_NX_TUN_IPV4_DST[],resubmit(,71)
 cookie=0x2000000000000, table=70, priority=0 actions=resubmit(,80)
 cookie=0x2000000000000, table=71, priority=210,ip,reg0=0x1/0xffff actions=resubmit(,80)
 cookie=0x2000000000000, table=71, priority=200,ip actions=dec_ttl,resubmit(,80)
 cookie=0x2000000000000, table=71, priority=0 actions=resubmit(,80)
 cookie=0x2000000000000, table=80, priority=200,dl_dst=62:71:14:27:89:7b actions=load:0x2->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,105)
 cookie=0x2000000000000, table=80, priority=200,dl_dst=aa:bb:cc:dd:ee:ff actions=load:0x1->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,105)
 cookie=0x2030000000000, table=80, priority=200,dl_dst=9a:1c:f6:e0:ef:d2 actions=load:0x3->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,85)
 cookie=0x2030000000000, table=80, priority=200,dl_dst=56:57:02:3a:33:b2 actions=load:0x4->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,85)
 cookie=0x2030000000000, table=80, priority=200,dl_dst=d6:e4:80:34:4f:11 actions=load:0x5->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,85)
 cookie=0x2030000000000, table=80, priority=200,dl_dst=c2:6b:be:8d:ee:77 actions=load:0x6->NXM_NX_REG1[],load:0x1->NXM_NX_REG0[16],resubmit(,85)
 cookie=0x2000000000000, table=80, priority=0 actions=resubmit(,105)
 cookie=0x2000000000000, table=85, priority=64990,ct_state=-new+est,ip actions=resubmit(,101)
 cookie=0x2000000000000, table=85, priority=0 actions=resubmit(,90)
 cookie=0x2000000000000, table=90, priority=210,ct_state=-new+est,ip actions=resubmit(,101)
 cookie=0x2000000000000, table=90, priority=210,ip,nw_src=100.96.0.1 actions=resubmit(,105)
 cookie=0x2000000000000, table=90, priority=0 actions=resubmit(,100)
 cookie=0x2000000000000, table=100, priority=64990,ct_state=-new+est,ip actions=resubmit(,101)
 cookie=0x2000000000000, table=100, priority=0 actions=resubmit(,101)
 cookie=0x2000000000000, table=101, priority=0 actions=resubmit(,105)
 cookie=0x2000000000000, table=105, priority=200,ct_state=+new+trk,ip,reg0=0x1/0xffff actions=ct(commit,table=106,zone=65520,exec(load:0x20->NXM_NX_CT_MARK[]))
 cookie=0x2000000000000, table=105, priority=190,ct_state=+new+trk,ip actions=ct(commit,table=106,zone=65520)
 cookie=0x2000000000000, table=105, priority=190,ct_state=+trk,ct_mark=0x21,ip,reg4=0x20000/0x70000 actions=resubmit(,106)
 cookie=0x2000000000000, table=105, priority=190,ct_state=+trk,ct_mark=0x21,ipv6,reg4=0x20000/0x70000 actions=resubmit(,106)
 cookie=0x2000000000000, table=105, priority=0 actions=resubmit(,106)
 cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.3,nw_dst=100.96.0.3 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
 cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.5,nw_dst=100.96.0.5 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
 cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.4,nw_dst=100.96.0.4 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
 cookie=0x2040000000000, table=106, priority=200,ip,nw_src=172.18.0.4,nw_dst=172.18.0.4 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
 cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.2,nw_dst=100.96.0.2 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
 cookie=0x2000000000000, table=106, priority=0 actions=resubmit(,110)
 cookie=0x2040000000000, table=110, priority=210,reg0=0x40000/0x40000 actions=IN_PORT
 cookie=0x2000000000000, table=110, priority=200,ip,reg0=0x10000/0x10000 actions=output:NXM_NX_REG1[]
 cookie=0x2000000000000, table=110, priority=200,ipv6,reg0=0x10000/0x10000 actions=output:NXM_NX_REG1[]
 cookie=0x2000000000000, table=110, priority=0 actions=drop
```

おおっとー...トラブル発生です。
ちょっと挫折しそうな行数 (120行超え!!) が表示されてしましました。これは、一筋縄ではいかないので、各 Flow の構造を分解しつつ、カテゴリ分けした上で、読み込んでいく必要がありそうです。

# 解読方針

まず、一つ下記の Flow をサンプルに、要素を分解してみます。

![サンプル Flow](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/9545a25f-c612-ed6d-a876-74b9c1d2a35e.png)

`cookie=0x2000000000000` の部分は、OpenFlow Controller (この場合 Antrea) が、各 Flow に割り当てる値で、Controller 自身が Flow を便利に管理するためのフィールドです。詳しくは触れませんが、うまく使うことで、複数の Flow を一気に変更したり、削除したりすることができます。ただ、あくまで Controller が Flow の管理を便利にするためのフィールドなので「Flow Table を読んでみよう」という意味では、一旦、重要度は下がります。

`table=10` の部分は、Flow がインストールされている Flow Table の番号 (id) を表します。つまり、この Flow は id=10 の Flow Table にインストールされています。OpenFlow では、パケットを受け取ると、id=0 の Flow Table から順番に評価していく仕組みでした。なので、Flow Table を読んでいく上では、同じ id を持つ Flow Table で、まずカテゴライズするのが重要です。

`priority=200,ip,in_port="antrea-gw0"` の部分は、"こういう条件のパケットが来たら" を表す部分で、Match と呼ばれるフィールドです。(*2) 実際には、かなりの種類があるのですが、全ては触れられませんが、この例では、「IPv4 パケット種別で、"antrea-gw0" というポート (インタフェース) から入って来たパケットに合致せよ。その上で、このルールの優先度は 200 である」 と読むことができます。

(*2) priority は、正確には Match とは別の概念です。Flow を評価する際の優先度を表し、仮に複数の Flow にパケットが合致してしまう場合には、priority が高い方が選択されます。

`actions=resubmit(,29)` の部分は、"こういう処理をしなさい" を合わす部分で、Actions と呼ばれるフィールドです。Match と同様にかなりの種類があります。この例で使われている `resubmit` は少し特殊で、OpenFlow 標準ではなく、OVS が独自に実装している拡張 Action になりますが、OVS の Flow を読む時に良く目にします。`resubmit=(,n)` と記載した際の意味としては、「id=n の Flow Table に行け」となります。

このように Flow は、まず、Flow Table の id でカテゴライズして、その単位で、priority 順に並べ、「こんなパケットが来たら、どれに合致しそうかな？」と読んでいけば、読み解くことができそうです。

# いざ解読

## table=0

明示的に `table=0` とついていませんが、下記の部分が id=0、つまり最初に評価される Flow Table になります。
※以降、便宜上、各 Flow に番号を付与してます。

```text
1. cookie=0x2000000000000, priority=200,in_port="antrea-gw0" actions=load:0x1->NXM_NX_REG0[0..15],resubmit(,10)
2. cookie=0x2000000000000, priority=200,in_port="antrea-tun0" actions=load:0->NXM_NX_REG0[0..15],load:0x1->NXM_NX_REG0[19],resubmit(,30)
3. cookie=0x2030000000000, priority=190,in_port="capi-kub-38a927" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
   cookie=0x2030000000000, priority=190,in_port="capi-kub-573f13" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
   cookie=0x2030000000000, priority=190,in_port="capi-con-10ca3e" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
   cookie=0x2030000000000, priority=190,in_port="capi-kub-5e7cce" actions=load:0x2->NXM_NX_REG0[0..15],resubmit(,10)
4. cookie=0x2000000000000, priority=0 actions=drop
```

出力としては、priority 順に既に並んでいるので、上から順にキーとなる `in_port` の部分に分かりやすく名前がついているので、これを頼りに読んでいければ良さそうです。

1. `"antrea-gw0"` とあるので、外部からの入り口 (GW) となるポートに到着したパケットとなります。`load:0x1->NXM_NX_REG0[0..15]` の部分がこれまた特殊ですが、「Register という 0-7番まである一時記憶領域 の 0-15ビット目に 0x1 という値を覚えさせておく」という意味になります。ちなみに、この一時記憶領域は 32ビット長です。その上で、「id=10 の Flow Table へ行け」とあるので、何らかのクラス分けの結果を保持して、次の工程へ進むと読めます。
2. `"antrea-tun0"` とあるので、Antrea の Overlay NW 経由で流入したパケットに合致すると読めます。その上で、「一時記憶領域の 0-15ビット目に 0 を、19ビット目を 1にして、今度は id=30 の Flow Table に行け」と読めます。
3. 一見「なんだこれ？？」という感じですが、どうやら Node に乗っている Pod と繋がっていそうな名前をしています。これらに対しては、0x2 というクラス分けをされて、1. と同じ工程に流されているようです。
4. この Flow は、「いずれの Flow にも合致しないパケットはドロップする」というルールが明示的に書かれているようです。処理すべきでないパケットが到着した場合には、早い段階でドロップするようになっているようです。

## table=10

table=0 において、「外部から流入したパケット」および「自身の Node に乗っている Pod からのパケット」がやってくる Flow Table です。

```text
1. cookie=0x2000000000000, table=10, priority=200,ipv6,ipv6_src=fe80::/10 actions=resubmit(,21)
2. cookie=0x2000000000000, table=10, priority=200,ip,in_port="antrea-gw0" actions=resubmit(,29)
3. cookie=0x2000000000000, table=10, priority=200,arp,in_port="antrea-gw0",arp_spa=100.96.0.1,arp_sha=62:71:14:27:89:7b actions=resubmit(,20)
   cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-38a927",arp_spa=100.96.0.2,arp_sha=9a:1c:f6:e0:ef:d2 actions=resubmit(,20)
   cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-573f13",arp_spa=100.96.0.3,arp_sha=56:57:02:3a:33:b2 actions=resubmit(,20)
   cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-con-10ca3e",arp_spa=100.96.0.4,arp_sha=d6:e4:80:34:4f:11 actions=resubmit(,20)
   cookie=0x2030000000000, table=10, priority=200,arp,in_port="capi-kub-5e7cce",arp_spa=100.96.0.5,arp_sha=c2:6b:be:8d:ee:77 actions=resubmit(,20)
4. cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-38a927",dl_src=9a:1c:f6:e0:ef:d2,nw_src=100.96.0.2 actions=resubmit(,29)
   cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-573f13",dl_src=56:57:02:3a:33:b2,nw_src=100.96.0.3 actions=resubmit(,29)
   cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-con-10ca3e",dl_src=d6:e4:80:34:4f:11,nw_src=100.96.0.4 actions=resubmit(,29)
   cookie=0x2030000000000, table=10, priority=200,ip,in_port="capi-kub-5e7cce",dl_src=c2:6b:be:8d:ee:77,nw_src=100.96.0.5 actions=resubmit(,29)
5. cookie=0x2000000000000, table=10, priority=0 actions=drop
```

最後の 6. の Flow 以外全て同じ priority なので、順番に読んでいきますが、大きくはパケット種別が、IPv6、IPv4、ARP でカテゴライズできそうです。

1. IPv6 パケット、かつ送信元が `fe80` 始まり、つまりリンクローカルアドレスのものが合致します。これはそのまま table=21 に進むようです。
2. IPv4 パケット、かつ外部からの流入パケットは、そのまま table=29 に進むようです。
3. ARP パケットで、外部からの流入または自身の Node に乗っている Pod が送信したパケットが合致します。"arp_spa" (ARP 送信元 IP アドレス) と "arp_sha" (ARP 送信元 MAC アドレス) の条件がついてますが、「ちゃんと ARP の送信元は正しいよね？」とセキュリティ的な確認 (偽装防止) している部分のようです。実際に、"antrea-gw0" の Flow について、Master Node の中で ip コマンドを叩いて確認 (実行結果は参考として後述) してみると、"antrea-gw0" の IP と MAC がちゃんと設定されていることが確認できました。 Actions 部分は「table=20 に進め」とあるので、おそらく table=20 が、ARP を処理する Flow Table になっていると想像できます。
4. IPv4 パケット、かつ送信元が自身の Node に乗っている Pod であるパケットが合致します。"dl_src" (送信元 MAC アドレス) と "nw_src" (送信元 IP アドレス) の条件がついてますが、これも先程の ARP の場合と同様に偽装防止のチェックと思われます。Actions の部分は「table=29 に進め」とあるので、table=29 以降が、実際に IP パケットをどう捌くかの Flow が入っていそうだなぁと読めます。
5. デフォルトのルールとして、明示的な Drop が書かれています。

(参考) Flow 3. に対する確認コマンド

```bash
$ docker exec -it management-control-plane-5ctvm ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ovs-netdev: <BROADCAST,MULTICAST,PROMISC> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 3e:22:32:db:f7:11 brd ff:ff:ff:ff:ff:ff
4: antrea-gw0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1450 qdisc fq_codel state UP group default qlen 1000
    link/ether 62:71:14:27:89:7b brd ff:ff:ff:ff:ff:ff
    inet 100.96.0.1/24 brd 100.96.0.255 scope global antrea-gw0
       valid_lft forever preferred_lft forever
```

## table=20

table=10 のルールを読んだ範囲だと、おそらく ARP 関連だろうと想像される Flow Table です。
なので、Match 部分には、デフォルトのルールを除いて、全て「ARP パケットであること」が入っています。

```text
1. cookie=0x2020000000000, table=20, priority=200,arp,arp_tpa=100.96.1.1,arp_op=1 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:aa:bb:cc:dd:ee:ff,load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],load:0xaabbccddeeff->NXM_NX_ARP_SHA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],load:0x64600101->NXM_OF_ARP_SPA[],IN_PORT
2. cookie=0x2000000000000, table=20, priority=190,arp actions=NORMAL
3. cookie=0x2000000000000, table=20, priority=0 actions=drop
```

1. ARP パケットで、"arp_tpa" (宛先 IP アドレス) が "100.96.1.1" (今回の場合は、Worker Node の "antrea-gw0" でした)で、"arp_op" (ARP オペレーションコード) が 1 (ARP 要求) であるものに合致します。Actions 部分がめっちゃ長いですが、簡単に言うと、受け取ったパケットを改変して、ARP 応答をコネコネしています。(詳細は参考として後述)
2. 上記の 1. のルールが IP アドレス狙い撃ちの狭い条件のルールである一方、こちらは「ARP パケットであること」のみの幅広ルールなので、優先度が 190 と少し下げられているところがミソです。しかし、Actions 部分は、`NORMAL` という予約された動作が指定されており、単純に「ARP として通常の NW 機器がとるべき行動を取れ」となっています。なので、table=10 を含めて、「セキュリティ的なチェックも終わっているので、普通に処理してね」ということのようです。
3. ここでも、デフォルトのルールとして、明示的な Drop が書かれています。

(参考) 下記読むのが億劫になりそうな Actions ですが、何をやっているのか紐解くと、割と普通なので、読み方を簡単に紹介します。

```text
actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:aa:bb:cc:dd:ee:ff,load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],load:0xaabbccddeeff->NXM_NX_ARP_SHA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],load:0x64600101->NXM_OF_ARP_SPA[],IN_PORT
```

- `move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]` を含め、`NXM_` という接頭語がついている部分がありますが、これは Nicira Extended Match の意味で、OVS 独自の Match (パケットに対する条件) 実装であることを意味しています。(*3) ここでは、「送信元 MAC アドレス ("ETH_SRC") を、宛先 MAC アドレス ("ETH_DST") に移動しろ」という命令になります。

(*3) Nicira 社は、OpenFlow の父 Martin Casado 氏が、立ち上げた会社で、OVS も初めは Nicira 社が開発を始めたものなので、OVS の実装の至る所で Nicira 社の名前を見たりします。ただ、`OF_ETH_SRC` は、送信元 MAC アドレスのことで、「基本的なヘッダ情報なんだから、普通に実装するでしょ」と思われるかと思います。はい、実際にその通りで、MAC アドレスに関する Match は、OpenFlow プロトコルの標準仕様にもあります。しかし、OpenFlow プロトコルの標準が定まるよりも先に、OVS は実装が進んでいたので、OVS を触っているとそのあたりの名残りがちらほらあったりします。

- `mod_dl_src:aa:bb:cc:dd:ee:ff` は、さっきの命令で、送信元 MAC アドレスを移動してしまったので、そこにダミーの MAC アドレスを入れ込んでます。(サンプルコードみたいなアドレスですよねぇ...)
- `load:0x2->NXM_OF_ARP_OP[]` は、ARP オペレーションコードを 2 (ARP 応答) に書き換える命令です。こちらの ARP_OP は、OpenFlow 標準 1.0 では、そもそもフィールドとして定義されていなかったので、OVS として独自実装し、その後 OpenFlow 標準で追加された以降も、残っているようです。
- `move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[]` は、最初の命令と同様の構造をしていますが、ARP パケットの中身に対して、送信元 MAC アドレスを、宛先 MAC アドレスに移動しています。
- `load:0xaabbccddeeff->NXM_NX_ARP_SHA[]` も、同様に、ARP パケットの中身に対して、移動してしまった送信元 MAC アドレスにダミーの MAC アドレスを入れ込んでいます。
- `move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]` も同様ですが、今度は IP アドレスに関して、ARP 要求元の IP アドレスを、ARP 応答を返す先の IP アドレスに移動しています。
- `load:0x64600101->NXM_OF_ARP_SPA[]` は、入れ込もうとしている値 `0x64600101` が 16進数表現なのでわかりにくいですが、実は IP アドレス `100.96.1.1` です。`0x64600101` -> `0x64 0x60 0x01 0x01` -> `100 96 1 1` -> `100.96.1.1` と解読できます。
- `IN_PORT` が最後の命令ですが、`output:IN_PORT` が短縮されており、「入ってきたポートからパケットを出せ」になります。OpenFlow では、NW ループの防止のため、「全てのポートからパケットを出せ = フラッディングしろ」という命令をした場合、パケットが最初に入って来たポートには出さないという暗黙的なルールが (スイッチによって実装状況がまちまちでしたが...) あったりします。そのため、明示的に `IN_PORT` と指定しない限り、入ってきたポートから返せなかったりします。(事実この仕様に悩まされた過去が...)

## table=21

table=10 のルールを読んだ範囲だと、おそらく IPv6 関連だろうと想像される Flow Table です。
なので、Match 部分には、`ipv6` や `icmpv6` の記述が見受けられます。

```text
1. cookie=0x2000000000000, table=21, priority=200,icmp6,icmp_type=135,icmp_code=0 actions=NORMAL
2. cookie=0x2000000000000, table=21, priority=200,icmp6,icmp_type=136,icmp_code=0 actions=NORMAL
3. cookie=0x2000000000000, table=21, priority=200,ipv6,ipv6_dst=ff00::/8 actions=NORMAL
4. cookie=0x2000000000000, table=21, priority=0 actions=resubmit(,29)
```

1. `icmp_type=135` なので、ICMPv6 の Neighbor Solicitation のパケットに合致します。IPv6 側は「NW 機器として通常の動作をしろ」とシンプルなルールになっていますね。
2. 上記と同様ですが、`icmp_type=136` なので、Neighbor Advertisement のパケットに合致します。
3. IPv6 パケットで、かつ宛先が `ff00` 始まりなので、マルチキャストパケットに合致します。ただこちらも `actions=NORMAL` なので、非常にシンプルです。
4. デフォルトルールで Drop なので、上記に掛からない通信は、デフォルトで許可されていないことがわかります。

全体的に ここまでの範囲では、IPv6 関連の通信はシンプルですね。

## table=29

table=10 において、外部から、および自身の Node に乗っている Pod から流入してくる IPv4 パケットに対する Flow Table です。

```text
1. cookie=0x2040000000000, table=29, priority=200,ip,nw_dst=169.254.169.252 actions=move:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:0x1->NXM_NX_REG0[18],resubmit(,30)
2. cookie=0x2000000000000, table=29, priority=0 actions=resubmit(,30)
```

1. 宛先 IP アドレスが `169.254.169.252` と特別な IP アドレス (*4) の場合に、合致するルールのようです。その場合、送信元 IP アドレスを宛先 IP アドレスに移動し、何やらフラグ的に REG0 という一時記憶領域の 18ビット目を 1 にして、table=30 に移動するようです。
2. おそらくこの Flow Table で実施したい内容が 1. の特別な IP アドレスの検知のようなので、他のパケットはそのまま table=30 に移動するようです。

(*4) 答え合わせのセクションで参照している Antrea の解説ページを見ると判明します。Pod -> Service とアクセスした際、実はその Pod 自身が宛先だったという場合において、Pod -> Service -> Pod と行って戻ってくる (ヘアピン) トラヒックを実現するために、Service という実体のない (論理的な) コンポーネントからの送信されたパケットとマーキングするための IP アドレスのようです。

## table=30

table=29 で前処理的なものがされてから、到達する Flow Table です。
ただ、ここから (もっと前から?) フォースの暗黒面に立ち入っているような感が出てきています...

```text
1. cookie=0x2000000000000, table=30, priority=200,ip actions=ct(table=31,zone=65520,nat)
2. cookie=0x2000000000000, table=30, priority=200,ipv6 actions=ct(table=31,zone=65510,nat)
```

まず、Actions に `ct` というのが指定されていますが、これは conntrack (Connection Tracking) の意味です。TCP や UDP などのコネクションベースのプロトコルに対して、Linux で実装されているコネクション追跡機能を呼び出している命令になります。例えば、IP アドレスが xxx.xxx.xxx.xxx で Port n 番のコネクションについて、そのコネクション確立状態、例えば「確立しようとするパケットが到着したところだ」「確率済みだ」「切断パケットが到着した」などを追跡してくれる機能になります。
詳細には触れませんが、Flow 1. が IPv4、Flow 2. が IPv6 のパケットに対して、それぞれ別のコネクション管理テーブル (zone) の id を振った上で、追跡を開始し、table=31 へ進めという記載になります。

## table=31

table=30 にて、コネクション追跡を有効にしたので、その上で、コネクションの確立状況に応じて、処理を実行している Flow Table です。

```text
1. cookie=0x2040000000000, table=31, priority=200,ct_state=-new+trk,ct_mark=0x21,ip actions=load:0x1->NXM_NX_REG0[19],resubmit(,50)
2. cookie=0x2000000000000, table=31, priority=190,ct_state=+inv+trk,ip actions=drop
3. cookie=0x2040000000000, table=31, priority=190,ct_state=-new+trk,ip actions=resubmit(,50)
4. cookie=0x2000000000000, table=31, priority=0 actions=resubmit(,40),resubmit(,41)
```

`ct_state` とある部分が、table=30 でコネクション追跡を有効化したからこそ使える「コネクション確立状態」を表している部分です。"-" が頭についている状態は「そのフラグが立っていないコネクション」、"+" が頭についている状態が「そのフラグが立っているコネクション」に合致します。
また `ct_mark` という部分もありますが、このコネクション追跡機能には、そのコネクションにマーカー (フラグ的な) を付けることができ、その値を Match 条件にすることができます。

1. `ct_state=-new+trk`の部分は、`-new` 新しくない (=確立済みの) コネクション、かつ `+trk` 追跡中のコネクションを意味します。また、何やら `ct_mark=0x21` と付いているので、`0x21` というマーカーが付けられている IPv4 のコネクションに合致すると読めます。このコネクションのパケットに対しては、REG0 という一時記憶領域の 19ビット目を 1 にして、table=50 に移動するようです。
2. 同じ要領で `+inv+trk` とあるので、`+inv` 不正な (invalid) コネクション、かつ `+trk` 追跡中の IPv4 パケットは Drop という命令です。
3. 先程の 1. の Flow と似てますが、`ct_mark=0x21` というマーカーが付いていないコネクションは、こちらが合致し、同様に table=50 に移動するようです。
4. 上記以外、となるので、新しいコネクションなど追跡されていないパケットは、この Flow が合致します。ちなみに Actions の部分で「table=40 に行け」と「table=41 に行け」と 2つの命令がありますが、これは並列して良い処理の場合に見受けられます。本来、OpenFlow 標準では Pipeline は直列処理で、並列処理できないのですが、「それじゃあ、遅いじゃん」というのもあるので、こんな飛び道具が OVS では実装されているようです。

以降 table=40,41 から table=105 までが、このコネクション追跡機能を使い、Antrea がいかに CNI としてのネットワーキング機能を提供しつつ、セキュアな通信を実現しているか！？なのですが、いよいよ本格的に暗黒面に突入してきており、いくら Deep Dive に興味ある方でも「読んでられるかぁ！」となりそうな (だし、私も力尽きてきた...) ところかと思います。
なので、最後 table=105 で、入ってきたパケットに対するコネクション追跡機能を閉じて、最終的に転送するところで終わりにしようと思います。

## table=105

この Flow Table まで来る部分は、割愛してしまいましたが、ここまでの Flow Table にて、「本当に通して良い通信なのか？」をコネクションレベルで追跡し、検査した結果、到達する Flow Table です。

```text
1. cookie=0x2000000000000, table=105, priority=200,ct_state=+new+trk,ip,reg0=0x1/0xffff actions=ct(commit,table=106,zone=65520,exec(load:0x20->NXM_NX_CT_MARK[]))
2. cookie=0x2000000000000, table=105, priority=190,ct_state=+new+trk,ip actions=ct(commit,table=106,zone=65520)
3. cookie=0x2000000000000, table=105, priority=190,ct_state=+trk,ct_mark=0x21,ip,reg4=0x20000/0x70000 actions=resubmit(,106)
4. cookie=0x2000000000000, table=105, priority=190,ct_state=+trk,ct_mark=0x21,ipv6,reg4=0x20000/0x70000 actions=resubmit(,106)
5. cookie=0x2000000000000, table=105, priority=0 actions=resubmit(,106)
```

多くの Flow 使われている命令がほぼ共通なので、ここでは、1. の Flow に着目します。

まず、`ct_state=+new+trk` の部分は、これまでにも登場して来ており、`+new` 新しいコネクション、かつ `+trk` 追跡中のコネクションを意味するな、`ip` は IPv4 パケットを意味するな、と読めます。

一方で、`reg0=0x1/0xffff` となっているような部分は、初めて出てくる表記かと思います。まず簡単に分解すると、`reg0` と言う table=0 でも出てきた「一次記憶領域」に対するパケット条件で、`0x1` と言う値 (Value) と比較し、その時の Mask は `0xffff` という命令になっています。ここで「Mask とはなんぞや」になるのですが、これはパケット条件として指定する値に対して、完全一致ではなく、部分一致を表現するための仕組みになります。意味合いとしては、IP アドレスの CIDR 表記のようなイメージで、Value と Mask を 2進数 (ビット表現) した際、「Mask の 1 になっている部分を比較し、0 になっている部分は無視 (任意) とする」となります。

図にすると下記の通りです。

![Match Mask の仕組み](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/25e4bf55-2602-cfd7-0f42-ff3d57fa169f.png)

NW 機器なので、流れてくるバイト列を、ビットレベルでの比較なので、分かりやすく正規表現とかではなく、とっつきづらさはありますが、ここでは雰囲気を感じていただければ OK です。

よって、1. の Flow において、Mask `0xffff` となっている部分は、「下位 16ビットを比較の対象とし、そこだけ見た場合に、`reg0` という一次記憶領域が `0x1 = 1` であれば、条件に合致する」という意味になります。

今度、Actions の部分も、これまた長いですが、先ほどから登場している `ct` コネクション追跡機能に関連しているようです。
`ct` の引数のように渡されている部分は、`commit` から始まっていますが、これは、今処理しているパケットに対するコネクション追跡機能を、一旦コミットする (処理を完了にする) という意味があります。コネクション追跡機能では、パケットを見ながら、`ct_mark` など、コネクションに対してマーキングできたりしましたが、これを SQL DB におけるコミット処理のように、確定させる命令です。
`zone=65520` の部分は、table=30 で出て来ましたが、IPv4 パケットのコネクションを追跡するための管理テーブルの番号でした。
`exec(load:0x20->NXM_NX_CT_MARK[])` の部分は、まさに `ct_mark` に `0x20` という値でマーキングしている処理になります。
これらが終わったら、最後 `table=106` とあるので、次は table=106 に進め、となっています。

## table=106

最後の一歩手前の Flow Table です。

```text
1. cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.3,nw_dst=100.96.0.3 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
   cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.5,nw_dst=100.96.0.5 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
   cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.4,nw_dst=100.96.0.4 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
   cookie=0x2040000000000, table=106, priority=200,ip,nw_src=172.18.0.4,nw_dst=172.18.0.4 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
   cookie=0x2040000000000, table=106, priority=200,ip,nw_src=100.96.0.2,nw_dst=100.96.0.2 actions=mod_nw_src:169.254.169.252,load:0x1->NXM_NX_REG0[18],resubmit(,110)
2. cookie=0x2000000000000, table=106, priority=0 actions=resubmit(,110)
```

とはいえ、この Flow Table は、これまでに比べるとそんなに難しくなさそうです。

1. 先に Actions の部分を見てみると、table=29 で登場していた特別な IP アドレス `169.254.169.252` が、`mod_nw_src` 送信元 IP アドレスに設定されています。これは、Pod から送信されたパケットの行き先が自分自身に戻ってくる (ヘアピン) トラヒックに設定される動作でした。なので、Match 部分で、宛先 IP アドレスも、送信元 IP アドレスも、どちらも同じ Pod の IP アドレス時に」という条件になっています。ここまで来ると、`load:0x1->NXM_NX_REG0[18]` という命令で `reg0` という一時記憶領域の 18ビット目の正体は、「このヘアピントラヒックに対するフラグか？」と予想が付けられそうです。
2. デフォルトの Flow として、「そのまま table=110 へ行け」となっているので、おそらく、この table=106 は 1. のヘアピントラヒックの処理用の Flow Table なのでしょう。

## table=110

いよいよ最後の Flow Table です！！
途中飛ばしましたが、それでも長かったですね...涙

```text
1. cookie=0x2040000000000, table=110, priority=210,reg0=0x40000/0x40000 actions=IN_PORT
2. cookie=0x2000000000000, table=110, priority=200,ip,reg0=0x10000/0x10000 actions=output:NXM_NX_REG1[]
3. cookie=0x2000000000000, table=110, priority=200,ipv6,reg0=0x10000/0x10000 actions=output:NXM_NX_REG1[]
4. cookie=0x2000000000000, table=110, priority=0 actions=drop
```

この Flow Table もサッと眺めた感じでは、これまで出てきた範囲の知識を使って、「雰囲気で」読んで行けそうです。

1. `reg0=0x40000/0x40000` とありますが、table=105 で出て来た Mask を使った表記です。`0x40000` と 16進数なので、読みづらさはありますが、要は 18ビット目のみに `1` が立ってい状況を示しているので、table=106 で立てたフラグを検出している条件になるので、ヘアピントラヒックの処理です。ですので、Actions も `actions=IN_PORT` 入ってきたポートから出せ、なので予想も合ってそうです。
2. ちょっと Actions の `actions=output:NXM_NX_REG1[]` が厄介ですが、これは「`reg1` という一時記憶領域に格納されている値が示すポート番号から出せ」という命令になります。`reg1` の部分は飛ばしてしまった部分ですが、実は table=80 で、宛先 MAC アドレスから出し先のポート決めており、そのポート番号を `reg1` に一時保存している感じです。
3. こちらは、Match 条件に `ipv6` とあるので、2. の Flow の IPv6 版です。
4. 最後、デフォルトのルールとして Drop の命令が指定されています。ちょっと、ここまですり抜けて来るのに Drop されるパケットなんてあるのか？とは思いますが、おそらく、table=80 で上手く宛先 MAC が見つからなかったけど、色々と条件が揃ってしまうと、ここまで来てしまうんだと思います。

# まとめ + 答え合わせ

結果としては、Antrea のもっともコアで、一番濃〜〜い部分を飛ばしてしまった感がありますが、「導入」のセクションを通じて、ざっくりと「へぇ、OpenFlow ってこうなってるのね」と感触を掴んでいただき、「いざ解読」のセクションでは、「何となくこんな雰囲気なんだ」「こんなの読むなんてエグいわぁ」と感じていただければ幸いです。

答え合わせとしては、Antrea の GitHub リポジトリに、各 Flow Table の詳細実装が、英語ですが図付きで解説されているので、こちらを参照いただければと思います。

[Antrea OVS Pipeline - antrea-io/antrea - GitHub](https://github.com/antrea-io/antrea/blob/main/docs/design/ovs-pipeline.md)

<img width="700" alt="Antrea OVS Pipeline" src="https://raw.githubusercontent.com/antrea-io/antrea/main/docs/assets/ovs-pipeline-antrea-proxy.svg">