netfilter-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH nft 1/3] src: add per-bytes limit
@ 2015-09-22 11:02 Pablo Neira Ayuso
  2015-09-22 11:02 ` [PATCH nft 2/3] src: add burst parameter to limit Pablo Neira Ayuso
  2015-09-22 11:02 ` [PATCH nft 3/3] tests: limit: extend them to validate new bytes/second and burst parameters Pablo Neira Ayuso
  0 siblings, 2 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2015-09-22 11:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kaber, fw

This example show how to accept packets below the ratelimit:

... limit rate 1024 mbytes/second counter accept

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/datatype.h                  |    4 +++
 include/linux/netfilter/nf_tables.h |    9 ++++++
 include/statement.h                 |    1 +
 src/datatype.c                      |   55 +++++++++++++++++++++++++++++++++++
 src/netlink_delinearize.c           |    1 +
 src/netlink_linearize.c             |    1 +
 src/parser_bison.y                  |   17 +++++++++++
 src/statement.c                     |   43 +++++++++++++++++++++++++--
 8 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/include/datatype.h b/include/datatype.h
index 2a6a4fc..ebafa65 100644
--- a/include/datatype.h
+++ b/include/datatype.h
@@ -235,4 +235,8 @@ extern void time_print(uint64_t seconds);
 extern struct error_record *time_parse(const struct location *loc,
 				       const char *c, uint64_t *res);
 
+extern struct error_record *rate_parse(const struct location *loc,
+				       const char *str, uint64_t *rate,
+				       uint64_t *unit);
+
 #endif /* NFTABLES_DATATYPE_H */
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index 33056dc..db0457d 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -747,16 +747,25 @@ enum nft_ct_attributes {
 };
 #define NFTA_CT_MAX		(__NFTA_CT_MAX - 1)
 
+enum nft_limit_type {
+	NFT_LIMIT_PKTS,
+	NFT_LIMIT_PKT_BYTES
+};
+
 /**
  * enum nft_limit_attributes - nf_tables limit expression netlink attributes
  *
  * @NFTA_LIMIT_RATE: refill rate (NLA_U64)
  * @NFTA_LIMIT_UNIT: refill unit (NLA_U64)
+ * @NFTA_LIMIT_BURST: burst (NLA_U32)
+ * @NFTA_LIMIT_TYPE: type of limit (NLA_U32: enum nft_limit_type)
  */
 enum nft_limit_attributes {
 	NFTA_LIMIT_UNSPEC,
 	NFTA_LIMIT_RATE,
 	NFTA_LIMIT_UNIT,
+	NFTA_LIMIT_BURST,
+	NFTA_LIMIT_TYPE,
 	__NFTA_LIMIT_MAX
 };
 #define NFTA_LIMIT_MAX		(__NFTA_LIMIT_MAX - 1)
diff --git a/include/statement.h b/include/statement.h
index 48e6130..d2d0852 100644
--- a/include/statement.h
+++ b/include/statement.h
@@ -51,6 +51,7 @@ extern struct stmt *log_stmt_alloc(const struct location *loc);
 struct limit_stmt {
 	uint64_t		rate;
 	uint64_t		unit;
+	enum nft_limit_type	type;
 };
 
 extern struct stmt *limit_stmt_alloc(const struct location *loc);
diff --git a/src/datatype.c b/src/datatype.c
index f79f5d2..e5a486f 100644
--- a/src/datatype.c
+++ b/src/datatype.c
@@ -976,3 +976,58 @@ void concat_type_destroy(const struct datatype *dtype)
 		xfree(dtype);
 	}
 }
