roa4 table rpki4;
roa6 table rpki6;
attribute int export_downstream;

protocol static default_v4 {
    ipv4 {};
    route 0.0.0.0/0 reject;
}

protocol static default_v6 {
    ipv6 {};
    route ::/0 reject;
}

# FIXME: Change this to your ASN.
define MY_ASN = 64500;

define LC_IXP_ID    = 1;
define LC_PEER_ASN  = 2;
define LC_INFO      = 3;

define LC_NO_EXPORT = 10;
define LC_PREPEND_1 = 11;
define LC_PREPEND_2 = 12;
define LC_PREPEND_3 = 13;

define LC_DOWNSTREAM_START  = 10;
define LC_DOWNSTREAM_END    = 13;

# FIXME: define your IXPs here:
# define IXP_EXAMPLE1 = 100;
# define IXP_EXAMPLE2 = 101;

define INFO_PEER        = 100;
define INFO_IXP_RS      = 101;
define INFO_TRANSIT     = 102;
define INFO_DOWNSTREAM  = 103;

define IPV4_BOGON = [
    0.0.0.0/8+,         # RFC 1122 'this' network
    10.0.0.0/8+,        # RFC 1918 private space
    100.64.0.0/10+,     # RFC 6598 Carrier grade nat space
    127.0.0.0/8+,       # RFC 1122 localhost
    169.254.0.0/16+,    # RFC 3927 link local
    172.16.0.0/12+,     # RFC 1918 private space
    192.0.2.0/24+,      # RFC 5737 TEST-NET-1
    192.88.99.0/24+,    # RFC 7526 6to4 anycast relay
    192.168.0.0/16+,    # RFC 1918 private space
    198.18.0.0/15+,     # RFC 2544 benchmarking
    198.51.100.0/24+,   # RFC 5737 TEST-NET-2
    203.0.113.0/24+,    # RFC 5737 TEST-NET-3
    224.0.0.0/4+,       # multicast
    240.0.0.0/4+        # reserved
];

define IPV6_BOGON = [
    ::/0,                   # Default
    ::/96,                  # IPv4-compatible IPv6 address - deprecated by RFC4291
    ::/128,                 # Unspecified address
    ::1/128,                # Local host loopback address
    ::ffff:0.0.0.0/96+,     # IPv4-mapped addresses
    ::224.0.0.0/100+,       # Compatible address (IPv4 format)
    ::127.0.0.0/104+,       # Compatible address (IPv4 format)
    ::0.0.0.0/104+,         # Compatible address (IPv4 format)
    ::255.0.0.0/104+,       # Compatible address (IPv4 format)
    0000::/8+,              # Pool used for unspecified, loopback and embedded IPv4 addresses
    0100::/8+,              # RFC 6666 - reserved for Discard-Only Address Block
    0200::/7+,              # OSI NSAP-mapped prefix set (RFC4548) - deprecated by RFC4048
    0400::/6+,              # RFC 4291 - Reserved by IETF
    0800::/5+,              # RFC 4291 - Reserved by IETF
    1000::/4+,              # RFC 4291 - Reserved by IETF
    2001:10::/28+,          # RFC 4843 - Deprecated (previously ORCHID)
    2001:20::/28+,          # RFC 7343 - ORCHIDv2
    2001:db8::/32+,         # Reserved by IANA for special purposes and documentation
    2002:e000::/20+,        # Invalid 6to4 packets (IPv4 multicast)
    2002:7f00::/24+,        # Invalid 6to4 packets (IPv4 loopback)
    2002:0000::/24+,        # Invalid 6to4 packets (IPv4 default)
    2002:ff00::/24+,        # Invalid 6to4 packets
    2002:0a00::/24+,        # Invalid 6to4 packets (IPv4 private 10.0.0.0/8 network)
    2002:ac10::/28+,        # Invalid 6to4 packets (IPv4 private 172.16.0.0/12 network)
    2002:c0a8::/32+,        # Invalid 6to4 packets (IPv4 private 192.168.0.0/16 network)
    3ffe::/16+,             # Former 6bone, now decommissioned
    4000::/3+,              # RFC 4291 - Reserved by IETF
    5f00::/8+,              # RFC 5156 - used for the 6bone but was returned
    6000::/3+,              # RFC 4291 - Reserved by IETF
    8000::/3+,              # RFC 4291 - Reserved by IETF
    a000::/3+,              # RFC 4291 - Reserved by IETF
    c000::/3+,              # RFC 4291 - Reserved by IETF
    e000::/4+,              # RFC 4291 - Reserved by IETF
    f000::/5+,              # RFC 4291 - Reserved by IETF
    f800::/6+,              # RFC 4291 - Reserved by IETF
    fc00::/7+,              # Unicast Unique Local Addresses (ULA) - RFC 4193
    fe80::/10+,             # Link-local Unicast
    fec0::/10+,             # Site-local Unicast - deprecated by RFC 3879 (replaced by ULA)
    ff00::/8+               # Multicast
];

