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