net: ipv6: add support for rpl sr exthdr

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>
This commit is contained in:
Alexander Aring
2020-03-27 18:00:20 -04:00
committed by David S. Miller
parent f37c605936
commit 8610c7c6e3
7 changed files with 370 additions and 3 deletions

View 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) {