From: Stefan Schmidt <s.schmidt@samsung.com>
To: 'Alexander Aring' <alex.aring@gmail.com>,
linux-bluetooth@vger.kernel.org
Cc: linux-wpan@vger.kernel.org, kernel@pengutronix.de,
'Jukka Rissanen' <jukka.rissanen@linux.intel.com>,
'Martin Townsend' <mtownsend1973@gmail.com>
Subject: Re: [PATCHv5 bluetooth-next 2/3] 6lowpan: add udp compression via nhc layer
Date: Fri, 09 Jan 2015 16:30:10 +0000 [thread overview]
Message-ID: <061301d02c29$9343f4d0$b9cbde70$@samsung.com> (raw)
In-Reply-To: <1420818179-18585-3-git-send-email-alex.aring@gmail.com>
Hello.
On 09/01/15 16:42, Alexander Aring wrote:
> This patch move UDP header compression and uncompression into the
> generic 6LoWPAN nhc header compression layer. Moreover this patch
> activates the nhc layer compression in iphc compression and
> uncompression functions.
>
> Signed-off-by: Alexander Aring <alex.aring@gmail.com>
> Cc: Jukka Rissanen <jukka.rissanen@linux.intel.com>
> Cc: Martin Townsend <mtownsend1973@gmail.com>
> ---
> net/6lowpan/Kconfig | 19 ++++-
> net/6lowpan/Makefile | 5 +-
> net/6lowpan/iphc.c | 194
> +++++++-------------------------------------------
> net/6lowpan/nhc_udp.c | 157 ++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 205 insertions(+), 170 deletions(-)
> create mode 100644 net/6lowpan/nhc_udp.c
>
> diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig
> index e4a02ef..e45c963 100644
> --- a/net/6lowpan/Kconfig
> +++ b/net/6lowpan/Kconfig
> @@ -1,6 +1,23 @@
> -config 6LOWPAN
> +menuconfig 6LOWPAN
> tristate "6LoWPAN Support"
> depends on IPV6
> ---help---
> This enables IPv6 over Low power Wireless Personal Area Network
> -
> "6LoWPAN" which is supported by IEEE 802.15.4 or Bluetooth
> stacks.
> +
> +menuconfig 6LOWPAN_NHC
> + tristate "Next Header Compression Support"
> + depends on 6LOWPAN
> + default y
> + ---help---
> + Support for next header compression.
> +
> +if 6LOWPAN_NHC
> +
> +config 6LOWPAN_NHC_UDP
> + tristate "UDP Header Support"
> + default y
> + ---help---
> + 6LoWPAN IPv6 UDP Header compression according to RFC6282.
> +
> +endif
> diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile
> index 4215602..abf551d 100644
> --- a/net/6lowpan/Makefile
> +++ b/net/6lowpan/Makefile
> @@ -1,3 +1,6 @@
> -obj-$(CONFIG_6LOWPAN) := 6lowpan.o
> +obj-$(CONFIG_6LOWPAN) += 6lowpan.o
>
> 6lowpan-y := iphc.o nhc.o
> +
> +#rfc6282 nhcs
> +obj-$(CONFIG_6LOWPAN_NHC_UDP) += nhc_udp.o
> diff --git a/net/6lowpan/iphc.c b/net/6lowpan/iphc.c
> index 32ffec6..390bdd9 100644
> --- a/net/6lowpan/iphc.c
> +++ b/net/6lowpan/iphc.c
> @@ -54,6 +54,8 @@
> #include <net/ipv6.h>
> #include <net/af_ieee802154.h>
>
> +#include "nhc.h"
> +
> /* Uncompress address function for source and
> * destination address(non-multicast).
> *
> @@ -224,77 +226,6 @@ static int lowpan_uncompress_multicast_daddr(struct
> sk_buff *skb,
> return 0;
> }
>
> -static int uncompress_udp_header(struct sk_buff *skb, struct udphdr *uh)
> -{
> - bool fail;
> - u8 tmp = 0, val = 0;
> -
> - fail = lowpan_fetch_skb(skb, &tmp, sizeof(tmp));
> -
> - if ((tmp & LOWPAN_NHC_UDP_MASK) == LOWPAN_NHC_UDP_ID) {
> - pr_debug("UDP header uncompression\n");
> - switch (tmp & LOWPAN_NHC_UDP_CS_P_11) {
> - case LOWPAN_NHC_UDP_CS_P_00:
> - fail |= lowpan_fetch_skb(skb, &uh->source,
> - sizeof(uh->source));
> - fail |= lowpan_fetch_skb(skb, &uh->dest,
> - sizeof(uh->dest));
> - break;
> - case LOWPAN_NHC_UDP_CS_P_01:
> - fail |= lowpan_fetch_skb(skb, &uh->source,
> - sizeof(uh->source));
> - fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> - uh->dest = htons(val + LOWPAN_NHC_UDP_8BIT_PORT);
> - break;
> - case LOWPAN_NHC_UDP_CS_P_10:
> - fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> - uh->source = htons(val +
> LOWPAN_NHC_UDP_8BIT_PORT);
> - fail |= lowpan_fetch_skb(skb, &uh->dest,
> - sizeof(uh->dest));
> - break;
> - case LOWPAN_NHC_UDP_CS_P_11:
> - fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> - uh->source = htons(LOWPAN_NHC_UDP_4BIT_PORT +
> - (val >> 4));
> - uh->dest = htons(LOWPAN_NHC_UDP_4BIT_PORT +
> - (val & 0x0f));
> - break;
> - default:
> - pr_debug("ERROR: unknown UDP format\n");
> - goto err;
> - }
> -
> - pr_debug("uncompressed UDP ports: src = %d, dst = %d\n",
> - ntohs(uh->source), ntohs(uh->dest));
> -
> - /* checksum */
> - if (tmp & LOWPAN_NHC_UDP_CS_C) {
> - pr_debug_ratelimited("checksum elided currently
> not supported\n");
> - goto err;
> - } else {
> - fail |= lowpan_fetch_skb(skb, &uh->check,
> - sizeof(uh->check));
> - }
> -
> - /* UDP length needs to be infered from the lower layers
> - * here, we obtain the hint from the remaining size of the
> - * frame
> - */
> - uh->len = htons(skb->len + sizeof(struct udphdr));
> - pr_debug("uncompressed UDP length: src = %d",
> ntohs(uh->len));
> - } else {
> - pr_debug("ERROR: unsupported NH format\n");
> - goto err;
> - }
> -
> - if (fail)
> - goto err;
> -
> - return 0;
> -err:
> - return -EINVAL;
> -}
> -
> /* TTL uncompression values */
> static const u8 lowpan_ttl_values[] = { 0, 1, 64, 255 };
>
> @@ -425,29 +356,11 @@ lowpan_header_decompress(struct sk_buff *skb, struct
> net_device *dev,
> return -EINVAL;
> }
>
> - /* UDP data uncompression */
> + /* Next header data uncompression */
> if (iphc0 & LOWPAN_IPHC_NH_C) {
> - struct udphdr uh;
> - const int needed = sizeof(struct udphdr) + sizeof(hdr);
> -
> - if (uncompress_udp_header(skb, &uh))
> - return -EINVAL;
> -
> - /* replace the compressed UDP head by the uncompressed UDP
> - * header
> - */
> - err = skb_cow(skb, needed);
> - if (unlikely(err))
> + err = lowpan_nhc_do_uncompression(skb, dev, &hdr);
> + if (err < 0)
> return err;
> -
> - skb_push(skb, sizeof(struct udphdr));
> - skb_reset_transport_header(skb);
> - skb_copy_to_linear_data(skb, &uh, sizeof(struct udphdr));
> -
> - raw_dump_table(__func__, "raw UDP header dump",
> - (u8 *)&uh, sizeof(uh));
> -
> - hdr.nexthdr = UIP_PROTO_UDP;
> } else {
> err = skb_cow(skb, sizeof(hdr));
> if (unlikely(err))
> @@ -500,71 +413,6 @@ static u8 lowpan_compress_addr_64(u8 **hc_ptr, u8
> shift,
> return rol8(val, shift);
> }
>
> -static void compress_udp_header(u8 **hc_ptr, struct sk_buff *skb)
> -{
> - struct udphdr *uh;
> - u8 tmp;
> -
> - /* In the case of RAW sockets the transport header is not set by
> - * the ip6 stack so we must set it ourselves
> - */
> - if (skb->transport_header == skb->network_header)
> - skb_set_transport_header(skb, sizeof(struct ipv6hdr));
> -
> - uh = udp_hdr(skb);
> -
> - if (((ntohs(uh->source) & LOWPAN_NHC_UDP_4BIT_MASK) ==
> - LOWPAN_NHC_UDP_4BIT_PORT) &&
> - ((ntohs(uh->dest) & LOWPAN_NHC_UDP_4BIT_MASK) ==
> - LOWPAN_NHC_UDP_4BIT_PORT)) {
> - pr_debug("UDP header: both ports compression to 4
> bits\n");
> - /* compression value */
> - tmp = LOWPAN_NHC_UDP_CS_P_11;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - /* source and destination port */
> - tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT +
> - ((ntohs(uh->source) - LOWPAN_NHC_UDP_4BIT_PORT) <<
> 4);
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - } else if ((ntohs(uh->dest) & LOWPAN_NHC_UDP_8BIT_MASK) ==
> - LOWPAN_NHC_UDP_8BIT_PORT) {
> - pr_debug("UDP header: remove 8 bits of dest\n");
> - /* compression value */
> - tmp = LOWPAN_NHC_UDP_CS_P_01;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - /* source port */
> - lowpan_push_hc_data(hc_ptr, &uh->source,
> sizeof(uh->source));
> - /* destination port */
> - tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - } else if ((ntohs(uh->source) & LOWPAN_NHC_UDP_8BIT_MASK) ==
> - LOWPAN_NHC_UDP_8BIT_PORT) {
> - pr_debug("UDP header: remove 8 bits of source\n");
> - /* compression value */
> - tmp = LOWPAN_NHC_UDP_CS_P_10;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - /* source port */
> - tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - /* destination port */
> - lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
> - } else {
> - pr_debug("UDP header: can't compress\n");
> - /* compression value */
> - tmp = LOWPAN_NHC_UDP_CS_P_00;
> - lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> - /* source port */
> - lowpan_push_hc_data(hc_ptr, &uh->source,
> sizeof(uh->source));
> - /* destination port */
> - lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
> - }
> -
> - /* checksum is always inline */
> - lowpan_push_hc_data(hc_ptr, &uh->check, sizeof(uh->check));
> -
> - /* skip the UDP header */
> - skb_pull(skb, sizeof(struct udphdr));
> -}
> -
> int lowpan_header_compress(struct sk_buff *skb, struct net_device *dev,
> unsigned short type, const void *_daddr,
> const void *_saddr, unsigned int len)
> @@ -572,7 +420,7 @@ int lowpan_header_compress(struct sk_buff *skb, struct
> net_device *dev,
> u8 tmp, iphc0, iphc1, *hc_ptr;
> struct ipv6hdr *hdr;
> u8 head[100] = {};
> - int addr_type;
> + int ret, addr_type;
>
> if (type != ETH_P_IPV6)
> return -EINVAL;
> @@ -649,13 +497,12 @@ int lowpan_header_compress(struct sk_buff *skb,
> struct net_device *dev,
>
> /* NOTE: payload length is always compressed */
>
> - /* Next Header is compress if UDP */
> - if (hdr->nexthdr == UIP_PROTO_UDP)
> - iphc0 |= LOWPAN_IPHC_NH_C;
> -
> - if ((iphc0 & LOWPAN_IPHC_NH_C) == 0)
> - lowpan_push_hc_data(&hc_ptr, &hdr->nexthdr,
> - sizeof(hdr->nexthdr));
> + /* Check if we provide the nhc format for nexthdr and compression
> + * functionality. If not nexthdr is handled inline and not
> compressed.
> + */
> + ret = lowpan_nhc_check_compression(skb, hdr, &hc_ptr, &iphc0);
> + if (ret < 0)
> + return ret;
>
> /* Hop limit
> * if 1: compress, encoding is 01
> @@ -741,9 +588,12 @@ int lowpan_header_compress(struct sk_buff *skb,
> struct net_device *dev,
> }
> }
>
> - /* UDP header compression */
> - if (hdr->nexthdr == UIP_PROTO_UDP)
> - compress_udp_header(&hc_ptr, skb);
> + /* next header compression */
> + if (iphc0 & LOWPAN_IPHC_NH_C) {
> + ret = lowpan_nhc_do_compression(skb, hdr, &hc_ptr);
> + if (ret < 0)
> + return ret;
> + }
>
> head[0] = iphc0;
> head[1] = iphc1;
> @@ -761,4 +611,12 @@ int lowpan_header_compress(struct sk_buff *skb,
> struct net_device *dev,
> }
> EXPORT_SYMBOL_GPL(lowpan_header_compress);
>
> +static int __init lowpan_module_init(void)
> +{
> + request_module_nowait("nhc_udp");
> +
> + return 0;
> +}
> +module_init(lowpan_module_init);
> +
> MODULE_LICENSE("GPL");
> diff --git a/net/6lowpan/nhc_udp.c b/net/6lowpan/nhc_udp.c
> new file mode 100644
> index 0000000..c6bcaeb
> --- /dev/null
> +++ b/net/6lowpan/nhc_udp.c
> @@ -0,0 +1,157 @@
> +/*
> + * 6LoWPAN IPv6 UDP compression according to RFC6282
> + *
> + *
> + * Authors:
> + * Alexander Aring <aar@pengutronix.de>
> + *
> + * Orignal written by:
> + * Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
> + * Jon Smirl <jonsmirl@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version
> + * 2 of the License, or (at your option) any later version.
> + */
> +
> +#include "nhc.h"
> +
> +#define LOWPAN_NHC_UDP_IDLEN 1
> +
> +static int udp_uncompress(struct sk_buff *skb, size_t needed)
> +{
> + u8 tmp = 0, val = 0;
> + struct udphdr uh;
> + bool fail;
> + int err;
> +
> + fail = lowpan_fetch_skb(skb, &tmp, sizeof(tmp));
> +
> + pr_debug("UDP header uncompression\n");
> + switch (tmp & LOWPAN_NHC_UDP_CS_P_11) {
> + case LOWPAN_NHC_UDP_CS_P_00:
> + fail |= lowpan_fetch_skb(skb, &uh.source,
> sizeof(uh.source));
> + fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest));
> + break;
> + case LOWPAN_NHC_UDP_CS_P_01:
> + fail |= lowpan_fetch_skb(skb, &uh.source,
> sizeof(uh.source));
> + fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> + uh.dest = htons(val + LOWPAN_NHC_UDP_8BIT_PORT);
> + break;
> + case LOWPAN_NHC_UDP_CS_P_10:
> + fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> + uh.source = htons(val + LOWPAN_NHC_UDP_8BIT_PORT);
> + fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest));
> + break;
> + case LOWPAN_NHC_UDP_CS_P_11:
> + fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
> + uh.source = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val >> 4));
> + uh.dest = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val & 0x0f));
> + break;
> + default:
> + BUG();
> + }
> +
> + pr_debug("uncompressed UDP ports: src = %d, dst = %d\n",
> + ntohs(uh.source), ntohs(uh.dest));
> +
> + /* checksum */
> + if (tmp & LOWPAN_NHC_UDP_CS_C) {
> + pr_debug_ratelimited("checksum elided currently not
> supported\n");
> + fail = true;
> + } else {
> + fail |= lowpan_fetch_skb(skb, &uh.check,
> sizeof(uh.check));
> + }
> +
> + if (fail)
> + return -EINVAL;
> +
> + /* UDP length needs to be infered from the lower layers
> + * here, we obtain the hint from the remaining size of the
> + * frame
> + */
> + uh.len = htons(skb->len + sizeof(struct udphdr));
> + pr_debug("uncompressed UDP length: src = %d", ntohs(uh.len));
> +
> + /* replace the compressed UDP head by the uncompressed UDP
> + * header
> + */
> + err = skb_cow(skb, needed);
> + if (unlikely(err))
> + return err;
> +
> + skb_push(skb, sizeof(struct udphdr));
> + skb_copy_to_linear_data(skb, &uh, sizeof(struct udphdr));
> +
> + return 0;
> +}
> +
> +static int udp_compress(struct sk_buff *skb, u8 **hc_ptr)
> +{
> + const struct udphdr *uh = udp_hdr(skb);
> + u8 tmp;
> +
> + if (((ntohs(uh->source) & LOWPAN_NHC_UDP_4BIT_MASK) ==
> + LOWPAN_NHC_UDP_4BIT_PORT) &&
> + ((ntohs(uh->dest) & LOWPAN_NHC_UDP_4BIT_MASK) ==
> + LOWPAN_NHC_UDP_4BIT_PORT)) {
> + pr_debug("UDP header: both ports compression to 4
> bits\n");
> + /* compression value */
> + tmp = LOWPAN_NHC_UDP_CS_P_11;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + /* source and destination port */
> + tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT +
> + ((ntohs(uh->source) - LOWPAN_NHC_UDP_4BIT_PORT) <<
> 4);
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + } else if ((ntohs(uh->dest) & LOWPAN_NHC_UDP_8BIT_MASK) ==
> + LOWPAN_NHC_UDP_8BIT_PORT) {
> + pr_debug("UDP header: remove 8 bits of dest\n");
> + /* compression value */
> + tmp = LOWPAN_NHC_UDP_CS_P_01;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + /* source port */
> + lowpan_push_hc_data(hc_ptr, &uh->source,
> sizeof(uh->source));
> + /* destination port */
> + tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + } else if ((ntohs(uh->source) & LOWPAN_NHC_UDP_8BIT_MASK) ==
> + LOWPAN_NHC_UDP_8BIT_PORT) {
> + pr_debug("UDP header: remove 8 bits of source\n");
> + /* compression value */
> + tmp = LOWPAN_NHC_UDP_CS_P_10;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + /* source port */
> + tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + /* destination port */
> + lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
> + } else {
> + pr_debug("UDP header: can't compress\n");
> + /* compression value */
> + tmp = LOWPAN_NHC_UDP_CS_P_00;
> + lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
> + /* source port */
> + lowpan_push_hc_data(hc_ptr, &uh->source,
> sizeof(uh->source));
> + /* destination port */
> + lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
> + }
> +
> + /* checksum is always inline */
> + lowpan_push_hc_data(hc_ptr, &uh->check, sizeof(uh->check));
> +
> + return 0;
> +}
> +
> +static void udp_nhid_setup(struct lowpan_nhc *nhc)
> +{
> + nhc->id[0] = LOWPAN_NHC_UDP_ID;
> + nhc->idmask[0] = LOWPAN_NHC_UDP_MASK;
> +}
> +
> +LOWPAN_NHC(nhc_udp, "RFC6282 UDP", NEXTHDR_UDP, sizeof(struct udphdr),
> + udp_nhid_setup, LOWPAN_NHC_UDP_IDLEN, udp_uncompress,
> udp_compress);
> +
> +module_lowpan_nhc(nhc_udp);
> +MODULE_DESCRIPTION("6LoWPAN next header RFC6282 UDP compression");
> +MODULE_LICENSE("GPL");
>
Reviewed-by: Stefan Schmidt <s.schmidt@samsung.com>
regards
Stefan Schmidt
next prev parent reply other threads:[~2015-01-09 16:30 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-01-09 15:42 [PATCHv5 bluetooth-next 0/3] 6lowpan: introduce nhc framework Alexander Aring
2015-01-09 15:42 ` [PATCHv5 bluetooth-next 1/3] 6lowpan: add generic nhc layer interface Alexander Aring
2015-01-09 16:30 ` Stefan Schmidt
2015-01-09 15:42 ` [PATCHv5 bluetooth-next 2/3] 6lowpan: add udp compression via nhc layer Alexander Aring
2015-01-09 16:30 ` Stefan Schmidt [this message]
2015-01-09 15:42 ` [PATCHv5 bluetooth-next 3/3] 6lowpan: nhc: add other known rfc6282 compressions Alexander Aring
2015-01-09 16:30 ` Stefan Schmidt
2015-01-12 9:23 ` [PATCHv5 bluetooth-next 0/3] 6lowpan: introduce nhc framework Jukka Rissanen
2015-02-05 9:07 ` Alexander Aring
2015-02-14 21:33 ` Alexander Aring
2015-02-14 21:46 ` Marcel Holtmann
2015-02-14 21:57 ` Alexander Aring
2015-02-14 22:08 ` Marcel Holtmann
2015-02-14 22:10 ` Marcel Holtmann
2015-02-14 22:42 ` Alexander Aring
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='061301d02c29$9343f4d0$b9cbde70$@samsung.com' \
--to=s.schmidt@samsung.com \
--cc=alex.aring@gmail.com \
--cc=jukka.rissanen@linux.intel.com \
--cc=kernel@pengutronix.de \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-wpan@vger.kernel.org \
--cc=mtownsend1973@gmail.com \
/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