DPDK patches and discussions
 help / color / mirror / Atom feed
From: Hemant Agrawal <hemant.agrawal@nxp.com>
To: dev@dpdk.org, akhil.goyal@nxp.com
Cc: Hemant Agrawal <hemant.agrawal@nxp.com>
Subject: [dpdk-dev] [PATCH 7/8] crypto/dpaa_sec: add AES-GCM support for lookaside case
Date: Fri, 25 Oct 2019 14:03:35 +0530	[thread overview]
Message-ID: <20191025083336.24212-7-hemant.agrawal@nxp.com> (raw)
In-Reply-To: <20191025083336.24212-1-hemant.agrawal@nxp.com>

This patch add support for AES-128-GCM, when used in
proto lookaside mode.

Signed-off-by: Hemant Agrawal <hemant.agrawal@nxp.com>
---
 drivers/crypto/dpaa_sec/dpaa_sec.c | 296 ++++++++++++++++++++---------
 1 file changed, 207 insertions(+), 89 deletions(-)

diff --git a/drivers/crypto/dpaa_sec/dpaa_sec.c b/drivers/crypto/dpaa_sec/dpaa_sec.c
index 20b6e355a..e86a1e3b7 100644
--- a/drivers/crypto/dpaa_sec/dpaa_sec.c
+++ b/drivers/crypto/dpaa_sec/dpaa_sec.c
@@ -382,12 +382,14 @@ dpaa_sec_prep_ipsec_cdb(dpaa_sec_session *ses)
 	cipherdata.algtype = ses->cipher_key.alg;
 	cipherdata.algmode = ses->cipher_key.algmode;
 
-	authdata.key = (size_t)ses->auth_key.data;
-	authdata.keylen = ses->auth_key.length;
-	authdata.key_enc_flags = 0;
-	authdata.key_type = RTA_DATA_IMM;
-	authdata.algtype = ses->auth_key.alg;
-	authdata.algmode = ses->auth_key.algmode;
+	if (ses->auth_key.length) {
+		authdata.key = (size_t)ses->auth_key.data;
+		authdata.keylen = ses->auth_key.length;
+		authdata.key_enc_flags = 0;
+		authdata.key_type = RTA_DATA_IMM;
+		authdata.algtype = ses->auth_key.alg;
+		authdata.algmode = ses->auth_key.algmode;
+	}
 
 	cdb->sh_desc[0] = cipherdata.keylen;
 	cdb->sh_desc[1] = authdata.keylen;
