All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH nftables 0/7] TCP option matching
@ 2017-02-07  2:14 Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 1/7] include: linux: netfilter: nf_tables: copy file from nf-next Manuel Messner
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

This patch set is part of the TCP option matching implementation for nftables.

These patch sets enable nft to match against the following TCP options:
* End of Option List
* No-Operation
* Maximum Segment Size
* Window Scale
* SACK
* SACK Permitted
* Timestamps

Florian Westphal (1):
  payload: insert implicit meta tcp dependency when matching tcp options

Manuel Messner (6):
  include: linux: netfilter: nf_tables: copy file from nf-next
  exthdr: prepare for tcp support
  exthdr: prepare exthdr_gen_dependency for tcp support
  src: add TCP option matching
  payload: automatically kill dependencies for exthdr and tcpopt
  tests: py: Add basic tests for ip, ip6 and inet

 doc/nft.xml                         | 178 +++++++++++++++++++++++-
 include/expression.h                |   1 +
 include/exthdr.h                    |   5 +-
 include/linux/netfilter/nf_tables.h |  17 ++-
 include/payload.h                   |   5 +-
 include/tcpopt.h                    |  26 ++++
 src/Makefile.am                     |   1 +
 src/evaluate.c                      |  42 +++++-
 src/exthdr.c                        |  36 ++++-
 src/netlink_delinearize.c           |   7 +-
 src/netlink_linearize.c             |   5 +-
 src/parser_bison.y                  |  46 +++++-
 src/payload.c                       |  39 +++++-
 src/scanner.l                       |   1 +
 src/tcpopt.c                        | 269 ++++++++++++++++++++++++++++++++++++
 tests/py/inet/tcpopt.t              |  38 +++++
 tests/py/inet/tcpopt.t.payload.inet | 181 ++++++++++++++++++++++++
 tests/py/ip/tcpopt.t                |  38 +++++
 tests/py/ip/tcpopt.t.payload        | 181 ++++++++++++++++++++++++
 tests/py/ip6/tcpopt.t               |  37 +++++
 tests/py/ip6/tcpopt.t.payload       | 181 ++++++++++++++++++++++++
 21 files changed, 1303 insertions(+), 31 deletions(-)
 create mode 100644 include/tcpopt.h
 create mode 100644 src/tcpopt.c
 create mode 100644 tests/py/inet/tcpopt.t
 create mode 100644 tests/py/inet/tcpopt.t.payload.inet
 create mode 100644 tests/py/ip/tcpopt.t
 create mode 100644 tests/py/ip/tcpopt.t.payload
 create mode 100644 tests/py/ip6/tcpopt.t
 create mode 100644 tests/py/ip6/tcpopt.t.payload

--
2.11.1


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

* [PATCH nftables 1/7] include: linux: netfilter: nf_tables: copy file from nf-next
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 2/7] exthdr: prepare for tcp support Manuel Messner
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

Signed-off-by: Manuel Messner <mm@skelett.io>
Reviewed-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/nf_tables.h | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index b00a05d..6c0c9ef 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -705,12 +705,13 @@ enum nft_payload_attributes {
 #define NFTA_PAYLOAD_MAX	(__NFTA_PAYLOAD_MAX - 1)
 
 /**
- * enum nft_exthdr_attributes - nf_tables IPv6 extension header expression netlink attributes
+ * enum nft_exthdr_attributes - nf_tables extension header expression netlink attributes
  *
  * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers)
  * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8)
  * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32)
  * @NFTA_EXTHDR_LEN: extension header length (NLA_U32)
+ * @NFTA_EXTHDR_OP: option match type (NLA_U8)
  */
 enum nft_exthdr_attributes {
 	NFTA_EXTHDR_UNSPEC,
@@ -718,11 +719,25 @@ enum nft_exthdr_attributes {
 	NFTA_EXTHDR_TYPE,
 	NFTA_EXTHDR_OFFSET,
 	NFTA_EXTHDR_LEN,
+	NFTA_EXTHDR_OP,
 	__NFTA_EXTHDR_MAX
 };
 #define NFTA_EXTHDR_MAX		(__NFTA_EXTHDR_MAX - 1)
 
 /**
+ * enum nft_exthdr_op - nf_tables match options
+ *
+ * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers
+ * @NFT_EXTHDR_OP_TCP: match against tcp options
+ */
+enum nft_exthdr_op {
+	NFT_EXTHDR_OP_IPV6,
+	NFT_EXTHDR_OP_TCPOPT,
+	__NFT_EXTHDR_OP_MAX
+};
+#define NFT_EXTHDR_OP_MAX	(__NFT_EXTHDR_OP_MAX - 1)
+
+/**
  * enum nft_meta_keys - nf_tables meta expression keys
  *
  * @NFT_META_LEN: packet length (skb->len)
-- 
2.11.1


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

* [PATCH nftables 2/7] exthdr: prepare for tcp support
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 1/7] include: linux: netfilter: nf_tables: copy file from nf-next Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 3/7] exthdr: prepare exthdr_gen_dependency " Manuel Messner
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

right now exthdr only deals with ipv6 extension headers, followup
patch will enable tcp option matching.

This adds the 'op' arg to exthdr_init.

Signed-off-by: Manuel Messner <mm@skelett.io>
Reviewed-by: Florian Westphal <fw@strlen.de>
---
 include/exthdr.h          | 3 ++-
 src/exthdr.c              | 5 +++--
 src/netlink_delinearize.c | 4 +++-
 src/netlink_linearize.c   | 4 ++--
 4 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/include/exthdr.h b/include/exthdr.h
index d17841b..93a53f3 100644
--- a/include/exthdr.h
+++ b/include/exthdr.h
@@ -21,7 +21,8 @@ extern struct expr *exthdr_expr_alloc(const struct location *loc,
 				      uint8_t type);
 
 extern void exthdr_init_raw(struct expr *expr, uint8_t type,
-			    unsigned int offset, unsigned int len);
+			    unsigned int offset, unsigned int len,
+			    enum nft_exthdr_op op);
 
 extern bool exthdr_find_template(struct expr *expr, const struct expr *mask,
 				 unsigned int *shift);
diff --git a/src/exthdr.c b/src/exthdr.c
index c641d4a..45b1b69 100644
--- a/src/exthdr.c
+++ b/src/exthdr.c
@@ -79,7 +79,8 @@ static const struct exthdr_desc *exthdr_protocols[IPPROTO_MAX] = {
 };
 
 void exthdr_init_raw(struct expr *expr, uint8_t type,
-		     unsigned int offset, unsigned int len)
+		     unsigned int offset, unsigned int len,
+		     enum nft_exthdr_op op)
 {
 	const struct proto_hdr_template *tmpl;
 	unsigned int i;
@@ -123,7 +124,7 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i
 	off += round_up(mask->len, BITS_PER_BYTE) - mask_len;
 
 	exthdr_init_raw(expr, expr->exthdr.desc->type,
-			off, mask_len - mask_offset);
+			off, mask_len - mask_offset, NFT_EXTHDR_OP_IPV6);
 
 	/* still failed to find a template... Bug. */
 	if (expr->exthdr.tmpl == &exthdr_unknown_template)
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 144edf5..d6a9fe1 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -499,6 +499,7 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx,
 				 const struct nftnl_expr *nle)
 {
 	enum nft_registers dreg;
+	enum nft_exthdr_op op;
 	uint32_t offset, len;
 	uint8_t type;
 	struct expr *expr;
@@ -506,9 +507,10 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx,
 	type   = nftnl_expr_get_u8(nle, NFTNL_EXPR_EXTHDR_TYPE);
 	offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET) * BITS_PER_BYTE;
 	len    = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_LEN) * BITS_PER_BYTE;
