Add the possibility to remove one or more consecutive TLVs without
messing up the alignment of others. For now, only IOAM requires this
behavior.

By default, an 8-octet boundary is automatically assumed. This is the
price to pay (at most a useless 4-octet padding) to make sure everything
is still aligned after the removal.

Proof: let's assume for instance the following alignments 2n, 4n and 8n
respectively for options X, Y and Z, inside a Hop-by-Hop extension
header.

Example 1:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Next header  |  Hdr Ext Len  |       X       |       X       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       X       |       X       |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
~                Option to be removed (8 octets)                ~
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Y       |       Y       |       Y       |       Y       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Padding    |    Padding    |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Result 1: assuming a 4-octet boundary would work, as well as an 8-octet
boundary (same result in both cases).

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Next header  |  Hdr Ext Len  |       X       |       X       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       X       |       X       |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Y       |       Y       |       Y       |       Y       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Padding    |    Padding    |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Example 2:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Next header  |  Hdr Ext Len  |       X       |       X       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       X       |       X       |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Option to be removed (4 octets)                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Y       |       Y       |       Y       |       Y       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Result 2: assuming a 4-octet boundary WOULD NOT WORK. Indeed, option Z
would not be 8n-aligned and the Hop-by-Hop size would not be a multiple
of 8 anymore.

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Next header  |  Hdr Ext Len  |       X       |       X       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       X       |       X       |    Padding    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Y       |       Y       |       Y       |       Y       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Z       |       Z       |       Z       |       Z       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Therefore, the largest (8-octet) boundary is assumed by default and for
all, which means that blocks are only moved in multiples of 8. This
assertion guarantees good alignment.

Signed-off-by: Justin Iurman <justin.iur...@uliege.be>
---
 net/ipv6/exthdrs.c | 134 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 108 insertions(+), 26 deletions(-)

diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index e9b366994475..f27ab3bf2e0c 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -52,17 +52,27 @@
 
 #include <linux/uaccess.h>
 
-/*
- *     Parsing tlv encoded headers.
+/* States for TLV parsing functions. */
+
+enum {
+       TLV_ACCEPT,
+       TLV_REJECT,
+       TLV_REMOVE,
+       __TLV_MAX
+};
+
+/* Parsing TLV encoded headers.
  *
- *     Parsing function "func" returns true, if parsing succeed
- *     and false, if it failed.
- *     It MUST NOT touch skb->h.
+ * Parsing function "func" returns either:
+ *  - TLV_ACCEPT if parsing succeeds
+ *  - TLV_REJECT if parsing fails
+ *  - TLV_REMOVE if TLV must be removed
+ * It MUST NOT touch skb->h.
  */
 
 struct tlvtype_proc {
        int     type;
-       bool    (*func)(struct sk_buff *skb, int offset);
+       int     (*func)(struct sk_buff *skb, int offset);
 };
 
 /*********************
@@ -109,19 +119,67 @@ static bool ip6_tlvopt_unknown(struct sk_buff *skb, int 
optoff,
        return false;
 }
 
+/* Remove one or several consecutive TLVs and recompute offsets, lengths */
+
+static int remove_tlv(int start, int end, struct sk_buff *skb)
+{
+       int len = end - start;
+       int padlen = len % 8;
+       unsigned char *h;
+       int rlen, off;
+       u16 pl_len;
+
+       rlen = len - padlen;
+       if (rlen) {
+               skb_pull(skb, rlen);
+               memmove(skb_network_header(skb) + rlen, skb_network_header(skb),
+                       start);
+               skb_postpull_rcsum(skb, skb_network_header(skb), rlen);
+
+               skb_reset_network_header(skb);
+               skb_set_transport_header(skb, sizeof(struct ipv6hdr));
+
+               pl_len = be16_to_cpu(ipv6_hdr(skb)->payload_len) - rlen;
+               ipv6_hdr(skb)->payload_len = cpu_to_be16(pl_len);
+
+               skb_transport_header(skb)[1] -= rlen >> 3;
+               end -= rlen;
+       }
+
+       if (padlen) {
+               off = end - padlen;
+               h = skb_network_header(skb);
+
+               if (padlen == 1) {
+                       h[off] = IPV6_TLV_PAD1;
+               } else {
+                       padlen -= 2;
+
+                       h[off] = IPV6_TLV_PADN;
+                       h[off + 1] = padlen;
+                       memset(&h[off + 2], 0, padlen);
+               }
+       }
+
+       return end;
+}
+
 /* 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 max_count,
+                         bool removable)
 {
        int len = (skb_transport_header(skb)[1] + 1) << 3;
-       const unsigned char *nh = skb_network_header(skb);
+       unsigned char *nh = skb_network_header(skb);
        int off = skb_network_header_len(skb);
        const struct tlvtype_proc *curr;
        bool disallow_unknowns = false;
+       int off_remove = 0;
        int tlv_count = 0;
        int padlen = 0;
+       int ret;
 
        if (unlikely(max_count < 0)) {
                disallow_unknowns = true;
@@ -173,12 +231,14 @@ static bool ip6_parse_tlv(const struct tlvtype_proc 
*procs,
                        if (tlv_count > max_count)
                                goto bad;
 
+                       ret = -1;
                        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)
+                                       ret = curr->func(skb, off);
+                                       if (ret == TLV_REJECT)
                                                return false;
                                        break;
                                }
@@ -187,6 +247,17 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
                            !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
                                return false;
 
+                       if (removable) {
+                               if (ret == TLV_REMOVE) {
+                                       if (!off_remove)
+                                               off_remove = off - padlen;
+                               } else if (off_remove) {
+                                       off = remove_tlv(off_remove, off, skb);
+                                       nh = skb_network_header(skb);
+                                       off_remove = 0;
+                               }
+                       }
+
                        padlen = 0;
                        break;
                }
@@ -194,8 +265,13 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
                len -= optlen;
        }
 
-       if (len == 0)
+       if (len == 0) {
+               /* Don't forget last TLV if it must be removed */
+               if (off_remove)
+                       remove_tlv(off_remove, off, skb);
+
                return true;
