Skip to content

Commit

Permalink
[feature] OpenWrt: Added support for new bridge syntax #196
Browse files Browse the repository at this point in the history
- netjsonconfig renders and parses configuration for bridges
in both old and new syntax.
- Added STP and IGMP options in OpenWrt bridge interface schema

Closes #196
  • Loading branch information
pandafy committed Mar 15, 2022
1 parent bd0db29 commit 7929e7e
Show file tree
Hide file tree
Showing 5 changed files with 417 additions and 33 deletions.
118 changes: 114 additions & 4 deletions netjsonconfig/backends/openwrt/converters/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ class Interfaces(OpenWrtConverter):
_uci_types = ['interface', 'globals']

def to_intermediate_loop(self, block, result, index=None):
result.setdefault('network', [])
uci_name = self._get_uci_name(block.get('network') or block['name'])
address_list = self.__intermediate_addresses(block)
interface = self.__intermediate_interface(block, uci_name)
uci_new_bridge = self.__intermediate_new_bridge(interface)
if uci_new_bridge:
result['network'].append(self.sorted_dict(uci_new_bridge))
# create one or more "config interface" UCI blocks
i = 1
for address in address_list:
Expand Down Expand Up @@ -158,6 +162,60 @@ def __intermediate_address(self, address):
del address[key]
return address

def __intermediate_new_bridge(self, interface):
"""
Converts NetJSON bridge to intermediate
data structure compatible with new syntax
introduced in OpenWrt 21.
"""

def _add_additional_options(property_name, property_options, bridge, interface):
if interface.get(property_name, False):
bridge[property_name] = True
for option in property_options:
if interface.get(option):
bridge[option] = interface[option]

if interface['type'] != 'bridge':
return
stp_options = [
'forward_delay',
'hello_time',
'priority',
'ageing_time',
'max_age',
]
igmp_snooping_options = [
'multicast_querier',
'query_interval',
'last_member_interval',
'hash_max',
'robustness',
]
bridge_options = ['vlan_filtering']
bridge = {
'.type': 'device',
'.name': 'device_{}'.format(interface['.name']),
'name': interface['ifname'],
'type': 'bridge',
}
# Only add STP options if STP is enabled
_add_additional_options('stp', stp_options, bridge, interface)

# Only add IGMP snooping options if IGMP snooping is enabled
_add_additional_options(
'igmp_snooping', igmp_snooping_options, bridge, interface
)

for option in bridge_options:
if interface.get(option):
bridge[option] = interface[option]
bridge['ports'] = interface.get('bridge_members')
if bridge['ports'] is None:
bridge['bridge_empty'] = True
del bridge['ports']
return self.sorted_dict(bridge)