+	op     = NFT_EXTHDR_OP_IPV6;
 
 	expr = exthdr_expr_alloc(loc, NULL, 0);
-	exthdr_init_raw(expr, type, offset, len);
+	exthdr_init_raw(expr, type, offset, len, op);
 
 	dreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_DREG);
 	netlink_set_register(ctx, dreg, expr);
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 5030135..056f113 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -162,14 +162,14 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx,
 			       const struct expr *expr,
 			       enum nft_registers dreg)
 {
+	unsigned int offset = expr->exthdr.tmpl->offset;
 	struct nftnl_expr *nle;
 
 	nle = alloc_nft_expr("exthdr");
 	netlink_put_register(nle, NFTNL_EXPR_EXTHDR_DREG, dreg);
 	nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_TYPE,
 			  expr->exthdr.desc->type);
-	nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET,
-			   expr->exthdr.tmpl->offset / BITS_PER_BYTE);
+	nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET, offset / BITS_PER_BYTE);
 	nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_LEN,
 			   div_round_up(expr->len, BITS_PER_BYTE));
 	nftnl_rule_add_expr(ctx->nlr, nle);
-- 
2.11.1


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

* [PATCH nftables 3/7] exthdr: prepare exthdr_gen_dependency for tcp support
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 1/7] include: linux: netfilter: nf_tables: copy file from nf-next Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 2/7] exthdr: prepare for tcp support Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 4/7] src: add TCP option matching Manuel Messner
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

currently exthdr always needs ipv6 dependency (i.e. link layer), but
with upcomming TCP option matching we also need to include TCP at the
network layer.

This patch prepares this change by adding two parameters to
exthdr_gen_dependency.

Signed-off-by: Manuel Messner <mm@skelett.io>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/payload.h | 3 ++-
 src/evaluate.c    | 9 +++++----
 src/payload.c     | 9 +++++----
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/include/payload.h b/include/payload.h
index bda3188..5952b24 100644
--- a/include/payload.h
+++ b/include/payload.h
@@ -16,7 +16,8 @@ struct stmt;
 extern int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
 				  struct stmt **res);
 extern int exthdr_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
-				  struct stmt **res);
+				 const struct proto_desc *dependency,
+				 enum proto_bases pb, struct stmt **res);
 
 /**
  * struct payload_dep_ctx - payload protocol dependency tracking
diff --git a/src/evaluate.c b/src/evaluate.c
index 94412f2..0e02548 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -448,19 +448,20 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
  */
 static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
 {
-	const struct proto_desc *base;
+	const struct proto_desc *base, *dependency = &proto_ip6;
+	enum proto_bases pb = PROTO_BASE_NETWORK_HDR;
 	struct expr *expr = *exprp;
 	struct stmt *nstmt;
 
-	base = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc;
-	if (base == &proto_ip6)
+	base = ctx->pctx.protocol[pb].desc;
+	if (base == dependency)
 		return __expr_evaluate_exthdr(ctx, exprp);
 
 	if (base)
 		return expr_error(ctx->msgs, expr,
 				  "cannot use exthdr with %s", base->name);
 
-	if (exthdr_gen_dependency(ctx, expr, &nstmt) < 0)
+	if (exthdr_gen_dependency(ctx, expr, dependency, pb - 1, &nstmt) < 0)
 		return -1;
 
 	list_add(&nstmt->list, &ctx->rule->stmts);
diff --git a/src/payload.c b/src/payload.c
index 74f8254..efd1960 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -317,18 +317,19 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
 }
 
 int exthdr_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
-			  struct stmt **res)
+			  const struct proto_desc *dependency,
+			  enum proto_bases pb, struct stmt **res)
 {
 	const struct proto_desc *desc;
 
-	desc = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc;
+	desc = ctx->pctx.protocol[pb].desc;
 	if (desc == NULL)
 		return expr_error(ctx->msgs, expr,
 				  "Cannot generate dependency: "
 				  "no %s protocol specified",
-				  proto_base_names[PROTO_BASE_LL_HDR]);
+				  proto_base_names[pb]);
 
