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: 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  */
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  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  */
185 static 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) {
232  talloc_free(my);
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:574
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition: base.h:130
#define fr_bio_error(_x)
Definition: base.h:192
Definition: base.h:112
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
#define UNUSED
Definition: build.h:313
next
Definition: dcursor.h:178
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_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:249
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
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
fr_assert(0)
#define VERIFY(_x)
Definition: trie.c:130
#define fr_strerror_const(_msg)
Definition: strerror.h:223