Concerning the structure of ASPA tables and AS0

Ralph Covelli rcovelli at he.net
Thu Dec 26 05:59:59 CET 2024


Hello Bird Team!

My name is Ralph.  I'm a network engineer and C programmer for Hurricane 
Electric.  I am a long time fan of the Bird project! Keep up the great 
work!!

Earlier this year I was tasked with implementing reactive ASPA in our 
network.  My code was based off your older implementation of static ASPA 
tables here:

https://gitlab.nic.cz/labs/bird/-/tree/aspa

This older implementation is based off of customer-provider pairs:

typedef struct net_addr_aspa {
   u8 type;
   u8 padding;
   u16 length;
   u32 customer_asn;
   u32 provider_asn;
} net_addr_aspa;

I've attached the patch as "bird-2.15.1-aspa-asn-pairs.patch".

I took a look at 2.16 and ran into 2 problems.  Respectfully, I would 
like to report two issues with the ASPA code in 2.16.

Issue #1)  There is no way to tell the difference between a transit 
entry and an "AS0" entry.

$ cat bird-aspa.conf
aspa table at;

protocol static
{
         aspa;
         route aspa 12345 transit;
         route aspa 970 provider 43, 56;
         route aspa 43970 provider 0;
}

---

$ ./sbin/birdc
BIRD 2.16 ready.
bird> show route table at all
Table at:
43970                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
970                   [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 43 56
12345                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
bird>

---

The treatment of AS0 providers is mentioned in section 5 of 
draft-ietf-sidrops-aspa-verification-19.  It is a mechanism for people 
to announce that "no one should announce this AS".  I've attached a 
snapshot of the global ASPA table as "bird-aspa-v2.16.conf".  There is 
one AS0 announcement as of today.

Issue #2)  Changes in static ASPA tables are not reflected until entries 
are removed and re-added.

$ cat bird-aspa.conf
aspa table at;

protocol static
{
         aspa;
         route aspa 12345 transit;
         route aspa 970 provider 43, 56;
         route aspa 43970 provider 0;
}

bird> show route table at all
Table at:
43970                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
970                   [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 43 56
12345                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
bird>

$ cat bird-aspa.conf
aspa table at;

protocol static
{
         aspa;
         route aspa 12345 transit;
         route aspa 970 provider 43, 56, 78;         <---- added AS78
         route aspa 43970 provider 0;
}

$ ./sbin/birdc
BIRD 2.16 ready.
bird> configure
Reading configuration from /home/rpki/bird/etc/bird.conf
Reconfigured
bird> show route table at all
Table at:
43970                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
970                   [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 43 56          <-------- changes not reflected ***
12345                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
bird>

$ cat bird-aspa.conf
aspa table at;

protocol static
{
         aspa;
         route aspa 12345 transit;
         #route aspa 970 provider 43, 56, 78;    <----- remove entries 
altogether
         route aspa 43970 provider 0;
}

$ ./sbin/birdc
BIRD 2.16 ready.
bird> configure
Reading configuration from /home/rpki/bird/etc/bird.conf
Reconfigured
bird> show route table at all
Table at:
43970                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
12345                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
bird>

$ cat bird-aspa.conf
aspa table at;

protocol static
{
         aspa;
         route aspa 12345 transit;
         route aspa 970 provider 43, 56, 78;     <------- add entry again
         route aspa 43970 provider 0;
}

$ ./sbin/birdc
BIRD 2.16 ready.
bird> configure
Reading configuration from /home/rpki/bird/etc/bird.conf
Reconfigured
bird> show route table at all
Table at:
43970                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
970                   [static1 20:17:30.142] * (200)
         Type: static univ
         aspa_providers: 43 56 78             <--------- changes 
reflected correctly
12345                 [static1 19:38:32.125] * (200)
         Type: static univ
         aspa_providers: 0
bird>

---

This problem does not occur when the ASPA elements are customer-provider 
pairs.  I believe this is an overall design issue, not a simple bug.  I 
will be bringing this issue up with ietf-sidrops.

Thanks!

--

Ralph Covelli
Hurricane Electric / AS6939
Network Engineer
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://trubka.network.cz/pipermail/bird-users/attachments/20241225/c58621a1/attachment.htm>
-------------- next part --------------
diff -ruN bird-2.15.1/README.aspa-asn-pairs bird-2.15.1-aspa-asn-pairs/README.aspa-asn-pairs
--- bird-2.15.1/README.aspa-asn-pairs	1969-12-31 16:00:00.000000000 -0800
+++ bird-2.15.1-aspa-asn-pairs/README.aspa-asn-pairs	2024-12-17 21:14:23.000000000 -0800
@@ -0,0 +1,239 @@
+
+This patch implements ASPA using tables of customer/provider ASN pairs.
+
+It is based off bird's original static ASPA implementation that can be found here:
+
+https://gitlab.nic.cz/labs/bird/-/tree/aspa
+
+The original ASPA code was written by Eugene Bogomazov.
+
+This patch adds support for customer_asn and provider_asn selection, RTR v2 support and updates draft-ietf-sidrops-aspa-verification. 
+
+https://www.ietf.org/id/draft-ietf-sidrops-8210bis-16.html
+https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/
+
+Here is an example static ASPA table:
+
+$ cat bird-aspa.conf
+aspa table at;
+
+protocol static
+{
+        aspa;
+        route aspa 970 54874;
+        route aspa 11358 835;
+        route aspa 11358 924;
+        route aspa 11358 6939;
+        route aspa 11358 20473;
+        route aspa 11358 34927;
+        route aspa 13852 6939;
+        route aspa 13852 20473;
+        route aspa 13852 34927;
+        route aspa 13852 52025;
+        route aspa 13852 209533;
+        route aspa 15562 2914;
+        route aspa 15562 8283;
+        route aspa 15562 51088;
+        route aspa 15562 206238;
+        route aspa 19330 393577;
+        route aspa 21957 970;
+...
+        route aspa 401111 1012;
+        route aspa 401111 2497;
+        route aspa 401111 17676;
+}
+
+---
+
+The first AS number is the customer_asn.  The second AS number is the provider_asn.
+
+Here is an example script that uses json dumps from rpki-client to generate a config file that can be included in your bird.conf:
+
+$ cat rpki-client-aspa-to-bird.pl
+#!/usr/bin/perl
+
+use JSON;
+
+my $argc = $#ARGV+1;
+
+if ($argc < 2) {
+  die("not enough args");
+}
+
+my $filename_in = @ARGV[0];
+my $filename_out = @ARGV[1];
+
+my $json_text = do {
+    open(my $json_fh, "<:encoding(UTF-8)", $filename_in)
+        or die("Can't open \"$filename_in\": $!\n");
+    local $/;
+    <$json_fh>
+};
+
+open(my $bird_fh, ">:encoding(UTF-8)", $filename_out)
+    or die("Can't open \"$filename_out\": $!\n");
+
+my $json = JSON->new;
+my $data = $json->decode($json_text);
+
+my $customer;
+
+print $bird_fh "aspa table at;\n";
+print $bird_fh "\n";
+print $bird_fh "protocol static\n";
+print $bird_fh "{\n";
+print $bird_fh "\taspa;\n";
+for ( @{$data->{aspas}} ) {
+    $customer = $_->{customer_asid};
+    for ( @{$_->{providers}} ) {
+        print $bird_fh "\troute aspa $customer $_;\n";
+    }
+}
+print $bird_fh "}\n";
+
+close($json_fh);
+close($bird_fh);
+
+---
+
+Here is a sample crontab script:
+
+$ cat update-bird-conf.sh
+#!/bin/bash
+
+LOCKFILE="/home/rpki/bird/etc/.update-bird-conf.sh.lock"
+
+touch $LOCKFILE
+
+exec {FDLOCK}<>$LOCKFILE
+
+if ! flock -n -x $FDLOCK; then
+  exit 1
+fi
+
+trap "rm -f $LOCKFILE" EXIT
+
+RPKI_CLIENT_BIN="/home/rpki/rpki-client/sbin/rpki-client"
+RPKI_CLIENT_JSON_DUMP="/home/rpki/rpki-client/var/db/rpki-client/json"
+BIRD_ASPA_TABLE_NEW="/home/rpki/bird/etc/bird-aspa.conf.new"
+BIRD_ASPA_TABLE="/home/rpki/bird/etc/bird-aspa.conf"
+BIRD_CONV_SCRIPT="/home/rpki/rpki-client-aspa-to-bird.pl"
+BIRDC_BIN="/home/rpki/bird/sbin/birdc"
+MIN_ASPA_COUNT=250
+
+TZ=Etc/UTC $RPKI_CLIENT_BIN -j
+
+$BIRD_CONV_SCRIPT $RPKI_CLIENT_JSON_DUMP $BIRD_ASPA_TABLE_NEW
+
+ASPA_COUNT=`grep route $BIRD_ASPA_TABLE_NEW | wc -l`
+
+if [ $ASPA_COUNT -le $MIN_ASPA_COUNT ]; then
+  rm -f $BIRD_ASPA_TABLE_NEW
+  exit 1
+fi
+
+mv $BIRD_ASPA_TABLE_NEW $BIRD_ASPA_TABLE
+
+$BIRDC_BIN 'configure'
+$BIRDC_BIN 'reload "bgp4"'
+$BIRDC_BIN 'reload "bgp6"'
+
+exit 0
+
+---
+
+Here is a function from bird.conf that can be called from a filter:
+
+include "bird-aspa.conf";
+
+# AS12345 as transit
+
+function is_aspa_invalid (bgppath path) -> bool {
+  if (path.len > 0) then {
+    if (path.first ~ [ 12345 ]) then {
+      return aspa_check(at, path, BGP_DIR_DOWN) = ASPA_INVALID;
+    } else {
+      return aspa_check(at, path, BGP_DIR_UP) = ASPA_INVALID;
+    }
+  } else {
+    return false;
+  }
+}
+
+---
+
+This is what the table looks like:
+
+bird> show route table at
+Table at:
+aspa AS50555 AS970    [static1 2024-11-27] * (200)
+aspa AS270470 AS53062  [static1 2024-11-27] * (200)
+aspa AS47272 AS212895  [static1 2024-11-27] * (200)
+aspa AS47272 AS212514  [static1 2024-11-27] * (200)
+aspa AS47272 AS210667  [static1 2024-11-27] * (200)
+aspa AS47272 AS58057  [static1 2024-11-27] * (200)
+aspa AS47272 AS52210  [static1 2024-11-27] * (200)
+aspa AS47272 AS52025  [static1 2024-11-27] * (200)
+aspa AS47272 AS50917  [static1 2024-11-27] * (200)
+...
+aspa AS203843 AS53667  [static1 2024-11-27] * (200)
+aspa AS203843 AS20473  [static1 2024-11-27] * (200)
+aspa AS203843 AS6939  [static1 2024-11-27] * (200)
+aspa AS216265 AS215051  [static1 2024-12-11] * (200)
+aspa AS216265 AS202673  [static1 2024-12-11] * (200)
+bird>
+
+---
+
+Selecting multiple customer_asns:
+
+bird> show route table at where net.customer_asn ~ [ 11358, 28584, 54218 ]
+Table at:
+aspa AS54218 AS57196  [static1 2024-12-04] * (200)
+aspa AS54218 AS35487  [static1 2024-12-02] * (200)
+aspa AS54218 AS59678  [static1 2024-11-27] * (200)
+aspa AS54218 AS53667  [static1 2024-11-27] * (200)
+aspa AS54218 AS37988  [static1 2024-11-27] * (200)
+aspa AS54218 AS16509  [static1 2024-11-27] * (200)
+aspa AS54218 AS917    [static1 2024-11-27] * (200)
+aspa AS11358 AS34927  [static1 2024-11-27] * (200)
+aspa AS11358 AS20473  [static1 2024-11-27] * (200)
+aspa AS11358 AS6939   [static1 2024-11-27] * (200)
+aspa AS11358 AS924    [static1 2024-11-27] * (200)
+aspa AS11358 AS835    [static1 2024-11-27] * (200)
+aspa AS28584 AS28605  [static1 2024-11-27] * (200)
+bird>
+
+---
+
+Selecting a provider_asn:
+
+bird> show route table at where net.provider_asn = 16509
+Table at:
+aspa AS216107 AS16509  [static1 2024-11-27] * (200)
+aspa AS54218 AS16509  [static1 2024-11-27] * (200)
+bird>
+
+---
+
+Configuring RPKI for ASPA:
+
+protocol rpki rpki1 {
+       roa4 { table r4; };
+       roa6 { table r6; };
+       aspa { table at; };
+
+       remote 127.0.0.1;
+       port 3323;
+
+       retry keep 90;
+       refresh keep 900;
+       expire keep 172800;
+       transport tcp;
+}
+
+
+---
+Ralph Covelli
+Hurricane Electric / AS6939
+rcovelli at he.net
diff -ruN bird-2.15.1/autogen.sh bird-2.15.1-aspa-asn-pairs/autogen.sh
--- bird-2.15.1/autogen.sh	1969-12-31 16:00:00.000000000 -0800
+++ bird-2.15.1-aspa-asn-pairs/autogen.sh	2024-09-17 18:03:26.000000000 -0700
@@ -0,0 +1,88 @@
+#! /bin/sh
+
+TOP_DIR=$(dirname $0)
+
+if test ! -f $TOP_DIR/configure.ac ; then
+   echo "You must execute this script from the top level directory."
+   exit 1
+fi
+
+AUTOCONF=${AUTOCONF:-autoconf}
+ACLOCAL=${ACLOCAL:-aclocal}
+#AUTOMAKE=${AUTOMAKE:-automake}
+AUTOHEADER=${AUTOHEADER:-autoheader}
+LIBTOOLIZE=${LIBTOOLIZE:-libtoolize}
+#SHTOOLIZE=${SHTOOLIZE:-shtoolize}
+
+dump_help_screen ()
+{
+   echo "Usage: $0 [options]"
+   echo
+   echo "options:"
+   echo "  -n           skip CVS changelog creation"
+   echo "  -h,--help    show this help screen"
+   echo
+   exit 0
+}
+
+parse_options ()
+{
+   while test "$1" != "" ; do
+      case $1 in
+         -h|--help)
+            dump_help_screen
+            ;;
+         -n)
+            SKIP_CVS_CHANGELOG=yes
+            ;;
+         *)
+            echo Invalid argument - $1
+            dump_help_screen
+            ;;
+      esac
+      shift
+   done
+}
+
+run_or_die ()
+{
+   COMMAND=$1
+
+   # check for empty commands
+   if test -z "$COMMAND" ; then
+      echo "*warning* no command specified"
+      return 1
+   fi
+
+   shift;
+
+   OPTIONS="$@"
+
+   # print a message
+   echo -n "*info* running $COMMAND"
+   if test -n "$OPTIONS" ; then
+      echo " ($OPTIONS)"
+   else
+      echo
+   fi
+
+   # run or die
+   $COMMAND $OPTIONS ; RESULT=$?
+   if test $RESULT -ne 0 ; then
+      echo "*error* $COMMAND failed. (exit code = $RESULT)"
+      exit 1
+   fi
+
+   return 0
+}
+
+parse_options "$@"
+
+echo "Building main autotools files."
+
+run_or_die $ACLOCAL -I m4
+run_or_die $LIBTOOLIZE --force --copy
+run_or_die $AUTOHEADER
+run_or_die $AUTOCONF
+#run_or_die $AUTOMAKE --add-missing --copy
+#run_or_die $SHTOOLIZE all
diff -ruN bird-2.15.1/bird-gdb.py bird-2.15.1-aspa-asn-pairs/bird-gdb.py
--- bird-2.15.1/bird-gdb.py	2023-10-07 04:59:53.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/bird-gdb.py	2024-09-17 18:23:56.000000000 -0700
@@ -31,6 +31,8 @@
             "T_ENUM_NETTYPE": "i",
             "T_ENUM_RA_PREFERENCE": "i",
             "T_ENUM_AF": "i",
