{% from 'macros.j2' import write_prefix_list %}
{% from 'macros.j2' import write_prefix_list_entry %}
{% from 'macros.j2' import write_prefixset_list %}
{% from 'macros.j2' import match_communities with context %}
{% from 'macros.j2' import add_communities with context %}
{% from 'macros.j2' import del_communities with context %}
{% from 'macros.j2' import ins_communities %}
{% from 'macros.j2' import delete_prepending_comm %}
# ---------------------------------------------------------
# FILTERS

# NO_ADVERTISE usage notes.
# The NO_ADVERTISE well-know community is used here to handle
# filters that span over multiple steps. At first it is added
# to any route, then it is removed as filters conditions are
# satisfied. Finally, if it is still present, it means that
# the route should be discarded.

{% macro internal_communities_list(all=false) %}
ext-community delete $INTCOMM_PREF_OK_ROA
	ext-community delete $INTCOMM_ROUTE_OK_WL
	ext-community delete $INTCOMM_PREF_OK_ARINDB
	ext-community delete $INTCOMM_PREF_OK_REGISTROBRDB
	ext-community delete $INTCOMM_ORIGIN_OK
	ext-community delete $INTCOMM_ORIGIN_KO
	ext-community delete $INTCOMM_PREFIX_OK
	ext-community delete $INTCOMM_PREFIX_KO
	ext-community delete $INTCOMM_IRR_REJECT
	ext-community delete $INTCOMM_RPKI_UNKNOWN
	{% if all %}
	ext-community delete $INTCOMM_RPKI_INVALID
	{% endif %}
	ext-community delete $INTCOMM_RPKI_VALID
	{% if all %}
	ext-community delete $INTCOMM_NO_EXPORT
	ext-community delete $INTCOMM_NO_ADVERTISE
	{% endif %}
	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS
{% endmacro %}

{% macro deny_inbound_route(tag_reject_policy, condition, cause_code) %}
# Reject inbound routes when '{{ condition }}' - reject code: {{ cause_code }}
{%  if tag_reject_policy %}
{%    if any_reject_cause_map_community_set %}
{%      set reject_cause_map_comm_name = "reject_cause_map_" ~ cause_code %}
{%      if cfg.communities[reject_cause_map_comm_name]|community_is_set %}
# community from reject_cause_map
allow quick {{ condition }} set {
	localpref 1
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=0).strip() }}
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=cause_code).strip() }}
	{{ ins_communities(cfg.communities[reject_cause_map_comm_name]).strip() }}
	community delete NO_ADVERTISE
	{{ internal_communities_list(all=true) }}
}
{%      else %}
allow quick {{ condition }} set {
	localpref 1
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=0).strip() }}
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=cause_code).strip() }}
	community delete NO_ADVERTISE
	{{ internal_communities_list(all=true) }}
}
{%      endif %}
{%    else %}
allow quick {{ condition }} set {
	localpref 1
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=0).strip() }}
	{{ ins_communities(cfg.communities.reject_cause, dyn_val=cause_code).strip() }}
	community delete NO_ADVERTISE
	{{ internal_communities_list(all=true) }}
}
{%    endif %}
{%  else %}
deny quick {{ condition }}
{%  endif %}
{% endmacro %}

