The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
network.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 50c1a9020f4408ea255115b41e0a99d161c96cc5 $
19 * @file lib/bio/network.c
20 * @brief BIO patricia trie filtering handlers
21 *
22 * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
23 */
24
25#include <freeradius-devel/util/value.h>
26#include <freeradius-devel/util/trie.h>
27
28#include <freeradius-devel/bio/bio_priv.h>
29#include <freeradius-devel/bio/fd_priv.h>
30
31#include <freeradius-devel/bio/network.h>
32
33/** The network filtering bio
34 */
35typedef struct {
37
38 fr_bio_read_t discard; //!< callback to run when discarding a packet due to filtering
39
40 size_t offset; //!< where #fr_bio_fd_packet_ctx_t is stored
41
42 fr_trie_t const *trie; //!< patricia trie for filtering
44
45/** Read a UDP packet, and only return packets from allowed sources.
46 *
47 */
48static ssize_t fr_bio_network_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
49{
50 ssize_t rcode;
51 bool *value;
52 fr_bio_network_t *my = talloc_get_type_abort(bio, fr_bio_network_t);
54 fr_bio_t *next;
55
56 next = fr_bio_next(&my->bio);
57 fr_assert(next != NULL);
58
59 rcode = next->read(next, packet_ctx, buffer, size);
60 if (rcode <= 0) return rcode;
61
62 if (!packet_ctx) return rcode;
63
64 addr = fr_bio_fd_packet_ctx(my, packet_ctx);
65
66 /*
67 * Look up this particular source. If it's not found, then we suppress this packet.
68 */
70 &addr->socket.inet.src_ipaddr.addr, addr->socket.inet.src_ipaddr.prefix);
72 if (my->discard) return my->discard(bio, packet_ctx, buffer, rcode);
73 return 0;
74 }
75
76 return rcode;
77}
78
79
80/** Allocate a bio for filtering IP addresses
81 *
82 * This is used for unconnected UDP bios, where we filter packets based on source IP address.
83 *
84 * It is also used for accept bios, where we filter new connections based on source IP address. The caller
85 * should chain this bio to the next FD bio, and then fr_bio_read() from the top-level bio. The result will
86 * be filtered or "clean" FDs.
87 *
88 * A patricia trie (but not the bio) could also be used in an haproxy "activate" callback, where the callback
89 * gets the haproxy socket info, and then checks if the source is allowed. However, that patricia trie is a
90 * property of the main "accept" bio, and should be managed by the activate() callback for the haproxy bio.
91 */
92fr_bio_t *fr_bio_network_alloc(TALLOC_CTX *ctx, fr_ipaddr_t const *allow, fr_ipaddr_t const *deny,
93 fr_bio_read_t discard, fr_bio_t *next)
94{
96 fr_bio_t *fd;
97 fr_bio_fd_info_t const *info;
98
99 /*
100 * We are only usable for FD bios. We need to get "offset" into the packet_ctx, and we don't
101 * want to have an API which allows for two different "offset" values to be passed to two
102 * different bios.
103 */
104 fd = NULL;
105
106 /*
107 * @todo - add an internal "type" to the bio?
108 */
109 do {
110 if (strcmp(talloc_get_name(next), "fr_bio_fd_t") == 0) {
111 fd = next;
112 break;
113 }
114 } while ((next = fr_bio_next(next)) != NULL);
115
116 if (!fd) return NULL;
117
118 info = fr_bio_fd_info(fd);
119 fr_assert(info != NULL);
120
121 /*
122 * We can only filter connections for IP address families.
123 *
124 * Unix domain sockets have to use a different method for filtering input connections.
125 */
126 if (!((info->socket.af == AF_INET) || (info->socket.af == AF_INET6))) return NULL;
127
128 /*
129 * We can only be used for accept() sockets, or unconnected UDP sockets.
130 */
131 switch (info->type) {
133 break;
134
138 return NULL;
139
140 case FR_BIO_FD_LISTEN:
141 break;
142 }
143
144 my = talloc_zero(ctx, fr_bio_network_t);
145 if (!my) return NULL;
146
147 my->offset = ((fr_bio_fd_t *) fd)->offset;
148 my->discard = discard;
149
150 my->bio.write = fr_bio_next_write;
151 my->bio.read = fr_bio_network_read;
152
153 my->trie = fr_bio_network_trie_alloc(my, info->socket.af, allow, deny);
154 if (!my->trie) {
156 return NULL;
157 }
158
159 fr_bio_chain(&my->bio, next);
160
161 return (fr_bio_t *) my;
162}
163
164/** Create a patricia trie for doing network filtering.
165 *
166 */
167fr_trie_t *fr_bio_network_trie_alloc(TALLOC_CTX *ctx, int af, fr_ipaddr_t const *allow, fr_ipaddr_t const *deny)
168{
169 size_t i, num;
170 fr_trie_t *trie;
171
172 trie = fr_trie_alloc(ctx, NULL, NULL);
173 if (!trie) return NULL;
174
175 num = talloc_array_length(allow);
176 fr_assert(num > 0);
177
178 for (i = 0; i < num; i++) {
179 bool *value;
180
181 /*
182 * Can't add v4 networks to a v6 socket, or vice versa.
183 */
184 if (allow[i].af != af) {
185 fr_strerror_printf("Address family in entry %zu - 'allow = %pV' "
186 "does not match 'ipaddr'", i + 1, fr_box_ipaddr(allow[i]));
187 fail:
188 talloc_free(trie);
189 return NULL;
190 }
191
192 /*
193 * Duplicates are bad.
194 */
195 value = fr_trie_match_by_key(trie, &allow[i].addr, allow[i].prefix);
196 if (value) {
197 fr_strerror_printf("Cannot add duplicate entry 'allow = %pV'",
198 fr_box_ipaddr(allow[i]));
199 goto fail;
200 }
201
202#if 0
203 /*
204 * Look for overlapping entries. i.e. the networks MUST be disjoint.
205 *
206 * Note that this catches 192.168.1/24 followed by 192.168/16, but NOT the other way
207 * around. The best fix is likely to add a flag to fr_trie_alloc() saying "we can only
208 * have terminal fr_trie_user_t nodes"
209 */
210 value = fr_trie_lookup_by_key(trie, &allow[i].addr, allow[i].prefix);
211 if (network && (network->prefix <= allow[i].prefix)) {
212 fr_strerror_printf("Cannot add overlapping entry 'allow = %pV'", fr_box_ipaddr(allow[i]));
213 fr_strerror_const("Entry is completely enclosed inside of a previously defined network.");
214 goto fail;
215 }
216#endif
217
218 /*
219 * Insert the network into the trie. Lookups will return a bool ptr of allow / deny.
220 */
221 if (fr_trie_insert_by_key(trie, &allow[i].addr, allow[i].prefix, FR_BIO_NETWORK_ALLOW) < 0) {
222 fr_strerror_printf("Failed adding 'allow = %pV' to filtering rules", fr_box_ipaddr(allow[i]));
223 return NULL;
224 }
225 }
226
227 /*
228 * And now check denied networks.
229 */
230 num = talloc_array_length(deny);
231 if (!num) return trie;
232
233 /*
234 * Since the default is to deny, you can only add a "deny" inside of a previous "allow".
235 */
236 for (i = 0; i < num; i++) {
237 bool *value;
238
239 /*
240 * Can't add v4 networks to a v6 socket, or vice versa.
241 */
242 if (deny[i].af != af) {
243 fr_strerror_printf("Address family in entry %zu - 'deny = %pV' "
244 "does not match 'ipaddr'", i + 1, fr_box_ipaddr(deny[i]));
245 goto fail;
246 }
247
248 /*
249 * Exact duplicates are forbidden.
250 */
251 value = fr_trie_match_by_key(trie, &deny[i].addr, deny[i].prefix);
252 if (value) {
253 fr_strerror_printf("Cannot add duplicate entry 'deny = %pV'", fr_box_ipaddr(deny[i]));
254 goto fail;
255 }
256
257 /*
258 * A "deny" can only be within a previous "allow".
259 */
260 value = fr_trie_lookup_by_key(trie, &deny[i].addr, deny[i].prefix);
261 if (!value) {
262 fr_strerror_printf("The network in entry %zu - 'deny = %pV' is not "
263 "contained within a previous 'allow'", i + 1, fr_box_ipaddr(deny[i]));
264 goto fail;
265 }
266
267 /*
268 * A "deny" cannot be within a previous "deny".
269 */
270 if (value == FR_BIO_NETWORK_DENY) {
271 fr_strerror_printf("The network in entry %zu - 'deny = %pV' is overlaps "
272 "with another 'deny' rule", i + 1, fr_box_ipaddr(deny[i]));
273 goto fail;
274 }
275
276 /*
277 * Insert the rule into the trie.
278 */
279 if (fr_trie_insert_by_key(trie, &deny[i].addr, deny[i].prefix, FR_BIO_NETWORK_DENY) < 0) {
280 fr_strerror_printf("Failed adding 'deny = %pV' to filtering rules", fr_box_ipaddr(deny[i]));
281 return NULL;
282 }
283 }
284
285 return trie;
286}
static int const char char buffer[256]
Definition acutest.h:576
fr_bio_read_t _CONST read
read from the underlying bio
Definition base.h:115
ssize_t(* fr_bio_read_t)(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Do a raw read from a socket, or other data source.
Definition base.h:80
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:130
size_t offset
where fr_bio_fd_packet_ctx_t is stored
Definition network.c:40
fr_trie_t * fr_bio_network_trie_alloc(TALLOC_CTX *ctx, int af, fr_ipaddr_t const *allow, fr_ipaddr_t const *deny)
Create a patricia trie for doing network filtering.
Definition network.c:167
fr_bio_t * fr_bio_network_alloc(TALLOC_CTX *ctx, fr_ipaddr_t const *allow, fr_ipaddr_t const *deny, fr_bio_read_t discard, fr_bio_t *next)
Allocate a bio for filtering IP addresses.
Definition network.c:92
fr_bio_read_t discard
callback to run when discarding a packet due to filtering
Definition network.c:38
static ssize_t fr_bio_network_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Read a UDP packet, and only return packets from allowed sources.
Definition network.c:48
fr_trie_t const * trie
patricia trie for filtering
Definition network.c:42
The network filtering bio.
Definition network.c:35
#define FR_BIO_NETWORK_DENY
Definition network.h:44
#define FR_BIO_NETWORK_ALLOW
Definition network.h:43
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:69
Test enumeration values.
Definition dict_test.h:92
fr_bio_fd_info_t const * fr_bio_fd_info(fr_bio_t *bio)
Returns a pointer to the bio-specific information.
Definition fd.c:1338
fr_socket_t socket
as connected socket
Definition fd.h:125
@ FR_BIO_FD_ACCEPTED
temporarily until it's connected.
Definition fd.h:71
@ FR_BIO_FD_CONNECTED
connected client sockets (UDP or TCP)
Definition fd.h:68
@ FR_BIO_FD_INVALID
not set
Definition fd.h:64
@ FR_BIO_FD_UNCONNECTED
unconnected UDP / datagram only
Definition fd.h:65
@ FR_BIO_FD_LISTEN
returns new fd in buffer on fr_bio_read() or fr_bio_fd_accept()
Definition fd.h:69
fr_bio_fd_type_t type
type of the socket
Definition fd.h:127
fr_socket_t socket
socket information, including FD.
Definition fd.h:52
Run-time status of the socket.
Definition fd.h:124
Per-packet context.
Definition fd.h:51
fr_bio_shutdown & my
Definition fd_errno.h:59
#define fr_bio_fd_packet_ctx(_my, _packet_ctx)
Definition fd_priv.h:59
Our FD bio structure.
Definition fd_priv.h:35
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
Definition inet.h:69
IPv4/6 prefix.
ssize_t fr_bio_next_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Internal bio function which just writes to the "next" bio.
Definition base.c:74
talloc_free(reap)
long int ssize_t
#define fr_assert(_expr)
Definition rad_assert.h:38
fr_trie_t * fr_trie_alloc(TALLOC_CTX *ctx, fr_trie_key_t get_key, fr_free_t free_data)
Allocate a trie.
Definition trie.c:741
void * fr_trie_lookup_by_key(fr_trie_t const *ft, void const *key, size_t keylen)
Lookup a key in a trie and return user ctx, if any.
Definition trie.c:1262
void * fr_trie_match_by_key(fr_trie_t const *ft, void const *key, size_t keylen)
Match a key and length in a trie and return user ctx, if any.
Definition trie.c:1286
int fr_trie_insert_by_key(fr_trie_t *ft, void const *key, size_t keylen, void const *data)
Insert a key and user ctx into a trie.
Definition trie.c:1875
int af
AF_INET, AF_INET6, or AF_UNIX.
Definition socket.h:78
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
#define fr_box_ipaddr(_val)
Definition value.h:294