皆様こんにちは。
TUNA-JP Advent Calendar 11日目の投稿になります。
今回は、Tanzu Community Edition に含まれる CNI の 1つ Antrea が、OpenFlow の仮想スイッチである Open vSwitch に書き込んでいる Flow Table を読んでみようと思います。
雰囲気を知っている人からすると「苦行過ぎない?マゾなの?」と言われる内容です。
導入
どういうことかと申しますと、まず、Antrea のご紹介から。
Antrea は、VMware がオープンソース (OSS) として開発しているコンテナネットワーク (CNI) プラグインです。VMware ベースの Kubernetes プラットフォームである Tanzu だけでなく、主要なマネージド Kubernetes サービス (EKS, AKS, GKE, etc) や、本来比較される製品である OpenShift や Rancher、さらには Windows 上でも (!!) 動くよう、「あらゆる Kubernetes 環境向け」を目指して開発されてます。
この Antrea が、あらゆる Kubernetes 環境をサポートするために、採用している仮想スイッチが、Open vSwitch (OVS) になります。
「最近だと、Windows 上で Linux も動くし、その Linux が提供する iptables でも良くない?」と思われる方もいらっしゃるかと思いますが、Linux もルーターや NAT などの NW 機器の用途向け専用に開発されている訳ではないので、広く普及している iptables と言えど、ネットワーキング用途では限界があります。
そこで、あらゆる環境でも動き、かつ性能を犠牲にすることなく、柔軟なネットワーキングを提供するための Data Plane として、OVS が採用されています。
この OVS についても少し紹介しますと、OpenFlow というプロトコルと切っても切り離せない関係にあります。
OpenFlow は、ネットワーク機器を Control Plane と Data Plane を分離し、Controller から一元的にネットワークを管理しようという、まさに SDN の先駆け的なプロトコルで、OVS は、この OpenFlow プロトコルを実装している仮想スイッチであり、歴史も非常に長く、今でも活発に OSS として開発されています。
OpenFlow の仕組みを少し紹介しますと、コンセプトとしてまず、"こういう条件のパケットが来たら" "こういう処理をしなさい" というルールを記述するという特徴があります。このルールを OpenFlow Controller から OpenFlow Switch に送り、OpenFlow Switch は受け取ったルールに基づいてルーティングをすることで、普通の NW 機器では実現できないようなルーティングを実現したり、仮想スイッチ上で様々な NW 機能 (ルーターや、ファイアウォール、NAT など) を実現できたりします。
この書き込まれるルール 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 の上で動かしたものを使おうと思います。
$ 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
を使ってます。
構築が終わるとこんな感じ。
$ 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 のようですので、こいつを見つけに行きます。
kubectl
を使って、Pod の一覧を取得します。
$ 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 の仮想スイッチがどんな感じに構成されているのか確認します。
$ 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 を取得してみます。
$ 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 をサンプルに、要素を分解してみます。
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 に番号を付与してます。
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
の部分に分かりやすく名前がついているので、これを頼りに読んでいければ良さそうです。
-
"antrea-gw0"
とあるので、外部からの入り口 (GW) となるポートに到着したパケットとなります。load:0x1->NXM_NX_REG0[0..15]
の部分がこれまた特殊ですが、「Register という 0-7番まである一時記憶領域 の 0-15ビット目に 0x1 という値を覚えさせておく」という意味になります。ちなみに、この一時記憶領域は 32ビット長です。その上で、「id=10 の Flow Table へ行け」とあるので、何らかのクラス分けの結果を保持して、次の工程へ進むと読めます。 -
"antrea-tun0"
とあるので、Antrea の Overlay NW 経由で流入したパケットに合致すると読めます。その上で、「一時記憶領域の 0-15ビット目に 0 を、19ビット目を 1にして、今度は id=30 の Flow Table に行け」と読めます。 - 一見「なんだこれ??」という感じですが、どうやら Node に乗っている Pod と繋がっていそうな名前をしています。これらに対しては、0x2 というクラス分けをされて、1. と同じ工程に流されているようです。
- この Flow は、「いずれの Flow にも合致しないパケットはドロップする」というルールが明示的に書かれているようです。処理すべきでないパケットが到着した場合には、早い段階でドロップするようになっているようです。
table=10
table=0 において、「外部から流入したパケット」および「自身の Node に乗っている Pod からのパケット」がやってくる Flow Table です。
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 でカテゴライズできそうです。
- IPv6 パケット、かつ送信元が
fe80
始まり、つまりリンクローカルアドレスのものが合致します。これはそのまま table=21 に進むようです。 - IPv4 パケット、かつ外部からの流入パケットは、そのまま table=29 に進むようです。
- 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 になっていると想像できます。
- IPv4 パケット、かつ送信元が自身の Node に乗っている Pod であるパケットが合致します。"dl_src" (送信元 MAC アドレス) と "nw_src" (送信元 IP アドレス) の条件がついてますが、これも先程の ARP の場合と同様に偽装防止のチェックと思われます。Actions の部分は「table=29 に進め」とあるので、table=29 以降が、実際に IP パケットをどう捌くかの Flow が入っていそうだなぁと読めます。
- デフォルトのルールとして、明示的な Drop が書かれています。
(参考) Flow 3. に対する確認コマンド
$ 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 パケットであること」が入っています。
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
- ARP パケットで、"arp_tpa" (宛先 IP アドレス) が "100.96.1.1" (今回の場合は、Worker Node の "antrea-gw0" でした)で、"arp_op" (ARP オペレーションコード) が 1 (ARP 要求) であるものに合致します。Actions 部分がめっちゃ長いですが、簡単に言うと、受け取ったパケットを改変して、ARP 応答をコネコネしています。(詳細は参考として後述)
- 上記の 1. のルールが IP アドレス狙い撃ちの狭い条件のルールである一方、こちらは「ARP パケットであること」のみの幅広ルールなので、優先度が 190 と少し下げられているところがミソです。しかし、Actions 部分は、
NORMAL
という予約された動作が指定されており、単純に「ARP として通常の NW 機器がとるべき行動を取れ」となっています。なので、table=10 を含めて、「セキュリティ的なチェックも終わっているので、普通に処理してね」ということのようです。 - ここでも、デフォルトのルールとして、明示的な Drop が書かれています。
(参考) 下記読むのが億劫になりそうな Actions ですが、何をやっているのか紐解くと、割と普通なので、読み方を簡単に紹介します。
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
の記述が見受けられます。
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)
-
icmp_type=135
なので、ICMPv6 の Neighbor Solicitation のパケットに合致します。IPv6 側は「NW 機器として通常の動作をしろ」とシンプルなルールになっていますね。 - 上記と同様ですが、
icmp_type=136
なので、Neighbor Advertisement のパケットに合致します。 - IPv6 パケットで、かつ宛先が
ff00
始まりなので、マルチキャストパケットに合致します。ただこちらもactions=NORMAL
なので、非常にシンプルです。 - デフォルトルールで Drop なので、上記に掛からない通信は、デフォルトで許可されていないことがわかります。
全体的に ここまでの範囲では、IPv6 関連の通信はシンプルですね。
table=29
table=10 において、外部から、および自身の Node に乗っている Pod から流入してくる IPv4 パケットに対する Flow Table です。
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)
- 宛先 IP アドレスが
169.254.169.252
と特別な IP アドレス (*4) の場合に、合致するルールのようです。その場合、送信元 IP アドレスを宛先 IP アドレスに移動し、何やらフラグ的に REG0 という一時記憶領域の 18ビット目を 1 にして、table=30 に移動するようです。 - おそらくこの Flow Table で実施したい内容が 1. の特別な IP アドレスの検知のようなので、他のパケットはそのまま table=30 に移動するようです。
(*4) 答え合わせのセクションで参照している Antrea の解説ページを見ると判明します。Pod -> Service とアクセスした際、実はその Pod 自身が宛先だったという場合において、Pod -> Service -> Pod と行って戻ってくる (ヘアピン) トラヒックを実現するために、Service という実体のない (論理的な) コンポーネントからの送信されたパケットとマーキングするための IP アドレスのようです。
table=30
table=29 で前処理的なものがされてから、到達する Flow Table です。
ただ、ここから (もっと前から?) フォースの暗黒面に立ち入っているような感が出てきています...
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 です。
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 条件にすることができます。
-
ct_state=-new+trk
の部分は、-new
新しくない (=確立済みの) コネクション、かつ+trk
追跡中のコネクションを意味します。また、何やらct_mark=0x21
と付いているので、0x21
というマーカーが付けられている IPv4 のコネクションに合致すると読めます。このコネクションのパケットに対しては、REG0 という一時記憶領域の 19ビット目を 1 にして、table=50 に移動するようです。 - 同じ要領で
+inv+trk
とあるので、+inv
不正な (invalid) コネクション、かつ+trk
追跡中の IPv4 パケットは Drop という命令です。 - 先程の 1. の Flow と似てますが、
ct_mark=0x21
というマーカーが付いていないコネクションは、こちらが合致し、同様に table=50 に移動するようです。 - 上記以外、となるので、新しいコネクションなど追跡されていないパケットは、この 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 です。
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 になっている部分は無視 (任意) とする」となります。
図にすると下記の通りです。
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 です。
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 は、これまでに比べるとそんなに難しくなさそうです。
- 先に 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ビット目の正体は、「このヘアピントラヒックに対するフラグか?」と予想が付けられそうです。 - デフォルトの Flow として、「そのまま table=110 へ行け」となっているので、おそらく、この table=106 は 1. のヘアピントラヒックの処理用の Flow Table なのでしょう。
table=110
いよいよ最後の Flow Table です!!
途中飛ばしましたが、それでも長かったですね...涙
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 もサッと眺めた感じでは、これまで出てきた範囲の知識を使って、「雰囲気で」読んで行けそうです。
-
reg0=0x40000/0x40000
とありますが、table=105 で出て来た Mask を使った表記です。0x40000
と 16進数なので、読みづらさはありますが、要は 18ビット目のみに1
が立ってい状況を示しているので、table=106 で立てたフラグを検出している条件になるので、ヘアピントラヒックの処理です。ですので、Actions もactions=IN_PORT
入ってきたポートから出せ、なので予想も合ってそうです。 - ちょっと Actions の
actions=output:NXM_NX_REG1[]
が厄介ですが、これは「reg1
という一時記憶領域に格納されている値が示すポート番号から出せ」という命令になります。reg1
の部分は飛ばしてしまった部分ですが、実は table=80 で、宛先 MAC アドレスから出し先のポート決めており、そのポート番号をreg1
に一時保存している感じです。 - こちらは、Match 条件に
ipv6
とあるので、2. の Flow の IPv6 版です。 - 最後、デフォルトのルールとして Drop の命令が指定されています。ちょっと、ここまですり抜けて来るのに Drop されるパケットなんてあるのか?とは思いますが、おそらく、table=80 で上手く宛先 MAC が見つからなかったけど、色々と条件が揃ってしまうと、ここまで来てしまうんだと思います。
まとめ + 答え合わせ
結果としては、Antrea のもっともコアで、一番濃〜〜い部分を飛ばしてしまった感がありますが、「導入」のセクションを通じて、ざっくりと「へぇ、OpenFlow ってこうなってるのね」と感触を掴んでいただき、「いざ解読」のセクションでは、「何となくこんな雰囲気なんだ」「こんなの読むなんてエグいわぁ」と感じていただければ幸いです。
答え合わせとしては、Antrea の GitHub リポジトリに、各 Flow Table の詳細実装が、英語ですが図付きで解説されているので、こちらを参照いただければと思います。
Antrea OVS Pipeline - antrea-io/antrea - GitHub