pax_global_header00006660000000000000000000000064152100166570014515gustar00rootroot0000000000000052 comment=f10f9d19ed06828ecfd588a86bcdabc5ae48e007 foomuuri-0.33/000077500000000000000000000000001521001665700133075ustar00rootroot00000000000000foomuuri-0.33/.github/000077500000000000000000000000001521001665700146475ustar00rootroot00000000000000foomuuri-0.33/.github/workflows/000077500000000000000000000000001521001665700167045ustar00rootroot00000000000000foomuuri-0.33/.github/workflows/run_tests.yml000066400000000000000000000011351521001665700214550ustar00rootroot00000000000000name: Foomuuri on: push: branches: - main pull_request: branches: - main jobs: run_tests: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.12", "3.14"] steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: pip install pylint ruff ty - name: Run test suite run: make test - name: Run lint run: make lint foomuuri-0.33/.gitignore000066400000000000000000000003261521001665700153000ustar00rootroot00000000000000foomuuri-*.tar.gz test/*/*.fw test/*/iplist-cache.json test/*/zone .pre-commit-config.yaml .coverage .ruff_cache htmlcov misc/webdoc/docs misc/webdoc/final misc/webdoc/site misc/webdoc/zensical.toml __pycache__ foomuuri-0.33/.ruff.toml000066400000000000000000000042541521001665700152310ustar00rootroot00000000000000target-version = "py39" lint.select = [ "ALL", ] lint.preview = true lint.ignore = [ "ANN", "C901", # function is too complex "COM812", # [*] Trailing comma missing "D203", "D213", "D401", # First line of docstring should be in imperative mood: "DTZ007", # Naive datetime constructed "EM101", # Exception must not use a string literal, assign to variable first "EM102", # Exception must not use an f-string literal, assign to variable first "ERA001", # Found commented-out code "FA100", # Add `from __future__ import annotations` to simplify "FBT001", # Boolean-typed positional argument in function definition "FBT002", # Boolean default positional argument in function definition "FBT003", # Boolean positional value in function call "N802", # Function name `changeZoneOfSource` should be lowercase "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments in function definition "PLR0915", # Too many statements "PLR2004", # Magic value used in comparison "PLW2901", # redefined-loop-name () "PT009", "PT019", # Fixture `_` without value is injected as parameter, use `@pytest.mark.usefixtures` instead "PT027", # Use `pytest.raises` instead of unittest-style `assertRaises` "RUF005", # Consider `[*cmd[:1], '--loop', *cmd[1:]]` instead of concatenation "S602", "S603", "S607", # Starting a process with a partial executable path "T201", "TRY003", # Avoid specifying long messages outside the exception class "TRY301", # Abstract `raise` to an inner function "UP007", # [*] Use `X | Y` for type annotations "UP045", # [*] Use `X | None` for type annotations # --preview "CPY001", # Missing copyright notice at top of file "DOC201", # `return` is not documented in docstring "DOC402", # `yield` is not documented in docstring "DOC501", # Raised exception `KeyboardInterrupt` missing from docstring "FURB118", # Use `operator.add` instead of defining a lambda "S404", # `subprocess` module is possibly insecure ] lint.flake8-quotes.inline-quotes = "single" format.quote-style = "single" line-length = 79 foomuuri-0.33/CHANGELOG.md000066400000000000000000000367111521001665700151300ustar00rootroot00000000000000# Changelog ## 0.33 (2026-06-03) * Documentation moved to https://foomuuri.foobar.fi/latest/ * Unify command line syntax: * `foomuuri list` is renamed to `foomuuri ruleset list`. * `foomuuri list macro` is renamed to `foomuuri macro list`. * `foomuuri list counter` is renamed to `foomuuri counter list`. * Add port knocking and automatic IP address banning (fail2ban) support. These happen fully on packet path, native on nftables level. No external programs are needed. * Add support for catch all interface `*` in `zone` section, for example `public *`. It will match all unassigned interfaces. Interfaces assigned to a zone in configuration file or by NetworkManager / D-Bus will use assigned zone. * Add `iplist status [IPLIST]...` command to list number of entries in iplist. * Add `iplist_add`, `iplist_update` and `iplist_delete` matchers to add/update/delete IP address to iplist on packet path. * Add `dynamic=yes` option to `iplist` section line. This option enables dynamic flag, needed for packet path updates. * Add `element_timeout=time` option to `iplist` section line. This option sets default expire timeout for iplist elements. * Add `start=no` option to `iplist` section line. Foomuuri will not add IP addresses to such lists at startup. Entries will be added later by `foomuuri-iplist.timer` service. This can be used with "unsafe" external iplist to make sure Foomuuri start will not fail or block your access for first minutes after reboot. * Change `iplist` section option `-merge` to `merge=no`. * Add `flowtable list-of-interfaces` to `foomuuri` section. It enables Netfilter flowtable infrastructure. It improves network forward performance for high speed interfaces. * Add `matter` macro to default services. * Fix: Interface `lo` is special. Matcher `oifname lo` does not work in prerouting (DNAT) chain. Output is as `fib daddr type local` instead. ## 0.32 (2026-03-11) * BREAKING CHANGE: Prometheus exporter `foomuuri_exporter` is renamed to `prometheus-foomuuri-exporter`. * Add validators to `foomuuri { option value }` settings. * Add deprecation warning to `resolve` section, deprecated in 0.28. * Add `nftrace` statement to enable nftrace ruleset debugging for matching packets. Tracing events can be viewed with `nft monitor trace` command. * Add `url_max_size=bytes` to `iplist` section. It defines maximum content size for downloaded IP address list. Default value is 33554432, 32 MiB. * Optimize duplicate accept rules to single rule. * Add `foomuuri iplist flush` command to delete all added IP addresses from all iplists. * Add `ntske`, `prometheus-keepalived` and `prometheus-ipmi` macros to default services. * Rename macro `gluster-client` to `gluster`. Old macro name is kept as an alias. * Fix: Use `fib daddr type` to match multicast/broadcast packets. It works with WireGuard interface, old `meta pkttype` does not. * Fix: Interface `lo` is special. Nftables matcher `iifname lo` does not work in most chains. Output that as `iif 0` instead. * Fix: Allow unicast traffic in `ospf` macro. ## 0.31 (2026-01-07) * CVE-2025-67603: Add PolicyKit authorization to D-Bus methods. * CVE-2025-67858: Verify `interface` input parameter on D-Bus methods. * Security hardening: * Add `ProtectSystem=full` to all systemd service files. This changes `/etc` to read-only for all Foomuuri processes. Make sure you don't write any state files there in your startup hook or Foomuuri Monitor event hook. * Change umask to 022 when using `--fork` to fork as a background daemon process. * More strict IP address verify for iplist entries. ## 0.30 (2025-12-12) * Add `-merge` option to `iplist` section line to disable IP address auto-merge. It should be disabled in `fail2ban` alike scenarios, where IP addresses are added and deleted by some external program. * Major speedup when handling large iplists. * If all iplist entries have `|missing-ok` filter then whole iplist is considered as `Warning: Iplist is empty`. If any entry doesn't have that filter then empty list is an error. * Pipe commands to nft instead of reading them from `dbus.fw` or `iplist-cache.fw` files. * Use atomic write to `iplist-cache.json` and other files. * Add `foomuuri_exporter` binary to collect statistics and export them to Prometheus. Similar Munin collector is included to Munin contrib repository. * Include `group` states to `foomuuri-monitor` statistics file. * Add `prometheus-*` and `sips` macros to default services. * Make `python-dbus` optional for small systems. It is still highly recommended. Now Foomuuri can be run without any additional Python modules. ## 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.33/COPYING000066400000000000000000000432541521001665700143520ustar00rootroot00000000000000 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.33/Makefile000066400000000000000000000075131521001665700147550ustar00rootroot00000000000000# Foomuuri - Multizone bidirectional nftables firewall. .PHONY: all lint test devel clean distclean install sysupdate release tar CURRENT_VERSION ?= $(shell grep ^VERSION src/foomuuri | awk -F\' '{ print $$2 }') # Default target is to run lint and test all: lint test # Run testsuite and check source # # Enable test coverage report: make clean test COVERAGE=y && coverage html lint test: $(MAKE) -C test $@ # Generate firewall ruleset to file, used in development devel: unshare --map-root-user --net 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 -rf src/__pycache__ src/tests/__pycache__ htmlcov .ruff_cache rm -f .coverage 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/ mkdir -p $(DESTDIR)/usr/share/polkit-1/actions/ cp systemd/fi.foobar.Foomuuri1.conf $(DESTDIR)/usr/share/dbus-1/system.d/ cp systemd/fi.foobar.Foomuuri1.policy $(DESTDIR)/usr/share/polkit-1/actions/ 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 man/foomuuri.8 $(DESTDIR)/usr/share/man/man8/ mkdir -p $(DESTDIR)/usr/share/man/man1 cp man/prometheus-foomuuri-exporter.1 $(DESTDIR)/usr/share/man/man1/ # 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)@" man/foomuuri.md man/prometheus-foomuuri-exporter.md sed -i -e "s@^\(date: \).*@\1$(shell date +'%b %d, %Y')@" man/foomuuri.md man/prometheus-foomuuri-exporter.md make --directory=man git diff git add CHANGELOG.md src/foomuuri man/foomuuri.md man/foomuuri.8 man/prometheus-foomuuri-exporter.md man/prometheus-foomuuri-exporter.1 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" @echo @echo "Update misc/webdoc/versions.json and Makefile. Export documentation." # Build tarball locally tar: clean tar cavf foomuuri-$(CURRENT_VERSION).tar.gz --transform=s,,foomuuri-$(CURRENT_VERSION)/, --show-transformed .gitignore * foomuuri-0.33/README.md000066400000000000000000000035611521001665700145730ustar00rootroot00000000000000# Foomuuri Foomuuri is a multizone bidirectional nftables firewall. Full documentation is available [here](https://foomuuri.foobar.fi/latest/). See [host firewall](https://foomuuri.foobar.fi/latest/example/host-firewall/) or [router firewall](https://foomuuri.foobar.fi/latest/example/router-firewall/) for example configuration files. [Installation](https://foomuuri.foobar.fi/latest/install/) page contains instructions how to install Foomuuri. Help is available via [GitHub discussions](https://github.com/FoobarOy/foomuuri/discussions) and IRC channel `#foomuuri` on Libera.Chat. ## 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 * Automatic IP address banning support * Port knocking support * 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 ## Example configuration Example configuration file to filter incoming traffic only: ``` zone { localhost public eth1 # Interface list can be empty if using NetworkManager } public-localhost { # Allow specified incoming traffic dhcp-client dhcpv6-client ping ssh drop log } localhost-public { # Allow all outgoing traffic accept } ``` foomuuri-0.33/docs/000077500000000000000000000000001521001665700142375ustar00rootroot00000000000000foomuuri-0.33/docs/config/000077500000000000000000000000001521001665700155045ustar00rootroot00000000000000foomuuri-0.33/docs/config/basic.md000066400000000000000000000073621521001665700171170ustar00rootroot00000000000000# Basics ## Configuration files Foomuuri reads configuration files from `/etc/foomuuri/*.conf` in alphabetical order, including all sub directories. Foomuuri also reads static configuration from `/usr/share/foomuuri/*.conf` which can be overwritten in `/etc/foomuuri`. Configuration can be written to single or multiple files. Following is just a recommendation, not a rule: * Simple configuration (less than 200 lines) in single `/etc/foomuuri/foomuuri.conf` file. * Large configuration should be split to `/etc/foomuuri/foomuuri.conf`, `/etc/foomuuri/localhost.conf` , `/etc/foomuuri/public.conf` etc. files. * `foomuuri.conf` contains everything but zone-zone sections. * `localhost.conf` contains all xxx-localhost sections (or localhost-xxx). * `public.conf` contains all xxx-public sections (or public-xxx). * Subdirectories can be used, for example `/etc/foomuuri/zones.d/localhost.conf`. * Very large configuration could be split to multiple `localhost-public.conf` etc. files, containing only single zone-zone section per file. Raw `nftables` rules can be written to `/etc/foomuuri/*.nft` and they will be included to generated ruleset. ## Zone names Following zones names are recommended, but you can use whatever you want to. ### localhost `localhost` is the zone name for the computer running Foomuuri, similar to "localhost" in hostnames. If you decide to use some other name then you must configure it in [foomuuri { localhost_zone }](section/foomuuri.md) section. ### public `public` is the default external network zone, similar to "internet". Basic host firewall has only `localhost` and `public` zones. If you decide to use some other name then you should also configure it in [foomuuri { dbus_zone }](section/foomuuri.md) section. `public` is for use in public areas. You do not trust the other computers on networks to not harm your computer. ### home Similar to `public`, but for use in home areas. You mostly trust the other computers on networks to not harm your computer. ### work Similar to `public`, but for use in work areas. You mostly trust the other computers on networks to not harm your computer. ### internal `internal` is your internal network ("intranet") zone for router firewall configurations. Remote connections from `public` should not be allowed to `internal`. ### dmz Demilitarized zone is publicly-accessible part of your internal network. Only selected incoming connections should be accepted from `public` to `dmz`, for example `https`. ### vpn IPsec and similar VPN traffic. ## Miscellaneous Comments can be written as `# comment`. Long line can be split to multiple lines by adding `\` to end of line. Multiple words can be combined to single word by writing them in quotes. For example `ssh accept log "accept ssh for testing"` will accept SSH traffic with log message `accept ssh for testing`. Output from external command can be used to generate rules. It can return single line, multiple lines or part of line. Syntax is `$(shell command to run with parameters)`. Command is run with shell so pipes and `;` will work. Be careful to run only trusted commands. `$(shell)` is processed before [macro](section/macro.md) expansion. Example: ``` # This is a comment macro { # Define local_port_range macro by reading correct value from /proc file local_port_range $(shell sed s/\\t/-/ < /proc/sys/net/ipv4/ip_local_port_range) } localhost-public { ssh # This is a comment smtp \ daddr 192.0.2.32 # Allow SMTP to single IP } public-localhost { tcp local_port_range # Allow TCP to ports 32768-60999 (default range) } ``` Command to run can not contain `)` character. For such complex commands it's better to create shell script and call that: ``` macro { mymacro $(shell /etc/foomuuri/mymacro.sh parameters) } ``` foomuuri-0.33/docs/config/rule/000077500000000000000000000000001521001665700164535ustar00rootroot00000000000000foomuuri-0.33/docs/config/rule/index.md000066400000000000000000000005101521001665700201000ustar00rootroot00000000000000# Rule Each line in configuration section is a single rule. Single rule contains optional matching parts and statement part. Order of the matchers and statement in a rule does not matter. This document uses "what - source - destination - other-matchers - statement - log" order, for example `http saddr 10.1.1.1 drop log`. foomuuri-0.33/docs/config/rule/logging.md000066400000000000000000000057511521001665700204330ustar00rootroot00000000000000# Logging ## counter Add byte and packet counter to rule. All traffic matching this rule will be counted. Counter can be named or anonymous. To name a counter add name after `counter`, for example `counter my_counter`. Example: ``` localhost-public { # Add named counter to count all outgoing traffic counter outgoing_traffic continue -conntrack # Accept ssh and add anonymous counter for it ssh counter # Accept http + https and add named counter http counter web_traffic https counter web_traffic # Reject SMTP with named counter smtp reject counter smtp_blocked } ``` Named counter values can be listed with `foomuuri counter list`. Anonymous counters can be listed with `foomuuri ruleset list`. ## log Write log entry (journal / syslog) when traffic matches this rule. Optional log prefix can be added. Default prefix is `szone-dzone STATEMENT`, for example `localhost-public REJECT`. Following variables are supported in log prefix: * `$(szone)` * `$(dzone)` * `$(statement)` Additional text to default log prefix can be added with `log + " my text"`, resulting `localhost-public REJECT my text`. Example: ``` public-localhost { # Drop and log ssh with default prefix "public-localhost DROP" ssh drop log # Drop and log http with custom prefix "incoming-http dropped" http drop log "incoming-http dropped" # Drop and log https with custom prefix with variables. This results to # prefix "public => localhost: DROP" https drop log "$(szone) => $(dzone): $(statement)" # Drop and log telnet with custom prefix "public-localhost DROP:telnet" telnet drop log + ":telnet" # no space included to get "DROP:telnet" # Drop and log ftp with custom prefix "public-localhost DROP ftp-is-disabled" ftp drop log + " ftp-is-disabled" # space is included here # Use default prefix "public-localhost DROP" drop log } ``` Foomuuri will limit logging to [log_rate](../section/foomuuri.md) rate. Default value is to log first three entries per source IP and then one additional entry per second. ## log_level This overrides global `foomuuri { log_level ... }` logging level for this single rule. Possible values are: * `level emerg` * `level alert` * `level crit` * `level err` * `level warn` * `level notice` * `level info` * `level debug` Optionally flags can be appended: * `flags tcp sequence,options` enables logging of TCP sequence and options * `flags ip options` enables IP options * `flags skuid` enables socket UID * `flags ether` enables ethernet link layer address * `flags all` enables all flags To use nflog infrastructure instead of syslog specify value `group 0` (or any other number) instead of `level x`. Nflog options can be appended: * `snaplen 256` specifies length of packet payload to include * `queue-threshold 20` will queue packets inside the kernel before sending them to userspace Example: ``` public-localhost { # Drop and log incoming http requests with critical level, all flags http drop log log_level "level crit flags all" ... } ``` foomuuri-0.33/docs/config/rule/matcher.md000066400000000000000000000240731521001665700204260ustar00rootroot00000000000000# Matchers ## tcp, udp, icmp, icmpv6 Matches TCP / UDP / ICMP / ICMPv6 traffic. This matcher is usually followed by port number. For example `tcp 443` matches TCP traffic to port 443 (https). Without port number all traffic is matched. See [protocol](matcher.md#protocol) how to match other protocols. Instead of using `tcp 443` it is recommended to use pre-defined `https` macro. All [known macros](https://github.com/FoobarOy/foomuuri/blob/main/etc/default.services.conf) can be listed with `foomuuri macro list` command. ## sport, dport Matches source / destination port, followed by port number. `dport` matcher is usually optional: `tcp 443` is equal to `tcp dport 443`. Port number can be: * Single number `443` * Multiple numbers `80 443`, matching 80 or 443 * Range `8880-9000` * Range and number `80 443 8880-9000` * Negative number `-443`, matching all but 443 * Multiple negative numbers `-80 -443`, matching all but 80 or 443 ## saddr, daddr Matches source / destination IPv4 or IPv6 address. Address can be * Single IPv4 address `10.0.0.1` * Single IPv6 address `fd00:f00::1` * Address with netmask `10.0.0.0/8` or `fd00:f00::/32` * IPv6 address with suffix netmask `::10:0:0:f00/-64` to match with netmask `::ffff:ffff:ffff:ffff` * Interval `10.0.0.10-10.0.0.20` or `fd00:f00::10-fd00:f00::20` * Multiple addresses `10.0.0.1 10.0.0.10-10.0.0.20 fd00:f00::/32`. You can list both IPv4 and IPv6 addresses in same rule. Foomuuri will split them automatically to correct traffic chains. * Negative address `-10.0.0.1` or `-fd00:f00::1`, meaning all other addresses. * Negative IPv6 address with suffix netmask `-::10:0:0:f00/-64` * Multiple negative addresses `-10.0.0.1 -fd00:f00::1` * Iplist set name `@listname` * Negative iplist set name `-@listname` This matcher is usually combined with `tcp port` matcher to allow traffic from specific source IP only, `tcp 443 saddr 10.0.0.1`, or to specific destination IP. ## mac_saddr, mac_daddr Matches source / destination MAC address for incoming traffic. This doesn't work for outgoing traffic. Address can be: * Single MAC address `01:23:45:67:89:ab` * Multiple MAC addresses `01:23:45:67:89:ab 01:23:45:67:89:cc` * Negative MAC address `-01:23:45:67:89:ab`, meaning all other addresses. * Multiple negative MAC addresses `-01:23:45:67:89:ab -01:23:45:67:89:cc` Example: ``` internal-localhost { # drop spoofed traffic from 10.0.0.3 saddr 10.0.0.3 mac_saddr -12:00:27:00:00:ce drop log "MAC-SPOOF" ... } ``` ## iifname, oifname Matches incoming / outgoing interface name. This is used mostly on `snat`, `dnat` and `zonemap` sections. For example in snat `saddr 10.0.0.0/8 oifname eth0 masquerade` will match all traffic coming from 10.0.0.0/8, going to eth0, and masquerades it. Multiple interface names can be specified. Negative interface name(s) works too, meaning all but specified name(s). ## ipv4, ipv6 Single rule will apply to both IPv4 and IPv6 traffic. Adding `ipv4` or `ipv6` matcher to rule will limit it to IPv4 or IPv6 only. ## multicast, broadcast Matches multicast or broadcast traffic. Foomuuri will silently drop all incoming multicast and broadcast traffic unless explicitly accepted by rule. Linux kernel can match multicast/broadcast only in incoming traffic. Therefore Foomuuri generates outgoing nft rule without multicast/broadcast matcher even if one was specified in the rule. This makes it easy to use same rule or macro for all traffic, no matter if it's incoming or outgoing. Rule `broadcast udp 11430` allows incoming broadcast messages to UDP port 11430, or all outgoing traffic to UDP port 11430. Rule `multicast` allows all incoming multicasts, nothing for outgoing. It is highly recommended to always specify `daddr` for multicast rules. Example: ``` public-localhost { # Allow all incoming multicasts multicast ... } localhost-public { # Allow some outgoing multicast addresses. This rule can also be written # without "multicast" matcher as it will be omitted. multicast daddr 224.0.0.0/8 239.0.0.0/8 ff00::/8 ... } ``` It is much better to accept specific multicast/broadcast only, not everything. For example macro `ssdp` is defined as: ``` macro { ssdp multicast udp 1900 daddr 239.255.255.250 ff02::c; udp sport 1900 } public-localhost { # Allow incoming ssdp ssdp ... } localhost-public { # Allow outgoing ssdp ssdp ... } ``` Above macro allows incoming traffic to specific multicast addresses and UDP port 1900, outgoing traffic to same addresses and port, and finally unicast from same port. ## protocol Matches specific protocol traffic. For example `protocol gre` will match all GRE traffic and `protocol sctp 22` will match SCTP traffic to port 22. For TCP, UDP, ICMP and ICMPv6 protocols it is recommended to use [shortcut](matcher.md#tcp-udp-icmp-icmpv6) matchers. ## sipsec, dipsec Matches incoming / outgoing IPsec traffic. These are useful if you want to allow traffic from/to IPsec without creating a new `vpn` zone for them. Example: ``` public-localhost { ssh # Allow SSH with and without IPsec tcp 1234 sipsec # Allow TCP 1234 with IPsec only ... } ``` To split IPsec and non-IPsec traffic to `vpn` and `public` zones you can use these matchers in `zonemap` [section](../section/zonemap.md). Usually it is not necessary to create separate zone for that. Negative matchers `-sipsec` and `-dipsec` can also be used. They match non-IPsec traffic. These are useful in `snat` and `dnat` [sections](../section/snat.md). ## uid, gid Matches traffic generated by uid / gid user. This works only for traffic from `localhost`. For example `tcp 2703 uid amavis` in `localhost-public` section allows outgoing TCP 2703 traffic from user `amavis`. Multiple uid / gid names or numbers can be specified. Negative value(s) works too, meaning all but specified value(s). ## mark_match Matches packet's mark. Argument can be: * Number `42` or `0x2a` * Number with mask `0x100/0xff00`, meaning check if `mark and 0xff00` is equal to `0x100` * Negative number `-42`, meaning all other marks than `42` * Negative number with mask `-0x0000/0xff00` for non-equal check ## mark_set Set packet's mark. Argument can be: * Number `42` or `0x2a` * Number with mask `0x100/0xff00`, meaning set bits `0xff00` to `0x100`. In other words, this keeps lower 8 bits as they are and sets upper 8 bits. This is not normal matcher as it matches everything. Usually some other matcher should be used first. Example: ``` prerouting { iifname eth0 mark_set 0x100/0xff00 # Mark traffic from eth0 and accept it iifname eth1 mark_set 0x200/0xff00 # Mark traffic from eth1 and accept it mark_set 0x300/0xff00 # Mark all other traffic } ``` ## priority_match Matches packet's traffic control class id. Argument can be: * Class id `1:ff01` or `1:0xff01` (`0x` is optional) * Text `none` for no priority set ## priority_set Set packet's traffic control class id. This is not normal matcher as it matches everything. Usually some other matcher should be used first. Example: ``` forward { daddr 192.168.0.0/16 priority_match none priority_set 1:ff01 saddr 192.168.0.0/16 priority_match none priority_set 1:ff01 priority_match none priority_set 1:2 } ``` ## iplist_add Add packet source or destination IP address to [iplist](../section/iplist.md), using default `element_timeout`. Timeout is not updated if IP address is already in iplist. Two arguments are needed: `saddr` or `daddr`, and iplist `@name`. Normally `iplist_update` is a better choice as it updates timeout. ## iplist_update Add/update packet source or destination IP address to [iplist](../section/iplist.md), using default `element_timeout`. Timeout will be updated if IP address is already in iplist. Two arguments are needed: `saddr` or `daddr`, and iplist `@name`. See [port knocking](../../example/advanced.md#port-knocking) and automatic IP address [banning](../../example/advanced.md#automatic-ip-address-banning) for examples. ## iplist_delete Delete packet source or destination IP address from [iplist](../section/iplist.md). Two arguments are needed: `saddr` or `daddr`, and iplist `@name`. See [port knocking](../../example/advanced.md#port-knocking) for example. ## ct_status Matches packet's conntrack status, mostly used with [SNAT or DNAT](../section/dnat.md). Valid arguments are: expected, seen-reply, assured, confirmed, snat, dnat, dying. ## cgroup Matches cgroup id or cgroupv2 name. Argument can be: * Single number `1234` * Multiple numbers `1234 1244` * Range `4000-5000` * Range and number `1234 1244 4000-5000` * Negative number `-1234`, matching all but 1234 * Multiple negative numbers `-1234 -1244`, matching all but 1234 or 1244 * cgroupv2 name `user.slice` or `system.slice/sshd.service` **Warning**: cgroupv2 must exist before starting Foomuuri. As Foomuuri starts very early on boot using this feature incorrectly can break your firewall startup. ## time Matches current time, date and day of the week. Argument can be: * Time `hh:mm` or `hh:mm:ss` * Time interval `hh:mm-hh:mm` (see warning below) * Date `yyyy-mm-dd` * Day of the week: `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday`, `Sunday` * Compare function: `==` (equal, the default), `!=` (not equal), `<`, `>`, `<=`, `>=` * Combination of above **Warning**: Some versions of `nft` do not handle time interval crossing midnight UTC. If your timezone is +03:00, interval `23:00-02:00` works but `02:00-06:00` fails. Example: ``` public-localhost { # Allow until year 2025 tcp 1234 time "< 2025-01-01" # Kids, go to bed! Reject traffic at night time. saddr @kids time "23:00-07:00" reject # Strange combination: Allow traffic on Mondays at 16-22, until year 2025 tcp 5001 time "Monday 16:00-22:00 < 2025-01-01" } ``` ## dscp Matches packet's differentiated services code point (DSCP) value. Example: ``` internal-public { saddr 10.0.0.4 dscp 10 dscp af13 } ``` ## tproxy Transparent proxy traffic to ipaddress:port. Example: ``` prerouting { # Use lower 8 bits to mark tproxy traffic mark_match -0x00/0xff # Anti-loop protection # All IPv4 and IPv6 TCP traffic tcp tproxy 127.0.0.1:8888 [::1]:8888 mark_set 0x01/0xff } ``` foomuuri-0.33/docs/config/rule/misc.md000066400000000000000000000046551521001665700177420ustar00rootroot00000000000000# Miscellaneous Rules ## szone, dzone, new_szone, new_dzone These can be specified in [zonemap](../section/zonemap.md) section to match original source or destination zone, and to change it to a new zone. These are used to branch out some specific traffic to its own zone, for example to split `vpn` and `public` (non-VPN) traffic. ## helper Linux kernel provides conntrack helper functionality to some services with multiple ports, like `ftp` (tcp 21). You can enable this functionality by appending `helper kernelname-port` after matcher. For example `tcp 21 helper ftp-21`. Linux has following helpers: `amanda, ftp, h323, irc, netbios_ns, pptp, sane, sip, snmp, tftp` ## mss Sets maximum segment size (MSS clamping) to all traffic. Some connections, like IPsec or PPPoE, might require this. Example: ``` localhost-vpn { mss 1390 ssh reject log } ``` Special value `mss pmtu` can be used to calculate the value in runtime based on what the routing cache has observed via Path MTU Discovery (PMTUD). Example: ``` forward { # internal-public mss pmtu } input { # public-localhost mss pmtu } output { # localhost-public mss pmtu } ``` ## conntrack, -conntrack Rules are processed after conntrack check (flag `conntrack`, default value). Flag `-conntrack` can be added to process rule before conntrack. Conntrack will accept established and related traffic so normal rule will see only new traffic. This can be used to count all traffic instead of new connections only, or to accept traffic without adding it to conntrack. For example high load DNS server might accept DNS traffic without conntrack. Example: ``` public-localhost { # Count all incoming traffic counter incoming_traffic continue -conntrack # Count incoming HTTP(S) traffic in web server tcp dport 80 443 counter web_traffic_in continue -conntrack ... } localhost-public { # Count outgoing HTTP(S) traffic in web server tcp sport 80 443 counter web_traffic_out continue -conntrack ... } ``` ## nft Raw nftables rule can be written with `nft "raw rule here"`. For example, `https` rule can be written as: ``` public-localhost { nft "tcp dport 443 accept" nft "udp dport 443 accept" ... } ``` `nft` can also be combined with matchers: ``` # Jump to my-custom-chain for UDP traffic to ports 1000-2000 udp 1000-2000 nft "jump my-custom-chain" ``` See [nftables web page](https://wiki.nftables.org/) for more information about nftables syntax. foomuuri-0.33/docs/config/rule/ratelimit.md000066400000000000000000000106421521001665700207720ustar00rootroot00000000000000# Rate limit ## global_rate Defines global rate limit without source or destination IP address check. For example `https global_rate "10/second burst 20"` allows https traffic with rate: * First 20 new connections are allowed without limit. * After burst is filled, up to 10 new connections per second are allowed. * This rate is "global", meaning that one single source IP can use all allowed slots, or 20 sources can use one slot each. * Here "connection" means new connection, not total number of established, active connections (see `ct count` below). There are three types of rates: * New connection rates (`x/time burst y`), ignoring if some of them have already been closed. * `"7/second burst 30"` * `"3/second"` (no burst specified, so `burst 5` is assumed) * `"1/minute burst 1"` * `"50/minute burst 200"` * `"30/minute"` * `"100/hour burst 200"` * `"100/hour"` * `"over 8/second burst 10"` * Bandwidth rates (`x bytes/time burst y bytes`): * `"10 mbytes/second burst 12000 kbytes"` * `"10 mbytes/second"` * `"over 10 mbytes/second burst 12000 kbytes"` * `"over 10 mbytes/second"` * Conntrack rates (`ct count x`) counting total number of established, active connections: * `"ct count 5"` * `"ct count over 6"` New connection rate limit can be visualized as leaking water bucket. `burst 10` specifies the bucket size as 10 units. `3/minute` specifies how much it leaks, 3 units per minute, or 1 unit per every 20 seconds. Every new connection adds one unit of water to it. If it fits, rule matches (usually: connection is accepted). If the bucket overflows, rule doesn't match. If burst is not specified, Linux kernel assumes value 5 for it. Minimum value for burst is 1. Some examples: * `5/minute burst 1` allows one new connection (`burst 1`) and new connection every 12 second (`5/minute`). * `3/minute burst 5` allows up to 5 new connections and new connection every 20 second after those 5 connections are used. Bandwidth rate can be used as simple traffic limiter. This can be specified per service, or total for all. Example: ``` public-localhost { # Limit all incoming traffic to 30 MiB/s, total for all traffic global_rate "over 30 mbytes/second" drop -conntrack # Limit incoming SSH traffic to 2 MiB/s, per service traffic ssh global_rate "over 2 mbytes/second" drop -conntrack # Allow incoming SSH connections ssh ... } ``` Rate `ct count 5` matches if there are up to 5 established connections, including current one. So rule `ssh global_rate "ct count 2"` allows two SSH connections (one old + current). Rate `ct count over 6` matches if there are more than 6 established connections. This is usually used with `drop` statement: `ssh global_rate "ct count over 6" drop`. ## saddr_rate, daddr_rate Source / destination IP address specific rate limit is similar to `global_rate`. `saddr_rate` allows limited amount of connections or bandwidth from single source IP address, but there is no total (global) limit. `daddr_rate` allows limited amount of connections or bandwidth for single destination IP address, which is usually some host in `dmz` zone. Both `saddr_rate` and `daddr_rate` can be specified for single rule. For example `https saddr_rate "10/second" daddr_rate "1000/second"` specifies: * Single IP can open 10 new connections per second. * Total of 1000 connection per second from all IPs are allowed. ## saddr_rate_mask, daddr_rate_mask Normally full IPv4 or IPv6 addresses are considered when counting `saddr_rate` or `daddr_rate`. This can be changed with netmask: `ping saddr_rate "5/second burst 20" saddr_rate_mask 24 56` uses /24 and /56 instead of full IP address when counting limits. ## saddr_rate_name, daddr_rate_name If you want to share same rate limit with two different rules you must specify a name for it. For example: ``` http saddr_rate "30/second burst 50" saddr_rate_name http_limit https saddr_rate "30/second burst 50" saddr_rate_name http_limit ``` This counts both http and https traffic as single, allowing total of 30 connections per IP per second. Without name it would allow 30 + 30 connections per IP per second. ## saddr_daddr_rate, saddr_daddr_rate_mask, saddr_daddr_rate_name This is special form of `saddr_rate + daddr_rate`, usable when you have multiple destination IPs per service (DNS round-robin). In this rule both source and destination address are counted as check key, instead of only source or destination. foomuuri-0.33/docs/config/rule/statement.md000066400000000000000000000041761521001665700210110ustar00rootroot00000000000000# Statements ## accept, drop, reject Accepts, drops or rejects traffic. Default statement for single rule is to accept matched traffic: `tcp 443` is equal to `tcp 443 accept`. You should always add explicit final statement as last rule to every [zone-zone](../section/zonezone.md) section in your configuration. * For incoming traffic from internet to localhost/intranet the recommended statement is `drop log`. * For outgoing traffic from localhost/intranet to internet the recommended statement is `reject log`. ## continue Continues to next rule. This is used mostly to debug rules. For example rule `saddr 10.0.0.4 counter log continue` counts and logs traffic from 10.0.0.4 and continues to next rule. ## return This is a special statement to return from current nftables chain to caller chain. Not normally used. ## masquerade, snat, dnat, snat_prefix, dnat_prefix These statements are used in `snat` and `dnat` [sections](../section/snat.md) to mangle traffic source or destination IP address. See that page for description and examples. ## notrack Mark matching packet to not be added to conntrack. This has to be done early in `prerouting` section. For example high load DNS server can use this for DNS queries. Example: ``` # Incoming traffic prerouting filter raw { domain notrack # dport tcp sport 53 notrack # sport udp sport 53 notrack } # Locally created traffic output filter raw { domain notrack tcp sport 53 notrack udp sport 53 notrack } ``` ## queue Forward packet to userspace for example for IPS/IDS inspection. Optional flags and target can be specified. Example: ``` forward { # Forward all packets to userspace for IPS inspection queue flags fanout,bypass to 3-5 # Forward matching packets only iifname eth0 oifname eth1 queue } ``` ## nftrace Enable nftrace ruleset debugging for matching packets. This is usually done in `input`, `output` or `forward` section. Tracing events can be viewed with `nft monitor trace` command. Example: ``` input { # Trace all incoming packets - generates lot of trace! nftrace } forward { # Trace all forwarded ssh packets ssh nftrace } ``` foomuuri-0.33/docs/config/section/000077500000000000000000000000001521001665700171505ustar00rootroot00000000000000foomuuri-0.33/docs/config/section/any.md000066400000000000000000000020621521001665700202610ustar00rootroot00000000000000# zone-any, any-zone, any-any These sections are similar to zone-zone section, except that they match any destination (zone-any), any source (any-zone) or all (any-any) traffic. These [rules](../rule/index.md) are processed first and then normal zone-zone rules. Example: ``` localhost-any { # Allow ping and SSH from localhost, no matter where it is going. ping ssh # Final drop/reject rule is usually added to specific localhost-zone # section, not in localhost-any. } localhost-public { # Accept all localhost-any rules (ping, SSH), plus HTTPS. https reject log } localhost-internal { # Accept all localhost-any rules (ping, SSH), plus DNS queries. domain reject log } ``` Matcher `szone -public` can be used in rule to skip adding it to `public-localhost`. Example: ``` any-localhost { ssh # allow ssh from anywhere https szone -public # allow https from anywhere except from public } localhost-any { ssh # allow ssh to anywhere vnc dzone -public # allow vnc to anywhere except to public } ``` foomuuri-0.33/docs/config/section/dnat.md000066400000000000000000000023051521001665700204200ustar00rootroot00000000000000# dnat Destination NAT is used to mangle traffic by using standard [rules](../rule/index.md). Example: ``` dnat { # http traffic to 10.0.0.1 is DNAT'ed to 10.0.0.2 port 8080 daddr 10.0.0.1 http dnat 10.0.0.2:8080 # http traffic to fd00:f00::1 is DNAT'ed to fd00:f00::2 port 8080 daddr fd00:f00::1 http dnat [fd00:f00::2]:8080 # http traffic to 10.0.0.6 is DNAT'ed to 10.0.0.7, port doesn't change daddr 10.0.0.6 http dnat 10.0.0.7 # All smtp traffic from eth2 is DNAT'ed to 10.0.0.8 or fd00:f00::8, # port doesn't change iifname eth2 smtp dnat 10.0.0.8 fd00:f00::8 # All traffic from eth1 inteface is DNAT'ed to 10.0.0.3 iifname eth1 dnat 10.0.0.3 # http traffic to 10.0.0.4 coming from interface eth2 is DNAT'ed to 10.0.0.5 iifname eth2 daddr 10.0.0.4 http dnat 10.0.0.5 # http traffic to localhost coming from interface eth3 is DNAT'ed to 10.0.0.6 iifname eth3 oifname lo http dnat 10.0.0.6 } ``` Remember to accept DNAT'ed traffic in zone-zone section. This can be done with specific rule or with `ct_status` matcher. Example: ``` public-internal { # Specific rule to accept https https daddr 10.0.0.9 # Generic rule to accept all DNAT'ed traffic ct_status dnat } ``` foomuuri-0.33/docs/config/section/foomuuri.md000066400000000000000000000103061521001665700213370ustar00rootroot00000000000000# foomuuri This section can be usually omitted as default values should be fine. This section defines common options for Foomuuri. If really needed, it is usually better to override single value, not full section. Example: ``` foomuuri { # Change rpfilter's value, keep everything else as default rpfilter no } ``` Full list of default values are: ``` foomuuri { 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 flowtable no counter no set_size 65535 recursion_limit 10000 priority_offset 5 dbus_firewalld no nft_bin nft try_reload_timeout 15 } ``` `log_rate` is the default logging rate per source IP. Default value is to log first three entries per source IP and then one additional entry per second. Rate [specification](../rule/ratelimit.md#global_rate) is the same as in rate limit rule. `log_input` ... `log_smurfs` defines default value for specific logging. Value can be: * `yes` to log it with `log_rate` * `no` to not log * `"3/second burst 10"` to log it with specific rate `log_prefix` defines the default log prefix for [logging](../rule/logging.md#log). `log_level` is the syslog level of logging. For possible values see rule specific [version](../rule/logging.md#log_level). `localhost_zone` is the name of zone used for the computer running Foomuuri, similar to "localhost" in hostnames. See [zone](zone.md) and [zone-zone](zonezone.md) sections for more information. `dbus_zone` is the name of your outgoing internet zone. This is used in D-Bus support. `rpfilter` is to enable or disable reverse path filtering. Value can be: * `yes` to enable it to all interfaces * `no` to disable it * `eth0 eth1 eth2` to enable it to specified interfaces `eth0 eth1 eth2` * `-eth1 -eth2` to enable it to all other interfaces than `eth1 eth2` `flowtable` is to enable Netfilter flowtable infrastructure for specified interfaces, for example `flowtable eth0 eth1`. It improves network forward performance for high speed interfaces. Optional `hw_offload=yes` keyword enables hardware offloading (make sure your interface supports `hw-tc-offload`). Value `yes` or negative notation does not work here. `counter` is to add anonymous byte and packet [counter](../rule/logging.md#counter) to all rules. Value can be: * `yes` to add it to all rules * `no` to not add it * `localhost-public public-localhost` to add it to all rules in `localhost-public` and `public-localhost` sections `set_size` is the size of [rate limit](../rule/ratelimit.md) set, [log rate limit](../rule/logging.md) set and [dynamic iplist](../rule/matcher.md#iplist_update) set. This is the maximum amount of entries in the set in kernel. Default value 65535 is fine for normal host. For company firewall larger value is required, for example 262143 (equals to 2^18 - 1). Setting this value too high doesn't harm. If set is full, new entry can't be added. This doesn't matter for log rate limits, but for rate limits and dynamic iplists this means that the statement (usually accept for rate limit, drop for iplist) is skipped. See `foomuuri ruleset list` for content of your currently active sets. `recursion_limit` is the internal limit to avoid macro and template expansion loop. Increase this value if you get false "Possible macro or template loop" error. `priority_offset` is the chain priority offset for rules generated by Foomuuri. Tune this value if you have multiple software adding chains and want to process them in some particular order. For example, iptables uses offset 0 and firewalld uses 10. Lowest priority is processed first. So to process Foomuuri rules first, use value `-5`. To process them last, use `20`. `dbus_firewalld` is to enable or disable firewalld D-Bus emulation inside Foomuuri. NetworkManager can tell firewalld to attach and detach interfaces to zones via D-Bus call. This option enables Foomuuri to listen to firewalld D-Bus and do the same thing. `nft_bin` is the name for `nft` binary. Full path can be specified if required. `try_reload_timeout` is the timeout in seconds for `foomuuri try-reload` command. foomuuri-0.33/docs/config/section/hook.md000066400000000000000000000005241521001665700204330ustar00rootroot00000000000000# hook Foomuuri can run external command before/after starting and stopping. This section configures those commands. Example: ``` hook { pre_start command-to-run with arguments before loading ruleset post_start echo firewall started pre_stop /etc/foomuuri/pre_stop.sh post_stop /etc/foomuuri/post_stop.sh with arguments } ``` foomuuri-0.33/docs/config/section/index.md000066400000000000000000000001761521001665700206050ustar00rootroot00000000000000# Sections All Foomuuri configurations must have one [zone](zone.md) section and multiple [zone-zone](zonezone.md) sections. foomuuri-0.33/docs/config/section/invalid.md000066400000000000000000000004711521001665700211220ustar00rootroot00000000000000# invalid, rpfilter, smurfs Packets entering `invalid`, `rpfilter` or `smurfs` chains will be dropped. These sections can be used to specify more rules to them. For example load balanced IPVS traffic might enter to `invalid` chain and must be accepted: ``` invalid { # Accept HTTPS IPVS traffic https } ``` foomuuri-0.33/docs/config/section/iplist.md000066400000000000000000000132101521001665700207730ustar00rootroot00000000000000# iplist Instead of using static IP addresses Foomuuri can perform periodical DNS hostname lookups and download external IP-lists. These addresses are stored to sets and cached across reboots and single lookup failures. First word in line is set name, which must begin with `@` character. Next words can be: * IPv4 or IPv6 address, with or without mask * DNS hostname * Filename containing IP addresses, with or without mask * URL for file containing IP addresses, with or without mask Example: ``` iplist { # Resolve known good hostnames @goodhost foobar.fi mydomain.com # Download Finnish IPv4 and IPv6 addresses from https://github.com/ipverse/rir-ip @fi https://raw.githubusercontent.com/ipverse/rir-ip/master/country/fi/ipv4-aggregated.txt @fi + https://raw.githubusercontent.com/ipverse/rir-ip/master/country/fi/ipv6-aggregated.txt # Download Finnish Elisa operator IP addresses from https://github.com/ipverse/asn-ip @elisa https://raw.githubusercontent.com/ipverse/asn-ip/master/as/719/ipv4-aggregated.txt @elisa + https://raw.githubusercontent.com/ipverse/asn-ip/master/as/719/ipv6-aggregated.txt # Read blacklist from text files @blacklist /etc/foomuuri/blacklist*.txt # Read content from file and add some extra IPs to it @whitelist /etc/foomuuri/whitelist*.txt 10.0.0.0/8 192.0.2.32 # Manipulate this list with "foomuuri iplist add mylist 10.0.0.1" command. # See command line help for "foomuuri add/del/flush" commands. @mylist } public-localhost { # Allow SSH from known good hosts ssh saddr @goodhost # Don't allow blacklisted addresses to IMAP imap saddr @blacklist drop log "public-localhost DROP-blacklist" # Allow mylist entries to IMAP without rate imap saddr @mylist # Allow Finnish users to IMAP with fast 1 per second rate imap saddr @fi saddr_rate "1/second burst 10" # Allow everybody to IMAP with slow 1 per minute rate. This includes # over rate limit Finnish users. imap saddr_rate "1/minute burst 1" # ...rest of the rules... } ``` Hostnames are refreshed every 15 minutes and they will timeout after 24 hours. URLs are refreshed once a day and timeout is 10 days. These values can be changed globally or per set. ``` iplist { # Define global timeouts dns_refresh=15m dns_timeout=24h url_refresh=1d url_timeout=10d # Define per set timeout @fasturl https://some/url url_refresh=1h30m url_timeout=2d } ``` Above timeouts are rounded to 15 minutes. This can be configured with `systemctl edit foomuuri-iplist.timer` command. Timeout can be: * `4w` weeks * `2d` days * `3h` hours * `15m` minutes * `900s` seconds * `2d3h15m` or `1w90m` combination of above Downloaded content can be filtered by adding `|filter` after filename, URL or hostname. Multiple filters can be chained. * `|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, using XPath filter * `|xml:XPath` parse it as XML data, using XPath filter * `|missing-ok` don't print warning if download or DNS resolve fails Example: ``` iplist { # Download Github IP address list and parse it as JSON, returning # "actions" list. @github https://api.github.com/meta|json:.actions[] # Download network scanner IP address list and parse it from HTML page. @netscanner https://internet-measurement.com/|html://div/pre/text() } ``` Maximum content size for downloaded IP address list can be defined with `url_max_size=bytes` line. Default value is 33554432 (32 MiB). List will be ignored if it's too large. Optional `dynamic=yes` option enables dynamic flag on generated ruleset. This flag is needed when updating iplist content on packet path with [`iplist_update`](../rule/matcher.md#iplist_update) matcher, for example in automatic IP address [banning](../../example/advanced.md#automatic-ip-address-banning). For normal usage this option should not be used. Optional `element_timeout=time` option sets default element expire timeout. This is needed in automatic IP address banning and port knocking. Optional `merge=no` option disables IP address auto-merge. Normally it is recommended to keep it enabled. When using external `fail2ban` program (see below) it is recommended to be disabled. Foomuuri startup will not add IP addresses to lists marked with optional `start=no` option. Entries will be added later by `foomuuri-iplist.timer` service. This can be used with "unsafe" external iplists to make sure Foomuuri start will not fail or block your access for first minutes after reboot. Iplists can be manipulated from command line, for example by `fail2ban` external program. Command `foomuuri iplist add @setname ipaddress` adds IP address to `@setname`. Other commands are `foomuuri iplist list`, `foomuuri iplist del` and `foomuuri iplist flush`. See command line help for usage. Example: ``` iplist { @banned dynamic=yes element_timeout=5m # automatic IP address banning @blacklist merge=no # fail2ban } # Manipulate with command: # # foomuuri iplist add @blacklist 10.0.0.1 # foomuuri iplist del @blacklist 10.0.0.1 ``` Another example how to create a macro to allow access to Valve Steam: ``` iplist { # Create iplist from Valve's IP addresses @valve_as https://raw.githubusercontent.com/ipverse/asn-ip/master/as/32590/ipv4-aggregated.txt @valve_as + https://raw.githubusercontent.com/ipverse/asn-ip/master/as/32590/ipv6-aggregated.txt } macro { # Create macro to allow outgoing traffic to Valve iplist valve-steam udp 3478 4379-4380 27000-27100 daddr @valve_as; valve-steam + tcp 27015-27050 daddr @valve_as } localhost-public { # Allow outgoing traffic to Valve Steam valve-steam } ``` foomuuri-0.33/docs/config/section/macro.md000066400000000000000000000036411521001665700205770ustar00rootroot00000000000000# macro Instead of writing rule `tcp 443` it is easier and more readable to use rule `https`. These alias names are called macros. Macro can be used in any part of rule you want to, defining rule fully or partially. ``` macro { # Define service as macro smtp tcp 25 https tcp 443; udp 443 googlemeet udp 3478 19302-19309; https # Define rate limit as macro ssh_rate saddr_rate "5/minute burst 5" # Long macro can be split to multiple lines with "+" (append to previous) or # "\" (continue in next line). # Warning: Using "+" or "\" does not add ";". You must add it yourself when # needed. good_hosts 10.0.0.1 fd00:f00::1 good_hosts + 10.0.0.2 fd00:f00::2 another 10.0.0.3 \ 10.0.0.4 semicolon http semicolon + ; https } ``` You can use above macros in other sections: ``` localhost-public { https daddr good_hosts # Allow https to specific IP addresses tcp 23 daddr good_hosts # Allow TCP 23 to specific IP addresses https reject # Reject all other https traffic googlemeet # Allow Google Meet to everywhere } public-localhost { ssh ssh_rate # Allow incoming ssh with rate limit } ``` Macro can include other macros, as `googlemeet` in above example does. Using `;` in macro splits it to multiple rule lines. You must use it when macro contains two different rules, like `tcp 443` and `udp 443` in `https` macro, or `udp XXX` and `https` in `googlemeet` macro. Do not use it when creating list of items (IP addresses for example) for single rule, like in `good_hosts`. All [known macros](https://github.com/FoobarOy/foomuuri/blob/main/etc/default.services.conf) can be listed with `foomuuri macro list` command. Macro expansion can be skipped by writing word in quotes. For example `"ssh"` is kept as `ssh` and not expanded to `tcp 22`. For safety reasons macro expansion is not done in `zone` or `foomuuri` sections. foomuuri-0.33/docs/config/section/snat.md000066400000000000000000000012351521001665700204400ustar00rootroot00000000000000# snat Source NAT is used to mangle traffic by using standard [rules](../rule/index.md). Example: ``` snat { # Masquerade all traffic from 10.0.0.0/8 going to eth0 interface. # New outgoing IP is eth0's IP address. saddr 10.0.0.0/8 oifname eth0 masquerade # Use outgoing IP 192.0.2.32 to all non-IPsec traffic coming from # 10.0.0.0/8 and going to eth1 interface. saddr 10.0.0.0/8 oifname eth1 -dipsec snat 192.0.2.32 # IPv6-to-IPv6 Network Prefix Translation (NPTv6) saddr fd00:f00:4444::/64 oifname eth2 snat_prefix to 2a03:1111:222:8888::/64 } ``` Remember to accept SNAT'ed traffic in zone-zone section. See [dnat](dnat.md) for more examples. foomuuri-0.33/docs/config/section/special.md000066400000000000000000000027521521001665700211200ustar00rootroot00000000000000# prerouting, postrouting, forward, input, output These sections are used to specify packet mangle rules. Mangle rules are processed before normal zone-zone filtering rules. * `prerouting` is for all incoming packets * `postrouting` is for all outgoing packets * `forward` is for all forwarded packets (for example `internal-public`) * `input` is for all packets targetted to `localhost` (`public-localhost`) * `output` is for all locally generated packets (`localhost-public`) Normally these sections are used to [set](../rule/matcher.md#mark_set) packet mark value or [MSS clamping](../rule/misc.md#mss). Example: ``` prerouting { # Do nothing if mark is already set mark_match -0x0000/0xff00 accept # Set default mark 0x100 to packet mark_set 0x100/0xff00 # Change mark to 0x200 if it is coming from eth2 iifname eth2 mark_set 0x200/0xff00 # Use mark 0x300 for SSH traffic from eth2 iifname eth2 ssh mark_set 0x300/0xff00 } ``` Chain type and hook priority can also be specified. Example: ``` prerouting filter raw { ... } postrouting nat srcnat + 20 { ... } ``` These sections are special. Rule `drop` or `reject` will drop/reject packet immediately. Default rule is `accept`, which will continue processing to other special sections, zone-zone section and again to other special sections. All of them have to accept the packet. See [packet flow in Netfilter](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for detailed section processing order. foomuuri-0.33/docs/config/section/targetgroup.md000066400000000000000000000002521521001665700220340ustar00rootroot00000000000000# target, group Foomuuri includes simple network [connectivity monitor](../../tool/monitor.md). These sections are used to [configure](../../tool/monitor.md#target) it. foomuuri-0.33/docs/config/section/template.md000066400000000000000000000013471521001665700213120ustar00rootroot00000000000000# template Template is very similar to [macro](macro.md). It's just another way to define list of rules. Usually macro refers to single service (like `domain` or `facetime`) while template refers to list of different services. Example: ``` template outgoing_services { # Define template called "outgoing_services" dhcp-server domain https ntp ping ssh } localhost-public { # Include template's content here template outgoing_services # Continue with other rules http reject log } dmz-public { # Use same template for traffic coming from dmz zone template outgoing_services # Continue with other rules reject log } ``` See [host firewall](../../example/host-firewall.md#multi-zone) for real life example. foomuuri-0.33/docs/config/section/zone.md000066400000000000000000000034351521001665700204520ustar00rootroot00000000000000# zone This section is required on all configurations. It lists all known zones. ``` zone { localhost public } ``` Above example defines two zones, `localhost` and `public`. All configurations must have zone `localhost`, which is the computer running Foomuuri, similar to "localhost" in hostnames. See [zone names](../basic.md#zone-names) for recommended zone naming. Above example assumes that you are using firewalld D-Bus (`dbus_firewalld` config option) emulation where interfaces are attached and detached to zones by NetworkManager. It is the recommended way for laptops and personal servers. This config option will be turned on by default when installing `foomuuri-firewalld` package. You can also define default interface to zone mapping by specifying interface name(s) after zone name. This is useful for corporate servers with static network configuration. This method can be used with or without firewalld D-Bus emulation. This mapping is only default, not static. Interfaces can still be moved to other zones with D-Bus calls. ``` zone { localhost # Localhost must be left empty public eth0 # eth0 is attached to public dmz eth1 eth2 # eth1 and eth2 are in dmz } ``` It is also possible to use wildcard interface names. If you define `wg*` then make sure that NetworkManager doesn't try to assign `wg0` to any zone. It would create "interval overlap" error as `wg*` and `wg0` conflicts. ``` zone { localhost public eth0 wireguard wg* # Matches wg0, wg1, wgfoo, and so on } ``` There is also catch all interface `*`. It will match all unassigned interfaces. Interfaces assigned to a zone in config or by NetworkManager will use that zone. ``` zone { localhost public * # All other than eth0 are assigned to public internal eth0 } ``` foomuuri-0.33/docs/config/section/zonemap.md000066400000000000000000000026711521001665700211510ustar00rootroot00000000000000# zonemap Normally Foomuuri will map incoming and outgoing traffic to zones by source and destination network interface. These interfaces are assigned to zones dynamically by NetworkManager, or configured in [zone](zone.md) section. Zonemap section can be used to map traffic to different zone by using standard [rules](../rule/index.md). Example: ``` zonemap { # Map outgoing IPsec traffic that is going to zone "public" to use zone # "vpn" instead. dipsec dzone public new_dzone vpn # Same for incoming. sipsec szone public new_szone vpn } localhost-public { # Rules for non-IPsec traffic ipsec # You must allow IPsec traffic here and in public-localhost ...accept some traffic... reject log } localhost-vpn { # Rules for IPsec traffic ...accept some traffic... reject log } ``` Above example, splitting traffic to IPsec and non-IPsec zones is the most common use case. You can use any matcher, for example `daddr` or `saddr` to map some IP addresses to own zones, or `uid` or `gid` to map outgoing traffic from some local user to own zone: ``` zonemap { # Map IP address 10.2.3.0/24 from internal to dmz saddr 10.2.3.0/24 szone internal new_szone dmz daddr 10.2.3.0/24 dzone internal new_dzone dmz # Map outgoing traffic from user myservice to myzone uid myservice szone localhost new_szone myzone # Map all outgoing IPsec traffic to xxx-vpn, no matter what the original # dzone was dipsec new_dzone vpn } ``` foomuuri-0.33/docs/config/section/zonezone.md000066400000000000000000000036371521001665700213520ustar00rootroot00000000000000# zone-zone FromZone-ToZone section defines [rules](../rule/index.md) for traffic coming from FromZone and going to ToZone. Normally you first accept some traffic and then [reject or drop](../rule/statement.md#accept-drop-reject) everything else as final rule. Rules inside zone-zone section are (mostly, see below) processed in listed order. Example: ``` public-localhost { # Allow some incoming traffic dhcp-client ping ssh # Drop everything else drop log } localhost-public { # Allow some outgoing traffic dhcp-server domain https ping ssh # Reject everything else reject log } ``` Foomuuri will automatically add final `drop log` (or `reject log` for `localhost-something`) rule to zone-zone section if not specified. It is always better to add explicit final rule to configuration. Zone-zone section `localhost-localhost` (aka loopback traffic, aka `127.0.0.1` and `::1`) is special case. It's final rule is `accept`. Usually there is no need to add `localhost-localhost`section. Normal use case for `localhost-localhost` is to deny some traffic and then accept everything else. ``` localhost-localhost { # Don't allow user "untrusted" to connect local services uid untrusted drop log # Don't allow local http traffic http reject log # Accept everything else accept } ``` Please note that loopback traffic from your public IP to your public IP belongs to `localhost-localhost`, not `public-public`. If you have a lot of zones there will be a lot of zone-zone pairs. See [configuration files](../basic.md#configuration-files) for recommendations how to split them to multiple files. "Mostly": Rules inside zone-zone section are automatically sorted and processed in following block order: 1. ICMP rules in listed order 2. Previously accepted established and related traffic is accepted by conntrack 3. Incoming multicast and broadcast rules in listed order 4. Everything else in listed order foomuuri-0.33/docs/example/000077500000000000000000000000001521001665700156725ustar00rootroot00000000000000foomuuri-0.33/docs/example/advanced.md000066400000000000000000000052321521001665700177630ustar00rootroot00000000000000# Advanced Filtering ## Port Knocking Port knocking is a technique where attempting to connect to port A enables access to port B from that same source IP address. This is usually used to hide SSH service. Example configuration: ``` zone { localhost public } iplist { # List of IP addresses that have performed initial knock.. # It is valid for 30 seconds. @knock dynamic=yes element_timeout=30s } public-localhost { # Allow SSH if IP address is in @knock iplist. ssh saddr @knock # Add source IP address to @knock iplist if there is UDP packet to port 5042. udp 5042 iplist_update saddr @knock drop log + ":knock" # Delete IP address from @knock iplist if there is any packet to any other # port. This prevents opening SSH if port scan is received. saddr @knock iplist_delete saddr @knock continue log + ":unknock" # ...other normal rules... drop log } ``` ## Automatic IP Address Banning Foomuuri supports automatic IP address banning without any external programs. This is usually enough, no `fail2ban` is needed. Banning happens fully on packet path, native on nftables level. Major differences compared to `fail2ban` program: * Banning is done on connection count/rate level only, not on protocol success/failure level. This means that too many successful SSH connection could result banning. Therefore it is important to use high rate limit or list of known good hosts, or both. * Foomuuri restart and reboot will clear ban list. * There's no need for external programs or complex log file parsing. Example configuration: ``` zone { localhost public } iplist { # List of known good hosts, don't ban these @good 192.168.0.0/24 foobar.fi # List containing banned IP addresses. They will be banned for 5 minutes. @banned dynamic=yes element_timeout=5m } public-localhost { # Drop all new traffic if source IP address is in @banned iplist. # Update/reset ban expire timeout. Add this as first rule. saddr @banned iplist_update saddr @banned drop log + ":banned" # Allow SSH from known good hosts and others with rate limit. # If there are more than 5/minute connections add them to @banned iplist. ssh saddr @good ssh saddr_rate "5/minute burst 5" ssh iplist_update saddr @banned drop log + ":ban-ssh" # ...other normal rules, with or without similar rate limit banning... # Instead of normal final "drop log" rule: # - Allow 2/min dropped connections without banning. # - Somebody is port scanning us. Add IP address to @banned iplist. # - Make sure @good addresses are never banned. saddr @good drop log saddr_rate "2/minute burst 10" drop log iplist_update saddr @banned drop log + ":ban-portscan" } ``` foomuuri-0.33/docs/example/host-firewall.md000066400000000000000000000070471521001665700210040ustar00rootroot00000000000000# Host Firewall Following examples apply for: * Your personal laptop * Your personal workstation * Corporate server behind [router firewall](router-firewall.md) * Corporate server on cloud * Any other system with only one network connection ## Incoming only This is the simplest possible firewall. All outgoing traffic is accepted and few listed incoming services are accepted. ``` mermaid flowchart LR public@{shape: cloud} --> localhost@{shape: stadium} ``` ``` zone { localhost public * # All network interfaces belong to zone "public" } public-localhost { # Allow specified incoming traffic dhcp-client dhcpv6-client ping ssh drop log } localhost-public { # Allow all outgoing traffic accept } ``` Above example is complete `/etc/foomuuri/foomuuri.conf` configuration file - there is nothing else to be added. It allows incoming (`public-localhost`) traffic: * DHCP reply packets to obtain a lease from external DHCP server (IPv4 and IPv6) * Ping packets (no ping-flood protection) * SSH * Everything else is dropped and logged All outgoing (`localhost-public`) traffic is accepted. This is usually safe but more specific bidirectional firewall is safer. ## Bidirectional This example accepts listed incoming services and listed outgoing services. ``` mermaid flowchart LR public@{shape: cloud} <--> localhost@{shape: stadium} ``` ``` zone { localhost public * } public-localhost { dhcp-client dhcpv6-client ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" drop log } localhost-public { dhcp-server dhcpv6-server domain http https imap ntp ping smtp ssh reject log } ``` This complete `/etc/foomuuri/foomuuri.conf` configuration file allows incoming: * DHCP reply packets * Ping packets, except ping-flood * SSH, up to 5 connections per minute per source IP * Everything else is dropped and logged Following outgoing traffic is allowed: * DHCP request packets to obtain a lease * DNS queries * HTTP and HTTPS * IMAP * NTP * Ping packets * SMTP * SSH * Everything else is rejected and logged ## Multi-zone This is similar to bidirectional example, except there are two outgoing zones: * `public` is the default untrusted connection. There is no network interface listed. Use NetworkManager to assign network interface to `public` zone when you're connecting to untrusted Wi-Fi network, for example in a cafe. * `home` is trusted connection. Again use NetworkManager to select `home` zone when you're in a safe place, like at your home or office. This example also shows you how to use `template` to avoid listing same basic services in `localhost-public` and in `localhost-home`. ``` mermaid flowchart LR subgraph WAN direction TB public@{shape: cloud} home@{shape: cloud} end public <--> localhost@{shape: stadium} home <--> localhost ``` ``` zone { localhost public home } public-localhost { # Incoming traffic in a cafe dhcp-client dhcpv6-client ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" drop log } home-localhost { # Incoming traffic in safe location dhcp-client dhcpv6-client lsdp mdns ping ssdp ssh drop log } template outgoing_services { # Common outgoing traffic dhcp-server dhcpv6-server domain http https imap ntp ping smtp ssh } localhost-public { # Outgoing traffic in a cafe template outgoing_services reject log } localhost-home { # Outgoing traffic in safe location template outgoing_services googlemeet ipp mdns ssdp reject log } ``` foomuuri-0.33/docs/example/multiple-isp.md000066400000000000000000000267151521001665700206530ustar00rootroot00000000000000# Multiple ISP Foomuuri supports multiple ISPs (aka multi-ISP aka multi-WAN aka multiple simultaneous uplink connections) with active-active (load balancing) or active-passive (failover) configuration. ## Configuration with static ISP routes This example requires static ISP routes. It works with NetworkManager and systemd-networkd. Both active-active and active-passive configurations are supported. Both configuration types are very similar, with only one line changed. This example configuration assumes: * ISP #1 is network interface `enp1s0`, my own IP is 172.23.70.36/24, gateway is 172.23.70.254. Traffic will be marked with value 0x100 and uses route table 1001. * ISP #2 is network interface `enp2s0`, my own IP is 172.23.12.31/24, gateway is 172.23.12.254. Traffic will be marked with value 0x200 and uses route table 1002. * Zone `internal` network interface is `enp8s0`, with network 10.0.0.0/8. Outgoing traffic to `public` zone is masqueraded. Example `foomuuri.conf` file: ``` # 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 } # 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 } ``` Example `/etc/foomuuri/multi-isp` script. Remember to save it as executable, `chmod 750 /etc/foomuuri/multi-isp`. ``` bash #!/bin/sh # Path to "ip" command IP=/usr/sbin/ip case "${1}" in "start") # Started, run by foomuuri-multi-isp.service echo "Foomuuri multi-ISP start" # Create new route tables for both ISPs having default route ${IP} route add table 1001 default dev enp1s0 via 172.23.70.254 ${IP} route add table 1002 default dev enp2s0 via 172.23.12.254 # Delete default routes from main table ${IP} route del table main default dev enp1s0 via 172.23.70.254 ${IP} route del table main default dev enp2s0 via 172.23.12.254 # Start rules with main-table lookup ${IP} rule add prio 1000 from all table main # Packets with specific source IP must go to correct ISP ${IP} rule add prio 1001 from 172.23.70.36 table 1001 ${IP} rule add prio 1002 from 172.23.12.31 table 1002 # Other ISP-specific rules will be added by monitor's up-event. ;; "stop") # Stopped, run by foomuuri-multi-isp.service echo "Foomuuri multi-ISP stop" # Add default route back to main table ${IP} route add table main default dev enp1s0 via 172.23.70.254 metric 100 ${IP} route add table main default dev enp2s0 via 172.23.12.254 metric 101 # Delete added rules ${IP} rule del prio 1000 ${IP} rule del prio 1001 ${IP} rule del prio 1002 ${IP} rule del prio 1101 2> /dev/null ${IP} rule del prio 1102 2> /dev/null ${IP} rule del prio 1201 2> /dev/null ${IP} rule del prio 1202 2> /dev/null # Delete added route tables ${IP} route flush table 1001 ${IP} route flush table 1002 ;; "up") # ISP is up, add rules to route traffic to it echo "Foomuuri multi-ISP isp${2} up" # Use packet mark to select this ISP ${IP} rule add prio 110${2} from all fwmark 0x${2}00/0xff00 table 100${2} # Fallback to this ISP if other is down ${IP} rule add prio 120${2} from all table 100${2} ;; "down") # ISP is down, delete its rules echo "Foomuuri multi-ISP isp${2} down" ${IP} rule del prio 110${2} ${IP} rule del prio 120${2} ;; *) echo "syntax error" exit 1 esac ``` Example `/etc/systemd/system/foomuuri-multi-isp.service` file. Remember to enable it with `systemctl enable foomuuri-multi-isp.service`. ``` systemd [Unit] Description=Multizone bidirectional nftables firewall - Multi-ISP Documentation=https://foomuuri.foobar.fi/latest/ After=network-online.target Requires=network-online.target PartOf=foomuuri.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/etc/foomuuri/multi-isp start ExecStop=/etc/foomuuri/multi-isp stop [Install] WantedBy=multi-user.target ``` ## Configuration with routes dynamically allocated by ISPs This example configuration assumes: * ISP #1 is on the network interface `enp1s0` with routes and IP addresses allocated via DHCP/IPv6RA. Traffic will be marked with value 0x100. * ISP #2 is on the network interface `enp2s0` with routes and IP addresses allocated via DHCP/IPv6RA. Traffic will be marked with value 0x200. * Zone `internal` network interface is `enp8s0` with IP 10.0.0.1/24. Outgoing traffic to `public` zone is masqueraded. * At least the public interfaces and their routing tables will be managed by `systemd-networkd`. * This example demonstrates a purely failover setup from ISP #1 to ISP #2. For load balancing add a second target monitor for `enp2s0` in `foomuuri.conf` and adjust the `switch.sh` script to identify and replace the fwmark randomizer as documented in the static routes example above. Initiate the routing tables by creating the following two files. `/etc/systemd/networkd.conf.d/table-primary.conf`: ``` [Network] RouteTable=primary:100 ``` `/etc/systemd/networkd.conf.d/table-secondary.conf`: ``` [Network] RouteTable=secondary:200 ``` Configure `enp1s0` as followed in `/etc/systemd/network/enp1s0.network`: ``` [Match] Name=enp1s0 [Network] DHCP=yes IPv6AcceptRA=yes [DHCPv4] RouteTable=primary [IPv6AcceptRA] RouteTable=primary [RoutingPolicyRule] FirewallMark=0x100/0xff00 Family=both Table=primary Priority=40100 [RoutingPolicyRule] Family=both Table=primary Priority=41000 ``` Configure `enp2s0` as followed in `/etc/systemd/network/enp2s0.network`: ``` [Match] Name=enp2s0 [Network] DHCP=yes IPv6AcceptRA=yes [DHCPv4] RouteTable=secondary [IPv6AcceptRA] RouteTable=secondary [RoutingPolicyRule] FirewallMark=0x200/0xff00 Family=both Table=secondary Priority=40200 [RoutingPolicyRule] Family=both Table=secondary Priority=42000 ``` After a restart of `systemd-networkd` the `ip route show table main` command should show this output: `10.0.0.0/24 dev enp8s0 proto kernel scope link src 10.0.0.1` As you can see no default route is defined in the main routing table because `systemd-networkd` added them only to our separate primary and secondary tables as defined above. This can be confirmed by checking the outputs of `ip route show table 100` and `ip route show table 200`. You should see the routes added via DHCP (`ip -6 route show table 100` for ipv6 router advertisement). `ip rule` should output something like this: ``` 0: from all lookup local 32766: from all lookup main 32767: from all lookup default 40100: from all fwmark 0x100/0xff00 lookup 100 proto static 40200: from all fwmark 0x200/0xff00 lookup 200 proto static 41000: from all lookup 100 proto static 42000: from all lookup 200 proto static ``` The catchall rules `41000` and `42000` are needed so `localhost` knows where to lookup the default routes. Now all that is missing is to configure Foomuuri to use the `fwmark` rules. Example `foomuuri.conf` file: ``` zone { localhost internal enp8s0 public enp1s0 enp2s0 } foomuuri { rpfilter -enp1s0 -enp2s0 } snat { oifname enp1s0 masquerade oifname enp2s0 masquerade } prerouting { mark_match -0x0000/0xff00 iifname enp1s0 mark_set 0x100/0xff00 iifname enp2s0 mark_set 0x200/0xff00 } target main { command fping --iface enp1s0 8.8.8.8 command_up /etc/foomuuri/switch.sh up command_down /etc/foomuuri/switch.sh down } zone-zone rules... ``` If the `fping` monitor recognizes no uplink on the main interface `enp1s0` the following `switch.sh` script (remember to `chmod 750`) will set the default fwmark to 0x200 from the backup interface `enp2s0` by directly manipulating the nft prerouting chain. ``` bash #!/bin/bash case "$1" in up) echo "main is up" FWMARK="0x00000100" FWMASK="0xffff01ff" ;; down) echo "main is down" FWMARK="0x00000200" FWMASK="0xffff02ff" ;; *) echo "syntax error" exit 1 esac HANDLE="$( nft --handle list chain inet foomuuri filter_prerouting_mangle | grep --only-matching --perl-regexp '^\s+meta mark set meta mark.*handle\s\K([0-9]+)$' )" if [[ $HANDLE ]] then echo "catchall fwmark rule with handle $HANDLE found" nft replace rule inet foomuuri filter_prerouting_mangle handle "$HANDLE" \ meta mark set meta mark \& "$FWMASK" \| "$FWMARK" \ ct mark set meta mark accept echo "replaced handle $HANDLE with new fwmark ${FWMARK}/${FWMASK}" else echo "no existing catchall fwmark rule found" nft add rule inet foomuuri filter_prerouting_mangle \ meta mark set meta mark \& "$FWMASK" \| "$FWMARK" \ ct mark set meta mark accept echo "added catchall fwmark ${FWMARK}/${FWMASK}" fi ``` foomuuri-0.33/docs/example/router-firewall.md000066400000000000000000000135301521001665700213410ustar00rootroot00000000000000# Router Firewall ## localhost - public - internal This example is for small corporate firewall: * Single firewall computer that runs: * DHCP server * DNS resolver * Email and all other services are run on cloud * Multiple laptops and workstations in internal network * Bidirectional firewalling ``` mermaid flowchart LR public@{shape: cloud} <--> localhost@{shape: stadium} subgraph Local Network localhost <--> internal end ``` ``` zone { localhost public eth0 internal eth1 } snat { # Masquerade traffic from internal to public. saddr 10.0.0.0/8 oifname eth0 masquerade } public-localhost { # Allow only ping and SSH to localhost. ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" # If localhost gets its public IP with DHCP, add "dhcp-client" here. drop log } internal-localhost { # localhost runs DHCP (IPv4 and IPv6) server and DNS resolver for internal # network, plus basic SSH etc. rules. 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 { # Basic services from localhost to internet: DNS queries, HTTPS, SSH, etc. # Allow also SMTP so that localhost can send email. template outgoing_services # If localhost gets its public IP with DHCP, add "dhcp-server" here and # "dhcp-client" to public-localhost. reject log } internal-public { # Laptops and workstations in internal network can access web and # email services in internet. This ruleset is similar as localhost-public # in host firewall example. This is the most important zone-zone # section to configure. template outgoing_services googlemeet imap 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 } ``` ### Enable packet forwarding For public-internal and other forwarding you must enable IP packet forwarding on Linux kernel. That can be done by creating `/etc/sysctl.d/50-ip.forwarding.conf` file with following lines: ``` # Enable IP packet forwarding net.ipv4.conf.all.forwarding = 1 net.ipv6.conf.all.forwarding = 1 ``` ## localhost - public - dmz - internal This example is for larger corporate firewall: * Single firewall computer that runs: * DHCP server * DNS resolver * Single dmz computer that runs: * Web server * Email server * Multiple laptops and workstations in internal network * Bidirectional firewalling See also note about enabling IP packet forwarding above. ``` mermaid flowchart LR public@{shape: cloud} <--> localhost@{shape: stadium} subgraph Local Network localhost <--> internal localhost <--> dmz end ``` ``` zone { 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 http https dnat 10.1.0.2 # public -> dmz iifname eth1 daddr 192.0.2.32 smtp http 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.33/docs/faq.md000066400000000000000000000056141521001665700153360ustar00rootroot00000000000000# FAQ ## GNOME NetworkManager supports dynamically [mapping](config/section/zone.md) connections with firewall zones but graphical connection editor GNOME Control Center doesn't. There are two alternative ways to edit connections with zone support: * Use graphical `nm-connection-editor` (recommended) * Use command line `nmcli connection modify connection.zone ` This needs to be done only when changing firewall zone for connection. All other edits can be done with GNOME Control Center. ## DHCP To obtain an IP address with DHCP you must allow both outgoing `dhcp-server` request and incoming `dhcp-client` reply. Example: ``` localhost-public { # Allow localhost's DHCP client to send a request to a DHCP server running # on public zone (discover/request a lease). dhcp-server dhcpv6-server } public-localhost { # Allow reply packet from public's DHCP server to localhost's client # (offer an IP address). dhcp-client dhcpv6-client } ``` Similar rules are required if you run DHCP server on `localhost` serving IP leases to your `internal` zone clients: ``` internal-localhost { # Allow incoming DHCP discover/request from internal's client to # DHCP server running on localhost. dhcp-server dhcpv6-server } localhost-internal { # Allow localhost's DHCP server to send an offer reply to internal's client. dhcp-client dhcpv6-client } ``` ## Proxy ARP See [discussions](https://github.com/FoobarOy/foomuuri/discussions/2) how to configure proxy ARP with Foomuuri using [hooks](config/section/hook.md). ## Hairpin NAT / NAT Loopback See [issues](https://github.com/FoobarOy/foomuuri/issues/8) how to configure hairpin NAT with Foomuuri using [snat](config/section/snat.md) and [dnat](config/section/dnat.md). Usually it is better to do split DNS instead of hairpin NAT. Split DNS has locally served zone with local IP addresses and publicly served zone with public IP addresses. ## fail2ban Foomuuri supports automatic IP address [banning](example/advanced.md#automatic-ip-address-banning) without any external programs. This is usually enough, no `fail2ban` is needed. Banning happens fully on packet path, native on nftables level. Alternatively, see [issues](https://github.com/FoobarOy/foomuuri/issues/9) how to integrate Foomuuri with `fail2ban` program. ## Custom nftables chains See [discussions](https://github.com/FoobarOy/foomuuri/discussions/31) how to define custom nftables chains and how to jump to them. ## QEMU/libvirt and vnet interfaces in a bridge See [discussions](https://github.com/FoobarOy/foomuuri/discussions/15) how to silently drop `OUTPUT REJECT IN= OUT=vnetXX` log entries. These lines are logged after bridge interface is added or removed. ## Don't route private networks to public internet See [issues](https://github.com/FoobarOy/foomuuri/issues/24) how to block routing 10.0.0.0/8 and similar private networks to public internet. foomuuri-0.33/docs/index.md000066400000000000000000000043151521001665700156730ustar00rootroot00000000000000--- hide: - navigation --- # Foomuuri Foomuuri is a multizone bidirectional nftables firewall. See [host firewall](example/host-firewall.md) or [router firewall](example/router-firewall.md) for example configuration files. [Installation](install.md) page contains instructions how to install Foomuuri. Help is available via [GitHub discussions](https://github.com/FoobarOy/foomuuri/discussions) and IRC channel `#foomuuri` on Libera.Chat. ## Features * Firewall [zones](config/section/zone.md) * [Bidirectional](example/host-firewall.md#bidirectional) firewalling for incoming, outgoing and forwarding traffic * Suitable for all systems from personal [laptop](example/host-firewall.md) to [corporate](example/router-firewall.md) firewalls * Rich rule language for flexible and complex [rules](config/rule/index.md) * Predefined list of [services](https://github.com/FoobarOy/foomuuri/blob/main/etc/default.services.conf) for simple rule writing * Rule language supports [macros](config/section/macro.md) and [templates](config/section/template.md) * IPv4 and IPv6 support with automatic rule [splitting](config/rule/matcher.md#saddr-daddr) per protocol * [SNAT](config/section/snat.md), [DNAT](config/section/dnat.md) and masquerading support * [Logging](config/rule/logging.md) and counting * [Rate](config/rule/ratelimit.md) limiting * DNS hostname [lookup](config/section/iplist.md) and IP-list support with dynamic IP address refreshing * [Country database](config/section/iplist.md) support aka geolocation * [Multiple ISP](example/multiple-isp.md) support with internal network [connectivity monitor](tool/monitor.md) * [IPsec](config/rule/matcher.md#sipsec-dipsec) matching support * Ability to [map](config/section/zonemap.md) certain traffic to separate zones * Automatic IP address [banning](example/advanced.md#automatic-ip-address-banning) support * [Port knocking](example/advanced.md#port-knocking) support * D-Bus API * Firewalld emulation for NetworkManager's zone support * Raw nftables [rules](config/rule/misc.md#nft) can be used * Fresh design, written to use modern nftables's features ## Changelog [Changelog](https://github.com/FoobarOy/foomuuri/blob/main/CHANGELOG.md) file contains recent changes. foomuuri-0.33/docs/install.md000066400000000000000000000067251521001665700162410ustar00rootroot00000000000000# Installation The easiest way to install Foomuuri is to use packages from your Linux distribution. See [host firewall](example/host-firewall.md) for example `/etc/foomuuri/foomuuri.conf` configuration file. ## Fedora, RHEL, CentOS Stream Foomuuri is included to Fedora and to EPEL. ``` # Install packages dnf install foomuuri foomuuri-firewalld # Configure Foomuuri and verify it $EDITOR /etc/foomuuri/foomuuri.conf foomuuri check # Disable and stop current firewall, for example: systemctl disable firewalld.service systemctl disable shorewall.service systemctl disable shorewall6.service systemctl disable shorewall-init.service nft flush ruleset # Start Foomuuri systemctl start foomuuri.service # Check journal log for firewall logging journalctl --follow --dmesg # If everything works, make Foomuuri persistent across reboots systemctl enable foomuuri.service ``` ## Debian, Ubuntu Foomuuri is include to Debian sid, forky, trixie (13) and bookworm-backports (12), Ubuntu 23.10 (Mantic) and Ubuntu 24.04 (Noble). ``` # Install packages apt install foomuuri foomuuri-firewalld # Configure Foomuuri and verify it $EDITOR /etc/foomuuri/foomuuri.conf foomuuri check # Disable and stop current firewall, for example: systemctl disable firewalld.service systemctl disable shorewall.service systemctl disable shorewall6.service systemctl disable shorewall-init.service nft flush ruleset # Start Foomuuri systemctl start foomuuri.service # Check journal log for firewall logging journalctl --follow --dmesg # If everything works, make Foomuuri persistent across reboots systemctl enable foomuuri.service ``` ## Arch Linux Foomuuri is included to Arch User Repository (AUR). ``` # Build and install packages git clone https://aur.archlinux.org/foomuuri.git cd foomuuri makepkg pacman -U foomuuri-*-x86_64.pkg.tar.zst # Configure Foomuuri and verify it $EDITOR /etc/foomuuri/foomuuri.conf foomuuri check # Disable and stop current firewall, for example: systemctl disable firewalld.service systemctl disable shorewall.service systemctl disable shorewall6.service systemctl disable shorewall-init.service nft flush ruleset # Start Foomuuri systemctl start foomuuri.service # Check journal log for firewall logging journalctl --follow --dmesg # If everything works, make Foomuuri persistent across reboots systemctl enable foomuuri.service ``` ## Source code Source code tarball is available in [releases page](https://github.com/FoobarOy/foomuuri/releases). Foomuuri depends on `nftables` (version 1.0.0 or higher, with JSON support enabled) and `python` (version 3.9 or higher). Optional but highly recommended D-Bus support needs `python3-dbus` and `python3-gobject` (called `python3-gi` in some distributions). Optionally Foomuuri will use `python3-systemd`, `python3-urllib3` and `python3-lxml` if they are available. ``` # Untar source tar xf foomuuri-0.??.tar.gz cd foomuuri-0.?? # Install it to root filesystem make install DESTDIR=/ systemctl daemon-reload sysctl --system # Configure Foomuuri and verify it $EDITOR /etc/foomuuri/foomuuri.conf foomuuri check # Disable and stop current firewall, for example: systemctl disable firewalld.service systemctl disable shorewall.service systemctl disable shorewall6.service systemctl disable shorewall-init.service nft flush ruleset # Start Foomuuri systemctl start foomuuri.service # Check journal log for firewall logging journalctl --follow --dmesg # If everything works, make Foomuuri persistent across reboots systemctl enable foomuuri.service ``` foomuuri-0.33/docs/tool/000077500000000000000000000000001521001665700152145ustar00rootroot00000000000000foomuuri-0.33/docs/tool/monitor.md000066400000000000000000000101421521001665700172230ustar00rootroot00000000000000# Foomuuri Monitor Foomuuri includes simple network connectivity monitor. It can monitor your internet connection by pinging some external server. Command can be run if network link goes up or down. Example [command](https://github.com/FoobarOy/foomuuri/blob/main/misc/monitor.event) to send an email notification to root is included in doc directory. Another examples are [multiple ISP](../example/multiple-isp.md) configurations and commands. ## target Minimal configuration is: ``` target google { command fping 8.8.4.4 } ``` This creates monitor called `google` and runs `fping` command pinging IP 8.8.4.4 every second. Foomuuri parses its output and logs up and down events. Multiple targets can be defined. Better real life example is: ``` target my-isp-router { command fping --interval=2000 172.25.31.149 command_up /etc/foomuuri/monitor.event command_down /etc/foomuuri/monitor.event } ``` This pings IP 172.25.31.149 every two seconds and runs `monitor.event` command when link goes up or down. That command sends an email notification to root. See `man fping` or its [website](https://www.fping.org/) for description of `fping` parameters. Foomuuri supports both `interval` and `squiet` modes. It is recommended to use full seconds in `--interval`. "Up" and "down" are defined with parameters: ``` target my-isp-router { history_size 100 # how many results are saved history_up 80 # count of UPs => n => UP history_down 30 # count of DOWNs >= n => DOWN consecutive_up 20 # last n were UP => UP consecutive_down 10 # last n were DOWN => DOWN ... } ``` Target is considered up if 80 of the last 100 pings were successful (allowing failures in between) and last 20 pings were successful (no failures allowed). Target is considered down if 30 of the last 100 pings were failures or last 10 pings were failures. `curl` and other programs can also be used instead of `fping`. See example [shell script](https://github.com/FoobarOy/foomuuri/blob/main/misc/monitor-example-command.sh) how to use them. It is recommended to use IP address instead of hostname as `fping` target. Hostname lookup will fail if network is down when `fping` starts. Foomuuri will handle this but it will cause 30 second delay and possible `fping` restart loop. Optional `command_down_interval` can be specified. Foomuuri will run it every `down_interval` seconds (default to 600, every 10 minutes). Example: ``` target my-isp-router { # Connectivity is still down. Ask NetworkManager to re-initialize # eth0 connection. command_down_interval nmcli connection up eth0 # Run it every 15 minutes down_interval 900 ... } ``` Up/down command receives status information in environment variables: * `FOOMUURI_CHANGE_TYPE`: type `target` or `group` * `FOOMUURI_CHANGE_NAME`: name of target or group changing state * `FOOMUURI_CHANGE_STATE`: state `up` or `down` * `FOOMUURI_CHANGE_LOG`: extra logging information * `FOOMUURI_CHANGE_HISTORY`: list of `!` (error) or `.` (ok) indicating status of last checks * `FOOMUURI_ALL_TARGET`: list of all configured targets * `FOOMUURI_ALL_GROUP`: list of all configured groups * `FOOMUURI_TARGET_xxx`: state `up` or `down` for target `xxx` * `FOOMUURI_GROUP_xxx`: state `up` or `down` for group `xxx` Only single command can be specified. If you need to run multiple commands use a shell wrapper script to run them. Monitor statistics are written to a file once a minute. ## group Multiple monitor [targets](monitor.md#target) can be grouped to single monitor. Example: ``` group my-isp-group { target my-isp-router google command_up /etc/foomuuri/monitor.event command_down /etc/foomuuri/monitor.event } ``` This creates a monitor called `my-isp-group` which includes two targets. Group is considered up if any of the targets is up. It is considered down if all of the targets are down. It is usually safer to run up and down commands in `group {}` with multiple targets than in single `target {}`. Optional `command_down_interval` and `down_interval` can also be defined. See [above](monitor.md#target) for description. foomuuri-0.33/docs/zensical.toml000066400000000000000000000063611521001665700167520ustar00rootroot00000000000000[project] nav = [ {"Home" = "index.md"}, {"Installation" = "install.md"}, {"Configuration" = [ {"Basics" = "config/basic.md"}, {"Sections" = [ "config/section/index.md", {"foomuuri" = "config/section/foomuuri.md"}, {"zone" = "config/section/zone.md"}, {"zone-zone" = "config/section/zonezone.md"}, {"zone-any, any-zone, any-any" = "config/section/any.md"}, {"macro" = "config/section/macro.md"}, {"iplist" = "config/section/iplist.md"}, {"snat" = "config/section/snat.md"}, {"dnat" = "config/section/dnat.md"}, {"template" = "config/section/template.md"}, {"hook" = "config/section/hook.md"}, {"zonemap" = "config/section/zonemap.md"}, {"prerouting, postrouting, forward, input, output" = "config/section/special.md"}, {"invalid, rpfilter, smurfs" = "config/section/invalid.md"}, {"target, group" = "config/section/targetgroup.md"}, ]}, {"Rule" = [ "config/rule/index.md", {"Matchers" = "config/rule/matcher.md"}, {"Statements" = "config/rule/statement.md"}, {"Logging" = "config/rule/logging.md"}, {"Rate Limit" = "config/rule/ratelimit.md"}, {"Miscellaneous" = "config/rule/misc.md"}, ]}, ]}, {"Examples" = [ {"Host Firewall" = "example/host-firewall.md"}, {"Router Firewall" = "example/router-firewall.md"}, {"Advanced Filtering" = "example/advanced.md"}, {"Multiple ISP" = "example/multiple-isp.md"}, ]}, {"Tools" = [ {"Connectivity Monitor" = "tool/monitor.md"}, ]}, {"FAQ" = "faq.md"}, ] site_name = "Foomuuri" site_url = "https://foomuuri.foobar.fi/latest" repo_url = "https://github.com/FoobarOy/foomuuri" copyright = "Copyright 2023-2026, Kim B. Heino, Foobar Oy and contributors." [project.extra.version] provider = "mike" alias = true [project.theme] custom_dir = "overrides" features = [ "content.code.copy", "content.code.select", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.tabs", "navigation.top", "navigation.tracking", "search.highlight", "toc.integrate", ] [project.markdown_extensions.toc] toc_depth = 2 # Palette toggle for automatic mode [[project.theme.palette]] media = "(prefers-color-scheme)" toggle.icon = "lucide/sun-moon" toggle.name = "Switch to light mode" # Palette toggle for light mode [[project.theme.palette]] media = "(prefers-color-scheme: light)" scheme = "default" toggle.icon = "lucide/sun" toggle.name = "Switch to dark mode" # Palette toggle for dark mode [[project.theme.palette]] media = "(prefers-color-scheme: dark)" scheme = "slate" toggle.icon = "lucide/moon" toggle.name = "Switch to system preference" [project.markdown_extensions.pymdownx.highlight] anchor_linenums = true line_spans = "__span" pygments_lang_class = true [project.markdown_extensions.pymdownx.inlinehilite] [project.markdown_extensions.pymdownx.snippets] [project.markdown_extensions.pymdownx.superfences] custom_fences = [ { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" } ] foomuuri-0.33/etc/000077500000000000000000000000001521001665700140625ustar00rootroot00000000000000foomuuri-0.33/etc/50-foomuuri.conf000066400000000000000000000016331521001665700170230ustar00rootroot00000000000000# Warning: Do not edit this file as Foomuuri update will overwrite it. # # 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 ## To enable IP packet forwarding copy these two lines uncommented to ## /etc/sysctl.d/50-ip.forwarding.conf file. Do not edit this file. # # net.ipv4.conf.all.forwarding = 1 # net.ipv6.conf.all.forwarding = 1 foomuuri-0.33/etc/block.fw000066400000000000000000000011411521001665700155070ustar00rootroot00000000000000# "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". # # Warning: Do not edit this file as Foomuuri update will overwrite it. 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.33/etc/default.services.conf000066400000000000000000000125731521001665700202070ustar00rootroot00000000000000# 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. # # Warning: Do not edit this file as Foomuuri update will overwrite it. 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 tcp 24007 49152-60999 gluster-client gluster warning "macro gluster-client is obsoleted, use gluster instead" 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 matter udp 5540 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 ntske tcp 4460 ospf multicast protocol "ospf" daddr 224.0.0.5 224.0.0.6 ff02::5 ff02::6; multicast protocol "igmp" daddr 224.0.0.5 224.0.0.6; protocol "ospf" ping icmp echo-request; icmpv6 echo-request pop3s tcp 995 postgresql tcp 5432 prometheus tcp 9090 prometheus-blackbox tcp 9115 prometheus-chrony tcp 9123 prometheus-foobar tcp 11042 prometheus-foomuuri tcp 11041 prometheus-gluster tcp 9713 prometheus-hcloud tcp 9501 prometheus-ipmi tcp 9290 prometheus-keepalived tcp 9165 prometheus-knot tcp 9433 prometheus-mysqld tcp 9104 prometheus-node tcp 9100 prometheus-nut tcp 9199 prometheus-php-fpm tcp 9253 prometheus-postfix tcp 9907 prometheus-postgresql tcp 9187 prometheus-redis tcp 9121 prometheus-smartctl tcp 9633 prometheus-ssl tcp 9219 prometheus-systemd tcp 9558 prometheus-unbound tcp 9167 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 sips tcp 5061 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.33/etc/static.nft000066400000000000000000000034331521001665700160650ustar00rootroot00000000000000 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.33/firewalld/000077500000000000000000000000001521001665700152605ustar00rootroot00000000000000foomuuri-0.33/firewalld/dbus-firewalld.conf000066400000000000000000000000421521001665700210270ustar00rootroot00000000000000foomuuri { dbus_firewalld yes } foomuuri-0.33/firewalld/fi.foobar.Foomuuri-FirewallD.conf000066400000000000000000000022711521001665700234510ustar00rootroot00000000000000 foomuuri-0.33/man/000077500000000000000000000000001521001665700140625ustar00rootroot00000000000000foomuuri-0.33/man/Makefile000066400000000000000000000002371521001665700155240ustar00rootroot00000000000000all: foomuuri.8 prometheus-foomuuri-exporter.1 %.1: %.md pandoc --standalone --to=man $^ --output $@ %.8: %.md pandoc --standalone --to=man $^ --output $@ foomuuri-0.33/man/foomuuri.8000066400000000000000000000062111521001665700160200ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.7.0.2 .\" .TH "FOOMUURI" "8" "Jun 03, 2026" "Foomuuri 0.33" "User Manual" .SH NAME foomuuri \- multizone bidirectional nftables firewall .SH SYNOPSIS \f[B]foomuuri\f[R] [\f[I]OPTIONS\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\(cqs zone support. .SH OPTIONS .TP \f[B]\-\-help\f[R] Print help and exit. .TP \f[B]\-\-version\f[R] Print version information and exit. .TP \f[B]\-\-verbose\f[R] Verbose output. .TP \f[B]\-\-quiet\f[R] Be quiet. .TP \f[B]\-\-force\f[R] Force some operations, don\(cqt check anything. .TP \f[B]\-\-soft\f[R] Don\(cqt force operations, check more. .TP \f[B]\-\-fork\f[R] Fork as a background daemon process. .TP \f[B]\-\-syslog\f[R] Enable syslog logging. .TP \f[B]\-\-set=OPTION=VALUE\f[R] Set foomuuri{} 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 \f[B]iplist refresh\f[R]. .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 \(lqblock all traffic\(rq ruleset to kernel. .TP \f[B]ruleset list [ZONE\-ZONE]\&...\f[R] List active ruleset currently loaded to kernel. Include whole ruleset or only specified \f[B]ZONE\-ZONE\f[R]. .TP \f[B]macro list [NAME | VALUE]\&...\f[R] List all macros or macros with specified NAME(s) or VALUE(s). .TP \f[B]counter list [COUNTER]\&...\f[R] List all or specified named COUNTER(s). .TP \f[B]iplist status [IPLIST]\&...\f[R] List number of entries of all or specified IPLIST(s). .TP \f[B]iplist list [IPLIST]\&...\f[R] List entries of all or specified IPLIST(s). .TP \f[B]iplist add IPLIST [TIMEOUT] IPADDRESS [IPADDRESS]\&...\f[R] Add or refresh IPADDRESS(es) to IPLIST. TIMEOUT format is the same as in iplist{} section, for example \(lq4h\(rq. .TP \f[B]iplist del IPLIST IPADDRESS [IPADDRESS]\&...\f[R] Delete IPADDRESS(es) from IPLIST. .TP \f[B]iplist flush [IPLIST]\&...\f[R] Delete all added IP addresses from all or specified IPLIST(s). .TP \f[B]iplist refresh [IPLIST]\&...\f[R] Refresh all or specified IPLIST(s) now. .TP \f[B]set interface INTERFACE zone {ZONE | \-}\f[R] Change INTERFACE to ZONE, or remove 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://foomuuri.foobar.fi/latest/example/host-firewall/ .UE \c \ for example configuration. .SH AUTHORS Kim B. Heino, b\(atbbbs.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://foomuuri.foobar.fi/latest/ .UE \c foomuuri-0.33/man/foomuuri.md000066400000000000000000000054461521001665700162620ustar00rootroot00000000000000--- title: FOOMUURI section: 8 header: User Manual footer: Foomuuri 0.33 date: Jun 03, 2026 --- # NAME foomuuri - multizone bidirectional nftables firewall # SYNOPSIS **foomuuri** [*OPTIONS*] *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** : Print help and exit. **\--version** : Print 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 foomuuri{} 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 to kernel. **ruleset list [ZONE-ZONE]...** : List active ruleset currently loaded to kernel. Include whole ruleset or only specified **ZONE-ZONE**. **macro list [NAME | VALUE]...** : List all macros or macros with specified NAME(s) or VALUE(s). **counter list [COUNTER]...** : List all or specified named COUNTER(s). **iplist status [IPLIST]...** : List number of entries of all or specified IPLIST(s). **iplist list [IPLIST]...** : List entries of all or specified IPLIST(s). **iplist add IPLIST [TIMEOUT] IPADDRESS [IPADDRESS]...** : Add or refresh IPADDRESS(es) to IPLIST. TIMEOUT format is the same as in iplist{} section, for example "4h". **iplist del IPLIST IPADDRESS [IPADDRESS]...** : Delete IPADDRESS(es) from IPLIST. **iplist flush [IPLIST]...** : Delete all added IP addresses from all or specified IPLIST(s). **iplist refresh [IPLIST]...** : Refresh all or specified IPLIST(s) now. **set interface INTERFACE zone {ZONE | -}** : Change INTERFACE to ZONE, or remove 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.33/man/prometheus-foomuuri-exporter.1000066400000000000000000000030761521001665700220560ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.7.0.2 .\" .TH "PROMETHEUS\-FOOMUURI\-EXPORTER" "1" "Jun 03, 2026" "prometheus\-foomuuri\-exporter 0.33" "User Manual" .SH NAME prometheus\-foomuuri\-exporter \- Prometheus exporter for Foomuuri metrics .SH SYNOPSIS \f[B]prometheus\-foomuuri\-exporter\f[R] [\f[I]OPTIONS\f[R]] .SH DESCRIPTION \f[B]prometheus\-foomuuri\-exporter\f[R] reads Foomuuri metrics and exports them to Prometheus. .SH OPTIONS .TP \f[B]\-\-help\f[R] Print help and exit. .TP \f[B]\-\-address ADDRESS\f[R] Listen address. (default: ::) .TP \f[B]\-\-port PORT\f[R] Listen port number. (default: 11041) .TP \f[B]\-\-tls\-certificate FILENAME\f[R] TLS certificate file name. .TP \f[B]\-\-tls\-key FILENAME\f[R] TLS key file name. .TP \f[B]\-\-no\-monitor\-statistics\f[R] Do not export Foomuuri Monitor statistics. .TP \f[B]\-\-no\-ruleset\-statistics\f[R] Do not export ruleset statistics. .TP \f[B]\-\-statistics\-file FILENAME\f[R] Foomuuri Monitor statistics file name. .TP \f[B]\-\-set\-include REGEXP\f[R] Set names to be included to ruleset size statistics. .TP \f[B]\-\-set\-exclude REGEXP\f[R] Set names to be excluded from ruleset size statistics. .TP \f[B]\-\-counter\-include REGEXP\f[R] Counter names to be included to ruleset traffic statistics. .TP \f[B]\-\-counter\-exclude REGEXP\f[R] Counter names to be excluded from ruleset traffic statistics. .SH AUTHORS Kim B. Heino, b\(atbbbs.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://foomuuri.foobar.fi/latest/ .UE \c foomuuri-0.33/man/prometheus-foomuuri-exporter.md000066400000000000000000000026141521001665700223130ustar00rootroot00000000000000--- title: PROMETHEUS-FOOMUURI-EXPORTER section: 1 header: User Manual footer: prometheus-foomuuri-exporter 0.33 date: Jun 03, 2026 --- # NAME prometheus-foomuuri-exporter - Prometheus exporter for Foomuuri metrics # SYNOPSIS **prometheus-foomuuri-exporter** [*OPTIONS*] # DESCRIPTION **prometheus-foomuuri-exporter** reads Foomuuri metrics and exports them to Prometheus. # OPTIONS **\--help** : Print help and exit. **\--address ADDRESS** : Listen address. (default: ::) **\--port PORT** : Listen port number. (default: 11041) **\--tls-certificate FILENAME** : TLS certificate file name. **\--tls-key FILENAME** : TLS key file name. **\--no-monitor-statistics** : Do not export Foomuuri Monitor statistics. **\--no-ruleset-statistics** : Do not export ruleset statistics. **\--statistics-file FILENAME** : Foomuuri Monitor statistics file name. **\--set-include REGEXP** : Set names to be included to ruleset size statistics. **\--set-exclude REGEXP** : Set names to be excluded from ruleset size statistics. **\--counter-include REGEXP** : Counter names to be included to ruleset traffic statistics. **\--counter-exclude REGEXP** : Counter names to be excluded from ruleset traffic statistics. # AUTHORS Kim B. Heino, b@bbbs.net, Foobar Oy # BUG REPORTS Submit bug reports # SEE ALSO Full documentation foomuuri-0.33/misc/000077500000000000000000000000001521001665700142425ustar00rootroot00000000000000foomuuri-0.33/misc/foomuuri-bash-completion000066400000000000000000000052451521001665700211220ustar00rootroot00000000000000# 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 ruleset macro counter iplist set" ;; 2) case ${prev} in ruleset | macro | counter) _comp_compgen -- -W "list" ;; iplist) _comp_compgen -- -W "status list add del flush refresh" ;; set) _comp_compgen -- -W "interface" ;; esac ;; 3) case ${prev} in list) case ${words[cword-2]} in ruleset) 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 for zone in ${zones}; do _comp_compgen -a -- -W "${zone}-" done compopt -o nospace fi ;; macro) macros=$(foomuuri --quiet --quiet macro list | sed '/^ /!d; s/^ *//; s/ .*//') _comp_compgen -- -W "${macros}" ;; counter) counters=$(foomuuri --quiet --quiet counter list | sed '1,2d; s/ .*//') _comp_compgen -- -W "${counters}" ;; iplist) iplists=$(foomuuri --quiet --quiet iplist status | sed '/^@/!d; s/ .*//') _comp_compgen -- -W "${iplists}" ;; esac ;; status | add | del | flush | refresh) # iplist iplists=$(foomuuri --quiet --quiet iplist status | sed '/^@/!d; 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 # set interface intf _comp_compgen -- -W "zone" fi done ;; 5) case ${prev} in zone) # set interface intf 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.33/misc/monitor-example-command.sh000077500000000000000000000007701521001665700213410ustar00rootroot00000000000000#!/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.33/misc/monitor.event000077500000000000000000000015641521001665700170050ustar00rootroot00000000000000#!/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.33/misc/webdoc/000077500000000000000000000000001521001665700155055ustar00rootroot00000000000000foomuuri-0.33/misc/webdoc/Makefile000066400000000000000000000026101521001665700171440ustar00rootroot00000000000000# Generate web documentation for https://foomuuri.foobar.fi/ .PHONY: all version worktree clean distclean all: distclean mkdir final $(MAKE) version VERSION=0.27 HASH=cdfd86778a61d1ed81eaad50dbebc54bd7e9452e $(MAKE) version VERSION=0.28 HASH=38c5d4a1097f30cc2addd093b66c4a82e0e95763 $(MAKE) version VERSION=0.29 HASH=81c54c327f79baffdb7246d0bc637df821645d31 $(MAKE) version VERSION=0.30 HASH=8a6de6c5e61f62d84cbe9b24cfb742f9c9a65616 $(MAKE) version VERSION=0.31 HASH=8a6de6c5e61f62d84cbe9b24cfb742f9c9a65616 $(MAKE) version VERSION=0.32 HASH=9f850bfa3d55fca9972ff4556fece079e6c10d44 $(MAKE) version VERSION=development HASH=HEAD cp versions.json robots.txt final/ ln -s $(shell jq -r '.[] | select(.version == "latest") | .title | ltrimstr("v")' versions.json) final/latest ln -s latest/sitemap.xml final/ $(MAKE) clean version: clean (cd ../.. && git archive $(HASH) docs/) | tar --extract [ -f docs/zensical.toml ] || cat docs/nav.toml common.toml > docs/zensical.toml mv docs/zensical.toml . zensical build --strict sed -i -e "/<.loc>$$/a \\ $$(git show -s --format=%cI $(HASH))" site/sitemap.xml find site -exec touch --date="$$(git show -s --format=%ci $(HASH))" {} \; mv site final/$(VERSION) worktree: clean cp -a ../../docs docs mv docs/zensical.toml . zensical build --strict clean: rm -rf .cache zensical.toml docs site distclean: clean rm -rf final foomuuri-0.33/misc/webdoc/common.toml000066400000000000000000000025611521001665700176760ustar00rootroot00000000000000# This file is needed for build documentation for versions 0.27 - 0.32. # [project] (in docs/nav.toml) site_name = "Foomuuri" site_url = "https://foomuuri.foobar.fi/latest" repo_url = "https://github.com/FoobarOy/foomuuri" copyright = "Copyright 2023-2026, Kim B. Heino, Foobar Oy and contributors." [project.extra.version] provider = "mike" alias = true [project.theme] custom_dir = "overrides" features = [ "content.code.copy", "content.code.select", "navigation.expand", "navigation.path", "navigation.top", "navigation.tracking", "search.highlight", ] # Palette toggle for automatic mode [[project.theme.palette]] media = "(prefers-color-scheme)" toggle.icon = "lucide/sun-moon" toggle.name = "Switch to light mode" # Palette toggle for light mode [[project.theme.palette]] media = "(prefers-color-scheme: light)" scheme = "default" toggle.icon = "lucide/sun" toggle.name = "Switch to dark mode" # Palette toggle for dark mode [[project.theme.palette]] media = "(prefers-color-scheme: dark)" scheme = "slate" toggle.icon = "lucide/moon" toggle.name = "Switch to system preference" [project.markdown_extensions.pymdownx.highlight] anchor_linenums = true line_spans = "__span" pygments_lang_class = true [project.markdown_extensions.pymdownx.inlinehilite] [project.markdown_extensions.pymdownx.snippets] [project.markdown_extensions.pymdownx.superfences] foomuuri-0.33/misc/webdoc/overrides/000077500000000000000000000000001521001665700175075ustar00rootroot00000000000000foomuuri-0.33/misc/webdoc/overrides/main.html000066400000000000000000000003041521001665700213160ustar00rootroot00000000000000{% extends "base.html" %} {% block outdated %} You're not viewing the latest version. Click here to go to latest. {% endblock %} foomuuri-0.33/misc/webdoc/robots.txt000066400000000000000000000001531521001665700175550ustar00rootroot00000000000000User-agent: * Allow: / Disallow: /0 Disallow: /development Sitemap: https://foomuuri.foobar.fi/sitemap.xml foomuuri-0.33/misc/webdoc/versions.json000066400000000000000000000006761521001665700202610ustar00rootroot00000000000000[ { "version": "development", "title": "development", "aliases": [] }, { "version": "latest", "title": "v0.32", "aliases": ["latest"] }, { "version": "0.31", "title": "v0.31", "aliases": [] }, { "version": "0.30", "title": "v0.30", "aliases": [] }, { "version": "0.29", "title": "v0.29", "aliases": [] }, { "version": "0.28", "title": "v0.28", "aliases": [] }, { "version": "0.27", "title": "v0.27", "aliases": [] } ] foomuuri-0.33/prometheus/000077500000000000000000000000001521001665700155025ustar00rootroot00000000000000foomuuri-0.33/prometheus/Makefile000066400000000000000000000017501521001665700171450ustar00rootroot00000000000000# Foomuuri - Multizone bidirectional nftables firewall. .PHONY: all test clean distclean install sysupdate # Tests are run as part of top level makefile. Nothing to do here. all test: # Nothing to be cleaned clean distclean: # Install current source to DESTDIR EXPORTER_DIR ?= /usr/bin EXPORTER_NAME ?= prometheus-foomuuri-exporter SYSTEMD_SYSTEM_LOCATION ?= /usr/lib/systemd/system SETTINGS_LOCATION ?= /etc/default install: mkdir -p $(DESTDIR)$(EXPORTER_DIR)/ cp prometheus-foomuuri-exporter $(DESTDIR)$(EXPORTER_DIR)/$(EXPORTER_NAME) mkdir -p $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/ cat prometheus-foomuuri-exporter.service | sed s/prometheus-foomuuri-exporter/$(EXPORTER_NAME)/g > $(DESTDIR)$(SYSTEMD_SYSTEM_LOCATION)/$(EXPORTER_NAME).service mkdir -p $(DESTDIR)$(SETTINGS_LOCATION)/ cp prometheus-foomuuri-exporter.default $(DESTDIR)$(SETTINGS_LOCATION)/$(EXPORTER_NAME) # Install current source to local system's root sysupdate: make install DESTDIR=/ systemctl daemon-reload foomuuri-0.33/prometheus/prometheus-foomuuri-exporter000077500000000000000000000157611521001665700233460ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name # pylint: enable=invalid-name """Prometheus exporter for Foomuuri metrics.""" import argparse import json import pathlib import re import subprocess import time from collections import Counter # pylint: disable=import-error from prometheus_client import REGISTRY, start_http_server from prometheus_client.core import CounterMetricFamily, GaugeMetricFamily from prometheus_client.registry import Collector class MonitorCollector(Collector): """Collect Foomuuri Monitor metrics.""" # pylint: disable=too-few-public-methods def __init__(self, args): """Initialize class.""" super().__init__() self._stat_file = pathlib.Path(args.statistics_file) def collect(self): """Return Foomuuri Monitor metrics.""" try: stats = json.loads(self._stat_file.read_text(encoding='utf-8')) except (OSError, ValueError): return # Target/group state g = GaugeMetricFamily( 'foomuuri_monitor_up', 'Target connectivity status.', labels=['type', 'name'], ) for name, value in stats.items(): g.add_metric([value['type'], name], value['state']) yield g # Target packet loss g = GaugeMetricFamily( 'foomuuri_monitor_packet_loss_ratio', 'Average ping packet loss.', labels=['type', 'name'], ) for name, value in stats.items(): if not value['time']: continue loss = sum(1 for item in value['time'] if item is None) g.add_metric([value['type'], name], loss / len(value['time'])) yield g # Target packet ping latency g = GaugeMetricFamily( 'foomuuri_monitor_ping_seconds', 'Average network round trip time.', labels=['type', 'name'], ) for name, value in stats.items(): times = [item / 1000 for item in value['time'] if item is not None] if not times: continue g.add_metric([value['type'], name], sum(times) / len(times)) yield g class RulesetCollector(Collector): """Collect ruleset metrics.""" # pylint: disable=too-few-public-methods def __init__(self, args): """Initialize class.""" super().__init__() self._set_include = re.compile(args.set_include) self._set_exclude = re.compile(args.set_exclude) self._counter_include = re.compile(args.counter_include) self._counter_exclude = re.compile(args.counter_exclude) def collect(self): """Return ruleset metrics.""" try: nftdata = json.loads( subprocess.run( ['nft', '--json', 'list', 'table', 'inet', 'foomuuri'], stdout=subprocess.PIPE, check=False, encoding='utf-8', ).stdout ) except (OSError, ValueError): return # Set size - merge IPv4 and IPv6 to single value sets = Counter() for item in nftdata.get('nftables', {}): if 'set' in item: name = item['set']['name'][:-2] # Without "_4" if self._set_include.search( name ) and not self._set_exclude.search(name): sets.update({name: len(item['set'].get('elem', []))}) g = GaugeMetricFamily( 'foomuuri_set_elements', 'Number of elements in set.', labels=['name'], ) for name, value in sets.items(): g.add_metric([name], value) yield g # Named counters counters = {} for item in nftdata.get('nftables', {}): if 'counter' in item: name = item['counter']['name'] if self._counter_include.search( name ) and not self._counter_exclude.search(name): counters[name] = { 'bytes': item['counter']['bytes'], 'packets': item['counter']['packets'], } g = CounterMetricFamily( 'foomuuri_counter_bytes_total', 'Counter bytes value.', labels=['name'], ) for name, value in counters.items(): g.add_metric([name], value['bytes']) yield g g = CounterMetricFamily( 'foomuuri_counter_packets_total', 'Counter packets value.', labels=['name'], ) for name, value in counters.items(): g.add_metric([name], value['packets']) yield g def main(): """Parse arguments and run.""" # Command line parser parser = argparse.ArgumentParser( description='Export Foomuuri statistics to Prometheus.' ) parser.add_argument( '--address', default='::', help='listen address (default: ::)' ) parser.add_argument( '--port', type=int, default=11041, help='listen port number (default: 11041)', ) parser.add_argument( '--tls-certificate', metavar='FILENAME', help='TLS certificate file name', ) parser.add_argument( '--tls-key', metavar='FILENAME', help='TLS key file name' ) parser.add_argument( '--no-monitor-statistics', action='store_true', help='do not export Foomuuri Monitor statistics', ) parser.add_argument( '--no-ruleset-statistics', action='store_true', help='do not export ruleset statistics', ) parser.add_argument( '--statistics-file', metavar='FILENAME', default='/var/lib/foomuuri/monitor.statistics', help='Foomuuri Monitor statistics file name', ) parser.add_argument( '--set-include', metavar='REGEXP', default='.', # default all help='set names to be included to ruleset size statistics', ) parser.add_argument( '--set-exclude', metavar='REGEXP', default='$^', # default nothing help='set names to be excluded from ruleset size statistics', ) parser.add_argument( '--counter-include', metavar='REGEXP', default='.', help='counter names to be included to ruleset traffic statistics', ) parser.add_argument( '--counter-exclude', metavar='REGEXP', default='$^', help='counter names to be excluded from ruleset traffic statistics', ) args = parser.parse_args() # Register collectors if not args.no_monitor_statistics: REGISTRY.register(MonitorCollector(args)) if not args.no_ruleset_statistics: REGISTRY.register(RulesetCollector(args)) # Run exporter start_http_server( port=args.port, addr=args.address, certfile=args.tls_certificate, keyfile=args.tls_key, ) while True: time.sleep(1) if __name__ == '__main__': main() foomuuri-0.33/prometheus/prometheus-foomuuri-exporter.default000066400000000000000000000000111521001665700247440ustar00rootroot00000000000000OPTIONS= foomuuri-0.33/prometheus/prometheus-foomuuri-exporter.service000066400000000000000000000005041521001665700247670ustar00rootroot00000000000000[Unit] Description=Prometheus exporter for Foomuuri metrics After=foomuuri-monitor.service After=network-online.target [Service] Environment=OPTIONS= EnvironmentFile=/etc/default/prometheus-foomuuri-exporter User=root Restart=on-failure ExecStart=prometheus-foomuuri-exporter $OPTIONS [Install] WantedBy=multi-user.target foomuuri-0.33/src/000077500000000000000000000000001521001665700140765ustar00rootroot00000000000000foomuuri-0.33/src/__init__.py000066400000000000000000000000001521001665700161750ustar00rootroot00000000000000foomuuri-0.33/src/foomuuri000077500000000000000000006054571521001665700157120ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=too-many-lines """Foomuuri - Multizone bidirectional nftables firewall. Copyright 2023-2026, Kim B. Heino, Foobar Oy and contributors. License: GPL-2.0-or-later """ import collections import concurrent.futures import contextlib import dataclasses import datetime import io 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 typing import unicodedata # dbus is optional but highly recommended for dynamic zone/interface changes try: import dbus import dbus.mainloop.glib import dbus.service HAVE_DBUS = True except ImportError: HAVE_DBUS = 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: import systemd.daemon HAVE_SYSTEMD = True except ImportError: HAVE_SYSTEMD = False VERSION = '0.33' class Validators: """Validators collection.""" @staticmethod def str_zone_name(value: str): """Assert value is a valid zone name.""" return all( [value.isascii(), re.fullmatch(r'[a-zA-Z][a-zA-Z_0-9./]*', value)] ) @staticmethod def str_iplist_name(value: str): """Assert value is a valid iplist name, including @.""" return all( [ value.isascii(), re.fullmatch(r'@[a-zA-Z_][-a-zA-Z_0-9./]*', value), ] ) @staticmethod def str_interface_name(value: str): r"""Assert value is a valid network interface name. Linux accepts also \\ and " characters. Reject them here as they would break JSONs without quoting. """ if any(letter <= ' ' for letter in value): return False # Reject whitespace/control chars as Linux does if '/' in value: return False # Linux rejects this if '\\' in value or '"' in value: return False # Linux accepts these return 1 <= len(value) <= 15 # IFNAMSIZ=16 including NUL @staticmethod def str_words(value: str) -> bool: """Assert value is one or more words.""" return len(value.split()) >= 1 @staticmethod def has_elements(value: typing.Union[set, list]) -> bool: """Assert value is non-empty list or set.""" return len(value) >= 1 @staticmethod def int_positive_or_zero(value: int): """Assert value is not negative.""" return value >= 0 @staticmethod def int_positive(value: int): """Assert value is positive.""" return value > 0 @staticmethod def str_yes_no(value: str): """Assert value is either 'yes' or 'no'.""" return value in {'yes', 'no'} class Converters: # pylint: disable=too-few-public-methods """Type converters collection.""" @staticmethod def str_yes_no_to_bool(value: str) -> bool: """Convert str 'yes'/'no' to bool True/False, or raise ValueError.""" if value == 'yes': return True if value == 'no': return False raise ValueError @dataclasses.dataclass class TypedConfig: """Storage class with simple attribute type enforcement and validation.""" def __setattr__(self, name, value): """Set attribute value.""" if (field := self._get_field(name)) is None: raise AttributeError(f'{name}: unknown attribute') if not isinstance(value, field.type): raise TypeError( f'{name}: expected value type {field.type}, got {type(value)}' ) validator = field.metadata.get('validate') if validator and not validator(value): raise ValueError(f'{name}: value failed validation') super().__setattr__(name, value) def get(self, name, default=None): """Get attribute value.""" return getattr(self, name, default) def __getitem__(self, name): """Get attribute value via index operator.""" return getattr(self, name) def __setitem__(self, name, value): """Set attribute value.""" self.__setattr__(name, value) def __iter__(self): """Return attributes list terator.""" # pylint: disable=no-member return iter(list(self.__dataclass_fields__)) def _check_set_append_args(self, name: str, value: str) -> None: """Verify attribute name and value sanity.""" if name not in self: raise AttributeError(f'{name}: unknown attribute') if not isinstance(value, str): raise TypeError( f'{name}: expected value type {str}, got {type(value)}' ) def _convert_set_append_value(self, name: str, value: str): """Convert value to attribute's type.""" field = self._get_field(name) # "Built-in" value converters. Executed when # convert_str is not defined in field metadata. implicit_converters = { int: int, set: lambda v: set(v.split()), list: str.split, pathlib.PosixPath: pathlib.PosixPath, } if converter := ( field.metadata.get('convert_str') or implicit_converters.get(field.type) ): return converter(value) if isinstance(value, field.type): return value raise TypeError( f'{name}: value conversion to {field.type} not implemented' ) def set_from_str(self, name, value: str) -> None: """Set attribute value from str with type conversion.""" self._check_set_append_args(name, value) new_value = self._convert_set_append_value(name, value) setattr(self, name, new_value) def append_from_str(self, name, value: str) -> None: """Appends attribute value from str with type conversion.""" self._check_set_append_args(name, value) new_value = self._convert_set_append_value(name, value) field = self._get_field(name) # "Built-in" implicit appenders. implicit_appenders = { str: lambda value, append: f'{value} {append}', set: lambda value, append: value | append, list: lambda value, append: value + append, } if (appender := implicit_appenders.get(field.type)) and isinstance( new_value, field.type ): new_value = appender(self[name], new_value) else: raise TypeError( f'{name}: appending of {field.type} not implemented' ) setattr(self, name, new_value) def _get_field(self, name, default=None): """Get dataclass field by attribute name.""" # pylint: disable=no-member return self.__dataclass_fields__.get(name, default) @dataclasses.dataclass class Config(TypedConfig): """Parsed foomuuri{} section variables from config files.""" # pylint: disable=too-many-instance-attributes # Basic settings log_rate: str = '1/second burst 3' log_input: str = 'yes' log_output: str = 'yes' log_forward: str = 'yes' log_rpfilter: str = 'yes' log_invalid: str = 'no' log_smurfs: str = 'no' log_prefix: str = '$(szone)-$(dzone) $(statement)' log_level: str = 'level info flags skuid' localhost_zone: str = dataclasses.field( default='localhost', metadata={'validate': Validators.str_zone_name} ) dbus_zone: str = dataclasses.field( default='public', metadata={'validate': Validators.str_zone_name} ) rpfilter: set = dataclasses.field( default_factory=lambda: {'yes'}, metadata={'validate': Validators.has_elements}, ) flowtable: set = dataclasses.field(default_factory=set) counter: set = dataclasses.field( default_factory=lambda: {'no'}, metadata={'validate': Validators.has_elements}, ) set_size: int = dataclasses.field( default=65535, metadata={'validate': Validators.int_positive} ) recursion_limit: int = dataclasses.field( default=10000, metadata={'validate': Validators.int_positive} ) priority_offset: int = 5 dbus_firewalld: str = dataclasses.field( default='no', metadata={'validate': Validators.str_yes_no} ) nft_bin: list = dataclasses.field( default_factory=lambda: ['nft'], metadata={ 'convert_str': shlex.split, 'validate': Validators.has_elements, }, ) try_reload_timeout: int = dataclasses.field( default=15, metadata={'validate': Validators.int_positive} ) # Directories and files. Files are relative to state_dir. etc_dir: pathlib.PosixPath = dataclasses.field( default_factory=lambda: pathlib.PosixPath('/etc/foomuuri') ) share_dir: pathlib.PosixPath = dataclasses.field( default_factory=lambda: pathlib.PosixPath('/usr/share/foomuuri') ) state_dir: pathlib.PosixPath = dataclasses.field( default_factory=lambda: pathlib.PosixPath('/var/lib/foomuuri') ) run_dir: pathlib.PosixPath = dataclasses.field( default_factory=lambda: pathlib.PosixPath('/run/foomuuri') ) good_file: str = 'good.fw' next_file: str = 'next.fw' resolve_file: str = 'resolve.fw' # old iplist/resolve iplist_file: str = 'iplist.fw' # old iplist/resolve iplist_manual_file: str = 'iplist-manual.fw' # old iplist/resolve iplist_cache_file: str = 'iplist-cache.json' zone_file: str = 'zone' monitor_statistics_file: str = 'monitor.statistics' # Variable in foomuuri{}, or command line fork: int = 0 # --fork verbose: int = 0 # --verbose syslog: int = 0 # --syslog @dataclasses.dataclass class Internal(TypedConfig): """Internal variables and command line options.""" # pylint: disable=too-many-instance-attributes # Command line command: str = '' # Command to run: "start" etc parameters: list = dataclasses.field( default_factory=list ) # Parameters for command force: int = 0 # --force # Other internally used variables # Added without default value to prevent uninitialized use iplist_missing_ok: set = dataclasses.field(init=False) keep_going: int = dataclasses.field(init=False) post_start: list = dataclasses.field(init=False) post_stop: list = dataclasses.field(init=False) pre_start: list = dataclasses.field(init=False) pre_stop: list = dataclasses.field(init=False) priority_offset: str = dataclasses.field(init=False) catch_all_interface_zone: str = dataclasses.field(init=False) root_power: bool = dataclasses.field(init=False) # Running as "root"? # Url loader # Constants url_chunk_size: int = 1_048_576 url_get_timeout: dict = dataclasses.field( default_factory=lambda: {'connect': 3.05, 'read': 3.05} ) # Timeouts (seconds) url_get_retries: dict = dataclasses.field( default_factory=lambda: { 'connect': 3, 'redirect': 3, 'read': 3, 'raise_on_redirect': False, } ) # Retries count and redirect behaviour @dataclasses.dataclass class IPListOptions(TypedConfig): """iplist{} options container.""" # pylint: disable=too-many-instance-attributes dns_timeout: int = dataclasses.field( default=86400, metadata={ 'convert_str': lambda d: parse_duration(d, 0), 'validate': Validators.int_positive, }, ) # 24h dns_refresh: int = dataclasses.field( default=900, metadata={ 'convert_str': lambda d: parse_duration(d, 0), 'validate': Validators.int_positive, }, ) # 15m url_timeout: int = dataclasses.field( default=864000, metadata={ 'convert_str': lambda d: parse_duration(d, 0), 'validate': Validators.int_positive, }, ) # 10d url_refresh: int = dataclasses.field( default=86400, metadata={ 'convert_str': lambda d: parse_duration(d, 0), 'validate': Validators.int_positive, }, ) # 1d element_timeout: int = dataclasses.field( default=0, metadata={ 'convert_str': lambda d: parse_duration(d, -1), 'validate': Validators.int_positive_or_zero, }, ) # disabled url_max_size: int = dataclasses.field( default=33_554_432, metadata={'validate': Validators.int_positive} ) # 32MiB start: bool = dataclasses.field( default=True, metadata={'convert_str': Converters.str_yes_no_to_bool} ) # load on start merge: bool = dataclasses.field( default=True, metadata={'convert_str': Converters.str_yes_no_to_bool} ) # merge dynamic: bool = dataclasses.field( default=False, metadata={'convert_str': Converters.str_yes_no_to_bool} ) # dynamic flag, implies merge=no and no interval def parse_option(self, option: str): """Parse and set iplist option value from option. Option is either a a string in option=value format, or specific word. """ shortcuts = { '-start': ('start', False), '-merge': ('merge', False), } if shortcut := shortcuts.get(option): name, value = shortcut self[name] = value return True if len(assignment := option.split('=', maxsplit=1)) == 2: name, value = assignment # Only try setting if name is a valid identifier if name.isidentifier(): self.set_from_str(name, value) return True return False @dataclasses.dataclass(eq=False) class IPList: """Container of iplist options and sources.""" options: IPListOptions = dataclasses.field(default_factory=IPListOptions) sources: list = dataclasses.field(default_factory=list) @dataclasses.dataclass(eq=False) class IPLists: """Mapping of iplist name to IPList.""" _entries: dict[str, IPList] = dataclasses.field(default_factory=dict) def __getitem__(self, name): """Get iplist value via index operator.""" return self._entries[name] def __setitem__(self, name, value): """Set iplist value via index operator.""" self._entries[name] = value def __iter__(self): """Iterate over iplist names.""" return iter(self._entries) def items(self): """Return iterable of iplist mapping.""" return self._entries.items() def values(self): """Return iterable of iplist mapping values.""" return self._entries.values() def filter_names_by(self, option: str, required_value) -> set[str]: """Return set of iplist names with required option value.""" return { name for name, value in self._entries.items() if value.options[option] == required_value } class IPListSourceOptions: # pylint: disable=too-few-public-methods """Container of iplist runtime source options.""" timeout: int = sys.maxsize refresh: int = sys.maxsize max_size: int = sys.maxsize def update_if_less(self, name: str, new_value: int) -> None: """Update attribute value if new value is less than current.""" setattr(self, name, min(new_value, getattr(self, name))) CONFIG = Config() CONFIG_OVERRIDE = {} INTERNAL = Internal() OUT = [] # Generated nftables ruleset / commands LOGRATES = {} # Lograte names and limits HELPERS = [] # List of helpers: (helper-object, protocol, ports) def fail(error: str, fatal: bool = True) -> int: """Exit with error message.""" if CONFIG.verbose >= -1: # Double-quiet will suppress output print(f'Error: {error}', flush=True) if CONFIG.syslog: syslog.syslog(syslog.LOG_ERR, f'Error: {error}') if fatal: sys.exit(1) return 1 def warning(text: str) -> None: """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: str, level: int = 1) -> None: """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 int_to_human(number: int) -> str: """Format int in human readable way.""" return f'{number:,}' def print_table( data: list[tuple], align: str = '<', header: bool = False, sep: str = ' ', ) -> None: """Simplistic formatted table printer.""" if not data or (header and len(data) == 1): return if not set(align).issubset(set('^<>')): raise ValueError('Supported alignment: left <, right >, center ^') columns = len(data[0]) width = [ max(len(str(row[column])) for row in data) for column in range(columns) ] if len(align) == 1: align *= columns elif len(align) != columns: raise ValueError('Align value does not match column count') for r, row in enumerate(data): print( sep.join( [ f'{column:{align[c]}{width[c]}}' for c, column in enumerate(row) ] ).rstrip(), ) if header and not r: print( sep.join([f'{"-" * width[c]}' for c in range(columns)]), ) def out(line: str) -> None: """Add single line to ruleset.""" OUT.append(line) def notify(message: str) -> None: """Send systemd notify message.""" if HAVE_SYSTEMD: try: systemd.daemon.notify(message) except OSError as error: warning(f'Failed to send systemd notify "{message}": {error}') def state_file(key): """Helper function to return CONFIG.state_dir / CONFIG.key. All xxx_file variables are relative to state_dir. Values "../filename" and "/full/path/filename" works too. """ return CONFIG.state_dir / CONFIG[key] def join_args(args: list) -> str: """Helper function to return joined string of stringified args.""" if isinstance(args, list): return shlex.join(map(str, args)) raise TypeError(f'args type must be a {list}, not a {type(args)}') def syslog_open_or_close() -> None: """Initialize or stop syslogging.""" if CONFIG.syslog: syslog.openlog('foomuuri', syslog.LOG_PID, syslog.LOG_DAEMON) else: syslog.closelog() def run_program_rc( args: list, *, env=None, stdin=None, print_output=True, quiet=False ) -> int: """Run external program and return its errorcode. Print its output.""" if not args: return 0 verbose(join_args(args)) try: proc = subprocess.run( args, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', env=env, input=stdin, timeout=60, ) except (OSError, subprocess.TimeoutExpired) as error: fail(f'Failed to run command "{join_args(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: list[str], text_input: typing.Optional[str], check_error: bool = True, ) -> typing.Optional[str]: """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(args)}": {error}') if proc.returncode and check_error: warning( f'Failed to run command "{join_args(args)}": ' f'return code {proc.returncode}' ) return None return proc.stdout def run_program_shell(args: str, fileline: str) -> str: """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: str) -> typing.Optional[dict]: """Run "nft --json cmd", return its output as json, ignore errors.""" if not cmd: return {} args = CONFIG.nft_bin + ['--json', cmd] verbose(join_args(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: if CONFIG.verbose >= 1 or proc.stdout.startswith('JSON support'): warning( f'Failed to run command "{join_args(args)}": ' f'{proc.stdout.strip()}' ) 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(0o022) 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(require_etc_config: bool): """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-locals # 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 and require_etc_config: fail( f'No configuration files "{CONFIG.etc_dir}/*.conf" found\n' '\n' 'See https://foomuuri.foobar.fi/latest/example/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)) # ty: ignore # 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 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 INTERNAL.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] == '-' and len(word) > 1: # 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.startswith('_'): continue # Internally used section, skip if section in {'foomuuri', 'zone'}: # Don't expand macros in these sections, print warning instead. # This is for security reasons. Bad things will happen if # there is "xyz" in these sections and macro "xyz" is added # to default macros config. for line in orig_lines: for item in line[1]: if item in macros: warning( f'{line[0]}Macro "{item}" is not expanded in ' f'"{section}" section: {" ".join(line[1])}' ) continue 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] > 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" and 'foo' to foo in config files. This function is called after macro expansion so that "ssh" is changed to 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 len(item) >= 2 and ( (item[0] == item[-1] == '"') or (item[0] == item[-1] == "'") ): line[index] = item[1:-1] def parse_config_foomuuri(config): """Parse foomuuri{} from config to CONFIG{}.""" # pylint: disable=too-many-branches for fileline, line in config.pop('foomuuri', []): name = line[0] value = ' '.join(line[1:]) if name == 'try-reload_timeout': # backward compatibility name = 'try_reload_timeout' warning( f'{fileline}foomuuri{{}} option "{line[0]}" is obsolete, ' f'use "{name}" instead' ) if name not in CONFIG: fail(f'{fileline}Unknown foomuuri{{}} option: {" ".join(line)}') try: if value.startswith('+ '): # append CONFIG.append_from_str(name, value[2:]) else: CONFIG.set_from_str(name, value) # overwrite except (ValueError, TypeError): fail( f'{fileline}Invalid foomuuri{{}} option value:' f' {" ".join(line)}' ) # Overrides from CLI arguments for name, value in CONFIG_OVERRIDE.items(): CONFIG.set_from_str(name, str(value)) # Reinitialize syslogging, foomuuri{} may have a different syslog setting syslog_open_or_close() # Convert chain priority offset to nft if CONFIG.priority_offset == 0: INTERNAL.priority_offset = '' elif CONFIG.priority_offset > 0: INTERNAL.priority_offset = f' + {CONFIG.priority_offset}' else: INTERNAL.priority_offset = f' - {-CONFIG.priority_offset}' # 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 parse_config_zones(config): """Parse zone{} from config.""" zones = {} INTERNAL.catch_all_interface_zone = '' for fileline, line in config.pop('zone', []): if not Validators.str_zone_name(line[0]): fail(f'{fileline}Invalid zone name: {" ".join(line)}') if line[0] in zones: fail(f'{fileline}Zone is already defined: {line[0]}') interface = [item for item in line[1:] if item != '*'] zones[line[0]] = {'interface': interface} if '*' in line[1:]: if INTERNAL.catch_all_interface_zone: fail( f'{fileline}Catch all interface "*" is already assigned ' f'to zone "{INTERNAL.catch_all_interface_zone}": ' f'{" ".join(line)}' ) INTERNAL.catch_all_interface_zone = line[0] return zones def parse_config_zonemap(config, zones): """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'has no effect: {" ".join(line)}' ) for key in ('szone', 'dzone', 'new_szone', 'new_dzone'): value = rule[key] if value and value not in zones: fail( f'{fileline}Zonemap "{key}" has unknown zone "{value}": ' f'{" ".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' + INTERNAL.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 + INTERNAL.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 ] # ty: ignore[invalid-assignment] return chain_rules 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)}') INTERNAL[line[0]] = line[1:] def minimal_config(require_etc_config=True): """Read and parse minimal config.""" config = read_config(require_etc_config) expand_macros(config) remove_quotes(config) parse_config_foomuuri(config) return config def is_ipv4_address(value, allow_network=True): """Is value IPv4 address, network or interval.""" if value.count('-') == 1: # Interval "IP-IP" try: addr_from, addr_to = value.split('-') return ipaddress.IPv4Address(addr_from) <= ipaddress.IPv4Address( addr_to ) except ValueError: return False 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, ) and allow_network ) except ValueError: return False def is_ipv6_address(value, allow_network=True): """Is value IPv6 address, network or interval.""" # pylint: disable=too-many-return-statements if value.count('/-') == 1: # Suffix mask "IP/-mask" addr, maskstr = value.split('/-') try: mask = int(maskstr) return 0 <= mask <= 128 and ipaddress.IPv6Address(addr) except ValueError: return False if value.count('-') == 1: # Interval "IP-IP" try: addr_from, addr_to = value.split('-') return ipaddress.IPv6Address(addr_from) <= ipaddress.IPv6Address( addr_to ) except ValueError: return False # 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, ) and allow_network ) except ValueError: return False def is_ip_address(value, *, allow_negative=True): """Check if value is IPv4 or IPv6 address, network or interval. Return 4, 6, or 0 if not detected. """ if value.startswith('-') and allow_negative: value = value[1:] # Negative is handled in single_or_set() 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 == '' # noqa: RUF100,PLC1901 -- it can be None too and key not in {'nat_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 ('iplist_add', 'iplist_update', 'iplist_delete'): value = rule[key] if value is None: continue if value.count(' ') != 1 or not value.startswith( ('saddr @', 'daddr @') ): fail( f'{fileline}"{key}" must be addrtype and iplist name: {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['nat_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 'nat_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, 'iplist_add': None, 'iplist_update': None, 'iplist_delete': None, 'dscp': None, 'cgroup': None, 'ct_status': None, 'time': None, 'after_conntrack': True, 'warning': None, # Print warning to user # 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 == 'nat_to' and not ret['nat_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', 'nftrace', }: 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 = 'nat_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', 'iplist_add', 'iplist_update', 'iplist_delete', 'dscp', 'cgroup', 'ct_status', 'time', 'warning', }: 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] += ' ' # ty: ignore[unsupported-operator] 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) # Print warning, used mostly in old macros if ret['warning']: warning(f'{fileline}{ret["warning"]}') 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: {section}' ) rules[szone, dzone] = [parse_rule_line(line) for line in lines] # Allow multicast + broadcast if last rule is "accept". # These can be added to the end as they are processed in # correct order in output_zone(). if rules[szone, dzone] and rules[szone, dzone][-1]['line'] == [ 'accept' ]: rules[szone, dzone].extend( [ parse_rule_line(('', ['multicast'])), parse_rule_line(('', ['broadcast'])), ] ) 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, unless rate is specified per hour. 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') elif 'hour' in rule[rate]: out('flags dynamic,timeout') out('timeout 1h') out('gc-interval 5m') # Default is timeout/4 else: out('flags dynamic,timeout') out('timeout 1m') out('}') def suffix_mask(value, compare): """Convert suffix mask "IP/-mask" to nft.""" if not compare: compare = '== ' addr, mask = value.split('/-') mask = f'{pow(2, int(mask)) - 1:x}' # 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: typing.Union[str, list, set], fileline: str = '', quote: bool = False, ) -> str: """Convert data to nft string containing single item or set.""" # Convert to list if isinstance(data, str): values = data.split() elif isinstance(data, list): values = data else: values = list(data) # 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 = '!= ' values[index] = value.removeprefix('-') elif neg: fail(f'{fileline}Can\'t mix "+" and "-" items: {" ".join(values)}') # 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 one @iplist 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) # hh:mm:ss, hh:mm or 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', 'nftrace': 'meta nftrace set 1', }.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 counter 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 checklist = {'yes', f'{szone}-{dzone}', f'{szone}-any', f'any-{dzone}'} if not rule['nft'] and ( rule['counter'] is not None # "counter" in this single rule or not checklist.isdisjoint(CONFIG.counter) # zone level enable ): 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) out(f'{match}{statement}') # Continue to next rule if this wasn't type echo-request (ping) or all if ( rule['dport'] and ping not in rule['dport'].split() and 'echo-request' not in rule['dport'].split() ): continue # This rule was for ping, usually accepting non-flood pings. Add # explicit rule to drop overflow and all other pings. # Skip adding drop rule if this rule is "match all" as then drop is # never reached. This simple check doesn't catch all types of # "match all". has_ping_rule = True if match in { 'ip protocol icmp ', 'ip6 nexthdr icmpv6 ', f'{icmp} type {ping} ', f'{icmp} type echo-request ', } and 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: {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. Interface "lo" is special: - "iifname lo" or "iif lo" doesn't work in most chains. This is also visible in kernel logging: "IN=". Use "iif 0" instead. It works on all chains. - "oifname lo" never works. "oif 0" works on all but prerouting (dnat) as all packets have "OUT=" there. Use "fib daddr type local" which works on all chains. """ ret = '' for key in ('iifname', 'oifname'): if not rule[key]: continue if key == 'iifname' and rule[key] == 'lo': ret += 'iif 0 ' elif key == 'oifname' and rule[key] == 'lo': ret += 'fib daddr type local ' else: 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 '' # ICMP is for IPv4, ICMPv6 is for IPv6. # IPv6 uses Multicast Listener Discovery ICMP instead of IGMP. if (ipv == 4 and protocol == 'icmpv6') or ( ipv == 6 and protocol in {'icmp', 'igmp'} ): return None 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_nat_to(rule, ipv): """Parse snat/dnat "to" rule to nft rule.""" if not rule['nat_to']: return '' # "to" can be IPv4, IPv6 or both, find correct one target = parse_iplist_to_single_ip( rule, rule['statement'], rule['nat_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( # noqa: PLR0914 -- Too many local variables 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 # pylint: disable=too-many-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'fib daddr type {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)} ct mark set meta mark ' ) if rule['priority_set']: meta_set += f'meta priority set "{rule["priority_set"]}" ' iplist_mangle = '' for operation in ('add', 'update', 'delete'): value = rule[f'iplist_{operation}'] if value and ' ' in value: addr, iplist_name = value.split(' ', 1) iplist_mangle += ( f'{operation} {iplist_name}_{ipv} ' f'{{ {"ip" if ipv == 4 else "ip6"} {addr} }} ' ) 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, skip_icmp=skip_icmp) 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}' f'{iplist_mangle}' ) 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 not match and cast is None and rule['cast'] != 'unicast': continue # Don't convert "multicast accept" to nft "accept" if rule['statement'] in {'snat', 'dnat', 'snat_prefix', 'dnat_prefix'}: fail( f'{rule["fileline"]}Statement "{rule["statement"]}" can\'t ' f'be used in "{szone}-{dzone}" section' ) 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: {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, skip_icmp=False) 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. # # "fib daddr type" works only for incoming packets so skip these if # szone=localhost (outgoing). # # "fib daddr type" 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('fib daddr type { broadcast, multicast } drop') else: out('fib daddr type 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 "fib daddr type" # 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_nat_to(rule, ipv) if to_rule is None: # ipv == 6 and "nat to IPv4" mismatch continue match = rule_matchers( rule, ipv, skip_options=False, skip_icmp=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(INTERNAL.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 == 'no': return if lograte == 'yes': # "yes" means use standard log rate lograte = CONFIG.log_rate flags = '' if chain == 'invalid' and 'group ' not in CONFIG.log_level: flags = ' flags ip options' # flags and group are mutually exclusive 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.""" # pylint: disable=too-many-branches # pylint: disable=too-many-statements # 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{INTERNAL.priority_offset}') out('iifname vmap @input_zones') if INTERNAL.catch_all_interface_zone: out( f'jump {INTERNAL.catch_all_interface_zone}-{CONFIG.localhost_zone}' ) output_static_chain_logging('input', 'drop') out('drop') out('}') out('chain output {') out(f'type filter hook output priority filter{INTERNAL.priority_offset}') out('oifname vmap @output_zones') if INTERNAL.catch_all_interface_zone: out( f'jump {CONFIG.localhost_zone}-{INTERNAL.catch_all_interface_zone}' ) # 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('}') flowtable = {item for item in CONFIG.flowtable if '=' not in item} if len(flowtable) > 1: out('flowtable fastpath {') out(f'hook ingress priority filter{INTERNAL.priority_offset}') out(f'devices = {single_or_set(flowtable, quote=True)}') if 'hw_offload=yes' in CONFIG.flowtable: out('flags offload') if 'counter=yes' in CONFIG.flowtable: out('counter') out('}') out('chain forward {') out(f'type filter hook forward priority filter{INTERNAL.priority_offset}') if len(flowtable) > 1: out('flow add @fastpath') out('iifname . oifname vmap @forward_zones') if INTERNAL.catch_all_interface_zone: out( f'jump {INTERNAL.catch_all_interface_zone}-' f'{INTERNAL.catch_all_interface_zone}' ) output_static_chain_logging('forward', 'drop') out('drop') out('}') # Rules starting with these matchers can be merged. # List them in preferred order. MERGES = [ 'fib daddr type multicast udp dport', 'fib daddr type 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(): # Check if accept line is: # "tcp dport number accept" # "tcp dport { number, number } accept" # "tcp dport number-number accept" 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 accept ports in preferred order 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. """ # Remove duplicate accept rules already_seen = set() linenum = 0 while linenum < len(OUT): line = OUT[linenum] if line in already_seen: # Already in seen, delete from ruleset del OUT[linenum] continue if line.endswith(' accept'): # Add accept to seen already_seen.add(line) else: # Something else, reset seen already_seen = set() linenum += 1 # Merge multiple "tcp daddr x accept" rules to single # rule "tcp daddr { x, y, z } accept". 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 ') ): # Line is "tcp dport ... accept" accepts.append(line) del OUT[linenum] else: # Line is something else, output merged accepts 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(iplists: IPLists) -> None: """Output empty iplist{} sets.""" for name in sorted(iplists): for ipv in (4, 6): out(f'set {name[1:]}_{ipv} {{') out(f'type ipv{ipv}_addr') if iplists[name].options.dynamic: out(f'size {CONFIG.set_size}') out('flags dynamic,timeout') else: out('flags interval,timeout') if iplists[name].options.merge: out('auto-merge') if iplists[name].options.element_timeout: out(f'timeout {iplists[name].options.element_timeout}s') if iplists[name].options.element_timeout > 20 * 60: out('gc-interval 5m') 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'{INTERNAL.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 'no' in CONFIG.rpfilter: return out('chain rpfilter {') out( f'type filter hook prerouting priority filter' f'{INTERNAL.priority_offset}' ) interfaces = '' if 'yes' not in CONFIG.rpfilter: # Specific interfaces? interfaces = f'iifname {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. Use temporary file to make this atomic.""" if str(filename) == '/dev/null': return if isinstance(lines, dict): text = json.dumps(lines, indent=2, sort_keys=True) + '\n' else: text = '\n'.join(lines) + '\n' tmpfile = filename.with_suffix(f'{filename.suffix}.{os.getpid()}') try: tmpfile.unlink(missing_ok=True) tmpfile.write_text(text, 'utf-8') tmpfile.chmod(0o600) tmpfile.rename(filename) except PermissionError as error: fail(f"File {filename}: Can't write: {error}") except FileNotFoundError as error: warning(f"File {filename}: Can't write: {error}") 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 if filename: save_file(filename, lines) return 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 with contextlib.suppress(OSError): os.kill(pid, signal.SIGHUP) def apply_final(): """Use final ruleset.""" # Check config good_file = state_file('good_file') next_file = state_file('next_file') if INTERNAL.command == 'check': if INTERNAL.root_power: # "nft check" requires root ret = run_program_rc( CONFIG.nft_bin + ['--check', '--file', 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(INTERNAL.get(f'pre_{INTERNAL.command}')) # Load "next" ret = run_program_rc( CONFIG.nft_bin + ['--file', 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 INTERNAL.command == 'start': good_file.unlink(missing_ok=True) next_file.rename(good_file) signal_childs() run_program_rc(INTERNAL.get(f'post_{INTERNAL.command}')) verbose(f'{INTERNAL.command} success', 0) return 0 def print_assembled_config(config, zones, zonemap, rules, chain_rules) -> None: """Print assembled configuration in foomuuri.conf format.""" def print_section(name: str, lines: list, indent: int = 2) -> None: """Section print helper.""" if lines: print(f'{name} {{') print(*[f'{" " * indent}{line}' for line in lines], sep='\n') print('}') print('Assembled configuration << EOF') print_section( 'foomuuri', [ f'{option} {value}' for option, value in dataclasses.asdict(CONFIG).items() ], ) print_section( 'zone', [ f'{zone} {" ".join(statement["interface"])}' for zone, statement in zones.items() ], ) print_section( 'zonemap', [f'{" ".join(statement["line"])}' for statement in zonemap] ) for chains, statements in chain_rules.items(): print_section( ' '.join(chains), [f'{" ".join(statement["line"])}' for statement in statements], ) print_section( 'iplist', [f'{" ".join(line[1])}' for line in config.get('iplist', [])] ) for zone2zone, statements in rules.items(): print_section( '-'.join(zone2zone), [f'{" ".join(statement["line"])}' for statement in statements], ) print_section( 'hook', [ f'{hook} {join_args(INTERNAL.get(hook))}' for hook in ( filter(lambda k: k.startswith(('pre_', 'post_')), INTERNAL) ) if INTERNAL.get(hook) ], ) print('EOF') def command_start(): """Process "start" or "check" command.""" # Read full config config = minimal_config() zones = parse_config_zones(config) zonemap = parse_config_zonemap(config, zones) iplists = parse_config_iplist(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(iplists) output_named_counters(rules, chain_rules) output_helpers() output_rpfilter() output_footer() # Add iplist elements from cache to output. Include only iplists # that are marked with "start=yes". if iplists_add_on_start := iplists.filter_names_by('start', True): command_iplist_refresh( iplists=iplists, currently_active=set(iplists), override_update_only=iplists_add_on_start, ) # Print assembled configuration if CONFIG.verbose >= 2: print_assembled_config(config, zones, zonemap, rules, chain_rules) # Save known zones to file save_file(state_file('zone_file'), zones.keys()) # Save and apply generated ruleset save_final(state_file('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(state_file('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() if INTERNAL.catch_all_interface_zone: zone_interface['*'] = INTERNAL.catch_all_interface_zone nft_tables = nft_json('list tables') or {} tables = { t['table'].get('name') for t in nft_tables.get('nftables', {}) if t.get('table') } # Running if 'foomuuri' not in tables: fail('Foomuuri is not running') return 1 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') # Check for multiple firewalls if 'firewalld' in tables: print() warning('Firewalld is running') # Zones if not zone_interface: print() print('Warning: There are no interfaces assigned to any zones') print() print('zone {') table = [] for zone in zones: interfaces = [ interface for interface, int_zones in zone_interface.items() if zone == int_zones ] table.append(('', zone, f'{" ".join(interfaces)}')) print_table(data=table, header=False) print('}') return 0 if HAVE_DBUS: class FoomuuriDbusException(dbus.DBusException): """Exception class for D-Bus interface.""" _dbus_error_name = 'fi.foobar.Foomuuri1.exception' class DbusCommon(dbus.service.Object): """D-Bus server and common functions.""" polkit = None def __init__(self, conn, object_path, zones): """Initialize dbus.service.Object.""" dbus.service.Object.__init__( self, conn=conn, object_path=object_path ) # 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.""" lines = '\n'.join(save_final(None)) return run_program_rc( CONFIG.nft_bin + ['--file', '-'], stdin=lines ) def verify_polkit_privilege(self, sender, privilege): """Verify authorization from PolicyKit.""" # Initialize polkit connections if not self.polkit: try: self.polkit = dbus.Interface( dbus.SystemBus().get_object( 'org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority', ), 'org.freedesktop.PolicyKit1.Authority', ) except dbus.DBusException: # No polkit, fallback to check that UID == 0 iface = dbus.Interface( dbus.SystemBus().get_object( 'org.freedesktop.DBus', '/org/freedesktop/DBus' ), 'org.freedesktop.DBus', ) with contextlib.suppress(ValueError): if int(iface.GetConnectionUnixUser(sender)) == 0: return raise FoomuuriDbusException( 'Authorization failed' ) from None # Verify authorization from polkit try: result = self.polkit.CheckAuthorization( ('system-bus-name', {'name': sender}), privilege, {}, 1, '', timeout=60, ) is_authorized = result[0] if is_authorized: return except dbus.DBusException as error: warning(str(error)) self.polkit = None # Reconnect on next call raise FoomuuriDbusException('Authorization failed') from None @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 ' f'{zone}-{CONFIG.localhost_zone} }}' ) out( f'delete element inet foomuuri output_zones ' f'{{ "{interface}" : jump ' f'{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 ' f'{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 other zones. """ # Add to input and output out( f'add element inet foomuuri input_zones ' f'{{ "{interface}" : jump ' f'{zone}-{CONFIG.localhost_zone} }}' ) out( f'add element inet foomuuri output_zones ' f'{{ "{interface}" : jump ' f'{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" 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) if self.apply_out(): raise FoomuuriDbusException( 'Failed to modify interface. See ' 'journal for more information.' ) 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". """ if not Validators.str_interface_name(interface): raise FoomuuriDbusException( f'Invalid interface name: {interface}' ) 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. """ if not Validators.str_interface_name(interface): raise FoomuuriDbusException( f'Invalid interface name: {interface}' ) 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.""" if not Validators.str_interface_name(interface): raise FoomuuriDbusException( f'Invalid interface name: {interface}' ) 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(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', sender_keyword='sender', ) def getZones(self, sender=None): """Get list of available zones.""" self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.info') return self.method_get_zones() @dbus.service.method( 'fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def removeInterface(self, zone, interface, sender=None): """Remove interface from zone, or from all if zone is empty. Return: previous zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_remove_interface(zone, interface) @dbus.service.method( 'fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def addInterface(self, zone, interface, sender=None): """Add interface to zone. Return: new zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_add_interface(zone, interface) @dbus.service.method( 'fi.foobar.Foomuuri1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def changeZoneOfInterface(self, zone, interface, sender=None): """Change interface to zone. Return: previous zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_change_zone_of_interface(zone, interface) class DbusFirewalld(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', sender_keyword='sender', ) def getZones(self, sender=None): """Get list of available zones.""" self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.info') return self.method_get_zones() @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def removeInterface(self, zone, interface, sender=None): """Remove interface from zone, or from all if zone is empty. Return: previous zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_remove_interface(zone, interface) @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def addInterface(self, zone, interface, sender=None): """Add interface to zone. Return: new zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_add_interface(zone, interface) @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def changeZoneOfInterface(self, zone, interface, sender=None): """Change interface to zone. Return: previous zone """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') return self.method_change_zone_of_interface(zone, interface) @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='s', out_signature='s', sender_keyword='sender', ) def getZoneOfSource(self, source, sender=None): """Get current zone of source. These calls are used by netavark (run by Podman) to set container's IP address to "trusted" zone. There is no such thing in Foomuuri. """ self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.info') if not is_ip_address(source): raise FoomuuriDbusException('Invalid source address') return '' # It's not in any zone @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def changeZoneOfSource(self, zone, source, sender=None): """Change source to zone. See above getZoneOfSource().""" self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') raise FoomuuriDbusException( f'Not implemented: change {source} to "{zone}"' ) @dbus.service.method( 'org.fedoraproject.FirewallD1.zone', in_signature='ss', out_signature='s', sender_keyword='sender', ) def removeSource(self, zone, source, sender=None): """Remove source from zone. See above getZoneOfSource().""" self.verify_polkit_privilege(sender, 'fi.foobar.Foomuuri1.config') raise FoomuuriDbusException( f'Not implemented: remove {source} from "{zone}"' ) def command_dbus(): """Start D-Bus daemon.""" # Import gi here as it is heavy module to load. try: # ruff: noqa: PLC0415 (`import` should be at the top-level) # pylint: disable=import-outside-toplevel import gi.repository.GLib except ImportError: fail('No python-gobject / python-gi installed') INTERNAL.keep_going = 1 while INTERNAL.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', 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 for pylint except dbus.exceptions.DBusException: fail( "Can't bind to system D-Bus: " 'org.federaproject.FirewallD1' ) firewalld_object = DbusFirewalld( bus, '/org/fedoraproject/FirewallD1', zones ) # Define reload/stop signal handler mainloop = gi.repository.GLib.MainLoop() def signal_handler(sig, _dummy_frame, mainloop=mainloop): if sig == signal.SIGINT: INTERNAL.keep_going = 0 notify('STOPPING=1') else: 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) 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(INTERNAL.parameters) != 4 or INTERNAL.parameters[0] != 'interface' or INTERNAL.parameters[2] != 'zone' ): command_help() interface = INTERNAL.parameters[1] zone = INTERNAL.parameters[3] try: bus = dbus.SystemBus() obj = bus.get_object('fi.foobar.Foomuuri1', '/fi/foobar/Foomuuri1') except dbus.exceptions.DBusException as error: fail(f"Can't connect to Foomuuri D-Bus: {error}") try: if zone in {'', '-'}: obj.removeInterface('', interface) else: obj.changeZoneOfInterface(zone, interface) except dbus.exceptions.DBusException as error: fail(f"Can't modify interface: {error}") else: # HAVE_DBUS is false def command_dbus(): """Start D-Bus daemon.""" fail('No python-dbus installed') def command_set(): """Modify runtime config by calling D-Bus methods.""" fail('No python-dbus installed') def remove_filters(text): """Remove tailing flags and filters from hostname or URL.""" return text.split('|')[0] def iplist_verify_filters(name, values, fileline): """Verify that iplist entry's filter is known.""" for value in values: for spec in value.split('|')[1:]: if not spec.startswith( ('missing-ok', 'shell:', 'json:', 'html:', 'xml:') ): warning( f'{fileline}Unknown filter "|{spec}" in iplist ' f'"{name} {value}"' ) def resolve_one_hostname(hostname): """Resolve hostname and return its IP addresses.""" ret = set() with contextlib.suppress(socket.gaierror): for item in socket.getaddrinfo(remove_filters(hostname), None): if item[4] and item[4][0]: ret.add(item[4][0]) return ret def resolve_all_hostnames(resolve, cache): """Resolve hostnames to IP addresses and save them to cache.""" # Collect list of hostnames to resolve now = int(time.time()) todo = set() for hostname, options in resolve.items(): cachevalue = cache.get(hostname, {}).get('ip', {}) cacherefresh = cache.get(hostname, {}).get('refresh') if cachevalue and cacherefresh: if now >= cacherefresh + options.timeout: # Expired entry cache[hostname]['ip'] = {} cache[hostname]['dirty'] = True elif now < cacherefresh + options.refresh and INTERNAL.force < 0: verbose(f'Using cached value for "{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 + resolve[hostname].timeout for addr in addrlist: cache[hostname]['ip'][addr] = expire verbose( f'Hostname "{remove_filters(hostname)}" resolved ' f'to: {", ".join(addrlist)}' ) elif '|missing-ok' in hostname: INTERNAL.iplist_missing_ok.add(hostname) else: 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: str, max_size: int) -> typing.Optional[str]: """Download URL and return it as text, or None if failure.""" def get_response_content(response, max_size: int) -> io.BytesIO: """Helper function to get response content.""" if response.status != 200: raise ValueError(f'status code: {response.status}') content_length = int(response.headers.get('content-length', 0)) if content_length > max_size: raise ValueError( f'content length {content_length} exceeds {max_size}' ) content = io.BytesIO() for chunk in response.stream(INTERNAL.url_chunk_size): content.write(chunk) if content.tell() > max_size: raise ValueError(f'content length exceeds {max_size}') return content try: # urllib3 is optional, needed to download iplist with HTTP GET. # Import it here as it is heavy module to load. # ruff: noqa: PLC0415 (`import` should be at the top-level of a file) # pylint: disable=import-outside-toplevel import urllib3 with urllib3.PoolManager().request( method='GET', url=remove_filters(url), decode_content=False, preload_content=False, retries=urllib3.Retry(**INTERNAL.url_get_retries), timeout=urllib3.Timeout(**INTERNAL.url_get_timeout), ) as response: return ( get_response_content(response, max_size) .getvalue() .decode('utf-8', errors='ignore') ) except ImportError: warning( f'Can\'t download iplist URL "{remove_filters(url)}": ' f'No python-urllib3 installed' ) except (ValueError, urllib3.exceptions.HTTPError) as error: if '|missing-ok' in url: INTERNAL.iplist_missing_ok.add(url) else: warning( f'Can\'t download iplist URL "{remove_filters(url)}": {error}' ) finally: with contextlib.suppress(NameError): response.drain_conn() response.release_conn() 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' in wildcard: INTERNAL.iplist_missing_ok.add(wildcard) else: 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_duration(timespec, fallback): """Parse timespec 1w2d3h4m5s to seconds.""" units = ( ('w', 'weeks'), ('d', 'days'), ('h', 'hours'), ('m', 'minutes'), ('s', 'seconds'), ) delta = collections.defaultdict(int) timespec = timespec.replace(' ', '').replace('_', '') for unit in units: pattern = rf'^(\d+){unit[0]}' if match := re.match(pattern, timespec): delta[unit[1]] = int(match[1]) timespec = re.sub(pattern, '', timespec, count=1) if delta and not timespec: with contextlib.suppress(OverflowError): return int(datetime.timedelta(**delta).total_seconds()) return fallback def seconds_to_duration(seconds: int) -> str: """Convert seconds to duration string.""" days, seconds = divmod(seconds, 86400) hours, seconds = divmod(seconds, 3600) minutes, seconds = divmod(seconds, 60) if days: return f'{days}d{hours:02d}h{minutes:02d}m{seconds:02d}s' if hours: return f'{hours}h{minutes:02d}m{seconds:02d}s' if minutes: return f'{minutes}m{seconds:02d}s' return f'{seconds}s' 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"]}/{ipaddr["prefix"]["len"]}'), expire, ) def command_iplist_status(): """List iplist(s) name and entries count.""" config = minimal_config() known = parse_config_iplist(config) table = [('Iplist', 'Entries')] error = 0 for setname in INTERNAL.parameters[1:] or sorted(known): setname = setname.removeprefix('@') 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') if data4 is None and data6 is None: warning(f'Unknown iplist: @{setname}') error = 1 continue count = len( list( itertools.chain( iterate_set_elements(data4), iterate_set_elements(data6) ) ) ) table.append((f'@{setname}', int_to_human(count))) print_table(data=table, align='<>', header=True) return error def command_iplist_list(): """List iplist entries.""" config = minimal_config() known = parse_config_iplist(config) table = [('Iplist', 'IPAddress', 'Timeout')] error = 0 for setname in INTERNAL.parameters[1:] or sorted(known): setname = setname.removeprefix('@') 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') if data4 is None and data6 is None: warning(f'Unknown iplist: @{setname}') error = 1 continue elems = sorted(iterate_set_elements(data4)) + sorted( iterate_set_elements(data6) ) if not elems: table.append((f'@{setname}', '', '')) else: table.extend( (f'@{setname}', elem, seconds_to_duration(timeout)) for elem, timeout in elems ) print_table(data=table, align='<<>', header=True) return error def command_iplist_add(): """Add entries to iplist.""" config = minimal_config() iplists = parse_config_iplist(config) cache = read_iplist_cache(iplists) setname = INTERNAL.parameters[1] if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplists: fail(f'Unknown iplist name: {setname}') timeout = max( iplists[setname].options.url_timeout, iplists[setname].options.dns_timeout, ) now = int(time.time()) expire = now + timeout # Add entries to cache and apply it iplist_cache_init(cache, f'manual.{setname}') for address in INTERNAL.parameters[2:]: ipv = is_ip_address(address, allow_negative=False) if ipv: cache[f'manual.{setname}']['ip'][address] = expire cache[f'manual.{setname}']['dirty'] = True else: timeout = parse_duration(address, 0) if not timeout: fail(f'Invalid IP address {address}') expire = now + timeout if timeout > 630720000: # More than 20 years is "forever" expire = 4000000000 # year 2096 return iplist_output_values(iplists, cache, {setname}, {setname}) def command_iplist_del(): """Delete entries from iplist.""" config = minimal_config() iplists = parse_config_iplist(config) cache = read_iplist_cache(iplists) setname = INTERNAL.parameters[1] if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplists: fail(f'Unknown iplist name: {setname}') # Delete entries from cache and apply it iplist_cache_init(cache, f'manual.{setname}') for address in INTERNAL.parameters[2:]: cache[f'manual.{setname}']['ip'].pop(address, None) cache[f'manual.{setname}']['dirty'] = True return iplist_output_values(iplists, cache, {setname}, {setname}) def command_iplist_flush(): """Delete all IP addresses from all or specific iplist(s).""" config = minimal_config() iplists = parse_config_iplist(config) cache = read_iplist_cache(iplists) update_only = set() target = set(INTERNAL.parameters[1:] or iplists) for setname in target: if not setname.startswith('@'): setname = f'@{setname}' if setname not in iplists: 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(iplists, cache, update_only, 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:'): if HAVE_LXML: text = lxml_parser( url, lxml.etree.HTMLParser(), spec[5:], text ) else: warning('No python-lxml installed') elif spec.startswith('xml:'): if HAVE_LXML: text = lxml_parser(url, lxml.etree.XMLParser(), spec[4:], text) else: warning('No python-lxml installed') return text def iplist_cleanup_file(url: str, text: str) -> list[str]: """Parse downloaded iplist file to list of IP addresses.""" # Apply filters text = iplist_url_filters(url, text) if not text: return [] # Collect IP addresses addresses = [] for line in text.splitlines(): for value in line.split(): if value.startswith(('#', ';')): # Remove comments break if is_ip_address(value, allow_negative=False): addresses.append(value) else: warning(f'Unknown entry in iplist "{url}": {value}') return addresses def get_url_or_file( url: str, options: IPListSourceOptions, cache: dict ) -> None: """Get URL or file content with cache check.""" now = int(time.time()) cachevalue = cache.get(url, {}).get('ip', {}) cacherefresh = cache.get(url, {}).get('refresh') if cachevalue and cacherefresh: if now >= cacherefresh + options.timeout: # Expired entry cache[url]['ip'] = {} cache[url]['dirty'] = True elif now < cacherefresh + options.refresh and INTERNAL.force < 0: verbose(f'Using cached value for "{url}"') return value = ( get_url(url, options.max_size) if url.startswith(('https:', 'http:')) else get_file(url) ) if value is not None: # Replace list, don't append. These lists are always complete # address lists. expire = now + options.timeout cache[url] = { 'ip': dict.fromkeys(iplist_cleanup_file(url, value), expire), 'dirty': True, 'refresh': now, } verbose( f'Iplist content for "{url}" refreshed, ' f'{len(cache[url]["ip"])} entries' ) def str_to_time(timestamp): """Convert time string to unix timestamp.""" obj = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S') obj = obj.replace(tzinfo=datetime.timezone.utc) return int(obj.timestamp()) 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 = state_file(file_key).read_text('utf-8') except OSError: continue # Silently ignore all errors for line in manual.splitlines(): line = line.removeprefix('# ') 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: # iplist_manual_file has expire stamp. timeout = str_to_time(items[11]) else: timeout = int(time.time()) + parse_duration(items[8], 0) iplist_cache_init(cache, setname) cache[setname]['ip'][items[6]] = timeout def read_iplist_cache(iplist): """Read current iplist values from cache.""" cache = {} filename = state_file('iplist_cache_file') try: cache = json.loads(filename.read_text('utf-8')) except json.decoder.JSONDecodeError as error: warning(f'File {filename}: Broken JSON format: {error}') except PermissionError as error: fail(f"File {filename}: Can't read: {error}") except FileNotFoundError: read_old_iplist_format(cache) now = int(time.time()) 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: if isinstance(cache[key]['refresh'], str): cache[key]['refresh'] = str_to_time(cache[key]['refresh']) except KeyError: cache[key]['refresh'] = now # Check IP address timeouts dellist = set() for addr, timeout in cache[key]['ip'].items(): if isinstance(timeout, str): timeout = str_to_time(timeout) cache[key]['ip'][addr] = timeout if now >= timeout: dellist.add(addr) for addr in dellist: verbose(f'Deleting expired iplist "{key}" entry "{addr}"') del cache[key]['ip'][addr] # 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'] save_file(state_file('iplist_cache_file'), cache) def parse_config_iplist(config) -> IPLists: """Parse iplist{} sections from config.""" # pylint: disable=too-many-branches def parse_option( options: IPListOptions, option: str, fileline: str ) -> bool: """Option parser helper.""" try: return options.parse_option(option) except AttributeError: fail(f'{fileline}Invalid iplist{{}} option name: {option}') except ValueError: fail(f'{fileline}Invalid iplist{{}} option value: {option}') return False default_options = IPListOptions() iplists = IPLists() if config.get('resolve'): warning('Section "resolve" is obsolete, use "iplist" instead') for fileline, line in config.get('resolve', []) + config.get('iplist', []): # Parse old default option syntax if line[0] in {'timeout', 'refresh'}: option = line[0] value = ' '.join(line[1:]) warning( f'{fileline}Iplist "{option}" is obsolete, use ' f'"dns_{option}" or "url_{option}" instead' ) parse_option(default_options, f'dns_{option}={value}', fileline) parse_option(default_options, f'url_{option}={value}', fileline) continue # Parse one or more 'equal-sign'-style default options in single line for index, default_option in enumerate(line): if not parse_option(default_options, default_option, fileline): if index == 0: break fail(f'{fileline}Unsupported syntax: {" ".join(line)}') else: continue # Parse 'space-separated'-style default option option = f'{line[0]}={" ".join(line[1:])}' if parse_option(default_options, option, fileline): continue # Is it an iplist? if not Validators.str_iplist_name(name := line[0]): fail(f'{fileline}Invalid iplist name: {" ".join(line)}') if name not in iplists: # Create new iplist with default_options collected so far iplists[name] = IPList( options=IPListOptions(*dataclasses.astuple(default_options)) ) if len(line) >= 3 and line[1] == '+': # Append iplist sources options = line[2:] else: if iplists[name].sources: # Overwrite iplist sources iplists[name].sources.clear() warning( f'{fileline}Overwriting iplist "{name}" with value ' f'"{" ".join(line[1:])}"' ) options = line[1:] # Parse the rest of iplist definition for option in options: # Ignore insignificant tokens if option in {'-', '+'}: continue # Inline option? if parse_option(iplists[name].options, option, fileline): continue # Add as a source iplists[name].sources.append(option) iplist_verify_filters(name, iplists[name].sources, fileline) if CONFIG.verbose >= 2: for name, iplist in iplists.items(): verbose(f'{name:9s} {" ".join(iplist.sources)}') INTERNAL.iplist_missing_ok = set() return iplists def iplist_collect_resolve_list(iplists: IPLists) -> tuple: """Collect URLs and hostnames to be resolved.""" if INTERNAL.command != 'iplist': # Don't resolve anything if called as "foomuuri start". # Use cached entries only. return {}, {} urls = collections.defaultdict(IPListSourceOptions) hostnames = collections.defaultdict(IPListSourceOptions) for iplist in iplists.values(): for source in iplist.sources: if source.startswith(('https://', 'http://', '.', '/')): for option in ('timeout', 'refresh', 'max_size'): urls[source].update_if_less( option, iplist.options[f'url_{option}'] ) elif not is_ip_address(source, allow_negative=False): for option in ('timeout', 'refresh'): hostnames[source].update_if_less( option, iplist.options[f'dns_{option}'] ) 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': int(time.time()), } 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']: if ':' in address: updates[6].add(address) else: updates[4].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, timeout - now) if seconds > 630720000: # 20 years, add without timeout out(f'{address},') else: out(f'{address} timeout {seconds}s,') out('}') def iplist_output_values( iplists: IPLists, cache: dict, currently_active: set[str], update_only: set[str], ) -> int: """Output and apply nft commands to add iplist entries.""" now = int(time.time()) error = 0 for setname in sorted(iplists): setvalues = iplists[setname].sources # 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, allow_negative=False): cache[setname]['ip'][value] = 4000000000 # 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 INTERNAL.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 INTERNAL.command == 'iplist' ): if all(name in INTERNAL.iplist_missing_ok for name in setvalues): warning(f'Iplist "{setname}" is empty') else: 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 INTERNAL.command != 'iplist' or not OUT: return 0 write_iplist_cache(cache) lines = '\n'.join(save_final(None)) ret = run_program_rc(CONFIG.nft_bin + ['--file', '-'], stdin=lines) if ret: fail(f'Failed to update iplists, nftables error code {ret}', False) return ret def command_iplist_refresh( iplists: typing.Optional[IPLists] = None, currently_active: typing.Optional[set[str]] = None, override_update_only: typing.Optional[set[str]] = None, ) -> int: """Refresh iplist{} entries.""" # Read minimal config and parse it if not iplists: config = minimal_config() iplists = parse_config_iplist(config) urls, hostnames = iplist_collect_resolve_list(iplists) # Read URLs and files to cache cache = read_iplist_cache(iplists) for url, options in urls.items(): get_url_or_file(url, options, 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 INTERNAL.root_power or CONFIG.nft_bin == ['true'] ): currently_active = set(iplists) # Parse which iplists should be updated. if override_update_only: update_only = override_update_only else: # Default is to update all iplists (empty set). # This can be filtered with "foomuuri iplist refresh setname...". update_only = { name if name.startswith('@') else f'@{name}' for name in INTERNAL.parameters[1:] } # Make sure systemd's TimeoutStartSec doesn't trigger while updating # ruleset or writing cache file. notify('EXTEND_TIMEOUT_USEC=120000000') # 2 minutes # Apply updates to ruleset ret = iplist_output_values(iplists, cache, currently_active, update_only) notify('READY=1') notify('STOPPING=1') return ret def command_counter_list() -> int: """List named counters.""" # Read config if available for nft binary path. minimal_config(require_etc_config=False) list_params = INTERNAL.parameters[1:] # Parse counter data from kernel data = nft_json('list counters table inet foomuuri') if not data: fail("Can't read counter data from kernel") return 1 counters = {} for toplevel in data.get('nftables', []): counter = toplevel.get('counter') name = (counter or {}).get('name') if not name: continue packet_value = int_to_human(counter.get('packets', 0)) byte_value = int_to_human(counter.get('bytes', 0)) counters[name] = (packet_value, byte_value) if not counters: fail('No named counters defined in currently active firewall') # Print counter data table = [('Counter', 'Packets', 'Bytes')] error = 0 for name in list_params or counters: values = counters.get(name) if not values: warning(f'Unknown counter: {name}') error = 1 continue table.append((name, values[0], values[1])) print_table(data=table, align='<>>', header=True) return error def command_macro_list() -> int: """List default system macros and user defined macros. Don't require config files so that this can be run before initial configuration. """ macros = parse_config_macros(read_config(require_etc_config=False)) list_params = INTERNAL.parameters[1:] print('macro {') table = [ ('', macro, f'{" ".join(macros[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_table(data=table, header=False) print('}') return 0 def command_ruleset_list() -> int: """List currently active ruleset.""" # List full ruleset if no zone-zone specified. Read config if available # for nft binary path. list_params = INTERNAL.parameters[1:] if not list_params: minimal_config(require_etc_config=False) return nft_command('list ruleset') # Read config for correct nft_bin value and zone-zone names config = minimal_config() zones = parse_config_zones(config) zonepairs = [f'{szone}-{dzone}' for szone in zones for dzone in zones] # List zone-zone rules error = 0 for zone in list_params: if zone not in zonepairs: warning(f'Unknown zone-zone: {zone}') error = 1 continue for ipv in (4, 6): error |= nft_command(f'list chain inet foomuuri {zone}_{ipv}') return error def command_subcommand() -> int: """Parse "foomuuri ruleset|iplist|..." subcommand.""" # Use default subcommand if not specified in command line if not INTERNAL.parameters: INTERNAL.parameters = [ { 'counter': 'list', 'iplist': 'status', 'macro': 'list', 'ruleset': 'list', }.get(INTERNAL.command) ] # Find correct handler. min_args includes "list" argument. handler, min_args = { ('counter', 'list'): (command_counter_list, 1), ('iplist', 'add'): (command_iplist_add, 3), ('iplist', 'del'): (command_iplist_del, 3), ('iplist', 'flush'): (command_iplist_flush, 1), ('iplist', 'list'): (command_iplist_list, 1), ('iplist', 'refresh'): (command_iplist_refresh, 1), ('iplist', 'status'): (command_iplist_status, 1), ('macro', 'list'): (command_macro_list, 1), ('ruleset', 'list'): (command_ruleset_list, 1), }.get((INTERNAL.command, INTERNAL.parameters[0]), (None, 0)) if handler and len(INTERNAL.parameters) >= min_args: return handler() return command_help() def command_list() -> int: """Backward compability command handler.""" if not INTERNAL.parameters: warning( 'Command "foomuuri list" is obsolete, use ' '"foomuuri ruleset list" instead' ) return command_ruleset_list() if INTERNAL.parameters[0] == 'macro': warning( 'Command "foomuuri list macro" is obsolete, use ' '"foomuuri macro list" instead' ) return command_macro_list() if INTERNAL.parameters[0] == 'counter': warning( 'Command "foomuuri list counter" is obsolete, use ' '"foomuuri counter list" instead' ) return command_counter_list() warning( 'Command "foomuuri list" is obsolete, use ' '"foomuuri ruleset list" instead' ) INTERNAL.parameters = ['list'] + INTERNAL.parameters return command_ruleset_list() def command_reload(): """Run start and refresh iplist.""" # Use same args args = [sys.argv[0]] args.extend(arg for arg in sys.argv[1:] if arg.startswith('--')) # 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.signal(signal.SIGHUP, alarm_handler) signal.signal(signal.SIGINT, alarm_handler) signal.signal(signal.SIGTERM, alarm_handler) signal.alarm(timeout) try: return input(prompt) except (KeyboardInterrupt, EOFError): 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, check_error=False, ) if not old_rules or 'netlink: Error' in old_rules: # Not running as root or similar error if old_rules: print(old_rules.strip()) fail('Failed to read old ruleset') return 1 # Load new ruleset error = command_reload() if error: return error # Verify from user if ruleset is ok timeout = CONFIG.try_reload_timeout 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. Revert to empty # ruleset if Foomuuri was not running. if not old_rules.startswith('table inet foomuuri {'): old_rules = '' warning('Revert to empty ruleset. Foomuuri was not running.') else: warning('Revert to old config') save_file( state_file('good_file'), ['table inet foomuuri', 'delete table inet foomuuri', '', old_rules], ) return ( run_program_rc(CONFIG.nft_bin + ['--file', state_file('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(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( # noqa: RUF100,PLR0917 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) elif count_up >= cfg['history_up'] and all( cfg['history'][-cfg['consecutive_up'] :] ): # Currently down. Target goes up if: # - history has enough ups # AND # - last n items were 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: with contextlib.suppress(ValueError): stat_ms = float(line.split('/')[-1]) 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: with contextlib.suppress(ValueError): stat_ms = float(result.split()[2]) # 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: with contextlib.suppress(ValueError): if int(squiet) * 1000 != int(interval): warning( f'Mismatching "--squiet" and "--interval" values in ' f'target "{target}": {" ".join(cmd)}' ) # 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] # ty: ignore[unsupported-operator] > 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'] # ty: ignore[unsupported-operator] - 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: # ty: ignore[not-iterable] if item not in targets: fail(f'{fileline}Undefined target "{item}" in "group {name}"') return groups def monitor_write_statistics(targets, groups): """Write statistics file.""" stats = {} for group, cfg in groups.items(): stats[group] = { 'type': 'group', # Config 'target': cfg['target'], # State 'state': cfg['state'], 'history': [], # Dummy fields to be compatible with targets 'time': [], } for target, cfg in targets.items(): stats[target] = { 'type': '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', []), 'time': cfg['time'], # fping time } save_file(state_file('monitor_statistics_file'), stats) def command_monitor(): """Monitor targets and run command when their state changes up or down.""" INTERNAL.keep_going = 1 while INTERNAL.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: notify('READY=1') notify('STOPPING=1') return 0 daemonize() # Define reload/stop signal handler def signal_handler(sig, _dummy_frame): if sig == signal.SIGINT: INTERNAL.keep_going = 0 notify('STOPPING=1') else: INTERNAL.keep_going = 2 notify('RELOADING=1') INTERNAL.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) notify('READY=1') stat_interval = min( cfg['statistics_interval'] for cfg in targets.values() ) next_stat = time.time() + stat_interval / 5 while INTERNAL.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, groups) monitor_terminate_targets(targets) return 0 def command_help(error=True): """Print command line help.""" print(f"""Foomuuri {VERSION} Usage: {sys.argv[0]} [OPTIONS] COMMAND Available COMMANDs: start Load configuration files and generate ruleset stop Remove ruleset reload Same as start, followed by iplist refresh try-reload Same as reload, ask confirmation to keep new config status Show current status: running, zone-interface mapping check Verify configuration files block Load "block all traffic" ruleset ruleset list [ZONE-ZONE]... List whole active ruleset or for ZONE-ZONE only macro list [NAME | VALUE]... List all macros or macros with specified NAME(s) or VALUE(s) counter list [COUNTER]... List all or specified named COUNTER(s) iplist status [IPLIST]... List number of entries of all or specified IPLIST(s) iplist list [IPLIST]... List entries of all or specified IPLIST(s) iplist add IPLIST [TIMEOUT] IPADDRESS [IPADDRESS]... Add or refresh IPADDRESS(es) to IPLIST iplist del IPLIST IPADDRESS [IPADDRESS]... Delete IPADDRESS(es) from IPLIST iplist flush [IPLIST]... Delete all added IP addresses from all or specified IPLIST(s) iplist refresh [IPLIST]... Refresh all or specified IPLIST(s) now set interface INTERFACE zone {{ZONE | -}} Change INTERFACE to ZONE, or remove from all zones Available OPTIONS: --version Print version --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 foomuuri{{}} config OPTION to VALUE Internally used commands: dbus Start D-Bus daemon monitor Start target monitor daemon""") if error: sys.exit(1) return 0 def parse_command_line(): """Parse command line to INTERNAL.command and INTERNAL.parameters.""" # pylint: disable=too-many-branches for arg in sys.argv[1:]: if arg == '--help': INTERNAL.command = 'help' elif arg == '--version': INTERNAL.command = 'version' elif arg == '--verbose': option = arg[2:] CONFIG[option] += 1 CONFIG_OVERRIDE[option] = CONFIG_OVERRIDE.get(option, 0) + 1 elif arg == '--quiet': option = 'verbose' CONFIG[option] -= 1 CONFIG_OVERRIDE[option] = CONFIG_OVERRIDE.get(option, 0) - 1 elif arg in {'--fork', '--syslog'}: CONFIG[arg[2:]] = 1 CONFIG_OVERRIDE[arg[2:]] = 1 elif arg == '--force': INTERNAL.force += 1 elif arg == '--soft': INTERNAL.force -= 1 elif arg.startswith('--set='): if arg.count('=') == 1: fail(f'Invalid syntax for --set=OPTION=VALUE: {arg}') _, option, value = arg.split('=', 2) if option not in CONFIG: fail(f'Unknown foomuuri{{}} option: {option}') try: CONFIG.set_from_str(option, value) CONFIG_OVERRIDE[option] = value except ValueError: fail(f'Invalid foomuuri{{}} option {option} value: {value}') elif arg.startswith('--'): fail(f'Unknown option: {arg}') elif not INTERNAL.command: INTERNAL.command = arg else: INTERNAL.parameters.append(arg) def run_command() -> int: """Run CONFIG[_command].""" # Help/version doesn't need root if not INTERNAL.command or INTERNAL.command == 'help': return command_help(error=False) if INTERNAL.command == 'version': print(VERSION) return 0 # Initialize syslogging syslog_open_or_close() # Warning if not running as root INTERNAL.root_power = not os.getuid() if not INTERNAL.root_power: warning('Foomuuri should be run as "root"') # Run command handler, allow_parameters = { 'start': (command_start, False), 'stop': (command_stop, False), 'reload': (command_reload, False), 'try-reload': (command_try_reload, False), 'status': (command_status, False), 'check': (command_start, False), 'block': (command_block, False), 'ruleset': (command_subcommand, True), 'macro': (command_subcommand, True), 'counter': (command_subcommand, True), 'iplist': (command_subcommand, True), 'list': (command_list, True), 'set': (command_set, True), 'dbus': (command_dbus, False), 'monitor': (command_monitor, False), }.get(INTERNAL.command, (None, False)) if not handler: return fail(f'Unknown command: {INTERNAL.command}') if INTERNAL.parameters and not allow_parameters: return command_help() 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.33/src/tests/000077500000000000000000000000001521001665700152405ustar00rootroot00000000000000foomuuri-0.33/src/tests/__init__.py000066400000000000000000000011571521001665700173550ustar00rootroot00000000000000"""Hack to import foomuuri without .py extension and proper package tree.""" import sys from importlib.abc import Loader from importlib.machinery import SourceFileLoader from importlib.util import module_from_spec, spec_from_loader if ( # noqa: RUF100,RUF067 ( spec := spec_from_loader( 'foomuuri', SourceFileLoader('foomuuri', './src/foomuuri') ) ) and (foomuuri := module_from_spec(spec)) and isinstance(spec.loader, Loader) ): spec.loader.exec_module(foomuuri) sys.modules['foomuuri'] = foomuuri else: raise SystemError('Failed to add foomuuri to sys.modules') foomuuri-0.33/src/tests/test_converters.py000066400000000000000000000010501521001665700210370ustar00rootroot00000000000000"""Basic unit tests of Converters.""" # pylint: disable=import-error import unittest from foomuuri import Converters class TestConverters(unittest.TestCase): """Test converter helpers.""" def test_str_yes_no_to_bool(self): """Test str_yes_no_to_bool.""" self.assertTrue(Converters.str_yes_no_to_bool('yes')) self.assertFalse(Converters.str_yes_no_to_bool('no')) self.assertRaises(ValueError, Converters.str_yes_no_to_bool, '') self.assertRaises(ValueError, Converters.str_yes_no_to_bool, 'invalid') foomuuri-0.33/src/tests/test_duration.py000066400000000000000000000033051521001665700204770ustar00rootroot00000000000000"""Basic unit tests of parse_duration() and seconds_to_duration().""" # pylint: disable=import-error import unittest from foomuuri import parse_duration, seconds_to_duration class TestParseDuration(unittest.TestCase): """Test parse_duration().""" def test_valid_duration(self): """Test valid outcomes.""" self.assertEqual(parse_duration(' 0s ', fallback=None), 0) self.assertEqual(parse_duration(' 1s ', fallback=0), 1) self.assertEqual(parse_duration(' 1h 1m ', fallback=0), 3660) self.assertEqual(parse_duration(' 1w1d1h1m1s ', fallback=0), 694861) self.assertEqual(parse_duration(' 1w_1d_1h_1m_1s', fallback=0), 694861) def test_invalid_duration(self): """Test invalid outcomes.""" self.assertIsNone(parse_duration('', fallback=None)) self.assertIsNone(parse_duration('1day', fallback=None)) self.assertIsNone(parse_duration('1y', fallback=None)) self.assertIsNone(parse_duration('s', fallback=None)) self.assertIsNone(parse_duration('1s1h', fallback=None)) class TestSecondsToDuration(unittest.TestCase): """Test seconds_to_duration().""" def test_valid_seconds(self): """Test valid outcomes.""" self.assertEqual(seconds_to_duration(0), '0s') self.assertEqual(seconds_to_duration(1), '1s') self.assertEqual(seconds_to_duration(42), '42s') self.assertEqual(seconds_to_duration(61), '1m01s') self.assertEqual(seconds_to_duration(3600), '1h00m00s') self.assertEqual(seconds_to_duration(99999), '1d03h46m39s') self.assertEqual(seconds_to_duration(999999), '11d13h46m39s') self.assertEqual(seconds_to_duration(9999999), '115d17h46m39s') foomuuri-0.33/src/tests/test_ipaddress.py000066400000000000000000000066041521001665700206350ustar00rootroot00000000000000"""Basic unit tests for test_ipv*_address() functions.""" # pylint: disable=import-error import unittest from foomuuri import is_ip_address, is_ipv4_address, is_ipv6_address class TestIsIPv4Address(unittest.TestCase): """Basic unit tests for test_ipv4_address().""" def test_ipv4_address(self): """Test for IPv4 address.""" self.assertFalse(is_ipv4_address('', allow_network=False)) self.assertFalse(is_ipv4_address('::', allow_network=False)) self.assertFalse(is_ipv4_address('127.0.0.0/8', allow_network=False)) self.assertTrue(is_ipv4_address('127.0.0.1', allow_network=False)) def test_ipv4_network(self): """Test for IPv4 network.""" self.assertFalse(is_ipv4_address('', allow_network=True)) self.assertFalse(is_ipv4_address('::/64', allow_network=True)) self.assertTrue(is_ipv4_address('127.0.0.0/8', allow_network=True)) self.assertTrue(is_ipv4_address('127.0.0.1/8', allow_network=True)) def test_ipv4_range(self): """Test for IPv4 range.""" self.assertFalse(is_ipv4_address('-')) self.assertFalse(is_ipv4_address('::-::')) self.assertFalse(is_ipv4_address('127.0.0.0/8-127.0.0.0/8')) self.assertFalse(is_ipv4_address('127.0.0.2-127.0.0.1')) self.assertTrue(is_ipv4_address('127.0.0.1-127.0.0.1')) self.assertTrue(is_ipv4_address('127.0.0.1-127.0.0.2')) class TestIsIPv6Address(unittest.TestCase): """Basic unit tests for test_ipv6_address().""" def test_ipv6_address(self): """Test for IPv6 address.""" self.assertFalse(is_ipv6_address('', allow_network=False)) self.assertFalse(is_ipv6_address('127.0.0.1', allow_network=False)) self.assertFalse(is_ipv6_address('[::]/64', allow_network=False)) self.assertTrue(is_ipv6_address('::', allow_network=False)) self.assertTrue(is_ipv6_address('[::]', allow_network=False)) def test_ipv6_network(self): """Test for IPv6 network.""" self.assertFalse(is_ipv6_address('', allow_network=True)) self.assertFalse(is_ipv6_address('127.0.0.0/8', allow_network=True)) self.assertFalse(is_ipv6_address('::/129', allow_network=True)) self.assertFalse(is_ipv6_address('[::]/-64', allow_network=True)) self.assertTrue(is_ipv6_address('::/64', allow_network=True)) self.assertTrue(is_ipv6_address('::/-64', allow_network=True)) self.assertTrue(is_ipv6_address('[::]/64', allow_network=True)) def test_ipv6_range(self): """Test for IPv6 range.""" self.assertFalse(is_ipv6_address('-')) self.assertFalse(is_ipv6_address('127.0.0.1-127.0.0.2')) self.assertFalse(is_ipv6_address('1::/64-1::/64')) self.assertFalse(is_ipv6_address('::-::/-64')) self.assertFalse(is_ipv6_address('::2-::1')) self.assertTrue(is_ipv6_address('::-::')) self.assertTrue(is_ipv6_address('::1-::2')) class TestIsIPAddress(unittest.TestCase): """Basic unit tests for test_ip_address().""" def test_ip_address(self): """Test address detection for allow_negative=True/False.""" self.assertEqual(is_ip_address('-127.0.0.1', allow_negative=True), 4) self.assertEqual(is_ip_address('-::', allow_negative=True), 6) self.assertEqual(is_ip_address('-127.0.0.1', allow_negative=False), 0) self.assertEqual(is_ip_address('-::', allow_negative=False), 0) foomuuri-0.33/src/tests/test_iplist.py000066400000000000000000000037221521001665700201610ustar00rootroot00000000000000"""Basic unit tests of IPList classes.""" # pylint: disable=invalid-name,import-error import collections.abc import unittest from foomuuri import IPList, IPLists class TestIPLists(unittest.TestCase): """Test IPLists.""" def setUp(self): """Prepare test fixture.""" self.iplists = IPLists() self.sources_foo = ['https://foob.ar', 'https://foob.az'] self.iplists['@foo'] = IPList(sources=self.sources_foo) self.sources_bar = ['https://arbo.of', 'https://azbp.of'] self.iplists['@bar'] = IPList(sources=self.sources_bar) self.iplists['@bar'].options.start = False def test_index_getter(self): """Test index getter by iplist name.""" self.assertEqual(self.iplists['@foo'].sources, self.sources_foo) self.assertEqual(self.iplists['@bar'].sources, self.sources_bar) def test_iter_names(self): """Test iterator over iplist names.""" self.assertEqual(list(self.iplists), ['@foo', '@bar']) def test_items(self): """Test iterable over iplist entries (name, value tuples).""" self.assertIsInstance(self.iplists.items(), collections.abc.Iterable) self.assertListEqual( list(self.iplists.items()), [ ('@foo', self.iplists['@foo']), ('@bar', self.iplists['@bar']), ], ) def test_values(self): """Test iterable over iplist entries values.""" self.assertIsInstance(self.iplists.values(), collections.abc.Iterable) self.assertListEqual( list(self.iplists.values()), [ self.iplists['@foo'], self.iplists['@bar'], ], ) def test_filter_names_by(self): """Test set of iplist names with specific iplist option value.""" self.assertIsInstance(self.iplists.filter_names_by('start', True), set) self.assertEqual(self.iplists.filter_names_by('start', True), {'@foo'}) foomuuri-0.33/src/tests/test_join_args.py000066400000000000000000000010661521001665700206270ustar00rootroot00000000000000"""Basic unit tests of join_args().""" # pylint: disable=import-error import pathlib import unittest from foomuuri import join_args class TestJoinArgs(unittest.TestCase): """Test join_args().""" def test_valid(self): """Test valid outcomes.""" self.assertEqual(join_args([]), '') self.assertEqual(join_args(['ab', 'cd']), 'ab cd') self.assertEqual(join_args(['ab', pathlib.PosixPath('cd')]), 'ab cd') def test_invalid(self): """Test invalid outcomes.""" self.assertRaises(TypeError, join_args, 'ab') foomuuri-0.33/src/tests/test_parse_command_line.py000066400000000000000000000172741521001665700225030ustar00rootroot00000000000000"""Basic unit tests of parse_command_line().""" # pylint: disable=invalid-name,import-error # ruff: noqa: N803 import copy import unittest import unittest.mock from foomuuri import CONFIG as BASE_CONFIG from foomuuri import INTERNAL as BASE_INTERNAL from foomuuri import parse_command_line @unittest.mock.patch('foomuuri.CONFIG_OVERRIDE', new_callable=dict) @unittest.mock.patch( 'foomuuri.CONFIG', new_callable=lambda: copy.deepcopy(BASE_CONFIG) ) @unittest.mock.patch( 'foomuuri.INTERNAL', new_callable=lambda: copy.deepcopy(BASE_INTERNAL) ) class TestParseCommandLine(unittest.TestCase): """Test parse_command_line().""" @unittest.mock.patch('sys.argv', ['foomuuri']) def test_no_arguments(self, INTERNAL, *_): """Test no arguments.""" parse_command_line() self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--help']) def test_option_help(self, INTERNAL, *_): """Test --help option (sets 'help' command).""" parse_command_line() self.assertEqual(INTERNAL.command, 'help') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--version']) def test_option_version(self, INTERNAL, *_): """Test --version option (sets 'version' command).""" parse_command_line() self.assertEqual(INTERNAL.command, 'version') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch( 'sys.argv', ['foomuuri', '--verbose', 'command', 'arg1', '--force', 'arg2'], ) def test_command_and_options(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test command with arguments, mixed with options.""" parse_command_line() self.assertEqual(INTERNAL.command, 'command') self.assertEqual(INTERNAL.parameters, ['arg1', 'arg2']) self.assertEqual(INTERNAL.force, 1) self.assertEqual(CONFIG.verbose, 1) self.assertEqual(CONFIG_OVERRIDE['verbose'], 1) @unittest.mock.patch('sys.argv', ['foomuuri', '--syslog', '--fork']) def test_options_syslog_fork(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test --syslog and --fork options.""" parse_command_line() self.assertEqual(CONFIG.syslog, 1) self.assertEqual(CONFIG.fork, 1) self.assertEqual(CONFIG_OVERRIDE['syslog'], 1) self.assertEqual(CONFIG_OVERRIDE['fork'], 1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--verbose']) def test_option_verbose(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test --verbose option.""" parse_command_line() self.assertEqual(CONFIG.verbose, 1) self.assertEqual(CONFIG_OVERRIDE['verbose'], 1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--verbose', '--verbose']) def test_options_verbose_verbose(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test --verbose --verbose options.""" parse_command_line() self.assertEqual(CONFIG.verbose, 2) self.assertEqual(CONFIG_OVERRIDE['verbose'], 2) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--quiet']) def test_option_quiet(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test --quiet option.""" parse_command_line() self.assertEqual(CONFIG.verbose, -1) self.assertEqual(CONFIG_OVERRIDE['verbose'], -1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--verbose', '--quiet']) def test_options_verbose_quiet(self, INTERNAL, CONFIG, CONFIG_OVERRIDE): """Test --verbose --quiet options.""" parse_command_line() self.assertEqual(CONFIG.verbose, 0) self.assertEqual(CONFIG_OVERRIDE['verbose'], 0) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--force']) def test_option_force(self, INTERNAL, *_): """Test --force option.""" parse_command_line() self.assertEqual(INTERNAL.force, 1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--soft']) def test_option_soft(self, INTERNAL, *_): """Test --soft option.""" parse_command_line() self.assertEqual(INTERNAL.force, -1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--set']) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_option_set_unknown(self, fail, *_): """Test --set option without suffix.""" self.assertRaises(SystemExit, parse_command_line) fail.assert_called_with('Unknown option: --set') @unittest.mock.patch('sys.argv', ['foomuuri', '--set=']) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_option_set_invalid_syntax(self, fail, *_): """Test incomplete --set option syntax.""" self.assertRaises(SystemExit, parse_command_line) fail.assert_called_with( 'Invalid syntax for --set=OPTION=VALUE: --set=' ) @unittest.mock.patch('sys.argv', ['foomuuri', '--set=unknown=value']) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_option_set_unknown_option(self, fail, *_): """Test --set option with unknown foomuuri{} CONFIG option.""" self.assertRaises(SystemExit, parse_command_line) fail.assert_called_with('Unknown foomuuri{} option: unknown') @unittest.mock.patch('sys.argv', ['foomuuri', '--set=priority_offset=yes']) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_option_set_invalid_value(self, fail, *_): """Test --set option with invalid value.""" self.assertRaises(SystemExit, parse_command_line) fail.assert_called_with( 'Invalid foomuuri{} option priority_offset value: yes' ) @unittest.mock.patch( 'sys.argv', ['foomuuri', '--set=priority_offset=42', '--set=set_size=1'], ) def test_options_set_valid(self, INTERNAL, CONFIG, _): """Test --set options with valid foomuuri{} CONFIG option/value.""" parse_command_line() self.assertEqual(CONFIG.priority_offset, 42) self.assertEqual(CONFIG.set_size, 1) self.assertEqual(INTERNAL.command, '') self.assertEqual(INTERNAL.parameters, []) @unittest.mock.patch('sys.argv', ['foomuuri', '--unknown']) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_unknown_option(self, fail, *_): """Test unknown option.""" self.assertRaises(SystemExit, parse_command_line) fail.assert_called_with('Unknown option: --unknown') @unittest.mock.patch('sys.argv', ['foomuuri', 'command', '-h', '-v']) def test_option_short(self, INTERNAL, *_): """Test short options. They are parsed as parameters.""" parse_command_line() self.assertEqual(INTERNAL.command, 'command') self.assertEqual(INTERNAL.parameters, ['-h', '-v']) @unittest.mock.patch('sys.argv', ['foomuuri', 'command', 'a', '-', 'b']) def test_option_hyphen(self, INTERNAL, *_): """Test hyphen. It is parsed as parameter.""" parse_command_line() self.assertEqual(INTERNAL.command, 'command') self.assertEqual(INTERNAL.parameters, ['a', '-', 'b']) foomuuri-0.33/src/tests/test_parse_config_foomuuri.py000066400000000000000000000122571521001665700232440ustar00rootroot00000000000000"""Basic unit tests of parse_command_line().""" # pylint: disable=invalid-name,import-error # ruff: noqa: N803 import contextlib import copy import unittest import unittest.mock from foomuuri import CONFIG as BASE_CONFIG from foomuuri import INTERNAL as BASE_INTERNAL from foomuuri import minimal_config @unittest.mock.patch( 'foomuuri.INTERNAL', new_callable=lambda: copy.deepcopy(BASE_INTERNAL) ) @unittest.mock.patch( 'foomuuri.CONFIG', new_callable=lambda: copy.deepcopy(BASE_CONFIG) ) class TestParseConfigFoomuuri(unittest.TestCase): """Test parse_config_foomuuri().""" @staticmethod @contextlib.contextmanager def mock_config_foomuuri(option, value): """Return context manager of foomuuri.find_config_files() patch. foomuuri.find_config_files() is patched to return generated foomuuri configuration file, containing foomuuri{} section with single option/value. """ # MagicMock of pathlib.PosixPAth returning desired foomuuri config config_file = unittest.mock.MagicMock() config_file.read_text.return_value = f""" foomuuri {{ {option} {value} }} """ # foomuuri.find_config_files() is called by foomuuri.read_config() # to load configuration files. # read_config() tries loading share_config first, and etc_config then. # We only care about etc_config, therefore side_effect list passed # to foomuuri.find_config_files mock patch has two elemeents, # [] - empty share_config, and [config_file] - generated config. with unittest.mock.patch( 'foomuuri.find_config_files', side_effect=[[], [config_file]] ): yield config_file @unittest.mock.patch('foomuuri.warning') def test_try_reload_timeout(self, warning, CONFIG, *_): """Test try-reload_timeout backwards compatible option.""" with self.mock_config_foomuuri( 'try-reload_timeout', 69, ) as config_file: _ = minimal_config() self.assertEqual(CONFIG.try_reload_timeout, 69) warning.assert_called_once_with( f'File {config_file} line 3: foomuuri{{}} option ' f'"try-reload_timeout" is obsolete, use ' f'"try_reload_timeout" instead' ) def test_append_value(self, CONFIG, *_): """Test appending of option value.""" with self.mock_config_foomuuri('log_level', '+ append'): _ = minimal_config() self.assertEqual(CONFIG.log_level, 'level info flags skuid append') def test_unknown_option(self, *_): """Test assigning unknown option.""" with ( self.mock_config_foomuuri( 'unknown_option', 'value' ) as config_file, unittest.mock.patch( 'foomuuri.fail', side_effect=SystemExit ) as fail, ): self.assertRaises(SystemExit, minimal_config) fail.assert_called_once_with( f'File {config_file} line 3: Unknown foomuuri{{}}' ' option: unknown_option value' ) def test_invalid_value(self, *_): """Test assigning invalid value.""" with ( self.mock_config_foomuuri( 'priority_offset', 'not_an_int' ) as config_file, unittest.mock.patch( 'foomuuri.fail', side_effect=SystemExit ) as fail, ): self.assertRaises(SystemExit, minimal_config) fail.assert_called_once_with( f'File {config_file} line 3: Invalid foomuuri{{}}' ' option value: priority_offset not_an_int' ) @unittest.mock.patch('foomuuri.CONFIG_OVERRIDE', new_callable=dict) def test_config_override(self, CONFIG_OVERRIDE, CONFIG, *_): """Test overriding of CONFIG from CONFIG_OVERRRIDE.""" def test_override(option, override, config): with self.mock_config_foomuuri(option, '0'): CONFIG_OVERRIDE[option] = override _ = minimal_config() self.assertEqual(CONFIG[option], config) # Overriding with correct value type (str) test_override(option='priority_offset', override='69', config=69) # Overriding with invalid value type (int). test_override(option='priority_offset', override=69, config=69) def test_postprocess_priority_offset(self, _, INTERNAL): """Test postprocessing of priority_offset option.""" def test_priority_offset(config, internal): with self.mock_config_foomuuri('priority_offset', config): _ = minimal_config() self.assertEqual(INTERNAL.priority_offset, internal) test_priority_offset(config=-1, internal=' - 1') test_priority_offset(config=0, internal='') test_priority_offset(config=1, internal=' + 1') def test_postprocess_log_rates(self, CONFIG, *_): """Test postprocessing of log_* rates options.""" with self.mock_config_foomuuri('log_rate', '1/second burst 3'): _ = minimal_config() self.assertTrue(CONFIG.log_rate.endswith(' packets')) foomuuri-0.33/src/tests/test_parse_config_iplist.py000066400000000000000000000375601521001665700227070ustar00rootroot00000000000000"""Basic unit tests of parse_config_iplist().""" # pylint: disable=invalid-name,import-error # ruff: noqa: N803 import contextlib import copy import unittest import unittest.mock from foomuuri import CONFIG as BASE_CONFIG from foomuuri import INTERNAL as BASE_INTERNAL from foomuuri import ( IPListOptions, minimal_config, parse_config_iplist, parse_duration, ) @unittest.mock.patch( 'foomuuri.INTERNAL', new_callable=lambda: copy.deepcopy(BASE_INTERNAL) ) @unittest.mock.patch( 'foomuuri.CONFIG', new_callable=lambda: copy.deepcopy(BASE_CONFIG) ) class TestIplistParseConfig(unittest.TestCase): """Test parse_config_iplist().""" @staticmethod @contextlib.contextmanager def mock_config_foomuuri(section: str = 'iplist', content: str = ''): """Return context manager of foomuuri.find_config_files() patch. foomuuri.find_config_files() is patched to return generated foomuuri configuration file, containing arbitrary section {} with specified content. """ # consult tests/test_parse_config_foomuuri.py for mock explanation config_file = unittest.mock.MagicMock() config_file.read_text.return_value = f""" {section} {{ {content} }} """ with unittest.mock.patch( 'foomuuri.find_config_files', side_effect=[[], [config_file]] ): yield config_file @unittest.mock.patch('foomuuri.warning') def test_obsolete_syntax(self, warning, *_): """Test obsolete resolve{} section and timeout/refresh options.""" with self.mock_config_foomuuri( section='resolve', content=""" timeout 1d 5m refresh 0d 30m @foo https://foob.ar/ """, ) as config_file: iplists = parse_config_iplist(minimal_config()) options = iplists['@foo'].options self.assertEqual(options.dns_timeout, parse_duration('1d 5m', 0)) self.assertEqual(options.url_timeout, parse_duration('1d 5m', 0)) self.assertEqual(options.dns_refresh, parse_duration('0d 30m', 0)) self.assertEqual(options.url_refresh, parse_duration('0d 30m', 0)) self.assertEqual(iplists['@foo'].sources, ['https://foob.ar/']) warning.assert_has_calls( [ unittest.mock.call( 'Section "resolve" is obsolete, use "iplist" instead', ), unittest.mock.call( f'File {config_file} line 4: Iplist "timeout" is ' 'obsolete, use "dns_timeout" or "url_timeout" instead', ), unittest.mock.call( f'File {config_file} line 5: Iplist "refresh" is ' 'obsolete, use "dns_refresh" or "url_refresh" instead', ), ] ) @unittest.mock.patch('foomuuri.warning') @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_obsolete_syntax_fail(self, fail, warning, *_): """Test iplist{} section with invalid obsolete timeout option.""" with self.mock_config_foomuuri( section='iplist', content=""" timeout # value is missing """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) warning.assert_called_once_with( f'File {config_file} line 4: Iplist "timeout" is obsolete, ' 'use "dns_timeout" or "url_timeout" instead', ) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option ' 'value: dns_timeout=' ) def test_empty_iplist_definition(self, *_): """Test empty iplist definition.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo @foo - """, ): iplists = parse_config_iplist(minimal_config()) self.assertEqual(iplists['@foo'].sources, []) def test_valid_default_options(self, *_): """Test valid default options, space and equal-sign styles.""" with self.mock_config_foomuuri( section='iplist', content=""" url_max_size 1024 url_timeout=69d url_refresh=42d dns_timeout 1d 2h # space-separated option value is # supported by parse_duration() @foo https://foob.ar/ """, ): iplists = parse_config_iplist(minimal_config()) options = iplists['@foo'].options self.assertEqual(options.url_timeout, parse_duration('69d', 0)) self.assertEqual(options.url_refresh, parse_duration('42d', 0)) self.assertEqual(options.dns_timeout, parse_duration('1d 2h', 0)) self.assertEqual(options.url_max_size, 1024) def test_single_line_multiple_valid_default_options(self, *_): """Test mulitple valid default options on single line.""" with self.mock_config_foomuuri( section='iplist', content=""" url_timeout=69d url_refresh=42d @foo https://foob.ar/ """, ): iplists = parse_config_iplist(minimal_config()) options = iplists['@foo'].options self.assertEqual(options.url_timeout, parse_duration('69d', 0)) self.assertEqual(options.url_refresh, parse_duration('42d', 0)) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_first_invalid_default_option(self, fail, *_): """Test first invalid default option on single line.""" with self.mock_config_foomuuri( section='iplist', content=""" invalid url_timeout=2d url_refresh=1d @foo https://foob.ar/ """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option name: ' 'invalid=url_timeout=2d url_refresh=1d' ) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_second_invalid_default_option(self, fail, *_): """Test second invalid default option on single line.""" with self.mock_config_foomuuri( section='iplist', content=""" url_timeout=2d invalid url_refresh=1d @foo https://foob.ar/ """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Unsupported syntax: ' 'url_timeout=2d invalid url_refresh=1d' ) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_invalid_space_style_default_option_name(self, fail, *_): """Test invalid space-style default option name.""" with self.mock_config_foomuuri( section='iplist', content=""" url_min_size 1024 """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option name: ' 'url_min_size=1024' ) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_invalid_space_style_default_option_value(self, fail, *_): """Test invalid space-style default option value.""" with self.mock_config_foomuuri( section='iplist', content=""" url_max_size abcd """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option value: ' 'url_max_size=abcd' ) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_invalid_default_option_name(self, fail, *_): """Test invalid default option name.""" with self.mock_config_foomuuri( section='iplist', content=""" invalid_option=whatever_value """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option name: ' 'invalid_option=whatever_value' ) @unittest.mock.patch('foomuuri.fail', side_effect=SystemExit) def test_invalid_default_option_value(self, fail, *_): """Test invalid default option value.""" with self.mock_config_foomuuri( section='iplist', content=""" url_max_size=abcd """, ) as config_file: with self.assertRaises(SystemExit): _ = parse_config_iplist(minimal_config()) fail.assert_called_once_with( f'File {config_file} line 4: Invalid iplist{{}} option value: ' 'url_max_size=abcd' ) def test_start_merge_url_size_options(self, *_): """Test start, merge, url_size options.""" with self.mock_config_foomuuri( section='iplist', content=""" -start -merge url_max_size=1024 @foo https://foob.ar/ @bar https://b.ar/ start=no merge=no @baz https://b.az/ start=yes merge=yes url_max_size=2048 """, ): iplists = parse_config_iplist(minimal_config()) default_options = IPListOptions() # Testing applcation as default options # Asserting applied options differ from default values self.assertNotEqual( iplists['@foo'].options.start, default_options.start ) self.assertNotEqual( iplists['@foo'].options.merge, default_options.merge ) self.assertNotEqual( iplists['@foo'].options.url_max_size, default_options.url_max_size, ) # Asserting applied option values are exactly as set self.assertFalse(iplists['@foo'].options.start) self.assertFalse(iplists['@foo'].options.merge) self.assertFalse(iplists['@foo'].options.dynamic) self.assertEqual(iplists['@foo'].options.url_max_size, 1024) # Alternative start/merge syntax (false) self.assertFalse(iplists['@bar'].options.start) self.assertFalse(iplists['@bar'].options.merge) # Alternative start/merge syntax (true) self.assertTrue(iplists['@baz'].options.start) self.assertTrue(iplists['@baz'].options.merge) # Iplist url_max_size overrides default option self.assertTrue(iplists['@baz'].options.url_max_size, 2048) @unittest.mock.patch('foomuuri.warning') def test_iplist_sources_overwrite(self, warning, *_): """Test overwriting of iplist sources.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo https://foob.ar/ @foo https://rabo.of/ """, ) as config_file: iplists = parse_config_iplist(minimal_config()) self.assertEqual( iplists['@foo'].sources, ['https://rabo.of/'], ) warning.assert_called_with( f'File {config_file} line 5: Overwriting iplist "@foo" ' 'with value "https://rabo.of/"' ) def test_iplist_sources_append(self, *_): """Test appending of iplist sources.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo https://foob.ar/ @foo + https://rabo.of/ @bar + https://foo.bar/ @baz + """, ): iplists = parse_config_iplist(minimal_config()) self.assertEqual( iplists['@foo'].sources, ['https://foob.ar/', 'https://rabo.of/'], ) self.assertEqual(iplists['@bar'].sources, ['https://foo.bar/']) self.assertEqual(iplists['@baz'].sources, []) def test_intermixing_iplist_sources_options(self, *_): """Test itermixing of iplist sources and options.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo start=no https://foob.ar/ merge=no https://rabo.of/ """, ): iplists = parse_config_iplist(minimal_config()) default_options = IPListOptions() # Asserting applied options differ from default values self.assertNotEqual( iplists['@foo'].options.start, default_options.start ) self.assertNotEqual( iplists['@foo'].options.merge, default_options.merge ) # Asserting appled options values are exactly as set self.assertFalse(iplists['@foo'].options.start) self.assertFalse(iplists['@foo'].options.merge) self.assertEqual( iplists['@foo'].sources, ['https://foob.ar/', 'https://rabo.of/'], ) def test_last_option_priority_wins(self, *_): """Test last option defined for same ipset definition wins.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo url_timeout=42d @foo url_timeout=69d """, ): iplists = parse_config_iplist(minimal_config()) self.assertEqual( iplists['@foo'].options.url_timeout, parse_duration('69d', 0) ) @unittest.mock.patch('foomuuri.verbose') def test_iplist_name_and_sources_vebose_output(self, verbose, CONFIG, *_): """Test output of iplists name and sources when verbose >= 2.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo https://foob.ar/ @bar https://foo.bar/ """, ): CONFIG.verbose = 2 _ = parse_config_iplist(minimal_config()) verbose.assert_has_calls( [ unittest.mock.call('@foo https://foob.ar/'), unittest.mock.call('@bar https://foo.bar/'), ] ) def test_iplist_dynamic(self, *_): """Test dynamic flag in iplist.""" with self.mock_config_foomuuri( section='iplist', content=""" @foo dynamic=yes @bar dynamic=yes element_timeout=1m30s """, ): iplists = parse_config_iplist(minimal_config()) self.assertTrue(iplists['@foo'].options.dynamic) self.assertTrue(iplists['@bar'].options.dynamic) self.assertEqual( iplists['@foo'].options.element_timeout, parse_duration('0s', -1), ) self.assertEqual( iplists['@bar'].options.element_timeout, parse_duration('1m30s', -1), ) def test_internal_iplist_missing_ok_init(self, _, INTERNAL, *__): """Test INTERNAL.iplist_missing_ok is initialized.""" with self.mock_config_foomuuri(): self.assertRaises( AttributeError, lambda: INTERNAL.iplist_missing_ok ) _ = parse_config_iplist(minimal_config()) self.assertEqual(INTERNAL.iplist_missing_ok, set()) foomuuri-0.33/src/tests/test_print_table.py000066400000000000000000000065061521001665700211630ustar00rootroot00000000000000"""Basic unit tests of print_table().""" # pylint: disable=invalid-name,import-error import io import unittest import unittest.mock from foomuuri import print_table @unittest.mock.patch('sys.stdout', new_callable=io.StringIO) class TestPrintTable(unittest.TestCase): """Test print_table().""" def setUp(self): """Fill in common fixture values.""" self.data_multiple_rows = [ ( 'row 1 column 1', 'row 1 colum 2', 'row 1 colu 3', ), ( 'row 2 colu 1', 'row 2 colum 2', 'row 2 column 3', ), ] self.data_no_rows = [] self.header = ( 'Header', 'Long Elaborate Header', 'Medium Header', ) def test_invalid_align_values(self, _): """Test invalid align values.""" invalid_align = [ '', # no alignment 'x', # invalid align value '<<', # too few values for 3 columns '<<<<', # too much values for 3 columns ] for align in invalid_align: with self.subTest(align): self.assertRaises( ValueError, print_table, self.data_multiple_rows, align=align, ) def test_print_table_all_args(self, stdout: io.StringIO): """Test table with all supported arguments.""" print_table( data=[self.header] + self.data_multiple_rows, header=True, align='<>^', sep=' ', ) self.assertEqual( stdout.getvalue(), """Header Long Elaborate Header Medium Header -------------- --------------------- -------------- row 1 column 1 row 1 colum 2 row 1 colu 3 row 2 colu 1 row 2 colum 2 row 2 column 3 """, ) def test_print_table_with_rows_no_headers(self, stdout: io.StringIO): """Test table without headers, rows only.""" print_table(data=self.data_multiple_rows) self.assertEqual( stdout.getvalue(), """row 1 column 1 row 1 colum 2 row 1 colu 3 row 2 colu 1 row 2 colum 2 row 2 column 3 """, ) def test_print_table_no_rows(self, stdout: io.StringIO): """Test table without headers and rows.""" print_table(data=self.data_no_rows) self.assertEqual( stdout.getvalue(), '', ) def test_print_table_with_headers_no_rows(self, stdout: io.StringIO): """Test table with headers, without rows.""" print_table(data=[self.header] + self.data_no_rows, header=True) self.assertEqual( stdout.getvalue(), '', ) def test_print_table_default_align(self, stdout: io.StringIO): """Test table with headers, and default (left) align.""" print_table(data=[self.header] + self.data_multiple_rows, header=True) self.assertEqual( stdout.getvalue(), """Header Long Elaborate Header Medium Header -------------- --------------------- -------------- row 1 column 1 row 1 colum 2 row 1 colu 3 row 2 colu 1 row 2 colum 2 row 2 column 3 """, ) foomuuri-0.33/src/tests/test_typed_config.py000066400000000000000000000160331521001665700213260ustar00rootroot00000000000000"""Basic unit tests of TypedConfig.""" # pylint: disable=import-error import pathlib import unittest from dataclasses import dataclass, field from foomuuri import TypedConfig class TestTypedConfig(unittest.TestCase): """Setters / getters / type conversion tests.""" def set(self, name, value): """Set attribute and return value helper.""" self.config[name] = value return self.config[name] def set_from_str(self, name, value): """Set attribute from string and return value helper.""" self.config.set_from_str(name, value) return self.config[name] def append_from_str(self, name, value): """Append attribute value from string and return value helper.""" self.config.append_from_str(name, value) return self.config[name] def setUp(self): """Setup tests.""" @dataclass class TestConfig(TypedConfig): """Test class.""" # pylint: disable=too-many-instance-attributes initialized_str: str = 'init_value1' uninitialized_str: str = field(init=False) initialized_untyped = 'init_value2' # noqa: RUF100,RUF045 type_conversion_float: float = 0.0 type_conversion_int: int = 0 type_conversion_list: list = field(default_factory=list) type_conversion_posixpath: pathlib.PosixPath = field(init=False) type_conversion_set: set = field(default_factory=set) validation: str = field( init=False, metadata={'validate': lambda v: v} ) conversion: list = field( init=False, metadata={'convert_str': lambda v: v.split()} ) self.config = TestConfig() def test_accessor_style(self): """Test dict/dot style accessor/setter.""" self.config.initialized_str = 'new_value1' self.assertEqual(self.config.initialized_str, 'new_value1') self.assertRaises( AttributeError, lambda: self.config.unknown_attribute ) self.config['initialized_str'] = 'new_value2' self.assertEqual(self.config['initialized_str'], 'new_value2') self.assertRaises( AttributeError, lambda: self.config['unknown_attribute'] ) self.assertRaises(AttributeError, self.set, 'unknown_attribute', '123') def test_initialized_typed(self): """Test assigning values to initialized typed attributes.""" self.assertRaises(TypeError, self.set, 'initialized_str', 123) self.assertEqual(self.set('initialized_str', 'value'), 'value') def test_uninitialized_typed(self): """Test assigning values to uninialized typed attributes.""" self.assertRaises( AttributeError, lambda: self.config.uninitialized_str ) self.assertRaises(TypeError, self.set, 'uninitialized_str', 123) self.assertEqual(self.set('uninitialized_str', 'value'), 'value') def test_initialized_untyped(self): """Test accessing listed untyped attributes.""" self.assertRaises( AttributeError, lambda: self.config.uninitialized_untyped ) def test_set_from_str(self): """Test attribute type conversion from str (set_from_str).""" # Invalid value type: only str supported self.assertRaises( TypeError, self.config.set_from_str, 'type_conversion_int', 123 ) # Supported conversion, from str to int self.assertEqual(self.set_from_str('type_conversion_int', '123'), 123) # Supported conversion, from str to pathlib.PosixPath self.assertEqual( self.set_from_str('type_conversion_posixpath', '/path/'), pathlib.PosixPath('/path/'), ) # Supported conversion, from str to list self.assertEqual( self.set_from_str('type_conversion_list', '1 2 2 3 4'), ['1', '2', '2', '3', '4'], ) # Supported conversion, from str to set self.assertEqual( self.set_from_str('type_conversion_set', '1 2 2 3 4'), {'1', '2', '3', '4'}, ) # Assignment, str to str self.assertEqual(self.set_from_str('initialized_str', '123'), '123') # Assignment, list converted from str self.assertEqual(self.set_from_str('conversion', '1'), ['1']) # Unsupported conversion, from str to float self.assertRaises( TypeError, self.config.set_from_str, 'type_conversion_float', '42.0', ) # Assign non-existing attribute self.assertRaises( AttributeError, self.config.set_from_str, 'unknownattribute', '123' ) def test_append_from_str(self): """Test attribute value append from str (append_from_str).""" # Invalid value type: only str supported self.assertRaises( TypeError, self.config.append_from_str, 'initialized_str', 123 ) # Appending, str to str self.assertEqual(self.set_from_str('initialized_str', '1'), '1') self.assertEqual(self.append_from_str('initialized_str', '2'), '1 2') # Appending, list converted from str to list self.assertEqual(self.set_from_str('conversion', '1'), ['1']) self.assertEqual(self.append_from_str('conversion', '2'), ['1', '2']) # Appending, str to list self.assertEqual( self.set_from_str('type_conversion_list', '1 2'), ['1', '2'], ) self.assertEqual( self.append_from_str('type_conversion_list', '2 3'), ['1', '2', '2', '3'], ) # Appending, str to set self.assertEqual( self.set_from_str('type_conversion_set', '1 2'), {'1', '2'}, ) self.assertEqual( self.append_from_str('type_conversion_set', '2 3'), {'1', '2', '3'}, ) # Unsupported append, from str to int self.assertRaises( TypeError, self.append_from_str, 'type_conversion_int', '1' ) # Append non-existing attribute self.assertRaises( AttributeError, self.append_from_str, 'unknownattribute', '123' ) def test_validation(self): """Test attribute validation helper call.""" self.assertRaises(ValueError, self.set, 'validation', '') self.assertEqual(self.set('validation', 'test'), 'test') def test_iter(self): """Test __iter__.""" self.assertEqual( sorted(self.config), [ 'conversion', 'initialized_str', 'type_conversion_float', 'type_conversion_int', 'type_conversion_list', 'type_conversion_posixpath', 'type_conversion_set', 'uninitialized_str', 'validation', ], ) def test_get(self): """Test get.""" self.assertEqual(self.config.get('initialized_str'), 'init_value1') self.assertEqual(self.config.get('unknown_attr'), None) foomuuri-0.33/src/tests/test_validators.py000066400000000000000000000055321521001665700210260ustar00rootroot00000000000000"""Basic unit tests of Validators.""" # pylint: disable=import-error import unittest from foomuuri import Validators class TestValidators(unittest.TestCase): """Test assert helpers.""" def test_assert_str_word(self): """Test invalid and valid outcomes.""" self.assertFalse(Validators.str_zone_name('')) self.assertFalse(Validators.str_zone_name('word word')) self.assertFalse(Validators.str_zone_name('3word')) self.assertFalse(Validators.str_zone_name('w#ord')) self.assertFalse(Validators.str_zone_name('_./_')) self.assertTrue(Validators.str_zone_name('wo_123_rd')) self.assertTrue(Validators.str_zone_name('w./_')) self.assertFalse(Validators.str_iplist_name('')) self.assertFalse(Validators.str_iplist_name('@')) self.assertFalse(Validators.str_iplist_name('@w w')) self.assertFalse(Validators.str_iplist_name('@kääk')) self.assertFalse(Validators.str_iplist_name('@3word')) self.assertTrue(Validators.str_iplist_name('@word')) self.assertTrue(Validators.str_iplist_name('@_')) self.assertTrue(Validators.str_iplist_name('@_./-')) self.assertFalse(Validators.str_interface_name('')) self.assertFalse(Validators.str_interface_name('word word')) self.assertFalse(Validators.str_interface_name('word\nword')) self.assertFalse(Validators.str_interface_name('word/word')) self.assertFalse(Validators.str_interface_name('word"word')) self.assertFalse(Validators.str_interface_name('word\\word')) self.assertTrue(Validators.str_interface_name('eth0')) self.assertTrue(Validators.str_interface_name(':')) self.assertTrue(Validators.str_interface_name('@')) self.assertTrue(Validators.str_interface_name('kääk')) self.assertFalse(Validators.str_words('')) self.assertTrue(Validators.str_words('word')) self.assertTrue(Validators.str_words('word word')) self.assertFalse(Validators.has_elements([])) self.assertTrue(Validators.has_elements(['a'])) self.assertTrue(Validators.has_elements(['a', 'b'])) self.assertFalse(Validators.has_elements({})) self.assertTrue(Validators.has_elements({'a'})) self.assertTrue(Validators.has_elements({'a', 'b'})) self.assertFalse(Validators.int_positive_or_zero(-1)) self.assertTrue(Validators.int_positive_or_zero(0)) self.assertTrue(Validators.int_positive_or_zero(1)) self.assertFalse(Validators.int_positive(-1)) self.assertTrue(Validators.int_positive(1)) self.assertFalse(Validators.str_yes_no('')) self.assertFalse(Validators.str_yes_no('maybe')) self.assertFalse(Validators.str_yes_no('YES')) self.assertTrue(Validators.str_yes_no('yes')) self.assertTrue(Validators.str_yes_no('no')) foomuuri-0.33/systemd/000077500000000000000000000000001521001665700147775ustar00rootroot00000000000000foomuuri-0.33/systemd/fi.foobar.Foomuuri1.conf000066400000000000000000000012721521001665700214020ustar00rootroot00000000000000 foomuuri-0.33/systemd/fi.foobar.Foomuuri1.policy000066400000000000000000000020001521001665700217420ustar00rootroot00000000000000 Foomuuri https://github.com/FoobarOy/foomuuri Foomuuri information System policy prevents getting Foomuuri information yes yes yes Foomuuri configuration System policy prevents changing Foomuuri configuration auth_admin_keep auth_admin_keep auth_admin_keep foomuuri-0.33/systemd/foomuuri-boot.service000066400000000000000000000005571521001665700211760ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Early boot Documentation=https://foomuuri.foobar.fi/latest/ 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 ProtectSystem=full foomuuri-0.33/systemd/foomuuri-dbus.service000066400000000000000000000006001521001665700211550ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - D-Bus handler Documentation=https://foomuuri.foobar.fi/latest/ 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 ProtectSystem=full foomuuri-0.33/systemd/foomuuri-iplist.service000066400000000000000000000003551521001665700215330ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Refresh iplist entries Documentation=https://foomuuri.foobar.fi/latest/ [Service] Type=notify ExecStart=foomuuri --soft iplist refresh TimeoutStartSec=2m ProtectSystem=full foomuuri-0.33/systemd/foomuuri-iplist.timer000066400000000000000000000003471521001665700212140ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Refresh iplist entries Documentation=https://foomuuri.foobar.fi/latest/ Requires=foomuuri.service PartOf=foomuuri.service [Timer] OnActiveSec=3m OnUnitInactiveSec=15m foomuuri-0.33/systemd/foomuuri-monitor.service000066400000000000000000000004631521001665700217160ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall - Connectivity monitor Documentation=https://foomuuri.foobar.fi/latest/ After=network-online.target After=foomuuri.service PartOf=foomuuri.service [Service] Type=notify ExecStart=foomuuri monitor ExecReload=kill -HUP $MAINPID ProtectSystem=full foomuuri-0.33/systemd/foomuuri.service000066400000000000000000000007661521001665700202370ustar00rootroot00000000000000[Unit] Description=Multizone bidirectional nftables firewall Documentation=https://foomuuri.foobar.fi/latest/ 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 ProtectSystem=full [Install] WantedBy=multi-user.target foomuuri-0.33/systemd/foomuuri.tmpfilesd000066400000000000000000000000411521001665700205500ustar00rootroot00000000000000d /run/foomuuri 0755 root root - foomuuri-0.33/test/000077500000000000000000000000001521001665700142665ustar00rootroot00000000000000foomuuri-0.33/test/10-host-multizone/000077500000000000000000000000001521001665700175055ustar00rootroot00000000000000foomuuri-0.33/test/10-host-multizone/foomuuri.conf000066400000000000000000000013041521001665700222170ustar00rootroot00000000000000zone { localhost public home } public-localhost { # Incoming traffic in a cafe dhcp-client dhcpv6-client ping saddr_rate "5/second burst 20" ssh saddr_rate "5/minute burst 5" drop log } home-localhost { # Incoming traffic in safe location dhcp-client dhcpv6-client lsdp mdns ping ssdp ssh drop log } template outgoing_services { # Common outgoing traffic dhcp-server dhcpv6-server domain http https imap ntp ping smtp ssh } localhost-public { # Outgoing traffic in a cafe template outgoing_services reject log } localhost-home { # Outgoing traffic in safe location template outgoing_services googlemeet ipp mdns ssdp reject log } foomuuri-0.33/test/10-host-multizone/golden.txt000066400000000000000000000344631521001665700215300ustar00rootroot00000000000000table 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 } fib daddr type broadcast udp dport 68 accept ip protocol igmp ip daddr 224.0.0.1 accept fib daddr type { 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 } fib daddr type 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 fib daddr type multicast ip daddr 224.0.0.251 udp dport 5353 accept fib daddr type multicast ip daddr 224.0.0.251 ip protocol igmp accept fib daddr type multicast ip daddr 239.255.255.250 udp dport 1900 accept fib daddr type multicast ip daddr 239.255.255.250 ip protocol igmp accept fib daddr type broadcast udp dport { 11430, 68 } accept fib daddr type { 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 } fib daddr type multicast ip6 daddr ff02::fb udp dport 5353 accept fib daddr type multicast ip6 daddr ff02::c udp dport 1900 accept fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/10-host/000077500000000000000000000000001521001665700154615ustar00rootroot00000000000000foomuuri-0.33/test/10-host/foomuuri.conf000066400000000000000000000004001521001665700201670ustar00rootroot00000000000000zone { localhost public * # All network interfaces belong to zone "public" } public-localhost { # Allow specified incoming traffic dhcp-client dhcpv6-client ping ssh drop log } localhost-public { # Allow all outgoing traffic accept } foomuuri-0.33/test/10-host/golden.txt000066400000000000000000000162721521001665700175020ustar00rootroot00000000000000table 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 jump public-localhost 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 jump localhost-public 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 jump public-public 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 } fib daddr type broadcast udp dport 68 accept ip protocol igmp ip daddr 224.0.0.1 accept fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/10-multi-isp/000077500000000000000000000000001521001665700164275ustar00rootroot00000000000000foomuuri-0.33/test/10-multi-isp/foomuuri.conf000066400000000000000000000062141521001665700211460ustar00rootroot00000000000000# 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.33/test/10-multi-isp/golden.txt000066400000000000000000000362761521001665700204560ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept fib daddr type { 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 } fib daddr type multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/20-router/000077500000000000000000000000001521001665700160255ustar00rootroot00000000000000foomuuri-0.33/test/20-router/foomuuri.conf000066400000000000000000000062741521001665700205520ustar00rootroot00000000000000zone { 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.33/test/20-router/golden.txt000066400000000000000000000606541521001665700200510ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept fib daddr type { 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 } fib daddr type multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept fib daddr type 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 } fib daddr type broadcast udp dport 67 accept ip protocol igmp ip daddr 224.0.0.1 accept fib daddr type { 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 } fib daddr type multicast ip6 daddr ff02::1:2 udp sport 546 udp dport 547 accept fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/50-accept/000077500000000000000000000000001521001665700157475ustar00rootroot00000000000000foomuuri-0.33/test/50-accept/foomuuri.conf000066400000000000000000000003251521001665700204630ustar00rootroot00000000000000zone { localhost a b c } a-b { # not added drop log } a-c { # both added accept } b-a { # multicast dropped, broadcast accepted multicast drop accept } b-c { # empty chain, not added } foomuuri-0.33/test/50-accept/golden.txt000066400000000000000000000412361521001665700177660ustar00rootroot00000000000000table 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 a-b { meta nfproto vmap { ipv4 : jump a-b_4, ipv6 : jump a-b_6 } } chain a-b_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-b DROP " level info flags skuid drop } chain a-b_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-b DROP " level info flags skuid drop } chain a-c { meta nfproto vmap { ipv4 : jump a-c_4, ipv6 : jump a-c_6 } } chain a-c_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type multicast accept fib daddr type broadcast accept fib daddr type { broadcast, multicast } drop accept } chain a-c_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast accept fib daddr type multicast drop accept } chain b-a { meta nfproto vmap { ipv4 : jump b-a_4, ipv6 : jump b-a_6 } } chain b-a_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type multicast drop fib daddr type multicast accept fib daddr type broadcast accept fib daddr type { broadcast, multicast } drop accept } chain b-a_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop fib daddr type multicast accept fib daddr type multicast drop accept } chain b-c { meta nfproto vmap { ipv4 : jump b-c_4, ipv6 : jump b-c_6 } } chain b-c_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-c DROP " level info flags skuid drop } chain b-c_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-c 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-a { meta nfproto vmap { ipv4 : jump localhost-a_4, ipv6 : jump localhost-a_6 } } chain localhost-a_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-a REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-a_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-a REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-b { meta nfproto vmap { ipv4 : jump localhost-b_4, ipv6 : jump localhost-b_6 } } chain localhost-b_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-b REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-b_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-b REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-c { meta nfproto vmap { ipv4 : jump localhost-c_4, ipv6 : jump localhost-c_6 } } chain localhost-c_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-c REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-c_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-c REJECT " level info flags skuid reject with icmpx admin-prohibited } chain a-localhost { meta nfproto vmap { ipv4 : jump a-localhost_4, ipv6 : jump a-localhost_6 } } chain a-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-localhost DROP " level info flags skuid drop } chain a-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-localhost DROP " level info flags skuid drop } chain a-a { meta nfproto vmap { ipv4 : jump a-a_4, ipv6 : jump a-a_6 } } chain a-a_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-a DROP " level info flags skuid drop } chain a-a_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-a DROP " level info flags skuid drop } chain b-localhost { meta nfproto vmap { ipv4 : jump b-localhost_4, ipv6 : jump b-localhost_6 } } chain b-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-localhost DROP " level info flags skuid drop } chain b-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-localhost DROP " level info flags skuid drop } chain b-b { meta nfproto vmap { ipv4 : jump b-b_4, ipv6 : jump b-b_6 } } chain b-b_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-b DROP " level info flags skuid drop } chain b-b_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-b DROP " level info flags skuid drop } chain c-localhost { meta nfproto vmap { ipv4 : jump c-localhost_4, ipv6 : jump c-localhost_6 } } chain c-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "c-localhost DROP " level info flags skuid drop } chain c-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "c-localhost DROP " level info flags skuid drop } chain c-a { meta nfproto vmap { ipv4 : jump c-a_4, ipv6 : jump c-a_6 } } chain c-a_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "c-a DROP " level info flags skuid drop } chain c-a_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "c-a DROP " level info flags skuid drop } chain c-b { meta nfproto vmap { ipv4 : jump c-b_4, ipv6 : jump c-b_6 } } chain c-b_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "c-b DROP " level info flags skuid drop } chain c-b_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "c-b DROP " level info flags skuid drop } chain c-c { meta nfproto vmap { ipv4 : jump c-c_4, ipv6 : jump c-c_6 } } chain c-c_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "c-c DROP " level info flags skuid drop } chain c-c_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "c-c 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.33/test/50-any/000077500000000000000000000000001521001665700152775ustar00rootroot00000000000000foomuuri-0.33/test/50-any/foomuuri.conf000066400000000000000000000004711521001665700200150ustar00rootroot00000000000000zone { 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.33/test/50-any/golden.txt000066400000000000000000000447101521001665700173160ustar00rootroot00000000000000table 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/50-parser/000077500000000000000000000000001521001665700160045ustar00rootroot00000000000000foomuuri-0.33/test/50-parser/foomuuri.conf000066400000000000000000000003441521001665700205210ustar00rootroot00000000000000zone { localhost public } macro { testmacro \ 1000 \ 1001 } localhost-public { tcp \ testmacro \ drop tcp $(shell cat 50-parser/portlist.txt | tr '\n' ' ') reject $(shell cat 50-parser/rules.txt) } foomuuri-0.33/test/50-parser/golden.txt000066400000000000000000000170341521001665700200220ustar00rootroot00000000000000table 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 { 1000, 1001 } drop tcp dport { 1002, 1003, 1004 } reject with icmpx admin-prohibited tcp dport 2001 accept tcp dport 2002 reject with icmpx admin-prohibited tcp dport 22 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public DROP " level info flags skuid drop } 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, 1001 } drop tcp dport { 1002, 1003, 1004 } reject with icmpx admin-prohibited tcp dport 2001 accept tcp dport 2002 reject with icmpx admin-prohibited tcp dport 22 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public 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-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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/50-parser/portlist.txt000066400000000000000000000000171521001665700204230ustar00rootroot000000000000001002 1003 1004 foomuuri-0.33/test/50-parser/rules.txt000066400000000000000000000000461521001665700176770ustar00rootroot00000000000000tcp 2001 tcp 2002 reject ssh drop log foomuuri-0.33/test/50-tcp/000077500000000000000000000000001521001665700152765ustar00rootroot00000000000000foomuuri-0.33/test/50-tcp/foomuuri.conf000066400000000000000000000004761521001665700200210ustar00rootroot00000000000000zone { 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.33/test/50-tcp/golden.txt000066400000000000000000000160511521001665700173120ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/60-cgroup/000077500000000000000000000000001521001665700160105ustar00rootroot00000000000000foomuuri-0.33/test/60-cgroup/foomuuri.conf000066400000000000000000000004541521001665700205270ustar00rootroot00000000000000foomuuri { 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.33/test/60-cgroup/golden.txt000066400000000000000000000174421521001665700200310ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/60-conntrack/000077500000000000000000000000001521001665700164735ustar00rootroot00000000000000foomuuri-0.33/test/60-conntrack/foomuuri.conf000066400000000000000000000021571521001665700212140ustar00rootroot00000000000000zone { 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 # Generic rule to accept all DNAT'ed traffic ct_status dnat # 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.33/test/60-conntrack/golden.txt000066400000000000000000000222151521001665700205060ustar00rootroot00000000000000table 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 fib daddr type { broadcast, multicast } drop ct status dnat accept 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 } fib daddr type multicast drop ct status dnat accept 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 } fib daddr type { 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 } fib daddr type 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.33/test/60-queue/000077500000000000000000000000001521001665700156355ustar00rootroot00000000000000foomuuri-0.33/test/60-queue/foomuuri.conf000066400000000000000000000004751521001665700203570ustar00rootroot00000000000000zone { 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.33/test/60-queue/golden.txt000066400000000000000000000166531521001665700176610ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/60-special-chains/000077500000000000000000000000001521001665700173745ustar00rootroot00000000000000foomuuri-0.33/test/60-special-chains/foomuuri.conf000066400000000000000000000015241521001665700221120ustar00rootroot00000000000000zone { 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 } dnat { icmp daddr 192.168.1.1 dnat 10.1.1.1 tcp daddr 192.168.1.2 dnat 10.1.1.2 udp daddr 192.168.1.3 dnat 10.1.1.3 daddr 192.168.1.4 dnat 10.1.1.4 daddr 192.0.2.1 ping dnat 172.16.2.1 daddr 2001:db8::1 ping dnat fc00:db8::1 } foomuuri-0.33/test/60-special-chains/golden.txt000066400000000000000000000215331521001665700214110ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 nat_prerouting_dstnat { type nat hook prerouting priority dstnat + 5 ip daddr 192.168.1.1 ip protocol icmp dnat ip to 10.1.1.1 ip daddr 192.168.1.2 ip protocol tcp dnat ip to 10.1.1.2 ip daddr 192.168.1.3 ip protocol udp dnat ip to 10.1.1.3 ip daddr 192.168.1.4 dnat ip to 10.1.1.4 ip daddr 192.0.2.1 icmp type echo-request dnat ip to 172.16.2.1 ip6 daddr 2001:db8::1 icmpv6 type echo-request dnat ip6 to fc00:db8::1 } 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.33/test/60-zone-wilcard/000077500000000000000000000000001521001665700171075ustar00rootroot00000000000000foomuuri-0.33/test/60-zone-wilcard/foomuuri.conf000066400000000000000000000000431521001665700216200ustar00rootroot00000000000000zone { localhost public eth* } foomuuri-0.33/test/60-zone-wilcard/golden.txt000066400000000000000000000165621521001665700211320ustar00rootroot00000000000000table 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 flags interval elements = { "lo" : accept, "eth*" : jump public-localhost, } } map output_zones { type ifname : verdict flags interval elements = { "lo" : accept, "eth*" : jump localhost-public, } } map forward_zones { type ifname . ifname : verdict flags interval elements = { "lo" . "lo" : accept, "eth*" . "eth*" : jump public-public, } } 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-ban-o-matic/000077500000000000000000000000001521001665700166015ustar00rootroot00000000000000foomuuri-0.33/test/70-ban-o-matic/foomuuri.conf000066400000000000000000000021471521001665700213210ustar00rootroot00000000000000zone { localhost public } iplist { # List of known good hosts, don't ban these @good 192.168.0.0/24 foobar.fi # List containing banned IP addresses. They will be banned for 5 minutes. @banned dynamic=yes element_timeout=5m } public-localhost { # Drop all new traffic if source IP address is in @banned iplist. # Update/reset ban expire timeout. Add this as first rule. saddr @banned iplist_update saddr @banned drop log + ":banned" # Allow SSH from known good hosts and others with rate limit. # If there are more than 5/minute connections add them to @banned iplist. ssh saddr @good ssh saddr_rate "5/minute burst 5" ssh iplist_update saddr @banned drop log + ":ban-ssh" # ...other normal rules, with or without similar rate limit banning... # Instead of normal final "drop log" rule: # - Allow 2/min dropped connections without banning. # - Somebody is port scanning us. Add IP address to @banned iplist. # - Make sure @good addresses are never banned. saddr @good drop log saddr_rate "2/minute burst 10" drop log iplist_update saddr @banned drop log + ":ban-portscan" } foomuuri-0.33/test/70-ban-o-matic/golden.txt000066400000000000000000000250731521001665700206210ustar00rootroot00000000000000table 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 } 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 fib daddr type { broadcast, multicast } drop ip saddr @banned_4 update @banned_4 { ip saddr } jump lograte_1 ip saddr @good_4 tcp dport 22 accept tcp dport 22 update @_rate_set_1_4 { ip saddr limit rate 5/minute burst 5 packets } accept tcp dport 22 update @banned_4 { ip saddr } jump lograte_2 ip saddr @good_4 jump lograte_3 update @_rate_set_2_4 { ip saddr limit rate 2/minute burst 10 packets } jump lograte_4 update @banned_4 { ip saddr } jump lograte_5 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 } fib daddr type multicast drop ip6 saddr @banned_6 update @banned_6 { ip6 saddr } jump lograte_7 ip6 saddr @good_6 tcp dport 22 accept tcp dport 22 update @_rate_set_1_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept tcp dport 22 update @banned_6 { ip6 saddr } jump lograte_8 ip6 saddr @good_6 jump lograte_9 update @_rate_set_2_6 { ip6 saddr limit rate 2/minute burst 10 packets } jump lograte_10 update @banned_6 { ip6 saddr } jump lograte_11 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 } fib daddr type { 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 } fib daddr type 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 "public-localhost DROP:banned " level info flags skuid drop } chain lograte_2 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:ban-ssh " level info flags skuid drop } chain lograte_3 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain lograte_4 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain lograte_5 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:ban-portscan " level info flags skuid drop } chain lograte_7 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:banned " level info flags skuid drop } chain lograte_8 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:ban-ssh " level info flags skuid drop } chain lograte_9 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain lograte_10 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP " level info flags skuid drop } chain lograte_11 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:ban-portscan " 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 banned_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 300s } set banned_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 300s } set good_4 { type ipv4_addr flags interval,timeout auto-merge } set good_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 banned_4 flush set inet foomuuri banned_6 flush set inet foomuuri good_4 add element inet foomuuri good_4 { 192.168.0.0/24, } flush set inet foomuuri good_6 foomuuri-0.33/test/70-counter/000077500000000000000000000000001521001665700161715ustar00rootroot00000000000000foomuuri-0.33/test/70-counter/foomuuri.conf000066400000000000000000000005511521001665700207060ustar00rootroot00000000000000foomuuri { counter dmz-internal any-public } zone { localhost public internal dmz } localhost-public { tcp 1001 tcp 1002 nft "tcp dport 1003" # not added to nft rules } public-public { tcp 2001 tcp 2002 } dmz-internal { tcp 3001 tcp 3002 } internal-dmz { # not added here tcp 4010 } public-dmz { # not added here tcp 4010 } foomuuri-0.33/test/70-counter/golden.txt000066400000000000000000000451461521001665700202140ustar00rootroot00000000000000table 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 1001 counter accept tcp dport 1002 counter accept tcp dport 1003 counter jump lograte_1 } 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 1001 counter accept tcp dport 1002 counter accept tcp dport 1003 counter jump lograte_2 } 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 } fib daddr type { broadcast, multicast } drop tcp dport 2001 counter accept tcp dport 2002 counter accept counter jump lograte_3 } 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 } fib daddr type multicast drop tcp dport 2001 counter accept tcp dport 2002 counter accept counter jump lograte_4 } 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 } fib daddr type { broadcast, multicast } drop tcp dport 3001 counter accept tcp dport 3002 counter accept counter jump lograte_5 } 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 } fib daddr type multicast drop tcp dport 3001 counter accept tcp dport 3002 counter accept counter jump lograte_6 } chain internal-dmz { meta nfproto vmap { ipv4 : jump internal-dmz_4, ipv6 : jump internal-dmz_6 } } chain internal-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 } fib daddr type { broadcast, multicast } drop tcp dport 4010 accept update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-dmz DROP " level info flags skuid drop } chain internal-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 } fib daddr type multicast drop tcp dport 4010 accept update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-dmz DROP " level info flags skuid drop } chain public-dmz { meta nfproto vmap { ipv4 : jump public-dmz_4, ipv6 : jump public-dmz_6 } } chain public-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 } fib daddr type { broadcast, multicast } drop tcp dport 4010 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 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop tcp dport 4010 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 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-internal { meta nfproto vmap { ipv4 : jump localhost-internal_4, ipv6 : jump localhost-internal_6 } } chain localhost-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 } 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 { 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-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 { 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-dmz REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-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 } 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-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 fib daddr type { 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 } fib daddr type 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-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 } fib daddr type { 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 } fib daddr type 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 internal-localhost { meta nfproto vmap { ipv4 : jump internal-localhost_4, ipv6 : jump internal-localhost_6 } } chain internal-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost DROP " level info flags skuid drop } chain internal-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost DROP " level info flags skuid drop } chain internal-public { meta nfproto vmap { ipv4 : jump internal-public_4, ipv6 : jump internal-public_6 } } chain internal-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 } fib daddr type { broadcast, multicast } drop counter jump lograte_21 } chain internal-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 } fib daddr type multicast drop counter jump lograte_22 } 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 } fib daddr type { 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 } fib daddr type 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-localhost { meta nfproto vmap { ipv4 : jump dmz-localhost_4, ipv6 : jump dmz-localhost_6 } } chain dmz-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-localhost DROP " level info flags skuid drop } chain dmz-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-localhost DROP " level info flags skuid drop } chain dmz-public { meta nfproto vmap { ipv4 : jump dmz-public_4, ipv6 : jump dmz-public_6 } } chain dmz-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 } fib daddr type { broadcast, multicast } drop counter jump lograte_27 } chain dmz-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 } fib daddr type multicast drop counter jump lograte_28 } 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 } fib daddr type { 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 } fib daddr type 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 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_2 { 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 lograte_3 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-public DROP " level info flags skuid drop } chain lograte_4 { 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_5 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-internal DROP " level info flags skuid drop } chain lograte_6 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-internal DROP " level info flags skuid drop } chain lograte_21 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-public DROP " level info flags skuid drop } chain lograte_22 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-public DROP " level info flags skuid drop } chain lograte_27 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "dmz-public DROP " level info flags skuid drop } chain lograte_28 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "dmz-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.33/test/70-dscp/000077500000000000000000000000001521001665700154435ustar00rootroot00000000000000foomuuri-0.33/test/70-dscp/foomuuri.conf000066400000000000000000000004331521001665700201570ustar00rootroot00000000000000zone { 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.33/test/70-dscp/golden.txt000066400000000000000000000203161521001665700174560ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-flowtable/000077500000000000000000000000001521001665700164715ustar00rootroot00000000000000foomuuri-0.33/test/70-flowtable/foomuuri.conf000066400000000000000000000001551521001665700212060ustar00rootroot00000000000000foomuuri { flowtable eth0 eth1 hw_offload=yes } zone { localhost internal eth0 public eth1 } foomuuri-0.33/test/70-flowtable/golden.txt000066400000000000000000000301511521001665700205020ustar00rootroot00000000000000table 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 } flowtable fastpath { hook ingress priority filter + 5 devices = { "eth0", "eth1" } flags offload } chain forward { type filter hook forward priority filter + 5 flow add @fastpath 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, "eth0" : jump internal-localhost, "eth1" : jump public-localhost, } } map output_zones { type ifname : verdict elements = { "lo" : accept, "eth0" : jump localhost-internal, "eth1" : jump localhost-public, } } map forward_zones { type ifname . ifname : verdict elements = { "lo" . "lo" : accept, "eth0" . "eth0" : jump internal-internal, "eth0" . "eth1" : jump internal-public, "eth1" . "eth0" : jump public-internal, "eth1" . "eth1" : jump public-public, } } 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-internal { meta nfproto vmap { ipv4 : jump localhost-internal_4, ipv6 : jump localhost-internal_6 } } chain localhost-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 } 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 { 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-internal 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 { 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 internal-localhost { meta nfproto vmap { ipv4 : jump internal-localhost_4, ipv6 : jump internal-localhost_6 } } chain internal-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost DROP " level info flags skuid drop } chain internal-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-localhost 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 } fib daddr type { 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 } fib daddr type 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 internal-public { meta nfproto vmap { ipv4 : jump internal-public_4, ipv6 : jump internal-public_6 } } chain internal-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 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "internal-public DROP " level info flags skuid drop } chain internal-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "internal-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 fib daddr type { 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 } fib daddr type 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-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 } fib daddr type { 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 } fib daddr type 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 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-helper/000077500000000000000000000000001521001665700157715ustar00rootroot00000000000000foomuuri-0.33/test/70-helper/foomuuri.conf000066400000000000000000000001261521001665700205040ustar00rootroot00000000000000zone { localhost public } localhost-public { ftp } public-localhost { ftp } foomuuri-0.33/test/70-helper/golden.txt000066400000000000000000000170521521001665700200070ustar00rootroot00000000000000table 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 21 accept ip protocol igmp ip daddr 224.0.0.22 accept ct helper "ftp" 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 21 accept ct helper "ftp" 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 fib daddr type { broadcast, multicast } drop tcp dport 21 accept ct helper "ftp" 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 } fib daddr type multicast drop tcp dport 21 accept ct helper "ftp" 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 } fib daddr type { 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 } fib daddr type 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 } ct helper ftp-21 { type "ftp" protocol tcp } chain helper { type filter hook prerouting priority filter + 5 tcp dport 21 ct helper set "ftp-21" } chain rpfilter { type filter hook prerouting priority filter + 5 fib saddr . mark . iif oif 0 meta ipsec missing jump rpfilter_drop } } foomuuri-0.33/test/70-ifname-lo/000077500000000000000000000000001521001665700163615ustar00rootroot00000000000000foomuuri-0.33/test/70-ifname-lo/foomuuri.conf000066400000000000000000000002361521001665700210760ustar00rootroot00000000000000zone { localhost public } prerouting { tcp 1000 iifname lo drop tcp 1001 oifname lo drop tcp 1002 iifname eth2 drop tcp 1003 oifname eth3 drop } foomuuri-0.33/test/70-ifname-lo/golden.txt000066400000000000000000000166711521001665700204050ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 iif 0 tcp dport 1000 drop fib daddr type local tcp dport 1001 drop iifname "eth2" tcp dport 1002 drop oifname "eth3" tcp dport 1003 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.33/test/70-iplist/000077500000000000000000000000001521001665700160165ustar00rootroot00000000000000foomuuri-0.33/test/70-iplist/Makefile000066400000000000000000000010061521001665700174530ustar00rootroot00000000000000.PHONY: all all: rm -f iplist-cache.json $(RUN) ../../src/foomuuri --set=etc_dir=. --set=share_dir=../../etc --set=state_dir=. --set=run_dir=. --set=nft_bin=true iplist refresh sed -i -E 's/[1-3][0-9]{9}/1800000000/g' iplist-cache.json # fix relative refresh sed -i -E 's/[5-9][0-9]{9}/4000000000/g' iplist-cache.json # fix relative timeout to "forever" value sed -i '/"::1":/d' iplist-cache.json sed -i -E 's/("127.0.0.1": 4000000000),/\1/' iplist-cache.json diff -u golden-cache-json.txt iplist-cache.json foomuuri-0.33/test/70-iplist/foomuuri.conf000066400000000000000000000006641521001665700205400ustar00rootroot00000000000000zone { localhost public } iplist { timeout 1h 2m # backward compabiliy (v0.27) url_refresh=1d2h3m @empty @empty_no_merge merge=no @one 10.0.0.1 @two 10.0.0.1 ff00::1 /non/existing/file|missing-ok dns_refresh=3h @three 10.0.0.4 10.0.0.5 ff00::1 /non/existing/file|missing-ok dns_refresh=3h merge=no @start 10.0.0.6 start=no @file ./iplist.txt url_timeout=40000d @dns localhost dns_timeout=40000d } foomuuri-0.33/test/70-iplist/golden-cache-json.txt000066400000000000000000000017361521001665700220460ustar00rootroot00000000000000{ "./iplist.txt": { "ip": { "10.1.0.1": 4000000000, "10.1.0.2": 4000000000, "10.1.0.3": 4000000000, "ff00::10": 4000000000, "ff00::20": 4000000000 }, "refresh": 1800000000 }, "@dns": { "ip": { "127.0.0.1": 4000000000 } }, "@empty": { "ip": {} }, "@empty_no_merge": { "ip": {} }, "@file": { "ip": { "10.1.0.1": 4000000000, "10.1.0.2": 4000000000, "10.1.0.3": 4000000000, "ff00::10": 4000000000, "ff00::20": 4000000000 } }, "@one": { "ip": { "10.0.0.1": 4000000000 } }, "@start": { "ip": { "10.0.0.6": 4000000000 } }, "@three": { "ip": { "10.0.0.4": 4000000000, "10.0.0.5": 4000000000, "ff00::1": 4000000000 } }, "@two": { "ip": { "10.0.0.1": 4000000000, "ff00::1": 4000000000 } }, "localhost": { "ip": { "127.0.0.1": 4000000000 }, "refresh": 1800000000 } } foomuuri-0.33/test/70-iplist/golden.txt000066400000000000000000000222641521001665700200350ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 dns_4 { type ipv4_addr flags interval,timeout auto-merge } set dns_6 { type ipv6_addr flags interval,timeout auto-merge } set empty_4 { type ipv4_addr flags interval,timeout auto-merge } set empty_6 { type ipv6_addr flags interval,timeout auto-merge } set empty_no_merge_4 { type ipv4_addr flags interval,timeout } set empty_no_merge_6 { type ipv6_addr flags interval,timeout } set file_4 { type ipv4_addr flags interval,timeout auto-merge } set file_6 { type ipv6_addr flags interval,timeout auto-merge } set one_4 { type ipv4_addr flags interval,timeout auto-merge } set one_6 { type ipv6_addr flags interval,timeout auto-merge } set start_4 { type ipv4_addr flags interval,timeout auto-merge } set start_6 { type ipv6_addr flags interval,timeout auto-merge } set three_4 { type ipv4_addr flags interval,timeout } set three_6 { type ipv6_addr flags interval,timeout } 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 dns_4 add element inet foomuuri dns_4 { 127.0.0.1, } flush set inet foomuuri dns_6 flush set inet foomuuri empty_4 flush set inet foomuuri empty_6 flush set inet foomuuri empty_no_merge_4 flush set inet foomuuri empty_no_merge_6 flush set inet foomuuri file_4 add element inet foomuuri file_4 { 10.1.0.1, 10.1.0.2, 10.1.0.3, } flush set inet foomuuri file_6 add element inet foomuuri file_6 { ff00::10, ff00::20, } flush set inet foomuuri one_4 add element inet foomuuri one_4 { 10.0.0.1, } flush set inet foomuuri one_6 flush set inet foomuuri three_4 add element inet foomuuri three_4 { 10.0.0.4, 10.0.0.5, } flush set inet foomuuri three_6 add element inet foomuuri three_6 { ff00::1, } 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.33/test/70-iplist/iplist.txt000066400000000000000000000001031521001665700200550ustar00rootroot0000000000000010.1.0.1 10.1.0.2 10.1.0.3 # comment to ignore ff00::10 ff00::20 foomuuri-0.33/test/70-ipsec/000077500000000000000000000000001521001665700156155ustar00rootroot00000000000000foomuuri-0.33/test/70-ipsec/foomuuri.conf000066400000000000000000000002241521001665700203270ustar00rootroot00000000000000zone { localhost public } localhost-public { tcp 1000 dipsec tcp 1001 -dipsec } public-localhost { tcp 2000 sipsec tcp 2001 -sipsec } foomuuri-0.33/test/70-ipsec/golden.txt000066400000000000000000000170341521001665700176330ustar00rootroot00000000000000table 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 rt ipsec exists tcp dport 1000 accept rt ipsec missing tcp dport 1001 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 } rt ipsec exists tcp dport 1000 accept rt ipsec missing tcp dport 1001 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 fib daddr type { broadcast, multicast } drop meta ipsec exists tcp dport 2000 accept meta ipsec missing tcp dport 2001 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 } fib daddr type multicast drop meta ipsec exists tcp dport 2000 accept meta ipsec missing tcp dport 2001 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-log/000077500000000000000000000000001521001665700152735ustar00rootroot00000000000000foomuuri-0.33/test/70-log/foomuuri.conf000066400000000000000000000003531521001665700200100ustar00rootroot00000000000000zone { localhost public } localhost-public { tcp 1001 log tcp 1002 log "abc def" tcp 1003 log + ":tag" tcp 1004 log "$(szone) => $(dzone): $(statement)" tcp 1005 log "new level" log_level "level crit flags ip options" } foomuuri-0.33/test/70-log/golden.txt000066400000000000000000000221751521001665700173130ustar00rootroot00000000000000table 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 1001 jump lograte_1 tcp dport 1002 jump lograte_2 tcp dport 1003 jump lograte_3 tcp dport 1004 jump lograte_4 tcp dport 1005 jump lograte_5 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 1001 jump lograte_7 tcp dport 1002 jump lograte_8 tcp dport 1003 jump lograte_9 tcp dport 1004 jump lograte_10 tcp dport 1005 jump lograte_11 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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 ACCEPT " level info flags skuid accept } chain lograte_2 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "abc def " level info flags skuid accept } chain lograte_3 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost-public ACCEPT:tag " level info flags skuid accept } chain lograte_4 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "localhost => public: ACCEPT " level info flags skuid accept } chain lograte_5 { update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "new level " level crit flags ip options accept } chain lograte_7 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public ACCEPT " level info flags skuid accept } chain lograte_8 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "abc def " level info flags skuid accept } chain lograte_9 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost-public ACCEPT:tag " level info flags skuid accept } chain lograte_10 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "localhost => public: ACCEPT " level info flags skuid accept } chain lograte_11 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "new level " level crit flags ip options 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.33/test/70-mac/000077500000000000000000000000001521001665700152525ustar00rootroot00000000000000foomuuri-0.33/test/70-mac/foomuuri.conf000066400000000000000000000002021521001665700177600ustar00rootroot00000000000000zone { localhost public } localhost-public { mac_saddr 11:22:33:44:55:66 mac_daddr de:ad:be:af:00:00 de:ad:be:af:00:01 } foomuuri-0.33/test/70-mac/golden.txt000066400000000000000000000166321521001665700172730ustar00rootroot00000000000000table 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 ether saddr 11:22:33:44:55:66 accept ether daddr { de:ad:be:af:00:00, de:ad:be:af:00:01 } 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 } ether saddr 11:22:33:44:55:66 accept ether daddr { de:ad:be:af:00:00, de:ad:be:af:00:01 } 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-macro/000077500000000000000000000000001521001665700156135ustar00rootroot00000000000000foomuuri-0.33/test/70-macro/foomuuri.conf000066400000000000000000000013331521001665700203270ustar00rootroot00000000000000zone { 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:: overwrite foo overwrite 10.3.3.1 # will print warning append + 10.2.2.1 append + 10.2.2.2 append + 10.2.2.3 } 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 tcp 1009 saddr overwrite tcp 1010 saddr append } foomuuri-0.33/test/70-macro/golden.txt000066400000000000000000000200111521001665700176160ustar00rootroot00000000000000table 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 ip saddr 10.3.3.1 tcp dport 1009 accept ip saddr { 10.2.2.1, 10.2.2.2, 10.2.2.3 } tcp dport 1010 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-mark/000077500000000000000000000000001521001665700154445ustar00rootroot00000000000000foomuuri-0.33/test/70-mark/foomuuri.conf000066400000000000000000000001601521001665700201550ustar00rootroot00000000000000zone { localhost public } forward { iifname eth0 mark_set 42 } localhost-public { ssh mark_match 42 } foomuuri-0.33/test/70-mark/golden.txt000066400000000000000000000171331521001665700174620ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-mss/000077500000000000000000000000001521001665700153145ustar00rootroot00000000000000foomuuri-0.33/test/70-mss/foomuuri.conf000066400000000000000000000007731521001665700200370ustar00rootroot00000000000000zone { 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.33/test/70-mss/golden.txt000066400000000000000000000202231521001665700173240ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-netmask/000077500000000000000000000000001521001665700161545ustar00rootroot00000000000000foomuuri-0.33/test/70-netmask/foomuuri.conf000066400000000000000000000007161521001665700206740ustar00rootroot00000000000000zone { localhost public } localhost-public { tcp 2000 daddr 10.0.1.0/24 tcp 2001 daddr -10.0.1.0/24 tcp 2002 daddr fffe:fff0:fff1:fff2:fff3:fff4:fff5:fff6 tcp 2003 daddr fffe:fff0:fff1:fff2:fff3:fff4:fff5:fff6/96 tcp 2004 daddr ::10:0:0:f00/-64 tcp 2005 daddr -::10:0:0:f00/-64 tcp 1000 saddr_rate "30/second burst 50" tcp 1001 saddr_rate "30/second burst 50" saddr_rate_mask 24 56 tcp 1002 daddr_rate "40/second" daddr_rate_mask 8 64 } foomuuri-0.33/test/70-netmask/golden.txt000066400000000000000000000213731521001665700201730ustar00rootroot00000000000000table 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 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 daddr 10.0.1.0/24 tcp dport 2000 accept ip daddr != 10.0.1.0/24 tcp dport 2001 accept tcp dport 1000 update @_rate_set_1_4 { ip saddr limit rate 30/second burst 50 packets } accept tcp dport 1001 update @_rate_set_2_4 { ip saddr and 255.255.255.0 limit rate 30/second burst 50 packets } accept tcp dport 1002 update @_rate_set_3_4 { ip daddr and 255.0.0.0 limit rate 40/second } 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 daddr fffe:fff0:fff1:fff2:fff3:fff4:fff5:fff6 tcp dport 2002 accept ip6 daddr fffe:fff0:fff1:fff2:fff3:fff4:fff5:fff6/96 tcp dport 2003 accept ip6 daddr & ::ffff:ffff:ffff:ffff == ::10:0:0:f00 tcp dport 2004 accept ip6 daddr & ::ffff:ffff:ffff:ffff != ::10:0:0:f00 tcp dport 2005 accept tcp dport 1000 update @_rate_set_1_6 { ip6 saddr limit rate 30/second burst 50 packets } accept tcp dport 1001 update @_rate_set_2_6 { ip6 saddr and ffff:ffff:ffff:ff00:: limit rate 30/second burst 50 packets } accept tcp dport 1002 update @_rate_set_3_6 { ip6 daddr and ffff:ffff:ffff:ffff:: limit rate 40/second } 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-port-knocking/000077500000000000000000000000001521001665700172775ustar00rootroot00000000000000foomuuri-0.33/test/70-port-knocking/foomuuri.conf000066400000000000000000000012171521001665700220140ustar00rootroot00000000000000zone { localhost public } iplist { # List of IP addresses that have performed initial knock.. # It is valid for 30 seconds. @knock dynamic=yes element_timeout=30s } public-localhost { # Allow SSH if IP address is in @knock iplist. ssh saddr @knock # Add source IP address to @knock iplist if there is UDP packet to port 5042. udp 5042 iplist_update saddr @knock drop log + ":knock" # Delete IP address from @knock iplist if there is any packet to any other # port. This prevents opening SSH if port scan is received. saddr @knock iplist_delete saddr @knock continue log + ":unknock" # ...other normal rules... drop log } foomuuri-0.33/test/70-port-knocking/golden.txt000066400000000000000000000205411521001665700213120ustar00rootroot00000000000000table 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 fib daddr type { broadcast, multicast } drop ip saddr @knock_4 tcp dport 22 accept udp dport 5042 update @knock_4 { ip saddr } jump lograte_1 ip saddr @knock_4 delete @knock_4 { ip saddr } update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-localhost CONTINUE:unknock " level info flags skuid 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 } fib daddr type multicast drop ip6 saddr @knock_6 tcp dport 22 accept udp dport 5042 update @knock_6 { ip6 saddr } jump lograte_4 ip6 saddr @knock_6 delete @knock_6 { ip6 saddr } update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost CONTINUE:unknock " level info flags skuid 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 } fib daddr type { 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 } fib daddr type 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 "public-localhost DROP:knock " level info flags skuid drop } chain lograte_4 { update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-localhost DROP:knock " 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 knock_4 { type ipv4_addr size 65535 flags dynamic,timeout timeout 30s } set knock_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 30s } 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 knock_4 flush set inet foomuuri knock_6 foomuuri-0.33/test/70-priority/000077500000000000000000000000001521001665700163735ustar00rootroot00000000000000foomuuri-0.33/test/70-priority/foomuuri.conf000066400000000000000000000003261521001665700211100ustar00rootroot00000000000000zone { 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.33/test/70-priority/golden.txt000066400000000000000000000171111521001665700204050ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-rate/000077500000000000000000000000001521001665700154455ustar00rootroot00000000000000foomuuri-0.33/test/70-rate/foomuuri.conf000066400000000000000000000010451521001665700201610ustar00rootroot00000000000000zone { 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 http saddr_rate "5/minute burst 5" ssh saddr_rate "20/hour burst 5" } foomuuri-0.33/test/70-rate/golden.txt000066400000000000000000000213101521001665700174530ustar00rootroot00000000000000table 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 1h gc-interval 5m } set _rate_set_2_6 { type ipv6_addr size 65535 flags dynamic,timeout timeout 1h gc-interval 5m } 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 fib daddr type { 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 tcp dport 80 update @_rate_set_1_4 { ip saddr limit rate 5/minute burst 5 packets } accept tcp dport 22 update @_rate_set_2_4 { ip saddr limit rate 20/hour 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 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type 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 tcp dport 80 update @_rate_set_1_6 { ip6 saddr limit rate 5/minute burst 5 packets } accept tcp dport 22 update @_rate_set_2_6 { ip6 saddr limit rate 20/hour 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 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-snat/000077500000000000000000000000001521001665700154575ustar00rootroot00000000000000foomuuri-0.33/test/70-snat/foomuuri.conf000066400000000000000000000013151521001665700201730ustar00rootroot00000000000000zone { 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.33/test/70-snat/golden.txt000066400000000000000000000177351521001665700175050ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-template/000077500000000000000000000000001521001665700163255ustar00rootroot00000000000000foomuuri-0.33/test/70-template/foomuuri.conf000066400000000000000000000012761521001665700210470ustar00rootroot00000000000000zone { 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.33/test/70-template/golden.txt000066400000000000000000000211301521001665700203330ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-time/000077500000000000000000000000001521001665700154505ustar00rootroot00000000000000foomuuri-0.33/test/70-time/foomuuri.conf000066400000000000000000000013271521001665700201670ustar00rootroot00000000000000zone { 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" # nft 1.1.5 accepts "22:00-03:00" but nft 1.1.3 doesn't. Don't test it. # 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.33/test/70-time/golden.txt000066400000000000000000000212021521001665700174560ustar00rootroot00000000000000table 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 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 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-tproxy/000077500000000000000000000000001521001665700160575ustar00rootroot00000000000000foomuuri-0.33/test/70-tproxy/foomuuri.conf000066400000000000000000000010001521001665700205620ustar00rootroot00000000000000zone { 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.33/test/70-tproxy/golden.txt000066400000000000000000000176521521001665700201030ustar00rootroot00000000000000table 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-uid/000077500000000000000000000000001521001665700152735ustar00rootroot00000000000000foomuuri-0.33/test/70-uid/foomuuri.conf000066400000000000000000000002351521001665700200070ustar00rootroot00000000000000zone { localhost public } localhost-public { # uid/gid only works on localhost-xxx tcp 1000 uid 123 tcp 1001 gid 234 tcp 1010 uid 111 gid 222 } foomuuri-0.33/test/70-uid/golden.txt000066400000000000000000000167301521001665700173130ustar00rootroot00000000000000table 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 1000 meta skuid 123 accept tcp dport 1001 meta skgid 234 accept tcp dport 1010 meta skuid 111 meta skgid 222 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 meta skuid 123 accept tcp dport 1001 meta skgid 234 accept tcp dport 1010 meta skuid 111 meta skgid 222 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 fib daddr type { 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 } fib daddr type 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 } fib daddr type { 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 } fib daddr type 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.33/test/70-zonemap/000077500000000000000000000000001521001665700161635ustar00rootroot00000000000000foomuuri-0.33/test/70-zonemap/foomuuri.conf000066400000000000000000000002511521001665700206750ustar00rootroot00000000000000zone { localhost public a b } zonemap { tcp 1000 new_dzone a tcp 1001 new_szone b tcp 1010 dzone public new_dzone a tcp 1011 szone public new_szone b } foomuuri-0.33/test/70-zonemap/golden.txt000066400000000000000000000461241521001665700202030ustar00rootroot00000000000000table 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 { tcp dport 1000 jump localhost-a_4 tcp dport 1001 jump b-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 { tcp dport 1000 jump localhost-a_6 tcp dport 1001 jump b-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 { tcp dport 1000 jump localhost-a_4 tcp dport 1001 jump b-public_4 tcp dport 1010 jump localhost-a_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 { tcp dport 1000 jump localhost-a_6 tcp dport 1001 jump b-public_6 tcp dport 1010 jump localhost-a_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 localhost-a { meta nfproto vmap { ipv4 : jump localhost-a_4, ipv6 : jump localhost-a_6 } } chain localhost-a_4 { tcp dport 1001 jump b-a_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-a REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-a_6 { tcp dport 1001 jump b-a_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-a REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-b { meta nfproto vmap { ipv4 : jump localhost-b_4, ipv6 : jump localhost-b_6 } } chain localhost-b_4 { tcp dport 1000 jump localhost-a_4 tcp dport 1001 jump b-b_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-b REJECT " level info flags skuid reject with icmpx admin-prohibited } chain localhost-b_6 { tcp dport 1000 jump localhost-a_6 tcp dport 1001 jump b-b_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-b 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 { tcp dport 1000 jump public-a_4 tcp dport 1001 jump b-localhost_4 tcp dport 1011 jump b-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 fib daddr type { 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 { tcp dport 1000 jump public-a_6 tcp dport 1001 jump b-localhost_6 tcp dport 1011 jump b-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 } fib daddr type 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 { tcp dport 1000 jump public-a_4 tcp dport 1001 jump b-public_4 tcp dport 1010 jump public-a_4 tcp dport 1011 jump b-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 } fib daddr type { 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 { tcp dport 1000 jump public-a_6 tcp dport 1001 jump b-public_6 tcp dport 1010 jump public-a_6 tcp dport 1011 jump b-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 } fib daddr type 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-a { meta nfproto vmap { ipv4 : jump public-a_4, ipv6 : jump public-a_6 } } chain public-a_4 { tcp dport 1001 jump b-a_4 tcp dport 1011 jump b-a_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-a DROP " level info flags skuid drop } chain public-a_6 { tcp dport 1001 jump b-a_6 tcp dport 1011 jump b-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-a DROP " level info flags skuid drop } chain public-b { meta nfproto vmap { ipv4 : jump public-b_4, ipv6 : jump public-b_6 } } chain public-b_4 { tcp dport 1000 jump public-a_4 tcp dport 1001 jump b-b_4 tcp dport 1011 jump b-b_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "public-b DROP " level info flags skuid drop } chain public-b_6 { tcp dport 1000 jump public-a_6 tcp dport 1001 jump b-b_6 tcp dport 1011 jump b-b_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "public-b DROP " level info flags skuid drop } chain a-localhost { meta nfproto vmap { ipv4 : jump a-localhost_4, ipv6 : jump a-localhost_6 } } chain a-localhost_4 { tcp dport 1000 jump a-a_4 tcp dport 1001 jump b-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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-localhost DROP " level info flags skuid drop } chain a-localhost_6 { tcp dport 1000 jump a-a_6 tcp dport 1001 jump b-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 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-localhost DROP " level info flags skuid drop } chain a-public { meta nfproto vmap { ipv4 : jump a-public_4, ipv6 : jump a-public_6 } } chain a-public_4 { tcp dport 1000 jump a-a_4 tcp dport 1001 jump b-public_4 tcp dport 1010 jump a-a_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-public DROP " level info flags skuid drop } chain a-public_6 { tcp dport 1000 jump a-a_6 tcp dport 1001 jump b-public_6 tcp dport 1010 jump a-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-public DROP " level info flags skuid drop } chain a-a { meta nfproto vmap { ipv4 : jump a-a_4, ipv6 : jump a-a_6 } } chain a-a_4 { tcp dport 1001 jump b-a_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-a DROP " level info flags skuid drop } chain a-a_6 { tcp dport 1001 jump b-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-a DROP " level info flags skuid drop } chain a-b { meta nfproto vmap { ipv4 : jump a-b_4, ipv6 : jump a-b_6 } } chain a-b_4 { tcp dport 1000 jump a-a_4 tcp dport 1001 jump b-b_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "a-b DROP " level info flags skuid drop } chain a-b_6 { tcp dport 1000 jump a-a_6 tcp dport 1001 jump b-b_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "a-b DROP " level info flags skuid drop } chain b-localhost { meta nfproto vmap { ipv4 : jump b-localhost_4, ipv6 : jump b-localhost_6 } } chain b-localhost_4 { tcp dport 1000 jump b-a_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 fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-localhost DROP " level info flags skuid drop } chain b-localhost_6 { tcp dport 1000 jump b-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-localhost DROP " level info flags skuid drop } chain b-public { meta nfproto vmap { ipv4 : jump b-public_4, ipv6 : jump b-public_6 } } chain b-public_4 { tcp dport 1000 jump b-a_4 tcp dport 1010 jump b-a_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-public DROP " level info flags skuid drop } chain b-public_6 { tcp dport 1000 jump b-a_6 tcp dport 1010 jump b-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-public DROP " level info flags skuid drop } chain b-a { meta nfproto vmap { ipv4 : jump b-a_4, ipv6 : jump b-a_6 } } chain b-a_4 { jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-a DROP " level info flags skuid drop } chain b-a_6 { jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-a DROP " level info flags skuid drop } chain b-b { meta nfproto vmap { ipv4 : jump b-b_4, ipv6 : jump b-b_6 } } chain b-b_4 { tcp dport 1000 jump b-a_4 jump allow_icmp_4 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_4, untracked : jump smurfs_4 } fib daddr type { broadcast, multicast } drop update @_lograte_set_4 { ip saddr limit rate 1/second burst 3 packets } log prefix "b-b DROP " level info flags skuid drop } chain b-b_6 { tcp dport 1000 jump b-a_6 jump allow_icmp_6 ct state vmap { established : accept, related : accept, invalid : jump invalid_drop, new : jump smurfs_6, untracked : jump smurfs_6 } fib daddr type multicast drop update @_lograte_set_6 { ip6 saddr limit rate 1/second burst 3 packets } log prefix "b-b 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.33/test/Makefile000066400000000000000000000015311521001665700157260ustar00rootroot00000000000000# Foomuuri - Multizone bidirectional nftables firewall. SUBDIRS ?= $(sort $(wildcard ??-*)) SOURCE ?= ../src/foomuuri ../src/tests/ ../prometheus/prometheus-foomuuri-exporter .PHONY: all lint test clean distclean $(SUBDIRS) ifdef COVERAGE export RUN = unshare --map-root-user --net coverage run --append --data-file=$(CURDIR)/../.coverage else export RUN = python3 endif all: lint test lint: ruff check $(SOURCE) ruff format --diff $(SOURCE) ty check --ignore=unresolved-import --ignore=unused-ignore-comment $(SOURCE) pylint $(SOURCE) test: $(SUBDIRS) cd ../ ; $(RUN) -m unittest clean distclean: rm -f */*.fw rm -f */iplist-cache.json rm -f */zone $(SUBDIRS): [ ! -f $@/Makefile ] || $(MAKE) -C $@ $(RUN) ../src/foomuuri --set=etc_dir=$@ --set=share_dir=../etc --set=state_dir=$@ --set=run_dir=$@ check diff -u $@/golden.txt $@/next.fw