---
# System prepended metadata

title: 洞悉 Cilium 如何傳遞 K8s Pod 網路封包

---

# 洞悉 Cilium 如何傳遞 K8s Pod 網路封包

## 1. Preface

本篇文章會在 CNI 使用 Cilium 的 Kubernetes 環境驗證，pod 跟 pod 之間互相溝通時，在同節點或在不同節點，網路的封包會如何傳遞，並搭配 tcpdump + wireshark 分析封包。

## 2. 環境資訊

1. K8s 節點及版本資訊

    ```
    kubectl get nodes -o wide
    ```

    執行結果：

    ```
    NAME                 STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
    kubeadm-c1.bbg.com   Ready    control-plane   27d   v1.34.1   172.20.6.11   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    kubeadm-c2.bbg.com   Ready    control-plane   27d   v1.34.1   172.20.6.12   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    kubeadm-c3.bbg.com   Ready    control-plane   26d   v1.34.1   172.20.6.13   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    kubeadm-w1.bbg.com   Ready    worker          26d   v1.34.1   172.20.6.14   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    kubeadm-w2.bbg.com   Ready    worker          26d   v1.34.1   172.20.6.15   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    kubeadm-w3.bbg.com   Ready    worker          26d   v1.34.1   172.20.6.16   <none>        Ubuntu 24.04.3 LTS   6.8.0-85-generic   containerd://2.1.4
    ```

2. cilium 設定資訊

    ```
    helm -n kube-system get values cilium
    ```

    執行結果：

    ```
    USER-SUPPLIED VALUES:   # 忽略此行
    bpf:
      masquerade: true
    hubble:
      relay:
        enabled: true
      ui:
        enabled: true
        service:
          type: NodePort
    ipam:
      mode: kubernetes
    ipv4NativeRoutingCIDR: 10.244.0.0/16
    k8sServiceHost: 172.20.6.10
    k8sServicePort: 6443
    kubeProxyReplacement: true
    ```

3. 確認 cilium 版本、routing 模式和 device mode

    ```
    kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -iE "^cilium:|^routing|^device mode"
    ```

    執行結果：

    ```
    Cilium:                  Ok   1.18.2 (v1.18.2-5bd307a8)
    Routing:                 Network: Tunnel [vxlan]   Host: BPF
    Device Mode:             veth
    ```

    > 本篇文章測試環境 routing-mode 為 `tunnel`，tunnel-protocol 使用 `vxlan`，沒有特別 tunning。


4. 建立 NGINX pods

    ```
    kubectl create deploy nginx --image=docker.io/library/nginx:latest --replicas=2
    ```

5. 建立 Client pods，replicas 設成 `2`，並指定跑在 worker node

    ```
    echo 'apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: client
      name: client
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: client
      template:
        metadata:
          labels:
            app: client
        spec:
          nodeSelector:
            node-role.kubernetes.io/worker: ""
          containers:
          - image: quay.io/cooloo9871/debug.alp
            name: debug-alp
            securityContext:
              privileged: true' | kubectl apply -f -
    ```

6. 檢視 pods 各至在哪一個節點

    ```
    kubectl get pods -o wide
    ```

    執行結果類似以下螢幕輸出：

    ```
    NAME                      STATUS    NODE
    client-65766844b7-72zg2   Running   kubeadm-w1.bbg.com
    client-65766844b7-c4w29   Running   kubeadm-w2.bbg.com
    nginx-66686b6766-6vd9x    Running   kubeadm-w2.bbg.com
    nginx-66686b6766-s25sh    Running   kubeadm-w3.bbg.com
    ```

## 3. 檢視單一 Pod 的網路架構

