{% from 'macros.j2' import write_prefix_list %}
{% from 'macros.j2' import del_communities %}
{% from 'macros.j2' import match_communities %}
{% from 'macros.j2' import add_communities %}
# ---------------------------------------------------------
# BOGONS

# This function returns True if 'net' is a bogon prefix
# or falls within a bogon prefix.
function prefix_is_bogon()
prefix set bogons;
{
	bogons = [
{{ write_prefix_list(bogons) }}
	];

	if net ~ bogons then return true;
	return false;
}

# This function returns True if 'net' falls within a
# prefix contained in the global blacklist (for example,
# local networks)
function prefix_is_in_global_blacklist()
prefix set global_blacklist;
{
{% if cfg.filtering.global_black_list_pref %}
	global_blacklist = [
{{ write_prefix_list(cfg.filtering.global_black_list_pref) }}
	];

	if net ~ global_blacklist then return true;
{% else %}
	# No prefixes configured under the cfg.filtering.global_black_list_pref section.
{% endif %}
	return false;
}

# This function returns True if the length of 'net' prefix
# falls within the range 'min'-'max' (included).
function prefix_len_is_valid (int pref_len_min; int pref_len_max) {
	if net.len < pref_len_min then return false;
	if net.len > pref_len_max then return false;
	return true;
}

# This function returns True if the AS_PATH contains one or
# more private/reserved ASN.
function as_path_contains_invalid_asn() 
int set invalid_asns;
{
	# http://www.iana.org/assignments/as-numbers/as-numbers.xhtml
	invalid_asns = [
		# 16-bit
		0,			# Reserved. RFC7607
		23456,			# AS_TRANS. RFC6793
		64496..64511,		# Reserved for use in documentation and sample code. RFC5398
		64512..65534,		# Reserved for Private Use. RFC6996
		65535,			# Reserved. RFC7300

		# 32-bit
		65536..65551,		# Reserved for use in documentation and sample code. RFC5398
		65552..131071,		# Reserved.
		4200000000..4294967294, # Reserved for Private Use. [RFC6996]
		4294967295		# Reserved. RFC7300
	];
	return bgp_path ~ invalid_asns;
}

# This function returns True if the AS_PATH contains one or
# more transit-free ASNs in a position different from the first.
function as_path_contains_transit_free_asn()
int set transit_free_asns;
{
{% if cfg.filtering.transit_free.asns %}
	transit_free_asns = [
		{% for asn in cfg.filtering.transit_free.asns %}
		{{ asn }}{% if not loop.last %}, {% endif %}{% if loop.index % 5 == 0 %}{{ "\n" }}{% endif %}
		{% endfor %}

	];
	if bgp_path.first ~ transit_free_asns then
		return false;
	return bgp_path ~ transit_free_asns;
{% else %}
	# No 'cfg.filtering.transit_free.asns' provided.
	return false;
{% endif %}
}

# This function scrubs BGP communities used by the route server
# for signaling purpose toward its clients. (RFC7454, Section 11)
# It must be applied on routes entering the route server.
function scrub_communities_in() {
{% for name in cfg.communities if cfg.communities[name].type == "outbound" %}
{% if cfg.communities[name]|community_is_set %}
	# {{ name }}
{{ del_communities(cfg.communities[name]) }}
{% endif %}
{% endfor %}
}

# This function scrubs BGP communities used by clients to instruct
# the route server to perform some actions.
# It must be applied on routes leaving the route server.
function scrub_communities_out() {
{% for name in cfg.communities if cfg.communities[name].type == "inbound" %}
{% if cfg.communities[name]|community_is_set %}
	# {{ name }}
{{ del_communities(cfg.communities[name], cfg.communities[name].peer_as) }}
{% endif %}
{% endfor %}
}

# This function verifies if the route is tagged with one of
# the blackhole filtering communities.
function is_blackhole_filtering_request() {
{% if ( ip_ver == 4 and cfg.blackhole_filtering.policy_ipv4 ) or
	( ip_ver == 6 and cfg.blackhole_filtering.policy_ipv6 ) %}
	if (65535, 666) ~ bgp_community then
		return true;
{{ match_communities(cfg.communities.blackholing, "return true;") }}
{% endif %}
	return false;
}

# This function must be applied to outgoing routes.
# It applies the blackhole filtering policy to the current route.
function apply_blackhole_filtering_policy() {
{% if ip_ver == 4 and cfg.blackhole_filtering.policy_ipv4 == "propagate-unchanged" %}
	# Configured policy: propagate-unchanged
	bgp_community.add((65535, 666));
{%	if cfg.blackhole_filtering.add_noexport %}
	# NO_EXPORT
	bgp_community.add((65535, 65281));
{%	endif %}
{% elif ip_ver == 4 and cfg.blackhole_filtering.policy_ipv4 == "rewrite-next-hop" %}
	# Configured policy: rewrite-next-hop
	bgp_community.add((65535, 666));
	bgp_next_hop = {{ cfg.blackhole_filtering.rewrite_next_hop_ipv4 }};
{%	if cfg.blackhole_filtering.add_noexport %}
	# NO_EXPORT
	bgp_community.add((65535, 65281));
{%	endif %}
{% elif ip_ver == 6 and cfg.blackhole_filtering.policy_ipv6 == "propagate-unchanged" %}
	# Configured policy: propagate-unchanged
	bgp_community.add((65535, 666));
{%	if cfg.blackhole_filtering.add_noexport %}
	# NO_EXPORT
	bgp_community.add((65535, 65281));
{%	endif %}
{% elif ip_ver == 6 and cfg.blackhole_filtering.policy_ipv6 == "rewrite-next-hop" %}
	# Configured policy: rewrite-next-hop
	bgp_community.add((65535, 666));
	bgp_next_hop = {{ cfg.blackhole_filtering.rewrite_next_hop_ipv6 }};
{%	if cfg.blackhole_filtering.add_noexport %}
	# NO_EXPORT
	bgp_community.add((65535, 65281));
{%	endif %}
{% else %}
	reject "blackhole filtering requested but no policy given - REJECTING ", net;
{% endif %}
}

