diff --git a/netjsonconfig/backends/openwrt/converters/firewall.py b/netjsonconfig/backends/openwrt/converters/firewall.py index 7bbe233d0..7893344d5 100644 --- a/netjsonconfig/backends/openwrt/converters/firewall.py +++ b/netjsonconfig/backends/openwrt/converters/firewall.py @@ -64,6 +64,11 @@ def __intermediate_zones(self, zones): resultdict = OrderedDict( ((".name", self.__get_auto_name_zone(zone)), (".type", "zone")) ) + # If network contains only a single value, force the use of a UCI "option" + # rather than "list"". + network = zone["network"] + if len(network) == 1: + zone["network"] = network[0] resultdict.update(zone) result.append(resultdict) return result @@ -109,6 +114,10 @@ def to_netjson_loop(self, block, result, index): rule = self.__netjson_rule(block) result["firewall"].setdefault("rules", []) result["firewall"]["rules"].append(rule) + if _type == "zone": + zone = self.__netjson_zone(block) + result["firewall"].setdefault("zones", []) + result["firewall"]["zones"].append(zone) return self.type_cast(result) @@ -124,3 +133,19 @@ def __netjson_rule(self, rule): rule["proto"] = [proto] return self.type_cast(rule) + + def __netjson_zone(self, zone): + network = zone["network"] + # network may be specified as a list in a single string e.g. + # option network 'wan wan6' + # Here we ensure that network is always a list. + if not isinstance(network, list): + zone["network"] = network.split() + + if "mtu_fix" in zone: + zone["mtu_fix"] = zone.pop("mtu_fix") == "1" + + if "masq" in zone: + zone["masq"] = zone.pop("masq") == "1" + + return self.type_cast(zone) diff --git a/tests/openwrt/test_firewall.py b/tests/openwrt/test_firewall.py index 6a79ec32f..be5c23ae5 100644 --- a/tests/openwrt/test_firewall.py +++ b/tests/openwrt/test_firewall.py @@ -176,3 +176,109 @@ def test_render_rule_4(self): def test_parse_rule_4(self): o = OpenWrt(native=self._rule_4_uci) self.assertEqual(o.config, self._rule_4_netjson) + + _zone_1_netjson = { + "firewall": { + "zones": [ + { + "name": "lan", + "input": "ACCEPT", + "output": "ACCEPT", + "forward": "ACCEPT", + "network": ["lan"], + "mtu_fix": True, + } + ] + } + } + + _zone_1_uci = textwrap.dedent( + """\ + package firewall + + config defaults 'defaults' + + config zone 'zone_lan' + option name 'lan' + option input 'ACCEPT' + option output 'ACCEPT' + option forward 'ACCEPT' + option network 'lan' + option mtu_fix '1' + """ + ) + + def test_render_zone_1(self): + o = OpenWrt(self._zone_1_netjson) + expected = self._tabs(self._zone_1_uci) + self.assertEqual(o.render(), expected) + + def test_parse_zone_1(self): + o = OpenWrt(native=self._zone_1_uci) + self.assertEqual(o.config, self._zone_1_netjson) + + _zone_2_netjson = { + "firewall": { + "zones": [ + { + "name": "wan", + "input": "DROP", + "output": "ACCEPT", + "forward": "DROP", + "network": ["wan", "wan6"], + "mtu_fix": True, + "masq": True, + } + ] + } + } + + _zone_2_uci = textwrap.dedent( + """\ + package firewall + + config defaults 'defaults' + + config zone 'zone_wan' + option name 'wan' + option input 'DROP' + option output 'ACCEPT' + option forward 'DROP' + list network 'wan' + list network 'wan6' + option mtu_fix '1' + option masq '1' + """ + ) + + # This one is the same as _zone_2_uci with the exception that the "network" + # parameter is specified as a single string. + _zone_3_uci = textwrap.dedent( + """\ + package firewall + + config defaults 'defaults' + + config zone 'zone_wan' + option name 'wan' + option input 'DROP' + option output 'ACCEPT' + option forward 'DROP' + option network 'wan wan6' + option mtu_fix '1' + option masq '1' + """ + ) + + def test_render_zone_2(self): + o = OpenWrt(self._zone_2_netjson) + expected = self._tabs(self._zone_2_uci) + self.assertEqual(o.render(), expected) + + def test_parse_zone_2(self): + o = OpenWrt(native=self._zone_2_uci) + self.assertEqual(o.config, self._zone_2_netjson) + + def test_parse_zone_3(self): + o = OpenWrt(native=self._zone_3_uci) + self.assertEqual(o.config, self._zone_2_netjson)