Skip to content

Commit 8610c7c

Browse files
alexaringdavem330
authored andcommitted
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 <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent f37c605 commit 8610c7c

File tree

7 files changed

+370
-3
lines changed

7 files changed

+370
-3
lines changed

include/linux/ipv6.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ struct ipv6_devconf {
7474
__u32 addr_gen_mode;
7575
__s32 disable_policy;
7676
__s32 ndisc_tclass;
77+
__s32 rpl_seg_enabled;
7778

7879
struct ctl_table_header *sysctl_header;
7980
};

include/net/rpl.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* SPDX-License-Identifier: GPL-2.0-or-later */
2+
/*
3+
* RPL implementation
4+
*
5+
* Author:
6+
* (C) 2020 Alexander Aring <[email protected]>
7+
*/
8+
9+
#ifndef _NET_RPL_H
10+
#define _NET_RPL_H
11+
12+
#include <linux/rpl.h>
13+
14+
/* Worst decompression memory usage ipv6 address (16) + pad 7 */
15+
#define IPV6_RPL_SRH_WORST_SWAP_SIZE (sizeof(struct in6_addr) + 7)
16+
17+
static inline size_t ipv6_rpl_srh_alloc_size(unsigned char n)
18+
{
19+
return sizeof(struct ipv6_rpl_sr_hdr) +
20+
((n + 1) * sizeof(struct in6_addr));
21+
}
22+
23+
size_t ipv6_rpl_srh_size(unsigned char n, unsigned char cmpri,
24+
unsigned char cmpre);
25+
26+
void ipv6_rpl_srh_decompress(struct ipv6_rpl_sr_hdr *outhdr,
27+
const struct ipv6_rpl_sr_hdr *inhdr,
28+
const struct in6_addr *daddr, unsigned char n);
29+
30+
void ipv6_rpl_srh_compress(struct ipv6_rpl_sr_hdr *outhdr,
31+
const struct ipv6_rpl_sr_hdr *inhdr,
32+
const struct in6_addr *daddr, unsigned char n);
33+
34+
#endif /* _NET_RPL_H */

include/uapi/linux/ipv6.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct in6_ifreq {
4040
#define IPV6_SRCRT_STRICT 0x01 /* Deprecated; will be removed */
4141
#define IPV6_SRCRT_TYPE_0 0 /* Deprecated; will be removed */
4242
#define IPV6_SRCRT_TYPE_2 2 /* IPv6 type 2 Routing Header */
43+
#define IPV6_SRCRT_TYPE_3 3 /* RPL Segment Routing with IPv6 */
4344
#define IPV6_SRCRT_TYPE_4 4 /* Segment Routing with IPv6 */
4445

4546
/*
@@ -187,6 +188,7 @@ enum {
187188
DEVCONF_DISABLE_POLICY,
188189
DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
189190
DEVCONF_NDISC_TCLASS,
191+
DEVCONF_RPL_SEG_ENABLED,
190192
DEVCONF_MAX
191193
};
192194

net/ipv6/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \
1010
route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \
1111
raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \
1212
exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \
13-
udp_offload.o seg6.o fib6_notifier.o
13+
udp_offload.o seg6.o fib6_notifier.o rpl.o
1414

1515
ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o
1616

net/ipv6/addrconf.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
236236
.enhanced_dad = 1,
237237
.addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64,
238238
.disable_policy = 0,
239+
.rpl_seg_enabled = 0,
239240
};
240241

241242
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -290,6 +291,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
290291
.enhanced_dad = 1,
291292
.addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64,
292293
.disable_policy = 0,
294+
.rpl_seg_enabled = 0,
293295
};
294296

295297
/* 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,
55205522
array[DEVCONF_ADDR_GEN_MODE] = cnf->addr_gen_mode;
55215523
array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy;
55225524
array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass;
5525+
array[DEVCONF_RPL_SEG_ENABLED] = cnf->rpl_seg_enabled;
55235526
}
55245527

55255528
static inline size_t inet6_ifla6_size(void)
@@ -6900,6 +6903,13 @@ static const struct ctl_table addrconf_sysctl[] = {
69006903
.extra1 = (void *)SYSCTL_ZERO,
69016904
.extra2 = (void *)&two_five_five,
69026905
},
6906+
{
6907+
.procname = "rpl_seg_enabled",
6908+
.data = &ipv6_devconf.rpl_seg_enabled,
6909+
.maxlen = sizeof(int),
6910+
.mode = 0644,
6911+
.proc_handler = proc_dointvec,
6912+
},
69036913
{
69046914
/* sentinel */
69056915
}

