現象描述
兩個網口都開啟nat?output-feature,路由模式進行大包轉發,網絡不同,小包轉發沒問題。
通過trace發現,在nat44-ed-in2out-output-slowpath節點丟包。
Packet 503:50:43:447292: handoff_traceHANDED-OFF: from thread 2 trace index 5
03:50:43:447292: nat44-ed-out2inNAT44_OUT2IN_ED_FAST_PATH: sw_if_index 2, next index 12search key local 192.168.93.146:1 remote 192.168.1.123:1 proto ICMP fib 0 thre
ad-index 0 session-index 0no reason for slow path
03:50:43:447304: ip4-inaclINACL: sw_if_index 2, next_index 1, table_index -1, offset -1no table
03:50:43:447311: ip4-lookupfib 0 dpo-idx 15 flow hash: 0x00000000ICMP: 192.168.93.146 -> 192.168.1.123tos 0x00, ttl 64, length 1300, checksum 0x4f79 dscp CS0 ecn NON_ECNfragment id 0x2612, flags MORE_FRAGMENTSICMP echo_reply checksum 0x4ff2 id 1
03:50:43:447318: ip4-rewritetx_sw_if_index 1 dpo-idx 15 : ipv4 via 192.168.1.123 eth0: mtu:2026 next:8 fla
gs:[features ] 0019e0772dfb9409d30081280800 flow hash: 0x0000000000000000: 0019e0772dfb9409d3008128080045000514261220003f015079c0a85d92c0a800000020: 017b00004ff2000102cf6162636465666768696a6b6c6d6e6f707172
03:50:43:447325: nat44-in2out-output-worker-handoffNAT44_IN2OUT_WORKER_HANDOFF OUTPUT-FEATURE: next-worker 1 trace index 4
03:50:43:447335: nat44-ed-in2out-outputNAT44_IN2OUT_ED_FAST_PATH: sw_if_index 2, next index 5search key local 69.0.5.20:0 remote 38.18.32.0:0 proto IL fib 0 thread-index 0session-index 0
03:50:43:447341: nat44-ed-in2out-output-slowpathNAT44_IN2OUT_ED_SLOW_PATH: sw_if_index 2, next index 0
03:50:43:447355: error-droprx:eth1
03:50:43:447358: dropip4-input: input ACL session deny drops
在函數nat44_ed_in2out_slow_path_node_fn_inline中加調試信息發現vnet_buffer (b0)->ip.reass.save_rewrite_length值為2,正常應該是14。
在重組節點(ip4-sv-reassembly-feature),添加調試,發現下面的代碼后ip.reass.save_rewrite_length會被修改成2。
分析了下vnet_buffer_opaque_t結構,終于明白了原因。
原因總結
vnet_buffer_opaque_t 結構中 ip.reass.owner_thread_index 和 ip.reass.save_rewrite_length內存重疊。修改兩者中的一個,另一個的值必然會被修改。
/* reassembly */union{/* group input/output to simplify the code, this way* we can handoff while keeping input variables intact */struct{/* input variables */struct{u32 next_index; /* index of next node - used by custom apps */u32 error_next_index; /* index of next node if error - used by custom apps */};/* handoff variables */struct{u16 owner_thread_index;};};/* output variables */struct{union{/* shallow virtual reassembly output variables */struct{u16 l4_src_port; /* tcp/udp/icmp src port */u16 l4_dst_port; /* tcp/udp/icmp dst port */u32 tcp_ack_number;u8 save_rewrite_length;u8 ip_proto; /* protocol in ip header */u8 icmp_type_or_tcp_flags;u8 is_non_first_fragment : 1;u8 l4_layer_truncated : 7;u32 tcp_seq_number;};/* full reassembly output variables */struct{u16 estimated_mtu; /* estimated MTU calculated during reassembly */};};};/* internal variables used during reassembly */struct{u16 fragment_first;u16 fragment_last;u16 range_first;u16 range_last;u32 next_range_bi;u16 ip6_frag_hdr_offset;};} reass;
當同一組分片包被投放到不同線程時(nat handoff會將數據包投遞到會話所在的線程),在重組節點(ip4-sv-reassembly-feature)會將線程ID記錄到ip.reass.owner_thread_index中,如下圖:
這時ip.reass.save_rewrite_length就會被修改。這就是為什么 ip.reass.save_rewrite_length 值為 2的原因,2是線程 id。
當數據包到達后續nat節點時,會通過ip.reass.save_rewrite_length獲取IP頭,這時獲取的IP頭時錯誤的,代碼中無法識別3層協議而丟包。
嘗試解決
修改 ip.reass.owner_thread_index?的位置,添加占位符 u8 _pad[6],使其不再和ip.reass.save_rewrite_length內存重疊。如下:
修改后還是會和tcp_seq_number有內存重疊,但TCP一般不會有分片包,問題不大。
經過上面的修改后,測試大包還是不通,又有了新的問題。O__O "…
新的問題
通過 trace 信息發現分片包(按照分片會話)沒有按照預期被投遞到別的線程(分片會話所在線程),并且進入ip4-sv-reassembly-feature(重組)節點,而是直接進入到后續的nat處理節點中,然后被丟包。丟包原因是ICMP?type?無效。
分片包的ip.reass的icmp_type_or_tcp_flags等信息需要匹配到會話,然后被賦值(從分片會話中獲取)。數據包沒有再次進入p4-sv-reassembly-feature(重組)節點,就無法完成這一步。
當數據包到達后續nat節點時,因為icmp_type_or_tcp_flags無效被丟包。
繼續找原因
通過?ip4_sv_reass_inline?函數代碼發現,如果 error0 不為?IP4_ERROR_NONE 時會查feature來確定下一個節點,從而直接走后面的feature。
如上圖中 next0 會被修改為nat44-out2in-worker-handoff節點,而分片包被投遞到分片會話所在的線程,這一操作是在節點 ip4-sv-reass-feature-hoff 中完成的。next0?在 handoff 處會被指定為IP4_SV_REASSEMBLY_NEXT_HANDOFF,數據包就會走到ip4-sv-reass-feature-hoff節點。該節點中會按照 ip.reass.owner_thread_index?記錄的值將數據包投遞到相應的線程。
嘗試修改2
經過上面的分析,解決方法就是在 handoff 處需要將error0賦值為非IP4_ERROR_NONE的值。如下:
經過上面的修改后,測試大包還是不通,又有了新的問題。O__O "………
新的問題2? ??
數據包成功被投遞到分片會話所在的線程,但在數據包在重組節點,即ip4_sv_reass_inline函數中被丟包。丟包位置如下:
進一步加調試信息,發現IP頭的獲取是有誤的,分析代碼發現是下面的問題
當數據包第二次進入 ip4_sv_reass_inline?函數時 is_output_feature?是為 0 的。
經過分析 ip4-sv-reass-feature-hoff?節點代碼,發現數據包的下一個節點會被固定指定為ip4-sv-reassembly(在函數ip4_sv_reass_init_function初始化指定),該節點在調用?ip4_sv_reass_inline?時會指定 is_output_feature?為 0。
這時的數據包處理是在路由后進行,正確的處理應該是在 ip4-sv-reass-feature-hoff?節點指定下一個節點為?ip4-sv-reassembly-output-feature。
嘗試修改3
按照上面的分析進行如下修改:
- 為函數ip4_sv_reass_handoff_node_inline添加參數 bool is_output_feature,然后新增節點 ip4-sv-reass-feature-hoff-output-feature,該節點在調用函數 ip4_sv_reass_handoff_node_inline?時指定參數 is_output_feature 為 1。? ? ? ?
/* *INDENT-OFF* */
VLIB_NODE_FN (ip4_sv_reass_feature_handoff_node_output_feature) (vlib_main_t * vm,vlib_node_runtime_t *node,vlib_frame_t * frame)
{return ip4_sv_reass_handoff_node_inline (vm, node, frame, true /* is_feature */, false /* is_custom_context */, true);
}
/* *INDENT-ON* *//* *INDENT-OFF* */
VLIB_REGISTER_NODE (ip4_sv_reass_feature_handoff_node_output_feature) = {.name = "ip4-sv-reass-feature-hoff-output-feature",.vector_size = sizeof (u32),.n_errors = ARRAY_LEN(ip4_sv_reass_handoff_error_strings),.error_strings = ip4_sv_reass_handoff_error_strings,.format_trace = format_ip4_sv_reass_handoff_trace,.n_next_nodes = 1,.next_nodes = {[0] = "error-drop",},
};
/* *INDENT-ON* */
- 修改ip4-sv-reassembly-output-feature節點的next_node,當 is_output_feature?為 1?時,讓數據包走新增的節點 ip4-sv-reass-feature-hoff-output-feature。
- 在 ip4_sv_reass_handoff_node_inline?函數中,如果 is_output_feature 為 1,則指定數據包的下一個節點為 ip4-sv-reassembly-output-feature。
- 在函數 ip4_sv_reass_inline?中的 handoff 處,如果 is_output_feature 為 1,則指定數據包的下一個節點為 ip4-sv-reass-feature-hoff-output-feature。
rm->fq_index_output_feature 和?rm->fq_feature_index 在函數 ip4_sv_reass_init_function?中初始化,如下:
總結
vpp對于分片的處理支持的并不是很好,本次遇到問題主要是兩方面的原因:
- vnet_buffer_opaque_t?聯合體的設計,導致變量內存重,進而導致互相影響。
- nat?handoff?選擇線程與重組 handoff 選擇線程的算法不同,導致數據包被多次從一個線程投遞到其他線程。
- 而重組?handoff?對于數據包的處理存在一定的問題,即上面遇到的后兩個問題。
所有的修改總結如下:
- 修改結構體vnet_buffer_opaque_t?中?ip.reass.owner_thread_index?的位置,添加占位符 u8 _pad[6],使其不再和ip.reass.save_rewrite_length內存重疊。
- ip4_sv_reass_inline?中的 handoff 處,進行兩處修改。如果 is_output_feature 為 1,則指定數據包的下一個節點為 ip4-sv-reass-feature-hoff-output-feature;指定error0 = IP4_ERROR_REASS_INTERNAL_ERROR;
- 新增?ip4-sv-reass-feature-hoff-output-feature?節點,讓數據包在路由后正確被處理。詳細見上面的修改3.