+       }
 bad:
        kfree_skb(skb);
        return false;
@@ -206,7 +282,7 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
  *****************************/
 
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
-static bool ipv6_dest_hao(struct sk_buff *skb, int optoff)
+static int ipv6_dest_hao(struct sk_buff *skb, int optoff)
 {
        struct ipv6_destopt_hao *hao;
        struct inet6_skb_parm *opt = IP6CB(skb);
@@ -257,11 +333,11 @@ static bool ipv6_dest_hao(struct sk_buff *skb, int optoff)
        if (skb->tstamp == 0)
                __net_timestamp(skb);
 
-       return true;
+       return TLV_ACCEPT;
 
  discard:
        kfree_skb(skb);
-       return false;
+       return TLV_REJECT;
 }
 #endif
 
@@ -306,7 +382,8 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
 #endif
 
        if (ip6_parse_tlv(tlvprocdestopt_lst, skb,
-                         init_net.ipv6.sysctl.max_dst_opts_cnt)) {
+                         init_net.ipv6.sysctl.max_dst_opts_cnt,
+                         false)) {
                skb->transport_header += extlen;
                opt = IP6CB(skb);
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -918,24 +995,24 @@ static inline struct net *ipv6_skb_net(struct sk_buff 
*skb)
 
 /* Router Alert as of RFC 2711 */
 
-static bool ipv6_hop_ra(struct sk_buff *skb, int optoff)
+static int ipv6_hop_ra(struct sk_buff *skb, int optoff)
 {
        const unsigned char *nh = skb_network_header(skb);
 
        if (nh[optoff + 1] == 2) {
                IP6CB(skb)->flags |= IP6SKB_ROUTERALERT;
                memcpy(&IP6CB(skb)->ra, nh + optoff + 2, 
sizeof(IP6CB(skb)->ra));
-               return true;
+               return TLV_ACCEPT;
        }
        net_dbg_ratelimited("ipv6_hop_ra: wrong RA length %d\n",
                            nh[optoff + 1]);
        kfree_skb(skb);
-       return false;
+       return TLV_REJECT;
 }
 
 /* Jumbo payload */
 
-static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
+static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
 {
        const unsigned char *nh = skb_network_header(skb);
        struct inet6_dev *idev = __in6_dev_get_safely(skb->dev);
@@ -953,12 +1030,12 @@ static bool ipv6_hop_jumbo(struct sk_buff *skb, int 
optoff)
        if (pkt_len <= IPV6_MAXPLEN) {
                __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
                icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2);
-               return false;
+               return TLV_REJECT;
        }
        if (ipv6_hdr(skb)->payload_len) {
                __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
                icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff);
-               return false;
+               return TLV_REJECT;
        }
 
        if (pkt_len > skb->len - sizeof(struct ipv6hdr)) {
@@ -970,16 +1047,16 @@ static bool ipv6_hop_jumbo(struct sk_buff *skb, int 
optoff)
                goto drop;
 
        IP6CB(skb)->flags |= IP6SKB_JUMBOGRAM;
-       return true;
+       return TLV_ACCEPT;
 
 drop:
        kfree_skb(skb);
-       return false;
+       return TLV_REJECT;
 }
 
 /* CALIPSO RFC 5570 */
 
-static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff)
+static int ipv6_hop_calipso(struct sk_buff *skb, int optoff)
 {
        const unsigned char *nh = skb_network_header(skb);
 
@@ -992,11 +1069,11 @@ static bool ipv6_hop_calipso(struct sk_buff *skb, int 
optoff)
        if (!calipso_validate(skb, nh + optoff))
                goto drop;
 
-       return true;
+       return TLV_ACCEPT;
 
 drop:
        kfree_skb(skb);
-       return false;
+       return TLV_REJECT;
 }
 
 static const struct tlvtype_proc tlvprochopopt_lst[] = {
@@ -1041,7 +1118,12 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
 
        opt->flags |= IP6SKB_HOPBYHOP;
        if (ip6_parse_tlv(tlvprochopopt_lst, skb,
-                         init_net.ipv6.sysctl.max_hbh_opts_cnt)) {
+                         init_net.ipv6.sysctl.max_hbh_opts_cnt,
+                         true)) {
+               /* we need to refresh the length in case
+                * at least one TLV was removed
+                */
+               extlen = (skb_transport_header(skb)[1] + 1) << 3;
                skb->transport_header += extlen;
                opt = IP6CB(skb);
                opt->nhoff = sizeof(struct ipv6hdr);
-- 
2.17.1

Reply via email to