Browse Source

The FFHO bird config.

Signed-off-by: Maximilian Wilhelm <max@rfc2324.org>
Maximilian Wilhelm 7 years ago
parent
commit
b5ee6346a9
9 changed files with 1010 additions and 0 deletions
  1. 127 0
      bird/IGP.conf
  2. 124 0
      bird/VRF_external.conf
  3. 75 0
      bird/bird.conf
  4. 74 0
      bird/bird6.conf
  5. 52 0
      bird/bird_apt.key
  6. 155 0
      bird/ff-policy.conf
  7. 88 0
      bird/ffrl.conf
  8. 56 0
      bird/ibgp.conf
  9. 259 0
      bird/init.sls

+ 127 - 0
bird/IGP.conf

@@ -0,0 +1,127 @@
+#
+# FFHO IGP / OSPF configuration (Salt managed)
+#
+
+{%- set node_config = salt['pillar.get']('nodes:' ~ grains['id'], {}) %}
+{%- set ospf_node_config = node_config.get('ospf', {}) %}
+{%- if 'stub_router' in ospf_node_config and ospf_node_config['stub_router'] in [ True, 'yes'] %}
+  {%- do ospf_node_config.update ({'stub_router': 'yes'}) %}
+{%- endif %}
+
+{%- if proto == 'v6' %}
+# Bloody workaround for bird6's unwillingness to read !LL IPv6 addresses from lo
+protocol direct lo_v6 {
+	interface "lo";
+}
+{%- endif %}
+
+protocol ospf IGP {	
+	import all;
+{%- if proto == 'v4' %}
+  {%- if grains['id'].startswith ('cr') %}
+	export filter {
+		if net = 0.0.0.0/0 then {
+			ospf_metric1 = 100;
+			accept;
+		}
+
+		reject;
+	};
+  {%- else %}
+	export none;
+  {%- endif %}
+{%- elif proto == 'v6' %}
+	export filter {
+  {%- if grains['id'].startswith ('cr') %}
+		if net = ::/0 then {
+			ospf_metric1 = 100;
+			accept;
+		}
+  {%- endif %}
+
+		if proto = "lo_v6" then {
+			ospf_metric1 = 100;
+			accept;
+		}
+
+		reject;
+	};
+{%- endif %}
+
+	area 0.0.0.0 {
+		stub {{ ospf_node_config.get ('stub_router', 'no') }} ;
+
+		interface "lo" {
+			stub yes;
+		};
+
+{#-
+ # Wired / Wireless IP-Backbone links
+ #
+ # Prefer direct Layer2 connections (via Ethernet cable or WBBL) between nodes
+ # Vlan 10xy are direct wired Ethernet connection
+ # Vlan 20xy are AF-X based WBBL
+ # Vlan 22xy are non-AF-X based WBBL
+ #
+ # Watch management interfaces as stub interfaces.
+ #}
+{%- set ospf_interface_config = salt['ffho_net.get_ospf_interface_config'](node_config, grains['id']) %}
+{%- for iface in ospf_interface_config|sort %}
+  {%- set config = ospf_interface_config.get (iface) %}
+
+  {#- Interface description? #}
+  {%- set desc = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':ifaces:' ~ iface ~ ':desc', "") %}
+
+		# {{ desc }}
+	  	interface "{{ iface }}" {
+    {%- if 'desc' in config %}
+			# {{ config.get ('desc') }}
+    {%- endif %}
+    {%- for attr in config|sort if attr not in ['desc'] %}
+			{{ attr }} {{ config.get (attr) }};
+    {%- endfor %}
+		};
+
+{%- endfor %}
+
+{#-
+ # Backbone OpenVPNs
+ #}
+{%- set interfaces = [] %}
+{%- for vpn, vpn_config in salt['pillar.get']('ovpn', {}).items () %}
+  {%- if grains['id'] in vpn_config %}
+    {%- set host_config = vpn_config.get (grains['id'], {}).get ('config', {}) %}
+    {%- set interface = host_config.get ('interface', vpn_config.get ('interface', '')) %}
+    {%- if interface.startswith ('bb-') or interface.startswith ('ovpn-') or interface.startswith ('oob-') %}
+      {%- do interfaces.append (interface) %}
+    {%- endif %}
+  {%- endif %}
+{%- endfor %}
+{%- for interface in interfaces|sort %}
+		interface "{{ interface }}" {
+  {%- if interface.startswith ('ovpn-er-') and proto == 'v6' %}
+			type broadcast;
+  {%- else %}
+			type pointopoint;
+  {%- endif %}
+
+  {%- if interface.startswith ('ovpn-cr') %}
+			cost 5000;
+  {%- else %}
+			cost 10000;
+  {%- endif %}
+
+  {%- if interface.startswith ('oob-') %}
+			stub yes;
+  {%- endif %}
+		};
+{% endfor %}
+
+{%- if 'veth_int2ext' in node_config.get ('ifaces', {}) %}
+		# Learn transfer prefix to external VRF for BGP recursive lookup.
+		interface "veth_int2ext" {
+			stub yes;
+		};
+{%- endif %}
+	};
+}

+ 124 - 0
bird/VRF_external.conf

@@ -0,0 +1,124 @@
+################################################################################
+#                              Internet table                                  #
+################################################################################
+
+{%- set ifaces = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':ifaces', {}) %}
+{%- set have_vrf_external = [] %}
+{%- for iface, iface_config in ifaces.items () %}
+  {%- if iface_config.get ('vrf', '') == 'vrf_external' %}
+    {%- do have_vrf_external.append (True) %}
+    {%- break %}
+  {%- endif %}
+{%- endfor %}
+
+{%- if True not in have_vrf_external %}
+#
+# No vrf_external configured on this node. Nothing to do.
+#
+{%- else %}
+table t_external;
+
+protocol kernel k_external {
+	scan time 20;
+
+	learn;
+	import filter external_IPs_to_learn;
+	export all;
+
+	table t_external;
+	kernel table 1023;
+}
+
+# Add unreachable routes for RFC1918, RFC 6598, APIPA so we don't route
+# anything private into the internet + null route some bogons.
+protocol static bogon_unreach_ext {
+	table t_external;
+
+  {%- if proto == 'v4' %}
+	route 0.0.0.0/8		unreachable;	# Host-Subnet
+	route 10.0.0.0/8	unreachable;	# RFC 1918
+	route 100.64.0.0/10 	unreachable;	# RFC 6598
+	route 169.254.0.0/16	unreachable;	# APIPA
+	route 172.16.0.0/12 	unreachable;	# RFC 1918
+	route 192.0.0.0/24	unreachable;	# IANA RESERVED
+	route 192.0.2.0/24	unreachable;	# TEST-NET-1
+	route 192.168.0.0/16	unreachable;	# RFC 1918
+	route 198.18.0.0/15	unreachable;	# BENCHMARK
+	route 198.51.100.0/24	unreachable;	# TEST-NET-2
+	route 203.0.113.0/24	unreachable;	# TEST-NET-3
+	route 224.0.0.0/3	unreachable;	# MCast + Class E
+  {%- else %}
+	route ::/96         unreachable; # RFC 4291
+	route 2001:db8::/32 unreachable; # Documentation
+	route fec0::/10     unreachable; # Site Local
+	route fc00::/7      unreachable; # ULA
+  {%- endif %}
+}
+
+  {%- if 'veth_int2ext' in ifaces and 'veth_ext2int' in ifaces %}
+    {%- set veth_ips = {} %}
+    {%- for iface in ifaces if iface in [ 'veth_int2ext', 'veth_ext2int' ] %}
+      {%- do veth_ips.update ({ iface : { 'v4' : None, 'v6' : None }}) %}
+      {%- for prefix in ifaces.get (iface, {}).get ('prefixes', []) %}
+        {%- if "." in prefix %}
+          {%- do veth_ips[iface].update ({ 'v4' : prefix.split ('/')[0] }) %}
+	{%- else %}
+          {%- do veth_ips[iface].update ({ 'v6' : prefix.split ('/')[0] }) %}
+        {%- endif %}
+      {%- endfor %}
+    {%- endfor %}
+
+#
+# VRF glue
+#
+{%- set internal_ip = veth_ips['veth_int2ext'][proto] %}
+{%- set external_ip = veth_ips['veth_ext2int'][proto] %}
+# Learn route on external side of VEth tunnel between VRFs for recursive BGP
+# nexthop lookup.
+protocol direct d_ext2int {
+	table t_external;
+
+	interface "veth_ext2int";
+}
+
+template bgp ibgp_vrf_glue {
+	local as AS_OWN;
+
+	enable route refresh yes;
+	graceful restart yes;
+}
+
+protocol bgp int2ext from ibgp_vrf_glue {
+	import filter external_IPs_to_learn;
+	export filter own_prefixes;
+
+	source address {{ internal_ip }};
+	neighbor {{ external_ip }} as AS_OWN;
+
+	rr client;
+	next hop self;
+}
+
+protocol bgp ext2int from ibgp_vrf_glue {
+	table t_external;
+
+	# External router!
+	router id {{ veth_ips['veth_ext2int']['v4'] }};
+
+	import filter own_prefixes;
+	export filter {
+		if proto = "k_external" then {
+			bgp_community.add (EXTERNAL_ROUTE);
+			accept;
+		}
+
+		reject;
+	};
+
+	source address {{ external_ip }};
+	neighbor {{ internal_ip }} as AS_OWN;
+
+	next hop self;
+}
+  {%- endif %} {#- veth int/ext tunnel #}
+{%- endif %} {#- vrf_external exists #}

+ 75 - 0
bird/bird.conf

@@ -0,0 +1,75 @@
+#
+# IPv4 Bird configuration (Salt managed)
+#
+{%- set node_config = salt['pillar.get']('nodes:' ~ grains['id'], {}) %}
+
+define AS_OWN  = 65132;
+define LO_IP = {{ salt['ffho_net.get_loopback_ip'](node_config, grains['id'], 'v4') }};
+
+router id {{ salt['ffho_net.get_router_id'](node_config, grains['id']) }};
+
+
+# this pseudo-protocol watches all interface up/down events
+protocol device {
+	scan time 10;
+};
+
+# This pseudo-protocol performs synchronization between BIRD's routing
+# tables and the kernel. If your kernel supports multiple routing tables
+# (as Linux 2.2.x does), you can run multiple instances of the kernel
+# protocol and synchronize different kernel tables with different BIRD tables.
+protocol kernel {
+	scan time 20;           # Scan kernel routing table every 20 seconds
+
+{% if 'vpn' in node_config.get ('roles') %}
+	# Learn host routes set up by VPN server(s) on this machine.
+	# As there are two VPN hosts it's important to learn an redistribute
+	# these internally to maintain full reachability.
+	learn;
+
+	import filter {
+		if net ~ [
+			   10.132.249.0/24+,	# OPS
+			   10.132.250.0/24+,	# User-srv
+			   10.132.251.0/24+,	# Infra-srv
+			   80.70.181.56/29+	# Vega-IPs
+			 ] then {
+
+			# Bump perference of learned kernel routes from 10(!) to very high,
+			# so they "win" in routed election and there's no clash with any
+			# backup route via OSPF.
+			preference = 12345;
+			accept;
+		}
+
+		reject;
+	};
+{% else %}
+	import none;
+{%- endif %}
+	export all;
+}
+
+
+# Add unreachable routes for RFC1918, RFC 6598, APIPA so we don't route
+# anything private into the internet + null route some bogons.
+protocol static bogon_unreach {
+	route 0.0.0.0/8		unreachable;	# Host-Subnet
+	route 10.0.0.0/8	unreachable;	# RFC 1918
+	route 100.64.0.0/10 	unreachable;	# RFC 6598
+	route 169.254.0.0/16	unreachable;	# APIPA
+	route 172.16.0.0/12 	unreachable;	# RFC 1918
+	route 192.0.0.0/24	unreachable;	# IANA RESERVED
+	route 192.0.2.0/24	unreachable;	# TEST-NET-1
+	route 192.168.0.0/16	unreachable;	# RFC 1918
+	route 198.18.0.0/15	unreachable;	# BENCHMARK
+	route 198.51.100.0/24	unreachable;	# TEST-NET-2
+	route 203.0.113.0/24	unreachable;	# TEST-NET-3
+	route 224.0.0.0/3	unreachable;	# MCast + Class E
+}
+
+
+#
+# Load additiional configuration (IGP, FFRL, ICVPN, 'n stuff)
+include "/etc/bird/ff-policy.conf";
+include "/etc/bird/bird.d/*.conf";

+ 74 - 0
bird/bird6.conf

@@ -0,0 +1,74 @@
+#
+# IPv6 Bird configuration (Salt managed)
+#
+{%- set node_config = salt['pillar.get']('nodes:' ~ grains['id'], {}) %}
+
+define AS_OWN  = 65132;
+define LO_IP = {{ salt['ffho_net.get_loopback_ip'](node_config, grains['id'], 'v6') }};
+
+router id {{ salt['ffho_net.get_router_id'](node_config, grains['id']) }};
+
+
+# this pseudo-protocol watches all interface up/down events
+protocol device {
+	scan time 10;
+};
+
+# This pseudo-protocol performs synchronization between BIRD's routing
+# tables and the kernel. If your kernel supports multiple routing tables
+# (as Linux 2.2.x does), you can run multiple instances of the kernel
+# protocol and synchronize different kernel tables with different BIRD tables.
+protocol kernel {
+	scan time 20;           # Scan kernel routing table every 20 seconds
+
+{% if 'vpn' in node_config.get ('roles') %}
+	# Learn host routes set up by VPN server(s) on this machine.
+	# As there are two VPN hosts it's important to learn an redistribute
+	# these internally to maintain full reachability.
+	learn;
+
+	import filter {
+		if net ~ [
+			  fdca:ffee:ff12:a249::/64+,	# OPS
+			  fdca:ffee:ff12:a250::/64+,	# User-srv
+			  fdca:ffee:ff12:a251::/64+,	# Infra-srv
+			  # Compatibility glue, to be REMOVED, when finally renumbered
+			  fdca:ffee:ff12:a254::/64+,	# Infra-srv legacy
+			  2a02:450:1::/60+		# Vega Assignment
+			 ] then {
+
+			# Bump perference of learned kernel routes from 10(!) to very high,
+			# so they "win" in routed election and there's no clash with any
+			# backup route via OSPF.
+			preference = 12345;
+			accept;
+		}
+
+		reject;
+	};
+{% else %}
+	import none;
+{%- endif %}
+	export all;
+
+	# Force bird to export device routes learned above to freifunk table
+	# This is needed as the routing policy on all nodes will force all
+	# traffic to be routed by this table so we have to make sure even
+	# locally connected networks are reachable from this table.
+	device routes yes;
+}
+
+# Add unreachable routes for any prefix we don't want to route to
+# the internet.
+protocol static bogon_unreach {
+	route ::/96         unreachable; # RFC 4291
+	route 2001:db8::/32 unreachable; # Documentation
+	route fec0::/10     unreachable; # Site Local
+	route fc00::/7      unreachable; # ULA
+}
+
+
+#
+# Load additiional configuration (IGP, FFRL, ICVPN, 'n stuff)
+include "/etc/bird/ff-policy6.conf";
+include "/etc/bird/bird6.d/*.conf";

+ 52 - 0
bird/bird_apt.key

@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+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
+=0VaR
+-----END PGP PUBLIC KEY BLOCK-----

+ 155 - 0
bird/ff-policy.conf

@@ -0,0 +1,155 @@
+#
+# FFHO Routing Policy
+#
+{%- set node_roles = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':roles', []) %}
+{%- set node_sites = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':sites', []) %}
+{%- set sites = salt['pillar.get']('sites', {}) %}
+{%- set te = salt['pillar.get']('te', {}) %}
+
+
+################################################################################
+#                              Static Filters                                  #
+################################################################################
+
+filter external_IPs_to_learn {
+	if net ~ [
+{%- if proto == 'v4' %}
+		80.70.181.59/32,	# mail.ffho.net
+		80.70.181.61/32		# fe01.ffho.net
+{%- else %}
+		2a02:450:1:0::/64	# Vega VM Subnet
+{%- endif %}
+	] then accept;
+
+	reject;
+};
+
+filter own_prefixes {
+	if net ~ [
+{%- if proto == 'v4' %}
+		10.132.0.0/16,
+		172.30.0.0/16{24,24}
+{%- else %}
+		fdca:ffee:ff12::/48,
+		2a03:2260:2342::/48
+{%- endif %}
+	] then accept;
+
+	reject;
+};
+
+
+################################################################################
+#               iBGP routing policy (Communities + Filter) + TE                #
+################################################################################
+
+{%- for site in node_sites %}
+  {%- set site_config = sites.get (site) %}
+  {%- set community = 'SITE_' ~ site|upper|replace('-', '') ~ '_ROUTE' %}
+  {%- set community_id = site_config.get ('site_no')|int + 100 %}
+define {{ community }} = (65132,{{ community_id }});
+{%- endfor %}
+
+# Prefixes longer that site prefix leaving a gateway as TE prefixes.
+# They are for TE core -> gateway only and must not be imported on other gateways.
+define GATEWAY_TE_ROUTE = (65132,300);
+
+# All our prefixes learned in the external VRF and redistributed into the
+# internal network
+define EXTERNAL_ROUTE = (65132,1023);
+
+# TE routes only to be exported by specific border routers
+define EXPORT_RESTRICT       = (65132, 100);
+define EXPORT_ONLY_AT_CR01   = (65132, 101);
+define EXPORT_ONLY_AT_CR02   = (65132, 102);
+define EXPORT_ONLY_AT_CR03   = (65132, 103);
+define EXPORT_ONLY_AT_BBR_KT = (65132, 197);
+
+
+# To be placed in /etc/bird/ff_killswitch.conf
+define SITE_LEGACY_ONLINE = 1;
+define SITE_PADCTY_ONLINE = 1;
+define SITE_PADUML_ONLINE = 1;
+define DRAINED = 0;
+
+
+{%- if 'batman_gw' in node_roles %}
+function tag_site_routes ()
+{
+  {%- for site in node_sites %}
+    {%- set site_config = sites.get (site) %}
+    {%- set prefix = site_config.get ('prefix_' ~ proto) %}
+    {%- set prefix_mask_te = prefix.split ('/')[1]|int + 1 %}
+    {%- set community = 'SITE_' ~ site|upper|replace('-', '') ~ '_ROUTE' %}
+	# {{ site_config.get ('name', site) }}
+	if net ~ [ {{ prefix }}+ ] then {
+		bgp_community.add ({{ community }});
+	}
+
+  {#-
+   # Tag all routes for prefixes longer that site prefix leaving a gateway
+   # as TE prefixes. They are for TE core -> gateway only and must not be
+   # imported on other gateways.
+   #}
+    {%- if proto == 'v4' %}
+	if net ~ [ {{ prefix ~ '{' ~ prefix_mask_te ~ ',32}' }} ] then {
+		bgp_community.add (GATEWAY_TE_ROUTE);
+	}
+    {%- endif %}
+  {% endfor %}
+}
+{%- endif %}
+
+
+filter ibgp_in {
+{#- Don't import other gateways TE prefixes here #}
+{%- if 'batman_gw' in node_roles %}
+        if (GATEWAY_TE_ROUTE ~ bgp_community) then {
+                reject;
+        }
+{%- endif %}
+
+        accept;
+}
+
+
+filter ibgp_out {
+	# Don't redistribute OSPF into iBGP
+	if "IGP" = proto then
+		reject;
+
+	if "bogon_unreach" = proto then
+		reject;
+
+	if "ffrl_te" = proto then
+		reject;
+
+	if 1 = DRAINED then
+		reject;
+
+{%- if 'batman_gw' in node_roles %}
+	tag_site_routes ();
+{%- endif %}
+
+	accept;
+}
+
+
+# Traffic engineering routes
+protocol static ffho_te {
+	preference 23;
+
+{% set prefixes = salt['ffho_net.get_te_prefixes'](te, grains['id'], proto) %}
+{% for prefix in prefixes|sort %}
+  {%- set config = prefixes.get (prefix) %}
+  {%- if 'desc' in config %}
+	# {{ config.get ('desc') }}
+  {%- endif %}
+	route {{ prefix }} unreachable {
+  {%- for community in config.get ('communities', []) %}
+		bgp_community.add ({{ community }});
+  {%- endfor %}
+	};
+{%- endfor %}
+
+}

+ 88 - 0
bird/ffrl.conf

@@ -0,0 +1,88 @@
+#
+# FFRL upstream (Salt maanged)
+#
+{%- set ifaces = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':ifaces', {}) %}
+{%- set sessions = salt['ffho_net.get_ffrl_bgp_config'](ifaces, proto) %}
+{%- set te_community_map_ffrl = salt['pillar.get']('te:community_map:' ~ grains['id'] ~ ':ffrl', [])|sort %}
+
+
+filter ffrl_in {
+	if net ~ [
+{%- if proto == 'v4'%}
+		0.0.0.0/0
+{%- else %}
+		::/0
+{%- endif %}
+	] then {
+		# Rewrite BGP next hop to loopback IP so we don't have to
+		# include transfer networks to AS201701 in IGP.
+		bgp_next_hop = LO_IP;
+		accept;
+	}
+
+	reject;
+};
+
+
+filter ffrl_out {
+{%- if proto == 'v4'%}
+	if proto != "p_nat" then
+		reject;
+
+	if net ~ [
+		185.66.194.80/29+,
+		185.66.194.84/31+,	# FRA-IPs
+		185.66.195.94/31+	# BER-IPs
+{%- else %}
+	if net ~ [
+		2a03:2260:2342::/48{48,56}
+{%- endif %}
+	] then {
+		# Is there an export restriction for this route?
+		if (EXPORT_RESTRICT ~ bgp_community) then {
+  {%- for community in te_community_map_ffrl %}
+			if ({{ community }} ~ bgp_community) then
+				accept;
+
+  {%- endfor %}
+		}
+
+		# No export restriction, go ahead
+		else {
+			accept;
+		}
+	}
+
+	reject;
+};
+
+{%- if proto == 'v4' %}
+
+protocol direct p_nat {
+	interface "nat";
+}
+{%- endif %}
+
+define AS_FFRL = 201701;
+
+template bgp as201701 {
+	import filter ffrl_in;
+	export filter ffrl_out;
+
+	local as 65132;
+
+	preference 200;
+}
+
+
+{% for session in sessions|sort %}
+  {%- set session_config = sessions.get (session) %}
+  {%- set bgp_local_pref = session_config.get ('bgp_local_pref') %}
+protocol bgp {{ session }} from as201701 {
+	source address {{ session_config.get ('local') }};
+	neighbor {{ session_config.get ('neighbor') }} as AS_FFRL;
+  {%- if bgp_local_pref %}
+	default bgp_local_pref {{ bgp_local_pref }};
+  {%- endif %}
+}
+{% endfor %}

+ 56 - 0
bird/ibgp.conf

@@ -0,0 +1,56 @@
+#
+# FFHO iBGP configuration (Salt managed)
+#
+
+template bgp ibgp {
+	import filter ibgp_in;
+	export filter ibgp_out;
+
+	local as AS_OWN;
+
+	source address LO_IP;
+
+	enable route refresh yes;
+	graceful restart yes;
+}
+
+
+{#- Gather information for iBGP sessions #}
+{%- set roles = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':roles', []) %}
+{%- set peers = [] %}
+{%- for node in salt['pillar.get']('nodes', [])|sort if node != grains['id'] %}
+  {%- set peer_node_config = salt['pillar.get']('nodes:' ~ node) %}
+  {%- set peer_roles = peer_node_config.get ('roles', []) %}
+
+  {#- save peers node name, mangle . and - to _ to make bird happy #}
+  {%- set peer_config = { 'node' : salt['ffho.re_replace']('[.-]', '_', node) } %}
+  {%- if 'router' in peer_roles %}
+    {#- Skip peer if neither we nor him are a RR #}
+    {%- if 'routereflector' not in roles and 'routereflector' not in peer_roles %}
+      {%- continue %}
+    {%- endif %}
+
+    {#- Is our peer a route reflector? #}
+    {%- if 'routereflector' in peer_roles %}
+      {%- do peer_config.update ({'rr' : True }) %}
+    {%- endif %}
+    
+    {#- get peers Lo IP #}
+    {%- do peer_config.update ({ 'ip' : salt['ffho_net.get_loopback_ip'] (peer_node_config, node, proto)}) %}
+
+    {%- do peers.append (peer_config) %}
+  {%- endif %}
+{%- endfor %}
+
+
+{#- configure iBGP sessions #}
+{% for peer_config in peers %}
+protocol bgp {{ peer_config.get ('node') }} from ibgp {
+	neighbor {{ peer_config.get ('ip') }} as AS_OWN;
+
+  {%- if 'routereflector' in roles and not 'rr' in peer_config %}
+	rr client;
+  {%- endif %}
+}
+
+{% endfor %}

+ 259 - 0
bird/init.sls

@@ -0,0 +1,259 @@
+#
+# Bird routing daemon
+#
+
+{%- set roles = salt['pillar.get']('nodes:' ~ grains['id'] ~ ':roles', []) %}
+
+include:
+  - network.interfaces
+
+bird-repo:
+  pkgrepo.managed:
+    - comments: "# Official bird repo"
+    - human_name: Official bird repository
+    - name: "deb http://bird.network.cz/debian/ {{ grains['oscodename'] }} main"
+    - dist: {{ grains['oscodename'] }}
+    - file: /etc/apt/sources.list.d/bird.list
+    - key_url: salt://bird/bird_apt.key
+
+
+bird-pkg:
+  pkg.installed:
+    - name: bird
+    - require:
+      - pkgrepo: bird-repo
+
+
+# Make sure both services are enabled
+bird:
+  service.running:
+    - enable: True
+    - running: True
+
+bird6:
+  service.running:
+    - enable: True
+    - running: True
+
+
+# Reload commands for bird{,6} to be tied to files which should trigger reconfiguration
+bird-configure:
+  cmd.wait:
+    - name: /usr/sbin/birdc configure
+    - watch: []
+
+bird6-configure:
+  cmd.wait:
+    - name: /usr/sbin/birdc6 configure
+    - watch: []
+
+
+/etc/bird:
+  file.directory:
+    - mode: 750
+    - user: bird
+    - group: bird
+    - require:
+      - pkg: bird
+
+
+/etc/bird/bird.d:
+  file.directory:
+    - makedirs: true
+    - mode: 755
+    - user: root
+    - group: bird
+    - require:
+      - file: /etc/bird
+
+/etc/bird/bird.conf:
+  file.managed:
+    - source: salt://bird/bird.conf
+    - template: jinja
+    - require:
+      - file: /etc/bird/bird.d
+    - require_in:
+      - service: bird
+    - watch_in:
+      - cmd: bird-configure
+    - mode: 644
+    - user: root
+    - group: bird
+
+
+/etc/bird/bird6.d:
+  file.directory:
+    - makedirs: true
+    - mode: 755
+    - user: root
+    - group: bird
+    - require:
+      - file: /etc/bird
+
+/etc/bird/bird6.conf:
+  file.managed:
+    - source: salt://bird/bird6.conf
+    - template: jinja
+    - require:
+      - file: /etc/bird/bird6.d
+    - watch_in:
+      - cmd: bird6-configure
+    - mode: 644
+    - user: root
+    - group: bird
+    - require_in:
+      - service: bird6
+
+#
+# External VRF / Routing table?
+#
+/etc/bird/bird.d/VRF_external.conf:
+  file.managed:
+    - source: salt://bird/VRF_external.conf
+    - template: jinja
+      proto: v4
+    - watch_in:
+      - cmd: bird-configure
+    - require:
+      - file: /etc/bird/bird.d
+    - require_in:
+      - service: bird
+
+/etc/bird/bird6.d/VRF_external.conf:
+  file.managed:
+    - source: salt://bird/VRF_external.conf
+    - template: jinja
+      proto: v6
+    - watch_in:
+      - cmd: bird6-configure
+    - require:
+      - file: /etc/bird/bird6.d
+    - require_in:
+      - service: bird6
+
+/etc/bird/bird.d/external.conf:
+  file.absent
+/etc/bird/bird6.d/external.conf:
+  file.absent
+
+
+#
+# IGP / OSPF
+#
+/etc/bird/bird.d/IGP.conf:
+  file.managed:
+    - source: salt://bird/IGP.conf
+    - template: jinja
+      proto: v4
+    - watch_in:
+      - cmd: bird-configure
+    - require:
+      - file: /etc/bird/bird.d
+    - require_in:
+      - service: bird
+
+/etc/bird/bird6.d/IGP.conf:
+  file.managed:
+    - source: salt://bird/IGP.conf
+    - template: jinja
+      proto: v6
+    - watch_in:
+      - cmd: bird6-configure
+    - require:
+      - file: /etc/bird/bird6.d
+    - require_in:
+      - service: bird6
+
+# Compatibility glue
+/etc/bird/bird6.d/IGP6.conf:
+  file.absent:
+    - watch_in:
+      - cmd: bird-configure
+
+
+#
+# iBGP
+#
+/etc/bird/ff-policy.conf:
+  file.managed:
+    - source: salt://bird/ff-policy.conf
+    - template: jinja
+      proto: v4
+    - watch_in:
+      - cmd: bird-configure
+    - require:
+      - file: /etc/bird/bird.d
+    - require_in:
+      - service: bird
+
+/etc/bird/ff-policy6.conf:
+  file.managed:
+    - source: salt://bird/ff-policy.conf
+    - template: jinja
+      proto: v6
+    - watch_in:
+      - cmd: bird6-configure
+    - require:
+      - file: /etc/bird/bird6.d
+    - require_in:
+      - service: bird6
+
+/etc/bird/bird.d/ibgp.conf:
+  file.managed:
+    - source: salt://bird/ibgp.conf
+    - template: jinja
+      proto: v4
+    - watch_in:
+      - cmd: bird-configure
+    - require:
+      - file: /etc/bird/bird.d
+    - require_in:
+      - service: bird
+
+/etc/bird/bird6.d/ibgp.conf:
+  file.managed:
+    - source: salt://bird/ibgp.conf
+    - template: jinja
+      proto: v6
+    - watch_in:
+      - cmd: bird6-configure
+    - require:
+      - file: /etc/bird/bird6.d
+    - require_in:
+      - service: bird6
+
+
+
+#
+# FFRL-exit
+#
+{% if 'ffrl-exit' in roles %}
+python-ipcalc:
+  pkg.installed
+
+/etc/bird/bird.d/ffrl.conf:
+  file.managed:
+    - source: salt://bird/ffrl.conf
+    - template: jinja
+      proto: v4
+    - watch_in:
+      - cmd: bird-configure
+    - require:
+      - file: /etc/bird/bird.d
+      - pkg: python-ipcalc
+    - require_in:
+      - service: bird
+
+/etc/bird/bird6.d/ffrl.conf:
+  file.managed:
+    - source: salt://bird/ffrl.conf
+    - template: jinja
+      proto: v6
+    - watch_in:
+      - cmd: bird6-configure
+    - require:
+      - file: /etc/bird/bird6.d
+      - pkg: python-ipcalc
+    - require_in:
+      - service: bird6
+{% endif %}