blob: 0ea7d79faa373abb37fde049e5a138a2e719a177 [file] [log] [blame]
Willy Tarreau8987e7a2020-08-28 11:37:21 +02001/*
2 * AF_INET/AF_INET6 SOCK_STREAM protocol layer (tcp)
3 *
4 * Copyright 2000-2013 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13/* this is to have tcp_info defined on systems using musl
14 * library, such as Alpine Linux.
15 */
16#define _GNU_SOURCE
17
18#include <ctype.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <time.h>
25
26#include <sys/param.h>
27#include <sys/socket.h>
28#include <sys/types.h>
29
30#include <netinet/tcp.h>
31#include <netinet/in.h>
32
33#include <haproxy/api.h>
34#include <haproxy/arg.h>
35#include <haproxy/connection.h>
Willy Tarreau6cd007d2021-10-06 19:01:21 +020036#include <haproxy/errors.h>
Willy Tarreau8987e7a2020-08-28 11:37:21 +020037#include <haproxy/global.h>
38#include <haproxy/listener-t.h>
39#include <haproxy/namespace.h>
40#include <haproxy/proxy-t.h>
41#include <haproxy/sample.h>
Willy Tarreau6cd007d2021-10-06 19:01:21 +020042#include <haproxy/session-t.h>
Willy Tarreau8987e7a2020-08-28 11:37:21 +020043#include <haproxy/tools.h>
44
45
Christopher Faulet7d081f02021-04-15 09:38:37 +020046/* Fetch the connection's source IPv4/IPv6 address. Depending on the keyword, it
47 * may be the frontend or the backend connection.
Willy Tarreau8987e7a2020-08-28 11:37:21 +020048 */
49static int
50smp_fetch_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
51{
Christopher Faulet003df1c2021-04-15 09:39:38 +020052 struct connection *conn;
53
54 if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
55 conn = (kw[0] == 'b') ? cs_conn(__objt_check(smp->sess->origin)->cs) : NULL;
56 else
57 conn = (kw[0] != 'b') ? objt_conn(smp->sess->origin) :
58 smp->strm ? cs_conn(objt_cs(smp->strm->si[1].end)) : NULL;
Willy Tarreau8987e7a2020-08-28 11:37:21 +020059
Christopher Faulet7d081f02021-04-15 09:38:37 +020060 if (!conn)
Willy Tarreau8987e7a2020-08-28 11:37:21 +020061 return 0;
62
Christopher Faulet7d081f02021-04-15 09:38:37 +020063 if (!conn_get_src(conn))
Willy Tarreau8987e7a2020-08-28 11:37:21 +020064 return 0;
65
Christopher Faulet7d081f02021-04-15 09:38:37 +020066 switch (conn->src->ss_family) {
Willy Tarreau8987e7a2020-08-28 11:37:21 +020067 case AF_INET:
Christopher Faulet7d081f02021-04-15 09:38:37 +020068 smp->data.u.ipv4 = ((struct sockaddr_in *)conn->src)->sin_addr;
Willy Tarreau8987e7a2020-08-28 11:37:21 +020069 smp->data.type = SMP_T_IPV4;
70 break;
71 case AF_INET6:
Christopher Faulet7d081f02021-04-15 09:38:37 +020072 smp->data.u.ipv6 = ((struct sockaddr_in6 *)conn->src)->sin6_addr;
Willy Tarreau8987e7a2020-08-28 11:37:21 +020073 smp->data.type = SMP_T_IPV6;
74 break;
75 default:
76 return 0;
77 }
78
79 smp->flags = 0;
80 return 1;
81}
82
Christopher Faulet7d081f02021-04-15 09:38:37 +020083/* set temp integer to the connection's source port. Depending on the
84 * keyword, it may be the frontend or the backend connection.
85 */
Willy Tarreau8987e7a2020-08-28 11:37:21 +020086static int
Christopher Faulet7d081f02021-04-15 09:38:37 +020087smp_fetch_sport(const struct arg *args, struct sample *smp, const char *kw, void *private)
Willy Tarreau8987e7a2020-08-28 11:37:21 +020088{
Christopher Faulet003df1c2021-04-15 09:39:38 +020089 struct connection *conn;
90
91 if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
92 conn = (kw[0] == 'b') ? cs_conn(__objt_check(smp->sess->origin)->cs) : NULL;
93 else
94 conn = (kw[0] != 'b') ? objt_conn(smp->sess->origin) :
95 smp->strm ? cs_conn(objt_cs(smp->strm->si[1].end)) : NULL;
Willy Tarreau8987e7a2020-08-28 11:37:21 +020096
Christopher Faulet7d081f02021-04-15 09:38:37 +020097 if (!conn)
Willy Tarreau8987e7a2020-08-28 11:37:21 +020098 return 0;
99
Christopher Faulet7d081f02021-04-15 09:38:37 +0200100 if (!conn_get_src(conn))
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200101 return 0;
102
103 smp->data.type = SMP_T_SINT;
Christopher Faulet7d081f02021-04-15 09:38:37 +0200104 if (!(smp->data.u.sint = get_host_port(conn->src)))
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200105 return 0;
106
107 smp->flags = 0;
108 return 1;
109}
110
Christopher Faulet7d081f02021-04-15 09:38:37 +0200111/* fetch the connection's destination IPv4/IPv6 address. Depending on the
112 * keyword, it may be the frontend or the backend connection.
113 */
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200114static int
115smp_fetch_dst(const struct arg *args, struct sample *smp, const char *kw, void *private)
116{
Christopher Faulet003df1c2021-04-15 09:39:38 +0200117 struct connection *conn;
118
119 if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
120 conn = (kw[0] == 'b') ? cs_conn(__objt_check(smp->sess->origin)->cs) : NULL;
121 else
122 conn = (kw[0] != 'b') ? objt_conn(smp->sess->origin) :
123 smp->strm ? cs_conn(objt_cs(smp->strm->si[1].end)) : NULL;
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200124
Christopher Faulet7d081f02021-04-15 09:38:37 +0200125 if (!conn)
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200126 return 0;
127
Christopher Faulet7d081f02021-04-15 09:38:37 +0200128 if (!conn_get_dst(conn))
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200129 return 0;
130
Christopher Faulet7d081f02021-04-15 09:38:37 +0200131 switch (conn->dst->ss_family) {
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200132 case AF_INET:
Christopher Faulet7d081f02021-04-15 09:38:37 +0200133 smp->data.u.ipv4 = ((struct sockaddr_in *)conn->dst)->sin_addr;
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200134 smp->data.type = SMP_T_IPV4;
135 break;
136 case AF_INET6:
Christopher Faulet7d081f02021-04-15 09:38:37 +0200137 smp->data.u.ipv6 = ((struct sockaddr_in6 *)conn->dst)->sin6_addr;
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200138 smp->data.type = SMP_T_IPV6;
139 break;
140 default:
141 return 0;
142 }
143
144 smp->flags = 0;
145 return 1;
146}
147
148/* check if the destination address of the front connection is local to the
149 * system or if it was intercepted.
150 */
151int smp_fetch_dst_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private)
152{
153 struct connection *conn = objt_conn(smp->sess->origin);
154 struct listener *li = smp->sess->listener;
155
156 if (!conn)
157 return 0;
158
159 if (!conn_get_dst(conn))
160 return 0;
161
162 smp->data.type = SMP_T_BOOL;
163 smp->flags = 0;
Willy Tarreau818a92e2020-09-03 07:50:19 +0200164 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->dst);
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200165 return smp->data.u.sint >= 0;
166}
167
168/* check if the source address of the front connection is local to the system
169 * or not.
170 */
171int smp_fetch_src_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private)
172{
173 struct connection *conn = objt_conn(smp->sess->origin);
174 struct listener *li = smp->sess->listener;
175
176 if (!conn)
177 return 0;
178
179 if (!conn_get_src(conn))
180 return 0;
181
182 smp->data.type = SMP_T_BOOL;
183 smp->flags = 0;
Willy Tarreau818a92e2020-09-03 07:50:19 +0200184 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->src);
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200185 return smp->data.u.sint >= 0;
186}
187
Christopher Faulet7d081f02021-04-15 09:38:37 +0200188/* set temp integer to the connexion's destination port. Depending on the
189 * keyword, it may be the frontend or the backend connection.
190 */
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200191static int
192smp_fetch_dport(const struct arg *args, struct sample *smp, const char *kw, void *private)
193{
Christopher Faulet003df1c2021-04-15 09:39:38 +0200194 struct connection *conn;
195
196 if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
197 conn = (kw[0] == 'b') ? cs_conn(__objt_check(smp->sess->origin)->cs) : NULL;
198 else
199 conn = (kw[0] != 'b') ? objt_conn(smp->sess->origin) :
200 smp->strm ? cs_conn(objt_cs(smp->strm->si[1].end)) : NULL;
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200201
Christopher Faulet7d081f02021-04-15 09:38:37 +0200202 if (!conn)
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200203 return 0;
204
Christopher Faulet7d081f02021-04-15 09:38:37 +0200205 if (!conn_get_dst(conn))
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200206 return 0;
207
208 smp->data.type = SMP_T_SINT;
Christopher Faulet7d081f02021-04-15 09:38:37 +0200209 if (!(smp->data.u.sint = get_host_port(conn->dst)))
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200210 return 0;
211
212 smp->flags = 0;
213 return 1;
214}
215
216#ifdef TCP_INFO
217
218
219/* Validates the arguments passed to "fc_*" fetch keywords returning a time
220 * value. These keywords support an optional string representing the unit of the
221 * result: "us" for microseconds and "ms" for milliseconds". Returns 0 on error
222 * and non-zero if OK.
223 */
224static int val_fc_time_value(struct arg *args, char **err)
225{
226 if (args[0].type == ARGT_STR) {
227 if (strcmp(args[0].data.str.area, "us") == 0) {
228 chunk_destroy(&args[0].data.str);
229 args[0].type = ARGT_SINT;
230 args[0].data.sint = TIME_UNIT_US;
231 }
232 else if (strcmp(args[0].data.str.area, "ms") == 0) {
233 chunk_destroy(&args[0].data.str);
234 args[0].type = ARGT_SINT;
235 args[0].data.sint = TIME_UNIT_MS;
236 }
237 else {
238 memprintf(err, "expects 'us' or 'ms', got '%s'",
239 args[0].data.str.area);
240 return 0;
241 }
242 }
243 else {
244 memprintf(err, "Unexpected arg type");
245 return 0;
246 }
247
248 return 1;
249}
250
251/* Validates the arguments passed to "fc_*" fetch keywords returning a
252 * counter. These keywords should be used without any keyword, but because of a
253 * bug in previous versions, an optional string argument may be passed. In such
254 * case, the argument is ignored and a warning is emitted. Returns 0 on error
255 * and non-zero if OK.
256 */
257static int var_fc_counter(struct arg *args, char **err)
258{
259 if (args[0].type != ARGT_STOP) {
260 ha_warning("no argument supported for 'fc_*' sample expressions returning counters.\n");
261 if (args[0].type == ARGT_STR)
262 chunk_destroy(&args[0].data.str);
263 args[0].type = ARGT_STOP;
264 }
265
266 return 1;
267}
268
269/* Returns some tcp_info data if it's available. "dir" must be set to 0 if
270 * the client connection is required, otherwise it is set to 1. "val" represents
271 * the required value.
272 * If the function fails it returns 0, otherwise it returns 1 and "result" is filled.
273 */
274static inline int get_tcp_info(const struct arg *args, struct sample *smp,
275 int dir, int val)
276{
277 struct connection *conn;
278 struct tcp_info info;
279 socklen_t optlen;
280
281 /* strm can be null. */
282 if (!smp->strm)
283 return 0;
284
285 /* get the object associated with the stream interface.The
286 * object can be other thing than a connection. For example,
287 * it be a appctx. */
288 conn = cs_conn(objt_cs(smp->strm->si[dir].end));
289 if (!conn)
290 return 0;
291
292 /* The fd may not be available for the tcp_info struct, and the
293 syscal can fail. */
294 optlen = sizeof(info);
Willy Tarreau4bfc6632021-03-31 08:45:47 +0200295 if (getsockopt(conn->handle.fd, IPPROTO_TCP, TCP_INFO, &info, &optlen) == -1)
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200296 return 0;
297
298 /* extract the value. */
299 smp->data.type = SMP_T_SINT;
300 switch (val) {
301 case 0: smp->data.u.sint = info.tcpi_rtt; break;
302 case 1: smp->data.u.sint = info.tcpi_rttvar; break;
303#if defined(__linux__)
304 /* these ones are common to all Linux versions */
305 case 2: smp->data.u.sint = info.tcpi_unacked; break;
306 case 3: smp->data.u.sint = info.tcpi_sacked; break;
307 case 4: smp->data.u.sint = info.tcpi_lost; break;
308 case 5: smp->data.u.sint = info.tcpi_retrans; break;
309 case 6: smp->data.u.sint = info.tcpi_fackets; break;
310 case 7: smp->data.u.sint = info.tcpi_reordering; break;
311#elif defined(__FreeBSD__) || defined(__NetBSD__)
312 /* the ones are found on FreeBSD and NetBSD featuring TCP_INFO */
313 case 2: smp->data.u.sint = info.__tcpi_unacked; break;
314 case 3: smp->data.u.sint = info.__tcpi_sacked; break;
315 case 4: smp->data.u.sint = info.__tcpi_lost; break;
316 case 5: smp->data.u.sint = info.__tcpi_retrans; break;
317 case 6: smp->data.u.sint = info.__tcpi_fackets; break;
318 case 7: smp->data.u.sint = info.__tcpi_reordering; break;
319#endif
320 default: return 0;
321 }
322
323 return 1;
324}
325
326/* get the mean rtt of a client connection */
327static int
328smp_fetch_fc_rtt(const struct arg *args, struct sample *smp, const char *kw, void *private)
329{
330 if (!get_tcp_info(args, smp, 0, 0))
331 return 0;
332
333 /* By default or if explicitly specified, convert rtt to ms */
334 if (!args || args[0].type == ARGT_STOP || args[0].data.sint == TIME_UNIT_MS)
335 smp->data.u.sint = (smp->data.u.sint + 500) / 1000;
336
337 return 1;
338}
339
340/* get the variance of the mean rtt of a client connection */
341static int
342smp_fetch_fc_rttvar(const struct arg *args, struct sample *smp, const char *kw, void *private)
343{
344 if (!get_tcp_info(args, smp, 0, 1))
345 return 0;
346
347 /* By default or if explicitly specified, convert rttvar to ms */
348 if (!args || args[0].type == ARGT_STOP || args[0].data.sint == TIME_UNIT_MS)
349 smp->data.u.sint = (smp->data.u.sint + 500) / 1000;
350
351 return 1;
352}
353
354#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
355
356/* get the unacked counter on a client connection */
357static int
358smp_fetch_fc_unacked(const struct arg *args, struct sample *smp, const char *kw, void *private)
359{
360 if (!get_tcp_info(args, smp, 0, 2))
361 return 0;
362 return 1;
363}
364
365/* get the sacked counter on a client connection */
366static int
367smp_fetch_fc_sacked(const struct arg *args, struct sample *smp, const char *kw, void *private)
368{
369 if (!get_tcp_info(args, smp, 0, 3))
370 return 0;
371 return 1;
372}
373
374/* get the lost counter on a client connection */
375static int
376smp_fetch_fc_lost(const struct arg *args, struct sample *smp, const char *kw, void *private)
377{
378 if (!get_tcp_info(args, smp, 0, 4))
379 return 0;
380 return 1;
381}
382
383/* get the retrans counter on a client connection */
384static int
385smp_fetch_fc_retrans(const struct arg *args, struct sample *smp, const char *kw, void *private)
386{
387 if (!get_tcp_info(args, smp, 0, 5))
388 return 0;
389 return 1;
390}
391
392/* get the fackets counter on a client connection */
393static int
394smp_fetch_fc_fackets(const struct arg *args, struct sample *smp, const char *kw, void *private)
395{
396 if (!get_tcp_info(args, smp, 0, 6))
397 return 0;
398 return 1;
399}
400
401/* get the reordering counter on a client connection */
402static int
403smp_fetch_fc_reordering(const struct arg *args, struct sample *smp, const char *kw, void *private)
404{
405 if (!get_tcp_info(args, smp, 0, 7))
406 return 0;
407 return 1;
408}
409#endif // linux || freebsd || netbsd
410#endif // TCP_INFO
411
412/* Note: must not be declared <const> as its list will be overwritten.
413 * Note: fetches that may return multiple types must be declared as the lowest
414 * common denominator, the type that can be casted into all other ones. For
415 * instance v4/v6 must be declared v4.
416 */
417static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet7d081f02021-04-15 09:38:37 +0200418 { "bc_dst", smp_fetch_dst, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
419 { "bc_dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
420 { "bc_src", smp_fetch_src, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
421 { "bc_src_port", smp_fetch_sport, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
422
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200423 { "dst", smp_fetch_dst, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI },
424 { "dst_is_local", smp_fetch_dst_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
425 { "dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
426 { "src", smp_fetch_src, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI },
427 { "src_is_local", smp_fetch_src_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
428 { "src_port", smp_fetch_sport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
429#ifdef TCP_INFO
430 { "fc_rtt", smp_fetch_fc_rtt, ARG1(0,STR), val_fc_time_value, SMP_T_SINT, SMP_USE_L4CLI },
431 { "fc_rttvar", smp_fetch_fc_rttvar, ARG1(0,STR), val_fc_time_value, SMP_T_SINT, SMP_USE_L4CLI },
432#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
433 { "fc_unacked", smp_fetch_fc_unacked, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
434 { "fc_sacked", smp_fetch_fc_sacked, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
435 { "fc_retrans", smp_fetch_fc_retrans, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
436 { "fc_fackets", smp_fetch_fc_fackets, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
437 { "fc_lost", smp_fetch_fc_lost, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
438 { "fc_reordering", smp_fetch_fc_reordering, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
439#endif // linux || freebsd || netbsd
440#endif // TCP_INFO
441 { /* END */ },
442}};
443
444INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
445
446
447/*
448 * Local variables:
449 * c-indent-level: 8
450 * c-basic-offset: 8
451 * End:
452 */