{% macro irrdb_filter(client, objects, int_comm_ok, int_comm_ko, comm_object_present_in_as_set, comm_object_not_present_in_as_set) %}
# IRRDB filters for {{ client.id }}, AS{{ client.asn }}: {{ objects }}
# add {{int_comm_ko}} to any; it will be removed later if at least one AS-SET authorizes this object
match from {{ client.ip  }} set ext-community {{ int_comm_ko }}
{%      if client.cfg.filtering.irrdb.as_set_bundle_ids %}
# verifying if object is authorized by AS-SETs
{%              for as_set_bundle_id in client.cfg.filtering.irrdb.as_set_bundle_ids|sort %}
{%			set as_set_bundle = irrdb_info[as_set_bundle_id] %}
{%			set as_set_data = as_set_bundle[objects] %}
{%			if as_set_data %}
{%				if objects == "asns" %}
{%					set matching_element = "source-as as-set" %}
{%				else %}
{%					set matching_element = "prefix-set" %}
{%				endif %}
{%				set condition = matching_element + " AS_SET_" ~ as_set_bundle.name ~ "_" ~ objects %}
match from {{ client.ip }} {{ condition }} set {
	ext-community delete {{ int_comm_ko }}
	ext-community {{ int_comm_ok }}
} # {{ as_set_bundle.name }}
{%			else %}
# AS-SET {{ as_set_bundle.name }} referenced but empty.
{%                      endif %}
{%              endfor %}
{%      endif %}
{%	if cfg.filtering.irrdb.tag_as_set %}
{%		if comm_object_not_present_in_as_set|community_is_set %}
# adding not_present_in_as_set community to unauthorized routes
{{ add_communities("match from " ~ client.ip ~ " ext-community " ~ int_comm_ko, comm_object_not_present_in_as_set) -}}
{%		endif %}
{%		if comm_object_present_in_as_set|community_is_set %}
# adding present_in_as_set community to authorized routes
{{ add_communities("match from " ~ client.ip ~ " ext-community " ~ int_comm_ok, comm_object_present_in_as_set) -}}
{%		endif %}
{%	endif %}
{% endmacro %}

{% if cfg.filtering.global_black_list_pref %}
prefix-set "global_black_list_pref" {
{{		write_prefixset_list(cfg.filtering.global_black_list_pref) }}
}
{% endif %}

prefix-set "bogons" {
{{	write_prefixset_list(bogons) }}
}

{% if never_via_route_servers_asns %}
# never via route-servers ASNs
as-set "neverviarouteserver" {
	{{ never_via_route_servers_asns|sort|join(", ") }}
}
{% endif %}

{{ "pre-filters"|include_local_file -}}

# =====================================================================================
# Global rules.

# This part of configuration is processed at the beginning of the filters.
# The rules defined in this part are applied to all the clients, and not on a
# client-by-client basis (see the 'match from group clients'), so only global policies
# can be implemented here, that is no client-level configuration are allowed.



# Scrub communities from inbound routes
{% for name in cfg.communities|sort if cfg.communities[name].type in ["outbound", "internal"] %}
{%	if cfg.communities[name]|community_is_set %}
# {{ name }}
{{ del_communities("match from group clients", cfg.communities[name], replace_dyn_val="*") -}}
{%	endif %}
{% endfor %}
{% for name in cfg.custom_communities|sort %}
{%	if cfg.custom_communities[name]|community_is_set %}
# {{ name }}
{{ del_communities("match from group clients", cfg.custom_communities[name]) -}}
{%	endif %}
{% endfor %}

# Scrub internal communities from inbound routes
match from group clients set {
	{{ internal_communities_list(all=true) }}
}



# The main goal of this block is to enrich routes received from clients by attaching to them
# internal informational communities which are used later by the rest of the filter rules.

{% if cfg.rfc1997_wellknown_communities.policy == "pass" %}
# Internal communities used for RFC1997 well-known communities handling

# Transform NO_EXPORT into $INTCOMM_NO_EXPORT
match from group clients community NO_EXPORT set { ext-community $INTCOMM_NO_EXPORT community delete NO_EXPORT }

# Transform NO_ADVERTISE into $INTCOMM_NO_ADVERTISE
match from group clients community NO_ADVERTISE set { ext-community $INTCOMM_NO_ADVERTISE community delete NO_ADVERTISE }
{% endif %}

{% include "rpki.j2" %}

{% if cfg.filtering.irrdb.use_arin_bulk_whois_data.enabled and
	arin_whois_records %}
# ARIN Whois records used for preifx validation
# ---------------------------------------------

# Add the $INTCOMM_PREF_OK_ARINDB ext community to routes whose
# origin ASN has an ARIN Whois record for the announced prefix.
# It will be used later during IRRDB validation in
# case the origin ASN is authorized by a client's
# AS-SET but the prefix is not.
origin-set "ARINDB" {
{%	for origin_asn in arin_whois_records|sort %}
{%		for prefix in arin_whois_records[origin_asn].prefixes|sort(attribute="prefix") %}
{{ write_prefix_list_entry(prefix) }} source-as {{ origin_asn|replace("AS", "") }}
{%    		endfor %}
{%	endfor %}
}
match from group clients origin-set ARINDB set ext-community $INTCOMM_PREF_OK_ARINDB
{% endif %}

