/* * Copyright (c) 2017 Mellanox Technologies. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * OpenIB.org BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include #include #include #include "ipsec.h" #include "ipsec_rxtx.h" #include "en.h" #include "esw/ipsec_fs.h" enum { MLX5E_IPSEC_TX_SYNDROME_OFFLOAD = 0x8, MLX5E_IPSEC_TX_SYNDROME_OFFLOAD_WITH_LSO_TCP = 0x9, }; static int mlx5e_ipsec_remove_trailer(struct sk_buff *skb, struct xfrm_state *x) { unsigned int alen = crypto_aead_authsize(x->data); struct ipv6hdr *ipv6hdr = ipv6_hdr(skb); struct iphdr *ipv4hdr = ip_hdr(skb); unsigned int trailer_len; u8 plen; int ret; ret = skb_copy_bits(skb, skb->len - alen - 2, &plen, 1); if (unlikely(ret)) return ret; trailer_len = alen + plen + 2; ret = pskb_trim(skb, skb->len - trailer_len); if (unlikely(ret)) return ret; if (skb->protocol == htons(ETH_P_IP)) { ipv4hdr->tot_len = htons(ntohs(ipv4hdr->tot_len) - trailer_len); ip_send_check(ipv4hdr); } else { ipv6hdr->payload_len = htons(ntohs(ipv6hdr->payload_len) - trailer_len); } return 0; } static void mlx5e_ipsec_set_swp(struct sk_buff *skb, struct mlx5_wqe_eth_seg *eseg, u8 mode, struct xfrm_offload *xo) { /* Tunnel Mode: * SWP: OutL3 InL3 InL4 * Pkt: MAC IP ESP IP L4 * * Transport Mode: * SWP: OutL3 OutL4 * Pkt: MAC IP ESP L4 * * Tunnel(VXLAN TCP/UDP) over Transport Mode * SWP: OutL3 InL3 InL4 * Pkt: MAC IP ESP UDP VXLAN IP L4 */ /* Shared settings */ eseg->swp_outer_l3_offset = skb_network_offset(skb) / 2; if (skb->protocol == htons(ETH_P_IPV6)) eseg->swp_flags |= MLX5_ETH_WQE_SWP_OUTER_L3_IPV6; /* Tunnel mode */ if (mode == XFRM_MODE_TUNNEL) { eseg->swp_inner_l3_offset = skb_inner_network_offset(skb) / 2; if (xo->proto == IPPROTO_IPV6) eseg->swp_flags |= MLX5_ETH_WQE_SWP_INNER_L3_IPV6; switch (xo->inner_ipproto) { case IPPROTO_UDP: eseg->swp_flags |= MLX5_ETH_WQE_SWP_INNER_L4_UDP; fallthrough; case IPPROTO_TCP: /* IP | ESP | IP | [TCP | UDP] */ eseg->swp_inner_l4_offset = skb_inner_transport_offset(skb) / 2; break; default: break; } return; } /* Transport mode */ if (mode != XFRM_MODE_TRANSPORT) return; if (!xo->inner_ipproto) { switch (xo->proto) { case IPPROTO_UDP: eseg->swp_flags |= MLX5_ETH_WQE_SWP_OUTER_L4_UDP; fallthrough; case IPPROTO_TCP: /* IP | ESP | TCP */ eseg->swp_outer_l4_offset = skb_inner_transport_offset(skb) / 2; break; default: break; } } else { /* Tunnel(VXLAN TCP/UDP) over Transport Mode */ switch (xo->inner_ipproto) { case IPPROTO_UDP: eseg->swp_flags |= MLX5_ETH_WQE_SWP_INNER_L4_UDP; fallthrough; case IPPROTO_TCP: eseg->swp_inner_l3_offset = skb_inner_network_offset(skb) / 2; eseg->swp_inner_l4_offset = (skb->csum_start + skb->head - skb->data) / 2; if (inner_ip_hdr(skb)->version == 6) eseg->swp_flags |= MLX5_ETH_WQE_SWP_INNER_L3_IPV6; break; default: break; } } } void mlx5e_ipsec_set_iv_esn(struct sk_buff *skb, struct xfrm_state *x, struct xfrm_offload *xo) { struct xfrm_replay_state_esn *replay_esn = x->replay_esn; __u32 oseq = replay_esn->oseq; int iv_offset; __be64 seqno; u32 seq_hi; if (unlikely(skb_is_gso(skb) && oseq < MLX5E_IPSEC_ESN_SCOPE_MID && MLX5E_IPSEC_ESN_SCOPE_MID < (oseq - skb_shinfo(skb)->gso_segs))) { seq_hi = xo->seq.hi - 1; } else { seq_hi = xo->seq.hi; } /* Place the SN in the IV field */ seqno = cpu_to_be64(xo->seq.low + ((u64)seq_hi << 32)); iv_offset = skb_transport_offset(skb) + sizeof(struct ip_esp_hdr); skb_store_bits(skb, iv_offset, &seqno, 8); } void mlx5e_ipsec_set_iv(struct sk_buff *skb, struct xfrm_state *x, struct xfrm_offload *xo) { int iv_offset; __be64 seqno; /* Place the SN in the IV field */ seqno = cpu_to_be64(xo->seq.low + ((u64)xo->seq.hi << 32)); iv_offset = skb_transport_offset(skb) + sizeof(struct ip_esp_hdr); skb_store_bits(skb, iv_offset, &seqno, 8); } void mlx5e_ipsec_handle_tx_wqe(struct mlx5e_tx_wqe *wqe, struct mlx5e_accel_tx_ipsec_state *ipsec_st, struct mlx5_wqe_inline_seg *inlseg) { inlseg->byte_count = cpu_to_be32(ipsec_st->tailen | MLX5_INLINE_SEG); esp_output_fill_trailer((u8 *)inlseg->data, 0, ipsec_st->plen, ipsec_st->xo->proto); } static int mlx5e_ipsec_set_state(struct mlx5e_priv *priv, struct sk_buff *skb, struct xfrm_state *x, struct xfrm_offload *xo, struct mlx5e_accel_tx_ipsec_state *ipsec_st) { unsigned int blksize, clen, alen, plen; struct crypto_aead *aead; unsigned int tailen; ipsec_st->x = x; ipsec_st->xo = xo; aead = x->data; alen = crypto_aead_authsize(aead); blksize = ALIGN(crypto_aead_blocksize(aead), 4); clen = ALIGN(skb->len + 2, blksize); plen = max_t(u32, clen - skb->len, 4); tailen = plen + alen; ipsec_st->plen = plen; ipsec_st->tailen = tailen; return 0; } void mlx5e_ipsec_tx_build_eseg(struct mlx5e_priv *priv, struct sk_buff *skb, struct mlx5_wqe_eth_seg *eseg) { struct xfrm_offload *xo = xfrm_offload(skb); struct xfrm_encap_tmpl *encap; struct xfrm_state *x; struct sec_path *sp; u8 l3_proto; sp = skb_sec_path(skb); if (unlikely(sp->len != 1)) return; x = xfrm_input_state(skb); if (unlikely(!x)) return; if (unlikely(!x->xso.offload_handle || (skb->protocol != htons(ETH_P_IP) && skb->protocol != htons(ETH_P_IPV6)))) return; mlx5e_ipsec_set_swp(skb, eseg, x->props.mode, xo); l3_proto = (x->props.family == AF_INET) ? ((struct iphdr *)skb_network_header(skb))->protocol : ((struct ipv6hdr *)skb_network_header(skb))->nexthdr; eseg->flow_table_metadata |= cpu_to_be32(MLX5_ETH_WQE_FT_META_IPSEC); eseg->trailer |= cpu_to_be32(MLX5_ETH_WQE_INSERT_TRAILER); encap = x->encap; if (!encap) { eseg->trailer |= (l3_proto == IPPROTO_ESP) ? cpu_to_be32(MLX5_ETH_WQE_TRAILER_HDR_OUTER_IP_ASSOC) : cpu_to_be32(MLX5_ETH_WQE_TRAILER_HDR_OUTER_L4_ASSOC); } else if (encap->encap_type == UDP_ENCAP_ESPINUDP) { eseg->trailer |= (l3_proto == IPPROTO_ESP) ? cpu_to_be32(MLX5_ETH_WQE_TRAILER_HDR_INNER_IP_ASSOC) : cpu_to_be32(MLX5_ETH_WQE_TRAILER_HDR_INNER_L4_ASSOC); } } bool mlx5e_ipsec_handle_tx_skb(struct net_device *netdev, struct sk_buff *skb, struct mlx5e_accel_tx_ipsec_state *ipsec_st) { struct mlx5e_priv *priv = netdev_priv(netdev); struct xfrm_offload *xo = xfrm_offload(skb); struct mlx5e_ipsec_sa_entry *sa_entry; struct xfrm_state *x; struct sec_path *sp; sp = skb_sec_path(skb); if (unlikely(sp->len != 1)) { atomic64_inc(&priv->ipsec->sw_stats.ipsec_tx_drop_bundle); goto drop; } x = xfrm_input_state(skb); if (unlikely(!x)) { atomic64_inc(&priv->ipsec->sw_stats.ipsec_tx_drop_no_state); goto drop; } if (unlikely(!x->xso.offload_handle || (skb->protocol != htons(ETH_P_IP) && skb->protocol != htons(ETH_P_IPV6)))) { atomic64_inc(&priv->ipsec->sw_stats.ipsec_tx_drop_not_ip); goto drop; } if (!skb_is_gso(skb)) if (unlikely(mlx5e_ipsec_remove_trailer(skb, x))) { atomic64_inc(&priv->ipsec->sw_stats.ipsec_tx_drop_trailer); goto drop; } sa_entry = (struct mlx5e_ipsec_sa_entry *)x->xso.offload_handle; sa_entry->set_iv_op(skb, x, xo); mlx5e_ipsec_set_state(priv, skb, x, xo, ipsec_st); return true; drop: kfree_skb(skb); return false; } void mlx5e_ipsec_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff *skb, u32 ipsec_meta_data) { struct mlx5e_priv *priv = netdev_priv(netdev); struct mlx5e_ipsec *ipsec = priv->ipsec; struct mlx5e_ipsec_sa_entry *sa_entry; struct xfrm_offload *xo; struct sec_path *sp; u32 sa_handle; sa_handle = MLX5_IPSEC_METADATA_HANDLE(ipsec_meta_data); sp = secpath_set(skb); if (unlikely(!sp)) { atomic64_inc(&ipsec->sw_stats.ipsec_rx_drop_sp_alloc); return; } rcu_read_lock(); sa_entry = xa_load(&ipsec->sadb, sa_handle); if (unlikely(!sa_entry)) { rcu_read_unlock(); atomic64_inc(&ipsec->sw_stats.ipsec_rx_drop_sadb_miss); return; } xfrm_state_hold(sa_entry->x); rcu_read_unlock(); sp->xvec[sp->len++] = sa_entry->x; sp->olen++; xo = xfrm_offload(skb); xo->flags = CRYPTO_DONE; xo->status = CRYPTO_SUCCESS; } int mlx5_esw_ipsec_rx_make_metadata(struct mlx5e_priv *priv, u32 id, u32 *metadata) { struct mlx5e_ipsec *ipsec = priv->ipsec; u32 ipsec_obj_id; int err; if (!ipsec || !ipsec->is_uplink_rep) return -EINVAL; err = mlx5_esw_ipsec_rx_ipsec_obj_id_search(priv, id, &ipsec_obj_id); if (err) { atomic64_inc(&ipsec->sw_stats.ipsec_rx_drop_sadb_miss); return err; } *metadata = ipsec_obj_id; return 0; }