All of lore.kernel.org
 help / color / mirror / Atom feed
* [nft PATCH v2 0/5] Get output under application control
@ 2017-09-28 15:17 Phil Sutter
  2017-09-28 15:17 ` [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init() Phil Sutter
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

The following series introduces struct output_ctx field 'output_fp' and
adjusts the whole code base to use that for regular (e.g. 'list'
command) and debug output.

Patches 1-4 contain preparation changes which I extracted for easier
review. Patch 5 is a modified version of what Eric Leblond sent earlier
(hence why I tagged this whole series as v2). I sent him parts of it
earlier, but didn't get a reply. So I'll just hijack his work and make
it a joint one between him and me.

Patches 1-4 are new ones not present in Eric's v1. Patch 5 contains a
changelog.

Note that with this series applied, the testsuite (including monitor
tests) passes (apart from the unrelated ICMPv6 asymmetry).

Phil Sutter (5):
  rule: Use C99-style initializer in cache_init()
  exthdr: Simplify tcp option printing a bit
  erec_print: Pass output FILE pointer to netlink_dump_expr()
  rule: Refactor chain_print_declaration()
  src: get rid of printf

 include/datatype.h      |   5 +-
 include/expression.h    |   2 +-
 include/netlink.h       |  10 +-
 include/nftables.h      |   6 +
 include/parser.h        |   2 +-
 include/rule.h          |   7 +-
 src/cli.c               |   2 +-
 src/ct.c                |  20 ++--
 src/datatype.c          |  60 +++++-----
 src/erec.c              |   2 +-
 src/evaluate.c          |  22 ++--
 src/expression.c        |  74 ++++++------
 src/exthdr.c            |  21 ++--
 src/fib.c               |  23 ++--
 src/hash.c              |  10 +-
 src/main.c              |  51 ++++++++-
 src/meta.c              |  32 +++---
 src/netlink.c           | 131 ++++++++++-----------
 src/netlink_linearize.c |   2 +-
 src/numgen.c            |   7 +-
 src/parser_bison.y      |   3 +-
 src/payload.c           |  10 +-
 src/rt.c                |   2 +-
 src/rule.c              | 297 +++++++++++++++++++++++++-----------------------
 src/statement.c         | 138 +++++++++++-----------
 25 files changed, 510 insertions(+), 429 deletions(-)

-- 
2.13.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init()
  2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
@ 2017-09-28 15:17 ` Phil Sutter
  2017-09-29 10:25   ` Pablo Neira Ayuso
  2017-09-28 15:17 ` [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit Phil Sutter
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 src/rule.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/rule.c b/src/rule.c
index 1e0558eaf0751..85505aecd5091 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -128,17 +128,16 @@ static int cache_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 	struct handle handle = {
 		.family = NFPROTO_UNSPEC,
 	};
-	struct netlink_ctx ctx;
+	struct netlink_ctx ctx = {
+		.list		= LIST_HEAD_INIT(ctx.list),
+		.nf_sock	= nf_sock,
+		.cache		= cache,
+		.msgs		= msgs,
+		.seqnum		= cache->seqnum++,
+		.debug_mask	= debug_mask,
+	};
 	int ret;
 
-	memset(&ctx, 0, sizeof(ctx));
-	init_list_head(&ctx.list);
-	ctx.nf_sock = nf_sock;
-	ctx.cache = cache;
-	ctx.msgs = msgs;
-	ctx.seqnum = cache->seqnum++;
-	ctx.debug_mask = debug_mask;
-
 	ret = cache_init_tables(&ctx, &handle, cache);
 	if (ret < 0)
 		return ret;
-- 
2.13.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit
  2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
  2017-09-28 15:17 ` [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init() Phil Sutter
@ 2017-09-28 15:17 ` Phil Sutter
  2017-09-29 10:25   ` Pablo Neira Ayuso
  2017-09-28 15:17 ` [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr() Phil Sutter
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

This eliminates the need for that temporary buffer.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 src/exthdr.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/exthdr.c b/src/exthdr.c
index 4add3da24ad87..37c7688401f60 100644
--- a/src/exthdr.c
+++ b/src/exthdr.c
@@ -31,17 +31,13 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx)
 		 * multiplicator
 		 */
 		unsigned int offset = expr->exthdr.offset / 64;
-		char buf[9] = {0};
 
-		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) {
-			printf("tcp option %s", expr->exthdr.desc->name);
+		printf("tcp option %s", expr->exthdr.desc->name);
+		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
 			return;
-		}
-
 		if (offset)
