ffho_net.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  1. #!/usr/bin/python
  2. import re
  3. mac_prefix = "f2"
  4. # VRF configuration map
  5. vrf_info = {
  6. 'vrf_external' : {
  7. 'table' : 1023,
  8. 'fwmark' : [ '0x1', '0x1023' ],
  9. },
  10. }
  11. #
  12. # Default parameters added to any given bonding interface,
  13. # if not specified at the interface configuration.
  14. default_bond_config = {
  15. 'bond-mode': '802.3ad',
  16. 'bond-min-links': '1',
  17. 'bond-xmit-hash-policy': 'layer3+4'
  18. }
  19. #
  20. # Default parameters added to any given bonding interface,
  21. # if not specified at the interface configuration.
  22. default_bridge_config = {
  23. 'bridge-fd' : '0',
  24. 'bridge-stp' : 'no'
  25. }
  26. #
  27. # Hop penalty to set if none is explicitly specified
  28. # Check if one of these roles is configured for any given node, use first match.
  29. default_hop_penalty_by_role = {
  30. 'bbr' : 5,
  31. 'bras' : 50,
  32. 'batman_gw' : 50,
  33. }
  34. batman_role_evaluation_order = [ 'bbr', 'batman_gw', 'bras' ]
  35. #
  36. # Default interface attributes to be added to GRE interface to AS201701 when
  37. # not already present in pillar interface configuration.
  38. GRE_FFRL_attrs = {
  39. 'mode' : 'gre',
  40. 'method' : 'tunnel',
  41. 'mtu' : '1400',
  42. 'ttl' : '64',
  43. }
  44. # The IPv4/IPv6 prefix use for Loopback IPs
  45. loopback_prefix = {
  46. 'v4' : '10.132.255.',
  47. 'v6' : '2a03:2260:2342:ffff::',
  48. }
  49. ################################################################################
  50. # Internal functions #
  51. # #
  52. # Touching anything below will void any warranty you never had ;) #
  53. # #
  54. ################################################################################
  55. sites = None
  56. def _get_site_no (sites_config, site_name):
  57. global sites
  58. if sites == None:
  59. sites = {}
  60. for site in sites_config:
  61. if site.startswith ("_"):
  62. continue
  63. sites[site] = sites_config[site].get ("site_no", -2)
  64. return sites.get (site_name, -1)
  65. #
  66. # Generate a MAC address after the format f2:dd:dd:ss:nn:nn where
  67. # dd:dd is the hexadecimal reprensentation of the nodes device_id
  68. # ff:ff representing the gluon nodes
  69. #
  70. # ss is the hexadecimal reprensentation of the site_id the interface is connected to
  71. #
  72. # nn:nn is the decimal representation of the network the interface is connected to, with
  73. # 00:00 being the dummy interface
  74. # 00:0f being the VEth internal side interface
  75. # 00:e0 being an external instance dummy interface
  76. # 00:e1 being an inter-gw-vpn interface
  77. # 00:e4 being an nodes fastd tunnel interface of IPv4 transport
  78. # 00:e6 being an nodes fastd tunnel interface of IPv6 transport
  79. # 00:ef being an extenral instance VEth interface side
  80. # 02:xx being a connection to local Vlan 2xx
  81. # 1b:24 being the ibss 2.4GHz bssid
  82. # 1b:05 being the ibss 5GHz bssid
  83. # xx:xx being a VXLAN tunnel for site ss, with xx being a the underlay VLAN ID (1xyz, 2xyz)
  84. # ff:ff being the gluon next-node interface
  85. def gen_batman_iface_mac (site_no, device_no, network):
  86. net_type_map = {
  87. 'dummy' : "00:00",
  88. 'int2ext' : "00:0f",
  89. 'dummy-e' : "00:e0",
  90. 'intergw' : "00:e1",
  91. 'nodes4' : "00:e4",
  92. 'nodes6' : "00:e6",
  93. 'ext2int' : "00:ef",
  94. }
  95. # Well-known network type?
  96. if network in net_type_map:
  97. last = net_type_map[network]
  98. elif type (network) == int:
  99. last = re.sub (r'(\d{2})(\d{2})', '\g<1>:\g<2>', "%04d" % network)
  100. else:
  101. last = "ee:ee"
  102. # Convert device_no to hex, format number to 4 digits with leading zeros and : betwwen 2nd and 3rd digit
  103. device_no_hex = re.sub (r'([0-9a-fA-F]{2})([0-9a-fA-F]{2})', '\g<1>:\g<2>', "%04x" % int (device_no))
  104. # Format site_no to two digit number with leading zero
  105. site_no_hex = "%02d" % int (site_no)
  106. return "%s:%s:%s:%s" % (mac_prefix, device_no_hex, site_no_hex, last)
  107. # Gather B.A.T.M.A.N. related config options for real batman devices (e.g. bat0)
  108. # as well as for batman member interfaces (e.g. eth0.100, fastd ifaces etc.)
  109. def _update_batman_config (node_config, iface, sites_config):
  110. try:
  111. node_batman_hop_penalty = int (node_config['batman']['hop-penalty'])
  112. except KeyError,ValueError:
  113. node_batman_hop_penalty = None
  114. iface_config = node_config['ifaces'][iface]
  115. iface_type = iface_config.get ('type', 'inet')
  116. batman_config = {}
  117. for item, value in iface_config.items ():
  118. if item.startswith ('batman-'):
  119. batman_config[item] = value
  120. iface_config.pop (item)
  121. # B.A.T.M.A.N. device (e.g. bat0)
  122. if iface_type == 'batman':
  123. if 'batman-hop-penalty' not in batman_config:
  124. # If there's a hop penalty set for the node, but not for the interface
  125. # apply the nodes hop penalty
  126. if node_batman_hop_penalty:
  127. batman_config['batman-hop-penalty'] = node_batman_hop_penalty
  128. # If there's no hop penalty set for the node, use a default hop penalty
  129. # for the roles the node might have, if any
  130. else:
  131. node_roles = node_config.get ('roles', [])
  132. for role in batman_role_evaluation_order:
  133. if role in node_roles:
  134. batman_config['batman-hop-penalty'] = default_hop_penalty_by_role[role]
  135. # If batman ifaces were specified as a list - which they should -
  136. # generate a sorted list of interface names as string representation
  137. if 'batman-ifaces' in batman_config and type (batman_config['batman-ifaces']) == list:
  138. batman_iface_str = " ".join (sorted (batman_config['batman-ifaces']))
  139. batman_config['batman-ifaces'] = batman_iface_str
  140. # B.A.T.M.A.N. member interface (e.g. eth.100, fastd ifaces, etc.)
  141. elif iface_type == 'batman_iface':
  142. # Generate unique MAC address for every batman iface, as B.A.T.M.A.N.
  143. # will get puzzled with multiple interfaces having the same MAC and
  144. # do nasty things.
  145. site = iface_config.get ('site')
  146. site_no = _get_site_no (sites_config, site)
  147. device_no = node_config.get ('id')
  148. network = 1234
  149. # Generate a unique BATMAN-MAC for this interfaces
  150. match = re.search (r'^vlan(\d+)', iface)
  151. if match:
  152. network = int (match.group (1))
  153. iface_config['hwaddress'] = gen_batman_iface_mac (site_no, device_no, network)
  154. iface_config['batman'] = batman_config
  155. # Mangle bond specific config items with default values and store them in
  156. # separate sub-dict for easier access and configuration.
  157. def _update_bond_config (config):
  158. bond_config = default_bond_config.copy ()
  159. for item, value in config.items ():
  160. if item.startswith ('bond-'):
  161. bond_config[item] = value
  162. config.pop (item)
  163. if bond_config['bond-mode'] not in ['2', 'balance-xor', '4', '802.3ad']:
  164. bond_config.pop ('bond-xmit-hash-policy')
  165. config['bond'] = bond_config
  166. # Mangle bridge specific config items with default values and store them in
  167. # separate sub-dict for easier access and configuration.
  168. def _update_bridge_config (config):
  169. bridge_config = default_bridge_config.copy ()
  170. for item, value in config.items ():
  171. if item.startswith ('bridge-'):
  172. bridge_config[item] = value
  173. config.pop (item)
  174. # Fix and salt mangled string interpretation back to real string.
  175. if type (value) == bool:
  176. bridge_config[item] = "yes" if value else "no"
  177. # If bridge ports were specified as a list - which they should -
  178. # generate a sorted list of interface names as string representation
  179. if 'bridge-ports' in bridge_config and type (bridge_config['bridge-ports']) == list:
  180. bridge_ports_str = " ".join (sorted (bridge_config['bridge-ports']))
  181. bridge_config['bridge-ports'] = bridge_ports_str
  182. config['bridge'] = bridge_config
  183. # Move vlan specific config items into a sub-dict for easier access and pretty-printing
  184. # in the configuration file
  185. def _update_vlan_config (config):
  186. vlan_config = {}
  187. for item, value in config.items ():
  188. if item.startswith ('vlan-'):
  189. vlan_config[item] = value
  190. config.pop (item)
  191. config['vlan'] = vlan_config
  192. # Pimp Veth interfaces
  193. # * Add peer interface name IF not present
  194. # * Add link-type veth IF not present
  195. def _update_veth_config (interface, config):
  196. veth_peer_name = {
  197. 'veth_ext2int' : 'veth_int2ext',
  198. 'veth_int2ext' : 'veth_ext2int'
  199. }
  200. if interface not in veth_peer_name:
  201. return
  202. if 'link-type' not in config:
  203. config['link-type'] = 'veth'
  204. if 'veth-peer-name' not in config:
  205. config['veth-peer-name'] = veth_peer_name[interface]
  206. # Generate configuration entries for any batman related interfaces not
  207. # configured explicitly, but asked for implicitly by role batman and a
  208. # (list of) site(s) specified in the node config.
  209. def _generate_batman_interface_config (node_config, ifaces, sites_config):
  210. # No role 'batman', nothing to do
  211. roles = node_config.get ('roles', [])
  212. if 'batman' not in roles:
  213. return
  214. # Should there be a 2nd external BATMAN instance?
  215. batman_ext = 'batman_ext' in roles or 'bras' in roles
  216. device_no = node_config.get ('id', -1)
  217. for site in node_config.get ('sites', []):
  218. site_no = _get_site_no (sites_config, site)
  219. # Predefine interface names for regular/external BATMAN instance
  220. # and possible VEth link pair for connecting both instances.
  221. bat_site_if = "bat-%s" % site
  222. dummy_site_if = "dummy-%s" % site
  223. bat_site_if_ext = "bat-%s-ext" % site
  224. dummy_site_if_ext = "dummy-%s-e" % site
  225. int2ext_site_if = "i2e-%s" % site
  226. ext2int_site_if = "e2i-%s" % site
  227. site_ifaces = {
  228. # Regular BATMAN interface, always present
  229. bat_site_if : {
  230. 'type' : 'batman',
  231. # int2ext_site_if will be added automagically if requred
  232. 'batman-ifaces' : [ dummy_site_if ],
  233. 'batman-ifaces-ignore-regex': '.*_.*',
  234. },
  235. # Dummy interface always present in regular BATMAN instance
  236. dummy_site_if : {
  237. 'link-type' : 'dummy',
  238. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'dummy'),
  239. },
  240. # Optional 2nd "external" BATMAN instance
  241. bat_site_if_ext : {
  242. 'type' : 'batman',
  243. 'batman-ifaces' : [ dummy_site_if_ext, ext2int_site_if ],
  244. 'batman-ifaces-ignore-regex': '.*_.*',
  245. 'ext_only' : True,
  246. },
  247. # Optional dummy interface always present in 2nd "external" BATMAN instance
  248. dummy_site_if_ext : {
  249. 'link-type' : 'dummy',
  250. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'dummy-e'),
  251. 'ext_only' : True,
  252. },
  253. # Optional VEth interface pair - internal side
  254. int2ext_site_if : {
  255. 'link-type' : 'veth',
  256. 'veth-peer-name' : ext2int_site_if,
  257. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'int2ext'),
  258. 'ext_only' : True,
  259. },
  260. # Optional VEth interface pair - "external" side
  261. ext2int_site_if : {
  262. 'link-type' : 'veth',
  263. 'veth-peer-name' : int2ext_site_if,
  264. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, 'ext2int'),
  265. 'ext_only' : True,
  266. },
  267. }
  268. for iface, iface_config_tmpl in site_ifaces.items ():
  269. # Ignore any interface only relevant when role batman_ext is set
  270. # but it isn't
  271. if not batman_ext and iface_config_tmpl.get ('ext_only', False):
  272. continue
  273. # Remove ext_only key so we don't leak it into ifaces dict
  274. if 'ext_only' in iface_config_tmpl:
  275. del iface_config_tmpl['ext_only']
  276. # If there is no trace of the desired iface config yet...
  277. if iface not in ifaces:
  278. # ... just place our template there.
  279. ifaces[iface] = iface_config_tmpl
  280. # If there should be an 2nd external BATMAN instance make sure
  281. # the internal side of the VEth iface pair is connected to the
  282. # internal BATMAN instance.
  283. if batman_ext and iface == bat_site_if:
  284. iface_config_tmpl['batman-ifaces'].append (int2ext_site_if)
  285. # If there already is an interface configuration try to enhance it with
  286. # meaningful values from our template and force correct hwaddress to be
  287. # used.
  288. else:
  289. iface_config = ifaces[iface]
  290. # Force hwaddress to be what we expect.
  291. if 'hwaddress' in iface_config_tmpl:
  292. iface_config['hwaddress'] = iface_config_tmpl['hwaddress']
  293. # Copy every attribute of the config template missing in iface config
  294. for attr in iface_config_tmpl:
  295. if attr not in iface_config:
  296. iface_config[attr] = iface_config_tmpl[attr]
  297. # Make sure there is a bridge present for every site where a mesh_breakout
  298. # interface should be configured.
  299. for iface, config in ifaces.items ():
  300. iface_type = config.get ('type', 'inet')
  301. if iface_type not in ['mesh_breakout', 'batman_iface']:
  302. continue
  303. site = config.get ('site')
  304. site_bridge = "br-%s" % site
  305. batman_site_if = "bat-%s" % site
  306. if iface_type == 'mesh_breakout':
  307. # If the bridge has already been defined (with an IP maybe) make
  308. # sure that the corresbonding batman device is part of the bridge-
  309. # ports.
  310. if site_bridge in ifaces:
  311. bridge_config = ifaces.get (site_bridge)
  312. # If there already is/are (a) bridge-port(s) defined, add
  313. # the batman and the breakout interfaces if not present...
  314. bridge_ports = bridge_config.get ('bridge-ports', None)
  315. if bridge_ports:
  316. for dev in (batman_site_if, iface):
  317. if not dev in bridge_ports:
  318. if type (bridge_ports) == list:
  319. bridge_ports.append (dev)
  320. else:
  321. bridge_config['bridge-ports'] += ' ' + dev
  322. # ...if there is no bridge-port defined yet, just used
  323. # the batman and breakout iface.
  324. else:
  325. bridge_config['bridge-ports'] = [ iface, batman_site_if ]
  326. # If the bridge isn't present alltogether, add it.
  327. else:
  328. ifaces[site_bridge] = {
  329. 'bridge-ports' : [ iface, batman_site_if ],
  330. }
  331. elif iface_type == 'batman_iface':
  332. batman_ifaces = ifaces[bat_site_if]['batman-ifaces']
  333. if iface not in batman_ifaces:
  334. if type (batman_ifaces) == list:
  335. batman_ifaces.append (iface)
  336. else:
  337. batman_ifaces += ' ' + iface
  338. #
  339. # Generate any implicitly defined VXLAN interfaces defined in the nodes iface
  340. # defined in pillar.
  341. # The keyword "batman_connect_sites" on an interface will trigger the
  342. # generation of a VXLAN overlay interfaces.
  343. def _generate_vxlan_interface_config (node_config, ifaces, sites_config):
  344. # No role 'batman', nothing to do
  345. if 'batman' not in node_config.get ('roles', []):
  346. return
  347. # Sites configured on this node. Nothing to do, if none.
  348. my_sites = node_config.get ('sites', [])
  349. if len (my_sites) == 0:
  350. return
  351. # As we're still here we can now safely assume that a B.A.T.M.A.N.
  352. # device has been configured for every site specified in sites list.
  353. device_no = node_config.get ('id', -1)
  354. for iface, iface_config in ifaces.items ():
  355. batman_connect_sites = iface_config.get ('batman_connect_sites', [])
  356. # If we got a string, convert it to a list with a single element
  357. if type (batman_connect_sites) == str:
  358. batman_connect_sites = [ batman_connect_sites ]
  359. # If the string 'all' is part of the list, blindly use all sites configured for this node
  360. if 'all' in batman_connect_sites:
  361. batman_connect_sites = my_sites
  362. for site in batman_connect_sites:
  363. # Silenty ignore sites not configured on this node
  364. if site not in my_sites:
  365. continue
  366. # iface_name := vx_<last 5 chars of underlay iface>_<site> stripped to 15 chars
  367. vx_iface = "vx_%s_%s" % (re.sub ('vlan', 'v', iface)[-5:], site)[:15]
  368. site_no = _get_site_no (sites_config, site)
  369. vni = 100 + site_no
  370. bat_iface = "bat-%s" % site
  371. try:
  372. iface_id = int (re.sub ('vlan', '', iface))
  373. # Gather interface specific mcast address.
  374. # The address is derived from the vlan-id of the underlying interface,
  375. # assuming that it in fact is a vlan interface.
  376. # Mangle the vlan-id into two 2 digit values, eliminating any leading zeros.
  377. iface_id_4digit = "%04d" % iface_id
  378. octet2 = int (iface_id_4digit[0:2])
  379. octet3 = int (iface_id_4digit[2:4])
  380. mcast_ip = "225.%s.%s.%s" % (octet2, octet3, site_no)
  381. vni = octet2 * 256 * 256 + octet3 * 256 + site_no
  382. except ValueError:
  383. iface_id = 9999
  384. mcast_ip = "225.0.0.%s" % site_no
  385. vni = site_no
  386. # bail out if VXLAN tunnel already configured
  387. if vx_iface in ifaces:
  388. continue
  389. # If there's no batman interface for this site, there's no point
  390. # in setting up a VXLAN interfaces
  391. if bat_iface not in ifaces:
  392. continue
  393. # Add the VXLAN interface
  394. ifaces[vx_iface] = {
  395. 'vxlan' : {
  396. 'vxlan-id' : vni,
  397. 'vxlan-svcnodeip' : mcast_ip,
  398. 'vxlan-physdev' : iface,
  399. },
  400. 'hwaddress' : gen_batman_iface_mac (site_no, device_no, iface_id),
  401. }
  402. # If the batman interface for this site doesn't have any interfaces
  403. # set up - which basicly cannot happen - add this VXLAN tunnel as
  404. # the first in the list.
  405. if not 'batman-ifaces' in ifaces[bat_iface]:
  406. ifaces[bat_iface]['batman-ifaces'] = [ vx_iface ]
  407. continue
  408. # In the hope there already are interfaces for batman set up already
  409. # add this VXLAN tunnel to the list
  410. batman_ifaces = ifaces[bat_iface]['batman-ifaces']
  411. if vx_iface not in batman_ifaces:
  412. if type (batman_ifaces) == list:
  413. batman_ifaces.append (vx_iface)
  414. else:
  415. batman_ifaces += ' ' + vx_iface
  416. #
  417. # Generate implicitly defined VRFs according to the vrf_info dict at the top
  418. # of this file
  419. def _generate_vrfs (ifaces):
  420. for iface, iface_config in ifaces.items ():
  421. vrf = iface_config.get ('vrf', None)
  422. if vrf and vrf not in ifaces:
  423. conf = vrf_info.get (vrf, {})
  424. table = conf.get ('table', 1234)
  425. fwmark = conf.get ('fwmark', None)
  426. ifaces[vrf] = {
  427. 'vrf-table' : table,
  428. }
  429. # Create ip rule's for any fwmarks defined
  430. if fwmark:
  431. up = []
  432. # Make sure we are dealing with a list even if there is only one mark to be set up
  433. if type (fwmark) in (str, int):
  434. fwmark = [ fwmark ]
  435. # Create ip rule entries for IPv4 and IPv6 for every fwmark
  436. for mark in fwmark:
  437. up.append ("ip rule add fwmark %s table %s" % (mark, table))
  438. up.append ("ip -6 rule add fwmark %s table %s" % (mark, table))
  439. ifaces[vrf]['up'] = up
  440. def _generate_ffrl_gre_tunnels (ifaces):
  441. for iface, iface_config in ifaces.items ():
  442. # We only care for GRE_FFRL type interfaces
  443. if iface_config.get ('type', '') != 'GRE_FFRL':
  444. continue
  445. # Copy default values to interface config
  446. for attr, val in GRE_FFRL_attrs.items ():
  447. if not attr in iface_config:
  448. iface_config[attr] = val
  449. # Guesstimate local IPv4 tunnel endpoint address from tunnel-physdev
  450. if not 'local' in iface_config and 'tunnel-physdev' in iface_config:
  451. try:
  452. physdev_prefixes = [p.split ('/')[0] for p in ifaces[iface_config['tunnel-physdev']]['prefixes'] if '.' in p]
  453. if len (physdev_prefixes) == 1:
  454. iface_config['local'] = physdev_prefixes[0]
  455. except KeyError:
  456. pass
  457. def _generate_loopback_ips (ifaces, node_config, node_id):
  458. v4_ip = "%s/32" % get_loopback_ip (node_config, node_id, 'v4')
  459. v6_ip = "%s/128" % get_loopback_ip (node_config, node_id, 'v6')
  460. # Interface lo already present?
  461. if 'lo' not in ifaces:
  462. ifaces['lo'] = { 'prefixes' : [] }
  463. # Add 'prefixes' list if not present
  464. if 'prefixes' not in ifaces['lo']:
  465. ifaces['lo']['prefixes'] = []
  466. prefixes = ifaces['lo']['prefixes']
  467. if v4_ip not in prefixes:
  468. prefixes.append (v4_ip)
  469. if v6_ip not in prefixes:
  470. prefixes.append (v6_ip)
  471. ################################################################################
  472. # Public functions #
  473. ################################################################################
  474. # Generate network interface configuration for given node.
  475. #
  476. # This function will read the network configuration from pillar and will
  477. # * enhance it with all default values configured at the top this file
  478. # * auto generate any implicitly configured
  479. # * VRFs
  480. # * B.A.T.M.A.N. instances and interfaces
  481. # * VXLAN interfaces to connect B.A.T.M.A.N. sites
  482. # * Loopback IPs derived from numeric node ID
  483. #
  484. # @param: node_config Pillar node configuration (as dict)
  485. # @param: sites_config Pillar sites configuration (as dict)
  486. # @param: node_id Minion name / Pillar node configuration key
  487. def get_interface_config (node_config, sites_config, node_id = ""):
  488. # Get config of this node and dict of all configured ifaces
  489. ifaces = node_config.get ('ifaces', {})
  490. # Generate configuration entries for any batman related interfaces not
  491. # configured explicitly, but asked for implicitly by role <batman> and
  492. # a (list of) site(s) specified in the node config.
  493. _generate_batman_interface_config (node_config, ifaces, sites_config)
  494. # Generate VXLAN tunnels for every interfaces specifying 'batman_connect_sites'
  495. _generate_vxlan_interface_config (node_config, ifaces, sites_config)
  496. # Enhance ifaces configuration with some meaningful defaults for
  497. # bonding, bridge and vlan interfaces, MAC address for batman ifaces, etc.
  498. for interface, config in ifaces.items ():
  499. if type (config) != dict:
  500. raise Exception ("Configuration for interface %s on node %s seems broken!" % (interface, node_id))
  501. iface_type = config.get ('type', 'inet')
  502. if 'batman-ifaces' in config or iface_type.startswith ('batman'):
  503. _update_batman_config (node_config, interface, sites_config)
  504. if 'bond-slaves' in config:
  505. _update_bond_config (config)
  506. # FIXME: This maybe will not match on bridges without any member ports configured!
  507. if 'bridge-ports' in config or interface.startswith ('br-'):
  508. _update_bridge_config (config)
  509. if 'vlan-raw-device' in config or 'vlan-id' in config:
  510. _update_vlan_config (config)
  511. # Pimp configuration for VEth link pairs
  512. if interface.startswith ('veth_'):
  513. _update_veth_config (interface, config)
  514. # Auto generate Loopback IPs IFF not present
  515. _generate_loopback_ips (ifaces, node_config, node_id)
  516. # Auto generated VRF devices for any VRF found in ifaces and not already configured.
  517. _generate_vrfs (ifaces)
  518. # Pimp GRE_FFRL type inteface configuration with default values
  519. _generate_ffrl_gre_tunnels (ifaces)
  520. # Drop any config parameters used in node interface configuration not
  521. # relevant anymore for config file generation.
  522. for interface, config in ifaces.items ():
  523. for key in [ 'batman_connect_sites', 'ospf', 'site', 'type' ]:
  524. if key in config:
  525. config.pop (key)
  526. # This leaves 'auto', 'prefixes' and 'desc' as keys which should not be directly
  527. # printed into the remaining configuration. These are handled within the jinja
  528. # interface template.
  529. return ifaces
  530. # Generate entries for /etc/bat-hosts for every batman interface we will configure on any node.
  531. # For readability purposes superflous/redundant information is being stripped/supressed.
  532. # As these names will only show up in batctl calls with a specific site, site_names in interfaces
  533. # are stripped. Dummy interfaces are stripped as well.
  534. def gen_bat_hosts (nodes_config, sites_config):
  535. bat_hosts = {}
  536. for node_id in sorted (nodes_config.keys ()):
  537. node_config = nodes_config.get (node_id)
  538. node_name = node_id.split ('.')[0]
  539. ifaces = get_interface_config (node_config, sites_config, node_id)
  540. for iface in sorted (ifaces):
  541. iface_config = ifaces.get (iface)
  542. hwaddress = iface_config.get ('hwaddress', None)
  543. if hwaddress == None:
  544. continue
  545. entry_name = node_name
  546. match = re.search (r'^dummy-(.+)(-e)?$', iface)
  547. if match:
  548. if match.group (2):
  549. entry_name += "-e"
  550. # Append site to make name unique
  551. entry_name += "/%s" % match.group (1)
  552. else:
  553. entry_name += "/%s" % re.sub (r'^(vx_.*|i2e|e2i)[_-](.*)$', '\g<1>/\g<2>', iface)
  554. bat_hosts[hwaddress] = entry_name
  555. if 'fastd' in node_config.get ('roles', []):
  556. device_no = node_config.get ('id')
  557. for site in node_config.get ('sites', []):
  558. site_no = _get_site_no (sites_config, site)
  559. for network in ('intergw', 'nodes4', 'nodes6'):
  560. hwaddress = gen_batman_iface_mac (site_no, device_no, network)
  561. bat_hosts[hwaddress] = "%s/%s/%s" % (node_name, network, site)
  562. return bat_hosts
  563. # Generate eBGP session parameters for FFRL Transit from nodes pillar information.
  564. def get_ffrl_bgp_config (ifaces, proto):
  565. from ipcalc import IP
  566. _generate_ffrl_gre_tunnels (ifaces)
  567. sessions = {}
  568. for iface in sorted (ifaces):
  569. # We only care for GRE tunnels to the FFRL Backbone
  570. if not iface.startswith ('gre_ffrl_'):
  571. continue
  572. iface_config = ifaces.get (iface)
  573. # Search for IPv4/IPv6 prefix as defined by proto parameter
  574. local = None
  575. neighbor = None
  576. for prefix in iface_config.get ('prefixes', []):
  577. if (proto == 'v4' and '.' in prefix) or (proto == 'v6' and ':' in prefix):
  578. local = prefix.split ('/')[0]
  579. # Calculate neighbor IP as <local IP> - 1
  580. if proto == 'v4':
  581. neighbor = str (IP (int (IP (local)) - 1, version = 4))
  582. else:
  583. neighbor = str (IP (int (IP (local)) - 1, version = 6))
  584. break
  585. # Strip gre_ prefix iface name and use it as identifier for the eBGP session.
  586. name = re.sub ('gre_ffrl_', 'ffrl_', iface)
  587. sessions[name] = {
  588. 'local' : local,
  589. 'neighbor' : neighbor,
  590. 'bgp_local_pref' : iface_config.get ('bgp_local_pref', None),
  591. }
  592. return sessions
  593. # Get list of IP address configured on given interface on given node.
  594. #
  595. # @param: node_config Pillar node configuration (as dict)
  596. # @param: iface_name Name of the interface defined in pillar node config
  597. # OR name of VRF ("vrf_<something>") whichs ifaces are
  598. # to be examined.
  599. def get_node_iface_ips (node_config, iface_name):
  600. ips = {
  601. 'v4' : [],
  602. 'v6' : [],
  603. }
  604. ifaces = node_config.get ('ifaces', {})
  605. ifaces_names = [ iface_name ]
  606. if iface_name.startswith ('vrf_'):
  607. # Reset list of ifaces_names to consider
  608. ifaces_names = []
  609. vrf = iface_name
  610. for iface, iface_config in ifaces.items ():
  611. # Ignore any iface NOT in the given VRF
  612. if iface_config.get ('vrf', None) != vrf:
  613. continue
  614. # Ignore any VEth pairs
  615. if iface.startswith ('veth'):
  616. continue
  617. ifaces_names.append (iface)
  618. try:
  619. for iface in ifaces_names:
  620. for prefix in ifaces[iface]['prefixes']:
  621. ip_ver = 'v6' if ':' in prefix else 'v4'
  622. ips[ip_ver].append (prefix.split ('/')[0])
  623. except KeyError:
  624. pass
  625. return ips
  626. #
  627. # Get the lookback IP of the given node for the given proto
  628. #
  629. # @param node_config: Pillar node configuration (as dict)
  630. # @param node_id: Minion name / Pillar node configuration key
  631. # @param proto: { 'v4', 'v6' }
  632. def get_loopback_ip (node_config, node_id, proto):
  633. if proto not in [ 'v4', 'v6' ]:
  634. raise Exception ("get_loopback_ip(): Invalid proto: \"%s\"." % proto)
  635. if not proto in loopback_prefix:
  636. raise Exception ("get_loopback_ip(): No loopback_prefix configured for IP%s in ffno_net module!" % proto)
  637. if not 'id' in node_config:
  638. raise Exception ("get_loopback_ip(): No 'id' configured in pillar for node \"%s\"!" % node_id)
  639. return "%s%s" % (loopback_prefix.get (proto), node_config.get ('id'))
  640. #
  641. # Get the router id (read: IPv4 Lo-IP) out of the given node config.
  642. def get_router_id (node_config, node_id):
  643. return get_loopback_ip (node_config, node_id, 'v4')
  644. # Compute minions OSPF interface configuration according to FFHO routing policy
  645. # See https://wiki.ffho.net/infrastruktur:vlans for information about Vlans
  646. def get_ospf_interface_config (node_config, grains_id):
  647. ospf_node_config = node_config.get ('ospf', {})
  648. ospf_interfaces = {}
  649. for iface, iface_config in node_config.get ('ifaces', {}).items ():
  650. # By default we don't speak OSPF on interfaces
  651. ospf_on = False
  652. # Defaults for OSPF interfaces
  653. ospf_config = {
  654. 'stub' : True, # Active/Passive interface
  655. 'cost' : 12345,
  656. # 'type' # Area type
  657. }
  658. # OSPF configuration for interface given?
  659. ospf_config_pillar = iface_config.get ('ospf', {})
  660. # Local Gigabit Ethernet based connections (PTP or L2 subnets), cost 10
  661. if re.search (r'^(br-?|br\d+\.|vlan)10\d\d$', iface):
  662. ospf_on = True
  663. ospf_config['stub'] = False
  664. ospf_config['cost'] = 10
  665. ospf_config['desc'] = "Wired Gigabit connection"
  666. # AF-X based WBBL connection
  667. elif re.search (r'^vlan20\d\d$', iface):
  668. ospf_on = True
  669. ospf_config['stub'] = False
  670. ospf_config['cost'] = 100
  671. ospf_config['desc'] = "AF-X based WBBL connection"
  672. # Non-AF-X based WBBL connection
  673. elif re.search (r'^vlan22\d\d$', iface):
  674. ospf_on = True
  675. ospf_config['stub'] = False
  676. ospf_config['cost'] = 1000
  677. ospf_config['desc'] = "Non-AF-X based WBBL connection"
  678. # Management Vlans
  679. elif re.search (r'^vlan30\d\d$', iface):
  680. ospf_on = True
  681. ospf_config['stub'] = True
  682. ospf_config['cost'] = 10
  683. # Active OSPF on OpenVPN tunnels, cost 10000
  684. elif iface.startswith ('ovpn-'):
  685. ospf_on = True
  686. ospf_config['stub'] = False
  687. ospf_config['cost'] = 10000
  688. # Inter-Core links should have cost 5000
  689. if iface.startswith ('ovpn-cr') and grains_id.startswith ('cr'):
  690. ospf_config['cost'] = 5000
  691. # OpenVPN tunnels to EdgeRouters
  692. elif iface.startswith ('ovpn-er-'):
  693. ospf_config['type'] = 'broadcast'
  694. # Configure Out-of-band OpenVPN tunnels as stub interfaces,
  695. # so recursive next-hop lookups for OOB-BGP-session will work.
  696. elif iface.startswith ('oob-'):
  697. ospf_on = True
  698. ospf_config['stub'] = True
  699. ospf_config['cost'] = 1000
  700. # OSPF explicitly enabled for interface
  701. elif 'ospf' in iface_config:
  702. ospf_on = True
  703. # iface ospf parameters will be applied later
  704. # Go on if OSPF should not be actived
  705. if not ospf_on:
  706. continue
  707. # Explicit OSPF interface configuration parameters take precendence over generated ones
  708. for attr, val in ospf_config_pillar:
  709. ospf_config[attr] = val
  710. # Convert boolean values to 'yes' / 'no' string values
  711. for attr, val in ospf_config.items ():
  712. if type (val) == bool:
  713. ospf_config[attr] = 'yes' if val else 'no'
  714. # Store interface configuration
  715. ospf_interfaces[iface] = ospf_config
  716. return ospf_interfaces
  717. # Return (possibly empty) subset of Traffic Engineering entries from 'te' pillar entry
  718. # relevenant for this minion and protocol (IPv4 / IPv6)
  719. def get_te_prefixes (te_node_config, grains_id, proto):
  720. te_config = {}
  721. for prefix, prefix_config in te_node_config.get ('prefixes', {}).items ():
  722. prefix_proto = 'v6' if ':' in prefix else 'v4'
  723. # Should this TE policy be applied on this node and is the prefix
  724. # of the proto we are looking for?
  725. if grains_id in prefix_config.get ('nodes', []) and prefix_proto == proto:
  726. te_config[prefix] = prefix_config
  727. return te_config