def __intermediate_bridge(self, interface, i):
"""
converts NetJSON bridge to
Expand Down Expand Up @@ -229,6 +287,26 @@ def __intermediate_dns_search(self, uci, address):
if dns_search:
return ' '.join(dns_search)

def __update_existing_bridge(self, interface, result):
"""
The UCI configuration might contain configuration
for bridge in both syntaxes (pre and post OpenWrt 21).
This method updates the existing bridge NetJSON if it
finds a repetitive bridge definition.
Returns "True" if the method updated an existing bridge.
Otherwise, returns "False".
"""
if not interface['type'] == 'bridge':
return False
for _interface in result['interfaces']:
if (
interface['name'] == _interface['name']
and interface['type'] == _interface['type']
):
_interface.update(interface)
return True
return False

def to_netjson_loop(self, block, result, index):
_type = block.get('.type')
if _type == 'globals':
Expand All @@ -242,7 +320,8 @@ def to_netjson_loop(self, block, result, index):
interface = self.__netjson_interface(block)
self.__netjson_dns(interface, result)
result.setdefault('interfaces', [])
result['interfaces'].append(interface)
if not self.__update_existing_bridge(interface, result):
result['interfaces'].append(interface)
return result

def __netjson_interface(self, interface):
Expand All @@ -267,14 +346,45 @@ def __netjson_interface(self, interface):
interface = method(interface)
return interface

def __clean_bridge(self, interface):
"""
The two bridge declaration syntaxes (pre and post OpenWrt 21)
defines bridge members differently.
This method parses bridge members from both syntaxes.
It also cleans "name" and "network" field for OpenWrt 21 syntax.
"""
if interface.pop('bridge_21', False):
interface['bridge_members'] = interface.pop('ports', [])
interface['name'] = interface['name'].lstrip('device_')
interface['network'] = interface['network'].lstrip('device_')
else:
interface['bridge_members'] = interface['name'].split()

def __netjson_type(self, interface):
if 'type' in interface and interface['type'] == 'bridge':
interface['bridge_members'] = interface['name'].split()
self.__clean_bridge(interface)
interface['name'] = 'br-{0}'.format(interface['network'])
# cleanup automatically generated "br_" network prefix
interface['name'] = interface['name'].replace('br_', '')
if 'stp' in interface:
interface['stp'] = interface['stp'] == '1'
for option in ['stp', 'igmp_snooping', 'multicast_querier']:
if option in interface:
interface[option] = interface[option] == '1'
for option in [
'forward_delay',
'hello_time',
'max_age',
'priority',
'query_interval',
'query_response_interval',
'last_member_interval',
'hash_max',
'robustness',
]:
if option in interface:
try:
interface[option] = int(interface[option])
except ValueError:
del interface[option]
if interface.pop('bridge_empty', None) == '1':
interface['bridge_members'] = []
return 'bridge'
Expand Down
7 changes: 7 additions & 0 deletions netjsonconfig/backends/openwrt/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,12 @@ def _get_uci_blocks(self, text):
# list options
else:
block[key] = block.get(key, []) + [value]
# The new bridge syntax of OpenWrt moved "bridges"
# under "device" config_type. netjsonconfig
# process bridges using the interface convertor,
# therefore we need to update block type here.
if block['.type'] == 'device' and block.get('type') == 'bridge':
block['.type'] = 'interface'
block['bridge_21'] = True
blocks.append(sorted_dict(block))
return blocks
117 changes: 116 additions & 1 deletion netjsonconfig/backends/openwrt/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,123 @@
"description": "sets the \"multicast_snooping\" kernel setting for a bridge",
"default": True,
"format": "checkbox",
"propertyOrder": 5,
},
"multicast_querier": {
"type": "boolean",
"title": "IGMP multicast querier",
"description": (
"enables the bridge as a mutlicast querier,"
" which keeps the multicast group to port mappings current."
),
"default": True,
"format": "checkbox",
"propertyOrder": 5,
},
"query_interval": {
"type": "integer",
"title": "IGMP query interval",
"description": (
"time interval in centiseconds between"
" multicast general queries"
),
"default": 12500,
"propertyOrder": 5,
},
"query_response_interval": {
"type": "integer",
"title": "IGMP query response interval",
"description": (
"the max response time in centiseconds inserted into"
" the periodic general queries"
),
"default": 1000,
"propertyOrder": 5,
},
"last_member_interval": {
"type": "integer",
"title": "IGMP last member interval",
"description": (
"The max response time in centiseconds inserted into"
" group-specific queries sent in response to leave group messages."
),
"default": 100,
"propertyOrder": 5,
},
"hash_max": {
"type": "integer",
"title": "IGMP hash max",
"description": "size of kernel multicast hash table",
"default": 512,
"propertyOrder": 5,
},
"robustness": {
"type": "integer",
"title": "IGMP Robustness",
"description": "sets Startup Query Count and Last Member Count",
"default": 2,
"propertyOrder": 5,
},
"forward_delay": {
"type": "integer",
"title": "STP forward delay",
"description": (
"Time in seconds to spend in listening"
" and learning states",
),
"default": 4,
"minimum": 2,
"maximum": 30,
"propertyOrder": 4,
}
},
"hello_time": {
"type": "integer",
"title": "STP hello time",
"description": "time interval in seconds for STP hello packets",
"default": 2,
"minimum": 1,
"maximum": 10,
"propertyOrder": 4,
},
"priority": {
"type": "integer",
"title": "STP priority",
"description": "STP bridge priority",
"default": 32767,
"minimum": 0,
"maximum": 65535,
"propertyOrder": 4,
},
"ageing_time": {
"type": "integer",
"title": "STP ageing time",
"description": (
"expiration time in seconds for dynamic MAC"
" entries in the filtering DB"
),
"default": 300,
"minimum": 10,
"maximum": 1000000,
"propertyOrder": 4,
},
"max_age": {
"type": "integer",
"title": "STP max age",
"description": (
"timeout in seconds until topology updates on link loss"
),
"default": 20,
"minimum": 0,
"maximum": 40,
"propertyOrder": 4,
},
"vlan_filtering": {
"type": "boolean",
"title": "VLAN filtering",
"description": "enabled VLAN aware bridge mode",
"default": False,
"propertyOrder": 5,
},
}
}
]
Expand Down

0 comments on commit 7929e7e

Please sign in to comment.