-	return payload_add_dependency(ctx, desc, &proto_ip6, expr, res);
+	return payload_add_dependency(ctx, desc, dependency, expr, res);
 }
 
 /**
-- 
2.11.1


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

* [PATCH nftables 4/7] src: add TCP option matching
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
                   ` (2 preceding siblings ...)
  2017-02-07  2:14 ` [PATCH nftables 3/7] exthdr: prepare exthdr_gen_dependency " Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 5/7] payload: insert implicit meta tcp dependency when matching tcp options Manuel Messner
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

This patch enables nft to match against TCP options.

Currently these TCP options are supported:
* End of Option List (eol)
* No-Operation (noop)
* Maximum Segment Size (maxseg)
* Window Scale (window)
* SACK Permitted (sack_permitted)
* SACK (sack)
* Timestamps (timestamp)

Syntax: tcp options $option_name [$offset] $field_name
Example:

 # count all incoming packets with a specific maximum segment size `x`
 # nft add rule filter input tcp option maxseg size x counter

 # count all incoming packets with a SACK TCP option where the third
 # (counted from zero) left field is greater `x`.
 # nft add rule filter input tcp option sack 2 left \> x counter

If the offset (the `2` in the example above) is zero, it can optionally
be omitted.
For all non-SACK TCP options it is always zero, thus can be left out.

Option names and field names are parsed from templates, similar to meta
and ct options rather than via keywords to prevent adding more keywords
than necessary.

Signed-off-by: Manuel Messner <mm@skelett.io>
Reviewed-by: Florian Westphal <fw@strlen.de>
---
 doc/nft.xml               | 178 +++++++++++++++++++++++++++++-
 include/expression.h      |   1 +
 include/exthdr.h          |   2 +
 include/tcpopt.h          |  26 +++++
 src/Makefile.am           |   1 +
 src/evaluate.c            |  35 +++++-
 src/exthdr.c              |  31 +++++-
 src/netlink_delinearize.c |   3 +-
 src/netlink_linearize.c   |   3 +-
 src/parser_bison.y        |  46 +++++++-
 src/scanner.l             |   1 +
 src/tcpopt.c              | 269 ++++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 580 insertions(+), 16 deletions(-)
 create mode 100644 include/tcpopt.h
 create mode 100644 src/tcpopt.c

diff --git a/doc/nft.xml b/doc/nft.xml
index 1455086..08ecdfa 100644
--- a/doc/nft.xml
+++ b/doc/nft.xml
@@ -2105,14 +2105,182 @@ inet filter meta nfproto ipv6 output rt nexthop fd00::1
 				</table>
 			</para>
 		</refsect2>
-	</refsect1>
 
-	<refsect1>
-		<title>bla</title>
 		<refsect2>
-			<title>IPv6 extension header expressions</title>
+			<title>Extension header expressions</title>
+			<para>
+				Extension header expressions refer to data from variable-sized protocol headers, such as IPv6 extension headers and
+				TCPs options.
+			</para>
+			<para>
+				nftables currently supports matching (finding) a given ipv6 extension header or TCP option.
+			</para>
+			<cmdsynopsis>
+				<command>hbh</command>
+				<group choice="req">
+					<arg>nexthdr</arg>
+					<arg>hdrlength</arg>
+				</group>
+			</cmdsynopsis>
+			<cmdsynopsis>
+				<command>frag</command>
+				<group choice="req">
+					<arg>nexthdr</arg>
+					<arg>frag-off</arg>
+					<arg>more-fragments</arg>
+					<arg>id</arg>
+				</group>
+			</cmdsynopsis>
+
+			<cmdsynopsis>
+				<command>rt</command>
+				<group choice="req">
+					<arg>nexthdr</arg>
+					<arg>hdrlength</arg>
+					<arg>type</arg>
+					<arg>seg-left</arg>
+				</group>
+			</cmdsynopsis>
+			<cmdsynopsis>
+				<command>dst</command>
+				<group choice="req">
+					<arg>nexthdr</arg>
+					<arg>hdrlength</arg>
+				</group>
+			</cmdsynopsis>
+			<cmdsynopsis>
+				<command>mh</command>
+				<group choice="req">
+					<arg>nexthdr</arg>
+					<arg>hdrlength</arg>
+					<arg>checksum</arg>
+					<arg>type</arg>
+				</group>
+			</cmdsynopsis>
+			<cmdsynopsis>
+				<command>tcp option</command>
+				<group choice="req">
+					<arg>eol</arg>
+					<arg>noop</arg>
+					<arg>maxseg</arg>
+					<arg>window</arg>
+					<arg>sack_permitted</arg>
+					<arg>sack</arg>
+					<arg>timestamp</arg>
+				</group>
+                <arg><replaceable>offset</replaceable></arg>
+				<arg choice="none"><replaceable>tcp_option_field</replaceable></arg>
+			</cmdsynopsis>
+			<para>
+				<table frame="all">
+					<title>IPv6 extension headers</title>
+					<tgroup cols='2' align='left' colsep='1' rowsep='1'>
+						<colspec colname='c1'/>
+						<colspec colname='c2'/>
+						<thead>
+							<row>
+								<entry>Keyword</entry>
+								<entry>Description</entry>
+							</row>
+						</thead>
+						<tbody>
+							<row>
+								<entry>hbh</entry>
+								<entry>Hop by Hop</entry>
+							</row>
+							<row>
+								<entry>rt</entry>
+								<entry>Routing Header</entry>
+							</row>
+							<row>
+								<entry>frag</entry>
+								<entry>Fragmentation header</entry>
+							</row>
+							<row>
+								<entry>dst</entry>
+								<entry>dst options</entry>
+							</row>
+							<row>
+								<entry>mh</entry>
+								<entry>Mobility Header</entry>
+							</row>
+						</tbody>
+					</tgroup>
+				</table>
+
+				<table frame="all">
+					<title>TCP Options</title>
+					<tgroup cols='3' align='left' colsep='1' rowsep='1'>
+						<colspec colname='c1'/>
+						<colspec colname='c2'/>
+						<colspec colname='c3'/>
+						<thead>
+							<row>
+								<entry>Keyword</entry>
+								<entry>Description</entry>
+								<entry>TCP option fields</entry>
+							</row>
+						</thead>
+						<tbody>
+							<row>
+								<entry>eol</entry>
+								<entry>End of option list</entry>
+								<entry>kind</entry>
+							</row>
+							<row>
+								<entry>noop</entry>
+								<entry>1 Byte TCP No-op options</entry>
+								<entry>kind</entry>
+							</row>
+							<row>
+								<entry>maxseg</entry>
+								<entry>TCP Maximum Segment Size</entry>
+								<entry>kind, length, size</entry>
+							</row>
+							<row>
+								<entry>window</entry>
+								<entry>TCP Window Scaling</entry>
+								<entry>kind, length, count</entry>
+							</row>
+							<row>
+								<entry>sack_permitted</entry>
+								<entry>TCP SACK permitted</entry>
+								<entry>kind, length</entry>
+							</row>
+							<row>
+								<entry>sack</entry>
+								<entry>TCP Selective Acknowledgement</entry>
+								<entry>kind, length, left, right</entry>
+							</row>
+							<row>
+								<entry>timestamp</entry>
+								<entry>TCP Timestamps</entry>
+								<entry>kind, length, tsval, tsecr</entry>
+							</row>
+						</tbody>
+					</tgroup>
+				</table>
+			</para>
+
+			<para>
+				The <replaceable>offset</replaceable> is only used for the SACK TCP option fields <command>left</command> and <command>right</command>.
+				For all non-SACK TCP options it is always zero.
+				<replaceable>Offsets</replaceable> which equals to zero can be omitted.
+			</para>
+
 			<para>
-				IPv6 extension header expressions refer to data from an IPv6 packet's extension headers.
+				<example>
+					<title>finding TCP options</title>
+					<programlisting>
+filter input tcp option sack_permitted kind 1 counter
+					</programlisting>
+				</example>
+				<example>
+				<title>matching IPv6 exthdr</title>
+					<programlisting>
+ip6 filter input frag more-fragments 1 counter
+					</programlisting>
+				</example>
 			</para>
 		</refsect2>
 
diff --git a/include/expression.h b/include/expression.h
index ec90265..83ecf11 100644
--- a/include/expression.h
+++ b/include/expression.h
@@ -281,6 +281,7 @@ struct expr {
 			const struct exthdr_desc	*desc;
 			const struct proto_hdr_template	*tmpl;
 			unsigned int			offset;
+			enum nft_exthdr_op		op;
 		} exthdr;
 		struct {
 			/* EXPR_META */
diff --git a/include/exthdr.h b/include/exthdr.h
index 93a53f3..cdcc2b9 100644
--- a/include/exthdr.h
+++ b/include/exthdr.h
@@ -2,6 +2,7 @@
 #define NFTABLES_EXTHDR_H
 
 #include <proto.h>
+#include <tcpopt.h>
 
 /**
  * struct exthdr_desc - extension header description
@@ -78,6 +79,7 @@ enum mh_hdr_fields {
 	MHHDR_CHECKSUM,
 };
 
+extern const struct expr_ops exthdr_expr_ops;
 extern const struct exthdr_desc exthdr_hbh;
 extern const struct exthdr_desc exthdr_rt;
 extern const struct exthdr_desc exthdr_rt0;
diff --git a/include/tcpopt.h b/include/tcpopt.h
new file mode 100644
index 0000000..5b99008
--- /dev/null
+++ b/include/tcpopt.h
@@ -0,0 +1,26 @@
+#ifndef NFTABLES_TCPOPT_H
+#define NFTABLES_TCPOPT_H
+
+#include <proto.h>
+#include <exthdr.h>
+
+extern struct expr *tcpopt_expr_alloc(const struct location *loc,
+				      const char *option_str,
+				      const unsigned int option_num,
+				      const char *optioni_field);
+
+extern void tcpopt_init_raw(struct expr *expr, uint8_t type,
+			    unsigned int offset, unsigned int len);
+
+extern bool tcpopt_find_template(struct expr *expr, const struct expr *mask,
+				 unsigned int *shift);
+
+extern const struct exthdr_desc tcpopt_eol;
+extern const struct exthdr_desc tcpopt_nop;
+extern const struct exthdr_desc tcpopt_maxseg;
+extern const struct exthdr_desc tcpopt_window;
+extern const struct exthdr_desc tcpopt_sack_permitted;
+extern const struct exthdr_desc tcpopt_sack;
+extern const struct exthdr_desc tcpopt_timestamp;
+
+#endif /* NFTABLES_TCPOPT_H */
diff --git a/src/Makefile.am b/src/Makefile.am
index c6586f5..99eef7b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -55,6 +55,7 @@ nft_SOURCES =	main.c				\
 		services.c			\
 		mergesort.c			\
 		scanner.l			\
