Skip to content

Commit 1ababeb

Browse files
David Lebrundavem330
authored andcommitted
ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)
Implement minimal support for processing of SR-enabled packets as described in https://tools.ietf.org/html/draft-ietf-6man-segment-routing-header-02. This patch implements the following operations: - Intermediate segment endpoint: incrementation of active segment and rerouting. - Egress for SR-encapsulated packets: decapsulation of outer IPv6 header + SRH and routing of inner packet. - Cleanup flag support for SR-inlined packets: removal of SRH if we are the penultimate segment endpoint. A per-interface sysctl seg6_enabled is provided, to accept/deny SR-enabled packets. Default is deny. This patch does not provide support for HMAC-signed packets. Signed-off-by: David Lebrun <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent dc0b2c9 commit 1ababeb

File tree

7 files changed

+284
-0
lines changed

7 files changed

+284
-0
lines changed

include/linux/ipv6.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct ipv6_devconf {
6464
} stable_secret;
6565
__s32 use_oif_addrs_only;
6666
__s32 keep_addr_on_down;
67+
__s32 seg6_enabled;
6768

6869
struct ctl_table_header *sysctl_header;
6970
};

include/linux/seg6.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef _LINUX_SEG6_H
2+
#define _LINUX_SEG6_H
3+
4+
#include <uapi/linux/seg6.h>
5+
6+
#endif

include/net/seg6.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SR-IPv6 implementation
3+
*
4+
* Author:
5+
* David Lebrun <[email protected]>
6+
*
7+
*
8+
* This program is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU General Public License
10+
* as published by the Free Software Foundation; either version
11+
* 2 of the License, or (at your option) any later version.
12+
*/
13+
14+
#ifndef _NET_SEG6_H
15+
#define _NET_SEG6_H
16+
17+
static inline void update_csum_diff4(struct sk_buff *skb, __be32 from,
18+
__be32 to)
19+
{
20+
__be32 diff[] = { ~from, to };
21+
22+
skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
23+
}
24+
25+
static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
26+
__be32 *to)
27+
{
28+
__be32 diff[] = {
29+
~from[0], ~from[1], ~from[2], ~from[3],
30+
to[0], to[1], to[2], to[3],
31+
};
32+
33+
skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
34+
}
35+
36+
#endif

include/uapi/linux/ipv6.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ struct in6_ifreq {
3939
#define IPV6_SRCRT_STRICT 0x01 /* Deprecated; will be removed */
4040
#define IPV6_SRCRT_TYPE_0 0 /* Deprecated; will be removed */
4141
#define IPV6_SRCRT_TYPE_2 2 /* IPv6 type 2 Routing Header */
42+
#define IPV6_SRCRT_TYPE_4 4 /* Segment Routing with IPv6 */
4243

4344
/*
4445
* routing header
@@ -178,6 +179,7 @@ enum {
178179
DEVCONF_DROP_UNSOLICITED_NA,
179180
DEVCONF_KEEP_ADDR_ON_DOWN,
180181
DEVCONF_RTR_SOLICIT_MAX_INTERVAL,
182+
DEVCONF_SEG6_ENABLED,
181183
DEVCONF_MAX
182184
};
183185

include/uapi/linux/seg6.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* SR-IPv6 implementation
3+
*
4+
* Author:
5+
* David Lebrun <[email protected]>
6+
*
7+
*
8+
* This program is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU General Public License
10+
* as published by the Free Software Foundation; either version
11+
* 2 of the License, or (at your option) any later version.
12+
*/
13+
14+
#ifndef _UAPI_LINUX_SEG6_H
15+
#define _UAPI_LINUX_SEG6_H
16+
17+
/*
18+
* SRH
19+
*/
20+
struct ipv6_sr_hdr {
21+
__u8 nexthdr;
22+
__u8 hdrlen;
23+
__u8 type;
24+
__u8 segments_left;
25+
__u8 first_segment;
26+
__u8 flag_1;
27+
__u8 flag_2;
28+
__u8 reserved;
29+
30+
struct in6_addr segments[0];
31+
};
32+
33+
#define SR6_FLAG1_CLEANUP (1 << 7)
34+
#define SR6_FLAG1_PROTECTED (1 << 6)
35+
#define SR6_FLAG1_OAM (1 << 5)
36+
#define SR6_FLAG1_ALERT (1 << 4)
37+
#define SR6_FLAG1_HMAC (1 << 3)
38+
39+
#define SR6_TLV_INGRESS 1
40+
#define SR6_TLV_EGRESS 2
41+
#define SR6_TLV_OPAQUE 3
42+
#define SR6_TLV_PADDING 4
43+
#define SR6_TLV_HMAC 5
44+
45+
#define sr_has_cleanup(srh) ((srh)->flag_1 & SR6_FLAG1_CLEANUP)
46+
#define sr_has_hmac(srh) ((srh)->flag_1 & SR6_FLAG1_HMAC)
47+
48+
struct sr6_tlv {
49+
__u8 type;
50+
__u8 len;
51+
__u8 data[0];
52+
};
53+
54+
#endif

net/ipv6/addrconf.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
238238
.use_oif_addrs_only = 0,
239239
.ignore_routes_with_linkdown = 0,
240240
.keep_addr_on_down = 0,
241+
.seg6_enabled = 0,
241242
};
242243

