1{% from 'macros.j2' import write_prefix_list %}
2{% from 'macros.j2' import del_communities %}
3{% from 'macros.j2' import match_communities %}
4{% from 'macros.j2' import add_communities %}
5{% from 'macros.j2' import match_rtt_communities %}
6# ---------------------------------------------------------
7# COMMON
8
9# This function returns True if 'net' is a bogon prefix
10# or falls within a bogon prefix.
11function prefix_is_bogon()
12{% for this_ip_ver in list_ip_vers %}
13prefix set bogons_{{ this_ip_ver }};
14{% endfor %}
15{
16{% for this_ip_ver in list_ip_vers %}
17	bogons_{{ this_ip_ver }} = [
18{{ write_prefix_list(bogons|selectattr("prefix", "is_ipver", this_ip_ver)) }}
19	];
20{% endfor %}
21
22{% for this_ip_ver in list_ip_vers %}
23{%   if "2.0"|target_version_ge %}
24	if net.type = NET_IP{{ this_ip_ver }} then
25		if net ~ bogons_{{ this_ip_ver }} then return true;
26{%   else %}
27	if net ~ bogons_{{ this_ip_ver }} then return true;
28{%   endif %}
29{% endfor %}
30	return false;
31}
32
33# This function returns True if 'net' falls within a
34# prefix contained in the global blacklist (for example,
35# local networks)
36function prefix_is_in_global_blacklist()
37{% for this_ip_ver in list_ip_vers %}
38{%   set prefixes = cfg.filtering.global_black_list_pref|selectattr("prefix", "is_ipver", this_ip_ver )|list %}
39{%   if prefixes|length > 0 %}
40prefix set global_blacklist_{{ this_ip_ver }};
41{%   endif %}
42{% endfor %}
43{
44{% for this_ip_ver in list_ip_vers %}
45{%   set prefixes = cfg.filtering.global_black_list_pref|selectattr("prefix", "is_ipver", this_ip_ver )|list %}
46{%   if prefixes|length > 0 %}
47	global_blacklist_{{ this_ip_ver }} = [
48{{ write_prefix_list(prefixes) }}
49	];
50
51{%     if "2.0"|target_version_ge %}
52	if net.type = NET_IP{{ this_ip_ver }} then
53		if net ~ global_blacklist_{{ this_ip_ver }} then return true;
54{%     else %}
55	if net ~ global_blacklist_{{ this_ip_ver }} then return true;
56{%     endif %}
57
58{%   else %}
59	# No IPv{{ this_ip_ver }} prefixes configured under the cfg.filtering.global_black_list_pref section.
60{%   endif %}
61{% endfor %}
62	return false;
63}
64
65# This function returns True if the length of 'net' prefix
66# falls within the range 'min'-'max' (included).
67function prefix_len_is_valid (int pref_len_min; int pref_len_max) {
68	if net.len < pref_len_min then return false;
69	if net.len > pref_len_max then return false;
70	return true;
71}
72
73# This function returns True if the AS_PATH contains one or
74# more private/reserved ASN.
75function as_path_contains_invalid_asn()
76int set invalid_asns;
77{
78	# http://www.iana.org/assignments/as-numbers/as-numbers.xhtml
79	invalid_asns = [
80		# 16-bit
81		0,			# Reserved. RFC7607
82		23456,			# AS_TRANS. RFC6793
83		64496..64511,		# Reserved for use in documentation and sample code. RFC5398
84		64512..65534,		# Reserved for Private Use. RFC6996
85		65535,			# Reserved. RFC7300
86
87		# 32-bit
88		65536..65551,		# Reserved for use in documentation and sample code. RFC5398
89		65552..131071,		# Reserved.
90		4200000000..4294967294, # Reserved for Private Use. [RFC6996]
91		4294967295		# Reserved. RFC7300
92	];
93	return bgp_path ~ invalid_asns;
94}
95
96{% if rtt_based_functions_are_used %}
97# This function returns the RTT measured for the peer given in client_ip.
98# If the RTT is not available it returns 0.
99function get_peer_rtt(ip client_ip) {
100	case client_ip {
101{%	for client in clients|sort(attribute="ip") if client.ip is current_ipver %}
102{%		if "rtt" in client and client["rtt"]|get_normalized_rtt %}
103		{{ client["ip"] }} : return {{ client["rtt"]|get_normalized_rtt }}; # {{ client["rtt"] }}
104{%		endif %}
105{%	endfor %}
106	}
107
108	return 0;
109}
110{% endif %}
111
112# This function scrubs BGP communities used by the route server
113# for signaling purpose toward its clients. (RFC7454, Section 11)
114# It must be applied on routes entering the route server.
115function scrub_communities_in() {
116{% for name in cfg.communities|sort if cfg.communities[name].type == "outbound" %}
117{% if cfg.communities[name]|community_is_set %}
118	# {{ name }}
119{{ del_communities(cfg.communities[name]) }}
120{% endif %}
121{% endfor %}
122{% for name in cfg.communities|sort if cfg.communities[name].type == "internal" %}
123{% if cfg.communities[name]|community_is_set %}
124	# {{ name }}
125{{ del_communities(cfg.communities[name], replace_dyn_val="*") }}
126{% endif %}
127{% endfor %}
128{% for name in cfg.custom_communities|sort %}
129{% if cfg.custom_communities[name]|community_is_set %}
130	# {{ name }}
131{{ del_communities(cfg.custom_communities[name]) }}
132{% endif %}
133{% endfor %}
134{% if "scrub_communities_in"|hook_is_set %}
135	hook_scrub_communities_in();
136{% endif %}
137}
138
139# This function scrubs BGP communities used by clients to instruct
140# the route server to perform some actions.
141# It must be applied on routes leaving the route server.
142function scrub_communities_out() {
143{% for name in cfg.communities|sort if cfg.communities[name].type == "inbound" %}
144{% if cfg.communities[name]|community_is_set %}
145	# {{ name }}
146{{ del_communities(cfg.communities[name], cfg.communities[name].peer_as, replace_dyn_val="*") }}
147{% endif %}
148{% endfor %}
149{% for name in cfg.communities|sort if cfg.communities[name].type == "internal" %}
150{% if cfg.communities[name]|community_is_set %}
151	# {{ name }}
152{{ del_communities(cfg.communities[name], replace_dyn_val="*") }}
153{% endif %}
154{% endfor %}
155{% if "scrub_communities_out"|hook_is_set %}
156	hook_scrub_communities_out();
157{% endif %}
158}
159
160# This function verifies if the route is tagged with one of
161# the blackhole filtering communities.
162function is_blackhole_filtering_request() {
163{%	for this_ip_ver in list_ip_vers %}
164{%		if "2.0"|target_version_ge %}
165	if net.type = NET_IP{{ this_ip_ver }} then {
166{%		endif %}
167{%		if cfg.blackhole_filtering["policy_ipv" ~ this_ip_ver] %}
168	if (65535, 666) ~ bgp_community then
169		return true;
170{{ match_communities(cfg.communities.blackholing, "return true;") }}
171{%		endif %}
172{%		if "2.0"|target_version_ge %}
173	}
174{%		endif %}
175{%	endfor %}
176	return false;
177}
178
179# This function must be applied to outgoing routes.
180# It applies the blackhole filtering policy to the current route.
181function apply_blackhole_filtering_policy() {
182{% for this_ip_ver in list_ip_vers %}
183{%		if "2.0"|target_version_ge %}
184	if net.type = NET_IP{{ this_ip_ver }} then {
185{%		endif %}
186
187{% if cfg.blackhole_filtering["policy_ipv" ~ this_ip_ver] == "propagate-unchanged" %}
188	# Configured policy: propagate-unchanged
189	bgp_community.add((65535, 666));
190{%	if cfg.blackhole_filtering.add_noexport %}
191	# NO_EXPORT
192	bgp_community.add((65535, 65281));
193{%	endif %}
194{% elif cfg.blackhole_filtering["policy_ipv" ~ this_ip_ver] == "rewrite-next-hop" %}
195	# Configured policy: rewrite-next-hop
196	bgp_community.add((65535, 666));
197	bgp_next_hop = {{ cfg.blackhole_filtering["rewrite_next_hop_ipv" ~ this_ip_ver] }};
198{%	if cfg.blackhole_filtering.add_noexport %}
199	# NO_EXPORT
200	bgp_community.add((65535, 65281));
201{%	endif %}
202{% else %}
203	reject "blackhole filtering requested but no IPv{{ this_ip_ver }} policy given - REJECTING ", net;
204{% endif %}
205
206{%		if "2.0"|target_version_ge %}
207	}
208{%		endif %}
209{% if "apply_blackhole_filtering_policy"|hook_is_set %}
210	hook_apply_blackhole_filtering_policy({{ this_ip_ver }});
211{% endif %}
212{% endfor %}
213}
214
215# This function verifies if the current route can be announced to
216# the given client on the basis of the attached control BGP
217# communities.
218function route_can_be_announced_to(int peer_as; ip client_ip; string client_id)
219int client_rtt;
220{
221{% if "route_can_be_announced_to"|hook_is_set %}
222	return hook_route_can_be_announced_to(peer_as, client_ip, client_id);
223{% else %}
224{%	if cfg.communities.do_not_announce_to_peer|community_is_set %}
225	# do_not_announce_to_peer
226{{-		match_communities(cfg.communities.do_not_announce_to_peer, "return false;") }}
227{%	endif %}
228{%	if cfg.communities.announce_to_peer|community_is_set %}
229	# announce_to_peer
230{{-		match_communities(cfg.communities.announce_to_peer, "return true;") }}
231{%	endif %}
232{%	if rtt_based_functions_are_used and
233	   ( cfg.communities.do_not_announce_to_peers_with_rtt_higher_than|community_is_set or
234	     cfg.communities.do_not_announce_to_peers_with_rtt_lower_than|community_is_set or
235	     cfg.communities.announce_to_peers_with_rtt_higher_than|community_is_set or
236	     cfg.communities.announce_to_peers_with_rtt_lower_than|community_is_set ) %}
237	client_rtt = get_peer_rtt(client_ip);
238
239	if client_rtt > 0 then {
240{%		if cfg.communities.do_not_announce_to_peers_with_rtt_higher_than|community_is_set %}
241		# do_not_announce_to_peers_with_rtt_higher_than
242{{			match_rtt_communities(cfg.rtt_thresholds, cfg.communities.do_not_announce_to_peers_with_rtt_higher_than, "client_rtt", ">", "return false;", tabs=2) }}
243{%		endif %}
244{%		if cfg.communities.do_not_announce_to_peers_with_rtt_lower_than|community_is_set %}
245		# do_not_announce_to_peers_with_rtt_lower_than
246{{			match_rtt_communities(cfg.rtt_thresholds, cfg.communities.do_not_announce_to_peers_with_rtt_lower_than, "client_rtt", "<=", "return false;", tabs=2) }}
247{%		endif %}
248{%		if cfg.communities.announce_to_peers_with_rtt_higher_than|community_is_set %}
249		# announce_to_peers_with_rtt_higher_than
250{{			match_rtt_communities(cfg.rtt_thresholds, cfg.communities.announce_to_peers_with_rtt_higher_than, "client_rtt", ">", "return true;", tabs=2) }}
251{%		endif %}
252{%		if cfg.communities.announce_to_peers_with_rtt_lower_than|community_is_set %}
253		# announce_to_peers_with_rtt_lower_than
254{{			match_rtt_communities(cfg.rtt_thresholds, cfg.communities.announce_to_peers_with_rtt_lower_than, "client_rtt", "<=", "return true;", tabs=2) }}
255{%		endif %}
256	}
257
258{%	endif %}
259{%	if cfg.communities.do_not_announce_to_any|community_is_set %}
260	# do_not_announce_to_any
261{{-		match_communities(cfg.communities.do_not_announce_to_any, "return false;") }}
262{%	endif %}
263	return true;
264{% endif %}
265}
266
267# This function prepends the left-most ASN <times> times.
268function do_prepend(int times) {
269	case times {
270		1: bgp_path.prepend(bgp_path.first);
271		2: bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first);
272		3: bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first); bgp_path.prepend(bgp_path.first);
273	}
274}
275
276# This function verifies if the current route matches one of the
277# control communities in charge of prepending client's ASN.
278function apply_prepend(int peer_as; ip client_ip)
279int client_rtt;
280{
281{% for times in ["once", "twice", "thrice"] %}
282{%	set comm_name = "prepend_" ~ times ~ "_to_peer" %}
283{%	if cfg.communities[comm_name]|community_is_set %}
284	# {{ comm_name }}
285{{-	match_communities(cfg.communities[comm_name], "{ do_prepend(" ~ loop.index ~ "); return true; }") }}
286{%	endif %}
287{% endfor %}
288
289{% if rtt_based_functions_are_used %}
290	client_rtt = get_peer_rtt(client_ip);
291
292	if client_rtt > 0 then {
293{%	for lower_higher, op, order in [("higher", ">", "desc"), ("lower", "<=", "asc")] %}
294{%		for threshold_val in cfg.rtt_thresholds|sort(reverse=(order == "desc")) %}
295{%			for times in ["once", "twice", "thrice"] %}
296{%				set comm_name = "prepend_" ~ times ~ "_to_peers_with_rtt_" ~ lower_higher ~ "_than" %}
297{%				if cfg.communities[comm_name]|community_is_set %}
298		# {{ comm_name }} {{ threshold_val }} ms
299{{-             match_communities(cfg.communities[comm_name], "{ if client_rtt " ~ op ~ " " ~ threshold_val ~ " then\n\t\t\t{ do_prepend(" ~ loop.index ~ "); return true; } }", replace_dyn_val=threshold_val, tabs=2 ) }}
300{%				endif %}
301{%			endfor %}
302{%		endfor %}
303{%	endfor %}
304	}
305{% endif %}
306
307{% for times in ["once", "twice", "thrice"] %}
308{%	set comm_name = "prepend_" ~ times ~ "_to_any" %}
309{%	if cfg.communities[comm_name]|community_is_set %}
310	# {{ comm_name }}
311{{-	match_communities(cfg.communities[comm_name], "{ do_prepend(" ~ loop.index ~ "); return true; }") }}
312{%	endif %}
313{% endfor %}
314
315	return true;
316}
317
318{% if cfg.filtering.rpki_bgp_origin_validation.enabled %}
319# This function adds the BGP communities used to
320# keep track of RPKI validation state.
321# RFC8097 extended communities are used here.
322function add_rpki_community(string comm_name) {
323	# RFC8097 BGP communities
324	if comm_name = "valid" then {
325		bgp_ext_community.add((unknown 0x4300, 0, 0));
326	}
327	if comm_name = "unknown" then {
328		bgp_ext_community.add((unknown 0x4300, 0, 1));
329	}
330	if comm_name = "invalid" then {
331		bgp_ext_community.add((unknown 0x4300, 0, 2));
332	}
333
334	{% if cfg.communities["rpki_bgp_origin_validation_valid"]|community_is_set %}
335	# rpki_bgp_origin_validation_valid communities
336	if comm_name = "valid" then {
337		{{ add_communities(cfg.communities["rpki_bgp_origin_validation_valid"], tabs=2) }}
338	}
339	{% endif %}
340	{% if cfg.communities["rpki_bgp_origin_validation_unknown"]|community_is_set %}
341	# rpki_bgp_origin_validation_unknown communities
342	if comm_name = "unknown" then {
343		{{ add_communities(cfg.communities["rpki_bgp_origin_validation_unknown"], tabs=2) }}
344	}
345	{% endif %}
346	{% if cfg.communities["rpki_bgp_origin_validation_invalid"]|community_is_set %}
347	# rpki_bgp_origin_validation_invalid communities
348	if comm_name = "invalid" then {
349		{{ add_communities(cfg.communities["rpki_bgp_origin_validation_invalid"], tabs=2) }}
350	}
351	{% endif %}
352}
353
354# This functions performs RPKI validation of the current
355# route and adds the informative communities.
356function perform_rpki_validation () {
357{% if "2.0.0"|target_version_le %}
358	case roa_check(RPKI) {
359		ROA_VALID: add_rpki_community("valid");
360		ROA_UNKNOWN: add_rpki_community("unknown");
361		ROA_INVALID: add_rpki_community("invalid");
362	}
363{% else %}
364	if net.type = NET_IP4 then {
365		case roa_check(RPKI4) {
366			ROA_VALID: add_rpki_community("valid");
367			ROA_UNKNOWN: add_rpki_community("unknown");
368			ROA_INVALID: add_rpki_community("invalid");
369		}
370	} else {
371		case roa_check(RPKI6) {
372			ROA_VALID: add_rpki_community("valid");
373			ROA_UNKNOWN: add_rpki_community("unknown");
374			ROA_INVALID: add_rpki_community("invalid");
375		}
376	}
377{% endif %}
378}
379
380# This function returns True if the route is INVALID.
381function route_is_rpki_invalid () {
382	return (unknown 0x4300, 0, 2) ~ bgp_ext_community;
383}
384
385# This function returns True if RPKI INVALID routes
386# should be announced to clients.
387function announce_rpki_invalid_to_client(int client_asn; ip client_ip; string client_id) {
388	{% if "announce_rpki_invalid_to_client"|hook_is_set %}
389	return hook_announce_rpki_invalid_to_client(client_asn, client_ip, client_id);
390	{% else %}
391	return false;
392	{% endif %}
393}
394{% endif %}
395
396# This function adds NO_EXPORT and/or NO_ADVERTISE
397# well-known communities.
398function add_noexport_noadvertise(int peer_as) {
399	{% if cfg.communities.add_noexport_to_any|community_is_set %}
400	# add_noexport_to_any
401	{{ match_communities(cfg.communities.add_noexport_to_any, "{ bgp_community.add((65535, 65281)); }") }}
402	{% endif %}
403	{% if cfg.communities.add_noadvertise_to_any|community_is_set %}
404	# add_noadvertise_to_any
405	{{ match_communities(cfg.communities.add_noadvertise_to_any, "{ bgp_community.add((65535, 65282)); }") }}
406	{% endif %}
407	{% if cfg.communities.add_noexport_to_peer|community_is_set %}
408	# add_noexport_to_peer
409	{{ match_communities(cfg.communities.add_noexport_to_peer, "{ bgp_community.add((65535, 65281)); }") }}
410	{% endif %}
411	{% if cfg.communities.add_noadvertise_to_peer|community_is_set %}
412	# add_noadvertise_to_peer
413	{{ match_communities(cfg.communities.add_noadvertise_to_peer, "{ bgp_community.add((65535, 65282)); }") }}
414	{% endif %}
415}
416
417{% if cfg.communities.reject_cause|community_is_set %}
418function tag_and_reject(int cause; int announcing_asn)
419int dyn_val;
420{
421	# 0: the route must be treated as discarded
422	dyn_val = 0;
423	{{ add_communities(cfg.communities.reject_cause) }}
424
425	# cause: the reject cause
426	dyn_val = cause;
427
428	# add the generic community from reject_cause
429	{{ add_communities(cfg.communities.reject_cause) }}
430
431	{% if any_reject_cause_map_community_set %}
432	# communities from reject_cause_map
433	case cause {
434		{% for reject_code in reject_reasons %}
435		{% 	 set reject_cause_map_comm_name = "reject_cause_map_" ~ reject_code %}
436		{% 	 if cfg.communities[reject_cause_map_comm_name]|community_is_set %}
437		# {{ reject_code }} = {{ reject_reasons[reject_code] }}
438		{{ reject_code }} : {{ add_communities(cfg.communities[reject_cause_map_comm_name], tabs=0)|replace(";\n", "; ")|trim }}
439		{% 	 endif %}
440		{% endfor %}
441	}
442	{% endif %}
443
444{%	if cfg.communities.rejected_route_announced_by|community_is_set %}
445	# announcing_asn: the ASN of the peer that announced the route
446	dyn_val = announcing_asn;
447	{{ add_communities(cfg.communities.rejected_route_announced_by) }}
448{%	endif %}
449
450	bgp_local_pref = 1;
451}
452{% endif %}
453
454{% if cfg.graceful_shutdown.enabled %}
455function honor_graceful_shutdown() {
456	if (65535, 0) ~ bgp_community then {
457		bgp_local_pref = {{ cfg.graceful_shutdown.local_pref }};
458	}
459}
460
461function prevent_graceful_shutdown() {
462	if (65535, 0) ~ bgp_community then {
463		bgp_community.delete([(65535, 0)]);
464	}
465}
466{% endif %}
467
468{% if perform_graceful_shutdown %}
469function perform_graceful_shutdown() {
470	bgp_community.add((65535, 0));
471}
472{% endif %}
473
474{% if cfg.filtering.irrdb.use_rpki_roas_as_route_objects.enabled %}
475# This function verifies if there is such a ROA for the
476# current route's origin ASN to validate the announced prefix.
477function prefix_in_rpki_roas_as_route_objects() {
478{% if "2.0.0"|target_version_le %}
479	case roa_check(RPKI) {
480		ROA_VALID: return true;
481	}
482{% else %}
483	if net.type = NET_IP4 then {
484		case roa_check(RPKI4) {
485			ROA_VALID: return true;
486		}
487	} else {
488		case roa_check(RPKI6) {
489			ROA_VALID: return true;
490		}
491	}
492{% endif %}
493	return false;
494}
495{% endif %}
496
497{% if cfg.filtering.irrdb.use_arin_bulk_whois_data.enabled and arin_whois_records %}
498# This function looks up the route's origin ASN in the ARIN
499# Whois DB: if there is such an entry for the current route's
500# origin ASN to validate the announced prefix the function
501# returns True, otherwise False.
502function prefix_in_arin_whois_db() {
503{%	for this_ip_ver in list_ip_vers %}
504{%		if "2.0"|target_version_ge %}
505	if net.type = NET_IP{{ this_ip_ver }} then {
506{%		endif %}
507	case bgp_path.last {
508{%		for origin_asn in arin_whois_records|sort %}
509{%			set prefixes = arin_whois_records[origin_asn].prefixes|selectattr("prefix", "is_ipver", this_ip_ver)|list %}
510{%			if prefixes|length > 0 %}
511		{{ origin_asn|replace("AS", "") }}: return net ~ ARIN_Whois_db_{{ origin_asn }}_{{ this_ip_ver }};
512{%			endif %}
513{%		endfor %}
514	}
515{%		if "2.0"|target_version_ge %}
516	}
517{%		endif %}
518{%	endfor %}
519	return false;
520}
521{% endif %}
522
523{% if cfg.filtering.irrdb.use_registrobr_bulk_whois_data.enabled and registrobr_whois_records %}
524# This function looks up the route's origin ASN in the Registro.br
525# Whois DB: if there is such an entry for the current route's
526# origin ASN to validate the announced prefix the function
527# returns True, otherwise False.
528function prefix_in_registrobr_whois_db() {
529{%	for this_ip_ver in list_ip_vers %}
530{%		if "2.0"|target_version_ge %}
531	if net.type = NET_IP{{ this_ip_ver }} then {
532{%		endif %}
533	case bgp_path.last {
534{%		for origin_asn in registrobr_whois_records|sort %}
535{%			set prefixes = registrobr_whois_records[origin_asn].prefixes|selectattr("prefix", "is_ipver", this_ip_ver)|list %}
536{%			if prefixes|length > 0 %}
537		{{ origin_asn|replace("AS", "") }}: return net ~ RegistroBR_Whois_db_{{ origin_asn }}_{{ this_ip_ver }};
538{%			endif %}
539{%		endfor %}
540	}
541{%		if "2.0"|target_version_ge %}
542	}
543{%		endif %}
544{%	endfor %}
545	return false;
546}
547{% endif %}
548