1 /*
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 */
47 static int
smp_fetch_src(const struct arg * args,struct sample * smp,const char * kw,void * private)48 smp_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 */
76 static int
smp_fetch_sport(const struct arg * args,struct sample * smp,const char * k,void * private)77 smp_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 */
96 static int
smp_fetch_dst(const struct arg * args,struct sample * smp,const char * kw,void * private)97 smp_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 */
smp_fetch_dst_is_local(const struct arg * args,struct sample * smp,const char * kw,void * private)127 int 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;
140 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->dst);
141 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 */
smp_fetch_src_is_local(const struct arg * args,struct sample * smp,const char * kw,void * private)147 int 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;
160 smp->data.u.sint = addr_is_local(li->rx.settings->netns, conn->src);
161 return smp->data.u.sint >= 0;
162 }
163
164 /* set temp integer to the frontend connexion's destination port */
165 static int
smp_fetch_dport(const struct arg * args,struct sample * smp,const char * kw,void * private)166 smp_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 */
val_fc_time_value(struct arg * args,char ** err)192 static 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 */
var_fc_counter(struct arg * args,char ** err)225 static 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 */
get_tcp_info(const struct arg * args,struct sample * smp,int dir,int val)242 static 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 */
295 static int
smp_fetch_fc_rtt(const struct arg * args,struct sample * smp,const char * kw,void * private)296 smp_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 */
309 static int
smp_fetch_fc_rttvar(const struct arg * args,struct sample * smp,const char * kw,void * private)310 smp_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 */
325 static int
smp_fetch_fc_unacked(const struct arg * args,struct sample * smp,const char * kw,void * private)326 smp_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 */
334 static int
smp_fetch_fc_sacked(const struct arg * args,struct sample * smp,const char * kw,void * private)335 smp_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 */
343 static int
smp_fetch_fc_lost(const struct arg * args,struct sample * smp,const char * kw,void * private)344 smp_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 */
352 static int
smp_fetch_fc_retrans(const struct arg * args,struct sample * smp,const char * kw,void * private)353 smp_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 */
361 static int
smp_fetch_fc_fackets(const struct arg * args,struct sample * smp,const char * kw,void * private)362 smp_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 */
370 static int
smp_fetch_fc_reordering(const struct arg * args,struct sample * smp,const char * kw,void * private)371 smp_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 */
385 static 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
407 INITCALL1(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 */
416