GNU Linux-libre 6.8.7-gnu
[releases.git] / net / netfilter / nf_conntrack_ovs.c
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. */
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
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  */
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  */
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);