Default: 2 (as specified by RFC3810 9.1)
        Minimum: 1 (as specified by RFC6636 4.5)
 
+max_dst_opts_cnt - INTEGER
+       Maximum number of non-padding TLVs allowed in a Destination
+       options extension header. If this value is less than zero
+       then unknown options are disallowed and the number of known
+       TLVs allowed is the absolute value of this number.
+       Default: 8
+
+max_hbh_opts_cnt - INTEGER
+       Maximum number of non-padding TLVs allowed in a Hop-by-Hop
+       options extension header. If this value is less than zero
+       then unknown options are disallowed and the number of known
+       TLVs allowed is the absolute value of this number.
+       Default: 8
+
+max dst_opts_len - INTEGER
+       Maximum length allowed for a Destination options extension
+       header.
+       Default: INT_MAX (unlimited)
+
+max hbh_opts_len - INTEGER
+       Maximum length allowed for a Hop-by-Hop options extension
+       header.
+       Default: INT_MAX (unlimited)
+
 IPv6 Fragmentation:
 
 ip6frag_high_thresh - INTEGER
 
 #define IPV6_DEFAULT_HOPLIMIT   64
 #define IPV6_DEFAULT_MCASTHOPS 1
 
+/* Limits on Hop-by-Hop and Destination options.
+ *
+ * Per RFC8200 there is no limit on the maximum number or lengths of options in
+ * Hop-by-Hop or Destination options other then the packet must fit in an MTU.
+ * We allow configurable limits in order to mitigate potential denial of
+ * service attacks.
+ *
+ * There are three limits that may be set:
+ *   - Limit the number of options in a Hop-by-Hop or Destination options
+ *     extension header
+ *   - Limit the byte length of a Hop-by-Hop or Destination options extension
+ *     header
+ *   - Disallow unknown options
+ *
+ * The limits are expressed in corresponding sysctls:
+ *
+ * ipv6.sysctl.max_dst_opts_cnt
+ * ipv6.sysctl.max_hbh_opts_cnt
+ * ipv6.sysctl.max_dst_opts_len
+ * ipv6.sysctl.max_hbh_opts_len
+ *
+ * max_*_opts_cnt is the number of TLVs that are allowed for Destination
+ * options or Hop-by-Hop options. If the number is less than zero then unknown
+ * TLVs are disallowed and the number of known options that are allowed is the
+ * absolute value. Setting the value to INT_MAX indicates no limit.
+ *
+ * max_*_opts_len is the length limit in bytes of a Destination or
+ * Hop-by-Hop options extension header. Setting the value to INT_MAX
+ * indicates no length limit.
+ *
+ * If a limit is exceeded when processing an extension header the packet is
+ * silently discarded.
+ */
+
+/* Default limits for Hop-by-Hop and Destination options */
+#define IP6_DEFAULT_MAX_DST_OPTS_CNT    8
+#define IP6_DEFAULT_MAX_HBH_OPTS_CNT    8
+#define IP6_DEFAULT_MAX_DST_OPTS_LEN    INT_MAX /* No limit */
+#define IP6_DEFAULT_MAX_HBH_OPTS_LEN    INT_MAX /* No limit */
+
 /*
  *     Addr type
  *     
 
        int idgen_delay;
        int flowlabel_state_ranges;
        int flowlabel_reflect;
+       int max_dst_opts_cnt;
+       int max_hbh_opts_cnt;
+       int max_dst_opts_len;
+       int max_hbh_opts_len;
 };
 
 struct netns_ipv6 {
 
        net->ipv6.sysctl.idgen_retries = 3;
        net->ipv6.sysctl.idgen_delay = 1 * HZ;
        net->ipv6.sysctl.flowlabel_state_ranges = 0;
+       net->ipv6.sysctl.max_dst_opts_cnt = IP6_DEFAULT_MAX_DST_OPTS_CNT;
+       net->ipv6.sysctl.max_hbh_opts_cnt = IP6_DEFAULT_MAX_HBH_OPTS_CNT;
+       net->ipv6.sysctl.max_dst_opts_len = IP6_DEFAULT_MAX_DST_OPTS_LEN;
+       net->ipv6.sysctl.max_hbh_opts_len = IP6_DEFAULT_MAX_HBH_OPTS_LEN;
        atomic_set(&net->ipv6.fib6_sernum, 1);
 
        err = ipv6_init_mibs(net);
 
 
 /* An unknown option is detected, decide what to do */
 
-static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff)
+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;
                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)
