[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1577210148-7328-5-git-send-email-tom@herbertland.com>
Date: Tue, 24 Dec 2019 09:55:43 -0800
From: Tom Herbert <tom@...bertland.com>
To: davem@...emloft.net, netdev@...r.kernel.org,
simon.horman@...ronome.com, willemdebruijn.kernel@...il.com
Cc: Tom Herbert <tom@...ntonium.net>, Tom Herbert <tom@...bertland.com>
Subject: [PATCH v7 net-next 4/9] ipeh: Generic TLV parser
From: Tom Herbert <tom@...ntonium.net>
Create a generic TLV parser. This will be used with various
extension headers that carry options including Destination,
Hop-by-Hop, Segment Routing TLVs, and other cases of simple
stateless TLV parsing.
Signed-off-by: Tom Herbert <tom@...bertland.com>
---
include/net/ipeh.h | 11 ++++
net/ipv6/exthdrs.c | 142 ++--------------------------------------------
net/ipv6/exthdrs_common.c | 137 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 154 insertions(+), 136 deletions(-)
diff --git a/include/net/ipeh.h b/include/net/ipeh.h
index 3b24831..81f92f8 100644
--- a/include/net/ipeh.h
+++ b/include/net/ipeh.h
@@ -31,4 +31,15 @@ struct ipv6_txoptions *ipeh_renew_options(struct sock *sk,
struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space,
struct ipv6_txoptions *opt);
+/* The generic TLV parser assumes that the type value of PAD1 is 0, and PADN
+ * is 1. This is true for IPv6 Destination and Hop-by-Hop Options. For Segment
+ * Routing TLVs, PAD1 is also 0, however PADN is 4 so the latter necessitates
+ * some change to the parser to support Segment Routing TLVs.
+ */
+#define IPEH_TLV_PAD1 0
+#define IPEH_TLV_PADN 1
+
+bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
+ int max_count, int off, int len);
+
#endif /* _NET_IPEH_H */
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 86e562c..7b4183c 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -54,138 +54,6 @@
Generic functions
*********************/
-/* An unknown option is detected, decide what to do */
-
-static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff,
- bool disallow_unknowns)
-{
- if (disallow_unknowns) {
- /* If unknown TLVs are disallowed by configuration
- * then always silently drop packet. Note this also
- * means no ICMP parameter problem is sent which
- * could be a good property to mitigate a reflection DOS
- * attack.
- */
-
- goto drop;
- }
-
- switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) {
- case 0: /* ignore */
- return true;
-
- case 1: /* drop packet */
- break;
-
- case 3: /* Send ICMP if not a multicast address and drop packet */
- /* Actually, it is redundant check. icmp_send
- will recheck in any case.
- */
- if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr))
- break;
- /* fall through */
- case 2: /* send ICMP PARM PROB regardless and drop packet */
- icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
- return false;
- }
-
-drop:
- kfree_skb(skb);
- return false;
-}
-
-/* Parse tlv encoded option header (hop-by-hop or destination) */
-
-static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
- struct sk_buff *skb,
- int max_count)
-{
- int len = (skb_transport_header(skb)[1] + 1) << 3;
- const unsigned char *nh = skb_network_header(skb);
- int off = skb_network_header_len(skb);
- const struct tlvtype_proc *curr;
- bool disallow_unknowns = false;
- int tlv_count = 0;
- int padlen = 0;
-
- if (unlikely(max_count < 0)) {
- disallow_unknowns = true;
- max_count = -max_count;
- }
-
- if (skb_transport_offset(skb) + len > skb_headlen(skb))
- goto bad;
-
- off += 2;
- len -= 2;
-
- while (len > 0) {
- int optlen = nh[off + 1] + 2;
- int i;
-
- switch (nh[off]) {
- case IPV6_TLV_PAD1:
- optlen = 1;
- padlen++;
- if (padlen > 7)
- goto bad;
- break;
-
- case IPV6_TLV_PADN:
- /* RFC 2460 states that the purpose of PadN is
- * to align the containing header to multiples
- * of 8. 7 is therefore the highest valid value.
- * See also RFC 4942, Section 2.1.9.5.
- */
- padlen += optlen;
- if (padlen > 7)
- goto bad;
- /* RFC 4942 recommends receiving hosts to
- * actively check PadN payload to contain
- * only zeroes.
- */
- for (i = 2; i < optlen; i++) {
- if (nh[off + i] != 0)
- goto bad;
- }
- break;
-
- default: /* Other TLV code so scan list */
- if (optlen > len)
- goto bad;
-
- tlv_count++;
- if (tlv_count > max_count)
- goto bad;
-
- for (curr = procs; curr->type >= 0; curr++) {
- if (curr->type == nh[off]) {
- /* type specific length/alignment
- checks will be performed in the
- func(). */
- if (curr->func(skb, off) == false)
- return false;
- break;
- }
- }
- if (curr->type < 0 &&
- !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
- return false;
-
- padlen = 0;
- break;
- }
- off += optlen;
- len -= optlen;
- }
-
- if (len == 0)
- return true;
-bad:
- kfree_skb(skb);
- return false;
-}
-
static int ipv6_destopt_rcv(struct sk_buff *skb)
{
struct inet6_skb_parm *opt = IP6CB(skb);
@@ -213,8 +81,9 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
dstbuf = opt->dst1;
#endif
- if (ip6_parse_tlv(tlvprocdestopt_lst, skb,
- init_net.ipv6.sysctl.max_dst_opts_cnt)) {
+ if (ipeh_parse_tlv(tlvprocdestopt_lst, skb,
+ init_net.ipv6.sysctl.max_dst_opts_cnt,
+ 2, extlen - 2)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
#if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -638,8 +507,9 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
goto fail_and_free;
opt->flags |= IP6SKB_HOPBYHOP;
- if (ip6_parse_tlv(tlvprochopopt_lst, skb,
- init_net.ipv6.sysctl.max_hbh_opts_cnt)) {
+ if (ipeh_parse_tlv(tlvprochopopt_lst, skb,
+ init_net.ipv6.sysctl.max_hbh_opts_cnt,
+ 2, extlen - 2)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
opt->nhoff = sizeof(struct ipv6hdr);
diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c
index 2c68184..d0c4ec3 100644
--- a/net/ipv6/exthdrs_common.c
+++ b/net/ipv6/exthdrs_common.c
@@ -142,3 +142,140 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space,
return opt;
}
EXPORT_SYMBOL_GPL(ipeh_fixup_options);
+
+/* An unknown option is detected, decide what to do */
+
+static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff,
+ bool disallow_unknowns)
+{
+ if (disallow_unknowns) {
+ /* If unknown TLVs are disallowed by configuration
+ * then always silently drop packet. Note this also
+ * means no ICMP parameter problem is sent which
+ * could be a good property to mitigate a reflection DOS
+ * attack.
+ */
+
+ goto drop;
+ }
+
+ switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) {
+ case 0: /* ignore */
+ return true;
+
+ case 1: /* drop packet */
+ break;
+
+ case 3: /* Send ICMP if not a multicast address and drop packet */
+ /* Actually, it is redundant check. icmp_send
+ * will recheck in any case.
+ */
+ if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr))
+ break;
+ /* fall through */
+ case 2: /* send ICMP PARM PROB regardless and drop packet */
+ icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
+ return false;
+ }
+
+drop:
+ kfree_skb(skb);
+ return false;
+}
+
+/* Generic extension header TLV parser
+ *
+ * Arguments:
+ * - skb_transport_header points to the extension header containing options
+ * - off is offset from skb_transport_header where first TLV is
+ * - len is length of TLV block
+ */
+bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
+ int max_count, int off, int len)
+{
+ const unsigned char *nh = skb_network_header(skb);
+ const struct tlvtype_proc *curr;
+ bool disallow_unknowns = false;
+ int tlv_count = 0;
+ int padlen = 0;
+
+ if (unlikely(max_count < 0)) {
+ disallow_unknowns = true;
+ max_count = -max_count;
+ }
+
+ if (skb_transport_offset(skb) + off + len > skb_headlen(skb))
+ goto bad;
+
+ /* ops function based offset on network header */
+ off += skb_network_header_len(skb);
+
+ while (len > 0) {
+ int optlen = nh[off + 1] + 2;
+ int i;
+
+ switch (nh[off]) {
+ case IPEH_TLV_PAD1:
+ optlen = 1;
+ padlen++;
+ if (padlen > 7)
+ goto bad;
+ break;
+
+ case IPEH_TLV_PADN:
+ /* RFC 2460 states that the purpose of PadN is
+ * to align the containing header to multiples
+ * of 8. 7 is therefore the highest valid value.
+ * See also RFC 4942, Section 2.1.9.5.
+ */
+ padlen += optlen;
+ if (padlen > 7)
+ goto bad;
+
+ /* RFC 4942 recommends receiving hosts to
+ * actively check PadN payload to contain
+ * only zeroes.
+ */
+ for (i = 2; i < optlen; i++) {
+ if (nh[off + i] != 0)
+ goto bad;
+ }
+ break;
+
+ default: /* Other TLV code so scan list */
+ if (optlen > len)
+ goto bad;
+
+ tlv_count++;
+ if (tlv_count > max_count)
+ goto bad;
+
+ for (curr = procs; curr->type >= 0; curr++) {
+ if (curr->type == nh[off]) {
+ /* type specific length/alignment
+ * checks will be performed in the
+ * func().
+ */
+ if (curr->func(skb, off) == false)
+ return false;
+ break;
+ }
+ }
+ if (curr->type < 0 &&
+ !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
+ return false;
+
+ padlen = 0;
+ break;
+ }
+ off += optlen;
+ len -= optlen;
+ }
+
+ if (len == 0)
+ return true;
+bad:
+ kfree_skb(skb);
+ return false;
+}
+EXPORT_SYMBOL(ipeh_parse_tlv);
--
2.7.4
Powered by blists - more mailing lists