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/ip.h>
7 
8 /* 'skb' should already be pulled to nh_ofs. */
9 int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct,
10 		 enum ip_conntrack_info ctinfo, u16 proto)
11 {
12 	const struct nf_conntrack_helper *helper;
13 	const struct nf_conn_help *help;
14 	unsigned int protoff;
15 	int err;
16 
17 	if (ctinfo == IP_CT_RELATED_REPLY)
18 		return NF_ACCEPT;
19 
20 	help = nfct_help(ct);
21 	if (!help)
22 		return NF_ACCEPT;
23 
24 	helper = rcu_dereference(help->helper);
25 	if (!helper)
26 		return NF_ACCEPT;
27 
28 	if (helper->tuple.src.l3num != NFPROTO_UNSPEC &&
29 	    helper->tuple.src.l3num != proto)
30 		return NF_ACCEPT;
31 
32 	switch (proto) {
33 	case NFPROTO_IPV4:
34 		protoff = ip_hdrlen(skb);
35 		proto = ip_hdr(skb)->protocol;
36 		break;
37 	case NFPROTO_IPV6: {
38 		u8 nexthdr = ipv6_hdr(skb)->nexthdr;
39 		__be16 frag_off;
40 		int ofs;
41 
42 		ofs = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
43 				       &frag_off);
44 		if (ofs < 0 || (frag_off & htons(~0x7)) != 0) {
45 			pr_debug("proto header not found\n");
46 			return NF_ACCEPT;
47 		}
48 		protoff = ofs;
49 		proto = nexthdr;
50 		break;
51 	}
52 	default:
53 		WARN_ONCE(1, "helper invoked on non-IP family!");
54 		return NF_DROP;
55 	}
56 
57 	if (helper->tuple.dst.protonum != proto)
58 		return NF_ACCEPT;
59 
60 	err = helper->help(skb, protoff, ct, ctinfo);
61 	if (err != NF_ACCEPT)
62 		return err;
63 
64 	/* Adjust seqs after helper.  This is needed due to some helpers (e.g.,
65 	 * FTP with NAT) adusting the TCP payload size when mangling IP
66 	 * addresses and/or port numbers in the text-based control connection.
67 	 */
68 	if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
69 	    !nf_ct_seq_adjust(skb, ct, ctinfo, protoff))
70 		return NF_DROP;
71 	return NF_ACCEPT;
72 }
73 EXPORT_SYMBOL_GPL(nf_ct_helper);
74 
75 int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family,
76 		     u8 proto, bool nat, struct nf_conntrack_helper **hp)
77 {
78 	struct nf_conntrack_helper *helper;
79 	struct nf_conn_help *help;
80 	int ret = 0;
81 
82 	helper = nf_conntrack_helper_try_module_get(name, family, proto);
83 	if (!helper)
84 		return -EINVAL;
85 
86 	help = nf_ct_helper_ext_add(ct, GFP_KERNEL);
87 	if (!help) {
88 		nf_conntrack_helper_put(helper);
89 		return -ENOMEM;
90 	}
91 #if IS_ENABLED(CONFIG_NF_NAT)
92 	if (nat) {
93 		ret = nf_nat_helper_try_module_get(name, family, proto);
94 		if (ret) {
95 			nf_conntrack_helper_put(helper);
96 			return ret;
97 		}
98 	}
99 #endif
100 	rcu_assign_pointer(help->helper, helper);
101 	*hp = helper;
102 	return ret;
103 }
104 EXPORT_SYMBOL_GPL(nf_ct_add_helper);
105 
106 /* Trim the skb to the length specified by the IP/IPv6 header,
107  * removing any trailing lower-layer padding. This prepares the skb
108  * for higher-layer processing that assumes skb->len excludes padding
109  * (such as nf_ip_checksum). The caller needs to pull the skb to the
110  * network header, and ensure ip_hdr/ipv6_hdr points to valid data.
111  */
112 int nf_ct_skb_network_trim(struct sk_buff *skb, int family)
113 {
114 	unsigned int len;
115 
116 	switch (family) {
117 	case NFPROTO_IPV4:
118 		len = skb_ip_totlen(skb);
119 		break;
120 	case NFPROTO_IPV6:
121 		len = sizeof(struct ipv6hdr)
122 			+ ntohs(ipv6_hdr(skb)->payload_len);
123 		break;
124 	default:
125 		len = skb->len;
126 	}
127 
128 	return pskb_trim_rcsum(skb, len);
129 }
130 EXPORT_SYMBOL_GPL(nf_ct_skb_network_trim);
131