[nf-next,v2,2/3] netfilter: Add support for IPv6 segment routing 'SEG6' target

Message ID 20180822131018.11286-2-amsalam20@gmail.com
State Under Review
Delegated to: Pablo Neira
Headers show
Series
  • Untitled series #61964
Related show

Commit Message

Ahmed Abdelsalam Aug. 22, 2018, 1:10 p.m.
Service Function Chaining (SFC) is one of the main use-cases
of IPv6 Segment Routing (SRv6) [1].

The Segment Routing Header (SRH) allows including a list of
segments in the IPv6 packet. This segment list can be used
to steer the packetthough a set of Virtual Network Functions
(VNFS) e.g., Firewall, DPI, IDS.

The edge node (SR ingress node) classifies the traffic and
consequently includes the segment lists in the IPv6 packet.

The matching of SRH previous, next and last SIDs has been
 added to ip6tables in kernel 4.16.  Once a packet matches
one of these options, we can perform an SR specific actions.

The IPv6 segment routing 'SEG6' target supports a set of
SR-specific actions.

The first set of actions include those actions necessary to
allow branching within a pre-defined SRv6 SFC policy
instead  of the usual linear exploration of the VNF chain.

This patch implements the following SR-specific actions:
(1) go-next: sends packets towards the next SID from SRH.
(2) skip-next: skips the next SID in the SRH.
(3) go-last: skips the remaining part of the segment list
and send  the packet towards the last segment.

[1] https://tools.ietf.org/html/draft-xuclad-spring-sr-service-programming-00

Signed-off-by: Ahmed Abdelsalam <amsalam20@gmail.com>
---
Changes in v2:
 - Added check on seg6->action and return -EOPNOTSUPP for non supported actions
 - Removed the pr_err() call from the evaluation function.

 include/uapi/linux/netfilter_ipv6/ip6t_SEG6.h |  22 +++++
 net/ipv6/netfilter/Kconfig                    |  15 ++++
 net/ipv6/netfilter/Makefile                   |   1 +
 net/ipv6/netfilter/ip6t_SEG6.c                | 120 ++++++++++++++++++++++++++
 4 files changed, 158 insertions(+)
 create mode 100644 include/uapi/linux/netfilter_ipv6/ip6t_SEG6.h
 create mode 100644 net/ipv6/netfilter/ip6t_SEG6.c

Comments