+
+static struct error_record *time_unit_parse(const struct location *loc,
+					    const char *str, uint64_t *unit)
+{
+	if (strcmp(str, "second") == 0)
+		*unit = 1ULL;
+	else if (strcmp(str, "minute") == 0)
+		*unit = 1ULL * 60;
+	else if (strcmp(str, "hour") == 0)
+		*unit = 1ULL * 60 * 60;
+	else if (strcmp(str, "day") == 0)
+		*unit = 1ULL * 60 * 60 * 24;
+	else if (strcmp(str, "week") == 0)
+		*unit = 1ULL * 60 * 60 * 24 * 7;
+	else
+		return error(loc, "Wrong rate format");
+
+	return NULL;
+}
+
+static struct error_record *data_unit_parse(const struct location *loc,
+					    const char *str, uint64_t *rate)
+{
+	if (strncmp(str, "bytes", strlen("bytes")) == 0)
+		*rate = 1ULL;
+	else if (strncmp(str, "kbytes", strlen("kbytes")) == 0)
+		*rate = 1024;
+	else if (strncmp(str, "mbytes", strlen("mbytes")) == 0)
+		*rate = 1024 * 1024;
+	else
+		return error(loc, "Wrong rate format");
+
+	return NULL;
+}
+
+struct error_record *rate_parse(const struct location *loc, const char *str,
+				uint64_t *rate, uint64_t *unit)
+{
+	struct error_record *erec;
+	const char *slash;
+
+	slash = strchr(str, '/');
+	if (!slash)
+		return error(loc, "wrong rate format");
+
+	erec = data_unit_parse(loc, str, rate);
+	if (erec != NULL)
+		return erec;
+
+	erec = time_unit_parse(loc, slash + 1, unit);
+	if (erec != NULL)
+		return erec;
+
+	return NULL;
+}
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 787eec7..569763b 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -583,6 +583,7 @@ static void netlink_parse_limit(struct netlink_parse_ctx *ctx,
 	stmt = limit_stmt_alloc(loc);
 	stmt->limit.rate = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_RATE);
 	stmt->limit.unit = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_UNIT);
+	stmt->limit.type = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_TYPE);
 	list_add_tail(&stmt->list, &ctx->rule->stmts);
 }
 
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 707df49..cebd2f1 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -656,6 +656,7 @@ static void netlink_gen_limit_stmt(struct netlink_linearize_ctx *ctx,
 	nle = alloc_nft_expr("limit");
 	nftnl_expr_set_u64(nle, NFTNL_EXPR_LIMIT_RATE, stmt->limit.rate);
 	nftnl_expr_set_u64(nle, NFTNL_EXPR_LIMIT_UNIT, stmt->limit.unit);
+	nftnl_expr_set_u32(nle, NFTNL_EXPR_LIMIT_TYPE, stmt->limit.type);
 	nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
diff --git a/src/parser_bison.y b/src/parser_bison.y
index cfb6b70..ec44a2c 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -1446,6 +1446,23 @@ limit_stmt		:	LIMIT	RATE	NUM	SLASH	time_unit
 				$$ = limit_stmt_alloc(&@$);
 				$$->limit.rate	= $3;
 				$$->limit.unit	= $5;
+				$$->limit.type	= NFT_LIMIT_PKTS;
+			}
+			|	LIMIT RATE	NUM	STRING
+			{
+				struct error_record *erec;
+				uint64_t rate, unit;
+
+				erec = rate_parse(&@$, $4, &rate, &unit);
+				if (erec != NULL) {
+					erec_queue(erec, state->msgs);
+					YYERROR;
+				}
+
+				$$ = limit_stmt_alloc(&@$);
+				$$->limit.rate	= rate * $3;
+				$$->limit.unit	= unit;
+				$$->limit.type	= NFT_LIMIT_PKT_BYTES;
 			}
 			;
 
diff --git a/src/statement.c b/src/statement.c
index 9ebc593..ba7b8be 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -185,10 +185,49 @@ static const char *get_unit(uint64_t u)
 	return "error";
 }
 
