From 4b089f27b3eda69be2fc8a9ef9f74d39cd00fc7f Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 17 Apr 2018 18:04:57 +0200 Subject: [PATCH] ipsec: support UDP encap/decap for NAT traversal Change-Id: I65c12617ad49e4d5ef242e53988782f0cefa5684 Signed-off-by: Klement Sekera --- src/vnet/ipsec/esp.h | 8 ++ src/vnet/ipsec/esp_encrypt.c | 74 ++++++++---- src/vnet/ipsec/ipsec.api | 4 + src/vnet/ipsec/ipsec.c | 6 +- src/vnet/ipsec/ipsec.h | 4 +- src/vnet/ipsec/ipsec_api.c | 3 +- src/vnet/ipsec/ipsec_cli.c | 7 +- src/vnet/ipsec/ipsec_input.c | 16 ++- src/vnet/udp/udp.h | 1 + test/test_ipsec_ah.py | 8 +- test/test_ipsec_esp.py | 12 +- test/test_ipsec_nat.py | 260 +++++++++++++++++++++++++++++++++++++++++++ test/vpp_papi_provider.py | 11 +- 13 files changed, 366 insertions(+), 48 deletions(-) create mode 100644 test/test_ipsec_nat.py diff --git a/src/vnet/ipsec/esp.h b/src/vnet/ipsec/esp.h index 8f7f8fab381..73d43262e26 100644 --- a/src/vnet/ipsec/esp.h +++ b/src/vnet/ipsec/esp.h @@ -42,6 +42,14 @@ typedef CLIB_PACKED (struct { }) ip4_and_esp_header_t; /* *INDENT-ON* */ +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + ip4_header_t ip4; + udp_header_t udp; + esp_header_t esp; +}) ip4_and_udp_and_esp_header_t; +/* *INDENT-ON* */ + /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { ip6_header_t ip6; diff --git a/src/vnet/ipsec/esp_encrypt.c b/src/vnet/ipsec/esp_encrypt.c index 44a0451d29a..0ce5e54aaeb 100644 --- a/src/vnet/ipsec/esp_encrypt.c +++ b/src/vnet/ipsec/esp_encrypt.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,7 @@ typedef struct { u32 spi; u32 seq; + u8 udp_encap; ipsec_crypto_alg_t crypto_alg; ipsec_integ_alg_t integ_alg; } esp_encrypt_trace_t; @@ -77,10 +79,11 @@ format_esp_encrypt_trace (u8 * s, va_list * args) CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); esp_encrypt_trace_t *t = va_arg (*args, esp_encrypt_trace_t *); - s = format (s, "esp: spi %u seq %u crypto %U integrity %U", + s = format (s, "esp: spi %u seq %u crypto %U integrity %U%s", t->spi, t->seq, format_ipsec_crypto_alg, t->crypto_alg, - format_ipsec_integ_alg, t->integ_alg); + format_ipsec_integ_alg, t->integ_alg, + t->udp_encap ? " udp-encap-enabled" : ""); return s; } @@ -155,13 +158,14 @@ esp_encrypt_node_fn (vlib_main_t * vm, vlib_buffer_t *i_b0, *o_b0 = 0; u32 sa_index0; ipsec_sa_t *sa0; - ip4_and_esp_header_t *ih0, *oh0 = 0; + ip4_and_esp_header_t *oh0 = 0; ip6_and_esp_header_t *ih6_0, *oh6_0 = 0; + ip4_and_udp_and_esp_header_t *iuh0, *ouh0 = 0; uword last_empty_buffer; esp_header_t *o_esp0; esp_footer_t *f0; u8 is_ipv6; - u8 ip_hdr_size; + u8 ip_udp_hdr_size; u8 next_hdr_type; u32 ip_proto = 0; u8 transport_mode = 0; @@ -198,7 +202,7 @@ esp_encrypt_node_fn (vlib_main_t * vm, o_b0 = vlib_get_buffer (vm, o_bi0); o_b0->flags = VLIB_BUFFER_TOTAL_LENGTH_VALID; o_b0->current_data = sizeof (ethernet_header_t); - ih0 = vlib_buffer_get_current (i_b0); + iuh0 = vlib_buffer_get_current (i_b0); vlib_prefetch_buffer_with_index (vm, empty_buffers[last_empty_buffer - 1], STORE); @@ -211,18 +215,18 @@ esp_encrypt_node_fn (vlib_main_t * vm, /* is ipv6 */ if (PREDICT_FALSE - ((ih0->ip4.ip_version_and_header_length & 0xF0) == 0x60)) + ((iuh0->ip4.ip_version_and_header_length & 0xF0) == 0x60)) { is_ipv6 = 1; ih6_0 = vlib_buffer_get_current (i_b0); - ip_hdr_size = sizeof (ip6_header_t); next_hdr_type = IP_PROTOCOL_IPV6; oh6_0 = vlib_buffer_get_current (o_b0); - o_esp0 = vlib_buffer_get_current (o_b0) + sizeof (ip6_header_t); oh6_0->ip6.ip_version_traffic_class_and_flow_label = ih6_0->ip6.ip_version_traffic_class_and_flow_label; oh6_0->ip6.protocol = IP_PROTOCOL_IPSEC_ESP; + ip_udp_hdr_size = sizeof (ip6_header_t); + o_esp0 = vlib_buffer_get_current (o_b0) + ip_udp_hdr_size; oh6_0->ip6.hop_limit = 254; oh6_0->ip6.src_address.as_u64[0] = ih6_0->ip6.src_address.as_u64[0]; @@ -232,8 +236,8 @@ esp_encrypt_node_fn (vlib_main_t * vm, ih6_0->ip6.dst_address.as_u64[0]; oh6_0->ip6.dst_address.as_u64[1] = ih6_0->ip6.dst_address.as_u64[1]; - oh6_0->esp.spi = clib_net_to_host_u32 (sa0->spi); - oh6_0->esp.seq = clib_net_to_host_u32 (sa0->seq); + o_esp0->spi = clib_net_to_host_u32 (sa0->spi); + o_esp0->seq = clib_net_to_host_u32 (sa0->seq); ip_proto = ih6_0->ip6.protocol; next0 = ESP_ENCRYPT_NEXT_IP6_LOOKUP; @@ -241,22 +245,37 @@ esp_encrypt_node_fn (vlib_main_t * vm, else { is_ipv6 = 0; - ip_hdr_size = sizeof (ip4_header_t); next_hdr_type = IP_PROTOCOL_IP_IN_IP; oh0 = vlib_buffer_get_current (o_b0); - o_esp0 = vlib_buffer_get_current (o_b0) + sizeof (ip4_header_t); + ouh0 = vlib_buffer_get_current (o_b0); oh0->ip4.ip_version_and_header_length = 0x45; - oh0->ip4.tos = ih0->ip4.tos; + oh0->ip4.tos = iuh0->ip4.tos; oh0->ip4.fragment_id = 0; oh0->ip4.flags_and_fragment_offset = 0; oh0->ip4.ttl = 254; - oh0->ip4.protocol = IP_PROTOCOL_IPSEC_ESP; - oh0->ip4.src_address.as_u32 = ih0->ip4.src_address.as_u32; - oh0->ip4.dst_address.as_u32 = ih0->ip4.dst_address.as_u32; - oh0->esp.spi = clib_net_to_host_u32 (sa0->spi); - oh0->esp.seq = clib_net_to_host_u32 (sa0->seq); - ip_proto = ih0->ip4.protocol; + if (sa0->udp_encap) + { + ouh0->udp.src_port = + clib_host_to_net_u16 (UDP_DST_PORT_ipsec); + ouh0->udp.dst_port = + clib_host_to_net_u16 (UDP_DST_PORT_ipsec); + ouh0->udp.checksum = 0; + ouh0->ip4.protocol = IP_PROTOCOL_UDP; + ip_udp_hdr_size = + sizeof (udp_header_t) + sizeof (ip4_header_t); + } + else + { + oh0->ip4.protocol = IP_PROTOCOL_IPSEC_ESP; + ip_udp_hdr_size = sizeof (ip4_header_t); + } + o_esp0 = vlib_buffer_get_current (o_b0) + ip_udp_hdr_size; + oh0->ip4.src_address.as_u32 = iuh0->ip4.src_address.as_u32; + oh0->ip4.dst_address.as_u32 = iuh0->ip4.dst_address.as_u32; + o_esp0->spi = clib_net_to_host_u32 (sa0->spi); + o_esp0->seq = clib_net_to_host_u32 (sa0->seq); + ip_proto = iuh0->ip4.protocol; next0 = ESP_ENCRYPT_NEXT_IP4_LOOKUP; } @@ -299,7 +318,7 @@ esp_encrypt_node_fn (vlib_main_t * vm, vnet_buffer (o_b0)->sw_if_index[VLIB_TX] = vnet_buffer (i_b0)->sw_if_index[VLIB_TX]; } - vlib_buffer_advance (i_b0, ip_hdr_size); + vlib_buffer_advance (i_b0, ip_udp_hdr_size); } ASSERT (sa0->crypto_alg < IPSEC_CRYPTO_N_ALG); @@ -327,7 +346,7 @@ esp_encrypt_node_fn (vlib_main_t * vm, f0->pad_length = pad_bytes; f0->next_header = next_hdr_type; - o_b0->current_length = ip_hdr_size + sizeof (esp_header_t) + + o_b0->current_length = ip_udp_hdr_size + sizeof (esp_header_t) + BLOCK_SIZE * blocks + IV_SIZE; vnet_buffer (o_b0)->sw_if_index[VLIB_RX] = @@ -338,14 +357,14 @@ esp_encrypt_node_fn (vlib_main_t * vm, RAND_bytes (iv, sizeof (iv)); clib_memcpy ((u8 *) vlib_buffer_get_current (o_b0) + - ip_hdr_size + sizeof (esp_header_t), iv, + ip_udp_hdr_size + sizeof (esp_header_t), iv, em->ipsec_proto_main_crypto_algs[sa0-> crypto_alg].iv_size); esp_encrypt_cbc (sa0->crypto_alg, (u8 *) vlib_buffer_get_current (i_b0), (u8 *) vlib_buffer_get_current (o_b0) + - ip_hdr_size + sizeof (esp_header_t) + + ip_udp_hdr_size + sizeof (esp_header_t) + IV_SIZE, BLOCK_SIZE * blocks, sa0->crypto_key, iv); } @@ -354,7 +373,7 @@ esp_encrypt_node_fn (vlib_main_t * vm, sa0->integ_key_len, (u8 *) o_esp0, o_b0->current_length - - ip_hdr_size, + ip_udp_hdr_size, vlib_buffer_get_current (o_b0) + o_b0->current_length, sa0->use_esn, sa0->seq_hi); @@ -371,6 +390,12 @@ esp_encrypt_node_fn (vlib_main_t * vm, oh0->ip4.length = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, o_b0)); oh0->ip4.checksum = ip4_header_checksum (&oh0->ip4); + if (sa0->udp_encap) + { + ouh0->udp.length = + clib_host_to_net_u16 (oh0->ip4.length - + ip4_header_bytes (&oh0->ip4)); + } } if (transport_mode) @@ -387,6 +412,7 @@ esp_encrypt_node_fn (vlib_main_t * vm, vlib_add_trace (vm, node, o_b0, sizeof (*tr)); tr->spi = sa0->spi; tr->seq = sa0->seq - 1; + tr->udp_encap = sa0->udp_encap; tr->crypto_alg = sa0->crypto_alg; tr->integ_alg = sa0->integ_alg; } diff --git a/src/vnet/ipsec/ipsec.api b/src/vnet/ipsec/ipsec.api index 5b8b04d9724..076583928de 100644 --- a/src/vnet/ipsec/ipsec.api +++ b/src/vnet/ipsec/ipsec.api @@ -130,6 +130,7 @@ autoreply define ipsec_spd_add_del_entry @param is_tunnel_ipv6 - IPsec tunnel mode is IPv6 if non-zero, else IPv4 tunnel only valid if is_tunnel is non-zero @param tunnel_src_address - IPsec tunnel source address IPv6 if is_tunnel_ipv6 is non-zero, else IPv4. Only valid if is_tunnel is non-zero @param tunnel_dst_address - IPsec tunnel destination address IPv6 if is_tunnel_ipv6 is non-zero, else IPv4. Only valid if is_tunnel is non-zero + @param udp_encap - enable UDP encapsulation for NAT traversal To be added: Anti-replay @@ -163,6 +164,7 @@ autoreply define ipsec_sad_add_del_entry u8 is_tunnel_ipv6; u8 tunnel_src_address[16]; u8 tunnel_dst_address[16]; + u8 udp_encap; }; /** \brief IPsec: Update Security Association keys @@ -587,6 +589,7 @@ define ipsec_sa_dump { @param last_seq_hi - high 32 bits of highest ESN received inbound @param replay_window - bit map of seq nums received relative to last_seq if using anti-replay @param total_data_size - total bytes sent or received + @param udp_encap - 1 if UDP encap enabled, 0 otherwise */ define ipsec_sa_details { u32 context; @@ -618,6 +621,7 @@ define ipsec_sa_details { u64 replay_window; u64 total_data_size; + u8 udp_encap; }; /** \brief Set key on IPsec interface diff --git a/src/vnet/ipsec/ipsec.c b/src/vnet/ipsec/ipsec.c index 4894022536b..928cafd5e25 100644 --- a/src/vnet/ipsec/ipsec.c +++ b/src/vnet/ipsec/ipsec.c @@ -19,12 +19,14 @@ #include #include #include +#include #include #include #include #include + ipsec_main_t ipsec_main; u32 @@ -411,7 +413,8 @@ ipsec_is_sa_used (u32 sa_index) } int -ipsec_add_del_sa (vlib_main_t * vm, ipsec_sa_t * new_sa, int is_add) +ipsec_add_del_sa (vlib_main_t * vm, ipsec_sa_t * new_sa, int is_add, + u8 udp_encap) { ipsec_main_t *im = &ipsec_main; ipsec_sa_t *sa = 0; @@ -450,6 +453,7 @@ ipsec_add_del_sa (vlib_main_t * vm, ipsec_sa_t * new_sa, int is_add) pool_get (im->sad, sa); clib_memcpy (sa, new_sa, sizeof (*sa)); sa_index = sa - im->sad; + sa->udp_encap = udp_encap ? 1 : 0; hash_set (im->sa_index_by_sa_id, sa->id, sa_index); if (im->cb.add_del_sa_sess_cb) { diff --git a/src/vnet/ipsec/ipsec.h b/src/vnet/ipsec/ipsec.h index bf9fab2c42c..5b548322d28 100644 --- a/src/vnet/ipsec/ipsec.h +++ b/src/vnet/ipsec/ipsec.h @@ -127,6 +127,7 @@ typedef struct u8 is_tunnel; u8 is_tunnel_ip6; + u8 udp_encap; ip46_address_t tunnel_src_addr; ip46_address_t tunnel_dst_addr; @@ -318,7 +319,8 @@ int ipsec_set_interface_spd (vlib_main_t * vm, u32 sw_if_index, u32 spd_id, int ipsec_add_del_spd (vlib_main_t * vm, u32 spd_id, int is_add); int ipsec_add_del_policy (vlib_main_t * vm, ipsec_policy_t * policy, int is_add); -int ipsec_add_del_sa (vlib_main_t * vm, ipsec_sa_t * new_sa, int is_add); +int ipsec_add_del_sa (vlib_main_t * vm, ipsec_sa_t * new_sa, int is_add, + u8 udp_encap); int ipsec_set_sa_key (vlib_main_t * vm, ipsec_sa_t * sa_update); u32 ipsec_get_sa_index_by_sa_id (u32 sa_id); diff --git a/src/vnet/ipsec/ipsec_api.c b/src/vnet/ipsec/ipsec_api.c index 74f993af85a..fc7d481ecb1 100644 --- a/src/vnet/ipsec/ipsec_api.c +++ b/src/vnet/ipsec/ipsec_api.c @@ -241,7 +241,7 @@ static void vl_api_ipsec_sad_add_del_entry_t_handler goto out; } - rv = ipsec_add_del_sa (vm, &sa, mp->is_add); + rv = ipsec_add_del_sa (vm, &sa, mp->is_add, mp->udp_encap); #else rv = VNET_API_ERROR_UNIMPLEMENTED; goto out; @@ -457,6 +457,7 @@ send_ipsec_sa_details (ipsec_sa_t * sa, vl_api_registration_t * reg, if (sa->use_anti_replay) mp->replay_window = clib_host_to_net_u64 (sa->replay_window); mp->total_data_size = clib_host_to_net_u64 (sa->total_data_size); + mp->udp_encap = sa->udp_encap; vl_api_send_msg (reg, (u8 *) mp); } diff --git a/src/vnet/ipsec/ipsec_cli.c b/src/vnet/ipsec/ipsec_cli.c index ef57d52ef67..238ef9774e1 100644 --- a/src/vnet/ipsec/ipsec_cli.c +++ b/src/vnet/ipsec/ipsec_cli.c @@ -176,7 +176,7 @@ ipsec_sa_add_del_command_fn (vlib_main_t * vm, goto done; } - ipsec_add_del_sa (vm, &sa, is_add); + ipsec_add_del_sa (vm, &sa, is_add, 0 /* enable nat traversal */ ); done: unformat_free (line_input); @@ -451,9 +451,10 @@ show_ipsec_command_fn (vlib_main_t * vm, /* *INDENT-OFF* */ pool_foreach (sa, im->sad, ({ if (sa->id) { - vlib_cli_output(vm, "sa %u spi %u mode %s protocol %s", sa->id, sa->spi, + vlib_cli_output(vm, "sa %u spi %u mode %s protocol %s%s", sa->id, sa->spi, sa->is_tunnel ? "tunnel" : "transport", - sa->protocol ? "esp" : "ah"); + sa->protocol ? "esp" : "ah", + sa->udp_encap ? " udp-encap-enabled" : ""); if (sa->protocol == IPSEC_PROTOCOL_ESP) { vlib_cli_output(vm, " crypto alg %U%s%U integrity alg %U%s%U", format_ipsec_crypto_alg, sa->crypto_alg, diff --git a/src/vnet/ipsec/ipsec_input.c b/src/vnet/ipsec/ipsec_input.c index 9aa5654c9da..08269d0244e 100644 --- a/src/vnet/ipsec/ipsec_input.c +++ b/src/vnet/ipsec/ipsec_input.c @@ -216,7 +216,9 @@ ipsec_input_ip4_node_fn (vlib_main_t * vm, ip0 = vlib_buffer_get_current (b0); - if (PREDICT_TRUE (ip0->protocol == IP_PROTOCOL_IPSEC_ESP)) + if (PREDICT_TRUE + (ip0->protocol == IP_PROTOCOL_IPSEC_ESP + || ip0->protocol == IP_PROTOCOL_UDP)) { #if 0 clib_warning @@ -228,6 +230,13 @@ ipsec_input_ip4_node_fn (vlib_main_t * vm, #endif esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0)); + if (PREDICT_FALSE (ip0->protocol == IP_PROTOCOL_UDP)) + { + esp0 = + (esp_header_t *) ((u8 *) esp0 + sizeof (udp_header_t)); + } + /* FIXME TODO missing check whether there is enough data inside + * IP/UDP to contain ESP header & stuff ? */ p0 = ipsec_input_protect_policy_match (spd0, clib_net_to_host_u32 (ip0->src_address. @@ -245,7 +254,7 @@ ipsec_input_ip4_node_fn (vlib_main_t * vm, vnet_buffer (b0)->ipsec.sad_index = p0->sa_index; vnet_buffer (b0)->ipsec.flags = 0; next0 = im->esp_decrypt_next_index; - vlib_buffer_advance (b0, ip4_header_bytes (ip0)); + vlib_buffer_advance (b0, ((u8 *) esp0 - (u8 *) ip0)); goto trace0; } @@ -255,7 +264,8 @@ ipsec_input_ip4_node_fn (vlib_main_t * vm, { ipsec_input_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); - if (ip0->protocol == IP_PROTOCOL_IPSEC_ESP) + if (ip0->protocol == IP_PROTOCOL_IPSEC_ESP || + ip0->protocol == IP_PROTOCOL_UDP) { if (p0) tr->sa_id = p0->sa_id; diff --git a/src/vnet/udp/udp.h b/src/vnet/udp/udp.h index c60dea07114..0382bc20d64 100644 --- a/src/vnet/udp/udp.h +++ b/src/vnet/udp/udp.h @@ -53,6 +53,7 @@ _ (3784, bfd4) \ _ (3785, bfd_echo4) \ _ (4341, lisp_gpe) \ _ (4342, lisp_cp) \ +_ (4500, ipsec) \ _ (4739, ipfix) \ _ (4789, vxlan) \ _ (4789, vxlan6) \ diff --git a/test/test_ipsec_ah.py b/test/test_ipsec_ah.py index 042bb88f5be..fc19d781561 100644 --- a/test/test_ipsec_ah.py +++ b/test/test_ipsec_ah.py @@ -99,14 +99,14 @@ class TestIpsecAh(VppTestCase): l_stopaddr, r_startaddr, r_stopaddr, - protocol=51) + protocol=socket.IPPROTO_AH) cls.vapi.ipsec_spd_add_del_entry( spd_id, l_startaddr, l_stopaddr, r_startaddr, r_stopaddr, - protocol=51, + protocol=socket.IPPROTO_AH, is_outbound=0) l_startaddr = l_stopaddr = socket.inet_pton( socket.AF_INET, cls.remote_pg0_lb_addr) @@ -164,14 +164,14 @@ class TestIpsecAh(VppTestCase): l_stopaddr, r_startaddr, r_stopaddr, - protocol=51) + protocol=socket.IPPROTO_AH) cls.vapi.ipsec_spd_add_del_entry( spd_id, l_startaddr, l_stopaddr, r_startaddr, r_stopaddr, - protocol=51, + protocol=socket.IPPROTO_AH, is_outbound=0) l_startaddr = l_stopaddr = cls.pg2.local_ip4n r_startaddr = r_stopaddr = cls.pg2.remote_ip4n diff --git a/test/test_ipsec_esp.py b/test/test_ipsec_esp.py index f6782749148..15ce4a96878 100644 --- a/test/test_ipsec_esp.py +++ b/test/test_ipsec_esp.py @@ -31,11 +31,11 @@ class TestIpsecEsp(VppTestCase): TUNNEL MODE: --- encrypt --- plain --- - |pg0| -------> |VPP| ------> |pg1| + |pg0| <------- |VPP| <------ |pg1| --- --- --- --- decrypt --- plain --- - |pg0| <------- |VPP| <------ |pg1| + |pg0| -------> |VPP| ------> |pg1| --- --- --- Note : IPv6 is not covered @@ -103,14 +103,14 @@ class TestIpsecEsp(VppTestCase): l_stopaddr, r_startaddr, r_stopaddr, - protocol=50) + protocol=socket.IPPROTO_ESP) cls.vapi.ipsec_spd_add_del_entry( spd_id, l_startaddr, l_stopaddr, r_startaddr, r_stopaddr, - protocol=50, + protocol=socket.IPPROTO_ESP, is_outbound=0) l_startaddr = l_stopaddr = socket.inet_pton( socket.AF_INET, cls.remote_pg0_lb_addr) @@ -172,14 +172,14 @@ class TestIpsecEsp(VppTestCase): l_stopaddr, r_startaddr, r_stopaddr, - protocol=50) + protocol=socket.IPPROTO_ESP) cls.vapi.ipsec_spd_add_del_entry( spd_id, l_startaddr, l_stopaddr, r_startaddr, r_stopaddr, - protocol=50, + protocol=socket.IPPROTO_ESP, is_outbound=0) l_startaddr = l_stopaddr = cls.pg2.local_ip4n r_startaddr = r_stopaddr = cls.pg2.remote_ip4n diff --git a/test/test_ipsec_nat.py b/test/test_ipsec_nat.py new file mode 100644 index 00000000000..9c22fbb559a --- /dev/null +++ b/test/test_ipsec_nat.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python + +import socket + +from scapy.layers.l2 import Ether +from scapy.layers.inet import ICMP, IP, TCP, UDP +from scapy.layers.ipsec import SecurityAssociation, ESP +from util import ppp, ppc +from framework import VppTestCase + + +class IPSecNATTestCase(VppTestCase): + """ IPSec/NAT + + TRANSPORT MODE: + + --- encrypt --- + |pg2| <-------> |VPP| + --- decrypt --- + + TUNNEL MODE: + + + public network | private network + --- encrypt --- plain --- + |pg0| <------- |VPP| <------ |pg1| + --- --- --- + + --- decrypt --- plain --- + |pg0| -------> |VPP| ------> |pg1| + --- --- --- + """ + + remote_pg0_client_addr = '1.1.1.1' + + @classmethod + def setUpClass(cls): + super(IPSecNATTestCase, cls).setUpClass() + cls.create_pg_interfaces(range(2)) + for i in cls.pg_interfaces: + i.configure_ipv4_neighbors() + i.admin_up() + i.config_ip4() + i.resolve_arp() + + cls.tcp_port_in = 6303 + cls.tcp_port_out = 6303 + cls.udp_port_in = 6304 + cls.udp_port_out = 6304 + cls.icmp_id_in = 6305 + cls.icmp_id_out = 6305 + cls.config_esp_tun() + cls.logger.info(cls.vapi.ppcli("show ipsec")) + + def create_stream_plain(self, src_mac, dst_mac, src_ip, dst_ip): + return [ + # TCP + Ether(src=src_mac, dst=dst_mac) / + IP(src=src_ip, dst=dst_ip) / + TCP(sport=self.tcp_port_in, dport=20), + # UDP + Ether(src=src_mac, dst=dst_mac) / + IP(src=src_ip, dst=dst_ip) / + UDP(sport=self.udp_port_in, dport=20), + # ICMP + Ether(src=src_mac, dst=dst_mac) / + IP(src=src_ip, dst=dst_ip) / + ICMP(id=self.icmp_id_in, type='echo-request') + ] + + def create_stream_encrypted(self, src_mac, dst_mac, src_ip, dst_ip, sa): + return [ + # TCP + Ether(src=src_mac, dst=dst_mac) / + sa.encrypt(IP(src=src_ip, dst=dst_ip) / + TCP(dport=self.tcp_port_out, sport=20)), + # UDP + Ether(src=src_mac, dst=dst_mac) / + sa.encrypt(IP(src=src_ip, dst=dst_ip) / + UDP(dport=self.udp_port_out, sport=20)), + # ICMP + Ether(src=src_mac, dst=dst_mac) / + sa.encrypt(IP(src=src_ip, dst=dst_ip) / + ICMP(id=self.icmp_id_out, type='echo-request')) + ] + + def check_checksum(self, pkt, layer): + """ Check checksum of the packet on given layer """ + new = pkt.__class__(str(pkt)) + del new[layer].chksum + new = new.__class__(str(new)) + self.assertEqual(new[layer].chksum, pkt[layer].chksum) + + def check_ip_checksum(self, pkt): + return self.check_checksum(pkt, 'IP') + + def check_tcp_checksum(self, pkt): + return self.check_checksum(pkt, 'TCP') + + def check_udp_checksum(self, pkt): + return self.check_checksum(pkt, 'UDP') + + def check_icmp_checksum(self, pkt): + return self.check_checksum(pkt, 'ICMP') + + def verify_capture_plain(self, capture): + for packet in capture: + try: + self.check_ip_checksum(packet) + self.assert_equal(packet[IP].src, self.pg0.remote_ip4, + "decrypted packet source address") + self.assert_equal(packet[IP].dst, self.pg1.remote_ip4, + "decrypted packet destination address") + if packet.haslayer(TCP): + self.assertFalse( + packet.haslayer(UDP), + "unexpected UDP header in decrypted packet") + self.assert_equal(packet[TCP].dport, self.tcp_port_in, + "decrypted packet TCP destination port") + self.check_tcp_checksum(packet) + elif packet.haslayer(UDP): + if packet[UDP].payload: + self.assertFalse( + packet[UDP][1].haslayer(UDP), + "unexpected UDP header in decrypted packet") + self.assert_equal(packet[UDP].dport, self.udp_port_in, + "decrypted packet UDP destination port") + else: + self.assertFalse( + packet.haslayer(UDP), + "unexpected UDP header in decrypted packet") + self.assert_equal(packet[ICMP].id, self.icmp_id_in, + "decrypted packet ICMP ID") + self.check_icmp_checksum(packet) + except Exception: + self.logger.error( + ppp("Unexpected or invalid plain packet:", packet)) + raise + + def verify_capture_encrypted(self, capture, sa): + for packet in capture: + try: + self.assertIn(ESP, packet[IP]) + decrypt_pkt = sa.decrypt(packet[IP]) + self.assert_equal(decrypt_pkt[IP].src, self.pg1.remote_ip4, + "encrypted packet source address") + self.assert_equal(decrypt_pkt[IP].dst, self.pg0.remote_ip4, + "encrypted packet destination address") + # if decrypt_pkt.haslayer(TCP): + # self.tcp_port_out = decrypt_pkt[TCP].sport + # elif decrypt_pkt.haslayer(UDP): + # self.udp_port_out = decrypt_pkt[UDP].sport + # else: + # self.icmp_id_out = decrypt_pkt[ICMP].id + except Exception: + self.logger.error( + ppp("Unexpected or invalid encrypted packet:", packet)) + raise + + @classmethod + def config_esp_tun(cls): + spd_id = 1 + remote_sa_id = 10 + local_sa_id = 20 + remote_tun_spi = 1001 + local_tun_spi = 1000 + client = socket.inet_pton(socket.AF_INET, cls.remote_pg0_client_addr) + cls.vapi.ip_add_del_route(client, 32, cls.pg0.remote_ip4n) + cls.vapi.ipsec_sad_add_del_entry(remote_sa_id, remote_tun_spi, + cls.pg1.remote_ip4n, + cls.pg0.remote_ip4n, + integrity_key_length=20, + crypto_key_length=16, + protocol=1, udp_encap=1) + cls.vapi.ipsec_sad_add_del_entry(local_sa_id, local_tun_spi, + cls.pg0.remote_ip4n, + cls.pg1.remote_ip4n, + integrity_key_length=20, + crypto_key_length=16, + protocol=1, udp_encap=1) + cls.vapi.ipsec_spd_add_del(spd_id) + cls.vapi.ipsec_interface_add_del_spd(spd_id, cls.pg0.sw_if_index) + l_startaddr = r_startaddr = socket.inet_pton(socket.AF_INET, + "0.0.0.0") + l_stopaddr = r_stopaddr = socket.inet_pton(socket.AF_INET, + "255.255.255.255") + cls.vapi.ipsec_spd_add_del_entry(spd_id, l_startaddr, l_stopaddr, + r_startaddr, r_stopaddr, + protocol=socket.IPPROTO_ESP) + cls.vapi.ipsec_spd_add_del_entry(spd_id, l_startaddr, l_stopaddr, + r_startaddr, r_stopaddr, + protocol=socket.IPPROTO_ESP, + is_outbound=0) + cls.vapi.ipsec_spd_add_del_entry(spd_id, l_startaddr, l_stopaddr, + r_startaddr, r_stopaddr, + remote_port_start=4500, + remote_port_stop=4500, + protocol=socket.IPPROTO_UDP) + cls.vapi.ipsec_spd_add_del_entry(spd_id, l_startaddr, l_stopaddr, + r_startaddr, r_stopaddr, + remote_port_start=4500, + remote_port_stop=4500, + protocol=socket.IPPROTO_UDP, + is_outbound=0) + l_startaddr = l_stopaddr = cls.pg0.remote_ip4n + r_startaddr = r_stopaddr = cls.pg1.remote_ip4n + cls.vapi.ipsec_spd_add_del_entry(spd_id, l_startaddr, l_stopaddr, + r_startaddr, r_stopaddr, + priority=10, policy=3, + is_outbound=0, sa_id=local_sa_id) + cls.vapi.ipsec_spd_add_del_entry(spd_id, r_startaddr, r_stopaddr, + l_startaddr, l_stopaddr, + priority=10, policy=3, + sa_id=remote_sa_id) + + def test_ipsec_nat_tun(self): + """ IPSec/NAT tunnel test case """ + local_tun_sa = SecurityAssociation(ESP, spi=0x000003e9, + crypt_algo='AES-CBC', + crypt_key='JPjyOWBeVEQiMe7h', + auth_algo='HMAC-SHA1-96', + auth_key='C91KUR9GYMm5GfkEvNjX', + tunnel_header=IP( + src=self.pg1.remote_ip4, + dst=self.pg0.remote_ip4), + nat_t_header=UDP( + sport=4500, + dport=4500)) + # in2out - from private network to public + pkts = self.create_stream_plain( + self.pg1.remote_mac, self.pg1.local_mac, + self.pg1.remote_ip4, self.pg0.remote_ip4) + self.pg1.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg0.get_capture(len(pkts)) + self.verify_capture_encrypted(capture, local_tun_sa) + + remote_tun_sa = SecurityAssociation(ESP, spi=0x000003e8, + crypt_algo='AES-CBC', + crypt_key='JPjyOWBeVEQiMe7h', + auth_algo='HMAC-SHA1-96', + auth_key='C91KUR9GYMm5GfkEvNjX', + tunnel_header=IP( + src=self.pg0.remote_ip4, + dst=self.pg1.remote_ip4), + nat_t_header=UDP( + sport=4500, + dport=4500)) + + # out2in - from public network to private + pkts = self.create_stream_encrypted( + self.pg0.remote_mac, self.pg0.local_mac, + self.pg0.remote_ip4, self.pg1.remote_ip4, remote_tun_sa) + self.logger.info(ppc("Sending packets:", pkts)) + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg1.get_capture(len(pkts)) + self.verify_capture_plain(capture) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 53be992aeeb..b3627317d9b 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -3060,9 +3060,8 @@ class VppPapiProvider(object): :returns: reply from the API """ return self.api( - self.papi.ipsec_interface_add_del_spd, { - 'spd_id': spd_id, - 'sw_if_index': sw_if_index, 'is_add': is_add}) + self.papi.ipsec_interface_add_del_spd, + {'spd_id': spd_id, 'sw_if_index': sw_if_index, 'is_add': is_add}) def ipsec_sad_add_del_entry(self, sad_id, @@ -3077,7 +3076,8 @@ class VppPapiProvider(object): crypto_key_length=0, crypto_key='JPjyOWBeVEQiMe7h', is_add=1, - is_tunnel=1): + is_tunnel=1, + udp_encap=0): """ IPSEC SA add/del Sample CLI : 'ipsec sa add 10 spi 1001 esp \ crypto-key 4a506a794f574265564551694d653768 \ @@ -3130,7 +3130,8 @@ class VppPapiProvider(object): 'crypto_key_length': crypto_key_length, 'crypto_key': crypto_key, 'is_add': is_add, - 'is_tunnel': is_tunnel}) + 'is_tunnel': is_tunnel, + 'udp_encap': udp_encap}) def ipsec_spd_add_del_entry(self, spd_id, -- 2.16.6