From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED.blaine.gmane.org!not-for-mail From: trentbuck@gmail.com (Trent W. Buck) Newsgroups: gmane.emacs.bugs Subject: bug#36759: 26.1; nftables major mode Date: Mon, 22 Jul 2019 17:45:33 +1000 Message-ID: <87d0i2ecma.fsf@goll.lan> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: blaine.gmane.org; posting-host="blaine.gmane.org:195.159.176.226"; logging-data="158922"; mail-complaints-to="usenet@blaine.gmane.org" To: 36759@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Mon Jul 22 09:46:16 2019 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([209.51.188.17]) by blaine.gmane.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.89) (envelope-from ) id 1hpT1R-000fBs-S8 for geb-bug-gnu-emacs@m.gmane.org; Mon, 22 Jul 2019 09:46:14 +0200 Original-Received: from localhost ([::1]:59492 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hpT1Q-0000OX-TL for geb-bug-gnu-emacs@m.gmane.org; Mon, 22 Jul 2019 03:46:12 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:37040) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hpT1K-0000No-IK for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:46:10 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hpT1H-0001ae-0H for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:46:06 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:51074) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hpT1G-0001aD-Oy for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:46:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1hpT1G-0002Bw-MQ for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:46:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: trentbuck@gmail.com (Trent W. Buck) Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Mon, 22 Jul 2019 07:46:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 36759 X-GNU-PR-Package: emacs X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.15637815568396 (code B ref -1); Mon, 22 Jul 2019 07:46:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 22 Jul 2019 07:45:56 +0000 Original-Received: from localhost ([127.0.0.1]:59895 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hpT19-0002BL-L0 for submit@debbugs.gnu.org; Mon, 22 Jul 2019 03:45:56 -0400 Original-Received: from lists.gnu.org ([209.51.188.17]:54137) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hpT17-0002BD-2i for submit@debbugs.gnu.org; Mon, 22 Jul 2019 03:45:54 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:36875) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hpT13-0000Ms-8l for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:45:52 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hpT0z-0001EF-Fw for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:45:49 -0400 Original-Received: from mail-pl1-x643.google.com ([2607:f8b0:4864:20::643]:43112) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hpT0w-00019q-7E for bug-gnu-emacs@gnu.org; Mon, 22 Jul 2019 03:45:44 -0400 Original-Received: by mail-pl1-x643.google.com with SMTP id 4so11833760pld.10 for ; Mon, 22 Jul 2019 00:45:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:mime-version; bh=hi1mNNMa2LZV/gfUPHEQH+Pn8A/S30BGFYfBy8s52qs=; b=WfdYpeNZhTiB6D3GT4TIUXHaj9LM1aVpIBrbv6iz8nGIA1WNrRXw4ImLKCjgpdaBNQ ZLqjY3C4PLvWhd8jP4tR2ZCWf/sl+wt3Xkrgqoaze8CX0q3hOcowHDxWD/V8x7u5aNwh wUohAHsscy1rE0HQS3VDPzfw0YvILhzjxTr22O0Gy/VzifDtABNhj+eUzXz/WmpdZ8BQ 9ln+rX1i4bSDvmO0SRecPDngrizPRSeIyfjXei/gZJj32S9Epd5TVRCQYPsMnUSujmhk lfLzuv0+SCKK5OhSvc9o9vZhFJ2K28TiYgTVKA7y4rR90WxMF+Em+ciVdFhRW6wRUNfA TuPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:mime-version; bh=hi1mNNMa2LZV/gfUPHEQH+Pn8A/S30BGFYfBy8s52qs=; b=Qgn95MSHKabwenmq1RFSHwLR1bHENwV78oXQQqh8tnUZ2bXRgoHQ2Aen0ZvXsq+any HTxS7OldRrDjhIs+6LzILP02gXdLsN2IJfIRbFnFoGkNgWn5YgoXVJW/2lo/YXqiW6sk k2qQBJ40MAM9qc1mu0MQ/Xwg9RWZ1jdSAgOpXHu6XkK7IxQrqVOz6YD2BuwbY0d+aqaW 2THoP/61SqnZJW/vMuBba5p7Q58CSAq60bKrGIUsTRHF139XQjrhUWjnPjULiWAGkk7I 2G9cr7FpDMj9bqOGi9A3tgejq08/kYTNEodi8DUDimggHkW5/ePrfdyXwRyPs7V13d4p fwvA== X-Gm-Message-State: APjAAAXh4HhwYfODm8uMfETmA0qmg2rlQ1D54m3S1pbisAdGdTih86gM 4PtfnNg2E7mouRkUy6AAMtjgtFSK X-Google-Smtp-Source: APXvYqxryCAfGegPpPGg0gpAItvMwPPwGGNIYL0rytj9BNjcsCtHpmRUOtoPw6R5A39Avl4kntM4HQ== X-Received: by 2002:a17:902:2a69:: with SMTP id i96mr73140538plb.108.1563781539575; Mon, 22 Jul 2019 00:45:39 -0700 (PDT) Original-Received: from localhost ([203.7.155.117]) by smtp.gmail.com with ESMTPSA id z19sm33429438pgv.35.2019.07.22.00.45.36 for (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Mon, 22 Jul 2019 00:45:37 -0700 (PDT) X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.51.188.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:163581 Archived-At: --=-=-= Content-Type: text/plain nftables is a Linux kernel firewall. Its configuration uses a complex BNF where many keywords are repeated; they mean different things in different places. I want syntax highlighting and smart indentation for such files, because they're very hard to read in just conf-mode, even with some conf-space-keywords. I couldn't find a major mode for this, so I wrote a basic one. This is working well enough for today, but I don't have the time or interest to maintain it properly. If someone else is prepared to adopt it and get it into mainline Emacs, that would be FANTASTIC. Background references: https://wiki.nftables.org https://salsa.debian.org/pkg-netfilter-team/pkg-nftables/blob/master/src/scanner.l https://salsa.debian.org/pkg-netfilter-team/pkg-nftables/blob/master/src/parser_bison.y file:///usr/share/nano/nftables.nanorc https://github.com/nfnty/vim-nftables --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=nftables-mode.el Content-Transfer-Encoding: quoted-printable (require 'rx) (require 'syntax) ; syntax-ppss, for indentation (defvar nftables-mode-map (make-sparse-keymap)) (defvar nftables-mode-hook nil) (defvar nftables-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?# "<\n" table) ; make #comment work (modify-syntax-entry ?\n ">#" table) ; make #comment work (modify-syntax-entry ?_ "w" table) ; foo_bar is 1 word (not 2) table)) ;;; NOTE: I started with the keywords in the nano highlighter, but ;;; they were really incomplete. So instead I looked at the ;;; flex/bison rules in the nft source code (as at debian/0.9.1-2-2-g3255aa= a): ;;; https://salsa.debian.org/pkg-netfilter-team/pkg-nftables/blob/maste= r/src/scanner.l ;;; https://salsa.debian.org/pkg-netfilter-team/pkg-nftables/blob/maste= r/src/parser_bison.y ;;; NOTE: not supporting multi-statement lines "list ruleset; flush ruleset= ". ;;; NOTE: not supporting multi-line statements "list \\\n ruleset". ;;; NOTE: not supporting arbitrary whitespace in some places. ;;; NOTE: identifiers are hard (e.g. bare addresses, names, quoted strings)= , so ;;; not supporting all those properly. ;;; NOTE: family can be omitted; it defaults to "ip" (IPv4 only). ;;; I am not supporting that, because you USUALLY want "inet" (IPv4/I= Pv6 dual-stack). ;;; NOTE: there are two main styles, I'm supporting only those and not a mi= x of same. ;;; ;;; Style #1: ;;; ;;; flush ruleset ;;; table inet foo { ;;; chain bar { ;;; type filter hook input priority filter ;;; policy drop ;;; predicate [counter] [log] ;;; } ;;; } ;;; ;;; Style #2 (everything at the "top level"): ;;; ;;; flush ruleset ;;; add table inet foo ;;; add chain inet foo bar { type filter hook input priority fil= ter; policy drop } ;;; add rule inet foo bar predicate [counter] [log] (defvar nftables-font-lock-keywords `( ;; include "foo" ;; list ruleset ;; flush ruleset (,(rx bol (or "include" "list ruleset" "flush ruleset" "list tables" "list counters" "list quotas") eow) . font-lock-preprocessor-face) ;; define foo =3D bar ;; define foo =3D { bar, baz } ;; redefine foo =3D bar ;; undefine foo (,(rx bol (group (or "define" "redefine" "undefine")) " " (group (one-or-more (any alnum ?_))) eow) (1 font-lock-type-face) (2 font-lock-variable-name-face)) ;; add table inet my_table { ... } ;; table inet my_table { ... } (,(rx bol (group (or "table" ; style #1 "add table")) ; style #2 " " ;; This is parser_bison.y:family_spec (group (or "ip" "ip6" "inet" "arp" "bridge" "netdev")) " " (group (one-or-more (any alnum ?_))) eow) (1 font-lock-type-face) (2 font-lock-constant-face) (3 font-lock-variable-name-face)) ;; chain my_chain { ;; set my_set { ;; map my_map { (,(rx bol (one-or-more blank) (group (or "chain" "set" "map")) " " (group (one-or-more (any alnum ?_)))) (1 font-lock-type-face) (2 font-lock-variable-name-face)) ;; add chain inet my_table my_chain { ... } ;; add set inet my_table my_set { ... } ;; add map inet my_table my_map { ... } ;; add rule inet my_table my_chain ... ;; add element inet my_table my_set { ... } ;; add element inet my_table my_map { ... } (,(rx bol (group "add " (or "chain" "set" "map" "rule" "element")) " " (group (or "ip" "ip6" "inet" "arp" "bridge" "netdev")) " " (group (one-or-more (any alnum ?_))) " " (group (one-or-more (any alnum ?_))) eow) (1 font-lock-type-face) (2 font-lock-constant-face) (3 font-lock-variable-name-face) (4 font-lock-variable-name-face)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; REMAINING RULES NOT ANCHORED AT BEGINNING-OF-LINE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; << chain specification >> ;; { type filter hook input priority filter; } (,(rx bow (group "type") " " (group (or "filter" "nat" "route")) " " (group "hook") " " (group (or "prerouting" "input" "forward" "output" "postrouting" "ingress" "dormant")) " " (group "priority") " " (group (or (and (opt "-") (one-or-more digit)) "raw" "mangle" "dstnat" "filter" "security" "srcnat" "dstnat" "filter" "out" "srcnat")) eow) (1 font-lock-type-face) (3 font-lock-type-face) (5 font-lock-type-face) (2 font-lock-constant-face) (4 font-lock-constant-face) (6 font-lock-constant-face)) ;; << Table 8. Set specifications >> ;; type x # set ;; type x : y # map ;; flags x , y , z # set/map ;; timeout 60s # set ;; gc-interval 12s # set ;; elements =3D { ... } # set/map ;; size 1000 # set/map ;; auto-merge # set (,(rx bow (group "type") " " (group (or "ipv4_addr" "ipv6_addr" "ether_addr" "inet_proto" "ine= t_service" "mark")) (optional " : " (group (or "ipv4_addr" "ipv6_addr" "ether_addr" "inet_proto" "in= et_service" "mark" "counter" "quota"))) eow) (1 font-lock-type-face) (2 font-lock-constant-face)) (,(rx bow (group "flags") " " (group (or "constant" "dynamic" "interval" "timeout") (zero-or-more ", " (or "constant" "dynamic" "interval" "timeout"))) eow) (1 font-lock-type-face) (2 font-lock-constant-face)) (,(rx bow (group (or "timeout" "gc-interval")) " " (group ; copied from scanner.l (optional (one-or-more digit) "d") (optional (one-or-more digit) "h") (optional (one-or-more digit) "m") (optional (one-or-more digit) "s") (optional (one-or-more digit) "ms")) eow) (1 font-lock-type-face) (2 font-lock-string-face)) (,(rx bow (group "size") " " (group (one-or-more digit)) eow) (1 font-lock-type-face) (2 font-lock-string-face)) (,(rx bow "auto-merge" eow) . font-lock-type-face) (,(rx bow (group "elements") " =3D " eow) (1 font-lock-type-face)) ;; policy accept ;; policy drop (,(rx (group "policy") " " (group (or "accept" "drop"))) (1 font-lock-type-face) (2 font-lock-function-name-face)) ;; $variable ;; @array (,(rx (or "@" "$") alpha (zero-or-more (any alnum ?_))) . font-lock-variable-name-face) ;; Simplified because scanner.l is INSANE for IPv6. ;; 1234 (e.g. port number) ;; 1.2.3.4 ;; ::1 (,(rx bow (or ;; IPv4 address (optional CIDR) (and digit (zero-or-more (any digit ".")) digit (optional "/" (one-or-more digit))) ;; IPv6 address (optional CIDR) ;; Oops, this was matching "add"! ;; WOW THIS IS REALLY REALLY HARD! (and (zero-or-more (or (and (repeat 1 4 hex-digit) ":") "::")) (repeat 1 4 hex-digit) (optional "/" (one-or-more digit))) ;; Bare digits. ;; Has to be after IPv4 address, or IPv4 address loses. ;; (or (one-or-more digit)) ) eow) . font-lock-string-face) ;; parser_bison.y:family_spec_explicit ;; (,(rx bow (or "ip" "ip6" "inet" "arp" "bridge" "netdev") eow) ;; . font-lock-constant-face) ;; parser_bison.y:verdict_expr (,(rx bow (or "accept" "drop" "continue" "return") eow) . font-lock-function-name-face) (,(rx bow (group (or "jump" "goto")) " " (group (one-or-more (any alnum ?_)))) ; chain_expr (1 font-lock-function-name-face) (2 font-lock-variable-name-face)) )) ;;; Based on equivalent for other editors: ;;; * /usr/share/nano/nftables.nanorc ;;; * https://github.com/nfnty/vim-nftables ;;;###autoload (define-derived-mode nftables-mode prog-mode "nft" "FIXME docstring" (setq-local comment-start "#") (setq-local font-lock-defaults `(nftables-font-lock-keywords nil nil)) ;; ;; make "table my_table {" result in indents on the next line. ;; (setq-local electric-indent-chars ?\}) (setq-local indent-line-function #'nftables-indent-line) (setq-local tab-width 4)) ;;; Stolen from parsnip's (bradyt's) dart-mode. ;;; https://github.com/bradyt/dart-mode/blob/199709f7/dart-mode.el#L315 (defun nftables-indent-line () (let (old-point) (save-excursion (back-to-indentation) (let ((depth (car (syntax-ppss)))) (if (=3D ?\) (char-syntax (char-after))) (setq depth (1- depth))) (indent-line-to (* depth tab-width))) (setq old-point (point))) (when (< (point) old-point) (back-to-indentation)))) (add-to-list 'auto-mode-alist '("\\.nft\\(?:ables\\)?\\'" . nftables-mode)) (add-to-list 'auto-mode-alist '("/etc/nftables.conf" . nftables-mode)) (add-to-list 'interpreter-mode-alist '("nft\\(?:ables\\)?" . nftables-mode)) (provide 'nftables-mode) --=-=-= Content-Type: text/plain Content-Disposition: inline; filename=nftables-mode-test.nft #!/sbin/nft -f $foo @bar ###################################################################### # EXAMPLE RULESETS FROM https://wiki.nftables.org/ ###################################################################### table inet filter { chain input { type filter hook input priority 0; # accept any localhost traffic iif lo accept # accept traffic originated from us ct state established,related accept # accept neighbour discovery otherwise connectivity breaks ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, echo-request, nd-router-advert, nd-neighbor-advert } accept # count and drop any other traffic counter drop } } # FLOWTABLE EXAMPLE (NOTE: 0.9 flowtable, not the unrelated 0.8 thing also called flowtable) table inet x { flowtable f { hook ingress priority 0 devices = { eth0, eth1 }; } chain y { type filter hook forward priority 0; policy accept; ip protocol tcp flow offload @f counter packets 0 bytes 0 } } # UPDATING A SET FROM ANOTHER CHAIN (a la iptables -m recent?) table ip filter { set myset { type inet_service flags timeout elements = { http expires 9s } } chain input { type filter hook input priority 0; policy accept; update @myset { tcp dport timeout 1m } } } table ip filter { set myset { type ipv4_addr elements = { 1.1.1.1 } } chain input { type filter hook input priority 0; policy accept; add @myset { ip saddr } } } add rule bridge filter forward ether type ip tcp dport 22 accept add rule bridge filter forward ether type arp accept add rule inet nat prerouting dnat tcp dport map { 1000 : 1.1.1.1, 2000 : 2.2.2.2, 3000 : 3.3.3.3} : tcp dport map { 1000 : 1234, 2000 : 2345, 3000 : 3456 } add rule inet nat postrouting snat ip saddr map { 192.168.1.1 : 1.1.1.1, 192.168.2.2 : 2.2.2.2, 192.168.3.3 : 3.3.3.3 } flush ruleset include "./defines.nft" table inet filter { chain global { ct state established,related accept ct state invalid drop ip protocol icmp accept ip6 nexthdr icmpv6 accept udp dport 53 accept } include "./inet-filter-sets.nft" include "./inet-filter-forward.nft" include "./inet-filter-local.nft" } # interfaces define nic_inet = bond0 define nic_dmz = bond1 define nic_lan = bond2 # network ranks define net_ipv4_dmz = 10.0.1.0/24 define net_ipv6_dmz = fe00:1::/64 define net_ipv4_lan = 10.0.2.0/24 define net_ipv6_lan = fe00:2::/64 # some machines define server1_ipv4 = 10.0.1.2 define server1_ipv6 = fe00:1::2 define workstation1_ipv4 = 10.0.2.2 define workstation1_ipv6 = fe00:2::2 set myset_ipv4 { type ipv4_addr; elements = { $server1_ipv4 , $workstation1_ipv4 } } set myset_ipv6 { type ipv6_addr; elements = { $server1_ipv6 , $workstation1_ipv6 } } chain dmz_in { # your rules for traffic to your dmz servers ip saddr @myset_ipv4 ip6 saddr @myset_ipv6 } chain dmz_out { # your rules for traffic from the dmz to internet } chain lan_in { # your rules for traffic to your LAN nodes } chain lan_out { # your rules for traffic from the LAN to the internet } chain forward { type filter hook forward priority 0; policy drop; jump global oifname vmap { $nic_dmz : jump dmz_in , $nic_lan : jump lan_in } oifname $nic_inet iifname vmap { $nic_dmz : jump dmz_out , $nic_lan : jump lan_out } } chain input { type filter hook input priority 0 ; policy drop; jump global # your rules for traffic to the firewall here } chain output { type filter hook output priority 0 ; policy drop; jump global # your rules for traffic originated from the firewall itself here } flush ruleset table ip Inet4 { set Knocked_1 { type ipv4_addr flags timeout, interval timeout 10s gc-interval 4s } set Knocked_2 { type ipv4_addr flags timeout timeout 10s gc-interval 4s } set Knocked_3 { type ipv4_addr flags timeout timeout 10s gc-interval 4s } set Knocked_4 { type ipv4_addr flags timeout timeout 2m gc-interval 4s } chain Knock_1 { set add ip saddr @Knocked_1 } chain Unknock_1 { set update ip saddr timeout 0s @Knocked_1 } chain Knock_2 { set update ip saddr timeout 0s @Knocked_1 set add ip saddr @Knocked_2 } chain Unknock_2 { set update ip saddr timeout 0s @Knocked_2 } chain Knock_3 { set update ip saddr timeout 0s @Knocked_2 set add ip saddr @Knocked_3 } chain Unknock_3 { set update ip saddr timeout 0s @Knocked_3 } chain Knock_4 { set update ip saddr timeout 0s @Knocked_3 set add ip saddr @Knocked_4 log prefix "Port-Knock accepted: " } chain RefreshKnock { set update ip saddr timeout 2m @Knocked_4 } chain PortKnock { ct state new ip saddr @Knocked_4 goto RefreshKnock tcp dport 456 ct state new ip saddr @Knocked_3 goto Knock_4 tcp dport 345 ct state new ip saddr @Knocked_3 return ip saddr @Knocked_3 ct state new goto Unknock_3 tcp dport 345 ct state new ip saddr @Knocked_2 goto Knock_3 tcp dport 234 ct state new ip saddr @Knocked_2 return ip saddr @Knocked_2 ct state new goto Unknock_2 tcp dport 234 ct state new ip saddr @Knocked_1 goto Knock_2 tcp dport 123 ct state new ip saddr @Knocked_1 return ip saddr @Knocked_1 ct state new goto Unknock_1 tcp dport 123 ct state new goto Knock_1 } chain FilterIn { type filter hook input priority 0 policy drop # allow established/related connections ct state established,related accept # early drop of invalid connections ct state invalid drop # allow from loopback meta iif lo accept # allow icmp ip protocol icmp accept # port-knocking jump PortKnock # misc. filtering # ... } chain FilterOut { type filter hook output priority 0 policy accept } } table ip filter { map subnet_map { type ipv4_addr : verdict flags interval elements = { 10.20.255.48/29 : goto group_114, 10.20.255.88/29 : goto group_114, 10.20.255.128/29 : goto group_114 } } set priority_set { type ipv4_addr flags interval elements = { 8.8.8.8, 8.8.4.4 } } map group_114 { type ipv4_addr : classid flags interval elements = { 10.20.255.50 : 1:ffd8, 10.20.255.90 : 1:ffd5, 10.20.255.130 : 1:ffd2 } } map group_114_prio { type ipv4_addr : classid flags interval elements = { 10.20.255.50 : 1:ffd9, 10.20.255.90 : 1:ffd6, 10.20.255.130 : 1:ffd3 } } chain forward { type filter hook forward priority filter; policy accept; meta priority none ip daddr vmap @subnet_map counter packets 0 bytes 0 meta priority none ip saddr vmap @subnet_map counter packets 0 bytes 0 ip daddr 192.168.0.0/16 meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "total - " ip saddr 192.168.0.0/16 meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "total - " ip daddr 10.0.0.0/8 meta priority none meta priority set 1:ffff counter packets 38931 bytes 2926076 log prefix "total - " ip saddr 10.0.0.0/8 meta priority none meta priority set 1:ffff counter packets 14 bytes 1064 log prefix "total - " meta priority none meta priority set 1:2 counter packets 0 bytes 0 log prefix "non_shaped - " } chain input { type filter hook input priority filter; policy accept; meta priority none meta priority set 1:2 counter packets 419381 bytes 45041195 } chain output { type filter hook output priority filter; policy accept; meta priority none meta priority set 1:2 counter packets 507779 bytes 51809859 } chain group_114 { meta priority none ip saddr @priority_set meta priority set ip daddr map @group_114_prio counter packets 0 bytes 0 meta priority none ip daddr @priority_set meta priority set ip saddr map @group_114_prio counter packets 0 bytes 0 meta priority none meta priority set ip daddr map @group_114 counter packets 0 bytes 0 meta priority none meta priority set ip saddr map @group_114 counter packets 0 bytes 0 meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "group_114 - " } } add table ip filter add chain ip filter forward { type filter hook forward priority 0; policy accept; } add map ip filter subnet_map { type ipv4_addr : verdict; flags interval; } add set ip filter priority_set { type ipv4_addr; flags interval; } add element ip filter priority_set {8.8.8.8 } add element ip filter priority_set {8.8.4.4 } add rule ip filter forward meta priority 0 ip daddr vmap @subnet_map counter add rule ip filter forward meta priority 0 ip saddr vmap @subnet_map counter add rule ip filter forward ip daddr 192.168.0.0/16 meta priority 0 meta priority set "1:0xffff" counter log prefix "total - " add rule ip filter forward ip saddr 192.168.0.0/16 meta priority 0 meta priority set "1:0xffff" counter log prefix "total - " add rule ip filter forward ip daddr 10.0.0.0/8 meta priority 0 meta priority set "1:0xffff" counter log prefix "total - " add rule ip filter forward ip saddr 10.0.0.0/8 meta priority 0 meta priority set "1:0xffff" counter log prefix "total - " add rule ip filter forward meta priority 0 meta priority set "1:0x2" counter log prefix "non_shaped - " add chain ip filter input { type filter hook input priority 0; policy accept; } add rule ip filter input meta priority 0 meta priority set "1:0x2" counter add chain ip filter output { type filter hook output priority 0; policy accept; } add rule ip filter output meta priority 0 meta priority set "1:0x2" counter add chain ip filter group_114 add map ip filter group_114 { type ipv4_addr : classid; flags interval; } add map ip filter group_114_prio { type ipv4_addr : classid; flags interval; } add rule ip filter group_114 meta priority 0 ip saddr @priority_set meta priority set ip daddr map @group_114_prio counter add rule ip filter group_114 meta priority 0 ip daddr @priority_set meta priority set ip saddr map @group_114_prio counter add rule ip filter group_114 meta priority 0 meta priority set ip daddr map @group_114 counter add rule ip filter group_114 meta priority 0 meta priority set ip saddr map @group_114 counter add rule ip filter group_114 meta priority 0 meta priority set "1:0xffff" counter log prefix "group_114 - " add element ip filter subnet_map { 10.20.255.48/29 : goto group_114 } add element ip filter subnet_map { 10.20.255.88/29 : goto group_114 } add element ip filter subnet_map { 10.20.255.128/29 : goto group_114 } add element ip filter group_114_prio { 10.20.255.50/32 : "1:0xffd9" } add element ip filter group_114 { 10.20.255.50/32 : "1:0xffd8" } add element ip filter group_114_prio { 10.20.255.90/32 : "1:0xffd6" } add element ip filter group_114 { 10.20.255.90/32 : "1:0xffd5" } add element ip filter group_114_prio { 10.20.255.130/32 : "1:0xffd3" } add element ip filter group_114 { 10.20.255.130/32 : "1:0xffd2" } # packet passing through server chain forward { # hook forward does the magic, not the name of the chain # priority filter can be used in newer versions of nftables > 0.9.0 type filter hook forward priority filter; policy accept; # packet is matched against subnet_map - it is verdict map = 10.20.255.48/29 : goto group_114 meta priority none ip daddr vmap @subnet_map counter packets 0 bytes 0 # packet's dst address is looked up # it contains decision on where to send the packet for further processing when matched - chain group_114 meta priority none ip saddr vmap @subnet_map counter packets 0 bytes 0 # packet's src address is looked up # private destination subnet without set priority is set to 1:0xffff ip daddr 192.168.0.0/16 meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "total - " # private source subnet without set priority is set to 1:0xffff ip saddr 192.168.0.0/16 meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "total - " ip daddr 10.0.0.0/8 meta priority none meta priority set 1:ffff counter packets 38931 bytes 2926076 log prefix "total - " ip saddr 10.0.0.0/8 meta priority none meta priority set 1:ffff counter packets 14 bytes 1064 log prefix "total - " # rest of traffic is sent to separate tc class object meta priority none meta priority set 1:2 counter packets 0 bytes 0 log prefix "non_shaped - " } # subnet_map redirected the packet here chain group_114 { # packet's source / destination address is matched against set named priority_set and it can't contain any priority set meta priority none ip saddr @priority_set meta priority set ip daddr map @group_114_prio counter packets 0 bytes 0 # when matched it compares destination address of the packet against group_114_prio map and sets the priority accordingly - 1:ffd9 meta priority none ip daddr @priority_set meta priority set ip saddr map @group_114_prio counter packets 0 bytes 0 # packets heading / originating to / from non prioritized addresses are matched in next steps meta priority none meta priority set ip daddr map @group_114 counter packets 0 bytes 0 meta priority none meta priority set ip saddr map @group_114 counter packets 0 bytes 0 # unknown traffic is set to untracked object - 1:0xffff meta priority none meta priority set 1:ffff counter packets 0 bytes 0 log prefix "group_114 - " } map group_114 { type ipv4_addr : classid flags interval elements = { 10.20.255.50 : 1:ffd8, 10.20.255.90 : 1:ffd5, 10.20.255.130 : 1:ffd2 } } map group_114_prio { type ipv4_addr : classid flags interval elements = { 10.20.255.50 : 1:ffd9, 10.20.255.90 : 1:ffd6, 10.20.255.130 : 1:ffd3 } } ###################################################################### # EXAMPLE STATEMENTS FROM THE MANPAGE ###################################################################### list ruleset flush ruleset list ruleset ip flush ruleset ip6 table my_table { ... } table arp my_table { ... } add table my_table { ... } add table arp my_table { ... } create table my_table { ... } create table arp my_table { ... } delete table my_table delete table arp my_table list table my_table list table arp my_table flush table my_table flush table arp my_table list tables delete table handle 1234 delete table arp handle 1234 create table inet mytable add chain inet mytable myin { type filter hook input priority 0; } add rule inet mytable myin counter add table inet mytable { flags dormant; } add table inet mytable chain my_table my_chain { type filter hook input priority filter } # {add | create} chain [family] table chain [{ type type hook hook [device device] priority priority ; [policy policy ;] }] # {delete | list | flush} chain [family] table chain # list chains # delete chain [family] table handle handle # rename chain [family] table chain newname add rule filter output ip daddr 192.168.0.0/24 accept # 'ip filter' is assumed # same command, slightly more verbose add rule ip filter output ip daddr 192.168.0.0/24 accept # nft -a list ruleset table inet filter { chain input { type filter hook input priority 0; policy accept; ct state established,related accept # handle 4 ip saddr 10.1.1.1 tcp dport ssh accept # handle 5 ... } } # delete the rule with handle 5 # nft delete rule inet filter input handle 5 add rule inet filter input ip saddr { 10.0.0.0/8, 192.168.0.0/16 } tcp dport { 22, 443 } accept add rule inet filter input ip saddr @allowed_hosts tcp dport @allowed_ports accept # add set [family] table set { type type ; [flags flags ;] [timeout timeout ;] [gc-interval gc-interval ;] [elements = { element[, ...] } ;] [size size ;] [policy policy ;] [auto-merge ;] } # {delete | list | flush} set [family] table set # list sets # delete set [family] table handle handle # {add | delete} element [family] table set { element[, ...] } # add map [family] table map { type type [flags flags ;] [elements = { element[, ...] } ;] [size size ;] [policy policy ;] } # {delete | list | flush} map [family] table map # list maps # {add | delete} element [family] table map { elements = { element[, ...] } ; } # {add | create} flowtable [family] table flowtable { hook hook priority priority ; devices = { device[, ...] } ; } # {delete | list} flowtable [family] table flowtable # {add | delete | list | reset} type [family] table object # delete type [family] table handle handle # list counters # list quotas # ct helper helper { type type protocol protocol ; [l3proto family ;] } table inet myhelpers { ct helper ftp-standard { type "ftp" protocol tcp } chain prerouting { type filter hook prerouting priority 0; tcp dport 21 ct helper set "ftp-standard" } } # ct timeout name { protocol protocol ; policy = { state: value [, ...] } ; [l3proto family ;] } table ip filter { ct timeout customtimeout { protocol tcp; l3proto ip policy = { established: 120, close: 20 } } chain output { type filter hook output priority filter; policy accept; ct timeout set "customtimeout" } } # counter [packets bytes] # quota [over | until] [used] # describe expression describe tcp flags # Interface name filter input iifname eth0 # Weird interface name filter input iifname "(eth0)" # Ethernet destination MAC address filter input ether daddr 20:c9:d0:43:12:d9 # dotted decimal notation filter output ip daddr 127.0.0.1 # host name filter output ip daddr localhost # abbreviated loopback address filter output ip6 daddr ::1 # without [] the port number (22) would be parsed as part of the # ipv6 address ip6 nat prerouting tcp dport 2222 dnat to [1ce::d0]:22 # match if route exists filter input fib daddr . iif oif exists # match only non-fragmented packets in IPv6 traffic filter input exthdr frag missing # match if TCP timestamp option is present filter input tcp option timestamp exists # match ping packets filter output icmp type { echo-request, echo-reply } # match ICMPv6 ping packets filter output icmpv6 type { echo-request, echo-reply } # meta {length | nfproto | l4proto | protocol | priority} # [meta] {mark | iif | iifname | iiftype | oif | oifname | oiftype | skuid | skgid | nftrace | rtclassid | ibrname | obrname | pkttype | cpu | iifgroup | oifgroup | cgroup | random | ipsec | iifkind | oifkind} filter input meta iif "foo" # qualified meta expression filter output meta oif eth0 # unqualified meta expression filter output oif eth0 # packet was subject to ipsec processing raw prerouting meta ipsec exists accept # socket {transparent | mark} # Mark packets that correspond to a transparent socket table inet x { chain y { type filter hook prerouting priority -150; policy accept; socket transparent 1 mark set 0x00000001 accept } } # Trace packets that corresponds to a socket with a mark value of 15 table inet x { chain y { type filter hook prerouting priority -150; policy accept; socket mark 0x0000000f nftrace set 1 } } # Set packet mark to socket mark table inet x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport 8080 mark set socket mark } } # osf [ttl {loose | skip}] {name | version} # Accept packets that match the "Linux" OS genre signature without comparing TTL. table inet x { chain y { type filter hook input priority 0; policy accept; osf ttl skip name "Linux" } } # fib {saddr | daddr | mark | iif | oif} [. ...] {oif | oifname | type} # drop packets without a reverse path filter prerouting fib saddr . iif oif missing drop # drop packets to address not configured on ininterface filter prerouting fib daddr . iif type != { local, broadcast, multicast } drop # perform lookup in a specific 'blackhole' table (0xdead, needs ip appropriate ip rule) filter prerouting meta mark set 0xdead fib daddr . mark type vmap { blackhole : drop, prohibit : jump prohibited, unreachable : drop } # rt [ip | ip6] {classid | nexthop | mtu | ipsec} # IP family independent rt expression filter output rt classid 10 filter output rt ipsec missing # IP family dependent rt expressions ip filter output rt nexthop 192.168.0.1 ip6 filter output rt nexthop fd00::1 inet filter output rt ip nexthop 192.168.0.1 inet filter output rt ip6 nexthop fd00::1 # ipsec {in | out} [ spnum NUM ] {reqid | spi} # ipsec {in | out} [ spnum NUM ] {ip | ip6} {saddr | daddr} # ether {daddr | saddr | type} # vlan {id | cfi | pcp | type} # arp {htype | ptype | hlen | plen | operation | saddr { ip | ether } | daddr { ip | ether } # ip {version | hdrlength | dscp | ecn | length | id | frag-off | ttl | protocol | checksum | saddr | daddr } # icmp {type | code | checksum | id | sequence | gateway | mtu} # igmp {type | mrt | checksum | group} # ip6 {version | dscp | ecn | flowlabel | length | nexthdr | hoplimit | saddr | daddr} # matching if first extension header indicates a fragment ip6 nexthdr ipv6-frag # icmpv6 {type | code | checksum | parameter-problem | packet-too-big | id | sequence | max-delay} # tcp {sport | dport | sequence | ackseq | doff | reserved | flags | window | checksum | urgptr} # udp {sport | dport | length | checksum} # udplite {sport | dport | checksum} # sctp {sport | dport | vtag | checksum} # dccp {sport | dport} # ah {nexthdr | hdrlength | reserved | spi | sequence} # esp {spi | sequence} # comp {nexthdr | flags | cpi} # @base,offset,length # Matching destination port of both UDP and TCP. inet filter input meta l4proto {tcp, udp} @th,16,16 { 53, 80 } # Rewrite arp packet target hardware address if target protocol address # matches a given address. input meta iifname enp2s0 arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 accept # hbh {nexthdr | hdrlength} # frag {nexthdr | frag-off | more-fragments | id} # rt {nexthdr | hdrlength | type | seg-left} # dst {nexthdr | hdrlength} # mh {nexthdr | hdrlength | checksum | type} # srh {flags | tag | sid | seg-left} # tcp option {eol | noop | maxseg | window | sack-permitted | sack | sack0 | sack1 | sack2 | sack3 | timestamp} tcp_option_field # exthdr {hbh | frag | rt | dst | mh} # tcp option {eol | noop | maxseg | window | sack-permitted | sack | sack0 | sack1 | sack2 | sack3 | timestamp} filter input tcp option sack-permitted kind 1 counter ip6 filter input frag more-fragments 1 counter # ct {state | direction | status | mark | expiration | helper | label} # ct [original | reply] {l3proto | protocol | bytes | packets | avgpkt | zone} # ct {original | reply} {proto-src | proto-dst} # ct {original | reply} {ip | ip6} {saddr | daddr} # restrict the number of parallel connections to a server. filter input tcp dport 22 meter test { ip saddr ct count over 2 } reject # {accept | drop | queue | continue | return} # {jump | goto} chain # process packets from eth0 and the internal network in from_lan # chain, drop all packets from eth0 with different source addresses. filter input iif eth0 ip saddr 192.168.0.0/24 jump from_lan filter input iif eth0 drop # payload_expression set value # route some packets instead of bridging. # redirect tcp:http from 192.160.0.0/16 to local machine for routing instead of bridging # assumes 00:11:22:33:44:55 is local MAC address. bridge input meta iif eth0 ip saddr 192.168.0.0/16 tcp dport 80 meta pkttype set unicast ether daddr set 00:11:22:33:44:55 # Set IPv4 DSCP header field. ip forward ip dscp set 42 # extension_header_expression set value tcp flags syn tcp option maxseg size set 1360 # set a size based on route information: tcp flags syn tcp option maxseg size set rt mtu # log the UID which generated the packet and ip options ip filter output log flags skuid flags ip options # log the tcp sequence numbers and tcp options from the TCP packet ip filter output log flags tcp sequence,options # enable all supported log flags ip6 filter output log flags all # counter packets number bytes number # counter { packets number | bytes number } # save packet nfmark in conntrack. ct mark set meta mark # set zone mapped via interface. table inet raw { chain prerouting { type filter hook prerouting priority -300; ct zone set iif map { "eth1" : 1, "veth1" : 2 } } chain output { type filter hook output priority -300; ct zone set oif map { "eth1" : 1, "veth1" : 2 } } } # restrict events reported by ctnetlink. ct event set new,related,destroy # meta {mark | priority | pkttype | nftrace} set value # limit rate [over] packet_number / TIME_UNIT [burst packet_number packets] # limit rate [over] byte_number BYTE_UNIT / TIME_UNIT [burst byte_number BYTE_UNIT] # TIME_UNIT := second | minute | hour | day # BYTE_UNIT := bytes | kbytes | mbytes # create a suitable table/chain setup for all further examples add table nat add chain nat prerouting { type nat hook prerouting priority 0; } add chain nat postrouting { type nat hook postrouting priority 100; } # translate source addresses of all packets leaving via eth0 to address 1.2.3.4 add rule nat postrouting oif eth0 snat to 1.2.3.4 # redirect all traffic entering via eth0 to destination address 192.168.1.120 add rule nat prerouting iif eth0 dnat to 192.168.1.120 # translate source addresses of all packets leaving via eth0 to whatever # locally generated packets would use as source to reach the same destination add rule nat postrouting oif eth0 masquerade # redirect incoming TCP traffic for port 22 to port 2222 add rule nat prerouting tcp dport 22 redirect to :2222 # inet family: # handle ip dnat: add rule inet nat prerouting dnat ip to 10.0.2.99 # handle ip6 dnat: add rule inet nat prerouting dnat ip6 to fe80::dead # this masquerades both ipv4 and ipv6: add rule inet nat postrouting meta oif ppp0 masquerade # Example ruleset for tproxy statement. table ip x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to 1.1.1.1 udp dport ssh tproxy to :2222 } } table ip6 x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to [dead::beef] udp dport ssh tproxy to :2222 } } table inet x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport 321 tproxy to :ssh tcp dport 99 tproxy ip to 1.1.1.1:999 udp dport 155 tproxy ip6 to [dead::beef]:smux } } flow add @flowtable # send to machine with ip address 10.2.3.4 on eth0 ip filter forward dup to 10.2.3.4 device "eth0" # copy raw frame to another interface netdetv ingress dup to "eth0" dup to "eth0" # combine with map dst addr to gateways dup to ip daddr map { 192.168.7.1 : "eth0", 192.168.7.2 : "eth1" } fwd to device # Example for simple blacklist. # declare a set, bound to table "filter", in family "ip". Timeout and size are mandatory because we will add elements from packet path. add set ip filter blackhole "{ type ipv4_addr; flags timeout; size 65536; }" # whitelist internal interface. add rule ip filter input meta iifname "internal" accept # drop packets coming from blacklisted ip addresses. add rule ip filter input ip saddr @blackhole counter drop # add source ip addresses to the blacklist if more than 10 tcp connection requests occurred per second and ip address. # entries will timeout after one minute, after which they might be re-added if limit condition persists. add rule ip filter input tcp flags syn tcp dport ssh meter flood size 128000 { ip saddr timeout 10s limit rate over 10/second} add @blackhole { ip saddr timeout 1m } drop # inspect state of the rate limit meter: list meter ip filter flood # inspect content of blackhole: list set ip filter blackhole # manually add two addresses to the set: add element filter blackhole { 10.2.3.4, 10.23.1.42 } # select DNAT target based on TCP dport: # connections to port 80 are redirected to 192.168.1.100, # connections to port 8888 are redirected to 192.168.1.101 add rule ip nat prerouting dnat tcp dport map { 80 : 192.168.1.100, 8888 : 192.168.1.101 } # source address based SNAT: # packets from net 192.168.1.0/24 will appear as originating from 10.0.0.1, # packets from net 192.168.2.0/24 will appear as originating from 10.0.0.2 add rule ip nat postrouting snat to ip saddr map { 192.168.1.0/24 : 10.0.0.1, 192.168.2.0/24 : 10.0.0.2 } # jump to different chains depending on layer 4 protocol type: add rule ip filter input ip protocol vmap { tcp : jump tcp-chain, udp : jump udp-chain , icmp : jump icmp-chain } monitor ruleset --=-=-=--