+static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
+                         struct sk_buff *skb,
+                         int max_count)
 {
-       const struct tlvtype_proc *curr;
+       int len = (skb_transport_header(skb)[1] + 1) << 3;
        const unsigned char *nh = skb_network_header(skb);
        int off = skb_network_header_len(skb);
-       int len = (skb_transport_header(skb)[1] + 1) << 3;
+       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;
 
                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
                                        break;
                                }
                        }
-                       if (curr->type < 0) {
-                               if (ip6_tlvopt_unknown(skb, off) == 0)
-                                       return false;
-                       }
+                       if (curr->type < 0 &&
+                           !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
+                               return false;
+
                        padlen = 0;
                        break;
                }
        __u16 dstbuf;
 #endif
        struct dst_entry *dst = skb_dst(skb);
+       struct net *net = dev_net(skb->dev);
+       int extlen;
 
        if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) ||
            !pskb_may_pull(skb, (skb_transport_offset(skb) +
                                 ((skb_transport_header(skb)[1] + 1) << 3)))) {
                __IP6_INC_STATS(dev_net(dst->dev), ip6_dst_idev(dst),
                                IPSTATS_MIB_INHDRERRORS);
+fail_and_free:
                kfree_skb(skb);
                return -1;
        }
 
+       extlen = (skb_transport_header(skb)[1] + 1) << 3;
+       if (extlen > net->ipv6.sysctl.max_dst_opts_len)
+               goto fail_and_free;
+
        opt->lastopt = opt->dst1 = skb_network_header_len(skb);
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
        dstbuf = opt->dst1;
 #endif
 
-       if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) {
-               skb->transport_header += (skb_transport_header(skb)[1] + 1) << 3;
+       if (ip6_parse_tlv(tlvprocdestopt_lst, skb,
+                         init_net.ipv6.sysctl.max_dst_opts_cnt)) {
+               skb->transport_header += extlen;
                opt = IP6CB(skb);
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
                opt->nhoff = dstbuf;
 int ipv6_parse_hopopts(struct sk_buff *skb)
 {
        struct inet6_skb_parm *opt = IP6CB(skb);
+       struct net *net = dev_net(skb->dev);
+       int extlen;
 
        /*
         * skb_network_header(skb) is equal to skb->data, and
        if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) ||
            !pskb_may_pull(skb, (sizeof(struct ipv6hdr) +
                                 ((skb_transport_header(skb)[1] + 1) << 3)))) {
+fail_and_free:
                kfree_skb(skb);
                return -1;
        }
 
+       extlen = (skb_transport_header(skb)[1] + 1) << 3;
+       if (extlen > net->ipv6.sysctl.max_hbh_opts_len)
+               goto fail_and_free;
+
        opt->flags |= IP6SKB_HOPBYHOP;
-       if (ip6_parse_tlv(tlvprochopopt_lst, skb)) {
-               skb->transport_header += (skb_transport_header(skb)[1] + 1) << 3;
+       if (ip6_parse_tlv(tlvprochopopt_lst, skb,
+                         init_net.ipv6.sysctl.max_hbh_opts_cnt)) {
+               skb->transport_header += extlen;
                opt = IP6CB(skb);
                opt->nhoff = sizeof(struct ipv6hdr);
                return 1;
 
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },
+       {
+               .procname       = "max_dst_opts_number",
+               .data           = &init_net.ipv6.sysctl.max_dst_opts_cnt,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec
+       },
+       {
+               .procname       = "max_hbh_opts_number",
+               .data           = &init_net.ipv6.sysctl.max_hbh_opts_cnt,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec
+       },
+       {
+               .procname       = "max_dst_opts_length",
+               .data           = &init_net.ipv6.sysctl.max_dst_opts_len,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec
+       },
+       {
+               .procname       = "max_hbh_length",
+               .data           = &init_net.ipv6.sysctl.max_hbh_opts_len,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec
+       },
        { }
 };
 
        ipv6_table[7].data = &net->ipv6.sysctl.flowlabel_state_ranges;
        ipv6_table[8].data = &net->ipv6.sysctl.ip_nonlocal_bind;
        ipv6_table[9].data = &net->ipv6.sysctl.flowlabel_reflect;
+       ipv6_table[10].data = &net->ipv6.sysctl.max_dst_opts_cnt;
+       ipv6_table[11].data = &net->ipv6.sysctl.max_hbh_opts_cnt;
+       ipv6_table[12].data = &net->ipv6.sysctl.max_dst_opts_len;
+       ipv6_table[13].data = &net->ipv6.sysctl.max_hbh_opts_len;
 
        ipv6_route_table = ipv6_route_sysctl_init(net);
        if (!ipv6_route_table)