From 613b2c3c78fbec12cc87a0095ee5488252449698 Mon Sep 17 00:00:00 2001 From: Nathan Skrzypczak Date: Thu, 10 Sep 2020 17:44:41 +0200 Subject: [PATCH] cnat: Add support for SNat ICMP Type: feature snat supports : * echo request/reply by allocating an identifier when translating echo requests * icmp errors in the same manner as dnat Change-Id: I684e983b0181f95c5eace5a984d40084e5625fa4 Signed-off-by: Nathan Skrzypczak --- src/plugins/cnat/cnat_node.h | 211 +++++++++++++++++++++++------ src/plugins/cnat/cnat_node_snat.c | 27 ++-- src/plugins/cnat/cnat_node_vip.c | 3 +- src/plugins/cnat/cnat_session.c | 2 +- src/plugins/cnat/cnat_types.c | 9 +- src/plugins/cnat/cnat_types.h | 81 ++++++++--- src/plugins/cnat/test/test_cnat.py | 271 +++++++++++++++++++++++++++---------- 7 files changed, 461 insertions(+), 143 deletions(-) diff --git a/src/plugins/cnat/cnat_node.h b/src/plugins/cnat/cnat_node.h index a3967960078..2e3b0e0275a 100644 --- a/src/plugins/cnat/cnat_node.h +++ b/src/plugins/cnat/cnat_node.h @@ -42,6 +42,30 @@ icmp_type_is_error_message (u8 icmp_type) return 0; } +static_always_inline u8 +icmp_type_is_echo (u8 icmp_type) +{ + switch (icmp_type) + { + case ICMP4_echo_request: + case ICMP4_echo_reply: + return 1; + } + return 0; +} + +static_always_inline u8 +icmp6_type_is_echo (u8 icmp_type) +{ + switch (icmp_type) + { + case ICMP6_echo_request: + case ICMP6_echo_reply: + return 1; + } + return 0; +} + static_always_inline u8 icmp6_type_is_error_message (u8 icmp_type) { @@ -170,20 +194,39 @@ cnat_tcp_update_session_lifetime (tcp_header_t * tcp, u32 index) } static_always_inline void -cnat_translation_icmp4 (ip4_header_t * outer_ip4, udp_header_t * outer_udp, - ip4_address_t outer_new_addr[VLIB_N_DIR], - u16 outer_new_port[VLIB_N_DIR], u8 snat_outer_ip) +cnat_translation_icmp4_echo (ip4_header_t * ip4, icmp46_header_t * icmp, + ip4_address_t new_addr[VLIB_N_DIR], + u16 new_port[VLIB_N_DIR]) +{ + ip_csum_t sum; + u16 old_port; + cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1); + + cnat_ip4_translate_l3 (ip4, new_addr); + old_port = echo->identifier; + echo->identifier = new_port[VLIB_RX]; + + sum = icmp->checksum; + sum = ip_csum_update (sum, old_port, new_port[VLIB_RX], + ip4_header_t /* cheat */ , + length /* changed member */ ); + + icmp->checksum = ip_csum_fold (sum); +} + +static_always_inline void +cnat_translation_icmp4_error (ip4_header_t * outer_ip4, + icmp46_header_t * icmp, + ip4_address_t outer_new_addr[VLIB_N_DIR], + u16 outer_new_port[VLIB_N_DIR], + u8 snat_outer_ip) { - icmp46_header_t *icmp = (icmp46_header_t *) outer_udp; ip4_address_t new_addr[VLIB_N_DIR]; ip4_address_t old_addr[VLIB_N_DIR]; u16 new_port[VLIB_N_DIR]; u16 old_port[VLIB_N_DIR]; ip_csum_t sum, old_ip_sum, inner_l4_sum, inner_l4_old_sum; - if (!icmp_type_is_error_message (icmp->type)) - return; - ip4_header_t *ip4 = (ip4_header_t *) (icmp + 2); udp_header_t *udp = (udp_header_t *) (ip4 + 1); tcp_header_t *tcp = (tcp_header_t *) udp; @@ -287,10 +330,18 @@ cnat_translation_ip4 (const cnat_session_t * session, } else if (ip4->protocol == IP_PROTOCOL_ICMP) { - /* SNAT only if src_addr was translated */ - u8 snat_outer_ip = - (ip4->src_address.as_u32 == session->key.cs_ip[VLIB_RX].ip4.as_u32); - cnat_translation_icmp4 (ip4, udp, new_addr, new_port, snat_outer_ip); + icmp46_header_t *icmp = (icmp46_header_t *) udp; + if (icmp_type_is_error_message (icmp->type)) + { + /* SNAT only if src_addr was translated */ + u8 snat_outer_ip = + (ip4->src_address.as_u32 == + session->key.cs_ip[VLIB_RX].ip4.as_u32); + cnat_translation_icmp4_error (ip4, icmp, new_addr, new_port, + snat_outer_ip); + } + else if (icmp_type_is_echo (icmp->type)) + cnat_translation_icmp4_echo (ip4, icmp, new_addr, new_port); } } @@ -358,11 +409,52 @@ cnat_ip6_translate_l4 (ip6_header_t * ip6, udp_header_t * udp, } static_always_inline void -cnat_translation_icmp6 (ip6_header_t * outer_ip6, udp_header_t * outer_udp, - ip6_address_t outer_new_addr[VLIB_N_DIR], - u16 outer_new_port[VLIB_N_DIR], u8 snat_outer_ip) +cnat_translation_icmp6_echo (ip6_header_t * ip6, icmp46_header_t * icmp, + ip6_address_t new_addr[VLIB_N_DIR], + u16 new_port[VLIB_N_DIR]) +{ + cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1); + ip6_address_t old_addr[VLIB_N_DIR]; + ip_csum_t sum; + u16 old_port; + old_port = echo->identifier; + ip6_address_copy (&old_addr[VLIB_TX], &ip6->dst_address); + ip6_address_copy (&old_addr[VLIB_RX], &ip6->src_address); + + sum = icmp->checksum; + + cnat_ip6_translate_l3 (ip6, new_addr); + if (has_ip6_address (&new_addr[VLIB_TX])) + { + sum = ip_csum_add_even (sum, new_addr[VLIB_TX].as_u64[0]); + sum = ip_csum_add_even (sum, new_addr[VLIB_TX].as_u64[1]); + sum = ip_csum_sub_even (sum, old_addr[VLIB_TX].as_u64[0]); + sum = ip_csum_sub_even (sum, old_addr[VLIB_TX].as_u64[1]); + } + + if (has_ip6_address (&new_addr[VLIB_RX])) + { + sum = ip_csum_add_even (sum, new_addr[VLIB_RX].as_u64[0]); + sum = ip_csum_add_even (sum, new_addr[VLIB_RX].as_u64[1]); + sum = ip_csum_sub_even (sum, old_addr[VLIB_RX].as_u64[0]); + sum = ip_csum_sub_even (sum, old_addr[VLIB_RX].as_u64[1]); + } + + echo->identifier = new_port[VLIB_RX]; + sum = ip_csum_update (sum, old_port, new_port[VLIB_RX], + ip4_header_t /* cheat */ , + length /* changed member */ ); + + icmp->checksum = ip_csum_fold (sum); +} + +static_always_inline void +cnat_translation_icmp6_error (ip6_header_t * outer_ip6, + icmp46_header_t * icmp, + ip6_address_t outer_new_addr[VLIB_N_DIR], + u16 outer_new_port[VLIB_N_DIR], + u8 snat_outer_ip) { - icmp46_header_t *icmp = (icmp46_header_t *) outer_udp; ip6_address_t new_addr[VLIB_N_DIR]; ip6_address_t old_addr[VLIB_N_DIR]; ip6_address_t outer_old_addr[VLIB_N_DIR]; @@ -411,6 +503,7 @@ cnat_translation_icmp6 (ip6_header_t * outer_ip6, udp_header_t * outer_udp, sum = ip_csum_sub_even (sum, outer_old_addr[VLIB_RX].as_u64[1]); } + /* Translate inner TCP / UDP */ if (ip6->protocol == IP_PROTOCOL_TCP) { inner_l4_old_sum = inner_l4_sum = tcp->checksum; @@ -494,10 +587,18 @@ cnat_translation_ip6 (const cnat_session_t * session, } else if (ip6->protocol == IP_PROTOCOL_ICMP6) { - /* SNAT only if src_addr was translated */ - u8 snat_outer_ip = cmp_ip6_address (&ip6->src_address, - &session->key.cs_ip[VLIB_RX].ip6); - cnat_translation_icmp6 (ip6, udp, new_addr, new_port, snat_outer_ip); + icmp46_header_t *icmp = (icmp46_header_t *) udp; + if (icmp6_type_is_error_message (icmp->type)) + { + /* SNAT only if src_addr was translated */ + u8 snat_outer_ip = cmp_ip6_address (&ip6->src_address, + &session->key. + cs_ip[VLIB_RX].ip6); + cnat_translation_icmp6_error (ip6, icmp, new_addr, new_port, + snat_outer_ip); + } + else if (icmp6_type_is_echo (icmp->type)) + cnat_translation_icmp6_echo (ip6, icmp, new_addr, new_port); } } @@ -517,18 +618,32 @@ cnat_session_make_key (vlib_buffer_t * b, ip_address_family_t af, if (PREDICT_FALSE (ip4->protocol == IP_PROTOCOL_ICMP)) { icmp46_header_t *icmp = (icmp46_header_t *) (ip4 + 1); - if (!icmp_type_is_error_message (icmp->type)) + if (icmp_type_is_error_message (icmp->type)) + { + ip4 = (ip4_header_t *) (icmp + 2); /* Use inner packet */ + udp = (udp_header_t *) (ip4 + 1); + /* Swap dst & src for search as ICMP payload is reversed */ + ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX], + &ip4->dst_address); + ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX], + &ip4->src_address); + session->key.cs_proto = ip4->protocol; + session->key.cs_port[VLIB_TX] = udp->src_port; + session->key.cs_port[VLIB_RX] = udp->dst_port; + } + else if (icmp_type_is_echo (icmp->type)) + { + cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1); + ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX], + &ip4->dst_address); + ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX], + &ip4->src_address); + session->key.cs_proto = ip4->protocol; + session->key.cs_port[VLIB_TX] = echo->identifier; + session->key.cs_port[VLIB_RX] = echo->identifier; + } + else goto error; - ip4 = (ip4_header_t *) (icmp + 2); /* Use inner packet */ - udp = (udp_header_t *) (ip4 + 1); - /* Swap dst & src for search as ICMP payload is reversed */ - ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX], - &ip4->dst_address); - ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX], - &ip4->src_address); - session->key.cs_proto = ip4->protocol; - session->key.cs_port[VLIB_TX] = udp->src_port; - session->key.cs_port[VLIB_RX] = udp->dst_port; } else { @@ -550,18 +665,32 @@ cnat_session_make_key (vlib_buffer_t * b, ip_address_family_t af, if (PREDICT_FALSE (ip6->protocol == IP_PROTOCOL_ICMP6)) { icmp46_header_t *icmp = (icmp46_header_t *) (ip6 + 1); - if (!icmp6_type_is_error_message (icmp->type)) + if (icmp6_type_is_error_message (icmp->type)) + { + ip6 = (ip6_header_t *) (icmp + 2); /* Use inner packet */ + udp = (udp_header_t *) (ip6 + 1); + /* Swap dst & src for search as ICMP payload is reversed */ + ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX], + &ip6->dst_address); + ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX], + &ip6->src_address); + session->key.cs_proto = ip6->protocol; + session->key.cs_port[VLIB_TX] = udp->src_port; + session->key.cs_port[VLIB_RX] = udp->dst_port; + } + else if (icmp6_type_is_echo (icmp->type)) + { + cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1); + ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX], + &ip6->dst_address); + ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX], + &ip6->src_address); + session->key.cs_proto = ip6->protocol; + session->key.cs_port[VLIB_TX] = echo->identifier; + session->key.cs_port[VLIB_RX] = echo->identifier; + } + else goto error; - ip6 = (ip6_header_t *) (icmp + 2); /* Use inner packet */ - udp = (udp_header_t *) (ip6 + 1); - /* Swap dst & src for search as ICMP payload is reversed */ - ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX], - &ip6->dst_address); - ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX], - &ip6->src_address); - session->key.cs_proto = ip6->protocol; - session->key.cs_port[VLIB_TX] = udp->src_port; - session->key.cs_port[VLIB_RX] = udp->dst_port; } else { diff --git a/src/plugins/cnat/cnat_node_snat.c b/src/plugins/cnat/cnat_node_snat.c index aaa9e162ef0..d6c49cf9174 100644 --- a/src/plugins/cnat/cnat_node_snat.c +++ b/src/plugins/cnat/cnat_node_snat.c @@ -25,8 +25,9 @@ typedef enum cnat_snat_next_ typedef struct cnat_snat_trace_ { - u32 found; cnat_session_t session; + u32 found_session; + u32 created_session; } cnat_snat_trace_t; vlib_node_registration_t cnat_snat_ip4_node; @@ -39,8 +40,11 @@ format_cnat_snat_trace (u8 * s, va_list * args) CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); cnat_snat_trace_t *t = va_arg (*args, cnat_snat_trace_t *); - if (t->found) + if (t->found_session) s = format (s, "found: %U", format_cnat_session, &t->session, 1); + else if (t->created_session) + s = format (s, "created: %U\n tr: %U", + format_cnat_session, &t->session, 1); else s = format (s, "not found"); return s; @@ -56,6 +60,7 @@ cnat_snat_inline (vlib_main_t * vm, cnat_node_ctx_t * ctx, int rv, cnat_session_t * session) { cnat_main_t *cm = &cnat_main; + int created_session = 0; ip4_header_t *ip4; ip_protocol_t iproto; ip6_header_t *ip6; @@ -126,10 +131,9 @@ cnat_snat_inline (vlib_main_t * vm, &ip6->dst_address); } - /* Port allocation, first try to use the original port, allocate one - if it is already used */ - sport = udp0->src_port; - rv = cnat_allocate_port (cm, &sport); + + sport = 0; + rv = cnat_allocate_port (&sport, iproto); if (rv) { vlib_node_increment_counter (vm, cnat_snat_ip4_node.index, @@ -137,13 +141,16 @@ cnat_snat_inline (vlib_main_t * vm, next0 = CNAT_SNAT_NEXT_DROP; goto trace; } - session->value.cs_port[VLIB_RX] = sport; - session->value.cs_port[VLIB_TX] = udp0->dst_port; + session->value.cs_port[VLIB_TX] = sport; + if (iproto == IP_PROTOCOL_TCP || iproto == IP_PROTOCOL_UDP) + session->value.cs_port[VLIB_TX] = udp0->dst_port; + session->value.cs_lbi = INDEX_INVALID; session->value.flags = CNAT_SESSION_FLAG_NO_CLIENT | CNAT_SESSION_FLAG_ALLOC_PORT; + created_session = 1; cnat_session_create (session, ctx, CNAT_SESSION_FLAG_HAS_SNAT); } @@ -160,7 +167,9 @@ trace: t = vlib_add_trace (vm, node, b, sizeof (*t)); - if (NULL != session) + t->found_session = !rv; + t->created_session = created_session; + if (t->found_session || t->created_session) clib_memcpy (&t->session, session, sizeof (t->session)); } return next0; diff --git a/src/plugins/cnat/cnat_node_vip.c b/src/plugins/cnat/cnat_node_vip.c index 10f228f974d..d041606786b 100644 --- a/src/plugins/cnat/cnat_node_vip.c +++ b/src/plugins/cnat/cnat_node_vip.c @@ -70,7 +70,6 @@ cnat_vip_inline (vlib_main_t * vm, cnat_node_ctx_t * ctx, int rv, cnat_session_t * session) { vlib_combined_counter_main_t *cntm = &cnat_translation_counters; - cnat_main_t *cm = &cnat_main; const cnat_translation_t *ct = NULL; ip4_header_t *ip4 = NULL; ip_protocol_t iproto; @@ -201,7 +200,7 @@ cnat_vip_inline (vlib_main_t * vm, && (rsession_flags & CNAT_SESSION_FLAG_HAS_SNAT)) { sport = 0; /* force allocation */ session->value.flags |= CNAT_SESSION_FLAG_ALLOC_PORT; - rv = cnat_allocate_port (cm, &sport); + rv = cnat_allocate_port (&sport, iproto); if (rv) { vlib_node_increment_counter (vm, cnat_vip_ip4_node.index, diff --git a/src/plugins/cnat/cnat_session.c b/src/plugins/cnat/cnat_session.c index 7f95e1bc501..4259f42f398 100644 --- a/src/plugins/cnat/cnat_session.c +++ b/src/plugins/cnat/cnat_session.c @@ -128,7 +128,7 @@ cnat_session_free (cnat_session_t * session) clib_bihash_kv_40_48_t *bkey = (clib_bihash_kv_40_48_t *) session; /* age it */ if (session->value.flags & CNAT_SESSION_FLAG_ALLOC_PORT) - cnat_free_port (session->value.cs_port[VLIB_RX]); + cnat_free_port (session->value.cs_port[VLIB_RX], session->key.cs_proto); if (!(session->value.flags & CNAT_SESSION_FLAG_NO_CLIENT)) cnat_client_free_by_ip (&session->key.cs_ip[VLIB_TX], session->key.cs_af); cnat_timestamp_free (session->value.cs_ts_index); diff --git a/src/plugins/cnat/cnat_types.c b/src/plugins/cnat/cnat_types.c index ae485a48d79..9db953f0174 100644 --- a/src/plugins/cnat/cnat_types.c +++ b/src/plugins/cnat/cnat_types.c @@ -80,9 +80,14 @@ cnat_types_init (vlib_main_t * vm) CNAT_FIB_SOURCE_PRIORITY, FIB_SOURCE_BH_SIMPLE); + clib_rwlock_init (&cnat_main.ts_lock); - clib_spinlock_init (&cnat_main.src_ports_lock); - clib_bitmap_validate (cnat_main.src_ports, UINT16_MAX); + vec_validate (cnat_main.src_ports, CNAT_N_SPORT_PROTO); + for (int i = 0; i < CNAT_N_SPORT_PROTO; i++) + { + clib_spinlock_init (&cnat_main.src_ports[i].lock); + clib_bitmap_validate (cnat_main.src_ports[i].bmap, UINT16_MAX); + } throttle_init (&cnat_throttle, n_vlib_mains, 1e-3); return (NULL); diff --git a/src/plugins/cnat/cnat_types.h b/src/plugins/cnat/cnat_types.h index ab59aaf2f37..c9c0b70b8c3 100644 --- a/src/plugins/cnat/cnat_types.h +++ b/src/plugins/cnat/cnat_types.h @@ -49,6 +49,15 @@ #define MIN_SRC_PORT ((u16) 0xC000) +typedef enum +{ + CNAT_SPORT_PROTO_TCP, + CNAT_SPORT_PROTO_UDP, + CNAT_SPORT_PROTO_ICMP, + CNAT_SPORT_PROTO_ICMP6, + CNAT_N_SPORT_PROTO +} cnat_sport_proto_t; + typedef struct cnat_endpoint_t_ { ip_address_t ce_ip; @@ -61,7 +70,11 @@ typedef struct cnat_endpoint_tuple_t_ cnat_endpoint_t src_ep; } cnat_endpoint_tuple_t; - +typedef struct +{ + u16 identifier; + u16 sequence; +} cnat_echo_header_t; typedef struct { @@ -80,6 +93,15 @@ typedef struct ip6_address_t ip_masks[129]; } cnat_snat_pfx_table_t; +typedef struct cnat_src_port_allocator_ +{ + /* Source ports bitmap for snat */ + clib_bitmap_t *bmap; + + /* Lock for src_ports access */ + clib_spinlock_t lock; +} cnat_src_port_allocator_t; + typedef struct cnat_main_ { /* Memory size of the session bihash */ @@ -113,11 +135,8 @@ typedef struct cnat_main_ /* Lock for the timestamp pool */ clib_rwlock_t ts_lock; - /* Source ports bitmap for snat */ - clib_bitmap_t *src_ports; - - /* Lock for src_ports access */ - clib_spinlock_t src_ports_lock; + /* Per proto source ports allocator for snat */ + cnat_src_port_allocator_t *src_ports; /* Ip4 Address to use for source NATing */ ip4_address_t snat_ip4; @@ -265,33 +284,59 @@ cnat_timestamp_free (u32 index) clib_rwlock_writer_unlock (&cnat_main.ts_lock); } -always_inline void -cnat_free_port (u16 port) +always_inline cnat_src_port_allocator_t * +cnat_get_src_port_allocator (ip_protocol_t iproto) { cnat_main_t *cm = &cnat_main; - clib_spinlock_lock (&cm->src_ports_lock); - clib_bitmap_set_no_check (cm->src_ports, port, 0); - clib_spinlock_unlock (&cm->src_ports_lock); + switch (iproto) + { + case IP_PROTOCOL_TCP: + return &cm->src_ports[CNAT_SPORT_PROTO_TCP]; + case IP_PROTOCOL_UDP: + return &cm->src_ports[CNAT_SPORT_PROTO_UDP]; + case IP_PROTOCOL_ICMP: + return &cm->src_ports[CNAT_SPORT_PROTO_ICMP]; + case IP_PROTOCOL_ICMP6: + return &cm->src_ports[CNAT_SPORT_PROTO_ICMP6]; + default: + return 0; + } +} + +always_inline void +cnat_free_port (u16 port, ip_protocol_t iproto) +{ + cnat_src_port_allocator_t *ca; + ca = cnat_get_src_port_allocator (iproto); + if (!ca) + return; + clib_spinlock_lock (&ca->lock); + clib_bitmap_set_no_check (ca->bmap, port, 0); + clib_spinlock_unlock (&ca->lock); } always_inline int -cnat_allocate_port (cnat_main_t * cm, u16 * port) +cnat_allocate_port (u16 * port, ip_protocol_t iproto) { *port = clib_net_to_host_u16 (*port); if (*port == 0) *port = MIN_SRC_PORT; - clib_spinlock_lock (&cm->src_ports_lock); - if (clib_bitmap_get_no_check (cm->src_ports, *port)) + cnat_src_port_allocator_t *ca; + ca = cnat_get_src_port_allocator (iproto); + if (!ca) + return -1; + clib_spinlock_lock (&ca->lock); + if (clib_bitmap_get_no_check (ca->bmap, *port)) { - *port = clib_bitmap_next_clear (cm->src_ports, *port); + *port = clib_bitmap_next_clear (ca->bmap, *port); if (PREDICT_FALSE (*port >= UINT16_MAX)) - *port = clib_bitmap_next_clear (cm->src_ports, MIN_SRC_PORT); + *port = clib_bitmap_next_clear (ca->bmap, MIN_SRC_PORT); if (PREDICT_FALSE (*port >= UINT16_MAX)) return -1; } - clib_bitmap_set_no_check (cm->src_ports, *port, 1); + clib_bitmap_set_no_check (ca->bmap, *port, 1); *port = clib_host_to_net_u16 (*port); - clib_spinlock_unlock (&cm->src_ports_lock); + clib_spinlock_unlock (&ca->lock); return 0; } diff --git a/src/plugins/cnat/test/test_cnat.py b/src/plugins/cnat/test/test_cnat.py index 34cd8b58240..3f8d33cec4e 100644 --- a/src/plugins/cnat/test/test_cnat.py +++ b/src/plugins/cnat/test/test_cnat.py @@ -10,6 +10,7 @@ from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP, TCP, ICMP from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror from scapy.layers.inet6 import IPv6, IPerror6, ICMPv6DestUnreach +from scapy.layers.inet6 import ICMPv6EchoRequest, ICMPv6EchoReply import struct @@ -123,34 +124,6 @@ class VppCNatTranslation(VppObject): return c[0][self.id] -class VppCNATSourceNat(VppObject): - - def __init__(self, test, address, exclude_subnets=[]): - self._test = test - self.address = address - self.exclude_subnets = exclude_subnets - - def add_vpp_config(self): - a = ip_address(self.address) - if 4 == a.version: - self._test.vapi.cnat_set_snat_addresses(snat_ip4=self.address) - else: - self._test.vapi.cnat_set_snat_addresses(snat_ip6=self.address) - for subnet in self.exclude_subnets: - self.cnat_exclude_subnet(subnet, True) - - def cnat_exclude_subnet(self, exclude_subnet, isAdd=True): - add = 1 if isAdd else 0 - self._test.vapi.cnat_add_del_snat_prefix( - prefix=exclude_subnet, is_add=add) - - def query_vpp_config(self): - return False - - def remove_vpp_config(self): - return False - - class TestCNatTranslation(VppTestCase): """ CNat Translation """ extra_vpp_punt_config = ["cnat", "{", @@ -568,49 +541,219 @@ class TestCNatSourceNAT(VppTestCase): i.config_ip6() i.resolve_ndp() + self.pg0.configure_ipv6_neighbors() + self.pg0.configure_ipv4_neighbors() + self.pg1.generate_remote_hosts(2) + self.pg1.configure_ipv4_neighbors() + self.pg1.configure_ipv6_neighbors() + + self.vapi.cli("test cnat scanner off") + self.vapi.cnat_set_snat_addresses( + snat_ip4=self.pg2.remote_hosts[0].ip4, + snat_ip6=self.pg2.remote_hosts[0].ip6) + self.vapi.feature_enable_disable( + enable=1, + arc_name="ip6-unicast", + feature_name="ip6-cnat-snat", + sw_if_index=self.pg0.sw_if_index) + self.vapi.feature_enable_disable( + enable=1, + arc_name="ip4-unicast", + feature_name="ip4-cnat-snat", + sw_if_index=self.pg0.sw_if_index) + def tearDown(self): + self.vapi.cnat_session_purge() for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.admin_down() super(TestCNatSourceNAT, self).tearDown() - def cnat_set_snat_address(self, srcNatAddr, interface, isV6=False): - t1 = VppCNATSourceNat(self, srcNatAddr) - t1.add_vpp_config() - cnat_arc_name = "ip6-unicast" if isV6 else "ip4-unicast" - cnat_feature_name = "ip6-cnat-snat" if isV6 else "ip4-cnat-snat" - self.vapi.feature_enable_disable( - enable=1, - arc_name=cnat_arc_name, - feature_name=cnat_feature_name, - sw_if_index=interface.sw_if_index) + def test_snat_v6(self): + # """ CNat Source Nat v6 """ + self.sourcenat_test_tcp_udp_conf(TCP, isV6=True) + self.sourcenat_test_tcp_udp_conf(UDP, isV6=True) + self.sourcenat_test_icmp_err_conf(isV6=True) + self.sourcenat_test_icmp_echo6_conf() - return t1 + def test_snat_v4(self): + # """ CNat Source Nat v4 """ + self.sourcenat_test_tcp_udp_conf(TCP) + self.sourcenat_test_tcp_udp_conf(UDP) + self.sourcenat_test_icmp_err_conf() + self.sourcenat_test_icmp_echo4_conf() - def cnat_test_sourcenat(self, srcNatAddr, l4p=TCP, isV6=False): - ip_v = "ip6" if isV6 else "ip4" - IP46 = IPv6 if isV6 else IP - sports = [1234, 1235, 1236] - dports = [6661, 6662, 6663] + def sourcenat_test_icmp_echo6_conf(self): + sports = [1234, 1235] + dports = [6661, 6662] - self.pg0.generate_remote_hosts(1) - self.pg0.configure_ipv4_neighbors() - self.pg0.configure_ipv6_neighbors() - self.pg1.generate_remote_hosts(len(sports)) - self.pg1.configure_ipv4_neighbors() - self.pg1.configure_ipv6_neighbors() + for nbr, remote_host in enumerate(self.pg1.remote_hosts): + client_addr = self.pg0.remote_hosts[0].ip6 + remote_addr = self.pg1.remote_hosts[nbr].ip6 + src_nat_addr = self.pg2.remote_hosts[0].ip6 - self.vapi.cli("test cnat scanner on") - t1 = self.cnat_set_snat_address(srcNatAddr, self.pg0, isV6) + # ping from pods to outside network + p1 = ( + Ether(dst=self.pg0.local_mac, + src=self.pg0.remote_hosts[0].mac) / + IPv6(src=client_addr, dst=remote_addr) / + ICMPv6EchoRequest(id=0xfeed) / + Raw()) + + rxs = self.send_and_expect( + self.pg0, + p1 * N_PKTS, + self.pg1) + + for rx in rxs: + self.assertEqual(rx[IPv6].src, src_nat_addr) + self.assert_packet_checksums_valid(rx) + + received_id = rx[0][ICMPv6EchoRequest].id + # ping reply from outside to pods + p2 = ( + Ether(dst=self.pg1.local_mac, + src=self.pg1.remote_hosts[nbr].mac) / + IPv6(src=remote_addr, dst=src_nat_addr) / + ICMPv6EchoReply(id=received_id)) + rxs = self.send_and_expect( + self.pg1, + p2 * N_PKTS, + self.pg0) + + for rx in rxs: + self.assert_packet_checksums_valid(rx) + self.assertEqual(rx[IPv6].src, remote_addr) + self.assertEqual(rx[ICMPv6EchoReply].id, 0xfeed) + + def sourcenat_test_icmp_echo4_conf(self): + sports = [1234, 1235] + dports = [6661, 6662] + + for nbr, remote_host in enumerate(self.pg1.remote_hosts): + IP46 = IP + client_addr = self.pg0.remote_hosts[0].ip4 + remote_addr = self.pg1.remote_hosts[nbr].ip4 + src_nat_addr = self.pg2.remote_hosts[0].ip4 + + # ping from pods to outside network + p1 = ( + Ether(dst=self.pg0.local_mac, + src=self.pg0.remote_hosts[0].mac) / + IP46(src=client_addr, dst=remote_addr) / + ICMP(type=8, id=0xfeed) / + Raw()) + + rxs = self.send_and_expect( + self.pg0, + p1 * N_PKTS, + self.pg1) + + for rx in rxs: + self.assertEqual(rx[IP46].src, src_nat_addr) + self.assert_packet_checksums_valid(rx) + + received_id = rx[0][ICMP].id + # ping reply from outside to pods + p2 = ( + Ether(dst=self.pg1.local_mac, + src=self.pg1.remote_hosts[nbr].mac) / + IP46(src=remote_addr, dst=src_nat_addr) / + ICMP(type=0, id=received_id)) + rxs = self.send_and_expect( + self.pg1, + p2 * N_PKTS, + self.pg0) + + for rx in rxs: + self.assert_packet_checksums_valid(rx) + self.assertEqual(rx[IP46].src, remote_addr) + self.assertEqual(rx[ICMP].id, 0xfeed) + + def sourcenat_test_icmp_err_conf(self, isV6=False): + sports = [1234, 1235] + dports = [6661, 6662] for nbr, remote_host in enumerate(self.pg1.remote_hosts): if isV6: + IP46 = IPv6 client_addr = self.pg0.remote_hosts[0].ip6 remote_addr = self.pg1.remote_hosts[nbr].ip6 + src_nat_addr = self.pg2.remote_hosts[0].ip6 + ICMP46 = ICMPv6DestUnreach + ICMPelem = ICMPv6DestUnreach(code=1) + IP46error = IPerror6 else: + IP46 = IP client_addr = self.pg0.remote_hosts[0].ip4 remote_addr = self.pg1.remote_hosts[nbr].ip4 + src_nat_addr = self.pg2.remote_hosts[0].ip4 + IP46error = IPerror + ICMP46 = ICMP + ICMPelem = ICMP(type=11) + + # from pods to outside network + p1 = ( + Ether(dst=self.pg0.local_mac, + src=self.pg0.remote_hosts[0].mac) / + IP46(src=client_addr, dst=remote_addr) / + TCP(sport=sports[nbr], dport=dports[nbr]) / + Raw()) + + rxs = self.send_and_expect( + self.pg0, + p1 * N_PKTS, + self.pg1) + for rx in rxs: + self.assert_packet_checksums_valid(rx) + self.assertEqual(rx[IP46].dst, remote_addr) + self.assertEqual(rx[TCP].dport, dports[nbr]) + self.assertEqual(rx[IP46].src, src_nat_addr) + sport = rx[TCP].sport + + InnerIP = rxs[0][IP46] + # from outside to pods, ICMP error + p2 = ( + Ether(dst=self.pg1.local_mac, + src=self.pg1.remote_hosts[nbr].mac) / + IP46(src=remote_addr, dst=src_nat_addr) / + ICMPelem / InnerIP) + + rxs = self.send_and_expect( + self.pg1, + p2 * N_PKTS, + self.pg0) + + for rx in rxs: + self.assert_packet_checksums_valid(rx) + self.assertEqual(rx[IP46].src, remote_addr) + self.assertEqual(rx[ICMP46][IP46error].src, client_addr) + self.assertEqual(rx[ICMP46][IP46error].dst, remote_addr) + self.assertEqual(rx[ICMP46][IP46error] + [TCPerror].sport, sports[nbr]) + self.assertEqual(rx[ICMP46][IP46error] + [TCPerror].dport, dports[nbr]) + + def sourcenat_test_tcp_udp_conf(self, l4p, isV6=False): + sports = [1234, 1235] + dports = [6661, 6662] + + for nbr, remote_host in enumerate(self.pg1.remote_hosts): + if isV6: + IP46 = IPv6 + client_addr = self.pg0.remote_hosts[0].ip6 + remote_addr = self.pg1.remote_hosts[nbr].ip6 + src_nat_addr = self.pg2.remote_hosts[0].ip6 + exclude_prefix = ip_network( + "%s/100" % remote_addr, strict=False) + else: + IP46 = IP + client_addr = self.pg0.remote_hosts[0].ip4 + remote_addr = self.pg1.remote_hosts[nbr].ip4 + src_nat_addr = self.pg2.remote_hosts[0].ip4 + exclude_prefix = ip_network( + "%s/16" % remote_addr, strict=False) # from pods to outside network p1 = ( Ether(dst=self.pg0.local_mac, @@ -627,14 +770,14 @@ class TestCNatSourceNAT(VppTestCase): self.assert_packet_checksums_valid(rx) self.assertEqual(rx[IP46].dst, remote_addr) self.assertEqual(rx[l4p].dport, dports[nbr]) - self.assertEqual(rx[IP46].src, srcNatAddr) + self.assertEqual(rx[IP46].src, src_nat_addr) sport = rx[l4p].sport # from outside to pods p2 = ( Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[nbr].mac) / - IP46(src=remote_addr, dst=srcNatAddr) / + IP46(src=remote_addr, dst=src_nat_addr) / l4p(sport=dports[nbr], dport=sport) / Raw()) @@ -651,11 +794,7 @@ class TestCNatSourceNAT(VppTestCase): self.assertEqual(rx[IP46].src, remote_addr) # add remote host to exclude list - subnet_mask = 100 if isV6 else 16 - subnet = "%s/%d" % (remote_addr, subnet_mask) - exclude_subnet = ip_network(subnet, strict=False) - - t1.cnat_exclude_subnet(exclude_subnet) + self.vapi.cnat_add_del_snat_prefix(prefix=exclude_prefix, is_add=1) self.vapi.cnat_session_purge() rxs = self.send_and_expect( @@ -669,7 +808,7 @@ class TestCNatSourceNAT(VppTestCase): self.assertEqual(rx[IP46].src, client_addr) # remove remote host from exclude list - t1.cnat_exclude_subnet(exclude_subnet, isAdd=False) + self.vapi.cnat_add_del_snat_prefix(prefix=exclude_prefix, is_add=0) self.vapi.cnat_session_purge() rxs = self.send_and_expect( @@ -681,17 +820,9 @@ class TestCNatSourceNAT(VppTestCase): self.assert_packet_checksums_valid(rx) self.assertEqual(rx[IP46].dst, remote_addr) self.assertEqual(rx[l4p].dport, dports[nbr]) - self.assertEqual(rx[IP46].src, srcNatAddr) + self.assertEqual(rx[IP46].src, src_nat_addr) - def test_cnat6_sourcenat(self): - # """ CNat Source Nat ipv6 """ - self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip6, TCP, True) - self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip6, UDP, True) - - def test_cnat4_sourcenat(self): - # """ CNat Source Nat ipv4 """ - self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip4, TCP) - self.cnat_test_sourcenat(self.pg2.remote_hosts[0].ip4, UDP) + self.vapi.cnat_session_purge() if __name__ == '__main__': -- 2.16.6