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. */
nf_ct_helper(struct sk_buff * skb,struct nf_conn * ct,enum ip_conntrack_info ctinfo,u16 proto)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, sizeof(struct ipv6hdr), &nexthdr,
46 				       &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 
nf_ct_add_helper(struct nf_conn * ct,const char * name,u8 family,u8 proto,bool nat,struct nf_conntrack_helper ** hp)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, family, 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, family, 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  */
nf_ct_skb_network_trim(struct sk_buff * skb,int family)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, &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  */
nf_ct_handle_fragments(struct net * net,struct sk_buff * skb,u16 zone,u8 family,u8 * proto,u16 * mru)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