{% from 'macros.j2' import write_prefix_list %}
{% from 'macros.j2' import add_communities %}
{% from 'macros.j2' import match_communities %}

{% macro write_max_pref_action(action) %}
{% if action == "shutdown" %}disable;
{% elif action == "restart" %}restart;
{% elif action == "block" %}block;
{% else %}warn;
{% endif %}
{% endmacro %}

{% macro reject(client, cause, err_msg, avoid_braces=False) %}
{% if client.cfg.filtering.reject_policy.policy == "reject" %}
reject {{ err_msg }};
{% else %}
{%	if not avoid_braces %}{{ "{ " }}{% endif %}tag_and_reject({{ cause}}, {{ client.asn }}); print {{ err_msg }}; accept;{% if not avoid_braces %}{{ " }" }}{% endif %}
{% endif %}
{% endmacro %}
# ---------------------------------------------------------
# MEMBERS

{% for client in clients|sort(attribute="id") if client.ip is current_ipver %}

# AS-SET for {{ client.id }}
function origin_as_is_in_{{ client.id }}_as_set() {
{% if client.cfg.filtering.irrdb.as_set_bundle_ids %}
{%	for as_set_bundle_id in client.cfg.filtering.irrdb.as_set_bundle_ids|sort %}
{%		if irrdb_info[as_set_bundle_id].asns %}
	if bgp_path.last ~ AS_SET_{{ irrdb_info[as_set_bundle_id].name }}_asns then
		return true;
{%		else %}
	# AS-SET {{ irrdb_info[as_set_bundle_id].name }} referenced but empty.
{%		endif %}
{%	endfor %}
{% endif %}
	return false;
}

# R-SET for {{ client.id }}
function prefix_is_in_{{ client.id }}_as_set() {
{% if client.cfg.filtering.irrdb.as_set_bundle_ids %}
{%	for as_set_bundle_id in client.cfg.filtering.irrdb.as_set_bundle_ids|sort %}
{%		if irrdb_info[as_set_bundle_id].prefixes %}
	if net ~ AS_SET_{{ irrdb_info[as_set_bundle_id].name }}_prefixes then
		return true;
{%		else %}
	# AS-SET {{ irrdb_info[as_set_bundle_id].name }} referenced but empty.
{%		endif %}
{%	endfor %}
{% endif %}
	return false;
}

function next_hop_is_valid_for_{{ client.id }}()
{
	# Checks if NEXT_HOP is one of those allowed for routes announced by {{ client.id }}
	{% if client.cfg.filtering.next_hop.policy == "strict" %}
	if bgp_next_hop = {{ client.ip }} then return true;
	{% elif client.cfg.filtering.next_hop.policy == "authorized_addresses" %}
	{%	for addr in client.cfg.filtering.next_hop.authorized_addresses_list|sort if addr is current_ipver %}
	if bgp_next_hop = {{ addr }} then return true;
	{%	endfor %}
	{% else %}
	{% for asn, same_as_clients in clients|groupby("asn") if asn == client.asn %}
	{% for same_as_client in same_as_clients|sort(attribute="ip") if same_as_client.ip is current_ipver %}
	if bgp_next_hop = {{ same_as_client.ip }} then return true;	# {{ same_as_client.id }}
	{% endfor %}
	{% endfor %}
	{% endif %}
	return false;
}

{% if client.cfg.filtering.black_list_pref %}
function prefix_is_in_{{ client.id }}_blacklist()
prefix set {{ client.id }}_blacklist;
{
	{{ client.id }}_blacklist = [
{{ write_prefix_list(client.cfg.filtering.black_list_pref) }}
	];
	return net ~ {{ client.id }}_blacklist;
}
{% endif %}