+		tcpopt.c			\
 		parser_bison.y
 
 if BUILD_CLI
diff --git a/src/evaluate.c b/src/evaluate.c
index 0e02548..4817a55 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -438,6 +438,26 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
 	    expr->len % BITS_PER_BYTE != 0)
 		expr_evaluate_bits(ctx, exprp);
 
+	switch (expr->exthdr.op) {
+	case NFT_EXTHDR_OP_TCPOPT: {
+		static const uint8_t tcphdrlen = 20 * BITS_PER_BYTE;
+		static const unsigned int max_tcpoptlen = 15 * 4 * BITS_PER_BYTE - tcphdrlen;
+		unsigned int totlen = 0;
+
+		totlen += expr->exthdr.tmpl->offset;
+		totlen += expr->exthdr.tmpl->len;
+		totlen += expr->exthdr.offset;
+
+		if (totlen > max_tcpoptlen)
+			return expr_error(ctx->msgs, expr,
+					  "offset and size %u exceeds max tcp headerlen (%u)",
+					  totlen, max_tcpoptlen);
+		break;
+	}
+	default:
+		break;
+	}
+
 	return 0;
 }
 
@@ -448,11 +468,24 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
  */
 static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
 {
-	const struct proto_desc *base, *dependency = &proto_ip6;
+	const struct proto_desc *base, *dependency = NULL;
 	enum proto_bases pb = PROTO_BASE_NETWORK_HDR;
 	struct expr *expr = *exprp;
 	struct stmt *nstmt;
 
+	switch (expr->exthdr.op) {
+	case NFT_EXTHDR_OP_TCPOPT:
+		dependency = &proto_tcp;
+		pb = PROTO_BASE_TRANSPORT_HDR;
+		break;
+	case NFT_EXTHDR_OP_IPV6:
+	default:
+		dependency = &proto_ip6;
+		break;
+	}
+
+	assert(dependency);
+
 	base = ctx->pctx.protocol[pb].desc;
 	if (base == dependency)
 		return __expr_evaluate_exthdr(ctx, exprp);
diff --git a/src/exthdr.c b/src/exthdr.c
index 45b1b69..cfc6bb6 100644
--- a/src/exthdr.c
+++ b/src/exthdr.c
@@ -24,13 +24,29 @@
 
 static void exthdr_expr_print(const struct expr *expr)
 {
-	printf("%s %s", expr->exthdr.desc->name, expr->exthdr.tmpl->token);
+	if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) {
+		/* Offset calcualtion is a bit hacky at this point.
+		 * There might be an tcp option one day with another
+		 * multiplicator
+		 */
+		unsigned int offset = expr->exthdr.offset / 64;
+		char buf[3] = {0};
+
+		if (offset)
+			snprintf(buf, sizeof buf, " %d", offset);
+		printf("tcp option %s%s %s", expr->exthdr.desc->name, buf,
+					     expr->exthdr.tmpl->token);
+	}
+	else
+		printf("%s %s", expr->exthdr.desc->name,
+				expr->exthdr.tmpl->token);
 }
 
 static bool exthdr_expr_cmp(const struct expr *e1, const struct expr *e2)
 {
 	return e1->exthdr.desc == e2->exthdr.desc &&
-	       e1->exthdr.tmpl == e2->exthdr.tmpl;
+	       e1->exthdr.tmpl == e2->exthdr.tmpl &&
+	       e1->exthdr.op == e2->exthdr.op;
 }
 
 static void exthdr_expr_clone(struct expr *new, const struct expr *expr)
@@ -38,9 +54,10 @@ static void exthdr_expr_clone(struct expr *new, const struct expr *expr)
 	new->exthdr.desc = expr->exthdr.desc;
 	new->exthdr.tmpl = expr->exthdr.tmpl;
 	new->exthdr.offset = expr->exthdr.offset;
+	new->exthdr.op = expr->exthdr.op;
 }
 
-static const struct expr_ops exthdr_expr_ops = {
+const struct expr_ops exthdr_expr_ops = {
 	.type		= EXPR_EXTHDR,
 	.name		= "exthdr",
 	.print		= exthdr_expr_print,
@@ -86,6 +103,8 @@ void exthdr_init_raw(struct expr *expr, uint8_t type,
 	unsigned int i;
 
 	assert(expr->ops->type == EXPR_EXTHDR);
+	if (op == NFT_EXTHDR_OP_TCPOPT)
+		return tcpopt_init_raw(expr, type, offset, len);
 
 	expr->len = len;
 	expr->exthdr.offset = offset;
@@ -117,6 +136,12 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i
 	if (expr->exthdr.tmpl != &exthdr_unknown_template)
 		return false;
 
+	/* In case we are handling tcp options instead of the default ipv6
+	 * extension headers.
+	 */
+	if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT)
+		return tcpopt_find_template(expr, mask, shift);
+
 	mask_offset = mpz_scan1(mask->value, 0);
 	mask_len = mask_length(mask);
 
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index d6a9fe1..87010f1 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -507,7 +507,7 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx,
 	type   = nftnl_expr_get_u8(nle, NFTNL_EXPR_EXTHDR_TYPE);
 	offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET) * BITS_PER_BYTE;
 	len    = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_LEN) * BITS_PER_BYTE;
-	op     = NFT_EXTHDR_OP_IPV6;
+	op     = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OP);
 
 	expr = exthdr_expr_alloc(loc, NULL, 0);
 	exthdr_init_raw(expr, type, offset, len, op);
@@ -1221,6 +1221,7 @@ static const struct {
 	{ .name = "numgen",	.parse = netlink_parse_numgen },
 	{ .name = "hash",	.parse = netlink_parse_hash },
 	{ .name = "fib",	.parse = netlink_parse_fib },
+	{ .name = "tcpopt",	.parse = netlink_parse_exthdr },
 };
 
 static int netlink_parse_expr(const struct nftnl_expr *nle,
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 056f113..8849b0e 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -162,7 +162,7 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx,
 			       const struct expr *expr,
 			       enum nft_registers dreg)
 {
-	unsigned int offset = expr->exthdr.tmpl->offset;
+	unsigned int offset = expr->exthdr.tmpl->offset + expr->exthdr.offset;
 	struct nftnl_expr *nle;
 
 	nle = alloc_nft_expr("exthdr");
@@ -172,6 +172,7 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx,
 	nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET, offset / BITS_PER_BYTE);
 	nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_LEN,
 			   div_round_up(expr->len, BITS_PER_BYTE));
+	nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_OP, expr->exthdr.op);
 	nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
diff --git a/src/parser_bison.y b/src/parser_bison.y
index d543e3e..b295bfd 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -308,6 +308,7 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %token DOFF			"doff"
 %token WINDOW			"window"
 %token URGPTR			"urgptr"
+%token OPTION			"option"
 
 %token DCCP			"dccp"
 
@@ -428,8 +429,8 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 
 %token NOTRACK			"notrack"
 
-%type <string>			identifier type_identifier string comment_spec
-%destructor { xfree($$); }	identifier type_identifier string comment_spec
+%type <string>			identifier type_identifier string comment_spec tcp_option_name tcp_option_field
+%destructor { xfree($$); }	identifier type_identifier string comment_spec tcp_option_name tcp_option_field
 
 %type <val>			time_spec quota_used
 
