18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/export.h>
38c2ecf20Sopenharmony_ci#include <linux/icmpv6.h>
48c2ecf20Sopenharmony_ci#include <linux/mutex.h>
58c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
68c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <net/ipv6.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#if !IS_BUILTIN(CONFIG_IPV6)
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic ip6_icmp_send_t __rcu *ip6_icmp_send;
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ciint inet6_register_icmp_sender(ip6_icmp_send_t *fn)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	return (cmpxchg((ip6_icmp_send_t **)&ip6_icmp_send, NULL, fn) == NULL) ?
198c2ecf20Sopenharmony_ci		0 : -EBUSY;
208c2ecf20Sopenharmony_ci}
218c2ecf20Sopenharmony_ciEXPORT_SYMBOL(inet6_register_icmp_sender);
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ciint inet6_unregister_icmp_sender(ip6_icmp_send_t *fn)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	int ret;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	ret = (cmpxchg((ip6_icmp_send_t **)&ip6_icmp_send, fn, NULL) == fn) ?
288c2ecf20Sopenharmony_ci	      0 : -EINVAL;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	synchronize_net();
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	return ret;
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ciEXPORT_SYMBOL(inet6_unregister_icmp_sender);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_civoid __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
378c2ecf20Sopenharmony_ci		   const struct inet6_skb_parm *parm)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	ip6_icmp_send_t *send;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	rcu_read_lock();
428c2ecf20Sopenharmony_ci	send = rcu_dereference(ip6_icmp_send);
438c2ecf20Sopenharmony_ci	if (send)
448c2ecf20Sopenharmony_ci		send(skb, type, code, info, NULL, parm);
458c2ecf20Sopenharmony_ci	rcu_read_unlock();
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__icmpv6_send);
488c2ecf20Sopenharmony_ci#endif
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_NF_NAT)
518c2ecf20Sopenharmony_ci#include <net/netfilter/nf_conntrack.h>
528c2ecf20Sopenharmony_civoid icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	struct inet6_skb_parm parm = { 0 };
558c2ecf20Sopenharmony_ci	struct sk_buff *cloned_skb = NULL;
568c2ecf20Sopenharmony_ci	enum ip_conntrack_info ctinfo;
578c2ecf20Sopenharmony_ci	struct in6_addr orig_ip;
588c2ecf20Sopenharmony_ci	struct nf_conn *ct;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	ct = nf_ct_get(skb_in, &ctinfo);
618c2ecf20Sopenharmony_ci	if (!ct || !(ct->status & IPS_SRC_NAT)) {
628c2ecf20Sopenharmony_ci		__icmpv6_send(skb_in, type, code, info, &parm);
638c2ecf20Sopenharmony_ci		return;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	if (skb_shared(skb_in))
678c2ecf20Sopenharmony_ci		skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
708c2ecf20Sopenharmony_ci	    (skb_network_header(skb_in) + sizeof(struct ipv6hdr)) >
718c2ecf20Sopenharmony_ci	    skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
728c2ecf20Sopenharmony_ci	    skb_network_offset(skb_in) + sizeof(struct ipv6hdr))))
738c2ecf20Sopenharmony_ci		goto out;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	orig_ip = ipv6_hdr(skb_in)->saddr;
768c2ecf20Sopenharmony_ci	ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
778c2ecf20Sopenharmony_ci	__icmpv6_send(skb_in, type, code, info, &parm);
788c2ecf20Sopenharmony_ci	ipv6_hdr(skb_in)->saddr = orig_ip;
798c2ecf20Sopenharmony_ciout:
808c2ecf20Sopenharmony_ci	consume_skb(cloned_skb);
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(icmpv6_ndo_send);
838c2ecf20Sopenharmony_ci#endif
848c2ecf20Sopenharmony_ci#endif
85