Hi folks, last month I wrote to this list [1] with a suggestion on how to extend the current firewall package (aka uci_firewall) to support IPv6 rules. I got a bit side tracked in between but after a lot of trying around and refactoring I've got a working (and I think nice) solution. I dubbed it firewall2, ie. bumped the PKG_VERSION (the PKG_RELEASE is currently at 0b1, I call that beta1 :).
I wrote [PATCH] in the subject but didn't really attach a patch but a tarball containing the files. You can also get and browser the files from my Git repository at Gitorious [2]. A real patch didn't make much sense because I moved the code around and refactored it a lot. The original code felt like spaghetti code (and had some small bugs obviously caused by that) so to truly grok what it does and glue in my iptables wrapper I started with some splitted the monolithic uci_firewall.sh into a bunch of separate files. Here's a diffstat anyway: package/firewall/Makefile | 10 +- package/firewall/files/20-firewall | 35 -- package/firewall/files/bin/fw | 47 +++ package/firewall/files/firewall.config | 28 +- package/firewall/files/firewall.hotplug | 18 + package/firewall/files/firewall.init | 18 +- package/firewall/files/lib/config.sh | 79 +++++ package/firewall/files/lib/core.sh | 216 ++++++++++++ package/firewall/files/lib/core_forwarding.sh | 40 +++ package/firewall/files/lib/core_interface.sh | 70 ++++ package/firewall/files/lib/core_redirect.sh | 54 +++ package/firewall/files/lib/core_rule.sh | 56 +++ package/firewall/files/lib/core_zone.sh | 72 ++++ package/firewall/files/lib/fw.sh | 164 +++++++++ package/firewall/files/lib/uci_firewall.sh | 5 + package/firewall/files/uci_firewall.sh | 457 ------------------------- 16 files changed, 855 insertions(+), 514 deletions(-) The files in lib are installed to /lib/firewall as they are. It would be trivial to merge/compile them into a single file to save space, but I checked and installed on JFFS, the split version takes up 20 kB while a merged version needs 18.5 kB. Not sure its worth it. So, let me try to summarize what I changed and the further plans I have: To start, I prefixed all functions with fw_, all global variables with FW_ and made sure that all variables are either localized or named in a way they won't clash with other globals, like stuff from /lib/network. Stuff like calling a variable interfaces and have stuff breaking at weird places really made my head hurt. Second, I didn't change any (IPv4) behaviour, just added things (like the IPv6 stuff). I tried to verify this by tracing the iptable calls and make sure they are the same in the same order. But typos are possible, so it would be great if somebody with a complex setup could verify this. From the user's perspective there is only one change: You can now specify both IPv4 and IPv6 addresses as src_ip and dest_ip in your firewall rules and everything works as expected, at least if ip6tables is installed (if not, the IPv6 rules are silently skipped). Even better, if you don't specify a src_ip or dest_ip, the rules are applied to both rulesets. This is based on my assumption that if you generally want to block access to a certain port, you probably don't want to have a gaping hole in your firewall just because you accidently enabled IPv6. So the new firewall should work exactly as before, just better :) Behind the scenes there's the obvious split into separate files. uci_firewall.sh is only a placeholder for backwards compatibility and to cope with opkg not removing old files when updating a package (or does it?). The rest can be categorized in three types of files: config.sh -- Here I introduced a function fw_config_get_section. I hoped to have something like this (ie. config_get_section) moved into /lib/config because it should be useful at other places/packages as well. I'll explain it in detail in a separate mail. core*.sh -- These bundle the firewall logic formerly found in the uci_firewall.sh. My plan is to make the firewall pluggable (more on this later), so the prefix "core" in /lib/firewall is reserved for the firewall itself. * core.sh contains more or less the public "API" and the init script is just a very thin wrapper around the start, stop, reload and restart functions found here. Also, here the defaults are loaded, includes are handled, general stuff like that. * The files core_rule.sh, core_forwarding.sh and core_redirect.sh represent these three types of firewall rules/sections in the config files. These are called in a config_foreach in core.sh. This is also true for core_zone.sh contains the setup of the helper tables and jumps for each zone definition. * Finally the stuff in core_interface.sh is called when interfaces come and go. That code was previously split between the hotplug file (which is now only a thin wrapper) and some functions in the core. I think especially that code path is now a lot easier to understand. * I already mentioned that I plan to make this firewall pluggable. It will work by packages just throwing scripts/libraries into /lib/firewall; these can define functions which are called via simple after and before hooks. I planned hooks for core, zone and interface. Ie. if you'd create a file /lib/firewall/foo.sh, the firewall looks for functions called foo_before_zone, foo_after_core, etc. and calls these when that stuff changes. fw.sh -- This, or more precise the function fw, is the wrapper which does all the magic. It has an interface of which an extended version also available via the script fw (installed in /sbin) on the command line. I'll first explain the command line interface because it's a bit nicer. I'll put an extensive documentation of these commands into the wiki and/or the official docs if the patch itsels is accepted. The general syntax boils down to what iptables actions are about and are always (some parameters are optional): fw $command $family $table $chain $target $position { $rules } Ie. execute $command for $family on table $table's chain $chain (at position $position) and make it jump to $target if $rules apply. Yes, the curly braces are part of the syntax. There is also an version with two blocks where this actually makes sense. I guess its easiest to explain by examples: # Enable forwarding for both IPv4 and IPv6 fw add ip filter FORWARD ACCEPT # Block access to the port mapper fw add ip filter INPUT DENY { --dport portmap } # Create a chain http_ip4_server fw add ip filter http_ip4_server # Oops, we wanted to create this for IPv4 only, delete this chain fw del ip6 filter http_ip4_filter # All IPv4 access to HTTP should go thru the rule above. The caret # means prepend this rule to the chain (the opposite would be the $ # but appending is default anyway). fw add ip4 filter INPUT http_ip4_server ^ { --proto tcp --dport 80 } # We can also specify a position by number, this is the same rule # as above. fw add ip4 filter INPUT http_ip4_server 1 { --proto tcp --dport 80 } # Remove that rule again. fw del ip4 filter INPUT http_ip4_server { --proto tcp --dport 80 } # Remove the first rule. We dont want to specify a target, so use # a dash. fw del ip4 filter INPUT - 1 # Beware, magic: Do what I say, but chose the correct protocol # family automatically based on the IP address(es) in the first # block of braces. ADDR=192.0.2.1 fw add ip nat PREROUTING DNAT { $ADDR } { \ --dport 443 \ --to-destination $ADDR \ } # Adding support for arptables... fw add arp filter IN DROP { -z aa:bb:cc:dd:ee:ff } # ... and ebtables was trivial, only a line for each. fw add eth filter INPUT DROP { -s aa:bb:cc:dd:ee:ff } I wrote before that the external syntax is a bit nicer than the internal one: The internal uses one-letter family identifiers (4, 6, i, e, a) and can't magically detect the two-brace version, you've got to use an upper case I for that. I first made this for performance reasons but I thought about it and think that I could also move the case from the script into the function, effectively making the second parameter an option: -4, -6, -i, -e or -a where -i is the default and decides between -i and -I based on the existence of the second brace block. The current wrapper doesn't really make a difference compared to the plain iptables yet and I doubt one case will make a measurable difference. Also, there is not default table as you might have noticed, you've always got to specify filter if you edit it. But there are abbreviations: filter, mangle, nat and raw can be written as f, m, n, and r on the command line, saving bytes all over the scripts ;) The fw command doesn't do add'ing and del'ing, it also knows the commands start, stop, restart, reload (which call the init script) and flush (for a table), policy (for a built-in chain) and list (always with -vn). Also there is a command called has which just returns a result code: # Is ip6tables installed? fw has ip6 # Are both iptables and ip6tables installed? fw has ip # Does IPv6 have a nat table? fw has ip6 nat These commands are used internally to ensure that rules are added only to tables which are supported. This is currently used to filter out the MASQUERADING rules for IPv6: ip6tables doesn't have a nat table per default. I've got to find a better, more explicit solution for this or things will break horribly for some people. I tested the firewall quite a bit but not with a very complex rule set. I really need somebody to do this, if you need help with installing the package, I can help you (on IRC as well). Oh, and the fw script has a nice feature: export FW_TRACE=1 and it will tell you about all calls to iptables it does. Any other comments are appreciated as well :) Cheers, Malte [1]http://thread.gmane.org/gmane.comp.embedded.openwrt.devel/3387 [2]http://gitorious.org/sixwrt/packages/trees/master/package/firewall --
firewall.tar.gz
Description: application/compressed-tar
_______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/mailman/listinfo/openwrt-devel