@@ -2523,33 +2525,79 @@ dpaa_sec_sym_session_clear(struct rte_cryptodev *dev,
 
 #ifdef RTE_LIBRTE_SECURITY
 static int
-dpaa_sec_set_ipsec_session(__rte_unused struct rte_cryptodev *dev,
-			   struct rte_security_session_conf *conf,
-			   void *sess)
+dpaa_sec_ipsec_aead_init(struct rte_crypto_aead_xform *aead_xform,
+			dpaa_sec_session *session)
 {
-	struct dpaa_sec_dev_private *internals = dev->data->dev_private;
-	struct rte_security_ipsec_xform *ipsec_xform = &conf->ipsec;
-	struct rte_crypto_auth_xform *auth_xform = NULL;
-	struct rte_crypto_cipher_xform *cipher_xform = NULL;
-	dpaa_sec_session *session = (dpaa_sec_session *)sess;
-	uint32_t i;
-
 	PMD_INIT_FUNC_TRACE();
 
-	memset(session, 0, sizeof(dpaa_sec_session));
-	if (ipsec_xform->direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS) {
-		cipher_xform = &conf->crypto_xform->cipher;
-		if (conf->crypto_xform->next)
-			auth_xform = &conf->crypto_xform->next->auth;
-	} else {
-		auth_xform = &conf->crypto_xform->auth;
-		if (conf->crypto_xform->next)
-			cipher_xform = &conf->crypto_xform->next->cipher;
+	session->aead_key.data = rte_zmalloc(NULL, aead_xform->key.length,
+					       RTE_CACHE_LINE_SIZE);
+	if (session->aead_key.data == NULL && aead_xform->key.length > 0) {
+		DPAA_SEC_ERR("No Memory for aead key");
+		return -1;
 	}
-	session->proto_alg = conf->protocol;
-	session->ctxt = DPAA_SEC_IPSEC;
+	memcpy(session->aead_key.data, aead_xform->key.data,
+	       aead_xform->key.length);
+
+	session->digest_length = aead_xform->digest_length;
+	session->aead_key.length = aead_xform->key.length;
+
+	switch (aead_xform->algo) {
+	case RTE_CRYPTO_AEAD_AES_GCM:
+		switch (session->digest_length) {
+		case 8:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_GCM8;
+			break;
+		case 12:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_GCM12;
+			break;
+		case 16:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_GCM16;
+			break;
+		default:
+			DPAA_SEC_ERR("Crypto: Undefined GCM digest %d",
+				     session->digest_length);
+			return -1;
+		}
+		session->aead_key.algmode = OP_ALG_AAI_GCM;
+		session->aead_alg = RTE_CRYPTO_AEAD_AES_GCM;
+		break;
+	case RTE_CRYPTO_AEAD_AES_CCM:
+		switch (session->digest_length) {
+		case 8:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_CCM8;
+			break;
+		case 12:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_CCM12;
+			break;
+		case 16:
+			session->aead_key.alg = OP_PCL_IPSEC_AES_CCM16;
+			break;
+		default:
+			DPAA_SEC_ERR("Crypto: Undefined CCM digest %d",
+				     session->digest_length);
+			return -1;
+		}
+		session->aead_key.algmode = OP_ALG_AAI_CCM;
+		session->aead_alg = RTE_CRYPTO_AEAD_AES_CCM;
+		break;
+	default:
+		DPAA_SEC_ERR("Crypto: Undefined AEAD specified %u",
+			      aead_xform->algo);
+		return -1;
+	}
+	session->dir = (aead_xform->op == RTE_CRYPTO_AEAD_OP_ENCRYPT) ?
+				DIR_ENC : DIR_DEC;
+
+	return 0;
+}
 
-	if (cipher_xform && cipher_xform->algo != RTE_CRYPTO_CIPHER_NULL) {
+static int
+dpaa_sec_ipsec_proto_init(struct rte_crypto_cipher_xform *cipher_xform,
+	struct rte_crypto_auth_xform *auth_xform,
+	dpaa_sec_session *session)
+{
+	if (cipher_xform) {
 		session->cipher_key.data = rte_zmalloc(NULL,
 						       cipher_xform->key.length,
 						       RTE_CACHE_LINE_SIZE);
@@ -2558,31 +2606,10 @@ dpaa_sec_set_ipsec_session(__rte_unused struct rte_cryptodev *dev,
 			DPAA_SEC_ERR("No Memory for cipher key");
 			return -ENOMEM;
 		}
+
+		session->cipher_key.length = cipher_xform->key.length;
 		memcpy(session->cipher_key.data, cipher_xform->key.data,
 				cipher_xform->key.length);
-		session->cipher_key.length = cipher_xform->key.length;
-
-		switch (cipher_xform->algo) {
-		case RTE_CRYPTO_CIPHER_NULL:
-			session->cipher_key.alg = OP_PCL_IPSEC_NULL;
-			break;
-		case RTE_CRYPTO_CIPHER_AES_CBC:
-			session->cipher_key.alg = OP_PCL_IPSEC_AES_CBC;
-			session->cipher_key.algmode = OP_ALG_AAI_CBC;
-			break;
-		case RTE_CRYPTO_CIPHER_3DES_CBC:
-			session->cipher_key.alg = OP_PCL_IPSEC_3DES;
-			session->cipher_key.algmode = OP_ALG_AAI_CBC;
-			break;
-		case RTE_CRYPTO_CIPHER_AES_CTR:
-			session->cipher_key.alg = OP_PCL_IPSEC_AES_CTR;
-			session->cipher_key.algmode = OP_ALG_AAI_CTR;
-			break;
-		default:
-			DPAA_SEC_ERR("Crypto: Unsupported Cipher alg %u",
-				cipher_xform->algo);
-			goto out;
-		}
 		session->cipher_alg = cipher_xform->algo;
 	} else {
 		session->cipher_key.data = NULL;
@@ -2590,54 +2617,18 @@ dpaa_sec_set_ipsec_session(__rte_unused struct rte_cryptodev *dev,
 		session->cipher_alg = RTE_CRYPTO_CIPHER_NULL;
 	}
 
-	if (auth_xform && auth_xform->algo != RTE_CRYPTO_AUTH_NULL) {
+	if (auth_xform) {
 		session->auth_key.data = rte_zmalloc(NULL,
 						auth_xform->key.length,
 						RTE_CACHE_LINE_SIZE);
 		if (session->auth_key.data == NULL &&
 				auth_xform->key.length > 0) {
 			DPAA_SEC_ERR("No Memory for auth key");
-			rte_free(session->cipher_key.data);
 			return -ENOMEM;
 		}
+		session->auth_key.length = auth_xform->key.length;
 		memcpy(session->auth_key.data, auth_xform->key.data,
 				auth_xform->key.length);
-		session->auth_key.length = auth_xform->key.length;
-
-		switch (auth_xform->algo) {
-		case RTE_CRYPTO_AUTH_NULL:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_NULL;
-			session->digest_length = 0;
-			break;
-		case RTE_CRYPTO_AUTH_MD5_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_MD5_96;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		case RTE_CRYPTO_AUTH_SHA1_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA1_96;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		case RTE_CRYPTO_AUTH_SHA224_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA1_160;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		case RTE_CRYPTO_AUTH_SHA256_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_256_128;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		case RTE_CRYPTO_AUTH_SHA384_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_384_192;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		case RTE_CRYPTO_AUTH_SHA512_HMAC:
-			session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_512_256;
-			session->auth_key.algmode = OP_ALG_AAI_HMAC;
-			break;
-		default:
-			DPAA_SEC_ERR("Crypto: Unsupported auth alg %u",
-				auth_xform->algo);
-			goto out;
-		}
 		session->auth_alg = auth_xform->algo;
 	} else {
 		session->auth_key.data = NULL;
@@ -2645,6 +2636,133 @@ dpaa_sec_set_ipsec_session(__rte_unused struct rte_cryptodev *dev,
 		session->auth_alg = RTE_CRYPTO_AUTH_NULL;
 	}
 
+	switch (session->auth_alg) {
+	case RTE_CRYPTO_AUTH_SHA1_HMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA1_96;
+		session->auth_key.algmode = OP_ALG_AAI_HMAC;
+		break;
+	case RTE_CRYPTO_AUTH_MD5_HMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_MD5_96;
+		session->auth_key.algmode = OP_ALG_AAI_HMAC;
+		break;
+	case RTE_CRYPTO_AUTH_SHA256_HMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_256_128;
+		session->auth_key.algmode = OP_ALG_AAI_HMAC;
+		break;
+	case RTE_CRYPTO_AUTH_SHA384_HMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_384_192;
+		session->auth_key.algmode = OP_ALG_AAI_HMAC;
+		break;
+	case RTE_CRYPTO_AUTH_SHA512_HMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_SHA2_512_256;
+		session->auth_key.algmode = OP_ALG_AAI_HMAC;
+		break;
+	case RTE_CRYPTO_AUTH_AES_CMAC:
+		session->auth_key.alg = OP_PCL_IPSEC_AES_CMAC_96;
+		break;
+	case RTE_CRYPTO_AUTH_NULL:
+		session->auth_key.alg = OP_PCL_IPSEC_HMAC_NULL;
+		break;
+	case RTE_CRYPTO_AUTH_SHA224_HMAC:
+	case RTE_CRYPTO_AUTH_AES_XCBC_MAC:
+	case RTE_CRYPTO_AUTH_SNOW3G_UIA2:
+	case RTE_CRYPTO_AUTH_SHA1:
+	case RTE_CRYPTO_AUTH_SHA256:
+	case RTE_CRYPTO_AUTH_SHA512:
+	case RTE_CRYPTO_AUTH_SHA224:
+	case RTE_CRYPTO_AUTH_SHA384:
+	case RTE_CRYPTO_AUTH_MD5:
+	case RTE_CRYPTO_AUTH_AES_GMAC:
+	case RTE_CRYPTO_AUTH_KASUMI_F9:
+	case RTE_CRYPTO_AUTH_AES_CBC_MAC:
+	case RTE_CRYPTO_AUTH_ZUC_EIA3:
+		DPAA_SEC_ERR("Crypto: Unsupported auth alg %u",
+			      session->auth_alg);
+		return -1;
+	default:
+		DPAA_SEC_ERR("Crypto: Undefined Auth specified %u",
+			      session->auth_alg);
+		return -1;
+	}
+
+	switch (session->cipher_alg) {
+	case RTE_CRYPTO_CIPHER_AES_CBC:
+		session->cipher_key.alg = OP_PCL_IPSEC_AES_CBC;
+		session->cipher_key.algmode = OP_ALG_AAI_CBC;
+		break;
+	case RTE_CRYPTO_CIPHER_3DES_CBC:
+		session->cipher_key.alg = OP_PCL_IPSEC_3DES;
+		session->cipher_key.algmode = OP_ALG_AAI_CBC;
+		break;
+	case RTE_CRYPTO_CIPHER_AES_CTR:
+		session->cipher_key.alg = OP_PCL_IPSEC_AES_CTR;
+		session->cipher_key.algmode = OP_ALG_AAI_CTR;
+		break;
+	case RTE_CRYPTO_CIPHER_NULL:
+		session->cipher_key.alg = OP_PCL_IPSEC_NULL;
+		break;
+	case RTE_CRYPTO_CIPHER_SNOW3G_UEA2:
+	case RTE_CRYPTO_CIPHER_ZUC_EEA3:
+	case RTE_CRYPTO_CIPHER_3DES_ECB:
+	case RTE_CRYPTO_CIPHER_AES_ECB:
+	case RTE_CRYPTO_CIPHER_KASUMI_F8:
+		DPAA_SEC_ERR("Crypto: Unsupported Cipher alg %u",
+			      session->cipher_alg);
+		return -1;
+	default:
+		DPAA_SEC_ERR("Crypto: Undefined Cipher specified %u",
+			      session->cipher_alg);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int
+dpaa_sec_set_ipsec_session(__rte_unused struct rte_cryptodev *dev,
+			   struct rte_security_session_conf *conf,
+			   void *sess)
+{
+	struct dpaa_sec_dev_private *internals = dev->data->dev_private;
+	struct rte_security_ipsec_xform *ipsec_xform = &conf->ipsec;
+	struct rte_crypto_auth_xform *auth_xform = NULL;
+	struct rte_crypto_cipher_xform *cipher_xform = NULL;
+	struct rte_crypto_aead_xform *aead_xform = NULL;
+	dpaa_sec_session *session = (dpaa_sec_session *)sess;
+	uint32_t i;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	memset(session, 0, sizeof(dpaa_sec_session));
+	session->proto_alg = conf->protocol;
+	session->ctxt = DPAA_SEC_IPSEC;
+
+	if (conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		cipher_xform = &conf->crypto_xform->cipher;
+		if (conf->crypto_xform->next)
+			auth_xform = &conf->crypto_xform->next->auth;
+		ret = dpaa_sec_ipsec_proto_init(cipher_xform, auth_xform,
+					session);
+	} else if (conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_AUTH) {
+		auth_xform = &conf->crypto_xform->auth;
+		if (conf->crypto_xform->next)
+			cipher_xform = &conf->crypto_xform->next->cipher;
+		ret = dpaa_sec_ipsec_proto_init(cipher_xform, auth_xform,
+					session);
+	} else if (conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_AEAD) {
+		aead_xform = &conf->crypto_xform->aead;
+		ret = dpaa_sec_ipsec_aead_init(aead_xform, session);
+	} else {
+		DPAA_SEC_ERR("XFORM not specified");
+		ret = -EINVAL;
+		goto out;
+	}
+	if (ret) {
+		DPAA_SEC_ERR("Failed to process xform");
+		goto out;
+	}
+
 	if (ipsec_xform->direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS) {
 		if (ipsec_xform->tunnel.type ==
 				RTE_SECURITY_IPSEC_TUNNEL_IPV4) {
-- 
2.17.1


  parent reply	other threads:[~2019-10-25  8:37 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-25  8:33 [dpdk-dev] [PATCH 1/8] crypto/dpaa_sec: fix to set PDCP capability flags Hemant Agrawal
2019-10-25  8:33 ` [dpdk-dev] [PATCH 2/8] crypto/dpaa2_sec: add check for the session validity Hemant Agrawal
2019-10-25  8:33 ` [dpdk-dev] [PATCH 3/8] crypto/dpaa_sec: " Hemant Agrawal
2019-10-25  8:33 ` [dpdk-dev] [PATCH 4/8] crypto/dpaa2_sec: adding NULL cipher or NULL auth Hemant Agrawal
2019-10-25  8:33 ` [dpdk-dev] [PATCH 5/8] crypto/dpaa_sec: " Hemant Agrawal
2019-10-25  8:33 ` [dpdk-dev] [PATCH 6/8] crypto/dpaa2_sec: add AES-GCM support for lookaside case Hemant Agrawal
2019-10-25  8:33 ` Hemant Agrawal [this message]
2019-10-25  8:33 ` [dpdk-dev] [PATCH 8/8] test/cryptodev: enable additional cases for dpaax Hemant Agrawal
2019-11-01 17:51 ` [dpdk-dev] [PATCH v2 01/13] crypto/dpaa_sec: fix to set PDCP capability flags Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 02/13] crypto/dpaa2_sec: add check for the session validity Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 03/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 04/13] crypto/dpaa2_sec: adding NULL cipher or NULL auth Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 05/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 06/13] crypto/dpaa2_sec: add AES-GCM support for lookaside case Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 07/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 08/13] test/cryptodev: enable additional cases for dpaax Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 09/13] crypto/dpaa2_sec: enable warning with truncated sha256 Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 10/13] crypto/dpaa2_sec: remove unwanted context type check Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 11/13] crypto/dpaa_sec: use macros in queue attach and detach Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 12/13] crypto/dpaa2_sec: use aes-ctr initial counter as 1 Hemant Agrawal
2019-11-01 17:51   ` [dpdk-dev] [PATCH v2 13/13] crypto/dpaa_sec: enable ipsec aes-ctr to use nonce Hemant Agrawal
2019-11-06  5:17   ` [dpdk-dev] [PATCH v3 01/13] crypto/dpaa_sec: fix to set PDCP capability flags Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 02/13] crypto/dpaa2_sec: add check for the session validity Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 03/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 04/13] crypto/dpaa2_sec: adding NULL cipher or NULL auth Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 05/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 06/13] crypto/dpaa2_sec: add AES-GCM support for lookaside case Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 07/13] crypto/dpaa_sec: " Hemant Agrawal
2019-11-07  8:48       ` Hemant Agrawal
2019-11-07 10:07         ` Akhil Goyal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 08/13] test/cryptodev: enable additional cases for dpaax Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 09/13] crypto/dpaa2_sec: enable warning with truncated sha256 Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 10/13] crypto/dpaa2_sec: remove unwanted context type check Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 11/13] crypto/dpaa_sec: use macros in queue attach and detach Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 12/13] crypto/dpaa2_sec: use aes-ctr initial counter as 1 Hemant Agrawal
2019-11-06  5:17     ` [dpdk-dev] [PATCH v3 13/13] crypto/dpaa_sec: enable ipsec aes-ctr to use nonce Hemant Agrawal
2019-11-06 13:09     ` [dpdk-dev] [PATCH v3 01/13] crypto/dpaa_sec: fix to set PDCP capability flags Akhil Goyal

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191025083336.24212-7-hemant.agrawal@nxp.com \
    --to=hemant.agrawal@nxp.com \
    --cc=akhil.goyal@nxp.com \
    --cc=dev@dpdk.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).