The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
haproxy.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: e7ae459732710f7eab4029f6b7dada6bb81de475 $
19 * @file lib/bio/haproxy.c
20 * @brief BIO abstractions for HA proxy protocol interceptors
21 *
22 * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
23 */
24
25#include <freeradius-devel/bio/bio_priv.h>
26#include <freeradius-devel/bio/null.h>
27#include <freeradius-devel/bio/buf.h>
28
29#include <freeradius-devel/bio/haproxy.h>
30
31#define HAPROXY_HEADER_V1_SIZE (108)
32
33/** The haproxy bio
34 *
35 */
36typedef struct {
38
39 fr_bio_haproxy_info_t info; //!< Information about the "real" client which has connected.
40 // @todo - for v2 of the haproxy protocol, add TLS parameters!
41
42 fr_bio_buf_t buffer; //!< intermediate buffer to read the haproxy header
43
44 bool available; //!< is the haxproxy header available and done
46
47/** Parse the haproxy header, version 1.
48 *
49 */
51{
52 int af, argc;
53 unsigned long port;
54 ssize_t rcode;
55 uint8_t *p, *end;
56 char *eos, *argv[5] = {};
57
58 p = my->buffer.read;
59 end = my->buffer.write;
60
61 fr_assert(fr_bio_buf_used(&my->buffer) >= 9);
62
63 /*
64 * We only support v1, and only TCP.
65 */
66 if (memcmp(my->buffer.read, "PROXY TCP", 9) != 0) {
67 fail:
68 (void) fr_bio_shutdown(&my->bio);
69 return fr_bio_error(VERIFY);
70 }
71 p += 9;
72
73 if (*p == '4') {
74 af = AF_INET;
75
76 } else if (*p == '6') {
77 af = AF_INET6;
78
79 } else {
80 goto fail;
81 }
82 p++;
83
84 if (*(p++) != ' ') goto fail;
85
86 argc = 0;
87 rcode = -1;
88 while (p < end) {
89 if (*p > ' ') {
90 if (argc > 4) goto fail;
91
92 argv[argc++] = (char *) p;
93
94 while ((p < end) && (*p > ' ')) p++;
95 continue;
96 }
97
98 if (*p < ' ') {
99 if ((end - p) < 2) goto fail;
100
101 if (memcmp(p, "\r\n", 2) != 0) goto fail;
102
103 *p = '\0';
104 end = p + 2;
105 rcode = 0;
106 break;
107 }
108
109 if (*p != ' ') goto fail;
110
111 *(p++) = '\0';
112 }
113
114 /*
115 * Didn't end with CRLF and zero.
116 */
117 if (rcode < 0) {
118 fr_strerror_const("haproxy v1 header did not end with CRLF");
119 goto fail;
120 }
121
122 /*
123 *
124 */
125 if (argc != 4) {
126 fr_strerror_const("haproxy v1 header did not have 4 parameters");
127 goto fail;
128 }
129
130 if (fr_inet_pton(&my->info.socket.inet.src_ipaddr, argv[0], -1, af, false, false) < 0) goto fail;
131 if (fr_inet_pton(&my->info.socket.inet.dst_ipaddr, argv[1], -1, af, false, false) < 0) goto fail;
132
133 port = strtoul(argv[2], &eos, 10);
134 if (port > 65535) goto fail;
135 if (*eos) goto fail;
136 my->info.socket.inet.src_port = port;
137
138 port = strtoul(argv[3], &eos, 10);
139 if (port > 65535) goto fail;
140 if (*eos) goto fail;
141 my->info.socket.inet.dst_port = port;
142
143 /*
144 * Return how many bytes we read. The remainder are for the application.
145 */
146 return (end - my->buffer.read);
147}
148
149/** Satisfy reads from the "next" bio
150 *
151 * The caveat is that there may be data left in our buffer which is needed for the application. We can't
152 * unchain ourselves until we've returned that data to the application, and emptied our buffer.
153 */
154static ssize_t fr_bio_haproxy_read_next(fr_bio_t *bio, UNUSED void *packet_ctx, void *buffer, size_t size)
155{
156 size_t used;
157 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
158
159 my->available = true;
160
161 used = fr_bio_buf_used(&my->buffer);
162
163 /*
164 * Somehow (magically) we can satisfy the read from our buffer. Do so. Note that we do NOT run
165 * the connected callback, as there is still data in our buffer
166 */
167 if (size < used) {
168 (void) fr_bio_buf_read(&my->buffer, buffer, size);
169 return size;
170 }
171
172 /*
173 * We are asked to empty the buffer. Copy the data to the caller.
174 */
175 (void) fr_bio_buf_read(&my->buffer, buffer, used);
176
177 /*
178 * Call the users "socket is now usable" function, which might remove us from the proxy chain.
179 */
180 if (my->cb.connected) my->cb.connected(bio);
181
182 return used;
183}
184
185/** Read from the next bio, and determine if we have an haproxy header.
186 *
187 */
188static ssize_t fr_bio_haproxy_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
189{
190 ssize_t rcode;
191 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
192 fr_bio_t *next;
193
194 next = fr_bio_next(&my->bio);
195 fr_assert(next != NULL);
196
197 fr_assert(fr_bio_buf_write_room(&my->buffer) > 0);
198
199 rcode = next->read(next, NULL, my->buffer.write, fr_bio_buf_write_room(&my->buffer));
200 if (rcode <= 0) return rcode;
201
202 fr_bio_buf_write_alloc(&my->buffer, rcode);
203
204 /*
205 * Not enough room for a full v1 header, tell the caller
206 * that no data was read. The caller should call us
207 * again when the underlying FD is readable.
208 */
209 if (fr_bio_buf_used(&my->buffer) < 16) return 0;
210
211 /*
212 * Process haproxy protocol v1 header.
213 */
214 rcode = fr_bio_haproxy_v1(my);
215 if (rcode <= 0) return rcode;
216
217 /*
218 * We've read a number of bytes from our buffer. The remaining ones are for the application.
219 */
220 (void) fr_bio_buf_read(&my->buffer, NULL, rcode);
221 my->bio.read = fr_bio_haproxy_read_next;
222
223 return fr_bio_haproxy_read_next(bio, packet_ctx, buffer, size);
224}
225
226/** Allocate an haproxy bio.
227 *
228 */
230{
232
233 my = talloc_zero(ctx, fr_bio_haproxy_t);
234 if (!my) return NULL;
235
236 if (fr_bio_buf_alloc(my, &my->buffer, HAPROXY_HEADER_V1_SIZE) < 0) {
238 return NULL;
239 }
240
241 my->bio.read = fr_bio_haproxy_read;
242 my->bio.write = fr_bio_null_write; /* can't write to this bio */
243 my->cb = *cb;
244
245 fr_bio_chain(&my->bio, next);
246
247 talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
248 return (fr_bio_t *) my;
249}
250
251/** Get client information from the haproxy bio.
252 *
253 */
255{
256 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
257
258 if (!my->available) return NULL;
259
260 return &my->info;
261}
static int const char char buffer[256]
Definition acutest.h:578
fr_bio_write_t _CONST write
write to the underlying bio
Definition base.h:117
fr_bio_read_t _CONST read
read from the underlying bio
Definition base.h:116
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:131
#define fr_bio_error(_x)
Definition base.h:200
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:84
size_t fr_bio_buf_read(fr_bio_buf_t *bio_buf, void *buffer, size_t size)
Definition buf.c:48
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
Definition buf.c:117
static size_t fr_bio_buf_write_room(fr_bio_buf_t const *bio_buf)
Definition buf.h:82
static size_t fr_bio_buf_used(fr_bio_buf_t const *bio_buf)
Definition buf.h:73
uint8_t * read
where in the buffer reads are taken from
Definition buf.h:33
static int fr_bio_buf_write_alloc(fr_bio_buf_t *bio_buf, size_t size)
Definition buf.h:98
#define UNUSED
Definition build.h:317
void fr_bio_shutdown & my
Definition fd_errno.h:70
fr_bio_t * fr_bio_haproxy_alloc(TALLOC_CTX *ctx, fr_bio_cb_funcs_t *cb, fr_bio_t *next)
Allocate an haproxy bio.
Definition haproxy.c:229
static ssize_t fr_bio_haproxy_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Read from the next bio, and determine if we have an haproxy header.
Definition haproxy.c:188
bool available
is the haxproxy header available and done
Definition haproxy.c:44
static ssize_t fr_bio_haproxy_v1(fr_bio_haproxy_t *my)
Parse the haproxy header, version 1.
Definition haproxy.c:50
#define HAPROXY_HEADER_V1_SIZE
Definition haproxy.c:31
fr_bio_haproxy_info_t info
Information about the "real" client which has connected.
Definition haproxy.c:39
fr_bio_haproxy_info_t const * fr_bio_haproxy_info(fr_bio_t *bio)
Get client information from the haproxy bio.
Definition haproxy.c:254
fr_bio_buf_t buffer
intermediate buffer to read the haproxy header
Definition haproxy.c:42
static ssize_t fr_bio_haproxy_read_next(fr_bio_t *bio, UNUSED void *packet_ctx, void *buffer, size_t size)
Satisfy reads from the "next" bio.
Definition haproxy.c:154
The haproxy bio.
Definition haproxy.c:36
Data structure which describes the "real" client connection.
Definition haproxy.h:41
talloc_free(hp)
int fr_inet_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, int af, bool resolve, bool mask)
Simple wrapper to decide whether an IP value is v4 or v6 and call the appropriate parser.
Definition inet.c:784
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
Definition base.c:35
int fr_bio_shutdown(fr_bio_t *bio)
Shut down a set of BIOs.
Definition base.c:98
long int ssize_t
unsigned char uint8_t
static size_t used
ssize_t fr_bio_null_write(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
Always return 0 on write.
Definition null.c:39
#define fr_assert(_expr)
Definition rad_assert.h:38
#define VERIFY(_x)
Definition trie.c:130
#define fr_strerror_const(_msg)
Definition strerror.h:223