| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* Support ct functions for openvswitch and used by OVS and TC conntrack. */ |
| 3 | |
| 4 | #include <net/netfilter/nf_conntrack_helper.h> |
| 5 | #include <net/netfilter/nf_conntrack_seqadj.h> |
| 6 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
| 7 | #include <net/ipv6_frag.h> |
| 8 | #include <net/ip.h> |
| 9 | #include <linux/netfilter_ipv6.h> |
| 10 | |
| 11 | /* 'skb' should already be pulled to nh_ofs. */ |
| 12 | int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, |
| 13 | enum ip_conntrack_info ctinfo, u16 proto) |
| 14 | { |
| 15 | const struct nf_conntrack_helper *helper; |
| 16 | const struct nf_conn_help *help; |
| 17 | unsigned int protoff; |
| 18 | int err; |
| 19 | |
| 20 | if (ctinfo == IP_CT_RELATED_REPLY) |
| 21 | return NF_ACCEPT; |
| 22 | |
| 23 | help = nfct_help(ct); |
| 24 | if (!help) |
| 25 | return NF_ACCEPT; |
| 26 | |
| 27 | helper = rcu_dereference(help->helper); |
| 28 | if (!helper) |
| 29 | return NF_ACCEPT; |
| 30 | |
| 31 | if (helper->tuple.src.l3num != NFPROTO_UNSPEC && |
| 32 | helper->tuple.src.l3num != proto) |
| 33 | return NF_ACCEPT; |
| 34 | |
| 35 | switch (proto) { |
| 36 | case NFPROTO_IPV4: |
| 37 | protoff = ip_hdrlen(skb); |
| 38 | proto = ip_hdr(skb)->protocol; |
| 39 | break; |
| 40 | case NFPROTO_IPV6: { |
| 41 | u8 nexthdr = ipv6_hdr(skb)->nexthdr; |
| 42 | __be16 frag_off; |
| 43 | int ofs; |
| 44 | |
| 45 | ofs = ipv6_skip_exthdr(skb, start: sizeof(struct ipv6hdr), nexthdrp: &nexthdr, |
| 46 | frag_offp: &frag_off); |
| 47 | if (ofs < 0 || (frag_off & htons(~0x7)) != 0) { |
| 48 | pr_debug("proto header not found\n" ); |
| 49 | return NF_ACCEPT; |
| 50 | } |
| 51 | protoff = ofs; |
| 52 | proto = nexthdr; |
| 53 | break; |
| 54 | } |
| 55 | default: |
| 56 | WARN_ONCE(1, "helper invoked on non-IP family!" ); |
| 57 | return NF_DROP; |
| 58 | } |
| 59 | |
| 60 | if (helper->tuple.dst.protonum != proto) |
| 61 | return NF_ACCEPT; |
| 62 | |
| 63 | err = helper->help(skb, protoff, ct, ctinfo); |
| 64 | if (err != NF_ACCEPT) |
| 65 | return err; |
| 66 | |
| 67 | /* Adjust seqs after helper. This is needed due to some helpers (e.g., |
| 68 | * FTP with NAT) adusting the TCP payload size when mangling IP |
| 69 | * addresses and/or port numbers in the text-based control connection. |
| 70 | */ |
| 71 | if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && |
| 72 | !nf_ct_seq_adjust(skb, ct, ctinfo, protoff)) |
| 73 | return NF_DROP; |
| 74 | return NF_ACCEPT; |
| 75 | } |
| 76 | EXPORT_SYMBOL_GPL(nf_ct_helper); |
| 77 | |
| 78 | int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family, |
| 79 | u8 proto, bool nat, struct nf_conntrack_helper **hp) |
| 80 | { |
| 81 | struct nf_conntrack_helper *helper; |
| 82 | struct nf_conn_help *help; |
| 83 | int ret = 0; |
| 84 | |
| 85 | helper = nf_conntrack_helper_try_module_get(name, l3num: family, protonum: proto); |
| 86 | if (!helper) |
| 87 | return -EINVAL; |
| 88 | |
| 89 | help = nf_ct_helper_ext_add(ct, GFP_KERNEL); |
| 90 | if (!help) { |
| 91 | nf_conntrack_helper_put(helper); |
| 92 | return -ENOMEM; |
| 93 | } |
| 94 | #if IS_ENABLED(CONFIG_NF_NAT) |
| 95 | if (nat) { |
| 96 | ret = nf_nat_helper_try_module_get(name, l3num: family, protonum: proto); |
| 97 | if (ret) { |
| 98 | nf_conntrack_helper_put(helper); |
| 99 | return ret; |
| 100 | } |
| 101 | } |
| 102 | #endif |
| 103 | rcu_assign_pointer(help->helper, helper); |
| 104 | *hp = helper; |
| 105 | return ret; |
| 106 | } |
| 107 | EXPORT_SYMBOL_GPL(nf_ct_add_helper); |
| 108 | |
| 109 | /* Trim the skb to the length specified by the IP/IPv6 header, |
| 110 | * removing any trailing lower-layer padding. This prepares the skb |
| 111 | * for higher-layer processing that assumes skb->len excludes padding |
| 112 | * (such as nf_ip_checksum). The caller needs to pull the skb to the |
| 113 | * network header, and ensure ip_hdr/ipv6_hdr points to valid data. |
| 114 | */ |
| 115 | int nf_ct_skb_network_trim(struct sk_buff *skb, int family) |
| 116 | { |
| 117 | unsigned int len; |
| 118 | |
| 119 | switch (family) { |
| 120 | case NFPROTO_IPV4: |
| 121 | len = skb_ip_totlen(skb); |
| 122 | break; |
| 123 | case NFPROTO_IPV6: |
| 124 | len = ntohs(ipv6_hdr(skb)->payload_len); |
| 125 | if (ipv6_hdr(skb)->nexthdr == NEXTHDR_HOP) { |
| 126 | int err = nf_ip6_check_hbh_len(skb, plen: &len); |
| 127 | |
| 128 | if (err) |
| 129 | return err; |
| 130 | } |
| 131 | len += sizeof(struct ipv6hdr); |
| 132 | break; |
| 133 | default: |
| 134 | len = skb->len; |
| 135 | } |
| 136 | |
| 137 | return pskb_trim_rcsum(skb, len); |
| 138 | } |
| 139 | EXPORT_SYMBOL_GPL(nf_ct_skb_network_trim); |
| 140 | |
| 141 | /* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero |
| 142 | * value if 'skb' is freed. |
| 143 | */ |
| 144 | int nf_ct_handle_fragments(struct net *net, struct sk_buff *skb, |
| 145 | u16 zone, u8 family, u8 *proto, u16 *mru) |
| 146 | { |
| 147 | int err; |
| 148 | |
| 149 | if (family == NFPROTO_IPV4) { |
| 150 | enum ip_defrag_users user = IP_DEFRAG_CONNTRACK_IN + zone; |
| 151 | |
| 152 | memset(IPCB(skb), 0, sizeof(struct inet_skb_parm)); |
| 153 | local_bh_disable(); |
| 154 | err = ip_defrag(net, skb, user); |
| 155 | local_bh_enable(); |
| 156 | if (err) |
| 157 | return err; |
| 158 | |
| 159 | *mru = IPCB(skb)->frag_max_size; |
| 160 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) |
| 161 | } else if (family == NFPROTO_IPV6) { |
| 162 | enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone; |
| 163 | |
| 164 | memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm)); |
| 165 | err = nf_ct_frag6_gather(net, skb, user); |
| 166 | if (err) { |
| 167 | if (err != -EINPROGRESS) |
| 168 | kfree_skb(skb); |
| 169 | return err; |
| 170 | } |
| 171 | |
| 172 | *proto = ipv6_hdr(skb)->nexthdr; |
| 173 | *mru = IP6CB(skb)->frag_max_size; |
| 174 | #endif |
| 175 | } else { |
| 176 | kfree_skb(skb); |
| 177 | return -EPFNOSUPPORT; |
| 178 | } |
| 179 | |
| 180 | skb_clear_hash(skb); |
| 181 | skb->ignore_df = 1; |
| 182 | |
| 183 | return 0; |
| 184 | } |
| 185 | EXPORT_SYMBOL_GPL(nf_ct_handle_fragments); |
| 186 | |