diff mbox series

[nft,v5,1/3] src: add ct timeout support

Message ID 20180813193656.17246-1-harshasharmaiitr@gmail.com
State Accepted
Delegated to: Pablo Neira
Headers show
Series [nft,v5,1/3] src: add ct timeout support | expand

Commit Message

Harsha Sharma Aug. 13, 2018, 7:36 p.m. UTC
This patch adds support for adding, listing and deleting ct timeout
objects which can be assigned via rule to assign connection tracking
timeout policies via objref infrastructure.

%nft add table filter
%nft add chain filter output
%nft add ct timeout filter test-tcp { protocol tcp \; policy = {
 established: 132, close: 13, close_wait: 17 } \; }
%nft add rule filter output ct timeout set test-tcp
%nft list ruleset

table ip filter {
	ct timeout test-tcp {
		protocol tcp;
		l3proto ip
		policy = {established: 132, close_wait: 17, close: 13}
	}

	chain output {
		ct timeout set "test-tcp"
	}
}

%nft delete rule filter output handle <handle>
%nft delete ct timeout filter test-tcp

Signed-off-by: Harsha Sharma <harshasharmaiitr@gmail.com>
---
Changes in v5:
 - Remove nftnl_timeout_policy_attr_set_u32
 - remove nftnl_obj_get_void
 - other changes with libnftnl patches
Changes in v4:
 - updated syntax and log message
 - fix parser_bison to parse input from files for ct timeout obj
 - output similar to output
Changes in v3:
 - parse multiple timeout policies
 - return error for invalid timeout state name
 - change in log message
Changes in v2:
 - Change in syntax for addition of ct timeout objects
 - remove tokens from scanner and parser_bison for timeout states
 - list only updated timeout values
 - change in log message accordingly
 - other minor changes

 include/linux/netfilter/nf_tables.h |  14 ++++-
 include/rule.h                      |  24 ++++++++
 src/evaluate.c                      |   4 ++
 src/netlink.c                       |  14 +++++
 src/parser_bison.y                  | 108 ++++++++++++++++++++++++++++++++-
 src/rule.c                          | 116 +++++++++++++++++++++++++++++++++++-
 src/statement.c                     |   4 ++
 7 files changed, 280 insertions(+), 4 deletions(-)

Comments

Pablo Neira Ayuso Aug. 31, 2018, 8:16 a.m. UTC | #1
On Tue, Aug 14, 2018 at 01:06:56AM +0530, Harsha Sharma wrote:
> This patch adds support for adding, listing and deleting ct timeout
> objects which can be assigned via rule to assign connection tracking
> timeout policies via objref infrastructure.

Applied this series, thanks. Let's keep refining this in the tree.
diff mbox series

Patch

diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index fe65652..143ebe2 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -972,6 +972,7 @@  enum nft_osf_attributes {
  * @NFT_CT_DST_IP: conntrack layer 3 protocol destination (IPv4 address)
  * @NFT_CT_SRC_IP6: conntrack layer 3 protocol source (IPv6 address)
  * @NFT_CT_DST_IP6: conntrack layer 3 protocol destination (IPv6 address)
+ * @NFT_CT_TIMEOUT: connection tracking timeout policy assigned to conntrack
  */
 enum nft_ct_keys {
 	NFT_CT_STATE,
@@ -997,6 +998,7 @@  enum nft_ct_keys {
 	NFT_CT_DST_IP,
 	NFT_CT_SRC_IP6,
 	NFT_CT_DST_IP6,
+	NFT_CT_TIMEOUT,
 	__NFT_CT_MAX
 };
 #define NFT_CT_MAX		(__NFT_CT_MAX - 1)
@@ -1403,13 +1405,23 @@  enum nft_ct_helper_attributes {
 };
 #define NFTA_CT_HELPER_MAX	(__NFTA_CT_HELPER_MAX - 1)
 
+enum nft_ct_timeout_attributes {
+	NFTA_CT_TIMEOUT_L3PROTO,
+	NFTA_CT_TIMEOUT_L4PROTO,
+	NFTA_CT_TIMEOUT_DATA,
+	__NFTA_CT_TIMEOUT_MAX,
+};
+#define NFTA_CT_TIMEOUT_MAX     (__NFTA_CT_TIMEOUT_MAX - 1)
+
 #define NFT_OBJECT_UNSPEC	0
 #define NFT_OBJECT_COUNTER	1
 #define NFT_OBJECT_QUOTA	2
 #define NFT_OBJECT_CT_HELPER	3
 #define NFT_OBJECT_LIMIT	4
 #define NFT_OBJECT_CONNLIMIT	5
-#define __NFT_OBJECT_MAX	6
+#define NFT_OBJECT_TUNNEL	6
+#define NFT_OBJECT_CT_TIMEOUT	7
+#define __NFT_OBJECT_MAX	8
 #define NFT_OBJECT_MAX		(__NFT_OBJECT_MAX - 1)
 
 /**
diff --git a/include/rule.h b/include/rule.h
index 909ff36..4190cb8 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -4,6 +4,7 @@ 
 #include <stdint.h>
 #include <nftables.h>
 #include <list.h>
+#include <netinet/in.h>
 
 /**
  * struct handle_spec - handle ID
@@ -308,6 +309,18 @@  struct ct_helper {
 	uint8_t l4proto;
 };
 
+struct ct_timeout {
+	uint16_t l3proto;
+	uint8_t l4proto;
+	uint32_t *timeout;
+	struct timeout_state {
+		uint8_t timeout_index;
+		uint32_t timeout_value;
+		uint8_t l4;
+		struct list_head timeout_list;
+	} timeout_state;
+};
+
 struct limit {
 	uint64_t	rate;
 	uint64_t	unit;
@@ -336,6 +349,7 @@  struct obj {
 		struct quota		quota;
 		struct ct_helper	ct_helper;
 		struct limit		limit;
+		struct ct_timeout	ct_timeout;
 	};
 };
 
@@ -462,6 +476,7 @@  enum cmd_obj {
 	CMD_OBJ_LIMITS,
 	CMD_OBJ_FLOWTABLE,
 	CMD_OBJ_FLOWTABLES,
+	CMD_OBJ_CT_TIMEOUT,
 };
 
 struct markup {
@@ -617,4 +632,13 @@  enum udata_set_elem_flags {
 	SET_ELEM_F_INTERVAL_OPEN	= 0x1,
 };
 
+struct timeout_protocol {
+	uint32_t attr_max;
+	const char *const *state_to_name;
+	uint32_t *dflt_timeout;
+};
+
+extern struct timeout_protocol timeout_protocol[IPPROTO_MAX];
+extern void timeout_str2num(const char *timeout_state, struct timeout_state *ts);
+
 #endif /* NFTABLES_RULE_H */
diff --git a/src/evaluate.c b/src/evaluate.c
index 1fc861f..304990e 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -3224,6 +3224,7 @@  static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_COUNTER:
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
+	case CMD_OBJ_CT_TIMEOUT:
 	case CMD_OBJ_LIMIT:
 		return 0;
 	default:
@@ -3251,6 +3252,7 @@  static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_COUNTER:
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
+	case CMD_OBJ_CT_TIMEOUT:
 	case CMD_OBJ_LIMIT:
 		return 0;
 	default:
@@ -3383,6 +3385,8 @@  static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd)
 		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_COUNTER);
 	case CMD_OBJ_CT_HELPER:
 		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
+	case CMD_OBJ_CT_TIMEOUT:
+		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_TIMEOUT);
 	case CMD_OBJ_LIMIT:
 		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_LIMIT);
 	case CMD_OBJ_COUNTERS:
diff --git a/src/netlink.c b/src/netlink.c
index 394af2f..c9717a1 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -41,6 +41,7 @@ 
 #include <utils.h>
 #include <erec.h>
 #include <iface.h>
+//#include <rule.h>
 
 #define nft_mon_print(monh, ...) nft_print(monh->ctx->octx, __VA_ARGS__)
 