Pablo Neira Ayuso Aug. 23, 2018, 6:11 p.m. | #1
On Wed, Aug 22, 2018 at 03:10:17PM +0200, Ahmed Abdelsalam wrote:
[...]
> diff --git a/net/ipv6/netfilter/ip6t_SEG6.c b/net/ipv6/netfilter/ip6t_SEG6.c
> new file mode 100644
> index 000000000000..0adfb98ccaf2
> --- /dev/null
> +++ b/net/ipv6/netfilter/ip6t_SEG6.c
> @@ -0,0 +1,120 @@
> +/**
> + * IPv6 Segment Routing target module (SEG6).
> + *
> + * Author:
> + * Ahmed Abdelsalam <amsalam20@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + *	modify it under the terms of the GNU General Public License
> + *	as published by the Free Software Foundation; either version 2
> + *	of the License, or (at your option) any later version.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/gfp.h>
> +#include <linux/module.h>
> +#include <linux/skbuff.h>
> +#include <linux/icmpv6.h>
> +#include <linux/netdevice.h>
> +#include <linux/netfilter/x_tables.h>
> +#include <linux/netfilter_ipv6/ip6_tables.h>
> +#include <linux/netfilter_ipv6/ip6t_SEG6.h>
> +
> +#include <net/flow.h>
> +#include <net/seg6.h>
> +#include <net/ip6_route.h>
> +
> +static int seg6_go_next(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
> +{
> +	if (srh->segments_left == 0)
> +		return NF_DROP;
> +	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
> +	seg6_lookup_nexthop(skb, NULL, 0);
> +	dst_input(skb);
> +	return NF_STOLEN;
> +}
> +
> +static int seg6_skip_next(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
> +{
> +	if (srh->segments_left < 2)
> +		return NF_DROP;
> +	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
> +	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
> +	seg6_lookup_nexthop(skb, NULL, 0);
> +	dst_input(skb);
> +	return NF_STOLEN;
> +}
> +
> +static int seg6_go_last(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
> +{
> +	if (srh->segments_left == 0)
> +		return NF_DROP;
> +	srh->segments_left = 1;
> +	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
> +	seg6_lookup_nexthop(skb, NULL, 0);
> +	dst_input(skb);
> +	return NF_STOLEN;
> +}
> +
> +static unsigned int
> +seg6_tg6(struct sk_buff *skb, const struct xt_action_param *par)
> +{
> +	struct ipv6_sr_hdr *srh;
> +	const struct ip6t_seg6_info *seg6 = par->targinfo;
> +
> +	srh = seg6_get_srh(skb);
> +	if (!srh)
> +		return NF_DROP;
> +
> +	switch (seg6->action) {
> +	case IP6T_SEG6_GO_NEXT:
> +		return seg6_go_next(skb, srh);
> +	case IP6T_SEG6_SKIP_NEXT:
> +		return seg6_skip_next(skb, srh);
> +	case IP6T_SEG6_GO_LAST:
> +		return seg6_go_last(skb, srh);
> +	}
> +
> +	return NF_DROP;
> +}
> +
> +static int seg6_check(const struct xt_tgchk_param *par)
> +{
> +	const struct ip6t_seg6_info *seg6 = par->targinfo;
> +
> +	switch (seg6->action) {
> +	case IP6T_SEG6_GO_NEXT:
> +	case IP6T_SEG6_SKIP_NEXT:
> +	case IP6T_SEG6_GO_LAST:
> +		return 0;
> +	default:
> +	return -EOPNOTSUPP;
> +	}
> +}
> +
> +static struct xt_target seg6_tg6_reg __read_mostly = {
> +	.name		= "SEG6",
> +	.family		= NFPROTO_IPV6,
> +	.target		= seg6_tg6,
> +	.targetsize	= sizeof(struct ip6t_seg6_info),

There is no .hook_mask here, can you really use this target from any
netfilter hook?

This is also mangling the IPv6 dst field, which breaks ongoing
conntracking/NAT, so I suspect at least you need to restrict this to
PREROUTING/raw table? Or this explicit needs a rule with -j NOTRACK?
Probably this extension would fit better in the ingress hook?

If you could post realistic example ruleset that would help understand
how you plan to use this, or even configuration example where this
extension is used. Interaction of this module with other existing
netfilter extensions is broken, I'd appreciate an explanation.

Thanks.
Ahmed Abdelsalam Aug. 24, 2018, 9:43 a.m. | #2
On Thu, 23 Aug 2018 20:11:47 +0200
Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> > +static struct xt_target seg6_tg6_reg __read_mostly = {
> > +	.name		= "SEG6",
> > +	.family		= NFPROTO_IPV6,
> > +	.target		= seg6_tg6,
> > +	.targetsize	= sizeof(struct ip6t_seg6_info),
> 
> There is no .hook_mask here, can you really use this target from any
> netfilter hook?
> 
> This is also mangling the IPv6 dst field, which breaks ongoing
> conntracking/NAT, so I suspect at least you need to restrict this to
> PREROUTING/raw table? Or this explicit needs a rule with -j NOTRACK?
> Probably this extension would fit better in the ingress hook?
> 
> If you could post realistic example ruleset that would help understand
> how you plan to use this, or even configuration example where this
> extension is used. Interaction of this module with other existing
> netfilter extensions is broken, I'd appreciate an explanation.
> 
> Thanks.

Hi Pablo, 
Let me elaborate more here and then I’ll answer the questions. 

Classification , Reclassification, and Branching are Core SFC Architecture Components, which is defined in RFC 7665 [1].
Initial classification occurs at the ingress to the SFC domain. The granularity of the initial classification is determined by the capabilities of the classifier. 
For instance, classification might be relatively coarse: all packets from this port are subject to SFC policy X , or quite granular: all packets matching this 5-tuple are subject to SFC policy Y. 

In case of IPv6 Segment Routing, the Linux kernel currently adding SRv6 SFC policies based on the destination address of packets. 
These policies are configured using iproute2 as follows [2]: 

ip -6 route add <prefix> encap seg6 mode <encapmode> segs <segments> [hmac <keyid>] dev <device>
 
However, In most of the cases, the SFC policies should be based on the traffic source which represent the per-customer policy. 
This can be achieved using some ip rules as follows which is also presented in the demo shown in [3]

echo “101 customer1” >> /etc/iproute2/rt_tables
ip -6 rule add from <cutomer1_prefix> lookup customer1
or 
ip -6 rule add iif <cutomer1_iface> lookup customer1

ip -6 route add <dst_prefix> encap seg6 mode <encapmode>  segs <<segments>  [hmac <keyid>] dev <device> table customer1

The SFC architecture should support reclassification (or non-initial classification) as well.  As packets traverse an SFP, reclassification may occur -- typically performed by a classification function co-resident with a service function.  
Reclassification may result in the selection of a new SFP,  which  is referred to as "branching".
The need for reclassification came from the fact that SFC policies were applied based on layer3 information, while some traffic patterns can only be detected at the application layer and/or some sophisticated firewall rules. 

Iptables as Service Function that could be part of an IPv6 SFC chain should be able to support such branching. Here comes the need for such SEG6 target actions. each of these actions can address a some different use-cases as follows: 
(1) go-next
consider a use-case where the customer policy requires traffic to be processed by two different firewalls (FW_1 and FW_2) that provides different security levels, as well as QoS treatment service (Q1) that shape the packets received at x Mbps). However, a subset of the traffic (e.g., voice traffic using RTP protocol to avoid delay) doesn’t need to go through all complex rules configured at FW_2.  This can be achieved by having the following rule in the second firewall (FW_2)

ip6tables -I INPUT -p udp --dport <rtp_port_range> -m srh --srh-psid <FW_1> -j SEG6 —seg6-action go-next 

(2) skip-next
The same use-case described in (1) can be done from FW_1 as follows:
ip6tables -I INPUT -p udp --dport <rtp_port_range> -m srh --srh-nsid <FW_2> -j SEG6 —seg6-action skip-next 

(3) go-last 
Network can be under congestion which means the Firewalls are totally overloaded, in such a case you might decide to totally skip SFC (for a subset of the traffic) by going to the last Service function in the chain
ip6tables -I INPUT -p udp --dport <portx> -j SEG6 —seg6-action go-last 

(4) bind-sid
The firewall might decide to apply DPI for some suspicious traffic which requires adding a new service to the chain 
ip6tables -I INPUT -p <protocol> --dport <portx> -j SEG6 —seg6-action bind-sid —sid BD::1

ip -6 route add BD::1 encap seg6 mode <encapmode>  segs <<segments>  [hmac <keyid>] dev <device> 

As for the question: 
- .hook_mask is required and as you can see should be the INPUT because the iptbables as a service function should be the packet destination. However the bind-action can be useful to be supported also in ingress/prerouting hook which allows to define more fine grained SRv6 polices 
- Regarding the problem with NAT, According to SFC RFC service functions (e.g., NAT ) should be SFC encapsulation aware which means be able to recognise the SFC encapsulation. So the NATing should be done based on dst addr of the real packet not the SFC encapsulation. For the SFC-unware SFs there should be at least a proxy which handles SFC-encapsulated packets before being handed to unaware SFs. A list of SRv6 proxy behaviours are defined in [4] and some of them are implemented in [5]. 

Thanks, 
Ahmed 

[1] https://tools.ietf.org/html/rfc7665
[2] https://segment-routing.org/index.php/Implementation/Configuration
[3] https://github.com/SRouting/SR-sfc-demo
[4] https://tools.ietf.org/html/draft-xuclad-spring-sr-service-programming-00
[5] https://github.com/SRouting/SRv6-net-prog

Patch

diff --git a/include/uapi/linux/netfilter_ipv6/ip6t_SEG6.h b/include/uapi/linux/netfilter_ipv6/ip6t_SEG6.h
new file mode 100644
index 000000000000..443805d0d1b2
--- /dev/null
+++ b/include/uapi/linux/netfilter_ipv6/ip6t_SEG6.h
@@ -0,0 +1,22 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _IP6T_SEG6_H
+#define _IP6T_SEG6_H
+
+#include <linux/types.h>
+
+/* SEG6 action options */
+enum ip6t_seg6_action {
+	IP6T_SEG6_GO_NEXT,
+	IP6T_SEG6_SKIP_NEXT,
+	IP6T_SEG6_GO_LAST,
+	IP6T_SEG6_BSID,
+};
+
+struct ip6t_seg6_info {
+	__u32			action; /* SEG6 action */
+	struct	in6_addr	bsid;	/* SRv6 Bind SID */
+	unsigned int		tbl;	/* Routing table of bsid */
+};
+
+#endif /*_IP6T_SEG6_H*/
diff --git a/net/ipv6/netfilter/Kconfig b/net/ipv6/netfilter/Kconfig
index 339d0762b027..a2502c54a837 100644
--- a/net/ipv6/netfilter/Kconfig
+++ b/net/ipv6/netfilter/Kconfig
@@ -344,6 +344,21 @@  config IP6_NF_TARGET_NPT
 
 endif # IP6_NF_NAT
 
