// SPDX-License-Identifier: GPL-2.0-only /* Support nat functions for openvswitch and used by OVS and TC conntrack. */ #include /* Modelled after nf_nat_ipv[46]_fn(). * range is only used for new, uninitialized NAT state. * Returns either NF_ACCEPT or NF_DROP. */ static int nf_ct_nat_execute(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info ctinfo, int *action, const struct nf_nat_range2 *range, enum nf_nat_manip_type maniptype) { __be16 proto = skb_protocol(skb, true); int hooknum, err = NF_ACCEPT; /* See HOOK2MANIP(). */ if (maniptype == NF_NAT_MANIP_SRC) hooknum = NF_INET_LOCAL_IN; /* Source NAT */ else hooknum = NF_INET_LOCAL_OUT; /* Destination NAT */ switch (ctinfo) { case IP_CT_RELATED: case IP_CT_RELATED_REPLY: if (proto == htons(ETH_P_IP) && ip_hdr(skb)->protocol == IPPROTO_ICMP) { if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo, hooknum)) err = NF_DROP; goto out; } else if (IS_ENABLED(CONFIG_IPV6) && proto == htons(ETH_P_IPV6)) { __be16 frag_off; u8 nexthdr = ipv6_hdr(skb)->nexthdr; int hdrlen = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, &frag_off); if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) { if (!nf_nat_icmpv6_reply_translation(skb, ct, ctinfo, hooknum, hdrlen)) err = NF_DROP; goto out; } } /* Non-ICMP, fall thru to initialize if needed. */ fallthrough; case IP_CT_NEW: /* Seen it before? This can happen for loopback, retrans, * or local packets. */ if (!nf_nat_initialized(ct, maniptype)) { /* Initialize according to the NAT action. */ err = (range && range->flags & NF_NAT_RANGE_MAP_IPS) /* Action is set up to establish a new * mapping. */ ? nf_nat_setup_info(ct, range, maniptype) : nf_nat_alloc_null_binding(ct, hooknum); if (err != NF_ACCEPT) goto out; } break; case IP_CT_ESTABLISHED: case IP_CT_ESTABLISHED_REPLY: break; default: err = NF_DROP; goto out; } err = nf_nat_packet(ct, ctinfo, hooknum, skb); out: if (err == NF_ACCEPT) *action |= BIT(maniptype); return err; } int nf_ct_nat(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info ctinfo, int *action, const struct nf_nat_range2 *range, bool commit) { enum nf_nat_manip_type maniptype; int err, ct_action = *action; *action = 0; /* Add NAT extension if not confirmed yet. */ if (!nf_ct_is_confirmed(ct) && !nf_ct_nat_ext_add(ct)) return NF_DROP; /* Can't NAT. */ if (ctinfo != IP_CT_NEW && (ct->status & IPS_NAT_MASK) && (ctinfo != IP_CT_RELATED || commit)) { /* NAT an established or related connection like before. */ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) /* This is the REPLY direction for a connection * for which NAT was applied in the forward * direction. Do the reverse NAT. */ maniptype = ct->status & IPS_SRC_NAT ? NF_NAT_MANIP_DST : NF_NAT_MANIP_SRC; else maniptype = ct->status & IPS_SRC_NAT ? NF_NAT_MANIP_SRC : NF_NAT_MANIP_DST; } else if (ct_action & BIT(NF_NAT_MANIP_SRC)) { maniptype = NF_NAT_MANIP_SRC; } else if (ct_action & BIT(NF_NAT_MANIP_DST)) { maniptype = NF_NAT_MANIP_DST; } else { return NF_ACCEPT; } err = nf_ct_nat_execute(skb, ct, ctinfo, action, range, maniptype); if (err == NF_ACCEPT && ct->status & IPS_DST_NAT) { if (ct->status & IPS_SRC_NAT) { if (maniptype == NF_NAT_MANIP_SRC) maniptype = NF_NAT_MANIP_DST; else maniptype = NF_NAT_MANIP_SRC; err = nf_ct_nat_execute(skb, ct, ctinfo, action, range, maniptype); } else if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) { err = nf_ct_nat_execute(skb, ct, ctinfo, action, NULL, NF_NAT_MANIP_SRC); } } return err; } EXPORT_SYMBOL_GPL(nf_ct_nat);