+static const char *data_unit[] = {
+	"bytes",
+	"kbytes",
+	"mbytes",
+	NULL
+};
+
+static const char *get_rate(uint64_t byte_rate, uint64_t *rate)
+{
+	uint64_t res, prev, rest;
+	int i;
+
+	res = prev = byte_rate;
+	for (i = 0;; i++) {
+		rest = res % 1024;
+		res /= 1024;
+		if (res <= 1 && rest != 0)
+			break;
+		if (data_unit[i + 1] == NULL)
+			break;
+		prev = res;
+	}
+	*rate = prev;
+	return data_unit[i];
+}
+
 static void limit_stmt_print(const struct stmt *stmt)
 {
-	printf("limit rate %" PRIu64 "/%s",
-	       stmt->limit.rate, get_unit(stmt->limit.unit));
+	const char *data_unit;
+	uint64_t rate;
+
+	switch (stmt->limit.type) {
+	case NFT_LIMIT_PKTS:
+		printf("limit rate %" PRIu64 "/%s",
+		       stmt->limit.rate, get_unit(stmt->limit.unit));
+		break;
+	case NFT_LIMIT_PKT_BYTES:
+		data_unit = get_rate(stmt->limit.rate, &rate);
+
+		printf("limit rate %" PRIu64 " %s/%s",
+		       rate, data_unit, get_unit(stmt->limit.unit));
+		break;
+	}
 }
 
 static const struct stmt_ops limit_stmt_ops = {
-- 
1.7.10.4


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

* [PATCH nft 2/3] src: add burst parameter to limit
  2015-09-22 11:02 [PATCH nft 1/3] src: add per-bytes limit Pablo Neira Ayuso
@ 2015-09-22 11:02 ` Pablo Neira Ayuso
  2015-09-22 11:02 ` [PATCH nft 3/3] tests: limit: extend them to validate new bytes/second and burst parameters Pablo Neira Ayuso
  1 sibling, 0 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2015-09-22 11:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kaber, fw

... limit rate 1024 mbytes/second burst 10240 bytes
... limit rate 1/second burst 3 packets

This parameter is optional.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/datatype.h        |    3 +++
 include/statement.h       |    1 +
 src/datatype.c            |    4 ++--
 src/netlink_delinearize.c |    1 +
 src/netlink_linearize.c   |    4 ++++
 src/parser_bison.y        |   26 +++++++++++++++++++++++---
 src/scanner.l             |    1 +
 src/statement.c           |    8 ++++++++
 8 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/include/datatype.h b/include/datatype.h
index ebafa65..07fedce 100644
--- a/include/datatype.h
+++ b/include/datatype.h
@@ -239,4 +239,7 @@ extern struct error_record *rate_parse(const struct location *loc,
 				       const char *str, uint64_t *rate,
 				       uint64_t *unit);
 
+extern struct error_record *data_unit_parse(const struct location *loc,
+					    const char *str, uint64_t *rate);
+
 #endif /* NFTABLES_DATATYPE_H */
diff --git a/include/statement.h b/include/statement.h
index d2d0852..bead0a6 100644
--- a/include/statement.h
+++ b/include/statement.h
@@ -52,6 +52,7 @@ struct limit_stmt {
 	uint64_t		rate;
 	uint64_t		unit;
 	enum nft_limit_type	type;
+	uint32_t		burst;
 };
 
 extern struct stmt *limit_stmt_alloc(const struct location *loc);
diff --git a/src/datatype.c b/src/datatype.c
index e5a486f..f56763b 100644
--- a/src/datatype.c
+++ b/src/datatype.c
@@ -996,8 +996,8 @@ static struct error_record *time_unit_parse(const struct location *loc,
 	return NULL;
 }
 
-static struct error_record *data_unit_parse(const struct location *loc,
-					    const char *str, uint64_t *rate)
+struct error_record *data_unit_parse(const struct location *loc,
+				     const char *str, uint64_t *rate)
 {
 	if (strncmp(str, "bytes", strlen("bytes")) == 0)
 		*rate = 1ULL;
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 569763b..ede9302 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -584,6 +584,7 @@ static void netlink_parse_limit(struct netlink_parse_ctx *ctx,
 	stmt->limit.rate = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_RATE);
 	stmt->limit.unit = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_UNIT);
 	stmt->limit.type = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_TYPE);