+if IPV6_SEG6_LWTUNNEL
+
+config IP6_NF_TARGET_SEG6
+	tristate 'IPv6 Segment Routing "SEG6" target support'
+	depends on NETFILTER_ADVANCED
+	help
+	 SEG6 is an ip6tables target for IPv6 Segment Routing encapsualted
+	 packets. It supports a set of Segment Routing specific actions
+	 that can be applied based on SRH information. It is useful for
+	 SRv6 Service Function chaining use-cases.
+
+	 To compile it as a module, choose M here.  If unsure, say N.
+
+endif # IPV6_SEG6_LWTUNNEL
+
 endif # IP6_NF_IPTABLES
 endmenu
 
diff --git a/net/ipv6/netfilter/Makefile b/net/ipv6/netfilter/Makefile
index 200c0c235565..68e86e18f15f 100644
--- a/net/ipv6/netfilter/Makefile
+++ b/net/ipv6/netfilter/Makefile
@@ -58,3 +58,4 @@  obj-$(CONFIG_IP6_NF_TARGET_MASQUERADE) += ip6t_MASQUERADE.o
 obj-$(CONFIG_IP6_NF_TARGET_NPT) += ip6t_NPT.o
 obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o
 obj-$(CONFIG_IP6_NF_TARGET_SYNPROXY) += ip6t_SYNPROXY.o