define ASN_BOGON = [
    0,                      # RFC 7607
    23456,                  # RFC 4893 AS_TRANS
    64496..64511,           # RFC 5398 and documentation/example ASNs
    64512..65534,           # RFC 6996 Private ASNs
    65535,                  # RFC 7300 Last 16 bit ASN
    65536..65551,           # RFC 5398 and documentation/example ASNs
    65552..131071,          # RFC IANA reserved ASNs
    4200000000..4294967294, # RFC 6996 Private ASNs
    4294967295              # RFC 7300 Last 32 bit ASN
];

function ip_bogon() {
    case net.type {
        NET_IP4: return net ~ IPV4_BOGON;
        NET_IP6: return net ~ IPV6_BOGON;
        else:    return true;
    }
}

function rpki_invalid() {
    case net.type {
        NET_IP4: return roa_check(rpki4, net, bgp_path.last) = ROA_INVALID;
        NET_IP6: return roa_check(rpki6, net, bgp_path.last) = ROA_INVALID;
        else:    return false;
    }
}

function is_default_route() {
    case net.type {
        NET_IP4: return net = 0.0.0.0/0;
        NET_IP6: return net = ::/0;
        else:    return false;
    }
}

function bad_prefix_len() {
    case net.type {
        NET_IP4: return net.len > 24;
        NET_IP6: return net.len > 48;
        else:    return false;
    }
}

function clean_own_communities() {
    bgp_large_community.delete([(MY_ASN, *, *)]);
}

function honour_graceful_shutdown() {
    # RFC 8326: Graceful BGP Session Shutdown
    if (65535, 0) ~ bgp_community then bgp_local_pref = 0;
}

function handle_prepend(int dest_asn) {
    if (MY_ASN, LC_PREPEND_1, dest_asn) ~ bgp_large_community then {
        bgp_path.prepend(MY_ASN);
    }

    if (MY_ASN, LC_PREPEND_2, dest_asn) ~ bgp_large_community then {
        bgp_path.prepend(MY_ASN);
        bgp_path.prepend(MY_ASN);
    }

    if (MY_ASN, LC_PREPEND_3, dest_asn) ~ bgp_large_community then {
        bgp_path.prepend(MY_ASN);
        bgp_path.prepend(MY_ASN);
        bgp_path.prepend(MY_ASN);
    }
}

function import_safe(bool allow_default) {
    if is_default_route() then {
        if allow_default then return true;
        print proto, ": ", net, ": unexpected default route";
        return false;
    }

    if ip_bogon() then {
        print proto, ": ", net, ": bogon prefix";
        return false;
    }

    if bgp_path ~ ASN_BOGON then {
        print proto, ": ", net, ": bogon in AS path: ", bgp_path;
        return false;
    }

    if bgp_path.len > 50 then {
        print proto, ": ", net, ": AS path too long: ", bgp_path;
        return false;
    }

    if bad_prefix_len() then {
        print proto, ": ", net, ": invalid prefix length";
        return false;
    }

    if rpki_invalid() then {
        print proto, ": ", net, ": invalid RPKI origin: ", bgp_path.last;
        return false;
    }

    export_downstream = 1;
    honour_graceful_shutdown();

    return true;
}

