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