{% if cfg.filtering.irrdb.use_registrobr_bulk_whois_data.enabled and
	registrobr_whois_records %}
# NIC.BR Whois records used for preifx validation
# -----------------------------------------------

# Add the $INTCOMM_PREF_OK_REGISTROBRDB ext community to routes whose
# origin ASN has a NIC.BR Whois record for the announced prefix.
# It will be used later during IRRDB validation in
# case the origin ASN is authorized by a client's
# AS-SET but the prefix is not.
origin-set "REGISTROBRDB" {
{%	for origin_asn in registrobr_whois_records|sort %}
{%		for prefix in registrobr_whois_records[origin_asn].prefixes|sort(attribute="prefix") %}
{{ write_prefix_list_entry(prefix) }} source-as {{ origin_asn|replace("AS", "") }}
{%		endfor %}
{%	endfor %}
}
match from group clients origin-set REGISTROBRDB set ext-community $INTCOMM_PREF_OK_REGISTROBRDB
{% endif %}



{% if cfg.communities.rejected_route_announced_by|community_is_set %}
# Set the 'rejected_route_announced_by' community for all the clients.
# It will be removed later if the route is not invalid
{%	for client in clients|sort(attribute="id") if client.ip is current_ipver %}
{%		if client.cfg.filtering.reject_policy.policy == "tag" %}
{{			add_communities("match from " ~ client.ip, cfg.communities.rejected_route_announced_by, dyn_val=client.asn, dyn_val_is_peer_as=True) }}
{%		endif %}
{%	endfor %}
{% endif %}



# AS_PATH: length
{{ deny_inbound_route(at_least_one_client_uses_tag_reject_policy, "from group clients max-as-len " ~ cfg.filtering.max_as_path_len, 1) }}

{% if cfg.filtering.global_black_list_pref %}
# Prefix: global blacklist
{{ deny_inbound_route(at_least_one_client_uses_tag_reject_policy, "from group clients prefix-set global_black_list_pref", 3) }}
{% endif %}

# Prefix: only IPv6 Global Unicast space allowed
match from group clients inet6 set community NO_ADVERTISE
match from group clients prefix 2000::/3 or-longer set community delete NO_ADVERTISE
{{ deny_inbound_route(at_least_one_client_uses_tag_reject_policy, "from group clients community NO_ADVERTISE", 10) }}

# Prefix: bogon
{{ deny_inbound_route(at_least_one_client_uses_tag_reject_policy, "from group clients prefix-set bogons", 2) }}



# =====================================================================================
# Per client rules.

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

# ---------------------------------------------
# client {{ client.id }}, inbound

{% set client_uses_tag_reject_policy = False %}
{% if client.cfg.filtering.reject_policy.policy == "tag" %}
{%	set client_uses_tag_reject_policy = True %}
{% endif %}

{% if client.cfg.attach_custom_communities %}
# Attach custom BGP communities
{%	for name in client.cfg.attach_custom_communities|sort %}
# {{ name }}
{{ add_communities("match from " ~ client.ip, cfg.custom_communities[name]) -}}
{%	endfor %}
{% endif %}

# NEXT_HOP
match from {{ client.ip }} set community NO_ADVERTISE
{% if client.cfg.filtering.next_hop.policy == "strict" %}
match from {{ client.ip }} nexthop {{ client.ip }} set community delete NO_ADVERTISE
{% elif client.cfg.filtering.next_hop.policy == "authorized_addresses" %}
{%	for addr in client.cfg.filtering.next_hop.authorized_addresses_list|sort if addr|ipaddr_ver == client.ip|ipaddr_ver %}
match from {{ client.ip }} nexthop {{ addr }} set community delete NO_ADVERTISE
{%	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|ipaddr_ver == client.ip|ipaddr_ver %}
match from {{ client.ip }} nexthop {{ same_as_client.ip }} set community delete NO_ADVERTISE
{%		endfor %}
{%	endfor %}
{% endif %}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " community NO_ADVERTISE", 5) }}

# AS_PATH: left-most ASN
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " peer-as != " ~ client.asn, 6) }}

{% if client.cfg.filtering.reject_invalid_as_in_as_path %}
# AS_PATH: invalid ASNs
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " AS 23456", 7) }}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " AS 64496 - 131071", 7) }}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " AS 4200000000 - 4294967295", 7) }}
{% endif %}

