tcp_options.c
20c64cc6
 /* 
  * $Id$
  * 
  * Copyright (C) 2007 iptelorg GmbH
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 /*
  * tcp options
  *
  * History:
  * --------
  *  2007-11-28  created by andrei
22db42e4
  *  2009-03-05  use cfg framework (andrei)
20c64cc6
  */
 
1d0661db
 /*!
  * \file
  * \brief SIP-router core :: 
  * \ingroup core
  * Module: \ref core
  */
 
20c64cc6
 #include "tcp_options.h"
 #include "dprint.h"
885b9f62
 #include "globals.h"
 #include "timer_ticks.h"
22db42e4
 #include "cfg/cfg.h"
3dc4f620
 #include "tcp_init.h" /* DEFAULT* */
20c64cc6
 
 
 
22db42e4
 /* default/initial values for tcp config options
    NOTE: all the options are initialized in init_tcp_options()
    depending on compile time defines */
 struct cfg_group_tcp tcp_default_cfg;
 
 
 
33bfeb9d
 static int fix_connect_to(void* cfg_h, str* gname, str* name, void** val);
 static int fix_send_to(void* cfg_h, str* gname, str* name, void** val);
 static int fix_con_lt(void* cfg_h, str* gname, str* name, void** val);
 static int fix_max_conns(void* cfg_h, str* gname, str* name, void** val);
c0c298d5
 
 
 
22db42e4
 /* cfg_group_tcp description (for the config framework)*/
 static cfg_def_t tcp_cfg_def[] = {
 	/*   name        , type |input type| chg type, min, max, fixup, proc. cbk 
 	      description */
c0c298d5
 	{ "connect_timeout", CFG_VAR_INT | CFG_ATOMIC,  -1,
 						TICKS_TO_S(MAX_TCP_CON_LIFETIME),  fix_connect_to,   0,
3dc4f620
 		"used only in non-async mode, in seconds"},
c0c298d5
 	{ "send_timeout", CFG_VAR_INT | CFG_ATOMIC,   -1,
ffc72fcf
 						MAX_TCP_CON_LIFETIME,               fix_send_to,     0,
3dc4f620
 		"in seconds"},
c0c298d5
 	{ "connection_lifetime", CFG_VAR_INT | CFG_ATOMIC,   -1,
9a74e06a
 						MAX_TCP_CON_LIFETIME,               fix_con_lt,      0,
3dc4f620
 		"connection lifetime (in seconds)"},
c0c298d5
 	{ "max_connections", CFG_VAR_INT | CFG_ATOMIC, 0, (1U<<31)-1,
 													       fix_max_conns,    0,
3dc4f620
 		"maximum connection number, soft limit"},
de223f01
 	{ "no_connect",   CFG_VAR_INT | CFG_ATOMIC,      0,   1,      0,         0,
 		"if set only accept new connections, never actively open new ones"},
3dc4f620
 	{ "fd_cache",     CFG_VAR_INT | CFG_READONLY,    0,   1,      0,         0,
22db42e4
 		"file descriptor cache for tcp_send"},
 	/* tcp async options */
 	{ "async",        CFG_VAR_INT | CFG_READONLY,    0,   1,      0,         0,
 		"async mode for writes and connects"},
 	{ "connect_wait", CFG_VAR_INT | CFG_READONLY,    0,   1,      0,         0,
 		"parallel simultaneous connects to the same dst. (0) or one connect"},
c0c298d5
 	{ "conn_wq_max",  CFG_VAR_INT | CFG_ATOMIC,      0, 1024*1024, 0,        0,
22db42e4
 		"maximum bytes queued for write per connection (depends on async)"},
c0c298d5
 	{ "wq_max",       CFG_VAR_INT | CFG_ATOMIC,      0,  1<<30,    0,        0,
22db42e4
 		"maximum bytes queued for write allowed globally (depends on async)"},
ffc72fcf
 	/* see also send_timeout above */
22db42e4
 	/* tcp socket options */
 	{ "defer_accept", CFG_VAR_INT | CFG_READONLY,    0,   3600,   0,         0,
 		"0/1 on linux, seconds on freebsd (see docs)"},
c0c298d5
 	{ "delayed_ack",  CFG_VAR_INT | CFG_ATOMIC,      0,      1,   0,         0,
22db42e4
 		"initial ack will be delayed and sent with the first data segment"},
c0c298d5
 	{ "syncnt",       CFG_VAR_INT | CFG_ATOMIC,      0,   1024,   0,         0,
22db42e4
 		"number of syn retransmissions before aborting a connect (0=not set)"},
c0c298d5
 	{ "linger2",      CFG_VAR_INT | CFG_ATOMIC,      0,   3600,   0,         0,
22db42e4
 		"lifetime of orphaned sockets in FIN_WAIT2 state in s (0=not set)"},
c0c298d5
 	{ "keepalive",    CFG_VAR_INT | CFG_ATOMIC,      0,      1,   0,         0,
22db42e4
 		"enables/disables keepalives for tcp"},
c0c298d5
 	{ "keepidle",     CFG_VAR_INT | CFG_ATOMIC,      0, 24*3600,  0,         0,
22db42e4
 		"time before sending a keepalive if the connection is idle (linux)"},
c0c298d5
 	{ "keepintvl",    CFG_VAR_INT | CFG_ATOMIC,      0, 24*3600,  0,         0,
22db42e4
 		"time interval between keepalive probes on failure (linux)"},
c0c298d5
 	{ "keepcnt",     CFG_VAR_INT | CFG_ATOMIC,       0,    1<<10,  0,        0,
22db42e4
 		"number of failed keepalives before dropping the connection (linux)"},
 	/* other options */
c0c298d5
 	{ "crlf_ping",   CFG_VAR_INT | CFG_ATOMIC,      0,        1,  0,         0,
22db42e4
 		"enable responding to CRLF SIP-level keepalives "},
c0c298d5
 	{ "accept_aliases", CFG_VAR_INT | CFG_ATOMIC,   0,        1,  0,         0,
3dc4f620
 		"turn on/off tcp aliases (see tcp_accept_aliases) "},
c0c298d5
 	{ "alias_flags", CFG_VAR_INT | CFG_ATOMIC,      0,        2,  0,         0,
3dc4f620
 		"flags used for adding new aliases (FORCE_ADD:1 , REPLACE:2) "},
c0c298d5
 	{ "new_conn_alias_flags", CFG_VAR_INT | CFG_ATOMIC, 0,    2,  0,         0,
3dc4f620
 		"flags for the def. aliases for a new conn. (FORCE_ADD:1, REPLACE:2 "},
866a3699
 	{ "accept_no_cl",   CFG_VAR_INT | CFG_ATOMIC,   0,        1,  0,         0,
 		"accept TCP messges without Content-Lenght "},
3dc4f620
 	/* internal and/or "fixed" versions of some vars
 	   (not supposed to be writeable, read will provide only debugging value*/
3c9a176b
 	{ "rd_buf_size", CFG_VAR_INT | CFG_ATOMIC,    512,    16777216,  0,         0,
e655392a
 		"internal read buffer size (should be > max. expected datagram)"},
 	{ "wq_blk_size", CFG_VAR_INT | CFG_ATOMIC,    1,    65535,  0,         0,
 		"internal async write block size (debugging use only for now)"},
22db42e4
 	{0, 0, 0, 0, 0, 0, 0}
 };
 
 
 void* tcp_cfg; /* tcp config handle */
