162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/export.h>
362306a36Sopenharmony_ci#include <linux/icmpv6.h>
462306a36Sopenharmony_ci#include <linux/mutex.h>
562306a36Sopenharmony_ci#include <linux/netdevice.h>
662306a36Sopenharmony_ci#include <linux/spinlock.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <net/ipv6.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#if !IS_BUILTIN(CONFIG_IPV6)
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic ip6_icmp_send_t __rcu *ip6_icmp_send;
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ciint inet6_register_icmp_sender(ip6_icmp_send_t *fn)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	return (cmpxchg((ip6_icmp_send_t **)&ip6_icmp_send, NULL, fn) == NULL) ?
1962306a36Sopenharmony_ci		0 : -EBUSY;
2062306a36Sopenharmony_ci}
2162306a36Sopenharmony_ciEXPORT_SYMBOL(inet6_register_icmp_sender);
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciint inet6_unregister_icmp_sender(ip6_icmp_send_t *fn)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	int ret;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	ret = (cmpxchg((ip6_icmp_send_t **)&ip6_icmp_send, fn, NULL) == fn) ?
2862306a36Sopenharmony_ci	      0 : -EINVAL;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	synchronize_net();
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return ret;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ciEXPORT_SYMBOL(inet6_unregister_icmp_sender);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_civoid __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
3762306a36Sopenharmony_ci		   const struct inet6_skb_parm *parm)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	ip6_icmp_send_t *send;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	rcu_read_lock();
4262306a36Sopenharmony_ci	send = rcu_dereference(ip6_icmp_send);
4362306a36Sopenharmony_ci	if (send)
4462306a36Sopenharmony_ci		send(skb, type, code, info, NULL, parm);
4562306a36Sopenharmony_ci	rcu_read_unlock();
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ciEXPORT_SYMBOL(__icmpv6_send);
4862306a36Sopenharmony_ci#endif
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NF_NAT)
5162306a36Sopenharmony_ci#include <net/netfilter/nf_conntrack.h>
5262306a36Sopenharmony_civoid icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct inet6_skb_parm parm = { 0 };
5562306a36Sopenharmony_ci	struct sk_buff *cloned_skb = NULL;
5662306a36Sopenharmony_ci	enum ip_conntrack_info ctinfo;
5762306a36Sopenharmony_ci	struct in6_addr orig_ip;
5862306a36Sopenharmony_ci	struct nf_conn *ct;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	ct = nf_ct_get(skb_in, &ctinfo);
6162306a36Sopenharmony_ci	if (!ct || !(ct->status & IPS_SRC_NAT)) {
6262306a36Sopenharmony_ci		__icmpv6_send(skb_in, type, code, info, &parm);
6362306a36Sopenharmony_ci		return;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	if (skb_shared(skb_in))
6762306a36Sopenharmony_ci		skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
7062306a36Sopenharmony_ci	    (skb_network_header(skb_in) + sizeof(struct ipv6hdr)) >
7162306a36Sopenharmony_ci	    skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
7262306a36Sopenharmony_ci	    skb_network_offset(skb_in) + sizeof(struct ipv6hdr))))
7362306a36Sopenharmony_ci		goto out;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	orig_ip = ipv6_hdr(skb_in)->saddr;
7662306a36Sopenharmony_ci	ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
7762306a36Sopenharmony_ci	__icmpv6_send(skb_in, type, code, info, &parm);
7862306a36Sopenharmony_ci	ipv6_hdr(skb_in)->saddr = orig_ip;
7962306a36Sopenharmony_ciout:
8062306a36Sopenharmony_ci	consume_skb(cloned_skb);
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ciEXPORT_SYMBOL(icmpv6_ndo_send);
8362306a36Sopenharmony_ci#endif
8462306a36Sopenharmony_ci#endif
85