{% set client_transit_free_asns = cfg.filtering.transit_free.asns|select("ne", client.asn) %}
{% if cfg.filtering.transit_free.action == "reject" and client_transit_free_asns %}
# AS_PATH: transit-free ASNs
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " AS { " ~ client_transit_free_asns|sort|join(", ") ~ " }", 8) }}
{% endif %}

{% if never_via_route_servers_asns %}
# AS_PATH: never via route-servers ASNs
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " AS as-set neverviarouteserver", 15) }}
{% endif %}

{% if client.cfg.filtering.irrdb.white_list_route %}
# client's white list
# Add the $INTCOMM_ROUTE_OK_WL ext community to routes which
# are validated by a client's white list entry.
# It will be used later during IRRDB validation in
# case the route is not authorized by a client's
# AS-SET.
{%	for route in client.cfg.filtering.irrdb.white_list_route|sort(attribute="prefix") if route.prefix is current_ipver %}
{%		if route.asn %}
match from {{ client.ip }} prefix {{ write_prefix_list_entry(route) }} source-as {{ route.asn }} set ext-community $INTCOMM_ROUTE_OK_WL	# {{ route.comment }}
{%		else %}
match from {{ client.ip }} prefix {{ write_prefix_list_entry(route) }} set ext-community $INTCOMM_ROUTE_OK_WL	# {{ route.comment }}
{%		endif %}
{%	endfor %}
{% endif %}

{% if client.cfg.filtering.irrdb.enforce_origin_in_as_set or client.cfg.filtering.irrdb.enforce_prefix_in_as_set or cfg.filtering.irrdb.tag_as_set %}
match from {{ client.ip }} set ext-community $INTCOMM_IRR_REJECT
{% endif %}

{% if client.cfg.filtering.irrdb.enforce_origin_in_as_set or cfg.filtering.irrdb.tag_as_set %}
# AS_PATH: check origin via AS-SET
{{ irrdb_filter(client, "asns", "$INTCOMM_ORIGIN_OK", "$INTCOMM_ORIGIN_KO",
		cfg.communities.origin_present_in_as_set, cfg.communities.origin_not_present_in_as_set) -}}
{% endif %}

{% if client.cfg.filtering.black_list_pref %}
# Prefix: client's blacklist
{%	set pref_list_name = "client_" ~ client.id ~ "_black_list_pref_ipv" ~ client.ip|ipaddr_ver %}
prefix-set "{{ pref_list_name }}" {
{{ write_prefixset_list(client.cfg.filtering.black_list_pref, client.ip|ipaddr_ver) }}
}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " prefix-set " ~ pref_list_name, 11) }}
{% endif %}

{% if client.cfg.filtering.irrdb.enforce_prefix_in_as_set or cfg.filtering.irrdb.tag_as_set %}
# Prefix: check prefix via AS-SET
{{ irrdb_filter(client, "prefixes", "$INTCOMM_PREFIX_OK", "$INTCOMM_PREFIX_KO",
		cfg.communities.prefix_present_in_as_set, cfg.communities.prefix_not_present_in_as_set) -}}
{% endif %}

{% if client.cfg.filtering.irrdb.enforce_origin_in_as_set or client.cfg.filtering.irrdb.enforce_prefix_in_as_set or cfg.filtering.irrdb.tag_as_set %}

{% if cfg.filtering.irrdb.use_rpki_roas_as_route_objects.enabled %}
# routes tagged with $INTCOMM_PREF_OK_ROA community have the prefix validated by a ROA; origin ASN previously validated ($INTCOMM_ORIGIN_OK)
{%	set when = "match from " ~ client.ip ~ " ext-community $INTCOMM_ORIGIN_OK ext-community $INTCOMM_PREF_OK_ROA" %}
{%	if cfg.filtering.irrdb.tag_as_set %}
{{		add_communities(when, cfg.communities.prefix_validated_via_rpki_roas) }}
{%	endif %}
{{ when }} set ext-community delete $INTCOMM_IRR_REJECT
{% endif %}

