ipsec: enable UDP encap for IPv6 ESP tun protect

Type: improvement

If an SA protecting an IPv6 tunnel interface has UDP encapsulation
enabled, the code in esp_encrypt_inline() inserts a UDP header but does
not set the next protocol or the UDP payload length, so the peer that
receives the packet drops it. Set the next protocol field and the UDP
payload length correctly.

The port(s) for UDP encapsulation of IPsec was not registered for IPv6.
Add this registration for IPv6 SAs when UDP encapsulation is enabled.

Add punt handling for IPv6 IKE on NAT-T port.
Add registration of linux-cp for the new punt reason.
Add unit tests of IPv6 ESP w/ UDP encapsulation on tun protect

Signed-off-by: Matthew Smith <mgsmith@netgate.com>
Change-Id: Ibb28e423ab8c7bcea2c1964782a788a0f4da5268
This commit is contained in:
Matthew Smith
2022-08-09 22:19:38 +00:00
parent 47c1b1c633
commit 6f1eb484c8
11 changed files with 261 additions and 51 deletions
+4 -2
View File
@@ -3857,7 +3857,8 @@ ikev2_set_local_key (vlib_main_t * vm, u8 * file)
static vnet_api_error_t
ikev2_register_udp_port (ikev2_profile_t *p, u16 port)
{
ipsec_register_udp_port (port);
ipsec_register_udp_port (port, 0 /* is_ip4 */);
ipsec_register_udp_port (port, 1 /* is_ip4 */);
p->ipsec_over_udp_port = port;
return 0;
}
@@ -3868,7 +3869,8 @@ ikev2_unregister_udp_port (ikev2_profile_t *p)
if (p->ipsec_over_udp_port == IPSEC_UDP_PORT_NONE)
return;
ipsec_unregister_udp_port (p->ipsec_over_udp_port);
ipsec_unregister_udp_port (p->ipsec_over_udp_port, 0 /* is_ip4 */);
ipsec_unregister_udp_port (p->ipsec_over_udp_port, 1 /* is_ip4 */);
p->ipsec_over_udp_port = IPSEC_UDP_PORT_NONE;
}
+2
View File
@@ -1192,6 +1192,8 @@ lcp_interface_init (vlib_main_t *vm)
/* punt IKE */
vlib_punt_register (punt_hdl, ipsec_punt_reason[IPSEC_PUNT_IP4_SPI_UDP_0],
"linux-cp-punt");
vlib_punt_register (punt_hdl, ipsec_punt_reason[IPSEC_PUNT_IP6_SPI_UDP_0],
"linux-cp-punt");
/* punt all unknown ports */
udp_punt_unknown (vm, 0, 1);
+11 -13
View File
@@ -887,42 +887,40 @@ esp_encrypt_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
else
l2_len = 0;
u16 len;
len = payload_len_total + hdr_len - l2_len;
if (VNET_LINK_IP6 == lt)
{
ip6_header_t *ip6 = (ip6_header_t *) (old_ip_hdr);
if (PREDICT_TRUE (NULL == ext_hdr))
{
*next_hdr_ptr = ip6->protocol;
ip6->protocol = IP_PROTOCOL_IPSEC_ESP;
ip6->protocol =
(udp) ? IP_PROTOCOL_UDP : IP_PROTOCOL_IPSEC_ESP;
}
else
{
*next_hdr_ptr = ext_hdr->next_hdr;
ext_hdr->next_hdr = IP_PROTOCOL_IPSEC_ESP;
ext_hdr->next_hdr =
(udp) ? IP_PROTOCOL_UDP : IP_PROTOCOL_IPSEC_ESP;
}
ip6->payload_length =
clib_host_to_net_u16 (payload_len_total + hdr_len - l2_len -
sizeof (ip6_header_t));
clib_host_to_net_u16 (len - sizeof (ip6_header_t));
}
else if (VNET_LINK_IP4 == lt)
{
u16 len;
ip4_header_t *ip4 = (ip4_header_t *) (old_ip_hdr);
*next_hdr_ptr = ip4->protocol;
len = payload_len_total + hdr_len - l2_len;
if (udp)
{
esp_update_ip4_hdr (ip4, len, /* is_transport */ 1, 1);
udp_len = len - ip_len;
}
else
esp_update_ip4_hdr (ip4, len, /* is_transport */ 1, 0);
esp_update_ip4_hdr (ip4, len, /* is_transport */ 1,
(udp != NULL));
}
clib_memcpy_le64 (ip_hdr, old_ip_hdr, ip_len);
if (udp)
{
udp_len = len - ip_len;
esp_fill_udp_hdr (sa0, udp, udp_len);
}
+27 -15
View File
@@ -182,14 +182,24 @@ ipsec_add_node (vlib_main_t * vm, const char *node_name,
*out_next_index = vlib_node_add_next (vm, prev_node->index, node->index);
}
static inline uword
ipsec_udp_registration_key (u16 port, u8 is_ip4)
{
uword key = (is_ip4) ? AF_IP4 : AF_IP6;
key |= (uword) (port << 16);
return key;
}
void
ipsec_unregister_udp_port (u16 port)
ipsec_unregister_udp_port (u16 port, u8 is_ip4)
{
ipsec_main_t *im = &ipsec_main;
u32 n_regs;
uword *p;
uword *p, key;
p = hash_get (im->udp_port_registrations, port);
key = ipsec_udp_registration_key (port, is_ip4);
p = hash_get (im->udp_port_registrations, key);
ASSERT (p);
@@ -197,33 +207,35 @@ ipsec_unregister_udp_port (u16 port)
if (0 == --n_regs)
{
udp_unregister_dst_port (vlib_get_main (), port, 1);
hash_unset (im->udp_port_registrations, port);
udp_unregister_dst_port (vlib_get_main (), port, is_ip4);
hash_unset (im->udp_port_registrations, key);
}
else
{
hash_unset (im->udp_port_registrations, port);
hash_set (im->udp_port_registrations, port, n_regs);
hash_unset (im->udp_port_registrations, key);
hash_set (im->udp_port_registrations, key, n_regs);
}
}
void
ipsec_register_udp_port (u16 port)
ipsec_register_udp_port (u16 port, u8 is_ip4)
{
ipsec_main_t *im = &ipsec_main;
u32 n_regs;
uword *p;
u32 n_regs, node_index;
uword *p, key;
p = hash_get (im->udp_port_registrations, port);
key = ipsec_udp_registration_key (port, is_ip4);
node_index =
(is_ip4) ? ipsec4_tun_input_node.index : ipsec6_tun_input_node.index;
p = hash_get (im->udp_port_registrations, key);
n_regs = (p ? p[0] : 0);
if (0 == n_regs++)
udp_register_dst_port (vlib_get_main (), port,
ipsec4_tun_input_node.index, 1);
udp_register_dst_port (vlib_get_main (), port, node_index, is_ip4);
hash_unset (im->udp_port_registrations, port);
hash_set (im->udp_port_registrations, port, n_regs);
hash_unset (im->udp_port_registrations, key);
hash_set (im->udp_port_registrations, key, n_regs);
}
u32
+2 -2
View File
@@ -364,8 +364,8 @@ int ipsec_select_esp_backend (ipsec_main_t * im, u32 esp_backend_idx);
clib_error_t *ipsec_rsc_in_use (ipsec_main_t * im);
void ipsec_set_async_mode (u32 is_enabled);
extern void ipsec_register_udp_port (u16 udp_port);
extern void ipsec_unregister_udp_port (u16 udp_port);
extern void ipsec_register_udp_port (u16 udp_port, u8 is_ip4);
extern void ipsec_unregister_udp_port (u16 udp_port, u8 is_ip4);
extern clib_error_t *ipsec_register_next_header (vlib_main_t *vm,
u8 next_header,
+2 -1
View File
@@ -20,7 +20,8 @@
#define foreach_ipsec_punt_reason \
_ (IP4_SPI_UDP_0, "ipsec4-spi-o-udp-0", IP4_PACKET) \
_ (IP4_NO_SUCH_TUNNEL, "ipsec4-no-such-tunnel", IP4_PACKET) \
_ (IP6_NO_SUCH_TUNNEL, "ipsec6-no-such-tunnel", IP6_PACKET)
_ (IP6_NO_SUCH_TUNNEL, "ipsec6-no-such-tunnel", IP6_PACKET) \
_ (IP6_SPI_UDP_0, "ipsec6-spi-o-udp-0", IP6_PACKET)
typedef enum ipsec_punt_reason_t_
{
+4 -2
View File
@@ -325,7 +325,8 @@ ipsec_sa_add_and_lock (u32 id, u32 spi, ipsec_protocol_t proto,
sa->udp_hdr.src_port = clib_host_to_net_u16 (src_port);
if (ipsec_sa_is_set_IS_INBOUND (sa))
ipsec_register_udp_port (clib_host_to_net_u16 (sa->udp_hdr.dst_port));
ipsec_register_udp_port (clib_host_to_net_u16 (sa->udp_hdr.dst_port),
!ipsec_sa_is_set_IS_TUNNEL_V6 (sa));
}
hash_set (im->sa_index_by_sa_id, sa->id, sa_index);
@@ -353,7 +354,8 @@ ipsec_sa_del (ipsec_sa_t * sa)
if (ipsec_sa_is_set_IS_ASYNC (sa))
vnet_crypto_request_async_mode (0);
if (ipsec_sa_is_set_UDP_ENCAP (sa) && ipsec_sa_is_set_IS_INBOUND (sa))
ipsec_unregister_udp_port (clib_net_to_host_u16 (sa->udp_hdr.dst_port));
ipsec_unregister_udp_port (clib_net_to_host_u16 (sa->udp_hdr.dst_port),
!ipsec_sa_is_set_IS_TUNNEL_V6 (sa));
if (ipsec_sa_is_set_IS_TUNNEL (sa) && !ipsec_sa_is_set_IS_INBOUND (sa))
dpo_reset (&sa->dpo);
+5 -9
View File
@@ -101,14 +101,12 @@ ipsec_tun_register_nodes (ip_address_family_t af)
if (0 == ipsec_tun_node_regs[af]++)
{
if (AF_IP4 == af)
{
ipsec_register_udp_port (UDP_DST_PORT_ipsec);
ip4_register_protocol (IP_PROTOCOL_IPSEC_ESP,
ipsec4_tun_input_node.index);
}
ip4_register_protocol (IP_PROTOCOL_IPSEC_ESP,
ipsec4_tun_input_node.index);
else
ip6_register_protocol (IP_PROTOCOL_IPSEC_ESP,
ipsec6_tun_input_node.index);
ipsec_register_udp_port (UDP_DST_PORT_ipsec, (AF_IP4 == af));
}
}
@@ -119,12 +117,10 @@ ipsec_tun_unregister_nodes (ip_address_family_t af)
if (0 == --ipsec_tun_node_regs[af])
{
if (AF_IP4 == af)
{
ipsec_unregister_udp_port (UDP_DST_PORT_ipsec);
ip4_unregister_protocol (IP_PROTOCOL_IPSEC_ESP);
}
ip4_unregister_protocol (IP_PROTOCOL_IPSEC_ESP);
else
ip6_unregister_protocol (IP_PROTOCOL_IPSEC_ESP);
ipsec_unregister_udp_port (UDP_DST_PORT_ipsec, (AF_IP4 == af));
}
}
+44 -7
View File
@@ -86,11 +86,21 @@ ipsec_ip4_if_no_tunnel (vlib_node_runtime_t * node,
}
always_inline u16
ipsec_ip6_if_no_tunnel (vlib_node_runtime_t * node,
vlib_buffer_t * b, const esp_header_t * esp)
ipsec_ip6_if_no_tunnel (vlib_node_runtime_t *node, vlib_buffer_t *b,
const esp_header_t *esp, const ip6_header_t *ip6)
{
b->error = node->errors[IPSEC_TUN_ERROR_NO_TUNNEL];
b->punt_reason = ipsec_punt_reason[IPSEC_PUNT_IP6_NO_SUCH_TUNNEL];
if (PREDICT_FALSE (0 == esp->spi))
{
b->error = node->errors[IPSEC_TUN_ERROR_SPI_0];
b->punt_reason = ipsec_punt_reason[(ip6->protocol == IP_PROTOCOL_UDP ?
IPSEC_PUNT_IP6_SPI_UDP_0 :
IPSEC_PUNT_IP6_NO_SUCH_TUNNEL)];
}
else
{
b->error = node->errors[IPSEC_TUN_ERROR_NO_TUNNEL];
b->punt_reason = ipsec_punt_reason[IPSEC_PUNT_IP6_NO_SUCH_TUNNEL];
}
return VNET_DEVICE_INPUT_NEXT_PUNT;
}
@@ -164,8 +174,35 @@ ipsec_tun_protect_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
if (is_ip6)
{
ip60 = (ip6_header_t *) ip40;
esp0 = (esp_header_t *) (ip60 + 1);
buf_rewind0 = hdr_sz0 = sizeof (ip6_header_t);
if (ip60->protocol == IP_PROTOCOL_UDP)
{
/* NAT UDP port 4500 case, don't advance any more */
esp0 = (esp_header_t *) ((u8 *) ip60 + sizeof (ip6_header_t) +
sizeof (udp_header_t));
hdr_sz0 = 0;
buf_rewind0 = sizeof (ip6_header_t) + sizeof (udp_header_t);
const udp_header_t *udp0 =
(udp_header_t *) ((u8 *) ip60 + sizeof (ip6_header_t));
/* length 9 = sizeof(udp_header) + 1 byte of special SPI */
if (clib_net_to_host_u16 (udp0->length) == 9 &&
esp0->spi_bytes[0] == 0xff)
{
b[0]->error = node->errors[IPSEC_TUN_ERROR_NAT_KEEPALIVE];
next[0] = VNET_DEVICE_INPUT_NEXT_IP6_DROP;
len0 = 0;
vlib_buffer_advance (b[0], -buf_rewind0);
goto trace00;
}
}
else
{
esp0 = (esp_header_t *) (ip60 + 1);
buf_rewind0 = hdr_sz0 = sizeof (ip6_header_t);
}
}
else
{
@@ -240,7 +277,7 @@ ipsec_tun_protect_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
}
else
{
next[0] = ipsec_ip6_if_no_tunnel (node, b[0], esp0);
next[0] = ipsec_ip6_if_no_tunnel (node, b[0], esp0, ip60);
n_no_tunnel++;
goto trace00;
}
+34
View File
@@ -1695,6 +1695,40 @@ class IpsecTun6(object):
self.logger.info(self.vapi.ppcli("show ipsec all"))
self.verify_counters6(p, p, count)
def verify_keepalive(self, p):
# the sizeof Raw is calculated to pad to the minimum ehternet
# frame size of 64 btyes
pkt = (
Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac)
/ IPv6(src=p.remote_tun_if_host, dst=self.tun_if.local_ip6)
/ UDP(sport=333, dport=4500)
/ Raw(b"\xff")
/ Padding(0 * 1)
)
self.send_and_assert_no_replies(self.tun_if, pkt * 31)
self.assert_error_counter_equal(
"/err/%s/nat_keepalive" % self.tun6_input_node, 31
)
pkt = (
Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac)
/ IPv6(src=p.remote_tun_if_host, dst=self.tun_if.local_ip6)
/ UDP(sport=333, dport=4500)
/ Raw(b"\xfe")
)
self.send_and_assert_no_replies(self.tun_if, pkt * 31)
self.assert_error_counter_equal("/err/%s/too_short" % self.tun6_input_node, 31)
pkt = (
Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac)
/ IPv6(src=p.remote_tun_if_host, dst=self.tun_if.local_ip6)
/ UDP(sport=333, dport=4500)
/ Raw(b"\xfe")
/ Padding(0 * 21)
)
self.send_and_assert_no_replies(self.tun_if, pkt * 31)
self.assert_error_counter_equal("/err/%s/too_short" % self.tun6_input_node, 62)
class IpsecTun6Tests(IpsecTun6):
"""UT test methods for Tunnel v6"""
+126
View File
@@ -561,6 +561,132 @@ class TemplateIpsec6TunIfEsp(TemplateIpsec6TunProtect, TemplateIpsec):
super(TemplateIpsec6TunIfEsp, self).tearDown()
class TemplateIpsec6TunIfEspUdp(TemplateIpsec6TunProtect, TemplateIpsec):
"""IPsec6 UDP tunnel interface tests"""
tun4_encrypt_node_name = "esp6-encrypt-tun"
tun4_decrypt_node_name = ["esp6-decrypt-tun", "esp6-decrypt-tun-post"]
encryption_type = ESP
@classmethod
def setUpClass(cls):
super(TemplateIpsec6TunIfEspUdp, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(TemplateIpsec6TunIfEspUdp, cls).tearDownClass()
def verify_encrypted(self, p, sa, rxs):
for rx in rxs:
try:
# ensure the UDP ports are correct before we decrypt
# which strips them
self.assertTrue(rx.haslayer(UDP))
self.assert_equal(rx[UDP].sport, p.nat_header.sport)
self.assert_equal(rx[UDP].dport, 4500)
pkt = sa.decrypt(rx[IP])
if not pkt.haslayer(IP):
pkt = IP(pkt[Raw].load)
self.assert_packet_checksums_valid(pkt)
self.assert_equal(
pkt[IP].dst, "1111:1111:1111:1111:1111:1111:1111:1111"
)
self.assert_equal(pkt[IP].src, self.pg1.remote_ip6)
except (IndexError, AssertionError):
self.logger.debug(ppp("Unexpected packet:", rx))
try:
self.logger.debug(ppp("Decrypted packet:", pkt))
except:
pass
raise
def config_sa_tra(self, p):
config_tun_params(p, self.encryption_type, p.tun_if)
p.tun_sa_out = VppIpsecSA(
self,
p.scapy_tun_sa_id,
p.scapy_tun_spi,
p.auth_algo_vpp_id,
p.auth_key,
p.crypt_algo_vpp_id,
p.crypt_key,
self.vpp_esp_protocol,
flags=p.flags,
udp_src=p.nat_header.sport,
udp_dst=p.nat_header.dport,
)
p.tun_sa_out.add_vpp_config()
p.tun_sa_in = VppIpsecSA(
self,
p.vpp_tun_sa_id,
p.vpp_tun_spi,
p.auth_algo_vpp_id,
p.auth_key,
p.crypt_algo_vpp_id,
p.crypt_key,
self.vpp_esp_protocol,
flags=p.flags,
udp_src=p.nat_header.sport,
udp_dst=p.nat_header.dport,
)
p.tun_sa_in.add_vpp_config()
def setUp(self):
super(TemplateIpsec6TunIfEspUdp, self).setUp()
p = self.ipv6_params
p.flags = VppEnum.vl_api_ipsec_sad_flags_t.IPSEC_API_SAD_FLAG_UDP_ENCAP
p.nat_header = UDP(sport=5454, dport=4500)
self.tun_if = self.pg0
self.config_network(p)
self.config_sa_tra(p)
self.config_protect(p)
def tearDown(self):
super(TemplateIpsec6TunIfEspUdp, self).tearDown()
class TestIpsec6TunIfEspUdp(TemplateIpsec6TunIfEspUdp, IpsecTun6Tests):
"""Ipsec ESP 6 UDP tests"""
tun6_input_node = "ipsec6-tun-input"
tun6_encrypt_node_name = "esp6-encrypt-tun"
tun6_decrypt_node_name = ["esp6-decrypt-tun", "esp6-decrypt-tun-post"]
def setUp(self):
super(TestIpsec6TunIfEspUdp, self).setUp()
def test_keepalive(self):
"""IPSEC6 NAT Keepalive"""
self.verify_keepalive(self.ipv6_params)
class TestIpsec6TunIfEspUdpGCM(TemplateIpsec6TunIfEspUdp, IpsecTun6Tests):
"""Ipsec ESP 6 UDP GCM tests"""
tun6_input_node = "ipsec6-tun-input"
tun6_encrypt_node_name = "esp6-encrypt-tun"
tun6_decrypt_node_name = ["esp6-decrypt-tun", "esp6-decrypt-tun-post"]
def setUp(self):
super(TestIpsec6TunIfEspUdpGCM, self).setUp()
p = self.ipv6_params
p.auth_algo_vpp_id = VppEnum.vl_api_ipsec_integ_alg_t.IPSEC_API_INTEG_ALG_NONE
p.crypt_algo_vpp_id = (
VppEnum.vl_api_ipsec_crypto_alg_t.IPSEC_API_CRYPTO_ALG_AES_GCM_256
)
p.crypt_algo = "AES-GCM"
p.auth_algo = "NULL"
p.crypt_key = b"JPjyOWBeVEQiMe7hJPjyOWBeVEQiMe7h"
p.salt = 0
class TestIpsec6TunIfEsp1(TemplateIpsec6TunIfEsp, IpsecTun6Tests):
"""Ipsec ESP - TUN tests"""