// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) /* Copyright (C) 2019 Netronome Systems, Inc. */ #include #include #include #include #include #include #include "../ccm.h" #include "../nfp_net.h" #include "crypto.h" #include "fw.h" #define NFP_NET_TLS_CCM_MBOX_OPS_MASK \ (BIT(NFP_CCM_TYPE_CRYPTO_RESET) | \ BIT(NFP_CCM_TYPE_CRYPTO_ADD) | \ BIT(NFP_CCM_TYPE_CRYPTO_DEL) | \ BIT(NFP_CCM_TYPE_CRYPTO_UPDATE)) #define NFP_NET_TLS_OPCODE_MASK_RX \ BIT(NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_DEC) #define NFP_NET_TLS_OPCODE_MASK_TX \ BIT(NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_ENC) #define NFP_NET_TLS_OPCODE_MASK \ (NFP_NET_TLS_OPCODE_MASK_RX | NFP_NET_TLS_OPCODE_MASK_TX) static void nfp_net_crypto_set_op(struct nfp_net *nn, u8 opcode, bool on) { u32 off, val; off = nn->tlv_caps.crypto_enable_off + round_down(opcode / 8, 4); val = nn_readl(nn, off); if (on) val |= BIT(opcode & 31); else val &= ~BIT(opcode & 31); nn_writel(nn, off, val); } static bool __nfp_net_tls_conn_cnt_changed(struct nfp_net *nn, int add, enum tls_offload_ctx_dir direction) { u8 opcode; int cnt; if (direction == TLS_OFFLOAD_CTX_DIR_TX) { opcode = NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_ENC; nn->ktls_tx_conn_cnt += add; cnt = nn->ktls_tx_conn_cnt; nn->dp.ktls_tx = !!nn->ktls_tx_conn_cnt; } else { opcode = NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_DEC; nn->ktls_rx_conn_cnt += add; cnt = nn->ktls_rx_conn_cnt; } /* Care only about 0 -> 1 and 1 -> 0 transitions */ if (cnt > 1) return false; nfp_net_crypto_set_op(nn, opcode, cnt); return true; } static int nfp_net_tls_conn_cnt_changed(struct nfp_net *nn, int add, enum tls_offload_ctx_dir direction) { int ret = 0; /* Use the BAR lock to protect the connection counts */ nn_ctrl_bar_lock(nn); if (__nfp_net_tls_conn_cnt_changed(nn, add, direction)) { ret = __nfp_net_reconfig(nn, NFP_NET_CFG_UPDATE_CRYPTO); /* Undo the cnt adjustment if failed */ if (ret) __nfp_net_tls_conn_cnt_changed(nn, -add, direction); } nn_ctrl_bar_unlock(nn); return ret; } static int nfp_net_tls_conn_add(struct nfp_net *nn, enum tls_offload_ctx_dir direction) { return nfp_net_tls_conn_cnt_changed(nn, 1, direction); } static int nfp_net_tls_conn_remove(struct nfp_net *nn, enum tls_offload_ctx_dir direction) { return nfp_net_tls_conn_cnt_changed(nn, -1, direction); } static struct sk_buff * nfp_net_tls_alloc_simple(struct nfp_net *nn, size_t req_sz, gfp_t flags) { return nfp_ccm_mbox_msg_alloc(nn, req_sz, sizeof(struct nfp_crypto_reply_simple), flags); } static int nfp_net_tls_communicate_simple(struct nfp_net *nn, struct sk_buff *skb, const char *name, enum nfp_ccm_type type) { struct nfp_crypto_reply_simple *reply; int err; err = __nfp_ccm_mbox_communicate(nn, skb, type, sizeof(*reply), sizeof(*reply), type == NFP_CCM_TYPE_CRYPTO_DEL); if (err) { nn_dp_warn(&nn->dp, "failed to %s TLS: %d\n", name, err); return err; } reply = (void *)skb->data; err = -be32_to_cpu(reply->error); if (err) nn_dp_warn(&nn->dp, "failed to %s TLS, fw replied: %d\n", name, err); dev_consume_skb_any(skb); return err; } static void nfp_net_tls_del_fw(struct nfp_net *nn, __be32 *fw_handle) { struct nfp_crypto_req_del *req; struct sk_buff *skb; skb = nfp_net_tls_alloc_simple(nn, sizeof(*req), GFP_KERNEL); if (!skb) return; req = (void *)skb->data; req->ep_id = 0; memcpy(req->handle, fw_handle, sizeof(req->handle)); nfp_net_tls_communicate_simple(nn, skb, "delete", NFP_CCM_TYPE_CRYPTO_DEL); } static void nfp_net_tls_set_ipver_vlan(struct nfp_crypto_req_add_front *front, u8 ipver) { front->ipver_vlan = cpu_to_be16(FIELD_PREP(NFP_NET_TLS_IPVER, ipver) | FIELD_PREP(NFP_NET_TLS_VLAN, NFP_NET_TLS_VLAN_UNUSED)); } static void nfp_net_tls_assign_conn_id(struct nfp_net *nn, struct nfp_crypto_req_add_front *front) { u32 len; u64 id; id = atomic64_inc_return(&nn->ktls_conn_id_gen); len = front->key_len - NFP_NET_TLS_NON_ADDR_KEY_LEN; memcpy(front->l3_addrs, &id, sizeof(id)); memset(front->l3_addrs + sizeof(id), 0, len - sizeof(id)); } static struct nfp_crypto_req_add_back * nfp_net_tls_set_ipv4(struct nfp_net *nn, struct nfp_crypto_req_add_v4 *req, struct sock *sk, int direction) { struct inet_sock *inet = inet_sk(sk); req->front.key_len += sizeof(__be32) * 2; if (direction == TLS_OFFLOAD_CTX_DIR_TX) { nfp_net_tls_assign_conn_id(nn, &req->front); } else { req->src_ip = inet->inet_daddr; req->dst_ip = inet->inet_saddr; } return &req->back; } static struct nfp_crypto_req_add_back * nfp_net_tls_set_ipv6(struct nfp_net *nn, struct nfp_crypto_req_add_v6 *req, struct sock *sk, int direction) { #if IS_ENABLED(CONFIG_IPV6) struct ipv6_pinfo *np = inet6_sk(sk); req->front.key_len += sizeof(struct in6_addr) * 2; if (direction == TLS_OFFLOAD_CTX_DIR_TX) { nfp_net_tls_assign_conn_id(nn, &req->front); } else { memcpy(req->src_ip, &sk->sk_v6_daddr, sizeof(req->src_ip)); memcpy(req->dst_ip, &np->saddr, sizeof(req->dst_ip)); } #endif return &req->back; } static void nfp_net_tls_set_l4(struct nfp_crypto_req_add_front *front, struct nfp_crypto_req_add_back *back, struct sock *sk, int direction) { struct inet_sock *inet = inet_sk(sk); front->l4_proto = IPPROTO_TCP; if (direction == TLS_OFFLOAD_CTX_DIR_TX) { back->src_port = 0; back->dst_port = 0; } else { back->src_port = inet->inet_dport; back->dst_port = inet->inet_sport; } } static u8 nfp_tls_1_2_dir_to_opcode(enum tls_offload_ctx_dir direction) { switch (direction) { case TLS_OFFLOAD_CTX_DIR_TX: return NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_ENC; case TLS_OFFLOAD_CTX_DIR_RX: return NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_DEC; default: WARN_ON_ONCE(1); return 0; } } static bool nfp_net_cipher_supported(struct nfp_net *nn, u16 cipher_type, enum tls_offload_ctx_dir direction) { u8 bit; switch (cipher_type) { case TLS_CIPHER_AES_GCM_128: if (direction == TLS_OFFLOAD_CTX_DIR_TX) bit = NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_ENC; else bit = NFP_NET_CRYPTO_OP_TLS_1_2_AES_GCM_128_DEC; break; default: return false; } return nn->tlv_caps.crypto_ops & BIT(bit); } static int nfp_net_tls_add(struct net_device *netdev, struct sock *sk, enum tls_offload_ctx_dir direction, struct tls_crypto_info *crypto_info, u32 start_offload_tcp_sn) { struct tls12_crypto_info_aes_gcm_128 *tls_ci; struct nfp_net *nn = netdev_priv(netdev); struct nfp_crypto_req_add_front *front; struct nfp_net_tls_offload_ctx *ntls; struct nfp_crypto_req_add_back *back; struct nfp_crypto_reply_add *reply; struct sk_buff *skb; size_t req_sz; void *req; bool ipv6; int err; BUILD_BUG_ON(sizeof(struct nfp_net_tls_offload_ctx) > TLS_DRIVER_STATE_SIZE_TX); BUILD_BUG_ON(offsetof(struct nfp_net_tls_offload_ctx, rx_end) > TLS_DRIVER_STATE_SIZE_RX); if (!nfp_net_cipher_supported(nn, crypto_info->cipher_type, direction)) return -EOPNOTSUPP; switch (sk->sk_family) { #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: if (sk->sk_ipv6only || ipv6_addr_type(&sk->sk_v6_daddr) != IPV6_ADDR_MAPPED) { req_sz = sizeof(struct nfp_crypto_req_add_v6); ipv6 = true; break; } fallthrough; #endif case AF_INET: req_sz = sizeof(struct nfp_crypto_req_add_v4); ipv6 = false; break; default: return -EOPNOTSUPP; } err = nfp_net_tls_conn_add(nn, direction); if (err) return err; skb = nfp_ccm_mbox_msg_alloc(nn, req_sz, sizeof(*reply), GFP_KERNEL); if (!skb) { err = -ENOMEM; goto err_conn_remove; } front = (void *)skb->data; front->ep_id = 0; front->key_len = NFP_NET_TLS_NON_ADDR_KEY_LEN; front->opcode = nfp_tls_1_2_dir_to_opcode(direction); memset(front->resv, 0, sizeof(front->resv)); nfp_net_tls_set_ipver_vlan(front, ipv6 ? 6 : 4); req = (void *)skb->data; if (ipv6) back = nfp_net_tls_set_ipv6(nn, req, sk, direction); else back = nfp_net_tls_set_ipv4(nn, req, sk, direction); nfp_net_tls_set_l4(front, back, sk, direction); back->counter = 0; back->tcp_seq = cpu_to_be32(start_offload_tcp_sn); tls_ci = (struct tls12_crypto_info_aes_gcm_128 *)crypto_info; memcpy(back->key, tls_ci->key, TLS_CIPHER_AES_GCM_128_KEY_SIZE); memset(&back->key[TLS_CIPHER_AES_GCM_128_KEY_SIZE / 4], 0, sizeof(back->key) - TLS_CIPHER_AES_GCM_128_KEY_SIZE); memcpy(back->iv, tls_ci->iv, TLS_CIPHER_AES_GCM_128_IV_SIZE); memcpy(&back->salt, tls_ci->salt, TLS_CIPHER_AES_GCM_128_SALT_SIZE); memcpy(back->rec_no, tls_ci->rec_seq, sizeof(tls_ci->rec_seq)); /* Get an extra ref on the skb so we can wipe the key after */ skb_get(skb); err = nfp_ccm_mbox_communicate(nn, skb, NFP_CCM_TYPE_CRYPTO_ADD, sizeof(*reply), sizeof(*reply)); reply = (void *)skb->data; /* We depend on CCM MBOX code not reallocating skb we sent * so we can clear the key material out of the memory. */ if (!WARN_ON_ONCE((u8 *)back < skb->head || (u8 *)back > skb_end_pointer(skb)) && !WARN_ON_ONCE((u8 *)&reply[1] > (u8 *)back)) memzero_explicit(back, sizeof(*back)); dev_consume_skb_any(skb); /* the extra ref from skb_get() above */ if (err) { nn_dp_warn(&nn->dp, "failed to add TLS: %d (%d)\n", err, direction == TLS_OFFLOAD_CTX_DIR_TX); /* communicate frees skb on error */ goto err_conn_remove; } err = -be32_to_cpu(reply->error); if (err) { if (err == -ENOSPC) { if (!atomic_fetch_inc(&nn->ktls_no_space)) nn_info(nn, "HW TLS table full\n"); } else { nn_dp_warn(&nn->dp, "failed to add TLS, FW replied: %d\n", err); } goto err_free_skb; } if (!reply->handle[0] && !reply->handle[1]) { nn_dp_warn(&nn->dp, "FW returned NULL handle\n"); err = -EINVAL; goto err_fw_remove; } ntls = tls_driver_ctx(sk, direction); memcpy(ntls->fw_handle, reply->handle, sizeof(ntls->fw_handle)); if (direction == TLS_OFFLOAD_CTX_DIR_TX) ntls->next_seq = start_offload_tcp_sn; dev_consume_skb_any(skb); if (direction == TLS_OFFLOAD_CTX_DIR_TX) return 0; if (!nn->tlv_caps.tls_resync_ss) tls_offload_rx_resync_set_type(sk, TLS_OFFLOAD_SYNC_TYPE_CORE_NEXT_HINT); return 0; err_fw_remove: nfp_net_tls_del_fw(nn, reply->handle); err_free_skb: dev_consume_skb_any(skb); err_conn_remove: nfp_net_tls_conn_remove(nn, direction); return err; } static void nfp_net_tls_del(struct net_device *netdev, struct tls_context *tls_ctx, enum tls_offload_ctx_dir direction) { struct nfp_net *nn = netdev_priv(netdev); struct nfp_net_tls_offload_ctx *ntls; nfp_net_tls_conn_remove(nn, direction); ntls = __tls_driver_ctx(tls_ctx, direction); nfp_net_tls_del_fw(nn, ntls->fw_handle); } static int nfp_net_tls_resync(struct net_device *netdev, struct sock *sk, u32 seq, u8 *rcd_sn, enum tls_offload_ctx_dir direction) { struct nfp_net *nn = netdev_priv(netdev); struct nfp_net_tls_offload_ctx *ntls; struct nfp_crypto_req_update *req; enum nfp_ccm_type type; struct sk_buff *skb; gfp_t flags; int err; flags = direction == TLS_OFFLOAD_CTX_DIR_TX ? GFP_KERNEL : GFP_ATOMIC; skb = nfp_net_tls_alloc_simple(nn, sizeof(*req), flags); if (!skb) return -ENOMEM; ntls = tls_driver_ctx(sk, direction); req = (void *)skb->data; req->ep_id = 0; req->opcode = nfp_tls_1_2_dir_to_opcode(direction); memset(req->resv, 0, sizeof(req->resv)); memcpy(req->handle, ntls->fw_handle, sizeof(ntls->fw_handle)); req->tcp_seq = cpu_to_be32(seq); memcpy(req->rec_no, rcd_sn, sizeof(req->rec_no)); type = NFP_CCM_TYPE_CRYPTO_UPDATE; if (direction == TLS_OFFLOAD_CTX_DIR_TX) { err = nfp_net_tls_communicate_simple(nn, skb, "sync", type); if (err) return err; ntls->next_seq = seq; } else { if (nn->tlv_caps.tls_resync_ss) type = NFP_CCM_TYPE_CRYPTO_RESYNC; nfp_ccm_mbox_post(nn, skb, type, sizeof(struct nfp_crypto_reply_simple)); atomic_inc(&nn->ktls_rx_resync_sent); } return 0; } static const struct tlsdev_ops nfp_net_tls_ops = { .tls_dev_add = nfp_net_tls_add, .tls_dev_del = nfp_net_tls_del, .tls_dev_resync = nfp_net_tls_resync, }; int nfp_net_tls_rx_resync_req(struct net_device *netdev, struct nfp_net_tls_resync_req *req, void *pkt, unsigned int pkt_len) { struct nfp_net *nn = netdev_priv(netdev); struct nfp_net_tls_offload_ctx *ntls; struct ipv6hdr *ipv6h; struct tcphdr *th; struct iphdr *iph; struct sock *sk; __be32 tcp_seq; int err; iph = pkt + req->l3_offset; ipv6h = pkt + req->l3_offset; th = pkt + req->l4_offset; if ((u8 *)&th[1] > (u8 *)pkt + pkt_len) { netdev_warn_once(netdev, "invalid TLS RX resync request (l3_off: %hhu l4_off: %hhu pkt_len: %u)\n", req->l3_offset, req->l4_offset, pkt_len); err = -EINVAL; goto err_cnt_ign; } switch (ipv6h->version) { case 4: sk = inet_lookup_established(dev_net(netdev), &tcp_hashinfo, iph->saddr, th->source, iph->daddr, th->dest, netdev->ifindex); break; #if IS_ENABLED(CONFIG_IPV6) case 6: sk = __inet6_lookup_established(dev_net(netdev), &tcp_hashinfo, &ipv6h->saddr, th->source, &ipv6h->daddr, ntohs(th->dest), netdev->ifindex, 0); break; #endif default: netdev_warn_once(netdev, "invalid TLS RX resync request (l3_off: %hhu l4_off: %hhu ipver: %u)\n", req->l3_offset, req->l4_offset, iph->version); err = -EINVAL; goto err_cnt_ign; } err = 0; if (!sk) goto err_cnt_ign; if (!tls_is_sk_rx_device_offloaded(sk) || sk->sk_shutdown & RCV_SHUTDOWN) goto err_put_sock; ntls = tls_driver_ctx(sk, TLS_OFFLOAD_CTX_DIR_RX); /* some FW versions can't report the handle and report 0s */ if (memchr_inv(&req->fw_handle, 0, sizeof(req->fw_handle)) && memcmp(&req->fw_handle, &ntls->fw_handle, sizeof(ntls->fw_handle))) goto err_put_sock; /* copy to ensure alignment */ memcpy(&tcp_seq, &req->tcp_seq, sizeof(tcp_seq)); tls_offload_rx_resync_request(sk, tcp_seq); atomic_inc(&nn->ktls_rx_resync_req); sock_gen_put(sk); return 0; err_put_sock: sock_gen_put(sk); err_cnt_ign: atomic_inc(&nn->ktls_rx_resync_ign); return err; } static int nfp_net_tls_reset(struct nfp_net *nn) { struct nfp_crypto_req_reset *req; struct sk_buff *skb; skb = nfp_net_tls_alloc_simple(nn, sizeof(*req), GFP_KERNEL); if (!skb) return -ENOMEM; req = (void *)skb->data; req->ep_id = 0; return nfp_net_tls_communicate_simple(nn, skb, "reset", NFP_CCM_TYPE_CRYPTO_RESET); } int nfp_net_tls_init(struct nfp_net *nn) { struct net_device *netdev = nn->dp.netdev; int err; if (!(nn->tlv_caps.crypto_ops & NFP_NET_TLS_OPCODE_MASK)) return 0; if ((nn->tlv_caps.mbox_cmsg_types & NFP_NET_TLS_CCM_MBOX_OPS_MASK) != NFP_NET_TLS_CCM_MBOX_OPS_MASK) return 0; if (!nfp_ccm_mbox_fits(nn, sizeof(struct nfp_crypto_req_add_v6))) { nn_warn(nn, "disabling TLS offload - mbox too small: %d\n", nn->tlv_caps.mbox_len); return 0; } err = nfp_net_tls_reset(nn); if (err) return err; nn_ctrl_bar_lock(nn); nn_writel(nn, nn->tlv_caps.crypto_enable_off, 0); err = __nfp_net_reconfig(nn, NFP_NET_CFG_UPDATE_CRYPTO); nn_ctrl_bar_unlock(nn); if (err) return err; if (nn->tlv_caps.crypto_ops & NFP_NET_TLS_OPCODE_MASK_RX) { netdev->hw_features |= NETIF_F_HW_TLS_RX; netdev->features |= NETIF_F_HW_TLS_RX; } if (nn->tlv_caps.crypto_ops & NFP_NET_TLS_OPCODE_MASK_TX) { netdev->hw_features |= NETIF_F_HW_TLS_TX; netdev->features |= NETIF_F_HW_TLS_TX; } netdev->tlsdev_ops = &nfp_net_tls_ops; return 0; }