1. 查看 client pod 的 ip address 和 subnet mask

    ```
    kubectl exec -it client-65766844b7-c4w29 -- ip a
    ```

    執行結果：

    ```
    ...以上省略
    34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
        link/ether d6:c4:d2:00:8c:2e brd ff:ff:ff:ff:ff:ff
        inet 10.244.7.250/32 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::d4c4:d2ff:fe00:8c2e/64 scope link
           valid_lft forever preferred_lft forever
    ```

    > **得知 subnet mask 為 `/32`，所以 pod 要上網一定會走 Default Gateway**


2. 查看 client Pod 的 Default Gateway

    ```
    kubectl exec -it client-65766844b7-c4w29 -- ip r
    ```

    執行結果：

    ```
    default via 10.244.7.247 dev eth0
    10.244.7.247 dev eth0 scope link
    ```
    > **確認 Default Gateway 的值為 `10.244.7.247`**

3. 查看 client Pod 裡的 Neighbour Table

    ```
    kubectl exec -it client-65766844b7-c4w29 -- ip n
    ```

    執行結果：

    ```
    10.244.7.247 dev eth0 lladdr 1e:49:e0:67:ec:e7 used 0/0/0 probes 4 STALE
    ```

    > **得知 Default Gateway `10.244.7.247` 對應的 Mac Address 為 `1e:49:e0:67:ec:e7`**

4. 到 pod 所在的節點查找 pod 內看到 Default Gateway 對應在主機是哪張網卡

    ```
    ip a s | grep -A 3 -B 2 10.244.7.247
    ```

    執行結果：

    ```
    4: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
        link/ether ba:af:34:f7:09:12 brd ff:ff:ff:ff:ff:ff
        inet 10.244.7.247/32 scope global cilium_host
           valid_lft forever preferred_lft forever
        inet6 fe80::b8af:34ff:fef7:912/64 scope link
           valid_lft forever preferred_lft forever
    ```

    > **發現 `10.244.7.247` 這張網卡對應的 Mac Address 竟然是 `ba:af:34:f7:09:12` 而不是在 client pod 裡面看到的 `1e:49:e0:67:ec:e7`**

5. 繼續在 pod 所在的節點查找 `1e:49:e0:67:ec:e7` 是哪張網卡

    ```
    ip a s | grep -A 3 -B 1 '1e:49:e0:67:ec:e7'
    ```

    執行結果：

    ```
    35: lxc7904b406cec6@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
        link/ether 1e:49:e0:67:ec:e7 brd ff:ff:ff:ff:ff:ff link-netns cni-a59f663a-3aed-32da-7743-287142c3ccda
        inet6 fe80::1c49:e0ff:fe67:ece7/64 scope link
           valid_lft forever preferred_lft forever
    ```
    > 由網卡名稱可以得知是 veth pair，並且另一端連接在 `cni-a59f663a-3aed-32da-7743-287142c3ccda` Linux Network Namespace

6. 進入這個 Linux Network Namespace 查看網卡資訊

    ```
    sudo ip netns exec cni-a59f663a-3aed-32da-7743-287142c3ccda 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
    34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
        link/ether d6:c4:d2:00:8c:2e brd ff:ff:ff:ff:ff:ff link-netnsid 0
        inet 10.244.7.250/32 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::d4c4:d2ff:fe00:8c2e/64 scope link
           valid_lft forever preferred_lft forever
    ```
    
    > 確認 client pod `eth0` 在主機側的網卡名稱是 `lxc7904b406cec6`
    
7. 確認初始狀態下 pod 內部 arp cache

    ```
    kubectl exec -it client-65766844b7-c4w29 -- arp -a
    ```

    執行結果
    
    ```
    ```
    
    > 得知 pod 在初始狀態下 arp cache 應為空

### 小結論
    
