467 lines
12 KiB
Django/Jinja
467 lines
12 KiB
Django/Jinja
#############################
|
|
## !!! Ansible Managed !!! ##
|
|
#############################
|
|
|
|
# Configure logging
|
|
log syslog all;
|
|
log "/var/log/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug };
|
|
|
|
# Set router ID. It is a unique identification of your router, usually one of
|
|
# IPv4 addresses of the router. It is recommended to configure it explicitly.
|
|
# router id 198.51.100.1; # TODO: set router id
|
|
|
|
# Turn on global debugging of all protocols (all messages or just selected classes)
|
|
# debug protocols all;
|
|
debug protocols { events, states };
|
|
|
|
# Turn on internal watchdog
|
|
watchdog warning 5 s;
|
|
watchdog timeout 30 s;
|
|
|
|
### START CONSTANTS ###
|
|
|
|
define MYASN = {{ asn }};
|
|
define MYIPv6 = {{ ipv6.primary }};
|
|
define MYNETSET6 = [
|
|
{% for route in routes %}
|
|
{{ route }}+{{"," if not loop.last}}
|
|
{% endfor %}
|
|
];
|
|
define INT_OSPF_ACCEPT6 = [
|
|
{% for route in ospf.accepted_routes %}
|
|
{{ route }}{{"," if not loop.last}}
|
|
{% endfor %}
|
|
];
|
|
|
|
define BOGON_ASNS = [
|
|
0, # RFC 7607
|
|
23456, # RFC 4893 AS_TRANS
|
|
64496..64511, # RFC 5398 and documentation/example ASNs
|
|
64512..65534, # RFC 6996 Private ASNs
|
|
65535, # RFC 7300 Last 16 bit ASN
|
|
65536..65551, # RFC 5398 and documentation/example ASNs
|
|
65552..131071, # RFC IANA reserved ASNs
|
|
4200000000..4294967294, # RFC 6996 Private ASNs
|
|
4294967295 # RFC 7300 Last 32 bit ASN
|
|
];
|
|
|
|
define TRANSIT_ASNS = [
|
|
174, # Cogent
|
|
701, # UUNET
|
|
1299, # Telia
|
|
2914, # NTT Ltd.
|
|
3257, # GTT Backbone
|
|
3320, # Deutsche Telekom AG (DTAG)
|
|
3356, # Level3
|
|
3491, # PCCW
|
|
4134, # Chinanet
|
|
5511, # Orange opentransit
|
|
6453, # Tata Communications
|
|
6461, # Zayo Bandwidth
|
|
6762, # Seabone / Telecom Italia
|
|
6830, # Liberty Global
|
|
7018 # AT&T
|
|
];
|
|
|
|
define BOGON_PREFIXES_V6 = [
|
|
::/8+, # RFC 4291 IPv4-compatible, loopback, et al
|
|
0100::/64+, # RFC 6666 Discard-Only
|
|
2001:2::/48+, # RFC 5180 BMWG
|
|
2001:10::/28+, # RFC 4843 ORCHID
|
|
2001:db8::/32+, # RFC 3849 documentation
|
|
3fff::/20+, # RFC 9637 documentation
|
|
2002::/16+, # RFC 7526 6to4 anycast relay
|
|
3ffe::/16+, # RFC 3701 old 6bone
|
|
5f00::/16+, # RFC 9602 SRv6
|
|
fc00::/7+, # RFC 4193 unique local unicast
|
|
fe80::/10+, # RFC 4291 link local unicast
|
|
fec0::/10+, # RFC 3879 old site local unicast
|
|
ff00::/8+ # RFC 4291 multicast
|
|
];
|
|
|
|
define IXP_LANS = [
|
|
2a0c:b641:700::/64+, # LocIX
|
|
2a0e:8f01:1000:11::/64, # BGP.Exchange Amsterdam
|
|
2a0e:8f01:1000:24::/64, # BGP.Exchange Frankfurt
|
|
2a0e:8f01:1000:46::/64 # BGP.Exchange Dusseldorf
|
|
];
|
|
|
|
### END CONSTANTS ###
|
|
|
|
### START RPKI ###
|
|
roa6 table r6;
|
|
### END RPKI ###
|
|
|
|
### START FUNCTIONS ###
|
|
|
|
# Is this network within our own network? Can match more specific prefixes, for example a /64 will return true if it is within our /48
|
|
function is_self_net_v6()
|
|
{
|
|
return net ~ MYNETSET6;
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/reject_invalids/#bird
|
|
# slightly modified
|
|
# Check if ROA has status ROA_VALID
|
|
function check_rpki_rov_valid()
|
|
{
|
|
if roa_check(r6, net, bgp_path.last) = ROA_VALID then {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
# Check if ROA has status ROA_INVALID
|
|
function reject_rpki_rov_invalid()
|
|
{
|
|
if roa_check(r6, net, bgp_path.last) = ROA_INVALID then {
|
|
print "Reject: ROA status ROA_INVALID: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 6));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/bogon_asns/#bird
|
|
function reject_bogon_asns()
|
|
int set bogon_asns;
|
|
{
|
|
bogon_asns = BOGON_ASNS;
|
|
|
|
if ( bgp_path ~ bogon_asns ) then {
|
|
print "Reject: bogon AS_PATH: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 2));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
#source: https://bgpfilterguide.nlnog.net/guides/bogon_prefixes/#bird-1
|
|
function reject_bogon_prefixes_v6()
|
|
prefix set bogon_prefixes;
|
|
{
|
|
bogon_prefixes = BOGON_PREFIXES_V6;
|
|
if (net ~ bogon_prefixes) then {
|
|
print "Reject: Bogon prefix: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 3));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/long_paths/#bird
|
|
function reject_long_aspaths()
|
|
{
|
|
if ( bgp_path.len > 100 ) then {
|
|
print "Reject: Too long AS path: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 4));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/small_prefixes/#bird
|
|
# slightly modified
|
|
function reject_small_prefixes_v6()
|
|
{
|
|
if ( net.len > 48 ) then {
|
|
print "Reject: Too small prefix: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 5));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: custom
|
|
function reject_default_route_v6()
|
|
{
|
|
if ( net = ::/0 ) then {
|
|
print "Reject: Default route: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 7));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
function reject_own_prefixes_v6()
|
|
{
|
|
if is_self_net_v6() then {
|
|
print "Reject: Own prefix: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 1));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/no_transit_leaks/
|
|
# BEWARE: check that peers do not provide transit to tier 1's
|
|
function reject_transit_paths()
|
|
int set transit_asns;
|
|
{
|
|
transit_asns = TRANSIT_ASNS;
|
|
if (bgp_path ~ transit_asns) then {
|
|
print "Reject: Transit ASNs found on IXP: ", net, " ", bgp_path;
|
|
bgp_large_community.add((MYASN, 7, 8));
|
|
reject;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/graceful_shutdown/#bird
|
|
function honor_graceful_shutdown()
|
|
{
|
|
if (65535, 0) ~ bgp_community then {
|
|
bgp_local_pref = 0;
|
|
}
|
|
}
|
|
|
|
# source: https://bgpfilterguide.nlnog.net/guides/many_communities/#bird
|
|
function strip_too_many_communities()
|
|
{
|
|
if ( ( bgp_community.len + bgp_ext_community.len + bgp_large_community.len ) >= 100 ) then {
|
|
print "Too many communities, stripped communities: ", net, " ", bgp_path;
|
|
bgp_community.empty;
|
|
bgp_ext_community.empty;
|
|
bgp_large_community.empty;
|
|
}
|
|
}
|
|
|
|
function scrub_own_communities()
|
|
{
|
|
bgp_large_community.delete([(MYASN, *, *)]);
|
|
}
|
|
|
|
function reject_ixp_lan()
|
|
{
|
|
if ( net ~ IXP_LANS ) then {
|
|
bgp_large_community.add((MYASN, 7, 10));
|
|
print "Reject: Prefix within IXP LAN: ", net, " ", bgp_path;
|
|
reject;
|
|
}
|
|
}
|
|
|
|
### END FUNCTIONS ###
|
|
|
|
### START FILTERS ###
|
|
|
|
filter int_ospf_in_v6 {
|
|
if net !~ INT_OSPF_ACCEPT6 then {
|
|
reject;
|
|
}
|
|
if ( net.len = 128 ) then {
|
|
accept;
|
|
}
|
|
if ( net.len > 64 ) then {
|
|
reject;
|
|
}
|
|
accept;
|
|
}
|
|
|
|
filter f_peer_in_v6 {
|
|
scrub_own_communities();
|
|
reject_rpki_rov_invalid();
|
|
reject_bogon_asns();
|
|
reject_long_aspaths();
|
|
reject_bogon_prefixes_v6();
|
|
reject_small_prefixes_v6();
|
|
reject_ixp_lan();
|
|
reject_default_route_v6();
|
|
honor_graceful_shutdown();
|
|
strip_too_many_communities();
|
|
|
|
bgp_large_community.add((MYASN, 3, 3)); # Route learned from a peering partner
|
|
|
|
accept;
|
|
}
|
|
|
|
filter f_transit_in_v6 {
|
|
scrub_own_communities();
|
|
reject_rpki_rov_invalid();
|
|
reject_bogon_asns();
|
|
reject_long_aspaths();
|
|
reject_bogon_prefixes_v6();
|
|
reject_small_prefixes_v6();
|
|
reject_ixp_lan();
|
|
reject_default_route_v6();
|
|
honor_graceful_shutdown();
|
|
strip_too_many_communities();
|
|
|
|
bgp_large_community.add((MYASN, 3, 4)); # Route learned from a transit provider
|
|
|
|
if check_rpki_rov_valid() then {
|
|
bgp_large_community.add((MYASN, 6, 1)); # Valid matching ROA exists
|
|
}
|
|
|
|
accept;
|
|
}
|
|
|
|
filter f_ebgp_out_v6 {
|
|
if source !~ [RTS_STATIC, RTS_BGP] then reject;
|
|
if ((MYASN, 3, 1) ~ bgp_large_community) then {
|
|
scrub_own_communities();
|
|
accept;
|
|
}
|
|
reject;
|
|
}
|
|
|
|
### END FILTERS ###
|
|
|
|
### START PROTOCOLS ###
|
|
|
|
protocol device {
|
|
scan time 10;
|
|
}
|
|
|
|
# Static route for our network
|
|
protocol static {
|
|
# Create routes for our network without specifying a next hop
|
|
{% for route in routes %}
|
|
route {{ route }} reject;
|
|
{% endfor %}
|
|
ipv6 {
|
|
import filter {
|
|
# Import static routes into bird routing table
|
|
bgp_large_community.add((MYASN, 3, 1)); # Route originated internally
|
|
accept;
|
|
};
|
|
export none; # Do not export routes to this proto
|
|
};
|
|
}
|
|
|
|
protocol kernel {
|
|
ipv6 {
|
|
import none;
|
|
export filter {
|
|
if source = RTS_STATIC then reject; # Do not import our full network into the kernel, we only announce these to peers transits and sometimes customers.
|
|
krt_prefsrc = MYIPv6; # prefer the loopback IP for outgoing connections.
|
|
accept;
|
|
};
|
|
};
|
|
}
|
|
|
|
{% if rpki.bird_rpki_remote_hostname is defined and rpki.bird_rpki_remote_hostname != "" %}
|
|
protocol rpki {
|
|
roa6 {
|
|
table r6;
|
|
};
|
|
remote "{{ rpki.bird_rpki_remote_hostname }}" port {{ rpki.bird_rpki_remote_port }};
|
|
}
|
|
{% endif %}
|
|
|
|
# Internal ospf
|
|
protocol ospf v3 {
|
|
ipv6 {
|
|
import filter int_ospf_in_v6;
|
|
export none; # do not export anything to ospf, ospf will figure things out itself
|
|
};
|
|
area 0.0.0.0 {
|
|
{% for interface in interfaces %}
|
|
{% if 'description' in interface %}
|
|
# desc: {{ interface.description }}
|
|
{% endif %}
|
|
interface "{{ interface.nic }}" {
|
|
{% if 'maintenance' in interface and interface.maintenance %}
|
|
cost 65535;
|
|
{% elif 'cost' in interface %}
|
|
cost {{ interface.cost }};
|
|
{% else %}
|
|
cost {{ ospf.default_cost }};
|
|
{% endif %}
|
|
{% if 'type' in interface and interface.type == 'p2p' %}
|
|
type pointopoint;
|
|
{% endif %}
|
|
{% if 'stub' in interface and interface.stub %}
|
|
stub yes;
|
|
{% endif %}
|
|
};
|
|
{% endfor %}
|
|
};
|
|
}
|
|
|
|
### END PROTOCOLS ###
|
|
|
|
### START TEMPLATES ###
|
|
template bgp internal_peer {
|
|
local as MYASN;
|
|
|
|
ipv6 {
|
|
export where source = RTS_BGP && !is_self_net_v6();
|
|
import where !is_self_net_v6();
|
|
next hop self; # for internal routes the next hop is always the neighboring router
|
|
};
|
|
}
|
|
|
|
template bgp transit {
|
|
local as MYASN;
|
|
hold time 90;
|
|
|
|
ipv6 {
|
|
export filter f_ebgp_out_v6;
|
|
import filter f_transit_in_v6;
|
|
import keep filtered;
|
|
};
|
|
}
|
|
|
|
template bgp peer {
|
|
local as MYASN;
|
|
hold time 90;
|
|
|
|
ipv6 {
|
|
export filter f_ebgp_out_v6;
|
|
import filter f_peer_in_v6;
|
|
import keep filtered;
|
|
};
|
|
}
|
|
|
|
### END TEMPLATES ###
|
|
|
|
### START INTERNAL PEERS ###
|
|
{% if internal_peers is defined and internal_peers | length > 0 %}
|
|
{% for peer in internal_peers | sort(attribute='name') %}
|
|
protocol bgp 'internal_{{ peer.name }}' from internal_peer {
|
|
neighbor {{ peer.ip }} as {{ asn }};
|
|
}
|
|
{% endfor %}
|
|
{% endif %}
|
|
### END INTERNAL PEERS ###
|
|
|
|
### START TRANSITS ###
|
|
{% if transits is defined and transits | length > 0 %}
|
|
{% for peer in transits | sort(attribute='asn,name') %}
|
|
protocol bgp 'transit_AS{{ peer.asn }}_{{ peer.name }}_v6' from transit {
|
|
neighbor {{ peer.peer_ipv6 }} as {{ peer.asn }};
|
|
}
|
|
{% endfor %}
|
|
{% endif %}
|
|
### END TRANSITS ###
|
|
|
|
### START PEERS ###
|
|
{% if peers is defined and peers | length > 0 %}
|
|
{% for peer in peers | sort(attribute='asn,name') %}
|
|
protocol bgp 'peer_AS{{ peer.asn }}_{{ peer.name }}' from peer {
|
|
neighbor {{ peer.peer_ipv6 }} as {{ peer.asn }};
|
|
}
|
|
{% endfor %}
|
|
{% endif %}
|
|
### END PEERS ###
|
|
|
|
### START CUTOM ###
|
|
|
|
{% if bgptools_feeder_enabled | default(false) %}
|
|
protocol bgp 'cust_212232_bgptools' {
|
|
local as MYASN;
|
|
neighbor 185.230.223.45 as 212232;
|
|
|
|
ipv6 {
|
|
export filter {
|
|
if source !~ [RTS_STATIC, RTS_BGP] then reject;
|
|
if ((MYASN, 3, 1) ~ bgp_large_community) then accept; # Internal
|
|
if ((MYASN, 3, 2) ~ bgp_large_community) then accept; # Customer
|
|
if ((MYASN, 3, 3) ~ bgp_large_community) then accept; # Peering
|
|
if ((MYASN, 3, 4) ~ bgp_large_community) then accept; # Transit
|
|
reject;
|
|
};
|
|
import none;
|
|
add paths;
|
|
};
|
|
|
|
multihop 255;
|
|
source address 194.28.98.155;
|
|
}
|
|
{% endif %}
|
|
|
|
### END CUSTOM ###
|