The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 607403de7b495f9d47a4eb8b9044ccef724fbc2a $
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  */
36 typedef 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  */
151 static ssize_t fr_bio_haproxy_read_next(fr_bio_t *bio, UNUSED void *packet_ctx, void *buffer, size_t size)
152 {
153  ssize_t rcode;
154  size_t used;
155  fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
156 
157  my->available = true;
158 
159  used = fr_bio_buf_used(&my->buffer);
160 
161  /*
162  * Somehow (magically) we can satisfy the read from our buffer. Do so. Note that we do NOT run
163  * the activation callback, as there is still data in our buffer
164  */
165  if (size < used) {
166  (void) fr_bio_buf_read(&my->buffer, buffer, size);
167  return size;
168  }
169 
170  /*
171  * We are asked to empty the buffer. Copy the data to the caller.
172  */
173  (void) fr_bio_buf_read(&my->buffer, buffer, used);
174 
175  /*
176  * Call the users activation function, which might remove us from the proxy chain.
177  */
178  if (my->cb.activate) {
179  rcode = my->cb.activate(bio);
180  if (rcode < 0) return rcode;
181  }
182 
183  return used;
184 }
185 
186 /** Read from the next bio, and determine if we have an haproxy header.
187  *
188  */
189 static ssize_t fr_bio_haproxy_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
190 {
191  ssize_t rcode;
192  fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
193  fr_bio_t *next;
194 
195  next = fr_bio_next(&my->bio);
196  fr_assert(next != NULL);
197 
199 
200  rcode = next->read(next, NULL, my->buffer.read, fr_bio_buf_write_room(&my->buffer));
201  if (rcode <= 0) return rcode;
202 
203  /*
204  * Not enough room for a full v1 header, tell the caller
205  * that no data was read. The caller should call us
206  * again when the underlying FD is readable.
207  */
208  if (fr_bio_buf_used(&my->buffer) < 16) return 0;
209 
210  /*
211  * Process haproxy protocol v1 header.
212  */
213  rcode = fr_bio_haproxy_v1(my);
214  if (rcode <= 0) return rcode;
215 
216  /*
217  * We've read a number of bytes from our buffer. The remaining ones are for the application.
218  */
219  (void) fr_bio_buf_read(&my->buffer, NULL, rcode);
220  my->bio.read = fr_bio_haproxy_read_next;
221 
222  return fr_bio_haproxy_read_next(bio, packet_ctx, buffer, size);
223 }
224 
225 /** Allocate an haproxy bio.
226  *
227  */
229 {
230  fr_bio_haproxy_t *my;
231 
232  my = talloc_zero(ctx, fr_bio_haproxy_t);
233  if (!my) return NULL;
234 
235  if (fr_bio_buf_alloc(my, &my->buffer, HAPROXY_HEADER_V1_SIZE) < 0) {
236  talloc_free(my);
237  return NULL;
238  }
239 
240  my->bio.read = fr_bio_haproxy_read;
241  my->bio.write = fr_bio_null_write; /* can't write to this bio */
242  my->cb = *cb;
243 
244  fr_bio_chain(&my->bio, next);
245 
246  talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
247  return (fr_bio_t *) my;
248 }
249 
250 /** Get client information from the haproxy bio.
251  *
252  */
254 {
255  fr_bio_haproxy_t *my = talloc_get_type_abort(bio, fr_bio_haproxy_t);
256 
257  if (!my->available) return NULL;
258 
259  return &my->info;
260 }
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
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition: base.h:121
#define fr_bio_error(_x)
Definition: base.h:184
Definition: base.h:103
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition: bio_priv.h:57
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
uint8_t * write
where in the buffer writes are sent to
Definition: buf.h:34
#define UNUSED
Definition: build.h:313
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:228
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:189
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_buf_t buffer
intermediate buffer to read the haproxy header
Definition: haproxy.c:42
fr_bio_haproxy_info_t const * fr_bio_haproxy_info(fr_bio_t *bio)
Get client information from the haproxy bio.
Definition: haproxy.c:253
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
fr_socket_t socket
Definition: haproxy.h:42
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:764
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:152
talloc_free(reap)
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
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
static fr_bio_t * bio
Definition: radclient-ng.c:86
fr_assert(0)
#define VERIFY(_x)
Definition: trie.c:130
#define fr_strerror_const(_msg)
Definition: strerror.h:223