diff --git a/docs/source/backends/openwrt.rst b/docs/source/backends/openwrt.rst index 2ba56c85a..127a4be9e 100644 --- a/docs/source/backends/openwrt.rst +++ b/docs/source/backends/openwrt.rst @@ -327,6 +327,8 @@ There are 4 main types of interfaces: - **bridge interfaces**: must be of type ``bridge`` - **dialup interfaces**: must be of type ``dialup`` - **modem manager interfaces**: must be of type ``modem-manager`` +- **mesh interfaces**: must be of type ``mesh`` or ``batadv_hardif`` +- **batman-adv interfaces**: must be of type ``batadv`` Interface object extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3244,6 +3246,201 @@ Will be rendered as follows: option public_key '94a+MnZSdzHCzOy5y2K+0+Xe7lQzaa4v7lEiBZ7elVE=' option route_allowed_ips '1' +BATMAN-adv (Better Approach To Mobile Ad-hoc Networking) +--------------------------------------------------------- + +``OpenWrt`` backend includes support for BATMAN-adv, a mesh networking protocol +that operates on Layer 2. BATMAN-adv is designed to provide decentralized mesh +networking capabilities with automatic route optimization. + +BATMAN-adv Interface Settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The main BATMAN-adv interface (``batadv`` type) represents the virtual mesh +interface and supports the following settings: + +====================== ======= ============== ===================================== +key name type default allowed values +====================== ======= ============== ===================================== +``type`` string ``batadv`` ``batadv`` +``proto`` string ``batadv`` ``batadv`` +``name`` string required interface name (e.g., ``bat0``) +``routing_algo`` string ``BATMAN_IV`` ``BATMAN_IV``, ``BATMAN_V`` +``bridge_loop_avoid.`` boolean ``True`` enable bridge loop avoidance +``gw_mode`` string ``off`` ``off``, ``client``, ``server`` +``hop_penalty`` integer ``30`` penalty for each hop (0-255) +``mtu`` integer ``1500`` Maximum Transmission Unit +``fragmentation`` boolean ``True`` enable fragmentation +====================== ======= ============== ===================================== + +.. note:: + **bridge_loop_avoidance** is shortened as ``bridge_loop_avoid.`` in the + table above due to space constraints. Use the full name + ``bridge_loop_avoidance`` in your configuration. + +BATMAN-adv Hard Interface Settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Hard interfaces (``batadv_hardif`` or ``mesh`` type) are the physical or +wireless interfaces that participate in the BATMAN-adv mesh network: + +============ ====== ========= ============================================= +key name type default description +============ ====== ========= ============================================= +``type`` string required ``batadv_hardif`` or ``mesh`` +``proto`` string required ``batadv_hardif`` +``name`` string required logical interface name +``device`` string required physical device name (e.g., ``phy1-mesh0``) +``master`` string ``bat0`` name of the BATMAN-adv master interface +============ ====== ========= ============================================= + +.. note:: + The ``mesh`` type is a convenience alias for ``batadv_hardif`` and is + typically used with 802.11s wireless mesh interfaces. Both types are + converted to the same ``batadv_hardif`` protocol in the OpenWrt + configuration. + +BATMAN-adv Interface Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following *configuration dictionary*: + +.. code-block:: python + + { + "interfaces": [ + { + "type": "batadv", + "proto": "batadv", + "name": "bat0", + "routing_algo": "BATMAN_V", + "bridge_loop_avoidance": True, + "gw_mode": "server", + "hop_penalty": 30, + "mtu": 1500, + "fragmentation": True, + } + ] + } + +Will be rendered as follows: + +:: + + package network + + config interface 'bat0' + option bridge_loop_avoidance '1' + option fragmentation '1' + option gw_mode 'server' + option hop_penalty '30' + option mtu '1500' + option proto 'batadv' + option routing_algo 'BATMAN_V' + +BATMAN-adv Hard Interface Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following *configuration dictionary*: + +.. code-block:: python + + { + "interfaces": [ + { + "type": "batadv_hardif", + "proto": "batadv_hardif", + "name": "batmesh", + "device": "phy1-mesh0", + "master": "bat0", + } + ] + } + +Will be rendered as follows: + +:: + + package network + + config interface 'batmesh' + option device 'phy1-mesh0' + option master 'bat0' + option proto 'batadv_hardif' + +Mesh Interface (802.11s) Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For wireless mesh interfaces using 802.11s, you can use the ``mesh`` type as +a convenient alias: + +.. code-block:: python + + { + "interfaces": [ + { + "type": "mesh", + "proto": "batadv_hardif", + "name": "batmesh", + "device": "phy1-mesh0", + "master": "bat0", + } + ] + } + +This will produce the same output as the previous example. + +Complete BATMAN-adv Mesh Network Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here's a complete example showing a BATMAN-adv mesh network with a main +interface and a hard interface: + +.. code-block:: python + + { + "interfaces": [ + { + "type": "batadv", + "proto": "batadv", + "name": "bat0", + "routing_algo": "BATMAN_V", + "bridge_loop_avoidance": True, + "gw_mode": "off", + "hop_penalty": 30, + "mtu": 1500, + "fragmentation": True, + }, + { + "type": "mesh", + "proto": "batadv_hardif", + "name": "batmesh", + "device": "phy1-mesh0", + "master": "bat0", + } + ] + } + +Will be rendered as follows: + +:: + + package network + + config interface 'bat0' + option bridge_loop_avoidance '1' + option fragmentation '1' + option gw_mode 'off' + option hop_penalty '30' + option mtu '1500' + option proto 'batadv' + option routing_algo 'BATMAN_V' + + config interface 'batmesh' + option device 'phy1-mesh0' + option master 'bat0' + option proto 'batadv_hardif' + All the other settings ---------------------- diff --git a/netjsonconfig/backends/openwrt/converters/interfaces.py b/netjsonconfig/backends/openwrt/converters/interfaces.py index eef1008bf..82989acbb 100644 --- a/netjsonconfig/backends/openwrt/converters/interfaces.py +++ b/netjsonconfig/backends/openwrt/converters/interfaces.py @@ -238,6 +238,32 @@ def _intermediate_vxlan(self, interface): interface["vid"] = interface.pop("vni") return interface + def _intermediate_batadv(self, interface): + interface["proto"] = "batadv" + interface.setdefault("routing_algo", "BATMAN_IV") + interface["bridge_loop_avoidance"] = int( + interface.get("bridge_loop_avoidance", True) + ) + interface.setdefault("gw_mode", "off") + interface.setdefault("hop_penalty", 30) + interface.setdefault("mtu", 1500) + interface["fragmentation"] = int(interface.get("fragmentation", True)) + interface.pop("ifname", None) + return interface + + def _intermediate_batadv_hardif(self, interface): + interface["proto"] = "batadv_hardif" + interface["device"] = interface.pop("device", interface.get("ifname", None)) + interface.setdefault("master", "bat0") + interface.pop("enabled", None) + interface.pop("ifname", None) + return interface + + def _intermediate_mesh(self, interface): + interface = self._intermediate_batadv_hardif(interface) + interface["proto"] = "batadv_hardif" + return interface + def _intermediate_8021_vlan(self, interface): interface["name"] = "{}.{}".format(interface["ifname"], interface["vid"]) interface[".name"] = interface.get("network") or "vlan_{}_{}".format( @@ -931,6 +957,38 @@ def _netjson_vxlan(self, interface): interface["port"] = interface["port"] return self.type_cast(interface, schema=self._vxlan_schema) + def _netjson_batadv(self, interface): + interface["type"] = "batadv" + # Convert numeric values back to booleans/ints for schema compliance + interface["bridge_loop_avoidance"] = bool( + int(interface.get("bridge_loop_avoidance", 1)) + ) + interface["fragmentation"] = bool(int(interface.get("fragmentation", 1))) + return interface + + def _netjson_batadv_hardif(self, interface): + interface["type"] = "batadv_hardif" + # Ensure proto is set + if "proto" not in interface: + interface["proto"] = "batadv_hardif" + # Move device from name to device field + if "name" in interface and "device" not in interface: + interface["device"] = interface["name"] + interface["name"] = interface.get( + "network", interface.get("ifname", "batmesh") + ) + if "enabled" in interface: + interface["enabled"] = bool(int(interface["enabled"])) + # Remove network if it's same as name + if interface.get("network") == interface.get("name"): + interface.pop("network", None) + return interface + + def _netjson_mesh(self, block): + data = self._netjson_batadv_hardif(block) + data["type"] = "mesh" + return data + def __netjson_address(self, address, interface): ip = ip_interface(address) family = "ipv{0}".format(ip.version) diff --git a/netjsonconfig/backends/openwrt/converters/wireguard_peers.py b/netjsonconfig/backends/openwrt/converters/wireguard_peers.py index a1f38168c..71ed3cb29 100644 --- a/netjsonconfig/backends/openwrt/converters/wireguard_peers.py +++ b/netjsonconfig/backends/openwrt/converters/wireguard_peers.py @@ -10,6 +10,13 @@ class WireguardPeers(OpenWrtConverter): # wireguard OpenWRT package, this is unpredictable _uci_types = None + def should_skip_block(self, block): + """ + Override should_skip_block to only process blocks with .type starting with 'wireguard_' + """ + _type = block.get(".type", "") + return not _type.startswith("wireguard_") + def to_intermediate_loop(self, block, result, index=None): result.setdefault("network", []) result["network"].append(self.__intermediate_peer(block, index)) diff --git a/netjsonconfig/backends/openwrt/schema.py b/netjsonconfig/backends/openwrt/schema.py index 2daf1983e..7fa38223f 100644 --- a/netjsonconfig/backends/openwrt/schema.py +++ b/netjsonconfig/backends/openwrt/schema.py @@ -735,6 +735,119 @@ {"$ref": "#/definitions/base_interface_settings"}, ], }, + "batadv_interface": { + "title": "BATMAN-adv Interface", + "type": "object", + "required": ["type", "proto", "name"], + "allOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["batadv"], + "default": "batadv", + "propertyOrder": 1, + }, + "proto": { + "type": "string", + "enum": ["batadv"], + "default": "batadv", + "propertyOrder": 2, + }, + "name": {"type": "string", "propertyOrder": 3}, + "routing_algo": { + "type": "string", + "enum": ["BATMAN_IV", "BATMAN_V"], + "default": "BATMAN_IV", + "propertyOrder": 4, + }, + "bridge_loop_avoidance": { + "type": "boolean", + "format": "checkbox", + "default": True, + "propertyOrder": 5, + }, + "gw_mode": { + "type": "string", + "enum": ["off", "client", "server"], + "default": "off", + "propertyOrder": 6, + }, + "hop_penalty": { + "type": "integer", + "default": 30, + "propertyOrder": 7, + }, + "mtu": { + "type": "integer", + "default": 1500, + "propertyOrder": 8, + }, + "fragmentation": { + "type": "boolean", + "format": "checkbox", + "default": True, + "propertyOrder": 9, + }, + } + }, + {"$ref": "#/definitions/base_interface_settings"}, + ], + }, + "batadv_hardif_interface": { + "title": "BATMAN-adv Hard Interface", + "type": "object", + "required": ["type", "proto", "name", "device", "master"], + "allOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["batadv_hardif"], + "default": "batadv_hardif", + "propertyOrder": 1, + }, + "proto": { + "type": "string", + "enum": ["batadv_hardif"], + "default": "batadv_hardif", + "propertyOrder": 2, + }, + "name": {"type": "string", "propertyOrder": 3}, + "device": {"type": "string", "propertyOrder": 4}, + "master": {"type": "string", "propertyOrder": 5}, + } + }, + {"$ref": "#/definitions/base_interface_settings"}, + ], + }, + "mesh_interface": { + "title": "Mesh Interface (802.11s)", + "type": "object", + "required": ["type", "proto", "name", "device", "master"], + "allOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["mesh"], + "default": "mesh", + "propertyOrder": 1, + }, + "proto": { + "type": "string", + "enum": ["batadv_hardif"], + "default": "batadv_hardif", + "propertyOrder": 2, + }, + "name": {"type": "string", "propertyOrder": 3}, + "device": {"type": "string", "propertyOrder": 4}, + "master": {"type": "string", "propertyOrder": 5}, + } + }, + {"$ref": "#/definitions/base_interface_settings"}, + ], + }, "base_radio_settings": { "properties": { "driver": { @@ -973,6 +1086,9 @@ {"$ref": "#/definitions/wireguard_interface"}, {"$ref": "#/definitions/vlan_8021q"}, {"$ref": "#/definitions/vlan_8021ad"}, + {"$ref": "#/definitions/batadv_interface"}, + {"$ref": "#/definitions/batadv_hardif_interface"}, + {"$ref": "#/definitions/mesh_interface"}, ] } }, diff --git a/netjsonconfig/schema.py b/netjsonconfig/schema.py index 66729e712..d6c994a6c 100644 --- a/netjsonconfig/schema.py +++ b/netjsonconfig/schema.py @@ -295,6 +295,19 @@ "maximum": 2346, "propertyOrder": 12, }, + "mesh_fwding": { + "type": "string", + "title": "mesh forwarding", + "description": "802.11s mesh forwarding", + "enum": ["0", "1"], + "propertyOrder": 13, + }, + "mesh_rssi_threshold": { + "type": "string", + "title": "mesh RSSI threshold", + "description": "802.11s mesh RSSI threshold", + "propertyOrder": 14, + }, }, }, "ssid_wireless_property": { @@ -407,7 +420,7 @@ "propertyOrder": 20, "oneOf": [ {"$ref": "#/definitions/encryption_none"}, - {"$ref": "#/definitions/encryption_wpa3_personal"}, + {"$ref": "#/definitions/encryption_wpa3_personal_mesh"}, {"$ref": "#/definitions/encryption_wpa_personal"}, {"$ref": "#/definitions/encryption_wep"}, ], @@ -472,6 +485,21 @@ } }, }, + "encryption_cipher_mesh": { + "properties": { + "cipher": { + "type": "string", + "enum": ["auto", "ccmp"], + "options": { + "enum_titles": [ + "auto", + "Force CCMP (AES)", + ] + }, + "propertyOrder": 3, + } + } + }, "encryption_mfp_property": { "properties": { "ieee80211w": { @@ -545,6 +573,22 @@ }, ], }, + "encryption_wpa3_personal_mesh": { + "title": "WPA3 Personal (Mesh)", + "allOf": [ + {"$ref": "#/definitions/encryption_base_settings"}, + {"$ref": "#/definitions/encryption_cipher_mesh"}, + { + "properties": { + "protocol": { + "enum": ["wpa3_personal"], + "options": {"enum_titles": ["WPA3 Personal"]}, + }, + "key": {"minLength": 8}, + } + }, + ], + }, "encryption_wpa3_personal_mixed": { "title": "WPA3/WPA2 Personal Mixed Mode", "allOf": [