20c64cc6
 
 /* set defaults */
 void init_tcp_options()
 {
3dc4f620
 	tcp_default_cfg.connect_timeout_s=DEFAULT_TCP_CONNECT_TIMEOUT;
ffc72fcf
 	tcp_default_cfg.send_timeout=S_TO_TICKS(DEFAULT_TCP_SEND_TIMEOUT);
9a74e06a
 	tcp_default_cfg.con_lifetime=S_TO_TICKS(DEFAULT_TCP_CONNECTION_LIFETIME_S);
2e8c0383
 #ifdef USE_TCP
3dc4f620
 	tcp_default_cfg.max_connections=tcp_max_connections;
2e8c0383
 #else /*USE_TCP*/
 	tcp_default_cfg.max_connections=0;
 #endif /*USE_TCP*/
76cb799e
 #ifdef TCP_ASYNC
 	tcp_default_cfg.async=1;
22db42e4
 	tcp_default_cfg.tcpconn_wq_max=32*1024; /* 32 k */
 	tcp_default_cfg.tcp_wq_max=10*1024*1024; /* 10 MB */
d22b82a0
 #ifdef TCP_CONNECT_WAIT
22db42e4
 	tcp_default_cfg.tcp_connect_wait=1;
d22b82a0
 #endif /* TCP_CONNECT_WAIT */
76cb799e
 #endif /* TCP_ASYNC */
20c64cc6
 #ifdef TCP_FD_CACHE
22db42e4
 	tcp_default_cfg.fd_cache=1;
20c64cc6
 #endif
 #ifdef HAVE_SO_KEEPALIVE
22db42e4
 	tcp_default_cfg.keepalive=1;
20c64cc6
 #endif
 /*
 #if defined HAVE_TCP_DEFER_ACCEPT || defined HAVE_TCP_ACCEPT_FILTER
22db42e4
 	tcp_default_cfg.defer_accept=1;
20c64cc6
 #endif
 */
 #ifdef HAVE_TCP_QUICKACK
22db42e4
 	tcp_default_cfg.delayed_ack=1;
20c64cc6
 #endif
22db42e4
 	tcp_default_cfg.crlf_ping=1;
3dc4f620
 	tcp_default_cfg.accept_aliases=0; /* don't accept aliases by default */
 	/* flags used for adding new aliases */
 	tcp_default_cfg.alias_flags=TCP_ALIAS_FORCE_ADD;
 	/* flags used for adding the default aliases of a new tcp connection */
 	tcp_default_cfg.new_conn_alias_flags=TCP_ALIAS_REPLACE;
e655392a
 	tcp_default_cfg.rd_buf_size=DEFAULT_TCP_BUF_SIZE;
 	tcp_default_cfg.wq_blk_size=DEFAULT_TCP_WBUF_SIZE;
20c64cc6
 }
 
 
 
 #define W_OPT_NC(option) \