net/ipv6/exthdrs.c

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#ifdef CONFIG_IPV6_SEG6_HMAC
4949
#include <net/seg6_hmac.h>
5050
#endif
51+
#include <net/rpl.h>
5152

5253
#include <linux/uaccess.h>
5354

@@ -468,6 +469,195 @@ static int ipv6_srh_rcv(struct sk_buff *skb)
468469
return -1;
469470
}
470471

472+
static int ipv6_rpl_srh_rcv(struct sk_buff *skb)
473+
{
474+
struct ipv6_rpl_sr_hdr *hdr, *ohdr, *chdr;
475+
struct inet6_skb_parm *opt = IP6CB(skb);
476+
struct net *net = dev_net(skb->dev);
477+
struct inet6_dev *idev;
478+
struct ipv6hdr *oldhdr;
479+
struct in6_addr addr;
480+
unsigned char *buf;
481+
int accept_rpl_seg;
482+
int i, err;
483+
u64 n = 0;
484+
u32 r;
485+
486+
idev = __in6_dev_get(skb->dev);
487+
488+
accept_rpl_seg = net->ipv6.devconf_all->rpl_seg_enabled;
489+
if (accept_rpl_seg > idev->cnf.rpl_seg_enabled)
490+
accept_rpl_seg = idev->cnf.rpl_seg_enabled;
491+
492+
if (!accept_rpl_seg) {
493+
kfree_skb(skb);
494+
return -1;
495+
}
496+
497+
looped_back:
498+
hdr = (struct ipv6_rpl_sr_hdr *)skb_transport_header(skb);
499+
500+
if (hdr->segments_left == 0) {
501+
if (hdr->nexthdr == NEXTHDR_IPV6) {
502+
int offset = (hdr->hdrlen + 1) << 3;
503+
504+
skb_postpull_rcsum(skb, skb_network_header(skb),
505+
skb_network_header_len(skb));
506+
507+
if (!pskb_pull(skb, offset)) {
508+
kfree_skb(skb);
509+
return -1;
510+
}
511+
skb_postpull_rcsum(skb, skb_transport_header(skb),
512+
offset);
513+
514+
skb_reset_network_header(skb);
515+
skb_reset_transport_header(skb);
516+
skb->encapsulation = 0;
517+
518+
__skb_tunnel_rx(skb, skb->dev, net);
519+
520+
netif_rx(skb);
521+
return -1;
522+
}
523+
524+
opt->srcrt = skb_network_header_len(skb);
525+
opt->lastopt = opt->srcrt;
526+
skb->transport_header += (hdr->hdrlen + 1) << 3;
527+
opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb);
528+
529+
return 1;
530+
}
531+
532+
if (!pskb_may_pull(skb, sizeof(*hdr))) {
533+
kfree_skb(skb);
534+
return -1;
535+
}
536+
537+
n = (hdr->hdrlen << 3) - hdr->pad - (16 - hdr->cmpre);
538+
r = do_div(n, (16 - hdr->cmpri));
539+
/* checks if calculation was without remainder and n fits into
540+
* unsigned char which is segments_left field. Should not be
541+
* higher than that.
542+
*/
543+
if (r || (n + 1) > 255) {
544+
kfree_skb(skb);
545+
return -1;
546+
}
547+
548+
if (hdr->segments_left > n + 1) {
549+
__IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
550+
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
551+
((&hdr->segments_left) -
552+
skb_network_header(skb)));
553+
return -1;
554+
}
555+
556+
if (skb_cloned(skb)) {
557+
if (pskb_expand_head(skb, IPV6_RPL_SRH_WORST_SWAP_SIZE, 0,
558+
GFP_ATOMIC)) {
559+
__IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
560+
IPSTATS_MIB_OUTDISCARDS);
561+
kfree_skb(skb);
562+
return -1;
563+
}
564+
} else {
565+
err = skb_cow_head(skb, IPV6_RPL_SRH_WORST_SWAP_SIZE);
566+
if (unlikely(err)) {
567+
kfree_skb(skb);
568+
return -1;
569+
}
570+
}
571+
572+
hdr = (struct ipv6_rpl_sr_hdr *)skb_transport_header(skb);
573+
574+
if (!pskb_may_pull(skb, ipv6_rpl_srh_size(n, hdr->cmpri,
575+
hdr->cmpre))) {
576+
kfree_skb(skb);
577+
return -1;
578+
}
579+
580+
hdr->segments_left--;
581+
i = n - hdr->segments_left;
582+
583+
buf = kzalloc(ipv6_rpl_srh_alloc_size(n + 1) * 2, GFP_ATOMIC);
584+
if (unlikely(!buf)) {
585+
kfree_skb(skb);
586+
return -1;
587+
}
588+
589+
ohdr = (struct ipv6_rpl_sr_hdr *)buf;
590+
ipv6_rpl_srh_decompress(ohdr, hdr, &ipv6_hdr(skb)->daddr, n);
591+
chdr = (struct ipv6_rpl_sr_hdr *)(buf + ((ohdr->hdrlen + 1) << 3));
592+
593+
if ((ipv6_addr_type(&ipv6_hdr(skb)->daddr) & IPV6_ADDR_MULTICAST) ||
594+
(ipv6_addr_type(&ohdr->rpl_segaddr[i]) & IPV6_ADDR_MULTICAST)) {
595+
kfree_skb(skb);
596+
kfree(buf);
597+
return -1;
598+
}
599+
600+
err = ipv6_chk_rpl_srh_loop(net, ohdr->rpl_segaddr, n + 1);
601+
if (err) {
602+
icmpv6_send(skb, ICMPV6_PARAMPROB, 0, 0);
603+
kfree_skb(skb);
604+
kfree(buf);
605+
return -1;
606+
}
607+
608+
addr = ipv6_hdr(skb)->daddr;
609+
ipv6_hdr(skb)->daddr = ohdr->rpl_segaddr[i];
610+
ohdr->rpl_segaddr[i] = addr;
611+
612+
ipv6_rpl_srh_compress(chdr, ohdr, &ipv6_hdr(skb)->daddr, n);
613+
614+
oldhdr = ipv6_hdr(skb);
615+
616+
skb_pull(skb, ((hdr->hdrlen + 1) << 3));
617+
skb_postpull_rcsum(skb, oldhdr,
618+
sizeof(struct ipv6hdr) + ((hdr->hdrlen + 1) << 3));
619+
skb_push(skb, ((chdr->hdrlen + 1) << 3) + sizeof(struct ipv6hdr));
620+
skb_reset_network_header(skb);
621+
skb_mac_header_rebuild(skb);
622+
skb_set_transport_header(skb, sizeof(struct ipv6hdr));
623+
624+
memmove(ipv6_hdr(skb), oldhdr, sizeof(struct ipv6hdr));
625+
memcpy(skb_transport_header(skb), chdr, (chdr->hdrlen + 1) << 3);
626+
627+
ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
628+
skb_postpush_rcsum(skb, ipv6_hdr(skb),
629+
sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3));
630+
631+
kfree(buf);
632+
633+
skb_dst_drop(skb);
634+
635+
ip6_route_input(skb);
636+
637+
if (skb_dst(skb)->error) {
638+
dst_input(skb);
639+
return -1;
640+
}
641+
642+
if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) {
643+
if (ipv6_hdr(skb)->hop_limit <= 1) {
644+
__IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
645+
icmpv6_send(skb, ICMPV6_TIME_EXCEED,
646+
ICMPV6_EXC_HOPLIMIT, 0);
647+
kfree_skb(skb);
648+
return -1;
649+
}
650+
ipv6_hdr(skb)->hop_limit--;
651+
652+
skb_pull(skb, sizeof(struct ipv6hdr));
653+
goto looped_back;
654+
}
655+
656+
dst_input(skb);
657+
658+
return -1;
659+
}
660+
471661
/********************************
472662
Routing header.
473663
********************************/
@@ -506,9 +696,16 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
506696
return -1;
507697
}
508698

509-
/* segment routing */
510-
if (hdr->type == IPV6_SRCRT_TYPE_4)
699+
switch (hdr->type) {
700+
case IPV6_SRCRT_TYPE_4:
701+
/* segment routing */
511702
return ipv6_srh_rcv(skb);
703+
case IPV6_SRCRT_TYPE_3:
704+
/* rpl segment routing */
705+
return ipv6_rpl_srh_rcv(skb);
706+
default:
707+
break;
708+
}
512709

513710
looped_back:
514711
if (hdr->segments_left == 0) {

0 commit comments

Comments
 (0)