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. */
12int 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}
76EXPORT_SYMBOL_GPL(nf_ct_helper);
77
78int 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}
107EXPORT_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 */
115int 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}
139EXPORT_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 */
144int 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}
185EXPORT_SYMBOL_GPL(nf_ct_handle_fragments);
186

source code of linux/net/netfilter/nf_conntrack_ovs.c