243244
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -284,6 +285,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
284285
.use_oif_addrs_only = 0,
285286
.ignore_routes_with_linkdown = 0,
286287
.keep_addr_on_down = 0,
288+
.seg6_enabled = 0,
287289
};
288290

289291
/* Check if a valid qdisc is available */
@@ -4944,6 +4946,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
49444946
array[DEVCONF_DROP_UNICAST_IN_L2_MULTICAST] = cnf->drop_unicast_in_l2_multicast;
49454947
array[DEVCONF_DROP_UNSOLICITED_NA] = cnf->drop_unsolicited_na;
49464948
array[DEVCONF_KEEP_ADDR_ON_DOWN] = cnf->keep_addr_on_down;
4949+
array[DEVCONF_SEG6_ENABLED] = cnf->seg6_enabled;
49474950
}
49484951

49494952
static inline size_t inet6_ifla6_size(void)
@@ -6035,6 +6038,13 @@ static const struct ctl_table addrconf_sysctl[] = {
60356038
.proc_handler = proc_dointvec,
60366039

60376040
},
6041+
{
6042+
.procname = "seg6_enabled",
6043+
.data = &ipv6_devconf.seg6_enabled,
6044+
.maxlen = sizeof(int),
6045+
.mode = 0644,
6046+
.proc_handler = proc_dointvec,
6047+
},
60386048
{
60396049
/* sentinel */
60406050
}

net/ipv6/exthdrs.c

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
#if IS_ENABLED(CONFIG_IPV6_MIP6)
4848
#include <net/xfrm.h>
4949
#endif
50+
#include <linux/seg6.h>
51+
#include <net/seg6.h>
5052

5153
#include <linux/uaccess.h>
5254

@@ -286,6 +288,175 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
286288
return -1;
287289
}
288290

