Skip to content

Commit

Permalink
ipsec: enable UDP encap for IPv6 ESP tun protect
Browse files Browse the repository at this point in the history
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 <[email protected]>
Change-Id: Ibb28e423ab8c7bcea2c1964782a788a0f4da5268
  • Loading branch information
mgsmith1000 committed Aug 19, 2022
1 parent 47c1b1c commit 6f1eb48
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 51 deletions.
6 changes: 4 additions & 2 deletions src/plugins/ikev2/ikev2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/linux-cp/lcp_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 11 additions & 13 deletions src/vnet/ipsec/esp_encrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
42 changes: 27 additions & 15 deletions src/vnet/ipsec/ipsec.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,48 +182,60 @@ 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);

n_regs = p[0];

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
Expand Down
4 changes: 2 additions & 2 deletions src/vnet/ipsec/ipsec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/vnet/ipsec/ipsec_punt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
{
Expand Down
6 changes: 4 additions & 2 deletions src/vnet/ipsec/ipsec_sa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 5 additions & 9 deletions src/vnet/ipsec/ipsec_tun.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand All @@ -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));
}
}

Expand Down
51 changes: 44 additions & 7 deletions src/vnet/ipsec/ipsec_tun_in.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}
Expand Down
34 changes: 34 additions & 0 deletions test/template_ipsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,40 @@ def verify_tun_46(self, p, count=1):
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"""
Expand Down
Loading

0 comments on commit 6f1eb48

Please sign in to comment.