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