ffho_net.py 46 KB

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