+            "T_ENUM_ASPA": "i",
+            "T_ENUM_BGP_DIR": "i",
             "T_IP": "ip",
             "T_NET": "net",
             "T_STRING": "s",
diff -ruN bird-2.15.1/conf/confbase.Y bird-2.15.1-aspa-asn-pairs/conf/confbase.Y
--- bird-2.15.1/conf/confbase.Y	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/conf/confbase.Y	2024-09-26 20:14:21.000000000 -0700
@@ -118,7 +118,7 @@
 %type <time> expr_us time
 %type <a> ipa
 %type <net> net_ip4_ net_ip4 net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa
-%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_
+%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_
 %type <mls> label_stack_start label_stack
 
 %type <t> text opttext
@@ -138,7 +138,7 @@
 
 %start config
 
-CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX, AS)
+CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, ASPA, FROM, MAX, AS)
 
 CF_GRAMMAR
 
@@ -299,6 +299,12 @@
   net_fill_mpls($$, $2);
 }
 
+net_aspa_: ASPA NUM NUM
+{
+  $$ = cfg_alloc(sizeof(net_addr_aspa));
+  net_fill_aspa($$, $2, $3);
+}
+
 net_ip_: net_ip4_ | net_ip6_ ;
 net_vpn_: net_vpn4_ | net_vpn6_ ;
 net_roa_: net_roa4_ | net_roa6_ ;
@@ -310,6 +316,7 @@
  | net_flow_
  | net_ip6_sadr_
  | net_mpls_
+ | net_aspa_
  ;
 
 
diff -ruN bird-2.15.1/filter/config.Y bird-2.15.1-aspa-asn-pairs/filter/config.Y
--- bird-2.15.1/filter/config.Y	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/filter/config.Y	2024-09-26 19:59:24.000000000 -0700
@@ -365,6 +365,7 @@
 	FROM, GW, NET, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS, GW_MPLS_STACK, ONLINK,
 	PREFERENCE,
 	ROA_CHECK,
+	ASPA_CHECK,
 	DEFINED,
 	ADD, DELETE, RESET,
 	PREPEND,
@@ -947,6 +948,9 @@
  | ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); }
  | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); }
 
+ | ASPA_CHECK '(' rtable ',' term ')' { $$ = f_new_inst(FI_ASPA_CHECK_IMPLICIT, $5, $3); }
+ | ASPA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ASPA_CHECK_EXPLICIT, $5, $7, $3); }
+
  | FORMAT '(' term ')' {  $$ = f_new_inst(FI_FORMAT, $3); }
 
  | term_bs
diff -ruN bird-2.15.1/filter/data.h bird-2.15.1-aspa-asn-pairs/filter/data.h
--- bird-2.15.1/filter/data.h	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/filter/data.h	2024-09-17 19:03:23.000000000 -0700
@@ -43,6 +43,8 @@
   T_ENUM_RA_PREFERENCE = 0x37,
   T_ENUM_AF = 0x38,
   T_ENUM_MPLS_POLICY = 0x39,
+  T_ENUM_ASPA = 0x3a,
+  T_ENUM_BGP_DIR = 0x3b,
 
 /* new enums go here */
   T_ENUM_EMPTY = 0x3f,	/* Special hack for atomic_aggr */
diff -ruN bird-2.15.1/filter/f-inst.c bird-2.15.1-aspa-asn-pairs/filter/f-inst.c
--- bird-2.15.1/filter/f-inst.c	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/filter/f-inst.c	2024-10-08 21:18:30.000000000 -0700
@@ -1094,6 +1094,22 @@
           ((net_addr_roa6 *) v1.val.net)->asn);
   ]]);
 
+  /* Get ASPA CUSTOMER_ASN */
+  METHOD(T_NET, customer_asn, 0, [[
+        if (!net_is_aspa(v1.val.net))
+          runtime( "ASPA expected" );
+
+        RESULT(T_INT, i, ((net_addr_aspa *) v1.val.net)->customer_asn);
+  ]]);
+
+  /* Get ASPA PROVIDER_ASN */
+  METHOD(T_NET, provider_asn, 0, [[
+        if (!net_is_aspa(v1.val.net))
+          runtime( "ASPA expected" );
+
+        RESULT(T_INT, i, ((net_addr_aspa *) v1.val.net)->provider_asn);
+  ]]);
+
   /* Convert prefix to IP */
   METHOD_R(T_NET, ip, T_IP, ip, net_prefix(v1.val.net));
 
@@ -1606,6 +1622,52 @@
 
   }
 
+  INST(FI_ASPA_CHECK_IMPLICIT, 1, 1) {	/* ASPA Check */
+    NEVER_CONSTANT;
+    ARG(1, T_ENUM_BGP_DIR);
+    RTC(2);
+    struct rtable *table = rtc->table;
+    ACCESS_RTE;
+    ACCESS_EATTRS;
+    const net_addr *net = (*fs->rte)->net->n.addr;
+
+    if (!table)
+      runtime("Missing ASPA table");
+
+    if (table->addr_type != NET_ASPA)
+      runtime("Table type must be ASPA");
+
+    /* We ignore temporary attributes, probably not a problem here */
+    /* 0x02 is a value of BA_AS_PATH, we don't want to include BGP headers */
+    eattr *e = ea_find(*fs->eattrs, EA_CODE(PROTOCOL_BGP, 0x02));
+
+    if (!e || ((e->type & EAF_TYPE_MASK) != EAF_TYPE_AS_PATH))
+      runtime("Missing AS_PATH attribute");
+
+    uint dir = v1.val.i;
+
+    if (!net_is_ip(net))
+      runtime("Network type must be IPv4 or IPv6");
+
+    RESULT(T_ENUM_ASPA, i, [[ net_aspa_check(fpool, table, e->u.ptr, dir) ]]);
+  }
+
+  INST(FI_ASPA_CHECK_EXPLICIT, 2, 1) {	/* ASPA Check */
+    NEVER_CONSTANT;
+    ARG(1, T_PATH);
+    ARG(2, T_ENUM_BGP_DIR);
+    RTC(3);
+    struct rtable *table = rtc->table;
+
+    if (!table)
+      runtime("Missing ASPA table");
+
+    if (table->addr_type != NET_ASPA)
+      runtime("Table type must be ASPA");
+
+    RESULT(T_ENUM_ASPA, i, [[ net_aspa_check(fpool, table, v1.val.ad, v2.val.i) ]]);
+  }
+
   INST(FI_FROM_HEX, 1, 1) {	/* Convert hex text to bytestring */
     ARG(1, T_STRING);
 
diff -ruN bird-2.15.1/filter/test.conf bird-2.15.1-aspa-asn-pairs/filter/test.conf
--- bird-2.15.1/filter/test.conf	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/filter/test.conf	2024-09-26 20:01:32.000000000 -0700
@@ -2222,6 +2222,72 @@
 
 bt_test_suite(t_roa_check, "Testing ROA");
 
+aspa table at;
+
+protocol static
+{
+	aspa;
+	route aspa 65000 65001;
+	route aspa 65000 65002;
+	route aspa 64999 65001;
+	route aspa 65004 64999;
+	route aspa 65001 0;
+	route aspa 65006 65100;
+	route aspa 65000 65003;
+}
+
+function t_aspa_check()
+bgppath p1;
+bgppath p2;
+{
+	# empty path
+	p1 = + empty + ;
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_VALID);
+
+	# 65000
+	p1 = prepend(p1, 65000);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_VALID);
+
+	# 65000 65000
+	p1 = prepend(p1, 65000);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_VALID);
+
+	# 65001 65000 65000
+	p1 = prepend(p1, 65001);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_VALID);
+
+	# 65003 65000
+	p1 = prepend(+ empty +, 65000);
+	p1 = prepend(p1, 65003);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_INVALID);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_VALID);
+
+	# 65000 65003
+	p1 = prepend(+ empty +, 65003);
+	p1 = prepend(p1, 65000);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_UNKNOWN);
+
+	# 65004 64999 65001 65001 65000
+	p1 = prepend(+ empty +, 65000);
+	p1 = prepend(p1, 65001);
+	p1 = prepend(p1, 65001);
+	p1 = prepend(p1, 64999);
+	p1 = prepend(p1, 65004);
+	bt_assert(aspa_check(at, p1, BGP_DIR_UP) = ASPA_INVALID);
+	bt_assert(aspa_check(at, p1, BGP_DIR_DOWN) = ASPA_VALID);
+
+	# 65005 65004 64999 65001 65001 65000
+	p2 = prepend(p1, 65005);
+	bt_assert(aspa_check(at, p2, BGP_DIR_UP) = ASPA_INVALID);
+	bt_assert(aspa_check(at, p2, BGP_DIR_DOWN) = ASPA_UNKNOWN);
+
+	# 65006 65004 64999 65001 65001 65000
+	p2 = prepend(p1, 65006);
+	bt_assert(aspa_check(at, p2, BGP_DIR_UP) = ASPA_INVALID);
+	bt_assert(aspa_check(at, p2, BGP_DIR_DOWN) = ASPA_INVALID);
+}
+
+bt_test_suite(t_aspa_check, "Testing ASPA");
 
 
 
diff -ruN bird-2.15.1/lib/net.c bird-2.15.1-aspa-asn-pairs/lib/net.c
--- bird-2.15.1/lib/net.c	2023-04-21 00:06:23.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/lib/net.c	2024-09-26 20:03:12.000000000 -0700
@@ -16,6 +16,7 @@
   [NET_FLOW6]	= "flow6",
   [NET_IP6_SADR]= "ipv6-sadr",
   [NET_MPLS]	= "mpls",