@@ -334,6 +335,14 @@  alloc_nftnl_obj(const struct handle *h, struct obj *obj)
 			nftnl_obj_set_u16(nlo, NFTNL_OBJ_CT_HELPER_L3PROTO,
 					  obj->ct_helper.l3proto);
 		break;
+	case NFT_OBJECT_CT_TIMEOUT:
+		nftnl_obj_set_u8(nlo, NFTNL_OBJ_CT_TIMEOUT_L4PROTO,
+				  obj->ct_timeout.l4proto);
+		if (obj->ct_timeout.l3proto)
+			nftnl_obj_set_u16(nlo, NFTNL_OBJ_CT_TIMEOUT_L3PROTO,
+					  obj->ct_timeout.l3proto);
+		nftnl_obj_set(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY, obj->ct_timeout.timeout);
+		break;
 	case NFT_OBJECT_LIMIT:
 		nftnl_obj_set_u64(nlo, NFTNL_OBJ_LIMIT_RATE, obj->limit.rate);
 		nftnl_obj_set_u64(nlo, NFTNL_OBJ_LIMIT_UNIT, obj->limit.unit);
@@ -1437,6 +1446,11 @@  struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx,
 		obj->ct_helper.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_HELPER_L3PROTO);
 		obj->ct_helper.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_HELPER_L4PROTO);
 		break;
+	case NFT_OBJECT_CT_TIMEOUT:
+		obj->ct_timeout.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_TIMEOUT_L3PROTO);
+		obj->ct_timeout.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_TIMEOUT_L4PROTO);
+		obj->ct_timeout.timeout = (uint32_t *)nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY);
+		break;
 	case NFT_OBJECT_LIMIT:
 		obj->limit.rate =
 			nftnl_obj_get_u64(nlo, NFTNL_OBJ_LIMIT_RATE);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index f3c8829..34e8d06 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -550,7 +550,7 @@  int nft_lex(void *, void *, void *);
 %type <flowtable>		flowtable_block_alloc flowtable_block
 %destructor { flowtable_free($$); }	flowtable_block_alloc
 
-%type <obj>			obj_block_alloc counter_block quota_block ct_helper_block limit_block
+%type <obj>			obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block limit_block
 %destructor { obj_free($$); }	obj_block_alloc
 
 %type <list>			stmt_list
@@ -753,6 +753,9 @@  int nft_lex(void *, void *, void *);
 
 %type <val>			ct_l4protoname ct_obj_type
 
+%type <list>			timeout_states timeout_state
+%destructor { xfree($$); }	timeout_states timeout_state
+
 %%
 
 input			:	/* empty */
@@ -960,6 +963,10 @@  add_cmd			:	TABLE		table_spec
 
 				$$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_HELPER, &$3, &@$, $4);
 			}
+			|	CT	TIMEOUT obj_spec	ct_obj_alloc	'{' ct_timeout_block '}' stmt_separator
+			{
+				$$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4);
+			}
 			|	LIMIT		obj_spec	limit_obj
 			{
 				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_LIMIT, &$2, &@$, $3);
@@ -1041,6 +1048,10 @@  create_cmd		:	TABLE		table_spec
 			{
 				$$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_HELPER, &$3, &@$, $4);
 			}
+			|	CT	TIMEOUT obj_spec	ct_obj_alloc	'{' ct_timeout_block '}' stmt_separator
+			{
+				$$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4);
+			}
 			|	LIMIT		obj_spec	limit_obj
 			{
 				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_LIMIT, &$2, &@$, $3);
@@ -1233,6 +1244,10 @@  list_cmd		:	TABLE		table_spec
 			{
 				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_HELPERS, &$4, &@$, NULL);
 			}
+			|	CT		TIMEOUT		TABLE		table_spec
+			{
+				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_TIMEOUT, &$4, &@$, NULL);
+			}
 			;
 
 reset_cmd		:	COUNTERS	ruleset_spec
@@ -1464,6 +1479,15 @@  table_block		:	/* empty */	{ $$ = $<table>-1; }
 				list_add_tail(&$5->list, &$1->objs);
 				$$ = $1;
 			}
