diff options
author | David Lebrun <david.lebrun@uclouvain.be> | 2016-11-08 08:57:42 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-11-09 20:40:06 -0500 |
commit | bf355b8d2c30a289232042cacc1cfaea4923936c (patch) | |
tree | e7f1a5472ac6ac4c5b6c46ff4fe54d9bb9c4ab0f | |
parent | 6c8702c60b88651072460f3f4026c7dfe2521d12 (diff) |
ipv6: sr: add core files for SR HMAC support
This patch adds the necessary functions to compute and check the HMAC signature
of an SR-enabled packet. Two HMAC algorithms are supported: hmac(sha1) and
hmac(sha256).
In order to avoid dynamic memory allocation for each HMAC computation,
a per-cpu ring buffer is allocated for this purpose.
A new per-interface sysctl called seg6_require_hmac is added, allowing a
user-defined policy for processing HMAC-signed SR-enabled packets.
A value of -1 means that the HMAC field will always be ignored.
A value of 0 means that if an HMAC field is present, its validity will
be enforced (the packet is dropped is the signature is incorrect).
Finally, a value of 1 means that any SR-enabled packet that does not
contain an HMAC signature or whose signature is incorrect will be dropped.
Signed-off-by: David Lebrun <david.lebrun@uclouvain.be>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/ipv6.h | 3 | ||||
-rw-r--r-- | include/linux/seg6_hmac.h | 6 | ||||
-rw-r--r-- | include/net/seg6.h | 4 | ||||
-rw-r--r-- | include/net/seg6_hmac.h | 62 | ||||
-rw-r--r-- | include/uapi/linux/ipv6.h | 1 | ||||
-rw-r--r-- | include/uapi/linux/seg6_hmac.h | 21 | ||||
-rw-r--r-- | net/ipv6/Kconfig | 12 | ||||
-rw-r--r-- | net/ipv6/Makefile | 1 | ||||
-rw-r--r-- | net/ipv6/addrconf.c | 18 | ||||
-rw-r--r-- | net/ipv6/seg6_hmac.c | 484 |
10 files changed, 612 insertions, 0 deletions
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h index 68d3f71f0abf..93756585521f 100644 --- a/include/linux/ipv6.h +++ b/include/linux/ipv6.h | |||
@@ -65,6 +65,9 @@ struct ipv6_devconf { | |||
65 | __s32 use_oif_addrs_only; | 65 | __s32 use_oif_addrs_only; |
66 | __s32 keep_addr_on_down; | 66 | __s32 keep_addr_on_down; |
67 | __s32 seg6_enabled; | 67 | __s32 seg6_enabled; |
68 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
69 | __s32 seg6_require_hmac; | ||
70 | #endif | ||
68 | 71 | ||
69 | struct ctl_table_header *sysctl_header; | 72 | struct ctl_table_header *sysctl_header; |
70 | }; | 73 | }; |
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 @@ | |||
1 | #ifndef _LINUX_SEG6_HMAC_H | ||
2 | #define _LINUX_SEG6_HMAC_H | ||
3 | |||
4 | #include <uapi/linux/seg6_hmac.h> | ||
5 | |||
6 | #endif | ||
diff --git a/include/net/seg6.h b/include/net/seg6.h index ff5da0ce83e9..4e0357517d79 100644 --- a/include/net/seg6.h +++ b/include/net/seg6.h | |||
@@ -18,6 +18,7 @@ | |||
18 | #include <linux/ipv6.h> | 18 | #include <linux/ipv6.h> |
19 | #include <net/lwtunnel.h> | 19 | #include <net/lwtunnel.h> |
20 | #include <linux/seg6.h> | 20 | #include <linux/seg6.h> |
21 | #include <linux/rhashtable.h> | ||
21 | 22 | ||
22 | static inline void update_csum_diff4(struct sk_buff *skb, __be32 from, | 23 | static inline void update_csum_diff4(struct sk_buff *skb, __be32 from, |
23 | __be32 to) | 24 | __be32 to) |
@@ -41,6 +42,9 @@ static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from, | |||
41 | struct seg6_pernet_data { | 42 | struct seg6_pernet_data { |
42 | struct mutex lock; | 43 | struct mutex lock; |
43 | struct in6_addr __rcu *tun_src; | 44 | struct in6_addr __rcu *tun_src; |
45 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
46 | struct rhashtable hmac_infos; | ||
47 | #endif | ||
44 | }; | 48 | }; |
45 | 49 | ||
46 | static inline struct seg6_pernet_data *seg6_pernet(struct net *net) | 50 | static inline struct seg6_pernet_data *seg6_pernet(struct net *net) |
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 @@ | |||
1 | /* | ||
2 | * SR-IPv6 implementation | ||
3 | * | ||
4 | * Author: | ||
5 | * David Lebrun <david.lebrun@uclouvain.be> | ||
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_HMAC_H | ||
15 | #define _NET_SEG6_HMAC_H | ||
16 | |||
17 | #include <net/flow.h> | ||
18 | #include <net/ip6_fib.h> | ||
19 | #include <net/sock.h> | ||
20 | #include <linux/ip.h> | ||
21 | #include <linux/ipv6.h> | ||
22 | #include <linux/route.h> | ||
23 | #include <net/seg6.h> | ||
24 | #include <linux/seg6_hmac.h> | ||
25 | #include <linux/rhashtable.h> | ||
26 | |||
27 | #define SEG6_HMAC_MAX_DIGESTSIZE 160 | ||
28 | #define SEG6_HMAC_RING_SIZE 256 | ||
29 | |||
30 | struct seg6_hmac_info { | ||
31 | struct rhash_head node; | ||
32 | struct rcu_head rcu; | ||
33 | |||
34 | u32 hmackeyid; | ||
35 | char secret[SEG6_HMAC_SECRET_LEN]; | ||
36 | u8 slen; | ||
37 | u8 alg_id; | ||
38 | }; | ||
39 | |||
40 | struct seg6_hmac_algo { | ||
41 | u8 alg_id; | ||
42 | char name[64]; | ||
43 | struct crypto_shash * __percpu *tfms; | ||
44 | struct shash_desc * __percpu *shashs; | ||
45 | }; | ||
46 | |||
47 | extern int seg6_hmac_compute(struct seg6_hmac_info *hinfo, | ||
48 | struct ipv6_sr_hdr *hdr, struct in6_addr *saddr, | ||
49 | u8 *output); | ||
50 | extern struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key); | ||
51 | extern int seg6_hmac_info_add(struct net *net, u32 key, | ||
52 | struct seg6_hmac_info *hinfo); | ||
53 | extern int seg6_hmac_info_del(struct net *net, u32 key); | ||
54 | extern int seg6_push_hmac(struct net *net, struct in6_addr *saddr, | ||
55 | struct ipv6_sr_hdr *srh); | ||
56 | extern bool seg6_hmac_validate_skb(struct sk_buff *skb); | ||
57 | extern int seg6_hmac_init(void); | ||
58 | extern void seg6_hmac_exit(void); | ||
59 | extern int seg6_hmac_net_init(struct net *net); | ||
60 | extern void seg6_hmac_net_exit(struct net *net); | ||
61 | |||
62 | #endif | ||
diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h index 7ff1d654e333..53561be1ac21 100644 --- a/include/uapi/linux/ipv6.h +++ b/include/uapi/linux/ipv6.h | |||
@@ -180,6 +180,7 @@ enum { | |||
180 | DEVCONF_KEEP_ADDR_ON_DOWN, | 180 | DEVCONF_KEEP_ADDR_ON_DOWN, |
181 | DEVCONF_RTR_SOLICIT_MAX_INTERVAL, | 181 | DEVCONF_RTR_SOLICIT_MAX_INTERVAL, |
182 | DEVCONF_SEG6_ENABLED, | 182 | DEVCONF_SEG6_ENABLED, |
183 | DEVCONF_SEG6_REQUIRE_HMAC, | ||
183 | DEVCONF_MAX | 184 | DEVCONF_MAX |
184 | }; | 185 | }; |
185 | 186 | ||
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 @@ | |||
1 | #ifndef _UAPI_LINUX_SEG6_HMAC_H | ||
2 | #define _UAPI_LINUX_SEG6_HMAC_H | ||
3 | |||
4 | #include <linux/seg6.h> | ||
5 | |||
6 | #define SEG6_HMAC_SECRET_LEN 64 | ||
7 | #define SEG6_HMAC_FIELD_LEN 32 | ||
8 | |||
9 | struct sr6_tlv_hmac { | ||
10 | struct sr6_tlv tlvhdr; | ||
11 | __u16 reserved; | ||
12 | __be32 hmackeyid; | ||
13 | __u8 hmac[SEG6_HMAC_FIELD_LEN]; | ||
14 | }; | ||
15 | |||
16 | enum { | ||
17 | SEG6_HMAC_ALGO_SHA1 = 1, | ||
18 | SEG6_HMAC_ALGO_SHA256 = 2, | ||
19 | }; | ||
20 | |||
21 | #endif | ||
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig index 1123a001d729..0f00811a785f 100644 --- a/net/ipv6/Kconfig +++ b/net/ipv6/Kconfig | |||
@@ -301,4 +301,16 @@ config IPV6_SEG6_INLINE | |||
301 | 301 | ||
302 | If unsure, say N. | 302 | If unsure, say N. |
303 | 303 | ||
304 | config IPV6_SEG6_HMAC | ||
305 | bool "IPv6: Segment Routing HMAC support" | ||
306 | depends on IPV6 | ||
307 | select CRYPTO_HMAC | ||
308 | select CRYPTO_SHA1 | ||
309 | select CRYPTO_SHA256 | ||
310 | ---help--- | ||
311 | Support for HMAC signature generation and verification | ||
312 | of SR-enabled packets. | ||
313 | |||
314 | If unsure, say N. | ||
315 | |||
304 | endif # IPV6 | 316 | endif # IPV6 |
diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 59ee92fb3689..129cad2ba960 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile | |||
@@ -44,6 +44,7 @@ obj-$(CONFIG_IPV6_SIT) += sit.o | |||
44 | obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o | 44 | obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o |
45 | obj-$(CONFIG_IPV6_GRE) += ip6_gre.o | 45 | obj-$(CONFIG_IPV6_GRE) += ip6_gre.o |
46 | obj-$(CONFIG_IPV6_FOU) += fou6.o | 46 | obj-$(CONFIG_IPV6_FOU) += fou6.o |
47 | obj-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o | ||
47 | 48 | ||
48 | obj-y += addrconf_core.o exthdrs_core.o ip6_checksum.o ip6_icmp.o | 49 | obj-y += addrconf_core.o exthdrs_core.o ip6_checksum.o ip6_icmp.o |
49 | obj-$(CONFIG_INET) += output_core.o protocol.o $(ipv6-offload) | 50 | obj-$(CONFIG_INET) += output_core.o protocol.o $(ipv6-offload) |
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 2ac6cb460af0..86219c0a0104 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c | |||
@@ -239,6 +239,9 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = { | |||
239 | .ignore_routes_with_linkdown = 0, | 239 | .ignore_routes_with_linkdown = 0, |
240 | .keep_addr_on_down = 0, | 240 | .keep_addr_on_down = 0, |
241 | .seg6_enabled = 0, | 241 | .seg6_enabled = 0, |
242 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
243 | .seg6_require_hmac = 0, | ||
244 | #endif | ||
242 | }; | 245 | }; |
243 | 246 | ||
244 | static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { | 247 | static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { |
@@ -286,6 +289,9 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { | |||
286 | .ignore_routes_with_linkdown = 0, | 289 | .ignore_routes_with_linkdown = 0, |
287 | .keep_addr_on_down = 0, | 290 | .keep_addr_on_down = 0, |
288 | .seg6_enabled = 0, | 291 | .seg6_enabled = 0, |
292 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
293 | .seg6_require_hmac = 0, | ||
294 | #endif | ||
289 | }; | 295 | }; |
290 | 296 | ||
291 | /* Check if a valid qdisc is available */ | 297 | /* Check if a valid qdisc is available */ |
@@ -4947,6 +4953,9 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf, | |||
4947 | array[DEVCONF_DROP_UNSOLICITED_NA] = cnf->drop_unsolicited_na; | 4953 | array[DEVCONF_DROP_UNSOLICITED_NA] = cnf->drop_unsolicited_na; |
4948 | array[DEVCONF_KEEP_ADDR_ON_DOWN] = cnf->keep_addr_on_down; | 4954 | array[DEVCONF_KEEP_ADDR_ON_DOWN] = cnf->keep_addr_on_down; |
4949 | array[DEVCONF_SEG6_ENABLED] = cnf->seg6_enabled; | 4955 | array[DEVCONF_SEG6_ENABLED] = cnf->seg6_enabled; |
4956 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
4957 | array[DEVCONF_SEG6_REQUIRE_HMAC] = cnf->seg6_require_hmac; | ||
4958 | #endif | ||
4950 | } | 4959 | } |
4951 | 4960 | ||
4952 | static inline size_t inet6_ifla6_size(void) | 4961 | static inline size_t inet6_ifla6_size(void) |
@@ -6045,6 +6054,15 @@ static const struct ctl_table addrconf_sysctl[] = { | |||
6045 | .mode = 0644, | 6054 | .mode = 0644, |
6046 | .proc_handler = proc_dointvec, | 6055 | .proc_handler = proc_dointvec, |
6047 | }, | 6056 | }, |
6057 | #ifdef CONFIG_IPV6_SEG6_HMAC | ||
6058 | { | ||
6059 | .procname = "seg6_require_hmac", | ||
6060 | .data = &ipv6_devconf.seg6_require_hmac, | ||
6061 | .maxlen = sizeof(int), | ||
6062 | .mode = 0644, | ||
6063 | .proc_handler = proc_dointvec, | ||
6064 | }, | ||
6065 | #endif | ||
6048 | { | 6066 | { |
6049 | /* sentinel */ | 6067 | /* sentinel */ |
6050 | } | 6068 | } |
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 @@ | |||
1 | /* | ||
2 | * SR-IPv6 implementation -- HMAC functions | ||
3 | * | ||
4 | * Author: | ||
5 | * David Lebrun <david.lebrun@uclouvain.be> | ||
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 | #include <linux/errno.h> | ||
15 | #include <linux/types.h> | ||
16 | #include <linux/socket.h> | ||
17 | #include <linux/sockios.h> | ||
18 | #include <linux/net.h> | ||
19 | #include <linux/netdevice.h> | ||
20 | #include <linux/in6.h> | ||
21 | #include <linux/icmpv6.h> | ||
22 | #include <linux/mroute6.h> | ||
23 | #include <linux/slab.h> | ||
24 | |||
25 | #include <linux/netfilter.h> | ||
26 | #include <linux/netfilter_ipv6.h> | ||
27 | |||
28 | #include <net/sock.h> | ||
29 | #include <net/snmp.h> | ||
30 | |||
31 | #include <net/ipv6.h> | ||
32 | #include <net/protocol.h> | ||
33 | #include <net/transp_v6.h> | ||
34 | #include <net/rawv6.h> | ||
35 | #include <net/ndisc.h> | ||
36 | #include <net/ip6_route.h> | ||
37 | #include <net/addrconf.h> | ||
38 | #include <net/xfrm.h> | ||
39 | |||
40 | #include <linux/cryptohash.h> | ||
41 | #include <crypto/hash.h> | ||
42 | #include <crypto/sha.h> | ||
43 | #include <net/seg6.h> | ||
44 | #include <net/genetlink.h> | ||
45 | #include <net/seg6_hmac.h> | ||
46 | #include <linux/random.h> | ||
47 | |||
48 | static char * __percpu *hmac_ring; | ||
49 | |||
50 | static int seg6_hmac_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) | ||
51 | { | ||
52 | const struct seg6_hmac_info *hinfo = obj; | ||
53 | |||
54 | return (hinfo->hmackeyid != *(__u32 *)arg->key); | ||
55 | } | ||
56 | |||
57 | static inline void seg6_hinfo_release(struct seg6_hmac_info *hinfo) | ||
58 | { | ||
59 | kfree_rcu(hinfo, rcu); | ||
60 | } | ||
61 | |||
62 | static void seg6_free_hi(void *ptr, void *arg) | ||
63 | { | ||
64 | struct seg6_hmac_info *hinfo = (struct seg6_hmac_info *)ptr; | ||
65 | |||
66 | if (hinfo) | ||
67 | seg6_hinfo_release(hinfo); | ||
68 | } | ||
69 | |||
70 | static const struct rhashtable_params rht_params = { | ||
71 | .head_offset = offsetof(struct seg6_hmac_info, node), | ||
72 | .key_offset = offsetof(struct seg6_hmac_info, hmackeyid), | ||
73 | .key_len = sizeof(u32), | ||
74 | .automatic_shrinking = true, | ||
75 | .obj_cmpfn = seg6_hmac_cmpfn, | ||
76 | }; | ||
77 | |||
78 | static struct seg6_hmac_algo hmac_algos[] = { | ||
79 | { | ||
80 | .alg_id = SEG6_HMAC_ALGO_SHA1, | ||
81 | .name = "hmac(sha1)", | ||
82 | }, | ||
83 | { | ||
84 | .alg_id = SEG6_HMAC_ALGO_SHA256, | ||
85 | .name = "hmac(sha256)", | ||
86 | }, | ||
87 | }; | ||
88 | |||
89 | static struct sr6_tlv_hmac *seg6_get_tlv_hmac(struct ipv6_sr_hdr *srh) | ||
90 | { | ||
91 | struct sr6_tlv_hmac *tlv; | ||
92 | |||
93 | if (srh->hdrlen < (srh->first_segment + 1) * 2 + 5) | ||
94 | return NULL; | ||
95 | |||
96 | if (!sr_has_hmac(srh)) | ||
97 | return NULL; | ||
98 | |||
99 | tlv = (struct sr6_tlv_hmac *) | ||
100 | ((char *)srh + ((srh->hdrlen + 1) << 3) - 40); | ||
101 | |||
102 | if (tlv->tlvhdr.type != SR6_TLV_HMAC || tlv->tlvhdr.len != 38) | ||
103 | return NULL; | ||
104 | |||
105 | return tlv; | ||
106 | } | ||
107 | |||
108 | static struct seg6_hmac_algo *__hmac_get_algo(u8 alg_id) | ||
109 | { | ||
110 | struct seg6_hmac_algo *algo; | ||
111 | int i, alg_count; | ||
112 | |||
113 | alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); | ||
114 | for (i = 0; i < alg_count; i++) { | ||
115 | algo = &hmac_algos[i]; | ||
116 | if (algo->alg_id == alg_id) | ||
117 | return algo; | ||
118 | } | ||
119 | |||
120 | return NULL; | ||
121 | } | ||
122 | |||
123 | static int __do_hmac(struct seg6_hmac_info *hinfo, const char *text, u8 psize, | ||
124 | u8 *output, int outlen) | ||
125 | { | ||
126 | struct seg6_hmac_algo *algo; | ||
127 | struct crypto_shash *tfm; | ||
128 | struct shash_desc *shash; | ||
129 | int ret, dgsize; | ||
130 | |||
131 | algo = __hmac_get_algo(hinfo->alg_id); | ||
132 | if (!algo) | ||
133 | return -ENOENT; | ||
134 | |||
135 | tfm = *this_cpu_ptr(algo->tfms); | ||
136 | |||
137 | dgsize = crypto_shash_digestsize(tfm); | ||
138 | if (dgsize > outlen) { | ||
139 | pr_debug("sr-ipv6: __do_hmac: digest size too big (%d / %d)\n", | ||
140 | dgsize, outlen); | ||
141 | return -ENOMEM; | ||
142 | } | ||
143 | |||
144 | ret = crypto_shash_setkey(tfm, hinfo->secret, hinfo->slen); | ||
145 | if (ret < 0) { | ||
146 | pr_debug("sr-ipv6: crypto_shash_setkey failed: err %d\n", ret); | ||
147 | goto failed; | ||
148 | } | ||
149 | |||
150 | shash = *this_cpu_ptr(algo->shashs); | ||
151 | shash->tfm = tfm; | ||
152 | |||
153 | ret = crypto_shash_digest(shash, text, psize, output); | ||
154 | if (ret < 0) { | ||
155 | pr_debug("sr-ipv6: crypto_shash_digest failed: err %d\n", ret); | ||
156 | goto failed; | ||
157 | } | ||
158 | |||
159 | return dgsize; | ||
160 | |||
161 | failed: | ||
162 | return ret; | ||
163 | } | ||
164 | |||
165 | int seg6_hmac_compute(struct seg6_hmac_info *hinfo, struct ipv6_sr_hdr *hdr, | ||
166 | struct in6_addr *saddr, u8 *output) | ||
167 | { | ||
168 | __be32 hmackeyid = cpu_to_be32(hinfo->hmackeyid); | ||
169 | u8 tmp_out[SEG6_HMAC_MAX_DIGESTSIZE]; | ||
170 | int plen, i, dgsize, wrsize; | ||
171 | char *ring, *off; | ||
172 | |||
173 | /* a 160-byte buffer for digest output allows to store highest known | ||
174 | * hash function (RadioGatun) with up to 1216 bits | ||
175 | */ | ||
176 | |||
177 | /* saddr(16) + first_seg(1) + cleanup(1) + keyid(4) + seglist(16n) */ | ||
178 | plen = 16 + 1 + 1 + 4 + (hdr->first_segment + 1) * 16; | ||
179 | |||
180 | /* this limit allows for 14 segments */ | ||
181 | if (plen >= SEG6_HMAC_RING_SIZE) | ||
182 | return -EMSGSIZE; | ||
183 | |||
184 | /* Let's build the HMAC text on the ring buffer. The text is composed | ||
185 | * as follows, in order: | ||
186 | * | ||
187 | * 1. Source IPv6 address (128 bits) | ||
188 | * 2. first_segment value (8 bits) | ||
189 | * 3. cleanup flag (8 bits: highest bit is cleanup value, others are 0) | ||
190 | * 4. HMAC Key ID (32 bits) | ||
191 | * 5. All segments in the segments list (n * 128 bits) | ||
192 | */ | ||
193 | |||
194 | local_bh_disable(); | ||
195 | ring = *this_cpu_ptr(hmac_ring); | ||
196 | off = ring; | ||
197 | |||
198 | /* source address */ | ||
199 | memcpy(off, saddr, 16); | ||
200 | off += 16; | ||
201 | |||
202 | /* first_segment value */ | ||
203 | *off++ = hdr->first_segment; | ||
204 | |||
205 | /* cleanup flag */ | ||
206 | *off++ = !!(sr_has_cleanup(hdr)) << 7; | ||
207 | |||
208 | /* HMAC Key ID */ | ||
209 | memcpy(off, &hmackeyid, 4); | ||
210 | off += 4; | ||
211 | |||
212 | /* all segments in the list */ | ||
213 | for (i = 0; i < hdr->first_segment + 1; i++) { | ||
214 | memcpy(off, hdr->segments + i, 16); | ||
215 | off += 16; | ||
216 | } | ||
217 | |||
218 | dgsize = __do_hmac(hinfo, ring, plen, tmp_out, | ||
219 | SEG6_HMAC_MAX_DIGESTSIZE); | ||
220 | local_bh_enable(); | ||
221 | |||
222 | if (dgsize < 0) | ||
223 | return dgsize; | ||
224 | |||
225 | wrsize = SEG6_HMAC_FIELD_LEN; | ||
226 | if (wrsize > dgsize) | ||
227 | wrsize = dgsize; | ||
228 | |||
229 | memset(output, 0, SEG6_HMAC_FIELD_LEN); | ||
230 | memcpy(output, tmp_out, wrsize); | ||
231 | |||
232 | return 0; | ||
233 | } | ||
234 | EXPORT_SYMBOL(seg6_hmac_compute); | ||
235 | |||
236 | /* checks if an incoming SR-enabled packet's HMAC status matches | ||
237 | * the incoming policy. | ||
238 | * | ||
239 | * called with rcu_read_lock() | ||
240 | */ | ||
241 | bool seg6_hmac_validate_skb(struct sk_buff *skb) | ||
242 | { | ||
243 | u8 hmac_output[SEG6_HMAC_FIELD_LEN]; | ||
244 | struct net *net = dev_net(skb->dev); | ||
245 | struct seg6_hmac_info *hinfo; | ||
246 | struct sr6_tlv_hmac *tlv; | ||
247 | struct ipv6_sr_hdr *srh; | ||
248 | struct inet6_dev *idev; | ||
249 | |||
250 | idev = __in6_dev_get(skb->dev); | ||
251 | |||
252 | srh = (struct ipv6_sr_hdr *)skb_transport_header(skb); | ||
253 | |||
254 | tlv = seg6_get_tlv_hmac(srh); | ||
255 | |||
256 | /* mandatory check but no tlv */ | ||
257 | if (idev->cnf.seg6_require_hmac > 0 && !tlv) | ||
258 | return false; | ||
259 | |||
260 | /* no check */ | ||
261 | if (idev->cnf.seg6_require_hmac < 0) | ||
262 | return true; | ||
263 | |||
264 | /* check only if present */ | ||
265 | if (idev->cnf.seg6_require_hmac == 0 && !tlv) | ||
266 | return true; | ||
267 | |||
268 | /* now, seg6_require_hmac >= 0 && tlv */ | ||
269 | |||
270 | hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); | ||
271 | if (!hinfo) | ||
272 | return false; | ||
273 | |||
274 | if (seg6_hmac_compute(hinfo, srh, &ipv6_hdr(skb)->saddr, hmac_output)) | ||
275 | return false; | ||
276 | |||
277 | if (memcmp(hmac_output, tlv->hmac, SEG6_HMAC_FIELD_LEN) != 0) | ||
278 | return false; | ||
279 | |||
280 | return true; | ||
281 | } | ||
282 | EXPORT_SYMBOL(seg6_hmac_validate_skb); | ||
283 | |||
284 | /* called with rcu_read_lock() */ | ||
285 | struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key) | ||
286 | { | ||
287 | struct seg6_pernet_data *sdata = seg6_pernet(net); | ||
288 | struct seg6_hmac_info *hinfo; | ||
289 | |||
290 | hinfo = rhashtable_lookup_fast(&sdata->hmac_infos, &key, rht_params); | ||
291 | |||
292 | return hinfo; | ||
293 | } | ||
294 | EXPORT_SYMBOL(seg6_hmac_info_lookup); | ||
295 | |||
296 | int seg6_hmac_info_add(struct net *net, u32 key, struct seg6_hmac_info *hinfo) | ||
297 | { | ||
298 | struct seg6_pernet_data *sdata = seg6_pernet(net); | ||
299 | int err; | ||
300 | |||
301 | err = rhashtable_lookup_insert_fast(&sdata->hmac_infos, &hinfo->node, | ||
302 | rht_params); | ||
303 | |||
304 | return err; | ||
305 | } | ||
306 | EXPORT_SYMBOL(seg6_hmac_info_add); | ||
307 | |||
308 | int seg6_hmac_info_del(struct net *net, u32 key) | ||
309 | { | ||
310 | struct seg6_pernet_data *sdata = seg6_pernet(net); | ||
311 | struct seg6_hmac_info *hinfo; | ||
312 | int err = -ENOENT; | ||
313 | |||
314 | hinfo = rhashtable_lookup_fast(&sdata->hmac_infos, &key, rht_params); | ||
315 | if (!hinfo) | ||
316 | goto out; | ||
317 | |||
318 | err = rhashtable_remove_fast(&sdata->hmac_infos, &hinfo->node, | ||
319 | rht_params); | ||
320 | if (err) | ||
321 | goto out; | ||
322 | |||
323 | seg6_hinfo_release(hinfo); | ||
324 | |||
325 | out: | ||
326 | return err; | ||
327 | } | ||
328 | EXPORT_SYMBOL(seg6_hmac_info_del); | ||
329 | |||
330 | int seg6_push_hmac(struct net *net, struct in6_addr *saddr, | ||
331 | struct ipv6_sr_hdr *srh) | ||
332 | { | ||
333 | struct seg6_hmac_info *hinfo; | ||
334 | struct sr6_tlv_hmac *tlv; | ||
335 | int err = -ENOENT; | ||
336 | |||
337 | tlv = seg6_get_tlv_hmac(srh); | ||
338 | if (!tlv) | ||
339 | return -EINVAL; | ||
340 | |||
341 | rcu_read_lock(); | ||
342 | |||
343 | hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); | ||
344 | if (!hinfo) | ||
345 | goto out; | ||
346 | |||
347 | memset(tlv->hmac, 0, SEG6_HMAC_FIELD_LEN); | ||
348 | err = seg6_hmac_compute(hinfo, srh, saddr, tlv->hmac); | ||
349 | |||
350 | out: | ||
351 | rcu_read_unlock(); | ||
352 | return err; | ||
353 | } | ||
354 | EXPORT_SYMBOL(seg6_push_hmac); | ||
355 | |||
356 | static int seg6_hmac_init_ring(void) | ||
357 | { | ||
358 | int i; | ||
359 | |||
360 | hmac_ring = alloc_percpu(char *); | ||
361 | |||
362 | if (!hmac_ring) | ||
363 | return -ENOMEM; | ||
364 | |||
365 | for_each_possible_cpu(i) { | ||
366 | char *ring = kzalloc(SEG6_HMAC_RING_SIZE, GFP_KERNEL); | ||
367 | |||
368 | if (!ring) | ||
369 | return -ENOMEM; | ||
370 | |||
371 | *per_cpu_ptr(hmac_ring, i) = ring; | ||
372 | } | ||
373 | |||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | static int seg6_hmac_init_algo(void) | ||
378 | { | ||
379 | struct seg6_hmac_algo *algo; | ||
380 | struct crypto_shash *tfm; | ||
381 | struct shash_desc *shash; | ||
382 | int i, alg_count, cpu; | ||
383 | |||
384 | alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); | ||
385 | |||
386 | for (i = 0; i < alg_count; i++) { | ||
387 | struct crypto_shash **p_tfm; | ||
388 | int shsize; | ||
389 | |||
390 | algo = &hmac_algos[i]; | ||
391 | algo->tfms = alloc_percpu(struct crypto_shash *); | ||
392 | if (!algo->tfms) | ||
393 | return -ENOMEM; | ||
394 | |||
395 | for_each_possible_cpu(cpu) { | ||
396 | tfm = crypto_alloc_shash(algo->name, 0, GFP_KERNEL); | ||
397 | if (IS_ERR(tfm)) | ||
398 | return PTR_ERR(tfm); | ||
399 | p_tfm = per_cpu_ptr(algo->tfms, cpu); | ||
400 | *p_tfm = tfm; | ||
401 | } | ||
402 | |||
403 | p_tfm = this_cpu_ptr(algo->tfms); | ||
404 | tfm = *p_tfm; | ||
405 | |||
406 | shsize = sizeof(*shash) + crypto_shash_descsize(tfm); | ||
407 | |||
408 | algo->shashs = alloc_percpu(struct shash_desc *); | ||
409 | if (!algo->shashs) | ||
410 | return -ENOMEM; | ||
411 | |||
412 | for_each_possible_cpu(cpu) { | ||
413 | shash = kzalloc(shsize, GFP_KERNEL); | ||
414 | if (!shash) | ||
415 | return -ENOMEM; | ||
416 | *per_cpu_ptr(algo->shashs, cpu) = shash; | ||
417 | } | ||
418 | } | ||
419 | |||
420 | return 0; | ||
421 | } | ||
422 | |||
423 | int __init seg6_hmac_init(void) | ||
424 | { | ||
425 | int ret; | ||
426 | |||
427 | ret = seg6_hmac_init_ring(); | ||
428 | if (ret < 0) | ||
429 | goto out; | ||
430 | |||
431 | ret = seg6_hmac_init_algo(); | ||
432 | |||
433 | out: | ||
434 | return ret; | ||
435 | } | ||
436 | EXPORT_SYMBOL(seg6_hmac_init); | ||
437 | |||
438 | int __net_init seg6_hmac_net_init(struct net *net) | ||
439 | { | ||
440 | struct seg6_pernet_data *sdata = seg6_pernet(net); | ||
441 | |||
442 | rhashtable_init(&sdata->hmac_infos, &rht_params); | ||
443 | |||
444 | return 0; | ||
445 | } | ||
446 | EXPORT_SYMBOL(seg6_hmac_net_init); | ||
447 | |||
448 | void seg6_hmac_exit(void) | ||
449 | { | ||
450 | struct seg6_hmac_algo *algo = NULL; | ||
451 | int i, alg_count, cpu; | ||
452 | |||
453 | for_each_possible_cpu(i) { | ||
454 | char *ring = *per_cpu_ptr(hmac_ring, i); | ||
455 | |||
456 | kfree(ring); | ||
457 | } | ||
458 | free_percpu(hmac_ring); | ||
459 | |||
460 | alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); | ||
461 | for (i = 0; i < alg_count; i++) { | ||
462 | algo = &hmac_algos[i]; | ||
463 | for_each_possible_cpu(cpu) { | ||
464 | struct crypto_shash *tfm; | ||
465 | struct shash_desc *shash; | ||
466 | |||
467 | shash = *per_cpu_ptr(algo->shashs, cpu); | ||
468 | kfree(shash); | ||
469 | tfm = *per_cpu_ptr(algo->tfms, cpu); | ||
470 | crypto_free_shash(tfm); | ||
471 | } | ||
472 | free_percpu(algo->tfms); | ||
473 | free_percpu(algo->shashs); | ||
474 | } | ||
475 | } | ||
476 | EXPORT_SYMBOL(seg6_hmac_exit); | ||
477 | |||
478 | void __net_exit seg6_hmac_net_exit(struct net *net) | ||
479 | { | ||
480 | struct seg6_pernet_data *sdata = seg6_pernet(net); | ||
481 | |||
482 | rhashtable_free_and_destroy(&sdata->hmac_infos, seg6_free_hi, NULL); | ||
483 | } | ||
484 | EXPORT_SYMBOL(seg6_hmac_net_exit); | ||