+  [NET_ASPA]	= "aspa",
 };
 
 const u16 net_addr_length[] = {
@@ -29,6 +30,7 @@
   [NET_FLOW6]	= 0,
   [NET_IP6_SADR]= sizeof(net_addr_ip6_sadr),
   [NET_MPLS]	= sizeof(net_addr_mpls),
+  [NET_ASPA]	= sizeof(net_addr_aspa),
 };
 
 const u8 net_max_prefix_length[] = {
@@ -42,6 +44,7 @@
   [NET_FLOW6]	= IP6_MAX_PREFIX_LENGTH,
   [NET_IP6_SADR]= IP6_MAX_PREFIX_LENGTH,
   [NET_MPLS]	= 0,
+  [NET_ASPA]	= 0,
 };
 
 const u16 net_max_text_length[] = {
@@ -55,6 +58,7 @@
   [NET_FLOW6]	= 0,	/* "flow6 { ... }" */
   [NET_IP6_SADR]= 92,	/* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128 from ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */
   [NET_MPLS]	= 7,	/* "1048575" */
+  [NET_ASPA]	= 30,	/* "aspa AS4294967295 AS4294967295" */
 };
 
 /* There should be no implicit padding in net_addr structures */
@@ -69,6 +73,7 @@
 STATIC_ASSERT(sizeof(net_addr_flow6)	== 20);
 STATIC_ASSERT(sizeof(net_addr_ip6_sadr)	== 40);
 STATIC_ASSERT(sizeof(net_addr_mpls)	==  8);
+STATIC_ASSERT(sizeof(net_addr_aspa)	== 12);
 
 
 int
@@ -123,6 +128,8 @@
     return bsnprintf(buf, buflen, "%I6/%d from %I6/%d", n->ip6_sadr.dst_prefix, n->ip6_sadr.dst_pxlen, n->ip6_sadr.src_prefix, n->ip6_sadr.src_pxlen);
   case NET_MPLS:
     return bsnprintf(buf, buflen, "%u", n->mpls.label);
+  case NET_ASPA:
+    return bsnprintf(buf, buflen, "aspa AS%u AS%u", n->aspa.customer_asn, n->aspa.provider_asn);
   }
 
   bug("unknown network type");
@@ -147,6 +154,7 @@
     return ipa_from_ip6(ip6_mkmask(net6_pxlen(a)));
 
   case NET_MPLS:
+  case NET_ASPA:
   default:
     return IPA_NONE;
   }
@@ -180,6 +188,8 @@
     return net_compare_ip6_sadr((const net_addr_ip6_sadr *) a, (const net_addr_ip6_sadr *) b);
   case NET_MPLS:
     return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *) b);
+  case NET_ASPA:
+    return net_compare_aspa((const net_addr_aspa *) a, (const net_addr_aspa *) b);
   }
   return 0;
 }
@@ -201,6 +211,7 @@
   case NET_FLOW6: return NET_HASH(n, flow6);
   case NET_IP6_SADR: return NET_HASH(n, ip6_sadr);
   case NET_MPLS: return NET_HASH(n, mpls);
+  case NET_ASPA: return NET_HASH(n, aspa);
   default: bug("invalid type");
   }
 }
@@ -223,6 +234,7 @@
   case NET_FLOW6: return NET_VALIDATE(n, flow6);
   case NET_IP6_SADR: return NET_VALIDATE(n, ip6_sadr);
   case NET_MPLS: return NET_VALIDATE(n, mpls);
+  case NET_ASPA: return NET_VALIDATE(n, aspa);
   default: return 0;
   }
 }
@@ -250,6 +262,7 @@
     return net_normalize_ip6_sadr(&n->ip6_sadr);
 
   case NET_MPLS:
+  case NET_ASPA:
     return;
   }
 }
@@ -277,6 +290,7 @@
     return ip6_zero(n->ip6_sadr.dst_prefix) ? (IADDR_HOST | SCOPE_UNIVERSE) : ip6_classify(&n->ip6_sadr.dst_prefix);
 
   case NET_MPLS:
+  case NET_ASPA:
     return IADDR_HOST | SCOPE_UNIVERSE;
   }
 
@@ -310,6 +324,7 @@
 			    ip6_mkmask(net6_pxlen(n))));
 
   case NET_MPLS:
+  case NET_ASPA:
   default:
     return 0;
   }
diff -ruN bird-2.15.1/lib/net.h bird-2.15.1-aspa-asn-pairs/lib/net.h
--- bird-2.15.1/lib/net.h	2023-10-07 04:59:48.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/lib/net.h	2024-10-06 21:22:27.000000000 -0700
@@ -23,7 +23,8 @@
 #define NET_FLOW6	8
 #define NET_IP6_SADR	9
 #define NET_MPLS	10
-#define NET_MAX		11
+#define NET_ASPA	11
+#define NET_MAX		12
 
 #define NB_IP4		(1 << NET_IP4)
 #define NB_IP6		(1 << NET_IP6)
@@ -35,6 +36,7 @@
 #define NB_FLOW6	(1 << NET_FLOW6)
 #define NB_IP6_SADR	(1 << NET_IP6_SADR)
 #define NB_MPLS		(1 << NET_MPLS)
+#define NB_ASPA		(1 << NET_ASPA)
 
 #define NB_IP		(NB_IP4 | NB_IP6)
 #define NB_VPN		(NB_VPN4 | NB_VPN6)
@@ -124,6 +126,14 @@
   u32 label;
 } net_addr_mpls;
 
+typedef struct net_addr_aspa {
+  u8 type;
+  u8 padding;
+  u16 length;
+  u32 customer_asn;
+  u32 provider_asn;
+} net_addr_aspa;
+
 typedef struct net_addr_ip6_sadr {
   u8 type;
   u8 dst_pxlen;
@@ -145,6 +155,7 @@
   net_addr_flow6 flow6;
   net_addr_ip6_sadr ip6_sadr;
   net_addr_mpls mpls;
+  net_addr_aspa aspa;
 } net_addr_union;
 
 
@@ -186,6 +197,8 @@
 #define NET_ADDR_MPLS(label) \
   ((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label })
 
+#define NET_ADDR_ASPA(customer_asn, provider_asn) \
+  ((net_addr_aspa) { NET_ASPA, 0, sizeof(net_addr_aspa), customer_asn, provider_asn })
 
 static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen)
 { *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); }
@@ -211,6 +224,9 @@
 static inline void net_fill_mpls(net_addr *a, u32 label)
 { *(net_addr_mpls *)a = NET_ADDR_MPLS(label); }
 
+static inline void net_fill_aspa(net_addr *a, u32 customer_asn, u32 provider_asn)
+{ *(net_addr_aspa *)a = NET_ADDR_ASPA(customer_asn, provider_asn); }
+
 static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen)
 {
   if (ipa_is_ip4(prefix))
@@ -272,6 +288,9 @@
 static inline int net_is_sadr(const net_addr *a)
 { return (a->type == NET_IP6_SADR); }
 
+static inline int net_is_aspa(const net_addr *a)
+{ return (a->type == NET_ASPA); }
+
 static inline ip4_addr net4_prefix(const net_addr *a)
 { return ((net_addr_ip4 *) a)->prefix; }
 
@@ -296,6 +315,7 @@
     return ipa_from_ip6(net6_prefix(a));
 
   case NET_MPLS:
+  case NET_ASPA:
   default:
     return IPA_NONE;
   }
@@ -366,6 +386,9 @@
 static inline int net_equal_mpls(const net_addr_mpls *a, const net_addr_mpls *b)
 { return !memcmp(a, b, sizeof(net_addr_mpls)); }
 
+static inline int net_equal_aspa(const net_addr_aspa *a, const net_addr_aspa *b)
+{ return !memcmp(a, b, sizeof(net_addr_aspa)); }
+
 
 static inline int net_equal_prefix_roa4(const net_addr_roa4 *a, const net_addr_roa4 *b)
 { return ip4_equal(a->prefix, b->prefix) && (a->pxlen == b->pxlen); }
@@ -379,6 +402,9 @@
 static inline int net_equal_src_ip6_sadr(const net_addr_ip6_sadr *a, const net_addr_ip6_sadr *b)
 { return ip6_equal(a->src_prefix, b->src_prefix) && (a->src_pxlen == b->src_pxlen); }
 
+static inline int net_equal_customer_aspa(const net_addr_aspa *a, const net_addr_aspa *b)
+{ return (a->customer_asn == b->customer_asn); }
+
 
 static inline int net_zero_ip4(const net_addr_ip4 *a)
 { return !a->pxlen && ip4_zero(a->prefix); }
@@ -407,6 +433,9 @@
 static inline int net_zero_mpls(const net_addr_mpls *a)
 { return !a->label; }
 
+static inline int net_zero_aspa(const net_addr_aspa *a)
+{ return !a->customer_asn && !a->provider_asn; }
+
 
 static inline int net_compare_ip4(const net_addr_ip4 *a, const net_addr_ip4 *b)
 { return ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); }
@@ -442,6 +471,9 @@
 static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls *b)
 { return uint_cmp(a->label, b->label); }
 
+static inline int net_compare_aspa(const net_addr_aspa *a, const net_addr_aspa *b)
+{ return uint_cmp(a->customer_asn, b->customer_asn) ?: uint_cmp(a->provider_asn, b->provider_asn); }
+
 int net_compare(const net_addr *a, const net_addr *b);
 
 
@@ -478,6 +510,8 @@
 static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src)
 { memcpy(dst, src, sizeof(net_addr_mpls)); }
 
+static inline void net_copy_aspa(net_addr_aspa *dst, const net_addr_aspa *src)
+{ memcpy(dst, src, sizeof(net_addr_aspa)); }
 
 static inline u32 px4_hash(ip4_addr prefix, u32 pxlen)
 { return ip4_hash(prefix) ^ (pxlen << 26); }
@@ -521,6 +555,9 @@
 static inline u32 net_hash_mpls(const net_addr_mpls *n)
 { return u32_hash(n->label); }
 
+static inline u32 net_hash_aspa(const net_addr_aspa *n)
+{ return u32_hash(n->customer_asn); }
+
 u32 net_hash(const net_addr *a);
 
 
@@ -573,6 +610,9 @@
 static inline int net_validate_ip6_sadr(const net_addr_ip6_sadr *n)
 { return net_validate_px6(n->dst_prefix, n->dst_pxlen) && net_validate_px6(n->src_prefix, n->src_pxlen); }
 
+static inline int net_validate_aspa(const net_addr_aspa *n)
+{ return n->customer_asn > 0; }
+
 int net_validate(const net_addr *N);
 
 
diff -ruN bird-2.15.1/nest/a-path.c bird-2.15.1-aspa-asn-pairs/nest/a-path.c
--- bird-2.15.1/nest/a-path.c	2023-10-07 04:59:53.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/a-path.c	2024-10-13 20:17:09.000000000 -0700
@@ -177,6 +177,27 @@
   return 0;
 }
 
+int
+as_path_contains_set(const struct adata *path)
+{
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
+
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint slen = 2 + BS * pos[1];
+
+    if ((type == AS_PATH_SET) ||
+	(type == AS_PATH_CONFED_SET))
+      return 1;
+
+    pos += slen;
+  }
+
+  return 0;
+}
+
 struct adata *
 as_path_strip_confed(struct linpool *pool, const struct adata *path)
 {
@@ -206,6 +227,55 @@
   return res;
 }
 
+int
+as_path_get_reverse_unique_asns(struct linpool *pool, const struct adata *path, u32 **data)
+{
+  u32 *asns = lp_alloc(pool, as_path_getlen(path) * sizeof(u32));
+  const byte *src = path->data;
+  const byte *end = src + path->length;
+  u32 *dst = asns;
+  u32 prev_as = 0;
+  u32 as;
+  uint i;
+
+  /* Step 1: create array of unique asns */
+  while (src < end)
+  {
+    uint t = src[0];
+    uint l = src[1];
+    src += 2;
+
+    if (t == AS_PATH_SEQUENCE)
+    {
+      for (i = 0; i < l; i++)
+      {
+        as = get_as(src);
+        if (as != prev_as)
+          *dst++ = as;
+        prev_as = as;
+        src += BS;
+      }
+    }
+    else
+      src += BS * l;
+  }
+  u32 num = dst - asns;
+
+  /* Step 2: Reverse the array */
+  u32 as_1, as_2;
+  for (i = 0; i < num/2; i++)
+  {
+    as_1 = asns[i];
+    as_2 = asns[num-i-1];
+    asns[i] = as_2;
+    asns[num-i-1] = as_1;
+  }
+
+  *data = asns;
+
+  return num;
+}
+
 struct adata *
 as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as)
 {
diff -ruN bird-2.15.1/nest/attrs.h bird-2.15.1-aspa-asn-pairs/nest/attrs.h
--- bird-2.15.1/nest/attrs.h	2023-10-07 04:59:53.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/attrs.h	2024-09-17 19:51:34.000000000 -0700
@@ -36,12 +36,14 @@
 int as_path_32to16(byte *dst, const byte *src, uint len);
 int as_path_contains_as4(const struct adata *path);
 int as_path_contains_confed(const struct adata *path);
+int as_path_contains_set(const struct adata *path);
 struct adata *as_path_strip_confed(struct linpool *pool, const struct adata *op);
 struct adata *as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as);
 struct adata *as_path_to_old(struct linpool *pool, const struct adata *path);
 struct adata *as_path_cut(struct linpool *pool, const struct adata *path, uint num);
 const struct adata *as_path_merge(struct linpool *pool, const struct adata *p1, const struct adata *p2);
 void as_path_format(const struct adata *path, byte *buf, uint size);