+obj-$(CONFIG_IP6_NF_TARGET_SEG6) += ip6t_SEG6.o
diff --git a/net/ipv6/netfilter/ip6t_SEG6.c b/net/ipv6/netfilter/ip6t_SEG6.c
new file mode 100644
index 000000000000..0adfb98ccaf2
--- /dev/null
+++ b/net/ipv6/netfilter/ip6t_SEG6.c
@@ -0,0 +1,120 @@ 
+/**
+ * IPv6 Segment Routing target module (SEG6).
+ *
+ * Author:
+ * Ahmed Abdelsalam <amsalam20@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/gfp.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/icmpv6.h>
+#include <linux/netdevice.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_SEG6.h>
+
+#include <net/flow.h>
+#include <net/seg6.h>
+#include <net/ip6_route.h>
+
+static int seg6_go_next(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
+{
+	if (srh->segments_left == 0)
+		return NF_DROP;
+	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
+	seg6_lookup_nexthop(skb, NULL, 0);
+	dst_input(skb);
+	return NF_STOLEN;
+}
+
+static int seg6_skip_next(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
+{
+	if (srh->segments_left < 2)
+		return NF_DROP;
+	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
+	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
+	seg6_lookup_nexthop(skb, NULL, 0);
+	dst_input(skb);
+	return NF_STOLEN;
+}
+
+static int seg6_go_last(struct sk_buff *skb, struct ipv6_sr_hdr *srh)
+{
+	if (srh->segments_left == 0)
+		return NF_DROP;
+	srh->segments_left = 1;
+	seg6_advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
+	seg6_lookup_nexthop(skb, NULL, 0);
+	dst_input(skb);
+	return NF_STOLEN;
+}
+
+static unsigned int
+seg6_tg6(struct sk_buff *skb, const struct xt_action_param *par)
+{
+	struct ipv6_sr_hdr *srh;
+	const struct ip6t_seg6_info *seg6 = par->targinfo;
+
+	srh = seg6_get_srh(skb);
+	if (!srh)
+		return NF_DROP;
+
+	switch (seg6->action) {
+	case IP6T_SEG6_GO_NEXT:
+		return seg6_go_next(skb, srh);
+	case IP6T_SEG6_SKIP_NEXT:
+		return seg6_skip_next(skb, srh);
+	case IP6T_SEG6_GO_LAST:
+		return seg6_go_last(skb, srh);
+	}
+
+	return NF_DROP;
+}
+
+static int seg6_check(const struct xt_tgchk_param *par)
+{
+	const struct ip6t_seg6_info *seg6 = par->targinfo;
+
+	switch (seg6->action) {
+	case IP6T_SEG6_GO_NEXT:
+	case IP6T_SEG6_SKIP_NEXT:
+	case IP6T_SEG6_GO_LAST:
+		return 0;
+	default:
+	return -EOPNOTSUPP;
+	}
+}
+
+static struct xt_target seg6_tg6_reg __read_mostly = {
+	.name		= "SEG6",
+	.family		= NFPROTO_IPV6,
+	.target		= seg6_tg6,
+	.targetsize	= sizeof(struct ip6t_seg6_info),
+	.checkentry	= seg6_check,
+	.me		= THIS_MODULE
+};
+
+static int __init seg6_tg6_init(void)
+{
+	return xt_register_target(&seg6_tg6_reg);
+}
+
+static void __exit seg6_tg6_exit(void)
+{
+	xt_unregister_target(&seg6_tg6_reg);
+}
+
+module_init(seg6_tg6_init);
+module_exit(seg6_tg6_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Xtables: IPv6 Segment Routing Target (SEG6)");
+MODULE_AUTHOR("Ahmed Abdelsalam <amsalam20@gmail.com>");