162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * xfrm6_output.c - Common IPsec encapsulation code for IPv6.
462306a36Sopenharmony_ci * Copyright (C) 2002 USAGI/WIDE Project
562306a36Sopenharmony_ci * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/if_ether.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/skbuff.h>
1262306a36Sopenharmony_ci#include <linux/icmpv6.h>
1362306a36Sopenharmony_ci#include <linux/netfilter_ipv6.h>
1462306a36Sopenharmony_ci#include <net/dst.h>
1562306a36Sopenharmony_ci#include <net/ipv6.h>
1662306a36Sopenharmony_ci#include <net/ip6_route.h>
1762306a36Sopenharmony_ci#include <net/xfrm.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_civoid xfrm6_local_rxpmtu(struct sk_buff *skb, u32 mtu)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	struct flowi6 fl6;
2262306a36Sopenharmony_ci	struct sock *sk = skb->sk;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	fl6.flowi6_oif = sk->sk_bound_dev_if;
2562306a36Sopenharmony_ci	fl6.daddr = ipv6_hdr(skb)->daddr;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	ipv6_local_rxpmtu(sk, &fl6, mtu);
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_civoid xfrm6_local_error(struct sk_buff *skb, u32 mtu)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct flowi6 fl6;
3362306a36Sopenharmony_ci	const struct ipv6hdr *hdr;
3462306a36Sopenharmony_ci	struct sock *sk = skb->sk;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	hdr = skb->encapsulation ? inner_ipv6_hdr(skb) : ipv6_hdr(skb);
3762306a36Sopenharmony_ci	fl6.fl6_dport = inet_sk(sk)->inet_dport;
3862306a36Sopenharmony_ci	fl6.daddr = hdr->daddr;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	ipv6_local_error(sk, EMSGSIZE, &fl6, mtu);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int __xfrm6_output_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	return xfrm_output(sk, skb);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int xfrm6_noneed_fragment(struct sk_buff *skb)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	struct frag_hdr *fh;
5162306a36Sopenharmony_ci	u8 prevhdr = ipv6_hdr(skb)->nexthdr;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (prevhdr != NEXTHDR_FRAGMENT)
5462306a36Sopenharmony_ci		return 0;
5562306a36Sopenharmony_ci	fh = (struct frag_hdr *)(skb->data + sizeof(struct ipv6hdr));
5662306a36Sopenharmony_ci	if (fh->nexthdr == NEXTHDR_ESP || fh->nexthdr == NEXTHDR_AUTH)
5762306a36Sopenharmony_ci		return 1;
5862306a36Sopenharmony_ci	return 0;
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int __xfrm6_output(struct net *net, struct sock *sk, struct sk_buff *skb)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct dst_entry *dst = skb_dst(skb);
6462306a36Sopenharmony_ci	struct xfrm_state *x = dst->xfrm;
6562306a36Sopenharmony_ci	unsigned int mtu;
6662306a36Sopenharmony_ci	bool toobig;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci#ifdef CONFIG_NETFILTER
6962306a36Sopenharmony_ci	if (!x) {
7062306a36Sopenharmony_ci		IP6CB(skb)->flags |= IP6SKB_REROUTED;
7162306a36Sopenharmony_ci		return dst_output(net, sk, skb);
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci#endif
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (x->props.mode != XFRM_MODE_TUNNEL)
7662306a36Sopenharmony_ci		goto skip_frag;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (skb->protocol == htons(ETH_P_IPV6))
7962306a36Sopenharmony_ci		mtu = ip6_skb_dst_mtu(skb);
8062306a36Sopenharmony_ci	else
8162306a36Sopenharmony_ci		mtu = dst_mtu(skb_dst(skb));
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	toobig = skb->len > mtu && !skb_is_gso(skb);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (toobig && xfrm6_local_dontfrag(skb->sk)) {
8662306a36Sopenharmony_ci		xfrm6_local_rxpmtu(skb, mtu);
8762306a36Sopenharmony_ci		kfree_skb(skb);
8862306a36Sopenharmony_ci		return -EMSGSIZE;
8962306a36Sopenharmony_ci	} else if (toobig && xfrm6_noneed_fragment(skb)) {
9062306a36Sopenharmony_ci		skb->ignore_df = 1;
9162306a36Sopenharmony_ci		goto skip_frag;
9262306a36Sopenharmony_ci	} else if (!skb->ignore_df && toobig && skb->sk) {
9362306a36Sopenharmony_ci		xfrm_local_error(skb, mtu);
9462306a36Sopenharmony_ci		kfree_skb(skb);
9562306a36Sopenharmony_ci		return -EMSGSIZE;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (toobig || dst_allfrag(skb_dst(skb)))
9962306a36Sopenharmony_ci		return ip6_fragment(net, sk, skb,
10062306a36Sopenharmony_ci				    __xfrm6_output_finish);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ciskip_frag:
10362306a36Sopenharmony_ci	return xfrm_output(sk, skb);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ciint xfrm6_output(struct net *net, struct sock *sk, struct sk_buff *skb)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING,
10962306a36Sopenharmony_ci			    net, sk, skb,  skb->dev, skb_dst(skb)->dev,
11062306a36Sopenharmony_ci			    __xfrm6_output,
11162306a36Sopenharmony_ci			    !(IP6CB(skb)->flags & IP6SKB_REROUTED));
11262306a36Sopenharmony_ci}
113