+int as_path_get_reverse_unique_asns(struct linpool *pool, const struct adata *path, u32 **data);
 int as_path_getlen(const struct adata *path);
 int as_path_getlen_int(const struct adata *path, int bs);
 int as_path_get_first(const struct adata *path, u32 *orig_as);
diff -ruN bird-2.15.1/nest/config.Y bird-2.15.1-aspa-asn-pairs/nest/config.Y
--- bird-2.15.1/nest/config.Y	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/config.Y	2024-09-19 19:29:40.000000000 -0700
@@ -115,7 +115,7 @@
 
 CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT)
 CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, TABLES, STATES, ROUTES, FILTERS)
-CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS)
+CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA)
 CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI)
 CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES)
 CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512)
@@ -130,7 +130,7 @@
 CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS)
 
 /* For r_args_channel */
-CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC)
+CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, ASPA, PRI, SEC)
 
 CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
 	RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL, RPKI, L3VPN,
@@ -138,6 +138,7 @@
 CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINED)
 CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, BLACKHOLE, UNREACHABLE, PROHIBIT)
 CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID)
+CF_ENUM(T_ENUM_ASPA, ASPA_, UNKNOWN, VALID, INVALID, UNVERIFIED)
 CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
 CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE, VRF)
 
@@ -200,6 +201,7 @@
  | VPN6 { $$ = NET_VPN6; }
  | ROA4 { $$ = NET_ROA4; }
  | ROA6 { $$ = NET_ROA6; }
+ | ASPA { $$ = NET_ASPA; }
  | FLOW4{ $$ = NET_FLOW4; }
  | FLOW6{ $$ = NET_FLOW6; }
  ;
@@ -209,7 +211,7 @@
  | MPLS { $$ = NET_MPLS; }
  ;
 
-CF_ENUM(T_ENUM_NETTYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, IP6_SADR, MPLS)
+CF_ENUM(T_ENUM_NETTYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, IP6_SADR, MPLS, ASPA)
 
 
 /* Creation of routing tables */
@@ -840,6 +842,7 @@
  | FLOW4	{ $$ = "flow4"; }
  | FLOW6	{ $$ = "flow6"; }
  | MPLS		{ $$ = "mpls"; }
+ | ASPA		{ $$ = "aspa"; }
  | PRI		{ $$ = "pri"; }
  | SEC		{ $$ = "sec"; }
  ;
diff -ruN bird-2.15.1/nest/proto.c bird-2.15.1-aspa-asn-pairs/nest/proto.c
--- bird-2.15.1/nest/proto.c	2023-10-07 04:59:53.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/proto.c	2024-12-14 19:06:47.000000000 -0800
@@ -187,6 +187,7 @@
   c->reloadable = 1;
 
   init_list(&c->roa_subscriptions);
+  init_list(&c->aspa_subscriptions);
 
   CALL(c->channel->init, c, cf);
 
@@ -445,6 +446,143 @@
     channel_roa_unsubscribe(s);
 }
 
+// ---
+
+static void
+channel_aspa_in_changed(struct rt_subscription *s)
+{
+  struct channel *c = s->data;
+  int active = c->reload_event && ev_active(c->reload_event);
+
+  CD(c, "Reload triggered by RPKI change%s", active ? " - already active" : "");
+
+  if (!active)
+    channel_request_reload(c);
+  else
+    c->reload_pending = 1;
+}
+
+static void
+channel_aspa_out_changed(struct rt_subscription *s)
+{
+  struct channel *c = s->data;
+  int active = (c->export_state == ES_FEEDING);
+
+  CD(c, "Feeding triggered by RPKI change%s", active ? " - already active" : "");
+
+  if (!active)
+    channel_request_feeding(c);
+  else
+    c->refeed_pending = 1;
+}
+
+struct aspa_subscription {
+  struct rt_subscription s;
+  node aspa_node;
+};
+
+static int
+channel_aspa_is_subscribed(struct channel *c, rtable *tab, int dir)
+{
+  void (*hook)(struct rt_subscription *) =
+    dir ? channel_aspa_in_changed : channel_aspa_out_changed;
+
+  struct aspa_subscription *s;
+  node *n;
+
+  WALK_LIST2(s, n, c->aspa_subscriptions, aspa_node)
+    if ((s->s.tab == tab) && (s->s.hook == hook))
+      return 1;
+
+  return 0;
+}
+
+
+static void
+channel_aspa_subscribe(struct channel *c, rtable *tab, int dir)
+{
+  if (channel_aspa_is_subscribed(c, tab, dir))
+    return;
+
+  struct aspa_subscription *s = mb_allocz(c->proto->pool, sizeof(struct aspa_subscription));
+
+  s->s.hook = dir ? channel_aspa_in_changed : channel_aspa_out_changed;
+  s->s.data = c;
+  rt_subscribe(tab, &s->s);
+
+  add_tail(&c->aspa_subscriptions, &s->aspa_node);
+}
+
+static void
+channel_aspa_unsubscribe(struct aspa_subscription *s)
+{
+  rt_unsubscribe(&s->s);
+  rem_node(&s->aspa_node);
+  mb_free(s);
+}
+
+static void
+channel_aspa_subscribe_filter(struct channel *c, int dir)
+{
+  const struct filter *f = dir ? c->in_filter : c->out_filter;
+  struct rtable *tab;
+  int valid = 1, found = 0;
+
+  if ((f == FILTER_ACCEPT) || (f == FILTER_REJECT))
+    return;
+
+  /* No automatic reload for non-reloadable channels */
+  if (dir && !channel_reloadable(c))
+    valid = 0;
+
+#ifdef CONFIG_BGP
+  /* No automatic reload for BGP channels without in_table / out_table */
+  if (c->channel == &channel_bgp)
+    valid = dir ? !!c->in_table : !!c->out_table;
+#endif
+
+  struct filter_iterator fit;
+  FILTER_ITERATE_INIT(&fit, f, c->proto->pool);
+
+  FILTER_ITERATE(&fit, fi)
+  {
+    switch (fi->fi_code)
+    {
+    case FI_ASPA_CHECK_IMPLICIT:
+      tab = fi->i_FI_ASPA_CHECK_IMPLICIT.rtc->table;
+      if (valid) channel_aspa_subscribe(c, tab, dir);
+      found = 1;
+      break;
+
+    case FI_ASPA_CHECK_EXPLICIT:
+      tab = fi->i_FI_ASPA_CHECK_EXPLICIT.rtc->table;
+      if (valid) channel_aspa_subscribe(c, tab, dir);
+      found = 1;
+      break;
+
+    default:
+      break;
+    }
+  }
+  FILTER_ITERATE_END;
+
+  FILTER_ITERATE_CLEANUP(&fit);
+
+  if (!valid && found)
+    log(L_WARN "%s.%s: Automatic RPKI reload not active for %s",
+	c->proto->name, c->name ?: "?", dir ? "import" : "export");
+}
+
+static void
+channel_aspa_unsubscribe_all(struct channel *c)
+{
+  struct aspa_subscription *s;
+  node *n, *x;
+
+  WALK_LIST2_DELSAFE(s, n, x, c->aspa_subscriptions, aspa_node)
+    channel_aspa_unsubscribe(s);
+}
+
 static void
 channel_start_export(struct channel *c)
 {
@@ -569,6 +707,8 @@
   {
     channel_roa_subscribe_filter(c, 1);
     channel_roa_subscribe_filter(c, 0);
+    channel_aspa_subscribe_filter(c, 1);
+    channel_aspa_subscribe_filter(c, 0);
   }
 }
 
@@ -591,6 +731,7 @@
   c->out_table = NULL;
 
   channel_roa_unsubscribe_all(c);
+  channel_aspa_unsubscribe_all(c);
 }
 
 static void
@@ -878,11 +1019,14 @@
   if (import_changed || export_changed || rpki_reload_changed)
   {
     channel_roa_unsubscribe_all(c);
+    channel_aspa_unsubscribe_all(c);
 
     if (c->rpki_reload)
     {
       channel_roa_subscribe_filter(c, 1);
       channel_roa_subscribe_filter(c, 0);
+      channel_aspa_subscribe_filter(c, 1);
+      channel_aspa_subscribe_filter(c, 0);
     }
   }
 
diff -ruN bird-2.15.1/nest/protocol.h bird-2.15.1-aspa-asn-pairs/nest/protocol.h
--- bird-2.15.1/nest/protocol.h	2023-10-07 04:59:53.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/protocol.h	2024-12-14 18:56:58.000000000 -0800
@@ -563,6 +563,7 @@
   struct rtable *out_table;		/* Internal table for exported routes */
 
   list roa_subscriptions;		/* List of active ROA table subscriptions based on filters roa_check() */
+  list aspa_subscriptions;		/* List of active ASPA table subscriptions based on filters aspa_check() */
 };
 
 
diff -ruN bird-2.15.1/nest/route.h bird-2.15.1-aspa-asn-pairs/nest/route.h
--- bird-2.15.1/nest/route.h	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/route.h	2024-10-08 21:46:46.000000000 -0700
@@ -360,6 +360,8 @@
 static inline int rt_is_flow(rtable *tab)
 { return (tab->addr_type == NET_FLOW4) || (tab->addr_type == NET_FLOW6); }
 
+static inline int rt_is_aspa(rtable *tab)
+{ return (tab->addr_type == NET_ASPA); }
 
 /* Default limit for ECMP next hops, defined in sysdep code */
 extern const int rt_default_ecmp;
@@ -577,7 +579,6 @@
 static inline int adata_same(const struct adata *a, const struct adata *b)
 { return (a->length == b->length && !memcmp(a->data, b->data, a->length)); }
 
-
 typedef struct ea_list {
   struct ea_list *next;			/* In case we have an override list */
   byte flags;				/* Flags: EALF_... */
@@ -590,6 +591,7 @@
 #define EALF_BISECT 2			/* Use interval bisection for searching */
 #define EALF_CACHED 4			/* Attributes belonging to cached rta */
 
+int net_aspa_check(struct linpool *lp, rtable *tab, const struct adata *path, uint dir);
 struct rte_src *rt_find_source(struct proto *p, u32 id);
 struct rte_src *rt_get_source(struct proto *p, u32 id);
 static inline void rt_lock_source(struct rte_src *src) { src->uc++; }
@@ -780,4 +782,17 @@
 #define ROA_VALID	1
 #define ROA_INVALID	2
 
+/*
+ *	ASPA verification status
+ */
+
+#define ASPA_UNKNOWN	0
+#define ASPA_VALID	1
+#define ASPA_INVALID	2
+#define ASPA_UNVERIFIED 3
+
+#define ASPA_HOP_PROVIDER       0
+#define ASPA_HOP_NOT_PROVIDER   1
+#define ASPA_HOP_NO_ATTESTATION 2
+
 #endif
diff -ruN bird-2.15.1/nest/rt-fib.c bird-2.15.1-aspa-asn-pairs/nest/rt-fib.c
--- bird-2.15.1/nest/rt-fib.c	2023-06-22 08:02:43.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/rt-fib.c	2024-09-17 20:00:29.000000000 -0700
@@ -279,6 +279,7 @@
   case NET_FLOW6: return FIB_FIND(f, a, flow6);
   case NET_IP6_SADR: return FIB_FIND(f, a, ip6_sadr);
   case NET_MPLS: return FIB_FIND(f, a, mpls);
+  case NET_ASPA: return FIB_FIND(f, a, aspa);
   default: bug("invalid type");
   }
 }
@@ -300,6 +301,7 @@
   case NET_FLOW6: FIB_INSERT(f, a, e, flow6); return;
   case NET_IP6_SADR: FIB_INSERT(f, a, e, ip6_sadr); return;
   case NET_MPLS: FIB_INSERT(f, a, e, mpls); return;
+  case NET_ASPA: FIB_INSERT(f, a, e, aspa); return;
   default: bug("invalid type");
   }
 }
