├── .github └── workflows │ ├── bird.yml │ └── python.yml ├── LICENSE ├── README.md ├── aspa ├── __init__.py ├── data.py ├── example.json ├── generate.py ├── rpki-client.json ├── test.py └── validate.py ├── filter_aspa.conf ├── filter_bgp.conf ├── irr-filters.example ├── make-bird-aspa ├── make-irr-filter ├── make-prefix-limits ├── prefix-limits.example ├── skeleton-aspa.conf └── skeleton.conf /.github/workflows/bird.yml: -------------------------------------------------------------------------------- 1 | name: Test bird configuration 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | amd64: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | packages: write 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Install bird2 13 | run: | 14 | sudo add-apt-repository ppa:cz.nic-labs/bird 15 | sudo apt-get update 16 | sudo apt-get install -y bird2 17 | - name: Test skeleton.conf syntax 18 | run: /usr/sbin/bird -c skeleton.conf -p 19 | 20 | - name: Generate aspa_invalids.conf 21 | run: ./make-bird-aspa aspa/example.json > aspa_invalids.conf 22 | - name: Test skeleton-aspa.conf syntax 23 | run: /usr/sbin/bird -c skeleton-aspa.conf -p 24 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Run Python tests 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | amd64: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Set up Python 3.11 11 | uses: actions/setup-python@v3 12 | with: 13 | python-version: '3.11' 14 | - name: Run Python unit tests 15 | run: python -m unittest discover . 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Guanzhong Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum's `bird` Filter Library 2 | 3 | This is meant to be a starter repository containing sample `bird` 2.x config 4 | files that you can use to build your own BGP filters. Filters are provided as 5 | composable `bird` functions and enables you to harness the full power of the 6 | `bird` filter mini-programming language, as an alternative to a more declarative 7 | solution like [PathVector][pv]. 8 | 9 | ## Quick start 10 | 11 | 1. Make sure `bird` 2.x is installed, e.g. on Debian or Ubuntu, through 12 | `sudo apt install bird2`. This library has not been tested on versions 13 | older than 2.0.13, so you may run into syntax errors on earlier versions. 14 | In these cases, you'll need to look into backports or PPAs for a newer 15 | version. 16 | 2. Clone this repository: 17 | ``` 18 | git clone https://github.com/quantum5/bird-filter.git 19 | cd bird-filter 20 | ``` 21 | 3. Customize [`filter_bgp.conf`][filter] by editing it. Pay special attention 22 | to anything tagged `FIXME`. 23 | 4. Install `filter_bgp.conf` into your `bird` configuration directory 24 | (`/etc/bird` by default): 25 | ``` 26 | sudo cp filter_bgp.conf /etc/bird 27 | ``` 28 | 29 | ## Defining BGP sessions 30 | 31 | You can use [`skeleton.conf`][skeleton] as a basic `bird` starting config. 32 | Remember to read the `NOTE`s and change the things marked `FIXME`. 33 | 34 | Also note that in this config, static protocol routes are internal to `bird` 35 | and will not be exported to the kernel routing table. You can change this by 36 | changing the export rules for `protocol kernel`. 37 | 38 | This filter library makes use of two basic static protocols: 39 | * `node_v4`: IPv4 routes to be exported by the `export_cone` helper. 40 | * `node_v6`: IPv6 routes to be exported by the `export_cone` helper. 41 | 42 | For example, to advertise `198.51.100.0/24` and `2001:db8:1000::/36`: 43 | 44 | ``` 45 | protocol static node_v4 { 46 | ipv4 {}; 47 | route 198.51.100.0/24 reject; 48 | } 49 | 50 | protocol static node_v6 { 51 | ipv6 {}; 52 | route 2001:db8:1000::/36 reject; 53 | } 54 | ``` 55 | 56 | Two additional static protocols are used to aid with traffic engineering for 57 | anycast prefixes: 58 | * `node_v4_anycast`: IPv4 routes to be exported by the `export_anycast` helper. 59 | * `node_v6_anycast`: IPv6 routes to be exported by the `export_anycast` helper. 60 | 61 | You can add `protocol` blocks to this config for each BGP neighbour. This is 62 | dependent on the neighbour type. 63 | 64 | In the follow examples, we assume the following local preferences: 65 | * 50 for upstreams; 66 | * 90 for IXPs; 67 | * 100 for direct peers; and 68 | * 120 for downstreams. 69 | 70 | ### Upstreams 71 | 72 | ``` 73 | protocol bgp example_upstream_v4 { 74 | description "Example Upstream (IPv4)"; 75 | local 192.0.2.25 as 64500; 76 | neighbor 192.0.2.24 as 64501; 77 | default bgp_local_pref 50; 78 | 79 | ipv4 { 80 | import keep filtered; 81 | import where import_transit(64501, false); 82 | export where export_cone(64501); 83 | }; 84 | } 85 | 86 | protocol bgp example_upstream_v6 { 87 | description "Example Upstream (IPv6)"; 88 | local 2001:db8:2000::2 as 64500; 89 | neighbor 2001:db8:2000::1 as 64501; 90 | default bgp_local_pref 50; 91 | 92 | ipv6 { 93 | import keep filtered; 94 | import where import_transit(64501, false); 95 | export where export_cone(64501); 96 | }; 97 | } 98 | ``` 99 | 100 | The example above assumes you are AS64500 and establishes BGP sessions over 101 | both IPv4 and IPv6 with an upstream AS64501 and exports your entire cone. It 102 | also assumes your upstream is sending you a full table and filters out the 103 | default route. If you expect a default route instead, use 104 | `import where import_transit(64501, true)`. 105 | 106 | To export your anycast as well, you can simply do 107 | `export where export_cone(64501) || export_anycast()`. 108 | 109 | ### Peers 110 | 111 | ``` 112 | protocol bgp example_peer_v4 { 113 | description "Example Peer (IPv4)"; 114 | local 192.0.2.25 as 64500; 115 | neighbor 192.0.2.28 as 64502; 116 | default bgp_local_pref 100; 117 | 118 | ipv4 { 119 | import keep filtered; 120 | import where import_peer_trusted(64502); 121 | export where export_cone(64502); 122 | }; 123 | } 124 | 125 | protocol bgp example_peer_v6 { 126 | description "Example Peer (IPv6)"; 127 | local 2001:db8:2000::2 as 64500; 128 | neighbor 2001:db8:2000::10 as 64502; 129 | default bgp_local_pref 100; 130 | 131 | ipv6 { 132 | import keep filtered; 133 | import where import_peer_trusted(64502); 134 | export where export_cone(64502); 135 | }; 136 | } 137 | ``` 138 | 139 | The example above assumes you are AS64500 and establishes BGP sessions over 140 | both IPv4 and IPv6 with a peer AS64502 and exports your entire cone. It assumes 141 | your peer is trusted and doesn't provide any IRR filtering. If you don't trust 142 | your peer, see the [IRR filtering](#irr-filtering) section below. 143 | 144 | ### IXP route servers 145 | 146 | ``` 147 | protocol bgp example_ixp_v4 { 148 | description "Example IXP Route Servers (IPv4)"; 149 | local 203.0.113.3 as 64500; 150 | neighbor 203.0.113.1 as 64503; 151 | default bgp_local_pref 90; 152 | 153 | ipv4 { 154 | import keep filtered; 155 | import where import_ixp_trusted(64503); 156 | export where export_cone(64503); 157 | }; 158 | } 159 | 160 | protocol bgp example_ixp_v6 { 161 | description "Example IXP Route Servers (IPv6)"; 162 | local 2001:db8:3000::3 as 64500; 163 | neighbor 2001:db8:3000::1 as 64503; 164 | default bgp_local_pref 90; 165 | 166 | ipv6 { 167 | import keep filtered; 168 | import where import_ixp_trusted(64503); 169 | export where export_cone(64503); 170 | }; 171 | } 172 | ``` 173 | 174 | The example above assumes you are AS64500 and establishes BGP sessions over 175 | both IPv4 and IPv6 with the IXP route server whose ASN is 64503 and exports 176 | your entire cone. It assumes your IXP is trusted and doesn't provide any IRR 177 | filtering. If you don't trust your IXP, see the [IRR filtering](#irr-filtering) 178 | section below. 179 | 180 | ### Downstreams 181 | 182 | ``` 183 | protocol bgp example_downstream_v4 { 184 | description "Example Downstream (IPv4)"; 185 | local 203.0.113.3 as 64500; 186 | neighbor 203.0.113.7 as 64504; 187 | default bgp_local_pref 120; 188 | 189 | ipv4 { 190 | import keep filtered; 191 | import where import_downstream(64504, IRR_DOWNSTREAM_V4, IRR_DOWNSTREAM_ASN); 192 | export where export_to_downstream(); 193 | }; 194 | } 195 | 196 | protocol bgp example_downstream_v6 { 197 | description "Example Downstream (IPv6)"; 198 | local 2001:db8:3000::3 as 64500; 199 | neighbor 2001:db8:3000::7 as 64504; 200 | default bgp_local_pref 120; 201 | 202 | ipv6 { 203 | import keep filtered; 204 | import where import_downstream(64504, IRR_DOWNSTREAM_V6, IRR_DOWNSTREAM_ASN); 205 | export where export_to_downstream(); 206 | }; 207 | } 208 | ``` 209 | 210 | The example above assumes you are AS64500 and establishes BGP sessions over 211 | both IPv4 and IPv6 with a downstream whose ASN is 64504 and exports all your 212 | routes. For your protection, downstream imports without IRR is *not* supported. 213 | For details about setting up IRR, see the [IRR filtering](#irr-filtering) 214 | section below. 215 | 216 | ### Route collectors 217 | 218 | ``` 219 | protocol bgp route_collector { 220 | description "Example Route Collector"; 221 | local 2001:db8:2000::2 as 64500; 222 | neighbor 2001:db8:9000::1 as 64505; 223 | multihop; 224 | 225 | ipv4 { 226 | add paths on; 227 | import none; 228 | export where export_monitoring(); 229 | }; 230 | 231 | ipv6 { 232 | add paths on; 233 | import none; 234 | export where export_monitoring(); 235 | }; 236 | } 237 | ``` 238 | 239 | The example above assumes you are AS64500 and establishes a multihop BGP 240 | session with your route collector over IPv6, using multiprotocol BGP to export 241 | routes for both IPv4 and IPv6 in a single session, using `add paths` to also 242 | all routes instead of the best routes available. 243 | 244 | ### iBGP 245 | 246 | There is no true convention regarding iBGP usage since it's strictly internal 247 | to an AS, so this filter library will not attempt to make any assumption about 248 | how you might use iBGP. This means that by default, any route received from 249 | iBGP will not be exported by functions like `export_cone` or 250 | `export_to_downstream`. This avoids potential accidents like internal traffic 251 | engineering hijacks from being exported to the Internet and causing a major 252 | incident. 253 | 254 | To export an iBGP route to downstreams, set `export_downstream = 1;` in the 255 | import filter when importing the iBGP route. 256 | 257 | To export an iBGP route to upstreams, create a new `export_*` function that 258 | returns `true` for the subset of routes you wish to export, such as 259 | `export_ibgp`. Then, you can write your export clause as follows: 260 | 261 | ``` 262 | export where export_cone([PEER_ASN]) || export_ibgp(); 263 | ``` 264 | 265 | ## BGP communities 266 | 267 | The following large informational communities are implemented by default: 268 | * `YOUR_ASN:1:x`: route received from IXP with ID x; 269 | * `YOUR_ASN:2:x`: route received from neighbour with ASN x; 270 | * `YOUR_ASN:3:100`: route received from peer; 271 | * `YOUR_ASN:3:101`: route received from IXP route server; 272 | * `YOUR_ASN:3:102`: route received from upstream; and 273 | * `YOUR_ASN:3:103`: route received from downstream. 274 | 275 | The following large control communities are implemented by default and can be 276 | used by downstreams: 277 | * `YOUR_ASN:10:x`: do not export route to ASx; 278 | * `YOUR_ASN:11:x`: prepend `YOUR_ASN` once upon export to ASx; 279 | * `YOUR_ASN:12:x`: prepend `YOUR_ASN` twice upon export to ASx; and 280 | * `YOUR_ASN:13:x`: prepend `YOUR_ASN` thrice upon export to ASx. 281 | 282 | ## IRR filtering 283 | 284 | 1. Follow [`irr-filters.example`][irr-conf] and create `/etc/bird/irr-filters` 285 | for the peers you would like to filter. (To use alternative locations, edit 286 | [`make-irr-filter`][irr-script] accordingly.) 287 | 2. Run `make-irr-filter` to re-generate IRR filters. 288 | 3. Add `include "filter_irr.conf";` into your `bird.conf`. 289 | 4. Instead of `import_peer_trusted(asn)` or `import_ixp_trusted(ixp_id)`, use 290 | `import_peer(asn, IRR_PEER_V4, IRR_PEER_ASN)` or 291 | `import_peer(asn, IRR_PEER_V6, IRR_PEER_ASN)`, and similarly for IXPs. 292 | 5. Create a cron job that runs `make-irr-filter` followed by `birdc configure`. 293 | Daily is a reasonable cadence. 294 | 295 | ## PeeringDB prefix limits 296 | 297 | 1. Follow [`prefix-limits.example`][prefix-conf] and create 298 | `/etc/bird/prefix-limits` for peers for whom you'd like to enforce a prefix 299 | limit. 300 | 2. Adjust [`make-prefix-limits`][prefix-script] to use your own PeeringDB mirror 301 | if you risk getting rate limited. 302 | 3. Run `make-prefix-limits` to re-generate the prefix limits file. 303 | 4. Add `include "prefix_limit.conf";` into your `bird.conf`. 304 | 5. You can use constants like `LIMIT_AS200351_V4` or `LIMIT_AS200351_V6` in your 305 | `bird.conf`, for example: 306 | ``` 307 | protocol bgp peer_v6 { 308 | ... 309 | 310 | ipv6 { 311 | import limit LIMIT_AS23456_V6 action disable; 312 | ... 313 | }; 314 | } 315 | ``` 316 | 6. Create a cron job that runs `make-prefix-limits` followed by 317 | `birdc configure`. Daily is a reasonable cadence. 318 | 319 | ## RPKI ROA filtering 320 | 321 | While this filter library implements RPKI Route Origin Authorization (ROA) 322 | filtering, you still need to populate the `rpki4` and `rpki6` routing tables via 323 | an `rpki` protocol in `bird`. Otherwise, all routes will be treated as RPKI 324 | unknown. This can be configured as follows: 325 | 326 | ``` 327 | protocol rpki { 328 | roa4 { table rpki4; }; 329 | roa6 { table rpki6; }; 330 | transport tcp; 331 | remote "127.0.0.1" port 9001; 332 | retry keep 90; 333 | refresh keep 900; 334 | expire keep 172800; 335 | } 336 | ``` 337 | 338 | The example above assumes you are running the RTR protocol on `127.0.0.1:9001`. 339 | This may be provided by something like Routinator, `rtrtr`, `gortr`, or 340 | something similar. I recommend using `rtrtr` to pull a JSON feed from someone's 341 | Routinator instance over HTTPS. 342 | 343 | ## ASPA filtering (experimental) 344 | 345 | This filter library also offers an implementation of the [draft ASPA 346 | standard][aspa] for `bird`. To use it, use the [`make-bird-aspa`][aspa-script] 347 | script to generate the `is_aspa_invalid_pair` function on which 348 | `filter_aspa.conf` depends. This requires the JSON output from Routinator (see 349 | [`aspa/example.json`][aspa-example] for an example): 350 | 351 | ```console 352 | $ ./make-bird-aspa aspa/example.json > aspa_invalids.conf 353 | ``` 354 | 355 | Then, apply the following patch to [`filter_bgp.conf`][filter]: 356 | 357 | ```diff 358 | diff --git a/filter_bgp.conf b/filter_bgp.conf 359 | index eb85db7..27a2162 100644 360 | --- a/filter_bgp.conf 361 | +++ b/filter_bgp.conf 362 | @@ -107,6 +107,9 @@ define ASN_BOGON = [ 363 | 4294967295 # RFC 7300 Last 32 bit ASN 364 | ]; 365 | 366 | +include "aspa_invalids.conf"; 367 | +include "filter_aspa.conf"; 368 | + 369 | function ip_bogon() { 370 | case net.type { 371 | NET_IP4: return net ~ IPV4_BOGON; 372 | @@ -208,6 +211,11 @@ function import_peer_trusted(int peer_asn) { 373 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_PEER)); 374 | bgp_large_community.add((MY_ASN, LC_PEER_ASN, peer_asn)); 375 | 376 | + if is_aspa_invalid_peer(peer_asn) then { 377 | + print proto, ": ", net, ": invalid ASPA: ", bgp_path; 378 | + return false; 379 | + } 380 | + 381 | return import_safe(false); 382 | } 383 | 384 | @@ -232,6 +240,11 @@ function import_ixp_trusted(int ixp_id) { 385 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_IXP_RS)); 386 | bgp_large_community.add((MY_ASN, LC_IXP_ID, ixp_id)); 387 | 388 | + if is_aspa_invalid_peer(bgp_path.first) then { 389 | + print proto, ": ", net, ": invalid ASPA: ", bgp_path; 390 | + return false; 391 | + } 392 | + 393 | return import_safe(false); 394 | } 395 | 396 | @@ -256,6 +269,11 @@ function import_transit(int transit_asn; bool default_route) { 397 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_TRANSIT)); 398 | bgp_large_community.add((MY_ASN, LC_PEER_ASN, transit_asn)); 399 | 400 | + if is_aspa_invalid_upstream() then { 401 | + print proto, ": ", net, ": invalid ASPA: ", bgp_path; 402 | + return false; 403 | + } 404 | + 405 | return import_safe(default_route); 406 | } 407 | 408 | @@ -272,6 +290,11 @@ function import_downstream(int downstream_asn; prefix set prefixes; int set as_s 409 | } 410 | } 411 | 412 | + if is_aspa_invalid_customer() then { 413 | + print proto, ": ", net, ": invalid ASPA: ", bgp_path; 414 | + return false; 415 | + } 416 | + 417 | # If they don't want to export this to us, then we won't take it at all. 418 | if (MY_ASN, LC_NO_EXPORT, MY_ASN) ~ bgp_large_community then { 419 | print proto, ": ", net, ": rejected by no-export to AS", MY_ASN; 420 | ``` 421 | 422 | A [Python implementation][py-aspa-validate] of the validation function is also 423 | available along with [a suite of tests][py-aspa-test]. This helps ensure the 424 | version in the bird filter language is correct. 425 | 426 | [pv]: https://pathvector.io/ 427 | [filter]: filter_bgp.conf 428 | [skeleton]: skeleton.conf 429 | [irr-conf]: irr-filters.example 430 | [irr-script]: make-irr-filter 431 | [prefix-conf]: prefix-limits.example 432 | [prefix-script]: make-prefix-limits 433 | [aspa]: https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ 434 | [aspa-script]: make-bird-aspa 435 | [aspa-example]: aspa/example.json 436 | [py-aspa-validate]: aspa/validate.py 437 | [py-aspa-test]: aspa/test.py 438 | -------------------------------------------------------------------------------- /aspa/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum5/bird-filter/fe4eedbf768d5aa7e183fc603358897ff0ac16ef/aspa/__init__.py -------------------------------------------------------------------------------- /aspa/data.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from dataclasses import dataclass 4 | from typing import Optional, Union 5 | 6 | reasn = re.compile(r"^AS(\d+)$") 7 | 8 | 9 | def parse_asn(value: Union[str, int]) -> Optional[int]: 10 | if isinstance(value, int): 11 | return value 12 | 13 | match = reasn.match(value) 14 | if match: 15 | return int(match.group(1)) 16 | 17 | 18 | @dataclass 19 | class ASPA: 20 | customer: int 21 | providers: list[int] 22 | ta: Optional[str] 23 | 24 | @classmethod 25 | def from_dict(cls, d): 26 | try: 27 | if 'customer' in d: 28 | customer = parse_asn(d['customer']) 29 | elif 'customer_asid' in d: 30 | customer = parse_asn(d['customer_asid']) 31 | else: 32 | return None 33 | 34 | providers = list(map(parse_asn, d['providers'])) 35 | return cls(customer, providers, d.get('ta')) 36 | except (KeyError, TypeError): 37 | return None 38 | 39 | 40 | def parse_json(data: str) -> list[ASPA]: 41 | data = json.loads(data) 42 | return list(filter(None, map(ASPA.from_dict, data.get('aspas', [])))) 43 | -------------------------------------------------------------------------------- /aspa/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "generated": 1730500980, 4 | "generatedTime": "2024-11-01T22:43:00Z" 5 | }, 6 | "aspas": [ 7 | { 8 | "customer": "AS970", 9 | "providers": [ 10 | "AS54874" 11 | ], 12 | "ta": "arin" 13 | }, 14 | { 15 | "customer": "AS11358", 16 | "providers": [ 17 | "AS835", 18 | "AS924", 19 | "AS6939", 20 | "AS20473", 21 | "AS34927" 22 | ], 23 | "ta": "arin" 24 | }, 25 | { 26 | "customer": "AS13852", 27 | "providers": [ 28 | "AS6939", 29 | "AS20473", 30 | "AS34927", 31 | "AS41051", 32 | "AS52025" 33 | ], 34 | "ta": "arin" 35 | }, 36 | { 37 | "customer": "AS15562", 38 | "providers": [ 39 | "AS2914", 40 | "AS8283", 41 | "AS51088", 42 | "AS206238" 43 | ], 44 | "ta": "ripe" 45 | }, 46 | { 47 | "customer": "AS19330", 48 | "providers": [ 49 | "AS393577" 50 | ], 51 | "ta": "arin" 52 | }, 53 | { 54 | "customer": "AS21957", 55 | "providers": [ 56 | "AS970" 57 | ], 58 | "ta": "arin" 59 | }, 60 | { 61 | "customer": "AS28584", 62 | "providers": [ 63 | "AS28605" 64 | ], 65 | "ta": "lacnic" 66 | }, 67 | { 68 | "customer": "AS33733", 69 | "providers": [ 70 | "AS174", 71 | "AS6939", 72 | "AS12186", 73 | "AS47787", 74 | "AS200454" 75 | ], 76 | "ta": "arin" 77 | }, 78 | { 79 | "customer": "AS40544", 80 | "providers": [ 81 | "AS924", 82 | "AS6939", 83 | "AS12186", 84 | "AS52025" 85 | ], 86 | "ta": "arin" 87 | }, 88 | { 89 | "customer": "AS41720", 90 | "providers": [ 91 | "AS174", 92 | "AS1299", 93 | "AS9002", 94 | "AS50877", 95 | "AS60068", 96 | "AS203446", 97 | "AS212508" 98 | ], 99 | "ta": "ripe" 100 | }, 101 | { 102 | "customer": "AS44324", 103 | "providers": [ 104 | "AS945", 105 | "AS955", 106 | "AS1299", 107 | "AS3204", 108 | "AS6939", 109 | "AS7720", 110 | "AS8772", 111 | "AS8849", 112 | "AS15353", 113 | "AS18041", 114 | "AS20473", 115 | "AS29632", 116 | "AS32595", 117 | "AS34465", 118 | "AS34927", 119 | "AS41051", 120 | "AS43426", 121 | "AS47272", 122 | "AS51087", 123 | "AS53667", 124 | "AS53808", 125 | "AS59538", 126 | "AS61112", 127 | "AS134823", 128 | "AS138997", 129 | "AS139317", 130 | "AS151364", 131 | "AS199545", 132 | "AS199765", 133 | "AS200105", 134 | "AS206499", 135 | "AS207656", 136 | "AS207841", 137 | "AS209554", 138 | "AS209735", 139 | "AS212483" 140 | ], 141 | "ta": "ripe" 142 | }, 143 | { 144 | "customer": "AS47272", 145 | "providers": [ 146 | "AS835", 147 | "AS924", 148 | "AS1299", 149 | "AS6830", 150 | "AS6939", 151 | "AS20473", 152 | "AS21738", 153 | "AS25759", 154 | "AS34927", 155 | "AS35133", 156 | "AS41051", 157 | "AS48605", 158 | "AS50391", 159 | "AS50917", 160 | "AS52025", 161 | "AS52210", 162 | "AS58057", 163 | "AS210667", 164 | "AS212514", 165 | "AS212895" 166 | ], 167 | "ta": "ripe" 168 | }, 169 | { 170 | "customer": "AS47689", 171 | "providers": [ 172 | "AS924", 173 | "AS1299", 174 | "AS6939", 175 | "AS21738", 176 | "AS34465", 177 | "AS34549", 178 | "AS34927", 179 | "AS395823" 180 | ], 181 | "ta": "ripe" 182 | }, 183 | { 184 | "customer": "AS48070", 185 | "providers": [ 186 | "AS174", 187 | "AS1299" 188 | ], 189 | "ta": "ripe" 190 | }, 191 | { 192 | "customer": "AS48606", 193 | "providers": [ 194 | "AS30893", 195 | "AS54681", 196 | "AS57974", 197 | "AS59678", 198 | "AS210691", 199 | "AS212276" 200 | ], 201 | "ta": "ripe" 202 | }, 203 | { 204 | "customer": "AS50224", 205 | "providers": [ 206 | "AS6939", 207 | "AS15353", 208 | "AS26930", 209 | "AS34465", 210 | "AS37988", 211 | "AS50104", 212 | "AS52025", 213 | "AS52210", 214 | "AS59920", 215 | "AS60841", 216 | "AS210475", 217 | "AS400304" 218 | ], 219 | "ta": "ripe" 220 | }, 221 | { 222 | "customer": "AS50391", 223 | "providers": [ 224 | "AS6939", 225 | "AS20473", 226 | "AS48070" 227 | ], 228 | "ta": "ripe" 229 | }, 230 | { 231 | "customer": "AS50555", 232 | "providers": [ 233 | "AS970" 234 | ], 235 | "ta": "ripe" 236 | }, 237 | { 238 | "customer": "AS51019", 239 | "providers": [ 240 | "AS6939", 241 | "AS34549", 242 | "AS34927", 243 | "AS52025", 244 | "AS207841" 245 | ], 246 | "ta": "ripe" 247 | }, 248 | { 249 | "customer": "AS51396", 250 | "providers": [ 251 | "AS6939", 252 | "AS30823", 253 | "AS44066", 254 | "AS49581", 255 | "AS60223", 256 | "AS203446", 257 | "AS214995", 258 | "AS215436" 259 | ], 260 | "ta": "ripe" 261 | }, 262 | { 263 | "customer": "AS52025", 264 | "providers": [ 265 | "AS174", 266 | "AS835", 267 | "AS906", 268 | "AS917", 269 | "AS924", 270 | "AS1299", 271 | "AS3257", 272 | "AS6204", 273 | "AS6939", 274 | "AS7720", 275 | "AS8860", 276 | "AS12186", 277 | "AS18041", 278 | "AS20473", 279 | "AS21700", 280 | "AS21738", 281 | "AS25369", 282 | "AS29802", 283 | "AS32097", 284 | "AS34549", 285 | "AS34927", 286 | "AS35133", 287 | "AS35661", 288 | "AS36369", 289 | "AS37988", 290 | "AS39409", 291 | "AS47787", 292 | "AS48070", 293 | "AS48605", 294 | "AS50917", 295 | "AS51519", 296 | "AS52210", 297 | "AS56655", 298 | "AS57695", 299 | "AS61138", 300 | "AS197216", 301 | "AS199545", 302 | "AS209022", 303 | "AS209533", 304 | "AS394177", 305 | "AS396998", 306 | "AS397423", 307 | "AS400304", 308 | "AS400587" 309 | ], 310 | "ta": "ripe" 311 | }, 312 | { 313 | "customer": "AS52210", 314 | "providers": [ 315 | "AS174", 316 | "AS835", 317 | "AS6939", 318 | "AS52025", 319 | "AS62513", 320 | "AS210667" 321 | ], 322 | "ta": "ripe" 323 | }, 324 | { 325 | "customer": "AS54148", 326 | "providers": [ 327 | "AS835", 328 | "AS924", 329 | "AS6939", 330 | "AS20473", 331 | "AS21738", 332 | "AS34927", 333 | "AS37988", 334 | "AS47272", 335 | "AS50917", 336 | "AS53667" 337 | ], 338 | "ta": "arin" 339 | }, 340 | { 341 | "customer": "AS54218", 342 | "providers": [ 343 | "AS917", 344 | "AS16509", 345 | "AS37988", 346 | "AS53667", 347 | "AS59678" 348 | ], 349 | "ta": "arin" 350 | }, 351 | { 352 | "customer": "AS56762", 353 | "providers": [ 354 | "AS47689" 355 | ], 356 | "ta": "ripe" 357 | }, 358 | { 359 | "customer": "AS57194", 360 | "providers": [ 361 | "AS924", 362 | "AS52210" 363 | ], 364 | "ta": "ripe" 365 | }, 366 | { 367 | "customer": "AS57984", 368 | "providers": [ 369 | "AS8283", 370 | "AS48112", 371 | "AS206763" 372 | ], 373 | "ta": "ripe" 374 | }, 375 | { 376 | "customer": "AS59678", 377 | "providers": [ 378 | "AS6939", 379 | "AS34927", 380 | "AS54218", 381 | "AS55081" 382 | ], 383 | "ta": "ripe" 384 | }, 385 | { 386 | "customer": "AS60223", 387 | "providers": [ 388 | "AS945", 389 | "AS49581" 390 | ], 391 | "ta": "ripe" 392 | }, 393 | { 394 | "customer": "AS60431", 395 | "providers": [ 396 | "AS34927", 397 | "AS41495", 398 | "AS50917" 399 | ], 400 | "ta": "ripe" 401 | }, 402 | { 403 | "customer": "AS60841", 404 | "providers": [ 405 | "AS30456", 406 | "AS400304" 407 | ], 408 | "ta": "ripe" 409 | }, 410 | { 411 | "customer": "AS149301", 412 | "providers": [ 413 | "AS20473", 414 | "AS61138" 415 | ], 416 | "ta": "apnic" 417 | }, 418 | { 419 | "customer": "AS151642", 420 | "providers": [ 421 | "AS20473", 422 | "AS61138" 423 | ], 424 | "ta": "apnic" 425 | }, 426 | { 427 | "customer": "AS198136", 428 | "providers": [ 429 | "AS0" 430 | ], 431 | "ta": "ripe" 432 | }, 433 | { 434 | "customer": "AS199376", 435 | "providers": [ 436 | "AS47311", 437 | "AS61112", 438 | "AS150249", 439 | "AS151544", 440 | "AS199762", 441 | "AS209533", 442 | "AS215379" 443 | ], 444 | "ta": "ripe" 445 | }, 446 | { 447 | "customer": "AS199762", 448 | "providers": [ 449 | "AS945", 450 | "AS6939", 451 | "AS8849", 452 | "AS15353", 453 | "AS34927", 454 | "AS44817", 455 | "AS47311", 456 | "AS48605", 457 | "AS140731", 458 | "AS150249" 459 | ], 460 | "ta": "ripe" 461 | }, 462 | { 463 | "customer": "AS200160", 464 | "providers": [ 465 | "AS945", 466 | "AS6939", 467 | "AS15353", 468 | "AS21738", 469 | "AS29632", 470 | "AS34465", 471 | "AS34549", 472 | "AS34927", 473 | "AS36369", 474 | "AS48605", 475 | "AS140731", 476 | "AS150249", 477 | "AS199762", 478 | "AS203686", 479 | "AS203913", 480 | "AS210152", 481 | "AS210475", 482 | "AS400304", 483 | "AS400818" 484 | ], 485 | "ta": "ripe" 486 | }, 487 | { 488 | "customer": "AS200242", 489 | "providers": [ 490 | "AS835", 491 | "AS6939", 492 | "AS21738", 493 | "AS34465", 494 | "AS34927", 495 | "AS52025", 496 | "AS59678", 497 | "AS60841", 498 | "AS210475" 499 | ], 500 | "ta": "ripe" 501 | }, 502 | { 503 | "customer": "AS200351", 504 | "providers": [ 505 | "AS34927", 506 | "AS47272", 507 | "AS209022", 508 | "AS210475" 509 | ], 510 | "ta": "arin" 511 | }, 512 | { 513 | "customer": "AS200454", 514 | "providers": [ 515 | "AS174", 516 | "AS6424", 517 | "AS6939", 518 | "AS9186", 519 | "AS12186" 520 | ], 521 | "ta": "ripe" 522 | }, 523 | { 524 | "customer": "AS202076", 525 | "providers": [ 526 | "AS207960" 527 | ], 528 | "ta": "ripe" 529 | }, 530 | { 531 | "customer": "AS202359", 532 | "providers": [ 533 | "AS30740", 534 | "AS33920", 535 | "AS41495" 536 | ], 537 | "ta": "ripe" 538 | }, 539 | { 540 | "customer": "AS202881", 541 | "providers": [ 542 | "AS205329" 543 | ], 544 | "ta": "ripe" 545 | }, 546 | { 547 | "customer": "AS203619", 548 | "providers": [ 549 | "AS200454" 550 | ], 551 | "ta": "ripe" 552 | }, 553 | { 554 | "customer": "AS203843", 555 | "providers": [ 556 | "AS6939", 557 | "AS20473", 558 | "AS53667" 559 | ], 560 | "ta": "ripe" 561 | }, 562 | { 563 | "customer": "AS204857", 564 | "providers": [ 565 | "AS50224", 566 | "AS59920", 567 | "AS60841", 568 | "AS400304" 569 | ], 570 | "ta": "ripe" 571 | }, 572 | { 573 | "customer": "AS204931", 574 | "providers": [ 575 | "AS34927", 576 | "AS44103", 577 | "AS207656" 578 | ], 579 | "ta": "ripe" 580 | }, 581 | { 582 | "customer": "AS205329", 583 | "providers": [ 584 | "AS945", 585 | "AS983", 586 | "AS6939", 587 | "AS7720", 588 | "AS8894", 589 | "AS9409", 590 | "AS20473", 591 | "AS34927", 592 | "AS41051", 593 | "AS131657", 594 | "AS134823", 595 | "AS151364" 596 | ], 597 | "ta": "ripe" 598 | }, 599 | { 600 | "customer": "AS205603", 601 | "providers": [ 602 | "AS38074", 603 | "AS59105" 604 | ], 605 | "ta": "ripe" 606 | }, 607 | { 608 | "customer": "AS205663", 609 | "providers": [ 610 | "AS20473", 611 | "AS61138" 612 | ], 613 | "ta": "arin" 614 | }, 615 | { 616 | "customer": "AS205789", 617 | "providers": [ 618 | "AS200242", 619 | "AS207960" 620 | ], 621 | "ta": "ripe" 622 | }, 623 | { 624 | "customer": "AS205848", 625 | "providers": [ 626 | "AS1299", 627 | "AS6939", 628 | "AS34549", 629 | "AS34927", 630 | "AS52025", 631 | "AS209022" 632 | ], 633 | "ta": "ripe" 634 | }, 635 | { 636 | "customer": "AS207487", 637 | "providers": [ 638 | "AS20473" 639 | ], 640 | "ta": "ripe" 641 | }, 642 | { 643 | "customer": "AS207841", 644 | "providers": [ 645 | "AS174", 646 | "AS6939", 647 | "AS137409" 648 | ], 649 | "ta": "ripe" 650 | }, 651 | { 652 | "customer": "AS207960", 653 | "providers": [ 654 | "AS6939", 655 | "AS20473", 656 | "AS34854", 657 | "AS34927", 658 | "AS136620", 659 | "AS202359", 660 | "AS207968" 661 | ], 662 | "ta": "ripe" 663 | }, 664 | { 665 | "customer": "AS209025", 666 | "providers": [ 667 | "AS945", 668 | "AS6939", 669 | "AS15353", 670 | "AS21738", 671 | "AS29632", 672 | "AS34465", 673 | "AS34549", 674 | "AS34927", 675 | "AS36369", 676 | "AS48605", 677 | "AS140731", 678 | "AS150249", 679 | "AS199762", 680 | "AS200160", 681 | "AS203686", 682 | "AS203913", 683 | "AS210152", 684 | "AS210475", 685 | "AS400304", 686 | "AS400818" 687 | ], 688 | "ta": "ripe" 689 | }, 690 | { 691 | "customer": "AS210561", 692 | "providers": [ 693 | "AS207960" 694 | ], 695 | "ta": "ripe" 696 | }, 697 | { 698 | "customer": "AS212068", 699 | "providers": [ 700 | "AS6939", 701 | "AS13237", 702 | "AS62240", 703 | "AS207960" 704 | ], 705 | "ta": "ripe" 706 | }, 707 | { 708 | "customer": "AS212245", 709 | "providers": [ 710 | "AS917", 711 | "AS57695", 712 | "AS212068" 713 | ], 714 | "ta": "ripe" 715 | }, 716 | { 717 | "customer": "AS212934", 718 | "providers": [ 719 | "AS835", 720 | "AS6939", 721 | "AS34927", 722 | "AS52210", 723 | "AS53667", 724 | "AS61138", 725 | "AS62513", 726 | "AS209533", 727 | "AS210667" 728 | ], 729 | "ta": "ripe" 730 | }, 731 | { 732 | "customer": "AS213086", 733 | "providers": [ 734 | "AS945", 735 | "AS6939", 736 | "AS15353", 737 | "AS21738", 738 | "AS29632", 739 | "AS34465", 740 | "AS34549", 741 | "AS34927", 742 | "AS36369", 743 | "AS48605", 744 | "AS140731", 745 | "AS150249", 746 | "AS199762", 747 | "AS200160", 748 | "AS203686", 749 | "AS203913", 750 | "AS207656", 751 | "AS210152", 752 | "AS210475", 753 | "AS400304", 754 | "AS400818" 755 | ], 756 | "ta": "ripe" 757 | }, 758 | { 759 | "customer": "AS215028", 760 | "providers": [ 761 | "AS216107" 762 | ], 763 | "ta": "ripe" 764 | }, 765 | { 766 | "customer": "AS215131", 767 | "providers": [ 768 | "AS8283", 769 | "AS34927" 770 | ], 771 | "ta": "ripe" 772 | }, 773 | { 774 | "customer": "AS215147", 775 | "providers": [ 776 | "AS47263", 777 | "AS207252", 778 | "AS215828" 779 | ], 780 | "ta": "ripe" 781 | }, 782 | { 783 | "customer": "AS215436", 784 | "providers": [ 785 | "AS6762", 786 | "AS6939", 787 | "AS51396", 788 | "AS60223" 789 | ], 790 | "ta": "ripe" 791 | }, 792 | { 793 | "customer": "AS215664", 794 | "providers": [ 795 | "AS1299", 796 | "AS3204", 797 | "AS6939", 798 | "AS34549", 799 | "AS41720", 800 | "AS56382", 801 | "AS203446", 802 | "AS212508" 803 | ], 804 | "ta": "ripe" 805 | }, 806 | { 807 | "customer": "AS215778", 808 | "providers": [ 809 | "AS39249", 810 | "AS41051", 811 | "AS207656", 812 | "AS209533", 813 | "AS393577" 814 | ], 815 | "ta": "ripe" 816 | }, 817 | { 818 | "customer": "AS215828", 819 | "providers": [ 820 | "AS6204", 821 | "AS6939", 822 | "AS8772", 823 | "AS29390", 824 | "AS29632", 825 | "AS34465", 826 | "AS34927", 827 | "AS39249", 828 | "AS41051", 829 | "AS44592", 830 | "AS48752", 831 | "AS50917", 832 | "AS51019", 833 | "AS58057", 834 | "AS62403", 835 | "AS152726", 836 | "AS197071", 837 | "AS199524", 838 | "AS203686", 839 | "AS207656", 840 | "AS209533", 841 | "AS210464", 842 | "AS216324", 843 | "AS216360", 844 | "AS393577" 845 | ], 846 | "ta": "ripe" 847 | }, 848 | { 849 | "customer": "AS215849", 850 | "providers": [ 851 | "AS215828" 852 | ], 853 | "ta": "ripe" 854 | }, 855 | { 856 | "customer": "AS216107", 857 | "providers": [ 858 | "AS924", 859 | "AS6939", 860 | "AS16509", 861 | "AS20473", 862 | "AS21738", 863 | "AS34927", 864 | "AS63473", 865 | "AS209022", 866 | "AS211588" 867 | ], 868 | "ta": "ripe" 869 | }, 870 | { 871 | "customer": "AS216311", 872 | "providers": [ 873 | "AS6939", 874 | "AS13852", 875 | "AS20473", 876 | "AS34927", 877 | "AS52025" 878 | ], 879 | "ta": "ripe" 880 | }, 881 | { 882 | "customer": "AS267386", 883 | "providers": [ 884 | "AS6939", 885 | "AS25933", 886 | "AS28220", 887 | "AS28649", 888 | "AS267613" 889 | ], 890 | "ta": "lacnic" 891 | }, 892 | { 893 | "customer": "AS270470", 894 | "providers": [ 895 | "AS53062" 896 | ], 897 | "ta": "lacnic" 898 | }, 899 | { 900 | "customer": "AS393577", 901 | "providers": [ 902 | "AS1299", 903 | "AS6939", 904 | "AS32097", 905 | "AS40676", 906 | "AS137409" 907 | ], 908 | "ta": "arin" 909 | }, 910 | { 911 | "customer": "AS401111", 912 | "providers": [ 913 | "AS945", 914 | "AS17676" 915 | ], 916 | "ta": "arin" 917 | } 918 | ] 919 | } 920 | -------------------------------------------------------------------------------- /aspa/generate.py: -------------------------------------------------------------------------------- 1 | from aspa.data import ASPA 2 | 3 | 4 | def generate_bird(aspas: list[ASPA]) -> str: 5 | lines = [ 6 | 'function is_aspa_invalid_pair(int upstream_asn; int downstream_asn) {', 7 | ' case downstream_asn {' 8 | ] 9 | 10 | for aspa in aspas: 11 | if aspa.providers: 12 | asns = ', '.join(map(str, aspa.providers)) 13 | lines.append(f' {aspa.customer}: if upstream_asn !~ [{asns}] then return true;') 14 | else: 15 | lines.append(f' {aspa.customer}: return true;') 16 | 17 | lines += [ 18 | ' }', 19 | ' return false;', 20 | '}' 21 | ] 22 | 23 | return '\n'.join(lines) 24 | -------------------------------------------------------------------------------- /aspa/rpki-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "aspas": [ 3 | { 4 | "customer_asid": 970, 5 | "expires": 1730538312, 6 | "providers": [ 7 | 54874 8 | ] 9 | }, 10 | { 11 | "customer_asid": 11358, 12 | "expires": 1730531608, 13 | "providers": [ 14 | 835, 15 | 924, 16 | 6939, 17 | 20473, 18 | 34927 19 | ] 20 | }, 21 | { 22 | "customer_asid": 13852, 23 | "expires": 1730549327, 24 | "providers": [ 25 | 6939, 26 | 20473, 27 | 34927, 28 | 41051, 29 | 52025 30 | ] 31 | }, 32 | { 33 | "customer_asid": 15562, 34 | "expires": 1730520011, 35 | "providers": [ 36 | 2914, 37 | 8283, 38 | 51088, 39 | 206238 40 | ] 41 | }, 42 | { 43 | "customer_asid": 19330, 44 | "expires": 1730571436, 45 | "providers": [ 46 | 393577 47 | ] 48 | }, 49 | { 50 | "customer_asid": 21957, 51 | "expires": 1730538312, 52 | "providers": [ 53 | 970 54 | ] 55 | }, 56 | { 57 | "customer_asid": 28584, 58 | "expires": 1730578637, 59 | "providers": [ 60 | 28605 61 | ] 62 | }, 63 | { 64 | "customer_asid": 33733, 65 | "expires": 1730582237, 66 | "providers": [ 67 | 174, 68 | 6939, 69 | 12186, 70 | 47787, 71 | 200454 72 | ] 73 | }, 74 | { 75 | "customer_asid": 40544, 76 | "expires": 1730556387, 77 | "providers": [ 78 | 924, 79 | 6939, 80 | 12186, 81 | 52025 82 | ] 83 | }, 84 | { 85 | "customer_asid": 41720, 86 | "expires": 1730563201, 87 | "providers": [ 88 | 174, 89 | 1299, 90 | 9002, 91 | 50877, 92 | 60068, 93 | 203446, 94 | 212508 95 | ] 96 | }, 97 | { 98 | "customer_asid": 44324, 99 | "expires": 1730540598, 100 | "providers": [ 101 | 945, 102 | 955, 103 | 1299, 104 | 3204, 105 | 6939, 106 | 7720, 107 | 8772, 108 | 8849, 109 | 15353, 110 | 18041, 111 | 20473, 112 | 29632, 113 | 32595, 114 | 34465, 115 | 34927, 116 | 41051, 117 | 43426, 118 | 47272, 119 | 51087, 120 | 53667, 121 | 53808, 122 | 59538, 123 | 61112, 124 | 134823, 125 | 138997, 126 | 139317, 127 | 151364, 128 | 199545, 129 | 199765, 130 | 200105, 131 | 206499, 132 | 207656, 133 | 207841, 134 | 209554, 135 | 209735, 136 | 212483 137 | ] 138 | }, 139 | { 140 | "customer_asid": 47272, 141 | "expires": 1730561019, 142 | "providers": [ 143 | 835, 144 | 924, 145 | 1299, 146 | 6830, 147 | 6939, 148 | 20473, 149 | 21738, 150 | 25759, 151 | 34927, 152 | 35133, 153 | 41051, 154 | 48605, 155 | 50391, 156 | 50917, 157 | 52025, 158 | 52210, 159 | 58057, 160 | 210667, 161 | 212514, 162 | 212895 163 | ] 164 | }, 165 | { 166 | "customer_asid": 47689, 167 | "expires": 1730563201, 168 | "providers": [ 169 | 924, 170 | 1299, 171 | 6939, 172 | 21738, 173 | 34465, 174 | 34549, 175 | 34927, 176 | 395823 177 | ] 178 | }, 179 | { 180 | "customer_asid": 48070, 181 | "expires": 1730563201, 182 | "providers": [ 183 | 174, 184 | 1299 185 | ] 186 | }, 187 | { 188 | "customer_asid": 48606, 189 | "expires": 1730531513, 190 | "providers": [ 191 | 30893, 192 | 54681, 193 | 57974, 194 | 59678, 195 | 210691, 196 | 212276 197 | ] 198 | }, 199 | { 200 | "customer_asid": 50224, 201 | "expires": 1730546559, 202 | "providers": [ 203 | 6939, 204 | 15353, 205 | 26930, 206 | 34465, 207 | 37988, 208 | 50104, 209 | 52025, 210 | 52210, 211 | 59920, 212 | 60841, 213 | 210475, 214 | 400304 215 | ] 216 | }, 217 | { 218 | "customer_asid": 50391, 219 | "expires": 1730537906, 220 | "providers": [ 221 | 6939, 222 | 20473, 223 | 48070 224 | ] 225 | }, 226 | { 227 | "customer_asid": 50555, 228 | "expires": 1730532071, 229 | "providers": [ 230 | 970 231 | ] 232 | }, 233 | { 234 | "customer_asid": 51019, 235 | "expires": 1730560829, 236 | "providers": [ 237 | 6939, 238 | 34549, 239 | 34927, 240 | 52025, 241 | 207841 242 | ] 243 | }, 244 | { 245 | "customer_asid": 51396, 246 | "expires": 1730563201, 247 | "providers": [ 248 | 6939, 249 | 30823, 250 | 44066, 251 | 49581, 252 | 60223, 253 | 203446, 254 | 214995, 255 | 215436 256 | ] 257 | }, 258 | { 259 | "customer_asid": 52025, 260 | "expires": 1730547627, 261 | "providers": [ 262 | 174, 263 | 835, 264 | 906, 265 | 917, 266 | 924, 267 | 1299, 268 | 3257, 269 | 6204, 270 | 6939, 271 | 7720, 272 | 8860, 273 | 12186, 274 | 18041, 275 | 20473, 276 | 21700, 277 | 21738, 278 | 25369, 279 | 29802, 280 | 32097, 281 | 34549, 282 | 34927, 283 | 35133, 284 | 35661, 285 | 36369, 286 | 37988, 287 | 39409, 288 | 47787, 289 | 48070, 290 | 48605, 291 | 50917, 292 | 51519, 293 | 52210, 294 | 56655, 295 | 57695, 296 | 61138, 297 | 197216, 298 | 199545, 299 | 209022, 300 | 209533, 301 | 394177, 302 | 396998, 303 | 397423, 304 | 400304, 305 | 400587 306 | ] 307 | }, 308 | { 309 | "customer_asid": 52210, 310 | "expires": 1730562314, 311 | "providers": [ 312 | 174, 313 | 835, 314 | 6939, 315 | 52025, 316 | 62513, 317 | 210667 318 | ] 319 | }, 320 | { 321 | "customer_asid": 54148, 322 | "expires": 1730563237, 323 | "providers": [ 324 | 835, 325 | 924, 326 | 6939, 327 | 20473, 328 | 21738, 329 | 34927, 330 | 37988, 331 | 47272, 332 | 50917, 333 | 53667 334 | ] 335 | }, 336 | { 337 | "customer_asid": 54218, 338 | "expires": 1730572673, 339 | "providers": [ 340 | 917, 341 | 16509, 342 | 37988, 343 | 53667, 344 | 59678 345 | ] 346 | }, 347 | { 348 | "customer_asid": 56762, 349 | "expires": 1730563201, 350 | "providers": [ 351 | 47689 352 | ] 353 | }, 354 | { 355 | "customer_asid": 57194, 356 | "expires": 1730562314, 357 | "providers": [ 358 | 924, 359 | 52210 360 | ] 361 | }, 362 | { 363 | "customer_asid": 57984, 364 | "expires": 1730563201, 365 | "providers": [ 366 | 8283, 367 | 48112, 368 | 206763 369 | ] 370 | }, 371 | { 372 | "customer_asid": 59678, 373 | "expires": 1730531513, 374 | "providers": [ 375 | 6939, 376 | 34927, 377 | 54218, 378 | 55081 379 | ] 380 | }, 381 | { 382 | "customer_asid": 60223, 383 | "expires": 1730563201, 384 | "providers": [ 385 | 945, 386 | 49581 387 | ] 388 | }, 389 | { 390 | "customer_asid": 60431, 391 | "expires": 1730563201, 392 | "providers": [ 393 | 34927, 394 | 41495, 395 | 50917 396 | ] 397 | }, 398 | { 399 | "customer_asid": 60841, 400 | "expires": 1730563201, 401 | "providers": [ 402 | 30456, 403 | 400304 404 | ] 405 | }, 406 | { 407 | "customer_asid": 149301, 408 | "expires": 1730539765, 409 | "providers": [ 410 | 20473, 411 | 61138 412 | ] 413 | }, 414 | { 415 | "customer_asid": 151642, 416 | "expires": 1730596405, 417 | "providers": [ 418 | 20473, 419 | 61138 420 | ] 421 | }, 422 | { 423 | "customer_asid": 198136, 424 | "expires": 1730563201, 425 | "providers": [ 426 | 0 427 | ] 428 | }, 429 | { 430 | "customer_asid": 199376, 431 | "expires": 1730563201, 432 | "providers": [ 433 | 47311, 434 | 61112, 435 | 150249, 436 | 151544, 437 | 199762, 438 | 209533, 439 | 215379 440 | ] 441 | }, 442 | { 443 | "customer_asid": 199762, 444 | "expires": 1730563201, 445 | "providers": [ 446 | 945, 447 | 6939, 448 | 8849, 449 | 15353, 450 | 34927, 451 | 44817, 452 | 47311, 453 | 48605, 454 | 140731, 455 | 150249 456 | ] 457 | }, 458 | { 459 | "customer_asid": 200160, 460 | "expires": 1730534753, 461 | "providers": [ 462 | 945, 463 | 6939, 464 | 15353, 465 | 21738, 466 | 29632, 467 | 34465, 468 | 34549, 469 | 34927, 470 | 36369, 471 | 48605, 472 | 140731, 473 | 150249, 474 | 199762, 475 | 203686, 476 | 203913, 477 | 210152, 478 | 210475, 479 | 400304, 480 | 400818 481 | ] 482 | }, 483 | { 484 | "customer_asid": 200242, 485 | "expires": 1730531523, 486 | "providers": [ 487 | 835, 488 | 6939, 489 | 21738, 490 | 34465, 491 | 34927, 492 | 52025, 493 | 59678, 494 | 60841, 495 | 210475 496 | ] 497 | }, 498 | { 499 | "customer_asid": 200351, 500 | "expires": 1730563237, 501 | "providers": [ 502 | 34927, 503 | 47272, 504 | 209022, 505 | 210475 506 | ] 507 | }, 508 | { 509 | "customer_asid": 200454, 510 | "expires": 1730563201, 511 | "providers": [ 512 | 174, 513 | 6424, 514 | 6939, 515 | 9186, 516 | 12186 517 | ] 518 | }, 519 | { 520 | "customer_asid": 202076, 521 | "expires": 1730563201, 522 | "providers": [ 523 | 207960 524 | ] 525 | }, 526 | { 527 | "customer_asid": 202359, 528 | "expires": 1730530237, 529 | "providers": [ 530 | 30740, 531 | 33920, 532 | 41495 533 | ] 534 | }, 535 | { 536 | "customer_asid": 202881, 537 | "expires": 1730563201, 538 | "providers": [ 539 | 205329 540 | ] 541 | }, 542 | { 543 | "customer_asid": 203619, 544 | "expires": 1730563201, 545 | "providers": [ 546 | 200454 547 | ] 548 | }, 549 | { 550 | "customer_asid": 203843, 551 | "expires": 1730546014, 552 | "providers": [ 553 | 6939, 554 | 20473, 555 | 53667 556 | ] 557 | }, 558 | { 559 | "customer_asid": 204857, 560 | "expires": 1730563201, 561 | "providers": [ 562 | 50224, 563 | 59920, 564 | 60841, 565 | 400304 566 | ] 567 | }, 568 | { 569 | "customer_asid": 204931, 570 | "expires": 1730563201, 571 | "providers": [ 572 | 34927, 573 | 44103, 574 | 207656 575 | ] 576 | }, 577 | { 578 | "customer_asid": 205329, 579 | "expires": 1730563201, 580 | "providers": [ 581 | 945, 582 | 983, 583 | 6939, 584 | 7720, 585 | 8894, 586 | 9409, 587 | 20473, 588 | 34927, 589 | 41051, 590 | 131657, 591 | 134823, 592 | 151364 593 | ] 594 | }, 595 | { 596 | "customer_asid": 205603, 597 | "expires": 1730563201, 598 | "providers": [ 599 | 38074, 600 | 59105 601 | ] 602 | }, 603 | { 604 | "customer_asid": 205663, 605 | "expires": 1730590405, 606 | "providers": [ 607 | 20473, 608 | 61138 609 | ] 610 | }, 611 | { 612 | "customer_asid": 205789, 613 | "expires": 1730563201, 614 | "providers": [ 615 | 200242, 616 | 207960 617 | ] 618 | }, 619 | { 620 | "customer_asid": 205848, 621 | "expires": 1730563201, 622 | "providers": [ 623 | 1299, 624 | 6939, 625 | 34549, 626 | 34927, 627 | 52025, 628 | 209022 629 | ] 630 | }, 631 | { 632 | "customer_asid": 207487, 633 | "expires": 1730563201, 634 | "providers": [ 635 | 20473 636 | ] 637 | }, 638 | { 639 | "customer_asid": 207841, 640 | "expires": 1730563201, 641 | "providers": [ 642 | 174, 643 | 6939, 644 | 137409 645 | ] 646 | }, 647 | { 648 | "customer_asid": 207960, 649 | "expires": 1730530237, 650 | "providers": [ 651 | 6939, 652 | 20473, 653 | 34854, 654 | 34927, 655 | 136620, 656 | 202359, 657 | 207968 658 | ] 659 | }, 660 | { 661 | "customer_asid": 209025, 662 | "expires": 1730563201, 663 | "providers": [ 664 | 945, 665 | 6939, 666 | 15353, 667 | 21738, 668 | 29632, 669 | 34465, 670 | 34549, 671 | 34927, 672 | 36369, 673 | 48605, 674 | 140731, 675 | 150249, 676 | 199762, 677 | 200160, 678 | 203686, 679 | 203913, 680 | 210152, 681 | 210475, 682 | 400304, 683 | 400818 684 | ] 685 | }, 686 | { 687 | "customer_asid": 210561, 688 | "expires": 1730540499, 689 | "providers": [ 690 | 207960 691 | ] 692 | }, 693 | { 694 | "customer_asid": 212068, 695 | "expires": 1730563201, 696 | "providers": [ 697 | 6939, 698 | 13237, 699 | 62240, 700 | 207960 701 | ] 702 | }, 703 | { 704 | "customer_asid": 212245, 705 | "expires": 1730553868, 706 | "providers": [ 707 | 917, 708 | 57695, 709 | 212068 710 | ] 711 | }, 712 | { 713 | "customer_asid": 212934, 714 | "expires": 1730562314, 715 | "providers": [ 716 | 835, 717 | 6939, 718 | 34927, 719 | 52210, 720 | 53667, 721 | 61138, 722 | 62513, 723 | 209533, 724 | 210667 725 | ] 726 | }, 727 | { 728 | "customer_asid": 213086, 729 | "expires": 1730552333, 730 | "providers": [ 731 | 945, 732 | 6939, 733 | 15353, 734 | 21738, 735 | 29632, 736 | 34465, 737 | 34549, 738 | 34927, 739 | 36369, 740 | 48605, 741 | 140731, 742 | 150249, 743 | 199762, 744 | 200160, 745 | 203686, 746 | 203913, 747 | 207656, 748 | 210152, 749 | 210475, 750 | 400304, 751 | 400818 752 | ] 753 | }, 754 | { 755 | "customer_asid": 215028, 756 | "expires": 1730561919, 757 | "providers": [ 758 | 216107 759 | ] 760 | }, 761 | { 762 | "customer_asid": 215131, 763 | "expires": 1730563201, 764 | "providers": [ 765 | 8283, 766 | 34927 767 | ] 768 | }, 769 | { 770 | "customer_asid": 215147, 771 | "expires": 1730532329, 772 | "providers": [ 773 | 47263, 774 | 207252, 775 | 215828 776 | ] 777 | }, 778 | { 779 | "customer_asid": 215436, 780 | "expires": 1730563201, 781 | "providers": [ 782 | 6762, 783 | 6939, 784 | 51396, 785 | 60223 786 | ] 787 | }, 788 | { 789 | "customer_asid": 215664, 790 | "expires": 1730537714, 791 | "providers": [ 792 | 1299, 793 | 3204, 794 | 6939, 795 | 34549, 796 | 41720, 797 | 56382, 798 | 203446, 799 | 212508 800 | ] 801 | }, 802 | { 803 | "customer_asid": 215778, 804 | "expires": 1730563201, 805 | "providers": [ 806 | 39249, 807 | 41051, 808 | 207656, 809 | 209533, 810 | 393577 811 | ] 812 | }, 813 | { 814 | "customer_asid": 215828, 815 | "expires": 1730546789, 816 | "providers": [ 817 | 6204, 818 | 6939, 819 | 8772, 820 | 29390, 821 | 29632, 822 | 34465, 823 | 34927, 824 | 39249, 825 | 41051, 826 | 44592, 827 | 48752, 828 | 50917, 829 | 51019, 830 | 58057, 831 | 62403, 832 | 152726, 833 | 197071, 834 | 199524, 835 | 203686, 836 | 207656, 837 | 209533, 838 | 210464, 839 | 216324, 840 | 216360, 841 | 393577 842 | ] 843 | }, 844 | { 845 | "customer_asid": 215849, 846 | "expires": 1730552922, 847 | "providers": [ 848 | 215828 849 | ] 850 | }, 851 | { 852 | "customer_asid": 216107, 853 | "expires": 1730561919, 854 | "providers": [ 855 | 924, 856 | 6939, 857 | 16509, 858 | 20473, 859 | 21738, 860 | 34927, 861 | 63473, 862 | 209022, 863 | 211588 864 | ] 865 | }, 866 | { 867 | "customer_asid": 216311, 868 | "expires": 1730546448, 869 | "providers": [ 870 | 6939, 871 | 13852, 872 | 20473, 873 | 34927, 874 | 52025 875 | ] 876 | }, 877 | { 878 | "customer_asid": 267386, 879 | "expires": 1730538516, 880 | "providers": [ 881 | 6939, 882 | 25933, 883 | 28220, 884 | 28649, 885 | 267613 886 | ] 887 | }, 888 | { 889 | "customer_asid": 270470, 890 | "expires": 1730571406, 891 | "providers": [ 892 | 53062 893 | ] 894 | }, 895 | { 896 | "customer_asid": 393577, 897 | "expires": 1730531956, 898 | "providers": [ 899 | 1299, 900 | 6939, 901 | 32097, 902 | 40676, 903 | 137409 904 | ] 905 | }, 906 | { 907 | "customer_asid": 401111, 908 | "expires": 1730531576, 909 | "providers": [ 910 | 945, 911 | 17676 912 | ] 913 | } 914 | ] 915 | } 916 | -------------------------------------------------------------------------------- /aspa/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pathlib import Path 3 | 4 | from aspa.data import ASPA, parse_json 5 | from aspa.validate import BirdValidator, Validator 6 | 7 | 8 | class ParserTest(unittest.TestCase): 9 | file = Path(__file__).parent / 'example.json' 10 | test_ta = True 11 | 12 | def test_parse(self): 13 | with open(self.file) as f: 14 | result = parse_json(f.read()) 15 | 16 | self.assertEqual(len(result), 75) 17 | 18 | for aspa in result: 19 | if aspa.customer == 54148: 20 | self.assertEqual(aspa.providers, [835, 924, 6939, 20473, 21738, 34927, 37988, 47272, 50917, 53667]) 21 | if self.test_ta: 22 | self.assertEqual(aspa.ta, 'arin') 23 | 24 | 25 | class RPKIClientTest(ParserTest): 26 | file = Path(__file__).parent / 'rpki-client.json' 27 | test_ta = False 28 | 29 | 30 | class CustomerTest(unittest.TestCase): 31 | validator_class = Validator 32 | 33 | def setUp(self): 34 | self.validator = self.validator_class([ 35 | ASPA(64500, [64501], 'test'), 36 | ASPA(64501, [64502], 'test'), 37 | ASPA(64502, [64503], 'test'), 38 | ASPA(64503, [64504], 'test'), 39 | ]) 40 | 41 | def test_all_valid(self): 42 | self.assertFalse(self.validator.is_aspa_invalid_customer(64501, [64500])) 43 | self.assertFalse(self.validator.is_aspa_invalid_customer(64502, [64501, 64500])) 44 | self.assertFalse(self.validator.is_aspa_invalid_customer(64503, [64502, 64501, 64500])) 45 | self.assertFalse(self.validator.is_aspa_invalid_customer(64504, [64503, 64502, 64501, 64500])) 46 | self.assertFalse(self.validator.is_aspa_invalid_customer(64504, [64503, 64502, 64501])) 47 | self.assertFalse(self.validator.is_aspa_invalid_customer(64504, [64503, 64502])) 48 | self.assertFalse(self.validator.is_aspa_invalid_customer(64504, [64503])) 49 | 50 | def test_some_unknown(self): 51 | self.assertFalse(self.validator.is_aspa_invalid_customer(64505, [64504, 64503, 64502, 64501])) 52 | self.assertFalse(self.validator.is_aspa_invalid_customer(64503, [64502, 64501, 64505])) 53 | 54 | def test_all_unknown(self): 55 | self.assertFalse(self.validator.is_aspa_invalid_customer(64506, [64507, 64508])) 56 | 57 | def test_some_invalid(self): 58 | self.assertTrue(self.validator.is_aspa_invalid_customer(64505, [64504, 64503, 64501, 64501])) 59 | self.assertTrue(self.validator.is_aspa_invalid_customer(64504, [64503, 64502, 64506, 64500])) 60 | 61 | 62 | class PeerTest(unittest.TestCase): 63 | validator_class = Validator 64 | 65 | def setUp(self): 66 | self.validator = self.validator_class([ 67 | ASPA(64500, [64501], 'test'), 68 | ASPA(64501, [64502], 'test'), 69 | ASPA(64502, [64503], 'test'), 70 | ASPA(64503, [64504], 'test'), 71 | ]) 72 | 73 | def test_all_valid(self): 74 | self.assertFalse(self.validator.is_aspa_invalid_peer([64501, 64500])) 75 | self.assertFalse(self.validator.is_aspa_invalid_peer([64502, 64501, 64500])) 76 | self.assertFalse(self.validator.is_aspa_invalid_peer([64503, 64502, 64501, 64500])) 77 | self.assertFalse(self.validator.is_aspa_invalid_peer([64504, 64503, 64502, 64501])) 78 | self.assertFalse(self.validator.is_aspa_invalid_peer([64504, 64503, 64502])) 79 | self.assertFalse(self.validator.is_aspa_invalid_peer([64504, 64503])) 80 | self.assertFalse(self.validator.is_aspa_invalid_peer([64504])) 81 | 82 | def test_prepend_valid(self): 83 | self.assertFalse(self.validator.is_aspa_invalid_peer([64501, 64500, 64500])) 84 | self.assertFalse(self.validator.is_aspa_invalid_peer([64502, 64502, 64501, 64501, 64500])) 85 | self.assertFalse(self.validator.is_aspa_invalid_peer([64503, 64502, 64502, 64501, 64500, 64500])) 86 | 87 | def test_some_unknown(self): 88 | self.assertFalse(self.validator.is_aspa_invalid_peer([64505, 64504, 64503, 64502])) 89 | self.assertFalse(self.validator.is_aspa_invalid_peer([64503, 64503, 64502, 64505])) 90 | 91 | def test_all_unknown(self): 92 | self.assertFalse(self.validator.is_aspa_invalid_peer([64506, 64507, 64508])) 93 | 94 | def test_some_invalid(self): 95 | self.assertTrue(self.validator.is_aspa_invalid_peer([64505, 64505, 64503, 64503])) 96 | self.assertTrue(self.validator.is_aspa_invalid_peer([64504, 64503, 64506, 64500])) 97 | 98 | def test_peer_originated(self): 99 | for i in [64500, 64501, 64502, 64503, 64504, 64505]: 100 | self.assertFalse(self.validator.is_aspa_invalid_peer([i])) 101 | 102 | 103 | class UpstreamTest(unittest.TestCase): 104 | validator_class = Validator 105 | 106 | def setUp(self): 107 | # 6451x are tier 1 ISPs 108 | # 6452x are tier 2 ISPs 109 | # 6453x are tier 3 ISPs 110 | self.validator = self.validator_class([ 111 | ASPA(64510, [], 'test'), 112 | ASPA(64511, [], 'test'), 113 | ASPA(64512, [], 'test'), 114 | ASPA(64514, [], 'test'), 115 | ASPA(64520, [64510, 64511], 'test'), 116 | ASPA(64521, [64512, 64513], 'test'), 117 | ASPA(64522, [64511, 64512], 'test'), 118 | ASPA(64530, [64520], 'test'), 119 | ASPA(64531, [64521, 64523], 'test'), 120 | ]) 121 | 122 | def test_all_valid(self): 123 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510, 64512, 64521, 64531])) 124 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64520, [64510, 64512, 64521, 64531])) 125 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64521, [64512, 64510, 64520, 64520, 64530])) 126 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64531, [64521, 64512, 64510, 64520, 64530])) 127 | 128 | def test_valid_edge(self): 129 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510])) 130 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520, 64521])) 131 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520])) 132 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64520, [64510])) 133 | 134 | def test_tier_1_tier_2_peer(self): 135 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520, 64512, 64521, 64531])) 136 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64521, [64510, 64510, 64520, 64520, 64530])) 137 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64531, [64521, 64510, 64520, 64530])) 138 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64531, [64521, 64510, 64510, 64520, 64520])) 139 | 140 | def test_some_unknown(self): 141 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510, 64513, 64521, 64531])) 142 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64520, [64510, 64513, 64521, 64531])) 143 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64521, [64510, 64510, 64520, 64520, 64530])) 144 | self.assertFalse(self.validator.is_aspa_invalid_upstream(64531, [64521, 64510, 64520, 64530])) 145 | 146 | def test_invalid_downramp(self): 147 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510, 64512, 64522, 64531])) 148 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510, 64512, 64531])) 149 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64521, 64512, 64510, 64530])) 150 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64521, 64512, 64510, 64522, 64530])) 151 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64512, 64522, 64531])) 152 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64521, [64510, 64510, 64520, 64522, 64530])) 153 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64521, [64510, 64510, 64522, 64520, 64530])) 154 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64510, 64522, 64531])) 155 | 156 | def test_invalid_upramp(self): 157 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64513, 64512, 64521, 64531])) 158 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64521, 64510, 64512, 64521, 64531])) 159 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64520, 64512, 64510, 64520, 64530])) 160 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64521, 64511, 64510, 64520, 64530])) 161 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64514, 64521, 64521, 64531])) 162 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64521, 64510, 64521, 64531, 64531])) 163 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64520, 64512, 64520, 64520, 64530])) 164 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64521, 64514, 64520, 64530, 64530])) 165 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64520, 64520, 64514, 64521, 64531])) 166 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64530, [64521, 64510, 64510, 64521, 64531])) 167 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64520, 64512, 64512, 64520, 64530])) 168 | self.assertTrue(self.validator.is_aspa_invalid_upstream(64531, [64521, 64521, 64514, 64520, 64530])) 169 | 170 | 171 | class BirdCustomerTest(CustomerTest): 172 | validator_class = BirdValidator 173 | 174 | 175 | class BirdPeerTest(PeerTest): 176 | validator_class = BirdValidator 177 | 178 | 179 | class BirdUpstreamTest(UpstreamTest): 180 | validator_class = BirdValidator 181 | 182 | 183 | if __name__ == '__main__': 184 | unittest.main() 185 | -------------------------------------------------------------------------------- /aspa/validate.py: -------------------------------------------------------------------------------- 1 | from itertools import chain, dropwhile 2 | 3 | from aspa.data import ASPA 4 | 5 | 6 | class Validator: 7 | def __init__(self, aspas: list[ASPA]): 8 | self.aspas = {aspa.customer: aspa for aspa in aspas} 9 | 10 | def is_invalid_pair(self, upstream: int, downstream: int) -> bool: 11 | if downstream not in self.aspas: 12 | return False 13 | 14 | return upstream not in self.aspas[downstream].providers 15 | 16 | def is_aspa_invalid_customer(self, my_asn: int, bgp_path: list[int]) -> bool: 17 | for prev_asn, asn in zip(chain([my_asn], bgp_path), bgp_path): 18 | # Ignore AS-path prepends 19 | if prev_asn == asn: 20 | continue 21 | 22 | if self.is_invalid_pair(prev_asn, asn): 23 | return True 24 | 25 | return False 26 | 27 | def is_aspa_invalid_peer(self, bgp_path: list[int]) -> bool: 28 | for prev_asn, asn in zip(bgp_path, bgp_path[1:]): 29 | if prev_asn == asn: 30 | continue 31 | 32 | if self.is_invalid_pair(prev_asn, asn): 33 | return True 34 | 35 | return False 36 | 37 | def is_aspa_invalid_upstream(self, my_asn: int, bgp_path: list[int]) -> bool: 38 | max_up_ramp = len(bgp_path) 39 | min_down_ramp = 0 40 | 41 | for i, (prev_asn, asn) in enumerate(zip(chain([my_asn], bgp_path), bgp_path)): 42 | if prev_asn == asn: 43 | continue 44 | 45 | if self.is_invalid_pair(asn, prev_asn): 46 | max_up_ramp = min(max_up_ramp, i) 47 | 48 | if self.is_invalid_pair(prev_asn, asn): 49 | min_down_ramp = max(min_down_ramp, i) 50 | 51 | return min_down_ramp > max_up_ramp 52 | 53 | 54 | class BirdValidator(Validator): 55 | """Validator coded with only language features available in bird DSL.""" 56 | 57 | def is_aspa_invalid_customer(self, my_asn: int, bgp_path: list[int]) -> bool: 58 | prev_asn = my_asn 59 | 60 | for asn in bgp_path: 61 | if prev_asn != asn and self.is_invalid_pair(prev_asn, asn): 62 | return True 63 | prev_asn = asn 64 | 65 | return False 66 | 67 | def is_aspa_invalid_peer(self, bgp_path: list[int]) -> bool: 68 | prev_asn = bgp_path[0] 69 | 70 | for asn in bgp_path: 71 | if prev_asn != asn and self.is_invalid_pair(prev_asn, asn): 72 | return True 73 | prev_asn = asn 74 | 75 | return False 76 | 77 | def is_aspa_invalid_upstream(self, my_asn: int, bgp_path: list[int]) -> bool: 78 | prev_asn = my_asn 79 | max_up_ramp = len(bgp_path) 80 | min_down_ramp = 0 81 | i = 0 82 | 83 | for asn in bgp_path: 84 | if prev_asn != asn: 85 | if self.is_invalid_pair(asn, prev_asn) and max_up_ramp > i: 86 | max_up_ramp = i 87 | 88 | if self.is_invalid_pair(prev_asn, asn): 89 | min_down_ramp = i 90 | 91 | prev_asn = asn 92 | i = i + 1 93 | 94 | return min_down_ramp > max_up_ramp 95 | -------------------------------------------------------------------------------- /filter_aspa.conf: -------------------------------------------------------------------------------- 1 | function is_aspa_invalid_customer() { 2 | int prev_asn = MY_ASN; 3 | 4 | for int cur_asn in bgp_path do { 5 | if prev_asn != cur_asn && is_aspa_invalid_pair(prev_asn, cur_asn) then 6 | return true; 7 | prev_asn = cur_asn; 8 | } 9 | 10 | return false; 11 | } 12 | 13 | function is_aspa_invalid_peer() { 14 | int prev_asn = bgp_path.first; 15 | 16 | for int cur_asn in bgp_path do { 17 | if prev_asn != cur_asn && is_aspa_invalid_pair(prev_asn, cur_asn) then 18 | return true; 19 | prev_asn = cur_asn; 20 | } 21 | 22 | return false; 23 | } 24 | 25 | function is_aspa_invalid_upstream() { 26 | int prev_asn = MY_ASN; 27 | int max_up_ramp = bgp_path.len; 28 | int min_down_ramp = 0; 29 | int i = 0; 30 | 31 | for int cur_asn in bgp_path do { 32 | if prev_asn != cur_asn then { 33 | if is_aspa_invalid_pair(cur_asn, cur_asn) && max_up_ramp > i then 34 | max_up_ramp = i; 35 | 36 | if is_aspa_invalid_pair(prev_asn, cur_asn) then 37 | min_down_ramp = i; 38 | } 39 | 40 | prev_asn = cur_asn; 41 | i = i + 1; 42 | } 43 | 44 | return min_down_ramp > max_up_ramp; 45 | } 46 | -------------------------------------------------------------------------------- /filter_bgp.conf: -------------------------------------------------------------------------------- 1 | roa4 table rpki4; 2 | roa6 table rpki6; 3 | attribute int export_downstream; 4 | 5 | protocol static default_v4 { 6 | ipv4 {}; 7 | route 0.0.0.0/0 reject; 8 | } 9 | 10 | protocol static default_v6 { 11 | ipv6 {}; 12 | route ::/0 reject; 13 | } 14 | 15 | # FIXME: Change this to your ASN. 16 | define MY_ASN = 64500; 17 | 18 | define LC_IXP_ID = 1; 19 | define LC_PEER_ASN = 2; 20 | define LC_INFO = 3; 21 | 22 | define LC_NO_EXPORT = 10; 23 | define LC_PREPEND_1 = 11; 24 | define LC_PREPEND_2 = 12; 25 | define LC_PREPEND_3 = 13; 26 | 27 | define LC_DOWNSTREAM_START = 10; 28 | define LC_DOWNSTREAM_END = 13; 29 | 30 | # FIXME: define your IXPs here: 31 | # define IXP_EXAMPLE1 = 100; 32 | # define IXP_EXAMPLE2 = 101; 33 | 34 | define INFO_PEER = 100; 35 | define INFO_IXP_RS = 101; 36 | define INFO_TRANSIT = 102; 37 | define INFO_DOWNSTREAM = 103; 38 | 39 | define IPV4_BOGON = [ 40 | 0.0.0.0/8+, # RFC 1122 'this' network 41 | 10.0.0.0/8+, # RFC 1918 private space 42 | 100.64.0.0/10+, # RFC 6598 Carrier grade nat space 43 | 127.0.0.0/8+, # RFC 1122 localhost 44 | 169.254.0.0/16+, # RFC 3927 link local 45 | 172.16.0.0/12+, # RFC 1918 private space 46 | 192.0.2.0/24+, # RFC 5737 TEST-NET-1 47 | 192.88.99.0/24+, # RFC 7526 6to4 anycast relay 48 | 192.168.0.0/16+, # RFC 1918 private space 49 | 198.18.0.0/15+, # RFC 2544 benchmarking 50 | 198.51.100.0/24+, # RFC 5737 TEST-NET-2 51 | 203.0.113.0/24+, # RFC 5737 TEST-NET-3 52 | 224.0.0.0/4+, # multicast 53 | 240.0.0.0/4+ # reserved 54 | ]; 55 | 56 | define IPV6_BOGON = [ 57 | ::/0, # Default 58 | ::/96, # IPv4-compatible IPv6 address - deprecated by RFC4291 59 | ::/128, # Unspecified address 60 | ::1/128, # Local host loopback address 61 | ::ffff:0.0.0.0/96+, # IPv4-mapped addresses 62 | ::224.0.0.0/100+, # Compatible address (IPv4 format) 63 | ::127.0.0.0/104+, # Compatible address (IPv4 format) 64 | ::0.0.0.0/104+, # Compatible address (IPv4 format) 65 | ::255.0.0.0/104+, # Compatible address (IPv4 format) 66 | 0000::/8+, # Pool used for unspecified, loopback and embedded IPv4 addresses 67 | 0100::/8+, # RFC 6666 - reserved for Discard-Only Address Block 68 | 0200::/7+, # OSI NSAP-mapped prefix set (RFC4548) - deprecated by RFC4048 69 | 0400::/6+, # RFC 4291 - Reserved by IETF 70 | 0800::/5+, # RFC 4291 - Reserved by IETF 71 | 1000::/4+, # RFC 4291 - Reserved by IETF 72 | 2001:10::/28+, # RFC 4843 - Deprecated (previously ORCHID) 73 | 2001:20::/28+, # RFC 7343 - ORCHIDv2 74 | 2001:db8::/32+, # Reserved by IANA for special purposes and documentation 75 | 2002:e000::/20+, # Invalid 6to4 packets (IPv4 multicast) 76 | 2002:7f00::/24+, # Invalid 6to4 packets (IPv4 loopback) 77 | 2002:0000::/24+, # Invalid 6to4 packets (IPv4 default) 78 | 2002:ff00::/24+, # Invalid 6to4 packets 79 | 2002:0a00::/24+, # Invalid 6to4 packets (IPv4 private 10.0.0.0/8 network) 80 | 2002:ac10::/28+, # Invalid 6to4 packets (IPv4 private 172.16.0.0/12 network) 81 | 2002:c0a8::/32+, # Invalid 6to4 packets (IPv4 private 192.168.0.0/16 network) 82 | 3ffe::/16+, # Former 6bone, now decommissioned 83 | 4000::/3+, # RFC 4291 - Reserved by IETF 84 | 5f00::/8+, # RFC 5156 - used for the 6bone but was returned 85 | 6000::/3+, # RFC 4291 - Reserved by IETF 86 | 8000::/3+, # RFC 4291 - Reserved by IETF 87 | a000::/3+, # RFC 4291 - Reserved by IETF 88 | c000::/3+, # RFC 4291 - Reserved by IETF 89 | e000::/4+, # RFC 4291 - Reserved by IETF 90 | f000::/5+, # RFC 4291 - Reserved by IETF 91 | f800::/6+, # RFC 4291 - Reserved by IETF 92 | fc00::/7+, # Unicast Unique Local Addresses (ULA) - RFC 4193 93 | fe80::/10+, # Link-local Unicast 94 | fec0::/10+, # Site-local Unicast - deprecated by RFC 3879 (replaced by ULA) 95 | ff00::/8+ # Multicast 96 | ]; 97 | 98 | define ASN_BOGON = [ 99 | 0, # RFC 7607 100 | 23456, # RFC 4893 AS_TRANS 101 | 64496..64511, # RFC 5398 and documentation/example ASNs 102 | 64512..65534, # RFC 6996 Private ASNs 103 | 65535, # RFC 7300 Last 16 bit ASN 104 | 65536..65551, # RFC 5398 and documentation/example ASNs 105 | 65552..131071, # RFC IANA reserved ASNs 106 | 4200000000..4294967294, # RFC 6996 Private ASNs 107 | 4294967295 # RFC 7300 Last 32 bit ASN 108 | ]; 109 | 110 | function ip_bogon() { 111 | case net.type { 112 | NET_IP4: return net ~ IPV4_BOGON; 113 | NET_IP6: return net ~ IPV6_BOGON; 114 | else: return true; 115 | } 116 | } 117 | 118 | function rpki_invalid() { 119 | case net.type { 120 | NET_IP4: return roa_check(rpki4, net, bgp_path.last) = ROA_INVALID; 121 | NET_IP6: return roa_check(rpki6, net, bgp_path.last) = ROA_INVALID; 122 | else: return false; 123 | } 124 | } 125 | 126 | function is_default_route() { 127 | case net.type { 128 | NET_IP4: return net = 0.0.0.0/0; 129 | NET_IP6: return net = ::/0; 130 | else: return false; 131 | } 132 | } 133 | 134 | function bad_prefix_len() { 135 | case net.type { 136 | NET_IP4: return net.len > 24; 137 | NET_IP6: return net.len > 48; 138 | else: return false; 139 | } 140 | } 141 | 142 | function clean_own_communities() { 143 | bgp_large_community.delete([(MY_ASN, *, *)]); 144 | } 145 | 146 | function honour_graceful_shutdown() { 147 | # RFC 8326: Graceful BGP Session Shutdown 148 | if (65535, 0) ~ bgp_community then bgp_local_pref = 0; 149 | } 150 | 151 | function handle_prepend(int dest_asn) { 152 | if (MY_ASN, LC_PREPEND_1, dest_asn) ~ bgp_large_community then { 153 | bgp_path.prepend(MY_ASN); 154 | } 155 | 156 | if (MY_ASN, LC_PREPEND_2, dest_asn) ~ bgp_large_community then { 157 | bgp_path.prepend(MY_ASN); 158 | bgp_path.prepend(MY_ASN); 159 | } 160 | 161 | if (MY_ASN, LC_PREPEND_3, dest_asn) ~ bgp_large_community then { 162 | bgp_path.prepend(MY_ASN); 163 | bgp_path.prepend(MY_ASN); 164 | bgp_path.prepend(MY_ASN); 165 | } 166 | } 167 | 168 | function import_safe(bool allow_default) { 169 | if is_default_route() then { 170 | if allow_default then return true; 171 | print proto, ": ", net, ": unexpected default route"; 172 | return false; 173 | } 174 | 175 | if ip_bogon() then { 176 | print proto, ": ", net, ": bogon prefix"; 177 | return false; 178 | } 179 | 180 | if bgp_path ~ ASN_BOGON then { 181 | print proto, ": ", net, ": bogon in AS path: ", bgp_path; 182 | return false; 183 | } 184 | 185 | if bgp_path.len > 50 then { 186 | print proto, ": ", net, ": AS path too long: ", bgp_path; 187 | return false; 188 | } 189 | 190 | if bad_prefix_len() then { 191 | print proto, ": ", net, ": invalid prefix length"; 192 | return false; 193 | } 194 | 195 | if rpki_invalid() then { 196 | print proto, ": ", net, ": invalid RPKI origin: ", bgp_path.last; 197 | return false; 198 | } 199 | 200 | export_downstream = 1; 201 | honour_graceful_shutdown(); 202 | 203 | return true; 204 | } 205 | 206 | function import_peer_trusted(int peer_asn) { 207 | clean_own_communities(); 208 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_PEER)); 209 | bgp_large_community.add((MY_ASN, LC_PEER_ASN, peer_asn)); 210 | 211 | return import_safe(false); 212 | } 213 | 214 | function import_peer(int peer_asn; prefix set prefixes; int set as_set) { 215 | if net !~ prefixes then { 216 | print proto, ": ", net, ": prefix not in as-set for peer AS", peer_asn; 217 | return false; 218 | } 219 | 220 | for int path_asn in bgp_path do { 221 | if path_asn !~ as_set then { 222 | print proto, ": ", net, ": AS", path_asn, " not in as-set for peer AS", peer_asn; 223 | return false; 224 | } 225 | } 226 | 227 | return import_peer_trusted(peer_asn); 228 | } 229 | 230 | function import_ixp_trusted(int ixp_id) { 231 | clean_own_communities(); 232 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_IXP_RS)); 233 | bgp_large_community.add((MY_ASN, LC_IXP_ID, ixp_id)); 234 | 235 | return import_safe(false); 236 | } 237 | 238 | function import_ixp(int ixp_id; prefix set prefixes; int set as_set) { 239 | if net !~ prefixes then { 240 | print proto, ": ", net, ": prefix not in as-set for IXP"; 241 | return false; 242 | } 243 | 244 | for int path_asn in bgp_path do { 245 | if path_asn !~ as_set then { 246 | print proto, ": ", net, ": not in as-set for IXP"; 247 | return false; 248 | } 249 | } 250 | 251 | return import_ixp_trusted(ixp_id); 252 | } 253 | 254 | function import_transit(int transit_asn; bool default_route) { 255 | clean_own_communities(); 256 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_TRANSIT)); 257 | bgp_large_community.add((MY_ASN, LC_PEER_ASN, transit_asn)); 258 | 259 | return import_safe(default_route); 260 | } 261 | 262 | function import_downstream(int downstream_asn; prefix set prefixes; int set as_set) { 263 | if net !~ prefixes then { 264 | print proto, ": ", net, ": prefix not in as-set for downstream AS", downstream_asn; 265 | return false; 266 | } 267 | 268 | for int path_asn in bgp_path do { 269 | if path_asn !~ as_set then { 270 | print proto, ": ", net, ": not in as-set for downstream AS", downstream_asn; 271 | return false; 272 | } 273 | } 274 | 275 | # If they don't want to export this to us, then we won't take it at all. 276 | if (MY_ASN, LC_NO_EXPORT, MY_ASN) ~ bgp_large_community then { 277 | print proto, ": ", net, ": rejected by no-export to AS", MY_ASN; 278 | return false; 279 | } 280 | 281 | bgp_large_community.delete([ 282 | (MY_ASN, 0..LC_DOWNSTREAM_START-1, *), 283 | (MY_ASN, LC_DOWNSTREAM_END+1..0xFFFFFFFF, *) 284 | ]); 285 | 286 | bgp_large_community.add((MY_ASN, LC_INFO, INFO_DOWNSTREAM)); 287 | bgp_large_community.add((MY_ASN, LC_PEER_ASN, downstream_asn)); 288 | 289 | return import_safe(false); 290 | } 291 | 292 | function export_to_downstream() { 293 | return (defined(export_downstream) && export_downstream = 1) || 294 | (source = RTS_STATIC && !bad_prefix_len() && ( 295 | proto = "node_v4" || 296 | proto = "node_v6" || 297 | proto = "node_v4_anycast" || 298 | proto = "node_v6_anycast" 299 | )); 300 | } 301 | 302 | function export_monitoring() { 303 | return export_to_downstream(); 304 | } 305 | 306 | function export_cone(int dest_asn) { 307 | if (MY_ASN, LC_NO_EXPORT, dest_asn) ~ bgp_large_community then return false; 308 | handle_prepend(dest_asn); 309 | 310 | if (MY_ASN, LC_INFO, INFO_DOWNSTREAM) ~ bgp_large_community then return true; 311 | 312 | case net.type { 313 | NET_IP4: return source = RTS_STATIC && proto = "node_v4"; 314 | NET_IP6: return source = RTS_STATIC && proto = "node_v6"; 315 | else: return false; 316 | } 317 | } 318 | 319 | function export_anycast() { 320 | case net.type { 321 | NET_IP4: return source = RTS_STATIC && proto = "node_v4_anycast"; 322 | NET_IP6: return source = RTS_STATIC && proto = "node_v6_anycast"; 323 | else: return false; 324 | } 325 | } 326 | 327 | function export_default() { 328 | case net.type { 329 | NET_IP4: return source = RTS_STATIC && proto = "default_v4"; 330 | NET_IP6: return source = RTS_STATIC && proto = "default_v6"; 331 | else: return false; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /irr-filters.example: -------------------------------------------------------------------------------- 1 | # You can use # for comments. 2 | EXAMPLE AS-EXAMPLE 3 | EXAMPLE2 RIPE::AS64500:AS-EXAMPLE2 v4 4 | EXAMPLE3 ARIN::AS-EXAMPLE3 v6 5 | # This will cause make-irr-filter to generate a configuration file containing: 6 | # * IRR_EXAMPLE_V4: allowed IPv4 prefixes queried from AS-EXAMPLE. 7 | # * IRR_EXAMPLE_V6: allowed IPv6 prefixes queried from AS-EXAMPLE. 8 | # * IRR_EXAMPLE_ASN: allowed ASNs queried from AS-EXAMPLE. 9 | # * IRR_EXAMPLE2_V4: allowed IPv4 prefixes queried from AS64500:AS-EXAMPLE2 defined in the RIPE database. 10 | # * IRR_EXAMPLE2_ASN: allowed ASNs queried from RIPE::AS64500:AS-EXAMPLE2. 11 | # * IRR_EXAMPLE3_V6: allowed IPv6 prefixes queried from AS-EXAMPLE3 defined in the ARIN database. 12 | # * IRR_EXAMPLE3_ASN: allowed ASNs queried from ARIN::AS-EXAMPLE3. 13 | -------------------------------------------------------------------------------- /make-bird-aspa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | from aspa.data import parse_json 6 | from aspa.generate import generate_bird 7 | 8 | 9 | def main(): 10 | parser = argparse.ArgumentParser(description='Generates is_aspa_invalid_pair function for bird') 11 | parser.add_argument('aspa_json', type=str, help='the JSON file with ASPA data') 12 | args = parser.parse_args() 13 | 14 | with open(args.aspa_json) as f: 15 | aspas = parse_json(f.read()) 16 | 17 | print(generate_bird(aspas)) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /make-irr-filter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | FILTER_SOURCE=/etc/bird/irr-filters 5 | FILTER_OUTPUT=/etc/bird/filter_irr.conf 6 | 7 | tmpfile="$(mktemp /tmp/bird-filter.XXXXXX)" 8 | cleanup() { 9 | rm -f "$tmpfile" 10 | } 11 | trap cleanup EXIT 12 | 13 | grep -v '^#' "$FILTER_SOURCE" | while IFS=$'\t ' read -r -a line; do 14 | name="${line[0]:-}" 15 | asset="${line[1]:-}" 16 | protocol="${line[2]:-}" 17 | if [ -z "$name" ] || [ -z "$asset" ]; then 18 | echo "Malformed line found" 1>&2 19 | continue 20 | fi 21 | 22 | [ "$protocol" != v6 ] && bgpq4 -4Ab -m24 "$asset" -l "define IRR_${name}_V4" 23 | [ "$protocol" != v4 ] && bgpq4 -6Ab -m48 "$asset" -l "define IRR_${name}_V6" 24 | bgpq4 -tb "$asset" -l "define IRR_${name}_ASN" 25 | done > "$tmpfile" 26 | 27 | mv "$tmpfile" "$FILTER_OUTPUT" 28 | chmod a+r "$FILTER_OUTPUT" 29 | -------------------------------------------------------------------------------- /make-prefix-limits: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Change this to a PeeringDB mirror 5 | PEERINGDB=https://www.peeringdb.com 6 | 7 | PEER_SOURCE=/etc/bird/prefix-limits 8 | LIMIT_OUTPUT=/etc/bird/prefix_limits.conf 9 | 10 | [ -f "$PEER_SOURCE" ] || exit 11 | 12 | tmpdir="$(mktemp -d /tmp/bird-prefix-limit.XXXXXX)" 13 | cleanup() { 14 | rm -rf "$tmpdir" 15 | } 16 | trap cleanup EXIT 17 | 18 | join_by() { 19 | local d=${1-} f=${2-} 20 | if shift 2; then 21 | printf %s "$f" "${@/#/$d}" 22 | fi 23 | } 24 | 25 | readarray -t asns < <(grep -vE '^#|^$' "$PEER_SOURCE") 26 | 27 | curl -s "$PEERINGDB/api/net?asn__in=$(join_by , "${asns[@]}")" | \ 28 | jq -r '(.data // [])[] | "define LIMIT_AS\(.asn)_V4 = \(.info_prefixes4);\ndefine LIMIT_AS\(.asn)_V6 = \(.info_prefixes6);"' \ 29 | > "$tmpdir/limits.conf" 30 | 31 | mv "$tmpdir/limits.conf" "$LIMIT_OUTPUT" 32 | chmod a+r "$LIMIT_OUTPUT" 33 | -------------------------------------------------------------------------------- /prefix-limits.example: -------------------------------------------------------------------------------- 1 | # You can use # for comments. 2 | # Cloudflare 3 | 13335 4 | # Quantum 5 | 200351 6 | -------------------------------------------------------------------------------- /skeleton-aspa.conf: -------------------------------------------------------------------------------- 1 | log syslog all; 2 | 3 | # FIXME: Change this to one of your router's IPv4 addresses. 4 | # If you have none, pick something random from 240.0.0.0/4. 5 | router id 192.0.2.1; 6 | 7 | protocol kernel { 8 | scan time 60; 9 | ipv4 { 10 | export where source != RTS_STATIC; 11 | # NOTE: this basic export above doesn't make the routes inserted into 12 | # the kernel prefer your own IPs. Things will work fine with your 13 | # server's IP assigned by the provider if you have a single upstream 14 | # but strange things will happen if you have more than one peer. 15 | # Instead, to use your own IP as the default source IP for outgoing 16 | # connections on your system, add an IP from your range to the `lo` 17 | # interface, remove the line above, and use the block below, changing 18 | # 192.0.2.1 to the IP used. 19 | # 20 | # export filter { 21 | # if source = RTS_STATIC then reject; 22 | # if source = RTS_BGP then krt_prefsrc = 192.0.2.1; 23 | # accept; 24 | # }; 25 | }; 26 | } 27 | 28 | protocol kernel { 29 | scan time 60; 30 | ipv6 { 31 | export where source != RTS_STATIC; 32 | # NOTE: similar to above, use the following block to change the default 33 | # source IP for outgoing connections. 34 | # export filter { 35 | # if source = RTS_STATIC then reject; 36 | # if source = RTS_BGP then krt_prefsrc = 2001:db8::1; 37 | # accept; 38 | # }; 39 | }; 40 | } 41 | 42 | protocol device { 43 | scan time 60; 44 | } 45 | 46 | define MY_ASN = 64500; 47 | include "aspa_invalids.conf"; 48 | include "filter_aspa.conf"; 49 | -------------------------------------------------------------------------------- /skeleton.conf: -------------------------------------------------------------------------------- 1 | log syslog all; 2 | 3 | # FIXME: Change this to one of your router's IPv4 addresses. 4 | # If you have none, pick something random from 240.0.0.0/4. 5 | router id 192.0.2.1; 6 | 7 | protocol kernel { 8 | scan time 60; 9 | ipv4 { 10 | export where source != RTS_STATIC; 11 | # NOTE: this basic export above doesn't make the routes inserted into 12 | # the kernel prefer your own IPs. Things will work fine with your 13 | # server's IP assigned by the provider if you have a single upstream 14 | # but strange things will happen if you have more than one peer. 15 | # Instead, to use your own IP as the default source IP for outgoing 16 | # connections on your system, add an IP from your range to the `lo` 17 | # interface, remove the line above, and use the block below, changing 18 | # 192.0.2.1 to the IP used. 19 | # 20 | # export filter { 21 | # if source = RTS_STATIC then reject; 22 | # if source = RTS_BGP then krt_prefsrc = 192.0.2.1; 23 | # accept; 24 | # }; 25 | }; 26 | } 27 | 28 | protocol kernel { 29 | scan time 60; 30 | ipv6 { 31 | export where source != RTS_STATIC; 32 | # NOTE: similar to above, use the following block to change the default 33 | # source IP for outgoing connections. 34 | # export filter { 35 | # if source = RTS_STATIC then reject; 36 | # if source = RTS_BGP then krt_prefsrc = 2001:db8::1; 37 | # accept; 38 | # }; 39 | }; 40 | } 41 | 42 | protocol device { 43 | scan time 60; 44 | } 45 | 46 | include "filter_bgp.conf"; 47 | 48 | protocol static node_v4 { 49 | ipv4 {}; 50 | } 51 | 52 | protocol static node_v6 { 53 | ipv6 {}; 54 | } 55 | 56 | protocol static node_v4_anycast { 57 | ipv4 {}; 58 | } 59 | 60 | protocol static node_v6_anycast { 61 | ipv6 {}; 62 | } 63 | --------------------------------------------------------------------------------