From: Ralf Lici <ralf@mandelbit.com>
To: netdev@vger.kernel.org
Cc: "David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@kernel.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Simon Horman" <horms@kernel.org>,
"Daniel Gröber" <dxld@darkboxed.org>,
"Antonio Quartulli" <antonio@mandelbit.com>,
"Xavier Hsinyuan" <xavierhsinyuan@outlook.com>,
linux-kernel@vger.kernel.org
Subject: [RFC net-next v2 09/18] net: ipxlat: add IPv6 packet validation
Date: Sat, 24 Oct 2026 11:25:19 +0200 [thread overview]
Message-ID: <20261024092532.172005-10-ralf@mandelbit.com> (raw)
In-Reply-To: <20261024092532.172005-1-ralf@mandelbit.com>
Validate IPv6 base and extension headers, transport headers, checksums
and bounded ICMPv6 quotes while recording the headers that survive
translation.
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
Changes since RFC v1 https://lore.kernel.org/netdev/20260319151230.655687-6-ralf@mandelbit.com/
- Split IPv6 parsing out of the v1 combined packet.c implementation.
- Replace the IPv6 header walk with one explicitly bounded by the packet
or quoted-datagram length.
- Include the IPv6 checksum declarations used by the ICMPv6 validator.
(Xavier)
- Validate the TCP data offset and pull the complete declared header
before later translation stages use it. (Sashiko)
- Accept a zero IPv6 payload length for the kernel's oversized GSO
representation instead of rejecting it unconditionally. (Sashiko)
- Validate ICMPv6 checksums represented by CHECKSUM_COMPLETE. (Sashiko)
- Stop IPv6 header walking at an Authentication Header instead of
silently removing it from the translated packet.
- Reject a zero UDPv6 checksum unless CHECKSUM_PARTIAL indicates that
checksum completion is still pending.
net/ipxlat/Makefile | 1 +
net/ipxlat/packet6.c | 468 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 469 insertions(+)
create mode 100644 net/ipxlat/packet6.c
diff --git a/net/ipxlat/Makefile b/net/ipxlat/Makefile
index f517eaa75423..56c8a78fc863 100644
--- a/net/ipxlat/Makefile
+++ b/net/ipxlat/Makefile
@@ -8,3 +8,4 @@ ipxlat-objs += config.o
ipxlat-objs += core.o
ipxlat-objs += address.o
ipxlat-objs += packet4.o
+ipxlat-objs += packet6.o
diff --git a/net/ipxlat/packet6.c b/net/ipxlat/packet6.c
new file mode 100644
index 000000000000..c772a2967b30
--- /dev/null
+++ b/net/ipxlat/packet6.c
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: GPL-2.0
+/* IPXLAT - Stateless IP/ICMP Translation (SIIT) engine
+ *
+ * Copyright (C) 2024- Alberto Leiva Popper <ydahhrk@gmail.com>
+ * Copyright (C) 2026- Mandelbit SRL
+ * Copyright (C) 2026- Daniel Gröber <dxld@darkboxed.org>
+ *
+ * Author: Alberto Leiva Popper <ydahhrk@gmail.com>
+ * Antonio Quartulli <antonio@mandelbit.com>
+ * Daniel Gröber <dxld@darkboxed.org>
+ * Ralf Lici <ralf@mandelbit.com>
+ */
+
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+
+#include <net/ip6_checksum.h>
+#include <net/ipxlat.h>
+
+#include "packet.h"
+
+static bool ipxlat_v6_validate_saddr(const struct in6_addr *addr6)
+{
+ return !(ipv6_addr_any(addr6) || ipv6_addr_loopback(addr6) ||
+ ipv6_addr_is_multicast(addr6));
+}
+
+static int ipxlat_v6_pull_l4(struct sk_buff *skb, unsigned int l4_offset,
+ u8 l4_proto, bool *is_icmp_err)
+{
+ unsigned int tcp_hdr_len;
+ struct icmp6hdr *icmp;
+ struct udphdr *udp;
+ struct tcphdr *tcp;
+
+ *is_icmp_err = false;
+
+ switch (l4_proto) {
+ case NEXTHDR_TCP:
+ if (unlikely(!pskb_may_pull(skb, l4_offset + sizeof(*tcp))))
+ return -EINVAL;
+
+ tcp = (struct tcphdr *)(skb->data + l4_offset);
+ if (unlikely(tcp->doff < 5))
+ return -EINVAL;
+
+ tcp_hdr_len = __tcp_hdrlen(tcp);
+ if (unlikely(!pskb_may_pull(skb, l4_offset + tcp_hdr_len)))
+ return -EINVAL;
+
+ return tcp_hdr_len;
+ case NEXTHDR_UDP:
+ if (unlikely(!pskb_may_pull(skb, l4_offset + sizeof(*udp))))
+ return -EINVAL;
+
+ udp = (struct udphdr *)(skb->data + l4_offset);
+ if (unlikely(ntohs(udp->len) < sizeof(*udp)))
+ return -EINVAL;
+
+ return sizeof(struct udphdr);
+ case NEXTHDR_ICMP:
+ if (unlikely(!pskb_may_pull(skb, l4_offset + sizeof(*icmp))))
+ return -EINVAL;
+
+ icmp = (struct icmp6hdr *)(skb->data + l4_offset);
+ *is_icmp_err = icmpv6_is_err(icmp->icmp6_type);
+ return sizeof(struct icmp6hdr);
+ default:
+ return 0;
+ }
+}
+
+/* see ipxlat_v4_pull_icmp_l4: a minimum ICMP quote need not contain a
+ * complete TCP header, so only inspect fields present in the first 8 bytes
+ */
+static int ipxlat_v6_pull_icmp_l4(struct sk_buff *skb, unsigned int l4_offset,
+ unsigned int quote_end, u8 l4_proto)
+{
+ struct icmp6hdr *icmp;
+ unsigned int need;
+
+ switch (l4_proto) {
+ case NEXTHDR_ICMP:
+ need = sizeof(*icmp);
+ break;
+ case NEXTHDR_UDP:
+ need = sizeof(struct udphdr);
+ break;
+ default:
+ return 0;
+ }
+
+ if (unlikely(l4_offset > quote_end || need > quote_end - l4_offset ||
+ !pskb_may_pull(skb, l4_offset + need)))
+ return -EINVAL;
+
+ if (l4_proto == NEXTHDR_ICMP) {
+ icmp = (struct icmp6hdr *)(skb->data + l4_offset);
+ if (unlikely(icmpv6_is_err(icmp->icmp6_type)))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ipxlat_v6_icmp_quote_end(const struct sk_buff *skb,
+ unsigned int payload_off,
+ unsigned int *quote_end)
+{
+ const struct icmp6hdr *icmp;
+ unsigned int quote_len;
+
+ icmp = (const struct icmp6hdr *)(skb->data + payload_off -
+ sizeof(*icmp));
+ quote_len = icmp->icmp6_datagram_len << 3;
+ if (!quote_len) {
+ *quote_end = skb->len;
+ return 0;
+ }
+
+ /* a nonzero RFC 4884 delimiter describes at least 128 quoted bytes */
+ if (unlikely(quote_len < ICMP_EXT_ORIG_DGRAM_MIN_LEN ||
+ payload_off > skb->len ||
+ quote_len > skb->len - payload_off))
+ return -EINVAL;
+
+ *quote_end = payload_off + quote_len;
+ return 0;
+}
+
+/* Walk one IPv6 header chain without reading past packet_end. Outer packets
+ * also reject an active Routing Header; quoted packets are parsed only far
+ * enough to translate the bytes they contain.
+ */
+struct ipxlat_v6_hdrs {
+ unsigned int fragh_off;
+ unsigned int l4_off;
+ u8 l4_proto;
+ bool has_l4;
+};
+
+static bool ipxlat_v6_may_pull(struct sk_buff *skb, unsigned int offset,
+ unsigned int len, unsigned int packet_end)
+{
+ if (offset > packet_end || len > packet_end - offset)
+ return false;
+ return pskb_may_pull(skb, offset + len);
+}
+
+static int ipxlat_v6_walk_hdrs(struct sk_buff *skb, unsigned int l3_offset,
+ unsigned int packet_end,
+ struct ipxlat_v6_hdrs *hdrs,
+ bool check_rh)
+{
+ unsigned int ext_len, off;
+ struct ipv6_opt_hdr *ext;
+ struct ipv6_rt_hdr *rh;
+ struct frag_hdr *frag;
+ struct ipv6hdr *ip6h;
+ u8 nexthdr;
+ u32 ptr;
+
+ if (unlikely(!ipxlat_v6_may_pull(skb, l3_offset, sizeof(*ip6h),
+ packet_end)))
+ return -EINVAL;
+
+ ip6h = (struct ipv6hdr *)(skb->data + l3_offset);
+ nexthdr = ip6h->nexthdr;
+ off = l3_offset + sizeof(*ip6h);
+ hdrs->has_l4 = true;
+ hdrs->fragh_off = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ /* AH is protected payload here: translation preserves it but
+ * does not inspect or rewrite anything after it
+ */
+ if (nexthdr == NEXTHDR_AUTH || nexthdr == NEXTHDR_NONE)
+ break;
+
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ if (unlikely(hdrs->fragh_off ||
+ !ipxlat_v6_may_pull(skb, off,
+ sizeof(*frag),
+ packet_end)))
+ return -EINVAL;
+
+ frag = (struct frag_hdr *)(skb->data + off);
+ hdrs->fragh_off = off;
+ nexthdr = frag->nexthdr;
+ off += sizeof(*frag);
+
+ /* IPv6 fragment offsets include any extension headers
+ * after the Fragment Header. Later fragments do not
+ * carry those headers, so a stateless translator
+ * cannot remove them and adjust every offset. AH also
+ * cannot survive the outer header rewrite because the
+ * translator cannot update its integrity value.
+ * ipv6_ext_hdr deliberately excludes ESP, which is
+ * preserved as opaque payload as required by RFC 7915.
+ */
+ if (unlikely(ipv6_ext_hdr(nexthdr) &&
+ nexthdr != NEXTHDR_NONE)) {
+ ipxlat_mark_icmp_drop(skb, ICMPV6_DEST_UNREACH,
+ ICMPV6_ADM_PROHIBITED, 0);
+ return -EINVAL;
+ }
+
+ if (!ipxlat_is_first_frag6(frag)) {
+ hdrs->l4_proto = nexthdr;
+ hdrs->l4_off = off;
+ hdrs->has_l4 = false;
+ return 0;
+ }
+ continue;
+ }
+
+ if (unlikely(!ipxlat_v6_may_pull(skb, off, sizeof(*ext),
+ packet_end)))
+ return -EINVAL;
+
+ ext = (struct ipv6_opt_hdr *)(skb->data + off);
+ ext_len = ipv6_optlen(ext);
+ if (unlikely(!ipxlat_v6_may_pull(skb, off, ext_len,
+ packet_end)))
+ return -EINVAL;
+
+ ext = (struct ipv6_opt_hdr *)(skb->data + off);
+ if (check_rh && nexthdr == NEXTHDR_ROUTING) {
+ rh = (struct ipv6_rt_hdr *)ext;
+ ptr = off - l3_offset +
+ offsetof(struct ipv6_rt_hdr, segments_left);
+
+ if (unlikely(rh->segments_left)) {
+ ipxlat_mark_icmp_drop(skb, ICMPV6_PARAMPROB,
+ ICMPV6_HDR_FIELD, ptr);
+ return -EINVAL;
+ }
+ }
+
+ nexthdr = ext->nexthdr;
+ off += ext_len;
+ }
+
+ hdrs->l4_proto = nexthdr;
+ hdrs->l4_off = off;
+ return 0;
+}
+
+static int ipxlat_v6_pull_outer_l3(struct sk_buff *skb)
+{
+ const unsigned int l3_off = skb_network_offset(skb);
+ unsigned int payload_len, skb_payload_len;
+ struct ipv6hdr *l3_hdr;
+
+ if (unlikely(!pskb_may_pull(skb, l3_off + sizeof(*l3_hdr))))
+ return -EINVAL;
+ l3_hdr = ipv6_hdr(skb);
+
+ if (unlikely(l3_hdr->version != 6 ||
+ !ipxlat_v6_validate_saddr(&l3_hdr->saddr)))
+ return -EINVAL;
+
+ payload_len = be16_to_cpu(l3_hdr->payload_len);
+ skb_payload_len = skb->len - l3_off - sizeof(*l3_hdr);
+ /* A zero payload length represents an empty packet, a wire jumbogram,
+ * or an oversized GSO aggregate. Jumbograms are not supported.
+ */
+ if (payload_len) {
+ if (unlikely(payload_len != skb_payload_len))
+ return -EINVAL;
+ return 0;
+ }
+ if (!skb_payload_len)
+ return 0;
+ if (unlikely(!skb_is_gso(skb) || skb_payload_len <= IPV6_MAXPLEN))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipxlat_v6_pull_icmp_inner(struct sk_buff *skb,
+ unsigned int outer_payload_off,
+ unsigned int quote_end)
+{
+ struct ipxlat_v6_hdrs hdrs;
+ unsigned int datagram_end, inner_l3_len;
+ unsigned int inner_payload_len;
+ struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ struct ipv6hdr *inner_ip6;
+ int err;
+
+ if (unlikely(!ipxlat_v6_may_pull(skb, outer_payload_off,
+ sizeof(*inner_ip6), quote_end)))
+ return -EINVAL;
+
+ inner_ip6 = (struct ipv6hdr *)(skb->data + outer_payload_off);
+ if (unlikely(inner_ip6->version != 6))
+ return -EINVAL;
+ inner_payload_len = be16_to_cpu(inner_ip6->payload_len);
+ datagram_end = outer_payload_off + sizeof(*inner_ip6) +
+ inner_payload_len;
+ quote_end = min(quote_end, datagram_end);
+
+ err = ipxlat_v6_walk_hdrs(skb, outer_payload_off, quote_end, &hdrs,
+ false);
+ if (unlikely(err))
+ return err;
+ inner_l3_len = hdrs.l4_off - outer_payload_off;
+ if (unlikely(inner_l3_len > sizeof(*inner_ip6) + inner_payload_len))
+ return -EINVAL;
+ if (unlikely(outer_payload_off > U16_MAX || hdrs.l4_off > U16_MAX ||
+ hdrs.fragh_off > U16_MAX))
+ return -E2BIG;
+
+ cb->inner_l3_offset = outer_payload_off;
+ cb->inner_l4_offset = hdrs.l4_off;
+ cb->inner_fragh_off = hdrs.fragh_off;
+ cb->inner_l4_proto = hdrs.l4_proto;
+
+ if (likely(hdrs.has_l4)) {
+ err = ipxlat_v6_pull_icmp_l4(skb, hdrs.l4_off, quote_end,
+ hdrs.l4_proto);
+ if (unlikely(err < 0))
+ return err;
+ }
+
+ return 0;
+}
+
+static int ipxlat_v6_pull_hdrs(struct sk_buff *skb)
+{
+ const unsigned int l3_off = skb_network_offset(skb);
+ unsigned int l3_len, payload_off, quote_end;
+ struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ struct ipxlat_v6_hdrs hdrs;
+ struct frag_hdr *frag;
+ int l4_len, err;
+
+ /* parse IPv6 base header and perform basic structural checks */
+ err = ipxlat_v6_pull_outer_l3(skb);
+ if (unlikely(err))
+ return err;
+
+ /* walk extension/fragment headers and locate the transport header */
+ err = ipxlat_v6_walk_hdrs(skb, l3_off, skb->len, &hdrs, true);
+ if (unlikely(err))
+ return err;
+
+ l3_len = hdrs.l4_off - l3_off;
+ payload_off = hdrs.l4_off;
+ if (unlikely(hdrs.l4_off > U16_MAX || hdrs.fragh_off > U16_MAX ||
+ l3_len > U16_MAX))
+ return -E2BIG;
+
+ cb->l4_proto = hdrs.l4_proto;
+ cb->l4_off = hdrs.l4_off;
+ cb->fragh_off = hdrs.fragh_off;
+ cb->l3_hdr_len = l3_len;
+
+ if (likely(hdrs.has_l4)) {
+ l4_len = ipxlat_v6_pull_l4(skb, hdrs.l4_off, hdrs.l4_proto,
+ &cb->is_icmp_err);
+ if (unlikely(l4_len < 0))
+ return l4_len;
+ payload_off += l4_len;
+ }
+ if (unlikely(payload_off > U16_MAX))
+ return -E2BIG;
+ cb->payload_off = payload_off;
+
+ if (unlikely(hdrs.l4_proto == NEXTHDR_ICMP && hdrs.fragh_off)) {
+ /* A stateless translator cannot reliably translate ICMP
+ * checksum across real IPv6 fragments, so fragmented ICMP is
+ * dropped. A Fragment Header alone, however, is not enough to
+ * decide: so-called atomic fragments (offset=0, M=0) carry a
+ * Fragment Header but are not actually fragmented.
+ */
+ frag = (struct frag_hdr *)(skb->data + hdrs.fragh_off);
+ if (unlikely(ipxlat_get_frag6_offset(frag) ||
+ (be16_to_cpu(frag->frag_off) & IP6_MF)))
+ return -EINVAL;
+ }
+
+ if (unlikely(cb->is_icmp_err)) {
+ /* validate the quoted packet in an ICMP error */
+ err = ipxlat_v6_icmp_quote_end(skb, payload_off, "e_end);
+ if (unlikely(err))
+ return err;
+
+ err = ipxlat_v6_pull_icmp_inner(skb, payload_off, quote_end);
+ if (unlikely(err))
+ return err;
+ }
+
+ DEBUG_NET_WARN_ON_ONCE(cb->is_icmp_err && cb->l4_proto != NEXTHDR_ICMP);
+
+ return 0;
+}
+
+static int ipxlat_v6_validate_icmp_csum(const struct sk_buff *skb,
+ unsigned int l4_off)
+{
+ struct ipv6hdr *iph6;
+ unsigned int len;
+ __sum16 csum;
+
+ switch (skb->ip_summed) {
+ case CHECKSUM_UNNECESSARY:
+ case CHECKSUM_PARTIAL:
+ return 0;
+ case CHECKSUM_COMPLETE:
+ iph6 = ipv6_hdr(skb);
+ len = skb->len - l4_off;
+ if (csum_ipv6_magic(&iph6->saddr, &iph6->daddr, len,
+ NEXTHDR_ICMP, skb->csum))
+ return -EINVAL;
+ return 0;
+ case CHECKSUM_NONE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ iph6 = ipv6_hdr(skb);
+ len = skb->len - l4_off;
+ csum = csum_ipv6_magic(&iph6->saddr, &iph6->daddr, len, NEXTHDR_ICMP,
+ skb_checksum(skb, l4_off, len, 0));
+
+ return unlikely(csum) ? -EINVAL : 0;
+}
+
+int ipxlat_v6_validate_skb(struct sk_buff *skb)
+{
+ struct ipxlat_cb *cb = ipxlat_skb_cb(skb);
+ unsigned int l3_off, max_len6, pkt_len4, pkt_len6;
+ int err;
+
+ err = ipxlat_v6_pull_hdrs(skb);
+ if (unlikely(err))
+ return err;
+
+ /* IPv6's payload length does not include its 40-byte base header, so a
+ * legal IPv6 packet can be 65575 bytes long. IPv4's total length does
+ * include its header, and zero is only meaningful for a GSO aggregate.
+ */
+ l3_off = skb_network_offset(skb);
+ pkt_len6 = skb->len - l3_off;
+ pkt_len4 = pkt_len6 - cb->l3_hdr_len + sizeof(struct iphdr);
+ if (unlikely(!skb_is_gso(skb) && pkt_len4 > IP_MAX_MTU)) {
+ /* IPv4 cannot represent the translated length: tell a remote
+ * IPv6 sender the largest packet that can cross this node.
+ */
+ max_len6 = IP_MAX_MTU + cb->l3_hdr_len - sizeof(struct iphdr);
+ ipxlat_mark_icmp_drop(skb, ICMPV6_PKT_TOOBIG, 0, max_len6);
+ return -EMSGSIZE;
+ }
+
+ if (unlikely(cb->is_icmp_err)) {
+ /* the translated ICMPv4 checksum is recomputed from scratch,
+ * so reject bad ICMPv6 error checksums before conversion
+ */
+ err = ipxlat_v6_validate_icmp_csum(skb, cb->l4_off);
+ if (unlikely(err))
+ return err;
+ }
+
+ return 0;
+}
--
2.55.0
next prev parent reply other threads:[~2026-10-24 9:26 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 9:25 [RFC net-next v2 00/18] net: introduce ipxlat stateless IPv4/IPv6 translation Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 01/18] lwtunnel: track callback dispatch separately from return value Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 02/18] ipv4/ipv6: add LWT receive helpers Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 03/18] ipv6: allow output fragmentation to reuse a Fragment Header Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 04/18] netfilter: preserve IPv6 refragmentation state across reassembly Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 05/18] net: ipxlat: add engine skeleton and configuration model Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 06/18] net: ipxlat: add RFC 6052 and EAM address translation Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 07/18] net: ipxlat: add packet metadata and header helpers Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 08/18] net: ipxlat: add IPv4 packet validation Ralf Lici
2026-10-24 9:25 ` Ralf Lici [this message]
2026-10-24 9:25 ` [RFC net-next v2 10/18] net: ipxlat: add checksum and offload helpers Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 11/18] net: ipxlat: add IPv4/IPv6 header and transport translation Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 12/18] net: ipxlat: add translation preparation and dispatch Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 13/18] net: ipxlat: add translator-generated ICMP errors Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 14/18] net: ipxlat: add ICMP informational translation Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 15/18] net: ipxlat: add ICMP error and quoted-packet translation Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 16/18] net: ipxlat: add lightweight tunnel attachment Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 17/18] selftests: net: add ipxlat coverage Ralf Lici
2026-10-24 9:25 ` [RFC net-next v2 18/18] docs: networking: add ipxlat guide Ralf Lici
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=20261024092532.172005-10-ralf@mandelbit.com \
--to=ralf@mandelbit.com \
--cc=antonio@mandelbit.com \
--cc=davem@davemloft.net \
--cc=dxld@darkboxed.org \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=xavierhsinyuan@outlook.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.