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