+	stmt->limit.burst = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_BURST);
 	list_add_tail(&stmt->list, &ctx->rule->stmts);
 }
 
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index cebd2f1..447219f 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -657,6 +657,10 @@ static void netlink_gen_limit_stmt(struct netlink_linearize_ctx *ctx,
 	nftnl_expr_set_u64(nle, NFTNL_EXPR_LIMIT_RATE, stmt->limit.rate);
 	nftnl_expr_set_u64(nle, NFTNL_EXPR_LIMIT_UNIT, stmt->limit.unit);
 	nftnl_expr_set_u32(nle, NFTNL_EXPR_LIMIT_TYPE, stmt->limit.type);
+	if (stmt->limit.burst > 0)
+		nftnl_expr_set_u32(nle, NFTNL_EXPR_LIMIT_BURST,
+				   stmt->limit.burst);
+
 	nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
diff --git a/src/parser_bison.y b/src/parser_bison.y
index ec44a2c..385e214 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -364,6 +364,7 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 
 %token LIMIT			"limit"
 %token RATE			"rate"
+%token BURST			"burst"
 
 %token NANOSECOND		"nanosecond"
 %token MICROSECOND		"microsecond"
@@ -450,7 +451,7 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %type <val>			level_type
 %type <stmt>			limit_stmt
 %destructor { stmt_free($$); }	limit_stmt
-%type <val>			time_unit
+%type <val>			limit_burst time_unit
 %type <stmt>			reject_stmt reject_stmt_alloc
 %destructor { stmt_free($$); }	reject_stmt reject_stmt_alloc
 %type <stmt>			nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc
@@ -1441,14 +1442,15 @@ level_type		:	LEVEL_EMERG	{ $$ = LOG_EMERG; }
 			|	LEVEL_DEBUG	{ $$ = LOG_DEBUG; }
 			;
 
-limit_stmt		:	LIMIT	RATE	NUM	SLASH	time_unit
+limit_stmt		:	LIMIT	RATE	NUM	SLASH	time_unit	limit_burst
 	    		{
 				$$ = limit_stmt_alloc(&@$);
 				$$->limit.rate	= $3;
 				$$->limit.unit	= $5;
+				$$->limit.burst	= $6;
 				$$->limit.type	= NFT_LIMIT_PKTS;
 			}
-			|	LIMIT RATE	NUM	STRING
+			|	LIMIT RATE	NUM	STRING	limit_burst
 			{
 				struct error_record *erec;
 				uint64_t rate, unit;
@@ -1462,10 +1464,28 @@ limit_stmt		:	LIMIT	RATE	NUM	SLASH	time_unit
 				$$ = limit_stmt_alloc(&@$);
 				$$->limit.rate	= rate * $3;
 				$$->limit.unit	= unit;
+				$$->limit.burst	= $5;
 				$$->limit.type	= NFT_LIMIT_PKT_BYTES;
 			}
 			;
 
+limit_burst		:	/* empty */			{ $$ = 0; }
+			|	BURST	NUM	PACKETS		{ $$ = $2; }
+			|	BURST	NUM	BYTES		{ $$ = $2; }
+			|	BURST	NUM	STRING
+			{
+				struct error_record *erec;
+				uint64_t rate;
+
+				erec = data_unit_parse(&@$, $3, &rate);
+				if (erec != NULL) {
+					erec_queue(erec, state->msgs);
+					YYERROR;
+				}
+				$$ = $2 * rate;
+			}
+			;
+
 time_unit		:	SECOND		{ $$ = 1ULL; }
 			|	MINUTE		{ $$ = 1ULL * 60; }
 			|	HOUR		{ $$ = 1ULL * 60 * 60; }
diff --git a/src/scanner.l b/src/scanner.l
index 2d9871d..bd8e572 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -309,6 +309,7 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 
 "limit"			{ return LIMIT; }
 "rate"			{ return RATE; }
+"burst"			{ return BURST; }
 
 "nanosecond"		{ return NANOSECOND; }
 "microsecond"		{ return MICROSECOND; }
diff --git a/src/statement.c b/src/statement.c
index ba7b8be..d620d1b 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -220,12 +220,20 @@ static void limit_stmt_print(const struct stmt *stmt)
 	case NFT_LIMIT_PKTS:
 		printf("limit rate %" PRIu64 "/%s",
 		       stmt->limit.rate, get_unit(stmt->limit.unit));
+		if (stmt->limit.burst > 0)
+			printf(" burst %u packets", stmt->limit.burst);
 		break;
 	case NFT_LIMIT_PKT_BYTES:
 		data_unit = get_rate(stmt->limit.rate, &rate);
 
 		printf("limit rate %" PRIu64 " %s/%s",
 		       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);
+		}
 		break;
 	}
 }
-- 
1.7.10.4


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

* [PATCH nft 3/3] tests: limit: extend them to validate new bytes/second and burst parameters
  2015-09-22 11:02 [PATCH nft 1/3] src: add per-bytes limit Pablo Neira Ayuso
  2015-09-22 11:02 ` [PATCH nft 2/3] src: add burst parameter to limit Pablo Neira Ayuso
@ 2015-09-22 11:02 ` Pablo Neira Ayuso
  1 sibling, 0 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2015-09-22 11:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kaber, fw

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 tests/regression/any/limit.t         |   15 +++++++++-
 tests/regression/any/limit.t.payload |   54 ++++++++++++++++++++++++++++++----
 2 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/tests/regression/any/limit.t b/tests/regression/any/limit.t