- Client Pod `eth0` 網卡對應在 host 主機這側的網卡是 `lxc7904b406cec6`
- 在 Client Pod 內部 Neighbour Table 紀錄 Default Gateway 的 IP 是在 host 主機 `cilium_host` 網卡的 ip，但是 Mac Address 卻是 pod 在 host 主機側網卡的 mac address
- pod 在初始狀態下無任何 arp cache
- 網路架構如下圖
  ![image](https://hackmd.io/_uploads/ByPWFdOybl.png)

## 4. 檢視同一台節點內 Pods 之間網路封包如何傳遞

1. 查詢 nginx pod 網卡資訊，在 pod 所在的節點執行以下命令

    ```
    pod_name="nginx-66686b6766-6vd9x"
    sudo ip netns exec $(sudo ip netns identify $(sudo crictl inspect $(sudo crictl ps -a | grep ${pod_name} | cut -d " " -f 1) | jq -r '.info.pid // .status.pid')) ip a s eth0
    ```

    執行結果：

    ```
    32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
        link/ether 9a:08:1c:60:8e:77 brd ff:ff:ff:ff:ff:ff link-netnsid 0
        inet 10.244.7.203/32 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::9808:1cff:fe60:8e77/64 scope link
           valid_lft forever preferred_lft forever
    ```

    > 得知 nginx pod 網卡資訊：
    > - 名稱為 `eth0`
    > - ip address 為 `10.244.7.203/32`
    > - mac address 為 `9a:08:1c:60:8e:77`

2.  找到 nginx pod 在主機側的網卡，在 pod 所在的節點執行以下命令

    ```
    pod_name="nginx-66686b6766-6vd9x"
    ip a s | grep -A 3 ^$(sudo ip netns exec $(sudo ip netns identify $(sudo crictl inspect $(sudo crictl ps -a | grep ${pod_name} | cut -d " " -f 1) | jq -r '.info.pid // .status.pid')) ip a s eth0 | head -n 1 | cut -d ":" -f 2 | tail -c 3)
    ```

    執行結果：

    ```
    33: lxcdad8b6341b55@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
        link/ether 2a:e0:31:24:98:d1 brd ff:ff:ff:ff:ff:ff link-netns cni-90146bc6-03b3-1ff1-72c8-61b61819bb95
        inet6 fe80::28e0:31ff:fe24:98d1/64 scope link
           valid_lft forever preferred_lft forever
    ```
    
    > 得知 nginx pod 在主機側網卡資訊：
    > - 名稱為 `lxcdad8b6341b55`
    > - mac address 為 `2a:e0:31:24:98:d1`

1. 額外開啟 4 個終端機，分別都 ssh 到 client 和 nginx pod 所在的同一台節點上，並依序執行以下 4 個收集封包的動作
    1. 在 client pod 主機側的網卡上，透過 `tcpdump` 命令收集 ARP 或 ICMP 封包
        ```
        sudo tcpdump -n -i lxc7904b406cec6 arp or icmp -vvv -w client-pod-host.pcap
        ```
    2. 在 nginx pod 主機側的網卡上，透過 `tcpdump` 命令收集 ARP 或 ICMP 封包
        ```
        sudo tcpdump -n -i lxcdad8b6341b55 arp or icmp -vvv -w nginx-pod-host.pcap
        ```
    3. 在 client pod Container 側的網卡上，透過 `tcpdump` 命令收集 ARP 或 ICMP 封包
        ```
        sudo ip netns exec cni-a59f663a-3aed-32da-7743-287142c3ccda tcpdump -n -i eth0 arp or icmp -vvv -w client-pod.pcap
        ```
    4. 在 nginx pod Container 側的網卡上，透過 `tcpdump` 命令收集 ARP 或 ICMP 封包
        ```
        sudo ip netns exec cni-90146bc6-03b3-1ff1-72c8-61b61819bb95 tcpdump -n -i eth0 arp or icmp -vvv -w nginx-pod.pcap
        ```

4. 切換到可執行 `kubectl` 主機的終端機，進到 `client` pod 去 ping `nginx` pod

    ```
    kubectl exec -it client-65766844b7-c4w29 -- ping -c 1 10.244.7.203
    ```

    執行結果：

    ```
    PING 10.244.7.203 (10.244.7.203): 56 data bytes
    64 bytes from 10.244.7.203: seq=0 ttl=63 time=0.198 ms

    --- 10.244.7.203 ping statistics ---
    1 packets transmitted, 1 packets received, 0% packet loss
    round-trip min/avg/max = 0.198/0.198/0.198 ms
    ```

### 小總結

![image](https://hackmd.io/_uploads/SkwtoKt1Zg.png)

1. client Pod 去 `ping` 同一節點的 nginx pod，因 subnet mask 為 `/32`，網路封包一定往 Default Gateway `10.244.7.247` 送，由於初始化 pod 內部 arp cache 為空，故觸發 arp 通訊協定來去得到 Default Gateway 的 mac address。
    ![image](https://hackmd.io/_uploads/SJNv6q_y-e.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/HknfCcOyWg.png)
2. 由 arp 通訊協定得知 Default Gateway 的 mac address 為 `1e:49:e0:67:ec:e7`，這 mac address 對應的網卡是 client pod 在主機側的網卡 `lxc7904b406cec6`。
    ![image](https://hackmd.io/_uploads/B1lD05dkbe.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/Hyf9AcuJWg.png)
3. 更詳細的運作流程：
    1. client Pod 以廣播的方式發送 arp request 封包 
    2. arp request 被在主機側的網卡收到，此時 cilium bpf `cil_from_container` 程式會立刻接手
    4. 經過一連串判斷，會用自己的 MAC address 假裝自己是 gateway 來回復這個 arp request，詳情請看 [shiun 大佬的源碼分析](https://ithelp.ithome.com.tw/m/articles/10387997#:~:text=bpf_lxc.c%20%E3%80%82-,cil_from_container,-cil_from_container%20%E5%9C%A8%E5%81%9A%E4%BB%80%E9%BA%BC)
        > `cil_from_container` 在做什麼？
        > 簡單一句話來解釋就是：「`cil_from_container` 是 application container 網路的出口警衛，專門檢查所有從 app container 送出的網路封包，並決定它們的下一步該怎麼走。」
    5. client pod 收到被偽造 Mac address 的 arp reply
4. 此時 icmp echo request 封包從 pod 內發送到在主機側的 `lxc7904b406cec6` 網卡
    ![image](https://hackmd.io/_uploads/rkW3HnOy-e.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/By31DadJbg.png)
5. 立刻又被 Cilium 掛在該網卡上的 BPF 程式 `cil_from_container` 接住，並作出以下判斷
    - 如果目標 pod 是同一個 Node → 找到目標 pod 在主機側的網卡 → 然後將封包送進 Container 側的網卡
        - 更詳細運作流程：
            1. 拿到封包後，把目的 MAC Address 和來源的 MAC Address 的換掉，確保能送到正確的 Pod
            2. 直接送達：它不把包裹交給大樓的收發室 (Linux Network Stack)，而是直接搭電梯（tail_call_policy）衝到收件 Pod 的門口（在主機側的網卡），把包裹交給門口的警衛 (Ingress BPF 程式) 做安全檢查 (Policy Enforcement)
        - 以下是從 client pod 在主機側網卡收到的 icmp echo request 封包資訊 (為更換 mac address 前)
            ![image](https://hackmd.io/_uploads/Sy-8maOkWg.png)
            封包詳細資訊：
            ![image](https://hackmd.io/_uploads/B107vaOkWl.png)
            遺憾的是沒收到更換完 mac address 的 icmp echo request 封包
    - 如果目標 pod 是跨 Node → 會轉交 `cilium_vxlan` / `ens33` 網卡
6. 很明顯此次的目標 pod 跟來源 pod 是在同一個 node，經過 cilium bpf 程式一番查找得知要傳給目標 pod 在主機側的網卡，所以 icmp echo request 封包會發送到 nginx pod 在主機側的網卡 `lxcdad8b6341b55` 
7. Nginx pod 在主機側的網卡收到 icmp echo request 封包後，cilium bpf 程式 `cil_from_container` 立馬接手，執行 nginx Pod 的 Ingress Policy 檢查。
8.  檢查沒問題後，BPF 對 kernel 說：「我檢查完了，這封包沒問題，你接手處理吧！」。
9.  此時封包會送進 nginx pod 的 Linux Network Namspace，由於之前已經修改了目的地和來源的 mac address，封包會被正確路由到 nginx Pod 在 Container 側的網卡。
    ![image](https://hackmd.io/_uploads/B1soBTdybl.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/rky-U6u1-x.png)
10. nginx pod 收到 icmp echo request 封包後，會需要回傳 icmp echo reply 的網路封包，同樣會因為 subnet mask 為 `/32`，網路封包一定往 Default Gateway `10.244.7.247` 送，**由於初始化 pod 內部 arp cache 為空，故又觸發 arp 通訊協定來去得到 Default Gateway 的 mac address(實際上會得到的是 nginx pod 在主機側網卡的 mac address)** (arp 詳細運作流程不再重複描述)
    ![image](https://hackmd.io/_uploads/BkSie0OkWx.png)
11. 透過 arp 通訊協定得知 nginx 在主機側網卡 mac 位址後，icmp echo reply 封包會發送至 nginx pod 在主機側的網卡
    ![image](https://hackmd.io/_uploads/HkZV7Ruk-g.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/B1WIQCuybg.png)
12. icmp echo reply 封包傳送到在 nginx pod 主機側的網卡後，又又又被 cilium bpf 程式 `cil_from_container` 接手，並作出以下判斷 :
    1. 目標 pod 是同一個 Node 
    2. 把目的 MAC Address 和來源的 MAC Address 的換掉，確保能送回 client pod
    3. 直接將網路封包送達 client pod 在主機側的網卡
12. client pod 在主機側的網卡收到 icmp echo reply 封包後，cilium bpf 程式 `cil_from_container` 接手，執行 client Pod 的 Ingress Policy 檢查，確認沒問題後最後傳回給 client pod 在 container 側的網卡。
    ![image](https://hackmd.io/_uploads/H1he8Ru1Zx.png)
    封包詳細資訊：
    ![image](https://hackmd.io/_uploads/SynGUAd1be.png)

### 補充說明

安裝 cilium 後，會在主機的 os 裡面，看到 `cilium_host`, `cilium_net`, 和 `cilium_vxlan` 這 3 個網路介面，目前根據上面的驗證結果可得知在同節點內 pod 跟 pod 之間互相溝通，並不會經過這 3 張網卡。

如果想驗證，可在 pods 之間互 ping 之前，執行以下收集封包的命令

1. 收集 `cilium_host` 網路介面的封包

    ```
    sudo tcpdump -n -i cilium_host arp or icmp -vvv -w cilium_host.pcap
    ```

2. 收集 `cilium_net` 網路介面的封包

    ```
    sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_net.pcap
    ```

3. 收集 `cilium_vxlan` 網路介面的封包

    ```
    sudo tcpdump -n -i cilium_net arp or icmp -vvv -w cilium_vxlan
    ```

收集結果會看到 icmp 封包，但 icmp 封包內要傳遞的 data 一定會跟 pod 之間互 ping 的 data 不一樣

![image](https://hackmd.io/_uploads/HkmTKKtJ-e.png)

所以可以確定在同節點內 pod 跟 pod 之間互相溝通，並不會經過 `cilium_host`, `cilium_net`, 和 `cilium_vxlan` 這 3 張網卡。

## 5. 參考文章

- [[Day 12] 探索 Cilium Pod to Pod Datapath (1) 背後竟然有 ARP 偽造？](https://ithelp.ithome.com.tw/m/articles/10387997)
- [[Day 13] 探索 Cilium Pod to Pod Datapath (2) eBPF 走捷徑直接送封包到目的 Pod 面前](https://ithelp.ithome.com.tw/m/articles/10388758)