@@ -581,9 +582,9 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %type <expr>			auth_hdr_expr	esp_hdr_expr		comp_hdr_expr
 %destructor { expr_free($$); }	auth_hdr_expr	esp_hdr_expr		comp_hdr_expr
 %type <val>			auth_hdr_field	esp_hdr_field		comp_hdr_field
-%type <expr>			udp_hdr_expr	udplite_hdr_expr	tcp_hdr_expr
-%destructor { expr_free($$); }	udp_hdr_expr	udplite_hdr_expr	tcp_hdr_expr
-%type <val>			udp_hdr_field	udplite_hdr_field	tcp_hdr_field
+%type <expr>			udp_hdr_expr	udplite_hdr_expr
+%destructor { expr_free($$); }	udp_hdr_expr	udplite_hdr_expr
+%type <val>			udp_hdr_field	udplite_hdr_field
 %type <expr>			dccp_hdr_expr	sctp_hdr_expr
 %destructor { expr_free($$); }	dccp_hdr_expr	sctp_hdr_expr
 %type <val>			dccp_hdr_field	sctp_hdr_field
@@ -600,6 +601,9 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %destructor { expr_free($$); }	mh_hdr_expr
 %type <val>			mh_hdr_field
 
+%type <expr>			tcp_hdr_optexpr
+%destructor { expr_free($$); }	tcp_hdr_optexpr
+
 %type <expr>			meta_expr
 %destructor { expr_free($$); }	meta_expr
 %type <val>			meta_key	meta_key_qualified	meta_key_unqualified	numgen_type
@@ -626,6 +630,10 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %type <quota>			quota_config
 %destructor { xfree($$); }	quota_config
 
+%type <expr>			tcp_hdr_expr
+%destructor { expr_free($$); }	tcp_hdr_expr
+%type <val>			tcp_hdr_field
+
 %%
 
 input			:	/* empty */
@@ -3232,6 +3240,7 @@ exthdr_expr		:	hbh_hdr_expr
 			|	frag_hdr_expr
 			|	dst_hdr_expr
 			|	mh_hdr_expr
+			|	tcp_hdr_optexpr
 			;
 
 hbh_hdr_expr		:	HBH	hbh_hdr_field
@@ -3314,4 +3323,31 @@ mh_hdr_field		:	NEXTHDR		{ $$ = MHHDR_NEXTHDR; }
 			|	CHECKSUM	{ $$ = MHHDR_CHECKSUM; }
 			;
 
+tcp_option_name		:	STRING		{ $$ = $1; }
+			|	WINDOW		{ $$ = xstrdup("window"); }
+			;
+
+tcp_option_field	:	STRING		{ $$ = $1; }
+			|	LENGTH		{ $$ = xstrdup("length"); }
+			|	SIZE		{ $$ = xstrdup("size"); }
+			;
+
+tcp_hdr_optexpr		:	TCP	OPTION	tcp_option_name		tcp_option_field
+			{
+				$$ = tcpopt_expr_alloc(&@$, $3, 0, $4);
+			}
+			|	TCP	OPTION	STRING	NUM	tcp_option_field
+			{
+				if (strcmp($3, "sack")) {
+					erec_queue(error(&@2, "tcp: number (%d) can only be used with sack option", $4), state->msgs);
+					YYERROR;
+				}
+
+				if ($4 > 3) {
+					erec_queue(error(&@2, "tcp: option block (%d) too large (0-3)", $4), state->msgs);
+					YYERROR;
+				}
+				$$ = tcpopt_expr_alloc(&@$, $3, $4, $5);
+			}
+			;
 %%
diff --git a/src/scanner.l b/src/scanner.l
index d0d25ea..922d8ec 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -411,6 +411,7 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 "doff"			{ return DOFF; }
 "window"		{ return WINDOW; }
 "urgptr"		{ return URGPTR; }
+"option"		{ return OPTION; }
 
 "dccp"			{ return DCCP; }
 
