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: 13be88c2cd32aae609acf1c58b9fc75f23609caf $
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, port;
53 ssize_t rcode;
54 uint8_t *p, *end;
55 char *eos, *argv[5] = {};
56
57 p = my->buffer.read;
58 end = my->buffer.write;
59
60 /*
61 * We only support v1, and only TCP.
62 */
63 if (memcmp(my->buffer.read, "PROXY TCP", 9) != 0) {
64 fail:
65 fr_bio_shutdown(&my->bio);
66 return fr_bio_error(VERIFY);
67 }
68 p += 9;
69
70 if (*p == '4') {
71 af = AF_INET;
72
73 } else if (*p == '6') {
74 af = AF_INET6;
75
76 } else {
77 goto fail;
78 }
79 p++;
80
81 if (*(p++) != ' ') goto fail;
82
83 argc = 0;
84 rcode = -1;
85 while (p < end) {
86 if (*p > ' ') {
87 if (argc > 4) goto fail;
88
89 argv[argc++] = (char *) p;
90
91 while ((*p > ' ') && (p < end)) p++;
92 continue;
93 }
94
95 if (*p < ' ') {
96 if ((end - p) < 3) goto fail;
97
98 if (memcmp(p, "\r\n", 3) != 0) goto fail;
99
100 *p = '\0';
101 end = p + 3;
102 rcode = 0;
103 break;
104 }
105
106 if (*p != ' ') goto fail;
107
108 *(p++) = '\0';
109 }
110
111 /*
112 * Didn't end with CRLF and zero.
113 */
114 if (rcode < 0) {
115 fr_strerror_const("haproxy v1 header did not end with CRLF");
116 goto fail;
117 }
118
119 /*
120 *
121 */
122 if (argc != 4) {
123 fr_strerror_const("haproxy v1 header did not have 4 parameters");
124 goto fail;
125 }
126
127 if (fr_inet_pton(&my->info.socket.inet.src_ipaddr, argv[0], -1, af, false, false) < 0) goto fail;
128 if (fr_inet_pton(&my->info.socket.inet.dst_ipaddr, argv[1], -1, af, false, false) < 0) goto fail;
129
130 port = strtoul(argv[2], &eos, 10);
131 if (port > 65535) goto fail;
132 if (*eos) goto fail;
133 my->info.socket.inet.src_port = port;
134
135 port = strtoul(argv[3], &eos, 10);
136 if (port > 65535) goto fail;
137 if (*eos) goto fail;
138 my->info.socket.inet.dst_port = port;
139
140 /*
141 * Return how many bytes we read. The remainder are for the application.
142 */
143 return (end - my->buffer.read);
144}
145
146/** Satisfy reads from the "next" bio
147 *
148 * The caveat is that there may be data left in our buffer which is needed for the application. We can't
149 * unchain ourselves until we've returned that data to the application, and emptied our buffer.
150 */
151static ssize_t fr_bio_haproxy_read_next(fr_bio_t *bio, UNUSED void *packet_ctx, void *buffer, size_t size)
152{
153 size_t used;
154 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
155
156 my->available = true;
157
158 used = fr_bio_buf_used(&my->buffer);
159
160 /*
161 * Somehow (magically) we can satisfy the read from our buffer. Do so. Note that we do NOT run
162 * the connected callback, as there is still data in our buffer
163 */
164 if (size < used) {
165 (void) fr_bio_buf_read(&my->buffer, buffer, size);
166 return size;
167 }
168
169 /*
170 * We are asked to empty the buffer. Copy the data to the caller.
171 */
172 (void) fr_bio_buf_read(&my->buffer, buffer, used);
173
174 /*
175 * Call the users "socket is now usable" function, which might remove us from the proxy chain.
176 */
177 if (my->cb.connected) my->cb.connected(bio);
178
179 return used;
180}
181
182/** Read from the next bio, and determine if we have an haproxy header.
183 *
184 */
185static ssize_t fr_bio_haproxy_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
186{
187 ssize_t rcode;
188 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
189 fr_bio_t *next;
190
191 next = fr_bio_next(&my->bio);
192 fr_assert(next != NULL);
193
194 fr_assert(fr_bio_buf_write_room(&my->buffer) > 0);
195
196 rcode = next->read(next, NULL, my->buffer.read, fr_bio_buf_write_room(&my->buffer));
197 if (rcode <= 0) return rcode;
198
199 /*
200 * Not enough room for a full v1 header, tell the caller
201 * that no data was read. The caller should call us
202 * again when the underlying FD is readable.
203 */
204 if (fr_bio_buf_used(&my->buffer) < 16) return 0;
205
206 /*
207 * Process haproxy protocol v1 header.
208 */
209 rcode = fr_bio_haproxy_v1(my);
210 if (rcode <= 0) return rcode;
211
212 /*
213 * We've read a number of bytes from our buffer. The remaining ones are for the application.
214 */
215 (void) fr_bio_buf_read(&my->buffer, NULL, rcode);
216 my->bio.read = fr_bio_haproxy_read_next;
217
218 return fr_bio_haproxy_read_next(bio, packet_ctx, buffer, size);
219}
220
221/** Allocate an haproxy bio.
222 *
223 */
225{
227
228 my = talloc_zero(ctx, fr_bio_haproxy_t);
229 if (!my) return NULL;
230
231 if (fr_bio_buf_alloc(my, &my->buffer, HAPROXY_HEADER_V1_SIZE) < 0) {
233 return NULL;
234 }
235
236 my->bio.read = fr_bio_haproxy_read;
237 my->bio.write = fr_bio_null_write; /* can't write to this bio */
238 my->cb = *cb;
239
240 fr_bio_chain(&my->bio, next);
241
242 talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
243 return (fr_bio_t *) my;
244}
245
246/** Get client information from the haproxy bio.
247 *
248 */
250{
251 fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
252
253 if (!my->available) return NULL;
254
255 return &my->info;
256}
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
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:130
#define fr_bio_error(_x)
Definition base.h:192
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:69
size_t fr_bio_buf_read(fr_bio_buf_t *bio_buf, void *buffer, size_t size)
Definition buf.c:45
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
Definition buf.c:114
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
#define UNUSED
Definition build.h:315
fr_bio_shutdown & my
Definition fd_errno.h:59
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:224
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:185
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:249
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:151
The haproxy bio.
Definition haproxy.c:36
Data structure which describes the "real" client connection.
Definition haproxy.h:41
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:778
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
Definition base.c:34
int fr_bio_shutdown(fr_bio_t *bio)
Shut down a set of BIOs.
Definition base.c:141
talloc_free(reap)
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