ipfw: flesh out IP_FW3 OPVER_0 compat layer as ipfw_compat
Commit 4a77657cbc01 ("ipfw: migrate ipfw to 32-bit size rule numbers")
broke the IP_FW3 sockopt ABI: the in-kernel rule representation now
uses 32-bit rule numbers and 32-bit named-object indexes, while old
ipfw(8) binaries from 14.x and earlier speak OPVER_0 with 16-bit
fields. The companion ip_fw_compat.c module added in that commit was
left as an "example": only XADD/XDEL were wired up, the remaining v0
handlers returned EOPNOTSUPP, and the module was not registered in
the build.
Implement enough of the compat layer to make the common 14.x ipfw(8)
operations work against a 15.x kernel:
- clear_rules_v0, move_rules_v0, manage_sets_v0: convert the v0 ipfw_range_tlv into the v1 layout via the existing check_range_tlv_v0() and dispatch to the shared kernel helpers.
- dump_config_v0: mirror dump_config() but emit ipfw_obj_ntlv_v0 (16-bit idx) for named objects and a v0-encoded cmd stream for each rule. Add convert_v1_to_v0() as the inverse of the existing convert_v0_to_v1(): O_{CHECK,KEEP,PROBE}_STATE, O_EXTERNAL_{ACTION,INSTANCE}, O_LIMIT, O_IP_*_LOOKUP, O_MAC_*_LOOKUP, O_SKIPTO and O_CALLRETURN are repacked back into their pre-15.x shapes; rules with rulenum > IPFW_DEFAULT_RULE are hidden from v0 callers since they cannot be represented. Dynamic state export is not yet handled and 'ipfw -d show' against an old userland will see an empty state list.
- support table management XADD, XDEL, XFIND and XLIST(entries): 14.x ipfw(8) sends opheader.version = 1 for these calls (it has always been the "current" wire version for entry operations), so the request bypasses the OPVER_0 registration entirely and lands in the unmodified v1 handler.
The v1 handlers set ti.uidx = tent->idx and look up the table by searching the tlvs buffer for ntlv.idx == uidx. With a v0 client tent->idx is the legacy marker 1, while ntlv.idx after v1 reinterpretation contains the type/set bytes spilled into the high half (e.g. 0x04000001 for an addr table). The search misses and add_table_entry() returns ESRCH ("table not found").
Hook the conversion in by replacing the v1 registrations of manage_table_ent_v1, dump_table_v1 and find_table_entry with compat wrappers at module load (and reinstating the originals at unload). The wrappers detect a v0 client by oh->spare != 0 (14.x always writes oh->idx = 1, which becomes oh->spare under v1 layout, while 15.x clients leave both fields zero) and rewrite ipfw_obj_header, ipfw_obj_ntlv and each ipfw_obj_tentry in place to canonical v1 layout: idx/spare cleared, set/type pulled from their v0 byte offsets. The tentry rewrite is bounded by the available buffer since it runs before the underlying handler validates ctlv->count. The same wrappers are also registered under OPVER_0 to cover builds that send version=0.
ipfw_table_value itself reordered fields between v0 and v1; that
only matters for ipfw table N add KEY VALUE with a non-zero VALUE
and is left as future work.
To support the above, expose move_range(), clear_range(),
ipfw_swap_sets(), ipfw_enable_sets(), export_rule1(),
export_cntr1_base() and mark_rule_objects() from ip_fw_sockopt.c, and
move struct dump_args to ip_fw_private.h, renaming it to
struct rule_dump_args to avoid clashing with the unrelated, private
struct dump_args in ip_fw_table.c.
Finally, add sys/modules/ipfw_compat/Makefile and register it in
sys/modules/Makefile so the module is actually built. It depends on
ipfw and is loaded as ipfw_compat.ko.
still I have some open questions how to move with this compatibility layer:
- Should it be implement as ipfw_compat (as was drafted in original commit) or better to follow usual route under #ifdef COMPAT_FREEBSD14