Browse Source

Nice 'n shiny Python helper functions to auto enhance and auto generate network config.

Signed-off-by: Maximilian Wilhelm <max@rfc2324.org>
Maximilian Wilhelm 8 years ago
parent
commit
1202b8ebb9
1 changed files with 551 additions and 0 deletions
  1. 551 0
      _modules/ffho_net.py

+ 551 - 0
_modules/ffho_net.py

@@ -0,0 +1,551 @@
+#!/usr/bin/python
+
+import re
+
+mac_prefix = "f2"
+
+vrf_table_map = {
+	'vrf_external' : 1023,
+}
+
+
+sites = None
+
+def _get_site_no (sites_config, site_name):
+	global sites
+
+	if sites == None:
+		sites = {}
+		for site in sites_config:
+			if site.startswith ("_"):
+				continue
+
+			sites[site] = sites_config[site].get ("site_no", -2)
+
+	return sites.get (site_name, -1)
+
+
+#
+# Generate a MAC address after the format f2:dd:dd:ss:nn:nn where
+#  dd:dd	is the hexadecimal reprensentation of the nodes device_id
+#    ff:ff	representing the gluon nodes
+#
+#  ss		is the hexadecimal reprensentation of the site_id the interface is connected to
+#
+#  nn:nn	is the decimal representation of the network the interface is connected to, with
+#    00:00	being the dummy interface
+#    00:01	being an inter-gw-vpn interface
+#    00:04	being an nodes fastd tunnel interface of IPv4 transport
+#    00:06	being an nodes fastd tunnel interface of IPv6 transport
+#    02:xx	being a connection to local Vlan 2xx
+#    07:xx	being a VXLAN tunnel for site ss, with xx being a consecutive number
+#    1b:24	being the ibss 2.4GHz bssid
+#    1b:05	being the ibss 5GHz bssid
+#    ff:ff	being the gluon next-node interface
+def gen_batman_iface_mac (site_no, device_no, network):
+	net_type_map = {
+		'dummy'   : 0,
+		'intergw' : 1,
+		'nodes4'  : 4,
+		'nodes6'  : 6,
+	}
+
+	# Well-known network type?
+	if network in net_type_map:
+		network = net_type_map[network]
+
+	if type (network) == int:
+		last = re.sub (r'(\d{2})(\d{2})', '\g<1>:\g<2>', "%04d" % network)
+	else:
+		last = "ee:ee"
+
+	# Convert device_no to hex, format number to 4 digits with leading zeros and : betwwen 2nd and 3rd digit
+	device_no_hex = re.sub (r'([0-9a-fA-F]{2})([0-9a-fA-F]{2})', '\g<1>:\g<2>', "%04x" % int (device_no))
+	# Format site_no to two digit number with leading zero
+	site_no_hex = "%02d" % int (site_no)
+
+	return "%s:%s:%s:%s" % (mac_prefix, device_no_hex, site_no_hex, last)
+
+
+#
+# Default parameters added to any given bonding/bridge interface,
+# if not specified at the interface configuration.
+default_bond_config = {
+	'bond-mode': '802.3ad',
+	'bond-min-links': '1',
+	'bond-xmit-hash-policy': 'layer3+4'
+}
+
+default_bridge_config = {
+	'bridge-fd' : '0',
+	'bridge-stp' : 'no'
+}
+
+
+#
+# Hop penalty to set if none is explicitly specified
+# Check if one of these roles is configured for any given node, use first match.
+default_hop_penalty_by_role = {
+	'bbr'       :  5,
+	'bras'      : 50,
+	'batman_gw' : 50,
+}
+batman_role_evaluation_order = [ 'bbr', 'batman_gw', 'bras' ]
+
+# Gather B.A.T.M.A.N. related config options for real batman devices (e.g. bat0)
+# as well as for batman member interfaces (e.g. eth0.100, fastd ifaces etc.)
+def _update_batman_config (node_config, iface, sites_config):
+	try:
+		node_batman_hop_penalty = int (node_config['batman']['hop-penalty'])
+	except KeyError,ValueError:
+		node_batman_hop_penalty = None
+
+	iface_config = node_config['ifaces'][iface]
+	iface_type = iface_config.get ('type', 'inet')
+	batman_config = {}
+
+	for item, value in iface_config.items ():
+		if item.startswith ('batman-'):
+			batman_config[item] = value
+			iface_config.pop (item)
+
+	# B.A.T.M.A.N. device (e.g. bat0)
+	if iface_type == 'batman':
+		if 'batman-hop-penalty' not in batman_config:
+			# If there's a hop penalty set for the node, but not for the interface
+			# apply the nodes hop penalty
+			if node_batman_hop_penalty:
+				batman_config['batman-hop-penalty'] = node_batman_hop_penalty
+
+			# If there's no hop penalty set for the node, use a default hop penalty
+			# for the roles the node might have, if any
+			else:
+				node_roles = node_config.get ('roles', [])
+				for role in batman_role_evaluation_order:
+					if role in node_roles:
+						batman_config['batman-hop-penalty'] = default_hop_penalty_by_role[role]
+
+		# If batman ifaces were specified as a list - which they should -
+		# generate a sorted list of interface names as string representation
+		if 'batman-ifaces' in batman_config and type (batman_config['batman-ifaces']) == list:
+			batman_iface_str = " ".join (sorted (batman_config['batman-ifaces']))
+			batman_config['batman-ifaces'] = batman_iface_str
+
+	# B.A.T.M.A.N. member interface (e.g. eth.100, fastd ifaces, etc.)
+	elif iface_type == 'batman_iface':
+		# Generate unique MAC address for every batman iface, as B.A.T.M.A.N.
+		# will get puzzled with multiple interfaces having the same MAC and
+		# do nasty things.
+
+		site = iface_config.get ('site')
+		site_no = _get_site_no (sites_config, site)
+		device_no = node_config.get ('id')
+
+		network = 1234
+		# Generate a unique BATMAN-MAC for this interfaces
+		match = re.search (r'^vlan(\d+)', iface)
+		if match:
+			network = int (match.group (1))
+
+		iface_config['hwaddress'] = gen_batman_iface_mac (site_no, device_no, network)
+
+	iface_config['batman'] = batman_config
+
+
+# Mangle bond specific config items with default values and store them in
+# separate sub-dict for easier access and configuration.
+def _update_bond_config (config):
+	bond_config = default_bond_config.copy ()
+
+	for item, value in config.items ():
+		if item.startswith ('bond-'):
+			bond_config[item] = value
+			config.pop (item)
+
+	if bond_config['bond-mode'] not in ['2', 'balance-xor', '4', '802.3ad']:
+		bond_config.pop ('bond-xmit-hash-policy')
+
+	config['bond'] = bond_config
+
+
+# Mangle bridge specific config items with default values and store them in
+# separate sub-dict for easier access and configuration.
+def _update_bridge_config (config):
+	bridge_config = default_bridge_config.copy ()
+
+	for item, value in config.items ():
+		if item.startswith ('bridge-'):
+			bridge_config[item] = value
+			config.pop (item)
+
+		# Fix and salt mangled string interpretation back to real string.
+		if type (value) == bool:
+			bridge_config[item] = "yes" if value else "no"
+
+	# If bridge ports were specified as a list - which they should -
+	# generate a sorted list of interface names as string representation
+	if 'bridge-ports' in bridge_config and type (bridge_config['bridge-ports']) == list:
+		bridge_ports_str = " ".join (sorted (bridge_config['bridge-ports']))
+		bridge_config['bridge-ports'] = bridge_ports_str
+
+	config['bridge'] = bridge_config
+
+
+# Move vlan specific config items into a sub-dict for easier access and pretty-printing
+# in the configuration file
+def _update_vlan_config (config):
+	vlan_config = {}
+
+	for item, value in config.items ():
+		if item.startswith ('vlan-'):
+			vlan_config[item] = value
+			config.pop (item)
+
+	config['vlan'] = vlan_config
+
+
+# Generate configuration entries for any batman related interfaces not
+# configured explicitly, but asked for implicitly by role batman and a
+# (list of) site(s) specified in the node config.
+def _generate_batman_interface_config (node_config, ifaces, sites_config):
+	# No role 'batman', nothing to do
+	if 'batman' not in node_config.get ('roles', []):
+		return
+
+	device_no = node_config.get ('id', -1)
+
+	for site in node_config.get ('sites', []):
+		bat_site_if = "bat-%s" % site
+		dummy_site_if = "dummy-%s" % site
+		site_no = _get_site_no (sites_config, site)
+
+		# Create bat-<site> interface config
+		if bat_site_if not in ifaces:
+			ifaces[bat_site_if] = {
+				'type' : 'batman',
+				'batman-ifaces' : [ dummy_site_if ],
+				'batman-ifaces-ignore-regex': '.*_.*',
+			}
+
+		# Create dummy-<site> interfaces config to ensure bat-<site> can
+		# be successfully configured (read: comes up)
+		if not dummy_site_if in ifaces:
+			ifaces[dummy_site_if] = {
+				'link-type' : 'dummy',
+				'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'dummy')
+			}
+
+
+	# Make sure there is a bridge present for every site where a mesh_breakout
+	# interface should be configured.
+	for iface, config in ifaces.items ():
+		iface_type = config.get ('type', 'inet')
+		if iface_type not in ['mesh_breakout', 'batman_iface']:
+			continue
+
+		site = config.get ('site')
+		site_bridge = "br-%s" % site
+		batman_site_if = "bat-%s" % site
+
+		if iface_type == 'mesh_breakout':
+			# If the bridge has already been defined (with an IP maybe) make
+			# sure that the corresbonding batman device is part of the bridge-
+			# ports.
+			if site_bridge in ifaces:
+				bridge_config = ifaces.get (site_bridge)
+
+				# If there already is/are (a) bridge-port(s) defined, add
+				# the batman and the breakout interfaces if not present...
+				bridge_ports = bridge_config.get ('bridge-ports', None)
+				if bridge_ports:
+					for dev in (batman_site_if, iface):
+						if not dev in bridge_ports:
+							if type (bridge_ports) == list:
+								bridge_ports.append (dev)
+							else:
+								bridge_config['bridge-ports'] += ' ' + dev
+
+				# ...if there is no bridge-port defined yet, just used
+				# the batman and breakout iface.
+				else:
+					bridge_config['bridge-ports'] = [ iface, batman_site_if ]
+
+			# If the bridge isn't present alltogether, add it.
+			else:
+				ifaces[site_bridge] = {
+					'bridge-ports' : [ iface, batman_site_if ],
+				}
+
+		elif iface_type == 'batman_iface':
+			batman_ifaces = ifaces[bat_site_if]['batman-ifaces']
+			if iface not in batman_ifaces:
+				if type (batman_ifaces) == list:
+					batman_ifaces.append (iface)
+				else:
+					batman_ifaces += ' ' + iface
+
+
+## Generate VXLAN tunnels for every configured batman peer for every site
+## configured on this and the peer node.
+#def _generate_vxlan_interface_config_complex (node_config, ifaces, node_id, nodes_config):
+#	# No role 'batman', nothing to do
+#	if 'batman' not in node_config.get ('roles', []):
+#		return
+#
+#	# No batman peers configred, nothing to do
+#	try:
+#		peers = node_config['batman']['peers']
+#		if type (peers) != list:
+#			return
+#	except KeyError:
+#		return
+#
+#	# Sites configured on this node. Nothing to do, if none.
+#	my_sites = node_config.get ('sites', [])
+#	if len (my_sites) == 0:
+#		return
+#
+#	device_no = node_config.get ('id', -1)
+#
+#	# ...
+#	for peer in peers:
+#		try:
+#			# Try to get node config of peer
+#			peer_config = nodes_config.get (peer)
+#
+#			# Not a batman node?
+#			if not 'batman' in peer_config['roles']:
+#				continue
+#
+#			# Verify we are in peers list of peer
+#			peers_of_peer = peer_config['batman']['peers']
+#			if type (peers_of_peer) != list:
+#				continue
+#			if node_id not in peers_of_peer:
+#				continue
+#
+#			# Get sites configured on peers
+#			sites_of_peer = peer_config.get ('sites')
+#		except KeyError:
+#			continue
+#
+#		for site in my_sites:
+#			if site not in sites_of_peer:
+#				continue
+#
+#			# Build tunnel here
+
+def _generate_vxlan_interface_config (node_config, ifaces, sites_config):
+	# No role 'batman', nothing to do
+	if 'batman' not in node_config.get ('roles', []):
+		return
+
+	# Sites configured on this node. Nothing to do, if none.
+	my_sites = node_config.get ('sites', [])
+	if len (my_sites) == 0:
+		return
+
+	# As we're still here we can now safely assume that a B.A.T.M.A.N.
+	# device has been configured for every site specified in sites list.
+
+	device_no = node_config.get ('id', -1)
+
+	for iface, iface_config in ifaces.items ():
+		batman_connect_sites = iface_config.get ('batman_connect_sites', [])
+
+		# If we got a string, convert it to a list with a single element
+		if type (batman_connect_sites) == str:
+			batman_connect_sites = [ batman_connect_sites ]
+
+		# If the string 'all' is part of the list, blindly use all sites configured for this node
+		if 'all' in batman_connect_sites:
+			batman_connect_sites = my_sites
+
+		for site in batman_connect_sites:
+			# Silenty ignore sites not configured on this node
+			if site not in my_sites:
+				continue
+
+			# iface_name := vx_<last 5 chars of underlay iface>_<site> stripped to 15 chars
+			vx_iface = "vx_%s_%s" % (re.sub ('vlan', 'v', iface)[-5:], site)[:15]
+			site_no = _get_site_no (sites_config, site)
+			vni = 100 + site_no
+			bat_iface = "bat-%s" % site
+
+			try:
+				iface_id = int (re.sub ('vlan', '', iface))
+
+				# Gather interface specific mcast address.
+				# The address is derived from the vlan-id of the underlying interface,
+				# assuming that it in fact is a vlan interface.
+				# Mangle the vlan-id into two 2 digit values, eliminating any leading zeros.
+				iface_id_4digit = "%04d" % iface_id
+				octet2 = int (iface_id_4digit[0:2])
+				octet3 = int (iface_id_4digit[2:4])
+				mcast_ip = "225.%s.%s.%s" % (octet2, octet3, site_no)
+
+				vni = octet2 * 256 * 256 + octet3 * 256 + site_no
+			except ValueError:
+				iface_id = 9999
+				mcast_ip = "225.0.0.%s" % site_no
+				vni = site_no
+
+			# bail out if VXLAN tunnel already configured
+			if vx_iface in ifaces:
+				continue
+
+			# If there's no batman interface for this site, there's no point
+			# in setting up a VXLAN interfaces
+			if bat_iface not in ifaces:
+				continue
+
+			# Add the VXLAN interface
+			ifaces[vx_iface] = {
+				'vxlan' : {
+					'vxlan-id'        : vni,
+					'vxlan-svcnodeip' : mcast_ip,
+					'vxlan-physdev'   : iface,
+				},
+				'hwaddress'       : gen_batman_iface_mac (site_no, device_no, iface_id),
+			}
+
+			# If the batman interface for this site doesn't have any interfaces
+			# set up - which basicly cannot happen - add this VXLAN tunnel as
+			# the first in the list.
+			if not 'batman-ifaces' in ifaces[bat_iface]:
+				ifaces[bat_iface]['batman-ifaces'] = [ vx_iface ]
+				continue
+
+			# In the hope there already are interfaces for batman set up already
+			# add this VXLAN tunnel to the list
+			batman_ifaces = ifaces[bat_iface]['batman-ifaces']
+			if vx_iface not in batman_ifaces:
+				if type (batman_ifaces) == list:
+					batman_ifaces.append (vx_iface)
+				else:
+					batman_ifaces += ' ' + vx_iface
+
+
+def _generate_vrfs (ifaces):
+	for iface, iface_config in ifaces.items ():
+		vrf = iface_config.get ('vrf', None)
+		if vrf and vrf not in ifaces:
+			ifaces[vrf] = {
+				'vrf-table' : vrf_table_map.get (vrf, 1234)
+			}
+
+
+GRE_FFRL_attrs = {
+	'mode'   : 'gre',
+	'method' : 'tunnel',
+	'mtu'    : '1400',
+	'ttl'    : '64',
+}
+
+
+def _generate_ffrl_gre_tunnels (ifaces):
+	for iface, iface_config in ifaces.items ():
+		# We only care for GRE_FFRL type interfaces
+		if iface_config.get ('type', '') != 'GRE_FFRL':
+			continue
+
+		# Copy default values to interface config
+		for attr, val in GRE_FFRL_attrs.items ():
+			if not attr in iface_config:
+				iface_config[attr] = val
+
+		# Guesstimate local IPv4 tunnel endpoint address from tunnel-physdev
+		if not 'local' in iface_config and 'tunnel-physdev' in iface_config:
+			try:
+				physdev_prefixes = [p.split ('/')[0] for p in ifaces[iface_config['tunnel-physdev']]['prefixes'] if '.' in p]
+				if len (physdev_prefixes) == 1:
+					iface_config['local'] = physdev_prefixes[0]
+			except KeyError:
+				pass
+
+
+def get_interface_config (nodes_config, node_id, sites_config):
+	# Get config of this node and dict of all configured ifaces
+	node_config = nodes_config.get (node_id, {})
+	ifaces = node_config.get ('ifaces', {})
+
+	# Generate configuration entries for any batman related interfaces not
+	# configured explicitly, but asked for implicitly by role <batman> and
+	# a (list of) site(s) specified in the node config.
+	_generate_batman_interface_config (node_config, ifaces, sites_config)
+
+	# Generate VXLAN tunnels for every interfaces specifying 'batman_connect_sites'
+	_generate_vxlan_interface_config (node_config, ifaces, sites_config)
+
+	# Enhance ifaces configuration with some meaningful defaults for
+	# bonding, bridge and vlan interfaces, MAC address for batman ifaces, etc.
+	for interface, config in ifaces.items ():
+		iface_type = config.get ('type', 'inet')
+
+		if 'batman-ifaces' in config or iface_type.startswith ('batman'):
+			_update_batman_config (node_config, interface, sites_config)
+
+		if 'bond-slaves' in config:
+			_update_bond_config (config)
+
+		# FIXME: This maybe will not match on bridges without any member ports configured!
+		if 'bridge-ports' in config or interface.startswith ('br-'):
+			_update_bridge_config (config)
+
+		if 'vlan-raw-device' in config or 'vlan-id' in config:
+			_update_vlan_config (config)
+
+	# Auto generated VRF devices for any VRF found in ifaces and not already configured.
+	_generate_vrfs (ifaces)
+
+	# Pimp GRE_FFRL type inteface configuration with default values
+	_generate_ffrl_gre_tunnels (ifaces)
+
+	# Drop any config parameters used in node interface configuration not
+	# relevant anymore for config file generation.
+	for interface, config in ifaces.items ():
+		for key in [ 'batman_connect_sites', 'ospf', 'site', 'type' ]:
+			if key in config:
+				config.pop (key)
+	# This leaves 'auto', 'prefixes' and 'desc' as keys which should not be directly
+	# printed into the remaining configuration. These are handled within the jinja
+	# interface template.
+
+	return ifaces
+
+
+# Generate entries for /etc/bat-hosts for every batman interface we will configure on any node.
+# For readability purposes superflous/redundant information is being stripped/supressed.
+# As these names will only show up in batctl calls with a specific site, site_names in interfaces
+# are stripped. Dummy interfaces are stripped as well.
+def gen_bat_hosts (nodes_config, sites_config):
+	bat_hosts = {}
+
+	for node_id in sorted (nodes_config.keys ()):
+		node_config = nodes_config.get (node_id)
+		node_name = node_id.split ('.')[0]
+
+		ifaces = get_interface_config (nodes_config, node_id, sites_config)
+		for iface in sorted (ifaces):
+			iface_config = ifaces.get (iface)
+
+			hwaddress = iface_config.get ('hwaddress', None)
+			if hwaddress == None:
+				continue
+
+			entry_name = node_name
+			if not iface.startswith ('dummy-'):
+				entry_name += "/%s" % re.sub (r'^(vx_.*)_(.*)$', '\g<1>', iface)
+
+			bat_hosts[hwaddress] = entry_name
+
+		if 'fastd' in node_config.get ('roles', []):
+			device_no = node_config.get ('id')
+			for site in node_config.get ('sites', []):
+				site_no = _get_site_no (sites_config, site)
+
+				for network in ('intergw', 'nodes4', 'nodes6'):
+					hwaddress = gen_batman_iface_mac (site_no, device_no, network)
+					bat_hosts[hwaddress] = "%s/%s" % (node_name, network)
+
+	return bat_hosts