pax_global_header00006660000000000000000000000064150654720700014520gustar00rootroot0000000000000052 comment=c532cc902a402bbaf88e90a972d078649425f34b foomuuri-0.29/000077500000000000000000000000001506547207000133175ustar00rootroot00000000000000foomuuri-0.29/.gitignore000066400000000000000000000001041506547207000153020ustar00rootroot00000000000000foomuuri-*.tar.gz test/*/*.fw test/*/iplist-cache.json test/*/zone foomuuri-0.29/CHANGELOG.md000066400000000000000000000256201506547207000151350ustar00rootroot00000000000000# ChangeLog ## 0.29 (2025-09-26) * Add `snat_prefix` and `dnat_prefix` statements for IPv6-to-IPv6 Network Prefix Translation (NPTv6). * Deprecate unneeded `to` after `snat` and `dnat` statements. It's quietly ignored. * Add `tproxy` matcher for transparent proxying. * Add `mss pmtu` to calculate the MTU in runtime based on what the routing cache has observed via Path MTU Discovery. * Fix `mss` to apply to IPv4 and IPv6. Previously it was only for IPv4. * Add `input` section. It processes incoming packets with destination `localhost`. * Add `try-reload` command to safely test new config. It loads new config, asks confirmation from user to keep it, and reverts back to old config if user didn't reply within 15 seconds. * Foomuuri Monitor supports and prefers fping's `--squiet=s` mode. It works correctly even if the interface is down. fping command option `--interval=ms` is automatically converted to `--squiet=s` if possible. * Add `command_down_interval` and `down_interval` to Foomuuri Monitor. Foomuuri Monitor will run that external command in regular intervals if network connectivity is still down. * Default log prefix can be configured with `foomuuri { log_prefix "..." }`. * Variables `$(szone)`, `$(dzone)` and `$(statement)` can be used in log prefix. * More text to default log prefix can be added with `log + "mytext"`. * Multiple interfaces can be specified to `iifname` and `oifname` matchers. Negative matching also works. * Use human readable output format in `list counter` command. * Add `prometheus`, `prometheus-*` and `alertmanager` macros to default services. * Add `--fork` command line option to fork as a background daemon process. This applies to `monitor` and `dbus` only. * Add `--syslog` command line option to enable syslog logging. * Add bash-completion (requires v2.12 or newer) script. ## 0.28 (2025-04-15) * Merge `iplist` and `resolve` sections to unified `iplist`. Old config will work as is, but updating it to new `iplist` format is recommended: simply rename `resolve {}` to `iplist {}` and check timeout and refresh options. * Add `url_timeout=10d`, `url_refresh=1d`, `dns_timeout=24h` and `dns_refresh=15m` options to `iplist` section to specify expiry timeout and refresh interval for URLs (HTTP or file) and resolved hostnames. * Old `timeout` and `refresh` options are deprecated. They set both `url_XXX` and `dns_XXX` values. * Downloaded `iplist` content can be filtered: * `|shell:/path/to/command` pipe it via external command. * `|json:filter` use external `jq` command to parse it as JSON data. * `|html:XPath` parse it as HTML data. * `|xml:XPath` parse it as XML data. * `|missing-ok` don't print warning if URL download or DNS resolve fails. * Improve `template foo` handling to support matchers and everything else that macros support. * Add `prerouting filter raw` and similar sections to allow specifying chain type and hook priority. * Add `notrack` statement to be used in prerouting section to mark packet to not be added to conntrack. * Add `10 mbytes/second` per byte support to rate limits (`global_rate` etc). * Add `over` support to rate limits to be used with `drop` statement. * Add `dscp` matcher to match packet's DSCP value. * Add `bgp` macro to default services. ## 0.27 (2025-01-28) * BREAKING CHANGES: * Previously `mark_set` was a statement. Now it's a matcher and rule's default statement `accept` is used if not specified. Usually `accept` is what you want to use. To keep previous behavior use `mark_set XX continue`. * `mark_save` and `mark_restore` are removed. They are automatically added when needed. * Multi-ISP example in GitHub wiki is updated to match above changes. * Add `priority_set` to set packet's traffic control class id. * Add `priority_match` to check packet's traffic control class id. * Add `nbd`, `pxe`, `salt`, and `tor` related macros to default services. * Add `--quiet` command line option to suppress warnings. * Print warning in `foomuuri check` if macro is overwritten. * Add more checks for invalid rules, like `ssh saddr` without address. * Add filters to `foomuuri list`: * `foomuuri list macro http https`: specified macros. * `foomuuri list macro 80 443`: macros that include value 80 or 443. * `foomuuri list counter traffic_in traffic_out`: specified counters. * Fix: `foomuuri iplist refresh` actually refreshes it now, no need for `--force`. Option `--soft` checks for next refresh time. ## 0.26 (2024-11-13) * Add `iplist flush name` command to delete all entries in iplist. * Add support for `-macro`, `macro/24`, `[macro]:123` and `macro:123` in macro expansion. * Add support for `icmp echo-request` (and other names) in addition to `icmp 8`. Use name in default `ping` macro instead of number. * Add `--force` command line option to force iplist refresh now. * Write `foomuuri-monitor` states and statistics to a file once a minute. * Add `rtsps` macro to default services. * Fix: Add range support to `iplist list`. * Fix: `iplist add` and `iplist del` didn't work correctly on all cases. ## 0.25 (2024-10-01) * Add `status` command to show if Foomuuri is running, current zone-interface mapping. * Add `set interface eth0 zone public` command to change interface to zone. * Add `set interface eth0 zone -` command to remove interface from all zones. * Add `queue` statement. It forwards packet to userspace, used for example for IPS/IDS. * Rules in `any-public` section will be added to `public-public` too. * Improve syntax error checks for rules. * More relaxed import for external `iplist` lists: handle `;` as comment, allow overlapping IP ranges. * IP address with or without netmask can be used as `resolve` or `iplist` entry. * Add warning if `resolve` hostname doesn't resolve or whole set is empty. * Add `ipsec-nat`, `pop3s`, `gluster-client`, `gluster-management`, `amqp`, `snmptrap` and `activedirectory` macros to default services. * Automatically add final `drop log` rule to zone-zone section if it is missing for improved logging. * Fix: Allow using nft's reserved words like `inet` as interface name or uid/gid. * Fix: Don't traceback if `resolve` or `iplist` refresh takes over 60 second on `foomuuri reload`. ## 0.24 (2024-06-19) * Remove `start-or-good` command line option. Add systemd `foomuuri-boot.service` to implement same functionality safer. * Add `block` command line option to load "block all traffic" ruleset. * Add `continue` statement. Rule `saddr 192.168.1.1 counter log continue` counts and logs traffic from 192.168.1.1 and continues to next rules. * Add `time` matcher to check hour, date and weekday. * Add `mac_saddr` and `mac_daddr` matchers to match MAC address. This works only for incoming traffic. * Add `ct_status` matcher to match conntrack status, for example `dnat` or `snat`. * Add `cgroup` matcher to match cgroup id or cgroupv2 name. * Add `-conntrack` flag to rule. This rule will be outputted before conntrack. This can be used to count all specific traffic, or to accept some traffic without adding it to conntrack (for example high load DNS server). * Add `redis`, `redis-sentinel`, `vnc`, `domain-quic` (DoQ) and `domain-tls` (DoT) macros to default services. * Matcher `szone -public` in `any-localhost` (or `dzone` in `localhost-any`) section can be used to skip adding rule to `public-localhost`. * Allow using any command (`curl` example included) instead of `fping` in network connectivity monitor. * Allow `foomuuri { nft_bin nft --optimize }` to specify options. * Fix: `counter myname` didn't work on `prerouting` section. * Fix: Restart network connectivity monitor `command` if it fails to start or dies. ## 0.23 (2024-03-20) * Rework `multicast` and `broadcast` handling for incoming/outgoing traffic. This simplifies macros and results more optimal ruleset. * Allow outgoing IGMP multicast membership reports, incoming IGMP query. * Change default `log_level` to `level info flags skuid` to include UID/GID for locally generated traffic. * Add `invalid`, `rpfilter` and `smurfs` sections to accept specific traffic that is normally dropped. * Add `ospf` macro to default services. * Add support for negative set matching `saddr -@geoip drop` * Fix: Separate `counter` and `accept counter` rules. * Fix: Better output for `foomuuri check` if not running as root. * Fix: Handle D-Bus change event for `lo` interface better. ## 0.22 (2023-12-12) * Add `protocol` matcher, for example `protocol gre` accepts GRE traffic. * Add support for `localhost-localhost` section and rules. If not defined, it defaults to `accept`. * Rule's log level can be set with `log_level "level crit"`. This overrides global `foomuuri { log_level ... }` setting. * Iplist refresh interval can be configured globally and per-iplist. * Pretty output for `foomuuri iplist list` instead of raw nft output. * Fix handling "[ipv6]", "[ipv6]/mask" and "[ipv6]:port" notations. ## 0.21 (2023-10-06) * Add support for `$(shell command)` in configuration file * Add support for named counters: `https counter web_traffic` * Add support for IPv6 suffix netmask: `::192:168:1:1/-64` * Add support for conntrack count rates: `saddr_rate "ct count 4"` * Add `chain_priority` to `foomuuri` section * Add lot of misconfiguration checks * `foomuuri reload` restarts firewall and refreshes resolve+iplist * `foomuuri list counter` lists all named counters * `foomuuri iplist` subcommands manipulates and lists iplist entries * Harden `dhcp`, `dhcpv6`, `mdns` and `ssdp` macros * Fix icmp to handle matchers correctly (`ping saddr 192.168.1.1 drop`) * Fix caching failed `resolve` section lookups for reboot ## 0.20 (2023-08-15) * Multi-ISP support with internal network connectivity monitor. See wiki's best practices for example configuration. * Add `mark_set`, `mark_restore` and `mark_save` statements * Add `mark_match` matcher * Add `prerouting`, `postrouting`, `output` and `forward` sections for marks * Expand macros and support quotes in `foomuuri` section * Fix running pre/post_stop hooks on `foomuuri stop` * Fix man page section from 1 to 8, other Makefile fixes * Foomuuri is now included to Fedora, EPEL and Debian. Remove local build rules for rpm/deb packages. ## 0.19 (2023-05-19) * Add `iplist` section to import IP address lists. These can be used to import IP country lists, whitelists, blacklists, etc. * Add `hook` section to run external commands when Foomuuri starts/stops * Fix `dhcpv6-server` macro in default.services.conf * Add man page and improve documentation * Add experimental connectivity monitor which will be used for upcoming multi ISP support ## 0.18 (2023-04-18) * Add rule line validator to configuration file parser * Rename zone `fw` to `localhost` and make in configurable * Initial deb packaging * Improve documentation * Lot of internal cleanup ## 0.17 (2023-03-31) * Add `foomuuri list macro` to list all known macros * New default.services.conf entries: mqtt, secure-mqtt, zabbix * Improve documentation * Lot of internal cleanup ## 0.16 (2023-02-27) * First public release foomuuri-0.29/COPYING000066400000000000000000000432541506547207000143620ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. foomuuri-0.29/Makefile000066400000000000000000000062501506547207000147620ustar00rootroot00000000000000# Foomuuri - Multizone bidirectional nftables firewall. .PHONY: all test devel clean distclean install sysupdate release tar CURRENT_VERSION ?= $(shell grep ^VERSION src/foomuuri | awk -F\' '{ print $$2 }') # Default target is to run tests all: test # Run testsuite and check source test: $(MAKE) -C test # Generate firewall ruleset to file, used in development devel: src/foomuuri --set=etc_dir=../devel --set=share_dir=etc --set=state_dir=../devel --set=run_dir=../devel check # Delete created files clean distclean: rm -f foomuuri-*.tar.gz $(MAKE) -C test $@ # Install current source to DESTDIR BINDIR ?= /usr/sbin SYSTEMD_SYSTEM_LOCATION ?= /usr/lib/systemd/system install: mkdir -p $(DESTDIR)/etc/foomuuri/ mkdir -p $(DESTDIR)$(BINDIR)/ cp src/foomuuri $(DESTDIR)$(BINDIR)/ mkdir -p $(DESTDIR)/usr/lib/sysctl.d/ cp etc/50-foomuuri.conf $(DESTDIR)/usr/lib/sysctl.d/50-foomuuri.conf mkdir -p $(DESTDIR)/usr/share/foomuuri/ cp etc/default.services.conf $(DESTDIR)/usr/share/foomuuri/ cp etc/static.nft $(DESTDIR)/usr/share/foomuuri/ cp etc/block.fw $(DESTDIR)/usr/share/foomuuri/ mkdir -p $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri.service $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri-boot.service $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri-dbus.service $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri-iplist.service $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri-iplist.timer $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cp systemd/foomuuri-monitor.service $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ mkdir -p $(DESTDIR)/usr/lib/tmpfiles.d/ cp systemd/foomuuri.tmpfilesd $(DESTDIR)/usr/lib/tmpfiles.d/foomuuri.conf mkdir -p $(DESTDIR)/run/foomuuri/ mkdir -p $(DESTDIR)/var/lib/foomuuri/ mkdir -p $(DESTDIR)/usr/share/dbus-1/system.d/ cp systemd/fi.foobar.Foomuuri1.conf $(DESTDIR)/usr/share/dbus-1/system.d/ cp firewalld/fi.foobar.Foomuuri-FirewallD.conf $(DESTDIR)/usr/share/dbus-1/system.d/ cp firewalld/dbus-firewalld.conf $(DESTDIR)/usr/share/foomuuri/ mkdir -p $(DESTDIR)/usr/share/man/man8 cp doc/foomuuri.8 $(DESTDIR)/usr/share/man/man8/ # Install current source to local system's root sysupdate: make install DESTDIR=/ systemctl daemon-reload # Make new release release: test clean @if [ -z "$(VERSION)" ]; then \ echo "Usage: make release VERSION=x.xx"; \ echo; \ echo "Current: $(CURRENT_VERSION)"; \ exit 1; \ fi git diff --exit-code git diff --cached --exit-code sed -i -e "s@^\(## ${VERSION} .\)20..-xx-xx.@\1$(shell date +'%Y-%m-%d')\)@" CHANGELOG.md sed -i -e "s@^\(VERSION = '\).*@\1$(VERSION)'@" src/foomuuri sed -i -e "s@^\(footer: .* \).*@\1$(VERSION)@" doc/foomuuri.md sed -i -e "s@^\(date: \).*@\1$(shell date +'%b %d, %Y')@" doc/foomuuri.md make --directory=doc git diff git add CHANGELOG.md src/foomuuri doc/foomuuri.md doc/foomuuri.8 git commit --message="v$(VERSION)" git tag "v$(VERSION)" @echo @echo "== TODO ==" @echo "git push && git push --tags" @echo "GitHub release: https://github.com/FoobarOy/foomuuri/releases/new" # Build tarball locally tar: clean tar cavf foomuuri-$(CURRENT_VERSION).tar.gz --transform=s,,foomuuri-$(CURRENT_VERSION)/, --show-transformed .gitignore * foomuuri-0.29/README.md000066400000000000000000000026511506547207000146020ustar00rootroot00000000000000# Foomuuri Foomuuri is a multizone bidirectional nftables firewall. See [wiki](https://github.com/FoobarOy/foomuuri/wiki) for documentation and [host firewall](https://github.com/FoobarOy/foomuuri/wiki/Host-Firewall) or [router firewall](https://github.com/FoobarOy/foomuuri/wiki/Router-Firewall) for example configuration files. [Getting started](https://github.com/FoobarOy/foomuuri/wiki/Getting-Started) page contains quick instructions how to install Foomuuri. Help is available via [discussions](https://github.com/FoobarOy/foomuuri/discussions). ## Features * Firewall zones * Bidirectional firewalling for incoming, outgoing and forwarding traffic * Suitable for all systems from personal laptop to corporate firewalls * Rich rule language for flexible and complex rules * Predefined list of services for simple rule writing * Rule language supports macros and templates * IPv4 and IPv6 support with automatic rule splitting per protocol * SNAT, DNAT and masquerading support * Logging and counting * Rate limiting * DNS hostname lookup and IP-list support with dynamic IP address refreshing * Country database support aka geolocation * Multiple ISP support with internal network connectivity monitor * IPsec matching support * Ability to map certain traffic to separate zones * D-Bus API * FirewallD emulation for NetworkManager's zone support * Raw nftables rules can be used * Fresh design, written to use modern nftables's features foomuuri-0.29/doc/000077500000000000000000000000001506547207000140645ustar00rootroot00000000000000foomuuri-0.29/doc/Makefile000066400000000000000000000001261506547207000155230ustar00rootroot00000000000000foomuuri.8: foomuuri.md pandoc --standalone --to=man foomuuri.md --output foomuuri.8 foomuuri-0.29/doc/foomuuri-bash-completion000066400000000000000000000045351506547207000207450ustar00rootroot00000000000000# Foomuuri completion -*- shell-script -*- _comp_cmd_foomuuri() { local cur prev words cword was_split comp_args _comp_initialize -s -- "$@" || return local option_count=0 for word in "${words[@]}"; do if [[ "${word}" == --* ]]; then option_count=$((option_count+1)) fi done case $((cword-option_count)) in 1) _comp_compgen -- -W "help start stop reload try-reload status check block list iplist set" ;; 2) case ${prev} in list) zones=$(foomuuri --quiet --quiet status | sed '/^ /!d; s/^ //; s/ .*//') if [[ ${cur} =~ "-" ]]; then for szone in ${zones}; do for dzone in ${zones}; do _comp_compgen -a -- -W "${szone}-${dzone}" done done else _comp_compgen -- -W "\"macro \" \"counter \"" for zone in ${zones}; do _comp_compgen -a -- -W "${zone}-" done compopt -o nospace fi ;; iplist) _comp_compgen -- -W "list add del flush refresh" ;; set) _comp_compgen -- -W "interface" ;; esac ;; 3) case ${prev} in counter) # list counters=$(foomuuri --quiet --quiet list counter | sed 's/ .*//') _comp_compgen -- -W "${counters}" ;; macro) # list macros=$(foomuuri --quiet --quiet list macro | sed '/^ /!d; s/^ //; s/ .*//') _comp_compgen -- -W "${macros}" ;; list | add | del | flush | refresh) # iplist iplists=$(foomuuri --quiet --quiet iplist list | sed 's/ .*//') _comp_compgen -- -W "${iplists}" ;; interface) # set interfaces=$(ip link show | sed '/^[0-9]\+: /!d; s/[0-9]\+: //; /lo:/d; s/:.*//') _comp_compgen -- -W "${interfaces}" ;; esac ;; 4) interfaces=$(ip link show | sed '/^[0-9]\+: /!d; s/[0-9]\+: //; /lo:/d; s/:.*//') for intf in ${interfaces}; do if [ "${prev}" = "${intf}" ]; then _comp_compgen -- -W "zone" fi done ;; 5) case ${prev} in zone) zones=$(foomuuri --quiet --quiet status | sed '/^ /!d; s/^ //; s/ .*//') _comp_compgen -- -W "${zones} -" return # "-" is not an option ;; esac ;; esac [[ ${was_split} ]] && return if [[ ${cur} == -* ]]; then _comp_compgen_help [[ ${COMPREPLY-} == *= ]] && compopt -o nospace fi } && complete -F _comp_cmd_foomuuri foomuuri # ex: filetype=sh foomuuri-0.29/doc/foomuuri.8000066400000000000000000000061771506547207000160350ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.11.1 .\" .TH "FOOMUURI" "8" "Sep 26, 2025" "Foomuuri 0.29" "User Manual" .SH NAME foomuuri \- multizone bidirectional nftables firewall .SH SYNOPSIS \f[B]foomuuri\f[R] [\f[I]OPTION\f[R]] [\f[I]COMMAND\f[R]] .SH DESCRIPTION \f[B]Foomuuri\f[R] is a firewall generator for nftables based on the concept of zones. It is suitable for all systems from personal machines to corporate firewalls, and supports advanced features such as a rich rule language, IPv4/IPv6 rule splitting, dynamic DNS lookups, a D\-Bus API and FirewallD emulation for NetworkManager\[cq]s zone support. .SH OPTIONS .TP \f[CR]\-\-help\f[R] display this help and exit .TP \f[CR]\-\-version\f[R] output version information and exit .TP \f[CR]\-\-verbose\f[R] verbose output .TP \f[CR]\-\-quiet\f[R] be quiet .TP \f[CR]\-\-force\f[R] force some operations, don\[cq]t check anything .TP \f[CR]\-\-soft\f[R] don\[cq]t force operations, check more .TP \f[CR]\-\-fork\f[R] fork as a background daemon process .TP \f[CR]\-\-syslog\f[R] enable syslog logging .TP \f[CR]\-\-set=option=value\f[R] set config option to value .SH COMMANDS .TP \f[B]start\f[R] load configuration files, generate new ruleset and load it to kernel .TP \f[B]stop\f[R] remove ruleset from kernel .TP \f[B]reload\f[R] same as \f[B]start\f[R], followed by iplist refresh .TP \f[B]try\-reload\f[R] same as \f[B]reload\f[R], ask confirmation to keep new config, revert back to old config if no reply .TP \f[B]status\f[R] show current status: running, zone\-interface mapping .TP \f[B]check\f[R] load configuration files and verify syntax .TP \f[B]block\f[R] load \[lq]block all traffic\[rq] ruleset .TP \f[B]list\f[R] list active ruleset currently loaded to kernel .TP \f[B]list zone\-zone {zone\-zone\&...}\f[R] list active ruleset for \f[B]zone\-zone\f[R] currently loaded to kernel .TP \f[B]list macro\f[R] list all known macros .TP \f[B]list macro name {name\&...}\f[R] list all macros with specified name or value .TP \f[B]list counter\f[R] list all named counters .TP \f[B]list counter name {name\&...}\f[R] list named counter with specified name .TP \f[B]iplist list\f[R] list entries in all configured iplists .TP \f[B]iplist list name {name\&...}\f[R] list entries in named iplist .TP \f[B]iplist add name {timeout} ipaddress {ipaddress\&...}\f[R] add or refresh IP address to iplist .TP \f[B]iplist del name ipaddress {ipaddress\&...}\f[R] delete IP address from iplist .TP \f[B]iplist flush name {name\&...}\f[R] delete all IP addresses from iplist .TP \f[B]iplist refresh name {name\&...}\f[R] refresh iplist \[at]name entries now .TP \f[B]set interface {interface} zone {zone}\f[R] change interface to zone .TP \f[B]set interface {interface} zone \-\f[R] remove interface from all zones .SH FILES \f[B]Foomuuri\f[R] reads configuration files from \f[I]/etc/foomuuri/*.conf\f[R]. See \c .UR https://github.com/FoobarOy/foomuuri/wiki/Host-Firewall .UE \c \ for example configuration. .SH AUTHORS Kim B. Heino, b\[at]bbbs.net, Foobar Oy .SH BUG REPORTS Submit bug reports \c .UR https://github.com/FoobarOy/foomuuri/issues .UE \c .SH SEE ALSO Full documentation \c .UR https://github.com/FoobarOy/foomuuri/wiki .UE \c foomuuri-0.29/doc/foomuuri.md000066400000000000000000000053361506547207000162620ustar00rootroot00000000000000--- title: FOOMUURI section: 8 header: User Manual footer: Foomuuri 0.29 date: Sep 26, 2025 --- # NAME foomuuri - multizone bidirectional nftables firewall # SYNOPSIS **foomuuri** [*OPTION*] [*COMMAND*] # DESCRIPTION **Foomuuri** is a firewall generator for nftables based on the concept of zones. It is suitable for all systems from personal machines to corporate firewalls, and supports advanced features such as a rich rule language, IPv4/IPv6 rule splitting, dynamic DNS lookups, a D-Bus API and FirewallD emulation for NetworkManager's zone support. # OPTIONS `--help` : display this help and exit `--version` : output version information and exit `--verbose` : verbose output `--quiet` : be quiet `--force` : force some operations, don't check anything `--soft` : don't force operations, check more `--fork` : fork as a background daemon process `--syslog` : enable syslog logging `--set=option=value` : set config option to value # COMMANDS **start** : load configuration files, generate new ruleset and load it to kernel **stop** : remove ruleset from kernel **reload** : same as **start**, followed by iplist refresh **try-reload** : same as **reload**, ask confirmation to keep new config, revert back to old config if no reply **status** : show current status: running, zone-interface mapping **check** : load configuration files and verify syntax **block** : load "block all traffic" ruleset **list** : list active ruleset currently loaded to kernel **list zone-zone {zone-zone...}** : list active ruleset for **zone-zone** currently loaded to kernel **list macro** : list all known macros **list macro name {name...}** : list all macros with specified name or value **list counter** : list all named counters **list counter name {name...}** : list named counter with specified name **iplist list** : list entries in all configured iplists **iplist list name {name...}** : list entries in named iplist **iplist add name {timeout} ipaddress {ipaddress...}** : add or refresh IP address to iplist **iplist del name ipaddress {ipaddress...}** : delete IP address from iplist **iplist flush name {name...}** : delete all IP addresses from iplist **iplist refresh name {name...}** : refresh iplist @name entries now **set interface {interface} zone {zone}** : change interface to zone **set interface {interface} zone -** : remove interface from all zones # FILES **Foomuuri** reads configuration files from */etc/foomuuri/\*.conf*. See for example configuration. # AUTHORS Kim B. Heino, b@bbbs.net, Foobar Oy # BUG REPORTS Submit bug reports # SEE ALSO Full documentation foomuuri-0.29/doc/monitor-example-command.sh000077500000000000000000000007701506547207000211630ustar00rootroot00000000000000#!/bin/sh # This is an example shell script how to use curl instead of fping to monitor # network connectivity. # # target foobar { # command /etc/foomuuri/monitor-example-command.sh # command_up /etc/foomuuri/monitor.event # command_down /etc/foomuuri/monitor.event # } while true; do # Echoed text must be "OK" or "ERROR", everything else is ignored [ "$(curl --silent http://foobar.fi/test/connectivity)" = "OK" ] && echo OK || echo ERROR # Small wait and repeat sleep 5 done foomuuri-0.29/doc/monitor.event000077500000000000000000000015641506547207000166270ustar00rootroot00000000000000#!/bin/sh # Example command_up / command_down script for foomuuri-monitor. # This script sends an email to root. # Ignore startup change event [ "${FOOMUURI_CHANGE_LOG}" = "startup change" ] && exit 0 # Notify root by email ( # Changed state echo "State change event:" echo " ${FOOMUURI_CHANGE_TYPE} ${FOOMUURI_CHANGE_NAME} ${FOOMUURI_CHANGE_STATE}" echo " ${FOOMUURI_CHANGE_LOG}" echo # All states echo "All states:" for name in ${FOOMUURI_ALL_TARGET}; do state_ref=FOOMUURI_TARGET_${name} state=$(eval "echo \"\$${state_ref}\"") echo " target ${name} ${state}" done for name in ${FOOMUURI_ALL_GROUP}; do state_ref=FOOMUURI_GROUP_${name} state=$(eval "echo \"\$${state_ref}\"") echo " group ${name} ${state}" done ) | mail -s "[foomuuri-monitor] ${FOOMUURI_CHANGE_TYPE} ${FOOMUURI_CHANGE_NAME} ${FOOMUURI_CHANGE_STATE}" root foomuuri-0.29/etc/000077500000000000000000000000001506547207000140725ustar00rootroot00000000000000foomuuri-0.29/etc/50-foomuuri.conf000066400000000000000000000013411506547207000170270ustar00rootroot00000000000000# foomuuri: not-conf # Use secure ARP settings net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.default.arp_ignore = 1 net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.default.arp_filter = 1 net.ipv4.conf.all.arp_filter = 1 # Don't accept or send redirects net.ipv6.conf.default.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.conf.all.send_redirects = 0 # Set printk logging level so that firewall logging doesn't show up in console kernel.printk = 4 # Enable packet forwarding # net.ipv4.conf.all.forwarding = 1 # net.ipv6.conf.all.forwarding = 1 foomuuri-0.29/etc/block.fw000066400000000000000000000010301506547207000155140ustar00rootroot00000000000000# "Block all traffic" ruleset that can be used in foomuuri-boot.service # instead of "good.fw". This file is also used by "foomuuri block" command. # # Use command "systemctl edit --full foomuuri-boot.service" to switch to this # file instead of "good.fw". table inet foomuuri delete table inet foomuuri table inet foomuuri { chain input { type filter hook input priority filter drop } chain output { type filter hook output priority filter drop } chain forward { type filter hook forward priority filter drop } } foomuuri-0.29/etc/default.services.conf000066400000000000000000000112011506547207000202020ustar00rootroot00000000000000# Known services as macros. # # Macro name should match service name in /etc/services file, macro # definitation should be minimal set of ports to open. Minimal means that # client and server should have separate macros, web-administration should # have it's own macro, etc. macro { activedirectory domain; kerberos; ntp; kpasswd; ldap; ldaps; udp 389; tcp 135 3268 3269 49152-65535 adb tcp 5555 afp tcp 548 # afpovertcp airport udp 192 # osu-nms alertmanager tcp 9093 amqp tcp 5672 android tcp 5228-5230 4070 4460; udp 5228-5230 2002; https apple tcp 2197 5223; https bgp tcp 179 cockpit tcp 9090 dhcp-client udp 68 ipv4; broadcast udp 68 # bootpc, from server to client dhcp-server udp 67 ipv4; broadcast udp 67 # bootps, from client to server dhcpv6-client udp sport 547 dport 546 daddr fe80::/10 dhcpv6-server multicast udp sport 546 dport 547 daddr ff02::1:2 discord udp 50000-65535; https domain tcp 53; udp 53 domain-quic udp 853 domain-s domain-quic; domain-tls domain-tls tcp 853 facetime udp 3478-3497 16384-16387 16393-16402; apple finger tcp 79 fooham tcp 9997; udp 9997 freeipa domain; http; https; kerberos; kpasswd; ldap; ldaps ftp tcp 21 helper ftp-21 ftps tcp 990 galera tcp 4444 4567-4568 git tcp 9418 gluster-client tcp 24007 49152-60999 gluster-management tcp 24008 googlemeet udp 3478 19302-19309; https gotomeeting tcp 3478; udp 3478; https hkp tcp 11371 http tcp 80 http-alt tcp 8000 8008 8080 8443 http2 tcp 443 https tcp 443; udp 443 imap tcp 143 imaps tcp 993 ipp tcp 631 ipsec udp 500 4500; protocol "esp" ipsec-nat udp sport 4500; ipsec irc tcp 6667 helper irc-6667 ircs-u tcp 6697 jetdirect tcp 9100 kerberos tcp 88; udp 88 kpasswd tcp 464; udp 464 ldap tcp 389 ldaps tcp 636 lsdp broadcast udp 11430 mdns multicast udp 5353 daddr 224.0.0.251 ff02::fb; multicast protocol "igmp" daddr 224.0.0.251; udp sport 5353 meetecho tcp 1935 8000 8181; https microsoftteams udp 3478-3481; https minecraft tcp 25565 mongodb tcp 27017 mqtt tcp 1883 ms-sql-m udp 1434 ms-sql-s tcp 1433 mysql tcp 3306 nbd tcp 10809 nfs tcp 2049 nfsv3 tcp 2049 111 20048 ntp udp 123 ospf multicast protocol "ospf" daddr 224.0.0.5 224.0.0.6 ff02::5 ff02::6 fe80::/10; multicast protocol "igmp" daddr 224.0.0.5 224.0.0.6 ping icmp echo-request; icmpv6 echo-request pop3s tcp 995 postgresql tcp 5432 prometheus tcp 9090 prometheus-blackbox tcp 9115 prometheus-knot tcp 9433 prometheus-mysqld tcp 9104 prometheus-node tcp 9100 prometheus-postgresql tcp 9187 prometheus-ssl tcp 9219 prometheus-windows tcp 9182 pxe udp 4011 razor tcp 2703 rdp tcp 3389 redis tcp 6379 redis-sentinel tcp 26379 rfb vnc rsync tcp 873 rtsps tcp 322 salt tcp 4505 4506 secure-mqtt tcp 8883 sieve tcp 4190 sip udp 5060 helper sip-5060 smb tcp 139 445 # cifs smtp tcp 25 snmp udp 161 helper snmp-161 snmptrap udp 162 ssdp multicast udp 1900 daddr 239.255.255.250 ff02::c; multicast protocol "igmp" daddr 239.255.255.250; udp sport 1900 ssh tcp 22 submission tcp 587 submissions tcp 465 svn tcp 3690 syslog tcp 514; udp 514 syslog-tls tcp 6514; udp 6514 telnet tcp 23 telnets tcp 992 tftp udp 69 helper tftp-69 tor tcp 9001 tor-browser-bundle tcp 9150 tor-control tcp 9051 tor-directory tcp 9030 tor-socks tcp 9050 traceroute udp 33434-33524 vnc tcp 5900 vrrp-multicast multicast protocol "vrrp" daddr 224.0.0.18 ff02::12; multicast protocol "igmp" daddr 224.0.0.18 whois tcp 43 4321 wireguard udp 51820 ws-discovery multicast udp 3702 daddr 239.255.255.250 ff02::c; multicast protocol "igmp" daddr 239.255.255.250; udp sport 3702; tcp 5357 xmpp-client tcp 5222 zabbix-agent tcp 10050 zabbix-trapper tcp 10051 zoom tcp 8801-8802; udp 3478-3479 8801-8810; https } foomuuri-0.29/etc/static.nft000066400000000000000000000034331506547207000160750ustar00rootroot00000000000000 chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } foomuuri-0.29/firewalld/000077500000000000000000000000001506547207000152705ustar00rootroot00000000000000foomuuri-0.29/firewalld/dbus-firewalld.conf000066400000000000000000000000421506547207000210370ustar00rootroot00000000000000foomuuri { dbus_firewalld yes } foomuuri-0.29/firewalld/fi.foobar.Foomuuri-FirewallD.conf000066400000000000000000000023441506547207000234620ustar00rootroot00000000000000 foomuuri-0.29/src/000077500000000000000000000000001506547207000141065ustar00rootroot00000000000000foomuuri-0.29/src/foomuuri000077500000000000000000004670001506547207000157100ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=too-many-lines """Foomuuri - Multizone bidirectional nftables firewall. Copyright 2023-2025, Kim B. Heino, Foobar Oy License: GPL-2.0-or-later """ import collections import concurrent.futures import datetime import ipaddress import itertools import json import os import pathlib import re import select import shlex import signal import socket import subprocess import sys import syslog import time import unicodedata import dbus import dbus.mainloop.glib import dbus.service from gi.repository import GLib # requests is optional, used by iplist http download try: import requests HAVE_REQUESTS = True except ImportError: HAVE_REQUESTS = False # lxml is optional, used by iplist xml and html filters try: import lxml.etree HAVE_LXML = True except ImportError: HAVE_LXML = False # SystemD notify support is optional try: from systemd.daemon import notify HAVE_NOTIFY = True except ImportError: HAVE_NOTIFY = False VERSION = '0.29' CONFIG = { # Parsed foomuuri{} from config files 'log_rate': '1/second burst 3', 'log_input': 'yes', 'log_output': 'yes', 'log_forward': 'yes', 'log_rpfilter': 'yes', 'log_invalid': 'no', 'log_smurfs': 'no', 'log_prefix': '$(szone)-$(dzone) $(statement)', 'log_level': 'level info flags skuid', 'localhost_zone': 'localhost', 'dbus_zone': 'public', 'rpfilter': 'yes', 'counter': 'no', 'set_size': '65535', 'recursion_limit': '10000', 'priority_offset': '5', 'dbus_firewalld': 'no', 'nft_bin': 'nft', 'try-reload_timeout': '15', # Directories and files. Files are relative to state_dir. 'etc_dir': '/etc/foomuuri', 'share_dir': '/usr/share/foomuuri', 'state_dir': '/var/lib/foomuuri', 'run_dir': '/run/foomuuri', 'good_file': 'good.fw', 'next_file': 'next.fw', 'dbus_file': 'dbus.fw', 'resolve_file': 'resolve.fw', # old iplist/resolve 'iplist_file': 'iplist.fw', # old iplist/resolve 'iplist_manual_file': 'iplist-manual.fw', # old iplist/resolve 'iplist_cache_file': 'iplist-cache.json', 'iplist_apply_file': 'iplist-cache.fw', 'zone_file': 'zone', 'monitor_statistics_file': 'monitor.statistics', # Parsed command line parameters - used internally 'command': '', 'parameters': [], 'root_power': True, 'verbose': 0, 'force': 0, 'fork': 0, 'syslog': 0, } OUT = [] # Generated nftables ruleset / commands LOGRATES = {} # Lograte names and limits HELPERS = [] # List of helpers: (helper-object, protocol, ports) def fail(error=None, fatal=True): """Exit with error message.""" if error and CONFIG['verbose'] >= -1: # Double-quiet will supress output print(f'Error: {error}', flush=True) if CONFIG['syslog']: syslog.syslog(syslog.LOG_ERR, f'Error: {error}') if fatal: sys.exit(1) def warning(text): """Print warning message.""" if CONFIG['verbose'] >= 0: print(f'Warning: {text}', flush=True) if CONFIG['syslog']: syslog.syslog(syslog.LOG_WARNING, f'Warning: {text}') def verbose(line, level=1): """Print line if --verbose was given in command line.""" if CONFIG['verbose'] >= level: print(line, flush=True) if CONFIG['syslog']: syslog.syslog(syslog.LOG_NOTICE, line) def out(line): """Add single line to ruleset.""" OUT.append(line) def run_program_rc(args, *, env=None, print_output=True, quiet=False): """Run external program and return its errorcode. Print its output.""" if not args: return 0 verbose(' '.join(map(str, args))) try: proc = subprocess.run(args, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', env=env, timeout=60) except (OSError, subprocess.TimeoutExpired) as error: fail(f'Failed to run command "{shlex.join(args)}": {error}', False) return 1 output = proc.stdout.rstrip() if ( output and (proc.returncode or print_output or CONFIG['verbose'] > 0) and not quiet ): verbose(output, 0) return proc.returncode def run_program_pipe(args, text_input): """Run external program with stdin, return its output.""" if not args: return '' try: proc = subprocess.run(args, check=False, input=text_input, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', timeout=60) except (OSError, subprocess.TimeoutExpired) as error: fail(f'Failed to run command "{" ".join(args)}": {error}') if proc.returncode: warning(f'Failed to run command "{" ".join(args)}": ' f'return code {proc.returncode}') return None return proc.stdout def run_program_shell(args, fileline): """Run external program as shell command and return its output. Command failure is fatal. Parameter args must be a string, not list. """ if not args: return '' try: proc = subprocess.run(args, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', shell=True, timeout=60) except (OSError, subprocess.TimeoutExpired) as error: fail(f'{fileline}Failed to run command "{args}": {error}') if proc.returncode: fail(f'{fileline}Failed to run command "{args}": ' f'return code {proc.returncode}') return proc.stdout.rstrip() def nft_command(cmd, **kwargs): """Run "nft cmd", wrapper to run_program_rc().""" return run_program_rc(CONFIG['_nft_bin'] + [cmd], **kwargs) def nft_json(cmd): """Run "nft --json cmd", return its output as json, ignore errors.""" if not cmd: return {} args = CONFIG['_nft_bin'] + ['--json', cmd] verbose(' '.join(map(str, args)), 2) try: proc = subprocess.run(args, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', timeout=60) except (OSError, subprocess.TimeoutExpired): return None if proc.returncode: return None try: verbose(proc.stdout, 2) return json.loads(proc.stdout) except json.decoder.JSONDecodeError: return None def daemonize(): """Fork as a background daemon. Silently ignore errors.""" # Enabled in config? if not CONFIG['fork']: return CONFIG['fork'] = 0 # Fork only once! # Fork #1 try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: return # Run in a new session. Don't do "os.chdir('/')" so that relative paths # work in development. os.umask(0) os.setsid() # Fork #2 try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: return # Redirect stdin/out/err for stream in (sys.stdin, sys.stdout, sys.stderr): devnull = os.open(os.devnull, os.O_RDWR) os.dup2(devnull, stream.fileno()) def shell_expansion(content, fileline): """Expand $(shell command) in configuration file. This is the first expansion done. Failure in command is fatal. """ while '$(shell ' in content: prefix, postfix = content.split('$(shell ', 1) if ')' not in postfix: postfix = postfix.splitlines()[0] fail(f'{fileline}"$(shell" without ")" in command: {postfix}') shell, postfix = postfix.split(')', 1) content = f'{prefix}{run_program_shell(shell, fileline)}{postfix}' return content def find_config_files(basedir, mask): """Find all config files in basedir, ignoring hidden and backups.""" files = sorted(basedir.rglob(mask)) return [item for item in files if not item.name.startswith(('.', '#'))] def read_config(): """Read all config files to config dict: section -> lines[]. Files are read in alphabetical order, ignoring backup and hidden files. """ # pylint: disable=too-many-branches # pylint: disable=too-many-statements # Find all config files share_config = find_config_files(CONFIG['_share_dir'], '*.conf') etc_config = find_config_files(CONFIG['_etc_dir'], '*.conf') if not etc_config: fail(f'No configuration files "{CONFIG["_etc_dir"]}/*.conf" found\n' '\n' 'See https://github.com/FoobarOy/foomuuri/wiki/Host-Firewall ' 'for example\n' 'configuration.') # There characters will combine to single word in shlex wordchars = ''.join(chr(letter) for letter in range(33, 256) # excludes: " # ' ; { } if letter not in (34, 35, 39, 59, 123, 125)) # Read all config files config = {} # Final config dict section = None # Currently open section name section_line = {} # Section_name -> filename_line for error messages for filename in share_config + etc_config: try: content = filename.read_text(encoding='utf-8') except PermissionError as error: fail(f'File {filename}: Can\'t read: {error}') # Expand $(shell in configuration file. Do this for whole file instead # of single line so that command can return multiple lines. content = shell_expansion(content, f'File {filename}: ') # Parse single config file content continuation = '' for linenumber, line in enumerate(content.splitlines()): if line == '# foomuuri: not-conf': break # sysctl's 50-foomuuri.conf is not my config # Combine lines if there is \ at end of line if line.endswith('\\'): continuation += line[:-1] + ' ' continue line = continuation + line continuation = '' # Parse single line to list of words. Keep " as is, it can be # used to avoid macro expansion. fileline = f'File {filename} line {linenumber + 1}: ' try: lexer = shlex.shlex(line, punctuation_chars=';{') lexer.wordchars = wordchars tokens = list(lexer) except ValueError as error: fail(f'{fileline}Can\'t parse line: {error}') if not tokens: continue # "}" is end of section if len(tokens) == 1 and tokens[0] == '}': # End of section if not section: fail(f'{fileline}Extra "}}"') section = None # Section start: # - "foo {" # - "template foo {" / "target foo" / "group foo" # - "prerouting {" # - "prerouting filter mangle - 10 {" elif len(tokens) >= 2 and tokens[-1] == '{': if section: fail(f'{fileline}New "{" ".join(tokens)}" while section ' f'"{section}" is still open') if ( tokens[0] in ('template', 'target', 'group') and len(tokens) != 3 ): fail(f'{fileline}Section "{tokens[0]}" must have single ' f' word name: {" ".join(tokens)}') if len(tokens) > 2 and tokens[0] not in ( 'template', 'target', 'group', 'snat', 'dnat', 'prerouting', 'postrouting', 'forward', 'input', 'output'): fail(f'{fileline}Section "{tokens[0]}" does not take ' f'parameters: {" ".join(tokens)}') section = ' '.join(tokens[:-1]) if section.startswith('_'): # _name is protected fail(f'{fileline}Unknown section: {section}') if section not in config: config[section] = [] section_line[section] = fileline # "foo" which is not inside section elif not section: fail(f'{fileline}Unknown line: {" ".join(tokens)}') # "foo" inside section else: config[section].append((fileline, tokens)) # End of file checks if continuation: fail(f'File {filename}: Continuation "\\" at end of file') if section: fail(f'File {filename}: Section "{section}" is missing "}}" at ' f'end of file') # Include section_name -> filename_line to config for error messages config['_section_line'] = section_line return config def config_to_pathlib(): """Convert str paths in CONFIG{} to pathlib.Paths.""" # "*_dir" are needed for reading config files keys = [key for key in CONFIG if not key.startswith('_')] for key in keys: if key.endswith('_dir'): CONFIG[f'_{key}'] = pathlib.Path(CONFIG[key]) # "*_file" are needed to save current state for key in keys: if key.endswith('_file'): CONFIG[f'_{key}'] = CONFIG['_state_dir'] / CONFIG[key] # "*_bin" are binaries with optional arguments for key in keys: if key.endswith('_bin'): CONFIG[f'_{key}'] = shlex.split(CONFIG[key]) def parse_config_templates(config, macros, macroline): """Parse "template foo { ... }" rules and convert them to macros.""" names = [item for item in config if item.startswith('template ')] for name in names: lines = config.pop(name) fileline = config['_section_line'][name] if not lines: fail(f'{fileline}Template "{name[9:]}" is empty') macro_name = f'_template_{name[9:]}' macroline[macro_name] = fileline macros[macro_name] = [] for line in lines: if macros[macro_name]: macros[macro_name].append(';') macros[macro_name].extend(line[1]) def parse_config_macros(config): """Parse macro{} from config. Recursively expand macro in macro{}.""" # Parse macro{} to dict macros = {} macroline = {} parse_config_templates(config, macros, macroline) for fileline, macro in config.pop('macro', []): key = macro[0] value = macro[1:] if not value: fail(f'{fileline}Macro "{key}" does not have value') macroline[key] = fileline if value[0] == '+': # append macros[key] = macros.get(key, []) + value[1:] else: if CONFIG['command'] == 'check' and macros.get(key): warning(f'{fileline}Overwriting macro "{key}" ' f'with value "{" ".join(value)}"') macros[key] = value # overwrite # Expand macro in macro{}. Keep going as long as there was some expansion # done. while True: found = False for check, cvalue in macros.items(): for macro, mvalue in macros.items(): try: pos = mvalue.index(check) # Full word expansion only except ValueError: continue if check == macro: # Macro "foo" expands to "foo bar" fail(f'{macroline[macro]}Macro "{macro}" expands to ' f'itself: {" ".join(mvalue)}') # Expand macro macros[macro] = mvalue[:pos] + cvalue + mvalue[pos + 1:] found = True if not found: # No new expansion was done return macros def macro_isdigit(word, separator): """Check if word contains number after separator.""" if word.count(separator) != 1: return False return word.split(separator)[1].isdigit() def change_template_to_macro_in_rule(fileline, line, macros): """Change "template foo" to "_template_foo".""" pos = 0 while pos < len(line) - 1: if line[pos] == 'template': del line[pos] macro_name = f'_template_{line[pos]}' if macro_name not in macros: fail(f'{fileline}Unknown template name: {line[pos]}') line[pos] = macro_name pos += 1 def expand_single_line(fileline, line, macros): """Expand first macro in line. Repeat call to expand all. Expansion can return multiple lines. Returns None if no expansion was done. """ # Change "template foo" to macro call change_template_to_macro_in_rule(fileline, line, macros) # Iterate words in line for pos, word in enumerate(line): # Cleanup "-macro", "macro/24", "[macro]:123" and "macro:123" # to prefix/macro/suffix parts word_prefix = word_suffix = '' if word[0] == '-': # Negative IP address word = word[1:] word_prefix = '-' if macro_isdigit(word, '/'): # Netmask word, word_suffix = word.split('/') word_suffix = f'/{word_suffix}' if ( word[0] == '[' and macro_isdigit(word, ']:') and not word_prefix and not word_suffix ): # [IPv6]:port word, word_suffix = word[1:].split(']:') word_prefix = '[' word_suffix = f']:{word_suffix}' if macro_isdigit(word, ':') and not word_suffix: # IPv4:port word, word_suffix = word.split(':') word_suffix = f':{word_suffix}' # Check cleaned value if word not in macros: continue # Found, add prefix/suffix to all macro's value mvalue = [] for item in macros[word]: if item == ';': mvalue.append(item) else: mvalue.append(f'{word_prefix}{item}{word_suffix}') # Expand mvalue to line and return list of expanded lines prefix = line[:pos] suffix = line[pos + 1:] return [(fileline, prefix + list(group) + suffix) for is_split, group in itertools.groupby( mvalue, lambda spl: spl == ';') if not is_split] return None def expand_macros(config): """Expand all macros in all config sections.""" macros = parse_config_macros(config) for section, orig_lines in config.items(): if section in ('foomuuri', 'zone') or section.startswith('_'): continue # Don't expand in these sections new_lines = [] recursion_limit = collections.Counter() while orig_lines: # Get next line and expand macros there fileline, line = orig_lines.pop(0) expanded = expand_single_line(fileline, line, macros) # Repeat call if some expansion was done if expanded: orig_lines = expanded + orig_lines else: # Not found, go to next line new_lines.append((fileline, line)) # Check for expansion loop recursion_limit.update([fileline]) if recursion_limit[fileline] > int(CONFIG['recursion_limit']): fail(f'{fileline}Possible macro or template loop ' f'in "{section}": {" ".join(line[:10])} ...') config[section] = new_lines def remove_quotes(config): """Change "foo" to foo in config entries. This is called after macro expansion so that '"ssh"' is 'ssh', not 'tcp 22'. """ for section, lines in config.items(): if section.startswith('_'): continue for _dummy_fileline, line in lines: for index, item in enumerate(line): if item.startswith('"') and item.endswith('"'): # "foo" line[index] = item[1:-1] elif item.startswith("'") and item.endswith("'"): # 'foo' line[index] = item[1:-1] def parse_config_foomuuri(config): """Parse foomuuri{} from config to CONFIG{}.""" for fileline, line in config.pop('foomuuri', []): name = line[0] value = ' '.join(line[1:]) if name not in CONFIG: fail(f'{fileline}Unknown foomuuri{{}} option: {" ".join(line)}') if value.startswith('+ '): # append CONFIG[name] = f'{CONFIG[name]} {value[2:]}' else: CONFIG[name] = value # overwrite config_to_pathlib() # Redo as config files can change these # Convert chain priority offset to nft. It is already converted on D-Bus # handler reload. if not CONFIG['priority_offset']: priority = 0 else: try: priority = int(CONFIG['priority_offset'].replace(' ', '')) except ValueError: fail(f'Invalid foomuuri{{}} priority_offset: ' f'{CONFIG["priority_offset"]}') if priority == 0: CONFIG['priority_offset'] = '' elif priority > 0: CONFIG['priority_offset'] = f' + {priority}' else: CONFIG['priority_offset'] = f' - {-priority}' # Add "packets" to log rates for key in list(CONFIG): if ( key.startswith('log_') and re.match(r'^\d+/(second|minute|hour) burst \d+$', CONFIG[key]) ): CONFIG[key] += ' packets' def check_name(name, fileline, prefix=''): """Check that name starts with letter, not number.""" if not re.fullmatch(r'[a-zA-Z_]', name[:1]): fail(f'{fileline}Invalid name: {prefix}{name}') def parse_config_zones(config): """Parse zone{} from config.""" zones = {} for fileline, line in config.pop('zone', []): check_name(line[0], fileline) if line[0] in zones: fail(f'{fileline}Zone is already defined: {line[0]}') zones[line[0]] = {'interface': line[1:]} return zones def parse_config_zonemap(config): """Parse zonemap{} rules from config.""" zonemap = [] for fileline, line in config.pop('zonemap', []): rule = parse_rule_line((fileline, line)) if not rule['new_dzone'] and not rule['new_szone']: fail(f'{fileline}Zonemap without "new_dzone" or "new_szone" ' f'is a no-op: {" ".join(line)}') zonemap.append(rule) return zonemap def parse_config_special_chains(config): """Parse snat{}, dnat{}, prerouting{} etc. rules from config.""" chain_rules = { # output+mangle is needed for mark restore in output_special_chains() ('output', 'route', 'mangle' + CONFIG['priority_offset']): [], } for prefix, def_type, def_priority in ( ('snat', 'nat', 'srcnat'), ('dnat', 'nat', 'dstnat'), ('prerouting', 'filter', 'mangle'), ('postrouting', 'filter', 'mangle'), ('forward', 'filter', 'mangle'), ('input', 'filter', 'mangle'), ('output', 'route', 'mangle'), ('invalid', None, None), ('rpfilter', None, None), ('smurfs', None, None)): for section in list(config): if not (section == prefix or section.startswith(f'{prefix} ')): continue fileline = config['_section_line'][section] items = section.split(' ', 2) if len(items) == 2: fail(f'{fileline}Invalid section: type or priority missing: ' f'{section}') if len(items) == 1: ftype = def_type priority = None if def_priority: priority = def_priority + CONFIG['priority_offset'] else: ftype = items[1] priority = items[2] if ftype not in ('filter', 'nat', 'route'): fail(f'{fileline}Invalid section type: {section}') lines = config.pop(section) chain_rules[(prefix, ftype, priority)] = [parse_rule_line(line) for line in lines] return chain_rules def parse_iplist_names(config): """Return list of resolve{} or iplist{} set names from config.""" ret = set() for section in ('iplist', 'resolve'): for fileline, line in config.get(section, []): if line[0].startswith(('dns_timeout=', 'dns_refresh=', 'url_timeout=', 'url_refresh=')): continue if line[0] in ('timeout', 'refresh'): continue if not line[0].startswith('@') or line[0] == '@': fail(f'{fileline}Invalid {section} name: {" ".join(line)}') check_name(line[0][1:], fileline, '@') ret.add(line[0]) return ret def parse_config_hook(config): """Parse hook{} from config.""" for fileline, line in config.pop('hook', []): if line[0] not in ( 'pre_start', 'post_start', 'pre_stop', 'post_stop', ): fail(f'{fileline}Unknown hook: {" ".join(line)}') CONFIG[line[0]] = line[1:] def minimal_config(): """Read and parse minimal config.""" config = read_config() expand_macros(config) remove_quotes(config) parse_config_foomuuri(config) return config def is_ipv4_address(value): """Is value IPv4 address, network or interval.""" if value.count('-') == 1: # Interval "IP-IP" addr_from, addr_to = value.split('-') return is_ipv4_address(addr_from) and is_ipv4_address(addr_to) try: # Address "IP" return isinstance(ipaddress.ip_address(value), ipaddress.IPv4Address) except ValueError: try: # Network "IP/mask" return isinstance(ipaddress.ip_network(value, strict=False), ipaddress.IPv4Network) except ValueError: return False def is_ipv6_address(value): """Is value IPv6 address, network or interval.""" if value.count('/-') == 1: # Suffix mask "IP/-mask" addr, maskstr = value.split('/-') try: mask = int(maskstr) return 0 <= mask <= 128 and is_ipv6_address(addr) except ValueError: return False if value.count('-') == 1: # Interval "IP-IP" addr_from, addr_to = value.split('-') return is_ipv6_address(addr_from) and is_ipv6_address(addr_to) # Python's ipaddress library doesn't handle "[ipv6]" notation. # Strip [] before validating the address. nft handles [] fine, it will # strip them. if value.startswith('['): if value.endswith(']'): # "[ipv6]" value = value[1:-1] elif ']/' in value: # "[ipv6]/56" value = value[1:].replace(']/', '/') try: # Address "IP" return isinstance(ipaddress.ip_address(value), ipaddress.IPv6Address) except ValueError: try: # Network "IP/mask" return isinstance(ipaddress.ip_network(value, strict=False), ipaddress.IPv6Network) except ValueError: return False def is_ip_address(value): """Check if value is IPv4 or IPv6 address. Return 4, 6, or 0 if not detected. """ if value.startswith('-'): # Negative is handled in single_or_set() value = value[1:] if is_ipv4_address(value): return 4 if is_ipv6_address(value): return 6 return 0 def is_port(value, protocol): """Check if value is port: "1", "1-2" or "1,2", or any combination. This is used in parse_rule_line so protocol must specified. Allow special keywords for protocol icmp/icmpv6. """ if not protocol: return False for item in value.split(','): if protocol in ('icmp', 'icmpv6') and ( # See: "nft describe icmp type; nft describe icmpv6 type" item == 'redirect' or re.match(r'^[a-z2]{2,}-[-a-z]{5,}$', item) ): continue for number in item.split('-'): if not number.isnumeric(): return False return True def rule_item_only_one(rule, keyword, value): """Verify that keyword is set only once, or set to same value.""" old = rule.get(f'_only_one_{keyword}', value) if old != value: fail(f'{rule["fileline"]}Rule\'s {keyword} is already set to ' f'"{old}": {" ".join(rule["line"])}') rule[f'_only_one_{keyword}'] = value def verify_rule_sanity(rule, fileline): """Do some basic verify that single rule is valid.""" # pylint: disable=too-many-branches for key, value in rule.items(): if value == '' and key not in ('to', 'queue', 'counter', 'log', 'fileline', 'line'): fail(f'{fileline}"{key}" without value is not valid') for key in ('saddr_rate_name', 'daddr_rate_name', 'saddr_daddr_rate_name', 'helper', 'counter', 'mss'): value = rule[key] or '' if ' ' in value: fail(f'{fileline}"{key}" must be single word: {value}') for key in ('global_rate', 'saddr_rate', 'daddr_rate', 'saddr_daddr_rate'): if not rule[key]: continue # Limit by packets: # # 3/second # 3/second burst 5 # 3/second burst 5 packets # over 3/second # over 3/second burst 5 # over 3/second burst 5 packets # # Limit by bytes: # # 10 mbytes/second # 10 mbytes/second burst 12000 kbytes # over 10 mbytes/second # over 10 mbytes/second burst 12000 kbytes # # Limit by connection count # # ct count 8 # ct count over 8 if re.match(r'^(over )?\d+/(second|minute|hour) burst \d+$', rule[key]): rule[key] += ' packets' # Add missing "packets" if not ( re.match(r'^(over )?' r'\d+/(second|minute|hour)' r'( burst \d+ packets)?$', rule[key]) or re.match(r'^(over )?' r'\d+ [km]?bytes/(second|minute|hour)' r'( burst \d+ [km]?bytes)?$', rule[key]) or re.match(r'^ct count (over )?\d+$', rule[key]) ): fail(f'{fileline}Invalid "{key}" value: {rule[key]}') for basic, extra in ( ('protocol', 'sport'), ('protocol', 'dport'), ('saddr_rate', 'saddr_rate_name'), ('saddr_rate', 'saddr_rate_mask'), ('daddr_rate', 'daddr_rate_name'), ('daddr_rate', 'daddr_rate_mask'), ('saddr_daddr_rate', 'saddr_daddr_rate_name'), ('saddr_daddr_rate', 'saddr_daddr_rate_mask'), ): if rule[extra] and not rule[basic]: fail(f'{fileline}"{extra}" without "{basic}" is not valid') if not rule['to'] and rule['statement'] in ('snat', 'dnat', 'snat_prefix', 'dnat_prefix'): fail(f'{fileline}"{rule["statement"]}" without address is not valid') if rule['ct_status'] and rule['ct_status'] not in ( 'expected', 'seen-reply', 'assured', 'confirmed', 'snat', 'dnat', 'dying', ): fail(f'{fileline}"Invalid "ct_status" value: {rule["ct_status"]}') def parse_rule_line(fileline_line): """Parse single config section line to rule dict. This parser is quite relaxed. Words can be in almost any order. For example, all following entries are equal: tcp 22 log <- preferred tcp 22 accept log accept tcp 22 log log tcp accept 22 """ # pylint: disable=too-many-branches # pylint: disable=too-many-statements fileline, line = fileline_line ret = { # Basic rules 'statement': 'accept', 'cast': 'unicast', 'protocol': None, 'saddr': None, 'sport': None, 'daddr': None, 'dport': None, 'oifname': None, 'iifname': None, 'mac_saddr': None, 'mac_daddr': None, # Is this IPv4/6 specific rule? 'ipv4': False, 'ipv6': False, # Rate limits 'global_rate': None, 'saddr_rate': None, 'saddr_rate_mask': None, 'saddr_rate_name': None, 'daddr_rate': None, 'daddr_rate_mask': None, 'daddr_rate_name': None, 'saddr_daddr_rate': None, 'saddr_daddr_rate_mask': None, 'saddr_daddr_rate_name': None, # User limits 'uid': None, 'gid': None, # Zonemap specific rules 'szone': None, 'dzone': None, 'new_szone': None, 'new_dzone': None, # Misc rules 'to': None, # snat/dnat to 'queue': None, # optional queue flags 'counter': None, 'helper': None, 'sipsec': None, 'dipsec': None, 'log': None, 'log_level': None, 'nft': None, 'mss': None, 'template': None, 'tproxy': None, 'mark_set': None, 'mark_match': None, 'priority_set': None, 'priority_match': None, 'dscp': None, 'cgroup': None, 'ct_status': None, 'time': None, 'after_conntrack': True, # Internal housekeeping 'plain': True, # Plain "log" or "counter" without anything else 'fileline': fileline, # For error messages 'line': line, # Original line for error messages } keyword = None for item in line: if item == ';': fail(f'{fileline}";" is not supported in rule, split it to ' f'separate lines: {" ".join(line)}') # "tcp 22" is shortcut for "tcp dport 22" if not keyword and is_port(item, ret['protocol']): keyword = 'dport' ret['plain'] = False if ret[keyword] is None: ret[keyword] = '' # Version 0.29 deprecates 'to' after 'snat' statement, ignore it if keyword == 'to' and not ret['to'] and item == 'to': continue # First item after start keyword is always a parameter for it, except # for "log" or "counter". Log will have good default value if not # defined. Counter without parameter will create anonymous counter. if keyword and not ret[keyword] and keyword not in ('log', 'counter'): ret[keyword] = item if keyword == 'protocol': # Single word only keyword = None # Non-start keywords elif item in ('accept', 'drop', 'return', 'continue', 'masquerade', 'notrack'): rule_item_only_one(ret, 'statement', item) ret['statement'] = item ret['plain'] = False keyword = None elif item == 'reject': rule_item_only_one(ret, 'statement', item) ret['statement'] = 'reject with icmpx admin-prohibited' ret['plain'] = False keyword = None elif item in ('multicast', 'broadcast'): rule_item_only_one(ret, 'cast', item) ret['cast'] = item ret['plain'] = False keyword = None elif item in ('tcp', 'udp', 'icmp', 'icmpv6', 'igmp', 'esp'): # "igmp" and "esp" are for backward compability (v0.21) rule_item_only_one(ret, 'protocol', item) ret['protocol'] = item ret['plain'] = False keyword = None elif item in ('ipv4', 'ipv6'): ret[item] = True ret['plain'] = False keyword = None elif item in ('sipsec', 'dipsec'): ret[item] = 'exists' ret['plain'] = False keyword = None elif item in ('-sipsec', '-dipsec'): ret[item[1:]] = 'missing' ret['plain'] = False keyword = None elif item in ('conntrack', '-conntrack'): rule_item_only_one(ret, 'conntrack', item) ret['after_conntrack'] = item == 'conntrack' ret['plain'] = False keyword = None # Start keywords elif item in ('snat', 'dnat', 'snat_prefix', 'dnat_prefix'): rule_item_only_one(ret, 'statement', item) ret['statement'] = item ret['plain'] = False keyword = 'to' ret[keyword] = ret[keyword] or '' elif item in ('protocol', 'saddr', 'sport', 'daddr', 'dport', 'oifname', 'iifname', 'mac_saddr', 'mac_daddr', 'global_rate', 'saddr_rate', 'saddr_rate_mask', 'saddr_rate_name', 'daddr_rate', 'daddr_rate_mask', 'daddr_rate_name', 'saddr_daddr_rate', 'saddr_daddr_rate_mask', 'saddr_daddr_rate_name', 'uid', 'gid', 'szone', 'dzone', 'new_szone', 'new_dzone', 'counter', 'helper', 'log', 'log_level', 'nft', 'mss', 'template', 'queue', 'tproxy', 'mark_set', 'mark_match', 'priority_set', 'priority_match', 'dscp', 'cgroup', 'ct_status', 'time', ): keyword = item ret[keyword] = ret[keyword] or '' if item == 'queue': # statement and start keyword rule_item_only_one(ret, 'statement', item) ret['statement'] = item if item not in ('counter', 'log', 'log_level'): ret['plain'] = False # More parameters for keyword elif keyword: if ret[keyword]: ret[keyword] += ' ' ret[keyword] += item # Unknown word after non-start keyword else: fail(f'{fileline}Can\'t parse line: {" ".join(line)}') # Use no-op statement "continue" for plain "log" or "counter" rule, # everything else defaults to "accept". Also mark them as -conntrack # so that they really log/count everything. if ret['plain']: ret['statement'] = 'continue' ret['after_conntrack'] = False verify_rule_sanity(ret, fileline) return ret def parse_config_rules(config): """Parse "zone-zone" rules from config. All other sections must be already parsed and removed from config. """ rules = {} for section, lines in config.items(): if section.startswith('_') or section in ('resolve', 'iplist'): continue try: szone, dzone = section.split('-') except ValueError: fail(f'{config["_section_line"][section]}Unknown section: ' f'{section}') rules[(szone, dzone)] = [parse_rule_line(line) for line in lines] return rules def filter_any_zonelist(rule, srcdst): """Parse rule[szone] list and return pos/neg boolean + zonelist.""" if not rule[srcdst]: return False, [] # "not in empty list" == everything inside = True zonelist = [] for item in rule[srcdst].split(): if item.startswith('-'): if inside and zonelist: fail(f'{rule["fileline"]}Can\'t mix "+" and "-" items: ' f'{rule[srcdst]}') inside = False zonelist.append(item[1:]) else: if not inside: fail(f'{rule["fileline"]}Can\'t mix "+" and "-" items: ' f'{rule[srcdst]}') zonelist.append(item) return inside, zonelist def insert_single_any(any_rules, rules, szone, dzone): """Insert single any_rules to rules[(szone, dzone)].""" if szone == dzone == CONFIG['localhost_zone']: return # Don't insert to localhost-localhost # Filter out "szone -public" when adding to zone "public-xxx" filtered = [] for rule in any_rules: inside, zonelist = filter_any_zonelist(rule, 'szone') if (szone in zonelist) != inside: continue inside, zonelist = filter_any_zonelist(rule, 'dzone') if (dzone in zonelist) != inside: continue filtered.append(rule) # Insert filtered rules to beginning if filtered: rules[(szone, dzone)] = filtered + rules.get((szone, dzone), []) def insert_any_zones(zones, rules): """Insert "any-zone", "zone-any" and "any-any" rules to "zone-zone" rules. These are inserted to beginning of zone-zone rules. """ for zone in zones: any_rules = rules.pop(('any', zone), []) # any-zone for szone in zones: insert_single_any(any_rules, rules, szone, zone) any_rules = rules.pop((zone, 'any'), []) # zone-any for dzone in zones: insert_single_any(any_rules, rules, zone, dzone) any_rules = rules.pop(('any', 'any'), []) # any-any for szone in zones: for dzone in zones: insert_single_any(any_rules, rules, szone, dzone) def verify_config(config, zones, rules): """Verify config data.""" if not zones: fail('No zones defined in section zone{}') localhost = CONFIG['localhost_zone'] if localhost not in zones: zones[localhost] = {'interface': []} warning(f'{config["_section_line"]["zone"]}Zone "{localhost}" ' f'is missing from zone{{}}, adding it') if zones[localhost]['interface']: fail(f'{config["_section_line"]["zone"]}Zone "{localhost}" has ' f'interfaces "{" ".join(zones[localhost]["interface"])}", ' f'it must be empty') if CONFIG['dbus_zone'] not in zones: warning(f'Config option dbus_zone value ' f'"{CONFIG["dbus_zone"]}" is missing from zone{{}}') # All zone-zone pairs must be known for szone, dzone in rules: if szone not in zones or dzone not in zones: fileline = config['_section_line'][f'{szone}-{dzone}'] fail(f'{fileline}Unknown zone-zone: {szone}-{dzone}') # Make sure all zone-zone pairs are defined. They are needed for # "ct established" return packets and for dynamic interface-to-zone # binding via D-Bus. # Add final rule to all zone-zone pairs, even if there already is # one. It will be optimized out later. for szone in zones: for dzone in zones: if (szone, dzone) not in rules: rules[(szone, dzone)] = [] if szone == dzone == localhost: rule = ['accept'] # localhost-localhost is accept elif szone == localhost: rule = ['reject', 'log'] # localhost-foo is reject else: rule = ['drop', 'log'] # everything else is drop rules[(szone, dzone)].append(parse_rule_line(('', rule))) def output_rate_names(rules): """Output empty saddr_rate sets to ruleset.""" counter = 1 already_added = set() for rulelist in rules.values(): for rule in rulelist: for rate in ('saddr_rate', 'daddr_rate', 'saddr_daddr_rate'): if not rule[rate]: continue # Rule with rate found. It can be pre-named or anonymous. setname = rule[f'{rate}_name'] if setname in already_added: continue # Pre-named and already added if not setname: # Anonymous - invent a name for it setname = rule[f'{rate}_name'] = f'_rate_set_{counter}' counter += 1 already_added.add(setname) # Output empty sets for IPv4 and IPv6. These will have # one minute timeout. for ipv in (4, 6): out(f'set {setname}_{ipv} {{') if rate == 'saddr_daddr_rate': out(f'type ipv{ipv}_addr . ipv{ipv}_addr') else: out(f'type ipv{ipv}_addr') out(f'size {CONFIG["set_size"]}') if rule[rate].startswith('ct '): out('flags dynamic') else: timeout = '1h' if 'hour' in rule[rate] else '1m' out('flags dynamic,timeout') out(f'timeout {timeout}') out('}') def suffix_mask(value, compare): """Convert suffix mask "IP/-mask" to nft.""" if not compare: compare = '== ' addr, mask = value.split('/-') mask = hex(pow(2, int(mask)) - 1)[2:] # As long hex splitted = '' # Convert to ":1234" parts while mask: splitted = f':{mask[-4:]}{splitted}' mask = mask[:-4] return f'& :{splitted} {compare}{addr}' def single_or_set(data, fileline='', quote=False): """Convert data to single item or set if multiple values.""" # Convert to list if isinstance(data, list): values = data else: values = data.split() # Handle negative: add "!=" to final rule neg = '' for index, value in enumerate(values): if value.startswith('-'): if index and not neg: fail(f'{fileline}Can\'t mix "+" and "-" items: ' f'{" ".join(values)}') neg = '!= ' elif neg: fail(f'{fileline}Can\'t mix "+" and "-" items: {" ".join(values)}') if neg: values[index] = value[1:] # Quote interface names and similar items. "inet" is a reserved word # but can also be an interface name. It must be quoted. if quote: values = [value if value.isnumeric() else f'"{value}"' for value in values] # Single item if len(values) == 1 and ' ' not in values[0]: if '/-' in values[0]: return suffix_mask(values[0], neg) return neg + values[0] # nft doesn't support "saddr { @foo, @bar }" if any(value.startswith('@') for value in values): fail(f'{fileline}Only single @list can be used: {" ".join(values)}') # Multiple, use "{set}" return f'{neg}{{ {", ".join(sorted(set(values)))} }}' def netmask_to_and(masklist, ipv, fileline): """Parse "masklist 24 56" rule and return "and 255.255.255.0 " string. First value is mask for IPv4 and second is for IPv6. """ if not masklist: return '' masks = [int(item) for item in masklist.split() if item.isnumeric()] if len(masks) != 2 or masks[0] > 32 or masks[1] > 128: fail(f'{fileline}Invalid rate_mask: {masklist}') if ipv == 4: ipaddr = ipaddress.IPv4Network(f'0.0.0.0/{masks[0]}') return f'and {ipaddr.netmask} ' ipaddr = ipaddress.IPv6Network(f'::/{masks[1]}') return f'and {ipaddr.netmask} ' def limit_rate_or_ct(rate): """Return "limit rate x" or "ct count x" according to rate.""" if rate.startswith('ct '): return f'{rate} ' return f'limit rate {rate} ' def rule_rate_limit(rule, ipv): """Return rule's rate limits as nft update-command.""" if rule['global_rate']: return limit_rate_or_ct(rule['global_rate']) ret = '' for rate in ('saddr_rate', 'daddr_rate', 'saddr_daddr_rate'): rate_limit = rule[rate] if not rate_limit: continue rate_name = rule[f'{rate}_name'] rate_mask = rule[f'{rate}_mask'] # "update @foo { ip saddr " ret += 'add' if rate_limit.startswith('ct ') else 'update' ret += f' @{rate_name}_{ipv} {{ ' if 'saddr' in rate: ret += f'{"ip" if ipv == 4 else "ip6"} saddr ' ret += netmask_to_and(rate_mask, ipv, rule['fileline']) if rate == 'saddr_daddr_rate': ret += '. ' if 'daddr' in rate: ret += f'{"ip" if ipv == 4 else "ip6"} daddr ' ret += netmask_to_and(rate_mask, ipv, rule['fileline']) # "limit rate 3/second } " ret += f'{limit_rate_or_ct(rate_limit)}}} ' return ret def mark_set_argument(rule): """Convert "x" to "x", "x/y" to "mark and ~y or x".""" value = rule['mark_set'] x_y = value.split('/') if len(x_y) > 2: fail(f'{rule["fileline"]}Invalid "mark_set" value: {value}') if len(x_y) == 1: return value # Convert "/0xff00" to "/0xffff00ff" so that same mask is used in # set and match operations in config. Nftables requires 0xffff00ff. try: mask = int(x_y[1], 0) # dec or hex except ValueError: fail(f'{rule["fileline"]}Invalid "mark_set" value: {value}') return f'meta mark & {hex(0xffffffff ^ mask)} | {x_y[0]}' def mark_match(rule): """Convert "x" to "== x", "x/y" to "and y == x". Negative "-x" can also be used to get "!= x". """ value = rule['mark_match'] if not value: return '' check = '==' if value.startswith('-'): check = '!=' value = value[1:] x_y = value.split('/') if len(x_y) > 2: fail(f'{rule["fileline"]}Invalid "mark_match" value: ' f'{rule["mark_match"]}') if len(x_y) == 1: return f'meta mark {check} {value} ' return f'meta mark & {x_y[1]} {check} {x_y[0]} ' def time_only_one(rule, oldvalue, newvalue): """Return newvalue if oldvalue is not set, else fail.""" if oldvalue or not newvalue: fail(f'{rule["fileline"]}Invalid "time" value: {rule["time"]}') return newvalue def time_match_single(rule, compare, value): """Return single time compare+value as nft.""" if not compare and not value: return '' time_only_one(rule, None, value) # Check that value is set # Parse value to day/hour/time wday = None hour = None date = None for item in value: if item.lower() in ('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'): wday = time_only_one(rule, wday, item.capitalize()) elif ( re.fullmatch(r'\d\d:\d\d(:\d\d)?', item) or # hh:mm:ss, hh:mm re.fullmatch(r'\d\d:\d\d-\d\d:\d\d', item) # hh:mm-hh:mm ): hour = time_only_one(rule, hour, item) elif re.fullmatch(r'\d\d\d\d-\d\d-\d\d', item): # yyyy-mm-dd date = time_only_one(rule, date, item) else: fail(f'{rule["fileline"]}Invalid "time" value: {rule["time"]}') # Output as nft if date and hour: # Combine if both set date += f' {hour}' hour = None ret = '' if wday: ret += f'day {compare}"{wday}" ' if date: ret += f'time {compare}"{date}" ' if hour: if '-' in hour: # hh:mm-hh:mm may not be in ", others must ret += f'hour {compare}{hour} ' else: ret += f'hour {compare}"{hour}" ' return ret def time_match(rule): """Convert "time Saturday" to nft, supporting time/day/hour.""" if not rule['time']: return '' compare = '' value = [] ret = '' for item in rule['time'].split(): if item in ('==', '!=', '<', '>', '<=', '>='): ret += time_match_single(rule, compare, value) compare = '' if item == '==' else f'{item} ' value = [] else: value.append(item) ret += time_match_single(rule, compare, value) return ret def cgroup_match(rule): """Parse cgroup to nft.""" cgroup = rule['cgroup'] if not cgroup: return '' # cgroupv2: non-numeric, single value only if not re.match(r'[-0-9]', cgroup[0]) and ' ' not in cgroup: level = cgroup.count('/') + 1 return f'socket cgroupv2 level {level} "{cgroup}" ' # cgroup return f'meta cgroup {single_or_set(cgroup, rule["fileline"])} ' def rule_statement(szone, dzone, rule, ipv, *, force_statement=None, do_lograte=True): """Return rule's rate, log and statement as nft command.""" # pylint: disable=too-many-arguments # Map internal statement to nft statement if rule['mss']: mss = 'rt mtu' if rule['mss'] == 'pmtu' else rule['mss'] statement = f'tcp flags syn tcp option maxseg size set {mss}' else: statement = force_statement or { 'snat_prefix': 'snat', 'dnat_prefix': 'dnat', }.get(rule['statement'], rule['statement']) # queue can have optional flags if statement == 'queue' and rule[statement]: statement = f'{statement} {rule[statement]}' # Rate, counter and log goes before statement prefix = rule_rate_limit(rule, ipv) # "counter" in single rule line adds counters to it. # # "foomuuri { counter xxx }" can be used to add counters to all rules: # yes - add to all rules in all zone-zone # zone-zone - add to all rules in single zone-zone # zone-any - add to all rules in all zone-* # any-zone - add to all rules in all *-zone # Multiple zone-pairs can be defined counterlist = CONFIG['counter'].split() if not rule['nft'] and ( # pylint: disable=too-many-boolean-expressions rule['counter'] is not None or # "counter" in this rule 'yes' in counterlist or # global "yes" f'{szone}-{dzone}' in counterlist or # matching zone-zone f'{szone}-any' in counterlist or f'any-{dzone}' in counterlist ): prefix += 'counter ' if rule['counter']: prefix += f'name "{rule["counter"]}" ' # "log" in rule will log all packets matching this rule. This is usually # used in final "drop log" rule. Default log prefix is # "zone-zone STATEMENT". if rule['log'] is not None or rule['log_level'] is not None: log_prefix = CONFIG['log_prefix'] if rule['log']: if rule['log'].startswith('+ '): log_prefix += rule['log'][2:] else: log_prefix = rule['log'] log_prefix = log_prefix.replace('$(szone)', szone) log_prefix = log_prefix.replace('$(dzone)', dzone) log_prefix = log_prefix.replace('$(statement)', statement.split()[0].upper()) log_level = (CONFIG['log_level'] if rule['log_level'] is None else rule['log_level']) log_nft = f'log prefix "{log_prefix} " {log_level}' if do_lograte and CONFIG['log_rate']: # Limit maximum amount of logging to "foomuuri { log_rate }". # This is important to avoid overlogging (DoS or filesystem full). rate = (f'update @_lograte_set_{ipv} ' f'{{ {"ip" if ipv == 4 else "ip6"} saddr ' f'limit rate {CONFIG["log_rate"]} }} ') if statement == 'continue': return f'{prefix}{rate}{log_nft}' logname = f'lograte_{len(LOGRATES) + 1}' LOGRATES[logname] = (f'{rate}{log_nft}', statement) return f'{prefix}jump {logname}' prefix += f'{log_nft} ' return prefix + statement def output_icmp(szone, dzone, rules, ipv): """Find and parse icmp and icmpv6 rules. These must be handled before ct as "ct established" would accept ping floods. Default is to drop pings. """ has_ping_rule = False has_match_all = False if ipv == 4: icmp = 'icmp' ping = '8' else: icmp = 'icmpv6' ping = '128' for rule in rules: if rule['protocol'] != icmp: continue match = rule_matchers(rule, ipv, skip_icmp=False) if match is None: continue statement = rule_statement(szone, dzone, rule, ipv) proto_ports = parse_protocol_ports(rule, ipv, skip_icmp=False) out(f'{proto_ports}{match}{statement}') if ( rule['dport'] and ping not in rule['dport'].split() and 'echo-request' not in rule['dport'].split() ): continue # Continue to next rule if this wasn't ping or match all # This rule was for ping, usually accepting non-flood pings. Add # explicit rule to drop overflow and all other pings. has_ping_rule = True if (match + statement).startswith( ('accept', 'drop', 'reject', 'jump', 'queue')): has_match_all = True elif has_match_all: # Specific ping rule after match-all rule warning(f'{rule["fileline"]}Unreachable ping rule') # Overflow-pings must be dropped before ct if has_ping_rule and not has_match_all: out(f'{icmp} type echo-request drop') # Allow needed icmp out(f'jump allow_icmp_{ipv}') def parse_iplist(rule, direction, ipv): """Parse IP address list in rule[direction] to nft rule.""" iplist = rule[direction] if not iplist: return '' ips = [] for item in iplist.split(): if item.startswith(('@', '-@')): # "@foo" to "@foo_4" ips.append(f'{item}_{ipv}') else: ipv_addr = is_ip_address(item) if ipv_addr == ipv: # Address for this ipv - add to list ips.append(item) elif not ipv_addr: # Invalid IP address fail(f'{rule["fileline"]}Invalid IP address "{item}" in: ' f'{iplist}') # No matching addresses for this ipv family if not ips: raise ValueError # Return "ip saddr 10.2.3.4 " string return (f'{"ip" if ipv == 4 else "ip6"} {direction} ' f'{single_or_set(ips, rule["fileline"])} ') def parse_maclist(rule, direction): """Parse MAC address list in rule[direction] to nft rule.""" maclist = rule[direction] if not maclist: return '' macs = [] for item in maclist.split(): item = item.lower() if re.match(r'^(-)?([0-9a-f]{2}:){5}[0-9a-f]{2}$', item): macs.append(item) else: fail(f'{rule["fileline"]}Invalid MAC address "{item}" in: ' f'{maclist}') # Return "ether saddr 0a:00:27:00:00:00 " string return f'ether {direction[4:]} {single_or_set(macs, rule["fileline"])} ' def parse_interface_names(rule): """Parse iifname/oifname to nft rule.""" ret = '' for key in ('iifname', 'oifname'): if not rule[key]: continue ret += f'{key} ' ret += single_or_set(rule[key], rule['fileline'], quote=True) ret += ' ' return ret def parse_protocol_ports(rule, ipv, skip_icmp=True): """Parse tcp/udp sport/dport to nft rule. This can also handle rules like: - "tcp" without dport to nft "protocol tcp" - "protocol esp" to nft "protocol esp" - "protocol esp 123" to nft "esp spi 123" - "protocol vlan 123" to nft "vlan id 123" """ protocol = rule['protocol'] if not protocol or (skip_icmp and protocol in ('icmp', 'icmpv6')): # Protocol is empty for rules like "drop log" and "dnat" # icmp is handled in output_icmp() return '' if ipv == 6 and protocol == 'igmp': return None # IPv6 uses Multicast Listener Discovery ICMP ports = '' for key in ('sport', 'dport'): if rule[key]: protokey = key if key == 'dport': # Change "dport" to protocol-specific key protokey = { 'ip': 'protocol', 'ip6': 'nexthdr', 'ah': 'spi', 'esp': 'spi', 'comp': 'nexthdr', 'icmp': 'type', 'icmpv6': 'type', 'dst': 'nexthdr', 'frag': 'nexthdr', 'hbh': 'nexthdr', 'mh': 'nexthdr', 'rt': 'nexthdr', 'vlan': 'id', 'arp': 'htype', }.get(protocol, protokey) ports += (f'{protocol} {protokey} ' f'{single_or_set(rule[key], rule["fileline"])} ') if ports: return ports return f'{"ip protocol" if ipv == 4 else "ip6 nexthdr"} {protocol} ' def parse_iplist_to_single_ip(rule, key, value, ipv): """Parse rule's iplist to single ipaddress, or None.""" target = [] for check in value.split(): if check.count(':') == 1 and is_ip_address(check.split(':')[0]) == 4: check_ipv = 4 # IPv4 address with port elif ( check.startswith('[') and ']:' in check and is_ip_address(check[1:].split(']:')[0]) == 6 ): check_ipv = 6 # IPv6 address with port else: check_ipv = is_ip_address(check) if check_ipv == 0: fail(f'{rule["fileline"]}Invalid IP address in ' f'"{key}" target: {check}') if check_ipv == ipv: target.append(check) if not target: # Nothing found for this ipv, don't generate rule return None if len(target) > 1: fail(f'{rule["fileline"]}Multiple "{key}" targets: {" ".join(target)}') return target[0] def parse_to(rule, ipv): """Parse snat/dnat "to" rule to nft rule.""" if not rule['to']: return '' if rule['statement'] == 'queue': # "to 3", "to 1-3", "to numgen", ... return f' to {rule["to"]}' # "to" can be IPv4, IPv6 or both, find correct one target = parse_iplist_to_single_ip(rule, rule['statement'], rule['to'], ipv) if not target: return None # Generate rule to_text = 'to' if rule['statement'] in ('snat_prefix', 'dnat_prefix'): to_text = 'prefix to' return f' {"ip" if ipv == 4 else "ip6"} {to_text} {target}' def rule_matchers(rule, ipv, *, cast=None, skip_options=True, skip_icmp=True): """Parse rule's matchers to nft rule. Return value "None" is "no match, skip rule". """ # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-return-statements if cast is not None and rule['cast'] != cast: return None # multi/broadcast doesn't match if ipv == 6 and rule['cast'] == 'broadcast': return None # broadcast is ipv4 only if skip_icmp and rule['protocol'] in ('icmp', 'icmpv6'): return None if skip_options and rule['mss']: # Handled in output_options() return None # IPv4/6 specific rule? if ipv == 4 and rule['ipv6'] and not rule['ipv4']: return None if ipv == 6 and rule['ipv4'] and not rule['ipv6']: return None # Convert matchers to nft castmeta = '' if cast and rule['cast'] != 'unicast': castmeta = f'meta pkttype {rule["cast"]} ' ipsecmeta = '' if rule['sipsec']: ipsecmeta += f'meta ipsec {rule["sipsec"]} ' if rule['dipsec']: ipsecmeta += f'rt ipsec {rule["dipsec"]} ' ct_status = '' if rule['ct_status']: ct_status = f'ct status {rule["ct_status"]} ' priority_match = '' if rule['priority_match']: priority_match = f'meta priority "{rule["priority_match"]}" ' uid = '' if rule['uid']: uid = (f'meta skuid ' f'{single_or_set(rule["uid"], rule["fileline"], quote=True)} ') gid = '' if rule['gid']: gid = (f'meta skgid ' f'{single_or_set(rule["gid"], rule["fileline"], quote=True)} ') meta_set = '' if rule['mark_set']: meta_set += (f'meta mark set {mark_set_argument(rule)} ' f'ct mark set meta mark ') if rule['priority_set']: meta_set += f'meta priority set "{rule["priority_set"]}" ' tproxy = '' if rule['tproxy']: tproxy_to = parse_iplist_to_single_ip(rule, 'tproxy', rule['tproxy'], ipv) if not tproxy_to: return None tproxy = f'tproxy {"ip" if ipv == 4 else "ip6"} to {tproxy_to} ' dscp = '' if rule['dscp']: dscp = (f'{"ip" if ipv == 4 else "ip6"} dscp ' f'{single_or_set(rule["dscp"], rule["fileline"])} ') ifname = parse_interface_names(rule) addrlist = '' try: addrlist += parse_iplist(rule, 'saddr', ipv) addrlist += parse_iplist(rule, 'daddr', ipv) addrlist += parse_maclist(rule, 'mac_saddr') addrlist += parse_maclist(rule, 'mac_daddr') except ValueError: return None proto_ports = parse_protocol_ports(rule, ipv) if proto_ports is None: return None # Return matcher string return (f'{ipsecmeta}{ct_status}{castmeta}{ifname}{addrlist}{proto_ports}' f'{cgroup_match(rule)}{time_match(rule)}{mark_match(rule)}' f'{priority_match}{dscp}{uid}{gid}{tproxy}{meta_set}') def output_cast(cast, szone, dzone, rules, ipv, *, after_conntrack=True): """Output all uni/multi/broadcast rules for single zone-zone.""" # pylint: disable=too-many-arguments has_mark_restore = False for rule in rules: if rule['after_conntrack'] != after_conntrack: continue match = rule_matchers(rule, ipv, cast=cast) if match is None: continue if match == '' and cast is None and rule['cast'] != 'unicast': continue # Don't convert "multicast accept" to nft "accept" statement = rule_statement(szone, dzone, rule, ipv, force_statement=rule['nft']) # Automatically restore mark from conntrack before mark match or set # is used. Mark set needs it too for OR-operations. if not has_mark_restore and (rule['mark_match'] or rule['mark_set']): has_mark_restore = True out('meta mark set ct mark') out(f'{match}{statement}') # Does this rule need kernel helper? if rule['helper']: if rule['helper'].count('-') != 1: fail(f'{rule["fileline"]}Invalid helper name: ' f'{rule["helper"]}') HELPERS.append((rule['helper'], rule['protocol'], rule['dport'])) kernelname = rule['helper'].split('-')[0].replace('_', '-') out(f'ct helper \"{kernelname}\" {rule["statement"]}') def output_zonemap(zonemap, szone, dzone, ipv): """Output zonemap{} rules for this szone-dzone.""" for rule in zonemap: if rule['szone'] and szone not in rule['szone'].split(): continue if rule['dzone'] and dzone not in rule['dzone'].split(): continue new_szone = rule['new_szone'] or szone new_dzone = rule['new_dzone'] or dzone if new_szone == szone and new_dzone == dzone: continue match = rule_matchers(rule, ipv) if match is None: continue out(f'{match}jump {new_szone}-{new_dzone}_{ipv}') def output_options(szone, dzone, rules, ipv): """Output "mss" etc options as first rules.""" for rule in rules: if rule['mss']: match = rule_matchers(rule, ipv, skip_options=False) if match is None: continue statement = rule_statement(szone, dzone, rule, ipv, do_lograte=False) out(f'{match}{statement}') def output_zone(zonemap, szone, dzone, rules, ipv): """Output single zone-zone_ipv4 nft chain.""" # Header + zonemap jumps + options out(f'chain {szone}-{dzone}_{ipv} {{') output_zonemap(zonemap, szone, dzone, ipv) output_options(szone, dzone, rules, ipv) # Rules with "-conntrack" or plain "log"/"counter" rule. Output these # before conntrack and icmp so that they see all traffic. output_cast(None, szone, dzone, rules, ipv, after_conntrack=False) # ICMP is special, keep it before ct output_icmp(szone, dzone, rules, ipv) # Connection tracking out('ct state vmap {') out('established : accept,') out('related : accept,') out('invalid : jump invalid_drop,') out(f'new : jump smurfs_{ipv},') out(f'untracked : jump smurfs_{ipv}') out('}') # Allow outgoing IGMP multicast membership reports and incoming IGMP # multicast query. output_chain = szone == CONFIG['localhost_zone'] input_chain = dzone == CONFIG['localhost_zone'] if ipv == 4 and output_chain and not input_chain: out('ip protocol igmp ip daddr 224.0.0.22 accept') # membership report if ipv == 4 and input_chain and not output_chain: out('ip protocol igmp ip daddr 224.0.0.1 accept') # query # Broadcast and multicast. # # "meta pkttype" works only for incoming packets so skip these if # szone=localhost (outgoing). # # "meta pkttype" works also for forwarding packets so write those rules # too (see below for zonemap reason). Multicast can't really be forwarded # as it is local to ip/netmask. Proxying is ok, where software listens one # interface and writes it to another. if not output_chain: output_cast('multicast', szone, dzone, rules, ipv) output_cast('broadcast', szone, dzone, rules, ipv) if ipv == 4: out('meta pkttype { broadcast, multicast } drop') else: out('meta pkttype multicast drop') # Unicast. # # Broadcast/multicast is already handled above for incoming packets so # output only unicast here for incoming. # # For forward/output add multicast rules without multicast matcher. This # means that for forward packets both with and without "meta pkttype" # rules will be outputted. This is needed for complex zonemap mangling # where szone=myservice might actually be from localhost. output_cast('unicast' if input_chain else None, szone, dzone, rules, ipv) out('}') def output_zone_vmaps(zones, rules): """Output interface verdict maps to jump to correct zone-zone.""" # pylint: disable=too-many-branches # Vmap must have interval-flag if there is wildcard-interface. localhost = CONFIG['localhost_zone'] has_wildcard = False for value in zones.values(): for interface in value['interface']: if '*' in interface: has_wildcard = True # Incoming zones out('map input_zones {') out('type ifname : verdict') if has_wildcard: out('flags interval') out('elements = {') out('"lo" : accept,') for zone, value in zones.items(): for interface in value['interface']: out(f'"{interface}" : jump {zone}-{localhost},') out('}') out('}') # Outgoing zones out('map output_zones {') out('type ifname : verdict') if has_wildcard: out('flags interval') out('elements = {') if len(rules[(localhost, localhost)]) > 1: # Jump to lo-lo if it has rules out(f'"lo" : jump {localhost}-{localhost},') else: out('"lo" : accept,') for zone, value in zones.items(): for interface in value['interface']: out(f'"{interface}" : jump {localhost}-{zone},') out('}') out('}') # Forwarding zones out('map forward_zones {') out('type ifname . ifname : verdict') if has_wildcard: out('flags interval') out('elements = {') out('"lo" . "lo" : accept,') for szone, svalue in zones.items(): for dzone, dvalue in zones.items(): for sinterface in svalue['interface']: for dinterface in dvalue['interface']: out(f'"{sinterface}" . "{dinterface}" : ' f'jump {szone}-{dzone},') out('}') out('}') def output_zone2zone_rules(rules, zonemap): """Output all zone-zone rules for both IPv4 and IPv6.""" for szone, dzone in rules: # Split to zone-zone_4 and zone-zone_6 out(f'chain {szone}-{dzone} {{') out('meta nfproto vmap {') out(f'ipv4 : jump {szone}-{dzone}_4,') out(f'ipv6 : jump {szone}-{dzone}_6') out('}') out('}') # IPv4 and IPv6 chains output_zone(zonemap, szone, dzone, rules[(szone, dzone)], 4) output_zone(zonemap, szone, dzone, rules[(szone, dzone)], 6) def output_rule_section_no_header(rules, section): """Output snat, dnat, prerouting, postrouting, etc. rules inside chain.""" if not rules: return has_mark_restore = False ip_merger = set() for rule in rules: for ipv in (4, 6): to_rule = parse_to(rule, ipv) if to_rule is None: continue match = rule_matchers(rule, ipv, skip_options=False) if match is None: continue statement = rule_statement(section.upper(), '', rule, ipv, force_statement=rule['nft'], do_lograte=False) # Automatically restore mark from conntrack when needed if ( not has_mark_restore and (rule['mark_match'] or rule['mark_set']) ): has_mark_restore = True out('meta mark set ct mark') # There are no separate IPv4/IPv6 chains so merge possible rules full_rule = f'{match}{statement}{to_rule}' if full_rule in ip_merger: continue ip_merger.add(full_rule) out(full_rule) def output_special_chains(chain_rules): """Output snat, dnat, prerouting postrouting, etc. chain + rules.""" for prefix in ('snat', 'dnat', 'prerouting', 'postrouting', 'forward', 'input', 'output'): for (section, ftype, priority), rules in chain_rules.items(): if prefix != section: continue # Output-chain must restore mark from conntrack if mark_set was # used for locally generated packets (usually in multiple ISP's # prerouting). # # Simply restore mark if mark_set was used in any chain. For that # reason output-chain should be outputted last. need_mark_restore = section == 'output' and any( 'ct mark set' in line for line in OUT) if not rules and not need_mark_restore: continue hook_chain = { 'snat': 'postrouting', 'dnat': 'prerouting', }.get(section, section) hook_nft = f'type {ftype} hook {hook_chain} priority {priority}' chain_name = f'{ftype}_{hook_chain}_{priority}' chain_name = chain_name.replace(CONFIG['priority_offset'], '') chain_name = chain_name.replace('+', '') chain_name = chain_name.replace(' ', '_') chain_name = chain_name.replace('__', '_') out(f'chain {chain_name} {{') out(hook_nft) if need_mark_restore: out('meta mark set ct mark') output_rule_section_no_header(rules, section) out('}') def output_static_chain_logging(chain, statement): """Output logging rules for input/output/forward/invalid/smurfs chains.""" lograte = CONFIG[f'log_{chain}'] # Enabled in foomuuri{} if lograte.lower() == 'no': return if lograte.lower() == 'yes': # "yes" means use standard log rate lograte = CONFIG['log_rate'] flags = ' flags ip options' if chain == 'invalid' else '' flags += f' {CONFIG["log_level"]}' chain = chain.upper() statement = statement.upper() if not lograte: out(f'log prefix "{chain} {statement} "{flags}') return out(f'update @_lograte_set_4 {{ ip saddr limit rate {lograte} }} ' f'log prefix "{chain} {statement} "{flags}') out(f'update @_lograte_set_6 {{ ip6 saddr limit rate {lograte} }} ' f'log prefix "{chain} {statement} "{flags}') def output_header(chain_rules): """Output generic nft header.""" # Delete current foomuuri table and add new out('table inet foomuuri') out('delete table inet foomuuri') out('') out('table inet foomuuri {') out('') # Insert include files for filename in (find_config_files(CONFIG['_share_dir'], '*.nft') + find_config_files(CONFIG['_etc_dir'], '*.nft')): try: lines = filename.read_text('utf-8').splitlines() except PermissionError as error: fail(f'File {filename}: Can\'t read: {error}') for line in lines: line = line.strip() if line: out(line) # Logging chains for chain in ('invalid', 'smurfs', 'rpfilter'): out(f'chain {chain}_drop {{') output_rule_section_no_header(chain_rules.get((chain, None, None), []), chain) if chain == 'rpfilter': out('udp sport 67 udp dport 68 return') output_static_chain_logging(chain, 'drop') out('drop') out('}') # input/output/forward jump chains out('chain input {') out(f'type filter hook input priority filter{CONFIG["priority_offset"]}') out('iifname vmap @input_zones') output_static_chain_logging('input', 'drop') out('drop') out('}') out('chain output {') out(f'type filter hook output priority filter{CONFIG["priority_offset"]}') out('oifname vmap @output_zones') # IGMP membership report and IPv6 equivalent must be allowed here too as # D-Bus interface change event might not be processed yet. out('ip protocol igmp ip daddr 224.0.0.22 accept') out('ip6 saddr :: icmpv6 type mld2-listener-report accept') output_static_chain_logging('output', 'reject') out('reject with icmpx admin-prohibited') out('}') out('chain forward {') out(f'type filter hook forward priority filter{CONFIG["priority_offset"]}') out('iifname . oifname vmap @forward_zones') output_static_chain_logging('forward', 'drop') out('drop') out('}') MERGES = [ # Preferred order for rules 'meta pkttype multicast udp dport', 'meta pkttype broadcast udp dport', 'udp dport', 'udp sport', 'tcp dport', 'tcp sport', 'ct helper', ] def merge_accepts(accepts, linenum): """Sort and merge found accept rules.""" merge = {key: [] for key in MERGES[::-1]} ret = 0 for accept in accepts: for key, ports in merge.items(): regex = f'^{key} (\\{{ )?([-\\d, ]+)( \\}})? accept$' match = re.match(regex, accept) if match: # Add "22" from "tcp dport 22 accept" to merged ports.append(match.group(2)) break else: # Can't merge, output as is OUT.insert(linenum + ret, accept) ret += 1 # Output merged for key, ports in merge.items(): if ports: OUT.insert(linenum, f'{key} {single_or_set(ports)} accept') ret += 1 return ret def optimize_accepts(): """Optimize ruleset accepts. This will change multiple accepts to single accept using set. """ accepts = [] linenum = 0 while linenum < len(OUT): line = OUT[linenum] if line == 'continue': # No-op line generated by plain "counter" del OUT[linenum] elif ( line.endswith(' accept') and line.startswith(tuple(MERGES) + ('ip ', 'ip6 ')) ): accepts.append(line) del OUT[linenum] else: linenum += merge_accepts(accepts, linenum) + 1 accepts = [] def optimize_jumps(): """Optimize lograte jumps in ruleset. This will change zone-zone's final "drop log" rule to optimized version. """ linenum = 0 while linenum < len(OUT): line = OUT[linenum] if line.startswith('jump lograte_'): logname = line.split()[1] log_nft, statement = LOGRATES.pop(logname) OUT[linenum] = log_nft OUT.insert(linenum + 1, statement) linenum += 1 def optimize_final_rules(): """Remove unreachable rules from chain after "drop" without matcher.""" linenum = 0 while linenum < len(OUT): if OUT[linenum].startswith(('accept', 'drop', 'reject', 'queue')): while OUT[linenum + 1] != '}': del OUT[linenum + 1] linenum += 1 def output_logrates(): """Output non-optimized lograte entries as chains.""" for logname, (log_nft, statement) in LOGRATES.items(): out(f'chain {logname} {{') out(log_nft) out(statement) out('}') # Output empty lograte sets used by "foomuuri { log_rate }". for ipv in (4, 6): out(f'set _lograte_set_{ipv} {{') out(f'type ipv{ipv}_addr') out(f'size {CONFIG["set_size"]}') out('flags dynamic,timeout') out('timeout 1m') out('}') def output_iplist_sets(iplist): """Output empty iplist{} sets.""" for name in sorted(iplist): for ipv in (4, 6): out(f'set {name[1:]}_{ipv} {{') out(f'type ipv{ipv}_addr') out('flags interval,timeout') out('auto-merge') out('}') def output_named_counters(rules, chain_rules): """Output named counters.""" # Collect all counter names names = set() for rulelist in list(rules.values()) + list(chain_rules.values()): for rule in rulelist: if rule['counter']: names.add(rule['counter']) # Output counters for name in sorted(names): out(f'counter {name} {{') out('}') def output_helpers(): """Output helpers.""" # Convert helper list to helper->proto->set(ports) dict helpers = {} for name, proto, ports in HELPERS: if name not in helpers: helpers[name] = {} if proto not in helpers[name]: helpers[name][proto] = set() for port in ports.split(): helpers[name][proto].add(port) if not helpers: return # Output "ct helper" lines for name, protos in helpers.items(): kernelname = name.split('-')[0].replace('_', '-') out(f'ct helper {name} {{') for proto in protos: out(f'type \"{kernelname}\" protocol {proto}') out('}') # Output prerouting out('chain helper {') out(f'type filter hook prerouting priority filter' f'{CONFIG["priority_offset"]}') for name, protos in helpers.items(): for proto, ports in protos.items(): out(f'{proto} dport {single_or_set(" ".join(ports))} ' f'ct helper set \"{name}\"') out('}') def output_rpfilter(): """Prerouting chain to check rpfilter.""" if CONFIG['rpfilter'] == 'no': return out('chain rpfilter {') out(f'type filter hook prerouting priority filter' f'{CONFIG["priority_offset"]}') interfaces = '' if CONFIG['rpfilter'] != 'yes': # Specific interfaces? interfaces = (f'iifname ' f'{single_or_set(CONFIG["rpfilter"], quote=True)} ') out(f'{interfaces}fib saddr . mark . iif oif 0 meta ipsec missing ' f'jump rpfilter_drop') out('}') def output_footer(): """Output generic ruleset footer.""" out('}') def save_file(filename, lines): """Write lines to file.""" try: filename.unlink(missing_ok=True) if isinstance(lines, dict): filename.write_text(json.dumps(lines, indent=2, sort_keys=True) + '\n', 'utf-8') else: filename.write_text('\n'.join(lines) + '\n', 'utf-8') filename.chmod(0o600) except PermissionError as error: fail(f'File {filename}: Can\'t write: {error}') except FileNotFoundError: # Simultaneos saves and chmod gives error pass def env_cleanup(text): """Allow only letters and numbers in text for environment variable.""" # Convert ä->a as isalpha('ä') is true value = unicodedata.normalize('NFKD', text) value = value.encode('ASCII', 'ignore').decode('utf-8') # Remove non-alphanumeric chars return ''.join(char if char.isalnum() else '_' for char in value) def save_final(filename): """Save final ruleset to file.""" # Convert to indented lines indent = 0 lines = [] for line in OUT: if line.startswith('}'): indent -= 1 if line: line = '\t' * indent + line lines.append(line) if line == '\t}': lines.append('') if line.endswith('{'): indent += 1 # Save to "next" file save_file(filename, lines) def signal_childs(): """Signal foomuuri-dbus and foomuuri-monitor to reload.""" for child in ('dbus', 'monitor'): # Read pid filename = CONFIG['_run_dir'] / f'foomuuri-{child}.pid' try: pid = int(filename.read_text(encoding='utf-8')) except PermissionError as error: fail(f'File {filename}: Can\'t read: {error}') except (FileNotFoundError, ValueError): continue # Send reload-signal try: os.kill(pid, signal.SIGHUP) except OSError: pass def apply_final(): """Use final ruleset.""" # Check config if CONFIG['command'] == 'check': if CONFIG['root_power']: # "nft check" requires root ret = run_program_rc(CONFIG['_nft_bin'] + ['--check', '--file', CONFIG['_next_file']]) else: ret = 0 warning('Not running as "root", skipping "nft check"') if ret: fail(f'Nftables failed to check ruleset, error code {ret}', False) else: verbose('check success', 0) return ret # Run pre_start / pre_stop hook run_program_rc(CONFIG.get(f'pre_{CONFIG["command"]}')) # Load "next" ret = run_program_rc(CONFIG['_nft_bin'] + ['--file', CONFIG['_next_file']], print_output=False) # Check failure if ret: fail(f'Failed to load ruleset to nftables, error code {ret}', False) return 1 # Success. Rename "next" to "good", signal dbus to reload and run hook if CONFIG['command'] == 'start': CONFIG['_good_file'].unlink(missing_ok=True) CONFIG['_next_file'].rename(CONFIG['_good_file']) signal_childs() run_program_rc(CONFIG.get(f'post_{CONFIG["command"]}')) verbose(f'{CONFIG["command"]} success', 0) return 0 def command_start(): """Process "start" or "check" command.""" # Read full config config = minimal_config() zones = parse_config_zones(config) zonemap = parse_config_zonemap(config) iplist = parse_iplist_names(config) chain_rules = parse_config_special_chains(config) parse_config_groups(config, parse_config_targets(config)) parse_config_hook(config) rules = parse_config_rules(config) # Also verify for unknown sections insert_any_zones(zones, rules) verify_config(config, zones, rules) # Generate output output_header(chain_rules) output_rate_names(rules) output_zone_vmaps(zones, rules) output_zone2zone_rules(rules, zonemap) output_special_chains(chain_rules) optimize_jumps() optimize_final_rules() optimize_accepts() output_logrates() output_iplist_sets(iplist) output_named_counters(rules, chain_rules) output_helpers() output_rpfilter() output_footer() command_iplist_refresh(config, iplist) # Save known zones to file save_file(CONFIG['_zone_file'], zones.keys()) # Save and apply generated ruleset save_final(CONFIG['_next_file']) return apply_final() def command_stop(): """Process "stop" command. This will remove all foomuuri rules.""" config = minimal_config() # Needed for pre_stop and post_stop hooks parse_config_hook(config) out('table inet foomuuri') out('delete table inet foomuuri') save_final(CONFIG['_next_file']) return apply_final() def command_block(): """Load "block all traffic" ruleset.""" minimal_config() return run_program_rc(CONFIG['_nft_bin'] + [ '--file', CONFIG['_share_dir'] / 'block.fw']) def parse_active_interface_zone(): """Parse current interface->zone mapping from active nft ruleset.""" data = nft_json('list map inet foomuuri input_zones') if not data: return {} ret = {} for item in data['nftables']: if 'map' in item: for interface, rule in item['map']['elem']: if interface != 'lo': ret[interface] = rule['jump']['target'].split('-')[0] return ret def command_status(): """Print if Foomuuri is running, zone<->mapping, etc.""" # Get minimal config and interface status config = minimal_config() zones = parse_config_zones(config) zone_interface = parse_active_interface_zone() # Running ruleset = nft_json('list table inet foomuuri') if not ruleset: fail('Foomuuri is not running') print('Foomuuri is running') # D-Bus, Monitor for child in ('dbus', 'monitor'): filename = CONFIG['_run_dir'] / f'foomuuri-{child}.pid' try: pid = int(filename.read_text(encoding='utf-8')) os.kill(pid, 0) print(f'Foomuuri-{child} is running, PID {pid}') except (FileNotFoundError, ValueError, PermissionError, ProcessLookupError): print(f'Foomuuri-{child} is not running') # Zones if not zone_interface: print() print('Warning: There are no interfaces assigned to any zones') print() print('zone {') for zone in zones: interfaces = [interface for interface, int_zones in zone_interface.items() if zone == int_zones] print(f' {zone:15s} {" ".join(interfaces)}') print('}') return 0 class FoomuuriDbusException(dbus.DBusException): """Exception class for D-Bus interface.""" _dbus_error_name = 'fi.foobar.Foomuuri1.exception' class DbusCommon: """D-Bus server - Common Functions.""" zones = None def set_data(self, zones): """Save config data: zone list is static.""" self.zones = zones @staticmethod def clean_out(): """Remove all entries from current OUT[] variable.""" while OUT: del OUT[0] @staticmethod def apply_out(): """Apply current OUT commands.""" save_final(CONFIG['_dbus_file']) run_program_rc(CONFIG['_nft_bin'] + ['--file', CONFIG['_dbus_file']]) @staticmethod def remove_interface(interface_zone, interface): """Remove interface from all zones.""" # Get interface's current zone zone = interface_zone.get(interface) if not zone: return '' # Remove from input and output out(f'delete element inet foomuuri input_zones ' f'{{ "{interface}" : jump {zone}-{CONFIG["localhost_zone"]} }}') out(f'delete element inet foomuuri output_zones ' f'{{ "{interface}" : jump {CONFIG["localhost_zone"]}-{zone} }}') # Remove from forward for other, otherzone in interface_zone.items(): out(f'delete element inet foomuuri forward_zones ' f'{{ "{other}" . "{interface}" : jump {otherzone}-{zone} }}') if other != interface: out(f'delete element inet foomuuri forward_zones ' f'{{ "{interface}" . "{other}" : ' f'jump {zone}-{otherzone} }}') return zone @staticmethod def add_interface(interface_zone, interface, zone): """Add interface to zone. It must be already removed from others.""" # Add to input and output out(f'add element inet foomuuri input_zones ' f'{{ "{interface}" : jump {zone}-{CONFIG["localhost_zone"]} }}') out(f'add element inet foomuuri output_zones ' f'{{ "{interface}" : jump {CONFIG["localhost_zone"]}-{zone} }}') # Add to forward for other, otherzone in interface_zone.items(): if other != interface: out(f'add element inet foomuuri forward_zones ' f'{{ "{other}" . "{interface}" : ' f'jump {otherzone}-{zone} }}') out(f'add element inet foomuuri forward_zones ' f'{{ "{interface}" . "{other}" : ' f'jump {zone}-{otherzone} }}') out(f'add element inet foomuuri forward_zones ' f'{{ "{interface}" . "{interface}" : jump {zone}-{zone} }}') def change_interface_zone(self, interface, new_zone): """Change interface to new_zone, or delete if new_zone is empty.""" interface, new_zone = str(interface), str(new_zone) if new_zone and new_zone not in self.zones: warning(f'Zone "{new_zone}" is unknown') raise FoomuuriDbusException(f'Zone "{new_zone}" is unknown') if interface == 'lo': # Interface "lo" must stay in "localhost" zone. # Other interfaces can be added to "localhost" only if # "localhost-localhost" section is defined. if new_zone != CONFIG['localhost_zone']: warning(f'Can\'t change interface "lo" to zone "{new_zone}"') raise FoomuuriDbusException(f'Can\'t change interface "lo" ' f'to zone "{new_zone}"') return '', '' interface_zone = parse_active_interface_zone() self.clean_out() old_zone = self.remove_interface(interface_zone, interface) if new_zone: self.add_interface(interface_zone, interface, new_zone) self.apply_out() return old_zone, new_zone def parse_default_zone(self, interface, zone): """Return zone, or dbus_zone if empty.""" interface, zone = str(interface), str(zone) if zone: return zone # Fallback to zones section, or to foomuuri.dbus_zone for key, value in self.zones.items(): if interface in value['interface']: return key return CONFIG['dbus_zone'] def method_get_zones(self): """Get list of available zones. "localhost" can't have any interfaces so don't include it. """ return [name for name in self.zones if name != CONFIG['localhost_zone']] def method_remove_interface(self, zone, interface): """Remove interface from zone, or from all if zone is empty. This is currently always handled as "from all". """ verbose(f'Interface "{interface}" remove from zone "{zone}"', 0) return self.change_interface_zone(interface, '')[0] def method_add_interface(self, zone, interface): """Add interface to zone. There can be only one zone per interface so it will be removed from previous zone if needed. """ zone = self.parse_default_zone(interface, zone) verbose(f'Interface "{interface}" add to zone "{zone}"', 0) return self.change_interface_zone(interface, zone)[1] def method_change_zone_of_interface(self, zone, interface): """Change interface to zone.""" zone = self.parse_default_zone(interface, zone) verbose(f'Interface "{interface}" change to zone "{zone}"', 0) return self.change_interface_zone(interface, zone)[0] class DbusFoomuuri(dbus.service.Object, DbusCommon): """D-Bus server for Foomuuri.""" # pylint: disable=invalid-name # dbus method names @dbus.service.method('fi.foobar.Foomuuri1.zone', in_signature='', out_signature='as') def getZones(self): """Get list of available zones.""" return self.method_get_zones() @dbus.service.method('fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s') def removeInterface(self, zone, interface): """Remove interface from zone, or from all if zone is empty. Return: previous zone """ return self.method_remove_interface(zone, interface) @dbus.service.method('fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s') def addInterface(self, zone, interface): """Add interface to zone. Return: new zone """ return self.method_add_interface(zone, interface) @dbus.service.method('fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s') def changeZoneOfInterface(self, zone, interface): """Change interface to zone. Return: previous zone """ return self.method_change_zone_of_interface(zone, interface) class DbusFirewallD(dbus.service.Object, DbusCommon): """D-Bus server for FirewallD emulation.""" # pylint: disable=invalid-name # dbus method names @dbus.service.method('org.fedoraproject.FirewallD1.zone', in_signature='', out_signature='as') def getZones(self): """Get list of available zones.""" return self.method_get_zones() @dbus.service.method('org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s') def removeInterface(self, zone, interface): """Remove interface from zone, or from all if zone is empty. Return: previous zone """ return self.method_remove_interface(zone, interface) @dbus.service.method('org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s') def addInterface(self, zone, interface): """Add interface to zone. Return: new zone """ return self.method_add_interface(zone, interface) @dbus.service.method('org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s') def changeZoneOfInterface(self, zone, interface): """Change interface to zone. Return: previous zone """ return self.method_change_zone_of_interface(zone, interface) def command_dbus(): """Start D-Bus daemon.""" CONFIG['keep_going'] = True while CONFIG['keep_going']: # Read minimal config config = minimal_config() zones = parse_config_zones(config) daemonize() # Initialize D-Bus dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() # Foomuuri D-Bus calls try: foomuuri_name = dbus.service.BusName('fi.foobar.Foomuuri1', bus) foomuuri_name.get_name() # Dummy call to get rid of pylint except dbus.exceptions.DBusException: fail('Can\'t bind to system D-Bus: fi.foobar.Foomuuri1') foomuuri_object = DbusFoomuuri(bus, '/fi/foobar/Foomuuri1') foomuuri_object.set_data(zones) # FirewallD emulation calls, if enabled in foomuuri{} config firewalld_object = None if CONFIG['dbus_firewalld'] == 'yes': try: firewalld_name = dbus.service.BusName( 'org.fedoraproject.FirewallD1', bus) firewalld_name.get_name() # Dummy call to get rid of pylint except dbus.exceptions.DBusException: fail('Can\'t bind to system D-Bus: ' 'org.federaproject.FirewallD1') firewalld_object = DbusFirewallD(bus, '/org/fedoraproject/FirewallD1') firewalld_object.set_data(zones) # Define reload/stop signal handler mainloop = GLib.MainLoop() def signal_handler(sig, _dummy_frame): if sig == signal.SIGINT: CONFIG['keep_going'] = False if HAVE_NOTIFY: notify('STOPPING=1') elif HAVE_NOTIFY: notify('RELOADING=1') mainloop.quit() signal.signal(signal.SIGHUP, signal_handler) signal.signal(signal.SIGINT, signal_handler) save_file(CONFIG['_run_dir'] / 'foomuuri-dbus.pid', [str(os.getpid())]) # Start processing messages verbose('D-Bus handler ready', 0) if HAVE_NOTIFY: notify('READY=1') mainloop.run() # Reload signal received. Disconnect from D-Bus. foomuuri_object.remove_from_connection() del foomuuri_object del foomuuri_name if firewalld_object: firewalld_object.remove_from_connection() del firewalld_object del firewalld_name del bus def command_set(): """Modify runtime config by calling D-Bus methods.""" if ( len(sys.argv) != 6 or sys.argv[2] != 'interface' or sys.argv[4] != 'zone' ): command_help() try: bus = dbus.SystemBus() obj = bus.get_object('fi.foobar.Foomuuri1', '/fi/foobar/Foomuuri1') if sys.argv[5] in ('', '-'): obj.removeInterface('', sys.argv[3]) else: obj.changeZoneOfInterface(sys.argv[5], sys.argv[3]) except dbus.exceptions.DBusException as error: fail(error) def remove_filters(text): """Remove tailing flags and filters from hostname or URL.""" return text.split('|')[0] def resolve_one_hostname(hostname): """Resolve hostname and return its IP addresses.""" ret = set() try: for item in socket.getaddrinfo(remove_filters(hostname), None): if item[4] and item[4][0]: ret.add(item[4][0]) except socket.gaierror: pass return ret def resolve_all_hostnames(resolve, cache): """Resolve hostnames to IP addresses and save them to cache.""" # pylint: disable=too-many-locals # Collect list of hostnames to resolve now = datetime.datetime.now(datetime.timezone.utc) todo = set() for hostname, (timeout, refresh) in resolve.items(): cachevalue = cache.get(hostname, {}).get('ip', {}) cacherefresh = cache.get(hostname, {}).get('refresh') if cachevalue and cacherefresh: host_timeout = cacherefresh + datetime.timedelta(seconds=timeout) next_refresh = cacherefresh + datetime.timedelta(seconds=refresh) if now >= host_timeout: cache[hostname]['ip'] = {} cache[hostname]['dirty'] = True elif now < next_refresh and CONFIG['force'] < 0: verbose(f'Using cached value for ' f'"{remove_filters(hostname)}"') continue todo.add(hostname) if not todo: return # Resolve them verbose(f'DNS lookup for: {", ".join(todo)}') with concurrent.futures.ThreadPoolExecutor() as executor: jobs = {executor.submit(resolve_one_hostname, hostname): hostname for hostname in todo} for future in concurrent.futures.as_completed(jobs): hostname = jobs[future] addrlist = future.result() if addrlist: # Append/update addresses, don't replace whole list. # Some cloud services will change server IP a lot, and some # will return only partial list of addresses. By appending # old addresses will still work until lookup timeout. iplist_cache_init(cache, hostname) cache[hostname]['dirty'] = True cache[hostname]['refresh'] = now expire = now + datetime.timedelta(seconds=resolve[hostname][0]) for addr in addrlist: cache[hostname]['ip'][addr] = expire verbose(f'Hostname "{remove_filters(hostname)}" resolved ' f'to: {", ".join(addrlist)}') elif '|missing-ok' not in hostname: warning(f'No IP address found for hostname ' f'"{remove_filters(hostname)}" in iplist') def active_sets(): """Return set names in currently active firewall.""" ret = set() data = nft_json('list sets table inet foomuuri') if not data: return ret for item in data['nftables']: if 'set' in item: ret.add(f'@{item["set"]["name"][:-2]}') return ret def get_url(url): """Download URL and return it as text, or None if failure.""" if HAVE_REQUESTS: for retry in range(3): if retry: time.sleep(10) try: response = requests.get(remove_filters(url), timeout=50) except OSError as error: err = f'error: {error}' else: if response.status_code == 200: return response.text err = f'status code: {response.status_code}' else: err = 'No python-requests installed' if '|missing-ok' not in url: warning(f'Can\'t download iplist URL "{remove_filters(url)}": {err}') return None def get_file(wildcard): """Read all files and return them as single text.""" wildpath = pathlib.Path(remove_filters(wildcard)) filenames = sorted(wildpath.parent.glob(wildpath.name)) if not filenames: if '|missing-ok' not in wildcard: warning(f'Can\'t read iplist file "{remove_filters(wildcard)}": ' f'No such file') return None text = '' for filename in filenames: try: content = filename.read_text(encoding='utf-8') except PermissionError as error: warning(f'Can\'t read iplist file "{filename}": {error}') return None text = text + '\n' + content return text def parse_hour_min(timespec, fallback): """Parse 4h3m to seconds. This is dummy parser without any good error checking. """ if not timespec: return fallback timespec = timespec.replace(' ', '') days = hours = minutes = seconds = 0 if 'd' in timespec: days, timespec = timespec.split('d', 1) if 'h' in timespec: hours, timespec = timespec.split('h', 1) if 'm' in timespec: minutes, timespec = timespec.split('m', 1) if 's' in timespec: seconds, timespec = timespec.split('s', 1) if timespec: return fallback try: return (int(days) * 86400 + int(hours) * 3600 + int(minutes) * 60 + int(seconds)) except ValueError: return fallback def iterate_set_elements(data): """Iterate elements from "nft --json list set" output.""" for toplevel in (data or {}).get('nftables', []): for elem in toplevel.get('set', {}).get('elem', []): if isinstance(elem, dict) and 'elem' in elem: ipaddr = elem['elem']['val'] expire = elem['elem'].get('expires', 864000) # 10 days else: ipaddr = elem expire = 864000 if isinstance(ipaddr, str): yield ipaddr, expire elif 'range' in ipaddr: yield f'{ipaddr["range"][0]}-{ipaddr["range"][1]}', expire else: yield (f'{ipaddr["prefix"]["addr"]}/' f'{ipaddr["prefix"]["len"]}', expire) def command_iplist_list(): """List iplist entries.""" config = minimal_config() known = parse_iplist_names(config) for setname in CONFIG['parameters'][1:] or sorted(known): if setname.startswith('@'): setname = setname[1:] if setname.endswith(('_4', '_6')): data4 = nft_json(f'list set inet foomuuri {setname}') data6 = {} else: data4 = nft_json(f'list set inet foomuuri {setname}_4') data6 = nft_json(f'list set inet foomuuri {setname}_6') elem4 = [ipaddr for ipaddr, _dummy in iterate_set_elements(data4)] elem6 = [ipaddr for ipaddr, _dummy in iterate_set_elements(data6)] elems = sorted(elem4) + sorted(elem6) if not elems: print(f'@{setname}') else: for elem in elems: print(f'@{setname:20s} {elem}') return 0 def command_iplist_add(): """Add entries to iplist.""" config = minimal_config() iplist, timeouts = iplist_parse_config(config) cache = read_iplist_cache(iplist) setname = CONFIG['parameters'][1] if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplist: fail(f'Unknown iplist name: {setname}') timeout = max(timeouts[setname]['url_timeout'], timeouts[setname]['dns_timeout']) now = datetime.datetime.now(datetime.timezone.utc) expire = now + datetime.timedelta(seconds=timeout) # Add entries to cache and apply it iplist_cache_init(cache, f'manual.{setname}') for address in CONFIG['parameters'][2:]: ipv = is_ip_address(address) if ipv: cache[f'manual.{setname}']['ip'][address] = expire cache[f'manual.{setname}']['dirty'] = True else: timeout = parse_hour_min(address, 0) if not timeout: fail(f'Invalid IP address {address}') expire = now + datetime.timedelta(seconds=timeout) if timeout > 630720000: # More than 20 years is "forever" expire = datetime.datetime(year=2100, month=1, day=1, tzinfo=datetime.timezone.utc) return iplist_output_values(iplist, cache, active_sets(), {setname}) def command_iplist_del(): """Delete entries from iplist.""" config = minimal_config() iplist, _dummy_timeouts = iplist_parse_config(config) cache = read_iplist_cache(iplist) setname = CONFIG['parameters'][1] if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplist: fail(f'Unknown iplist name: {setname}') # Delete entries from cache and apply it iplist_cache_init(cache, f'manual.{setname}') for address in CONFIG['parameters'][2:]: cache[f'manual.{setname}']['ip'].pop(address, None) cache[f'manual.{setname}']['dirty'] = True return iplist_output_values(iplist, cache, active_sets(), {setname}) def command_iplist_flush(): """Delete all entries from iplist.""" config = minimal_config() iplist, _dummy_timeouts = iplist_parse_config(config) cache = read_iplist_cache(iplist) update_only = set() for setname in CONFIG['parameters'][1:]: if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplist: fail(f'Unknown iplist name: {setname}') update_only.add(setname) iplist_cache_init(cache, f'manual.{setname}') cache[f'manual.{setname}']['ip'] = {} cache[f'manual.{setname}']['dirty'] = True return iplist_output_values(iplist, cache, active_sets(), update_only) def lxml_parser(url, parser, path, text): """Parse html/xml text with lxml.""" # pylint: disable=c-extension-no-member try: root = lxml.etree.fromstring(text, parser) nodes = root.xpath(path) except lxml.etree.LxmlError as error: warning(f'Failed to parse iplist xml/html "{url}": {error}') return None return '\n'.join(nodes) def iplist_url_filters(url, text): """Apply filters to downloaded iplist content.""" for spec in url.split('|')[1:]: if not text: break # pylint: disable=c-extension-no-member if spec.startswith('shell:'): text = run_program_pipe([spec[6:]], text) elif spec.startswith('json:'): text = run_program_pipe(['jq', '--raw-output', spec[5:]], text) elif spec.startswith('html:') and HAVE_LXML: text = lxml_parser(url, lxml.etree.HTMLParser(), spec[5:], text) elif spec.startswith('xml:') and HAVE_LXML: text = lxml_parser(url, lxml.etree.XMLParser(), spec[4:], text) return text def iplist_cleanup_file(url, text): """Remove comments and empty lines from downloaded iplist file.""" # Apply filters text = iplist_url_filters(url, text) if not text: return [] # Collect IP addresses values = [] for line in text.splitlines(): if '#' in line: line = line.split('#', 1)[0] if ';' in line: line = line.split(';', 1)[0] for value in line.split(): if is_ip_address(value): values.append(value) else: warning(f'Unknown entry in iplist "{url}": {value}') return values def get_url_or_file(url, timeout, refresh, cache): """Get URL or file content with cache check.""" now = datetime.datetime.now(datetime.timezone.utc) cachevalue = cache.get(url, {}).get('ip', {}) cacherefresh = cache.get(url, {}).get('refresh') if cachevalue and cacherefresh: url_timeout = cacherefresh + datetime.timedelta(seconds=timeout) next_refresh = cacherefresh + datetime.timedelta(seconds=refresh) if now >= url_timeout: cache[url]['ip'] = {} cache[url]['dirty'] = True elif now < next_refresh and CONFIG['force'] < 0: verbose(f'Using cached value for "{url}"') return if url.startswith(('https:', 'http:')): value = get_url(url) else: value = get_file(url) if value is not None: # Replace list, don't append. These lists are always complete # address lists. expire = now + datetime.timedelta(seconds=timeout) cache[url] = { 'ip': {addr: expire for addr in iplist_cleanup_file(url, value)}, 'dirty': True, 'refresh': datetime.datetime.now(datetime.timezone.utc), } verbose(f'Iplist content for "{url}" refreshed, ' f'{len(cache[url]["ip"])} entries') def iplist_parse_timeout(fileline, timeouts, setting, extra): """Parse key=value from setting and set timeouts[key] = value.""" # Parse setting to key=value if extra: # Backward compabililty (v0.27) key = setting value = ''.join(extra) else: key, value = setting.split('=', 1) if not value: fail(f'{fileline}No value for "{key}"') # Update timeout if key in ('dns_timeout', 'dns_refresh', 'url_timeout', 'url_refresh'): timeouts[key] = parse_hour_min(value, timeouts[key]) elif key == 'timeout': timeouts['dns_timeout'] = timeouts['url_timeout'] = parse_hour_min( value, timeouts['dns_timeout']) else: # refresh timeouts['dns_refresh'] = timeouts['url_refresh'] = parse_hour_min( value, timeouts['dns_refresh']) def read_old_iplist_format(cache): """Read old iplist.fw (v0.27) files and convert them to new cache format. Example entries: # add element inet foomuuri fi_6 { 2a13:aec0::/32 timeout 10d } add element inet foomuuri foo_4 { 10.0.9.98 timeout 24h } add element inet foomuuri dynamic_4 { 10.0.0.4 timeout 798169s } # 2024-12-11T17:16:28 """ for file_key in ('_resolve_file', '_iplist_file', '_iplist_manual_file'): try: manual = CONFIG[file_key].read_text('utf-8') except OSError: continue # Silently ignore all errors for line in manual.splitlines(): if line.startswith('# '): line = line[2:] items = line.split() if ( not line.startswith('add element inet') or len(items) not in (10, 12) or not is_ip_address(items[6]) ): continue setname = f'manual.@{items[4][:-2]}' if len(items) == 12: timeout = items[11] # iplist_manual_file has expire stamp else: timeout = (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=parse_hour_min( items[8], 0))).strftime('%Y-%m-%dT%H:%M:%S') iplist_cache_init(cache, setname) cache[setname]['ip'][items[6]] = timeout def read_iplist_cache(iplist): """Read current iplist values from cache.""" cache = {} try: cache = json.loads(CONFIG['_iplist_cache_file'].read_text('utf-8')) except json.decoder.JSONDecodeError as error: warning(f'File {CONFIG["_iplist_cache_file"]}: Broken JSON ' f'format: {error}') except PermissionError as error: fail(f'File {CONFIG["_iplist_cache_file"]}: Can\'t read: {error}') except FileNotFoundError: read_old_iplist_format(cache) now = datetime.datetime.now(datetime.timezone.utc) for key in list(cache): if ( (key.startswith('@') and key not in iplist) or (key.startswith('manual.@') and key[7:] not in iplist) or 'ip' not in cache[key] ): # Iplist in cache, not in config del cache[key] verbose(f'Deleting iplist "{key}" from cache, not in config') continue # Check top level refresh. Missing or invalid value is ok. try: refresh = datetime.datetime.strptime( cache[key]['refresh'], '%Y-%m-%dT%H:%M:%S') refresh = refresh.replace(tzinfo=datetime.timezone.utc) except (ValueError, KeyError, TypeError): refresh = now cache[key]['refresh'] = refresh # Check IP address timeouts for value in list(cache[key]['ip']): try: timeout = datetime.datetime.strptime( cache[key]['ip'][value], '%Y-%m-%dT%H:%M:%S') timeout = timeout.replace(tzinfo=datetime.timezone.utc) except (ValueError, KeyError): warning(f'Invalid iplist "{key}" entry "{value}" timestamp ' f'in cache') timeout = now # Gets deleted cache[key]['ip'][value] = timeout if now >= timeout: verbose(f'Deleting expired iplist "{key}" entry "{value}"') del cache[key]['ip'][value] # Don't keep empty sets if not cache[key]['ip']: del cache[key] return cache def write_iplist_cache(cache): """Write iplist cache to disk for next run.""" for key in cache: cache[key].pop('dirty', None) if key.startswith(('@', 'manual.@')): del cache[key]['refresh'] else: cache[key]['refresh'] = cache[key]['refresh'].strftime( '%Y-%m-%dT%H:%M:%S') for address, value in cache[key]['ip'].items(): cache[key]['ip'][address] = value.strftime('%Y-%m-%dT%H:%M:%S') save_file(CONFIG['_iplist_cache_file'], cache) def iplist_parse_config(config): """Parse resolve{} and iplist{} sections from config.""" def_times = { 'dns_timeout': 86400, # 24h 'dns_refresh': 900, # 15m 'url_timeout': 886400, # 10d 'url_refresh': 86400, # 24h 'timeout': 0, # Backward compability keyword (v0.27) 'refresh': 0, # Backward compability keyword } iplist = {} timeouts = {} for fileline, line in config.pop('resolve', []) + config.pop('iplist', []): name = line[0] # Parse global timeout/refresh value if ( ( '=' in name and name.split('=')[0] in def_times and len(line) == 1 ) or ( # Backward compabililty (v0.27) name in ('timeout', 'refresh') and len(line) > 1 ) ): iplist_parse_timeout(fileline, def_times, name, line[1:]) continue # Name must be set name, check it if not name.startswith('@') or name == '@': fail(f'{fileline}Invalid iplist name: {" ".join(line)}') check_name(name[1:], fileline, '@') # Parse line items to value[] if len(line) == 1 or line[1:] == ['-']: # Empty set value = [] elif line[1] == '+': # Append to previous value value = iplist.get(name, []) + line[2:] else: # Overwrite previous value if name in iplist: verbose(f'{fileline}Overwriting iplist "{name}" with value ' f'"{" ".join(line[1:])}"') value = line[1:] # Parse pre-entry timeouts this_value = [] this_timeouts = timeouts.get(name, def_times).copy() for item in value: if '=' in item and item.split('=')[0] in def_times: iplist_parse_timeout(fileline, this_timeouts, item, None) else: this_value.append(item) iplist[name] = this_value timeouts[name] = this_timeouts if CONFIG['verbose'] >= 2: for name, values in iplist.items(): verbose(f'{name:9s} {" ".join(values)}') return iplist, timeouts def iplist_collect_resolve_list(iplist, timeouts): """Collect URLs and hostnames to be resolved.""" if CONFIG['command'] != 'iplist': # Don't resolve anything if called as "foomuuri start". # Use cached entries only. return {}, {} urls = {} hostnames = {} for name, values in iplist.items(): for value in values: if value.startswith(('https://', 'http://', '.', '/')): if value in urls: urls[value] = ( min(urls[value][0], timeouts[name]['url_timeout']), min(urls[value][1], timeouts[name]['url_refresh'])) else: urls[value] = (timeouts[name]['url_timeout'], timeouts[name]['url_refresh']) elif not is_ip_address(value): if value in hostnames: hostnames[value] = (min(hostnames[value][0], timeouts[name]['dns_timeout']), min(hostnames[value][1], timeouts[name]['dns_refresh'])) else: hostnames[value] = (timeouts[name]['dns_timeout'], timeouts[name]['dns_refresh']) if CONFIG['verbose'] >= 2: verbose(f'URLs: {urls}') verbose(f'Hostnames: {hostnames}') return urls, hostnames def iplist_cache_init(cache, setname): """Add empty setname to cache.""" if setname in cache: return cache[setname] = { 'ip': {}, 'refresh': datetime.datetime.now(datetime.timezone.utc), } def iplist_cache_copy_ip(cache, source, destination): """Copy IP addresses from cache[source] to cache[destination].""" if source not in cache: return if cache[source].get('dirty'): cache[destination]['dirty'] = True for address, timeout in cache[source]['ip'].items(): # Choose maximum timeout if both dynamic (resolved from hostname) # and manually added address exists. old_timeout = cache[destination]['ip'].get(address, timeout) cache[destination]['ip'][address] = max(old_timeout, timeout) def iplist_output_add_element(cache, setname, now): """Output nft commands to update iplist values to sets. Auto-merge in nftables set requires that adds are done set by set, not mixing foo_4 and foo_6 adds. """ # Collect addresses to IPv4 and IPv6 sets updates = { 4: set(), 6: set(), } for address in cache[setname]['ip']: updates[is_ip_address(address)].add(address) # Output add to sets out('') for ipv in (4, 6): out(f'flush set inet foomuuri {setname[1:]}_{ipv}') if not updates[ipv]: continue out(f'add element inet foomuuri {setname[1:]}_{ipv} {{') for address in sorted(updates[ipv]): timeout = cache[setname]['ip'][address] seconds = max(1, int((timeout - now).total_seconds())) if seconds > 630720000: # 20 years, add without timeout out(f'{address},') else: out(f'{address} timeout {seconds}s,') out('}') def iplist_output_values(iplist, cache, currently_active, update_only): """Output and apply nft commands to add iplist entries.""" forever = datetime.datetime(year=2100, month=1, day=1, tzinfo=datetime.timezone.utc) now = datetime.datetime.now(datetime.timezone.utc) error = 0 for setname in sorted(iplist): setvalues = iplist[setname] # Collect new values to cache[@setname] cache[setname] = { 'ip': {}, 'refresh': now, } for value in setvalues: # Static IP address doesn't need refresh, so timeout is forever if is_ip_address(value): cache[setname]['ip'][value] = forever cache[setname]['dirty'] = True continue # Add IPs from URL/hostname iplist_cache_copy_ip(cache, value, setname) # Add manually added IPs iplist_cache_copy_ip(cache, f'manual.{setname}', setname) # Check that this set should be updated if update_only and setname not in update_only: continue # Filtered away by command line parameter if setname not in currently_active: warning(f'Iplist "{setname}" does not exist in currently active ' f'firewall') continue if not cache[setname].get('dirty') and CONFIG['force'] < 0: verbose(f'Iplist "{setname}" is clean, skipping update') continue # Print error if set had values, but result is empty if ( setvalues and not cache[setname]['ip'] and CONFIG['command'] == 'iplist' ): fail(f'Iplist "{setname}" is empty', False) error = 2 else: verbose(f'Updating iplist "{setname}", ' f'{len(cache[setname]["ip"])} entries') # Add elements to sets iplist_output_add_element(cache, setname, now) return iplist_apply_values(cache) | error def iplist_apply_values(cache): """Save iplist changes to disk and use nft to apply it.""" if CONFIG['command'] != 'iplist' or not OUT: return 0 write_iplist_cache(cache) save_final(CONFIG['_iplist_apply_file']) ret = run_program_rc(CONFIG['_nft_bin'] + ['--file', CONFIG['_iplist_apply_file']]) if ret: fail(f'Failed to update iplists, nftables error code {ret}', False) return ret def command_iplist_refresh(config=None, currently_active=None): """Refresh iplist{} entries.""" # Read minimal config and parse it if not config: config = minimal_config() iplist, timeouts = iplist_parse_config(config) urls, hostnames = iplist_collect_resolve_list(iplist, timeouts) # Read URLs and files to cache cache = read_iplist_cache(iplist) for url, (timeout, refresh) in urls.items(): get_url_or_file(url, timeout, refresh, cache) # Resolve hostnames to cache resolve_all_hostnames(hostnames, cache) # Read active set names from nft if not provided by "foomuuri start" if currently_active is None: currently_active = active_sets() # Special case for testing (non-root) or faked nft_bin if not currently_active and ( not CONFIG['root_power'] or CONFIG['nft_bin'] == 'true' ): currently_active = set(iplist) # Default to update all sets, "foomuuri iplist refresh setname" can filter update_only = None if CONFIG['parameters'][1:]: update_only = [] for name in CONFIG['parameters'][1:]: if name.startswith('@'): update_only.append(name) else: update_only.append(f'@{name}') # Apply updates to ruleset return iplist_output_values(iplist, cache, currently_active, update_only) def command_iplist(): """Parse "foomuuri iplist" subcommand.""" if len(CONFIG['parameters']) >= 1 and CONFIG['parameters'][0] == 'list': return command_iplist_list() if len(CONFIG['parameters']) >= 3 and CONFIG['parameters'][0] == 'add': return command_iplist_add() if len(CONFIG['parameters']) >= 3 and CONFIG['parameters'][0] == 'del': return command_iplist_del() if len(CONFIG['parameters']) >= 2 and CONFIG['parameters'][0] == 'flush': return command_iplist_flush() if len(CONFIG['parameters']) >= 1 and CONFIG['parameters'][0] == 'refresh': return command_iplist_refresh() return command_help() def list_named_counter(command): """Parse and print named counter values.""" data = nft_json(command) if not data: return 1 for toplevel in data.get('nftables', []): counter = toplevel.get('counter') name = (counter or {}).get('name') if not name: continue packet_value = counter.get('packets', 0) byte_value = counter.get('bytes', 0) print(f'{name:21s} {packet_value} packets, {byte_value} bytes') return 0 def command_list(): """List currently active ruleset or other things.""" # List all if not CONFIG['parameters']: return nft_command('list ruleset') # Parse list of possible zone-zone pairs config = minimal_config() zones = parse_config_zones(config) zonepairs = [f'{szone}-{dzone}' for szone in zones for dzone in zones] # Parse parameters list_type = CONFIG['parameters'][0] list_params = CONFIG['parameters'][1:] ret = 0 if list_type == 'macro': # List macros. config{} doesn't have macro{} anymore so config # must be re-read. macros = parse_config_macros(read_config()) print('macro {') for macro in sorted(macros): if not macro.startswith('_template_') and ( not list_params or macro in list_params or any(item in list_params for item in macros[macro]) ): print(f' {macro:15s} {" ".join(macros[macro])}') print('}') return 0 if list_type == 'counter': # List named counters if not list_params: ret |= list_named_counter('list counters table inet foomuuri') else: for counter in list_params: ret |= list_named_counter(f'list counter inet foomuuri ' f'{counter}') return ret # List zone-zone rules for zone in CONFIG['parameters']: if zone not in zonepairs: fail(f'Unknown zone-zone: {zone}') for ipv in (4, 6): ret += nft_command(f'list chain inet foomuuri {zone}_{ipv}') return ret def command_reload(): """Run start and refresh iplist.""" # Use same args args = [sys.argv[0]] for arg in sys.argv[1:]: if arg.startswith('--'): args.append(arg) # Run commands for sub, fatal in ((['start'], True), (['iplist', 'refresh'], False)): ret = run_program_rc(args + sub) if ret and fatal: return ret return 0 def alarm_handler(_dummy_signum, _dummy_frame): """Signal handler to catch input timeout.""" raise KeyboardInterrupt def input_timeout(prompt, timeout): """Ask something from user with timeout.""" signal.signal(signal.SIGALRM, alarm_handler) signal.alarm(timeout) try: return input(prompt) except KeyboardInterrupt: print() print('Timeout') finally: signal.alarm(0) return '' def command_try_reload(): """Reload, ask confirmation, optionally revert to previous rules. Revert is not fail safe. Things can break if: - Zone is added/deleted to config and NetworkManager sends new "change to zone" request via D-Bus. This happens only if user reconfigures NetworkManager or attach ethernet cable or similar. - Needed entry is removed from iplist config and foomuuri-iplist.timer updates iplist contents. There is a long expiry time for entries so there is plenty of time to fix config. It is recommended to fix config immediately and run "foomuuri reload" or "foomuuri try-reload". """ # Get copy of currently active ruleset minimal_config() old_rules = run_program_pipe(CONFIG['_nft_bin'] + ['list', 'table', 'inet', 'foomuuri'], None) if not old_rules or not old_rules.startswith('table inet foomuuri {'): fail('Foomuuri is not running, use "foomuuri start" instead') # Load new ruleset error = command_reload() if error: return error # Verify from user if ruleset is ok try: timeout = int(CONFIG['try-reload_timeout']) except ValueError: timeout = 15 print(f'You have {timeout} seconds to accept the changes.') answer = input_timeout('Can you establish NEW connections (yes/no)? ', timeout) if answer.lower().startswith('y'): return 0 # Revert to old ruleset and return errorcode 2 warning('Revert to old config') save_file(CONFIG['_good_file'], [ 'table inet foomuuri', 'delete table inet foomuuri', '', old_rules]) return run_program_rc(CONFIG['_nft_bin'] + ['--file', CONFIG['_good_file']]) + 2 def seconds_to_human(seconds): """Convert seconds int to human readable "19 days, 18:37" format.""" day = seconds // 86400 hour = (seconds // 3600) % 24 minute = (seconds // 60) % 60 second = seconds % 60 if day: return f'{day} days, {hour:02d}:{minute:02d}:{second:02d}' return f'{hour:02d}:{minute:02d}:{second:02d}' def monitor_state_command(targets, groups, cfg, grouptarget, name): """Run command if group/target state changes.""" # pylint: disable=too-many-locals # Log state change updown = 'up' if cfg['state'] else 'down' now = time.time() prev = cfg.get('state_time') history = None if prev: seconds = int(now - prev + 0.5) extra = f'previous change was {seconds_to_human(seconds)} ago' if grouptarget == 'target': for cons in range(0, cfg['history_size']): if cfg['history'][-1 - cons] != cfg['state']: break historycount = cfg['history'].count(cfg['state']) extra = (f'{extra}, consecutive_{updown} {cons}, ' f'history_{updown} {historycount}') history = ''.join('.' if item else '!' for item in cfg['history']) else: extra = 'startup change' verbose(f'{grouptarget} {name} changed state to {updown}, {extra}', 0) if history: verbose(f'{grouptarget} {name} history: {history}', 0) cfg['state_time'] = cfg['last_down_interval'] = now monitor_run_command(targets, groups, cfg, grouptarget, name, updown, extra, history) def monitor_run_command(targets, groups, cfg, grouptarget, name, updown, extra, history): """Run monitor's external command if configured. It will receive current state change event info and all states in environment variables. """ # pylint: disable=too-many-arguments # pylint: disable=too-many-positional-arguments proc = cfg[f'command_{updown}'] if not proc: return env = { # Change state event 'FOOMUURI_CHANGE_TYPE': grouptarget, 'FOOMUURI_CHANGE_NAME': env_cleanup(name), 'FOOMUURI_CHANGE_STATE': updown, 'FOOMUURI_CHANGE_LOG': extra, 'FOOMUURI_CHANGE_HISTORY': history or '', # List of configured targets 'FOOMUURI_ALL_TARGET': ' '.join(env_cleanup(item) for item in targets), # List of configured groups 'FOOMUURI_ALL_GROUP': ' '.join(env_cleanup(item) for item in groups), } for target, icfg in targets.items(): env[f'FOOMUURI_TARGET_{env_cleanup(target)}'] = ( 'up' if icfg['state'] else 'down') for group, icfg in groups.items(): env[f'FOOMUURI_GROUP_{env_cleanup(group)}'] = ( 'up' if icfg.get('state', True) else 'down') run_program_rc(proc, env=env) def monitor_down_interval(targets, groups, cfg, grouptarget, name): """Group/target is still down, run command in regular intervals.""" now = time.time() if now < cfg['last_down_interval'] + cfg['down_interval']: return cfg['last_down_interval'] = now seconds = int(now - cfg['state_time'] + 0.5) extra = f'previous change was {seconds_to_human(seconds)} ago' verbose(f'{grouptarget} {name} is still down, {extra}', 0) monitor_run_command(targets, groups, cfg, grouptarget, name, 'down_interval', extra, None) def monitor_update_groups(targets, groups): """Update all group statuses. On startup make decision and send event for all groups after first reply from any target. """ for group, cfg in groups.items(): any_up = any(targets[target]['state'] for target in cfg['target']) state = cfg.get('state', not any_up) # Undef in startup if not any_up: if state: # Change from up to down cfg['state'] = False monitor_state_command(targets, groups, cfg, 'group', group) else: # Still down, check interval monitor_down_interval(targets, groups, cfg, 'group', group) elif any_up and not state: # Change from down to up cfg['state'] = True monitor_state_command(targets, groups, cfg, 'group', group) def monitor_update_target(targets, groups, target, state): """Add state to target's history and change its state.""" # Add new state to end of history cfg = targets[target] startup_change = False if 'history' not in cfg: # First reply ever, fill history and force change event cfg['history'] = [True] * cfg['history_size'] startup_change = True cfg['history'] = cfg['history'][1:] + [state] # Target state can't change if added state is same as current state if cfg['state'] == state and not startup_change: if not state: monitor_down_interval(targets, groups, cfg, 'target', target) return # Check if target state is changed count_up = sum(cfg['history']) if cfg['state']: # Currently up. Target goes down if: # - history has too many downs # OR # - last n items were down if ( cfg['history_size'] - count_up >= cfg['history_down'] or not any(cfg['history'][-cfg['consecutive_down']:]) ): cfg['state'] = False monitor_state_command(targets, groups, cfg, 'target', target) elif startup_change: # Always send an event on startup monitor_state_command(targets, groups, cfg, 'target', target) else: # Currently down. Target goes up if: # - history has enough ups # AND # - last n items were up if ( count_up >= cfg['history_up'] and all(cfg['history'][-cfg['consecutive_up']:]) ): cfg['state'] = True monitor_state_command(targets, groups, cfg, 'target', target) def monitor_parse_line(targets, groups, target, line): """Parse result line from monitor's command.""" verbose(f'target {target}: {line}', 2) stat_ms = None # Value for statistics, assume error if line in ('OK', 'ERROR'): # Generic "OK" or "ERROR" reply state = line == 'OK' if state: stat_ms = 1 # There is no time, use 1 elif line.startswith('ICMP Unreachable (Communication Administratively'): # fping reply if packet is rejected state = False elif 'xmt/rcv/%loss' in line: # fping with "--squiet" if '0/0/0%' in line: return # --squiet is less than --interval, ignore line state = '/100%' not in line # It's up if loss is less than 100% if state: try: stat_ms = float(line.split('/')[-1]) except ValueError: pass else: # fping with "--interval" match = re.match(r'^[^ ]+ : \[\d+\], (.+)$', line) if not match: return # No match, ignore line result = match.group(1) state = ' bytes, ' in result and ' ms (' in result if state: try: stat_ms = float(result.split()[2]) except ValueError: pass # Update target state monitor_update_target(targets, groups, target, state) # Add result to statistics cfg = targets[target] cfg['time'] = (cfg['time'] + [stat_ms])[-cfg['statistics_size']:] def command_option_values(cmd): """Quick and dirty parser for "--foo n" and "--foo=n".""" ret = {} for index, item in enumerate(cmd): if not item.startswith('--'): continue if '=' in item: item, value = item.split('=', 1) else: try: value = cmd[index + 1] except IndexError: value = None ret[item] = value return ret def mangle_fping_command(target, cfg): """Mangle fping's command line to contain needed options.""" cmd = cfg['command'] if 'fping' not in cmd[0]: return cmd options = command_option_values(cmd) # --squiet works even if interface is down or missing. Automatically # use it if missing (1s) or --interval=n000 is specified. # Use plain --interval if value is not exact second. squiet = options.get('--squiet', '') interval = options.get('--interval', '') if not squiet and not interval: # Nothing, use 1s / 1000ms cmd = cmd[:1] + ['--squiet=1', '--interval=1000'] + cmd[1:] elif squiet and not interval: # squiet specified, use it as interval cmd = cmd[:1] + [f'--interval={squiet}000'] + cmd[1:] elif not squiet and interval.endswith('000'): # Use interval as squiet cmd = cmd[:1] + [f'--squiet={interval[:-3]}'] + cmd[1:] elif squiet and interval: try: if int(squiet) * 1000 != int(interval): warning(f'Mismatching "--squiet" and "--interval" values in ' f'target "{target}": {" ".join(cmd)}') except ValueError: pass # Add --loop if missing to get continous reports if '--loop' not in options: cmd = cmd[:1] + ['--loop'] + cmd[1:] return cmd def monitor_start_targets(targets, groups): """Start pinging all targets.""" started_something = False for target, cfg in targets.items(): # Check if target is already running, or still waiting for restart if cfg.get('proc') or cfg['proc_restart'] > time.time(): continue # Assume it is up on first startup if 'state' not in cfg: cfg['state'] = True # Start command (pylint: disable=consider-using-with) cmd = mangle_fping_command(target, cfg) verbose(f'target {target} command: {" ".join(map(str, cmd))}', 2) try: cfg['proc'] = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8') verbose(f'target {target} monitoring command started') started_something = True except OSError as error: verbose(f'target {target} monitoring command failed: {error}', 0) cfg['proc'] = None cfg['proc_restart'] = time.time() + 30 # Mark it as down immediately cfg['history'] = [False] * cfg['history_size'] monitor_update_target(targets, groups, target, False) # Small initial sleep so that first read_target() will receive more # replies in one go. This helps to get more "target up" events before # "group up" events. if started_something: time.sleep(0.5) def monitor_close_target(targets, groups, target): """Target monitoring command died. Mark it as down.""" # Terminate proc and schedule restart verbose(f'target {target} monitoring command died, restarting it in 30s', 0) cfg = targets[target] cfg['proc'].terminate() cfg['proc'].wait(timeout=0.1) cfg['proc'] = None cfg['proc_restart'] = time.time() + 30 cfg['time'] = [] # Mark it as down immediately cfg['history'] = [False] * cfg['history_size'] monitor_update_target(targets, groups, target, False) def monitor_read_targets(targets, groups): """Read incoming lines from all targets.""" # (Re)Start targets if not running monitor_start_targets(targets, groups) # Wait for incoming lines from any client, up to 10 seconds readfd = [item['proc'].stdout for item in targets.values() if item['proc']] poll = select.select(readfd, [], [], 10.0) # Read all incoming lines for readfd in poll[0]: for target, cfg in targets.items(): if cfg['proc'] and cfg['proc'].stdout == readfd: line = readfd.readline() if line: monitor_parse_line(targets, groups, target, line.strip()) else: monitor_close_target(targets, groups, target) break # Update all groups monitor_update_groups(targets, groups) def monitor_terminate_targets(targets): """Terminate all subprocesses.""" for cfg in targets.values(): if cfg['proc']: cfg['proc'].terminate() for cfg in targets.values(): if cfg['proc']: cfg['proc'].wait(timeout=0.1) def parse_config_targets(config): """Parse "target foo { ... }" entries from config.""" targets = {} names = [item for item in config if item.startswith('target ')] for name in names: name = name[7:] cfg = targets[name] = { 'command': [], 'command_up': [], 'command_down': [], 'command_down_interval': [], 'consecutive_up': 20, # last n were UP => UP 'consecutive_down': 10, # last n were DOWN => DOWN 'history_up': 80, # count of UPs => n => UP 'history_down': 30, # count of DOWNs >= n => DOWN 'history_size': 100, 'statistics_size': 300, # Keep last 300 results 'statistics_interval': 60, # Write it once a minute 'down_interval': 600, # How often to run command_down_interval # Internal items, don't set in config: # 'state': True, # Up or down # 'state_time': time(), # Timestamp for last state change # 'last_down_interval': time(), # Still down timestamp # 'history': [True] * history_size, # 'proc': Popen() 'proc_restart': 0, # Start immediately 'time': [], # Statistics } fileline = '' for fileline, line in config.pop(f'target {name}'): if line[0] not in cfg: fail(f'{fileline}Unknown keyword: {" ".join(line)}') if isinstance(cfg[line[0]], list): cfg[line[0]] = line[1:] else: try: cfg[line[0]] = int(' '.join(line[1:])) except ValueError: fail(f'{fileline}Invalid value: {" ".join(line)}') # Verify section if not cfg['command']: fail(f'{fileline}Missing "command" keyword in "target {name}"') for key in ('consecutive_up', 'consecutive_down', 'history_up', 'history_down'): if cfg[key] > cfg['history_size']: fail(f'{fileline}{key} is larger than history_size in ' f'"target {name}": {cfg[key]} > {cfg["history_size"]}') if cfg['history_size'] - cfg['history_up'] >= cfg['history_down']: warning(f'{fileline}Possible up-down loop in ' f'"target {name}": history_size {cfg["history_size"]} - ' f'history_up {cfg["history_up"]} >= ' f'history_down {cfg["history_down"]}') return targets def parse_config_groups(config, targets): """Parse "group foo { ... }" entries from config.""" groups = {} names = [item for item in config if item.startswith('group ')] for name in names: name = name[6:] cfg = groups[name] = { 'target': [], 'command_up': [], 'command_down': [], 'command_down_interval': [], 'down_interval': 600, # How often to run command_down_interval } fileline = '' for fileline, line in config.pop(f'group {name}'): if line[0] not in cfg: fail(f'{fileline}Unknown keyword: {" ".join(line)}') if isinstance(cfg[line[0]], list): cfg[line[0]] = line[1:] else: try: cfg[line[0]] = int(' '.join(line[1:])) except ValueError: fail(f'{fileline}Invalid value: {" ".join(line)}') # Verify section target = cfg.get('target') if not target: fail(f'{fileline}Missing "target" keyword in "group {name}"') for item in target: if item not in targets: fail(f'{fileline}Undefined target "{item}" in "group {name}"') return groups def monitor_write_statistics(targets): """Write statistics file.""" stats = { target: { # Config 'consecutive_up': cfg['consecutive_up'], 'consecutive_down': cfg['consecutive_down'], 'history_up': cfg['history_up'], 'history_down': cfg['history_down'], # State 'state': cfg['state'], 'history': cfg.get('history', []), # fping time 'time': cfg['time'], } for target, cfg in targets.items() } save_file(CONFIG['_monitor_statistics_file'], stats) def command_monitor(): """Monitor targets and run command when their state changes up or down.""" CONFIG['keep_going'] = 1 while CONFIG['keep_going']: # Read minimal config config = minimal_config() targets = parse_config_targets(config) groups = parse_config_groups(config, targets) # Exit silently with OK if no targets defined in configuration if not targets: if HAVE_NOTIFY: notify('READY=1') notify('STOPPING=1') return 0 daemonize() # Define reload/stop signal handler def signal_handler(sig, _dummy_frame): if sig == signal.SIGINT: CONFIG['keep_going'] = 0 if HAVE_NOTIFY: notify('STOPPING=1') else: CONFIG['keep_going'] = 2 if HAVE_NOTIFY: notify('RELOADING=1') CONFIG['keep_going'] = 1 signal.signal(signal.SIGHUP, signal_handler) signal.signal(signal.SIGINT, signal_handler) save_file(CONFIG['_run_dir'] / 'foomuuri-monitor.pid', [str(os.getpid())]) # Start monitoring verbose('Target monitor ready', 0) if HAVE_NOTIFY: notify('READY=1') stat_interval = min(cfg['statistics_interval'] for cfg in targets.values()) next_stat = time.time() + stat_interval / 5 while CONFIG['keep_going'] == 1: monitor_read_targets(targets, groups) # Periodic statistics file update if stat_interval and time.time() >= next_stat: next_stat += stat_interval monitor_write_statistics(targets) monitor_terminate_targets(targets) return 0 def command_help(error=True): """Print command line help.""" # pylint: disable=too-many-statements print(f'Foomuuri {VERSION}') print() print(f'Usage: {sys.argv[0]} {{options}} command') print() print('Available commands:') print() print(' start Load configuration files and generate ruleset') print(' stop Remove ruleset') print(' reload Same as start, followed by iplist refresh') print(' try-reload Same as reload, ask confirmation to keep new ' 'config') print(' status Show current status: running, zone-interface ' 'mapping') print(' check Verify configuration files') print(' block Load "block all traffic" ruleset') print(' list List active ruleset') print(' list zone-zone {zone-zone...}') print(' List active ruleset for zone-zone') print(' list macro List all known macros') print(' list macro name {name...}') print(' List all macros with specified name or value') print(' list counter List all named counters') print(' list counter name {name...}') print(' List named counter with specified name') print(' iplist list List entries in all configured iplists') print(' iplist list name {name...}') print(' List entries in named iplist') print(' iplist add name {timeout} ipaddress {ipaddress...}') print(' Add or refresh IP address to iplist') print(' iplist del name ipaddress {ipaddress...}') print(' Delete IP address from iplist') print(' iplist flush name {name...}') print(' Delete all IP addresses from iplist') print(' iplist refresh Refresh all iplists now') print(' iplist refresh name {name...}') print(' Refresh named iplists now') print(' set interface {interface} zone {zone}') print(' Change interface to zone') print(' set interface {interface} zone -') print(' Remove interface from all zones') print() print('Available options:') print() print(' --version Print version') print(' --verbose Verbose output') print(' --quiet Be quiet') print(' --force Force some operations, don\'t check anything') print(' --soft Don\'t force operations, check more') print(' --fork Fork as a background daemon process') print(' --syslog Enable syslog logging') print(' --set=option=value') print(' Set config option to value') print() print('Internally used commands:') print() print(' dbus Start D-Bus daemon') print(' monitor Start target monitor daemon') if error: fail() return 0 def parse_command_line(): """Parse command line to CONFIG[command] and CONFIG[parameters].""" for arg in sys.argv[1:]: if arg == '--help': CONFIG['command'] = 'help' elif arg == '--version': print(VERSION) sys.exit(0) elif arg in ('--verbose', '--force', '--fork', '--syslog'): CONFIG[arg[2:]] += 1 elif arg == '--quiet': CONFIG['verbose'] -= 1 elif arg == '--soft': CONFIG['force'] -= 1 elif arg.startswith('--set='): if arg.count('=') == 1: fail(f'Invalid syntax for --set=option=value: {arg}') _dummy, option, value = arg.split('=', 2) if option not in CONFIG: fail(f'Unknown foomuuri{{}} option: {arg}') CONFIG[option] = value elif not CONFIG['command']: CONFIG['command'] = arg else: CONFIG['parameters'].append(arg) config_to_pathlib() # Needed to read config files and for --set=x_dir=y def run_command(): """Run CONFIG[command].""" # Only some commands can take arguments if CONFIG['parameters'] and CONFIG['command'] not in ( 'list', 'iplist', 'set', 'help'): return command_help() # Help doesn't need root if not CONFIG['command'] or CONFIG['command'] == 'help': return command_help(error=False) # Initialize syslogging if CONFIG['syslog']: syslog.openlog('foomuuri', syslog.LOG_PID, syslog.LOG_DAEMON) # Warning if not running as root CONFIG['root_power'] = not os.getuid() if not CONFIG['root_power']: warning('Foomuuri should be run as "root"') # Run command handler = { 'start': command_start, 'stop': command_stop, 'reload': command_reload, 'try-reload': command_try_reload, 'status': command_status, 'check': command_start, 'block': command_block, 'list': command_list, 'iplist': command_iplist, 'set': command_set, 'dbus': command_dbus, 'monitor': command_monitor, }.get(CONFIG['command']) if not handler: fail(f'Unknown command: {CONFIG["command"]}') return handler() def main(): """Parse command line and run command.""" parse_command_line() return run_command() if __name__ == '__main__': try: sys.exit(main()) except BrokenPipeError: # Python flushes standard streams on exit; redirect remaining output # to devnull to avoid another BrokenPipeError at shutdown os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) sys.exit(1) foomuuri-0.29/systemd/000077500000000000000000000000001506547207000150075ustar00rootroot00000000000000foomuuri-0.29/systemd/fi.foobar.Foomuuri1.conf000066400000000000000000000005461506547207000214150ustar00rootroot00000000000000 foomuuri-0.29/systemd/foomuuri-boot.service000066400000000000000000000005431506547207000212010ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Early boot Documentation=https://github.com/FoobarOy/foomuuri/wiki After=local-fs.target ConditionPathExists=/var/lib/foomuuri/good.fw [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c 'cat /var/lib/foomuuri/good.fw | nft -f -' # Above "sh 'cat | nft'" is a SELinux workaround foomuuri-0.29/systemd/foomuuri-dbus.service000066400000000000000000000005641506547207000211760ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - D-Bus handler Documentation=https://github.com/FoobarOy/foomuuri/wiki After=dbus.service After=polkit.service After=foomuuri.service Requires=foomuuri.service PartOf=foomuuri.service Conflicts=firewalld.service BindsTo=dbus.service [Service] Type=notify ExecStart=foomuuri dbus ExecReload=kill -HUP $MAINPID foomuuri-0.29/systemd/foomuuri-iplist.service000066400000000000000000000003171506547207000215410ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Refresh iplist entries Documentation=https://github.com/FoobarOy/foomuuri/wiki [Service] Type=oneshot ExecStart=foomuuri --soft iplist refresh foomuuri-0.29/systemd/foomuuri-iplist.timer000066400000000000000000000003561506547207000212240ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Refresh iplist entries Documentation=https://github.com/FoobarOy/foomuuri/wiki Requires=foomuuri.service PartOf=foomuuri.service [Timer] OnActiveSec=3m OnUnitInactiveSec=15m foomuuri-0.29/systemd/foomuuri-monitor.service000066400000000000000000000004471506547207000217300ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Connectivity monitor Documentation=https://github.com/FoobarOy/foomuuri/wiki After=network-online.target After=foomuuri.service PartOf=foomuuri.service [Service] Type=notify ExecStart=foomuuri monitor ExecReload=kill -HUP $MAINPID foomuuri-0.29/systemd/foomuuri.service000066400000000000000000000007521506547207000202420ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall Documentation=https://github.com/FoobarOy/foomuuri/wiki After=local-fs.target After=foomuuri-boot.service Before=network-pre.target Wants=network-pre.target Wants=foomuuri-boot.service Wants=foomuuri-dbus.service Wants=foomuuri-iplist.timer Wants=foomuuri-monitor.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=foomuuri start ExecReload=foomuuri reload ExecStop=foomuuri stop [Install] WantedBy=multi-user.target foomuuri-0.29/systemd/foomuuri.tmpfilesd000066400000000000000000000000411506547207000205600ustar00rootroot00000000000000d /run/foomuuri 0755 root root - foomuuri-0.29/test/000077500000000000000000000000001506547207000142765ustar00rootroot00000000000000foomuuri-0.29/test/10-host-multizone/000077500000000000000000000000001506547207000175155ustar00rootroot00000000000000foomuuri-0.29/test/10-host-multizone/foomuuri.conf000066400000000000000000000010431506547207000222270ustar00rootroot00000000000000zone { localhost public home } public-localhost { dhcp-client dhcpv6-client ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" drop log } home-localhost { dhcp-client dhcpv6-client lsdp mdns ping ssdp ssh drop log } template outgoing_services { dhcp-server dhcpv6-server domain http https imap ntp ping smtp ssh } localhost-public { template outgoing_services reject log } localhost-home { template outgoing_services googlemeet ipp mdns ssdp reject log } foomuuri-0.29/test/10-host-multizone/golden.txt000066400000000000000000000344131506547207000215330ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } set _rate_set_1_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_1_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { icmp type echo-request update @_rate_set_1_4 { ip saddr limit rate 5/second burst 20 packets } accept icmp type echo-request drop jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype broadcast udp dport 68 accept ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop udp dport 68 accept tcp dport 22 update @_rate_set_3_4 { ip saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { icmpv6 type echo-request update @_rate_set_2_6 { ip6 saddr limit rate 5/second burst 20 packets } accept icmpv6 type echo-request drop jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept tcp dport 22 update @_rate_set_3_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain home-localhost { meta nfproto vmap { ipv4 : jump home-localhost_4, ipv6 : jump home-localhost_6 } } chain home-localhost_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype multicast ip daddr 224.0.0.251 udp dport 5353 accept meta pkttype multicast ip daddr 224.0.0.251 ip protocol igmp accept meta pkttype multicast ip daddr 239.255.255.250 udp dport 1900 accept meta pkttype multicast ip daddr 239.255.255.250 ip protocol igmp accept meta pkttype broadcast udp dport { 11430, 68 } accept meta pkttype { broadcast, multicast } drop udp dport 68 accept udp sport { 1900, 5353 } accept tcp dport 22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "home-localhost DROP " level info flags skuid drop } chain home-localhost_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast ip6 daddr ff02::fb udp dport 5353 accept meta pkttype multicast ip6 daddr ff02::c udp dport 1900 accept meta pkttype multicast drop udp sport { 1900, 5353 } accept tcp dport 22 accept ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "home-localhost DROP " level info flags skuid drop } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 123, 443, 53, 67 } accept tcp dport { 143, 22, 25, 443, 53, 80 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } udp dport { 123, 443, 53 } accept tcp dport { 143, 22, 25, 443, 53, 80 } accept ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-home { meta nfproto vmap { ipv4 : jump localhost-home_4, ipv6 : jump localhost-home_6 } } chain localhost-home_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 123, 19302-19309, 3478, 443, 53, 67 } accept udp sport { 1900, 5353 } accept tcp dport { 143, 22, 25, 443, 53, 631, 80 } accept ip protocol igmp ip daddr 224.0.0.22 accept ip daddr 224.0.0.251 udp dport 5353 accept ip daddr 224.0.0.251 ip protocol igmp accept ip daddr 239.255.255.250 udp dport 1900 accept ip daddr 239.255.255.250 ip protocol igmp accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-home REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-home_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } udp dport { 123, 19302-19309, 3478, 443, 53 } accept udp sport { 1900, 5353 } accept tcp dport { 143, 22, 25, 443, 53, 631, 80 } accept ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept ip6 daddr ff02::fb udp dport 5353 accept ip6 daddr ff02::c udp dport 1900 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-home REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-home { meta nfproto vmap { ipv4 : jump public-home_4, ipv6 : jump public-home_6 } } chain public-home_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-home DROP " level info flags skuid drop } chain public-home_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-home DROP " level info flags skuid drop } chain home-public { meta nfproto vmap { ipv4 : jump home-public_4, ipv6 : jump home-public_6 } } chain home-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "home-public DROP " level info flags skuid drop } chain home-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "home-public DROP " level info flags skuid drop } chain home-home { meta nfproto vmap { ipv4 : jump home-home_4, ipv6 : jump home-home_6 } } chain home-home_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "home-home DROP " level info flags skuid drop } chain home-home_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "home-home DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/10-host/000077500000000000000000000000001506547207000154715ustar00rootroot00000000000000foomuuri-0.29/test/10-host/foomuuri.conf000066400000000000000000000002111506547207000201770ustar00rootroot00000000000000zone { localhost public } public-localhost { dhcp-client dhcpv6-client ping ssh drop log } localhost-public { accept } foomuuri-0.29/test/10-host/golden.txt000066400000000000000000000161531506547207000175100ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype broadcast udp dport 68 accept ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop udp dport 68 accept tcp dport 22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 22 accept ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept accept } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/10-multi-isp/000077500000000000000000000000001506547207000164375ustar00rootroot00000000000000foomuuri-0.29/test/10-multi-isp/foomuuri.conf000066400000000000000000000062141506547207000211560ustar00rootroot00000000000000# Basic configuration: zone { # Define zones with interfaces localhost public enp1s0 enp2s0 internal enp8s0 } foomuuri { # Reverse path filtering must be disabled for public interfaces rpfilter -enp1s0 -enp2s0 } snat { # Masquerade outgoing traffic from internal to public. Both ISPs must be # masqueraded separately. saddr 10.0.0.0/8 oifname enp1s0 masquerade saddr 10.0.0.0/8 oifname enp2s0 masquerade } # Multi-ISP magic is here, using marks to select which ISP to use. Order # of the rules is important. Specific rules should be first, generic last. prerouting { # Accept if mark is already set (not zero). Existing mark will be used. mark_match -0x0000/0xff00 # == Incoming traffic == # Mark traffic from enp1s0 as 0x100 (ISP1) and enp2s0 as 0x200 (ISP2). # This is needed for correctly routing reply packets. iifname enp1s0 mark_set 0x100/0xff00 iifname enp2s0 mark_set 0x200/0xff00 # == Outgoing traffic == # Specific rules should be added first. For example, uncomment next line to # route all SSH traffic from internal to public via ISP2. #iifname enp8s0 ssh mark_set 0x200/0xff00 # Similarly, some source IPs can always be routed via ISP1. #saddr 10.0.1.0/24 mark_set 0x100/0xff00 # For active-active configuration use following line. It uses random number # generator to mark traffic with 0x100 or 0x200. This routes 60% (0-5) # of outgoing traffic to ISP1 and 40% (6-9) to ISP2. nft "meta mark set numgen random mod 10 map { 0-5: 0x100, 6-9: 0x200 } ct mark set meta mark accept" # For active-passive configuration uncomment next line and add comment to # above nft-line. It simply assigns mark 0x100 (ISP1) to all traffic and # uses ISP2 only as fallback. #mark_set 0x100/0xff00 } hook { # Setup "ip rule" and "ip route" rules when Foomuuri starts or stops. # See below for example "multi-isp" file. post_start /etc/foomuuri/multi-isp start post_stop /etc/foomuuri/multi-isp stop } # foomuuri-monitor config: target isp1 { # Monitor ISP1 connectivity by pinging 8.8.4.4. Ideally this would be # some ISP1's router's IP address. command fping --iface enp1s0 8.8.4.4 command_up /etc/foomuuri/multi-isp up 1 command_down /etc/foomuuri/multi-isp down 1 } target isp2 { # Monitor ISP2 connectivity by pinging their router 172.25.31.149. command fping --iface enp2s0 172.25.31.149 command_up /etc/foomuuri/multi-isp up 2 command_down /etc/foomuuri/multi-isp down 2 } # Normal zone-zone rules, copied from router firewall example configuration: public-localhost { ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" drop log } internal-localhost { dhcp-server dhcpv6-server domain domain-s ntp ping ssh reject log } template outgoing_services { # Shared list of services for localhost-public and internal-public. domain domain-s http https ntp ping smtp ssh } localhost-public { template outgoing_services reject log } internal-public { template outgoing_services googlemeet imap reject log } public-internal { drop log } localhost-internal { dhcp-client dhcpv6-client ping ssh reject log } foomuuri-0.29/test/10-multi-isp/golden.txt000066400000000000000000000362461506547207000204630ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } set _rate_set_1_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_1_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } map input_zones { type ifname : verdict elements = { "lo" : accept, "enp1s0" : jump public-localhost, "enp2s0" : jump public-localhost, "enp8s0" : jump internal-localhost, } } map output_zones { type ifname : verdict elements = { "lo" : accept, "enp1s0" : jump localhost-public, "enp2s0" : jump localhost-public, "enp8s0" : jump localhost-internal, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, "enp1s0" . "enp1s0" : jump public-public, "enp1s0" . "enp2s0" : jump public-public, "enp2s0" . "enp1s0" : jump public-public, "enp2s0" . "enp2s0" : jump public-public, "enp1s0" . "enp8s0" : jump public-internal, "enp2s0" . "enp8s0" : jump public-internal, "enp8s0" . "enp1s0" : jump internal-public, "enp8s0" . "enp2s0" : jump internal-public, "enp8s0" . "enp8s0" : jump internal-internal, } } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { icmp type echo-request update @_rate_set_1_4 { ip saddr limit rate 5/second burst 20 packets } accept icmp type echo-request drop jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport 22 update @_rate_set_3_4 { ip saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { icmpv6 type echo-request update @_rate_set_2_6 { ip6 saddr limit rate 5/second burst 20 packets } accept icmpv6 type echo-request drop jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 22 update @_rate_set_3_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain internal-localhost { meta nfproto vmap { ipv4 : jump internal-localhost_4, ipv6 : jump internal-localhost_6 } } chain internal-localhost_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop udp dport { 123, 53, 67, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-localhost_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept meta pkttype multicast drop udp dport { 123, 53, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 25, 443, 53, 80, 853 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 25, 443, 53, 80, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-public { meta nfproto vmap { ipv4 : jump internal-public_4, ipv6 : jump internal-public_6 } } chain internal-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop udp dport { 123, 19302-19309, 3478, 443, 53, 853 } accept tcp dport { 143, 22, 25, 443, 53, 80, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop udp dport { 123, 19302-19309, 3478, 443, 53, 853 } accept tcp dport { 143, 22, 25, 443, 53, 80, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-internal { meta nfproto vmap { ipv4 : jump public-internal_4, ipv6 : jump public-internal_6 } } chain public-internal_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-internal DROP " level info flags skuid drop } chain public-internal_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-internal DROP " level info flags skuid drop } chain localhost-internal { meta nfproto vmap { ipv4 : jump localhost-internal_4, ipv6 : jump localhost-internal_6 } } chain localhost-internal_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 68 } accept tcp dport 22 accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-internal_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 22 accept ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain internal-internal { meta nfproto vmap { ipv4 : jump internal-internal_4, ipv6 : jump internal-internal_6 } } chain internal-internal_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-internal DROP " level info flags skuid drop } chain internal-internal_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-internal DROP " level info flags skuid drop } chain nat_postrouting_srcnat { type nat hook postrouting priority srcnat + 5 oifname "enp1s0" ip saddr 10.0.0.0/8 masquerade oifname "enp2s0" ip saddr 10.0.0.0/8 masquerade } chain filter_prerouting_mangle { type filter hook prerouting priority mangle + 5 meta mark set ct mark meta mark & 0xff00 != 0x0000 accept iifname "enp1s0" meta mark set meta mark & 0xffff00ff | 0x100 ct mark set meta mark accept iifname "enp2s0" meta mark set meta mark & 0xffff00ff | 0x200 ct mark set meta mark accept meta mark set numgen random mod 10 map { 0-5: 0x100, 6-9: 0x200 } ct mark set meta mark accept } chain route_output_mangle { type route hook output priority mangle + 5 meta mark set ct mark } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 iifname != { "enp1s0", "enp2s0" } fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/20-router/000077500000000000000000000000001506547207000160355ustar00rootroot00000000000000foomuuri-0.29/test/20-router/foomuuri.conf000066400000000000000000000062741506547207000205620ustar00rootroot00000000000000zone { localhost public eth0 internal eth1 dmz eth2 } snat { # Masquerade traffic from internal to public. saddr 10.0.0.0/8 oifname eth0 masquerade } dnat { # DNAT incoming SMTP and HTTPS traffic from public to dmz server. This # section is needed only if dmz server doesn't have public IP address. iifname eth0 smtp dnat 10.1.0.2 # public -> dmz iifname eth0 http dnat 10.1.0.2 # public -> dmz iifname eth0 https dnat 10.1.0.2 # public -> dmz iifname eth0 tcp 111 112 113 dnat 10.1.0.2 # set of ports iifname eth1 daddr 192.0.2.32 smtp dnat 10.1.0.2 # internal -> dmz iifname eth1 daddr 192.0.2.32 http dnat 10.1.0.2 # internal -> dmz iifname eth1 daddr 192.0.2.32 https dnat 10.1.0.2 # internal -> dmz } macro { # Define rate limits as macros as same limits are used in public-localhost # and in public-dmz. http_rate saddr_rate "100/second burst 400" saddr_rate_name http_limit mail_rate saddr_rate "1/second burst 10" ping_rate saddr_rate "5/second burst 20" ssh_rate saddr_rate "5/minute burst 5" } template localhost_services { # Shared list of services running on localhost. It runs DHCP server and DNS # resolver for internal and dmz networks, plus basic SSH etc. rules. dhcp-server dhcpv6-server domain domain-s ntp ping ssh } public-localhost { # Allow only ping and SSH from internet to localhost. ping ping_rate ssh ssh_rate drop log } internal-localhost { # Servers on internal network can access localhost's basic services. template localhost_services reject log } dmz-localhost { # Servers on dmz can access localhost's basic services, similar to # internal-localhost. template localhost_services reject log } template public_services { # Shared list of services that run on internet. domain domain-s http https ntp ping ssh } localhost-public { # Basic services from localhost to internet: DNS queries, HTTPS, SSH, etc. template public_services reject log } internal-public { # Basic services from internal to internet: DNS queries, HTTPS, SSH, etc. template public_services googlemeet reject log } dmz-public { # Basic services from dmz to internet, plus SMTP for email transfer.. template public_services smtp reject log } public-internal { # No traffic is allowed from internet to internal network. drop log } localhost-internal { # DHCP server reply packets dhcp-client dhcpv6-client # Very limited access from localhost to internal network. ping ssh reject log } dmz-internal { # Servers on dmz don't need any access to internal network. reject log } template dmz_services { # Shared list of services that run on dmz. http https ping smtp ssh } localhost-dmz { # DHCP server reply packets dhcp-client dhcpv6-client # localhost can access dmz server services. template dmz_services reject log } public-dmz { # Allow traffic from internet to dmz server with rate limits. http http_rate https http_rate smtp mail_rate ping ping_rate ssh ssh_rate drop log } internal-dmz { # Laptops and workstations in internal network can access dmz server # services, plus IMAP for reading email. template dmz_services imap reject log } foomuuri-0.29/test/20-router/golden.txt000066400000000000000000000605701506547207000200560ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } set _rate_set_1_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_1_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_2_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_3_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set http_limit_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set http_limit_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_4_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_4_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_5_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_5_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_6_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_6_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_7_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _rate_set_7_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } map input_zones { type ifname : verdict elements = { "lo" : accept, "eth0" : jump public-localhost, "eth1" : jump internal-localhost, "eth2" : jump dmz-localhost, } } map output_zones { type ifname : verdict elements = { "lo" : accept, "eth0" : jump localhost-public, "eth1" : jump localhost-internal, "eth2" : jump localhost-dmz, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, "eth0" . "eth0" : jump public-public, "eth0" . "eth1" : jump public-internal, "eth0" . "eth2" : jump public-dmz, "eth1" . "eth0" : jump internal-public, "eth1" . "eth1" : jump internal-internal, "eth1" . "eth2" : jump internal-dmz, "eth2" . "eth0" : jump dmz-public, "eth2" . "eth1" : jump dmz-internal, "eth2" . "eth2" : jump dmz-dmz, } } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { icmp type echo-request update @_rate_set_1_4 { ip saddr limit rate 5/second burst 20 packets } accept icmp type echo-request drop jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport 22 update @_rate_set_3_4 { ip saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { icmpv6 type echo-request update @_rate_set_2_6 { ip6 saddr limit rate 5/second burst 20 packets } accept icmpv6 type echo-request drop jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 22 update @_rate_set_3_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain internal-localhost { meta nfproto vmap { ipv4 : jump internal-localhost_4, ipv6 : jump internal-localhost_6 } } chain internal-localhost_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop udp dport { 123, 53, 67, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-localhost_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept meta pkttype multicast drop udp dport { 123, 53, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-localhost { meta nfproto vmap { ipv4 : jump dmz-localhost_4, ipv6 : jump dmz-localhost_6 } } chain dmz-localhost_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop udp dport { 123, 53, 67, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-localhost_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept meta pkttype multicast drop udp dport { 123, 53, 853 } accept tcp dport { 22, 53, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-localhost REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 443, 53, 80, 853 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 443, 53, 80, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-public { meta nfproto vmap { ipv4 : jump internal-public_4, ipv6 : jump internal-public_6 } } chain internal-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop udp dport { 123, 19302-19309, 3478, 443, 53, 853 } accept tcp dport { 22, 443, 53, 80, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop udp dport { 123, 19302-19309, 3478, 443, 53, 853 } accept tcp dport { 22, 443, 53, 80, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-public { meta nfproto vmap { ipv4 : jump dmz-public_4, ipv6 : jump dmz-public_6 } } chain dmz-public_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 25, 443, 53, 80, 853 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-public_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop udp dport { 123, 443, 53, 853 } accept tcp dport { 22, 25, 443, 53, 80, 853 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-internal { meta nfproto vmap { ipv4 : jump public-internal_4, ipv6 : jump public-internal_6 } } chain public-internal_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-internal DROP " level info flags skuid drop } chain public-internal_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-internal DROP " level info flags skuid drop } chain localhost-internal { meta nfproto vmap { ipv4 : jump localhost-internal_4, ipv6 : jump localhost-internal_6 } } chain localhost-internal_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 68 } accept tcp dport 22 accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-internal_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 22 accept ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-internal { meta nfproto vmap { ipv4 : jump dmz-internal_4, ipv6 : jump dmz-internal_6 } } chain dmz-internal_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain dmz-internal_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-internal REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-dmz { meta nfproto vmap { ipv4 : jump localhost-dmz_4, ipv6 : jump localhost-dmz_6 } } chain localhost-dmz_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } udp dport { 443, 68 } accept tcp dport { 22, 25, 443, 80 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-dmz REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-dmz_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } udp dport 443 accept tcp dport { 22, 25, 443, 80 } accept ip6 daddr fe80::/10 udp sport 547 udp dport 546 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-dmz REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-dmz { meta nfproto vmap { ipv4 : jump public-dmz_4, ipv6 : jump public-dmz_6 } } chain public-dmz_4 { icmp type echo-request update @_rate_set_5_4 { ip saddr limit rate 5/second burst 20 packets } accept icmp type echo-request drop jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport 80 update @http_limit_4 { ip saddr limit rate 100/second burst 400 packets } accept tcp dport 443 update @http_limit_4 { ip saddr limit rate 100/second burst 400 packets } accept udp dport 443 update @http_limit_4 { ip saddr limit rate 100/second burst 400 packets } accept tcp dport 25 update @_rate_set_4_4 { ip saddr limit rate 1/second burst 10 packets } accept tcp dport 22 update @_rate_set_7_4 { ip saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-dmz DROP " level info flags skuid drop } chain public-dmz_6 { icmpv6 type echo-request update @_rate_set_6_6 { ip6 saddr limit rate 5/second burst 20 packets } accept icmpv6 type echo-request drop jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 80 update @http_limit_6 { ip6 saddr limit rate 100/second burst 400 packets } accept tcp dport 443 update @http_limit_6 { ip6 saddr limit rate 100/second burst 400 packets } accept udp dport 443 update @http_limit_6 { ip6 saddr limit rate 100/second burst 400 packets } accept tcp dport 25 update @_rate_set_4_6 { ip6 saddr limit rate 1/second burst 10 packets } accept tcp dport 22 update @_rate_set_7_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-dmz DROP " level info flags skuid drop } chain internal-dmz { meta nfproto vmap { ipv4 : jump internal-dmz_4, ipv6 : jump internal-dmz_6 } } chain internal-dmz_4 { icmp type echo-request accept jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop udp dport 443 accept tcp dport { 143, 22, 25, 443, 80 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-dmz REJECT " level info flags skuid reject with icmpx admin-prohibited } chain internal-dmz_6 { icmpv6 type echo-request accept jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop udp dport 443 accept tcp dport { 143, 22, 25, 443, 80 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-dmz REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain internal-internal { meta nfproto vmap { ipv4 : jump internal-internal_4, ipv6 : jump internal-internal_6 } } chain internal-internal_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-internal DROP " level info flags skuid drop } chain internal-internal_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-internal DROP " level info flags skuid drop } chain dmz-dmz { meta nfproto vmap { ipv4 : jump dmz-dmz_4, ipv6 : jump dmz-dmz_6 } } chain dmz-dmz_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-dmz DROP " level info flags skuid drop } chain dmz-dmz_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-dmz DROP " level info flags skuid drop } chain nat_postrouting_srcnat { type nat hook postrouting priority srcnat + 5 oifname "eth0" ip saddr 10.0.0.0/8 masquerade } chain nat_prerouting_dstnat { type nat hook prerouting priority dstnat + 5 iifname "eth0" tcp dport 25 dnat ip to 10.1.0.2 iifname "eth0" tcp dport 80 dnat ip to 10.1.0.2 iifname "eth0" tcp dport 443 dnat ip to 10.1.0.2 iifname "eth0" udp dport 443 dnat ip to 10.1.0.2 iifname "eth0" tcp dport { 111, 112, 113 } dnat ip to 10.1.0.2 iifname "eth1" ip daddr 192.0.2.32 tcp dport 25 dnat ip to 10.1.0.2 iifname "eth1" ip daddr 192.0.2.32 tcp dport 80 dnat ip to 10.1.0.2 iifname "eth1" ip daddr 192.0.2.32 tcp dport 443 dnat ip to 10.1.0.2 iifname "eth1" ip daddr 192.0.2.32 udp dport 443 dnat ip to 10.1.0.2 } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/50-any/000077500000000000000000000000001506547207000153075ustar00rootroot00000000000000foomuuri-0.29/test/50-any/foomuuri.conf000066400000000000000000000004711506547207000200250ustar00rootroot00000000000000zone { localhost public foo bar } any-public { tcp 1000 tcp 1001 szone foo tcp 1002 szone -foo tcp 1003 szone -foo -bar } public-any { tcp 2000 tcp 2001 dzone foo tcp 2002 dzone -foo tcp 2003 dzone -foo -bar } any-any { tcp 3000 tcp 3001 dzone foo tcp 3002 dzone foo szone -bar } foomuuri-0.29/test/50-any/golden.txt000066400000000000000000000446301506547207000173270ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } tcp dport { 1000, 1002, 1003, 3000 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport { 1000, 1002, 1003, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 1000, 1002, 1003, 2000, 2002, 2003, 3000 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 1000, 1002, 1003, 2000, 2002, 2003, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain foo-public { meta nfproto vmap { ipv4 : jump foo-public_4, ipv6 : jump foo-public_6 } } chain foo-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 1000, 1001, 3000 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "foo-public DROP " level info flags skuid drop } chain foo-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 1000, 1001, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "foo-public DROP " level info flags skuid drop } chain bar-public { meta nfproto vmap { ipv4 : jump bar-public_4, ipv6 : jump bar-public_6 } } chain bar-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 1000, 1002, 3000 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "bar-public DROP " level info flags skuid drop } chain bar-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 1000, 1002, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "bar-public DROP " level info flags skuid drop } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport { 2000, 2002, 2003, 3000 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 2000, 2002, 2003, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-foo { meta nfproto vmap { ipv4 : jump public-foo_4, ipv6 : jump public-foo_6 } } chain public-foo_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 2000, 2001, 3000, 3001, 3002 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-foo DROP " level info flags skuid drop } chain public-foo_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 2000, 2001, 3000, 3001, 3002 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-foo DROP " level info flags skuid drop } chain public-bar { meta nfproto vmap { ipv4 : jump public-bar_4, ipv6 : jump public-bar_6 } } chain public-bar_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 2000, 2002, 3000 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-bar DROP " level info flags skuid drop } chain public-bar_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 2000, 2002, 3000 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-bar DROP " level info flags skuid drop } chain localhost-foo { meta nfproto vmap { ipv4 : jump localhost-foo_4, ipv6 : jump localhost-foo_6 } } chain localhost-foo_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } tcp dport { 3000, 3001, 3002 } accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-foo REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-foo_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport { 3000, 3001, 3002 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-foo REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-bar { meta nfproto vmap { ipv4 : jump localhost-bar_4, ipv6 : jump localhost-bar_6 } } chain localhost-bar_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } tcp dport 3000 accept ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-bar REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-bar_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 3000 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-bar REJECT " level info flags skuid reject with icmpx admin-prohibited } chain foo-localhost { meta nfproto vmap { ipv4 : jump foo-localhost_4, ipv6 : jump foo-localhost_6 } } chain foo-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport 3000 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "foo-localhost DROP " level info flags skuid drop } chain foo-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 3000 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "foo-localhost DROP " level info flags skuid drop } chain foo-foo { meta nfproto vmap { ipv4 : jump foo-foo_4, ipv6 : jump foo-foo_6 } } chain foo-foo_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 3000, 3001, 3002 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "foo-foo DROP " level info flags skuid drop } chain foo-foo_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 3000, 3001, 3002 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "foo-foo DROP " level info flags skuid drop } chain foo-bar { meta nfproto vmap { ipv4 : jump foo-bar_4, ipv6 : jump foo-bar_6 } } chain foo-bar_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport 3000 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "foo-bar DROP " level info flags skuid drop } chain foo-bar_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 3000 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "foo-bar DROP " level info flags skuid drop } chain bar-localhost { meta nfproto vmap { ipv4 : jump bar-localhost_4, ipv6 : jump bar-localhost_6 } } chain bar-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport 3000 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "bar-localhost DROP " level info flags skuid drop } chain bar-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 3000 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "bar-localhost DROP " level info flags skuid drop } chain bar-foo { meta nfproto vmap { ipv4 : jump bar-foo_4, ipv6 : jump bar-foo_6 } } chain bar-foo_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport { 3000, 3001 } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "bar-foo DROP " level info flags skuid drop } chain bar-foo_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport { 3000, 3001 } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "bar-foo DROP " level info flags skuid drop } chain bar-bar { meta nfproto vmap { ipv4 : jump bar-bar_4, ipv6 : jump bar-bar_6 } } chain bar-bar_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop tcp dport 3000 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "bar-bar DROP " level info flags skuid drop } chain bar-bar_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 3000 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "bar-bar DROP " level info flags skuid drop } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/50-tcp/000077500000000000000000000000001506547207000153065ustar00rootroot00000000000000foomuuri-0.29/test/50-tcp/foomuuri.conf000066400000000000000000000004761506547207000200310ustar00rootroot00000000000000zone { localhost public } localhost-public { tcp # all tcp traffic reject log # final rule ssh # unreached rule, will not be added } public-localhost { udp # all udp traffic drop # final rule accept # unreached rule, will not be added ssh # unreached rule, will not be added } foomuuri-0.29/test/50-tcp/golden.txt000066400000000000000000000160411506547207000173210ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept ip protocol tcp accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } ip6 nexthdr tcp accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop ip protocol udp accept drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop ip6 nexthdr udp accept drop } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/60-cgroup/000077500000000000000000000000001506547207000160205ustar00rootroot00000000000000foomuuri-0.29/test/60-cgroup/foomuuri.conf000066400000000000000000000004541506547207000205370ustar00rootroot00000000000000foomuuri { nft_bin true # nft checks if cgroup exists, skip it } zone { localhost public } localhost-public { cgroup 10 cgroup 11-14 cgroup 15 16 20-30 cgroup -40 cgroup -41 -42 cgroup -43 -50-60 cgroup "user.slice" } public-localhost { cgroup "system.slice/sshd.service" } foomuuri-0.29/test/60-cgroup/golden.txt000066400000000000000000000174321506547207000200400ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept meta cgroup 10 accept meta cgroup 11-14 accept meta cgroup { 15, 16, 20-30 } accept meta cgroup != 40 accept meta cgroup != { 41, 42 } accept meta cgroup != { 43, 50-60 } accept socket cgroupv2 level 1 "user.slice" accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta cgroup 10 accept meta cgroup 11-14 accept meta cgroup { 15, 16, 20-30 } accept meta cgroup != 40 accept meta cgroup != { 41, 42 } accept meta cgroup != { 43, 50-60 } accept socket cgroupv2 level 1 "user.slice" accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop socket cgroupv2 level 2 "system.slice/sshd.service" accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop socket cgroupv2 level 2 "system.slice/sshd.service" accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/60-conntrack/000077500000000000000000000000001506547207000165035ustar00rootroot00000000000000foomuuri-0.29/test/60-conntrack/foomuuri.conf000066400000000000000000000020561506547207000212220ustar00rootroot00000000000000zone { localhost public } localhost-public { # pre-ct: log and count traffic, don't accept yet log "outgoing" # plain rule, implicit -conntrack and continue log "outgoing_2" continue -conntrack # same as above rule counter all_out # plain rule, implicit -conntrack and continue counter all_out_2 continue -conntrack # same as above rule tcp sport 22 counter ssh_out continue -conntrack # post-ct: normal rules ssh https counter new_https # post-ct, so counts only new, not established log "post-ct,pre-reject" continue conntrack reject } public-localhost { # pre-ct: log and count traffic, don't accept yet log "incoming" log "incoming_2" continue -conntrack counter all_in counter all_in_2 continue -conntrack tcp dport 22 counter ssh_in continue -conntrack # post-ct: normal rules ssh drop } # Incoming traffic prerouting filter raw { domain notrack tcp sport 53 notrack udp sport 53 notrack } # Locally created traffic output filter raw { domain notrack tcp sport 53 notrack udp sport 53 notrack } foomuuri-0.29/test/60-conntrack/golden.txt000066400000000000000000000221251506547207000205160ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "outgoing " level info flags skuid update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "outgoing_2 " level info flags skuid counter name "all_out" continue counter name "all_out_2" continue tcp sport 22 counter name "ssh_out" continue jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } tcp dport 22 accept ip protocol igmp ip daddr 224.0.0.22 accept tcp dport 443 counter name "new_https" accept udp dport 443 counter name "new_https" accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "post-ct,pre-reject " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "outgoing " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "outgoing_2 " level info flags skuid counter name "all_out" continue counter name "all_out_2" continue tcp sport 22 counter name "ssh_out" continue jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 22 accept tcp dport 443 counter name "new_https" accept udp dport 443 counter name "new_https" accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "post-ct,pre-reject " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "incoming " level info flags skuid update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "incoming_2 " level info flags skuid counter name "all_in" continue counter name "all_in_2" continue tcp dport 22 counter name "ssh_in" continue jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop tcp dport 22 accept drop } chain public-localhost_6 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "incoming " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "incoming_2 " level info flags skuid counter name "all_in" continue counter name "all_in_2" continue tcp dport 22 counter name "ssh_in" continue jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop tcp dport 22 accept drop } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_prerouting_raw { type filter hook prerouting priority raw tcp dport 53 notrack udp dport 53 notrack tcp sport 53 notrack udp sport 53 notrack } chain filter_output_raw { type filter hook output priority raw tcp dport 53 notrack udp dport 53 notrack tcp sport 53 notrack udp sport 53 notrack } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } counter all_in { } counter all_in_2 { } counter all_out { } counter all_out_2 { } counter new_https { } counter ssh_in { } counter ssh_out { } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/60-queue/000077500000000000000000000000001506547207000156455ustar00rootroot00000000000000foomuuri-0.29/test/60-queue/foomuuri.conf000066400000000000000000000004751506547207000203670ustar00rootroot00000000000000zone { localhost public } forward { # Log all packets using nflog group 3 log "Forward-IPS" log_level "group 3" # Count all packets counter # Forward matching packets only iifname eth0 oifname eth1 queue # Forward all packets to userspace for IPS inspection queue flags fanout,bypass to 3-5 } foomuuri-0.29/test/60-queue/golden.txt000066400000000000000000000166431506547207000176700ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_forward_mangle { type filter hook forward priority mangle + 5 log prefix "Forward-IPS " group 3 continue counter continue iifname "eth0" oifname "eth1" queue queue flags fanout,bypass to 3-5 } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/60-special-chains/000077500000000000000000000000001506547207000174045ustar00rootroot00000000000000foomuuri-0.29/test/60-special-chains/foomuuri.conf000066400000000000000000000011421506547207000221160ustar00rootroot00000000000000zone { localhost public } snat { } snat { saddr 10.0.0.1 accept } snat nat srcnat + 10 { saddr 10.0.0.2 accept } postrouting nat srcnat + 20 { saddr 10.0.0.22 accept } prerouting { saddr 10.0.0.3 accept } prerouting filter raw { saddr 10.0.0.4 accept } prerouting filter -300 { saddr 10.0.0.5 accept } prerouting filter 200 { saddr 10.0.0.6 accept } prerouting filter raw + 20 { saddr 10.0.0.7 accept } prerouting filter raw - 10 { saddr 10.0.0.77 accept } output { saddr 10.0.0.8 accept } output filter raw { saddr 10.0.0.9 accept } rpfilter { saddr 10.0.0.10 accept } foomuuri-0.29/test/60-special-chains/golden.txt000066400000000000000000000206311506547207000214170ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { ip saddr 10.0.0.10 accept udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain nat_postrouting_srcnat { type nat hook postrouting priority srcnat + 5 ip saddr 10.0.0.1 accept } chain nat_postrouting_srcnat_10 { type nat hook postrouting priority srcnat + 10 ip saddr 10.0.0.2 accept } chain filter_prerouting_mangle { type filter hook prerouting priority mangle + 5 ip saddr 10.0.0.3 accept } chain filter_prerouting_raw { type filter hook prerouting priority raw ip saddr 10.0.0.4 accept } chain filter_prerouting_-300 { type filter hook prerouting priority -300 ip saddr 10.0.0.5 accept } chain filter_prerouting_200 { type filter hook prerouting priority 200 ip saddr 10.0.0.6 accept } chain filter_prerouting_raw_20 { type filter hook prerouting priority raw + 20 ip saddr 10.0.0.7 accept } chain filter_prerouting_raw_-_10 { type filter hook prerouting priority raw - 10 ip saddr 10.0.0.77 accept } chain nat_postrouting_srcnat_20 { type nat hook postrouting priority srcnat + 20 ip saddr 10.0.0.22 accept } chain route_output_mangle { type route hook output priority mangle + 5 ip saddr 10.0.0.8 accept } chain filter_output_raw { type filter hook output priority raw ip saddr 10.0.0.9 accept } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-dscp/000077500000000000000000000000001506547207000154535ustar00rootroot00000000000000foomuuri-0.29/test/70-dscp/foomuuri.conf000066400000000000000000000004331506547207000201670ustar00rootroot00000000000000zone { localhost public } localhost-public { dscp 1 saddr 10.0.0.1 dscp 2 saddr ff00::2 dscp 3 dscp 0x38 drop dscp -0x20 log reject ssh dscp cs1 dscp -cs1 -cs2 dscp cs0 cs1 cs2 cs3 cs4 cs5 cs6 cs7 af11 af12 af13 af21 af22 af23 af31 af32 af33 af41 af42 af43 ef } foomuuri-0.29/test/70-dscp/golden.txt000066400000000000000000000203061506547207000174650ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept ip dscp 1 accept ip saddr 10.0.0.1 ip dscp 2 accept ip dscp 0x38 drop ip dscp != 0x20 jump lograte_1 tcp dport 22 ip dscp cs1 accept ip dscp != { cs1, cs2 } accept ip dscp { af11, af12, af13, af21, af22, af23, af31, af32, af33, af41, af42, af43, cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7, ef } accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } ip6 dscp 1 accept ip6 saddr ff00::2 ip6 dscp 3 accept ip6 dscp 0x38 drop ip6 dscp != 0x20 jump lograte_3 tcp dport 22 ip6 dscp cs1 accept ip6 dscp != { cs1, cs2 } accept ip6 dscp { af11, af12, af13, af21, af22, af23, af31, af32, af33, af41, af42, af43, cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7, ef } accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain lograte_1 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain lograte_3 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-iplist/000077500000000000000000000000001506547207000160265ustar00rootroot00000000000000foomuuri-0.29/test/70-iplist/Makefile000066400000000000000000000003621506547207000174670ustar00rootroot00000000000000.PHONY: all all: ../../src/foomuuri --set=etc_dir=. --set=share_dir=../../etc --set=state_dir=. --set=run_dir=. --set=nft_bin=true iplist refresh diff -u golden-cache-json.txt iplist-cache.json diff -u golden-cache-fw.txt iplist-cache.fw foomuuri-0.29/test/70-iplist/foomuuri.conf000066400000000000000000000002771506547207000205500ustar00rootroot00000000000000zone { localhost public } iplist { timeout 1h 2m # backward compabiliy (v0.27) url_refresh=1d2h3m @empty @two 10.0.0.1 ff00::1 /non/existing/file|missing-ok dns_refresh=3h } foomuuri-0.29/test/70-iplist/golden-cache-fw.txt000066400000000000000000000003331506547207000215110ustar00rootroot00000000000000 flush set inet foomuuri empty_4 flush set inet foomuuri empty_6 flush set inet foomuuri two_4 add element inet foomuuri two_4 { 10.0.0.1, } flush set inet foomuuri two_6 add element inet foomuuri two_6 { ff00::1, } foomuuri-0.29/test/70-iplist/golden-cache-json.txt000066400000000000000000000002261506547207000220470ustar00rootroot00000000000000{ "@empty": { "ip": {} }, "@two": { "ip": { "10.0.0.1": "2100-01-01T00:00:00", "ff00::1": "2100-01-01T00:00:00" } } } foomuuri-0.29/test/70-iplist/golden.txt000066400000000000000000000173071506547207000200470ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } set empty_4 { type ipv4_addr flags interval,timeout auto-merge } set empty_6 { type ipv6_addr flags interval,timeout auto-merge } set two_4 { type ipv4_addr flags interval,timeout auto-merge } set two_6 { type ipv6_addr flags interval,timeout auto-merge } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } flush set inet foomuuri empty_4 flush set inet foomuuri empty_6 flush set inet foomuuri two_4 add element inet foomuuri two_4 { 10.0.0.1, } flush set inet foomuuri two_6 add element inet foomuuri two_6 { ff00::1, } foomuuri-0.29/test/70-macro/000077500000000000000000000000001506547207000156235ustar00rootroot00000000000000foomuuri-0.29/test/70-macro/foomuuri.conf000066400000000000000000000010411506547207000203330ustar00rootroot00000000000000zone { localhost public } macro { single 10.1.1.1 multiple 10.1.2.2 10.1.3.3 10.1.4.4 netmask 10.1.2.0 10.1.3.0 10.1.4.0 single6 ffe0::1 netmask6 ffe0:1:: ffe0:2:: } dnat { iifname eth4 tcp 123 dnat single:44 iifname eth6 tcp 123 dnat [single6]:66 } localhost-public { tcp 1000 saddr single tcp 1001 saddr -single tcp 1002 saddr multiple tcp 1003 saddr -multiple tcp 1004 saddr netmask/24 tcp 1005 saddr -netmask/24 tcp 1006 saddr single6 tcp 1007 saddr -single6 tcp 1008 saddr netmask6/64 } foomuuri-0.29/test/70-macro/golden.txt000066400000000000000000000176251506547207000176470ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept ip saddr 10.1.1.1 tcp dport 1000 accept ip saddr != 10.1.1.1 tcp dport 1001 accept ip saddr { 10.1.2.2, 10.1.3.3, 10.1.4.4 } tcp dport 1002 accept ip saddr != { 10.1.2.2, 10.1.3.3, 10.1.4.4 } tcp dport 1003 accept ip saddr { 10.1.2.0/24, 10.1.3.0/24, 10.1.4.0/24 } tcp dport 1004 accept ip saddr != { 10.1.2.0/24, 10.1.3.0/24, 10.1.4.0/24 } tcp dport 1005 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } ip6 saddr ffe0::1 tcp dport 1006 accept ip6 saddr != ffe0::1 tcp dport 1007 accept ip6 saddr { ffe0:1::/64, ffe0:2::/64 } tcp dport 1008 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain nat_prerouting_dstnat { type nat hook prerouting priority dstnat + 5 iifname "eth4" tcp dport 123 dnat ip to 10.1.1.1:44 iifname "eth6" tcp dport 123 dnat ip6 to [ffe0::1]:66 } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-mark/000077500000000000000000000000001506547207000154545ustar00rootroot00000000000000foomuuri-0.29/test/70-mark/foomuuri.conf000066400000000000000000000001601506547207000201650ustar00rootroot00000000000000zone { localhost public } forward { iifname eth0 mark_set 42 } localhost-public { ssh mark_match 42 } foomuuri-0.29/test/70-mark/golden.txt000066400000000000000000000171231506547207000174710ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept meta mark set ct mark tcp dport 22 meta mark == 42 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta mark set ct mark tcp dport 22 meta mark == 42 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_forward_mangle { type filter hook forward priority mangle + 5 meta mark set ct mark iifname "eth0" meta mark set 42 ct mark set meta mark accept } chain route_output_mangle { type route hook output priority mangle + 5 meta mark set ct mark } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-mss/000077500000000000000000000000001506547207000153245ustar00rootroot00000000000000foomuuri-0.29/test/70-mss/foomuuri.conf000066400000000000000000000007731506547207000200470ustar00rootroot00000000000000zone { localhost public } macro { only_4 10.0.0.0/24 only_6 ff00::/64 both only_4 only_6 } forward { ipv4 iifname eth4 daddr both mss 1004 ipv6 iifname eth6 daddr both mss 1006 iifname eth0 daddr both mss pmtu ipv6 saddr only_4 mss pmtu counter log "empty" ipv6 saddr only_6 mss pmtu counter log "flood my log" ipv6 saddr both mss pmtu counter log "flood my log even more" } localhost-public { mss 1000 ipv4 mss 1004 ipv6 mss 1006 iifname eth6 daddr only_6 mss pmtu } foomuuri-0.29/test/70-mss/golden.txt000066400000000000000000000202131506547207000173330ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { tcp flags syn tcp option maxseg size set 1000 tcp flags syn tcp option maxseg size set 1004 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { tcp flags syn tcp option maxseg size set 1000 tcp flags syn tcp option maxseg size set 1006 iifname "eth6" ip6 daddr ff00::/64 tcp flags syn tcp option maxseg size set rt mtu jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_forward_mangle { type filter hook forward priority mangle + 5 iifname "eth4" ip daddr 10.0.0.0/24 tcp flags syn tcp option maxseg size set 1004 iifname "eth6" ip6 daddr ff00::/64 tcp flags syn tcp option maxseg size set 1006 iifname "eth0" ip daddr 10.0.0.0/24 tcp flags syn tcp option maxseg size set rt mtu iifname "eth0" ip6 daddr ff00::/64 tcp flags syn tcp option maxseg size set rt mtu ip6 saddr ff00::/64 counter log prefix "flood my log " level info flags skuid tcp flags syn tcp option maxseg size set rt mtu ip6 saddr ff00::/64 counter log prefix "flood my log even more " level info flags skuid tcp flags syn tcp option maxseg size set rt mtu } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-priority/000077500000000000000000000000001506547207000164035ustar00rootroot00000000000000foomuuri-0.29/test/70-priority/foomuuri.conf000066400000000000000000000003261506547207000211200ustar00rootroot00000000000000zone { localhost public } forward { iifname eth0 priority_set 1:4242 iifname eth1 priority_match none priority_set 1:4343 } localhost-public { ssh priority_match none drop ssh priority_match 1:4242 } foomuuri-0.29/test/70-priority/golden.txt000066400000000000000000000171011506547207000204140ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept tcp dport 22 meta priority "none" drop tcp dport 22 meta priority "1:4242" accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 22 meta priority "none" drop tcp dport 22 meta priority "1:4242" accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_forward_mangle { type filter hook forward priority mangle + 5 iifname "eth0" meta priority set "1:4242" accept iifname "eth1" meta priority "none" meta priority set "1:4343" accept } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-rate/000077500000000000000000000000001506547207000154555ustar00rootroot00000000000000foomuuri-0.29/test/70-rate/foomuuri.conf000066400000000000000000000007341506547207000201750ustar00rootroot00000000000000zone { localhost public } public-localhost { global_rate "3/second" accept global_rate "4/minute burst 5" accept global_rate "5/hour burst 6 packets" accept global_rate "over 3/minute" drop global_rate "2 bytes/second" accept global_rate "3 kbytes/minute burst 4 mbytes" accept global_rate "over 1 mbytes/second" accept global_rate "over 6 kbytes/second burst 1 mbytes" accept global_rate "ct count 4" accept global_rate "ct count over 5" accept } foomuuri-0.29/test/70-rate/golden.txt000066400000000000000000000176721506547207000175030ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop limit rate 3/second accept limit rate 4/minute burst 5 packets accept limit rate 5/hour burst 6 packets accept limit rate over 3/minute drop limit rate 2 bytes/second accept limit rate 3 kbytes/minute burst 4 mbytes accept limit rate over 1 mbytes/second accept limit rate over 6 kbytes/second burst 1 mbytes accept ct count 4 accept ct count over 5 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop limit rate 3/second accept limit rate 4/minute burst 5 packets accept limit rate 5/hour burst 6 packets accept limit rate over 3/minute drop limit rate 2 bytes/second accept limit rate 3 kbytes/minute burst 4 mbytes accept limit rate over 1 mbytes/second accept limit rate over 6 kbytes/second burst 1 mbytes accept ct count 4 accept ct count over 5 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-snat/000077500000000000000000000000001506547207000154675ustar00rootroot00000000000000foomuuri-0.29/test/70-snat/foomuuri.conf000066400000000000000000000013151506547207000202030ustar00rootroot00000000000000zone { localhost public } snat { # basic oifname eth0 snat 2a03:1111:1111::1 saddr fc00:ffff:2222::/64 snat 2a03:1111:222:2222::2 # port tcp saddr fc00:ffff:3333::/64 snat [2a03:1111:222:3333::3]:9999-11000 # pool saddr fc00:ffff:4444::/64 snat 2a03:1111:222:4444::/64 # ipv6 prefix saddr fc00:ffff:5555::/64 snat_prefix 2a03:1111:222:5555::/64 # ipv4 port tcp saddr 10.1.2.0/24 snat 10.0.0.5:9999-11000 # ipv4 pool saddr 10.2.3.0/24 snat 10.0.0.2/31 saddr 10.3.4.0/24 snat 10.0.0.4-10.0.0.127 # IPv4 and IPv6 in one line oifname eth1 snat 10.0.0.6 2a03:1111:222:6666::6 # deprecated 'to' keyword is still accepted oifname eth2 snat to 10.0.0.7 2a03:1111:222:7777::7 } foomuuri-0.29/test/70-snat/golden.txt000066400000000000000000000177251506547207000175140ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain nat_postrouting_srcnat { type nat hook postrouting priority srcnat + 5 oifname "eth0" snat ip6 to 2a03:1111:1111::1 ip6 saddr fc00:ffff:2222::/64 snat ip6 to 2a03:1111:222:2222::2 ip6 saddr fc00:ffff:3333::/64 ip6 nexthdr tcp snat ip6 to [2a03:1111:222:3333::3]:9999-11000 ip6 saddr fc00:ffff:4444::/64 snat ip6 to 2a03:1111:222:4444::/64 ip6 saddr fc00:ffff:5555::/64 snat ip6 prefix to 2a03:1111:222:5555::/64 ip saddr 10.1.2.0/24 ip protocol tcp snat ip to 10.0.0.5:9999-11000 ip saddr 10.2.3.0/24 snat ip to 10.0.0.2/31 ip saddr 10.3.4.0/24 snat ip to 10.0.0.4-10.0.0.127 oifname "eth1" snat ip to 10.0.0.6 oifname "eth1" snat ip6 to 2a03:1111:222:6666::6 oifname "eth2" snat ip to 10.0.0.7 oifname "eth2" snat ip6 to 2a03:1111:222:7777::7 } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-template/000077500000000000000000000000001506547207000163355ustar00rootroot00000000000000foomuuri-0.29/test/70-template/foomuuri.conf000066400000000000000000000012761506547207000210570ustar00rootroot00000000000000zone { localhost public } template simple { tcp 1 tcp 2 tcp 3 drop } template double { udp 4 template simple udp 5 } template multi_1 { tcp udp } template multi_2 { dport 6 sport 7 } template multi_3 { reject } template services { tcp 10 tcp 11 tcp 12 udp 13 drop udp 14 drop } template addresses { saddr 10.0.0.5 saddr 10.0.0.6 } localhost-public { template simple template double template services saddr 10.0.0.2 template services saddr 10.0.0.4 tcp 20 21 template addresses drop template services template addresses # saddr tcp/udp dport6/sport7 reject => 4 lines template multi_1 saddr 10.0.0.1 template multi_2 template multi_3 } foomuuri-0.29/test/70-template/golden.txt000066400000000000000000000211201506547207000203420ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } tcp dport { 1, 2 } accept ip protocol igmp ip daddr 224.0.0.22 accept tcp dport 3 drop udp dport 4 accept tcp dport { 1, 2 } accept tcp dport 3 drop udp dport 5 accept ip saddr 10.0.0.2 tcp dport 10 accept ip saddr 10.0.0.2 tcp dport 11 accept ip saddr 10.0.0.2 tcp dport 12 accept ip saddr 10.0.0.2 udp dport 13 drop ip saddr 10.0.0.2 udp dport 14 drop ip saddr 10.0.0.4 tcp dport 10 accept ip saddr 10.0.0.4 tcp dport 11 accept ip saddr 10.0.0.4 tcp dport 12 accept ip saddr 10.0.0.4 udp dport 13 drop ip saddr 10.0.0.4 udp dport 14 drop ip saddr 10.0.0.5 tcp dport { 20, 21 } drop ip saddr 10.0.0.6 tcp dport { 20, 21 } drop ip saddr 10.0.0.5 tcp dport 10 accept ip saddr 10.0.0.6 tcp dport 10 accept ip saddr 10.0.0.5 tcp dport 11 accept ip saddr 10.0.0.6 tcp dport 11 accept ip saddr 10.0.0.5 tcp dport 12 accept ip saddr 10.0.0.6 tcp dport 12 accept ip saddr 10.0.0.5 udp dport 13 drop ip saddr 10.0.0.6 udp dport 13 drop ip saddr 10.0.0.5 udp dport 14 drop ip saddr 10.0.0.6 udp dport 14 drop ip saddr 10.0.0.1 tcp dport 6 reject with icmpx admin-prohibited ip saddr 10.0.0.1 tcp sport 7 reject with icmpx admin-prohibited ip saddr 10.0.0.1 udp dport 6 reject with icmpx admin-prohibited ip saddr 10.0.0.1 udp sport 7 reject with icmpx admin-prohibited update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport { 1, 2 } accept tcp dport 3 drop udp dport 4 accept tcp dport { 1, 2 } accept tcp dport 3 drop udp dport 5 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-time/000077500000000000000000000000001506547207000154605ustar00rootroot00000000000000foomuuri-0.29/test/70-time/foomuuri.conf000066400000000000000000000012131506547207000201710ustar00rootroot00000000000000zone { localhost public } localhost-public { # day tcp 2000 time monday tcp 2001 time "<= tuesday" # hour tcp 3000 time 16:00 tcp 3001 time 16:00:01 tcp 3002 time "== 16:00:02" tcp 3003 time "> 17:00:23" tcp 3004 time "18:00-19:00" tcp 3005 time "22:00-03:00" # time tcp 4000 time 2024-05-03 tcp 4001 time ">= 2024-05-04 19:00" tcp 4002 time "< 2024-05-04 19:00:01" tcp 4003 time "!= 2024-05-05 19:30-21:00" # misc tcp 5001 time "monday 20:00-22:00 > 2025-01-01" tcp 5002 time "> 2025-01-02 != Tuesday" tcp 5003 time "!= Tuesday > 2025-01-02" tcp 5004 time Friday > 21:00 # Not recommended without " } foomuuri-0.29/test/70-time/golden.txt000066400000000000000000000213141506547207000174720ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept tcp dport 2000 day "Monday" accept tcp dport 2001 day <= "Tuesday" accept tcp dport 3000 hour "16:00" accept tcp dport 3001 hour "16:00:01" accept tcp dport 3002 hour "16:00:02" accept tcp dport 3003 hour > "17:00:23" accept tcp dport 3004 hour 18:00-19:00 accept tcp dport 3005 hour 22:00-03:00 accept tcp dport 4000 time "2024-05-03" accept tcp dport 4001 time >= "2024-05-04 19:00" accept tcp dport 4002 time < "2024-05-04 19:00:01" accept tcp dport 4003 time != "2024-05-05 19:30-21:00" accept tcp dport 5001 day "Monday" hour 20:00-22:00 time > "2025-01-01" accept tcp dport 5002 time > "2025-01-02" day != "Tuesday" accept tcp dport 5003 day != "Tuesday" time > "2025-01-02" accept tcp dport 5004 day "Friday" hour > "21:00" accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } tcp dport 2000 day "Monday" accept tcp dport 2001 day <= "Tuesday" accept tcp dport 3000 hour "16:00" accept tcp dport 3001 hour "16:00:01" accept tcp dport 3002 hour "16:00:02" accept tcp dport 3003 hour > "17:00:23" accept tcp dport 3004 hour 18:00-19:00 accept tcp dport 3005 hour 22:00-03:00 accept tcp dport 4000 time "2024-05-03" accept tcp dport 4001 time >= "2024-05-04 19:00" accept tcp dport 4002 time < "2024-05-04 19:00:01" accept tcp dport 4003 time != "2024-05-05 19:30-21:00" accept tcp dport 5001 day "Monday" hour 20:00-22:00 time > "2025-01-01" accept tcp dport 5002 time > "2025-01-02" day != "Tuesday" accept tcp dport 5003 day != "Tuesday" time > "2025-01-02" accept tcp dport 5004 day "Friday" hour > "21:00" accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/70-tproxy/000077500000000000000000000000001506547207000160675ustar00rootroot00000000000000foomuuri-0.29/test/70-tproxy/foomuuri.conf000066400000000000000000000010001506547207000205720ustar00rootroot00000000000000zone { localhost public } prerouting { # Use lower 8 bits to mark tproxy traffic mark_match -0x00/0xff # Anti-loop protection # Specific IPv4 port only. My own IP is 192.168.0.1 so that # sysctl net.ipv4.conf.eth0.route_localnet=1 is not needed. tcp 8888 tproxy 192.168.0.1:18888 mark_set 0x01/0xff # All IPv4 TCP traffic, does not match IPv6 tcp tproxy 192.168.0.1:12345 mark_set 0x02/0xff # All IPv4 and IPv6 TCP traffic tcp tproxy 192.168.0.1:42424 [::1]:42424 mark_set 0x03/0xff } foomuuri-0.29/test/70-tproxy/golden.txt000066400000000000000000000176421506547207000201120ustar00rootroot00000000000000table inet foomuuri delete table inet foomuuri table inet foomuuri { chain allow_icmp_4 { icmp type { destination-unreachable, # 3, Destination Unreachable time-exceeded, # 11, Time Exceeded parameter-problem # 12, Parameter Problem } accept } chain allow_icmp_6 { icmpv6 type { destination-unreachable, # 1, Destination Unreachable packet-too-big, # 2, Packet Too Big time-exceeded, # 3, Time Exceeded parameter-problem, # 4, Parameter Problem nd-router-solicit, # 133, Router Solicitation nd-neighbor-solicit, # 135, Neighbor Solicitation nd-neighbor-advert, # 136, Neighbor Advertisement ind-neighbor-solicit, # 141, Inverse Neighbor Discovery Solicitation Message ind-neighbor-advert # 142, Inverse Neighbor Discovery Advertisement Message } accept icmpv6 type . ip6 saddr { nd-router-advert . fe80::/10, # 134, Router Advertisement mld-listener-query . fe80::/10, # 130, Multicast Listener Query mld-listener-report . fe80::/10, # 131, Multicast Listener Report mld-listener-done . fe80::/10, # 132, Multicast Listener Done 149 . fe80::/10, # 149, Certification Path Advertisement Message 151 . fe80::/10, # 151, Multicast Router Advertisement 152 . fe80::/10, # 152, Multicast Router Solicitation 153 . fe80::/10, # 153, Multicast Router Termination mld2-listener-report . fe80::/10, # 143, Version 2 Multicast Listener Report mld2-listener-report . ::, 148 . fe80::/10, # 148, Certification Path Solicitation Message 148 . :: } accept } chain smurfs_4 { ip saddr 0.0.0.0 return fib saddr type { broadcast, multicast } jump smurfs_drop } chain smurfs_6 { fib saddr type multicast jump smurfs_drop } chain invalid_drop { drop } chain smurfs_drop { drop } chain rpfilter_drop { udp sport 67 udp dport 68 return update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "RPFILTER DROP " level info flags skuid drop } chain input { type filter hook input priority filter + 5 iifname vmap @input_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "INPUT DROP " level info flags skuid drop } chain output { type filter hook output priority filter + 5 oifname vmap @output_zones ip protocol igmp ip daddr 224.0.0.22 accept ip6 saddr :: icmpv6 type mld2-listener-report accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "OUTPUT REJECT " level info flags skuid reject with icmpx admin-prohibited } chain forward { type filter hook forward priority filter + 5 iifname . oifname vmap @forward_zones update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "FORWARD DROP " level info flags skuid drop } map input_zones { type ifname : verdict elements = { "lo" : accept, } } map output_zones { type ifname : verdict elements = { "lo" : accept, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, } } chain localhost-localhost { meta nfproto vmap { ipv4 : jump localhost-localhost_4, ipv6 : jump localhost-localhost_6 } } chain localhost-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } accept } chain localhost-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } accept } chain localhost-public { meta nfproto vmap { ipv4 : jump localhost-public_4, ipv6 : jump localhost-public_6 } } chain localhost-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public REJECT " level info flags skuid reject with icmpx admin-prohibited } chain public-localhost { meta nfproto vmap { ipv4 : jump public-localhost_4, ipv6 : jump public-localhost_6 } } chain public-localhost_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } ip protocol igmp ip daddr 224.0.0.1 accept meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-localhost_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain public-public { meta nfproto vmap { ipv4 : jump public-public_4, ipv6 : jump public-public_6 } } chain public-public_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } meta pkttype { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain public-public_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } meta pkttype multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain filter_prerouting_mangle { type filter hook prerouting priority mangle + 5 meta mark set ct mark meta mark & 0xff != 0x00 accept tcp dport 8888 tproxy ip to 192.168.0.1:18888 meta mark set meta mark & 0xffffff00 | 0x01 ct mark set meta mark accept ip protocol tcp tproxy ip to 192.168.0.1:12345 meta mark set meta mark & 0xffffff00 | 0x02 ct mark set meta mark accept ip protocol tcp tproxy ip to 192.168.0.1:42424 meta mark set meta mark & 0xffffff00 | 0x03 ct mark set meta mark accept ip6 nexthdr tcp tproxy ip6 to [::1]:42424 meta mark set meta mark & 0xffffff00 | 0x03 ct mark set meta mark accept } chain route_output_mangle { type route hook output priority mangle + 5 meta mark set ct mark } set _lograte_set_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 1m } set _lograte_set_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1m } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.29/test/Makefile000066400000000000000000000006541506547207000157430ustar00rootroot00000000000000SUBDIRS ?= $(sort $(wildcard ??-*)) .PHONY: all clean distclean $(SUBDIRS) all: $(SUBDIRS) flake8 ../src/foomuuri pycodestyle ../src/foomuuri pylint ../src/foomuuri clean distclean: rm -f */*.fw rm -f */iplist-cache.json rm -f */zone $(SUBDIRS): ../src/foomuuri --set=etc_dir=$@ --set=share_dir=../etc --set=state_dir=$@ --set=run_dir=$@ check diff -u $@/golden.txt $@/next.fw [ ! -f $@/Makefile ] || $(MAKE) -C $@