291+
static void seg6_update_csum(struct sk_buff *skb)
292+
{
293+
struct ipv6_sr_hdr *hdr;
294+
struct in6_addr *addr;
295+
__be32 from, to;
296+
297+
/* srh is at transport offset and seg_left is already decremented
298+
* but daddr is not yet updated with next segment
299+
*/
300+
301+
hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
302+
addr = hdr->segments + hdr->segments_left;
303+
304+
hdr->segments_left++;
305+
from = *(__be32 *)hdr;
306+
307+
hdr->segments_left--;
308+
to = *(__be32 *)hdr;
309+
310+
/* update skb csum with diff resulting from seg_left decrement */
311+
312+
update_csum_diff4(skb, from, to);
313+
314+
/* compute csum diff between current and next segment and update */
315+
316+
update_csum_diff16(skb, (__be32 *)(&ipv6_hdr(skb)->daddr),
317+
(__be32 *)addr);
318+
}
319+
320+
static int ipv6_srh_rcv(struct sk_buff *skb)
321+
{
322+
struct inet6_skb_parm *opt = IP6CB(skb);
323+
struct net *net = dev_net(skb->dev);
324+
struct ipv6_sr_hdr *hdr;
325+
struct inet6_dev *idev;
326+
struct in6_addr *addr;
327+
bool cleanup = false;
328+
int accept_seg6;
329+
330+
hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
331+
332+
idev = __in6_dev_get(skb->dev);
333+
334+
accept_seg6 = net->ipv6.devconf_all->seg6_enabled;
335+
if (accept_seg6 > idev->cnf.seg6_enabled)
336+
accept_seg6 = idev->cnf.seg6_enabled;
337+
338+
if (!accept_seg6) {
339+
kfree_skb(skb);
340+
return -1;
341+
}
342+
343+
looped_back:
344+
if (hdr->segments_left > 0) {
345+
if (hdr->nexthdr != NEXTHDR_IPV6 && hdr->segments_left == 1 &&
346+
sr_has_cleanup(hdr))
347+
cleanup = true;
348+
} else {
349+
if (hdr->nexthdr == NEXTHDR_IPV6) {
350+
int offset = (hdr->hdrlen + 1) << 3;
351+
352+
skb_postpull_rcsum(skb, skb_network_header(skb),
353+
skb_network_header_len(skb));
354+
355+
if (!pskb_pull(skb, offset)) {
356+
kfree_skb(skb);
357+
return -1;
358+
}
359+
skb_postpull_rcsum(skb, skb_transport_header(skb),
360+
offset);
361+
362+
skb_reset_network_header(skb);
363+
skb_reset_transport_header(skb);
364+
skb->encapsulation = 0;
365+
366+
__skb_tunnel_rx(skb, skb->dev, net);
367+
368+
netif_rx(skb);
369+
return -1;
370+
}
371+
372+
opt->srcrt = skb_network_header_len(skb);
373+
opt->lastopt = opt->srcrt;
374+
skb->transport_header += (hdr->hdrlen + 1) << 3;
375+
opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb);
376+
377+
return 1;
378+
}
379+
380+
if (hdr->segments_left >= (hdr->hdrlen >> 1)) {
381+
__IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
382+
IPSTATS_MIB_INHDRERRORS);
383+
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
384+
((&hdr->segments_left) -
385+
skb_network_header(skb)));
386+
kfree_skb(skb);
387+
return -1;
388+
}
389+
390+
if (skb_cloned(skb)) {
391+
if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) {
392+
__IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
393+
IPSTATS_MIB_OUTDISCARDS);
394+
kfree_skb(skb);
395+
return -1;
396+
}
397+
}
398+
399+
hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
400+
401+
hdr->segments_left--;
402+
addr = hdr->segments + hdr->segments_left;
403+
404+
skb_push(skb, sizeof(struct ipv6hdr));
405+
406+
if (skb->ip_summed == CHECKSUM_COMPLETE)
407+
seg6_update_csum(skb);
408+
409+
ipv6_hdr(skb)->daddr = *addr;
410+
411+
if (cleanup) {
412+
int srhlen = (hdr->hdrlen + 1) << 3;
413+
int nh = hdr->nexthdr;
414+
415+
skb_pull_rcsum(skb, sizeof(struct ipv6hdr) + srhlen);
416+
memmove(skb_network_header(skb) + srhlen,
417+
skb_network_header(skb),
418+
(unsigned char *)hdr - skb_network_header(skb));
419+
skb->network_header += srhlen;
420+
ipv6_hdr(skb)->nexthdr = nh;
421+
ipv6_hdr(skb)->payload_len = htons(skb->len -
422+
sizeof(struct ipv6hdr));
423+
skb_push_rcsum(skb, sizeof(struct ipv6hdr));
424+
}
425+
426+
skb_dst_drop(skb);
427+
428+
ip6_route_input(skb);
429+
430+
if (skb_dst(skb)->error) {
431+
dst_input(skb);
432+
return -1;
433+
}
434+
435+
if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) {
436+
if (ipv6_hdr(skb)->hop_limit <= 1) {
437+
__IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
438+
IPSTATS_MIB_INHDRERRORS);
439+
icmpv6_send(skb, ICMPV6_TIME_EXCEED,
440+
ICMPV6_EXC_HOPLIMIT, 0);
441+
kfree_skb(skb);
442+
return -1;
443+
}
444+
ipv6_hdr(skb)->hop_limit--;
445+
446+
/* be sure that srh is still present before reinjecting */
447+
if (!cleanup) {
448+
skb_pull(skb, sizeof(struct ipv6hdr));
449+
goto looped_back;
450+
}
451+
skb_set_transport_header(skb, sizeof(struct ipv6hdr));
452+
IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr);
453+
}
454+
455+
dst_input(skb);
456+
457+
return -1;
458+
}
459+
289460
/********************************
290461
Routing header.
291462
********************************/
@@ -326,6 +497,10 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
326497
return -1;
327498
}
328499

500+
/* segment routing */
501+
if (hdr->type == IPV6_SRCRT_TYPE_4)
502+
return ipv6_srh_rcv(skb);
503+
329504
looped_back:
330505
if (hdr->segments_left == 0) {
331506
switch (hdr->type) {

0 commit comments

Comments
 (0)