diff --git a/src/tcpopt.c b/src/tcpopt.c
new file mode 100644
index 0000000..e6f92bc
--- /dev/null
+++ b/src/tcpopt.c
@@ -0,0 +1,269 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/tcp.h>
+
+#include <utils.h>
+#include <headers.h>
+#include <expression.h>
+#include <tcpopt.h>
+
+/* We do not need to export these enums, because the tcpopts are parsed at
+ * runtime and not by bison.
+ */
+enum tcpopt_eol_hdr_fields {
+	TCPOPT_EOLHDR_KIND,
+};
+
+enum tcpopt_nop_hdr_fields {
+	TCPOPT_NOPHDR_KIND,
+};
+
+enum tcpopt_maxseg_hdr_fields {
+	TCPOPT_MAXSEGHDR_KIND,
+	TCPOPT_MAXSEGHDR_LENGTH,
+	TCPOPT_MAXSEGHDR_SIZE,
+};
+
+enum tcpopt_window_hdr_fields {
+	TCPOPT_WINDOWHDR_KIND,
+	TCPOPT_WINDOWHDR_LENGTH,
+	TCPOPT_WINDOWHDR_COUNT,
+};
+
+enum tcpopt_sack_permitted_hdr_fields {
+	TCPOPT_SACKPERMHDR_KIND,
+	TCPOPT_SACKPERMHDR_LENGTH,
+};
+
+enum tcpopt_sack_hdr_fields {
+	TCPOPT_SACKHDR_KIND,
+	TCPOPT_SACKHDR_LENGTH,
+	TCPOPT_SACKHDR_LEFT,
+	TCPOPT_SACKHDR_RIGHT,
+};
+
+enum tcpopt_timestamp_hdr_fields {
+	TCPOPT_TIMESTAMPSHDR_KIND,
+	TCPOPT_TIMESTAMPSHDR_LENGTH,
+	TCPOPT_TIMESTAMPSHDR_TSVAL,
+	TCPOPT_TIMESTAMPSHDR_TSECR,
+};
+
+static const struct proto_hdr_template tcpopt_unknown_template =
+	PROTO_HDR_TEMPLATE("unknown", &invalid_type, BYTEORDER_INVALID, 0, 0);
+
+#define PHT(__token, __offset, __len) \
+	PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \
+			   __offset, __len)
+const struct exthdr_desc tcpopt_eol = {
+	.name		= "eol",
+	.type		= TCPOPT_EOL,
+	.templates	= {
+		[TCPOPT_EOLHDR_KIND]		= PHT("kind",  0,    8),
+	},
+};
+
+const struct exthdr_desc tcpopt_nop = {
+	.name		= "noop",
+	.type		= TCPOPT_NOP,
+	.templates	= {
+		[TCPOPT_NOPHDR_KIND]		= PHT("kind",   0,   8),
+	},
+};
+
+const struct exthdr_desc tcptopt_maxseg = {
+	.name		= "maxseg",
+	.type		= TCPOPT_MAXSEG,
+	.templates	= {
+		[TCPOPT_MAXSEGHDR_KIND]		= PHT("kind",   0,  8),
+		[TCPOPT_MAXSEGHDR_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_MAXSEGHDR_SIZE]		= PHT("size",  16, 16),
+	},
+};
+
+const struct exthdr_desc tcpopt_window = {
+	.name		= "window",
+	.type		= TCPOPT_WINDOW,
+	.templates	= {
+		[TCPOPT_WINDOWHDR_KIND]		= PHT("kind",   0,  8),
+		[TCPOPT_WINDOWHDR_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_WINDOWHDR_COUNT]	= PHT("count", 16,  8),
+	},
+};
+
+const struct exthdr_desc tcpopt_sack_permitted = {
+	.name		= "sack_permitted",
+	.type		= TCPOPT_SACK_PERMITTED,
+	.templates	= {
+		[TCPOPT_SACKPERMHDR_KIND]	= PHT("kind",   0, 8),
+		[TCPOPT_SACKPERMHDR_LENGTH]	= PHT("length", 8, 8),
+	},
+};
+
+const struct exthdr_desc tcpopt_sack = {
+	.name		= "sack",
+	.type		= TCPOPT_SACK,
+	.templates	= {
+		[TCPOPT_SACKHDR_KIND]		= PHT("kind",   0,   8),
+		[TCPOPT_SACKHDR_LENGTH]		= PHT("length", 8,   8),
+		[TCPOPT_SACKHDR_LEFT]		= PHT("left",  16,  32),
+		[TCPOPT_SACKHDR_RIGHT]		= PHT("right", 48,  32),
+	},
+};
+
+const struct exthdr_desc tcpopt_timestamp = {
+	.name		= "timestamp",
+	.type		= TCPOPT_TIMESTAMP,
+	.templates	= {
+		[TCPOPT_TIMESTAMPSHDR_KIND]	= PHT("kind",   0,  8),
+		[TCPOPT_TIMESTAMPSHDR_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_TIMESTAMPSHDR_TSVAL]	= PHT("tsval",  16, 32),
+		[TCPOPT_TIMESTAMPSHDR_TSECR]	= PHT("tsecr",  48, 32),
+	},
+};
+#undef PHT
+
+#define TCPOPT_OBSOLETE ((struct exthdr_desc *)NULL)
+#define TCPOPT_ECHO 6
+#define TCPOPT_ECHO_REPLY 7
+const struct exthdr_desc *tcpopt_protocols[] = {
+	[TCPOPT_EOL]		= &tcpopt_eol,
+	[TCPOPT_NOP]		= &tcpopt_nop,
+	[TCPOPT_MAXSEG]		= &tcptopt_maxseg,
+	[TCPOPT_WINDOW]		= &tcpopt_window,
+	[TCPOPT_SACK_PERMITTED]	= &tcpopt_sack_permitted,
+	[TCPOPT_SACK]		= &tcpopt_sack,
+	[TCPOPT_ECHO]		= TCPOPT_OBSOLETE,
+	[TCPOPT_ECHO_REPLY]	= TCPOPT_OBSOLETE,
+	[TCPOPT_TIMESTAMP]	= &tcpopt_timestamp,
+};
+
+static unsigned int calc_offset(const struct exthdr_desc *desc,
+				const struct proto_hdr_template *tmpl,
+				unsigned int num)
+{
+	if (!desc || tmpl == &tcpopt_unknown_template)
+		return 0;
+
+	switch (desc->type) {
+	case TCPOPT_SACK:
+		/* Make sure, offset calculations only apply to left and right
+		 * fields
+		 */
+		return (tmpl->offset < 16) ? 0 : num * 64;
+	default:
+		return 0;
+	}
+}
+
+
+static unsigned int calc_offset_reverse(const struct exthdr_desc *desc,
+					const struct proto_hdr_template *tmpl,
+					unsigned int offset)
+{
+	if (!desc || tmpl == &tcpopt_unknown_template)
+		return offset;
+
+	switch (desc->type) {
+	case TCPOPT_SACK:
+		/* We can safely ignore the first left/right field */
+		return offset < 80 ? offset : (offset % 64);
+	default:
+		return offset;
+	}
+}
+
+
+struct expr *tcpopt_expr_alloc(const struct location *loc,
+			       const char *option_str,
+			       const unsigned int option_num,
+			       const char *option_field)
+{
+	const struct proto_hdr_template *tmp, *tmpl = &tcpopt_unknown_template;
+	const struct exthdr_desc *desc = NULL;
+	struct expr *expr;
+	unsigned int i, j;
+
+	for (i = 0; i < array_size(tcpopt_protocols); ++i) {
+		if (tcpopt_protocols[i] == TCPOPT_OBSOLETE)
+			continue;
+
+		if (!tcpopt_protocols[i]->name ||
+		    strcmp(option_str, tcpopt_protocols[i]->name))
+			continue;
+
+		for (j = 0; j < array_size(tcpopt_protocols[i]->templates); ++j) {
+			tmp = &tcpopt_protocols[i]->templates[j];
+			if (!tmp->token || strcmp(option_field, tmp->token))
+				continue;
+
+			desc = tcpopt_protocols[i];
+			tmpl = tmp;
+			goto found;
+		}
+	}
+
+found:
+	/* tmpl still points to tcpopt_unknown_template if nothing was found and
+	 * desc is null
+	 */
+	expr = expr_alloc(loc, &exthdr_expr_ops, tmpl->dtype,
+			  BYTEORDER_BIG_ENDIAN, tmpl->len);
+	expr->exthdr.desc   = desc;
+	expr->exthdr.tmpl   = tmpl;
+	expr->exthdr.op     = NFT_EXTHDR_OP_TCPOPT;
+	expr->exthdr.offset = calc_offset(desc, tmpl, option_num);
+
+	return expr;
+}
+
+void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int offset,
+		     unsigned int len)
+{
+	const struct proto_hdr_template *tmpl;
+	unsigned int i, off;
+
+	assert(expr->ops->type == EXPR_EXTHDR);
+
+	expr->len = len;
+	expr->exthdr.offset = offset;
+
+	assert(type < array_size(tcpopt_protocols));
+	expr->exthdr.desc = tcpopt_protocols[type];
+	assert(expr->exthdr.desc != TCPOPT_OBSOLETE);
+
+	for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) {
+		tmpl = &expr->exthdr.desc->templates[i];
+		/* We have to reverse calculate the offset for the sack options
+		 * at this point
+		 */
+		off = calc_offset_reverse(expr->exthdr.desc, tmpl, offset);
+		if (tmpl->offset != off || tmpl->len != len)
+			continue;
+
+		expr->dtype       = tmpl->dtype;
+		expr->exthdr.tmpl = tmpl;
+		expr->exthdr.op   = NFT_EXTHDR_OP_TCPOPT;
+		break;
+	}
+}
+
+bool tcpopt_find_template(struct expr *expr, const struct expr *mask,
+			  unsigned int *shift)
+{
+	if (expr->exthdr.tmpl != &tcpopt_unknown_template)
+		return false;
+
+	tcpopt_init_raw(expr, expr->exthdr.desc->type, expr->exthdr.offset,
+			expr->len);
+
+	if (expr->exthdr.tmpl == &tcpopt_unknown_template)
+		return false;
+
+	return true;
+}
-- 
2.11.1


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

* [PATCH nftables 5/7] payload: insert implicit meta tcp dependency when matching tcp options
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
                   ` (3 preceding siblings ...)
  2017-02-07  2:14 ` [PATCH nftables 4/7] src: add TCP option matching Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 6/7] payload: automatically kill dependencies for exthdr and tcpopt Manuel Messner
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal, mm

From: Florian Westphal <fw@strlen.de>

nft add rule inet filter input tcp option sack 4 left 1
<cmdline>:1:28-49: Error: Cannot generate dependency: no network layer protocol specified

Users can avoid this via 'meta l4proto tcp', this enables implicit
dependency injection for the inet/bridge/netdev families.

