| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/export.h> | |
| #include <linux/icmpv6.h> | |
| #include <linux/mutex.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/spinlock.h> | |
| #include <net/ipv6.h> | |
| #if IS_ENABLED(CONFIG_IPV6) && IS_ENABLED(CONFIG_NF_NAT) | |
| #include <net/netfilter/nf_conntrack.h> | |
| void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info) | |
| { | |
| struct inet6_skb_parm parm = { 0 }; | |
| struct sk_buff *cloned_skb = NULL; | |
| enum ip_conntrack_info ctinfo; | |
| enum ip_conntrack_dir dir; | |
| struct in6_addr orig_ip; | |
| struct nf_conn *ct; | |
| ct = nf_ct_get(skb_in, &ctinfo); | |
| if (!ct || !(READ_ONCE(ct->status) & IPS_NAT_MASK)) { | |
| icmp6_send(skb_in, type, code, info, NULL, &parm); | |
| return; | |
| } | |
| if (skb_shared(skb_in)) | |
| skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC); | |
| if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head || | |
| (skb_network_header(skb_in) + sizeof(struct ipv6hdr)) > | |
| skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in, | |
| skb_network_offset(skb_in) + sizeof(struct ipv6hdr)))) | |
| goto out; | |
| orig_ip = ipv6_hdr(skb_in)->saddr; | |
| dir = CTINFO2DIR(ctinfo); | |
| ipv6_hdr(skb_in)->saddr = ct->tuplehash[dir].tuple.src.u3.in6; | |
| icmp6_send(skb_in, type, code, info, NULL, &parm); | |
| ipv6_hdr(skb_in)->saddr = orig_ip; | |
| out: | |
| consume_skb(cloned_skb); | |
| } | |
| EXPORT_SYMBOL(icmpv6_ndo_send); | |
| #endif |