diff -ruN bird-2.15.1/nest/rt-table.c bird-2.15.1-aspa-asn-pairs/nest/rt-table.c
--- bird-2.15.1/nest/rt-table.c	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/nest/rt-table.c	2024-12-20 14:14:16.000000000 -0800
@@ -346,6 +346,132 @@
 #undef FW
 }
 
+static int
+net_aspa_hop_check(rtable *tab, u32 customer_asn, u32 provider_asn)
+{
+  struct net_addr_aspa n = NET_ADDR_ASPA(customer_asn, provider_asn);
+  struct fib_node *fn;
+  int found_customer = 0;
+
+  for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next)
+  {
+    net_addr_aspa *aspa = (void *) fn->addr;
+    net *r = fib_node_to_user(&tab->fib, fn);
+
+    if (net_equal_customer_aspa(aspa, &n) && rte_is_valid(r->routes))
+    {
+      found_customer = 1;
+      if (net_equal_aspa(aspa, &n))
+        return ASPA_HOP_PROVIDER;
+    }
+  }
+
+  return found_customer ? ASPA_HOP_NOT_PROVIDER : ASPA_HOP_NO_ATTESTATION;
+}
+
+/* draft-ietf-sidrops-aspa-verification-19 */
+
+int
+net_aspa_check(linpool *lp, rtable *tab, const struct adata *path, uint dir)
+{
+  u32 *as_path;
+  int n;
+
+  int i, j;
+  u32 customer;
+  u32 provider;
+  int hc;
+
+  int max_up_ramp, max_down_ramp;
+  int min_up_ramp, min_down_ramp;
+
+  ASSERT(tab->addr_type == NET_ASPA);
+
+  if (as_path_contains_set(path))
+  {
+    if (dir == BGP_DIR_UP)
+      return ASPA_INVALID;
+    else
+      return ASPA_UNKNOWN;
+  }
+
+  n = as_path_get_reverse_unique_asns(lp, path, &as_path);
+
+  if (n < 1)
+    return ASPA_INVALID;
+
+  /* AS_PATH {AS(N), AS(N-1),..., AS(2), AS(1)} */
+
+  max_up_ramp = n;
+  for (i = 0; i < n - 1; i++)
+  {
+    customer = as_path[i];
+    provider = as_path[i + 1];
+    hc = net_aspa_hop_check(tab, customer, provider);
+    if (hc == ASPA_HOP_NOT_PROVIDER)
+    {
+      max_up_ramp = i + 1;
+      break;
+    }
+  }
+
+  min_up_ramp = n;
+  for (i = 0; i < n - 1; i++)
+  {
+    customer = as_path[i];
+    provider = as_path[i + 1];
+    hc = net_aspa_hop_check(tab, customer, provider);
+    if (hc != ASPA_HOP_PROVIDER)
+    {
+      min_up_ramp = i + 1;
+      break;
+    }
+  }
+
+  if (dir == BGP_DIR_DOWN)
+  {
+    max_down_ramp = n;
+    for(j = n - 1; j > 0; j--)
+    {
+      customer = as_path[j];
+      provider = as_path[j - 1];
+      hc = net_aspa_hop_check(tab, customer, provider);
+      if (hc == ASPA_HOP_NOT_PROVIDER)
+      {
+        max_down_ramp = n - (j + 1) + 1;
+        break;
+      }
+    }
+
+    min_down_ramp = n;
+    for(j = n - 1; j > 0; j--)
+    {
+      customer = as_path[j];
+      provider = as_path[j - 1];
+      hc = net_aspa_hop_check(tab, customer, provider);
+      if (hc != ASPA_HOP_PROVIDER)
+      {
+        min_down_ramp = n - (j + 1) + 1;
+        break;
+      }
+    }
+  }
+  else
+  {
+    /* BGP_DIR_UP */
+    max_down_ramp = 0;
+    min_down_ramp = 0;
+  }
+
+  if (max_up_ramp + max_down_ramp < n)
+    return ASPA_INVALID;
+
+  if (min_up_ramp + min_down_ramp < n)
+    return ASPA_UNKNOWN;
+
+  return ASPA_VALID;
+}
+
 /**
  * rte_find - find a route
  * @net: network node
diff -ruN bird-2.15.1/proto/bgp/bgp.h bird-2.15.1-aspa-asn-pairs/proto/bgp/bgp.h
--- bird-2.15.1/proto/bgp/bgp.h	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/proto/bgp/bgp.h	2024-09-17 20:03:38.000000000 -0700
@@ -816,5 +816,9 @@
 #define ORIGIN_EGP		1
 #define ORIGIN_INCOMPLETE	2
 
+/* BGP dir */
+
+#define BGP_DIR_DOWN		0
+#define BGP_DIR_UP		1
 
 #endif
diff -ruN bird-2.15.1/proto/bgp/config.Y bird-2.15.1-aspa-asn-pairs/proto/bgp/config.Y
--- bird-2.15.1/proto/bgp/config.Y	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/proto/bgp/config.Y	2024-09-17 20:06:41.000000000 -0700
@@ -34,6 +34,9 @@
 	FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
 	RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND)
 
+CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
+CF_ENUM(T_ENUM_BGP_DIR, BGP_DIR_, DOWN, UP)
+
 %type <i> bgp_nh
 %type <i32> bgp_afi
 
@@ -391,8 +394,6 @@
   cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, a);
 };
 
-CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
-
 CF_CODE
 
 CF_END
diff -ruN bird-2.15.1/proto/rpki/packets.c bird-2.15.1-aspa-asn-pairs/proto/rpki/packets.c
--- bird-2.15.1/proto/rpki/packets.c	2023-06-22 08:02:43.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/proto/rpki/packets.c	2024-12-14 20:29:46.000000000 -0800
@@ -35,6 +35,8 @@
   UNSUPPORTED_PDU_TYPE 		= 5,
   WITHDRAWAL_OF_UNKNOWN_RECORD 	= 6,
   DUPLICATE_ANNOUNCEMENT 	= 7,
+  UNEXPECTED_PROTOCOL_VERSION	= 8,
+  ASPA_PROVIDER_LIST_ERROR	= 9,
   PDU_TOO_BIG 			= 32
 };
 
@@ -47,6 +49,8 @@
   [UNSUPPORTED_PDU_TYPE] 	= "Unsupported-PDU-Type",
   [WITHDRAWAL_OF_UNKNOWN_RECORD]= "Withdrawal-Of-Unknown-Record",
   [DUPLICATE_ANNOUNCEMENT] 	= "Duplicate-Announcement",
+  [UNEXPECTED_PROTOCOL_VERSION] = "Unexpected-Protocol-Version",
+  [ASPA_PROVIDER_LIST_ERROR]	= "ASPA-Provider-List-Error",
   [PDU_TOO_BIG] 		= "PDU-Too-Big",
 };
 
@@ -62,6 +66,7 @@
   CACHE_RESET 			= 8,
   ROUTER_KEY 			= 9,
   ERROR 			= 10,
+  ASPA_PDU 			= 11,
   PDU_TYPE_MAX
 };
 
@@ -76,7 +81,8 @@
   [END_OF_DATA] 		= "End of Data",
   [CACHE_RESET] 		= "Cache Reset",
   [ROUTER_KEY] 			= "Router Key",
-  [ERROR] 			= "Error"
+  [ERROR] 			= "Error",
+  [ASPA_PDU] 			= "ASPA PDU"
 };
 
 static const char *str_pdu_type(uint type) {
@@ -193,6 +199,16 @@
 				 * Error Diagnostic Message */
 } PACKED;
 
+struct pdu_aspa {
+  u8 ver;
+  u8 type;
+  u8 flags;
+  u8 zero;
+  u32 len;
+  u32 customer_asn;
+  u32 provider_asns[];			/* 0 or more u32 provider_asn entries */
+} PACKED;
+
 struct pdu_reset_query {
   u8 ver;
   u8 type;
@@ -231,6 +247,7 @@
   [CACHE_RESET] 		= sizeof(struct pdu_cache_response),
   [ROUTER_KEY] 			= sizeof(struct pdu_header), /* FIXME */
   [ERROR] 			= 16,
+  [ASPA_PDU] 			= sizeof(struct pdu_aspa),
 };
 
 static int rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...);
@@ -272,7 +289,7 @@
 rpki_pdu_to_host_byte_order(struct pdu_header *pdu)
 {
   /* The Router Key PDU has two one-byte fields instead of one two-bytes field. */
-  if (pdu->type != ROUTER_KEY)
+  if ((pdu->type != ROUTER_KEY) && (pdu->type != ASPA_PDU))
     pdu->reserved = ntohs(pdu->reserved);
 
   pdu->len = ntohl(pdu->len);
@@ -293,7 +310,7 @@
     struct pdu_end_of_data_v0 *eod0 = (void *) pdu;
     eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */
 
-    if (pdu->ver == RPKI_VERSION_1)
+    if (pdu->ver >= RPKI_VERSION_1)
     {
       struct pdu_end_of_data_v1 *eod1 = (void *) pdu;
       eod1->expire_interval = ntohl(eod1->expire_interval);
@@ -319,6 +336,19 @@
     break;
   }
 
+  case ASPA_PDU:
+  {
+    struct pdu_aspa *aspa = (void *) pdu;
+    int provider_count = (aspa->len - min_pdu_size[ASPA_PDU]) / sizeof(u32);
+    u32 *provider_asn;
+    aspa->customer_asn = ntohl(aspa->customer_asn);
+    for (int i = 0; i < provider_count; i++) {
+      provider_asn = aspa->provider_asns + i;
+      *provider_asn = ntohl(*provider_asn);
+    }
+    break;
+  }
+
   case ERROR:
   {
     /* Note that a error_code is converted using converting header->reserved */
@@ -387,7 +417,7 @@
   case END_OF_DATA:
   {
     const struct pdu_end_of_data_v1 *eod = (void *) pdu;
-    if (eod->ver == RPKI_VERSION_1)
+    if (eod->ver >= RPKI_VERSION_1)
       SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u, refresh: %us, retry: %us, expire: %us)", eod->session_id, eod->serial_num, eod->refresh_interval, eod->retry_interval, eod->expire_interval));
     else
       SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", eod->session_id, eod->serial_num));
@@ -412,6 +442,22 @@
     break;
   }
 
+  case ASPA_PDU:
+  {
+    const struct pdu_aspa *aspa = (void *) pdu;
+    int provider_count = (aspa->len - min_pdu_size[ASPA_PDU]) / sizeof(u32);
+    u32 *provider_asn;
+    SAVE(bsnprintf(detail, sizeof(detail), "(aspa customer: AS%u", aspa->customer_asn));
+    if (provider_count > 0)
+      SAVE(bsnprintf(detail, sizeof(detail), " provider%s:", (provider_count > 1)?"s":""));
+    for (int i = 0; i < provider_count; i++) {
+      provider_asn = (u32 *)aspa->provider_asns + i;
+      SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), " AS%u", *provider_asn));
+    }
+    SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ")"));
+    break;
+  }
+
   case ROUTER_KEY:
     /* We don't support saving Router Key PDUs yet */
     SAVE(bsnprintf(detail, sizeof(detail), "(ignored)"));
@@ -571,7 +617,7 @@
     }
   }
 
-  if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY))
+  if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver < RPKI_VERSION_1 && pdu->type == ROUTER_KEY) || (pdu->ver < RPKI_VERSION_2 && pdu->type == ASPA_PDU))
   {
     rpki_send_error_pdu(cache, UNSUPPORTED_PDU_TYPE, pdu_len, pdu, "Unsupported PDU type %u received", pdu->type);
     return RPKI_ERROR;
@@ -595,6 +641,7 @@
   case INTERNAL_ERROR:
   case INVALID_REQUEST:
   case UNSUPPORTED_PDU_TYPE:
+  case ASPA_PROVIDER_LIST_ERROR:
     rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
     break;
 
@@ -664,6 +711,8 @@
 	rt_refresh_begin(cache->p->roa4_channel->table, cache->p->roa4_channel);
       if (cache->p->roa6_channel)
 	rt_refresh_begin(cache->p->roa6_channel->table, cache->p->roa6_channel);
+      if (cache->p->aspa_channel)
+	rt_refresh_begin(cache->p->aspa_channel->table, cache->p->aspa_channel);
 
       cache->p->refresh_channels = 1;
     }
@@ -785,18 +834,163 @@
     return RPKI_ERROR;
   }
 
-  cache->last_rx_prefix = current_time();
+  cache->last_rx_prefix_or_aspa = current_time();
 
   /* A place for 'flags' is same for both data structures pdu_ipv4 or pdu_ipv6  */
   struct pdu_ipv4 *pfx = (void *) pdu;
   if (pfx->flags & RPKI_ADD_FLAG)