{% if cfg.filtering.irrdb.use_arin_bulk_whois_data.enabled and arin_whois_records %}
# routes tagged with $INTCOMM_PREF_OK_ARINDB community have the prefix validated by an ARIN Whois record; origin ASN previously validated ($INTCOMM_ORIGIN_OK)
{%	set when = "match from " ~ client.ip ~ " ext-community $INTCOMM_ORIGIN_OK ext-community $INTCOMM_PREF_OK_ARINDB" %}
{%	if cfg.filtering.irrdb.tag_as_set %}
{{		add_communities(when, cfg.communities.prefix_validated_via_arin_whois_db_dump) }}
{%	endif %}
{{ when }} set ext-community delete $INTCOMM_IRR_REJECT
{% endif %}

{% if cfg.filtering.irrdb.use_registrobr_bulk_whois_data.enabled and registrobr_whois_records %}
# routes tagged with $INTCOMM_PREF_OK_REGISTROBRDB community have the prefix validated by a NICBR Whois record; origin ASN previously validated ($INTCOMM_ORIGIN_OK)
{%	set when = "match from " ~ client.ip ~ " ext-community $INTCOMM_ORIGIN_OK ext-community $INTCOMM_PREF_OK_REGISTROBRDB" %}
{%	if cfg.filtering.irrdb.tag_as_set %}
{{		add_communities(when, cfg.communities.prefix_validated_via_registrobr_whois_db_dump) }}
{%	endif %}
{{ when }} set ext-community delete $INTCOMM_IRR_REJECT
{% endif %}

{% if client.cfg.filtering.irrdb.white_list_route %}
# route authorized by a client's white list?
{%	set when = "match from " ~ client.ip ~ " ext-community $INTCOMM_IRR_REJECT ext-community $INTCOMM_ROUTE_OK_WL" %}
{%      if cfg.filtering.irrdb.tag_as_set %}
{{              add_communities(when, cfg.communities.route_validated_via_white_list) }}
{%	endif %}
{{ when }} set ext-community delete $INTCOMM_IRR_REJECT
{% endif %}

{% if client.cfg.filtering.irrdb.enforce_origin_in_as_set %}
# enforcing: origin ASN
{{ deny_inbound_route(client.cfg.filtering.reject_policy.policy == "tag",
		      "from " ~ client.ip ~ " ext-community $INTCOMM_IRR_REJECT ext-community $INTCOMM_ORIGIN_KO", 9) }}
{% endif %}
{% if client.cfg.filtering.irrdb.enforce_prefix_in_as_set %}
# enforcing: prefix
{{ deny_inbound_route(client.cfg.filtering.reject_policy.policy == "tag",
		      "from " ~ client.ip ~ " ext-community $INTCOMM_IRR_REJECT ext-community $INTCOMM_PREFIX_KO", 12) }}
{% endif %}
{% endif %}

{% if ( client.ip|ipaddr_ver == 4 and cfg.blackhole_filtering.policy_ipv4 ) or
      ( client.ip|ipaddr_ver == 6 and cfg.blackhole_filtering.policy_ipv6 ) %}
# Blackhole request?
{{ del_communities("match from " ~ client.ip, cfg.communities.rejected_route_announced_by, replace_dyn_val=client.asn, dyn_val_is_peer_as=True) }}
# Remove internal communities before accepting the route
match from {{ client.ip }} community BLACKHOLE set {
	ext-community delete $INTCOMM_RPKI_INVALID
	{{ internal_communities_list() }}
}
{{ match_communities("allow from " ~ client.ip, cfg.communities.blackholing, "set {\n	ext-community delete $INTCOMM_RPKI_INVALID\n	" ~ internal_communities_list() ~ "\n}") }}
{%    if cfg.communities.rpki_bgp_origin_validation_not_performed|community_is_set %}
# Add the rpki_bgp_origin_validation_not_performed community
{{ add_communities("match from " ~ client.ip ~ " community BLACKHOLE", cfg.communities.rpki_bgp_origin_validation_not_performed) }}
{{ match_communities("match from " ~ client.ip, cfg.communities.blackholing, "set {" ~ ins_communities(cfg.communities.rpki_bgp_origin_validation_not_performed) ~ "}") }}
{%    endif %}
allow quick from {{ client.ip }} community BLACKHOLE
{{ match_communities("allow quick from " ~ client.ip, cfg.communities.blackholing, "") }}
{{ add_communities("match from " ~ client.ip, cfg.communities.rejected_route_announced_by, dyn_val=client.asn, dyn_val_is_peer_as=True) }}
{% endif %}

