ffho_net.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417
  1. #!/usr/bin/python
  2. import collections
  3. from functools import cmp_to_key
  4. import ipaddress
  5. import re
  6. from copy import deepcopy
  7. import ffho
  8. mac_prefix = "f2"
  9. # VRF configuration map
  10. vrf_info = {
  11. 'vrf_external' : {
  12. 'table' : 1023,
  13. 'fwmark' : [ '0x1', '0x1023' ],
  14. },
  15. 'vrf_mgmt' : {
  16. 'table' : 1042,
  17. },
  18. }
  19. #
  20. # Default parameters added to any given bonding interface,
  21. # if not specified at the interface configuration.
  22. default_bond_config = {
  23. 'bond-mode': '802.3ad',
  24. 'bond-min-links': '1',
  25. 'bond-xmit-hash-policy': 'layer3+4'
  26. }
  27. #
  28. # Default parameters added to any given bridge interface,
  29. # if not specified at the interface configuration.
  30. default_bridge_config = {
  31. 'bridge-fd' : '0',
  32. 'bridge-stp' : 'no',
  33. 'bridge-ports-condone-regex' : '^[a-zA-Z0-9]+_(v[0-9]{1,4}|eth[0-9])$',
  34. }
  35. #
  36. # Hop penalty to be set if none is explicitly specified.
  37. # Check if one of these roles is configured for any given node, use first match.
  38. default_hop_penalty_by_role = {
  39. 'bbr' : 5,
  40. 'bras' : 50,
  41. 'batman_gw' : 50,
  42. 'batman_ext': 50,
  43. }
  44. batman_role_evaluation_order = [ 'bbr', 'batman_gw', 'bras' ]
  45. # By default we do not set any penalty on an interface and rely on the
  46. # regular hop penalty to do the right thing. We only want set another
  47. # penalty, if there are additional paths and we want to influnce which
  48. # path is being taken. One example are routers which have a wifi link
  49. # to the local backbone as well as a VPN link to a gateway, or a fiber
  50. # link plus a wifi backup link.
  51. default_batman_iface_penalty_by_role = {
  52. 'default' : 0,
  53. 'WBBL' : 5,
  54. 'WBBL_backup' : 10,
  55. 'VPN_intergw' : 80,
  56. 'VPN_node' : 100,
  57. }
  58. #
  59. # Default interface attributes to be added to GRE interface to AS201701 when
  60. # not already present in pillar interface configuration.
  61. GRE_FFRL_attrs = {
  62. 'mode' : 'gre',
  63. 'method' : 'tunnel',
  64. 'mtu' : '1400',
  65. 'ttl' : '64',
  66. }
  67. # The IPv4/IPv6 prefix used for Loopback IPs
  68. loopback_prefix = {
  69. 'v4' : '10.132.255.',
  70. 'v6' : '2a03:2260:2342:ffff::',
  71. }
  72. # MTU configuration
  73. MTU = {
  74. # The default MTU for any interface which does not have a MTU configured
  75. # explicitly in the pillar node config or does not get a MTU configured
  76. # by any means of this SDN stuff here.
  77. 'default' : 1500,
  78. # A batman underlay device, probably a VXLAN or VLAN interface.
  79. #
  80. # 1500
  81. # + 60 B.A.T.M.A.N. adv header + network coding (activated by default by Debian)
  82. 'batman_underlay_iface' : 1560,
  83. # VXLAN underlay device, probably a VLAN within $POP or between two BBRs.
  84. #
  85. # 1560
  86. # + 14 Inner Ethernet Frame
  87. # + 8 VXLAN Header
  88. # + 8 UDP Header
  89. # + 20 IPv4 Header
  90. 'vxlan_underlay_iface' : 1610,
  91. }
  92. ################################################################################
  93. # #
  94. # Internal data types and functions #
  95. # #
  96. # Touching anything below will void any warranty you never had ;) #
  97. # #
  98. ################################################################################
  99. ################################################################################
  100. # Data types #
  101. ################################################################################
  102. class Prefix (object):
  103. """An internet address with a prefix length.
  104. The given address is expected to be of format ip/plen in CIDR notation.
  105. The IP as well as the prefix length and address family will be stored
  106. in attributes.
  107. .. code-block:: pycon
  108. >>> a = Prefix ('10.132.23.42/24')
  109. >>> str (a.ip)
  110. '10.132.23.42'
  111. >>> str (a.af)
  112. '4'
  113. >>> str (a.plen)
  114. '24'
  115. >>> str (a.netmask)
  116. '255.255.255.0'
  117. >>> str (a.network_address)
  118. '10.132.23.0'
  119. """
  120. def __init__ (self, prefix):
  121. self.prefix = prefix
  122. self.ip_network = ipaddress.ip_network (u'%s' % prefix, strict = False)
  123. def __eq__ (self, other):
  124. if isinstance (other, Prefix):
  125. return self.ip_network == other.ip_network
  126. return NotImplemented
  127. def __lt__ (self, other):
  128. if isinstance (other, Prefix):
  129. return self.ip_network < other.ip_network
  130. return NotImplemented
  131. def __str__ (self):
  132. return self.prefix
  133. @property
  134. def ip (self):
  135. return self.prefix.split ('/')[0]
  136. @property
  137. def af (self):
  138. return self.ip_network.version
  139. @property
  140. def plen (self):
  141. return self.ip_network.prefixlen
  142. @property
  143. def netmask (self):
  144. return self.ip_network.netmask
  145. @property
  146. def network_address (self):
  147. return self.ip_network.network_address
  148. ################################################################################
  149. # Functions #
  150. ################################################################################
  151. sites = None
  152. def _get_site_no (sites_config, site_name):
  153. global sites
  154. if sites == None:
  155. sites = {}
  156. for site in sites_config:
  157. if site.startswith ("_"):
  158. continue
  159. sites[site] = sites_config[site].get ("site_no", -2)
  160. return sites.get (site_name, -1)
  161. #
  162. # Generate a MAC address after the format f2:dd:dd:ss:nn:nn where
  163. # dd:dd is the hexadecimal reprensentation of the nodes device_id
  164. # ff:ff representing the gluon nodes
  165. #
  166. # ss is the hexadecimal reprensentation of the site_id the interface is connected to
  167. #
  168. # nn:nn is the decimal representation of the network the interface is connected to, with
  169. # 00:00 being the BATMAN interface
  170. # 00:0d being the dummy interface
  171. # 00:0f being the VEth internal side interface
  172. # 00:e0 being an external instance BATMAN interface
  173. # 00:ed being an external instance dummy interface
  174. # 00:e1 being an inter-gw-vpn interface
  175. # 00:e4 being an nodes fastd tunnel interface of IPv4 transport
  176. # 00:e6 being an nodes fastd tunnel interface of IPv6 transport
  177. # 00:ef being an extenral instance VEth interface side
  178. # 02:xx being a connection to local Vlan 2xx
  179. # xx:xx being a VXLAN tunnel for site ss, with xx being the underlay VLAN ID (1xyz, 2xyz)
  180. # ff:ff being the gluon next-node interface
  181. def gen_batman_iface_mac (site_no, device_no, network):
  182. net_type_map = {
  183. 'bat' : "00:00",
  184. 'dummy' : "00:0d",
  185. 'int2ext' : "00:0f",
  186. 'bat-e' : "00:e0",
  187. 'intergw' : "00:e1",
  188. 'nodes4' : "00:e4",
  189. 'nodes6' : "00:e6",
  190. 'dummy-e' : "00:ed",
  191. 'ext2int' : "00:ef",
  192. }
  193. # Well-known network type?
  194. if network in net_type_map:
  195. last = net_type_map[network]
  196. elif type (network) == int:
  197. last = re.sub (r'(\d{2})(\d{2})', '\g<1>:\g<2>', "%04d" % network)
  198. else:
  199. last = "ee:ee"
  200. # Convert device_no to hex, format number to 4 digits with leading zeros and : betwwen 2nd and 3rd digit
  201. device_no_hex = re.sub (r'([0-9a-fA-F]{2})([0-9a-fA-F]{2})', '\g<1>:\g<2>', "%04x" % int (device_no))
  202. # Format site_no to two digit number with leading zero
  203. site_no_hex = "%02d" % int (site_no)
  204. return "%s:%s:%s:%s" % (mac_prefix, device_no_hex, site_no_hex, last)
  205. # Gather B.A.T.M.A.N. related config options for real batman devices (e.g. bat0)
  206. # as well as for batman member interfaces (e.g. eth0.100, fastd ifaces etc.)
  207. def _update_batman_config (node_config, iface, sites_config):
  208. try:
  209. node_batman_hop_penalty = int (node_config['batman']['hop-penalty'])
  210. except (KeyError,ValueError):
  211. node_batman_hop_penalty = None
  212. iface_config = node_config['ifaces'][iface]
  213. iface_type = iface_config.get ('type', 'inet')
  214. batman_config = {}
  215. for item in list (iface_config.keys ()):
  216. value = iface_config.get (item)
  217. if item.startswith ('batman-'):
  218. batman_config[item] = value
  219. iface_config.pop (item)
  220. # B.A.T.M.A.N. device (e.g. bat0)
  221. if iface_type == 'batman':
  222. if 'batman-hop-penalty' not in batman_config:
  223. # If there's a hop penalty set for the node, but not for the interface
  224. # apply the nodes hop penalty
  225. if node_batman_hop_penalty:
  226. batman_config['batman-hop-penalty'] = node_batman_hop_penalty
  227. # If there's no hop penalty set for the node, use a default hop penalty
  228. # for the roles the node might have, if any
  229. else:
  230. node_roles = node_config.get ('roles', [])
  231. for role in batman_role_evaluation_order:
  232. if role in node_roles:
  233. batman_config['batman-hop-penalty'] = default_hop_penalty_by_role[role]
  234. break
  235. if 'batman_ext' in node_roles and iface.endswith('-ext'):
  236. batman_config['batman-hop-penalty'] = default_hop_penalty_by_role['batman_ext']
  237. # If batman ifaces were specified as a list - which they should -
  238. # generate a sorted list of interface names as string representation
  239. if 'batman-ifaces' in batman_config and type (batman_config['batman-ifaces']) == list:
  240. batman_iface_str = " ".join (sorted (batman_config['batman-ifaces']))
  241. batman_config['batman-ifaces'] = batman_iface_str
  242. # B.A.T.M.A.N. member interface (e.g. eth.100, fastd ifaces, etc.)
  243. elif iface_type == 'batman_iface':
  244. # Generate unique MAC address for every batman iface, as B.A.T.M.A.N.
  245. # will get puzzled with multiple interfaces having the same MAC and
  246. # do nasty things.
  247. site = iface_config.get ('site')
  248. site_no = _get_site_no (sites_config, site)
  249. device_no = node_config.get ('id')
  250. network = 1234
  251. # Generate a unique BATMAN-MAC for this interfaces
  252. match = re.search (r'^vlan(\d+)', iface)
  253. if match:
  254. network = int (match.group (1))
  255. iface_config['hwaddress'] = gen_batman_iface_mac (site_no, device_no, network)
  256. iface_config['batman'] = batman_config
  257. # Mangle bond specific config items with default values and store them in
  258. # separate sub-dict for easier access and configuration.
  259. def _update_bond_config (config):
  260. bond_config = default_bond_config.copy ()
  261. to_pop = []
  262. for item, value in config.items ():
  263. if item.startswith ('bond-'):
  264. bond_config[item] = value
  265. to_pop.append (item)
  266. for item in to_pop:
  267. config.pop (item)
  268. if bond_config['bond-mode'] not in ['2', 'balance-xor', '4', '802.3ad']:
  269. bond_config.pop ('bond-xmit-hash-policy')
  270. config['bond'] = bond_config
  271. # Mangle bridge specific config items with default values and store them in
  272. # separate sub-dict for easier access and configuration.
  273. def _update_bridge_config (config):
  274. bridge_config = default_bridge_config.copy ()
  275. for item in list (config.keys ()):
  276. value = config.get (item)
  277. if not item.startswith ('bridge-'):
  278. continue
  279. bridge_config[item] = value
  280. config.pop (item)
  281. # Fix any salt mangled string interpretation back to real string.
  282. if type (value) == bool:
  283. bridge_config[item] = "yes" if value else "no"
  284. # If bridge ports were specified as a list - which they should -
  285. # generate a sorted list of interface names as string representation
  286. if 'bridge-ports' in bridge_config and type (bridge_config['bridge-ports']) == list:
  287. bridge_ports_str = " ".join (sorted (bridge_config['bridge-ports']))
  288. if not bridge_ports_str:
  289. bridge_ports_str = "none"
  290. bridge_config['bridge-ports'] = bridge_ports_str
  291. if config.get ('vlan-mode') == 'tagged':
  292. bridge_config['bridge-vlan-aware'] = 'yes'
  293. if config.get ('tagged_vlans'):
  294. bridge_config['bridge-vids'] = " ".join (map (str, config['tagged_vlans']))
  295. config['bridge'] = bridge_config
  296. # Generate config options for vlan-aware-bridge member interfaces
  297. def _update_bridge_member_config (config):
  298. bridge_config = {}
  299. if config.get ('tagged_vlans'):
  300. bridge_config['bridge-vids'] = " ".join (map (str, config['tagged_vlans']))
  301. config['bridge'] = bridge_config
  302. # Move vlan specific config items into a sub-dict for easier access and pretty-printing
  303. # in the configuration file
  304. def _update_vlan_config (config):
  305. vlan_config = {}
  306. for item in list (config.keys ()):
  307. value = config.get (item)
  308. if item.startswith ('vlan-'):
  309. vlan_config[item] = value
  310. config.pop (item)
  311. config['vlan'] = vlan_config
  312. # Pimp Veth interfaces
  313. # * Add peer interface name IF not present
  314. # * Add link-type veth IF not present
  315. def _update_veth_config (interface, config):
  316. veth_peer_name = {
  317. 'veth_ext2int' : 'veth_int2ext',
  318. 'veth_int2ext' : 'veth_ext2int'
  319. }
  320. if interface not in veth_peer_name:
  321. return
  322. if 'link-type' not in config:
  323. config['link-type'] = 'veth'
  324. if 'veth-peer-name' not in config:
  325. config['veth-peer-name'] = veth_peer_name[interface]
  326. # The given MTU to the given interface - presented by it's interface config dict -
  327. # IFF no MTU has already been set in the node pillar.
  328. #
  329. # @param ifaces: All interface configuration (as dict)
  330. # @param iface_name: Name of the interface to set MTU for
  331. # @param mtu: The MTU value to set (integer)
  332. # When <mtu> is <= 0, the <mtu> configured for <iface_name>
  333. # will be used to set the MTU of the upper interface, and the
  334. # default MTU if none is configured explicitly.
  335. def _set_mtu_to_iface_and_upper (ifaces, iface_name, mtu):
  336. iface_config = ifaces.get (iface_name)
  337. # By default we assume that we should set the given MTU value as the 'automtu'
  338. # attribute to allow distinction between manually set and autogenerated MTU
  339. # values.
  340. set_automtu = True
  341. # If a mtu values <= 0 is given, use the MTU configured for this interface
  342. # or, if none is set, the default value when configuring the vlan-raw-device.
  343. if mtu <= 0:
  344. set_automtu = False
  345. mtu = iface_config.get ('mtu', MTU['default'])
  346. # If this interface already has a MTU set - probably because someone manually
  347. # specified one in the node pillar - we do not touch the MTU of this interface.
  348. # Nevertheless it's worth looking at any underlying interface.
  349. if 'mtu' in iface_config:
  350. set_automtu = False
  351. # There might be - read: "we have" - a situation where on top of e.g. bond0
  352. # there are vlans holding VXLAN communicaton as well as VLANs directly carrying
  353. # BATMAN traffic. Now depending on which interface is evaluated first, the upper
  354. # MTU is either correct, or maybe to small.
  355. #
  356. # If any former autogenerated MTU is greater-or-equal than the one we want to
  357. # set now, we'll ignore it, and go for the greater one.
  358. elif 'automtu' in iface_config and iface_config['automtu'] >= mtu:
  359. set_automtu = False
  360. # If we still consider this a good move, set given MTU to this device.
  361. if set_automtu:
  362. iface_config['automtu'] = mtu
  363. # If this is a VLAN - which it probably is - fix the MTU of the underlying interface, too.
  364. # Check for 'vlan-raw-device' in iface_config and in vlan subconfig (yeah, that's not ideal).
  365. vlan_raw_device = None
  366. if 'vlan-raw-device' in iface_config:
  367. vlan_raw_device = iface_config['vlan-raw-device']
  368. elif 'vlan' in iface_config and 'vlan-raw-device' in iface_config['vlan']:
  369. vlan_raw_device = iface_config['vlan']['vlan-raw-device']
  370. if vlan_raw_device:
  371. vlan_raw_device_config = ifaces.get (vlan_raw_device, None)
  372. # vlan-raw-device might point to ethX which usually isn't configured explicitly
  373. # as ifupdown2 simply will bring it up anyway by itself. To set the MTU of such
  374. # an interface we have to add a configuration stanza for it here.
  375. if vlan_raw_device_config == None:
  376. vlan_raw_device_config = {}
  377. ifaces[vlan_raw_device] = vlan_raw_device_config
  378. # If there is a manually set MTU for this device, we don't do nothin'
  379. if 'mtu' in vlan_raw_device_config:
  380. return
  381. if 'automtu' in vlan_raw_device_config and vlan_raw_device_config['automtu'] >= mtu:
  382. return
  383. vlan_raw_device_config['automtu'] = mtu
  384. # Generate configuration entries for any batman related interfaces not
  385. # configured explicitly, but asked for implicitly by role batman and a
  386. # (list of) site(s) specified in the node config.
  387. def _generate_batman_interface_config (node_config, ifaces, sites_config):
  388. # No role 'batman', nothing to do
  389. roles = node_config.get ('roles', [])
  390. if 'batman' not in roles:
  391. return
  392. # Should there be a 2nd external BATMAN instance?
  393. batman_ext = 'batman_ext' in roles or 'bras' in roles
  394. device_no = node_config.get ('id', -1)
  395. for site in node_config.get ('sites', []):
  396. site_no = _get_site_no (sites_config, site)
  397. # Predefine interface names for regular/external BATMAN instance
  398. # and possible VEth link pair for connecting both instances.
  399. bat_site_if = "bat-%s" % site
  400. dummy_site_if = "dummy-%s" % site
  401. bat_site_if_ext = "bat-%s-ext" % site
  402. dummy_site_if_ext = "dummy-%s-e" % site
  403. int2ext_site_if = "i2e-%s" % site
  404. ext2int_site_if = "e2i-%s" % site
  405. site_ifaces = {
  406. # Regular BATMAN interface, always present
  407. bat_site_if : {
  408. 'type' : 'batman',
  409. # int2ext_site_if will be added automagically if requred
  410. 'batman-ifaces' : [ dummy_site_if ],
  411. 'batman-ifaces-ignore-regex': '.*_.*',
  412. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'bat'),
  413. },
  414. # Dummy interface always present in regular BATMAN instance
  415. dummy_site_if : {
  416. 'link-type' : 'dummy',
  417. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'dummy'),
  418. 'mtu' : MTU['batman_underlay_iface'],
  419. },
  420. # Optional 2nd "external" BATMAN instance
  421. bat_site_if_ext : {
  422. 'type' : 'batman',
  423. 'batman-ifaces' : [ dummy_site_if_ext, ext2int_site_if ],
  424. 'batman-ifaces-ignore-regex': '.*_.*',
  425. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'bat-e'),
  426. 'ext_only' : True,
  427. },
  428. # Optional dummy interface always present in 2nd "external" BATMAN instance
  429. dummy_site_if_ext : {
  430. 'link-type' : 'dummy',
  431. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'dummy-e'),
  432. 'ext_only' : True,
  433. 'mtu' : MTU['batman_underlay_iface'],
  434. },
  435. # Optional VEth interface pair - internal side
  436. int2ext_site_if : {
  437. 'link-type' : 'veth',
  438. 'veth-peer-name' : ext2int_site_if,
  439. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'int2ext'),
  440. 'mtu' : MTU['batman_underlay_iface'],
  441. 'ext_only' : True,
  442. },
  443. # Optional VEth interface pair - "external" side
  444. ext2int_site_if : {
  445. 'link-type' : 'veth',
  446. 'veth-peer-name' : int2ext_site_if,
  447. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'ext2int'),
  448. 'mtu' : MTU['batman_underlay_iface'],
  449. 'ext_only' : True,
  450. },
  451. }
  452. for iface, iface_config_tmpl in site_ifaces.items ():
  453. # Ignore any interface only relevant when role batman_ext is set
  454. # but it isn't
  455. if not batman_ext and iface_config_tmpl.get ('ext_only', False):
  456. continue
  457. # Remove ext_only key so we don't leak it into ifaces dict
  458. if 'ext_only' in iface_config_tmpl:
  459. del iface_config_tmpl['ext_only']
  460. # If there is no trace of the desired iface config yet...
  461. if iface not in ifaces:
  462. # ... just place our template there.
  463. ifaces[iface] = iface_config_tmpl
  464. # If there should be an 2nd external BATMAN instance make sure
  465. # the internal side of the VEth iface pair is connected to the
  466. # internal BATMAN instance.
  467. if batman_ext and iface == bat_site_if:
  468. iface_config_tmpl['batman-ifaces'].append (int2ext_site_if)
  469. # If there already is an interface configuration try to enhance it with
  470. # meaningful values from our template and force correct hwaddress to be
  471. # used.
  472. else:
  473. iface_config = ifaces[iface]
  474. # Force hwaddress to be what we expect.
  475. if 'hwaddress' in iface_config_tmpl:
  476. iface_config['hwaddress'] = iface_config_tmpl['hwaddress']
  477. # Copy every attribute of the config template missing in iface config
  478. for attr in iface_config_tmpl:
  479. if attr not in iface_config:
  480. iface_config[attr] = iface_config_tmpl[attr]
  481. # Configure bat_site_if to be part of br-<site>, if present
  482. site_bridge = "br-%s" % site
  483. if site_bridge in ifaces:
  484. bridge_config = ifaces.get (site_bridge)
  485. bridge_ports = bridge_config.get ('bridge-ports', None)
  486. # There are bridge-ports configured already, but bat_site_if is not present
  487. if bridge_ports and bat_site_if not in bridge_ports:
  488. if type (bridge_ports) == list:
  489. bridge_ports.append (bat_site_if)
  490. else:
  491. bridge_config['bridge-ports'] += ' ' + bat_site_if
  492. # There are no bridge-ports configured
  493. if not bridge_ports:
  494. bridge_config['bridge-ports'] = bat_site_if
  495. # Make sure there is a bridge present for every site where a mesh_breakout
  496. # interface should be configured.
  497. for iface in list (ifaces.keys ()):
  498. config = ifaces.get (iface)
  499. iface_type = config.get ('type', 'inet')
  500. if iface_type not in ['mesh_breakout', 'batman_iface']:
  501. continue
  502. site = config.get ('site')
  503. site_bridge = "br-%s" % site
  504. batman_site_if = "bat-%s" % site
  505. if iface_type == 'mesh_breakout':
  506. # If the bridge has already been defined (with an IP maybe) make
  507. # sure that the corresbonding batman device is part of the bridge-
  508. # ports.
  509. if site_bridge in ifaces:
  510. bridge_config = ifaces.get (site_bridge)
  511. # If there already is/are (a) bridge-port(s) defined, add
  512. # the batman and the breakout interfaces if not present...
  513. bridge_ports = bridge_config.get ('bridge-ports', None)
  514. if bridge_ports:
  515. for dev in (batman_site_if, iface):
  516. if not dev in bridge_ports:
  517. if type (bridge_ports) == list:
  518. bridge_ports.append (dev)
  519. else:
  520. bridge_config['bridge-ports'] += ' ' + dev
  521. # ...if there is no bridge-port defined yet, just used
  522. # the batman and breakout iface.
  523. else:
  524. bridge_config['bridge-ports'] = [ iface, batman_site_if ]
  525. # If the bridge isn't present alltogether, add it.
  526. else:
  527. ifaces[site_bridge] = {
  528. 'bridge-ports' : [ iface, batman_site_if ],
  529. }
  530. elif iface_type == 'batman_iface':
  531. batman_ifaces = ifaces[batman_site_if]['batman-ifaces']
  532. if iface not in batman_ifaces:
  533. if type (batman_ifaces) == list:
  534. batman_ifaces.append (iface)
  535. else:
  536. batman_ifaces += ' ' + iface
  537. # Use the MTU configured for this interface or, if none is set,
  538. # the default value for batman underlay iface.
  539. mtu = config.get('mtu', MTU['batman_underlay_iface'])
  540. _set_mtu_to_iface_and_upper (ifaces, iface, mtu)
  541. #
  542. # Generate any implicitly defined VXLAN interfaces defined in the nodes iface
  543. # defined in pillar.
  544. # The keyword "batman_connect_sites" on an interface will trigger the
  545. # generation of a VXLAN overlay interfaces.
  546. def _generate_vxlan_interface_config (node_config, ifaces, sites_config):
  547. # No role 'batman', nothing to do
  548. if 'batman' not in node_config.get ('roles', []):
  549. return
  550. # Sites configured on this node. Nothing to do, if none.
  551. my_sites = node_config.get ('sites', [])
  552. if len (my_sites) == 0:
  553. return
  554. # As we're still here we can now safely assume that a B.A.T.M.A.N.
  555. # device has been configured for every site specified in sites list.
  556. device_no = node_config.get ('id', -1)
  557. for iface in list (ifaces.keys ()):
  558. iface_config = ifaces.get (iface)
  559. batman_connect_sites = iface_config.get ('batman_connect_sites', [])
  560. iface_has_prefixes = len (iface_config.get ('prefixes', {})) != 0
  561. # If we got a string, convert it to a list with a single element
  562. if type (batman_connect_sites) == str:
  563. batman_connect_sites = [ batman_connect_sites ]
  564. # If the list of sites to connect is empty, there's nothing to do here.
  565. if len (batman_connect_sites) == 0:
  566. continue
  567. # Set the MTU of this (probably) VLAN device to the MTU required for a VXLAN underlay
  568. # device, where B.A.T.M.A.N. adv. is to be expected within the VXLAN overlay.
  569. _set_mtu_to_iface_and_upper (ifaces, iface, MTU['vxlan_underlay_iface'])
  570. # If the string 'all' is part of the list, blindly use all sites configured for this node
  571. if 'all' in batman_connect_sites:
  572. batman_connect_sites = my_sites
  573. for site in batman_connect_sites:
  574. # Silenty ignore sites not configured on this node
  575. if site not in my_sites:
  576. continue
  577. # iface_name := vx_<last 5 chars of underlay iface>_<site> stripped to 15 chars
  578. vx_iface = ("vx_%s_%s" % (re.sub ('vlan', 'v', iface)[-5:], re.sub (r'[_-]', '', site)))[:15]
  579. site_no = _get_site_no (sites_config, site)
  580. bat_iface = "bat-%s" % site
  581. # If there's no batman interface for this site, there's no point
  582. # in setting up a VXLAN interfaces
  583. if bat_iface not in ifaces:
  584. continue
  585. # bail out if VXLAN tunnel already configured
  586. if vx_iface in ifaces:
  587. continue
  588. try:
  589. iface_id = int (re.sub ('vlan', '', iface))
  590. # Gather interface specific mcast address.
  591. # The address is derived from the vlan-id of the underlying interface,
  592. # assuming that it in fact is a vlan interface.
  593. # Mangle the vlan-id into two 2 digit values, eliminating any leading zeros.
  594. iface_id_4digit = "%04d" % iface_id
  595. octet2 = int (iface_id_4digit[0:2])
  596. octet3 = int (iface_id_4digit[2:4])
  597. vni = octet2 * 256 * 256 + octet3 * 256 + site_no
  598. vtep_config = {
  599. 'vxlan-id' : vni,
  600. 'vxlan-physdev' : iface,
  601. }
  602. # If there are prefixes configured on this underlay interface go for legacy IPv4 Multicast (for now)
  603. if iface_has_prefixes:
  604. vtep_config['vxlan-svcnodeip'] = "225.%s.%s.%s" % (octet2, octet3, site_no)
  605. else:
  606. vtep_config['vxlan-remote-group'] = "ff42:%s::%s" % (iface_id, site_no)
  607. except ValueError as v:
  608. vtep_config = {
  609. 'vxlan-config-error' : str (v),
  610. }
  611. iface_id = 9999
  612. mcast_ip = "225.0.0.%s" % site_no
  613. vni = site_no
  614. # Add the VXLAN interface
  615. ifaces[vx_iface] = {
  616. 'vxlan' : vtep_config,
  617. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, iface_id),
  618. 'mtu' : MTU['batman_underlay_iface'],
  619. }
  620. iface_penalty = get_batman_iface_penalty (iface)
  621. if iface_penalty:
  622. ifaces[vx_iface]['batman'] = {
  623. 'batman-hop-penalty' : iface_penalty
  624. }
  625. # If the batman interface for this site doesn't have any interfaces
  626. # set up - which basicly cannot happen - add this VXLAN tunnel as
  627. # the first in the list.
  628. if not 'batman-ifaces' in ifaces[bat_iface]:
  629. ifaces[bat_iface]['batman-ifaces'] = [ vx_iface ]
  630. continue
  631. # In the hope there already are interfaces for batman set up already
  632. # add this VXLAN tunnel to the list
  633. batman_ifaces = ifaces[bat_iface]['batman-ifaces']
  634. if vx_iface not in batman_ifaces:
  635. if type (batman_ifaces) == list:
  636. batman_ifaces.append (vx_iface)
  637. else:
  638. batman_ifaces += ' ' + vx_iface
  639. #
  640. # Generate implicitly defined VRFs according to the vrf_info dict at the top
  641. # of this file
  642. def _generate_vrfs (ifaces):
  643. for iface in list (ifaces.keys ()):
  644. iface_config = ifaces.get (iface)
  645. vrf = iface_config.get ('vrf', None)
  646. if vrf and vrf not in ifaces:
  647. conf = vrf_info.get (vrf, {})
  648. table = conf.get ('table', 1234)
  649. fwmark = conf.get ('fwmark', None)
  650. ifaces[vrf] = {
  651. 'vrf-table' : table,
  652. }
  653. # Create ip rule's for any fwmarks defined
  654. if fwmark:
  655. up = []
  656. # Make sure we are dealing with a list even if there is only one mark to be set up
  657. if type (fwmark) in (str, int):
  658. fwmark = [ fwmark ]
  659. # Create ip rule entries for IPv4 and IPv6 for every fwmark
  660. for mark in fwmark:
  661. up.append ("ip rule add fwmark %s table %s" % (mark, table))
  662. up.append ("ip -6 rule add fwmark %s table %s" % (mark, table))
  663. ifaces[vrf]['up'] = up
  664. def _generate_ffrl_gre_tunnels (ifaces):
  665. for iface, iface_config in ifaces.items ():
  666. # We only care for GRE_FFRL type interfaces
  667. if iface_config.get ('type', '') != 'GRE_FFRL':
  668. continue
  669. # Copy default values to interface config
  670. for attr, val in GRE_FFRL_attrs.items ():
  671. if not attr in iface_config:
  672. iface_config[attr] = val
  673. # Guesstimate local IPv4 tunnel endpoint address from tunnel-physdev
  674. if not 'local' in iface_config and 'tunnel-physdev' in iface_config:
  675. try:
  676. physdev_prefixes = [p.split ('/')[0] for p in ifaces[iface_config['tunnel-physdev']]['prefixes'] if '.' in p]
  677. if len (physdev_prefixes) == 1:
  678. iface_config['local'] = physdev_prefixes[0]
  679. except KeyError:
  680. pass
  681. def _generate_loopback_ips (ifaces, node_config, node_id):
  682. # If this node has primary_ips set and filled there are either IPs
  683. # configured on lo or IPs on another interface - possibly ones on
  684. # the only interface present - are considered as primary IPs.
  685. if node_config.get ('primary_ips', False):
  686. return
  687. v4_ip = "%s/32" % get_primary_ip (node_config, 'v4').ip
  688. v6_ip = "%s/128" % get_primary_ip (node_config, 'v6').ip
  689. # Interface lo already present?
  690. if 'lo' not in ifaces:
  691. ifaces['lo'] = { 'prefixes' : [] }
  692. # Add 'prefixes' list if not present
  693. if 'prefixes' not in ifaces['lo']:
  694. ifaces['lo']['prefixes'] = []
  695. prefixes = ifaces['lo']['prefixes']
  696. if v4_ip not in prefixes:
  697. prefixes.append (v4_ip)
  698. if v6_ip not in prefixes:
  699. prefixes.append (v6_ip)
  700. # Generate interface descriptions / aliases for auto generated or manually
  701. # created interfaces. Currently this only is done for bridges associated
  702. # with BATMAN instanzes.
  703. #
  704. # @param node_config: The configuration of the given node (as dict)
  705. # @param sites_config Global sites configuration (as dict)
  706. def _update_interface_desc (node_config, sites_config):
  707. # Currently we only care for nodes with batman role.
  708. if 'batman' not in node_config.get ('roles', []):
  709. return
  710. for iface, iface_config in node_config.get ('ifaces', {}).items ():
  711. if 'desc' in sites_config:
  712. continue
  713. # If the interface name looks like a bridge for a BATMAN instance
  714. # try to get the name of the corresponding site
  715. match = re.search (r'^br-([a-z_-]+)$', iface)
  716. if match and match.group (1) in sites_config:
  717. try:
  718. iface_config['desc'] = sites_config[match.group (1)]['name']
  719. except KeyError:
  720. pass
  721. ################################################################################
  722. # Public functions #
  723. ################################################################################
  724. # Generate network interface configuration for given node.
  725. #
  726. # This function will read the network configuration from pillar and will
  727. # * enhance it with all default values configured at the top this file
  728. # * auto generate any implicitly configured
  729. # * VRFs
  730. # * B.A.T.M.A.N. instances and interfaces
  731. # * VXLAN interfaces to connect B.A.T.M.A.N. sites
  732. # * Loopback IPs derived from numeric node ID
  733. #
  734. # @param: node_config Pillar node configuration (as dict)
  735. # @param: sites_config Pillar sites configuration (as dict)
  736. # @param: node_id Minion name / Pillar node configuration key
  737. def get_interface_config (node_config, sites_config, node_id = ""):
  738. # Make a copy of the node_config dictionary to suppress side-effects.
  739. # This function deletes some keys from the node_config which will break
  740. # any re-run of this function or other functions relying on the node_config
  741. # to be complete.
  742. node_config = deepcopy (node_config)
  743. # Get config of this node and dict of all configured ifaces
  744. ifaces = node_config.get ('ifaces', {})
  745. # Generate configuration entries for any batman related interfaces not
  746. # configured explicitly, but asked for implicitly by role <batman> and
  747. # a (list of) site(s) specified in the node config.
  748. _generate_batman_interface_config (node_config, ifaces, sites_config)
  749. # Generate VXLAN tunnels for every interfaces specifying 'batman_connect_sites'
  750. _generate_vxlan_interface_config (node_config, ifaces, sites_config)
  751. # Enhance ifaces configuration with some meaningful defaults for
  752. # bonding, bridge and vlan interfaces, MAC address for batman ifaces, etc.
  753. for interface in list (ifaces.keys ()):
  754. config = ifaces.get (interface)
  755. iface_type = config.get ('type', 'inet')
  756. # Remove any disable interfaces here as they aren't relevant for /e/n/i
  757. if config.get ('enabled', True) == False:
  758. del ifaces[interface]
  759. continue
  760. # Ignore interfaces used for PPPoE
  761. if 'pppoe' in config.get ('tags', []):
  762. del ifaces[interface]
  763. continue
  764. if 'batman-ifaces' in config or iface_type.startswith ('batman'):
  765. _update_batman_config (node_config, interface, sites_config)
  766. if 'bond-slaves' in config:
  767. _update_bond_config (config)
  768. # FIXME: This maybe will not match on bridges without any member ports configured!
  769. if 'bridge-ports' in config or interface.startswith ('br-'):
  770. _update_bridge_config (config)
  771. if 'bridge-member' in config:
  772. _update_bridge_member_config (config)
  773. if 'vlan-raw-device' in config or 'vlan-id' in config:
  774. _update_vlan_config (config)
  775. _set_mtu_to_iface_and_upper (ifaces, interface, 0)
  776. # Pimp configuration for VEth link pairs
  777. if interface.startswith ('veth_'):
  778. _update_veth_config (interface, config)
  779. # Auto generate Loopback IPs IFF not present
  780. _generate_loopback_ips (ifaces, node_config, node_id)
  781. # Auto generated VRF devices for any VRF found in ifaces and not already configured.
  782. _generate_vrfs (ifaces)
  783. # Pimp GRE_FFRL type inteface configuration with default values
  784. _generate_ffrl_gre_tunnels (ifaces)
  785. # Drop any config parameters used in node interface configuration not
  786. # relevant anymore for config file generation.
  787. for interface, config in ifaces.items ():
  788. # Set default MTU if not already set manually or by any earlier function
  789. if interface != 'lo' and ('mtu' not in config):
  790. # Set the MTU value of this interface to the autogenerated value (if any)
  791. # or set the default, when no automtu is present.
  792. config['mtu'] = config.get ('automtu', MTU['default'])
  793. for key in [ 'automtu', 'enabled', 'batman_connect_sites', 'bridge-member', 'has_gateway', 'ospf', 'site', 'type', 'tagged_vlans', 'vlan-mode' ]:
  794. if key in config:
  795. config.pop (key)
  796. # Remove route metric on non-router nodes
  797. if 'metric' in config and not 'router' in node_config.get ('roles', []):
  798. config.pop ('metric')
  799. # This leaves 'auto', 'prefixes' and 'desc' as keys which should not be directly
  800. # printed into the remaining configuration. These are handled within the jinja
  801. # interface template.
  802. # Generate meaningful interface descriptions / aliases where useful
  803. _update_interface_desc (node_config, sites_config)
  804. return ifaces
  805. vlan_vxlan_iface_re = re.compile (r'^vlan(\d+)|^vx_v(\d+)_(\w+)')
  806. def _iface_sort (iface_a, iface_b):
  807. a = vlan_vxlan_iface_re.search (iface_a)
  808. b = vlan_vxlan_iface_re.search (iface_b)
  809. # At least one interface didn't match, do regular comparison
  810. if not a or not b:
  811. return ffho.cmp (iface_a, iface_b)
  812. # Extract VLAN ID from VLAN interface (if given) or VXLAN
  813. vid_a = a.group (1) if a.group (1) else a.group (2)
  814. vid_b = b.group (1) if b.group (1) else b.group (2)
  815. # If it's different type of interfaces (one VLAN, one VXLAN), do regular comparison
  816. if (a.group (1) == None) != (b.group (1) == None):
  817. return ffho.cmp (iface_a, iface_b)
  818. # Ok, t's two VLAN or two VXLAN interfaces
  819. # If it's VXLAN interfaces and the VLAN ID is the same, sort by site name
  820. if a.group (2) and vid_a == vid_b:
  821. return ffho.cmp (a.groups (2), b.groups (2))
  822. # If it's two VLANs or two VXLANs with different VLAN IDs, sort by VLAN ID
  823. else:
  824. return ffho.cmp (int (vid_a), int (vid_b))
  825. def get_interface_list (ifaces):
  826. iface_list = []
  827. for iface in sorted (ifaces.keys (), key = cmp_to_key (_iface_sort)):
  828. iface_list.append (iface)
  829. return iface_list
  830. # Generate entries for /etc/bat-hosts for every batman interface we will configure on any node.
  831. # For readability purposes superflous/redundant information is being stripped/supressed.
  832. # As these names will only show up in batctl calls with a specific site, site_names in interfaces
  833. # are stripped. Dummy interfaces are stripped as well.
  834. def gen_bat_hosts (nodes_config, sites_config):
  835. bat_hosts = {}
  836. for node_id in sorted (nodes_config.keys ()):
  837. node_config = nodes_config.get (node_id)
  838. node_name = node_id.split ('.')[0]
  839. if 'batman' not in node_config['roles']:
  840. continue
  841. ifaces = get_interface_config (node_config, sites_config, node_id)
  842. for iface in sorted (ifaces):
  843. iface_config = ifaces.get (iface)
  844. hwaddress = iface_config.get ('hwaddress', None)
  845. if hwaddress == None:
  846. continue
  847. entry_name = node_name
  848. match = re.search (r'^dummy-(.+)(-e)?$', iface)
  849. if match:
  850. if match.group (2):
  851. entry_name += "-e"
  852. # Append site to make name unique
  853. entry_name += "/%s" % match.group (1)
  854. else:
  855. entry_name += "/%s" % re.sub (r'^(vx_.*|i2e|e2i)[_-](.*)$', '\g<1>/\g<2>', iface)
  856. bat_hosts[hwaddress] = entry_name
  857. if 'fastd' in node_config.get ('roles', []):
  858. device_no = node_config.get ('id')
  859. for site in node_config.get ('sites', []):
  860. site_no = _get_site_no (sites_config, site)
  861. for network in ('intergw', 'nodes4', 'nodes6'):
  862. hwaddress = gen_batman_iface_mac (site_no, device_no, network)
  863. bat_hosts[hwaddress] = "%s/%s/%s" % (node_name, network, site)
  864. return bat_hosts
  865. # Return the appropriate hop penalty to configure for the given interface.
  866. def get_batman_iface_penalty (iface):
  867. if iface.startswith ('vlan'):
  868. vid = int (re.sub ('vlan', '', iface))
  869. if 2000 <= vid < 2100:
  870. return default_batman_iface_penalty_by_role.get ('WBBL')
  871. if 2200 <= vid < 2300:
  872. return default_batman_iface_penalty_by_role.get ('WBBL_backup')
  873. if 'intergw' in iface:
  874. return default_batman_iface_penalty_by_role.get ('VPN_intergw')
  875. if 'nodes' in iface:
  876. return default_batman_iface_penalty_by_role.get ('VPN_node')
  877. return default_batman_iface_penalty_by_role.get ('default', 0)
  878. # Generate eBGP session parameters for FFRL Transit from nodes pillar information.
  879. def get_ffrl_bgp_config (ifaces, proto):
  880. _generate_ffrl_gre_tunnels (ifaces)
  881. sessions = {}
  882. for iface in sorted (ifaces):
  883. # We only care for GRE tunnels to the FFRL Backbone
  884. if not iface.startswith ('gre_ffrl_'):
  885. continue
  886. iface_config = ifaces.get (iface)
  887. # Search for IPv4/IPv6 prefix as defined by proto parameter
  888. local = None
  889. neighbor = None
  890. for prefix in iface_config.get ('prefixes', []):
  891. if (proto == 'v4' and '.' in prefix) or (proto == 'v6' and ':' in prefix):
  892. local = prefix.split ('/')[0]
  893. # Calculate neighbor IP as <local IP> - 1
  894. neighbor = str (ipaddress.ip_address (u'%s' % local) - 1)
  895. break
  896. # Strip gre_ prefix iface name and use it as identifier for the eBGP session.
  897. name = re.sub ('gre_ffrl_', 'ffrl_', iface)
  898. sessions[name] = {
  899. 'local' : local,
  900. 'neighbor' : neighbor,
  901. 'bgp_local_pref' : iface_config.get ('bgp_local_pref', None),
  902. }
  903. return sessions
  904. # Get list of IP address configured on given interface on given node.
  905. #
  906. # @param: node_config Pillar node configuration (as dict)
  907. # @param: iface_name Name of the interface defined in pillar node config
  908. # OR name of VRF ("vrf_<something>") whichs ifaces are
  909. # to be examined.
  910. # @param: with_mask Don't strip the netmask from the prefix. (Default false)
  911. def get_node_iface_ips (node_config, iface_name, with_mask = False):
  912. ips = {
  913. 'v4' : [],
  914. 'v6' : [],
  915. }
  916. ifaces = node_config.get ('ifaces', {})
  917. ifaces_names = [ iface_name ]
  918. if iface_name.startswith ('vrf_'):
  919. # Reset list of ifaces_names to consider
  920. ifaces_names = []
  921. vrf = iface_name
  922. for iface, iface_config in ifaces.items ():
  923. # Ignore any iface NOT in the given VRF
  924. if iface_config.get ('vrf', None) != vrf:
  925. continue
  926. # Ignore any VEth pairs
  927. if iface.startswith ('veth'):
  928. continue
  929. ifaces_names.append (iface)
  930. try:
  931. for iface in ifaces_names:
  932. for prefix in ifaces[iface]['prefixes']:
  933. ip_ver = 'v6' if ':' in prefix else 'v4'
  934. if not with_mask:
  935. prefix = prefix.split ('/')[0]
  936. ips[ip_ver].append (prefix)
  937. except KeyError:
  938. pass
  939. return ips
  940. #
  941. # Get the primary IP(s) of the given node
  942. #
  943. # @param node_config: Pillar node configuration (as dict)
  944. # @param af: Address family
  945. def get_primary_ip (node_config, af):
  946. # Compatility glue
  947. if 'primary_ips' not in node_config:
  948. return Prefix ("%s%s" % (loopback_prefix[af], node_config['id']))
  949. return Prefix (node_config['primary_ips'][af])
  950. #
  951. # Get the router id (read: IPv4 Lo-IP) out of the given node config.
  952. def get_router_id (node_config, node_id):
  953. return get_primary_ip (node_config, 'v4').ip
  954. # Compute minions OSPF interface configuration according to FFHO routing policy
  955. # See https://wiki.ffho.net/infrastruktur:vlans for information about Vlans
  956. #
  957. # Costs are based on the following reference values:
  958. #
  959. # Iface speed | Cost
  960. # ------------+---------
  961. # 100 Gbit/s | 1
  962. # 40 Gbit/s | 2
  963. # 25 Gbit/s | 4
  964. # 20 Gbit/s | 5
  965. # 10 Gbit/s | 10
  966. # 1 Gbit/s | 100
  967. # 100 Mbit/s | 1000
  968. # VPN | 10000
  969. #
  970. def get_ospf_config (node_config, grains_id):
  971. ospf_config = {
  972. # <area> : {
  973. # <iface> : {
  974. # config ...
  975. # }
  976. # }
  977. }
  978. for iface, iface_config in node_config.get ('ifaces', {}).items ():
  979. # By default we don't speak OSPF on interfaces
  980. ospf_on = False
  981. area = 0
  982. # Defaults for OSPF interfaces
  983. ospf_iface_cfg = {
  984. 'stub' : True, # Active/Passive interface
  985. 'cost' : 12345,
  986. # 'type' # Area type
  987. }
  988. # OSPF configuration for interface present?
  989. ospf_iface_cfg_pillar = iface_config.get ('ospf', {})
  990. # Should be completely ignore this interface?
  991. if ospf_iface_cfg_pillar.get ('ignore', False):
  992. continue
  993. # Ignore interfaces without any IPs configured
  994. if not iface_config.get ('prefixes'):
  995. continue
  996. # If this interface is within a (non-default) VRF, don't OSPF here
  997. if iface_config.get ('vrf'):
  998. continue
  999. # Wireless Local Links (WLL)
  1000. if re.search (r'^vlan90\d$', iface):
  1001. ospf_on = True
  1002. ospf_iface_cfg['stub'] = True
  1003. ospf_iface_cfg['cost'] = 10
  1004. ospf_iface_cfg['desc'] = "Wireless Local Link (WLL)"
  1005. # Local Gigabit Ethernet based connections (PTP or L2 subnets), cost 10
  1006. elif re.search (r'^(br-?|br\d+\.|vlan)10\d\d$', iface):
  1007. ospf_on = True
  1008. ospf_iface_cfg['stub'] = False
  1009. ospf_iface_cfg['cost'] = 100
  1010. ospf_iface_cfg['desc'] = "Wired Gigabit connection"
  1011. # 10/20 Gbit/s Dark Fiber connection
  1012. elif re.search (r'^vlan12\d\d$', iface):
  1013. ospf_on = True
  1014. ospf_iface_cfg['stub'] = False
  1015. ospf_iface_cfg['cost'] = 10
  1016. ospf_iface_cfg['desc'] = "Wired 10Gb/s connection"
  1017. # VLL connection
  1018. elif re.search (r'^vlan15\d\d$', iface):
  1019. ospf_on = True
  1020. ospf_iface_cfg['stub'] = False
  1021. ospf_iface_cfg['cost'] = 200
  1022. ospf_iface_cfg['desc'] = "VLL connection"
  1023. # WBBL connection
  1024. elif re.search (r'^vlan20\d\d$', iface):
  1025. ospf_on = True
  1026. ospf_iface_cfg['stub'] = False
  1027. ospf_iface_cfg['cost'] = 1000
  1028. ospf_iface_cfg['desc'] = "WBBL connection"
  1029. # Legacy WBBL connection
  1030. elif re.search (r'^vlan22\d\d$', iface):
  1031. ospf_on = True
  1032. ospf_iface_cfg['stub'] = False
  1033. ospf_iface_cfg['cost'] = 1000
  1034. ospf_iface_cfg['desc'] = "WBBL connection"
  1035. # Management Vlans
  1036. elif re.search (r'^vlan30\d\d$', iface):
  1037. ospf_on = True
  1038. ospf_iface_cfg['stub'] = True
  1039. ospf_iface_cfg['cost'] = 10
  1040. # Management X-Connects
  1041. elif re.search (r'^vlan32\d\d$', iface):
  1042. ospf_on = True
  1043. ospf_iface_cfg['stub'] = False
  1044. ospf_iface_cfg['cost'] = 10
  1045. ospf_iface_cfg['AF'] = 4
  1046. area = 51
  1047. # OPS Vlans
  1048. elif re.search (r'^vlan39\d\d$', iface):
  1049. ospf_on = True
  1050. ospf_iface_cfg['stub'] = True
  1051. ospf_iface_cfg['cost'] = 10
  1052. # Active OSPF on OpenVPN tunnels, cost 10000
  1053. elif iface.startswith ('ovpn-'):
  1054. ospf_on = True
  1055. ospf_iface_cfg['stub'] = False
  1056. ospf_iface_cfg['cost'] = 10000
  1057. # Inter-Core links should have cost 5000
  1058. if iface.startswith ('ovpn-cr') and grains_id.startswith ('cr'):
  1059. ospf_iface_cfg['cost'] = 5000
  1060. # OpenVPN tunnels to EdgeRouters
  1061. elif iface.startswith ('ovpn-er-'):
  1062. ospf_iface_cfg['type'] = 'broadcast'
  1063. # Active OSPF on Wireguard tunnels, cost 10000
  1064. elif iface.startswith ('wg-'):
  1065. ospf_on = True
  1066. ospf_iface_cfg['stub'] = False
  1067. ospf_iface_cfg['cost'] = 10000
  1068. # Inter-Core links should have cost 5000
  1069. if iface.startswith ('wg-cr') and grains_id.startswith ('cr'):
  1070. ospf_iface_cfg['cost'] = 5000
  1071. # OSPF explicitly enabled for interface
  1072. elif 'ospf' in iface_config:
  1073. ospf_on = True
  1074. # iface ospf parameters will be applied later
  1075. # Go on if OSPF should not be actived
  1076. if not ospf_on:
  1077. continue
  1078. # Explicit OSPF interface configuration parameters take precendence over generated ones
  1079. for attr, val in ospf_iface_cfg_pillar.items ():
  1080. ospf_iface_cfg[attr] = val
  1081. # Store interface configuration
  1082. if area not in ospf_config:
  1083. ospf_config[area] = {}
  1084. ospf_config[area][iface] = ospf_iface_cfg
  1085. return ospf_config
  1086. # Return (possibly empty) subset of Traffic Engineering entries from 'te' pillar entry
  1087. # relevenant for this minion and protocol (IPv4 / IPv6)
  1088. def get_te_prefixes (te_node_config, grains_id, proto):
  1089. te_config = {}
  1090. for prefix, prefix_config in te_node_config.get ('prefixes', {}).items ():
  1091. prefix_proto = 'v6' if ':' in prefix else 'v4'
  1092. # Should this TE policy be applied on this node and is the prefix
  1093. # of the proto we are looking for?
  1094. if grains_id in prefix_config.get ('nodes', []) and prefix_proto == proto:
  1095. te_config[prefix] = prefix_config
  1096. return te_config
  1097. # Convert the CIDR network from the given prefix into a dotted netmask
  1098. def cidr_to_dotted_mask (prefix):
  1099. return str (ipaddress.ip_network (prefix, strict = False).netmask)
  1100. def is_subprefix (prefix, subprefix):
  1101. p = ipaddress.ip_network (prefix, strict = False)
  1102. s = ipaddress.ip_network (subprefix, strict = False)
  1103. return s.subnet_of (p)
  1104. # Return the network address of the given prefix
  1105. def get_network_address (prefix, with_prefixlen = False):
  1106. net_h = ipaddress.ip_network (u'%s' % prefix, strict = False)
  1107. network = str (net_h.network_address)
  1108. if with_prefixlen:
  1109. network += "/%s" % net_h.prefixlen
  1110. return network