22db42e4
 	if (tcp_default_cfg.option){\
885b9f62
 		WARN("tcp_options: tcp_" #option \
a0b4a4b9
 				" cannot be enabled (recompile needed)\n"); \
22db42e4
 		tcp_default_cfg.option=0; \
20c64cc6
 	}
 
 
 
 #define W_OPT_NS(option) \
22db42e4
 	if (tcp_default_cfg.option){\
885b9f62
 		WARN("tcp_options: tcp_" #option \
a0b4a4b9
 				" cannot be enabled (no OS support)\n"); \
22db42e4
 		tcp_default_cfg.option=0; \
20c64cc6
 	}
 
 
3dc4f620
 
 /* if *to<0 to=default_val, else if to>max_val to=max_val */
 static void fix_timeout(char* name, int* to, int default_val, unsigned max_val)
 {
 	if (*to < 0) *to=default_val;
 	else if ((unsigned)*to > max_val){
 		WARN("%s: timeout too big (%u), the maximum value is %u\n",
 				name, *to, max_val);
 		*to=max_val;
 	}
 }
 
 
 
33bfeb9d
 static int fix_connect_to(void* cfg_h, str* gname, str* name, void** val)
c0c298d5
 {
 	int v;
 	v=(int)(long)*val;
 	fix_timeout("tcp_connect_timeout", &v, DEFAULT_TCP_CONNECT_TIMEOUT,
 						TICKS_TO_S(MAX_TCP_CON_LIFETIME));
 	*val=(void*)(long)v;
 	return 0;
 }
 
 
33bfeb9d
 static int fix_send_to(void* cfg_h, str* gname, str* name, void** val)
c0c298d5
 {
 	int v;
ffc72fcf
 	v=S_TO_TICKS((int)(long)*val);
 	fix_timeout("tcp_send_timeout", &v, S_TO_TICKS(DEFAULT_TCP_SEND_TIMEOUT),
 						MAX_TCP_CON_LIFETIME);
c0c298d5
 	*val=(void*)(long)v;
 	return 0;
 }
 
 
33bfeb9d
 static int fix_con_lt(void* cfg_h, str* gname, str* name, void** val)
c0c298d5
 {
 	int v;
9a74e06a
 	v=S_TO_TICKS((int)(long)*val);
 	fix_timeout("tcp_connection_lifetime", &v, 
 					MAX_TCP_CON_LIFETIME, MAX_TCP_CON_LIFETIME);
c0c298d5
 	*val=(void*)(long)v;
 	return 0;
 }
 
 
33bfeb9d
 static int fix_max_conns(void* cfg_h, str* gname, str* name, void** val)
c0c298d5
 {
 	int v;
 	v=(int)(long)*val;
2e8c0383
 #ifdef USE_TCP
c0c298d5
 	if (v>tcp_max_connections){
 		INFO("cannot override hard tcp_max_connections limit, please"
 				" restart and increase tcp_max_connections in the cfg.\n");
 		v=tcp_max_connections;
 	}
2e8c0383
 #else /* USE_TCP */
 	if (v){
 		ERR("TCP support disabled at compile-time, tcp_max_connection is"
 				" hardwired to 0.\n");
 		v=0;
 	}
 #endif /*USE_TCP */
c0c298d5
 	*val=(void*)(long)v;
 	return 0;
 }
 
 
 