{% if cfg.filtering.rpki_bgp_origin_validation.enabled and
	client.cfg.filtering.rpki_bgp_origin_validation.enabled and
	client.cfg.filtering.rpki_bgp_origin_validation.reject_invalid %}
# RPKI-based Origin Validation
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " ext-community $INTCOMM_RPKI_INVALID", 14) }}
{% endif %}

# Prefix: length
{% if client.ip|ipaddr_ver == 4 %}
{% set min_pref_len = client.cfg.filtering.ipv4_pref_len.min %}
{% set max_pref_len = client.cfg.filtering.ipv4_pref_len.max %}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " prefix 0.0.0.0/0 prefixlen " ~ min_pref_len ~ " >< " ~ max_pref_len, 13) }}
{% else %}
{% set min_pref_len = client.cfg.filtering.ipv6_pref_len.min %}
{% set max_pref_len = client.cfg.filtering.ipv6_pref_len.max %}
{{ deny_inbound_route(client_uses_tag_reject_policy, "from " ~ client.ip ~ " prefix ::/0 prefixlen " ~ min_pref_len ~ " >< " ~ max_pref_len, 13) }}
{% endif %}

{% if cfg.graceful_shutdown.enabled %}
# Graceful shutdown
{%	if client.cfg.graceful_shutdown.enabled %}
match from {{ client.ip }} community GRACEFUL_SHUTDOWN set localpref {{ cfg.graceful_shutdown.local_pref }}
{%	else %}
match from {{ client.ip }} community GRACEFUL_SHUTDOWN set community delete GRACEFUL_SHUTDOWN
{%	endif %}
{% endif %}

# Remove internal communities before accepting the route
match from {{ client.ip }} set {
	{{ internal_communities_list() }}
}

{{ del_communities("match from " ~ client.ip, cfg.communities.rejected_route_announced_by, replace_dyn_val=client.asn, dyn_val_is_peer_as=True) }}

allow quick from {{ client.ip }}



# ---------------------------------------------
# client {{ client.id }}, outbound

{% if cfg.communities.reject_cause|community_is_set %}
{{	match_communities("deny quick to " ~ client.ip, cfg.communities.reject_cause, "", dyn_val=0) }}
{% endif %}

# Blackhole request?
{% if not client.cfg.blackhole_filtering.announce_to_client %}
# Client not enabled to receive blackhole routes
deny quick to {{ client.ip }} community BLACKHOLE
{{ match_communities("deny quick to " ~ client.ip, cfg.communities.blackholing, "") -}}
{% else %}
{%	if ( client.ip|ipaddr_ver == 4 and cfg.blackhole_filtering.policy_ipv4 == "propagate-unchanged" ) or
	   ( client.ip|ipaddr_ver == 6 and cfg.blackhole_filtering.policy_ipv6 == "propagate-unchanged" ) %}
# Configured policy: propagate-unchanged
{{ match_communities("match to " ~ client.ip, cfg.communities.blackholing, "set community BLACKHOLE") -}}
{%		if cfg.blackhole_filtering.add_noexport %}
match to {{ client.ip }} community BLACKHOLE set community NO_EXPORT
{%      	endif %}

{%	elif ( client.ip|ipaddr_ver == 4 and cfg.blackhole_filtering.policy_ipv4 == "rewrite-next-hop" ) or
	     ( client.ip|ipaddr_ver == 6 and cfg.blackhole_filtering.policy_ipv6 == "rewrite-next-hop" ) %}
# Configured policy: rewrite-next-hop
{{ match_communities("match to " ~ client.ip, cfg.communities.blackholing, "set community BLACKHOLE") -}}
{%		if cfg.blackhole_filtering.add_noexport %}
match to {{ client.ip }} community BLACKHOLE set community NO_EXPORT
{%      	endif %}
{%		if client.ip|ipaddr_ver == 4 %}
{%			set bhn = cfg.blackhole_filtering.rewrite_next_hop_ipv4 %}
{%		else %}
{%			set bhn = cfg.blackhole_filtering.rewrite_next_hop_ipv6 %}
{%		endif %}
match to {{ client.ip }} community BLACKHOLE set nexthop {{ bhn }}

