summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/networking/seg6-sysctl.txt18
-rw-r--r--include/linux/ipv6.h4
-rw-r--r--include/linux/seg6.h6
-rw-r--r--include/linux/seg6_genl.h6
-rw-r--r--include/linux/seg6_hmac.h6
-rw-r--r--include/linux/seg6_iptunnel.h6
-rw-r--r--include/net/ipv6.h3
-rw-r--r--include/net/netns/ipv6.h1
-rw-r--r--include/net/seg6.h62
-rw-r--r--include/net/seg6_hmac.h62
-rw-r--r--include/uapi/linux/ipv6.h3
-rw-r--r--include/uapi/linux/lwtunnel.h1
-rw-r--r--include/uapi/linux/seg6.h54
-rw-r--r--include/uapi/linux/seg6_genl.h32
-rw-r--r--include/uapi/linux/seg6_hmac.h21
-rw-r--r--include/uapi/linux/seg6_iptunnel.h44
-rw-r--r--net/core/lwtunnel.c2
-rw-r--r--net/ipv6/Kconfig24
-rw-r--r--net/ipv6/Makefile3
-rw-r--r--net/ipv6/addrconf.c28
-rw-r--r--net/ipv6/af_inet6.c9
-rw-r--r--net/ipv6/exthdrs.c268
-rw-r--r--net/ipv6/ip6_output.c5
-rw-r--r--net/ipv6/ip6_tunnel.c2
-rw-r--r--net/ipv6/ipv6_sockglue.c10
-rw-r--r--net/ipv6/seg6.c487
-rw-r--r--net/ipv6/seg6_hmac.c484
-rw-r--r--net/ipv6/seg6_iptunnel.c431
28 files changed, 2070 insertions, 12 deletions
diff --git a/Documentation/networking/seg6-sysctl.txt b/Documentation/networking/seg6-sysctl.txt
new file mode 100644
index 000000000000..bdbde23b19cb
--- /dev/null
+++ b/Documentation/networking/seg6-sysctl.txt
@@ -0,0 +1,18 @@
+/proc/sys/net/conf/<iface>/seg6_* variables:
+
+seg6_enabled - BOOL
+ Accept or drop SR-enabled IPv6 packets on this interface.
+
+ Relevant packets are those with SRH present and DA = local.
+
+ 0 - disabled (default)
+ not 0 - enabled
+
+seg6_require_hmac - INTEGER
+ Define HMAC policy for ingress SR-enabled packets on this interface.
+
+ -1 - Ignore HMAC field
+ 0 - Accept SR packets without HMAC, validate SR packets with HMAC
+ 1 - Drop SR packets without HMAC, validate SR packets with HMAC
+
+ Default is 0.
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 1afb6e8d35c3..93756585521f 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -64,6 +64,10 @@ struct ipv6_devconf {
} stable_secret;
__s32 use_oif_addrs_only;
__s32 keep_addr_on_down;
+ __s32 seg6_enabled;
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ __s32 seg6_require_hmac;
+#endif
struct ctl_table_header *sysctl_header;
};
diff --git a/include/linux/seg6.h b/include/linux/seg6.h
new file mode 100644
index 000000000000..7a66d2b4c5a6
--- /dev/null
+++ b/include/linux/seg6.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_SEG6_H
+#define _LINUX_SEG6_H
+
+#include <uapi/linux/seg6.h>
+
+#endif
diff --git a/include/linux/seg6_genl.h b/include/linux/seg6_genl.h
new file mode 100644
index 000000000000..d6c3fb4f3734
--- /dev/null
+++ b/include/linux/seg6_genl.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_SEG6_GENL_H
+#define _LINUX_SEG6_GENL_H
+
+#include <uapi/linux/seg6_genl.h>
+
+#endif
diff --git a/include/linux/seg6_hmac.h b/include/linux/seg6_hmac.h
new file mode 100644
index 000000000000..da437ebdc6cd
--- /dev/null
+++ b/include/linux/seg6_hmac.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_SEG6_HMAC_H
+#define _LINUX_SEG6_HMAC_H
+
+#include <uapi/linux/seg6_hmac.h>
+
+#endif
diff --git a/include/linux/seg6_iptunnel.h b/include/linux/seg6_iptunnel.h
new file mode 100644
index 000000000000..5377cf6a5a02
--- /dev/null
+++ b/include/linux/seg6_iptunnel.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_SEG6_IPTUNNEL_H
+#define _LINUX_SEG6_IPTUNNEL_H
+
+#include <uapi/linux/seg6_iptunnel.h>
+
+#endif
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 8fed1cd78658..0a3622bf086f 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -932,7 +932,8 @@ int ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
*/
void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,
- u8 *proto, struct in6_addr **daddr_p);
+ u8 *proto, struct in6_addr **daddr_p,
+ struct in6_addr *saddr);
void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,
u8 *proto);
diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index 10d0848f5b8a..de7745e2edcc 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -85,6 +85,7 @@ struct netns_ipv6 {
#endif
atomic_t dev_addr_genid;
atomic_t fib6_sernum;
+ struct seg6_pernet_data *seg6_data;
};
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
diff --git a/include/net/seg6.h b/include/net/seg6.h
new file mode 100644
index 000000000000..4e0357517d79
--- /dev/null
+++ b/include/net/seg6.h
@@ -0,0 +1,62 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#ifndef _NET_SEG6_H
+#define _NET_SEG6_H
+
+#include <linux/net.h>
+#include <linux/ipv6.h>
+#include <net/lwtunnel.h>
+#include <linux/seg6.h>
+#include <linux/rhashtable.h>
+
+static inline void update_csum_diff4(struct sk_buff *skb, __be32 from,
+ __be32 to)
+{
+ __be32 diff[] = { ~from, to };
+
+ skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
+}
+
+static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
+ __be32 *to)
+{
+ __be32 diff[] = {
+ ~from[0], ~from[1], ~from[2], ~from[3],
+ to[0], to[1], to[2], to[3],
+ };
+
+ skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
+}
+
+struct seg6_pernet_data {
+ struct mutex lock;
+ struct in6_addr __rcu *tun_src;
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ struct rhashtable hmac_infos;
+#endif
+};
+
+static inline struct seg6_pernet_data *seg6_pernet(struct net *net)
+{
+ return net->ipv6.seg6_data;
+}
+
+extern int seg6_init(void);
+extern void seg6_exit(void);
+extern int seg6_iptunnel_init(void);
+extern void seg6_iptunnel_exit(void);
+
+extern bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len);
+
+#endif
diff --git a/include/net/seg6_hmac.h b/include/net/seg6_hmac.h
new file mode 100644
index 000000000000..69c3a106056b
--- /dev/null
+++ b/include/net/seg6_hmac.h
@@ -0,0 +1,62 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#ifndef _NET_SEG6_HMAC_H
+#define _NET_SEG6_HMAC_H
+
+#include <net/flow.h>
+#include <net/ip6_fib.h>
+#include <net/sock.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/route.h>
+#include <net/seg6.h>
+#include <linux/seg6_hmac.h>
+#include <linux/rhashtable.h>
+
+#define SEG6_HMAC_MAX_DIGESTSIZE 160
+#define SEG6_HMAC_RING_SIZE 256
+
+struct seg6_hmac_info {
+ struct rhash_head node;
+ struct rcu_head rcu;
+
+ u32 hmackeyid;
+ char secret[SEG6_HMAC_SECRET_LEN];
+ u8 slen;
+ u8 alg_id;
+};
+
+struct seg6_hmac_algo {
+ u8 alg_id;
+ char name[64];
+ struct crypto_shash * __percpu *tfms;
+ struct shash_desc * __percpu *shashs;
+};
+
+extern int seg6_hmac_compute(struct seg6_hmac_info *hinfo,
+ struct ipv6_sr_hdr *hdr, struct in6_addr *saddr,
+ u8 *output);
+extern struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key);
+extern int seg6_hmac_info_add(struct net *net, u32 key,
+ struct seg6_hmac_info *hinfo);
+extern int seg6_hmac_info_del(struct net *net, u32 key);
+extern int seg6_push_hmac(struct net *net, struct in6_addr *saddr,
+ struct ipv6_sr_hdr *srh);
+extern bool seg6_hmac_validate_skb(struct sk_buff *skb);
+extern int seg6_hmac_init(void);
+extern void seg6_hmac_exit(void);
+extern int seg6_hmac_net_init(struct net *net);
+extern void seg6_hmac_net_exit(struct net *net);
+
+#endif
diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h
index 8c2772340c3f..53561be1ac21 100644
--- a/include/uapi/linux/ipv6.h
+++ b/include/uapi/linux/ipv6.h
@@ -39,6 +39,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_4 4 /* Segment Routing with IPv6 */
/*
* routing header
@@ -178,6 +179,8 @@ enum {
DEVCONF_DROP_UNSOLICITED_NA,
DEVCONF_KEEP_ADDR_ON_DOWN,
DEVCONF_RTR_SOLICIT_MAX_INTERVAL,
+ DEVCONF_SEG6_ENABLED,
+ DEVCONF_SEG6_REQUIRE_HMAC,
DEVCONF_MAX
};
diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
index a478fe80e203..453cc6215bfd 100644
--- a/include/uapi/linux/lwtunnel.h
+++ b/include/uapi/linux/lwtunnel.h
@@ -9,6 +9,7 @@ enum lwtunnel_encap_types {
LWTUNNEL_ENCAP_IP,
LWTUNNEL_ENCAP_ILA,
LWTUNNEL_ENCAP_IP6,
+ LWTUNNEL_ENCAP_SEG6,
__LWTUNNEL_ENCAP_MAX,
};
diff --git a/include/uapi/linux/seg6.h b/include/uapi/linux/seg6.h
new file mode 100644
index 000000000000..c396a8052f73
--- /dev/null
+++ b/include/uapi/linux/seg6.h
@@ -0,0 +1,54 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#ifndef _UAPI_LINUX_SEG6_H
+#define _UAPI_LINUX_SEG6_H
+
+/*
+ * SRH
+ */
+struct ipv6_sr_hdr {
+ __u8 nexthdr;
+ __u8 hdrlen;
+ __u8 type;
+ __u8 segments_left;
+ __u8 first_segment;
+ __u8 flag_1;
+ __u8 flag_2;
+ __u8 reserved;
+
+ struct in6_addr segments[0];
+};
+
+#define SR6_FLAG1_CLEANUP (1 << 7)
+#define SR6_FLAG1_PROTECTED (1 << 6)
+#define SR6_FLAG1_OAM (1 << 5)
+#define SR6_FLAG1_ALERT (1 << 4)
+#define SR6_FLAG1_HMAC (1 << 3)
+
+#define SR6_TLV_INGRESS 1
+#define SR6_TLV_EGRESS 2
+#define SR6_TLV_OPAQUE 3
+#define SR6_TLV_PADDING 4
+#define SR6_TLV_HMAC 5
+
+#define sr_has_cleanup(srh) ((srh)->flag_1 & SR6_FLAG1_CLEANUP)
+#define sr_has_hmac(srh) ((srh)->flag_1 & SR6_FLAG1_HMAC)
+
+struct sr6_tlv {
+ __u8 type;
+ __u8 len;
+ __u8 data[0];
+};
+
+#endif
diff --git a/include/uapi/linux/seg6_genl.h b/include/uapi/linux/seg6_genl.h
new file mode 100644
index 000000000000..fcf1c60d7df3
--- /dev/null
+++ b/include/uapi/linux/seg6_genl.h
@@ -0,0 +1,32 @@
+#ifndef _UAPI_LINUX_SEG6_GENL_H
+#define _UAPI_LINUX_SEG6_GENL_H
+
+#define SEG6_GENL_NAME "SEG6"
+#define SEG6_GENL_VERSION 0x1
+
+enum {
+ SEG6_ATTR_UNSPEC,
+ SEG6_ATTR_DST,
+ SEG6_ATTR_DSTLEN,
+ SEG6_ATTR_HMACKEYID,
+ SEG6_ATTR_SECRET,
+ SEG6_ATTR_SECRETLEN,
+ SEG6_ATTR_ALGID,
+ SEG6_ATTR_HMACINFO,
+ __SEG6_ATTR_MAX,
+};
+
+#define SEG6_ATTR_MAX (__SEG6_ATTR_MAX - 1)
+
+enum {
+ SEG6_CMD_UNSPEC,
+ SEG6_CMD_SETHMAC,
+ SEG6_CMD_DUMPHMAC,
+ SEG6_CMD_SET_TUNSRC,
+ SEG6_CMD_GET_TUNSRC,
+ __SEG6_CMD_MAX,
+};
+
+#define SEG6_CMD_MAX (__SEG6_CMD_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/seg6_hmac.h b/include/uapi/linux/seg6_hmac.h
new file mode 100644
index 000000000000..b652dfd51bc5
--- /dev/null
+++ b/include/uapi/linux/seg6_hmac.h
@@ -0,0 +1,21 @@
+#ifndef _UAPI_LINUX_SEG6_HMAC_H
+#define _UAPI_LINUX_SEG6_HMAC_H
+
+#include <linux/seg6.h>
+
+#define SEG6_HMAC_SECRET_LEN 64
+#define SEG6_HMAC_FIELD_LEN 32
+
+struct sr6_tlv_hmac {
+ struct sr6_tlv tlvhdr;
+ __u16 reserved;
+ __be32 hmackeyid;
+ __u8 hmac[SEG6_HMAC_FIELD_LEN];
+};
+
+enum {
+ SEG6_HMAC_ALGO_SHA1 = 1,
+ SEG6_HMAC_ALGO_SHA256 = 2,
+};
+
+#endif
diff --git a/include/uapi/linux/seg6_iptunnel.h b/include/uapi/linux/seg6_iptunnel.h
new file mode 100644
index 000000000000..0f7dbd280a9c
--- /dev/null
+++ b/include/uapi/linux/seg6_iptunnel.h
@@ -0,0 +1,44 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#ifndef _UAPI_LINUX_SEG6_IPTUNNEL_H
+#define _UAPI_LINUX_SEG6_IPTUNNEL_H
+
+enum {
+ SEG6_IPTUNNEL_UNSPEC,
+ SEG6_IPTUNNEL_SRH,
+ __SEG6_IPTUNNEL_MAX,
+};
+#define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1)
+
+struct seg6_iptunnel_encap {
+ int mode;
+ struct ipv6_sr_hdr srh[0];
+};
+
+#define SEG6_IPTUN_ENCAP_SIZE(x) ((sizeof(*x)) + (((x)->srh->hdrlen + 1) << 3))
+
+enum {
+ SEG6_IPTUN_MODE_INLINE,
+ SEG6_IPTUN_MODE_ENCAP,
+};
+
+static inline size_t seg6_lwt_headroom(struct seg6_iptunnel_encap *tuninfo)
+{
+ int encap = (tuninfo->mode == SEG6_IPTUN_MODE_ENCAP);
+
+ return ((tuninfo->srh->hdrlen + 1) << 3) +
+ (encap * sizeof(struct ipv6hdr));
+}
+
+#endif
diff --git a/net/core/lwtunnel.c b/net/core/lwtunnel.c
index 88fd64250b02..03976e939818 100644
--- a/net/core/lwtunnel.c
+++ b/net/core/lwtunnel.c
@@ -39,6 +39,8 @@ static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type)
return "MPLS";
case LWTUNNEL_ENCAP_ILA:
return "ILA";
+ case LWTUNNEL_ENCAP_SEG6:
+ return "SEG6";
case LWTUNNEL_ENCAP_IP6:
case LWTUNNEL_ENCAP_IP:
case LWTUNNEL_ENCAP_NONE:
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index 2343e4f2e0bf..0f00811a785f 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -289,4 +289,28 @@ config IPV6_PIMSM_V2
Support for IPv6 PIM multicast routing protocol PIM-SMv2.
If unsure, say N.
+config IPV6_SEG6_INLINE
+ bool "IPv6: direct Segment Routing Header insertion "
+ depends on IPV6
+ ---help---
+ Support for direct insertion of the Segment Routing Header,
+ also known as inline mode. Be aware that direct insertion of
+ extension headers (as opposed to encapsulation) may break
+ multiple mechanisms such as PMTUD or IPSec AH. Use this feature
+ only if you know exactly what you are doing.
+
+ If unsure, say N.
+
+config IPV6_SEG6_HMAC
+ bool "IPv6: Segment Routing HMAC support"
+ depends on IPV6
+ select CRYPTO_HMAC
+ select CRYPTO_SHA1
+ select CRYPTO_SHA256
+ ---help---
+ Support for HMAC signature generation and verification
+ of SR-enabled packets.
+
+ If unsure, say N.
+
endif # IPV6
diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile
index c174ccb340a1..129cad2ba960 100644
--- a/net/ipv6/Makefile
+++ b/net/ipv6/Makefile
@@ -9,7 +9,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
+ udp_offload.o seg6.o seg6_iptunnel.o
ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o
@@ -44,6 +44,7 @@ obj-$(CONFIG_IPV6_SIT) += sit.o
obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o
obj-$(CONFIG_IPV6_GRE) += ip6_gre.o
obj-$(CONFIG_IPV6_FOU) += fou6.o
+obj-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
obj-y += addrconf_core.o exthdrs_core.o ip6_checksum.o ip6_icmp.o
obj-$(CONFIG_INET) += output_core.o protocol.o $(ipv6-offload)
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 060dd9922018..86219c0a0104 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -238,6 +238,10 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
.use_oif_addrs_only = 0,
.ignore_routes_with_linkdown = 0,
.keep_addr_on_down = 0,
+ .seg6_enabled = 0,
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ .seg6_require_hmac = 0,
+#endif
};
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -284,6 +288,10 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.use_oif_addrs_only = 0,
.ignore_routes_with_linkdown = 0,
.keep_addr_on_down = 0,
+ .seg6_enabled = 0,
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ .seg6_require_hmac = 0,
+#endif
};
/* Check if a valid qdisc is available */
@@ -4944,6 +4952,10 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
array[DEVCONF_DROP_UNICAST_IN_L2_MULTICAST] = cnf->drop_unicast_in_l2_multicast;
array[DEVCONF_DROP_UNSOLICITED_NA] = cnf->drop_unsolicited_na;
array[DEVCONF_KEEP_ADDR_ON_DOWN] = cnf->keep_addr_on_down;
+ array[DEVCONF_SEG6_ENABLED] = cnf->seg6_enabled;
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ array[DEVCONF_SEG6_REQUIRE_HMAC] = cnf->seg6_require_hmac;
+#endif
}
static inline size_t inet6_ifla6_size(void)
@@ -6036,6 +6048,22 @@ static const struct ctl_table addrconf_sysctl[] = {
},
{
+ .procname = "seg6_enabled",
+ .data = &ipv6_devconf.seg6_enabled,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ {
+ .procname = "seg6_require_hmac",
+ .data = &ipv6_devconf.seg6_require_hmac,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+#endif
+ {
/* sentinel */
}
};
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index c86911b63f8a..d424f3a3737a 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -61,6 +61,7 @@
#include <net/ip6_tunnel.h>
#endif
#include <net/calipso.h>
+#include <net/seg6.h>
#include <asm/uaccess.h>
#include <linux/mroute6.h>
@@ -991,6 +992,10 @@ static int __init inet6_init(void)
if (err)
goto calipso_fail;
+ err = seg6_init();
+ if (err)
+ goto seg6_fail;
+
#ifdef CONFIG_SYSCTL
err = ipv6_sysctl_register();
if (err)
@@ -1001,8 +1006,10 @@ out:
#ifdef CONFIG_SYSCTL
sysctl_fail:
- calipso_exit();
+ seg6_exit();
#endif
+seg6_fail:
+ calipso_exit();
calipso_fail:
pingv6_exit();
pingv6_fail:
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 139ceb68bd37..926818c331e5 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -47,6 +47,11 @@
#if IS_ENABLED(CONFIG_IPV6_MIP6)
#include <net/xfrm.h>
#endif
+#include <linux/seg6.h>
+#include <net/seg6.h>
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
#include <linux/uaccess.h>
@@ -286,6 +291,182 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
return -1;
}
+static void seg6_update_csum(struct sk_buff *skb)
+{
+ struct ipv6_sr_hdr *hdr;
+ struct in6_addr *addr;
+ __be32 from, to;
+
+ /* srh is at transport offset and seg_left is already decremented
+ * but daddr is not yet updated with next segment
+ */
+
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
+ addr = hdr->segments + hdr->segments_left;
+
+ hdr->segments_left++;
+ from = *(__be32 *)hdr;
+
+ hdr->segments_left--;
+ to = *(__be32 *)hdr;
+
+ /* update skb csum with diff resulting from seg_left decrement */
+
+ update_csum_diff4(skb, from, to);
+
+ /* compute csum diff between current and next segment and update */
+
+ update_csum_diff16(skb, (__be32 *)(&ipv6_hdr(skb)->daddr),
+ (__be32 *)addr);
+}
+
+static int ipv6_srh_rcv(struct sk_buff *skb)
+{
+ struct inet6_skb_parm *opt = IP6CB(skb);
+ struct net *net = dev_net(skb->dev);
+ struct ipv6_sr_hdr *hdr;
+ struct inet6_dev *idev;
+ struct in6_addr *addr;
+ bool cleanup = false;
+ int accept_seg6;
+
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
+
+ idev = __in6_dev_get(skb->dev);
+
+ accept_seg6 = net->ipv6.devconf_all->seg6_enabled;
+ if (accept_seg6 > idev->cnf.seg6_enabled)
+ accept_seg6 = idev->cnf.seg6_enabled;
+
+ if (!accept_seg6) {
+ kfree_skb(skb);
+ return -1;
+ }
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (!seg6_hmac_validate_skb(skb)) {
+ kfree_skb(skb);
+ return -1;
+ }
+#endif
+
+looped_back:
+ if (hdr->segments_left > 0) {
+ if (hdr->nexthdr != NEXTHDR_IPV6 && hdr->segments_left == 1 &&
+ sr_has_cleanup(hdr))
+ cleanup = true;
+ } else {
+ 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 (hdr->segments_left >= (hdr->hdrlen >> 1)) {
+ __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
+ IPSTATS_MIB_INHDRERRORS);
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
+ ((&hdr->segments_left) -
+ skb_network_header(skb)));
+ kfree_skb(skb);
+ return -1;
+ }
+
+ if (skb_cloned(skb)) {
+ if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) {
+ __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
+ IPSTATS_MIB_OUTDISCARDS);
+ kfree_skb(skb);
+ return -1;
+ }
+ }
+
+ hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb);
+
+ hdr->segments_left--;
+ addr = hdr->segments + hdr->segments_left;
+
+ skb_push(skb, sizeof(struct ipv6hdr));
+
+ if (skb->ip_summed == CHECKSUM_COMPLETE)
+ seg6_update_csum(skb);
+
+ ipv6_hdr(skb)->daddr = *addr;
+
+ if (cleanup) {
+ int srhlen = (hdr->hdrlen + 1) << 3;
+ int nh = hdr->nexthdr;
+
+ skb_pull_rcsum(skb, sizeof(struct ipv6hdr) + srhlen);
+ memmove(skb_network_header(skb) + srhlen,
+ skb_network_header(skb),
+ (unsigned char *)hdr - skb_network_header(skb));
+ skb->network_header += srhlen;
+ ipv6_hdr(skb)->nexthdr = nh;
+ ipv6_hdr(skb)->payload_len = htons(skb->len -
+ sizeof(struct ipv6hdr));
+ skb_push_rcsum(skb, sizeof(struct ipv6hdr));
+ }
+
+ 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, ip6_dst_idev(skb_dst(skb)),
+ IPSTATS_MIB_INHDRERRORS);
+ icmpv6_send(skb, ICMPV6_TIME_EXCEED,
+ ICMPV6_EXC_HOPLIMIT, 0);
+ kfree_skb(skb);
+ return -1;
+ }
+ ipv6_hdr(skb)->hop_limit--;
+
+ /* be sure that srh is still present before reinjecting */
+ if (!cleanup) {
+ skb_pull(skb, sizeof(struct ipv6hdr));
+ goto looped_back;
+ }
+ skb_set_transport_header(skb, sizeof(struct ipv6hdr));
+ IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr);
+ }
+
+ dst_input(skb);
+
+ return -1;
+}
+
/********************************
Routing header.
********************************/
@@ -326,6 +507,10 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb)
return -1;
}
+ /* segment routing */
+ if (hdr->type == IPV6_SRCRT_TYPE_4)
+ return ipv6_srh_rcv(skb);
+
looped_back:
if (hdr->segments_left == 0) {
switch (hdr->type) {
@@ -679,9 +864,9 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
* for headers.
*/
-static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto,
- struct ipv6_rt_hdr *opt,
- struct in6_addr **addr_p)
+static void ipv6_push_rthdr0(struct sk_buff *skb, u8 *proto,
+ struct ipv6_rt_hdr *opt,
+ struct in6_addr **addr_p, struct in6_addr *saddr)
{
struct rt0_hdr *phdr, *ihdr;
int hops;
@@ -704,6 +889,62 @@ static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto,
*proto = NEXTHDR_ROUTING;
}
+static void ipv6_push_rthdr4(struct sk_buff *skb, u8 *proto,
+ struct ipv6_rt_hdr *opt,
+ struct in6_addr **addr_p, struct in6_addr *saddr)
+{
+ struct ipv6_sr_hdr *sr_phdr, *sr_ihdr;
+ int plen, hops;
+
+ sr_ihdr = (struct ipv6_sr_hdr *)opt;
+ plen = (sr_ihdr->hdrlen + 1) << 3;
+
+ sr_phdr = (struct ipv6_sr_hdr *)skb_push(skb, plen);
+ memcpy(sr_phdr, sr_ihdr, sizeof(struct ipv6_sr_hdr));
+
+ hops = sr_ihdr->first_segment + 1;
+ memcpy(sr_phdr->segments + 1, sr_ihdr->segments + 1,
+ (hops - 1) * sizeof(struct in6_addr));
+
+ sr_phdr->segments[0] = **addr_p;
+ *addr_p = &sr_ihdr->segments[hops - 1];
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (sr_has_hmac(sr_phdr)) {
+ struct net *net = NULL;
+
+ if (skb->dev)
+ net = dev_net(skb->dev);
+ else if (skb->sk)
+ net = sock_net(skb->sk);
+
+ WARN_ON(!net);
+
+ if (net)
+ seg6_push_hmac(net, saddr, sr_phdr);
+ }
+#endif
+
+ sr_phdr->nexthdr = *proto;
+ *proto = NEXTHDR_ROUTING;
+}
+
+static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto,
+ struct ipv6_rt_hdr *opt,
+ struct in6_addr **addr_p, struct in6_addr *saddr)
+{
+ switch (opt->type) {
+ case IPV6_SRCRT_TYPE_0:
+ ipv6_push_rthdr0(skb, proto, opt, addr_p, saddr);
+ break;
+ case IPV6_SRCRT_TYPE_4:
+ ipv6_push_rthdr4(skb, proto, opt, addr_p, saddr);
+ break;
+ default:
+ break;
+ }
+}
+
static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv6_opt_hdr *opt)
{
struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_push(skb, ipv6_optlen(opt));
@@ -715,10 +956,10 @@ static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv
void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,
u8 *proto,
- struct in6_addr **daddr)
+ struct in6_addr **daddr, struct in6_addr *saddr)
{
if (opt->srcrt) {
- ipv6_push_rthdr(skb, proto, opt->srcrt, daddr);
+ ipv6_push_rthdr(skb, proto, opt->srcrt, daddr, saddr);
/*
* IPV6_RTHDRDSTOPTS is ignored
* unless IPV6_RTHDR is set (RFC3542).
@@ -945,7 +1186,22 @@ struct in6_addr *fl6_update_dst(struct flowi6 *fl6,
return NULL;
*orig = fl6->daddr;
- fl6->daddr = *((struct rt0_hdr *)opt->srcrt)->addr;
+
+ switch (opt->srcrt->type) {
+ case IPV6_SRCRT_TYPE_0:
+ fl6->daddr = *((struct rt0_hdr *)opt->srcrt)->addr;
+ break;
+ case IPV6_SRCRT_TYPE_4:
+ {
+ struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)opt->srcrt;
+
+ fl6->daddr = srh->segments[srh->first_segment];
+ break;
+ }
+ default:
+ return NULL;
+ }
+
return orig;
}
EXPORT_SYMBOL_GPL(fl6_update_dst);
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 6001e781164e..ddc878d2cc6d 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -203,7 +203,8 @@ int ip6_xmit(const struct sock *sk, struct sk_buff *skb, struct flowi6 *fl6,
if (opt->opt_flen)
ipv6_push_frag_opts(skb, opt, &proto);
if (opt->opt_nflen)
- ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop);
+ ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop,
+ &fl6->saddr);
}
skb_push(skb, sizeof(struct ipv6hdr));
@@ -1672,7 +1673,7 @@ struct sk_buff *__ip6_make_skb(struct sock *sk,
if (opt && opt->opt_flen)
ipv6_push_frag_opts(skb, opt, &proto);
if (opt && opt->opt_nflen)
- ipv6_push_nfrag_opts(skb, opt, &proto, &final_dst);
+ ipv6_push_nfrag_opts(skb, opt, &proto, &final_dst, &fl6->saddr);
skb_push(skb, sizeof(struct ipv6hdr));
skb_reset_network_header(skb);
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index bfa889c2a87b..259e8507d2cd 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1157,7 +1157,7 @@ route_lookup:
if (encap_limit >= 0) {
init_tel_txopt(&opt, encap_limit);
- ipv6_push_nfrag_opts(skb, &opt.ops, &proto, NULL);
+ ipv6_push_nfrag_opts(skb, &opt.ops, &proto, NULL, NULL);
}
/* Calculate max headroom for all the headers and adjust
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 6c126780fcf2..3ba530373560 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -52,6 +52,7 @@
#include <net/udplite.h>
#include <net/xfrm.h>
#include <net/compat.h>
+#include <net/seg6.h>
#include <asm/uaccess.h>
@@ -430,6 +431,15 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
break;
#endif
+ case IPV6_SRCRT_TYPE_4:
+ {
+ struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)
+ opt->srcrt;
+
+ if (!seg6_validate_srh(srh, optlen))
+ goto sticky_done;
+ break;
+ }
default:
goto sticky_done;
}
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
new file mode 100644
index 000000000000..50f6e0663d1d
--- /dev/null
+++ b/net/ipv6/seg6.c
@@ -0,0 +1,487 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/slab.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+
+#include <net/seg6.h>
+#include <net/genetlink.h>
+#include <linux/seg6.h>
+#include <linux/seg6_genl.h>
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
+
+bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len)
+{
+ int trailing;
+ unsigned int tlv_offset;
+
+ if (srh->type != IPV6_SRCRT_TYPE_4)
+ return false;
+
+ if (((srh->hdrlen + 1) << 3) != len)
+ return false;
+
+ if (srh->segments_left != srh->first_segment)
+ return false;
+
+ tlv_offset = sizeof(*srh) + ((srh->first_segment + 1) << 4);
+
+ trailing = len - tlv_offset;
+ if (trailing < 0)
+ return false;
+
+ while (trailing) {
+ struct sr6_tlv *tlv;
+ unsigned int tlv_len;
+
+ tlv = (struct sr6_tlv *)((unsigned char *)srh + tlv_offset);
+ tlv_len = sizeof(*tlv) + tlv->len;
+
+ trailing -= tlv_len;
+ if (trailing < 0)
+ return false;
+
+ tlv_offset += tlv_len;
+ }
+
+ return true;
+}
+
+static struct genl_family seg6_genl_family;
+
+static const struct nla_policy seg6_genl_policy[SEG6_ATTR_MAX + 1] = {
+ [SEG6_ATTR_DST] = { .type = NLA_BINARY,
+ .len = sizeof(struct in6_addr) },
+ [SEG6_ATTR_DSTLEN] = { .type = NLA_S32, },
+ [SEG6_ATTR_HMACKEYID] = { .type = NLA_U32, },
+ [SEG6_ATTR_SECRET] = { .type = NLA_BINARY, },
+ [SEG6_ATTR_SECRETLEN] = { .type = NLA_U8, },
+ [SEG6_ATTR_ALGID] = { .type = NLA_U8, },
+ [SEG6_ATTR_HMACINFO] = { .type = NLA_NESTED, },
+};
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+
+static int seg6_genl_sethmac(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct seg6_pernet_data *sdata;
+ struct seg6_hmac_info *hinfo;
+ u32 hmackeyid;
+ char *secret;
+ int err = 0;
+ u8 algid;
+ u8 slen;
+
+ sdata = seg6_pernet(net);
+
+ if (!info->attrs[SEG6_ATTR_HMACKEYID] ||
+ !info->attrs[SEG6_ATTR_SECRETLEN] ||
+ !info->attrs[SEG6_ATTR_ALGID])
+ return -EINVAL;
+
+ hmackeyid = nla_get_u32(info->attrs[SEG6_ATTR_HMACKEYID]);
+ slen = nla_get_u8(info->attrs[SEG6_ATTR_SECRETLEN]);
+ algid = nla_get_u8(info->attrs[SEG6_ATTR_ALGID]);
+
+ if (hmackeyid == 0)
+ return -EINVAL;
+
+ if (slen > SEG6_HMAC_SECRET_LEN)
+ return -EINVAL;
+
+ mutex_lock(&sdata->lock);
+ hinfo = seg6_hmac_info_lookup(net, hmackeyid);
+
+ if (!slen) {
+ if (!hinfo)
+ err = -ENOENT;
+
+ err = seg6_hmac_info_del(net, hmackeyid);
+
+ goto out_unlock;
+ }
+
+ if (!info->attrs[SEG6_ATTR_SECRET]) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (hinfo) {
+ err = seg6_hmac_info_del(net, hmackeyid);
+ if (err)
+ goto out_unlock;
+ }
+
+ secret = (char *)nla_data(info->attrs[SEG6_ATTR_SECRET]);
+
+ hinfo = kzalloc(sizeof(*hinfo), GFP_KERNEL);
+ if (!hinfo) {
+ err = -ENOMEM;
+ goto out_unlock;
+ }
+
+ memcpy(hinfo->secret, secret, slen);
+ hinfo->slen = slen;
+ hinfo->alg_id = algid;
+ hinfo->hmackeyid = hmackeyid;
+
+ err = seg6_hmac_info_add(net, hmackeyid, hinfo);
+ if (err)
+ kfree(hinfo);
+
+out_unlock:
+ mutex_unlock(&sdata->lock);
+ return err;
+}
+
+#else
+
+static int seg6_genl_sethmac(struct sk_buff *skb, struct genl_info *info)
+{
+ return -ENOTSUPP;
+}
+
+#endif
+
+static int seg6_genl_set_tunsrc(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct in6_addr *val, *t_old, *t_new;
+ struct seg6_pernet_data *sdata;
+
+ sdata = seg6_pernet(net);
+
+ if (!info->attrs[SEG6_ATTR_DST])
+ return -EINVAL;
+
+ val = nla_data(info->attrs[SEG6_ATTR_DST]);
+ t_new = kmemdup(val, sizeof(*val), GFP_KERNEL);
+
+ mutex_lock(&sdata->lock);
+
+ t_old = sdata->tun_src;
+ rcu_assign_pointer(sdata->tun_src, t_new);
+
+ mutex_unlock(&sdata->lock);
+
+ synchronize_net();
+ kfree(t_old);
+
+ return 0;
+}
+
+static int seg6_genl_get_tunsrc(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct in6_addr *tun_src;
+ struct sk_buff *msg;
+ void *hdr;
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &seg6_genl_family, 0, SEG6_CMD_GET_TUNSRC);
+ if (!hdr)
+ goto free_msg;
+
+ rcu_read_lock();
+ tun_src = rcu_dereference(seg6_pernet(net)->tun_src);
+
+ if (nla_put(msg, SEG6_ATTR_DST, sizeof(struct in6_addr), tun_src))
+ goto nla_put_failure;
+
+ rcu_read_unlock();
+
+ genlmsg_end(msg, hdr);
+ genlmsg_reply(msg, info);
+
+ return 0;
+
+nla_put_failure:
+ rcu_read_unlock();
+ genlmsg_cancel(msg, hdr);
+free_msg:
+ nlmsg_free(msg);
+ return -ENOMEM;
+}
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+
+static int __seg6_hmac_fill_info(struct seg6_hmac_info *hinfo,
+ struct sk_buff *msg)
+{
+ if (nla_put_u32(msg, SEG6_ATTR_HMACKEYID, hinfo->hmackeyid) ||
+ nla_put_u8(msg, SEG6_ATTR_SECRETLEN, hinfo->slen) ||
+ nla_put(msg, SEG6_ATTR_SECRET, hinfo->slen, hinfo->secret) ||
+ nla_put_u8(msg, SEG6_ATTR_ALGID, hinfo->alg_id))
+ return -1;
+
+ return 0;
+}
+
+static int __seg6_genl_dumphmac_element(struct seg6_hmac_info *hinfo,
+ u32 portid, u32 seq, u32 flags,
+ struct sk_buff *skb, u8 cmd)
+{
+ void *hdr;
+
+ hdr = genlmsg_put(skb, portid, seq, &seg6_genl_family, flags, cmd);
+ if (!hdr)
+ return -ENOMEM;
+
+ if (__seg6_hmac_fill_info(hinfo, skb) < 0)
+ goto nla_put_failure;
+
+ genlmsg_end(skb, hdr);
+ return 0;
+
+nla_put_failure:
+ genlmsg_cancel(skb, hdr);
+ return -EMSGSIZE;
+}
+
+static int seg6_genl_dumphmac_start(struct netlink_callback *cb)
+{
+ struct net *net = sock_net(cb->skb->sk);
+ struct seg6_pernet_data *sdata;
+ struct rhashtable_iter *iter;
+
+ sdata = seg6_pernet(net);
+ iter = (struct rhashtable_iter *)cb->args[0];
+
+ if (!iter) {
+ iter = kmalloc(sizeof(*iter), GFP_KERNEL);
+ if (!iter)
+ return -ENOMEM;
+
+ cb->args[0] = (long)iter;
+ }
+
+ rhashtable_walk_enter(&sdata->hmac_infos, iter);
+
+ return 0;
+}
+
+static int seg6_genl_dumphmac_done(struct netlink_callback *cb)
+{
+ struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0];
+
+ rhashtable_walk_exit(iter);
+
+ kfree(iter);
+
+ return 0;
+}
+
+static int seg6_genl_dumphmac(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0];
+ struct net *net = sock_net(skb->sk);
+ struct seg6_pernet_data *sdata;
+ struct seg6_hmac_info *hinfo;
+ int ret;
+
+ sdata = seg6_pernet(net);
+
+ ret = rhashtable_walk_start(iter);
+ if (ret && ret != -EAGAIN)
+ goto done;
+
+ for (;;) {
+ hinfo = rhashtable_walk_next(iter);
+
+ if (IS_ERR(hinfo)) {
+ if (PTR_ERR(hinfo) == -EAGAIN)
+ continue;
+ ret = PTR_ERR(hinfo);
+ goto done;
+ } else if (!hinfo) {
+ break;
+ }
+
+ ret = __seg6_genl_dumphmac_element(hinfo,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ NLM_F_MULTI,
+ skb, SEG6_CMD_DUMPHMAC);
+ if (ret)
+ goto done;
+ }
+
+ ret = skb->len;
+
+done:
+ rhashtable_walk_stop(iter);
+ return ret;
+}
+
+#else
+
+static int seg6_genl_dumphmac_start(struct netlink_callback *cb)
+{
+ return 0;
+}
+
+static int seg6_genl_dumphmac_done(struct netlink_callback *cb)
+{
+ return 0;
+}
+
+static int seg6_genl_dumphmac(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ return -ENOTSUPP;
+}
+
+#endif
+
+static int __net_init seg6_net_init(struct net *net)
+{
+ struct seg6_pernet_data *sdata;
+
+ sdata = kzalloc(sizeof(*sdata), GFP_KERNEL);
+ if (!sdata)
+ return -ENOMEM;
+
+ mutex_init(&sdata->lock);
+
+ sdata->tun_src = kzalloc(sizeof(*sdata->tun_src), GFP_KERNEL);
+ if (!sdata->tun_src) {
+ kfree(sdata);
+ return -ENOMEM;
+ }
+
+ net->ipv6.seg6_data = sdata;
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ seg6_hmac_net_init(net);
+#endif
+
+ return 0;
+}
+
+static void __net_exit seg6_net_exit(struct net *net)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ seg6_hmac_net_exit(net);
+#endif
+
+ kfree(sdata->tun_src);
+ kfree(sdata);
+}
+
+static struct pernet_operations ip6_segments_ops = {
+ .init = seg6_net_init,
+ .exit = seg6_net_exit,
+};
+
+static const struct genl_ops seg6_genl_ops[] = {
+ {
+ .cmd = SEG6_CMD_SETHMAC,
+ .doit = seg6_genl_sethmac,
+ .policy = seg6_genl_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = SEG6_CMD_DUMPHMAC,
+ .start = seg6_genl_dumphmac_start,
+ .dumpit = seg6_genl_dumphmac,
+ .done = seg6_genl_dumphmac_done,
+ .policy = seg6_genl_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = SEG6_CMD_SET_TUNSRC,
+ .doit = seg6_genl_set_tunsrc,
+ .policy = seg6_genl_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = SEG6_CMD_GET_TUNSRC,
+ .doit = seg6_genl_get_tunsrc,
+ .policy = seg6_genl_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+};
+
+static struct genl_family seg6_genl_family __ro_after_init = {
+ .hdrsize = 0,
+ .name = SEG6_GENL_NAME,
+ .version = SEG6_GENL_VERSION,
+ .maxattr = SEG6_ATTR_MAX,
+ .netnsok = true,
+ .parallel_ops = true,
+ .ops = seg6_genl_ops,
+ .n_ops = ARRAY_SIZE(seg6_genl_ops),
+ .module = THIS_MODULE,
+};
+
+int __init seg6_init(void)
+{
+ int err = -ENOMEM;
+
+ err = genl_register_family(&seg6_genl_family);
+ if (err)
+ goto out;
+
+ err = register_pernet_subsys(&ip6_segments_ops);
+ if (err)
+ goto out_unregister_genl;
+
+ err = seg6_iptunnel_init();
+ if (err)
+ goto out_unregister_pernet;
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ err = seg6_hmac_init();
+ if (err)
+ goto out_unregister_iptun;
+#endif
+
+ pr_info("Segment Routing with IPv6\n");
+
+out:
+ return err;
+#ifdef CONFIG_IPV6_SEG6_HMAC
+out_unregister_iptun:
+ seg6_iptunnel_exit();
+#endif
+out_unregister_pernet:
+ unregister_pernet_subsys(&ip6_segments_ops);
+out_unregister_genl:
+ genl_unregister_family(&seg6_genl_family);
+ goto out;
+}
+
+void seg6_exit(void)
+{
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ seg6_hmac_exit();
+#endif
+ seg6_iptunnel_exit();
+ unregister_pernet_subsys(&ip6_segments_ops);
+ genl_unregister_family(&seg6_genl_family);
+}
diff --git a/net/ipv6/seg6_hmac.c b/net/ipv6/seg6_hmac.c
new file mode 100644
index 000000000000..ef1c8a46e7ac
--- /dev/null
+++ b/net/ipv6/seg6_hmac.c
@@ -0,0 +1,484 @@
+/*
+ * SR-IPv6 implementation -- HMAC functions
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/icmpv6.h>
+#include <linux/mroute6.h>
+#include <linux/slab.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/xfrm.h>
+
+#include <linux/cryptohash.h>
+#include <crypto/hash.h>
+#include <crypto/sha.h>
+#include <net/seg6.h>
+#include <net/genetlink.h>
+#include <net/seg6_hmac.h>
+#include <linux/random.h>
+
+static char * __percpu *hmac_ring;
+
+static int seg6_hmac_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
+{
+ const struct seg6_hmac_info *hinfo = obj;
+
+ return (hinfo->hmackeyid != *(__u32 *)arg->key);
+}
+
+static inline void seg6_hinfo_release(struct seg6_hmac_info *hinfo)
+{
+ kfree_rcu(hinfo, rcu);
+}
+
+static void seg6_free_hi(void *ptr, void *arg)
+{
+ struct seg6_hmac_info *hinfo = (struct seg6_hmac_info *)ptr;
+
+ if (hinfo)
+ seg6_hinfo_release(hinfo);
+}
+
+static const struct rhashtable_params rht_params = {
+ .head_offset = offsetof(struct seg6_hmac_info, node),
+ .key_offset = offsetof(struct seg6_hmac_info, hmackeyid),
+ .key_len = sizeof(u32),
+ .automatic_shrinking = true,
+ .obj_cmpfn = seg6_hmac_cmpfn,
+};
+
+static struct seg6_hmac_algo hmac_algos[] = {
+ {
+ .alg_id = SEG6_HMAC_ALGO_SHA1,
+ .name = "hmac(sha1)",
+ },
+ {
+ .alg_id = SEG6_HMAC_ALGO_SHA256,
+ .name = "hmac(sha256)",
+ },
+};
+
+static struct sr6_tlv_hmac *seg6_get_tlv_hmac(struct ipv6_sr_hdr *srh)
+{
+ struct sr6_tlv_hmac *tlv;
+
+ if (srh->hdrlen < (srh->first_segment + 1) * 2 + 5)
+ return NULL;
+
+ if (!sr_has_hmac(srh))
+ return NULL;
+
+ tlv = (struct sr6_tlv_hmac *)
+ ((char *)srh + ((srh->hdrlen + 1) << 3) - 40);
+
+ if (tlv->tlvhdr.type != SR6_TLV_HMAC || tlv->tlvhdr.len != 38)
+ return NULL;
+
+ return tlv;
+}
+
+static struct seg6_hmac_algo *__hmac_get_algo(u8 alg_id)
+{
+ struct seg6_hmac_algo *algo;
+ int i, alg_count;
+
+ alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo);
+ for (i = 0; i < alg_count; i++) {
+ algo = &hmac_algos[i];
+ if (algo->alg_id == alg_id)
+ return algo;
+ }
+
+ return NULL;
+}
+
+static int __do_hmac(struct seg6_hmac_info *hinfo, const char *text, u8 psize,
+ u8 *output, int outlen)
+{
+ struct seg6_hmac_algo *algo;
+ struct crypto_shash *tfm;
+ struct shash_desc *shash;
+ int ret, dgsize;
+
+ algo = __hmac_get_algo(hinfo->alg_id);
+ if (!algo)
+ return -ENOENT;
+
+ tfm = *this_cpu_ptr(algo->tfms);
+
+ dgsize = crypto_shash_digestsize(tfm);
+ if (dgsize > outlen) {
+ pr_debug("sr-ipv6: __do_hmac: digest size too big (%d / %d)\n",
+ dgsize, outlen);
+ return -ENOMEM;
+ }
+
+ ret = crypto_shash_setkey(tfm, hinfo->secret, hinfo->slen);
+ if (ret < 0) {
+ pr_debug("sr-ipv6: crypto_shash_setkey failed: err %d\n", ret);
+ goto failed;
+ }
+
+ shash = *this_cpu_ptr(algo->shashs);
+ shash->tfm = tfm;
+
+ ret = crypto_shash_digest(shash, text, psize, output);
+ if (ret < 0) {
+ pr_debug("sr-ipv6: crypto_shash_digest failed: err %d\n", ret);
+ goto failed;
+ }
+
+ return dgsize;
+
+failed:
+ return ret;
+}
+
+int seg6_hmac_compute(struct seg6_hmac_info *hinfo, struct ipv6_sr_hdr *hdr,
+ struct in6_addr *saddr, u8 *output)
+{
+ __be32 hmackeyid = cpu_to_be32(hinfo->hmackeyid);
+ u8 tmp_out[SEG6_HMAC_MAX_DIGESTSIZE];
+ int plen, i, dgsize, wrsize;
+ char *ring, *off;
+
+ /* a 160-byte buffer for digest output allows to store highest known
+ * hash function (RadioGatun) with up to 1216 bits
+ */
+
+ /* saddr(16) + first_seg(1) + cleanup(1) + keyid(4) + seglist(16n) */
+ plen = 16 + 1 + 1 + 4 + (hdr->first_segment + 1) * 16;
+
+ /* this limit allows for 14 segments */
+ if (plen >= SEG6_HMAC_RING_SIZE)
+ return -EMSGSIZE;
+
+ /* Let's build the HMAC text on the ring buffer. The text is composed
+ * as follows, in order:
+ *
+ * 1. Source IPv6 address (128 bits)
+ * 2. first_segment value (8 bits)
+ * 3. cleanup flag (8 bits: highest bit is cleanup value, others are 0)
+ * 4. HMAC Key ID (32 bits)
+ * 5. All segments in the segments list (n * 128 bits)
+ */
+
+ local_bh_disable();
+ ring = *this_cpu_ptr(hmac_ring);
+ off = ring;
+
+ /* source address */
+ memcpy(off, saddr, 16);
+ off += 16;
+
+ /* first_segment value */
+ *off++ = hdr->first_segment;
+
+ /* cleanup flag */
+ *off++ = !!(sr_has_cleanup(hdr)) << 7;
+
+ /* HMAC Key ID */
+ memcpy(off, &hmackeyid, 4);
+ off += 4;
+
+ /* all segments in the list */
+ for (i = 0; i < hdr->first_segment + 1; i++) {
+ memcpy(off, hdr->segments + i, 16);
+ off += 16;
+ }
+
+ dgsize = __do_hmac(hinfo, ring, plen, tmp_out,
+ SEG6_HMAC_MAX_DIGESTSIZE);
+ local_bh_enable();
+
+ if (dgsize < 0)
+ return dgsize;
+
+ wrsize = SEG6_HMAC_FIELD_LEN;
+ if (wrsize > dgsize)
+ wrsize = dgsize;
+
+ memset(output, 0, SEG6_HMAC_FIELD_LEN);
+ memcpy(output, tmp_out, wrsize);
+
+ return 0;
+}
+EXPORT_SYMBOL(seg6_hmac_compute);
+
+/* checks if an incoming SR-enabled packet's HMAC status matches
+ * the incoming policy.
+ *
+ * called with rcu_read_lock()
+ */
+bool seg6_hmac_validate_skb(struct sk_buff *skb)
+{
+ u8 hmac_output[SEG6_HMAC_FIELD_LEN];
+ struct net *net = dev_net(skb->dev);
+ struct seg6_hmac_info *hinfo;
+ struct sr6_tlv_hmac *tlv;
+ struct ipv6_sr_hdr *srh;
+ struct inet6_dev *idev;
+
+ idev = __in6_dev_get(skb->dev);
+
+ srh = (struct ipv6_sr_hdr *)skb_transport_header(skb);
+
+ tlv = seg6_get_tlv_hmac(srh);
+
+ /* mandatory check but no tlv */
+ if (idev->cnf.seg6_require_hmac > 0 && !tlv)
+ return false;
+
+ /* no check */
+ if (idev->cnf.seg6_require_hmac < 0)
+ return true;
+
+ /* check only if present */
+ if (idev->cnf.seg6_require_hmac == 0 && !tlv)
+ return true;
+
+ /* now, seg6_require_hmac >= 0 && tlv */
+
+ hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid));
+ if (!hinfo)
+ return false;
+
+ if (seg6_hmac_compute(hinfo, srh, &ipv6_hdr(skb)->saddr, hmac_output))
+ return false;
+
+ if (memcmp(hmac_output, tlv->hmac, SEG6_HMAC_FIELD_LEN) != 0)
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL(seg6_hmac_validate_skb);
+
+/* called with rcu_read_lock() */
+struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+ struct seg6_hmac_info *hinfo;
+
+ hinfo = rhashtable_lookup_fast(&sdata->hmac_infos, &key, rht_params);
+
+ return hinfo;
+}
+EXPORT_SYMBOL(seg6_hmac_info_lookup);
+
+int seg6_hmac_info_add(struct net *net, u32 key, struct seg6_hmac_info *hinfo)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+ int err;
+
+ err = rhashtable_lookup_insert_fast(&sdata->hmac_infos, &hinfo->node,
+ rht_params);
+
+ return err;
+}
+EXPORT_SYMBOL(seg6_hmac_info_add);
+
+int seg6_hmac_info_del(struct net *net, u32 key)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+ struct seg6_hmac_info *hinfo;
+ int err = -ENOENT;
+
+ hinfo = rhashtable_lookup_fast(&sdata->hmac_infos, &key, rht_params);
+ if (!hinfo)
+ goto out;
+
+ err = rhashtable_remove_fast(&sdata->hmac_infos, &hinfo->node,
+ rht_params);
+ if (err)
+ goto out;
+
+ seg6_hinfo_release(hinfo);
+
+out:
+ return err;
+}
+EXPORT_SYMBOL(seg6_hmac_info_del);
+
+int seg6_push_hmac(struct net *net, struct in6_addr *saddr,
+ struct ipv6_sr_hdr *srh)
+{
+ struct seg6_hmac_info *hinfo;
+ struct sr6_tlv_hmac *tlv;
+ int err = -ENOENT;
+
+ tlv = seg6_get_tlv_hmac(srh);
+ if (!tlv)
+ return -EINVAL;
+
+ rcu_read_lock();
+
+ hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid));
+ if (!hinfo)
+ goto out;
+
+ memset(tlv->hmac, 0, SEG6_HMAC_FIELD_LEN);
+ err = seg6_hmac_compute(hinfo, srh, saddr, tlv->hmac);
+
+out:
+ rcu_read_unlock();
+ return err;
+}
+EXPORT_SYMBOL(seg6_push_hmac);
+
+static int seg6_hmac_init_ring(void)
+{
+ int i;
+
+ hmac_ring = alloc_percpu(char *);
+
+ if (!hmac_ring)
+ return -ENOMEM;
+
+ for_each_possible_cpu(i) {
+ char *ring = kzalloc(SEG6_HMAC_RING_SIZE, GFP_KERNEL);
+
+ if (!ring)
+ return -ENOMEM;
+
+ *per_cpu_ptr(hmac_ring, i) = ring;
+ }
+
+ return 0;
+}
+
+static int seg6_hmac_init_algo(void)
+{
+ struct seg6_hmac_algo *algo;
+ struct crypto_shash *tfm;
+ struct shash_desc *shash;
+ int i, alg_count, cpu;
+
+ alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo);
+
+ for (i = 0; i < alg_count; i++) {
+ struct crypto_shash **p_tfm;
+ int shsize;
+
+ algo = &hmac_algos[i];
+ algo->tfms = alloc_percpu(struct crypto_shash *);
+ if (!algo->tfms)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ tfm = crypto_alloc_shash(algo->name, 0, GFP_KERNEL);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+ p_tfm = per_cpu_ptr(algo->tfms, cpu);
+ *p_tfm = tfm;
+ }
+
+ p_tfm = this_cpu_ptr(algo->tfms);
+ tfm = *p_tfm;
+
+ shsize = sizeof(*shash) + crypto_shash_descsize(tfm);
+
+ algo->shashs = alloc_percpu(struct shash_desc *);
+ if (!algo->shashs)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ shash = kzalloc(shsize, GFP_KERNEL);
+ if (!shash)
+ return -ENOMEM;
+ *per_cpu_ptr(algo->shashs, cpu) = shash;
+ }
+ }
+
+ return 0;
+}
+
+int __init seg6_hmac_init(void)
+{
+ int ret;
+
+ ret = seg6_hmac_init_ring();
+ if (ret < 0)
+ goto out;
+
+ ret = seg6_hmac_init_algo();
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(seg6_hmac_init);
+
+int __net_init seg6_hmac_net_init(struct net *net)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+
+ rhashtable_init(&sdata->hmac_infos, &rht_params);
+
+ return 0;
+}
+EXPORT_SYMBOL(seg6_hmac_net_init);
+
+void seg6_hmac_exit(void)
+{
+ struct seg6_hmac_algo *algo = NULL;
+ int i, alg_count, cpu;
+
+ for_each_possible_cpu(i) {
+ char *ring = *per_cpu_ptr(hmac_ring, i);
+
+ kfree(ring);
+ }
+ free_percpu(hmac_ring);
+
+ alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo);
+ for (i = 0; i < alg_count; i++) {
+ algo = &hmac_algos[i];
+ for_each_possible_cpu(cpu) {
+ struct crypto_shash *tfm;
+ struct shash_desc *shash;
+
+ shash = *per_cpu_ptr(algo->shashs, cpu);
+ kfree(shash);
+ tfm = *per_cpu_ptr(algo->tfms, cpu);
+ crypto_free_shash(tfm);
+ }
+ free_percpu(algo->tfms);
+ free_percpu(algo->shashs);
+ }
+}
+EXPORT_SYMBOL(seg6_hmac_exit);
+
+void __net_exit seg6_hmac_net_exit(struct net *net)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+
+ rhashtable_free_and_destroy(&sdata->hmac_infos, seg6_free_hi, NULL);
+}
+EXPORT_SYMBOL(seg6_hmac_net_exit);
diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c
new file mode 100644
index 000000000000..bbfca22c34ae
--- /dev/null
+++ b/net/ipv6/seg6_iptunnel.c
@@ -0,0 +1,431 @@
+/*
+ * SR-IPv6 implementation
+ *
+ * Author:
+ * David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/module.h>
+#include <net/ip.h>
+#include <net/lwtunnel.h>
+#include <net/netevent.h>
+#include <net/netns/generic.h>
+#include <net/ip6_fib.h>
+#include <net/route.h>
+#include <net/seg6.h>
+#include <linux/seg6.h>
+#include <linux/seg6_iptunnel.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+#ifdef CONFIG_DST_CACHE
+#include <net/dst_cache.h>
+#endif
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
+
+struct seg6_lwt {
+#ifdef CONFIG_DST_CACHE
+ struct dst_cache cache;
+#endif
+ struct seg6_iptunnel_encap tuninfo[0];
+};
+
+static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt)
+{
+ return (struct seg6_lwt *)lwt->data;
+}
+
+static inline struct seg6_iptunnel_encap *
+seg6_encap_lwtunnel(struct lwtunnel_state *lwt)
+{
+ return seg6_lwt_lwtunnel(lwt)->tuninfo;
+}
+
+static const struct nla_policy seg6_iptunnel_policy[SEG6_IPTUNNEL_MAX + 1] = {
+ [SEG6_IPTUNNEL_SRH] = { .type = NLA_BINARY },
+};
+
+int nla_put_srh(struct sk_buff *skb, int attrtype,
+ struct seg6_iptunnel_encap *tuninfo)
+{
+ struct seg6_iptunnel_encap *data;
+ struct nlattr *nla;
+ int len;
+
+ len = SEG6_IPTUN_ENCAP_SIZE(tuninfo);
+
+ nla = nla_reserve(skb, attrtype, len);
+ if (!nla)
+ return -EMSGSIZE;
+
+ data = nla_data(nla);
+ memcpy(data, tuninfo, len);
+
+ return 0;
+}
+
+static void set_tun_src(struct net *net, struct net_device *dev,
+ struct in6_addr *daddr, struct in6_addr *saddr)
+{
+ struct seg6_pernet_data *sdata = seg6_pernet(net);
+ struct in6_addr *tun_src;
+
+ rcu_read_lock();
+
+ tun_src = rcu_dereference(sdata->tun_src);
+
+ if (!ipv6_addr_any(tun_src)) {
+ memcpy(saddr, tun_src, sizeof(struct in6_addr));
+ } else {
+ ipv6_dev_get_saddr(net, dev, daddr, IPV6_PREFER_SRC_PUBLIC,
+ saddr);
+ }
+
+ rcu_read_unlock();
+}
+
+/* encapsulate an IPv6 packet within an outer IPv6 header with a given SRH */
+static int seg6_do_srh_encap(struct sk_buff *skb, struct ipv6_sr_hdr *osrh)
+{
+ struct net *net = dev_net(skb_dst(skb)->dev);
+ struct ipv6hdr *hdr, *inner_hdr;
+ struct ipv6_sr_hdr *isrh;
+ int hdrlen, tot_len, err;
+
+ hdrlen = (osrh->hdrlen + 1) << 3;
+ tot_len = hdrlen + sizeof(*hdr);
+
+ err = pskb_expand_head(skb, tot_len, 0, GFP_ATOMIC);
+ if (unlikely(err))
+ return err;
+
+ inner_hdr = ipv6_hdr(skb);
+
+ skb_push(skb, tot_len);
+ skb_reset_network_header(skb);
+ skb_mac_header_rebuild(skb);
+ hdr = ipv6_hdr(skb);
+
+ /* inherit tc, flowlabel and hlim
+ * hlim will be decremented in ip6_forward() afterwards and
+ * decapsulation will overwrite inner hlim with outer hlim
+ */
+ ip6_flow_hdr(hdr, ip6_tclass(ip6_flowinfo(inner_hdr)),
+ ip6_flowlabel(inner_hdr));
+ hdr->hop_limit = inner_hdr->hop_limit;
+ hdr->nexthdr = NEXTHDR_ROUTING;
+
+ isrh = (void *)hdr + sizeof(*hdr);
+ memcpy(isrh, osrh, hdrlen);
+
+ isrh->nexthdr = NEXTHDR_IPV6;
+
+ hdr->daddr = isrh->segments[isrh->first_segment];
+ set_tun_src(net, skb->dev, &hdr->daddr, &hdr->saddr);
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (sr_has_hmac(isrh)) {
+ err = seg6_push_hmac(net, &hdr->saddr, isrh);
+ if (unlikely(err))
+ return err;
+ }
+#endif
+
+ skb_postpush_rcsum(skb, hdr, tot_len);
+
+ return 0;
+}
+
+/* insert an SRH within an IPv6 packet, just after the IPv6 header */
+#ifdef CONFIG_IPV6_SEG6_INLINE
+static int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh)
+{
+ struct ipv6hdr *hdr, *oldhdr;
+ struct ipv6_sr_hdr *isrh;
+ int hdrlen, err;
+
+ hdrlen = (osrh->hdrlen + 1) << 3;
+
+ err = pskb_expand_head(skb, hdrlen, 0, GFP_ATOMIC);
+ if (unlikely(err))
+ return err;
+
+ oldhdr = ipv6_hdr(skb);
+
+ skb_pull(skb, sizeof(struct ipv6hdr));
+ skb_postpull_rcsum(skb, skb_network_header(skb),
+ sizeof(struct ipv6hdr));
+
+ skb_push(skb, sizeof(struct ipv6hdr) + hdrlen);
+ skb_reset_network_header(skb);
+ skb_mac_header_rebuild(skb);
+
+ hdr = ipv6_hdr(skb);
+
+ memmove(hdr, oldhdr, sizeof(*hdr));
+
+ isrh = (void *)hdr + sizeof(*hdr);
+ memcpy(isrh, osrh, hdrlen);
+
+ isrh->nexthdr = hdr->nexthdr;
+ hdr->nexthdr = NEXTHDR_ROUTING;
+
+ isrh->segments[0] = hdr->daddr;
+ hdr->daddr = isrh->segments[isrh->first_segment];
+
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (sr_has_hmac(isrh)) {
+ struct net *net = dev_net(skb_dst(skb)->dev);
+
+ err = seg6_push_hmac(net, &hdr->saddr, isrh);
+ if (unlikely(err))
+ return err;
+ }
+#endif
+
+ skb_postpush_rcsum(skb, hdr, sizeof(struct ipv6hdr) + hdrlen);
+
+ return 0;
+}
+#endif
+
+static int seg6_do_srh(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb_dst(skb);
+ struct seg6_iptunnel_encap *tinfo;
+ int err = 0;
+
+ tinfo = seg6_encap_lwtunnel(dst->lwtstate);
+
+ if (likely(!skb->encapsulation)) {
+ skb_reset_inner_headers(skb);
+ skb->encapsulation = 1;
+ }
+
+ switch (tinfo->mode) {
+#ifdef CONFIG_IPV6_SEG6_INLINE
+ case SEG6_IPTUN_MODE_INLINE:
+ err = seg6_do_srh_inline(skb, tinfo->srh);
+ skb_reset_inner_headers(skb);
+ break;
+#endif
+ case SEG6_IPTUN_MODE_ENCAP:
+ err = seg6_do_srh_encap(skb, tinfo->srh);
+ break;
+ }
+
+ if (err)
+ return err;
+
+ ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+ skb_set_transport_header(skb, sizeof(struct ipv6hdr));
+
+ skb_set_inner_protocol(skb, skb->protocol);
+
+ return 0;
+}
+
+int seg6_input(struct sk_buff *skb)
+{
+ int err;
+
+ err = seg6_do_srh(skb);
+ if (unlikely(err)) {
+ kfree_skb(skb);
+ return err;
+ }
+
+ skb_dst_drop(skb);
+ ip6_route_input(skb);
+
+ return dst_input(skb);
+}
+
+int seg6_output(struct net *net, struct sock *sk, struct sk_buff *skb)
+{
+ struct dst_entry *orig_dst = skb_dst(skb);
+ struct dst_entry *dst = NULL;
+ struct seg6_lwt *slwt;
+ int err = -EINVAL;
+
+ err = seg6_do_srh(skb);
+ if (unlikely(err))
+ goto drop;
+
+ slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate);
+
+#ifdef CONFIG_DST_CACHE
+ dst = dst_cache_get(&slwt->cache);
+#endif
+
+ if (unlikely(!dst)) {
+ struct ipv6hdr *hdr = ipv6_hdr(skb);
+ struct flowi6 fl6;
+
+ fl6.daddr = hdr->daddr;
+ fl6.saddr = hdr->saddr;
+ fl6.flowlabel = ip6_flowinfo(hdr);
+ fl6.flowi6_mark = skb->mark;
+ fl6.flowi6_proto = hdr->nexthdr;
+
+ dst = ip6_route_output(net, NULL, &fl6);
+ if (dst->error) {
+ err = dst->error;
+ dst_release(dst);
+ goto drop;
+ }
+
+#ifdef CONFIG_DST_CACHE
+ dst_cache_set_ip6(&slwt->cache, dst, &fl6.saddr);
+#endif
+ }
+
+ skb_dst_drop(skb);
+ skb_dst_set(skb, dst);
+
+ return dst_output(net, sk, skb);
+drop:
+ kfree_skb(skb);
+ return err;
+}
+
+static int seg6_build_state(struct net_device *dev, struct nlattr *nla,
+ unsigned int family, const void *cfg,
+ struct lwtunnel_state **ts)
+{
+ struct nlattr *tb[SEG6_IPTUNNEL_MAX + 1];
+ struct seg6_iptunnel_encap *tuninfo;
+ struct lwtunnel_state *newts;
+ int tuninfo_len, min_size;
+ struct seg6_lwt *slwt;
+ int err;
+
+ err = nla_parse_nested(tb, SEG6_IPTUNNEL_MAX, nla,
+ seg6_iptunnel_policy);
+
+ if (err < 0)
+ return err;
+
+ if (!tb[SEG6_IPTUNNEL_SRH])
+ return -EINVAL;
+
+ tuninfo = nla_data(tb[SEG6_IPTUNNEL_SRH]);
+ tuninfo_len = nla_len(tb[SEG6_IPTUNNEL_SRH]);
+
+ /* tuninfo must contain at least the iptunnel encap structure,
+ * the SRH and one segment
+ */
+ min_size = sizeof(*tuninfo) + sizeof(struct ipv6_sr_hdr) +
+ sizeof(struct in6_addr);
+ if (tuninfo_len < min_size)
+ return -EINVAL;
+
+ switch (tuninfo->mode) {
+#ifdef CONFIG_IPV6_SEG6_INLINE
+ case SEG6_IPTUN_MODE_INLINE:
+ break;
+#endif
+ case SEG6_IPTUN_MODE_ENCAP:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* verify that SRH is consistent */
+ if (!seg6_validate_srh(tuninfo->srh, tuninfo_len - sizeof(*tuninfo)))
+ return -EINVAL;
+
+ newts = lwtunnel_state_alloc(tuninfo_len + sizeof(*slwt));
+ if (!newts)
+ return -ENOMEM;
+
+ slwt = seg6_lwt_lwtunnel(newts);
+
+#ifdef CONFIG_DST_CACHE
+ err = dst_cache_init(&slwt->cache, GFP_KERNEL);
+ if (err) {
+ kfree(newts);
+ return err;
+ }
+#endif
+
+ memcpy(&slwt->tuninfo, tuninfo, tuninfo_len);
+
+ newts->type = LWTUNNEL_ENCAP_SEG6;
+ newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT |
+ LWTUNNEL_STATE_INPUT_REDIRECT;
+ newts->headroom = seg6_lwt_headroom(tuninfo);
+
+ *ts = newts;
+
+ return 0;
+}
+
+#ifdef CONFIG_DST_CACHE
+static void seg6_destroy_state(struct lwtunnel_state *lwt)
+{
+ dst_cache_destroy(&seg6_lwt_lwtunnel(lwt)->cache);
+}
+#endif
+
+static int seg6_fill_encap_info(struct sk_buff *skb,
+ struct lwtunnel_state *lwtstate)
+{
+ struct seg6_iptunnel_encap *tuninfo = seg6_encap_lwtunnel(lwtstate);
+
+ if (nla_put_srh(skb, SEG6_IPTUNNEL_SRH, tuninfo))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int seg6_encap_nlsize(struct lwtunnel_state *lwtstate)
+{
+ struct seg6_iptunnel_encap *tuninfo = seg6_encap_lwtunnel(lwtstate);
+
+ return nla_total_size(SEG6_IPTUN_ENCAP_SIZE(tuninfo));
+}
+
+static int seg6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
+{
+ struct seg6_iptunnel_encap *a_hdr = seg6_encap_lwtunnel(a);
+ struct seg6_iptunnel_encap *b_hdr = seg6_encap_lwtunnel(b);
+ int len = SEG6_IPTUN_ENCAP_SIZE(a_hdr);
+
+ if (len != SEG6_IPTUN_ENCAP_SIZE(b_hdr))
+ return 1;
+
+ return memcmp(a_hdr, b_hdr, len);
+}
+
+static const struct lwtunnel_encap_ops seg6_iptun_ops = {
+ .build_state = seg6_build_state,
+#ifdef CONFIG_DST_CACHE
+ .destroy_state = seg6_destroy_state,
+#endif
+ .output = seg6_output,
+ .input = seg6_input,
+ .fill_encap = seg6_fill_encap_info,
+ .get_encap_size = seg6_encap_nlsize,
+ .cmp_encap = seg6_encap_cmp,
+};
+
+int __init seg6_iptunnel_init(void)
+{
+ return lwtunnel_encap_add_ops(&seg6_iptun_ops, LWTUNNEL_ENCAP_SEG6);
+}
+
+void seg6_iptunnel_exit(void)
+{
+ lwtunnel_encap_del_ops(&seg6_iptun_ops, LWTUNNEL_ENCAP_SEG6);
+}