filter receive_from_{{ client.id }} {
	if !(source = RTS_BGP ) then
		{{ reject(client, 65535, '"source != RTS_BGP - REJECTING ", net') }}

	{% if "pre_receive_from_client"|hook_is_set %}
	if !hook_pre_receive_from_client({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
		{{ reject(client, 65535, '"hook_pre_receive_from_client returned false - REJECTING ", net') }}
	{% endif %}

	scrub_communities_in();

	{% if client.cfg.attach_custom_communities %}
	# Attach custom BGP communities
	{% for name in client.cfg.attach_custom_communities|sort %}
	# {{ name }}
	{{ add_communities(cfg.custom_communities[name]) }}
	{% endfor %}
	{% endif %}

	# NEXT_HOP
	if !next_hop_is_valid_for_{{ client.id }}() then
		{{ reject(client, 5, '"NEXT_HOP [", bgp_next_hop, "] not allowed - REJECTING ", net') }}

	# AS_PATH: length
	if bgp_path.len > {{ cfg.filtering.max_as_path_len }} then
		{{ reject(client, 1, '"AS_PATH len [", bgp_path.len ,"] longer than ' ~ cfg.filtering.max_as_path_len ~ ' - REJECTING ", net') }}

	# AS_PATH: left-most ASN
	if bgp_path.first != {{ client.asn }} then
		{{ reject(client, 6, '"invalid left-most ASN [", bgp_path.first, "] - REJECTING ", net') }}

	{% if client.cfg.filtering.reject_invalid_as_in_as_path %}
	# AS_PATH: invalid ASNs
	if as_path_contains_invalid_asn() then
		{{ reject(client, 7, '"AS_PATH [", bgp_path ,"] contains invalid ASN - REJECTING ", net') }}
	{% endif %}

	{% if cfg.filtering.transit_free.action %}
	# AS_PATH: transit-free ASNs
	if as_path_contains_transit_free_asn() then
	{%	if cfg.filtering.transit_free.action == "reject" %}
		{{ reject(client, 8, '"AS_PATH [", bgp_path ,"] contains transit-free ASN - REJECTING ", net') }}
	{%	else %}
		print "AS_PATH [", bgp_path ,"] contains transit-free ASN - REJECTING ", net;
	{%	endif %}
	{% endif %}

	{% if client.cfg.filtering.irrdb.enforce_origin_in_as_set or cfg.filtering.irrdb.tag_as_set %}
	# AS_PATH: enforce origin via AS-SET (or tag)
	if !origin_as_is_in_{{ client.id }}_as_set() then {
	{%	if client.cfg.filtering.irrdb.enforce_origin_in_as_set %}
		{{ reject(client, 9, '"origin ASN [", bgp_path.last, "] not in allowed as-sets - REJECTING ", net', avoid_braces=True) }}
	{%	else %}
	{%		if cfg.communities.origin_not_present_in_as_set|community_is_set %}
		{{ add_communities(cfg.communities.origin_not_present_in_as_set) }}
	{%		else %}
		print "'tag_as_set' is True but 'origin_not_present_in_as_set' community is not given";
	{%		endif %}
	{%	endif %}
	{%	if cfg.filtering.irrdb.tag_as_set and cfg.communities.origin_present_in_as_set|community_is_set %}
	} else {
	{{	add_communities(cfg.communities.origin_present_in_as_set) }}
	{%	endif %}
	}
	{% endif %}

	{% if ip_ver == 6 %}
	# Prefix: only IPv6 Global Unicast space allowed
	if net !~ [2000::/3+] then
		{{ reject(client, 10, '"prefix is not in IPv6 Global Unicast space - REJECTING ", net') }}
	{% endif %}

	# Prefix: global blacklist
	if prefix_is_in_global_blacklist() then
		{{ reject(client, 3, '"prefix is in global blacklist - REJECTING ", net') }}

	{% if client.cfg.filtering.black_list_pref %}
	# Prefix: client's blacklist
	if prefix_is_in_{{ client.id }}_blacklist() then
		{{ reject(client, 11, '"prefix is in client\'s blacklist - REJECTING ", net') }}
	{% endif %}

	# Prefix: bogon
	if prefix_is_bogon() then
		{{ reject(client, 2, '"prefix is bogon - REJECTING ", net') }}

	{% if client.cfg.filtering.irrdb.enforce_prefix_in_as_set or cfg.filtering.irrdb.tag_as_set %}
	# Prefix: enforce prefix via AS-SET (or tag)
	if !prefix_is_in_{{ client.id }}_as_set() then {
	{%	if client.cfg.filtering.irrdb.enforce_prefix_in_as_set %}
		{{ reject(client, 12, '"prefix not in client\'s r_set - REJECTING ", net', avoid_braces=True) }}
	{%	else %}
	{%		if cfg.communities.prefix_not_present_in_as_set|community_is_set %}
		{{ add_communities(cfg.communities.prefix_not_present_in_as_set) }}
	{%		else %}
		print "'tag_as_set' is True but 'prefix_not_present_in_as_set' community is not given";
	{%		endif %}
	{%	endif %}
	{%	if cfg.filtering.irrdb.tag_as_set and cfg.communities.prefix_present_in_as_set|community_is_set %}
	} else {
	{{	add_communities(cfg.communities.prefix_present_in_as_set) }}
	{%	endif %}
	}
	{% endif %}

	# Blackhole request?
	if is_blackhole_filtering_request() then {
		accept "blackhole filtering request from {{ client.id }} - ACCEPTING ", net;
	} else {
		{% if cfg.filtering.rpki.enabled %}
		perform_rpki_validation();

		{%	if client.cfg.filtering.rpki.reject_invalid %}
		if route_is_rpki_invalid() then
			{{ reject(client, 14, '"RPKI, route is INVALID - REJECTING ", net') }}
		{%	endif %}
		{% endif %}

		# Prefix: length
		{% if ip_ver == 4 %}
		{% set min_pref_len = client.cfg.filtering.ipv4_pref_len.min %}
		{% set max_pref_len = client.cfg.filtering.ipv4_pref_len.max %}
		{% else %}
		{% set min_pref_len = client.cfg.filtering.ipv6_pref_len.min %}
		{% set max_pref_len = client.cfg.filtering.ipv6_pref_len.max %}
		{% endif %}
		if !prefix_len_is_valid({{ min_pref_len }}, {{ max_pref_len }}) then
			{{ reject(client, 13, '"prefix len [", net.len, "] not in ' ~ min_pref_len ~ '-' ~ max_pref_len ~ ' - REJECTING ", net') }}

		{% if cfg.graceful_shutdown.enabled %}
		{%	if client.cfg.graceful_shutdown.enabled %}
		honor_graceful_shutdown();
		{%	else %}
		prevent_graceful_shutdown();
		{%	endif %}
		{% endif %}
	}

	{% if "post_receive_from_client"|hook_is_set %}
	if !hook_post_receive_from_client({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
		{{ reject(client, 65535, '"hook_post_receive_from_client returned false - REJECTING ", net') }}
	{% endif %}

	accept;
}

filter announce_to_{{ client.id }} {
	{% if "pre_announce_to_client"|hook_is_set %}
	if !hook_pre_announce_to_client({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
		reject "hook_pre_announce_to_client returned false - NOT ANNOUNCING ", net, " TO {{ client.id}}";
	{% endif %}

	{% if cfg.communities.reject_cause|community_is_set %}
	{{- match_communities(cfg.communities.reject_cause, "reject;", replace_dyn_val="0") }}
	{% endif %}

	# Blackhole request?
	if is_blackhole_filtering_request() then {
	{% if not client.cfg.blackhole_filtering.announce_to_client %}
		reject "client {{ client.id }} not enabled to receive blackhole prefixes - NOT ANNOUNCING ", net, " TO {{ client.id}}";
	{% else %}
		apply_blackhole_filtering_policy();
	{% endif %}
	{% if cfg.filtering.rpki.enabled %}
	} else {
		# RPKI: do not announce INVALID to clients
		if route_is_rpki_invalid() then {
			if !announce_rpki_invalid_to_client({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
				reject "route is INVALID - NOT ANNOUNCING ", net, " TO {{ client.id}}";
		}
	}
	{% else %}
	}
	{% endif %}

	# BGP control communities
	if !route_can_be_announced_to({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
		reject "route didn't pass control communities checks - NOT ANNOUNCING ", net, " TO {{ client.id }}";

	apply_prepend({{ client.asn }}, {{ client.ip }});

	{% if client.cfg.prepend_rs_as %}
	# {{ client.id }} has been configured with 'prepend_rs_as'
	bgp_path.prepend({{ cfg.rs_as }});
	{% endif %}

	add_noexport_noadvertise({{ client.asn }});

	scrub_communities_out();

	{% if perform_graceful_shutdown %}
	perform_graceful_shutdown();
	{% endif %}

	{% if "post_announce_to_client"|hook_is_set %}
	if !hook_post_announce_to_client({{ client.asn }}, {{ client.ip }}, "{{ client.id }}") then
		reject "hook_post_announce_to_client returned false - NOT ANNOUNCING ", net, " TO {{ client.id}}";
	{% endif %}

	accept;
}

{% if "rtt" in client and client["rtt"] %}
# RTT: {{ client["rtt"] }} ms (normalized value: {{ client["rtt"]|get_normalized_rtt }})
{% endif %}
protocol bgp {{ client.id }} {
	{% if client.description %}
	description "{{ client.description|replace("\"", "") }}";
	{% endif %}

	local as {{ cfg.rs_as }};
	neighbor {{ client.ip }} as {{ client.asn }};
	rs client;

	{% if client.password %}
	password "{{ client.password }}";
	{% endif -%}

	{% if client.cfg.passive %}
	passive on;
	{% else %}
	passive off;
	{% endif -%}

	{% if client.cfg.gtsm %}
	ttl security on;
	{% else %}
	ttl security off;
	{% endif -%}

	{% if client.cfg.add_path %}
	add paths tx;
	{% endif %}

	{% if cfg.path_hiding %}
	secondary;
	{% endif %}

	{% if cfg.rfc1997_wellknown_communities.policy == "rfc1997" %}
	interpret communities on;
	{% else %}
	interpret communities off;
	{% endif %}

	{% if client.cfg.filtering.max_prefix.action %}
	{%	if ip_ver == 4 and client.cfg.filtering.max_prefix.limit_ipv4 %}
	{%		set max_pref_limit = client.cfg.filtering.max_prefix.limit_ipv4 %}
	{% 	elif ip_ver == 6 and client.cfg.filtering.max_prefix.limit_ipv6 %}
	{%      	set max_pref_limit = client.cfg.filtering.max_prefix.limit_ipv6 %}
	{% 	else %}
	{%		set max_pref_limit = 0 %}
	{% 	endif %}
	{% 	if max_pref_limit > 0 %}
	import limit {{ max_pref_limit }} action {{ write_max_pref_action(client.cfg.filtering.max_prefix.action) }}
	{% 	endif %}
	{% endif %}

	import keep filtered on;

	{{ "client"|include_local_file -}}
	{% if ip_ver == 4 %}
	{{ "client4"|include_local_file -}}
	{% else %}
	{{ "client6"|include_local_file -}}
	{% endif %}

	import filter receive_from_{{ client.id }};
	export filter announce_to_{{ client.id }};
}

{% endfor -%}