e655392a
 /** fix *val according to the cfg entry "name".
  * (*val must be integer)
  * 1. check if *val is between name min..max and if not change it to
  *    the corresp. value
  * 2. call fixup callback if defined in the cfg
  * @return 0 on success
  */
 static int tcp_cfg_def_fix(char* name, int* val)
 {
 	cfg_def_t* c;
 	str s;
 	
 	for (c=&tcp_cfg_def[0]; c->name; c++){
 		if (strcmp(name, c->name)==0){
 			/* found */
 			if ((c->type & CFG_VAR_INT)  && (c->min || c->max)){
 				if (*val < c->min) *val=c->min;
 				else if (*val > c->max) *val=c->max;
 				if (c->on_change_cb){
 					s.s=c->name;
 					s.len=strlen(s.s);
33bfeb9d
 					return c->on_change_cb(&tcp_default_cfg, NULL, &s, (void*)val);
e655392a
 				}
 			}
 			return 0;
 		}
 	}
 	WARN("tcp config option \"%s\" not found\n", name);
 	return -1; /* not found */
 }
 
 
 
20c64cc6
 /* checks & warns if some tcp_option cannot be enabled */
 void tcp_options_check()
 {
 #ifndef TCP_FD_CACHE
 	W_OPT_NC(defer_accept);
 #endif
 
76cb799e
 #ifndef TCP_ASYNC
 	W_OPT_NC(async);
885b9f62
 	W_OPT_NC(tcpconn_wq_max);
 	W_OPT_NC(tcp_wq_max);
76cb799e
 #endif /* TCP_ASYNC */
d22b82a0
 #ifndef TCP_CONNECT_WAIT
 	W_OPT_NC(tcp_connect_wait);
 #endif /* TCP_CONNECT_WAIT */
 	
76cb799e
 	if (tcp_default_cfg.tcp_connect_wait && !tcp_default_cfg.async){
22db42e4
 		tcp_default_cfg.tcp_connect_wait=0;
d22b82a0
 	}
 	
20c64cc6
 #if ! defined HAVE_TCP_DEFER_ACCEPT && ! defined HAVE_TCP_ACCEPT_FILTER
 	W_OPT_NS(defer_accept);
 #endif
 #ifndef HAVE_TCP_SYNCNT
 	W_OPT_NS(syncnt);
 #endif
 #ifndef HAVE_TCP_LINGER2
 	W_OPT_NS(linger2);
 #endif
 #ifndef HAVE_TCP_KEEPINTVL
 	W_OPT_NS(keepintvl);
 #endif
 #ifndef HAVE_TCP_KEEPIDLE
 	W_OPT_NS(keepidle);
 #endif
 #ifndef HAVE_TCP_KEEPCNT
 	W_OPT_NS(keepcnt);
 #endif
3dc4f620
 	if (tcp_default_cfg.keepintvl || tcp_default_cfg.keepidle ||
22db42e4
 			tcp_default_cfg.keepcnt){
 		tcp_default_cfg.keepalive=1; /* force on */
20c64cc6
 	}
 #ifndef HAVE_SO_KEEPALIVE
 	W_OPT_NS(keepalive);
 #endif
 #ifndef HAVE_TCP_QUICKACK
 	W_OPT_NS(delayed_ack);
 #endif
3dc4f620
 	/* fix various timeouts */
 	fix_timeout("tcp_connect_timeout", &tcp_default_cfg.connect_timeout_s,
 						DEFAULT_TCP_CONNECT_TIMEOUT,
 						TICKS_TO_S(MAX_TCP_CON_LIFETIME));
ffc72fcf
 	fix_timeout("tcp_send_timeout", &tcp_default_cfg.send_timeout,
 						S_TO_TICKS(DEFAULT_TCP_SEND_TIMEOUT),
 						MAX_TCP_CON_LIFETIME);
9a74e06a
 	fix_timeout("tcp_connection_lifetime", &tcp_default_cfg.con_lifetime,
 						MAX_TCP_CON_LIFETIME, MAX_TCP_CON_LIFETIME);
2e8c0383
 #ifdef USE_TCP
3dc4f620
 	tcp_default_cfg.max_connections=tcp_max_connections;
2e8c0383
 #else /* USE_TCP */
 	tcp_default_cfg.max_connections=0;
 #endif /* USE_TCP */
e655392a
 	tcp_cfg_def_fix("rd_buf_size", (int*)&tcp_default_cfg.rd_buf_size);
 	tcp_cfg_def_fix("wq_blk_size", (int*)&tcp_default_cfg.wq_blk_size);
20c64cc6
 }
 
 
 
22db42e4
 void tcp_options_get(struct cfg_group_tcp* t)
20c64cc6
 {
c0c298d5
 	*t=*(struct cfg_group_tcp*)tcp_cfg;
22db42e4
 }
 
 
 
 /** register tcp config into the configuration framework.
  *  @return 0 on succes, -1 on error*/
 int tcp_register_cfg()
 {
 	if (cfg_declare("tcp", tcp_cfg_def, &tcp_default_cfg, cfg_sizeof(tcp),
 					&tcp_cfg))
 		return -1;
 	if (tcp_cfg==0){
 		BUG("null tcp cfg");
 		return -1;
 	}
 	return 0;
20c64cc6
 }