index 9af1ea8..96ffe60 100644
--- a/tests/regression/any/limit.t
+++ b/tests/regression/any/limit.t
@@ -8,5 +8,18 @@
 limit rate 400/minute;ok
 limit rate 20/second;ok
 limit rate 400/hour;ok
-limit rate 400/week;ok
 limit rate 40/day;ok
+limit rate 400/week;ok
+limit rate 1023/second burst 10 packets;ok
+
+limit rate 1 kbytes/second;ok
+limit rate 2 kbytes/second;ok
+limit rate 1025 kbytes/second;ok
+limit rate 1023 mbytes/second;ok
+limit rate 10230 mbytes/second;ok
+limit rate 1023000 mbytes/second;ok
+
+limit rate 1025 bytes/second burst 512 bytes;ok
+limit rate 1025 kbytes/second burst 1023 kbytes;ok
+limit rate 1025 mbytes/second burst 1025 kbytes;ok
+limit rate 1025000 mbytes/second burst 1023 mbytes;ok
diff --git a/tests/regression/any/limit.t.payload b/tests/regression/any/limit.t.payload
index c196f12..a3c87d8 100644
--- a/tests/regression/any/limit.t.payload
+++ b/tests/regression/any/limit.t.payload
@@ -1,20 +1,64 @@
 # limit rate 400/minute
 ip test-ip4 output
-  [ limit rate 400/minute ]
+  [ limit rate 400/minute burst 0 type packets ]
 
 # limit rate 20/second
 ip test-ip4 output
-  [ limit rate 20/second ]
+  [ limit rate 20/second burst 0 type packets ]
 
 # limit rate 400/hour
 ip test-ip4 output
-  [ limit rate 400/hour ]
+  [ limit rate 400/hour burst 0 type packets ]
 
 # limit rate 400/week
 ip test-ip4 output
-  [ limit rate 400/week ]
+  [ limit rate 400/week burst 0 type packets ]
 
 # limit rate 40/day
 ip test-ip4 output
-  [ limit rate 40/day ]
+  [ limit rate 40/day burst 0 type packets ]
+
+# limit rate 1023/second burst 10 packets
+ip test-ip4 output
+  [ limit rate 1023/second burst 10 type packets ]
+
+# limit rate 1 kbytes/second
+ip test-ip4 output
+  [ limit rate 1024/second burst 0 type bytes ]
+
+# limit rate 2 kbytes/second
+ip test-ip4 output
+  [ limit rate 2048/second burst 0 type bytes ]
+
+# limit rate 1025 kbytes/second
+ip test-ip4 output
+  [ limit rate 1049600/second burst 0 type bytes ]
+
+# limit rate 1023 mbytes/second
+ip test-ip4 output
+  [ limit rate 1072693248/second burst 0 type bytes ]
+
+# limit rate 10230 mbytes/second
+ip test-ip4 output
+  [ limit rate 10726932480/second burst 0 type bytes ]
+
+# limit rate 1023000 mbytes/second
+ip test-ip4 output
+  [ limit rate 1072693248000/second burst 0 type bytes ]
+
+# limit rate 1025 bytes/second burst 512 bytes
+ip test-ip4 output
+  [ limit rate 1025/second burst 512 type bytes ]
+
+# limit rate 1025 kbytes/second burst 1023 kbytes
+ip test-ip4 output
+  [ limit rate 1049600/second burst 1047552 type bytes ]
+
+# limit rate 1025 mbytes/second burst 1025 kbytes
+ip test-ip4 output
+  [ limit rate 1074790400/second burst 1049600 type bytes ]
+
+# limit rate 1025000 mbytes/second burst 1023 mbytes
+ip test-ip4 output
+  [ limit rate 1074790400000/second burst 1072693248 type bytes ]
 
-- 
1.7.10.4


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

end of thread, other threads:[~2015-09-22 10:55 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-09-22 11:02 [PATCH nft 1/3] src: add per-bytes limit Pablo Neira Ayuso
2015-09-22 11:02 ` [PATCH nft 2/3] src: add burst parameter to limit Pablo Neira Ayuso
2015-09-22 11:02 ` [PATCH nft 3/3] tests: limit: extend them to validate new bytes/second and burst parameters Pablo Neira Ayuso

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).