+static_always_inline int
+is_hairpinning (snat_main_t *sm, ip4_address_t * dst_addr)
+{
+ snat_address_t * ap;
+ clib_bihash_kv_8_8_t kv, value;
+ snat_session_key_t m_key;
+
+ vec_foreach (ap, sm->addresses)
+ {
+ if (ap->addr.as_u32 == dst_addr->as_u32)
+ return 1;
+ }
+
+ m_key.addr.as_u32 = dst_addr->as_u32;
+ m_key.fib_index = sm->outside_fib_index;
+ m_key.port = 0;
+ m_key.protocol = 0;
+ kv.key = m_key.as_u64;
+ if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+ return 1;
+
+ return 0;
+}
+
+static uword
+snat_hairpin_dst_fn (vlib_main_t * vm,
+ vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+ u32 n_left_from, * from, * to_next;
+ snat_in2out_next_t next_index;
+ u32 pkts_processed = 0;
+ snat_main_t * sm = &snat_main;
+
+ from = vlib_frame_vector_args (frame);
+ n_left_from = frame->n_vectors;
+ next_index = node->cached_next_index;
+
+ while (n_left_from > 0)
+ {
+ u32 n_left_to_next;
+
+ vlib_get_next_frame (vm, node, next_index,
+ to_next, n_left_to_next);
+
+ while (n_left_from > 0 && n_left_to_next > 0)
+ {
+ u32 bi0;
+ vlib_buffer_t * b0;
+ u32 next0;
+ ip4_header_t * ip0;
+ u32 proto0;
+
+ /* speculatively enqueue b0 to the current next frame */
+ bi0 = from[0];
+ to_next[0] = bi0;
+ from += 1;
+ to_next += 1;
+ n_left_from -= 1;
+ n_left_to_next -= 1;
+
+ b0 = vlib_get_buffer (vm, bi0);
+ next0 = SNAT_IN2OUT_NEXT_LOOKUP;
+ ip0 = vlib_buffer_get_current (b0);
+
+ proto0 = ip_proto_to_snat_proto (ip0->protocol);
+
+ vnet_buffer (b0)->snat.flags = 0;
+ if (PREDICT_FALSE (is_hairpinning (sm, &ip0->dst_address)))
+ {
+ if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP)
+ {
+ udp_header_t * udp0 = ip4_next_header (ip0);
+ tcp_header_t * tcp0 = (tcp_header_t *) udp0;
+
+ snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0);
+ }
+ else if (proto0 == SNAT_PROTOCOL_ICMP)
+ {
+ icmp46_header_t * icmp0 = ip4_next_header (ip0);
+
+ snat_icmp_hairpinning (sm, b0, ip0, icmp0);
+ }
+ else
+ {
+ snat_hairpinning_unknown_proto (sm, b0, ip0);
+ }
+
+ vnet_buffer (b0)->snat.flags = SNAT_FLAG_HAIRPINNING;
+ clib_warning("is hairpinning");
+ }
+
+ pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
+
+ /* verify speculative enqueue, maybe switch current next frame */
+ vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+ to_next, n_left_to_next,
+ bi0, next0);
+ }
+
+ vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+ }
+
+ vlib_node_increment_counter (vm, snat_hairpin_dst_node.index,
+ SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
+ pkts_processed);
+ return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (snat_hairpin_dst_node) = {
+ .function = snat_hairpin_dst_fn,
+ .name = "snat-hairpin-dst",
+ .vector_size = sizeof (u32),
+ .type = VLIB_NODE_TYPE_INTERNAL,
+ .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+ .error_strings = snat_in2out_error_strings,
+ .n_next_nodes = 2,
+ .next_nodes = {
+ [SNAT_IN2OUT_NEXT_DROP] = "error-drop",
+ [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup",
+ },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_dst_node,
+ snat_hairpin_dst_fn);
+
+static uword
+snat_hairpin_src_fn (vlib_main_t * vm,
+ vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+ u32 n_left_from, * from, * to_next;
+ snat_in2out_next_t next_index;
+ u32 pkts_processed = 0;
+ snat_main_t *sm = &snat_main;
+
+ from = vlib_frame_vector_args (frame);
+ n_left_from = frame->n_vectors;
+ next_index = node->cached_next_index;
+
+ while (n_left_from > 0)
+ {
+ u32 n_left_to_next;
+
+ vlib_get_next_frame (vm, node, next_index,
+ to_next, n_left_to_next);
+
+ while (n_left_from > 0 && n_left_to_next > 0)
+ {
+ u32 bi0;
+ vlib_buffer_t * b0;
+ u32 next0;
+
+ /* speculatively enqueue b0 to the current next frame */
+ bi0 = from[0];
+ to_next[0] = bi0;
+ from += 1;
+ to_next += 1;
+ n_left_from -= 1;
+ n_left_to_next -= 1;
+
+ b0 = vlib_get_buffer (vm, bi0);
+ next0 = SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT;
+
+ if (PREDICT_FALSE ((vnet_buffer (b0)->snat.flags) & SNAT_FLAG_HAIRPINNING))
+ {
+ if (PREDICT_TRUE (sm->num_workers > 1))
+ next0 = SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH;
+ else
+ next0 = SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT;
+ }
+
+ pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP;
+
+ /* verify speculative enqueue, maybe switch current next frame */
+ vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+ to_next, n_left_to_next,
+ bi0, next0);
+ }
+
+ vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+ }
+
+ vlib_node_increment_counter (vm, snat_hairpin_src_node.index,
+ SNAT_IN2OUT_ERROR_IN2OUT_PACKETS,
+ pkts_processed);
+ return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (snat_hairpin_src_node) = {
+ .function = snat_hairpin_src_fn,
+ .name = "snat-hairpin-src",
+ .vector_size = sizeof (u32),
+ .type = VLIB_NODE_TYPE_INTERNAL,
+ .n_errors = ARRAY_LEN(snat_in2out_error_strings),
+ .error_strings = snat_in2out_error_strings,
+ .n_next_nodes = SNAT_HAIRPIN_SRC_N_NEXT,
+ .next_nodes = {
+ [SNAT_HAIRPIN_SRC_NEXT_DROP] = "error-drop",
+ [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT] = "snat-in2out-output",
+ [SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT] = "interface-output",
+ [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH] = "snat-in2out-output-worker-handoff",
+ },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_src_node,
+ snat_hairpin_src_fn);
+