blob: 91a5fb306693c377c9885e0fc8da578853aec796 [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>
36#include <haproxy/global.h>
37#include <haproxy/listener-t.h>
38#include <haproxy/namespace.h>
39#include <haproxy/proxy-t.h>
40#include <haproxy/sample.h>
41#include <haproxy/tools.h>
42
43
44/* Fetch the connection's source IPv4/IPv6 address. Note that this is also
45 * directly called by stick_table.c and as such must remain publicly visible.
46 */
47static int
48smp_fetch_src(const struct arg *args, struct sample *smp, const char *kw, void *private)
49{
50 struct connection *cli_conn = objt_conn(smp->sess->origin);
51
52 if (!cli_conn)
53 return 0;
54
55 if (!conn_get_src(cli_conn))
56 return 0;
57
58 switch (cli_conn->src->ss_family) {
59 case AF_INET:
60 smp->data.u.ipv4 = ((struct sockaddr_in *)cli_conn->src)->sin_addr;
61 smp->data.type = SMP_T_IPV4;
62 break;
63 case AF_INET6:
64 smp->data.u.ipv6 = ((struct sockaddr_in6 *)cli_conn->src)->sin6_addr;
65 smp->data.type = SMP_T_IPV6;
66 break;
67 default:
68 return 0;
69 }
70
71 smp->flags = 0;
72 return 1;
73}
74
75/* set temp integer to the connection's source port */
76static int
77smp_fetch_sport(const struct arg *args, struct sample *smp, const char *k, void *private)
78{
79 struct connection *cli_conn = objt_conn(smp->sess->origin);
80
81 if (!cli_conn)
82 return 0;
83
84 if (!conn_get_src(cli_conn))
85 return 0;
86
87 smp->data.type = SMP_T_SINT;
88 if (!(smp->data.u.sint = get_host_port(cli_conn->src)))
89 return 0;
90
91 smp->flags = 0;
92 return 1;
93}
94
95/* fetch the connection's destination IPv4/IPv6 address */
96static int
97smp_fetch_dst(const struct arg *args, struct sample *smp, const char *kw, void *private)
98{
99 struct connection *cli_conn = objt_conn(smp->sess->origin);
100
101 if (!cli_conn)
102 return 0;
103
104 if (!conn_get_dst(cli_conn))
105 return 0;
106
107 switch (cli_conn->dst->ss_family) {
108 case AF_INET:
109 smp->data.u.ipv4 = ((struct sockaddr_in *)cli_conn->dst)->sin_addr;
110 smp->data.type = SMP_T_IPV4;
111 break;
112 case AF_INET6:
113 smp->data.u.ipv6 = ((struct sockaddr_in6 *)cli_conn->dst)->sin6_addr;
114 smp->data.type = SMP_T_IPV6;
115 break;
116 default:
117 return 0;
118 }
119
120 smp->flags = 0;
121 return 1;
122}
123
124/* check if the destination address of the front connection is local to the
125 * system or if it was intercepted.
126 */
127int smp_fetch_dst_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private)
128{
129 struct connection *conn = objt_conn(smp->sess->origin);
130 struct listener *li = smp->sess->listener;
131
132 if (!conn)
133 return 0;
134
135 if (!conn_get_dst(conn))
136 return 0;
137
138 smp->data.type = SMP_T_BOOL;
139 smp->flags = 0;
Willy Tarreau818a92e2020-09-03 07:50:19 +0200140 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->dst);
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200141 return smp->data.u.sint >= 0;
142}
143
144/* check if the source address of the front connection is local to the system
145 * or not.
146 */
147int smp_fetch_src_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private)
148{
149 struct connection *conn = objt_conn(smp->sess->origin);
150 struct listener *li = smp->sess->listener;
151
152 if (!conn)
153 return 0;
154
155 if (!conn_get_src(conn))
156 return 0;
157
158 smp->data.type = SMP_T_BOOL;
159 smp->flags = 0;
Willy Tarreau818a92e2020-09-03 07:50:19 +0200160 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->src);
Willy Tarreau8987e7a2020-08-28 11:37:21 +0200161 return smp->data.u.sint >= 0;
162}
163
164/* set temp integer to the frontend connexion's destination port */
165static int
166smp_fetch_dport(const struct arg *args, struct sample *smp, const char *kw, void *private)
167{
168 struct connection *cli_conn = objt_conn(smp->sess->origin);
169
170 if (!cli_conn)
171 return 0;
172
173 if (!conn_get_dst(cli_conn))
174 return 0;
175
176 smp->data.type = SMP_T_SINT;
177 if (!(smp->data.u.sint = get_host_port(cli_conn->dst)))
178 return 0;
179
180 smp->flags = 0;
181 return 1;
182}
183
184#ifdef TCP_INFO
185
186
187/* Validates the arguments passed to "fc_*" fetch keywords returning a time
188 * value. These keywords support an optional string representing the unit of the
189 * result: "us" for microseconds and "ms" for milliseconds". Returns 0 on error
190 * and non-zero if OK.
191 */
192static int val_fc_time_value(struct arg *args, char **err)
193{
194 if (args[0].type == ARGT_STR) {
195 if (strcmp(args[0].data.str.area, "us") == 0) {
196 chunk_destroy(&args[0].data.str);
197 args[0].type = ARGT_SINT;
198 args[0].data.sint = TIME_UNIT_US;
199 }
200 else if (strcmp(args[0].data.str.area, "ms") == 0) {
201 chunk_destroy(&args[0].data.str);
202 args[0].type = ARGT_SINT;
203 args[0].data.sint = TIME_UNIT_MS;
204 }
205 else {
206 memprintf(err, "expects 'us' or 'ms', got '%s'",
207 args[0].data.str.area);
208 return 0;
209 }
210 }
211 else {
212 memprintf(err, "Unexpected arg type");
213 return 0;
214 }
215
216 return 1;
217}
218
219/* Validates the arguments passed to "fc_*" fetch keywords returning a
220 * counter. These keywords should be used without any keyword, but because of a
221 * bug in previous versions, an optional string argument may be passed. In such
222 * case, the argument is ignored and a warning is emitted. Returns 0 on error
223 * and non-zero if OK.
224 */
225static int var_fc_counter(struct arg *args, char **err)
226{
227 if (args[0].type != ARGT_STOP) {
228 ha_warning("no argument supported for 'fc_*' sample expressions returning counters.\n");
229 if (args[0].type == ARGT_STR)
230 chunk_destroy(&args[0].data.str);
231 args[0].type = ARGT_STOP;
232 }
233
234 return 1;
235}
236
237/* Returns some tcp_info data if it's available. "dir" must be set to 0 if
238 * the client connection is required, otherwise it is set to 1. "val" represents
239 * the required value.
240 * If the function fails it returns 0, otherwise it returns 1 and "result" is filled.
241 */
242static inline int get_tcp_info(const struct arg *args, struct sample *smp,
243 int dir, int val)
244{
245 struct connection *conn;
246 struct tcp_info info;
247 socklen_t optlen;
248
249 /* strm can be null. */
250 if (!smp->strm)
251 return 0;
252
253 /* get the object associated with the stream interface.The
254 * object can be other thing than a connection. For example,
255 * it be a appctx. */
256 conn = cs_conn(objt_cs(smp->strm->si[dir].end));
257 if (!conn)
258 return 0;
259
260 /* The fd may not be available for the tcp_info struct, and the
261 syscal can fail. */
262 optlen = sizeof(info);
263 if (getsockopt(conn->handle.fd, SOL_TCP, TCP_INFO, &info, &optlen) == -1)
264 return 0;
265
266 /* extract the value. */
267 smp->data.type = SMP_T_SINT;
268 switch (val) {
269 case 0: smp->data.u.sint = info.tcpi_rtt; break;
270 case 1: smp->data.u.sint = info.tcpi_rttvar; break;
271#if defined(__linux__)
272 /* these ones are common to all Linux versions */
273 case 2: smp->data.u.sint = info.tcpi_unacked; break;
274 case 3: smp->data.u.sint = info.tcpi_sacked; break;
275 case 4: smp->data.u.sint = info.tcpi_lost; break;
276 case 5: smp->data.u.sint = info.tcpi_retrans; break;
277 case 6: smp->data.u.sint = info.tcpi_fackets; break;
278 case 7: smp->data.u.sint = info.tcpi_reordering; break;
279#elif defined(__FreeBSD__) || defined(__NetBSD__)
280 /* the ones are found on FreeBSD and NetBSD featuring TCP_INFO */
281 case 2: smp->data.u.sint = info.__tcpi_unacked; break;
282 case 3: smp->data.u.sint = info.__tcpi_sacked; break;
283 case 4: smp->data.u.sint = info.__tcpi_lost; break;
284 case 5: smp->data.u.sint = info.__tcpi_retrans; break;
285 case 6: smp->data.u.sint = info.__tcpi_fackets; break;
286 case 7: smp->data.u.sint = info.__tcpi_reordering; break;
287#endif
288 default: return 0;
289 }
290
291 return 1;
292}
293
294/* get the mean rtt of a client connection */
295static int
296smp_fetch_fc_rtt(const struct arg *args, struct sample *smp, const char *kw, void *private)
297{
298 if (!get_tcp_info(args, smp, 0, 0))
299 return 0;
300
301 /* By default or if explicitly specified, convert rtt to ms */
302 if (!args || args[0].type == ARGT_STOP || args[0].data.sint == TIME_UNIT_MS)
303 smp->data.u.sint = (smp->data.u.sint + 500) / 1000;
304
305 return 1;
306}
307
308/* get the variance of the mean rtt of a client connection */
309static int
310smp_fetch_fc_rttvar(const struct arg *args, struct sample *smp, const char *kw, void *private)
311{
312 if (!get_tcp_info(args, smp, 0, 1))
313 return 0;
314
315 /* By default or if explicitly specified, convert rttvar to ms */
316 if (!args || args[0].type == ARGT_STOP || args[0].data.sint == TIME_UNIT_MS)
317 smp->data.u.sint = (smp->data.u.sint + 500) / 1000;
318
319 return 1;
320}
321
322#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
323
324/* get the unacked counter on a client connection */
325static int
326smp_fetch_fc_unacked(const struct arg *args, struct sample *smp, const char *kw, void *private)
327{
328 if (!get_tcp_info(args, smp, 0, 2))
329 return 0;
330 return 1;
331}
332
333/* get the sacked counter on a client connection */
334static int
335smp_fetch_fc_sacked(const struct arg *args, struct sample *smp, const char *kw, void *private)
336{
337 if (!get_tcp_info(args, smp, 0, 3))
338 return 0;
339 return 1;
340}
341
342/* get the lost counter on a client connection */
343static int
344smp_fetch_fc_lost(const struct arg *args, struct sample *smp, const char *kw, void *private)
345{
346 if (!get_tcp_info(args, smp, 0, 4))
347 return 0;
348 return 1;
349}
350
351/* get the retrans counter on a client connection */
352static int
353smp_fetch_fc_retrans(const struct arg *args, struct sample *smp, const char *kw, void *private)
354{
355 if (!get_tcp_info(args, smp, 0, 5))
356 return 0;
357 return 1;
358}
359
360/* get the fackets counter on a client connection */
361static int
362smp_fetch_fc_fackets(const struct arg *args, struct sample *smp, const char *kw, void *private)
363{
364 if (!get_tcp_info(args, smp, 0, 6))
365 return 0;
366 return 1;
367}
368
369/* get the reordering counter on a client connection */
370static int
371smp_fetch_fc_reordering(const struct arg *args, struct sample *smp, const char *kw, void *private)
372{
373 if (!get_tcp_info(args, smp, 0, 7))
374 return 0;
375 return 1;
376}
377#endif // linux || freebsd || netbsd
378#endif // TCP_INFO
379
380/* Note: must not be declared <const> as its list will be overwritten.
381 * Note: fetches that may return multiple types must be declared as the lowest
382 * common denominator, the type that can be casted into all other ones. For
383 * instance v4/v6 must be declared v4.
384 */
385static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
386 { "dst", smp_fetch_dst, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI },
387 { "dst_is_local", smp_fetch_dst_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
388 { "dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
389 { "src", smp_fetch_src, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI },
390 { "src_is_local", smp_fetch_src_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
391 { "src_port", smp_fetch_sport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
392#ifdef TCP_INFO
393 { "fc_rtt", smp_fetch_fc_rtt, ARG1(0,STR), val_fc_time_value, SMP_T_SINT, SMP_USE_L4CLI },
394 { "fc_rttvar", smp_fetch_fc_rttvar, ARG1(0,STR), val_fc_time_value, SMP_T_SINT, SMP_USE_L4CLI },
395#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
396 { "fc_unacked", smp_fetch_fc_unacked, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
397 { "fc_sacked", smp_fetch_fc_sacked, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
398 { "fc_retrans", smp_fetch_fc_retrans, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
399 { "fc_fackets", smp_fetch_fc_fackets, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
400 { "fc_lost", smp_fetch_fc_lost, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
401 { "fc_reordering", smp_fetch_fc_reordering, ARG1(0,STR), var_fc_counter, SMP_T_SINT, SMP_USE_L4CLI },
402#endif // linux || freebsd || netbsd
403#endif // TCP_INFO
404 { /* END */ },
405}};
406
407INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
408
409
410/*
411 * Local variables:
412 * c-indent-level: 8
413 * c-basic-offset: 8
414 * End:
415 */