The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 48fda491369cb10dc471045f89e858d6976eed62 $
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  */
35 typedef 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  */
48 static 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);
71  if (value != FR_BIO_NETWORK_ALLOW) {
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  */
92 fr_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 {
95  fr_bio_network_t *my;
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 
135  case FR_BIO_FD_CONNECTED:
136  return NULL;
137 
138  case FR_BIO_FD_ACCEPT:
139  break;
140  }
141 
142  my = talloc_zero(ctx, fr_bio_network_t);
143  if (!my) return NULL;
144 
145  my->offset = ((fr_bio_fd_t *) fd)->offset;
146  my->discard = discard;
147 
148  my->bio.write = fr_bio_next_write;
149  my->bio.read = fr_bio_network_read;
150 
151  my->trie = fr_bio_network_trie_alloc(my, info->socket.af, allow, deny);
152  if (!my->trie) {
153  talloc_free(my);
154  return NULL;
155  }
156 
157  fr_bio_chain(&my->bio, next);
158 
159  return (fr_bio_t *) my;
160 }
161 
162 /** Create a patricia trie for doing network filtering.
163  *
164  */
165 fr_trie_t *fr_bio_network_trie_alloc(TALLOC_CTX *ctx, int af, fr_ipaddr_t const *allow, fr_ipaddr_t const *deny)
166 {
167  size_t i, num;
168  fr_trie_t *trie;
169 
170  trie = fr_trie_alloc(ctx, NULL, NULL);
171  if (!trie) return NULL;
172 
173  num = talloc_array_length(allow);
174  fr_assert(num > 0);
175 
176  for (i = 0; i < num; i++) {
177  bool *value;
178 
179  /*
180  * Can't add v4 networks to a v6 socket, or vice versa.
181  */
182  if (allow[i].af != af) {
183  fr_strerror_printf("Address family in entry %zd - 'allow = %pV' "
184  "does not match 'ipaddr'", i + 1, fr_box_ipaddr(allow[i]));
185  fail:
186  talloc_free(trie);
187  return NULL;
188  }
189 
190  /*
191  * Duplicates are bad.
192  */
193  value = fr_trie_match_by_key(trie, &allow[i].addr, allow[i].prefix);
194  if (value) {
195  fr_strerror_printf("Cannot add duplicate entry 'allow = %pV'",
196  fr_box_ipaddr(allow[i]));
197  goto fail;
198  }
199 
200 #if 0
201  /*
202  * Look for overlapping entries. i.e. the networks MUST be disjoint.
203  *
204  * Note that this catches 192.168.1/24 followed by 192.168/16, but NOT the other way
205  * around. The best fix is likely to add a flag to fr_trie_alloc() saying "we can only
206  * have terminal fr_trie_user_t nodes"
207  */
208  value = fr_trie_lookup_by_key(trie, &allow[i].addr, allow[i].prefix);
209  if (network && (network->prefix <= allow[i].prefix)) {
210  fr_strerror_printf("Cannot add overlapping entry 'allow = %pV'", fr_box_ipaddr(allow[i]));
211  fr_strerror_const("Entry is completely enclosed inside of a previously defined network.");
212  goto fail;
213  }
214 #endif
215 
216  /*
217  * Insert the network into the trie. Lookups will return a bool ptr of allow / deny.
218  */
219  if (fr_trie_insert_by_key(trie, &allow[i].addr, allow[i].prefix, FR_BIO_NETWORK_ALLOW) < 0) {
220  fr_strerror_printf("Failed adding 'allow = %pV' to filtering rules", fr_box_ipaddr(allow[i]));
221  return NULL;
222  }
223  }
224 
225  /*
226  * And now check denied networks.
227  */
228  num = talloc_array_length(deny);
229  if (!num) return trie;
230 
231  /*
232  * Since the default is to deny, you can only add a "deny" inside of a previous "allow".
233  */
234  for (i = 0; i < num; i++) {
235  bool *value;
236 
237  /*
238  * Can't add v4 networks to a v6 socket, or vice versa.
239  */
240  if (deny[i].af != af) {
241  fr_strerror_printf("Address family in entry %zd - 'deny = %pV' "
242  "does not match 'ipaddr'", i + 1, fr_box_ipaddr(deny[i]));
243  goto fail;
244  }
245 
246  /*
247  * Exact duplicates are forbidden.
248  */
249  value = fr_trie_match_by_key(trie, &deny[i].addr, deny[i].prefix);
250  if (value) {
251  fr_strerror_printf("Cannot add duplicate entry 'deny = %pV'", fr_box_ipaddr(deny[i]));
252  goto fail;
253  }
254 
255  /*
256  * A "deny" can only be within a previous "allow".
257  */
258  value = fr_trie_lookup_by_key(trie, &deny[i].addr, deny[i].prefix);
259  if (!value) {
260  fr_strerror_printf("The network in entry %zd - 'deny = %pV' is not "
261  "contained within a previous 'allow'", i + 1, fr_box_ipaddr(deny[i]));
262  goto fail;
263  }
264 
265  /*
266  * A "deny" cannot be within a previous "deny".
267  */
268  if (value == FR_BIO_NETWORK_DENY) {
269  fr_strerror_printf("The network in entry %zd - 'deny = %pV' is overlaps "
270  "with another 'deny' rule", i + 1, fr_box_ipaddr(deny[i]));
271  goto fail;
272  }
273 
274  /*
275  * Insert the rule into the trie.
276  */
277  if (fr_trie_insert_by_key(trie, &deny[i].addr, deny[i].prefix, FR_BIO_NETWORK_DENY) < 0) {
278  fr_strerror_printf("Failed adding 'deny = %pV' to filtering rules", fr_box_ipaddr(deny[i]));
279  return NULL;
280  }
281  }
282 
283  return trie;
284 }
static int const char char buffer[256]
Definition: acutest.h:574
fr_bio_read_t _CONST read
read from the underlying bio
Definition: base.h:106
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:81
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition: base.h:121
Definition: base.h:103
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:165
fr_bio_read_t discard
callback to run when discarding a packet due to filtering
Definition: network.c:38
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
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:57
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:1167
fr_socket_t socket
as connected socket
Definition: fd.h:108
@ FR_BIO_FD_CONNECTED
connected client sockets (UDP or TCP)
Definition: fd.h:64
@ FR_BIO_FD_UNCONNECTED
unconnected UDP / datagram only
Definition: fd.h:61
@ FR_BIO_FD_ACCEPT
returns new fd in buffer on fr_bio_read()
Definition: fd.h:65
fr_bio_fd_type_t type
type of the socket
Definition: fd.h:110
fr_socket_t socket
socket information, including FD.
Definition: fd.h:49
Run-time status of the socket.
Definition: fd.h:107
Per-packet context.
Definition: fd.h:48
#define fr_bio_fd_packet_ctx(_my, _packet_ctx)
Definition: fd_priv.h:51
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.
Definition: merged_model.c:272
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:82
talloc_free(reap)
long int ssize_t
Definition: merged_model.c:24
static fr_bio_t * bio
Definition: radclient-ng.c:86
fr_assert(0)
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:743
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:1288
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:1877
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:1264
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:287