+			|	table_block	CT	TIMEOUT obj_identifier obj_block_alloc '{'	ct_timeout_block	'}' stmt_separator
+			{
+				$5->location = @4;
+				$5->type = NFT_OBJECT_CT_TIMEOUT;
+				handle_merge(&$5->handle, &$4);
+				handle_free(&$4);
+				list_add_tail(&$5->list, &$1->objs);
+				$$ = $1;
+			}
 			|	table_block	LIMIT		obj_identifier
 					obj_block_alloc	'{'	limit_block	'}'
 					stmt_separator
@@ -1759,6 +1783,15 @@  ct_helper_block		:	/* empty */	{ $$ = $<obj>-1; }
 			}
 			;
 
+ct_timeout_block	:	/*empty */	{ $$ = $<obj>-1; }
+			|	ct_timeout_block     common_block
+			|	ct_timeout_block     stmt_separator
+			|	ct_timeout_block     ct_timeout_config
+			{
+				$$ = $1;
+			}
+			;
+
 limit_block		:	/* empty */	{ $$ = $<obj>-1; }
 			|       limit_block     common_block
 			|       limit_block     stmt_separator
@@ -3229,12 +3262,43 @@  quota_obj		:	quota_config
 			;
 
 ct_obj_type		:	HELPER		{ $$ = NFT_OBJECT_CT_HELPER; }
+			|	TIMEOUT		{ $$ = NFT_OBJECT_CT_TIMEOUT; }
 			;
 
 ct_l4protoname		:	TCP	{ $$ = IPPROTO_TCP; }
 			|	UDP	{ $$ = IPPROTO_UDP; }
 			;
 
+timeout_states		:	timeout_state
+			{
+				$$ = xmalloc(sizeof(*$$));
+				init_list_head($$);
+				list_add_tail($1, $$);
+			}
+			|	timeout_states	COMMA	timeout_state
+			{
+				list_add_tail($3, $1);
+				$$ = $1;
+			}
+			;
+
+timeout_state		:	STRING	COLON	NUM
+
+			{
+				struct timeout_state *ts;
+
+				ts = xzalloc(sizeof(*ts));
+				timeout_str2num($1, ts);
+				if (!ts) {
+					erec_queue(error(&@2, "invalid timeout state name '%s'\n", $1), state->msgs);
+					YYERROR;
+				}
+				ts->timeout_value = $3;
+				init_list_head(&ts->timeout_list);
+				$$ = &ts->timeout_list;
+			}
+			;
+
 ct_helper_config		:	TYPE	QUOTED_STRING	PROTOCOL	ct_l4protoname	stmt_separator
 			{
 				struct ct_helper *ct;
@@ -3256,6 +3320,42 @@  ct_helper_config		:	TYPE	QUOTED_STRING	PROTOCOL	ct_l4protoname	stmt_separator
 			}
 			;
 