-    rpki_table_add_roa(cache, channel, &addr);
+    rpki_table_add_entry(cache, channel, &addr);
   else
-    rpki_table_remove_roa(cache, channel, &addr);
+    rpki_table_remove_entry(cache, channel, &addr);
+
+  return RPKI_SUCCESS;
+}
+
+static net_addr_union *
+rpki_aspa_pair_2_net_addr(const u32 customer_asn, const u32 provider_asn, net_addr_union *n)
+{
+  n->aspa.type = NET_ASPA;
+  n->aspa.length = sizeof(net_addr_aspa);
+  n->aspa.customer_asn = customer_asn;
+  n->aspa.provider_asn = provider_asn;
+
+  return n;
+}
+
+static int
+rpki_aspa_provider_list_contains_asn(const u32 provider_asn, const u32 provider_list[], const int count)
+{
+  for(int i = 0; i < count; i++)
+    if (provider_list[i] == provider_asn)
+      return 1;
+  return 0;
+}
+
+static int
+rpki_handle_aspa_pdu(struct rpki_cache *cache, const struct pdu_header *pdu)
+{
+  const enum pdu_type type = pdu->type;
+  ASSERT(type == ASPA_PDU);
+
+  net_addr_union addr = {};
+
+  struct pdu_aspa *aspa = (void *) pdu;
+  int provider_count = (aspa->len - min_pdu_size[ASPA_PDU]) / sizeof(u32);
+
+  /* If we are withdrawing an ASPA entry there must be no providers listed */
+  if (!(aspa->flags & RPKI_ADD_FLAG) && (provider_count > 0))
+  {
+    RPKI_WARN(cache->p, "Received corrupt packet from RPKI cache server: invalid provider count");
+    byte *tmp = mb_alloc(cache->pool, pdu->len);
+    const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+    rpki_send_error_pdu(cache, ASPA_PROVIDER_LIST_ERROR, pdu->len, hton_pdu, "ASPA Provider List Error: invalid provider count");
+    mb_free(tmp);
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    return RPKI_ERROR;
+  }
+
+  /* If we are announcing an ASPA entry there must be providers listed */
+  if ((aspa->flags & RPKI_ADD_FLAG) && (provider_count < 1))
+  {
+    RPKI_WARN(cache->p, "Received corrupt packet from RPKI cache server: no providers");
+    byte *tmp = mb_alloc(cache->pool, pdu->len);
+    const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+    rpki_send_error_pdu(cache, ASPA_PROVIDER_LIST_ERROR, pdu->len, hton_pdu, "ASPA Provider List Error: no providers");
+    mb_free(tmp);
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    return RPKI_ERROR;
+  }
+
+  if (provider_count > RPKI_ASPA_MAX_PROVIDERS)
+  {
+    RPKI_WARN(cache->p, "Received corrupt packet from RPKI cache server: too many providers");
+    byte *tmp = mb_alloc(cache->pool, pdu->len);
+    const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+    rpki_send_error_pdu(cache, ASPA_PROVIDER_LIST_ERROR, pdu->len, hton_pdu, "ASPA Provider List Error: too many providers");
+    mb_free(tmp);
+    rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+    return RPKI_ERROR;
+  }
+
+  struct channel *channel = NULL;
+
+  if (type == ASPA_PDU)
+    channel = cache->p->aspa_channel;
+
+  if (!channel)
+  {
+    CACHE_TRACE(D_ROUTES, cache, "Skip %N, missing %s channel", &addr, "aspa", addr);
+    return RPKI_ERROR;
+  }
+
+  cache->last_rx_prefix_or_aspa = current_time();
+
+  struct net_addr_aspa n = NET_ADDR_ASPA(aspa->customer_asn, 0);
+  struct fib_node *fn;
+  rtable *tab = channel->table;
+
+  u32 skip_list[RPKI_ASPA_MAX_PROVIDERS];
+  int skip_count = 0;
+
+  /* Walk the table */
+
+  for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next)
+  {
+    net_addr_aspa *a = (void *) fn->addr;
+    net *r = fib_node_to_user(&tab->fib, fn);
+
+    if (net_equal_customer_aspa(a, &n) && rte_is_valid(r->routes))
+    {
+      if (rpki_aspa_provider_list_contains_asn(a->provider_asn, aspa->provider_asns, provider_count))
+      {
+        if (aspa->flags & RPKI_ADD_FLAG)
+        {
+          /* Copy into the skip list. Nothing is changing */
+          if (skip_count >= RPKI_ASPA_MAX_PROVIDERS)
+          {
+            RPKI_WARN(cache->p, "Received corrupt packet from RPKI cache server: too many providers to skip");
+            byte *tmp = mb_alloc(cache->pool, pdu->len);
+            const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu);
+            rpki_send_error_pdu(cache, ASPA_PROVIDER_LIST_ERROR, pdu->len, hton_pdu, "ASPA Provider List Error: too many providers to skip");
+            mb_free(tmp);
+            rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
+            return RPKI_ERROR;
+          }
+          skip_list[skip_count] = a->provider_asn;
+          skip_count++;
+        }
+      }
+      else
+      {
+        /* We've been removed */
+        rpki_aspa_pair_2_net_addr(aspa->customer_asn, a->provider_asn, &addr);
+        rpki_table_remove_entry(cache, channel, &addr);
+      }
+    }
+  }
+
+  if (!(aspa->flags & RPKI_ADD_FLAG))
+    return RPKI_SUCCESS;
+
+  u32 *provider_asn;
+
+  /* Walk the provider list */
+
+  for( int i = 0; i < provider_count; i++ )
+  {
+    provider_asn = aspa->provider_asns + i;
+    if (!rpki_aspa_provider_list_contains_asn(*provider_asn, skip_list, skip_count))
+    {
+      /* Add the entries that we dont already have */
+      rpki_aspa_pair_2_net_addr(aspa->customer_asn, *provider_asn, &addr);
+      rpki_table_add_entry(cache, channel, &addr);
+    }
+  }
 
   return RPKI_SUCCESS;
 }
 
+
 static uint
 rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval)
 {
@@ -822,7 +1016,7 @@
     return;
   }
 
-  if (pdu->ver == RPKI_VERSION_1)
+  if (pdu->ver >= RPKI_VERSION_1)
   {
     if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval))
       cache->refresh_interval = pdu->refresh_interval;
@@ -849,6 +1043,8 @@
       rt_refresh_end(cache->p->roa4_channel->table, cache->p->roa4_channel);
     if (cache->p->roa6_channel)
       rt_refresh_end(cache->p->roa6_channel->table, cache->p->roa6_channel);
+    if (cache->p->aspa_channel)
+      rt_refresh_end(cache->p->aspa_channel->table, cache->p->aspa_channel);
   }
 
   cache->last_update = current_time();
@@ -913,6 +1109,10 @@
     /* TODO: Implement Router Key PDU handling */
     break;
 
+  case ASPA_PDU:
+    rpki_handle_aspa_pdu(cache, pdu);
+    break;
+
   default:
     CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type);
   };
diff -ruN bird-2.15.1/proto/rpki/packets.h bird-2.15.1-aspa-asn-pairs/proto/rpki/packets.h
--- bird-2.15.1/proto/rpki/packets.h	2022-03-02 01:29:17.000000000 -0800
+++ bird-2.15.1-aspa-asn-pairs/proto/rpki/packets.h	2024-12-09 19:07:44.000000000 -0800
@@ -19,15 +19,20 @@
 /* A Error PDU size is the biggest (has encapsulate PDU inside):
  * 	   +8 bytes (Header size)
  * 	   +4 bytes (Length of Encapsulated PDU)
- * 	  +32 bytes (Encapsulated PDU IPv6 32)
+ *     +40012 bytes (Encapsulated max length ASPA PDU)
  * 	   +4 bytes (Length of inserted text)
  * 	 +800 bytes (UTF-8 text 400*2 bytes)
  * 	------------
- * 	= 848 bytes (Maximal expected PDU size) */
-#define RPKI_PDU_MAX_LEN	848
+ *    = 40828 bytes (Maximal expected PDU size) */
+#define RPKI_PDU_MAX_LEN	40828
+
+#define RPKI_ASPA_MAX_PROVIDERS 10000
+
+/* #define RPKI_ASPA_MAX_LEN	(sizeof(struct pdu_aspa)+(RPKI_ASPA_MAX_PROVIDERS*sizeof(u32))) */
 
 /* RX buffer size has a great impact to scheduler granularity */
-#define RPKI_RX_BUFFER_SIZE	4096
+/* #define RPKI_RX_BUFFER_SIZE	4096 */
+#define RPKI_RX_BUFFER_SIZE	65536
 #define RPKI_TX_BUFFER_SIZE	RPKI_PDU_MAX_LEN
 
 /* Return values */
diff -ruN bird-2.15.1/proto/rpki/rpki.c bird-2.15.1-aspa-asn-pairs/proto/rpki/rpki.c
--- bird-2.15.1/proto/rpki/rpki.c	2024-03-22 01:26:27.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/proto/rpki/rpki.c	2024-12-11 17:55:34.000000000 -0800
@@ -116,7 +116,7 @@
  */
 
 void
-rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+rpki_table_add_entry(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
 {
   struct rpki_proto *p = cache->p;
 
@@ -134,7 +134,7 @@
 }
 
 void
-rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
+rpki_table_remove_entry(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
 {
   struct rpki_proto *p = cache->p;
   rte_update2(channel, &pfxr->n, NULL, p->p.main_source);
@@ -369,7 +369,7 @@
 rpki_sync_is_stuck(struct rpki_cache *cache)
 {
   return !sk_rx_ready(cache->tr_sock->sk) && (
-      !cache->last_rx_prefix || (current_time() - cache->last_rx_prefix > 10 S)
+      !cache->last_rx_prefix_or_aspa || (current_time() - cache->last_rx_prefix_or_aspa > 10 S)
       );
 }
 
@@ -755,7 +755,8 @@
   struct rpki_cache *cache = p->cache;
 
   if (!proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)) ||
-      !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)))
+      !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)) ||
+      !proto_configure_channel(&p->p, &p->aspa_channel, proto_cf_find_channel(CF, NET_ASPA)))
     return NEED_RESTART;
 
   if (rpki_reconfigure_cache(p, cache, new, old) != SUCCESSFUL_RECONF)
@@ -777,6 +778,7 @@
 
   proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4));
   proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6));
+  proto_configure_channel(&p->p, &p->aspa_channel, proto_cf_find_channel(CF, NET_ASPA));
 
   return P;
 }
@@ -886,6 +888,11 @@
       channel_show_info(p->roa6_channel);
     else
       cli_msg(-1006, "  No roa6 channel");
+
+    if (p->aspa_channel)
+      channel_show_info(p->aspa_channel);
+    else
+      cli_msg(-1006, "  No aspa channel");
   }
 }
 
@@ -957,7 +964,7 @@
   .init = 		rpki_init,
   .start = 		rpki_start,
   .postconfig = 	rpki_postconfig,
-  .channel_mask =	(NB_ROA4 | NB_ROA6),
+  .channel_mask =	(NB_ROA4 | NB_ROA6 | NB_ASPA),
   .show_proto_info =	rpki_show_proto_info,
   .shutdown = 		rpki_shutdown,
   .copy_config = 	rpki_copy_config,
diff -ruN bird-2.15.1/proto/rpki/rpki.h bird-2.15.1-aspa-asn-pairs/proto/rpki/rpki.h
--- bird-2.15.1/proto/rpki/rpki.h	2024-03-10 13:42:50.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/proto/rpki/rpki.h	2024-12-11 17:54:57.000000000 -0800
@@ -29,7 +29,8 @@
 
 #define RPKI_VERSION_0		0
 #define RPKI_VERSION_1		1
-#define RPKI_MAX_VERSION 	RPKI_VERSION_1
+#define RPKI_VERSION_2		2
+#define RPKI_MAX_VERSION 	RPKI_VERSION_2
 
 
 /*
@@ -61,7 +62,7 @@
   u32 serial_num;			/* Serial number denotes the logical version of data from cache server */
   u8 version;				/* Protocol version */
   btime last_update;			/* Last successful synchronization with cache server */
-  btime last_rx_prefix;			/* Last received prefix PDU */
+  btime last_rx_prefix_or_aspa;		/* Last received prefix or aspa PDU */
 
   /* Intervals can be changed by cache server on the fly */
   u32 refresh_interval;			/* Actual refresh interval (in seconds) */
@@ -80,8 +81,8 @@
  * 	Routes handling
  */
 