Signed-off-by: Florian Westphal <fw@strlen.de>
Reviewed-by: Manuel Messner <mm@skelett.io>
Signed-off-by: Manuel Messner <mm@skelett.io>
---
 src/payload.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/payload.c b/src/payload.c
index efd1960..0207296 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -323,12 +323,26 @@ int exthdr_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
 	const struct proto_desc *desc;
 
 	desc = ctx->pctx.protocol[pb].desc;
-	if (desc == NULL)
+	if (desc == NULL) {
+		if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) {
+			switch (ctx->pctx.family) {
+			case NFPROTO_NETDEV:
+			case NFPROTO_BRIDGE:
+			case NFPROTO_INET:
+				desc = &proto_inet_service;
+				goto found;
+			default:
+				break;
+			}
+		}
+
 		return expr_error(ctx->msgs, expr,
 				  "Cannot generate dependency: "
 				  "no %s protocol specified",
 				  proto_base_names[pb]);
+	}
 
+ found:
 	return payload_add_dependency(ctx, desc, dependency, expr, res);
 }
 
-- 
2.11.1


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

* [PATCH nftables 6/7] payload: automatically kill dependencies for exthdr and tcpopt
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
                   ` (4 preceding siblings ...)
  2017-02-07  2:14 ` [PATCH nftables 5/7] payload: insert implicit meta tcp dependency when matching tcp options Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-07  2:14 ` [PATCH nftables 7/7] tests: py: Add basic tests for ip, ip6 and inet Manuel Messner
  2017-02-08  9:32 ` [PATCH nftables 0/7] TCP option matching Pablo Neira Ayuso
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

This patch automatically removes the dependencies for exthdr and tcpopt.

 # nft add rule filter input tcp option maxseg kind 3 counter.
 # nft list table filter input

Before:

 # ip protocol 6 tcp option maxseg kind 3 counter

After:

 # tcp option maxseg kind 3 counter

Thus allowing to write tests as follows:

 # tcp option maxseg kind 3;ok

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Manuel Messner <mm@skelett.io>
---
 include/payload.h         |  2 ++
 src/netlink_delinearize.c |  2 +-
 src/payload.c             | 14 ++++++++++++++
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/include/payload.h b/include/payload.h
index 5952b24..a3d2309 100644
--- a/include/payload.h
+++ b/include/payload.h
@@ -42,6 +42,8 @@ extern void __payload_dependency_kill(struct payload_dep_ctx *ctx,
 				      enum proto_bases base);
 extern void payload_dependency_kill(struct payload_dep_ctx *ctx,
 				    struct expr *expr);
+extern void exthdr_dependency_kill(struct payload_dep_ctx *ctx,
+				   struct expr *expr);
 
 extern bool payload_can_merge(const struct expr *e1, const struct expr *e2);
 extern struct expr *payload_expr_join(const struct expr *e1,
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 87010f1..e23c48b 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -1841,7 +1841,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
 		expr_postprocess(ctx, &expr->key);
 		break;
 	case EXPR_EXTHDR:
-		__payload_dependency_kill(&ctx->pdctx, PROTO_BASE_NETWORK_HDR);
+		exthdr_dependency_kill(&ctx->pdctx, expr);
 		break;
 	case EXPR_SET_REF:
 	case EXPR_META:
diff --git a/src/payload.c b/src/payload.c
index 0207296..169954b 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -410,6 +410,20 @@ void payload_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr)
 	__payload_dependency_kill(ctx, expr->payload.base);
 }
 