+ct_timeout_config	:	PROTOCOL	ct_l4protoname	SEMICOLON
+			{
+				struct ct_timeout *ct;
+				int l4proto = $2;
+
+				ct = &$<obj>0->ct_timeout;
+				ct->l4proto = l4proto;
+			}
+			|	POLICY 	'=' 	'{' 	timeout_states 	'}'	 stmt_separator
+			{
+				int l4proto = IPPROTO_TCP;
+				size_t timeout_array_size;
+				struct timeout_state *ts;
+				struct ct_timeout *ct;
+				uint32_t *timeout;
+
+				ct = &$<obj>0->ct_timeout;
+				init_list_head(&ct->timeout_state.timeout_list);
+				timeout_array_size = sizeof(uint32_t) * (timeout_protocol[l4proto].attr_max);
+				timeout = xzalloc(timeout_array_size);
+				list_for_each_entry(ts, $4, timeout_list) {
+					if (ct->l4proto == ts->l4) {
+						timeout[ts->timeout_index] = ts->timeout_value;
+					} else {
+						erec_queue(error(&@2, "invalid timeout state name for given l4proto\n"), state->msgs);
+						YYERROR;
+					}
+				}
+				ct->timeout = timeout;
+			}
+			|	L3PROTOCOL	family_spec_explicit	stmt_separator
+			{
+				$<obj>0->ct_timeout.l3proto = $2;
+			}
+			;
+
 ct_obj_alloc		:
 			{
 				$$ = obj_alloc(&@$);
@@ -3731,6 +3831,7 @@  ct_key			:	L3PROTOCOL	{ $$ = NFT_CT_L3PROTOCOL; }
 			|	PROTO_DST	{ $$ = NFT_CT_PROTO_DST; }
 			|	LABEL		{ $$ = NFT_CT_LABELS; }
 			|	EVENT		{ $$ = NFT_CT_EVENTMASK; }
+			|	TIMEOUT 	{ $$ = NFT_CT_TIMEOUT; }
 			|	ct_key_dir_optional
 			;
 
@@ -3779,6 +3880,11 @@  ct_stmt			:	CT	ct_key		SET	stmt_expr
 					$$->objref.type = NFT_OBJECT_CT_HELPER;
 					$$->objref.expr = $4;
 					break;
+				case NFT_CT_TIMEOUT:
+					$$ = objref_stmt_alloc(&@$);
+					$$->objref.type = NFT_OBJECT_CT_TIMEOUT;
+					$$->objref.expr = $4;
+					break;
 				default:
 					$$ = ct_stmt_alloc(&@$, $2, -1, $4);
 					break;
diff --git a/src/rule.c b/src/rule.c
index 7a7ac73..604c117 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -29,6 +29,72 @@ 
 #include <linux/netfilter.h>
 #include <linux/netfilter_arp.h>
 
+const char *const tcp_state_to_name[] = {
+	[NFTNL_CTTIMEOUT_TCP_SYN_SENT]	= "syn_sent",
+	[NFTNL_CTTIMEOUT_TCP_SYN_RECV]	= "syn_recv",
+	[NFTNL_CTTIMEOUT_TCP_ESTABLISHED]	= "established",
+	[NFTNL_CTTIMEOUT_TCP_FIN_WAIT]	= "fin_wait",
+	[NFTNL_CTTIMEOUT_TCP_CLOSE_WAIT]	= "close_wait",
+	[NFTNL_CTTIMEOUT_TCP_LAST_ACK]	= "last_ack",
+	[NFTNL_CTTIMEOUT_TCP_TIME_WAIT]	= "time_wait",
+	[NFTNL_CTTIMEOUT_TCP_CLOSE]	= "close",
+	[NFTNL_CTTIMEOUT_TCP_SYN_SENT2]	= "syn_sent2",
+	[NFTNL_CTTIMEOUT_TCP_RETRANS]	= "retrans",
+	[NFTNL_CTTIMEOUT_TCP_UNACK]	= "unack",
+};
+
+const char *const udp_state_to_name[] = {
+	[NFTNL_CTTIMEOUT_UDP_UNREPLIED]	= "unreplied",
+	[NFTNL_CTTIMEOUT_UDP_REPLIED]	= "replied",
+};
+
+uint32_t tcp_dflt_timeout[] = {
+	[NFTNL_CTTIMEOUT_TCP_SYN_SENT]	= 120,
+	[NFTNL_CTTIMEOUT_TCP_SYN_RECV]	= 60,
+	[NFTNL_CTTIMEOUT_TCP_ESTABLISHED]	= 432000,
+	[NFTNL_CTTIMEOUT_TCP_FIN_WAIT]	= 120,
+	[NFTNL_CTTIMEOUT_TCP_CLOSE_WAIT]	= 60,
+	[NFTNL_CTTIMEOUT_TCP_LAST_ACK]	= 30,
+	[NFTNL_CTTIMEOUT_TCP_TIME_WAIT]	= 120,
+	[NFTNL_CTTIMEOUT_TCP_CLOSE]	= 10,
+	[NFTNL_CTTIMEOUT_TCP_SYN_SENT2]	= 120,
+	[NFTNL_CTTIMEOUT_TCP_RETRANS]	= 300,
+	[NFTNL_CTTIMEOUT_TCP_UNACK]	= 300,
+
+};
+
+uint32_t udp_dflt_timeout[] = {
+	[NFTNL_CTTIMEOUT_UDP_UNREPLIED]	= 30,
+	[NFTNL_CTTIMEOUT_UDP_REPLIED]	= 180,
+};
+
+struct timeout_protocol timeout_protocol[IPPROTO_MAX] = {
+	[IPPROTO_TCP]	= {
+		.attr_max	= NFTNL_CTTIMEOUT_TCP_MAX,
+		.state_to_name	= tcp_state_to_name,
+		.dflt_timeout	= tcp_dflt_timeout,
+	},
+	[IPPROTO_UDP]	= {
+		.attr_max	= NFTNL_CTTIMEOUT_UDP_MAX,
+		.state_to_name	= udp_state_to_name,
+		.dflt_timeout	= udp_dflt_timeout,
+	},
+};
+
+void timeout_str2num(const char *timeout_state, struct timeout_state *ts)
+{
+	unsigned int i, l4;
+
+	for (l4 = 0; l4 < IPPROTO_MAX; l4++) {
+		for (i = 0; i < timeout_protocol[l4].attr_max; i++) {
+			if (!strcmp(timeout_protocol[l4].state_to_name[i], timeout_state)) {
+				ts->timeout_index = i;
+				ts->l4 = l4;
+			}
+		}
+	}
+}
+
 void handle_free(struct handle *h)
 {
 	xfree(h->table.name);
@@ -1093,6 +1159,7 @@  void cmd_free(struct cmd *cmd)
 		case CMD_OBJ_COUNTER:
 		case CMD_OBJ_QUOTA:
 		case CMD_OBJ_CT_HELPER:
+		case CMD_OBJ_CT_TIMEOUT:
 		case CMD_OBJ_LIMIT:
 			obj_free(cmd->object);
 			break;
@@ -1187,6 +1254,7 @@  static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl)
 	case CMD_OBJ_COUNTER:
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
+	case CMD_OBJ_CT_TIMEOUT:
 	case CMD_OBJ_LIMIT:
 		return netlink_add_obj(ctx, cmd, flags);
 	case CMD_OBJ_FLOWTABLE:
@@ -1272,6 +1340,9 @@  static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd)
 		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_QUOTA);
 	case CMD_OBJ_CT_HELPER:
 		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
