OSDN Git Service

net: ipv6: add support for rpl sr exthdr
authorAlexander Aring <alex.aring@gmail.com>
Fri, 27 Mar 2020 22:00:20 +0000 (18:00 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 30 Mar 2020 05:30:57 +0000 (22:30 -0700)
This patch adds rpl source routing receive handling. Everything works
only if sysconf "rpl_seg_enabled" and source routing is enabled. Mostly
the same behaviour as IPv6 segmentation routing. To handle compression
and uncompression a rpl.c file is created which contains the necessary
functionality. The receive handling will also care about IPv6
encapsulated so far it's specified as possible nexthdr in RFC 6554.

Signed-off-by: Alexander Aring <alex.aring@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/ipv6.h
include/net/rpl.h [new file with mode: 0644]
include/uapi/linux/ipv6.h
net/ipv6/Makefile
net/ipv6/addrconf.c
net/ipv6/exthdrs.c
net/ipv6/rpl.c [new file with mode: 0644]

index ea7c790..2cb445a 100644 (file)
@@ -74,6 +74,7 @@ struct ipv6_devconf {
        __u32           addr_gen_mode;
        __s32           disable_policy;
        __s32           ndisc_tclass;
+       __s32           rpl_seg_enabled;
 
        struct ctl_table_header *sysctl_header;
 };
diff --git a/include/net/rpl.h b/include/net/rpl.h
new file mode 100644 (file)
index 0000000..d694d49
--- /dev/null
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  RPL implementation
+ *
+ *  Author:
+ *  (C) 2020 Alexander Aring <alex.aring@gmail.com>
+ */
+
+#ifndef _NET_RPL_H
+#define _NET_RPL_H
+
+#include <linux/rpl.h>
+
+/* Worst decompression memory usage ipv6 address (16) + pad 7 */
+#define IPV6_RPL_SRH_WORST_SWAP_SIZE (sizeof(struct in6_addr) + 7)
+
+static inline size_t ipv6_rpl_srh_alloc_size(unsigned char n)
+{
+       return sizeof(struct ipv6_rpl_sr_hdr) +
+               ((n + 1) * sizeof(struct in6_addr));
+}
+
+size_t ipv6_rpl_srh_size(unsigned char n, unsigned char cmpri,
+                        unsigned char cmpre);
+
+void ipv6_rpl_srh_decompress(struct ipv6_rpl_sr_hdr *outhdr,
+                            const struct ipv6_rpl_sr_hdr *inhdr,
+                            const struct in6_addr *daddr, unsigned char n);
+
+void ipv6_rpl_srh_compress(struct ipv6_rpl_sr_hdr *outhdr,
+                          const struct ipv6_rpl_sr_hdr *inhdr,
+                          const struct in6_addr *daddr, unsigned char n);
+
+#endif /* _NET_RPL_H */
index 9c0f4a9..13e8751 100644 (file)
@@ -40,6 +40,7 @@ struct in6_ifreq {
 #define IPV6_SRCRT_STRICT      0x01    /* Deprecated; will be removed */
 #define IPV6_SRCRT_TYPE_0      0       /* Deprecated; will be removed */
 #define IPV6_SRCRT_TYPE_2      2       /* IPv6 type 2 Routing Header   */
+#define IPV6_SRCRT_TYPE_3      3       /* RPL Segment Routing with IPv6 */
 #define IPV6_SRCRT_TYPE_4      4       /* Segment Routing with IPv6 */
 
 /*
@@ -187,6 +188,7 @@ enum {
        DEVCONF_DISABLE_POLICY,
        DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
        DEVCONF_NDISC_TCLASS,
+       DEVCONF_RPL_SEG_ENABLED,
        DEVCONF_MAX
 };
 
index 8ccf355..9d3e9bd 100644 (file)
@@ -10,7 +10,7 @@ ipv6-objs :=  af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \
                route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \
                raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \
                exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \
-               udp_offload.o seg6.o fib6_notifier.o
+               udp_offload.o seg6.o fib6_notifier.o rpl.o
 
 ipv6-offload :=        ip6_offload.o tcpv6_offload.o exthdrs_offload.o
 
index 594963a..a11fd4d 100644 (file)
@@ -236,6 +236,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
        .enhanced_dad           = 1,
        .addr_gen_mode          = IN6_ADDR_GEN_MODE_EUI64,
        .disable_policy         = 0,
+       .rpl_seg_enabled        = 0,
 };
 
 static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -290,6 +291,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
        .enhanced_dad           = 1,
        .addr_gen_mode          = IN6_ADDR_GEN_MODE_EUI64,
        .disable_policy         = 0,
+       .rpl_seg_enabled        = 0,
 };
 
 /* Check if link is ready: is it up and is a valid qdisc available */
@@ -5520,6 +5522,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
        array[DEVCONF_ADDR_GEN_MODE] = cnf->addr_gen_mode;
        array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy;
        array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass;
+       array[DEVCONF_RPL_SEG_ENABLED] = cnf->rpl_seg_enabled;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -6901,6 +6904,13 @@ static const struct ctl_table addrconf_sysctl[] = {
                .extra2         = (void *)&two_five_five,
        },
        {
+               .procname       = "rpl_seg_enabled",
+               .data           = &ipv6_devconf.rpl_seg_enabled,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec,
+       },
+       {
                /* sentinel */
        }
 };
index bcb9f5e..5a8bbcd 100644 (file)
@@ -48,6 +48,7 @@
 #ifdef CONFIG_IPV6_SEG6_HMAC
 #include <net/seg6_hmac.h>
 #endif
+#include <net/rpl.h>
 
 #include <linux/uaccess.h>
 
@@ -468,6 +469,195 @@ looped_back:
        return -1;
 }
 