+void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr)
+{
+	switch (expr->exthdr.op) {
+	case NFT_EXTHDR_OP_TCPOPT:
+		__payload_dependency_kill(ctx, PROTO_BASE_TRANSPORT_HDR);
+		break;
+	case NFT_EXTHDR_OP_IPV6:
+		__payload_dependency_kill(ctx, PROTO_BASE_NETWORK_HDR);
+		break;
+	default:
+		break;
+	}
+}
+
 /**
  * payload_expr_complete - fill in type information of a raw payload expr
  *
-- 
2.11.1


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

* [PATCH nftables 7/7] tests: py: Add basic tests for ip, ip6 and inet
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
                   ` (5 preceding siblings ...)
  2017-02-07  2:14 ` [PATCH nftables 6/7] payload: automatically kill dependencies for exthdr and tcpopt Manuel Messner
@ 2017-02-07  2:14 ` Manuel Messner
  2017-02-08  9:32 ` [PATCH nftables 0/7] TCP option matching Pablo Neira Ayuso
  7 siblings, 0 replies; 9+ messages in thread
From: Manuel Messner @ 2017-02-07  2:14 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw, mm

Signed-off-by: Manuel Messner <mm@skelett.io>
Reviewed-by: Florian Westphal <fw@strlen.de>
---
 tests/py/inet/tcpopt.t              |  38 ++++++++
 tests/py/inet/tcpopt.t.payload.inet | 181 ++++++++++++++++++++++++++++++++++++
 tests/py/ip/tcpopt.t                |  38 ++++++++
 tests/py/ip/tcpopt.t.payload        | 181 ++++++++++++++++++++++++++++++++++++
 tests/py/ip6/tcpopt.t               |  37 ++++++++
 tests/py/ip6/tcpopt.t.payload       | 181 ++++++++++++++++++++++++++++++++++++
 6 files changed, 656 insertions(+)
 create mode 100644 tests/py/inet/tcpopt.t
 create mode 100644 tests/py/inet/tcpopt.t.payload.inet
 create mode 100644 tests/py/ip/tcpopt.t
 create mode 100644 tests/py/ip/tcpopt.t.payload
 create mode 100644 tests/py/ip6/tcpopt.t
 create mode 100644 tests/py/ip6/tcpopt.t.payload

diff --git a/tests/py/inet/tcpopt.t b/tests/py/inet/tcpopt.t
new file mode 100644
index 0000000..39204fd
--- /dev/null
+++ b/tests/py/inet/tcpopt.t
@@ -0,0 +1,38 @@
+:input;type filter hook input priority 0
+
+*inet;test-inet;input
+
+tcp option eol kind 1;ok
+tcp option noop kind 1;ok
+tcp option maxseg kind 1;ok
+tcp option maxseg length 1;ok
+tcp option maxseg size 1;ok
+tcp option window kind 1;ok
+tcp option window length 1;ok
+tcp option window count 1;ok
+tcp option sack_permitted kind 1;ok
+tcp option sack_permitted length 1;ok
+tcp option sack kind 1;ok
+tcp option sack length 1;ok
+tcp option sack left 1;ok
+tcp option sack 0 left 1;ok;tcp option sack left 1
+tcp option sack 1 left 1;ok
+tcp option sack 2 left 1;ok
+tcp option sack 3 left 1;ok
+tcp option sack right 1;ok
+tcp option sack 0 right 1;ok;tcp option sack right 1
+tcp option sack 1 right 1;ok
+tcp option sack 2 right 1;ok
+tcp option sack 3 right 1;ok
+tcp option timestamp kind 1;ok
+tcp option timestamp length 1;ok
+tcp option timestamp tsval 1;ok
+tcp option timestamp tsecr 1;ok
+
+tcp option foobar;fail
+tcp option foo bar;fail
+tcp option eol left;fail
+tcp option eol left 1;fail
+tcp option eol left 1;fail
+tcp option sack window;fail
+tcp option sack window 1;fail
diff --git a/tests/py/inet/tcpopt.t.payload.inet b/tests/py/inet/tcpopt.t.payload.inet
new file mode 100644
index 0000000..1343ae5
--- /dev/null
+++ b/tests/py/inet/tcpopt.t.payload.inet
@@ -0,0 +1,181 @@
+# tcp option eol kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 0 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option noop kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 1 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg length 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg size 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 2b @ 2 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+
+# tcp option window kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window length 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window count 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted length 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack length 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack left 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 left 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 left 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 10 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 left 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 18 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 left 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 26 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack right 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 right 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 right 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 14 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 right 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 22 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 right 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 30 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp kind 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp length 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp tsval 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp tsecr 1
+inet test-inet input
+  [ meta load l4proto => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
diff --git a/tests/py/ip/tcpopt.t b/tests/py/ip/tcpopt.t
new file mode 100644
index 0000000..fccc6dd
--- /dev/null
+++ b/tests/py/ip/tcpopt.t
@@ -0,0 +1,38 @@
+:input;type filter hook input priority 0
+
+*ip;test-ip;input
+
+tcp option eol kind 1;ok
+tcp option noop kind 1;ok
+tcp option maxseg kind 1;ok
+tcp option maxseg length 1;ok
+tcp option maxseg size 1;ok
+tcp option window kind 1;ok
+tcp option window length 1;ok
+tcp option window count 1;ok
+tcp option sack_permitted kind 1;ok
+tcp option sack_permitted length 1;ok
+tcp option sack kind 1;ok
+tcp option sack length 1;ok
+tcp option sack left 1;ok
+tcp option sack 0 left 1;ok;tcp option sack left 1
+tcp option sack 1 left 1;ok
+tcp option sack 2 left 1;ok
+tcp option sack 3 left 1;ok
+tcp option sack right 1;ok
+tcp option sack 0 right 1;ok;tcp option sack right 1
+tcp option sack 1 right 1;ok
+tcp option sack 2 right 1;ok
+tcp option sack 3 right 1;ok
+tcp option timestamp kind 1;ok
+tcp option timestamp length 1;ok
+tcp option timestamp tsval 1;ok
+tcp option timestamp tsecr 1;ok
+
+tcp option foobar;fail
+tcp option foo bar;fail
+tcp option eol left;fail
+tcp option eol left 1;fail
+tcp option eol left 1;fail
+tcp option sack window;fail
+tcp option sack window 1;fail
diff --git a/tests/py/ip/tcpopt.t.payload b/tests/py/ip/tcpopt.t.payload
new file mode 100644
index 0000000..47e4dee
--- /dev/null
+++ b/tests/py/ip/tcpopt.t.payload
@@ -0,0 +1,181 @@
+# tcp option eol kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 0 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option noop kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 1 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg length 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg size 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 2b @ 2 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+
+# tcp option window kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window length 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window count 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted length 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack length 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack left 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 left 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 left 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 10 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 left 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 18 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 left 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 26 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack right 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 right 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 right 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 14 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 right 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 22 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 right 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 30 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp kind 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp length 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp tsval 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp tsecr 1
+ip test-ip input
+  [ payload load 1b @ network header + 9 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
diff --git a/tests/py/ip6/tcpopt.t b/tests/py/ip6/tcpopt.t
new file mode 100644
index 0000000..d6f2dba
--- /dev/null
+++ b/tests/py/ip6/tcpopt.t
@@ -0,0 +1,37 @@
+:input;type filter hook input priority 0
+*ip6;test-ip6;input
+
+tcp option eol kind 1;ok
+tcp option noop kind 1;ok
+tcp option maxseg kind 1;ok
+tcp option maxseg length 1;ok
+tcp option maxseg size 1;ok
+tcp option window kind 1;ok
+tcp option window length 1;ok
+tcp option window count 1;ok
+tcp option sack_permitted kind 1;ok
+tcp option sack_permitted length 1;ok
+tcp option sack kind 1;ok
+tcp option sack length 1;ok
+tcp option sack left 1;ok
+tcp option sack 0 left 1;ok;tcp option sack left 1
+tcp option sack 1 left 1;ok
+tcp option sack 2 left 1;ok
+tcp option sack 3 left 1;ok
+tcp option sack right 1;ok
+tcp option sack 0 right 1;ok;tcp option sack right 1
+tcp option sack 1 right 1;ok
+tcp option sack 2 right 1;ok
+tcp option sack 3 right 1;ok
+tcp option timestamp kind 1;ok
+tcp option timestamp length 1;ok
+tcp option timestamp tsval 1;ok
+tcp option timestamp tsecr 1;ok
+
+tcp option foobar;fail
+tcp option foo bar;fail
+tcp option eol left;fail
+tcp option eol left 1;fail
+tcp option eol left 1;fail
+tcp option sack window;fail
+tcp option sack window 1;fail
diff --git a/tests/py/ip6/tcpopt.t.payload b/tests/py/ip6/tcpopt.t.payload
new file mode 100644
index 0000000..98389b0
--- /dev/null
+++ b/tests/py/ip6/tcpopt.t.payload
@@ -0,0 +1,181 @@
+# tcp option eol kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 0 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option noop kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 1 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg length 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 2 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option maxseg size 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 2b @ 2 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+
+# tcp option window kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window length 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option window count 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 3 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack_permitted length 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 4 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack length 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 5 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option sack left 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 left 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 left 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 10 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 left 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 18 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 left 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 26 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack right 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 0 right 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 1 right 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 14 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 2 right 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 22 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option sack 3 right 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 5 + 30 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp kind 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp length 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 1b @ 8 + 1 => reg 1 ]
+  [ cmp eq reg 1 0x00000001 ]
+
+# tcp option timestamp tsval 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 2 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
+
+# tcp option timestamp tsecr 1
+ip6 test-ip input
+  [ payload load 1b @ network header + 6 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ exthdr load tcpopt 4b @ 8 + 6 => reg 1 ]
+  [ cmp eq reg 1 0x01000000 ]
-- 
2.11.1


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

* Re: [PATCH nftables 0/7] TCP option matching
  2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
                   ` (6 preceding siblings ...)
  2017-02-07  2:14 ` [PATCH nftables 7/7] tests: py: Add basic tests for ip, ip6 and inet Manuel Messner
@ 2017-02-08  9:32 ` Pablo Neira Ayuso
  7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2017-02-08  9:32 UTC (permalink / raw)
  To: Manuel Messner; +Cc: netfilter-devel, fw

On Tue, Feb 07, 2017 at 03:14:08AM +0100, Manuel Messner wrote:
> This patch set is part of the TCP option matching implementation for nftables.
> 
> These patch sets enable nft to match against the following TCP options:
> * End of Option List
> * No-Operation
> * Maximum Segment Size
> * Window Scale
> * SACK
> * SACK Permitted
> * Timestamps

Florian, please feel free to push out this once the kernel side hits
David's tree.

Thanks!

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

end of thread, other threads:[~2017-02-08  9:32 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-07  2:14 [PATCH nftables 0/7] TCP option matching Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 1/7] include: linux: netfilter: nf_tables: copy file from nf-next Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 2/7] exthdr: prepare for tcp support Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 3/7] exthdr: prepare exthdr_gen_dependency " Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 4/7] src: add TCP option matching Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 5/7] payload: insert implicit meta tcp dependency when matching tcp options Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 6/7] payload: automatically kill dependencies for exthdr and tcpopt Manuel Messner
2017-02-07  2:14 ` [PATCH nftables 7/7] tests: py: Add basic tests for ip, ip6 and inet Manuel Messner
2017-02-08  9:32 ` [PATCH nftables 0/7] TCP option matching 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.