{%	else %}
# No blackhole filtering policy given
deny quick to {{ client.ip }} community BLACKHOLE
{{ match_communities("deny quick to " ~ client.ip, cfg.communities.blackholing, "") -}}
{%	endif %}
{% endif %}

{% if cfg.filtering.rpki_bgp_origin_validation.enabled %}
# RPKI-based Origin Validation
# Do not announce INVALID to clients
deny quick to {{ client.ip }} ext-community $INTCOMM_RPKI_INVALID
{% endif %}

# NO_EXPORT and NO_ADVERTISE communities
{% if cfg.communities.add_noexport_to_any|community_is_set %}
# add_noexport_to_any
{{	match_communities("match to " ~ client.ip, cfg.communities.add_noexport_to_any, "set community NO_EXPORT") -}}
{% endif %}
{% if cfg.communities.add_noadvertise_to_any|community_is_set %}
# add_noadvertise_to_any
{{	match_communities("match to " ~ client.ip, cfg.communities.add_noadvertise_to_any, "set community NO_ADVERTISE") -}}
{% endif %}
{% if cfg.communities.add_noexport_to_peer|community_is_set %}
# add_noexport_to_peer
{{	match_communities("match to " ~ client.ip, cfg.communities.add_noexport_to_peer, "set community NO_EXPORT", client.asn) -}}
{% endif %}
{% if cfg.communities.add_noadvertise_to_peer|community_is_set %}
# add_noadvertise_to_peer
{{	match_communities("match to " ~ client.ip, cfg.communities.add_noadvertise_to_peer, "set community NO_ADVERTISE", client.asn) -}}
{% endif %}

# BGP control communities
allow to {{ client.ip }}

{% if cfg.communities.do_not_announce_to_any|community_is_set %}
# do_not_announce_to_any
{{	match_communities("deny to " ~ client.ip, cfg.communities.do_not_announce_to_any, "") -}}
{% endif %}
{% if cfg.communities.do_not_announce_to_peer|community_is_set %}
# do_not_announce_to_peer
{{	match_communities("deny quick to " ~ client.ip, cfg.communities.do_not_announce_to_peer, "", client.asn) -}}
{% endif %}
{% if rtt_based_functions_are_used and
	"rtt" in client and client["rtt"]|get_normalized_rtt > 0 and
	( cfg.communities.do_not_announce_to_peers_with_rtt_higher_than|community_is_set or
	  cfg.communities.do_not_announce_to_peers_with_rtt_lower_than|community_is_set or
	  cfg.communities.announce_to_peers_with_rtt_higher_than|community_is_set or
	  cfg.communities.announce_to_peers_with_rtt_lower_than|community_is_set ) %}
{%	for lower_higher in ["lower", "higher"] %}
{%		for ann_do_not in ["do_not_announce", "announce"] %}
{%			set comm_name = ann_do_not ~ "_to_peers_with_rtt_" ~ lower_higher ~ "_than" %}
{%			if cfg.communities[comm_name]|community_is_set %}
{%				for threshold_val in cfg.rtt_thresholds %}
{%					if (lower_higher == "lower" and client["rtt"]|get_normalized_rtt <= threshold_val) or
					   (lower_higher == "higher" and client["rtt"]|get_normalized_rtt > threshold_val) %}
# {{ comm_name }} {{ threshold_val }} ms
{%						if ann_do_not == "announce" %}
{{							match_communities("allow to " ~ client.ip, cfg.communities[comm_name], "", dyn_val=threshold_val) }}
{%						else %}
{{							match_communities("deny to " ~ client.ip, cfg.communities[comm_name], "", dyn_val=threshold_val) }}
{%						endif %}
{%					endif %}
{%				endfor %}
{%			endif %}
{%		endfor %}
{%	endfor %}
{% endif %}
{% if cfg.communities.announce_to_peer|community_is_set %}
# announce_to_peer
{{	match_communities("allow to " ~ client.ip, cfg.communities.announce_to_peer, "", client.asn) -}}
{% endif %}

# Add the $INTCOMM_PROCESS_PREPEND_COMMS ext community to signal that communities
# for prepending can be processed. As soon as one prepending action is performed,
# this internal community is removed, so that further actions are not processed.
match to {{ client.ip }} set ext-community $INTCOMM_PROCESS_PREPEND_COMMS