+static int ipv6_rpl_srh_rcv(struct sk_buff *skb)
+{
+       struct ipv6_rpl_sr_hdr *hdr, *ohdr, *chdr;
+       struct inet6_skb_parm *opt = IP6CB(skb);
+       struct net *net = dev_net(skb->dev);
+       struct inet6_dev *idev;
+       struct ipv6hdr *oldhdr;
+       struct in6_addr addr;
+       unsigned char *buf;
+       int accept_rpl_seg;
+       int i, err;
+       u64 n = 0;
+       u32 r;
+
+       idev = __in6_dev_get(skb->dev);
+
+       accept_rpl_seg = net->ipv6.devconf_all->rpl_seg_enabled;
+       if (accept_rpl_seg > idev->cnf.rpl_seg_enabled)
+               accept_rpl_seg = idev->cnf.rpl_seg_enabled;
+
+       if (!accept_rpl_seg) {
+               kfree_skb(skb);
+               return -1;
+       }
+
+looped_back:
+       hdr = (struct ipv6_rpl_sr_hdr *)skb_transport_header(skb);
+
+       if (hdr->segments_left == 0) {
+               if (hdr->nexthdr == NEXTHDR_IPV6) {
+                       int offset = (hdr->hdrlen + 1) << 3;
+
+                       skb_postpull_rcsum(skb, skb_network_header(skb),
+                                          skb_network_header_len(skb));
+
+                       if (!pskb_pull(skb, offset)) {
+                               kfree_skb(skb);
+                               return -1;
+                       }
+                       skb_postpull_rcsum(skb, skb_transport_header(skb),
+                                          offset);
+
+                       skb_reset_network_header(skb);
+                       skb_reset_transport_header(skb);
+                       skb->encapsulation = 0;
+
+                       __skb_tunnel_rx(skb, skb->dev, net);
+
+                       netif_rx(skb);
+                       return -1;
+               }
+
+               opt->srcrt = skb_network_header_len(skb);
+               opt->lastopt = opt->srcrt;
+               skb->transport_header += (hdr->hdrlen + 1) << 3;
+               opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb);
+
+               return 1;
+       }
+
+       if (!pskb_may_pull(skb, sizeof(*hdr))) {
+               kfree_skb(skb);
+               return -1;
+       }
+
+       n = (hdr->hdrlen << 3) - hdr->pad - (16 - hdr->cmpre);
+       r = do_div(n, (16 - hdr->cmpri));
+       /* checks if calculation was without remainder and n fits into
+        * unsigned char which is segments_left field. Should not be
+        * higher than that.
+        */
+       if (r || (n + 1) > 255) {
+               kfree_skb(skb);
+               return -1;
+       }
+
+       if (hdr->segments_left > n + 1) {
+               __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
+               icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
+                                 ((&hdr->segments_left) -
+                                  skb_network_header(skb)));
+               return -1;
+       }
+
+       if (skb_cloned(skb)) {
+               if (pskb_expand_head(skb, IPV6_RPL_SRH_WORST_SWAP_SIZE, 0,
+                                    GFP_ATOMIC)) {
+                       __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
+                                       IPSTATS_MIB_OUTDISCARDS);
+                       kfree_skb(skb);
+                       return -1;
+               }
+       } else {
+               err = skb_cow_head(skb, IPV6_RPL_SRH_WORST_SWAP_SIZE);
+               if (unlikely(err)) {
+                       kfree_skb(skb);
+                       return -1;
+               }
+       }
+
+       hdr = (struct ipv6_rpl_sr_hdr *)skb_transport_header(skb);
+
+       if (!pskb_may_pull(skb, ipv6_rpl_srh_size(n, hdr->cmpri,
+                                                 hdr->cmpre))) {
+               kfree_skb(skb);
+               return -1;
+       }
+
+       hdr->segments_left--;
+       i = n - hdr->segments_left;
+
+       buf = kzalloc(ipv6_rpl_srh_alloc_size(n + 1) * 2, GFP_ATOMIC);
+       if (unlikely(!buf)) {
+               kfree_skb(skb);
+               return -1;
+       }
+
+       ohdr = (struct ipv6_rpl_sr_hdr *)buf;
+       ipv6_rpl_srh_decompress(ohdr, hdr, &ipv6_hdr(skb)->daddr, n);
+       chdr = (struct ipv6_rpl_sr_hdr *)(buf + ((ohdr->hdrlen + 1) << 3));
+
+       if ((ipv6_addr_type(&ipv6_hdr(skb)->daddr) & IPV6_ADDR_MULTICAST) ||
+           (ipv6_addr_type(&ohdr->rpl_segaddr[i]) & IPV6_ADDR_MULTICAST)) {
+               kfree_skb(skb);
+               kfree(buf);
+               return -1;
+       }
+
+       err = ipv6_chk_rpl_srh_loop(net, ohdr->rpl_segaddr, n + 1);
+       if (err) {
+               icmpv6_send(skb, ICMPV6_PARAMPROB, 0, 0);
+               kfree_skb(skb);
+               kfree(buf);
+               return -1;
+       }
+
+       addr = ipv6_hdr(skb)->daddr;
+       ipv6_hdr(skb)->daddr = ohdr->rpl_segaddr[i];
+       ohdr->rpl_segaddr[i] = addr;
+
+       ipv6_rpl_srh_compress(chdr, ohdr, &ipv6_hdr(skb)->daddr, n);
+
+       oldhdr = ipv6_hdr(skb);
+
+       skb_pull(skb, ((hdr->hdrlen + 1) << 3));
+       skb_postpull_rcsum(skb, oldhdr,
+                          sizeof(struct ipv6hdr) + ((hdr->hdrlen + 1) << 3));
+       skb_push(skb, ((chdr->hdrlen + 1) << 3) + sizeof(struct ipv6hdr));
+       skb_reset_network_header(skb);
+       skb_mac_header_rebuild(skb);
+       skb_set_transport_header(skb, sizeof(struct ipv6hdr));
+
+       memmove(ipv6_hdr(skb), oldhdr, sizeof(struct ipv6hdr));
+       memcpy(skb_transport_header(skb), chdr, (chdr->hdrlen + 1) << 3);
+
+       ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+       skb_postpush_rcsum(skb, ipv6_hdr(skb),
+                          sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3));
+
+       kfree(buf);
+
+       skb_dst_drop(skb);
+
+       ip6_route_input(skb);
+
+       if (skb_dst(skb)->error) {
+               dst_input(skb);
+               return -1;
+       }
+
+       if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) {
+               if (ipv6_hdr(skb)->hop_limit <= 1) {
+                       __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
+                       icmpv6_send(skb, ICMPV6_TIME_EXCEED,
+                                   ICMPV6_EXC_HOPLIMIT, 0);
+                       kfree_skb(skb);
+                       return -1;
+               }
+               ipv6_hdr(skb)->hop_limit--;
+
+               skb_pull(skb, sizeof(struct ipv6hdr));
+               goto looped_back;
+       }
+
+       dst_input(skb);
+
+       return -1;
+}
+
 /********************************
   Routing header.
  ********************************/
