Skip to content

Commit

Permalink
net: ipv6: add support for rpl sr exthdr
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
alexaring authored and davem330 committed Mar 30, 2020
1 parent f37c605 commit 8610c7c
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 3 deletions.
1 change: 1 addition & 0 deletions include/linux/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
34 changes: 34 additions & 0 deletions include/net/rpl.h
Original file line number Diff line number Diff line change
@@ -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 */
2 changes: 2 additions & 0 deletions include/uapi/linux/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

/*
Expand Down Expand Up @@ -187,6 +188,7 @@ enum {
DEVCONF_DISABLE_POLICY,
DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
DEVCONF_NDISC_TCLASS,
DEVCONF_RPL_SEG_ENABLED,
DEVCONF_MAX
};

Expand Down
2 changes: 1 addition & 1 deletion net/ipv6/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions net/ipv6/addrconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -6900,6 +6903,13 @@ static const struct ctl_table addrconf_sysctl[] = {
.extra1 = (void *)SYSCTL_ZERO,
.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 */
}
Expand Down
201 changes: 199 additions & 2 deletions net/ipv6/exthdrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#ifdef CONFIG_IPV6_SEG6_HMAC
#include <net/seg6_hmac.h>
#endif
#include <net/rpl.h>

#include <linux/uaccess.h>

Expand Down Expand Up @@ -468,6 +469,195 @@ static int ipv6_srh_rcv(struct sk_buff *skb)
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.
********************************/
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 8610c7c

Please sign in to comment.