-			snprintf(buf, sizeof buf, "%d", offset);
-		printf("tcp option %s%s %s", expr->exthdr.desc->name, buf,
-					     expr->exthdr.tmpl->token);
+			printf("%d", offset);
+		printf(" %s", expr->exthdr.tmpl->token);
 	} else {
 		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
 			printf("exthdr %s", expr->exthdr.desc->name);
-- 
2.13.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr()
  2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
  2017-09-28 15:17 ` [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init() Phil Sutter
  2017-09-28 15:17 ` [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit Phil Sutter
@ 2017-09-28 15:17 ` Phil Sutter
  2017-09-29 10:26   ` Pablo Neira Ayuso
  2017-09-28 15:17 ` [nft PATCH v2 4/5] rule: Refactor chain_print_declaration() Phil Sutter
  2017-09-28 15:17 ` [nft PATCH v2 5/5] src: get rid of printf Phil Sutter
  4 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

It was a bit odd that erec_print() outputs to a given FILE pointer but
then calls netlink_dump_expr() which just prints to stdout. Fix this by
passing the given FILE pointer along so output is guaranteed to go to
the same destination.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 include/netlink.h | 2 +-
 src/erec.c        | 2 +-
 src/netlink.c     | 5 +++--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/include/netlink.h b/include/netlink.h
index b395cf1cd9add..79dc08f873b98 100644
--- a/include/netlink.h
+++ b/include/netlink.h
@@ -184,7 +184,7 @@ extern void netlink_dump_chain(const struct nftnl_chain *nlc,
 extern void netlink_dump_rule(const struct nftnl_rule *nlr,
 			      unsigned int debug_mask);
 extern void netlink_dump_expr(const struct nftnl_expr *nle,
-			      unsigned int debug_mask);
+			      FILE *fp, unsigned int debug_mask);
 extern void netlink_dump_set(const struct nftnl_set *nls,
 			     unsigned int debug_mask);
 extern void netlink_dump_obj(struct nftnl_obj *nlo, unsigned int debug_mask);
diff --git a/src/erec.c b/src/erec.c
index f62bc78ccdfab..174d1aeb0ca8f 100644
--- a/src/erec.c
+++ b/src/erec.c
@@ -153,7 +153,7 @@ void erec_print(FILE *f, const struct error_record *erec,
 		fprintf(f, "%s\n", erec->msg);
 		for (l = 0; l < (int)erec->num_locations; l++) {
 			loc = &erec->locations[l];
-			netlink_dump_expr(loc->nle, debug_mask);
+			netlink_dump_expr(loc->nle, f, debug_mask);
 		}
 		fprintf(f, "\n");
 	} else {
diff --git a/src/netlink.c b/src/netlink.c
index e414718ba1b9d..3183a8100f752 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -515,7 +515,8 @@ void netlink_dump_rule(const struct nftnl_rule *nlr, unsigned int debug_mask)
 	fprintf(stdout, "%s\n", buf);
 }
 
-void netlink_dump_expr(const struct nftnl_expr *nle, unsigned int debug_mask)
+void netlink_dump_expr(const struct nftnl_expr *nle,
+		       FILE *fp, unsigned int debug_mask)
 {
 	char buf[4096];
 
@@ -523,7 +524,7 @@ void netlink_dump_expr(const struct nftnl_expr *nle, unsigned int debug_mask)
 		return;
 
 	nftnl_expr_snprintf(buf, sizeof(buf), nle, 0, 0);
-	fprintf(stdout, "%s\n", buf);
+	fprintf(fp, "%s\n", buf);
 }
 
 static int list_rule_cb(struct nftnl_rule *nlr, void *arg)
-- 
2.13.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [nft PATCH v2 4/5] rule: Refactor chain_print_declaration()
  2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
                   ` (2 preceding siblings ...)
  2017-09-28 15:17 ` [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr() Phil Sutter
@ 2017-09-28 15:17 ` Phil Sutter
  2017-09-29 10:26   ` Pablo Neira Ayuso
  2017-09-28 15:17 ` [nft PATCH v2 5/5] src: get rid of printf Phil Sutter
  4 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

Instead of having two nearly identical printf() calls for netdev and
other chains, print the common parts separately and include the device
bit only for netdev chains.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 src/rule.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/rule.c b/src/rule.c
index 85505aecd5091..91129734a3d8f 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -666,18 +666,12 @@ static void chain_print_declaration(const struct chain *chain)
 {
 	printf("\tchain %s {\n", chain->handle.chain);
 	if (chain->flags & CHAIN_F_BASECHAIN) {
-		if (chain->dev != NULL) {
-			printf("\t\ttype %s hook %s device %s priority %d; policy %s;\n",
-			       chain->type,
-			       hooknum2str(chain->handle.family, chain->hooknum),
-			       chain->dev, chain->priority,
-			       chain_policy2str(chain->policy));
-		} else {
-			printf("\t\ttype %s hook %s priority %d; policy %s;\n",
-			       chain->type,
-			       hooknum2str(chain->handle.family, chain->hooknum),
-			       chain->priority, chain_policy2str(chain->policy));
-		}
+		printf("\t\ttype %s hook %s", chain->type,
+		       hooknum2str(chain->handle.family, chain->hooknum));
+		if (chain->dev != NULL)
+			printf(" device %s", chain->dev);
+		printf(" priority %d; policy %s;\n",
+		       chain->priority, chain_policy2str(chain->policy));
 	}
 }
 
-- 
2.13.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [nft PATCH v2 5/5] src: get rid of printf
  2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
                   ` (3 preceding siblings ...)
  2017-09-28 15:17 ` [nft PATCH v2 4/5] rule: Refactor chain_print_declaration() Phil Sutter
@ 2017-09-28 15:17 ` Phil Sutter
  2017-09-29 10:38   ` Pablo Neira Ayuso
  4 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-28 15:17 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

This patch introduces nft_print()/nft_gmp_print() functions which have
to be used instead of printf to output information that were previously
send to stdout. These functions print to a FILE pointer defined in
struct output_ctx. It is set by calling:

| old_fp = nft_ctx_set_output(ctx, new_fp);

Having an application-defined FILE pointer is actually quite flexible:
Using fmemopen() or even fopencookie(), an application gains full
control over what is printed and where it should go to.

Signed-off-by: Eric Leblond <eric@regit.org>
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v1:
- Print into a FILE pointer instead of a buffer since that allows to
  include monitor code into the same solution.
- Change monitor code to use nft_print() as well.
- Introduce nft_gmp_print() to replace former calls to gmp_printf().
- Add struct output_ctx pointer to struct eval_ctx, this is required by
  cache_update().
- Make all netlink_dump_*() functions print to output_fp by making use
  of nftnl_*_fprintf() functions.
---
 include/datatype.h      |   5 +-
 include/expression.h    |   2 +-
 include/netlink.h       |   8 +-
 include/nftables.h      |   6 ++
 include/parser.h        |   2 +-
 include/rule.h          |   7 +-
 src/cli.c               |   2 +-
 src/ct.c                |  20 ++--
 src/datatype.c          |  60 +++++------
 src/evaluate.c          |  22 ++--
 src/expression.c        |  74 ++++++-------
 src/exthdr.c            |  15 +--
 src/fib.c               |  23 ++--
 src/hash.c              |  10 +-
 src/main.c              |  51 ++++++++-
 src/meta.c              |  32 +++---
 src/netlink.c           | 126 +++++++++++-----------
 src/netlink_linearize.c |   2 +-
 src/numgen.c            |   7 +-
 src/parser_bison.y      |   3 +-
 src/payload.c           |  10 +-
 src/rt.c                |   2 +-
 src/rule.c              | 272 +++++++++++++++++++++++++-----------------------
 src/statement.c         | 138 ++++++++++++------------
 24 files changed, 495 insertions(+), 404 deletions(-)

diff --git a/include/datatype.h b/include/datatype.h
index 2e345910be83d..e9f60798d1a0c 100644
--- a/include/datatype.h
+++ b/include/datatype.h
@@ -209,7 +209,8 @@ extern void symbolic_constant_print(const struct symbol_table *tbl,
 				    struct output_ctx *octx);
 extern void symbol_table_print(const struct symbol_table *tbl,
 			       const struct datatype *dtype,
-			       enum byteorder byteorder);
+			       enum byteorder byteorder,
+			       struct output_ctx *octx);
 
 extern struct symbol_table *rt_symbol_table_init(const char *filename);
 extern void rt_symbol_table_free(struct symbol_table *tbl);
@@ -261,7 +262,7 @@ extern const struct datatype *
 set_datatype_alloc(const struct datatype *orig_dtype, unsigned int byteorder);
 extern void set_datatype_destroy(const struct datatype *dtype);
 
-extern void time_print(uint64_t seconds);
+extern void time_print(uint64_t seconds, struct output_ctx *octx);
 extern struct error_record *time_parse(const struct location *loc,
 				       const char *c, uint64_t *res);
 
diff --git a/include/expression.h b/include/expression.h
index 32d4423a5e06e..ce6b702a7f378 100644
--- a/include/expression.h
+++ b/include/expression.h
@@ -334,7 +334,7 @@ extern struct expr *expr_get(struct expr *expr);
 extern void expr_free(struct expr *expr);
 extern void expr_print(const struct expr *expr, struct output_ctx *octx);
 extern bool expr_cmp(const struct expr *e1, const struct expr *e2);
-extern void expr_describe(const struct expr *expr);
+extern void expr_describe(const struct expr *expr, struct output_ctx *octx);
 
 extern const struct datatype *expr_basetype(const struct expr *expr);
 extern void expr_set_type(struct expr *expr, const struct datatype *dtype,
diff --git a/include/netlink.h b/include/netlink.h
index 79dc08f873b98..2ca6f345b6ac9 100644
--- a/include/netlink.h
+++ b/include/netlink.h
@@ -180,14 +180,14 @@ extern int netlink_delete_obj(struct netlink_ctx *ctx, const struct handle *h,
 			      struct location *loc, uint32_t type);
 
 extern void netlink_dump_chain(const struct nftnl_chain *nlc,
-			       unsigned int debug_mask);
+			       struct netlink_ctx *ctx);
 extern void netlink_dump_rule(const struct nftnl_rule *nlr,
-			      unsigned int debug_mask);
+			      struct netlink_ctx *ctx);
 extern void netlink_dump_expr(const struct nftnl_expr *nle,
 			      FILE *fp, unsigned int debug_mask);
 extern void netlink_dump_set(const struct nftnl_set *nls,
-			     unsigned int debug_mask);
-extern void netlink_dump_obj(struct nftnl_obj *nlo, unsigned int debug_mask);
+			     struct netlink_ctx *ctx);
+extern void netlink_dump_obj(struct nftnl_obj *nlo, struct netlink_ctx *ctx);
 
 extern int netlink_batch_send(struct netlink_ctx *ctx, struct list_head *err_list);
 
diff --git a/include/nftables.h b/include/nftables.h
index 3429e4c127983..01d72a87212ea 100644
--- a/include/nftables.h
+++ b/include/nftables.h
@@ -30,6 +30,7 @@ struct output_ctx {
 	unsigned int ip2name;
 	unsigned int handle;
 	unsigned int echo;
+	FILE *output_fp;
 };
 
 struct nft_cache {
@@ -148,4 +149,9 @@ void realm_table_meta_exit(void);
 void devgroup_table_exit(void);
 void realm_table_rt_exit(void);
 
+int nft_print(struct output_ctx *octx, const char *fmt, ...)
+	__attribute__((format(printf, 2, 3)));
+int nft_gmp_print(struct output_ctx *octx, const char *fmt, ...)
+	__attribute__((format(printf, 2, 0)));
+
 #endif /* NFTABLES_NFTABLES_H */
diff --git a/include/parser.h b/include/parser.h
index 431edfb3d4e34..0bdb3fa8225c0 100644
--- a/include/parser.h
+++ b/include/parser.h
@@ -33,7 +33,7 @@ struct mnl_socket;
 
 extern void parser_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 			struct parser_state *state, struct list_head *msgs,
-			unsigned int debug_level);
+			unsigned int debug_level, struct output_ctx *octx);
 extern int nft_parse(struct nft_ctx *ctx, void *, struct parser_state *state);
 
 extern void *scanner_init(struct parser_state *state);
diff --git a/include/rule.h b/include/rule.h
index e2a5c87b70022..1e01127e7d195 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -172,7 +172,8 @@ extern struct chain *chain_lookup(const struct table *table,
 
 extern const char *family2str(unsigned int family);
 extern const char *hooknum2str(unsigned int family, unsigned int hooknum);
-extern void chain_print_plain(const struct chain *chain);
+extern void chain_print_plain(const struct chain *chain,
+			      struct output_ctx *octx);
 
 /**
  * struct rule - nftables rule
@@ -493,6 +494,7 @@ struct eval_ctx {
 	struct set		*set;
 	struct stmt		*stmt;
 	struct nft_cache	*cache;
+	struct output_ctx	*octx;
 	unsigned int		debug_mask;
 	struct expr_ctx		ectx;
 	struct proto_ctx	pctx;
@@ -506,7 +508,8 @@ struct netlink_ctx;
 extern int do_command(struct netlink_ctx *ctx, struct cmd *cmd);
 
 extern int cache_update(struct mnl_socket *nf_sock, struct nft_cache *cache,
-			 enum cmd_ops cmd, struct list_head *msgs, bool debug);
+			enum cmd_ops cmd, struct list_head *msgs, bool debug,
+			struct output_ctx *octx);
 extern void cache_flush(struct list_head *table_list);
 extern void cache_release(struct nft_cache *cache);
 
diff --git a/src/cli.c b/src/cli.c
index d923ff7d36175..692d1731461d1 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -135,7 +135,7 @@ static void cli_complete(char *line)
 	line = s;
 
 	parser_init(cli_nf_sock, &cli_nft->cache, state, &msgs,
-		    cli_nft->debug_mask);
+		    cli_nft->debug_mask, &cli_nft->output);
 	scanner_push_buffer(scanner, &indesc_cli, line);
 	nft_run(cli_nft, cli_nf_sock, scanner, state, &msgs);
 	erec_print_list(stdout, &msgs, cli_nft->debug_mask);
diff --git a/src/ct.c b/src/ct.c
index d64f46724002a..482fbe05cb36b 100644
--- a/src/ct.c
+++ b/src/ct.c
@@ -141,11 +141,11 @@ static void ct_label_type_print(const struct expr *expr,
 	for (s = ct_label_tbl->symbols; s->identifier != NULL; s++) {
 		if (bit != s->value)
 			continue;
-		printf("\"%s\"", s->identifier);
+		nft_print(octx, "\"%s\"", s->identifier);
 		return;
 	}
 	/* can happen when connlabel.conf is altered after rules were added */
-	printf("%ld\n", (long)mpz_scan1(expr->value, 0));
+	nft_print(octx, "%ld\n", (long)mpz_scan1(expr->value, 0));
 }
 
 static struct error_record *ct_label_type_parse(const struct expr *sym,
@@ -269,27 +269,27 @@ static const struct ct_template ct_templates[] = {
 					      BYTEORDER_HOST_ENDIAN, 32),
 };
 
-static void ct_print(enum nft_ct_keys key, int8_t dir)
+static void ct_print(enum nft_ct_keys key, int8_t dir, struct output_ctx *octx)
 {
 	const struct symbolic_constant *s;
 
-	printf("ct ");
+	nft_print(octx, "ct ");
 	if (dir < 0)
 		goto done;
 
 	for (s = ct_dir_tbl.symbols; s->identifier != NULL; s++) {
 		if (dir == (int)s->value) {
-			printf("%s ", s->identifier);
+			nft_print(octx, "%s ", s->identifier);
 			break;
 		}
 	}
  done:
-	printf("%s", ct_templates[key].token);
+	nft_print(octx, "%s", ct_templates[key].token);
 }
 
 static void ct_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
-	ct_print(expr->ct.key, expr->ct.direction);
+	ct_print(expr->ct.key, expr->ct.direction, octx);
 }
 
 static bool ct_expr_cmp(const struct expr *e1, const struct expr *e2)
@@ -445,8 +445,8 @@ void ct_expr_update_type(struct proto_ctx *ctx, struct expr *expr)
 
 static void ct_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	ct_print(stmt->ct.key, stmt->ct.direction);
-	printf(" set ");
+	ct_print(stmt->ct.key, stmt->ct.direction, octx);
+	nft_print(octx, " set ");
 	expr_print(stmt->ct.expr, octx);
 }
 
@@ -472,7 +472,7 @@ struct stmt *ct_stmt_alloc(const struct location *loc, enum nft_ct_keys key,
 
 static void notrack_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("notrack");
+	nft_print(octx, "notrack");
 }
 
 static const struct stmt_ops notrack_stmt_ops = {
diff --git a/src/datatype.c b/src/datatype.c
index 5bd0c7b37a2b6..94b1224c66e6e 100644
--- a/src/datatype.c
+++ b/src/datatype.c
@@ -192,15 +192,15 @@ void symbolic_constant_print(const struct symbol_table *tbl,
 		return expr_basetype(expr)->print(expr, octx);
 
 	if (quotes)
-		printf("\"");
+		nft_print(octx, "\"");
 
 	if (octx->numeric > NUMERIC_ALL)
-		printf("%"PRIu64"", val);
+		nft_print(octx, "%" PRIu64 "", val);
 	else
-		printf("%s", s->identifier);
+		nft_print(octx, "%s", s->identifier);
 
 	if (quotes)
-		printf("\"");
+		nft_print(octx, "\"");
 }
 
 static void switch_byteorder(void *data, unsigned int len)
@@ -215,7 +215,8 @@ static void switch_byteorder(void *data, unsigned int len)
 
 void symbol_table_print(const struct symbol_table *tbl,
 			const struct datatype *dtype,
-			enum byteorder byteorder)
+			enum byteorder byteorder,
+			struct output_ctx *octx)
 {
 	const struct symbolic_constant *s;
 	unsigned int len = dtype->size / BITS_PER_BYTE;
@@ -228,16 +229,17 @@ void symbol_table_print(const struct symbol_table *tbl,
 			switch_byteorder(&value, len);
 
 		if (tbl->base == BASE_DECIMAL)
-			printf("\t%-30s\t%20"PRIu64"\n", s->identifier, value);
+			nft_print(octx, "\t%-30s\t%20" PRIu64 "\n",
+				  s->identifier, value);
 		else
-			printf("\t%-30s\t0x%.*" PRIx64 "\n",
-			       s->identifier, 2 * len, value);
+			nft_print(octx, "\t%-30s\t0x%.*" PRIx64 "\n",
+				  s->identifier, 2 * len, value);
 	}
 }
 
 static void invalid_type_print(const struct expr *expr, struct output_ctx *octx)
 {
-	gmp_printf("0x%Zx [invalid type]", expr->value);
+	nft_gmp_print(octx, "0x%Zx [invalid type]", expr->value);
 }
 
 const struct datatype invalid_type = {
@@ -251,30 +253,30 @@ static void verdict_type_print(const struct expr *expr, struct output_ctx *octx)
 {
 	switch (expr->verdict) {
 	case NFT_CONTINUE:
-		printf("continue");
+		nft_print(octx, "continue");
 		break;
 	case NFT_BREAK:
-		printf("break");
+		nft_print(octx, "break");
 		break;
 	case NFT_JUMP:
-		printf("jump %s", expr->chain);
+		nft_print(octx, "jump %s", expr->chain);
 		break;
 	case NFT_GOTO:
-		printf("goto %s", expr->chain);
+		nft_print(octx, "goto %s", expr->chain);
 		break;
 	case NFT_RETURN:
-		printf("return");
+		nft_print(octx, "return");
 		break;
 	default:
 		switch (expr->verdict & NF_VERDICT_MASK) {
 		case NF_ACCEPT:
-			printf("accept");
+			nft_print(octx, "accept");
 			break;
 		case NF_DROP:
-			printf("drop");
+			nft_print(octx, "drop");
 			break;
 		case NF_QUEUE:
-			printf("queue");
+			nft_print(octx, "queue");
 			break;
 		default:
 			BUG("invalid verdict value %u\n", expr->verdict);
@@ -327,7 +329,7 @@ static void integer_type_print(const struct expr *expr, struct output_ctx *octx)
 		}
 	} while ((dtype = dtype->basetype));
 
-	gmp_printf(fmt, expr->value);
+	nft_gmp_print(octx, fmt, expr->value);
 }
 
 static struct error_record *integer_type_parse(const struct expr *sym,
@@ -364,7 +366,7 @@ static void string_type_print(const struct expr *expr, struct output_ctx *octx)
 
 	mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
 	data[len] = '\0';
-	printf("\"%s\"", data);
+	nft_print(octx, "\"%s\"", data);
 }
 
 static struct error_record *string_type_parse(const struct expr *sym,
@@ -396,7 +398,7 @@ static void lladdr_type_print(const struct expr *expr, struct output_ctx *octx)
 	mpz_export_data(data, expr->value, BYTEORDER_BIG_ENDIAN, len);
 
 	for (i = 0; i < len; i++) {
-		printf("%s%.2x", delim, data[i]);
+		nft_print(octx, "%s%.2x", delim, data[i]);
 		delim = ":";
 	}
 }
@@ -449,7 +451,7 @@ static void ipaddr_type_print(const struct expr *expr, struct output_ctx *octx)
 		getnameinfo((struct sockaddr *)&sin, sizeof(sin), buf,
 			    sizeof(buf), NULL, 0, NI_NUMERICHOST);
 	}
-	printf("%s", buf);
+	nft_print(octx, "%s", buf);
 }
 
 static struct error_record *ipaddr_type_parse(const struct expr *sym,
@@ -507,7 +509,7 @@ static void ip6addr_type_print(const struct expr *expr, struct output_ctx *octx)
 		getnameinfo((struct sockaddr *)&sin6, sizeof(sin6), buf,
 			    sizeof(buf), NULL, 0, NI_NUMERICHOST);
 	}
-	printf("%s", buf);
+	nft_print(octx, "%s", buf);
 }
 
 static struct error_record *ip6addr_type_parse(const struct expr *sym,
@@ -557,7 +559,7 @@ static void inet_protocol_type_print(const struct expr *expr,
 	if (octx->numeric < NUMERIC_ALL) {
 		p = getprotobynumber(mpz_get_uint8(expr->value));
 		if (p != NULL) {
-			printf("%s", p->p_name);
+			nft_print(octx, "%s", p->p_name);
 			return;
 		}
 	}
@@ -821,7 +823,7 @@ const struct datatype icmpx_code_type = {
 	.sym_tbl	= &icmpx_code_tbl,
 };
 
-void time_print(uint64_t seconds)
+void time_print(uint64_t seconds, struct output_ctx *octx)
 {
 	uint64_t days, hours, minutes;
 
@@ -835,13 +837,13 @@ void time_print(uint64_t seconds)
 	seconds %= 60;
 
 	if (days > 0)
-		printf("%"PRIu64"d", days);
+		nft_print(octx, "%" PRIu64 "d", days);
 	if (hours > 0)
-		printf("%"PRIu64"h", hours);
+		nft_print(octx, "%" PRIu64 "h", hours);
 	if (minutes > 0)
-		printf("%"PRIu64"m", minutes);
+		nft_print(octx, "%" PRIu64 "m", minutes);
 	if (seconds > 0)
-		printf("%"PRIu64"s", seconds);
+		nft_print(octx, "%" PRIu64 "s", seconds);
 }
 
 enum {
@@ -933,7 +935,7 @@ struct error_record *time_parse(const struct location *loc, const char *str,
 
 static void time_type_print(const struct expr *expr, struct output_ctx *octx)
 {
-	time_print(mpz_get_uint64(expr->value) / MSEC_PER_SEC);
+	time_print(mpz_get_uint64(expr->value) / MSEC_PER_SEC, octx);
 }
 
 static struct error_record *time_type_parse(const struct expr *sym,
diff --git a/src/evaluate.c b/src/evaluate.c
index 836c95288fdab..c796c3c364014 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -193,7 +193,7 @@ static int expr_evaluate_symbol(struct eval_ctx *ctx, struct expr **expr)
 		break;
 	case SYMBOL_SET:
 		ret = cache_update(ctx->nf_sock, ctx->cache, ctx->cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -2986,14 +2986,14 @@ static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
 	switch (cmd->obj) {
 	case CMD_OBJ_SETELEM:
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
 		return setelem_evaluate(ctx, &cmd->expr);
 	case CMD_OBJ_SET:
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -3004,7 +3004,7 @@ static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
 		return rule_evaluate(ctx, cmd->rule);
 	case CMD_OBJ_CHAIN:
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -3028,7 +3028,7 @@ static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd)
 	switch (cmd->obj) {
 	case CMD_OBJ_SETELEM:
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -3072,7 +3072,7 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd)
 	int ret;
 
 	ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs,
-			   ctx->debug_mask & DEBUG_NETLINK);
+			   ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 	if (ret < 0)
 		return ret;
 
@@ -3159,7 +3159,7 @@ static int cmd_evaluate_reset(struct eval_ctx *ctx, struct cmd *cmd)
 	int ret;
 
 	ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs,
-			   ctx->debug_mask & DEBUG_NETLINK);
+			   ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 	if (ret < 0)
 		return ret;
 
@@ -3186,7 +3186,7 @@ static int cmd_evaluate_flush(struct eval_ctx *ctx, struct cmd *cmd)
 	int ret;
 
 	ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs,
-			   ctx->debug_mask & DEBUG_NETLINK);
+			   ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 	if (ret < 0)
 		return ret;
 
@@ -3245,7 +3245,7 @@ static int cmd_evaluate_rename(struct eval_ctx *ctx, struct cmd *cmd)
 	switch (cmd->obj) {
 	case CMD_OBJ_CHAIN:
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op,
-				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->msgs, ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -3343,7 +3343,7 @@ static int cmd_evaluate_monitor(struct eval_ctx *ctx, struct cmd *cmd)
 	int ret;
 
 	ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs,
-			   ctx->debug_mask & DEBUG_NETLINK);
+			   ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 	if (ret < 0)
 		return ret;
 
@@ -3365,7 +3365,7 @@ static int cmd_evaluate_monitor(struct eval_ctx *ctx, struct cmd *cmd)
 static int cmd_evaluate_export(struct eval_ctx *ctx, struct cmd *cmd)
 {
 	return cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs,
-			    ctx->debug_mask & DEBUG_NETLINK);
+			    ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 }
 
 static const char *cmd_op_name[] = {
diff --git a/src/expression.c b/src/expression.c
index ff3550c7cd855..fc1097a1cffda 100644
--- a/src/expression.c
+++ b/src/expression.c
@@ -86,41 +86,42 @@ bool expr_cmp(const struct expr *e1, const struct expr *e2)
 	return e1->ops->cmp(e1, e2);
 }
 
-void expr_describe(const struct expr *expr)
+void expr_describe(const struct expr *expr, struct output_ctx *octx)
 {
 	const struct datatype *dtype = expr->dtype;
 	const char *delim = "";
 
-	printf("%s expression, datatype %s (%s)",
-		expr->ops->name, dtype->name, dtype->desc);
+	nft_print(octx, "%s expression, datatype %s (%s)",
+		  expr->ops->name, dtype->name, dtype->desc);
 	if (dtype->basetype != NULL) {
-		printf(" (basetype ");
+		nft_print(octx, " (basetype ");
 		for (dtype = dtype->basetype; dtype != NULL;
 		     dtype = dtype->basetype) {
-			printf("%s%s", delim, dtype->desc);
+			nft_print(octx, "%s%s", delim, dtype->desc);
 			delim = ", ";
 		}
-		printf(")");
+		nft_print(octx, ")");
 	}
 
 	if (expr_basetype(expr)->type == TYPE_STRING) {
 		if (expr->len)
-			printf(", %u characters", expr->len / BITS_PER_BYTE);
+			nft_print(octx, ", %u characters",
+				  expr->len / BITS_PER_BYTE);
 		else
-			printf(", dynamic length");
+			nft_print(octx, ", dynamic length");
 	} else
-		printf(", %u bits", expr->len);
+		nft_print(octx, ", %u bits", expr->len);
 
-	printf("\n");
+	nft_print(octx, "\n");
 
 	if (expr->dtype->sym_tbl != NULL) {
-		printf("\npre-defined symbolic constants ");
+		nft_print(octx, "\npre-defined symbolic constants ");
 		if (expr->dtype->sym_tbl->base == BASE_DECIMAL)
-			printf("(in decimal):\n");
+			nft_print(octx, "(in decimal):\n");
 		else
-			printf("(in hexadecimal):\n");
+			nft_print(octx, "(in hexadecimal):\n");
 		symbol_table_print(expr->dtype->sym_tbl, expr->dtype,
-				   expr->byteorder);
+				   expr->byteorder, octx);
 	}
 }
 
@@ -215,7 +216,8 @@ struct expr *verdict_expr_alloc(const struct location *loc,
 
 static void symbol_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
-	printf("%s%s", expr->scope != NULL ? "$" : "", expr->identifier);
+	nft_print(octx, "%s%s", expr->scope != NULL ? "$" : "",
+		  expr->identifier);
 }
 
 static void symbol_expr_clone(struct expr *new, const struct expr *expr)
@@ -398,7 +400,7 @@ struct expr *bitmask_expr_to_binops(struct expr *expr)
 static void prefix_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	expr_print(expr->prefix, octx);
-	printf("/%u", expr->prefix_len);
+	nft_print(octx, "/%u", expr->prefix_len);
 }
 
 static void prefix_expr_set_type(const struct expr *expr,
@@ -513,10 +515,10 @@ static void binop_arg_print(const struct expr *op, const struct expr *arg,
 		prec = 1;
 
 	if (prec)
-		printf("(");
+		nft_print(octx, "(");
 	expr_print(arg, octx);
 	if (prec)
-		printf(")");
+		nft_print(octx, ")");
 }
 
 static bool must_print_eq_op(const struct expr *expr)
@@ -534,9 +536,9 @@ static void binop_expr_print(const struct expr *expr, struct output_ctx *octx)
 
 	if (expr_op_symbols[expr->op] &&
 	    (expr->op != OP_EQ || must_print_eq_op(expr)))
-		printf(" %s ", expr_op_symbols[expr->op]);
+		nft_print(octx, " %s ", expr_op_symbols[expr->op]);
 	else
-		printf(" ");
+		nft_print(octx, " ");
 
 	binop_arg_print(expr, expr->right, octx);
 }
@@ -602,7 +604,7 @@ static void range_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	octx->numeric += NUMERIC_ALL + 1;
 	expr_print(expr->left, octx);
-	printf("-");
+	nft_print(octx, "-");
 	expr_print(expr->right, octx);
 	octx->numeric -= NUMERIC_ALL + 1;
 }
@@ -682,7 +684,7 @@ static void compound_expr_print(const struct expr *expr, const char *delim,
 	const char *d = "";
 
 	list_for_each_entry(i, &expr->expressions, list) {
-		printf("%s", d);
+		nft_print(octx, "%s", d);
 		expr_print(i, octx);
 		d = delim;
 	}
@@ -793,16 +795,16 @@ static void set_expr_print(const struct expr *expr, struct output_ctx *octx)
 	const char *d = "";
 	int count = 0;
 
-	printf("{ ");
+	nft_print(octx, "{ ");
 
 	list_for_each_entry(i, &expr->expressions, list) {
-		printf("%s", d);
+		nft_print(octx, "%s", d);
 		expr_print(i, octx);
 		count++;
 		d = calculate_delim(expr, &count);
 	}
 
-	printf(" }");
+	nft_print(octx, " }");
 }
 
 static void set_expr_set_type(const struct expr *expr,
@@ -840,7 +842,7 @@ struct expr *set_expr_alloc(const struct location *loc, const struct set *set)
 static void mapping_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	expr_print(expr->left, octx);
-	printf(" : ");
+	nft_print(octx, " : ");
 	expr_print(expr->right, octx);
 }
 
@@ -889,9 +891,9 @@ static void map_expr_print(const struct expr *expr, struct output_ctx *octx)
 	expr_print(expr->map, octx);
 	if (expr->mappings->ops->type == EXPR_SET_REF &&
 	    expr->mappings->set->datatype->type == TYPE_VERDICT)
-		printf(" vmap ");
+		nft_print(octx, " vmap ");
 	else
-		printf(" map ");
+		nft_print(octx, " map ");
 	expr_print(expr->mappings, octx);
 }
 
@@ -930,11 +932,11 @@ static void set_ref_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	if (expr->set->flags & NFT_SET_ANONYMOUS) {
 		if (expr->set->flags & NFT_SET_EVAL)
-			printf("table %s", expr->set->handle.set);
+			nft_print(octx, "table %s", expr->set->handle.set);
 		else
 			expr_print(expr->set->init, octx);
 	} else {
-		printf("@%s", expr->set->handle.set);
+		nft_print(octx, "@%s", expr->set->handle.set);
 	}
 }
 
@@ -971,18 +973,18 @@ static void set_elem_expr_print(const struct expr *expr,
 {
 	expr_print(expr->key, octx);
 	if (expr->timeout) {
-		printf(" timeout ");
-		time_print(expr->timeout / 1000);
+		nft_print(octx, " timeout ");
+		time_print(expr->timeout / 1000, octx);
 	}
 	if (!octx->stateless && expr->expiration) {
-		printf(" expires ");
-		time_print(expr->expiration / 1000);
+		nft_print(octx, " expires ");
+		time_print(expr->expiration / 1000, octx);
 	}
 	if (expr->comment)
-		printf(" comment \"%s\"", expr->comment);
+		nft_print(octx, " comment \"%s\"", expr->comment);
 
 	if (expr->stmt) {
-		printf(" : ");
+		nft_print(octx, " : ");
 		stmt_print(expr->stmt, octx);
 	}
 }
diff --git a/src/exthdr.c b/src/exthdr.c
index 37c7688401f60..ac3e16320d388 100644
--- a/src/exthdr.c
+++ b/src/exthdr.c
@@ -32,18 +32,19 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx)
 		 */
 		unsigned int offset = expr->exthdr.offset / 64;
 
-		printf("tcp option %s", expr->exthdr.desc->name);
+		nft_print(octx, "tcp option %s", expr->exthdr.desc->name);
 		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
 			return;
 		if (offset)
-			printf("%d", offset);
-		printf(" %s", expr->exthdr.tmpl->token);
+			nft_print(octx, "%d", offset);
+		nft_print(octx, " %s", expr->exthdr.tmpl->token);
 	} else {
 		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
-			printf("exthdr %s", expr->exthdr.desc->name);
+			nft_print(octx, "exthdr %s", expr->exthdr.desc->name);
 		else {
-			printf("%s %s", expr->exthdr.desc ? expr->exthdr.desc->name : "unknown-exthdr",
-					expr->exthdr.tmpl->token);
+			nft_print(octx, "%s %s",
+				  expr->exthdr.desc ? expr->exthdr.desc->name : "unknown-exthdr",
+				  expr->exthdr.tmpl->token);
 		}
 	}
 }
@@ -98,7 +99,7 @@ struct expr *exthdr_expr_alloc(const struct location *loc,
 static void exthdr_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
 	expr_print(stmt->exthdr.expr, octx);
-	printf(" set ");
+	nft_print(octx, " set ");
 	expr_print(stmt->exthdr.val, octx);
 }
 
diff --git a/src/fib.c b/src/fib.c
index b3488afff3923..21bc69a9f0a38 100644
--- a/src/fib.c
+++ b/src/fib.c
@@ -60,32 +60,33 @@ static const char *fib_result_str(enum nft_fib_result result)
 	return "unknown";
 }
 
-static void __fib_expr_print_f(unsigned int *flags, unsigned int f, const char *s)
+static void __fib_expr_print_f(unsigned int *flags, unsigned int f,
+			       const char *s, struct output_ctx *octx)
 {
 	if ((*flags & f) == 0)
 		return;
 
-	printf("%s", s);
+	nft_print(octx, "%s", s);
 	*flags &= ~f;
 	if (*flags)
-		printf(" . ");
+		nft_print(octx, " . ");
 }
 
 static void fib_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT;
 
-	printf("fib ");
-	__fib_expr_print_f(&flags, NFTA_FIB_F_SADDR, "saddr");
-	__fib_expr_print_f(&flags, NFTA_FIB_F_DADDR, "daddr");
-	__fib_expr_print_f(&flags, NFTA_FIB_F_MARK, "mark");
-	__fib_expr_print_f(&flags, NFTA_FIB_F_IIF, "iif");
-	__fib_expr_print_f(&flags, NFTA_FIB_F_OIF, "oif");
+	nft_print(octx, "fib ");
+	__fib_expr_print_f(&flags, NFTA_FIB_F_SADDR, "saddr", octx);
+	__fib_expr_print_f(&flags, NFTA_FIB_F_DADDR, "daddr", octx);
+	__fib_expr_print_f(&flags, NFTA_FIB_F_MARK, "mark", octx);
+	__fib_expr_print_f(&flags, NFTA_FIB_F_IIF, "iif", octx);
+	__fib_expr_print_f(&flags, NFTA_FIB_F_OIF, "oif", octx);
 
 	if (flags)
-		printf("0x%x", flags);
+		nft_print(octx, "0x%x", flags);
 
-	printf(" %s", fib_result_str(expr->fib.result));
+	nft_print(octx, " %s", fib_result_str(expr->fib.result));
 }
 
 static bool fib_expr_cmp(const struct expr *e1, const struct expr *e2)
diff --git a/src/hash.c b/src/hash.c
index 1a4bfb30814f1..9cd3c8cfa173d 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -19,19 +19,19 @@ static void hash_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	switch (expr->hash.type) {
 	case NFT_HASH_SYM:
-		printf("symhash");
+		nft_print(octx, "symhash");
 	break;
 	case NFT_HASH_JENKINS:
 	default:
-		printf("jhash ");
+		nft_print(octx, "jhash ");
 		expr_print(expr->hash.expr, octx);
 	}
 
-	printf(" mod %u", expr->hash.mod);
+	nft_print(octx, " mod %u", expr->hash.mod);
 	if (expr->hash.seed_set)
-		printf(" seed 0x%x", expr->hash.seed);
+		nft_print(octx, " seed 0x%x", expr->hash.seed);
 	if (expr->hash.offset)
-		printf(" offset %u", expr->hash.offset);
+		nft_print(octx, " offset %u", expr->hash.offset);
 }
 
 static bool hash_expr_cmp(const struct expr *e1, const struct expr *e2)
diff --git a/src/main.c b/src/main.c
index 8e7b586d347cc..079a05b80e511 100644
--- a/src/main.c
+++ b/src/main.c
@@ -316,6 +316,15 @@ static void nft_ctx_free(const struct nft_ctx *ctx)
 	nft_exit();
 }
 
+static FILE *nft_ctx_set_output(struct nft_ctx *ctx, FILE *fp)
+{
+	FILE *old = ctx->output.output_fp;
+
+	ctx->output.output_fp = fp;
+
+	return old;
+}
+
 static int nft_run_cmd_from_buffer(struct nft_ctx *nft,
 				   char *buf, size_t buflen)
 {
@@ -324,7 +333,8 @@ static int nft_run_cmd_from_buffer(struct nft_ctx *nft,
 	LIST_HEAD(msgs);
 	void *scanner;
 
-	parser_init(nft->nf_sock, &nft->cache, &state, &msgs, nft->debug_mask);
+	parser_init(nft->nf_sock, &nft->cache, &state,
+		    &msgs, nft->debug_mask, &nft->output);
 	scanner = scanner_init(&state);
 	scanner_push_buffer(scanner, &indesc_cmdline, buf);
 
@@ -345,11 +355,12 @@ static int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename)
 	int rc;
 
 	rc = cache_update(nft->nf_sock, &nft->cache, CMD_INVALID, &msgs,
-			  nft->debug_mask);
+			  nft->debug_mask, &nft->output);
 	if (rc < 0)
 		return NFT_EXIT_FAILURE;
 
-	parser_init(nft->nf_sock, &nft->cache, &state, &msgs, nft->debug_mask);
+	parser_init(nft->nf_sock, &nft->cache, &state,
+		    &msgs, nft->debug_mask, &nft->output);
 	scanner = scanner_init(&state);
 	if (scanner_read_file(scanner, filename, &internal_location) < 0) {
 		rc = NFT_EXIT_FAILURE;
@@ -365,6 +376,37 @@ err:
 	return rc;
 }
 
+int nft_print(struct output_ctx *octx, const char *fmt, ...)
+{
+	int ret;
+	va_list arg;
+
+	if (!octx->output_fp)
+		return -1;
+
+	va_start(arg, fmt);
+	ret = vfprintf(octx->output_fp, fmt, arg);
+	va_end(arg);
+	fflush(octx->output_fp);
+
+	return ret;
+}
+
+int nft_gmp_print(struct output_ctx *octx, const char *fmt, ...)
+{
+	int ret;
+	va_list arg;
+
+	if (!octx->output_fp)
+		return -1;
+
+	va_start(arg, fmt);
+	ret = gmp_vfprintf(octx->output_fp, fmt, arg);
+	va_end(arg);
+
+	return ret;
+}
+
 int main(int argc, char * const *argv)
 {
 	char *buf = NULL, *filename = NULL;
@@ -372,8 +414,11 @@ int main(int argc, char * const *argv)
 	bool interactive = false;
 	struct parser_state state;
 	int i, val, rc;
+	FILE *outfp = fdopen(dup(STDOUT_FILENO), "w");
 
 	nft = nft_ctx_new(NFT_CTX_DEFAULT);
+	nft_ctx_set_output(nft, outfp);
+	close(STDOUT_FILENO);
 
 	while (1) {
 		val = getopt_long(argc, argv, OPTSTRING, options, NULL);
diff --git a/src/meta.c b/src/meta.c
index 9c808930532ea..56b9e29699743 100644
--- a/src/meta.c
+++ b/src/meta.c
@@ -54,13 +54,15 @@ static void tchandle_type_print(const struct expr *expr,
 
 	switch(handle) {
 	case TC_H_ROOT:
-		printf("root");
+		nft_print(octx, "root");
 		break;
 	case TC_H_UNSPEC:
-		printf("none");
+		nft_print(octx, "none");
 		break;
 	default:
-		printf("%0x:%0x", TC_H_MAJ(handle) >> 16, TC_H_MIN(handle));
+		nft_print(octx, "%0x:%0x",
+			  TC_H_MAJ(handle) >> 16,
+			  TC_H_MIN(handle));
 		break;
 	}
 }
@@ -134,9 +136,9 @@ static void ifindex_type_print(const struct expr *expr, struct output_ctx *octx)
 
 	ifindex = mpz_get_uint32(expr->value);
 	if (nft_if_indextoname(ifindex, name))
-		printf("\"%s\"", name);
+		nft_print(octx, "\"%s\"", name);
 	else
-		printf("%d", ifindex);
+		nft_print(octx, "%d", ifindex);
 }
 
 static struct error_record *ifindex_type_parse(const struct expr *sym,
@@ -209,9 +211,9 @@ static void uid_type_print(const struct expr *expr, struct output_ctx *octx)
 
 		pw = getpwuid(uid);
 		if (pw != NULL)
-			printf("\"%s\"", pw->pw_name);
+			nft_print(octx, "\"%s\"", pw->pw_name);
 		else
-			printf("%d", uid);
+			nft_print(octx, "%d", uid);
 		return;
 	}
 	expr_basetype(expr)->print(expr, octx);
@@ -261,9 +263,9 @@ static void gid_type_print(const struct expr *expr, struct output_ctx *octx)
 
 		gr = getgrgid(gid);
 		if (gr != NULL)
-			printf("\"%s\"", gr->gr_name);
+			nft_print(octx, "\"%s\"", gr->gr_name);
 		else
-			printf("%u", gid);
+			nft_print(octx, "%u", gid);
 		return;
 	}
 	expr_basetype(expr)->print(expr, octx);
@@ -446,9 +448,11 @@ static bool meta_key_is_qualified(enum nft_meta_keys key)
 static void meta_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	if (meta_key_is_qualified(expr->meta.key))
-		printf("meta %s", meta_templates[expr->meta.key].token);
+		nft_print(octx, "meta %s",
+			  meta_templates[expr->meta.key].token);
 	else
-		printf("%s", meta_templates[expr->meta.key].token);
+		nft_print(octx, "%s",
+			  meta_templates[expr->meta.key].token);
 }
 
 static bool meta_expr_cmp(const struct expr *e1, const struct expr *e2)
@@ -573,9 +577,11 @@ struct expr *meta_expr_alloc(const struct location *loc, enum nft_meta_keys key)
 static void meta_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
 	if (meta_key_is_qualified(stmt->meta.key))
-		printf("meta %s set ", meta_templates[stmt->meta.key].token);
+		nft_print(octx, "meta %s set ",
+			  meta_templates[stmt->meta.key].token);
 	else
-		printf("%s set ", meta_templates[stmt->meta.key].token);
+		nft_print(octx, "%s set ",
+			  meta_templates[stmt->meta.key].token);
 
 	expr_print(stmt->meta.expr, octx);
 }
diff --git a/src/netlink.c b/src/netlink.c
index 3183a8100f752..d5d410a847624 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -39,6 +39,8 @@
 #include <erec.h>
 #include <iface.h>
 
+#define nft_mon_print(monh, ...) nft_print(monh->ctx->octx, __VA_ARGS__)
+
 const struct input_descriptor indesc_netlink = {
 	.name	= "netlink",
 	.type	= INDESC_NETLINK,
@@ -470,7 +472,7 @@ int netlink_replace_rule_batch(struct netlink_ctx *ctx, const struct handle *h,
 	if (ctx->octx->echo) {
 		err = cache_update(ctx->nf_sock, ctx->cache,
 				   CMD_INVALID, ctx->msgs,
-				   ctx->debug_mask & DEBUG_NETLINK);
+				   ctx->debug_mask & DEBUG_NETLINK, ctx->octx);
 		if (err < 0)
 			return err;
 
@@ -504,15 +506,15 @@ int netlink_del_rule_batch(struct netlink_ctx *ctx, const struct handle *h,
 	return err;
 }
 
-void netlink_dump_rule(const struct nftnl_rule *nlr, unsigned int debug_mask)
+void netlink_dump_rule(const struct nftnl_rule *nlr, struct netlink_ctx *ctx)
 {
-	char buf[4096];
+	FILE *fp = ctx->octx->output_fp;
 
-	if (!(debug_mask & DEBUG_NETLINK))
+	if (!(ctx->debug_mask & DEBUG_NETLINK) || !fp)
 		return;
 
-	nftnl_rule_snprintf(buf, sizeof(buf), nlr, 0, 0);
-	fprintf(stdout, "%s\n", buf);
+	nftnl_rule_fprintf(fp, nlr, 0, 0);
+	fprintf(fp, "\n");
 }
 
 void netlink_dump_expr(const struct nftnl_expr *nle,
@@ -544,7 +546,7 @@ static int list_rule_cb(struct nftnl_rule *nlr, void *arg)
 	    (h->chain && strcmp(chain, h->chain) != 0))
 		return 0;
 
-	netlink_dump_rule(nlr, ctx->debug_mask);
+	netlink_dump_rule(nlr, ctx);
 	rule = netlink_delinearize_rule(ctx, nlr);
 	list_add_tail(&rule->list, &ctx->list);
 
@@ -576,15 +578,15 @@ static int netlink_flush_rules(struct netlink_ctx *ctx, const struct handle *h,
 	return netlink_del_rule_batch(ctx, h, loc);
 }
 
-void netlink_dump_chain(const struct nftnl_chain *nlc, unsigned int debug_mask)
+void netlink_dump_chain(const struct nftnl_chain *nlc, struct netlink_ctx *ctx)
 {
-	char buf[4096];
+	FILE *fp = ctx->octx->output_fp;
 
-	if (!(debug_mask & DEBUG_NETLINK))
+	if (!(ctx->debug_mask & DEBUG_NETLINK) || !fp)
 		return;
 
-	nftnl_chain_snprintf(buf, sizeof(buf), nlc, 0, 0);
-	fprintf(stdout, "%s\n", buf);
+	nftnl_chain_fprintf(fp, nlc, 0, 0);
+	fprintf(fp, "\n");
 }
 
 static int netlink_add_chain_compat(struct netlink_ctx *ctx,
@@ -610,7 +612,7 @@ static int netlink_add_chain_compat(struct netlink_ctx *ctx,
 					    chain->policy);
 	}
 
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_add(ctx->nf_sock, nlc, flags, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -646,7 +648,7 @@ static int netlink_add_chain_batch(struct netlink_ctx *ctx,
 					    chain->dev);
 	}
 
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_batch_add(nlc, ctx->batch, flags, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -676,7 +678,7 @@ static int netlink_rename_chain_compat(struct netlink_ctx *ctx,
 
 	nlc = alloc_nftnl_chain(h);
 	nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, name);
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_add(ctx->nf_sock, nlc, 0, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -696,7 +698,7 @@ static int netlink_rename_chain_batch(struct netlink_ctx *ctx,
 
 	nlc = alloc_nftnl_chain(h);
 	nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, name);
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_batch_add(nlc, ctx->batch, 0, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -723,7 +725,7 @@ static int netlink_del_chain_compat(struct netlink_ctx *ctx,
 	int err;
 
 	nlc = alloc_nftnl_chain(h);
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_delete(ctx->nf_sock, nlc, 0, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -741,7 +743,7 @@ static int netlink_del_chain_batch(struct netlink_ctx *ctx,
 	int err;
 
 	nlc = alloc_nftnl_chain(h);
-	netlink_dump_chain(nlc, ctx->debug_mask);
+	netlink_dump_chain(nlc, ctx);
 	err = mnl_nft_chain_batch_del(nlc, ctx->batch, 0, ctx->seqnum);
 	nftnl_chain_free(nlc);
 
@@ -1031,15 +1033,15 @@ static const struct datatype *dtype_map_from_kernel(enum nft_data_types type)
 	}
 }
 
-void netlink_dump_set(const struct nftnl_set *nls, unsigned int debug_mask)
+void netlink_dump_set(const struct nftnl_set *nls, struct netlink_ctx *ctx)
 {
-	char buf[4096];
+	FILE *fp = ctx->octx->output_fp;
 
-	if (!(debug_mask & DEBUG_NETLINK))
+	if (!(ctx->debug_mask & DEBUG_NETLINK) || !fp)
 		return;
 
-	nftnl_set_snprintf(buf, sizeof(buf), nls, 0, 0);
-	fprintf(stdout, "%s\n", buf);
+	nftnl_set_fprintf(fp, nls, 0, 0);
+	fprintf(fp, "\n");
 }
 
 static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data)
@@ -1171,7 +1173,7 @@ static int netlink_add_set_compat(struct netlink_ctx *ctx,
 		nftnl_set_set_u32(nls, NFTNL_SET_DATA_LEN,
 				  set->datalen / BITS_PER_BYTE);
 	}
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_set_add(ctx->nf_sock, nls, NLM_F_ECHO | flags,
 			      ctx->seqnum);
@@ -1242,7 +1244,7 @@ static int netlink_add_set_batch(struct netlink_ctx *ctx,
 			   nftnl_udata_buf_len(udbuf));
 	nftnl_udata_buf_free(udbuf);
 
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_set_batch_add(nls, ctx->batch, flags, ctx->seqnum);
 	if (err < 0)
@@ -1357,7 +1359,7 @@ static int netlink_add_setelems_batch(struct netlink_ctx *ctx,
 
 	nls = alloc_nftnl_set(h);
 	alloc_setelem_cache(expr, nls);
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_setelem_batch_add(nls, ctx->batch, flags, ctx->seqnum);
 	nftnl_set_free(nls);
@@ -1377,7 +1379,7 @@ static int netlink_add_setelems_compat(struct netlink_ctx *ctx,
 
 	nls = alloc_nftnl_set(h);
 	alloc_setelem_cache(expr, nls);
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_setelem_add(ctx->nf_sock, nls, flags, ctx->seqnum);
 	nftnl_set_free(nls);
@@ -1407,7 +1409,7 @@ static int netlink_del_setelems_batch(struct netlink_ctx *ctx,
 	nls = alloc_nftnl_set(h);
 	if (expr)
 		alloc_setelem_cache(expr, nls);
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_setelem_batch_del(nls, ctx->batch, 0, ctx->seqnum);
 	nftnl_set_free(nls);
@@ -1427,7 +1429,7 @@ static int netlink_del_setelems_compat(struct netlink_ctx *ctx,
 
 	nls = alloc_nftnl_set(h);
 	alloc_setelem_cache(expr, nls);
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_setelem_delete(ctx->nf_sock, nls, 0, ctx->seqnum);
 	nftnl_set_free(nls);
@@ -1445,7 +1447,7 @@ int netlink_flush_setelems(struct netlink_ctx *ctx, const struct handle *h,
 	int err;
 
 	nls = alloc_nftnl_set(h);
-	netlink_dump_set(nls, ctx->debug_mask);
+	netlink_dump_set(nls, ctx);
 
 	err = mnl_nft_setelem_batch_flush(nls, ctx->batch, 0, ctx->seqnum);
 	nftnl_set_free(nls);
@@ -1657,15 +1659,15 @@ out:
 	return err;
 }
 
-void netlink_dump_obj(struct nftnl_obj *nln, unsigned int debug_mask)
+void netlink_dump_obj(struct nftnl_obj *nln, struct netlink_ctx *ctx)
 {
-	char buf[4096];
+	FILE *fp = ctx->octx->output_fp;
 
-	if (!(debug_mask & DEBUG_NETLINK))
+	if (!(ctx->debug_mask & DEBUG_NETLINK) || !fp)
 		return;
 
-	nftnl_obj_snprintf(buf, sizeof(buf), nln, 0, 0);
-	fprintf(stdout, "%s\n", buf);
+	nftnl_obj_fprintf(fp, nln, 0, 0);
+	fprintf(fp, "\n");
 }
 
 int netlink_add_obj(struct netlink_ctx *ctx, const struct handle *h,
@@ -1675,7 +1677,7 @@ int netlink_add_obj(struct netlink_ctx *ctx, const struct handle *h,
 	int err;
 
 	nlo = alloc_nftnl_obj(h, obj);
-	netlink_dump_obj(nlo, ctx->debug_mask);
+	netlink_dump_obj(nlo, ctx);
 
 	err = mnl_nft_obj_batch_add(nlo, ctx->batch, flags, ctx->seqnum);
 	if (err < 0)
@@ -1693,7 +1695,7 @@ int netlink_delete_obj(struct netlink_ctx *ctx, const struct handle *h,
 	int err;
 
 	nlo = __alloc_nftnl_obj(h, type);
-	netlink_dump_obj(nlo, ctx->debug_mask);
+	netlink_dump_obj(nlo, ctx);
 
 	err = mnl_nft_obj_batch_del(nlo, ctx->batch, 0, ctx->seqnum);
 	if (err < 0)
@@ -1999,16 +2001,16 @@ static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type,
 	case NFTNL_OUTPUT_DEFAULT:
 		if (type == NFT_MSG_NEWTABLE) {
 			if (nlh->nlmsg_flags & NLM_F_EXCL)
-				printf("update table ");
+				nft_mon_print(monh, "update table ");
 			else
-				printf("add table ");
+				nft_mon_print(monh, "add table ");
 		} else {
-			printf("delete table ");
+			nft_mon_print(monh, "delete table ");
 		}
 
 		family = nftnl_table_get_u32(nlt, NFTNL_TABLE_FAMILY);
 
-		printf("%s %s\n", family2str(family),
+		nft_mon_print(monh, "%s %s\n", family2str(family),
 		       nftnl_table_get_str(nlt, NFTNL_TABLE_NAME));
 		break;
 	case NFTNL_OUTPUT_XML:
@@ -2037,17 +2039,17 @@ static int netlink_events_chain_cb(const struct nlmsghdr *nlh, int type,
 		switch (type) {
 		case NFT_MSG_NEWCHAIN:
 			if (nlh->nlmsg_flags & NLM_F_EXCL)
-				printf("update ");
+				nft_mon_print(monh, "update ");
 			else
-				printf("add ");
+				nft_mon_print(monh, "add ");
 
 			c = netlink_delinearize_chain(monh->ctx, nlc);
-			chain_print_plain(c);
+			chain_print_plain(c, monh->ctx->octx);
 			chain_free(c);
 			break;
 		case NFT_MSG_DELCHAIN:
 			family = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY);
-			printf("delete chain %s %s %s\n", family2str(family),
+			nft_mon_print(monh, "delete chain %s %s %s\n", family2str(family),
 			       nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE),
 			       nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME));
 			break;
@@ -2081,7 +2083,7 @@ static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type,
 	case NFTNL_OUTPUT_DEFAULT:
 		switch (type) {
 		case NFT_MSG_NEWSET:
-			printf("add ");
+			nft_mon_print(monh, "add ");
 			set = netlink_delinearize_set(monh->ctx, nls);
 			if (set == NULL) {
 				nftnl_set_free(nls);
@@ -2089,11 +2091,11 @@ static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type,
 			}
 			set_print_plain(set, monh->ctx->octx);
 			set_free(set);
-			printf("\n");
+			nft_mon_print(monh, "\n");
 			break;
 		case NFT_MSG_DELSET:
 			family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY);
-			printf("delete set %s %s %s\n",
+			nft_mon_print(monh, "delete set %s %s %s\n",
 			       family2str(family),
 			       nftnl_set_get_str(nls, NFTNL_SET_TABLE),
 			       nftnl_set_get_str(nls, NFTNL_SET_NAME));
@@ -2234,18 +2236,18 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type,
 
 		switch (type) {
 		case NFT_MSG_NEWSETELEM:
-			printf("add ");
+			nft_mon_print(monh, "add ");
 			break;
 		case NFT_MSG_DELSETELEM:
-			printf("delete ");
+			nft_mon_print(monh, "delete ");
 			break;
 		default:
 			set_free(dummyset);
 			goto out;
 		}
-		printf("element %s %s %s ", family2str(family), table, setname);
+		nft_mon_print(monh, "element %s %s %s ", family2str(family), table, setname);
 		expr_print(dummyset->init, monh->ctx->octx);
-		printf("\n");
+		nft_mon_print(monh, "\n");
 
 		set_free(dummyset);
 		break;
@@ -2274,7 +2276,7 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type,
 	case NFTNL_OUTPUT_DEFAULT:
 		switch (type) {
 		case NFT_MSG_NEWOBJ:
-			printf("add ");
+			nft_mon_print(monh, "add ");
 			obj = netlink_delinearize_obj(monh->ctx, nlo);
 			if (obj == NULL) {
 				nftnl_obj_free(nlo);
@@ -2282,11 +2284,11 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type,
 			}
 			obj_print_plain(obj, monh->ctx->octx);
 			obj_free(obj);
-			printf("\n");
+			nft_mon_print(monh, "\n");
 			break;
 		case NFT_MSG_DELOBJ:
 			family = nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY);
-			printf("delete %s %s %s %s\n",
+			nft_mon_print(monh, "delete %s %s %s %s\n",
 			       obj_type_name(nftnl_obj_get_u32(nlo, NFTNL_OBJ_TYPE)),
 			       family2str(family),
 			       nftnl_obj_get_str(nlo, NFTNL_OBJ_TABLE),
@@ -2338,14 +2340,14 @@ static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type,
 			nlr_for_each_set(nlr, rule_map_decompose_cb, NULL,
 					 monh->cache);
 
-			printf("add rule %s %s %s ", family, table, chain);
+			nft_mon_print(monh, "add rule %s %s %s ", family, table, chain);
 			rule_print(r, monh->ctx->octx);
-			printf("\n");
+			nft_mon_print(monh, "\n");
 
 			rule_free(r);
 			break;
 		case NFT_MSG_DELRULE:
-			printf("delete rule %s %s %s handle %u\n",
+			nft_mon_print(monh, "delete rule %s %s %s handle %u\n",
 			       family, table, chain, (unsigned int)handle);
 			break;
 		}
@@ -2923,13 +2925,13 @@ static int netlink_events_newgen_cb(const struct nlmsghdr *nlh, int type,
 		}
 	}
 	if (genid >= 0) {
-		printf("# new generation %d", genid);
+		nft_mon_print(monh, "# new generation %d", genid);
 		if (pid >= 0) {
-			printf(" by process %d", pid);
+			nft_mon_print(monh, " by process %d", pid);
 			if (!monh->ctx->octx->numeric)
-				printf(" (%s)", name);
+				nft_mon_print(monh, " (%s)", name);
 		}
-		printf("\n");
+		nft_mon_print(monh, "\n");
 	}
 
 	return MNL_CB_OK;
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index c070fee2f6525..1712cba2d45a0 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -1337,5 +1337,5 @@ void netlink_linearize_rule(struct netlink_ctx *ctx, struct nftnl_rule *nlr,
 		nftnl_udata_buf_free(udata);
 	}
 
-	netlink_dump_rule(nlr, ctx->debug_mask);
+	netlink_dump_rule(nlr, ctx);
 }
diff --git a/src/numgen.c b/src/numgen.c
index 19a4a9ce06450..aa6da490d5d99 100644
--- a/src/numgen.c
+++ b/src/numgen.c
@@ -30,10 +30,11 @@ static const char *numgen_type_str(enum nft_ng_types type)
 
 static void numgen_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
-	printf("numgen %s mod %u", numgen_type_str(expr->numgen.type),
-	       expr->numgen.mod);
+	nft_print(octx, "numgen %s mod %u",
+		  numgen_type_str(expr->numgen.type),
+		  expr->numgen.mod);
 	if (expr->numgen.offset)
-		printf(" offset %u", expr->numgen.offset);
+		nft_print(octx, " offset %u", expr->numgen.offset);
 }
 
 static bool numgen_expr_cmp(const struct expr *e1, const struct expr *e2)
diff --git a/src/parser_bison.y b/src/parser_bison.y
index c7ba1495adf33..fad7036022130 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -37,7 +37,7 @@
 
 void parser_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 		 struct parser_state *state, struct list_head *msgs,
-		 unsigned int debug_mask)
+		 unsigned int debug_mask, struct output_ctx *octx)
 {
 	memset(state, 0, sizeof(*state));
 	init_list_head(&state->cmds);
@@ -48,6 +48,7 @@ void parser_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 	state->ectx.msgs = msgs;
 	state->ectx.nf_sock = nf_sock;
 	state->ectx.debug_mask = debug_mask;
+	state->ectx.octx = octx;
 }
 
 static void yyerror(struct location *loc, struct nft_ctx *nft, void *scanner,
diff --git a/src/payload.c b/src/payload.c
index 7f94ff7f2a47c..aa8a95ad59f1c 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -46,11 +46,11 @@ static void payload_expr_print(const struct expr *expr, struct output_ctx *octx)
 	desc = expr->payload.desc;
 	tmpl = expr->payload.tmpl;
 	if (payload_is_known(expr))
-		printf("%s %s", desc->name, tmpl->token);
+		nft_print(octx, "%s %s", desc->name, tmpl->token);
 	else
-		printf("payload @%s,%u,%u",
-		       proto_base_tokens[expr->payload.base],
-		       expr->payload.offset, expr->len);
+		nft_print(octx, "payload @%s,%u,%u",
+			  proto_base_tokens[expr->payload.base],
+			  expr->payload.offset, expr->len);
 }
 
 static bool payload_expr_cmp(const struct expr *e1, const struct expr *e2)
@@ -187,7 +187,7 @@ unsigned int payload_hdr_field(const struct expr *expr)
 static void payload_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
 	expr_print(stmt->payload.expr, octx);
-	printf(" set ");
+	nft_print(octx, " set ");
 	expr_print(stmt->payload.val, octx);
 }
 
diff --git a/src/rt.c b/src/rt.c
index 91be5a11c5a28..9ad9e398ebef3 100644
--- a/src/rt.c
+++ b/src/rt.c
@@ -82,7 +82,7 @@ static const struct rt_template rt_templates[] = {
 
 static void rt_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
-	printf("rt %s", rt_templates[expr->rt.key].token);
+	nft_print(octx, "rt %s", rt_templates[expr->rt.key].token);
 }
 
 static bool rt_expr_cmp(const struct expr *e1, const struct expr *e2)
diff --git a/src/rule.c b/src/rule.c
index 91129734a3d8f..7092d2d6141b7 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -123,7 +123,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, enum cmd_ops cmd)
 
 static int cache_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 		      enum cmd_ops cmd, struct list_head *msgs,
-		      unsigned int debug_mask)
+		      unsigned int debug_mask, struct output_ctx *octx)
 {
 	struct handle handle = {
 		.family = NFPROTO_UNSPEC,
@@ -135,6 +135,7 @@ static int cache_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 		.msgs		= msgs,
 		.seqnum		= cache->seqnum++,
 		.debug_mask	= debug_mask,
+		.octx		= octx,
 	};
 	int ret;
 
@@ -149,7 +150,8 @@ static int cache_init(struct mnl_socket *nf_sock, struct nft_cache *cache,
 }
 
 int cache_update(struct mnl_socket *nf_sock, struct nft_cache *cache,
-		 enum cmd_ops cmd, struct list_head *msgs, bool debug)
+		 enum cmd_ops cmd, struct list_head *msgs, bool debug,
+		 struct output_ctx *octx)
 {
 	int ret;
 
@@ -157,7 +159,7 @@ int cache_update(struct mnl_socket *nf_sock, struct nft_cache *cache,
 		return 0;
 replay:
 	netlink_genid_get(nf_sock, cache->seqnum++);
-	ret = cache_init(nf_sock, cache, cmd, msgs, debug);
+	ret = cache_init(nf_sock, cache, cmd, msgs, debug, octx);
 	if (ret < 0) {
 		cache_release(cache);
 		if (errno == EINTR) {
@@ -272,7 +274,8 @@ static const char *set_policy2str(uint32_t policy)
 }
 
 static void set_print_declaration(const struct set *set,
-				  struct print_fmt_options *opts)
+				  struct print_fmt_options *opts,
+				  struct output_ctx *octx)
 {
 	const char *delim = "";
 	const char *type;
@@ -285,34 +288,38 @@ static void set_print_declaration(const struct set *set,
 	else
 		type = "set";
 
-	printf("%s%s", opts->tab, type);
+	nft_print(octx, "%s%s", opts->tab, type);
 
 	if (opts->family != NULL)
-		printf(" %s", opts->family);
+		nft_print(octx, " %s", opts->family);
 
 	if (opts->table != NULL)
-		printf(" %s", opts->table);
+		nft_print(octx, " %s", opts->table);
 
-	printf(" %s {%s", set->handle.set, opts->nl);
+	nft_print(octx, " %s {%s", set->handle.set, opts->nl);
 
-	printf("%s%stype %s", opts->tab, opts->tab, set->key->dtype->name);
+	nft_print(octx, "%s%stype %s",
+		  opts->tab, opts->tab, set->key->dtype->name);
 	if (set->flags & NFT_SET_MAP)
-		printf(" : %s", set->datatype->name);
+		nft_print(octx, " : %s", set->datatype->name);
 	else if (set->flags & NFT_SET_OBJECT)
-		printf(" : %s", obj_type_name(set->objtype));
+		nft_print(octx, " : %s", obj_type_name(set->objtype));
 
-	printf("%s", opts->stmt_separator);
+	nft_print(octx, "%s", opts->stmt_separator);
 
 	if (!(set->flags & (NFT_SET_CONSTANT))) {
 		if (set->policy != NFT_SET_POL_PERFORMANCE) {
-			printf("%s%spolicy %s%s", opts->tab, opts->tab,
-			       set_policy2str(set->policy),
-			       opts->stmt_separator);
+			nft_print(octx, "%s%spolicy %s%s",
+				  opts->tab, opts->tab,
+				  set_policy2str(set->policy),
+				  opts->stmt_separator);
 		}
 
 		if (set->desc.size > 0) {
-			printf("%s%ssize %u%s", opts->tab, opts->tab,
-			       set->desc.size, opts->stmt_separator);
+			nft_print(octx, "%s%ssize %u%s",
+				  opts->tab, opts->tab,
+				  set->desc.size,
+				  opts->stmt_separator);
 		}
 	}
 
@@ -322,45 +329,45 @@ static void set_print_declaration(const struct set *set,
 		flags &= ~NFT_SET_TIMEOUT;
 
 	if (flags & (NFT_SET_CONSTANT | NFT_SET_INTERVAL | NFT_SET_TIMEOUT)) {
-		printf("%s%sflags ", opts->tab, opts->tab);
+		nft_print(octx, "%s%sflags ", opts->tab, opts->tab);
 		if (set->flags & NFT_SET_CONSTANT) {
-			printf("%sconstant", delim);
+			nft_print(octx, "%sconstant", delim);
 			delim = ",";
 		}
 		if (set->flags & NFT_SET_INTERVAL) {
-			printf("%sinterval", delim);
+			nft_print(octx, "%sinterval", delim);
 			delim = ",";
 		}
 		if (set->flags & NFT_SET_TIMEOUT) {
-			printf("%stimeout", delim);
+			nft_print(octx, "%stimeout", delim);
 			delim = ",";
 		}
-		printf("%s", opts->stmt_separator);
+		nft_print(octx, "%s", opts->stmt_separator);
 	}
 
 	if (set->timeout) {
-		printf("%s%stimeout ", opts->tab, opts->tab);
-		time_print(set->timeout / 1000);
-		printf("%s", opts->stmt_separator);
+		nft_print(octx, "%s%stimeout ", opts->tab, opts->tab);
+		time_print(set->timeout / 1000, octx);
+		nft_print(octx, "%s", opts->stmt_separator);
 	}
 	if (set->gc_int) {
-		printf("%s%sgc-interval ", opts->tab, opts->tab);
-		time_print(set->gc_int / 1000);
-		printf("%s", opts->stmt_separator);
+		nft_print(octx, "%s%sgc-interval ", opts->tab, opts->tab);
+		time_print(set->gc_int / 1000, octx);
+		nft_print(octx, "%s", opts->stmt_separator);
 	}
 }
 
 static void do_set_print(const struct set *set, struct print_fmt_options *opts,
 			  struct output_ctx *octx)
 {
-	set_print_declaration(set, opts);
+	set_print_declaration(set, opts, octx);
 
 	if (set->init != NULL && set->init->size > 0) {
-		printf("%s%selements = ", opts->tab, opts->tab);
+		nft_print(octx, "%s%selements = ", opts->tab, opts->tab);
 		expr_print(set->init, octx);
-		printf("%s", opts->nl);
+		nft_print(octx, "%s", opts->nl);
 	}
-	printf("%s}%s", opts->tab, opts->nl);
+	nft_print(octx, "%s}%s", opts->tab, opts->nl);
 }
 
 void set_print(const struct set *s, struct output_ctx *octx)
@@ -425,14 +432,14 @@ void rule_print(const struct rule *rule, struct output_ctx *octx)
 	list_for_each_entry(stmt, &rule->stmts, list) {
 		stmt->ops->print(stmt, octx);
 		if (!list_is_last(&stmt->list, &rule->stmts))
-			printf(" ");
+			nft_print(octx, " ");
 	}
 
 	if (rule->comment)
-		printf(" comment \"%s\"", rule->comment);
+		nft_print(octx, " comment \"%s\"", rule->comment);
 
 	if (octx->handle > 0)
-		printf(" # handle %" PRIu64, rule->handle.handle.id);
+		nft_print(octx, " # handle %" PRIu64, rule->handle.handle.id);
 }
 
 struct rule *rule_lookup(const struct chain *chain, uint64_t handle)
@@ -662,16 +669,17 @@ static const char *chain_policy2str(uint32_t policy)
 	return "unknown";
 }
 
-static void chain_print_declaration(const struct chain *chain)
+static void chain_print_declaration(const struct chain *chain,
+				    struct output_ctx *octx)
 {
-	printf("\tchain %s {\n", chain->handle.chain);
+	nft_print(octx, "\tchain %s {\n", chain->handle.chain);
 	if (chain->flags & CHAIN_F_BASECHAIN) {
-		printf("\t\ttype %s hook %s", chain->type,
-		       hooknum2str(chain->handle.family, chain->hooknum));
+		nft_print(octx, "\t\ttype %s hook %s", chain->type,
+			  hooknum2str(chain->handle.family, chain->hooknum));
 		if (chain->dev != NULL)
-			printf(" device %s", chain->dev);
-		printf(" priority %d; policy %s;\n",
-		       chain->priority, chain_policy2str(chain->policy));
+			nft_print(octx, " device %s", chain->dev);
+		nft_print(octx, " priority %d; policy %s;\n",
+			  chain->priority, chain_policy2str(chain->policy));
 	}
 }
 
@@ -679,28 +687,28 @@ static void chain_print(const struct chain *chain, struct output_ctx *octx)
 {
 	struct rule *rule;
 
-	chain_print_declaration(chain);
+	chain_print_declaration(chain, octx);
 
 	list_for_each_entry(rule, &chain->rules, list) {
-		printf("\t\t");
+		nft_print(octx, "\t\t");
 		rule_print(rule, octx);
-		printf("\n");
+		nft_print(octx, "\n");
 	}
-	printf("\t}\n");
+	nft_print(octx, "\t}\n");
 }
 
-void chain_print_plain(const struct chain *chain)
+void chain_print_plain(const struct chain *chain, struct output_ctx *octx)
 {
-	printf("chain %s %s %s", family2str(chain->handle.family),
-	       chain->handle.table, chain->handle.chain);
+	nft_print(octx, "chain %s %s %s", family2str(chain->handle.family),
+		  chain->handle.table, chain->handle.chain);
 
 	if (chain->flags & CHAIN_F_BASECHAIN) {
-		printf(" { type %s hook %s priority %d; policy %s; }",
-		       chain->type, chain->hookstr,
-		       chain->priority, chain_policy2str(chain->policy));
+		nft_print(octx, " { type %s hook %s priority %d; policy %s; }",
+			  chain->type, chain->hookstr,
+			  chain->priority, chain_policy2str(chain->policy));
 	}
 
-	printf("\n");
+	nft_print(octx, "\n");
 }
 
 struct table *table_alloc(void)
@@ -763,22 +771,23 @@ const char *table_flags_name[TABLE_FLAGS_MAX] = {
 	"dormant",
 };
 
-static void table_print_options(const struct table *table, const char **delim)
+static void table_print_options(const struct table *table, const char **delim,
+				struct output_ctx *octx)
 {
 	uint32_t flags = table->flags;
 	int i;
 
 	if (flags) {
-		printf("\tflags ");
+		nft_print(octx, "\tflags ");
 
 		for (i = 0; i < TABLE_FLAGS_MAX; i++) {
 			if (flags & 0x1)
-				printf("%s", table_flags_name[i]);
+				nft_print(octx, "%s", table_flags_name[i]);
 			flags >>= 1;
 			if (flags)
-				printf(",");
+				nft_print(octx, ",");
 		}
-		printf("\n");
+		nft_print(octx, "\n");
 		*delim = "\n";
 	}
 }
@@ -791,27 +800,27 @@ static void table_print(const struct table *table, struct output_ctx *octx)
 	const char *delim = "";
 	const char *family = family2str(table->handle.family);
 
-	printf("table %s %s {\n", family, table->handle.table);
-	table_print_options(table, &delim);
+	nft_print(octx, "table %s %s {\n", family, table->handle.table);
+	table_print_options(table, &delim, octx);
 
 	list_for_each_entry(obj, &table->objs, list) {
-		printf("%s", delim);
+		nft_print(octx, "%s", delim);
 		obj_print(obj, octx);
 		delim = "\n";
 	}
 	list_for_each_entry(set, &table->sets, list) {
 		if (set->flags & NFT_SET_ANONYMOUS)
 			continue;
-		printf("%s", delim);
+		nft_print(octx, "%s", delim);
 		set_print(set, octx);
 		delim = "\n";
 	}
 	list_for_each_entry(chain, &table->chains, list) {
-		printf("%s", delim);
+		nft_print(octx, "%s", delim);
 		chain_print(chain, octx);
 		delim = "\n";
 	}
-	printf("}\n");
+	nft_print(octx, "}\n");
 }
 
 struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
@@ -1020,7 +1029,7 @@ static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl)
 		int ret;
 
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->obj,
-				   ctx->msgs, ctx->debug_mask);
+				   ctx->msgs, ctx->debug_mask, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -1072,7 +1081,7 @@ static int do_command_insert(struct netlink_ctx *ctx, struct cmd *cmd)
 		int ret;
 
 		ret = cache_update(ctx->nf_sock, ctx->cache, cmd->obj,
-				   ctx->msgs, ctx->debug_mask);
+				   ctx->msgs, ctx->debug_mask, ctx->octx);
 		if (ret < 0)
 			return ret;
 
@@ -1178,9 +1187,9 @@ static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd)
 		    cmd->handle.family != table->handle.family)
 			continue;
 
-		printf("table %s %s {\n",
-		       family2str(table->handle.family),
-		       table->handle.table);
+		nft_print(ctx->octx, "table %s %s {\n",
+			  family2str(table->handle.family),
+			  table->handle.table);
 
 		list_for_each_entry(set, &table->sets, list) {
 			if (cmd->obj == CMD_OBJ_SETS &&
@@ -1193,11 +1202,11 @@ static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd)
 			if (cmd->obj == CMD_OBJ_MAPS &&
 			    !(set->flags & NFT_SET_MAP))
 				continue;
-			set_print_declaration(set, &opts);
-			printf("%s}%s", opts.tab, opts.nl);
+			set_print_declaration(set, &opts, ctx->octx);
+			nft_print(ctx->octx, "%s}%s", opts.tab, opts.nl);
 		}
 
-		printf("}\n");
+		nft_print(ctx->octx, "}\n");
 	}
 	return 0;
 }
@@ -1246,14 +1255,14 @@ struct obj *obj_lookup(const struct table *table, const char *name,
 	return NULL;
 }
 
-static void print_proto_name_proto(uint8_t l4)
+static void print_proto_name_proto(uint8_t l4, struct output_ctx *octx)
 {
 	const struct protoent *p = getprotobynumber(l4);
 
 	if (p)
-		printf("%s\n", p->p_name);
+		nft_print(octx, "%s\n", p->p_name);
 	else
-		printf("%d\n", l4);
+		nft_print(octx, "%d\n", l4);
 }
 
 static void obj_print_data(const struct obj *obj,
@@ -1262,36 +1271,39 @@ static void obj_print_data(const struct obj *obj,
 {
 	switch (obj->type) {
 	case NFT_OBJECT_COUNTER:
-		printf(" %s {%s%s%s", obj->handle.obj,
-				      opts->nl, opts->tab, opts->tab);
+		nft_print(octx, " %s {%s%s%s", obj->handle.obj,
+			  opts->nl, opts->tab, opts->tab);
 		if (octx->stateless) {
-			printf("packets 0 bytes 0");
+			nft_print(octx, "packets 0 bytes 0");
 			break;
 		}
-		printf("packets %"PRIu64" bytes %"PRIu64"",
-		       obj->counter.packets, obj->counter.bytes);
+		nft_print(octx, "packets %" PRIu64 " bytes %" PRIu64 "",
+			  obj->counter.packets, obj->counter.bytes);
 		break;
 	case NFT_OBJECT_QUOTA: {
 		const char *data_unit;
 		uint64_t bytes;
 
-		printf(" %s {%s%s%s", obj->handle.obj,
-				      opts->nl, opts->tab, opts->tab);
+		nft_print(octx, " %s {%s%s%s", obj->handle.obj,
+			  opts->nl, opts->tab, opts->tab);
 		data_unit = get_rate(obj->quota.bytes, &bytes);
-		printf("%s%"PRIu64" %s",
-		       obj->quota.flags & NFT_QUOTA_F_INV ? "over " : "",
-		       bytes, data_unit);
+		nft_print(octx, "%s%" PRIu64 " %s",
+			  obj->quota.flags & NFT_QUOTA_F_INV ? "over " : "",
+			  bytes, data_unit);
 		if (!octx->stateless && obj->quota.used) {
 			data_unit = get_rate(obj->quota.used, &bytes);
-			printf(" used %"PRIu64" %s", bytes, data_unit);
+			nft_print(octx, " used %" PRIu64 " %s",
+				  bytes, data_unit);
 		}
 		}
 		break;
 	case NFT_OBJECT_CT_HELPER: {
-		printf("ct helper %s {\n", obj->handle.obj);
-		printf("\t\ttype \"%s\" protocol ", obj->ct_helper.name);
-		print_proto_name_proto(obj->ct_helper.l4proto);
-		printf("\t\tl3proto %s", family2str(obj->ct_helper.l3proto));
+		nft_print(octx, "ct helper %s {\n", obj->handle.obj);
+		nft_print(octx, "\t\ttype \"%s\" protocol ",
+			  obj->ct_helper.name);
+		print_proto_name_proto(obj->ct_helper.l4proto, octx);
+		nft_print(octx, "\t\tl3proto %s",
+			  family2str(obj->ct_helper.l3proto));
 		break;
 		}
 	case NFT_OBJECT_LIMIT: {
@@ -1299,34 +1311,36 @@ static void obj_print_data(const struct obj *obj,
 		const char *data_unit;
 		uint64_t rate;
 
-		printf(" %s {%s%s%s", obj->handle.obj,
-				      opts->nl, opts->tab, opts->tab);
+		nft_print(octx, " %s {%s%s%s", obj->handle.obj,
+			  opts->nl, opts->tab, opts->tab);
 		switch (obj->limit.type) {
 		case NFT_LIMIT_PKTS:
-			printf("limit rate %s%" PRIu64 "/%s",
-			       inv ? "over " : "", obj->limit.rate,
-			       get_unit(obj->limit.unit));
+			nft_print(octx, "limit rate %s%" PRIu64 "/%s",
+				  inv ? "over " : "", obj->limit.rate,
+				  get_unit(obj->limit.unit));
 			if (obj->limit.burst > 0)
-				printf(" burst %u packets", obj->limit.burst);
+				nft_print(octx, " burst %u packets",
+					  obj->limit.burst);
 			break;
 		case NFT_LIMIT_PKT_BYTES:
 			data_unit = get_rate(obj->limit.rate, &rate);
 
-			printf("limit rate %s%" PRIu64 " %s/%s",
-			       inv ? "over " : "", rate, data_unit,
-			       get_unit(obj->limit.unit));
+			nft_print(octx, "limit rate %s%" PRIu64 " %s/%s",
+				  inv ? "over " : "", rate, data_unit,
+				  get_unit(obj->limit.unit));
 			if (obj->limit.burst > 0) {
 				uint64_t burst;
 
 				data_unit = get_rate(obj->limit.burst, &burst);
-				printf(" burst %"PRIu64" %s", burst, data_unit);
+				nft_print(octx, " burst %"PRIu64" %s",
+					  burst, data_unit);
 			}
 			break;
 		}
 		}
 		break;
 	default:
-		printf("unknown {%s", opts->nl);
+		nft_print(octx, "unknown {%s", opts->nl);
 		break;
 	}
 }
@@ -1363,17 +1377,17 @@ static void obj_print_declaration(const struct obj *obj,
 				  struct print_fmt_options *opts,
 				  struct output_ctx *octx)
 {
-	printf("%s%s", opts->tab, obj_type_name(obj->type));
+	nft_print(octx, "%s%s", opts->tab, obj_type_name(obj->type));
 
 	if (opts->family != NULL)
-		printf(" %s", opts->family);
+		nft_print(octx, " %s", opts->family);
 
 	if (opts->table != NULL)
-		printf(" %s", opts->table);
+		nft_print(octx, " %s", opts->table);
 
 	obj_print_data(obj, opts, octx);
 
-	printf("%s%s}%s", opts->nl, opts->tab, opts->nl);
+	nft_print(octx, "%s%s}%s", opts->nl, opts->tab, opts->nl);
 }
 
 void obj_print(const struct obj *obj, struct output_ctx *octx)
@@ -1414,13 +1428,13 @@ static int do_list_obj(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t type)
 		    cmd->handle.family != table->handle.family)
 			continue;
 
-		printf("table %s %s {\n",
-		       family2str(table->handle.family),
-		       table->handle.table);
+		nft_print(ctx->octx, "table %s %s {\n",
+			  family2str(table->handle.family),
+			  table->handle.table);
 
 		if (cmd->handle.table != NULL &&
 		    strcmp(cmd->handle.table, table->handle.table)) {
-			printf("}\n");
+			nft_print(ctx->octx, "}\n");
 			continue;
 		}
 
@@ -1433,7 +1447,7 @@ static int do_list_obj(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t type)
 			obj_print_declaration(obj, &opts, ctx->octx);
 		}
 
-		printf("}\n");
+		nft_print(ctx->octx, "}\n");
 	}
 	return 0;
 }
@@ -1469,19 +1483,20 @@ static int do_list_tables(struct netlink_ctx *ctx, struct cmd *cmd)
 		    cmd->handle.family != table->handle.family)
 			continue;
 
-		printf("table %s %s\n",
-		       family2str(table->handle.family),
-		       table->handle.table);
+		nft_print(ctx->octx, "table %s %s\n",
+			  family2str(table->handle.family),
+			  table->handle.table);
 	}
 
 	return 0;
 }
 
-static void table_print_declaration(struct table *table)
+static void table_print_declaration(struct table *table,
+				    struct output_ctx *octx)
 {
-	printf("table %s %s {\n",
-		family2str(table->handle.family),
-		table->handle.table);
+	nft_print(octx, "table %s %s {\n",
+		  family2str(table->handle.family),
+		  table->handle.table);
 }
 
 static int do_list_chain(struct netlink_ctx *ctx, struct cmd *cmd,
@@ -1489,7 +1504,7 @@ static int do_list_chain(struct netlink_ctx *ctx, struct cmd *cmd,
 {
 	struct chain *chain;
 
-	table_print_declaration(table);
+	table_print_declaration(table, ctx->octx);
 
 	list_for_each_entry(chain, &table->chains, list) {
 		if (chain->handle.family != cmd->handle.family ||
@@ -1499,7 +1514,7 @@ static int do_list_chain(struct netlink_ctx *ctx, struct cmd *cmd,
 		chain_print(chain, ctx->octx);
 	}
 
-	printf("}\n");
+	nft_print(ctx->octx, "}\n");
 
 	return 0;
 }
@@ -1514,13 +1529,13 @@ static int do_list_chains(struct netlink_ctx *ctx, struct cmd *cmd)
 		    cmd->handle.family != table->handle.family)
 			continue;
 
-		table_print_declaration(table);
+		table_print_declaration(table, ctx->octx);
 
 		list_for_each_entry(chain, &table->chains, list) {
-			chain_print_declaration(chain);
-			printf("\t}\n");
+			chain_print_declaration(chain, ctx->octx);
+			nft_print(ctx->octx, "\t}\n");
 		}
-		printf("}\n");
+		nft_print(ctx->octx, "}\n");
 	}
 
 	return 0;
@@ -1535,9 +1550,9 @@ static int do_list_set(struct netlink_ctx *ctx, struct cmd *cmd,
 	if (set == NULL)
 		return -1;
 
-	table_print_declaration(table);
+	table_print_declaration(table, ctx->octx);
 	set_print(set, ctx->octx);
-	printf("}\n");
+	nft_print(ctx->octx, "}\n");
 
 	return 0;
 }
@@ -1728,9 +1743,10 @@ static int do_command_monitor(struct netlink_ctx *ctx, struct cmd *cmd)
 	return netlink_monitor(&monhandler, ctx->nf_sock);
 }
 
-static int do_command_describe(struct netlink_ctx *ctx, struct cmd *cmd)
+static int do_command_describe(struct netlink_ctx *ctx, struct cmd *cmd,
+			       struct output_ctx *octx)
 {
-	expr_describe(cmd->expr);
+	expr_describe(cmd->expr, octx);
 	return 0;
 }
 
@@ -1776,7 +1792,7 @@ int do_command(struct netlink_ctx *ctx, struct cmd *cmd)
 	case CMD_MONITOR:
 		return do_command_monitor(ctx, cmd);
 	case CMD_DESCRIBE:
-		return do_command_describe(ctx, cmd);
+		return do_command_describe(ctx, cmd, ctx->octx);
 	default:
 		BUG("invalid command object type %u\n", cmd->obj);
 	}
diff --git a/src/statement.c b/src/statement.c
index 0b2c28bc374af..6166863bacedc 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -109,20 +109,20 @@ struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr)
 
 static void flow_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("flow ");
+	nft_print(octx, "flow ");
 	if (stmt->flow.set) {
 		expr_print(stmt->flow.set, octx);
-		printf(" ");
+		nft_print(octx, " ");
 	}
-	printf("{ ");
+	nft_print(octx, "{ ");
 	expr_print(stmt->flow.key, octx);
-	printf(" ");
+	nft_print(octx, " ");
 
 	octx->stateless++;
 	stmt_print(stmt->flow.stmt, octx);
 	octx->stateless--;
 
-	printf("} ");
+	nft_print(octx, "} ");
 
 }
 
@@ -147,13 +147,13 @@ struct stmt *flow_stmt_alloc(const struct location *loc)
 
 static void counter_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("counter");
+	nft_print(octx, "counter");
 
 	if (octx->stateless)
 		return;
 
-	printf(" packets %" PRIu64 " bytes %" PRIu64,
-	       stmt->counter.packets, stmt->counter.bytes);
+	nft_print(octx, " packets %" PRIu64 " bytes %" PRIu64,
+		  stmt->counter.packets, stmt->counter.bytes);
 }
 
 static const struct stmt_ops counter_stmt_ops = {
@@ -190,10 +190,11 @@ static void objref_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
 	switch (stmt->objref.type) {
 	case NFT_OBJECT_CT_HELPER:
-		printf("ct helper set ");
+		nft_print(octx, "ct helper set ");
 		break;
 	default:
-		printf("%s name ", objref_type_name(stmt->objref.type));
+		nft_print(octx, "%s name ",
+			  objref_type_name(stmt->objref.type));
 		break;
 	}
 	expr_print(stmt->objref.expr, octx);
@@ -234,39 +235,40 @@ static const char *log_level(uint32_t level)
 
 static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("log");
+	nft_print(octx, "log");
 	if (stmt->log.flags & STMT_LOG_PREFIX)
-		printf(" prefix \"%s\"", stmt->log.prefix);
+		nft_print(octx, " prefix \"%s\"", stmt->log.prefix);
 	if (stmt->log.flags & STMT_LOG_GROUP)
-		printf(" group %u", stmt->log.group);
+		nft_print(octx, " group %u", stmt->log.group);
 	if (stmt->log.flags & STMT_LOG_SNAPLEN)
-		printf(" snaplen %u", stmt->log.snaplen);
+		nft_print(octx, " snaplen %u", stmt->log.snaplen);
 	if (stmt->log.flags & STMT_LOG_QTHRESHOLD)
-		printf(" queue-threshold %u", stmt->log.qthreshold);
+		nft_print(octx, " queue-threshold %u", stmt->log.qthreshold);
 	if ((stmt->log.flags & STMT_LOG_LEVEL) &&
 	    stmt->log.level != LOG_WARNING)
-		printf(" level %s", log_level(stmt->log.level));
+		nft_print(octx, " level %s", log_level(stmt->log.level));
 
 	if ((stmt->log.logflags & NF_LOG_MASK) == NF_LOG_MASK) {
-		printf(" flags all");
+		nft_print(octx, " flags all");
 	} else {
 		if (stmt->log.logflags & (NF_LOG_TCPSEQ | NF_LOG_TCPOPT)) {
 			const char *delim = " ";
 
-			printf(" flags tcp");
+			nft_print(octx, " flags tcp");
 			if (stmt->log.logflags & NF_LOG_TCPSEQ) {
-				printf(" sequence");
+				nft_print(octx, " sequence");
 				delim = ",";
 			}
 			if (stmt->log.logflags & NF_LOG_TCPOPT)
-				printf("%soptions", delim);
+				nft_print(octx, "%soptions",
+							delim);
 		}
 		if (stmt->log.logflags & NF_LOG_IPOPT)
-			printf(" flags ip options");
+			nft_print(octx, " flags ip options");
 		if (stmt->log.logflags & NF_LOG_UID)
-			printf(" flags skuid");
+			nft_print(octx, " flags skuid");
 		if (stmt->log.logflags & NF_LOG_MACDECODE)
-			printf(" flags ether");
+			nft_print(octx, " flags ether");
 	}
 }
 
@@ -329,23 +331,25 @@ static void limit_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 
 	switch (stmt->limit.type) {
 	case NFT_LIMIT_PKTS:
-		printf("limit rate %s%" PRIu64 "/%s",
-		       inv ? "over " : "", stmt->limit.rate,
-		       get_unit(stmt->limit.unit));
+		nft_print(octx, "limit rate %s%" PRIu64 "/%s",
+			  inv ? "over " : "", stmt->limit.rate,
+			  get_unit(stmt->limit.unit));
 		if (stmt->limit.burst > 0)
-			printf(" burst %u packets", stmt->limit.burst);
+			nft_print(octx, " burst %u packets",
+				  stmt->limit.burst);
 		break;
 	case NFT_LIMIT_PKT_BYTES:
 		data_unit = get_rate(stmt->limit.rate, &rate);
 
-		printf("limit rate %s%" PRIu64 " %s/%s",
-		       inv ? "over " : "", rate, data_unit,
-		       get_unit(stmt->limit.unit));
+		nft_print(octx,	"limit rate %s%" PRIu64 " %s/%s",
+			  inv ? "over " : "", rate, data_unit,
+			  get_unit(stmt->limit.unit));
 		if (stmt->limit.burst > 0) {
 			uint64_t burst;
 
 			data_unit = get_rate(stmt->limit.burst, &burst);
-			printf(" burst %"PRIu64" %s", burst, data_unit);
+			nft_print(octx, " burst %" PRIu64 " %s", burst,
+				  data_unit);
 		}
 		break;
 	}
@@ -370,17 +374,17 @@ static void queue_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
 	const char *delim = " ";
 
-	printf("queue");
+	nft_print(octx, "queue");
 	if (stmt->queue.queue != NULL) {
-		printf(" num ");
+		nft_print(octx, " num ");
 		expr_print(stmt->queue.queue, octx);
 	}
 	if (stmt->queue.flags & NFT_QUEUE_FLAG_BYPASS) {
-		printf("%sbypass", delim);
+		nft_print(octx, "%sbypass", delim);
 		delim = ",";
 	}
 	if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT)
-		printf("%sfanout", delim);
+		nft_print(octx, "%sfanout", delim);
 
 }
 
@@ -402,12 +406,12 @@ static void quota_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 	uint64_t bytes, used;
 
 	data_unit = get_rate(stmt->quota.bytes, &bytes);
-	printf("quota %s%"PRIu64" %s",
-	       inv ? "over " : "", bytes, data_unit);
+	nft_print(octx, "quota %s%" PRIu64 " %s",
+		  inv ? "over " : "", bytes, data_unit);
 
 	if (!octx->stateless && stmt->quota.used) {
 		data_unit = get_rate(stmt->quota.used, &used);
-		printf(" used %"PRIu64" %s", used, data_unit);
+		nft_print(octx, " used %" PRIu64 " %s", used, data_unit);
 	}
 }
 
@@ -428,15 +432,15 @@ struct stmt *quota_stmt_alloc(const struct location *loc)
 
 static void reject_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("reject");
+	nft_print(octx, "reject");
 	switch (stmt->reject.type) {
 	case NFT_REJECT_TCP_RST:
-		printf(" with tcp reset");
+		nft_print(octx, " with tcp reset");
 		break;
 	case NFT_REJECT_ICMPX_UNREACH:
 		if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH)
 			break;
-		printf(" with icmpx type ");
+		nft_print(octx, " with icmpx type ");
 		expr_print(stmt->reject.expr, octx);
 		break;
 	case NFT_REJECT_ICMP_UNREACH:
@@ -444,13 +448,13 @@ static void reject_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 		case NFPROTO_IPV4:
 			if (stmt->reject.icmp_code == ICMP_PORT_UNREACH)
 				break;
-			printf(" with icmp type ");
+			nft_print(octx, " with icmp type ");
 			expr_print(stmt->reject.expr, octx);
 			break;
 		case NFPROTO_IPV6:
 			if (stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT)
 				break;
-			printf(" with icmpv6 type ");
+			nft_print(octx, " with icmpv6 type ");
 			expr_print(stmt->reject.expr, octx);
 			break;
 		}
@@ -469,7 +473,7 @@ struct stmt *reject_stmt_alloc(const struct location *loc)
 	return stmt_alloc(loc, &reject_stmt_ops);
 }
 
-static void print_nf_nat_flags(uint32_t flags)
+static void print_nf_nat_flags(uint32_t flags, struct output_ctx *octx)
 {
 	const char *delim = " ";
 
@@ -477,17 +481,17 @@ static void print_nf_nat_flags(uint32_t flags)
 		return;
 
 	if (flags & NF_NAT_RANGE_PROTO_RANDOM) {
-		printf("%srandom", delim);
+		nft_print(octx, "%srandom", delim);
 		delim = ",";
 	}
 
 	if (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) {
-		printf("%sfully-random", delim);
+		nft_print(octx, "%sfully-random", delim);
 		delim = ",";
 	}
 
 	if (flags & NF_NAT_RANGE_PERSISTENT)
-		printf("%spersistent", delim);
+		nft_print(octx, "%spersistent", delim);
 }
 
 static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
@@ -497,21 +501,21 @@ static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 		[NFT_NAT_DNAT]	= "dnat",
 	};
 
-	printf("%s to ", nat_types[stmt->nat.type]);
+	nft_print(octx, "%s to ", nat_types[stmt->nat.type]);
 	if (stmt->nat.addr) {
 		if (stmt->nat.proto) {
 			if (stmt->nat.addr->ops->type == EXPR_VALUE &&
 			    stmt->nat.addr->dtype->type == TYPE_IP6ADDR) {
-				printf("[");
+				nft_print(octx, "[");
 				expr_print(stmt->nat.addr, octx);
-				printf("]");
+				nft_print(octx, "]");
 			} else if (stmt->nat.addr->ops->type == EXPR_RANGE &&
 				   stmt->nat.addr->left->dtype->type == TYPE_IP6ADDR) {
-				printf("[");
+				nft_print(octx, "[");
 				expr_print(stmt->nat.addr->left, octx);
-				printf("]-[");
+				nft_print(octx, "]-[");
 				expr_print(stmt->nat.addr->right, octx);
-				printf("]");
+				nft_print(octx, "]");
 			} else {
 				expr_print(stmt->nat.addr, octx);
 			}
@@ -521,11 +525,11 @@ static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 	}
 
 	if (stmt->nat.proto) {
-		printf(":");
+		nft_print(octx, ":");
 		expr_print(stmt->nat.proto, octx);
 	}
 
-	print_nf_nat_flags(stmt->nat.flags);
+	print_nf_nat_flags(stmt->nat.flags, octx);
 }
 
 static void nat_stmt_destroy(struct stmt *stmt)
@@ -548,14 +552,14 @@ struct stmt *nat_stmt_alloc(const struct location *loc)
 
 static void masq_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("masquerade");
+	nft_print(octx, "masquerade");
 
 	if (stmt->masq.proto) {
-		printf(" to :");
+		nft_print(octx, " to :");
 		expr_print(stmt->masq.proto, octx);
 	}
 
-	print_nf_nat_flags(stmt->masq.flags);
+	print_nf_nat_flags(stmt->masq.flags, octx);
 }
 
 static void masq_stmt_destroy(struct stmt *stmt)
@@ -577,14 +581,14 @@ struct stmt *masq_stmt_alloc(const struct location *loc)
 
 static void redir_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("redirect");
+	nft_print(octx, "redirect");
 
 	if (stmt->redir.proto) {
-		printf(" to :");
+		nft_print(octx, " to :");
 		expr_print(stmt->redir.proto, octx);
 	}
 
-	print_nf_nat_flags(stmt->redir.flags);
+	print_nf_nat_flags(stmt->redir.flags, octx);
 }
 
 static void redir_stmt_destroy(struct stmt *stmt)
@@ -611,9 +615,9 @@ static const char * const set_stmt_op_names[] = {
 
 static void set_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("set %s ", set_stmt_op_names[stmt->set.op]);
+	nft_print(octx, "set %s ", set_stmt_op_names[stmt->set.op]);
 	expr_print(stmt->set.key, octx);
-	printf(" ");
+	nft_print(octx, " ");
 	expr_print(stmt->set.set, octx);
 }
 
@@ -637,13 +641,13 @@ struct stmt *set_stmt_alloc(const struct location *loc)
 
 static void dup_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("dup");
+	nft_print(octx, "dup");
 	if (stmt->dup.to != NULL) {
-		printf(" to ");
+		nft_print(octx, " to ");
 		expr_print(stmt->dup.to, octx);
 
 		if (stmt->dup.dev != NULL) {
-			printf(" device ");
+			nft_print(octx, " device ");
 			expr_print(stmt->dup.dev, octx);
 		}
 	}
@@ -669,7 +673,7 @@ struct stmt *dup_stmt_alloc(const struct location *loc)
 
 static void fwd_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
-	printf("fwd to ");
+	nft_print(octx, "fwd to ");
 	expr_print(stmt->fwd.to, octx);
 }
 
-- 
2.13.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init()
  2017-09-28 15:17 ` [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init() Phil Sutter
@ 2017-09-29 10:25   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 10:25 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

Applied, thanks Phil.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit
  2017-09-28 15:17 ` [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit Phil Sutter
@ 2017-09-29 10:25   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 10:25 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

On Thu, Sep 28, 2017 at 05:17:42PM +0200, Phil Sutter wrote:
> This eliminates the need for that temporary buffer.

Also applied, thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr()
  2017-09-28 15:17 ` [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr() Phil Sutter
@ 2017-09-29 10:26   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 10:26 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

On Thu, Sep 28, 2017 at 05:17:43PM +0200, Phil Sutter wrote:
> It was a bit odd that erec_print() outputs to a given FILE pointer but
> then calls netlink_dump_expr() which just prints to stdout. Fix this by
> passing the given FILE pointer along so output is guaranteed to go to
> the same destination.

Applied, thanks Phil.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 4/5] rule: Refactor chain_print_declaration()
  2017-09-28 15:17 ` [nft PATCH v2 4/5] rule: Refactor chain_print_declaration() Phil Sutter
@ 2017-09-29 10:26   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 10:26 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

On Thu, Sep 28, 2017 at 05:17:44PM +0200, Phil Sutter wrote:
> Instead of having two nearly identical printf() calls for netdev and
> other chains, print the common parts separately and include the device
> bit only for netdev chains.

Applied, thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 5/5] src: get rid of printf
  2017-09-28 15:17 ` [nft PATCH v2 5/5] src: get rid of printf Phil Sutter
@ 2017-09-29 10:38   ` Pablo Neira Ayuso
  2017-09-29 10:57     ` Phil Sutter
  0 siblings, 1 reply; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 10:38 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

On Thu, Sep 28, 2017 at 05:17:45PM +0200, Phil Sutter wrote:
> This patch introduces nft_print()/nft_gmp_print() functions which have
> to be used instead of printf to output information that were previously
> send to stdout. These functions print to a FILE pointer defined in
> struct output_ctx. It is set by calling:
> 
> | old_fp = nft_ctx_set_output(ctx, new_fp);
> 
> Having an application-defined FILE pointer is actually quite flexible:
> Using fmemopen() or even fopencookie(), an application gains full
> control over what is printed and where it should go to.

Applied, thanks a lot for taking over this work.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 5/5] src: get rid of printf
  2017-09-29 10:38   ` Pablo Neira Ayuso
@ 2017-09-29 10:57     ` Phil Sutter
  2017-09-29 11:19       ` Pablo Neira Ayuso
  0 siblings, 1 reply; 13+ messages in thread
From: Phil Sutter @ 2017-09-29 10:57 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Eric Leblond, Florian Westphal

Hi Pablo,

On Fri, Sep 29, 2017 at 12:38:15PM +0200, Pablo Neira Ayuso wrote:
> On Thu, Sep 28, 2017 at 05:17:45PM +0200, Phil Sutter wrote:
> > This patch introduces nft_print()/nft_gmp_print() functions which have
> > to be used instead of printf to output information that were previously
> > send to stdout. These functions print to a FILE pointer defined in
> > struct output_ctx. It is set by calling:
> > 
> > | old_fp = nft_ctx_set_output(ctx, new_fp);
> > 
> > Having an application-defined FILE pointer is actually quite flexible:
> > Using fmemopen() or even fopencookie(), an application gains full
> > control over what is printed and where it should go to.
> 
> Applied, thanks a lot for taking over this work.

Cool, thanks!

Note that I missed two spots when reviewing the patches:

* nft_gmp_print() lacks a call to fflush(). This is not a problem in
  practice, but inconsistent with regards to nft_print(). I'll send a
  follow-up fixing it.

* Did you notice how my patch changes main() to move stdout to another
  file descriptor? I used that to make sure I didn't miss a spot when
  converting the code. In theory, we could just drop it again since
  everything's working. What do you suggest?

Cheers, Phil

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [nft PATCH v2 5/5] src: get rid of printf
  2017-09-29 10:57     ` Phil Sutter
@ 2017-09-29 11:19       ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2017-09-29 11:19 UTC (permalink / raw)
  To: Phil Sutter, netfilter-devel, Eric Leblond, Florian Westphal

On Fri, Sep 29, 2017 at 12:57:35PM +0200, Phil Sutter wrote:
> Hi Pablo,
> 
> On Fri, Sep 29, 2017 at 12:38:15PM +0200, Pablo Neira Ayuso wrote:
> > On Thu, Sep 28, 2017 at 05:17:45PM +0200, Phil Sutter wrote:
> > > This patch introduces nft_print()/nft_gmp_print() functions which have
> > > to be used instead of printf to output information that were previously
> > > send to stdout. These functions print to a FILE pointer defined in
> > > struct output_ctx. It is set by calling:
> > > 
> > > | old_fp = nft_ctx_set_output(ctx, new_fp);
> > > 
> > > Having an application-defined FILE pointer is actually quite flexible:
> > > Using fmemopen() or even fopencookie(), an application gains full
> > > control over what is printed and where it should go to.
> > 
> > Applied, thanks a lot for taking over this work.
> 
> Cool, thanks!
> 
> Note that I missed two spots when reviewing the patches:
> 
> * nft_gmp_print() lacks a call to fflush(). This is not a problem in
>   practice, but inconsistent with regards to nft_print(). I'll send a
>   follow-up fixing it.

OK.

> * Did you notice how my patch changes main() to move stdout to another
>   file descriptor? I used that to make sure I didn't miss a spot when
>   converting the code. In theory, we could just drop it again since
>   everything's working. What do you suggest?

Please, drop it. Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2017-09-29 11:20 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-09-28 15:17 [nft PATCH v2 0/5] Get output under application control Phil Sutter
2017-09-28 15:17 ` [nft PATCH v2 1/5] rule: Use C99-style initializer in cache_init() Phil Sutter
2017-09-29 10:25   ` Pablo Neira Ayuso
2017-09-28 15:17 ` [nft PATCH v2 2/5] exthdr: Simplify tcp option printing a bit Phil Sutter
2017-09-29 10:25   ` Pablo Neira Ayuso
2017-09-28 15:17 ` [nft PATCH v2 3/5] erec_print: Pass output FILE pointer to netlink_dump_expr() Phil Sutter
2017-09-29 10:26   ` Pablo Neira Ayuso
2017-09-28 15:17 ` [nft PATCH v2 4/5] rule: Refactor chain_print_declaration() Phil Sutter
2017-09-29 10:26   ` Pablo Neira Ayuso
2017-09-28 15:17 ` [nft PATCH v2 5/5] src: get rid of printf Phil Sutter
2017-09-29 10:38   ` Pablo Neira Ayuso
2017-09-29 10:57     ` Phil Sutter
2017-09-29 11:19       ` Pablo Neira Ayuso

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.