-void rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
-void rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+void rpki_table_add_entry(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+void rpki_table_remove_entry(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
 
 
 /*
@@ -110,6 +111,7 @@
 
   struct channel *roa4_channel;
   struct channel *roa6_channel;
+  struct channel *aspa_channel;
   u8 refresh_channels;			/* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */
 };
 
diff -ruN bird-2.15.1/sysdep/config.h bird-2.15.1-aspa-asn-pairs/sysdep/config.h
--- bird-2.15.1/sysdep/config.h	2024-03-22 01:26:27.000000000 -0700
+++ bird-2.15.1-aspa-asn-pairs/sysdep/config.h	2024-12-20 14:15:33.000000000 -0800
@@ -13,7 +13,7 @@
 #ifdef GIT_LABEL
 #define BIRD_VERSION XSTR1(GIT_LABEL)
 #else
-#define BIRD_VERSION "2.15.1"
+#define BIRD_VERSION "2.15.1-aspa-asn-pairs"
 #endif
 
 /* Include parameters determined by configure script */
-------------- next part --------------
aspa table at;

protocol static
{
	aspa;
	#transit as AS12345
	#route aspa 12345 transit;
	route aspa 970 provider 54874;
	route aspa 11358 provider 835, 924, 6939, 20473, 34927;
	route aspa 13852 provider 6939, 20473, 34927, 52025, 209533;
	route aspa 15562 provider 2914, 8283, 51088, 206238;
	route aspa 16909 provider 20473, 21738, 47272, 53356, 53667, 152900, 212477;
	route aspa 19330 provider 393577;
	route aspa 21957 provider 970;
	route aspa 28584 provider 28605;
	route aspa 33733 provider 174, 6939, 12186, 47787, 200454;
	route aspa 40544 provider 924, 6939, 12186, 52025;
	route aspa 41720 provider 174, 1299, 9002, 50877, 60068, 203446, 212508;
	route aspa 44324 provider 945, 955, 1299, 3204, 6939, 7720, 8772, 8849, 15353, 18041, 20473, 29632, 32595, 34465, 34927, 41051, 43426, 47272, 48266, 51087, 53667, 53808, 59105, 59538, 61112, 134823, 134835, 138997, 139317, 150452, 151364, 199545, 199765, 200105, 206499, 207656, 207841, 209554, 209735, 212483, 213856;
	route aspa 44355 provider 207841;
	route aspa 47272 provider 174, 835, 924, 1299, 6830, 6939, 20473, 21738, 25759, 34927, 35133, 41051, 48605, 50391, 50917, 52025, 52210, 58057, 210667, 212514, 212895;
	route aspa 47689 provider 924, 1299, 6939, 21738, 34465, 34549, 34927, 395823;
	route aspa 48070 provider 174, 1299;
	route aspa 48606 provider 30893, 54681, 57974, 59678, 210691, 212276;
	route aspa 50224 provider 6939, 15353, 26930, 34465, 37988, 50104, 52025, 52210, 59920, 60841, 210475, 400304;
	route aspa 50391 provider 6939, 20473, 48070;
	route aspa 50555 provider 970;
	route aspa 51019 provider 6939, 34549, 34927, 52025, 207841;
	route aspa 51396 provider 6939, 30823, 35133, 44066, 44592, 49581, 60223, 64457, 203446, 214497, 214995, 215436;
	route aspa 52025 provider 174, 835, 906, 917, 924, 1299, 3257, 6204, 6939, 7720, 8860, 12186, 18041, 20473, 21700, 21738, 25369, 29802, 32097, 34549, 34927, 35133, 35661, 36369, 37988, 39409, 47787, 48070, 48605, 50917, 51519, 52210, 56655, 57695, 61138, 64289, 197216, 199545, 208453, 209022, 209533, 394177, 396998, 397423, 400304, 400587;
	route aspa 52210 provider 174, 835, 6939, 52025, 62513, 210667;
	route aspa 54148 provider 835, 924, 6939, 20473, 21738, 34927, 37988, 47272, 50917, 53616, 53667, 207841, 209022, 209735;
	route aspa 54218 provider 917, 16509, 35487, 37988, 53667, 57196, 59678;
	route aspa 56762 provider 47689;
	route aspa 57984 provider 8283, 48112, 206763;
	route aspa 59678 provider 6939, 34927, 54218, 55081;
	route aspa 60223 provider 945, 49581;
	route aspa 60431 provider 34927, 41495, 50917;
	route aspa 60841 provider 30456, 400304;
	route aspa 149301 provider 20473, 61138;
	route aspa 151642 provider 20473, 61138;
	route aspa 153176 provider 15353, 34872, 47311, 208210, 210233, 215084;
	route aspa 198136 provider 0;
	route aspa 199310 provider 44324, 134835, 138997, 139317, 202662, 204844, 212895;
	route aspa 199376 provider 47311, 61112, 150249, 151544, 199762, 209533, 215379;
	route aspa 199762 provider 945, 6939, 8849, 15353, 34927, 44817, 47311, 48605, 140731, 150249;
	route aspa 200160 provider 945, 6939, 15353, 21738, 29632, 34465, 34549, 34927, 36369, 48605, 140731, 150249, 199762, 203686, 203913, 210152, 210475, 400304, 400818;
	route aspa 200242 provider 835, 6939, 21738, 34465, 34927, 52025, 59678, 60841, 210475;
	route aspa 200351 provider 34927, 47272, 209022, 210475;
	route aspa 200454 provider 174, 6424, 6939, 9186, 12186;
	route aspa 202076 provider 207960;
	route aspa 202359 provider 30740, 33920, 41495;
	route aspa 202881 provider 205329;
	route aspa 203236 provider 20473, 44324, 200105, 201217, 203314;
	route aspa 203619 provider 200454;
	route aspa 203843 provider 6939, 20473, 53667;
	route aspa 204857 provider 50224, 59920, 60841, 400304;
	route aspa 204931 provider 34927, 44103, 207656;
	route aspa 205329 provider 945, 983, 6939, 7720, 8894, 9409, 20473, 34927, 41051, 131657, 134823, 151364;
	route aspa 205603 provider 38008, 38074, 59105;
	route aspa 205663 provider 20473, 61138;
	route aspa 205789 provider 200242, 207960;
	route aspa 205848 provider 1299, 6939, 34549, 34927, 52025, 209022;
	route aspa 207487 provider 20473;
	route aspa 207841 provider 6939, 137409;
	route aspa 207960 provider 6939, 20473, 34854, 34927, 136620, 202359, 207968;
	route aspa 209025 provider 945, 6939, 15353, 21738, 29632, 34465, 34549, 34927, 36369, 48605, 140731, 150249, 199762, 200160, 203686, 203913, 210152, 210475, 400304, 400818;
	route aspa 209735 provider 6939, 207841;
	route aspa 210561 provider 207960;
	route aspa 212068 provider 6939, 13237, 62240, 207960;
	route aspa 212245 provider 917, 57695, 212068;
	route aspa 212516 provider 3204, 6939, 41720;
	route aspa 212934 provider 835, 6939, 34927, 52210, 53667, 61138, 62513, 209533, 210667;
	route aspa 213086 provider 945, 6939, 15353, 21738, 29632, 34465, 34549, 34927, 36369, 48605, 140731, 150249, 199762, 200160, 203686, 203913, 207656, 210152, 210475, 400304, 400818;
	route aspa 213915 provider 215147, 215828;
	route aspa 214498 provider 16276, 207841;
	route aspa 215084 provider 6939, 20473, 34927, 207841, 212271, 393577;
	route aspa 215131 provider 8283, 34927, 212477;
	route aspa 215147 provider 47263, 207252, 213915, 215828;
	route aspa 215368 provider 207841;
	route aspa 215436 provider 6762, 6939, 51396, 60223;
	route aspa 215664 provider 1299, 3204, 6939, 34549, 41720, 56382, 203446, 212508;
	route aspa 215778 provider 8772, 29632, 39249, 41051, 207656, 209533;
	route aspa 215828 provider 6204, 6939, 8772, 29390, 29632, 34465, 34927, 39249, 41051, 44592, 48752, 50917, 51019, 51202, 58057, 62403, 152726, 197071, 199524, 203686, 207656, 209533, 210464, 215147, 216324, 216360, 393577;
	route aspa 215849 provider 215828;
	route aspa 216107 provider 924, 6939, 16509, 20473, 21738, 34927, 63473, 209022, 211588;
	route aspa 216265 provider 202673, 215051;
	route aspa 216311 provider 6939, 13852, 20473, 34927, 52025;
	route aspa 267386 provider 6939, 25933, 28220, 28649, 267613;
	route aspa 270470 provider 53062;
	route aspa 393577 provider 1299, 6939, 32097, 40676, 137409;
	route aspa 401111 provider 1012, 2497, 17676;
	route aspa 401507 provider 53356, 212477;
}
-------------- next part --------------
aspa table at;

protocol static
{
	aspa;
	route aspa 970 54874;
	route aspa 11358 835;
	route aspa 11358 924;
	route aspa 11358 6939;
	route aspa 11358 20473;
	route aspa 11358 34927;
	route aspa 13852 6939;
	route aspa 13852 20473;
	route aspa 13852 34927;
	route aspa 13852 52025;
	route aspa 13852 209533;
	route aspa 15562 2914;
	route aspa 15562 8283;
	route aspa 15562 51088;
	route aspa 15562 206238;
	route aspa 16909 20473;
	route aspa 16909 21738;
	route aspa 16909 47272;
	route aspa 16909 53356;
	route aspa 16909 53667;
	route aspa 16909 152900;
	route aspa 16909 212477;
	route aspa 19330 393577;
	route aspa 21957 970;
	route aspa 28584 28605;
	route aspa 33733 174;
	route aspa 33733 6939;
	route aspa 33733 12186;
	route aspa 33733 47787;
	route aspa 33733 200454;
	route aspa 40544 924;
	route aspa 40544 6939;
	route aspa 40544 12186;
	route aspa 40544 52025;
	route aspa 41720 174;
	route aspa 41720 1299;
	route aspa 41720 9002;
	route aspa 41720 50877;
	route aspa 41720 60068;
	route aspa 41720 203446;
	route aspa 41720 212508;
	route aspa 44324 945;
	route aspa 44324 955;
	route aspa 44324 1299;
	route aspa 44324 3204;
	route aspa 44324 6939;
	route aspa 44324 7720;
	route aspa 44324 8772;
	route aspa 44324 8849;
	route aspa 44324 15353;
	route aspa 44324 18041;
	route aspa 44324 20473;
	route aspa 44324 29632;
	route aspa 44324 32595;
	route aspa 44324 34465;
	route aspa 44324 34927;
	route aspa 44324 41051;
	route aspa 44324 43426;
	route aspa 44324 47272;
	route aspa 44324 48266;
	route aspa 44324 51087;
	route aspa 44324 53667;
	route aspa 44324 53808;
	route aspa 44324 59105;
	route aspa 44324 59538;
	route aspa 44324 61112;
	route aspa 44324 134823;
	route aspa 44324 134835;
	route aspa 44324 138997;
	route aspa 44324 139317;
	route aspa 44324 150452;
	route aspa 44324 151364;
	route aspa 44324 199545;
	route aspa 44324 199765;
	route aspa 44324 200105;
	route aspa 44324 206499;
	route aspa 44324 207656;
	route aspa 44324 207841;
	route aspa 44324 209554;
	route aspa 44324 209735;
	route aspa 44324 212483;
	route aspa 44324 213856;
	route aspa 44355 207841;
	route aspa 47272 174;
	route aspa 47272 835;
	route aspa 47272 924;
	route aspa 47272 1299;
	route aspa 47272 6830;
	route aspa 47272 6939;
	route aspa 47272 20473;
	route aspa 47272 21738;
	route aspa 47272 25759;
	route aspa 47272 34927;
	route aspa 47272 35133;
	route aspa 47272 41051;
	route aspa 47272 48605;
	route aspa 47272 50391;
	route aspa 47272 50917;
	route aspa 47272 52025;
	route aspa 47272 52210;
	route aspa 47272 58057;
	route aspa 47272 210667;
	route aspa 47272 212514;
	route aspa 47272 212895;
	route aspa 47689 924;
	route aspa 47689 1299;
	route aspa 47689 6939;
	route aspa 47689 21738;
	route aspa 47689 34465;
	route aspa 47689 34549;
	route aspa 47689 34927;
	route aspa 47689 395823;
	route aspa 48070 174;
	route aspa 48070 1299;
	route aspa 48606 30893;
	route aspa 48606 54681;
	route aspa 48606 57974;
	route aspa 48606 59678;
	route aspa 48606 210691;
	route aspa 48606 212276;
	route aspa 50224 6939;
	route aspa 50224 15353;
	route aspa 50224 26930;
	route aspa 50224 34465;
	route aspa 50224 37988;
	route aspa 50224 50104;
	route aspa 50224 52025;
	route aspa 50224 52210;
	route aspa 50224 59920;
	route aspa 50224 60841;
	route aspa 50224 210475;
	route aspa 50224 400304;
	route aspa 50391 6939;
	route aspa 50391 20473;
	route aspa 50391 48070;
	route aspa 50555 970;
	route aspa 51019 6939;
	route aspa 51019 34549;
	route aspa 51019 34927;
	route aspa 51019 52025;
	route aspa 51019 207841;
	route aspa 51396 6939;
	route aspa 51396 30823;
	route aspa 51396 35133;
	route aspa 51396 44066;
	route aspa 51396 44592;
	route aspa 51396 49581;
	route aspa 51396 60223;
	route aspa 51396 64457;
	route aspa 51396 203446;
	route aspa 51396 214497;
	route aspa 51396 214995;
	route aspa 51396 215436;
	route aspa 52025 174;
	route aspa 52025 835;
	route aspa 52025 906;
	route aspa 52025 917;
	route aspa 52025 924;
	route aspa 52025 1299;
	route aspa 52025 3257;
	route aspa 52025 6204;
	route aspa 52025 6939;
	route aspa 52025 7720;
	route aspa 52025 8860;
	route aspa 52025 12186;
	route aspa 52025 18041;
	route aspa 52025 20473;
	route aspa 52025 21700;
	route aspa 52025 21738;
	route aspa 52025 25369;
	route aspa 52025 29802;
	route aspa 52025 32097;
	route aspa 52025 34549;
	route aspa 52025 34927;
	route aspa 52025 35133;
	route aspa 52025 35661;
	route aspa 52025 36369;
	route aspa 52025 37988;
	route aspa 52025 39409;
	route aspa 52025 47787;
	route aspa 52025 48070;
	route aspa 52025 48605;
	route aspa 52025 50917;
	route aspa 52025 51519;
	route aspa 52025 52210;
	route aspa 52025 56655;
	route aspa 52025 57695;
	route aspa 52025 61138;
	route aspa 52025 64289;
	route aspa 52025 197216;
	route aspa 52025 199545;
	route aspa 52025 208453;
	route aspa 52025 209022;
	route aspa 52025 209533;
	route aspa 52025 394177;
	route aspa 52025 396998;
	route aspa 52025 397423;
	route aspa 52025 400304;
	route aspa 52025 400587;
	route aspa 52210 174;
	route aspa 52210 835;
	route aspa 52210 6939;
	route aspa 52210 52025;
	route aspa 52210 62513;
	route aspa 52210 210667;
	route aspa 54148 835;
	route aspa 54148 924;
	route aspa 54148 6939;
	route aspa 54148 20473;
	route aspa 54148 21738;
	route aspa 54148 34927;
	route aspa 54148 37988;
	route aspa 54148 47272;
	route aspa 54148 50917;
	route aspa 54148 53616;
	route aspa 54148 53667;
	route aspa 54148 207841;
	route aspa 54148 209022;
	route aspa 54148 209735;
	route aspa 54218 917;
	route aspa 54218 16509;
	route aspa 54218 35487;
	route aspa 54218 37988;
	route aspa 54218 53667;
	route aspa 54218 57196;
	route aspa 54218 59678;
	route aspa 56762 47689;
	route aspa 57984 8283;
	route aspa 57984 48112;
	route aspa 57984 206763;
	route aspa 59678 6939;
	route aspa 59678 34927;
	route aspa 59678 54218;
	route aspa 59678 55081;
	route aspa 60223 945;
	route aspa 60223 49581;
	route aspa 60431 34927;
	route aspa 60431 41495;
	route aspa 60431 50917;
	route aspa 60841 30456;
	route aspa 60841 400304;
	route aspa 149301 20473;
	route aspa 149301 61138;
	route aspa 151642 20473;
	route aspa 151642 61138;
	route aspa 153176 15353;
	route aspa 153176 34872;
	route aspa 153176 47311;
	route aspa 153176 208210;
	route aspa 153176 210233;
	route aspa 153176 215084;
	route aspa 198136 0;
	route aspa 199310 44324;
	route aspa 199310 134835;
	route aspa 199310 138997;
	route aspa 199310 139317;
	route aspa 199310 202662;
	route aspa 199310 204844;
	route aspa 199310 212895;
	route aspa 199376 47311;
	route aspa 199376 61112;
	route aspa 199376 150249;
	route aspa 199376 151544;
	route aspa 199376 199762;
	route aspa 199376 209533;
	route aspa 199376 215379;
	route aspa 199762 945;
	route aspa 199762 6939;
	route aspa 199762 8849;
	route aspa 199762 15353;
	route aspa 199762 34927;
	route aspa 199762 44817;
	route aspa 199762 47311;
	route aspa 199762 48605;
	route aspa 199762 140731;
	route aspa 199762 150249;
	route aspa 200160 945;
	route aspa 200160 6939;
	route aspa 200160 15353;
	route aspa 200160 21738;
	route aspa 200160 29632;
	route aspa 200160 34465;
	route aspa 200160 34549;
	route aspa 200160 34927;
	route aspa 200160 36369;
	route aspa 200160 48605;
	route aspa 200160 140731;
	route aspa 200160 150249;
	route aspa 200160 199762;
	route aspa 200160 203686;
	route aspa 200160 203913;
	route aspa 200160 210152;
	route aspa 200160 210475;
	route aspa 200160 400304;
	route aspa 200160 400818;
	route aspa 200242 835;
	route aspa 200242 6939;
	route aspa 200242 21738;
	route aspa 200242 34465;
	route aspa 200242 34927;
	route aspa 200242 52025;
	route aspa 200242 59678;
	route aspa 200242 60841;
	route aspa 200242 210475;
	route aspa 200351 34927;
	route aspa 200351 47272;
	route aspa 200351 209022;
	route aspa 200351 210475;
	route aspa 200454 174;
	route aspa 200454 6424;
	route aspa 200454 6939;
	route aspa 200454 9186;
	route aspa 200454 12186;
	route aspa 202076 207960;
	route aspa 202359 30740;
	route aspa 202359 33920;
	route aspa 202359 41495;
	route aspa 202881 205329;
	route aspa 203236 20473;
	route aspa 203236 44324;
	route aspa 203236 200105;
	route aspa 203236 201217;
	route aspa 203236 203314;
	route aspa 203619 200454;
	route aspa 203843 6939;
	route aspa 203843 20473;
	route aspa 203843 53667;
	route aspa 204857 50224;
	route aspa 204857 59920;
	route aspa 204857 60841;
	route aspa 204857 400304;
	route aspa 204931 34927;
	route aspa 204931 44103;
	route aspa 204931 207656;
	route aspa 205329 945;
	route aspa 205329 983;
	route aspa 205329 6939;
	route aspa 205329 7720;
	route aspa 205329 8894;
	route aspa 205329 9409;
	route aspa 205329 20473;
	route aspa 205329 34927;
	route aspa 205329 41051;
	route aspa 205329 131657;
	route aspa 205329 134823;
	route aspa 205329 151364;
	route aspa 205603 38008;
	route aspa 205603 38074;
	route aspa 205603 59105;
	route aspa 205663 20473;
	route aspa 205663 61138;
	route aspa 205789 200242;
	route aspa 205789 207960;
	route aspa 205848 1299;
	route aspa 205848 6939;
	route aspa 205848 34549;
	route aspa 205848 34927;
	route aspa 205848 52025;
	route aspa 205848 209022;
	route aspa 207487 20473;
	route aspa 207841 6939;
	route aspa 207841 137409;
	route aspa 207960 6939;
	route aspa 207960 20473;
	route aspa 207960 34854;
	route aspa 207960 34927;
	route aspa 207960 136620;
	route aspa 207960 202359;
	route aspa 207960 207968;
	route aspa 209025 945;
	route aspa 209025 6939;
	route aspa 209025 15353;
	route aspa 209025 21738;
	route aspa 209025 29632;
	route aspa 209025 34465;
	route aspa 209025 34549;
	route aspa 209025 34927;
	route aspa 209025 36369;
	route aspa 209025 48605;
	route aspa 209025 140731;
	route aspa 209025 150249;
	route aspa 209025 199762;
	route aspa 209025 200160;
	route aspa 209025 203686;
	route aspa 209025 203913;
	route aspa 209025 210152;
	route aspa 209025 210475;
	route aspa 209025 400304;
	route aspa 209025 400818;
	route aspa 209735 6939;
	route aspa 209735 207841;
	route aspa 210561 207960;
	route aspa 212068 6939;
	route aspa 212068 13237;
	route aspa 212068 62240;
	route aspa 212068 207960;
	route aspa 212245 917;
	route aspa 212245 57695;
	route aspa 212245 212068;
	route aspa 212516 3204;
	route aspa 212516 6939;
	route aspa 212516 41720;
	route aspa 212934 835;
	route aspa 212934 6939;
	route aspa 212934 34927;
	route aspa 212934 52210;
	route aspa 212934 53667;
	route aspa 212934 61138;
	route aspa 212934 62513;
	route aspa 212934 209533;
	route aspa 212934 210667;
	route aspa 213086 945;
	route aspa 213086 6939;
	route aspa 213086 15353;
	route aspa 213086 21738;
	route aspa 213086 29632;
	route aspa 213086 34465;
	route aspa 213086 34549;
	route aspa 213086 34927;
	route aspa 213086 36369;
	route aspa 213086 48605;
	route aspa 213086 140731;
	route aspa 213086 150249;
	route aspa 213086 199762;
	route aspa 213086 200160;
	route aspa 213086 203686;
	route aspa 213086 203913;
	route aspa 213086 207656;
	route aspa 213086 210152;
	route aspa 213086 210475;
	route aspa 213086 400304;
	route aspa 213086 400818;
	route aspa 213915 215147;
	route aspa 213915 215828;
	route aspa 214498 16276;
	route aspa 214498 207841;
	route aspa 215084 6939;
	route aspa 215084 20473;
	route aspa 215084 34927;
	route aspa 215084 207841;
	route aspa 215084 212271;
	route aspa 215084 393577;
	route aspa 215131 8283;
	route aspa 215131 34927;
	route aspa 215131 212477;
	route aspa 215147 47263;
	route aspa 215147 207252;
	route aspa 215147 213915;
	route aspa 215147 215828;
	route aspa 215368 207841;
	route aspa 215436 6762;
	route aspa 215436 6939;
	route aspa 215436 51396;
	route aspa 215436 60223;
	route aspa 215664 1299;
	route aspa 215664 3204;
	route aspa 215664 6939;
	route aspa 215664 34549;
	route aspa 215664 41720;
	route aspa 215664 56382;
	route aspa 215664 203446;
	route aspa 215664 212508;
	route aspa 215778 8772;
	route aspa 215778 29632;
	route aspa 215778 39249;
	route aspa 215778 41051;
	route aspa 215778 207656;
	route aspa 215778 209533;
	route aspa 215828 6204;
	route aspa 215828 6939;
	route aspa 215828 8772;
	route aspa 215828 29390;
	route aspa 215828 29632;
	route aspa 215828 34465;
	route aspa 215828 34927;
	route aspa 215828 39249;
	route aspa 215828 41051;
	route aspa 215828 44592;
	route aspa 215828 48752;
	route aspa 215828 50917;
	route aspa 215828 51019;
	route aspa 215828 51202;
	route aspa 215828 58057;
	route aspa 215828 62403;
	route aspa 215828 152726;
	route aspa 215828 197071;
	route aspa 215828 199524;
	route aspa 215828 203686;
	route aspa 215828 207656;
	route aspa 215828 209533;
	route aspa 215828 210464;
	route aspa 215828 215147;
	route aspa 215828 216324;
	route aspa 215828 216360;
	route aspa 215828 393577;
	route aspa 215849 215828;
	route aspa 216107 924;
	route aspa 216107 6939;
	route aspa 216107 16509;
	route aspa 216107 20473;
	route aspa 216107 21738;
	route aspa 216107 34927;
	route aspa 216107 63473;
	route aspa 216107 209022;
	route aspa 216107 211588;
	route aspa 216265 202673;
	route aspa 216265 215051;
	route aspa 216311 6939;
	route aspa 216311 13852;
	route aspa 216311 20473;
	route aspa 216311 34927;
	route aspa 216311 52025;
	route aspa 267386 6939;
	route aspa 267386 25933;
	route aspa 267386 28220;
	route aspa 267386 28649;
	route aspa 267386 267613;
	route aspa 270470 53062;
	route aspa 393577 1299;
	route aspa 393577 6939;
	route aspa 393577 32097;
	route aspa 393577 40676;
	route aspa 393577 137409;
	route aspa 401111 1012;
	route aspa 401111 2497;
	route aspa 401111 17676;
	route aspa 401507 53356;
	route aspa 401507 212477;
}


More information about the Bird-users mailing list