This is an old revision of the document!


IPFW NG (WIP)

This is a page for work-in-progress ipfw modernization project, IPFW NetGraph (or Next Generation if you insist, but that words are worn out). This page is more developer-oriented, but please keep in mind that CLI interface must be user-friendly (being more user-friendly at that level is oen of the goals).

Our ipfw(4) is good, but outdated a lot. It's a crazy mix of shiny blocks, brilliant ideas and ancient squeaky farts. It's opcode-based structure, amongst some other cool things, has very great potential - it theory - which often stays still unimplemented in practice. And everyone who had to manage rulesets of thousands rules knows it is manageable, but requires effort. In fact, in some cases even half-a-hundred-rules setup for doing something non-trivial and complex could be very hard to grok - imagine e.g. a NAT for several ext channels with “hairpinning” (from LAN to gw's ext addr so then again to LAN) port redirects.

So this drives the motivation to do ipfw's… rewrite? No. Rewriting anything from scratch is almost always a very bad idea and loss of userbase accustomed to previous syntax etc. and angry due to new bugs. So this will be rework, preserving as many backwards compatibility for users (POLA) as possible. Given that ipfw already has many good ideas in it's internals, this is doable.

The first thing that simplifies ruleset management and was demanded by users, was ability to support multiple rulesets. That is, a thing like Cisco ACLs - one can have a separate ruleset for every interface/direction (in or out). Something slightly more good is a thing like pf anchors or iptables “chains” - when one can call a chain like a subroutine and then return from it. I've already implemented ipfw call/ipfw return actions (available in 8.3R and 9.0R), but that was just a hack very quick to implement, a stub for those who need at least something to ease maintainability immediately. A more complete consistent solution must be created.

The second big thing in ipfw which is inadequate to current needs is dynamic rules. But they still have to be rewritten just to support multiple rulesets, as it will be shown below.

To be generic and Right Thing™, this is a HUGE work, because we need to implement the following:

  • each ipfw chain becomes independent netgraph(4) node
  • generic ng_pfil node usable not only for firewalls
  • chains may be called from each other (see iptables)
  • chains (actually netgraph nodes) may be bind to ifaces or any other place
  • main unnamed chain is called from pfil(9) as before
  • rewrite ipfw & dummynet management from setsockopt() to netgraph messages
  • completely rewrite ipfw dynamic rules to not conflict with multiple rulesets, as now they just jump to parent static rule (need to be more like pf or iptables states). This item is hard but essential (you'll get a mess jumping to another ruleset), and ipfw contexts don't handle ot
  • while here, do other needed things, e.g. adding support for modules in both kernel and userland, loadable opcodes, keywords, etc.
  • what yet? may be generic instructions and “registers” like in bpf(4) to support arythmetics etc. in rules? like e.g. denying packets every other second
ipfw chain mychain create
ipfw chain common create default-policy allow
ipfw chain lanusers create
ipfw chain mychain add 200 ...
ipfw chain mychain delete 300 ...
ipfw chain mychain destroy
ipfw add 100 call common ip from any to any   # call from main ruleset
ipfw ng_bpf mypktbody create
ipfw ng_bpf mypktbody config proto udp and udp[42:4]=0x01020304
ipfw add 200 deny ngmatch mypktbody
ipfw bind mychain to interface em0 direction out
ipfw bind lanusers to interface vlan100
ipfw pfil-order move ipfilter bottom

Hope that's enough user-intuitive. And netgraph-based design here is very straightforward: for examples above, you just create netgraph nodes named “ipfwmychain”, “ipfwcommon”, etc., then do send NGM_IPFW_ADD message to the node with name of appropriate “chain” keyword, or main ng_ipfw if none.

Netgraph here is not a stack, but a clean alternative to hack raw_ip.c for new setsockopt()'s. Currently, ipfw is not very modular - to implement a new command, you need to introduce new socket option and put into a file totally unrelated to ipfw. And support part's of ipfw code there - just to check if module is loaded, for example. Or invent a home-grown protocol to multiplex all new commands into one optname. And try to support previous versions (it was hard to keep compatible interfaces when ABI changed in 8.2)… all of that sounds not hard and not important, but still ipfw's control is nowadays a total mess (remember at least ipfw nat broken buffer for several major releases…).

And in Netgraph all that problems are already solved in a clean way (netgraph messages do provide clean versioning, etc.). As a bonus, advanced user is able to manually assign node (ruleset lives in node) in complex configuration to some non-typical point (usually the /sbin/ipfw will do). In fact, Netgraph main use here is to provide something like Linux' Netlink alternative for us

NB: For typical uses all Netgraph complexity must be hidden from user behind the habitual /sbin/ipfw. This must follow the mpd's model - you may know something about Netgraph, but you don't have to (as a consequence, mpd is widely used, but Netgraph itself, scary to user, is not). The system, however, should be transparent to advanced user knowing what he is doing

The main is that it is just a pointer to static rule:

  • suppose you have router with interfaces X, Y.
  • you have TCP/UDP session from A to B, incoming on X and leaving through Y
  • you have per-interface rules, for X on input, for Y on output

Then, if a packet entered via iface X, and there is a state (dynamic rule) there, then, whatever you write in rules for Y - the first keep-state will accept packet, no matter what rule, it will be accepted.

You can try to expoit the fact that in ipfw, dynamic rule just jumps to parent rule's action part, and, if that was “skipto”, continues on a static ruleset (where it could be checked, which interface). But this becomes unmaintainable mess even on 3-4 interfaces (it's like assembly language programming), and if you have hundreds of ifaces, you may encounter there is not enough numbers in 65536 to handle all X*Y combinations (not all ifaces need be stateful) even if config is somehow generated automatically. There is company which really comes up to such situation, and ipfw context can't handle it.

Really, ipfw and many other components of network subsytem suffer from the fact each component is forced to do it's own connection state tracking, e.g. NAT, dynamic rules, Netflow, all are separate. This can be good sometimes, but:

  • this consumes memory
  • it's a PITA when you try to combine them, e.g. ipfw nat + dynamic rules can cause kernel panics, infinite recursion, etc.

We need unified state tracking in our firewall which will do all needed operations, it will be like Linux' conntrack or pf's states, in that it must:

  • support NAT and redirects natively, including NAT64 (RFC 6146)
  • be designed the way to support “ipfwsync” (a-la pfsync)
  • be able to be manipulated well and to support related connections: e.g. dynamic rule for ftp-data connection will be related to main dynamic rule, or another subsystem is able to insert a dynrule in EXPECTED state for yet not started connection (useful e.g. for P2P tracking).

Modules

We need loadable modules able to implement loadable opcodes. For example, leave first 128 opcodes for built-in functions (present nowadays) and other 128 for dynamically registered modules. Currently there is only one module, ipfw_nat, and it is implemented as a hack. DIFFUSE is another example, it tried better but still not in the tree. In fact, having a framework for modules will encourage people to write them, DIFFUSE may have been done and committed much earlier if we had a framework, who knows…

Another application of modules is utilizing hooks in dynamic rules. E.g. a helper for tracking FTP to allow data connections.

Parser in /sbin/ipfw

Totally a mess now, but we can't rewrite it to lex/yacc because all historical quirks of syntax do not fit into clean grammar. Given that modules are wanted, the idea should be borrowed from GEOM - a userland module for each kernel module, consistent keywords in the beginning of command line with passing rest of it to module, etc.

More human-friendly and morer readable? Let's introduce a directory /etc/ipfw where files, if present, will allow to control behaviour. E.g. we can have /etc/ipfw/tables listing pairs number and name, just like /etc/protocols do (idea stolen from route tables naming in file in iproute2). Benefits:

  • One can have any comfortable mapping of strings to table (e.g. unique client contract ID to table) without wasting space for string names in kernel memory.
  • Machine-friendly: pf have names, but compiles them to internal numbers on every reload, and this is not visible to other subsystem. Keeping numbers in file still allows everything in the system use the same numbers (let's be open and extensible).

TO BE CONTINUED

  • vadimgoncharov/ipfwng.1335392918.txt.gz
  • Last modified: 2012/04/25 22:28
  • by VadimGoncharov