1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * nf_nat_snmp_basic.c |
4 | * |
5 | * Basic SNMP Application Layer Gateway |
6 | * |
7 | * This IP NAT module is intended for use with SNMP network |
8 | * discovery and monitoring applications where target networks use |
9 | * conflicting private address realms. |
10 | * |
11 | * Static NAT is used to remap the networks from the view of the network |
12 | * management system at the IP layer, and this module remaps some application |
13 | * layer addresses to match. |
14 | * |
15 | * The simplest form of ALG is performed, where only tagged IP addresses |
16 | * are modified. The module does not need to be MIB aware and only scans |
17 | * messages at the ASN.1/BER level. |
18 | * |
19 | * Currently, only SNMPv1 and SNMPv2 are supported. |
20 | * |
21 | * More information on ALG and associated issues can be found in |
22 | * RFC 2962 |
23 | * |
24 | * The ASB.1/BER parsing code is derived from the gxsnmp package by Gregory |
25 | * McLean & Jochen Friedrich, stripped down for use in the kernel. |
26 | * |
27 | * Copyright (c) 2000 RP Internet (www.rpi.net.au). |
28 | * |
29 | * Author: James Morris <jmorris@intercode.com.au> |
30 | * |
31 | * Copyright (c) 2006-2010 Patrick McHardy <kaber@trash.net> |
32 | */ |
33 | #include <linux/module.h> |
34 | #include <linux/moduleparam.h> |
35 | #include <linux/types.h> |
36 | #include <linux/kernel.h> |
37 | #include <linux/in.h> |
38 | #include <linux/ip.h> |
39 | #include <linux/udp.h> |
40 | #include <net/checksum.h> |
41 | #include <net/udp.h> |
42 | |
43 | #include <net/netfilter/nf_nat.h> |
44 | #include <net/netfilter/nf_conntrack_expect.h> |
45 | #include <net/netfilter/nf_conntrack_helper.h> |
46 | #include <linux/netfilter/nf_conntrack_snmp.h> |
47 | #include "nf_nat_snmp_basic.asn1.h" |
48 | |
49 | MODULE_LICENSE("GPL" ); |
50 | MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>" ); |
51 | MODULE_DESCRIPTION("Basic SNMP Application Layer Gateway" ); |
52 | MODULE_ALIAS("ip_nat_snmp_basic" ); |
53 | MODULE_ALIAS_NFCT_HELPER("snmp_trap" ); |
54 | |
55 | #define SNMP_PORT 161 |
56 | #define SNMP_TRAP_PORT 162 |
57 | |
58 | static DEFINE_SPINLOCK(snmp_lock); |
59 | |
60 | struct snmp_ctx { |
61 | unsigned char *begin; |
62 | __sum16 *check; |
63 | __be32 from; |
64 | __be32 to; |
65 | }; |
66 | |
67 | static void fast_csum(struct snmp_ctx *ctx, unsigned char offset) |
68 | { |
69 | unsigned char s[12] = {0,}; |
70 | int size; |
71 | |
72 | if (offset & 1) { |
73 | memcpy(&s[1], &ctx->from, 4); |
74 | memcpy(&s[7], &ctx->to, 4); |
75 | s[0] = ~0; |
76 | s[1] = ~s[1]; |
77 | s[2] = ~s[2]; |
78 | s[3] = ~s[3]; |
79 | s[4] = ~s[4]; |
80 | s[5] = ~0; |
81 | size = 12; |
82 | } else { |
83 | memcpy(&s[0], &ctx->from, 4); |
84 | memcpy(&s[4], &ctx->to, 4); |
85 | s[0] = ~s[0]; |
86 | s[1] = ~s[1]; |
87 | s[2] = ~s[2]; |
88 | s[3] = ~s[3]; |
89 | size = 8; |
90 | } |
91 | *ctx->check = csum_fold(sum: csum_partial(buff: s, len: size, |
92 | sum: ~csum_unfold(n: *ctx->check))); |
93 | } |
94 | |
95 | int snmp_version(void *context, size_t hdrlen, unsigned char tag, |
96 | const void *data, size_t datalen) |
97 | { |
98 | if (datalen != 1) |
99 | return -EINVAL; |
100 | if (*(unsigned char *)data > 1) |
101 | return -ENOTSUPP; |
102 | return 1; |
103 | } |
104 | |
105 | int snmp_helper(void *context, size_t hdrlen, unsigned char tag, |
106 | const void *data, size_t datalen) |
107 | { |
108 | struct snmp_ctx *ctx = (struct snmp_ctx *)context; |
109 | __be32 *pdata; |
110 | |
111 | if (datalen != 4) |
112 | return -EINVAL; |
113 | pdata = (__be32 *)data; |
114 | if (*pdata == ctx->from) { |
115 | pr_debug("%s: %pI4 to %pI4\n" , __func__, |
116 | (void *)&ctx->from, (void *)&ctx->to); |
117 | |
118 | if (*ctx->check) |
119 | fast_csum(ctx, offset: (unsigned char *)data - ctx->begin); |
120 | *pdata = ctx->to; |
121 | } |
122 | |
123 | return 1; |
124 | } |
125 | |
126 | static int snmp_translate(struct nf_conn *ct, int dir, struct sk_buff *skb) |
127 | { |
128 | struct iphdr *iph = ip_hdr(skb); |
129 | struct udphdr *udph = (struct udphdr *)((__be32 *)iph + iph->ihl); |
130 | u16 datalen = ntohs(udph->len) - sizeof(struct udphdr); |
131 | char *data = (unsigned char *)udph + sizeof(struct udphdr); |
132 | struct snmp_ctx ctx; |
133 | int ret; |
134 | |
135 | if (dir == IP_CT_DIR_ORIGINAL) { |
136 | ctx.from = ct->tuplehash[dir].tuple.src.u3.ip; |
137 | ctx.to = ct->tuplehash[!dir].tuple.dst.u3.ip; |
138 | } else { |
139 | ctx.from = ct->tuplehash[!dir].tuple.src.u3.ip; |
140 | ctx.to = ct->tuplehash[dir].tuple.dst.u3.ip; |
141 | } |
142 | |
143 | if (ctx.from == ctx.to) |
144 | return NF_ACCEPT; |
145 | |
146 | ctx.begin = (unsigned char *)udph + sizeof(struct udphdr); |
147 | ctx.check = &udph->check; |
148 | ret = asn1_ber_decoder(&nf_nat_snmp_basic_decoder, &ctx, data, datalen); |
149 | if (ret < 0) { |
150 | nf_ct_helper_log(skb, ct, fmt: "parser failed\n" ); |
151 | return NF_DROP; |
152 | } |
153 | |
154 | return NF_ACCEPT; |
155 | } |
156 | |
157 | /* We don't actually set up expectations, just adjust internal IP |
158 | * addresses if this is being NATted |
159 | */ |
160 | static int help(struct sk_buff *skb, unsigned int protoff, |
161 | struct nf_conn *ct, |
162 | enum ip_conntrack_info ctinfo) |
163 | { |
164 | int dir = CTINFO2DIR(ctinfo); |
165 | unsigned int ret; |
166 | const struct iphdr *iph = ip_hdr(skb); |
167 | const struct udphdr *udph = (struct udphdr *)((__be32 *)iph + iph->ihl); |
168 | |
169 | /* SNMP replies and originating SNMP traps get mangled */ |
170 | if (udph->source == htons(SNMP_PORT) && dir != IP_CT_DIR_REPLY) |
171 | return NF_ACCEPT; |
172 | if (udph->dest == htons(SNMP_TRAP_PORT) && dir != IP_CT_DIR_ORIGINAL) |
173 | return NF_ACCEPT; |
174 | |
175 | /* No NAT? */ |
176 | if (!(ct->status & IPS_NAT_MASK)) |
177 | return NF_ACCEPT; |
178 | |
179 | /* Make sure the packet length is ok. So far, we were only guaranteed |
180 | * to have a valid length IP header plus 8 bytes, which means we have |
181 | * enough room for a UDP header. Just verify the UDP length field so we |
182 | * can mess around with the payload. |
183 | */ |
184 | if (ntohs(udph->len) != skb->len - (iph->ihl << 2)) { |
185 | nf_ct_helper_log(skb, ct, fmt: "dropping malformed packet\n" ); |
186 | return NF_DROP; |
187 | } |
188 | |
189 | if (skb_ensure_writable(skb, write_len: skb->len)) { |
190 | nf_ct_helper_log(skb, ct, fmt: "cannot mangle packet" ); |
191 | return NF_DROP; |
192 | } |
193 | |
194 | spin_lock_bh(lock: &snmp_lock); |
195 | ret = snmp_translate(ct, dir, skb); |
196 | spin_unlock_bh(lock: &snmp_lock); |
197 | return ret; |
198 | } |
199 | |
200 | static const struct nf_conntrack_expect_policy snmp_exp_policy = { |
201 | .max_expected = 0, |
202 | .timeout = 180, |
203 | }; |
204 | |
205 | static struct nf_conntrack_helper snmp_trap_helper __read_mostly = { |
206 | .me = THIS_MODULE, |
207 | .help = help, |
208 | .expect_policy = &snmp_exp_policy, |
209 | .name = "snmp_trap" , |
210 | .tuple.src.l3num = AF_INET, |
211 | .tuple.src.u.udp.port = cpu_to_be16(SNMP_TRAP_PORT), |
212 | .tuple.dst.protonum = IPPROTO_UDP, |
213 | }; |
214 | |
215 | static int __init nf_nat_snmp_basic_init(void) |
216 | { |
217 | BUG_ON(nf_nat_snmp_hook != NULL); |
218 | RCU_INIT_POINTER(nf_nat_snmp_hook, help); |
219 | |
220 | return nf_conntrack_helper_register(&snmp_trap_helper); |
221 | } |
222 | |
223 | static void __exit nf_nat_snmp_basic_fini(void) |
224 | { |
225 | RCU_INIT_POINTER(nf_nat_snmp_hook, NULL); |
226 | synchronize_rcu(); |
227 | nf_conntrack_helper_unregister(&snmp_trap_helper); |
228 | } |
229 | |
230 | module_init(nf_nat_snmp_basic_init); |
231 | module_exit(nf_nat_snmp_basic_fini); |
232 | |