function import_peer_trusted(int peer_asn) {
    clean_own_communities();
    bgp_large_community.add((MY_ASN, LC_INFO, INFO_PEER));
    bgp_large_community.add((MY_ASN, LC_PEER_ASN, peer_asn));

    return import_safe(false);
}

function import_peer(int peer_asn; prefix set prefixes; int set as_set) {
    if net !~ prefixes then {
        print proto, ": ", net, ": prefix not in as-set for peer AS", peer_asn;
        return false;
    }

    for int path_asn in bgp_path do {
        if path_asn !~ as_set then {
            print proto, ": ", net, ": AS", path_asn, " not in as-set for peer AS", peer_asn;
            return false;
        }
    }

    return import_peer_trusted(peer_asn);
}

function import_ixp_trusted(int ixp_id) {
    clean_own_communities();
    bgp_large_community.add((MY_ASN, LC_INFO, INFO_IXP_RS));
    bgp_large_community.add((MY_ASN, LC_IXP_ID, ixp_id));

    return import_safe(false);
}

function import_ixp(int ixp_id; prefix set prefixes; int set as_set) {
    if net !~ prefixes then {
        print proto, ": ", net, ": prefix not in as-set for IXP";
        return false;
    }

    for int path_asn in bgp_path do {
        if path_asn !~ as_set then {
            print proto, ": ", net, ": not in as-set for IXP";
            return false;
        }
    }

    return import_ixp_trusted(ixp_id);
}

function import_transit(int transit_asn; bool default_route) {
    clean_own_communities();
    bgp_large_community.add((MY_ASN, LC_INFO, INFO_TRANSIT));
    bgp_large_community.add((MY_ASN, LC_PEER_ASN, transit_asn));

    return import_safe(default_route);
}

function import_downstream(int downstream_asn; prefix set prefixes; int set as_set) {
    if net !~ prefixes then {
        print proto, ": ", net, ": prefix not in as-set for downstream AS", downstream_asn;
        return false;
    }

    for int path_asn in bgp_path do {
        if path_asn !~ as_set then {
            print proto, ": ", net, ": not in as-set for downstream AS", downstream_asn;
            return false;
        }
    }

    # If they don't want to export this to us, then we won't take it at all.
    if (MY_ASN, LC_NO_EXPORT, MY_ASN) ~ bgp_large_community then {
        print proto, ": ", net, ": rejected by no-export to AS", MY_ASN;
        return false;
    }

    bgp_large_community.delete([
        (MY_ASN, 0..LC_DOWNSTREAM_START-1, *),
        (MY_ASN, LC_DOWNSTREAM_END+1..0xFFFFFFFF, *)
    ]);

    bgp_large_community.add((MY_ASN, LC_INFO, INFO_DOWNSTREAM));
    bgp_large_community.add((MY_ASN, LC_PEER_ASN, downstream_asn));

    return import_safe(false);
}

function export_to_downstream() {
    return (defined(export_downstream) && export_downstream = 1) ||
        (source = RTS_STATIC && !bad_prefix_len() && (
            proto = "node_v4" ||
            proto = "node_v6" ||
            proto = "node_v4_anycast" ||
            proto = "node_v6_anycast"
        ));
}

function export_monitoring() {
    return export_to_downstream();
}

function export_cone(int dest_asn) {
    if (MY_ASN, LC_NO_EXPORT, dest_asn) ~ bgp_large_community then return false;
    handle_prepend(dest_asn);

    if (MY_ASN, LC_INFO, INFO_DOWNSTREAM) ~ bgp_large_community then return true;

    case net.type {
        NET_IP4: return source = RTS_STATIC && proto = "node_v4";
        NET_IP6: return source = RTS_STATIC && proto = "node_v6";
        else:    return false;
    }
}

function export_anycast() {
    case net.type {
        NET_IP4: return source = RTS_STATIC && proto = "node_v4_anycast";
        NET_IP6: return source = RTS_STATIC && proto = "node_v6_anycast";
        else:    return false;
    }
}

function export_default() {
    case net.type {
        NET_IP4: return source = RTS_STATIC && proto = "default_v4";
        NET_IP6: return source = RTS_STATIC && proto = "default_v6";
        else:    return false;
    }
}