# This function verifies if the current route can be announced to
# the given client on the basis of the attached control BGP
# communities.
function route_can_be_announced_to(int peer_as) {
	{% if cfg.communities.do_not_announce_to_peer|community_is_set %}
	# do_not_announce_to_peer
	{{- match_communities(cfg.communities.do_not_announce_to_peer, "return false;") }}
	{% endif %}
	{% if cfg.communities.announce_to_peer|community_is_set %}
	# announce_to_peer
	{{- match_communities(cfg.communities.announce_to_peer, "return true;") }}
	{% endif %}
	{% if cfg.communities.do_not_announce_to_any|community_is_set %}
	# do_not_announce_to_any
	{{- match_communities(cfg.communities.do_not_announce_to_any, "return false;") }}
	{% endif %}
	return true;
}

# This function verifies if the current route matches one of the
# control communities in charge of prepending client's ASN.
function apply_prepend(int peer_as) {
	{% if cfg.communities.prepend_once_to_peer|community_is_set %}
	# prepend_once_to_peer
	{{ match_communities(cfg.communities.prepend_once_to_peer, "{ bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}
	{% if cfg.communities.prepend_twice_to_peer|community_is_set %}
	# prepend_twice_to_peer
	{{ match_communities(cfg.communities.prepend_twice_to_peer, "{ bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}
	{% if cfg.communities.prepend_thrice_to_peer|community_is_set %}
	# prepend_thrice_to_peer
	{{ match_communities(cfg.communities.prepend_thrice_to_peer, "{ bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}

	{% if cfg.communities.prepend_once_to_any|community_is_set %}
	# prepend_once_to_any
	{{ match_communities(cfg.communities.prepend_once_to_any, "{ bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}
	{% if cfg.communities.prepend_twice_to_any|community_is_set %}
	# prepend_twice_to_any
	{{ match_communities(cfg.communities.prepend_twice_to_any, "{ bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}
	{% if cfg.communities.prepend_thrice_to_any|community_is_set %}
	# prepend_thrice_to_any
	{{ match_communities(cfg.communities.prepend_thrice_to_any, "{ bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); return true; }") }}
	{% endif %}
}

{% if cfg.filtering.rpki.enabled %}
# This function adds the BGP communities used to
# inform clients about the RPKI validity state of
# a route.
function add_rpki_community(string comm_name) {
	if comm_name = "valid" then {
{% if cfg.communities.roa_valid|community_is_set %}
{{ add_communities(cfg.communities.roa_valid) }}
{% endif %}
	}
	if comm_name = "unknown" then {
{% if cfg.communities.roa_unknown|community_is_set %}
{{ add_communities(cfg.communities.roa_unknown) }}
{% endif %}
	}
	if comm_name = "invalid" then {
{% if cfg.communities.roa_invalid|community_is_set %}
{{ add_communities(cfg.communities.roa_invalid) }}
{% endif %}
	}
}

# This functions performs RPKI validation of the current
# route and adds the informative communities.
# If the route is INVALID and the 'rpki.reject_invalid'
# option is True the functions return false; otherwise it
# returns true.
function route_passes_rpki_validation () {
	case roa_check(RPKI) {
		ROA_VALID: add_rpki_community("valid");
		ROA_UNKNOWN: add_rpki_community("unknown");
		ROA_INVALID: add_rpki_community("invalid");{% if cfg.filtering.rpki.enabled and cfg.filtering.rpki.reject_invalid %} return false;{{ "\n" }}{% endif %}
	}
	return true;
}
{% endif %}

# This function adds NO_EXPORT and/or NO_ADVERTISE
# well-known communities.
function add_noexport_noadvertise(int peer_as) {
	{% if cfg.communities.add_noexport_to_any|community_is_set %}
	# add_noexport_to_any
	{{ match_communities(cfg.communities.add_noexport_to_any, "{ bgp_community.add((65535, 65281)); }") }}
	{% endif %}
	{% if cfg.communities.add_noadvertise_to_any|community_is_set %}
	# add_noadvertise_to_any
	{{ match_communities(cfg.communities.add_noadvertise_to_any, "{ bgp_community.add((65535, 65282)); }") }}
	{% endif %}
	{% if cfg.communities.add_noexport_to_peer|community_is_set %}
	# add_noexport_to_peer
	{{ match_communities(cfg.communities.add_noexport_to_peer, "{ bgp_community.add((65535, 65281)); }") }}
	{% endif %}
	{% if cfg.communities.add_noadvertise_to_peer|community_is_set %}
	# add_noadvertise_to_peer
	{{ match_communities(cfg.communities.add_noadvertise_to_peer, "{ bgp_community.add((65535, 65282)); }") }}
	{% endif %}
}
