Table of Contents
Created by gh-md-toc
DragonFlow SNAT + DNAT + Provider 混合部署
在 openstack pike dragonflow 集群中,單獨部署 SNAT,DNAT 和 Provider 網絡,都可以正常工作。然而同時啟用 snat,dnat 和 provider,我們發現了兩個虛機不通的情況。經過分析,確定原因是 dragonflow 流表的問題,修改相關代碼后,所有網絡都可以正常工作。
Commit:
https://github.com/wujieqian/dragonflow/commit/aa3c98cdf466bf4ea9e3a9bdceb5c2f4140011ef
SNAT + Provider 廣播風暴
在 snat 和 provider 兩個 app 同時啟用下,snat 虛機無法與外網通信,并且發現 provider 網絡的 ovs 轉發表被破壞。
網絡拓撲
dragon flow 配置文件,編輯 /etc/neutron/dragonflow.ini
SNAT 配置
[df]
pub_sub_driver = redis_db_pubsub_driver
enable_selective_topology_distribution = False
external_host_ip = 172.24.4.200
integration_bridge = br-int
Provider 配置
[df_provider_networks_app]
bridge_mappings = public:br-ex
vm1: 10.0.0.12
snat: 172.24.4.200
|
------------ provider -------------
| 1|<----------->|1 br-ex |
| br-int | snat | |
| 5|<----------->|2 172.24.4.1 |
------------ -------------
問題分析
vm1 與 br-ex 通信時,以 ICMP 為例,vm1 內部 IP 10.0.0.12 會經過 br-int 上 snat 動作,轉換
為 172.24.4.200 外部 IP,然后從 snat patch port 5 轉發出去。
接下來,br-ex 會收到 icmp 報文,送給宿主機協議棧。由于宿主機沒有 172.24.4.200 的 mac 地址記錄,協議棧會發送 arp request, 請求 172.24.4.200 的物理地址。通過抓包,我們可以在
br-ex 上看到如下報文:
[root@host-10-15-255-16 ~]# tcpdump -ni br-ex -e
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br-ex, link-type EN10MB (Ethernet), capture size 65535 bytes
10:42:24.466733 91:92:ac:18:04:c8 > 12:5d:01:40:2a:4f, ethertype IPv4 (0x0800), length 98: 172.24.4.200 > 172.24.4.1: ICMP echo request, id 33538, seq 0, length 64
10:42:24.466777 12:5d:01:40:2a:4f > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 172.24.4.200 tell 172.24.4.1, length 28
10:42:24.466891 91:92:ac:18:04:c8 > 12:5d:01:40:2a:4f, ethertype ARP (0x0806), length 42: Reply 172.24.4.200 is-at 91:92:ac:18:04:c8, length 28
10:42:24.466895 12:5d:01:40:2a:4f > 91:92:ac:18:04:c8, ethertype IPv4 (0x0800), length 98: 172.24.4.1 > 172.24.4.200: ICMP echo reply, id 33538, seq 0, length 64
br-ex 是一個 Normal 網橋,因此 arp request 包會被 flood 出去。 br-ex 兩個出口 port 1 和 port 2 分別同 br-int 的 port 1 和 port 5連接,因此 br-int 的 port 1 和 port 5 都會收到廣播報文。
對于 snat patch port,由于目的 mac 地址并不是 snat 定義的地址,會直接丟棄。
而對于 provider patch port,流表不會直接丟棄報文,也無法從 provider port 轉發回去,因此廣播報文最終會進入到 table=115,由于沒有匹配到任何特定的出口,最終會選擇優先級最低的 snat 端口轉發出去,而正是這個報文,引發了網絡問題。
cookie=0x0, duration=1629749.533s, table=115, n_packets=4, n_bytes=168, idle_age=2048, hard_age=65534, priority=1 actions=output:5
從 br-int port 5 回到 br-ex port 2 的報文,會被 br-ex 進行 mac 地址學習,不巧該報文的源 mac 地址是 br-ex 本身,因此 br-ex 這個本地地址會錯誤的學習成了 port 2,然后繼續洪泛。
# ovs-appctl fdb/show br-ex
port VLAN MAC Age
2 0 12:5d:01:40:2a:4f 0
解決方法
分析到這里,我們明白這其實是兩個交換機環路問題。如果是物理交換機,有 STP 協議來解決,而對于 br-int 這樣純 openflow 轉發表交換機,我們只能自己添加規則解決環路。
顯然,解決環路,只要在中途丟棄報文就可以了。這里有兩個可以斷開環路的點 1.封住入口 2. 封住出口
上文在 br-ex 上引入的兩條 flow, 既是方法1,讓 ARP 報文僅從 snat-patch(端口2) 進入 br-int,
對于 snat IP, snat-patch 能夠正確處理 arp 報文,不會引發 flood。但對于其他IP,如 provider IP 虛機,是需要從 br-ex-patch(端口1) 進入 br-int,因此考慮方法2, 在 br-int 上的 snat 出口丟棄
廣播包。這樣方法就很簡單了,在 table=115 添加 drop 流表:
ovs-ofctl add-flow br-int table=115,priority=10,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00,action=drop
DNAT 和 SNAT 沖突
在解決問題1后,SNAT 和 Provider 終于可以正常工作了。無論對端是在 br-ex 上還是 br-int 上,
vm1 都可以與之通信。但是當我們繼續啟動一臺 DNAT 虛機,原本可以正常通信的 vm1 現在無法 ping 通
br-int 上的 provider 虛機了。最終分析原因,是 DNAT 新添加的流表破壞了原本的轉發流程。
網絡拓撲
vm1: 10.0.0.12
snat: 172.24.4.200
mac: 91:92:ac:18:04:c8
|
------------ provider -------------
| 1|<----------->|1 br-ex |
| br-int | snat | |
| 5|<----------->|2 172.24.4.1 |
------------ -------------
| |
vm2:172.24.4.8 dnat 172.24.4.14
vm3: 10.0.0.6
問題分析
ovs 網絡沒通,有幾個位置需要檢查:
- 首先抓包看 ping echo 和 reply 發送接收情況。事實上 echo 報文成功發送到了 vm2,并且
vm2 也正確返回了 reply。
# tcpdump -ni tap9c7d035e-be -e
tcpdump: WARNING: tap9c7d035e-be: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap9c7d035e-be, link-type EN10MB (Ethernet), capture size 65535 bytes
14:04:49.091435 12:5d:01:40:2a:4f > fa:16:3e:d4:b7:7b, ethertype IPv4 (0x0800), length 98: 172.24.4.200 > 172.24.4.8: ICMP echo request, id 35842, seq 0, length 64
14:04:49.093390 fa:16:3e:d4:b7:7b > 91:92:ac:18:04:c8, ethertype IPv4 (0x0800), length 98: 172.24.4.8 > 172.24.4.200: ICMP echo reply, id 35842, seq 0, length 64
- 由于 dnat enable 之后出現問題,所以對比流表區別。table=55 中 針對廣播報文規則動作中在正常和異常環境中均有計數器更新,說明這一條規則是轉發的關鍵路徑。
并且在異常環境中,action 里多加了一項 load:0x9->NXM_NX_REG7[],resubmit(,75)
cookie=0x0, duration=1638901.089s, table=55, n_packets=8, n_bytes=336, idle_age=7294, hard_age=65534, priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=load:0x9->NXM_NX_REG7[],resubmit(,75),load:0x4->NXM_NX_REG7[],resubmit(,75),load:0x7->NXM_NX_REG7[],resubmit(,75),load:0->NXM_NX_REG7[],resubmit(,75)
在這里 n_packets 計數器也有改變,證明報文確實匹配到了這里。按照 dragonflow 定義,table 55 是 L2_LOOKUP_TABLE,也就是根據目的 mac 地址,設置 reg7 計數器,在后面的匹配表里找到對應的出口轉出交換機。
看到這里,有兩個疑問 1. dl_dst 表示這一條 rule 是用來匹配 mac 多播的,為什么發向 snat 的 reply 報文會走到這里?
- 正常情況下,reply 報文應該繼續走哪一個 action?
在正常流程下,snat 虛機的 mac 地址為 91:92:ac:18:04:c8,計算掩碼后為 01:00:00:00:00:00,
是一個多播 mac 地址,因此匹配到了多播規則。對于為什么 snat 的 mac 地址使用了多播格式,猜測 df 的開發者是有意為之。直接讓 dst mac = snat 的報文走多播的路徑,也就是讓該報文從每個 provider port 發送一份,其中自然包括了 provider patch port。
snat 虛機的外部 IP 上是一個 provider 網段 IP 地址,DF SNAT 設計者希望 br-int 把報文從 provider port 轉發出去,之后再從 snat port 回 br-int 走 snat 匹配。正常流程下的流表路徑也印證了這一點:
table=55
cookie=0x0, duration=1638901.089s, table=55, n_packets=8, n_bytes=336, idle_age=7294, hard_age=65534, priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=load:0x4->NXM_NX_REG7[],resubmit(,75),load:0x7->NXM_NX_REG7[],resubmit(,75),load:0->NXM_NX_REG7[],resubmit(,75)
-> table=75
# 沒有對 reg7 的要求,其實也就是 reg7=0x0,既默認值
cookie=0x0, duration=1642589.397s, table=75, n_packets=45, n_bytes=3794, idle_age=2738, hard_age=65534, priority=50,metadata=0x2 actions=resubmit(,80)
-> table=80
cookie=0x0, duration=1642589.397s, table=80, n_packets=25, n_bytes=1834, idle_age=2738, hard_age=65534, priority=100,metadata=0x2 actions=output:1
該流程也可以通過 ovs-appctl 注冊模擬報文來觀察:
# ovs-appctl ofproto/trace br-int icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,in_port=10 -generate
然而,在異常情況下,我們并沒有看到 reg7=0x0 在 table=75, table=80的計數器更新,說明在 table=55 的連續 resubmit 中,pipeline 沒有正確執行。
由于正常和異常的 action 區別只有 load:0x9->NXM_NX_REG7[],resubmit(,75)
這一個新加的動作,那么現在的問題就變成了 resubmit 究竟做了什么?
使用 ovs-appctl 注冊模擬報文,發現 flow 匹配到 table 55后,的確觸發了多個 resubmit 動作,4個 resubmit 分別是 reg7=9,reg7=4, reg7=7, reg7=0,以深度優先執行4個 resubmit,最終會走到我們期望的 reg7=0 這個分支。
模擬報文說明 flow 表設計的邏輯是說得通的,但是經過 debug,邏輯順序和實際情況竟然并不一致。在 ovs 中我們添加 debug 信息,觀察輸出結果,發現只有 reg7=9 最終會修改報文信息,包括 metadata 和 mac 地址。而后面的resubmit 用的報文并不是原始副本,而是reg7=9 修改過的版本,導致正常流程被破壞。
看到這里,DF 設計者顯然被 openflow resubmit 字面迷惑了,以為 resubmit 使用的獨立副本,不會影響 pipeline 后續的動作。然而 ovs 對于 resubmit 動作的實現里,報文是使用了同一個結構體實例。
openvswitch/ofproto/ofproto-dpif-xlate.c
static void
xlate_recursively(struct xlate_ctx *ctx, struct rule_dpif *rule)
{
struct rule_dpif *old_rule = ctx->rule;
ovs_be64 old_cookie = ctx->rule_cookie;
const struct rule_actions *actions;
if (ctx->xin->resubmit_stats) {
rule_dpif_credit_stats(rule, ctx->xin->resubmit_stats);
}
ctx->resubmits++;
ctx->recurse++;
ctx->rule = rule;
ctx->rule_cookie = rule_dpif_get_flow_cookie(rule);
actions = rule_dpif_get_actions(rule);
do_xlate_actions(actions->ofpacts, actions->ofpacts_len, ctx);
ctx->rule_cookie = old_cookie;
ctx->rule = old_rule;
ctx->recurse--;
}
解決方法
既然 openvswitch openflow 實現里,resubmit 始終使用同一份內存空間, 我們需要引其他規則來規避這個問題。這里有兩個方案,1. 更改 snat 的 mac 前綴,改為單播地址。2. 為 91:92 地址添加獨立的轉發規則。
為以后方便和 upstream merge, 我們選擇方案2實現。
模擬報文
這里給出模擬報文的輸出片段,可以觀察 table 55 的4次 resubmit 動作。
#ovs-appctl ofproto/trace br-int icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,in_port=10 -generate ;id=0x1; ovs-appctl ofproto/trace br-int icmp,dl_dst=91:92:ac:18:04:c8,dl_src=fa:16:3e:d4:b7:7b,nw_src=172.24.4.8,nw_dst=172.24.4.200,icmp_type=0,ct_state="new|trk",recirc_id=0x$id -generate
Rule: table=20 cookie=0 priority=1
OpenFlow actions=goto_table:55
Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,ip,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no
Rule: table=55 cookie=0 priority=100,metadata=0x2,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00
OpenFlow actions=set_field:0x9->reg7,resubmit(,75),set_field:0x4->reg7,resubmit(,75),set_field:0x7->reg7,resubmit(,75),set_field:0->reg7,resubmit(,75)
1. `resubmit 1, reg7=0x9`
Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x9,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x9 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,ip,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no
Rule: table=75 cookie=0 priority=200,reg7=0x9
OpenFlow actions=goto_table:76
Resubmitted flow: unchanged
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x9 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=76 cookie=0 priority=100,ip,reg7=0x9
OpenFlow actions=dec_ttl,set_field:fa:16:3e:58:eb:ba->eth_src,set_field:fa:16:3e:5c:bd:e0->eth_dst,set_field:10.0.0.6->ip_dst,set_field:0x6->reg7,set_field:0x1->metadata,resubmit(,55)
2. `resubmit 2, reg7=0x4`
Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x4,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=75 cookie=0 priority=100,reg7=0x4
OpenFlow actions=goto_table:105
Resubmitted flow: unchanged
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=105 cookie=0 priority=1
OpenFlow actions=goto_table:115
Resubmitted flow: unchanged
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x4 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=115 cookie=0 priority=10,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00
OpenFlow actions=drop
3. `resubmit 3, reg7=0x7`
Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,reg7=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x7 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=75 cookie=0 priority=100,reg7=0x7
OpenFlow actions=goto_table:105
Resubmitted flow: unchanged
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x7 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: drop
Resubmitted megaflow: recirc_id=0x35f,icmp,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=105 cookie=0 priority=100,ip,reg7=0x7
OpenFlow actions=ct(table=110,zone=OXM_OF_METADATA[0..15])
4. `resubmit 4, reg7=0x0`
Resubmitted flow: recirc_id=0x35f,ct_state=new|trk,icmp,reg6=0x7,metadata=0x2,vlan_tci=0x0000,dl_src=fa:16:3e:d4:b7:7b,dl_dst=91:92:ac:18:04:c8,nw_src=172.24.4.8,nw_dst=172.24.4.200,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=0,icmp_code=0
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: ct(zone=2),recirc(0x360)
Resubmitted megaflow: recirc_id=0x35f,icmp,reg6=0,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=75 cookie=0 priority=50,metadata=0x2
OpenFlow actions=goto_table:80
Resubmitted flow: unchanged
Resubmitted regs: reg0=0x0 reg1=0x0 reg2=0x0 reg3=0x0 reg4=0x0 reg5=0x0 reg6=0x7 reg7=0x0 reg8=0x0 reg9=0x0 reg10=0x0 reg11=0x0 reg12=0x0 reg13=0x0 reg14=0x0 reg15=0x0
Resubmitted odp: ct(zone=2),recirc(0x360)
Resubmitted megaflow: recirc_id=0x35f,icmp,reg6=0,reg7=0,metadata=0,in_port=ANY,dl_dst=91:92:ac:18:04:c8,nw_dst=172.24.4.128/25,nw_ttl=0,nw_frag=no,icmp_type=0x0/0xfffe
Rule: table=80 cookie=0 priority=100,metadata=0x2
OpenFlow actions=output:1