+	case CMD_OBJ_CT_TIMEOUT:
+		return netlink_delete_obj(ctx, cmd,
+					  NFT_OBJECT_CT_TIMEOUT);
 	case CMD_OBJ_LIMIT:
 		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_LIMIT);
 	case CMD_OBJ_FLOWTABLE:
@@ -1417,9 +1488,28 @@  static void print_proto_name_proto(uint8_t l4, struct output_ctx *octx)
 	const struct protoent *p = getprotobynumber(l4);
 
 	if (p)
-		nft_print(octx, "%s\n", p->p_name);
+		nft_print(octx, "%s", p->p_name);
 	else
-		nft_print(octx, "%d\n", l4);
+		nft_print(octx, "%d", l4);
+}
+
+static void print_proto_timeout_policy(uint8_t l4, uint32_t *timeout,
+				       struct output_ctx *octx)
+{
+	unsigned int i, b = 0;
+
+	nft_print(octx, "\t\tpolicy = {");
+	for (i = 0; i < timeout_protocol[l4].attr_max; i++) {
+		if (timeout[i] != timeout_protocol[l4].dflt_timeout[i]) {
+			if (b)
+				nft_print(octx, ", ");
+			nft_print(octx, "%s: %u",
+				  timeout_protocol[l4].state_to_name[i],
+				  timeout[i]);
+			b = 1;
+		}
+	}
+	nft_print(octx, "}");
 }
 
 static void obj_print_data(const struct obj *obj,
@@ -1466,9 +1556,24 @@  static void obj_print_data(const struct obj *obj,
 		nft_print(octx, "\t\ttype \"%s\" protocol ",
 			  obj->ct_helper.name);
 		print_proto_name_proto(obj->ct_helper.l4proto, octx);
+		nft_print(octx, "\n");
 		nft_print(octx, "\t\tl3proto %s",
 			  family2str(obj->ct_helper.l3proto));
 		break;
+	case NFT_OBJECT_CT_TIMEOUT:
+		nft_print(octx, "ct timeout %s {", obj->handle.obj.name);
+		if (octx->handle > 0)
+			nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id);
+		nft_print(octx, "%s", opts->nl);
+		nft_print(octx, "\t\tprotocol ");
+		print_proto_name_proto(obj->ct_timeout.l4proto, octx);
+		nft_print(octx, ";%s", opts->nl);
+		nft_print(octx, "\t\tl3proto %s",
+			  family2str(obj->ct_timeout.l3proto));
+		nft_print(octx, "%s", opts->nl);
+		print_proto_timeout_policy(obj->ct_timeout.l4proto,
+					   obj->ct_timeout.timeout, octx);
+		break;
 	case NFT_OBJECT_LIMIT: {
 		bool inv = obj->limit.flags & NFT_LIMIT_F_INV;
 		const char *data_unit;
@@ -1515,6 +1620,7 @@  static const char * const obj_type_name_array[] = {
 	[NFT_OBJECT_QUOTA]	= "quota",
 	[NFT_OBJECT_CT_HELPER]	= "",
 	[NFT_OBJECT_LIMIT]	= "limit",
+	[NFT_OBJECT_CT_TIMEOUT] = "",
 };
 
 const char *obj_type_name(enum stmt_types type)
@@ -1529,6 +1635,7 @@  static uint32_t obj_type_cmd_array[NFT_OBJECT_MAX + 1] = {
 	[NFT_OBJECT_QUOTA]	= CMD_OBJ_QUOTA,
 	[NFT_OBJECT_CT_HELPER]	= CMD_OBJ_CT_HELPER,
 	[NFT_OBJECT_LIMIT]	= CMD_OBJ_LIMIT,
+	[NFT_OBJECT_CT_TIMEOUT] = CMD_OBJ_CT_TIMEOUT,
 };
 
 uint32_t obj_type_to_cmd(uint32_t type)
@@ -1877,6 +1984,8 @@  static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_CT_HELPER:
 	case CMD_OBJ_CT_HELPERS:
 		return do_list_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
+	case CMD_OBJ_CT_TIMEOUT:
+		return do_list_obj(ctx, cmd, NFT_OBJECT_CT_TIMEOUT);
 	case CMD_OBJ_LIMIT:
 	case CMD_OBJ_LIMITS:
 		return do_list_obj(ctx, cmd, NFT_OBJECT_LIMIT);
@@ -2094,6 +2203,9 @@  struct cmd *cmd_alloc_obj_ct(enum cmd_ops op, int type, const struct handle *h,
 	case NFT_OBJECT_CT_HELPER:
 		cmd_obj = CMD_OBJ_CT_HELPER;
 		break;
+	case NFT_OBJECT_CT_TIMEOUT:
+		cmd_obj = CMD_OBJ_CT_TIMEOUT;
+		break;
 	default:
 		BUG("missing type mapping");
 	}
diff --git a/src/statement.c b/src/statement.c
index 3040476..25d75e9 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -203,6 +203,7 @@  static const char *objref_type[NFT_OBJECT_MAX + 1] = {
 	[NFT_OBJECT_QUOTA]	= "quota",
 	[NFT_OBJECT_CT_HELPER]	= "ct helper",
 	[NFT_OBJECT_LIMIT]	= "limit",
+	[NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
 };
 
 const char *objref_type_name(uint32_t type)
@@ -219,6 +220,9 @@  static void objref_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 	case NFT_OBJECT_CT_HELPER:
 		nft_print(octx, "ct helper set ");
 		break;
+	case NFT_OBJECT_CT_TIMEOUT:
+		nft_print(octx, "ct timeout set ");
+		break;
 	default:
 		nft_print(octx, "%s name ",
 			  objref_type_name(stmt->objref.type));