{% if cfg.communities.prepend_once_to_peer|community_is_set %}
# prepend_once_to_peer AS{{ client.asn }}; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_once_to_peer, "set {\n	prepend-neighbor 1\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}", client.asn) -}}
{% endif %}
{% if cfg.communities.prepend_twice_to_peer|community_is_set %}
# prepend_twice_to_peer AS{{ client.asn }}; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_twice_to_peer, "set {\n	prepend-neighbor 2\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}", client.asn) -}}
{% endif %}
{% if cfg.communities.prepend_thrice_to_peer|community_is_set %}
# prepend_thrice_to_peer AS{{ client.asn }}; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_thrice_to_peer, "set {\n	prepend-neighbor 3\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}", client.asn) -}}

{% endif %}

{% if rtt_based_functions_are_used and
	"rtt" in client and client["rtt"]|get_normalized_rtt > 0 %}
{%	for lower_higher in ["higher", "lower"] %}
{%		for threshold_val in cfg.rtt_thresholds|sort(reverse=(lower_higher == "higher")) %}
{%                      for times, times_num in [("once", 1), ("twice", 2), ("thrice", 3)] %}
{%				set comm_name = "prepend_" ~ times ~ "_to_peers_with_rtt_" ~ lower_higher ~ "_than" %}
{%				if cfg.communities[comm_name]|community_is_set %}
{%					if (lower_higher == "lower" and client["rtt"]|get_normalized_rtt <= threshold_val) or
				           (lower_higher == "higher" and client["rtt"]|get_normalized_rtt > threshold_val) %}
# {{ comm_name }} {{ threshold_val }} ms; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{						match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities[comm_name], "set {\n	prepend-neighbor " ~ times_num ~ "\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}", dyn_val=threshold_val) }}
{%					endif %}
{%				endif %}
{%			endfor %}
{%		endfor %}
{%	endfor %}
{% endif %}

{% if cfg.communities.prepend_once_to_any|community_is_set %}
# prepend_once_to_any; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_once_to_any, "set {\n	prepend-neighbor 1\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}") -}}
{% endif %}
{% if cfg.communities.prepend_twice_to_any|community_is_set %}
# prepend_twice_to_any; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_twice_to_any, "set {\n	prepend-neighbor 2\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}") -}}
{% endif %}
{% if cfg.communities.prepend_thrice_to_any|community_is_set %}
# prepend_thrice_to_any; remove INTCOMM_PROCESS_PREPEND_COMMS to prevent further prepending actions
{{	match_communities("match to " ~ client.ip ~ " ext-community $INTCOMM_PROCESS_PREPEND_COMMS", cfg.communities.prepend_thrice_to_any, "set {\n	prepend-neighbor 3\n	ext-community delete $INTCOMM_PROCESS_PREPEND_COMMS\n}") -}}
{% endif %}

{% endfor %}



# Scrub communities from outbound routes
{% for name in cfg.communities|sort if cfg.communities[name].type in ["inbound", "internal"] %}
{%	if cfg.communities[name]|community_is_set %}
# {{ name }}
{{ del_communities("match to group clients", cfg.communities[name], cfg.communities[name].peer_as, replace_dyn_val="*") -}}
{%	endif %}
{% endfor %}

# Scrub prepending communities
{% for comm_name in cfg.communities|sort if "prepend" in comm_name and cfg.communities[comm_name]|community_is_set %}
match to group clients set {
{{ delete_prepending_comm(cfg.communities[comm_name], cfg.rtt_thresholds) }}
}
{% endfor %}

{% if perform_graceful_shutdown %}
match to group clients set community GRACEFUL_SHUTDOWN
{% endif %}

{% if cfg.rfc1997_wellknown_communities.policy == "pass" %}
# RFC1997 NO_EXPORT/NO_ADVERTISE received from clients and propagated because of pass-through policy
match to group clients ext-community $INTCOMM_NO_EXPORT set community NO_EXPORT
match to group clients ext-community $INTCOMM_NO_ADVERTISE set community NO_ADVERTISE
{% endif %}

# Remove internal communities before announcing the route
match to group clients set {
	{{ internal_communities_list(all=True) }}
}

{{ "post-filters"|include_local_file -}}