@@ -506,9 +696,16 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
                return -1;
        }
 
-       /* segment routing */
-       if (hdr->type == IPV6_SRCRT_TYPE_4)
+       switch (hdr->type) {
+       case IPV6_SRCRT_TYPE_4:
+               /* segment routing */
                return ipv6_srh_rcv(skb);
+       case IPV6_SRCRT_TYPE_3:
+               /* rpl segment routing */
+               return ipv6_rpl_srh_rcv(skb);
+       default:
+               break;
+       }
 
 looped_back:
        if (hdr->segments_left == 0) {
diff --git a/net/ipv6/rpl.c b/net/ipv6/rpl.c
new file mode 100644 (file)
index 0000000..dc4f20e
--- /dev/null
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * Authors:
+ * (C) 2020 Alexander Aring <alex.aring@gmail.com>
+ */
+
+#include <net/ipv6.h>
+#include <net/rpl.h>
+
+#define IPV6_PFXTAIL_LEN(x) (sizeof(struct in6_addr) - (x))
+
+static void ipv6_rpl_addr_decompress(struct in6_addr *dst,
+                                    const struct in6_addr *daddr,
+                                    const void *post, unsigned char pfx)
+{
+       memcpy(dst, daddr, pfx);
+       memcpy(&dst->s6_addr[pfx], post, IPV6_PFXTAIL_LEN(pfx));
+}
+
+static void ipv6_rpl_addr_compress(void *dst, const struct in6_addr *addr,
+                                  unsigned char pfx)
+{
+       memcpy(dst, &addr->s6_addr[pfx], IPV6_PFXTAIL_LEN(pfx));
+}
+
+static void *ipv6_rpl_segdata_pos(const struct ipv6_rpl_sr_hdr *hdr, int i)
+{
+       return (void *)&hdr->rpl_segdata[i * IPV6_PFXTAIL_LEN(hdr->cmpri)];
+}
+
+size_t ipv6_rpl_srh_size(unsigned char n, unsigned char cmpri,
+                        unsigned char cmpre)
+{
+       return (n * IPV6_PFXTAIL_LEN(cmpri)) + IPV6_PFXTAIL_LEN(cmpre);
+}
+
+void ipv6_rpl_srh_decompress(struct ipv6_rpl_sr_hdr *outhdr,
+                            const struct ipv6_rpl_sr_hdr *inhdr,
+                            const struct in6_addr *daddr, unsigned char n)
+{
+       int i;
+
+       outhdr->nexthdr = inhdr->nexthdr;
+       outhdr->hdrlen = (((n + 1) * sizeof(struct in6_addr)) >> 3);
+       outhdr->pad = 0;
+       outhdr->type = inhdr->type;
+       outhdr->segments_left = inhdr->segments_left;
+       outhdr->cmpri = 0;
+       outhdr->cmpre = 0;
+
+       for (i = 0; i <= n; i++)
+               ipv6_rpl_addr_decompress(&outhdr->rpl_segaddr[i], daddr,
+                                        ipv6_rpl_segdata_pos(inhdr, i),
+                                        inhdr->cmpri);
+
+       ipv6_rpl_addr_decompress(&outhdr->rpl_segaddr[n], daddr,
+                                ipv6_rpl_segdata_pos(inhdr, n),
+                                inhdr->cmpre);
+}
+
+static unsigned char ipv6_rpl_srh_calc_cmpri(const struct ipv6_rpl_sr_hdr *inhdr,
+                                            const struct in6_addr *daddr,
+                                            unsigned char n)
+{
+       unsigned char plen;
+       int i;
+
+       for (plen = 0; plen < sizeof(*daddr); plen++) {
+               for (i = 0; i <= n; i++) {
+                       if (daddr->s6_addr[plen] !=
+                           inhdr->rpl_segaddr[i].s6_addr[plen])
+                               return plen;
+               }
+       }
+
+       return plen;
+}
+
+static unsigned char ipv6_rpl_srh_calc_cmpre(const struct in6_addr *daddr,
+                                            const struct in6_addr *last_segment)
+{
+       unsigned int plen;
+
+       for (plen = 0; plen < sizeof(*daddr); plen++) {
+               if (daddr->s6_addr[plen] != last_segment->s6_addr[plen])
+                       break;
+       }
+
+       return plen;
+}
+
+void ipv6_rpl_srh_compress(struct ipv6_rpl_sr_hdr *outhdr,
+                          const struct ipv6_rpl_sr_hdr *inhdr,
+                          const struct in6_addr *daddr, unsigned char n)
+{
+       unsigned char cmpri, cmpre;
+       size_t seglen;
+       int i;
+
+       cmpri = ipv6_rpl_srh_calc_cmpri(inhdr, daddr, n);
+       cmpre = ipv6_rpl_srh_calc_cmpre(daddr, &inhdr->rpl_segaddr[n]);
+
+       outhdr->nexthdr = inhdr->nexthdr;
+       seglen = (n * IPV6_PFXTAIL_LEN(cmpri)) + IPV6_PFXTAIL_LEN(cmpre);
+       outhdr->hdrlen = seglen >> 3;
+       if (seglen & 0x7) {
+               outhdr->hdrlen++;
+               outhdr->pad = 8 - (seglen & 0x7);
+       } else {
+               outhdr->pad = 0;
+       }
+       outhdr->type = inhdr->type;
+       outhdr->segments_left = inhdr->segments_left;
+       outhdr->cmpri = cmpri;
+       outhdr->cmpre = cmpre;
+
+       for (i = 0; i <= n; i++)
+               ipv6_rpl_addr_compress(ipv6_rpl_segdata_pos(outhdr, i),
+                                      &inhdr->rpl_segaddr[i], cmpri);
+
+       ipv6_rpl_addr_compress(ipv6_rpl_segdata_pos(outhdr, n),
+                              &inhdr->rpl_segaddr[n], cmpre);
+}