| rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 | 
|  | 2 | /* | 
|  | 3 | * xfrm6_policy.c: based on xfrm4_policy.c | 
|  | 4 | * | 
|  | 5 | * Authors: | 
|  | 6 | *	Mitsuru KANDA @USAGI | 
|  | 7 | *	Kazunori MIYAZAWA @USAGI | 
|  | 8 | *	Kunihiro Ishiguro <kunihiro@ipinfusion.com> | 
|  | 9 | *		IPv6 support | 
|  | 10 | *	YOSHIFUJI Hideaki | 
|  | 11 | *		Split up af-specific portion | 
|  | 12 | * | 
|  | 13 | */ | 
|  | 14 |  | 
|  | 15 | #include <linux/err.h> | 
|  | 16 | #include <linux/kernel.h> | 
|  | 17 | #include <linux/netdevice.h> | 
|  | 18 | #include <net/addrconf.h> | 
|  | 19 | #include <net/dst.h> | 
|  | 20 | #include <net/xfrm.h> | 
|  | 21 | #include <net/ip.h> | 
|  | 22 | #include <net/ipv6.h> | 
|  | 23 | #include <net/ip6_route.h> | 
|  | 24 | #include <net/l3mdev.h> | 
|  | 25 | #if IS_ENABLED(CONFIG_IPV6_MIP6) | 
|  | 26 | #include <net/mip6.h> | 
|  | 27 | #endif | 
|  | 28 |  | 
|  | 29 | static struct dst_entry *xfrm6_dst_lookup(struct net *net, int tos, int oif, | 
|  | 30 | const xfrm_address_t *saddr, | 
|  | 31 | const xfrm_address_t *daddr, | 
|  | 32 | u32 mark) | 
|  | 33 | { | 
|  | 34 | struct flowi6 fl6; | 
|  | 35 | struct dst_entry *dst; | 
|  | 36 | int err; | 
|  | 37 |  | 
|  | 38 | memset(&fl6, 0, sizeof(fl6)); | 
|  | 39 | fl6.flowi6_oif = l3mdev_master_ifindex_by_index(net, oif); | 
|  | 40 | fl6.flowi6_flags = FLOWI_FLAG_SKIP_NH_OIF; | 
|  | 41 | fl6.flowi6_mark = mark; | 
|  | 42 | memcpy(&fl6.daddr, daddr, sizeof(fl6.daddr)); | 
|  | 43 | if (saddr) | 
|  | 44 | memcpy(&fl6.saddr, saddr, sizeof(fl6.saddr)); | 
|  | 45 |  | 
|  | 46 | dst = ip6_route_output(net, NULL, &fl6); | 
|  | 47 |  | 
|  | 48 | err = dst->error; | 
|  | 49 | if (dst->error) { | 
|  | 50 | dst_release(dst); | 
|  | 51 | dst = ERR_PTR(err); | 
|  | 52 | } | 
|  | 53 |  | 
|  | 54 | return dst; | 
|  | 55 | } | 
|  | 56 |  | 
|  | 57 | static int xfrm6_get_saddr(struct net *net, int oif, | 
|  | 58 | xfrm_address_t *saddr, xfrm_address_t *daddr, | 
|  | 59 | u32 mark) | 
|  | 60 | { | 
|  | 61 | struct dst_entry *dst; | 
|  | 62 | struct net_device *dev; | 
|  | 63 |  | 
|  | 64 | dst = xfrm6_dst_lookup(net, 0, oif, NULL, daddr, mark); | 
|  | 65 | if (IS_ERR(dst)) | 
|  | 66 | return -EHOSTUNREACH; | 
|  | 67 |  | 
|  | 68 | dev = ip6_dst_idev(dst)->dev; | 
|  | 69 | ipv6_dev_get_saddr(dev_net(dev), dev, &daddr->in6, 0, &saddr->in6); | 
|  | 70 | dst_release(dst); | 
|  | 71 | return 0; | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | static int xfrm6_get_tos(const struct flowi *fl) | 
|  | 75 | { | 
|  | 76 | return 0; | 
|  | 77 | } | 
|  | 78 |  | 
|  | 79 | static int xfrm6_init_path(struct xfrm_dst *path, struct dst_entry *dst, | 
|  | 80 | int nfheader_len) | 
|  | 81 | { | 
|  | 82 | if (dst->ops->family == AF_INET6) { | 
|  | 83 | struct rt6_info *rt = (struct rt6_info *)dst; | 
|  | 84 | path->path_cookie = rt6_get_cookie(rt); | 
|  | 85 | } | 
|  | 86 |  | 
|  | 87 | path->u.rt6.rt6i_nfheader_len = nfheader_len; | 
|  | 88 |  | 
|  | 89 | return 0; | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev, | 
|  | 93 | const struct flowi *fl) | 
|  | 94 | { | 
|  | 95 | struct rt6_info *rt = (struct rt6_info *)xdst->route; | 
|  | 96 |  | 
|  | 97 | xdst->u.dst.dev = dev; | 
|  | 98 | dev_hold(dev); | 
|  | 99 |  | 
|  | 100 | xdst->u.rt6.rt6i_idev = in6_dev_get(dev); | 
|  | 101 | if (!xdst->u.rt6.rt6i_idev) { | 
|  | 102 | dev_put(dev); | 
|  | 103 | return -ENODEV; | 
|  | 104 | } | 
|  | 105 |  | 
|  | 106 | /* Sheit... I remember I did this right. Apparently, | 
|  | 107 | * it was magically lost, so this code needs audit */ | 
|  | 108 | xdst->u.rt6.rt6i_flags = rt->rt6i_flags & (RTF_ANYCAST | | 
|  | 109 | RTF_LOCAL); | 
|  | 110 | xdst->u.rt6.rt6i_metric = rt->rt6i_metric; | 
|  | 111 | xdst->u.rt6.rt6i_node = rt->rt6i_node; | 
|  | 112 | xdst->route_cookie = rt6_get_cookie(rt); | 
|  | 113 | xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway; | 
|  | 114 | xdst->u.rt6.rt6i_dst = rt->rt6i_dst; | 
|  | 115 | xdst->u.rt6.rt6i_src = rt->rt6i_src; | 
|  | 116 |  | 
|  | 117 | return 0; | 
|  | 118 | } | 
|  | 119 |  | 
|  | 120 | static inline void | 
|  | 121 | _decode_session6(struct sk_buff *skb, struct flowi *fl, int reverse) | 
|  | 122 | { | 
|  | 123 | struct flowi6 *fl6 = &fl->u.ip6; | 
|  | 124 | int onlyproto = 0; | 
|  | 125 | const struct ipv6hdr *hdr = ipv6_hdr(skb); | 
|  | 126 | u32 offset = sizeof(*hdr); | 
|  | 127 | struct ipv6_opt_hdr *exthdr; | 
|  | 128 | const unsigned char *nh = skb_network_header(skb); | 
|  | 129 | u16 nhoff = IP6CB(skb)->nhoff; | 
|  | 130 | int oif = 0; | 
|  | 131 | u8 nexthdr; | 
|  | 132 |  | 
|  | 133 | if (!nhoff) | 
|  | 134 | nhoff = offsetof(struct ipv6hdr, nexthdr); | 
|  | 135 |  | 
|  | 136 | nexthdr = nh[nhoff]; | 
|  | 137 |  | 
|  | 138 | if (skb_dst(skb)) | 
|  | 139 | oif = skb_dst(skb)->dev->ifindex; | 
|  | 140 |  | 
|  | 141 | memset(fl6, 0, sizeof(struct flowi6)); | 
|  | 142 | fl6->flowi6_mark = skb->mark; | 
|  | 143 | fl6->flowi6_oif = reverse ? skb->skb_iif : oif; | 
|  | 144 |  | 
|  | 145 | fl6->daddr = reverse ? hdr->saddr : hdr->daddr; | 
|  | 146 | fl6->saddr = reverse ? hdr->daddr : hdr->saddr; | 
|  | 147 |  | 
|  | 148 | while (nh + offset + 1 < skb->data || | 
|  | 149 | pskb_may_pull(skb, nh + offset + 1 - skb->data)) { | 
|  | 150 | nh = skb_network_header(skb); | 
|  | 151 | exthdr = (struct ipv6_opt_hdr *)(nh + offset); | 
|  | 152 |  | 
|  | 153 | switch (nexthdr) { | 
|  | 154 | case NEXTHDR_FRAGMENT: | 
|  | 155 | onlyproto = 1; | 
|  | 156 | case NEXTHDR_ROUTING: | 
|  | 157 | case NEXTHDR_HOP: | 
|  | 158 | case NEXTHDR_DEST: | 
|  | 159 | offset += ipv6_optlen(exthdr); | 
|  | 160 | nexthdr = exthdr->nexthdr; | 
|  | 161 | exthdr = (struct ipv6_opt_hdr *)(nh + offset); | 
|  | 162 | break; | 
|  | 163 |  | 
|  | 164 | case IPPROTO_UDP: | 
|  | 165 | case IPPROTO_UDPLITE: | 
|  | 166 | case IPPROTO_TCP: | 
|  | 167 | case IPPROTO_SCTP: | 
|  | 168 | case IPPROTO_DCCP: | 
|  | 169 | if (!onlyproto && (nh + offset + 4 < skb->data || | 
|  | 170 | pskb_may_pull(skb, nh + offset + 4 - skb->data))) { | 
|  | 171 | __be16 *ports; | 
|  | 172 |  | 
|  | 173 | nh = skb_network_header(skb); | 
|  | 174 | ports = (__be16 *)(nh + offset); | 
|  | 175 | fl6->fl6_sport = ports[!!reverse]; | 
|  | 176 | fl6->fl6_dport = ports[!reverse]; | 
|  | 177 | } | 
|  | 178 | fl6->flowi6_proto = nexthdr; | 
|  | 179 | return; | 
|  | 180 |  | 
|  | 181 | case IPPROTO_ICMPV6: | 
|  | 182 | if (!onlyproto && (nh + offset + 2 < skb->data || | 
|  | 183 | pskb_may_pull(skb, nh + offset + 2 - skb->data))) { | 
|  | 184 | u8 *icmp; | 
|  | 185 |  | 
|  | 186 | nh = skb_network_header(skb); | 
|  | 187 | icmp = (u8 *)(nh + offset); | 
|  | 188 | fl6->fl6_icmp_type = icmp[0]; | 
|  | 189 | fl6->fl6_icmp_code = icmp[1]; | 
|  | 190 | } | 
|  | 191 | fl6->flowi6_proto = nexthdr; | 
|  | 192 | return; | 
|  | 193 |  | 
|  | 194 | #if IS_ENABLED(CONFIG_IPV6_MIP6) | 
|  | 195 | case IPPROTO_MH: | 
|  | 196 | offset += ipv6_optlen(exthdr); | 
|  | 197 | if (!onlyproto && (nh + offset + 3 < skb->data || | 
|  | 198 | pskb_may_pull(skb, nh + offset + 3 - skb->data))) { | 
|  | 199 | struct ip6_mh *mh; | 
|  | 200 |  | 
|  | 201 | nh = skb_network_header(skb); | 
|  | 202 | mh = (struct ip6_mh *)(nh + offset); | 
|  | 203 | fl6->fl6_mh_type = mh->ip6mh_type; | 
|  | 204 | } | 
|  | 205 | fl6->flowi6_proto = nexthdr; | 
|  | 206 | return; | 
|  | 207 | #endif | 
|  | 208 |  | 
|  | 209 | /* XXX Why are there these headers? */ | 
|  | 210 | case IPPROTO_AH: | 
|  | 211 | case IPPROTO_ESP: | 
|  | 212 | case IPPROTO_COMP: | 
|  | 213 | default: | 
|  | 214 | fl6->fl6_ipsec_spi = 0; | 
|  | 215 | fl6->flowi6_proto = nexthdr; | 
|  | 216 | return; | 
|  | 217 | } | 
|  | 218 | } | 
|  | 219 | } | 
|  | 220 |  | 
|  | 221 | static void xfrm6_update_pmtu(struct dst_entry *dst, struct sock *sk, | 
|  | 222 | struct sk_buff *skb, u32 mtu, | 
|  | 223 | bool confirm_neigh) | 
|  | 224 | { | 
|  | 225 | struct xfrm_dst *xdst = (struct xfrm_dst *)dst; | 
|  | 226 | struct dst_entry *path = xdst->route; | 
|  | 227 |  | 
|  | 228 | path->ops->update_pmtu(path, sk, skb, mtu, confirm_neigh); | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | static void xfrm6_redirect(struct dst_entry *dst, struct sock *sk, | 
|  | 232 | struct sk_buff *skb) | 
|  | 233 | { | 
|  | 234 | struct xfrm_dst *xdst = (struct xfrm_dst *)dst; | 
|  | 235 | struct dst_entry *path = xdst->route; | 
|  | 236 |  | 
|  | 237 | path->ops->redirect(path, sk, skb); | 
|  | 238 | } | 
|  | 239 |  | 
|  | 240 | static void xfrm6_dst_destroy(struct dst_entry *dst) | 
|  | 241 | { | 
|  | 242 | struct xfrm_dst *xdst = (struct xfrm_dst *)dst; | 
|  | 243 |  | 
|  | 244 | if (likely(xdst->u.rt6.rt6i_idev)) | 
|  | 245 | in6_dev_put(xdst->u.rt6.rt6i_idev); | 
|  | 246 | dst_destroy_metrics_generic(dst); | 
|  | 247 | xfrm_dst_destroy(xdst); | 
|  | 248 | } | 
|  | 249 |  | 
|  | 250 | static void xfrm6_dst_ifdown(struct dst_entry *dst, struct net_device *dev, | 
|  | 251 | int unregister) | 
|  | 252 | { | 
|  | 253 | struct xfrm_dst *xdst; | 
|  | 254 |  | 
|  | 255 | if (!unregister) | 
|  | 256 | return; | 
|  | 257 |  | 
|  | 258 | xdst = (struct xfrm_dst *)dst; | 
|  | 259 | if (xdst->u.rt6.rt6i_idev->dev == dev) { | 
|  | 260 | struct inet6_dev *loopback_idev = | 
|  | 261 | in6_dev_get(dev_net(dev)->loopback_dev); | 
|  | 262 | BUG_ON(!loopback_idev); | 
|  | 263 |  | 
|  | 264 | do { | 
|  | 265 | in6_dev_put(xdst->u.rt6.rt6i_idev); | 
|  | 266 | xdst->u.rt6.rt6i_idev = loopback_idev; | 
|  | 267 | in6_dev_hold(loopback_idev); | 
|  | 268 | xdst = (struct xfrm_dst *)xdst->u.dst.child; | 
|  | 269 | } while (xdst->u.dst.xfrm); | 
|  | 270 |  | 
|  | 271 | __in6_dev_put(loopback_idev); | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | xfrm_dst_ifdown(dst, dev); | 
|  | 275 | } | 
|  | 276 |  | 
|  | 277 | static struct dst_ops xfrm6_dst_ops_template = { | 
|  | 278 | .family =		AF_INET6, | 
|  | 279 | .update_pmtu =		xfrm6_update_pmtu, | 
|  | 280 | .redirect =		xfrm6_redirect, | 
|  | 281 | .cow_metrics =		dst_cow_metrics_generic, | 
|  | 282 | .destroy =		xfrm6_dst_destroy, | 
|  | 283 | .ifdown =		xfrm6_dst_ifdown, | 
|  | 284 | .local_out =		__ip6_local_out, | 
|  | 285 | .gc_thresh =		32768, | 
|  | 286 | }; | 
|  | 287 |  | 
|  | 288 | static const struct xfrm_policy_afinfo xfrm6_policy_afinfo = { | 
|  | 289 | .dst_ops =		&xfrm6_dst_ops_template, | 
|  | 290 | .dst_lookup =		xfrm6_dst_lookup, | 
|  | 291 | .get_saddr =		xfrm6_get_saddr, | 
|  | 292 | .decode_session =	_decode_session6, | 
|  | 293 | .get_tos =		xfrm6_get_tos, | 
|  | 294 | .init_path =		xfrm6_init_path, | 
|  | 295 | .fill_dst =		xfrm6_fill_dst, | 
|  | 296 | .blackhole_route =	ip6_blackhole_route, | 
|  | 297 | }; | 
|  | 298 |  | 
|  | 299 | static int __init xfrm6_policy_init(void) | 
|  | 300 | { | 
|  | 301 | return xfrm_policy_register_afinfo(&xfrm6_policy_afinfo, AF_INET6); | 
|  | 302 | } | 
|  | 303 |  | 
|  | 304 | static void xfrm6_policy_fini(void) | 
|  | 305 | { | 
|  | 306 | xfrm_policy_unregister_afinfo(&xfrm6_policy_afinfo); | 
|  | 307 | } | 
|  | 308 |  | 
|  | 309 | #ifdef CONFIG_SYSCTL | 
|  | 310 | static struct ctl_table xfrm6_policy_table[] = { | 
|  | 311 | { | 
|  | 312 | .procname       = "xfrm6_gc_thresh", | 
|  | 313 | .data		= &init_net.xfrm.xfrm6_dst_ops.gc_thresh, | 
|  | 314 | .maxlen		= sizeof(int), | 
|  | 315 | .mode		= 0644, | 
|  | 316 | .proc_handler   = proc_dointvec, | 
|  | 317 | }, | 
|  | 318 | { } | 
|  | 319 | }; | 
|  | 320 |  | 
|  | 321 | static int __net_init xfrm6_net_sysctl_init(struct net *net) | 
|  | 322 | { | 
|  | 323 | struct ctl_table *table; | 
|  | 324 | struct ctl_table_header *hdr; | 
|  | 325 |  | 
|  | 326 | table = xfrm6_policy_table; | 
|  | 327 | if (!net_eq(net, &init_net)) { | 
|  | 328 | table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL); | 
|  | 329 | if (!table) | 
|  | 330 | goto err_alloc; | 
|  | 331 |  | 
|  | 332 | table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh; | 
|  | 333 | } | 
|  | 334 |  | 
|  | 335 | hdr = register_net_sysctl(net, "net/ipv6", table); | 
|  | 336 | if (!hdr) | 
|  | 337 | goto err_reg; | 
|  | 338 |  | 
|  | 339 | net->ipv6.sysctl.xfrm6_hdr = hdr; | 
|  | 340 | return 0; | 
|  | 341 |  | 
|  | 342 | err_reg: | 
|  | 343 | if (!net_eq(net, &init_net)) | 
|  | 344 | kfree(table); | 
|  | 345 | err_alloc: | 
|  | 346 | return -ENOMEM; | 
|  | 347 | } | 
|  | 348 |  | 
|  | 349 | static void __net_exit xfrm6_net_sysctl_exit(struct net *net) | 
|  | 350 | { | 
|  | 351 | struct ctl_table *table; | 
|  | 352 |  | 
|  | 353 | if (!net->ipv6.sysctl.xfrm6_hdr) | 
|  | 354 | return; | 
|  | 355 |  | 
|  | 356 | table = net->ipv6.sysctl.xfrm6_hdr->ctl_table_arg; | 
|  | 357 | unregister_net_sysctl_table(net->ipv6.sysctl.xfrm6_hdr); | 
|  | 358 | if (!net_eq(net, &init_net)) | 
|  | 359 | kfree(table); | 
|  | 360 | } | 
|  | 361 | #else /* CONFIG_SYSCTL */ | 
|  | 362 | static inline int xfrm6_net_sysctl_init(struct net *net) | 
|  | 363 | { | 
|  | 364 | return 0; | 
|  | 365 | } | 
|  | 366 |  | 
|  | 367 | static inline void xfrm6_net_sysctl_exit(struct net *net) | 
|  | 368 | { | 
|  | 369 | } | 
|  | 370 | #endif | 
|  | 371 |  | 
|  | 372 | static int __net_init xfrm6_net_init(struct net *net) | 
|  | 373 | { | 
|  | 374 | int ret; | 
|  | 375 |  | 
|  | 376 | memcpy(&net->xfrm.xfrm6_dst_ops, &xfrm6_dst_ops_template, | 
|  | 377 | sizeof(xfrm6_dst_ops_template)); | 
|  | 378 | ret = dst_entries_init(&net->xfrm.xfrm6_dst_ops); | 
|  | 379 | if (ret) | 
|  | 380 | return ret; | 
|  | 381 |  | 
|  | 382 | ret = xfrm6_net_sysctl_init(net); | 
|  | 383 | if (ret) | 
|  | 384 | dst_entries_destroy(&net->xfrm.xfrm6_dst_ops); | 
|  | 385 |  | 
|  | 386 | return ret; | 
|  | 387 | } | 
|  | 388 |  | 
|  | 389 | static void __net_exit xfrm6_net_exit(struct net *net) | 
|  | 390 | { | 
|  | 391 | xfrm6_net_sysctl_exit(net); | 
|  | 392 | dst_entries_destroy(&net->xfrm.xfrm6_dst_ops); | 
|  | 393 | } | 
|  | 394 |  | 
|  | 395 | static struct pernet_operations xfrm6_net_ops = { | 
|  | 396 | .init	= xfrm6_net_init, | 
|  | 397 | .exit	= xfrm6_net_exit, | 
|  | 398 | }; | 
|  | 399 |  | 
|  | 400 | int __init xfrm6_init(void) | 
|  | 401 | { | 
|  | 402 | int ret; | 
|  | 403 |  | 
|  | 404 | ret = xfrm6_policy_init(); | 
|  | 405 | if (ret) | 
|  | 406 | goto out; | 
|  | 407 | ret = xfrm6_state_init(); | 
|  | 408 | if (ret) | 
|  | 409 | goto out_policy; | 
|  | 410 |  | 
|  | 411 | ret = xfrm6_protocol_init(); | 
|  | 412 | if (ret) | 
|  | 413 | goto out_state; | 
|  | 414 |  | 
|  | 415 | register_pernet_subsys(&xfrm6_net_ops); | 
|  | 416 | out: | 
|  | 417 | return ret; | 
|  | 418 | out_state: | 
|  | 419 | xfrm6_state_fini(); | 
|  | 420 | out_policy: | 
|  | 421 | xfrm6_policy_fini(); | 
|  | 422 | goto out; | 
|  | 423 | } | 
|  | 424 |  | 
|  | 425 | void xfrm6_fini(void) | 
|  | 426 | { | 
|  | 427 | unregister_pernet_subsys(&xfrm6_net_ops); | 
|  | 428 | xfrm6_protocol_fini(); | 
|  | 429 | xfrm6_policy_fini(); | 
|  | 430 | xfrm6_state_fini(); | 
|  | 431 | } |