1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* SANE connection tracking helper |
3 | * (SANE = Scanner Access Now Easy) |
4 | * For documentation about the SANE network protocol see |
5 | * http://www.sane-project.org/html/doc015.html |
6 | */ |
7 | |
8 | /* Copyright (C) 2007 Red Hat, Inc. |
9 | * Author: Michal Schmidt <mschmidt@redhat.com> |
10 | * Based on the FTP conntrack helper (net/netfilter/nf_conntrack_ftp.c): |
11 | * (C) 1999-2001 Paul `Rusty' Russell |
12 | * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> |
13 | * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org> |
14 | * (C) 2003 Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp> |
15 | */ |
16 | |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> |
21 | #include <linux/netfilter.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/in.h> |
24 | #include <linux/tcp.h> |
25 | #include <net/netfilter/nf_conntrack.h> |
26 | #include <net/netfilter/nf_conntrack_helper.h> |
27 | #include <net/netfilter/nf_conntrack_expect.h> |
28 | #include <linux/netfilter/nf_conntrack_sane.h> |
29 | |
30 | #define HELPER_NAME "sane" |
31 | |
32 | MODULE_LICENSE("GPL" ); |
33 | MODULE_AUTHOR("Michal Schmidt <mschmidt@redhat.com>" ); |
34 | MODULE_DESCRIPTION("SANE connection tracking helper" ); |
35 | MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); |
36 | |
37 | #define MAX_PORTS 8 |
38 | static u_int16_t ports[MAX_PORTS]; |
39 | static unsigned int ports_c; |
40 | module_param_array(ports, ushort, &ports_c, 0400); |
41 | |
42 | struct sane_request { |
43 | __be32 RPC_code; |
44 | #define SANE_NET_START 7 /* RPC code */ |
45 | |
46 | __be32 handle; |
47 | }; |
48 | |
49 | struct sane_reply_net_start { |
50 | __be32 status; |
51 | #define SANE_STATUS_SUCCESS 0 |
52 | |
53 | __be16 zero; |
54 | __be16 port; |
55 | /* other fields aren't interesting for conntrack */ |
56 | }; |
57 | |
58 | static int help(struct sk_buff *skb, |
59 | unsigned int protoff, |
60 | struct nf_conn *ct, |
61 | enum ip_conntrack_info ctinfo) |
62 | { |
63 | unsigned int dataoff, datalen; |
64 | const struct tcphdr *th; |
65 | struct tcphdr _tcph; |
66 | int ret = NF_ACCEPT; |
67 | int dir = CTINFO2DIR(ctinfo); |
68 | struct nf_ct_sane_master *ct_sane_info = nfct_help_data(ct); |
69 | struct nf_conntrack_expect *exp; |
70 | struct nf_conntrack_tuple *tuple; |
71 | struct sane_reply_net_start *reply; |
72 | union { |
73 | struct sane_request req; |
74 | struct sane_reply_net_start repl; |
75 | } buf; |
76 | |
77 | /* Until there's been traffic both ways, don't look in packets. */ |
78 | if (ctinfo != IP_CT_ESTABLISHED && |
79 | ctinfo != IP_CT_ESTABLISHED_REPLY) |
80 | return NF_ACCEPT; |
81 | |
82 | /* Not a full tcp header? */ |
83 | th = skb_header_pointer(skb, offset: protoff, len: sizeof(_tcph), buffer: &_tcph); |
84 | if (th == NULL) |
85 | return NF_ACCEPT; |
86 | |
87 | /* No data? */ |
88 | dataoff = protoff + th->doff * 4; |
89 | if (dataoff >= skb->len) |
90 | return NF_ACCEPT; |
91 | |
92 | datalen = skb->len - dataoff; |
93 | if (dir == IP_CT_DIR_ORIGINAL) { |
94 | const struct sane_request *req; |
95 | |
96 | if (datalen != sizeof(struct sane_request)) |
97 | return NF_ACCEPT; |
98 | |
99 | req = skb_header_pointer(skb, offset: dataoff, len: datalen, buffer: &buf.req); |
100 | if (!req) |
101 | return NF_ACCEPT; |
102 | |
103 | if (req->RPC_code != htonl(SANE_NET_START)) { |
104 | /* Not an interesting command */ |
105 | WRITE_ONCE(ct_sane_info->state, SANE_STATE_NORMAL); |
106 | return NF_ACCEPT; |
107 | } |
108 | |
109 | /* We're interested in the next reply */ |
110 | WRITE_ONCE(ct_sane_info->state, SANE_STATE_START_REQUESTED); |
111 | return NF_ACCEPT; |
112 | } |
113 | |
114 | /* IP_CT_DIR_REPLY */ |
115 | |
116 | /* Is it a reply to an uninteresting command? */ |
117 | if (READ_ONCE(ct_sane_info->state) != SANE_STATE_START_REQUESTED) |
118 | return NF_ACCEPT; |
119 | |
120 | /* It's a reply to SANE_NET_START. */ |
121 | WRITE_ONCE(ct_sane_info->state, SANE_STATE_NORMAL); |
122 | |
123 | if (datalen < sizeof(struct sane_reply_net_start)) { |
124 | pr_debug("NET_START reply too short\n" ); |
125 | return NF_ACCEPT; |
126 | } |
127 | |
128 | datalen = sizeof(struct sane_reply_net_start); |
129 | |
130 | reply = skb_header_pointer(skb, offset: dataoff, len: datalen, buffer: &buf.repl); |
131 | if (!reply) |
132 | return NF_ACCEPT; |
133 | |
134 | if (reply->status != htonl(SANE_STATUS_SUCCESS)) { |
135 | /* saned refused the command */ |
136 | pr_debug("unsuccessful SANE_STATUS = %u\n" , |
137 | ntohl(reply->status)); |
138 | return NF_ACCEPT; |
139 | } |
140 | |
141 | /* Invalid saned reply? Ignore it. */ |
142 | if (reply->zero != 0) |
143 | return NF_ACCEPT; |
144 | |
145 | exp = nf_ct_expect_alloc(me: ct); |
146 | if (exp == NULL) { |
147 | nf_ct_helper_log(skb, ct, fmt: "cannot alloc expectation" ); |
148 | return NF_DROP; |
149 | } |
150 | |
151 | tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; |
152 | nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct), |
153 | &tuple->src.u3, &tuple->dst.u3, |
154 | IPPROTO_TCP, NULL, &reply->port); |
155 | |
156 | pr_debug("expect: " ); |
157 | nf_ct_dump_tuple(t: &exp->tuple); |
158 | |
159 | /* Can't expect this? Best to drop packet now. */ |
160 | if (nf_ct_expect_related(expect: exp, flags: 0) != 0) { |
161 | nf_ct_helper_log(skb, ct, fmt: "cannot add expectation" ); |
162 | ret = NF_DROP; |
163 | } |
164 | |
165 | nf_ct_expect_put(exp); |
166 | return ret; |
167 | } |
168 | |
169 | static struct nf_conntrack_helper sane[MAX_PORTS * 2] __read_mostly; |
170 | |
171 | static const struct nf_conntrack_expect_policy sane_exp_policy = { |
172 | .max_expected = 1, |
173 | .timeout = 5 * 60, |
174 | }; |
175 | |
176 | static void __exit nf_conntrack_sane_fini(void) |
177 | { |
178 | nf_conntrack_helpers_unregister(sane, ports_c * 2); |
179 | } |
180 | |
181 | static int __init nf_conntrack_sane_init(void) |
182 | { |
183 | int i, ret = 0; |
184 | |
185 | NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sane_master)); |
186 | |
187 | if (ports_c == 0) |
188 | ports[ports_c++] = SANE_PORT; |
189 | |
190 | /* FIXME should be configurable whether IPv4 and IPv6 connections |
191 | are tracked or not - YK */ |
192 | for (i = 0; i < ports_c; i++) { |
193 | nf_ct_helper_init(helper: &sane[2 * i], AF_INET, IPPROTO_TCP, |
194 | HELPER_NAME, SANE_PORT, spec_port: ports[i], id: ports[i], |
195 | exp_pol: &sane_exp_policy, expect_class_max: 0, help, NULL, |
196 | THIS_MODULE); |
197 | nf_ct_helper_init(helper: &sane[2 * i + 1], AF_INET6, IPPROTO_TCP, |
198 | HELPER_NAME, SANE_PORT, spec_port: ports[i], id: ports[i], |
199 | exp_pol: &sane_exp_policy, expect_class_max: 0, help, NULL, |
200 | THIS_MODULE); |
201 | } |
202 | |
203 | ret = nf_conntrack_helpers_register(sane, ports_c * 2); |
204 | if (ret < 0) { |
205 | pr_err("failed to register helpers\n" ); |
206 | return ret; |
207 | } |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | module_init(nf_conntrack_sane_init); |
213 | module_exit(nf_conntrack_sane_fini); |
214 | |