diff --git a/README.md b/README.md
index e05d1e7..f84ad1b 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
     <img src="https://repology.org/badge/vertical-allrepos/stayrtr.svg" alt="Packaging status" align="right">
 </a>
 
-StayRTR is an open-source implementation of RPKI-to-Router protocol (RFC 6810, RFC 8210); based on GoRTR using the [the Go Programming Language](http://golang.org/).
+StayRTR is an open-source implementation of RPKI-to-Router protocol (RFC 6810, RFC 8210, RFC 8210bis); based on GoRTR using the [the Go Programming Language](http://golang.org/).
 
 * `/lib` contains a library to create your own server and client.
 * `/prefixfile` contains the structure of a JSON export file and signing capabilities.
@@ -27,7 +27,7 @@ Special thanks for support to the Route Server Support Foundation [RSSF](https:/
 
 ## Features of the server
 
-* Dissemination of validated ROA and BGPsec payloads
+* Dissemination of validated ROA, BGPsec, and ASPA payloads
 * Refreshes a JSON list of prefixes
 * Automatic expiration of outdated information (when using JSON produced by [rpki-client](https://www.rpki-client.org))
 * Prometheus metrics
@@ -45,6 +45,7 @@ Special thanks for support to the Route Server Support Foundation [RSSF](https:/
 
 * Protocol v0 of [RFC6810](https://tools.ietf.org/html/rfc6810)
 * Protocol v1 of [RFC8210](https://tools.ietf.org/html/rfc8210)
+* Protocol v2 of [draft-ietf-sidrops-8210bis-25](https://tools.ietf.org/html/draft-ietf-sidrops-8210bis-25)
 * Event-driven API
 * TLS
 * SSH
diff --git a/cmd/rtrdump/rtrdump.go b/cmd/rtrdump/rtrdump.go
index 1e959d0..89ba30e 100644
--- a/cmd/rtrdump/rtrdump.go
+++ b/cmd/rtrdump/rtrdump.go
@@ -4,6 +4,7 @@ import (
 	"crypto/tls"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
 	"flag"
 	"fmt"
 	"io"
@@ -36,7 +37,7 @@ var (
 	Serial     = flag.Int("serial.value", 0, "Serial number")
 	Session    = flag.Int("session.id", 0, "Session ID")
 
-	FlagVersion = flag.Int("rtr.version", 1, "What RTR version you want to use, Version 1 is RFC8210")
+	FlagVersion = flag.Int("rtr.version", 2, "RTR version to use. Version 2 conforms to draft-ietf-sidrops-8210bis-25")
 
 	ConnType     = flag.String("type", "plain", "Type of connection: plain, tls or ssh")
 	ValidateCert = flag.Bool("tls.validate", true, "Validate TLS")
@@ -109,6 +110,21 @@ func (c *Client) HandlePDU(cs *rtr.ClientSession, pdu rtr.PDU) {
 		}
 		c.Data.BgpSecKeys = append(c.Data.BgpSecKeys, rj)
 
+		if *LogDataPDU {
+			log.Debugf("Received: %v", pdu)
+		}
+
+	case *rtr.PDUASPA:
+		if c.Data.ASPA == nil {
+			c.Data.ASPA = make([]prefixfile.VAPJson, 0)
+		}
+		aj := prefixfile.VAPJson{
+			CustomerAsid: pdu.CustomerASNumber,
+			Providers:    pdu.ProviderASNumbers,
+		}
+
+		c.Data.ASPA = append(c.Data.ASPA, aj)
+
 		if *LogDataPDU {
 			log.Debugf("Received: %v", pdu)
 		}
@@ -149,11 +165,13 @@ func main() {
 	}
 
 	targetVersion := rtr.PROTOCOL_VERSION_0
-	if *FlagVersion > 1 {
-		log.Fatalf("Invalid RTR Version provided, the highest version this release supports is 1")
+	if *FlagVersion > 2 {
+		log.Fatalf("Invalid RTR Version provided, the highest version this release supports is 2")
 	}
 	if *FlagVersion == 1 {
 		targetVersion = rtr.PROTOCOL_VERSION_1
+	} else if *FlagVersion == 2 {
+		targetVersion = rtr.PROTOCOL_VERSION_2
 	}
 
 	lvl, _ := log.ParseLevel(*LogLevel)
@@ -225,6 +243,9 @@ func main() {
 	log.Infof("Connecting with %v to %v", *ConnType, *Connect)
 	err := clientSession.Start(*Connect, typeToId[*ConnType], configTLS, configSSH)
 	if err != nil {
+		if errors.Is(err, io.EOF) && targetVersion == rtr.PROTOCOL_VERSION_2 {
+			log.Warnf("EOF From remote side, This might be due to version 2 being requested, try using -rtr.version 1")
+		}
 		log.Fatal(err)
 	}
 
diff --git a/cmd/stayrtr/stayrtr.go b/cmd/stayrtr/stayrtr.go
index e2d8a23..565d7e6 100644
--- a/cmd/stayrtr/stayrtr.go
+++ b/cmd/stayrtr/stayrtr.go
@@ -55,13 +55,14 @@ var (
 	ExportPath           = flag.String("export.path", "/rpki.json", "Export path")
 	EnableUpdateEndpoint = flag.Bool("update.endpoint", false, "Enable HTTP endpoint that expedites the next fetch")
 
-	RTRVersion     = flag.Int("protocol", 1, "RTR protocol version. Default is version 1 (RFC 8210)")
+	RTRVersion     = flag.Int("protocol", 2, "RTR protocol version. Default is version 2 (draft-ietf-sidrops-8210bis-25)")
 	RefreshRTR     = flag.Int("rtr.refresh", 3600, "Refresh interval")
 	RetryRTR       = flag.Int("rtr.retry", 600, "Retry interval")
 	ExpireRTR      = flag.Int("rtr.expire", 7200, "Expire interval")
 	SendNotifs     = flag.Bool("notifications", true, "Send notifications to clients (disable with -notifications=false)")
 	EnforceVersion = flag.Bool("enforce.version", false, "Disable version negotiation")
 	DisableBGPSec  = flag.Bool("disable.bgpsec", false, "Disable sending out BGPSEC Router Keys")
+	DisableASPA    = flag.Bool("disable.aspa", false, "Disable sending out ASPA objects")
 	EnableNODELAY  = flag.Bool("enable.nodelay", false, "Force enable TCP NODELAY (Likely increases CPU)")
 
 	Bind = flag.String("bind", ":8282", "Bind address")
@@ -104,6 +105,7 @@ var (
 	protoverToLib = map[int]uint8{
 		0: rtr.PROTOCOL_VERSION_0,
 		1: rtr.PROTOCOL_VERSION_1,
+		2: rtr.PROTOCOL_VERSION_2,
 	}
 )
 
@@ -146,9 +148,10 @@ func isValidPrefixLength(prefix netip.Prefix, maxLength uint8) bool {
 // 1 - The prefix is a valid prefix
 // 2 - The ASN is a valid ASN
 // 3 - The MaxLength is valid
-// Will return a deduped slice, as well as total VRPs, IPv4 VRPs, IPv6 VRPs and BGPsec Keys
+// Will return a deduped slice, as well as total VRPs, IPv4 VRPs, IPv6 VRPs, BGPsec Keys and ASPA records
 func processData(vrplistjson []prefixfile.VRPJson,
-	brklistjson []prefixfile.BgpSecKeyJson) /*Export*/ ([]rtr.VRP, []rtr.BgpsecKey, int, int) {
+	brklistjson []prefixfile.BgpSecKeyJson,
+	aspajson []prefixfile.VAPJson) /*Export*/ ([]rtr.VRP, []rtr.BgpsecKey, []rtr.VAP, int, int) {
 	filterDuplicates := make(map[string]struct{})
 
 	// It may be tempting to change this to a simple time.Since() but that will
@@ -159,6 +162,7 @@ func processData(vrplistjson []prefixfile.VRPJson,
 
 	var vrplist []rtr.VRP
 	var brklist = make([]rtr.BgpsecKey, 0)
+	var aspalist = make([]rtr.VAP, 0)
 	var countv4 int
 	var countv6 int
 
@@ -259,7 +263,27 @@ func processData(vrplistjson []prefixfile.VRPJson,
 		})
 	}
 
-	return vrplist, brklist, countv4, countv6
+	for _, v := range aspajson {
+		if v.Expires != nil {
+			if NowUnix > *v.Expires {
+				continue
+			}
+		}
+
+		// Ensure that these are sorted, otherwise they
+		// don't hash right. Also, the standard requests it:
+		// https://www.ietf.org/archive/id/draft-ietf-sidrops-8210bis-25.html#name-aspa-pdus
+		sort.Slice(v.Providers, func(i, j int) bool {
+			return v.Providers[i] < v.Providers[j]
+		})
+
+		aspalist = append(aspalist, rtr.VAP{
+			CustomerASN: v.CustomerAsid,
+			Providers:   v.Providers,
+		})
+	}
+
+	return vrplist, brklist, aspalist, countv4, countv6
 }
 
 type IdenticalFile struct {
@@ -282,6 +306,10 @@ func (s *state) updateFromNewState() error {
 	if bgpsecjson == nil {
 		bgpsecjson = make([]prefixfile.BgpSecKeyJson, 0)
 	}
+	aspajson := s.lastdata.ASPA
+	if aspajson == nil {
+		aspajson = make([]prefixfile.VAPJson, 0)
+	}
 
 	buildtime, err := time.Parse(time.RFC3339, s.lastdata.Metadata.Buildtime)
 	if s.lastdata.Metadata.GeneratedUnix != nil {
@@ -299,14 +327,14 @@ func (s *state) updateFromNewState() error {
 	}
 
 	if s.slurm != nil {
-		vrpsjson, bgpsecjson = s.slurm.FilterAssert(vrpsjson, bgpsecjson, log.StandardLogger())
+		vrpsjson, aspajson, bgpsecjson = s.slurm.FilterAssert(vrpsjson, aspajson, bgpsecjson, log.StandardLogger())
 	}
 
-	vrps, brks, countv4, countv6 := processData(vrpsjson, bgpsecjson)
-	count := len(vrps) + len(brks)
+	vrps, brks, vaps, countv4, countv6 := processData(vrpsjson, bgpsecjson, aspajson)
+	count := len(vrps) + len(brks) + len(vaps)
 
-	log.Infof("New update (%v uniques, %v total prefixes, %v router keys).", len(vrps), count, len(brks))
-	return s.applyUpdateFromNewState(vrps, brks, vrpsjson, bgpsecjson, countv4, countv6)
+	log.Infof("New update (%v uniques, %v total prefixes, %v vaps, %v router keys).", len(vrps), count, len(vaps), len(brks))
+	return s.applyUpdateFromNewState(vrps, brks, vaps, vrpsjson, bgpsecjson, aspajson, countv4, countv6)
 }
 
 // Update the state based on the currently loaded files
@@ -319,6 +347,10 @@ func (s *state) reloadFromCurrentState() error {
 	if bgpsecjson == nil {
 		bgpsecjson = make([]prefixfile.BgpSecKeyJson, 0)
 	}
+	aspajson := s.lastdata.ASPA
+	if aspajson == nil {
+		aspajson = make([]prefixfile.VAPJson, 0)
+	}
 
 	buildtime, err := time.Parse(time.RFC3339, s.lastdata.Metadata.Buildtime)
 	if s.lastdata.Metadata.GeneratedUnix != nil {
@@ -336,29 +368,32 @@ func (s *state) reloadFromCurrentState() error {
 	}
 
 	if s.slurm != nil {
-		vrpsjson, bgpsecjson = s.slurm.FilterAssert(vrpsjson, bgpsecjson, log.StandardLogger())
+		vrpsjson, aspajson, bgpsecjson = s.slurm.FilterAssert(vrpsjson, aspajson, bgpsecjson, log.StandardLogger())
 	}
 
-	vrps, brks, countv4, countv6 := processData(vrpsjson, bgpsecjson)
-	count := len(vrps) + len(brks)
+	vrps, brks, vaps, countv4, countv6 := processData(vrpsjson, bgpsecjson, aspajson)
+	count := len(vrps) + len(brks) + len(vaps)
 	if s.server.CountSDs() != count {
 		log.Infof("New update to old state (%v uniques, %v total prefixes). (old %v - new %v)", len(vrps), count, s.server.CountSDs(), count)
-		return s.applyUpdateFromNewState(vrps, brks, vrpsjson, bgpsecjson, countv4, countv6)
+		return s.applyUpdateFromNewState(vrps, brks, vaps, vrpsjson, bgpsecjson, aspajson, countv4, countv6)
 	}
 	return nil
 }
 
-func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, brks []rtr.BgpsecKey,
-	vrpsjson []prefixfile.VRPJson, brksjson []prefixfile.BgpSecKeyJson,
+func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, brks []rtr.BgpsecKey, vaps []rtr.VAP,
+	vrpsjson []prefixfile.VRPJson, brksjson []prefixfile.BgpSecKeyJson, aspajson []prefixfile.VAPJson,
 	countv4 int, countv6 int) error {
 
-	SDs := make([]rtr.SendableData, 0, len(vrps)+len(brks))
+	SDs := make([]rtr.SendableData, 0, len(vrps)+len(brks)+len(vaps))
 	for _, v := range vrps {
 		SDs = append(SDs, v.Copy())
 	}
 	for _, v := range brks {
 		SDs = append(SDs, v.Copy())
 	}
+	for _, v := range vaps {
+		SDs = append(SDs, v.Copy())
+	}
 	if !s.server.AddData(SDs) {
 		log.Info("No difference to current cache")
 		return nil
@@ -380,6 +415,7 @@ func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, brks []rtr.BgpsecKey,
 		},
 		ROA:        vrpsjson,
 		BgpSecKeys: brksjson,
+		ASPA:       aspajson,
 	}
 	s.lockJson.Unlock()
 
@@ -393,7 +429,7 @@ func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, brks []rtr.BgpsecKey,
 				countv6_dup++
 			}
 		}
-		s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastchange, s.lastts, *CacheBin, len(brks))
+		s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastchange, s.lastts, *CacheBin, len(brks), len(vaps))
 	}
 
 	return nil
@@ -658,7 +694,8 @@ func (m *metricsEvent) HandlePDU(c *rtr.Client, pdu rtr.PDU) {
 				"_", -1))).Inc()
 }
 
-func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, changed time.Time, refreshed time.Time, file string, brkCount int) {
+func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, changed time.Time, refreshed time.Time, file string, brkCount int, aspaCount int) {
+	server_metrics.NumberOfObjects.WithLabelValues("vaps").Set(float64(aspaCount))
 	server_metrics.NumberOfObjects.WithLabelValues("bgpsec_pubkeys").Set(float64(brkCount))
 	server_metrics.NumberOfObjects.WithLabelValues("vrps").Set(float64(numIPv4 + numIPv6))
 	server_metrics.NumberOfObjects.WithLabelValues("effective_vrps").Set(float64(numIPv4filtered + numIPv6filtered))
@@ -713,6 +750,7 @@ func run() error {
 
 		EnforceVersion: *EnforceVersion,
 		DisableBGPSec:  *DisableBGPSec,
+		DisableASPA:    *DisableASPA,
 		EnableNODELAY:  *EnableNODELAY,
 	}
 
diff --git a/cmd/stayrtr/stayrtr_test.go b/cmd/stayrtr/stayrtr_test.go
index cbb8a70..5c54df3 100644
--- a/cmd/stayrtr/stayrtr_test.go
+++ b/cmd/stayrtr/stayrtr_test.go
@@ -100,7 +100,7 @@ func TestProcessData(t *testing.T) {
 			Expires: &ExpiredTime,
 		},
 	)
-	got, _, v4count, v6count := processData(stuff, nil)
+	got, _, _, v4count, v6count := processData(stuff, nil, nil)
 	want := []rtr.VRP{
 		{
 			Prefix: netip.MustParsePrefix("2001:db8::/32"),
diff --git a/lib/client.go b/lib/client.go
index bffa9de..a8d3b05 100644
--- a/lib/client.go
+++ b/lib/client.go
@@ -116,11 +116,19 @@ func (c *ClientSession) StartRW(rd io.Reader, wr io.Writer) error {
 			c.Disconnect()
 			return err
 		}
-		if c.version == PROTOCOL_VERSION_1 && dec.GetVersion() == PROTOCOL_VERSION_0 {
-			if c.log != nil {
-				c.log.Infof("Downgrading to version 0")
+		if c.version > PROTOCOL_VERSION_0 {
+			downgraded := false
+			switch dec.GetVersion() {
+			case PROTOCOL_VERSION_0:
+				c.version = PROTOCOL_VERSION_0
+				downgraded = true
+			case PROTOCOL_VERSION_1:
+				c.version = PROTOCOL_VERSION_1
+				downgraded = true
+			}
+			if c.log != nil && downgraded {
+				c.log.Infof("Downgrading to version %d", c.version)
 			}
-			c.version = PROTOCOL_VERSION_0
 		}
 
 		if c.handler != nil {
diff --git a/lib/client_test.go b/lib/client_test.go
index 6988ece..77aab61 100644
--- a/lib/client_test.go
+++ b/lib/client_test.go
@@ -62,6 +62,10 @@ func TestSendResetQuery(t *testing.T) {
 		desc:    "Reset Query, Version 1",
 		version: 1,
 		want:    &PDUResetQuery{PROTOCOL_VERSION_1},
+	}, {
+		desc:    "Reset Query, Version 2",
+		version: 2,
+		want:    &PDUResetQuery{PROTOCOL_VERSION_2},
 	}}
 
 	for _, tc := range tests {
@@ -83,9 +87,13 @@ func TestSendSerialQuery(t *testing.T) {
 		version int
 		want    PDU
 	}{{
-		desc:    "Serial Query PDU",
+		desc:    "Serial Query PDU, Version 1",
 		version: 1,
 		want:    &PDUSerialQuery{PROTOCOL_VERSION_1, 123, 456},
+	}, {
+		desc:    "Serial Query PDU, Version 2",
+		version: 2,
+		want:    &PDUSerialQuery{PROTOCOL_VERSION_2, 123, 456},
 	}}
 
 	for _, tc := range tests {
@@ -126,3 +134,66 @@ func TestRouterKeyEncodeDecode(t *testing.T) {
 		t.FailNow()
 	}
 }
+
+func TestASPAEncodeDecode(t *testing.T) {
+	p := &PDUASPA{
+		Version:           1,
+		Flags:             1,
+		CustomerASNumber:  64497,
+		ProviderASNumbers: []uint32{64498, 64499},
+	}
+
+	buf := bytes.NewBuffer(nil)
+	p.Write(buf)
+
+	outputPdu, err := Decode(buf)
+
+	if err != nil {
+		t.FailNow()
+	}
+
+	orig := fmt.Sprintf("%#v", p)
+	decode := fmt.Sprintf("%#v", outputPdu)
+	if orig != decode {
+		t.Fatalf("%s\n is not\n%s", orig, decode)
+		t.FailNow()
+	}
+}
+
+func TestASPAProvider0(t *testing.T) {
+	p := &PDUASPA{
+		Version:           2,
+		Flags:             1,
+		CustomerASNumber:  64497,
+		ProviderASNumbers: []uint32{0, 64498},
+	}
+
+	buf := bytes.NewBuffer(nil)
+	p.Write(buf)
+
+	outputPdu, err := Decode(buf)
+
+	if err == nil {
+		t.Fatalf("PDU %s contains 0 and another ASN but no error raised\n", fmt.Sprintf("%#v", outputPdu))
+		t.FailNow()
+	}
+}
+
+func TestASPAUnsortedProviders(t *testing.T) {
+	p := &PDUASPA{
+		Version:           2,
+		Flags:             1,
+		CustomerASNumber:  64497,
+		ProviderASNumbers: []uint32{65000, 64498},
+	}
+
+	buf := bytes.NewBuffer(nil)
+	p.Write(buf)
+
+	outputPdu, err := Decode(buf)
+
+	if err == nil {
+		t.Fatalf("PDU %s is unsorted but no error raised\n", fmt.Sprintf("%#v", outputPdu))
+		t.FailNow()
+	}
+}
diff --git a/lib/server.go b/lib/server.go
index 3e9a8ac..ec79e76 100644
--- a/lib/server.go
+++ b/lib/server.go
@@ -13,6 +13,7 @@ import (
 	"net/http"
 	"net/netip"
 	"sync"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/sync/errgroup"
@@ -33,7 +34,7 @@ type RTREventHandler interface {
 	RequestNewVersion(*Client, uint16, uint32)
 }
 
-// This is a general interface for things like a VRP or BGPsec Router key
+// This is a general interface for things like a VRP, BGPsec Router key or ASPA object
 // Be sure to have all of these as pointers, or SetFlag() cannot work!
 type SendableData interface {
 	Copy() SendableData
@@ -45,7 +46,7 @@ type SendableData interface {
 	GetFlag() uint8
 }
 
-// This handles things like ROAs, BGPsec Router keys info etc
+// This handles things like ROAs, BGPsec Router keys, ASPA info etc
 type SendableDataManager interface {
 	GetCurrentSerial() (uint32, bool)
 	GetSessionId(uint8) uint16
@@ -138,6 +139,7 @@ type Server struct {
 	simpleHandler  RTREventHandler
 	enforceVersion bool
 	disableBGPSec  bool
+	disableASPA    bool
 	enableNODELAY  bool
 
 	sdlock          *sync.RWMutex
@@ -163,6 +165,7 @@ type ServerConfiguration struct {
 	SessId int
 
 	DisableBGPSec bool
+	DisableASPA   bool
 	EnableNODELAY bool
 
 	RefreshInterval uint32
@@ -207,6 +210,7 @@ func NewServer(configuration ServerConfiguration, handler RTRServerEventHandler,
 
 		enforceVersion: configuration.EnforceVersion,
 		disableBGPSec:  configuration.DisableBGPSec,
+		disableASPA:    configuration.DisableASPA,
 
 		pduRefreshInterval: refreshInterval,
 		pduRetryInterval:   retryInterval,
@@ -516,6 +520,9 @@ func (s *Server) acceptClientTCP(tcpconn net.Conn) error {
 	if s.disableBGPSec {
 		client.DisableBGPsec()
 	}
+	if s.disableASPA {
+		client.DisableASPA()
+	}
 	go client.Start()
 	return nil
 }
@@ -688,6 +695,7 @@ type Client struct {
 	expireInterval  uint32
 
 	dontSendBGPsecKeys bool
+	dontSendASPA       bool
 
 	log Logger
 }
@@ -712,6 +720,10 @@ func (c *Client) DisableBGPsec() {
 	c.dontSendBGPsecKeys = true
 }
 
+func (c *Client) DisableASPA() {
+	c.dontSendASPA = true
+}
+
 func (c *Client) SetIntervals(refreshInterval uint32, retryInterval uint32, expireInterval uint32) {
 	c.refreshInterval = refreshInterval
 	c.retryInterval = retryInterval
@@ -728,13 +740,24 @@ func (c *Client) SetDisableVersionCheck(disableCheck bool) {
 }
 
 func (c *Client) checkVersion(newversion uint8) error {
-	if (!c.versionset || newversion == c.version) && (newversion == PROTOCOL_VERSION_1 || newversion == PROTOCOL_VERSION_0) {
+	if (!c.versionset || newversion == c.version) && (newversion == PROTOCOL_VERSION_2 || newversion == PROTOCOL_VERSION_1 || newversion == PROTOCOL_VERSION_0) {
 		c.SetVersion(newversion)
 	} else {
 		if c.log != nil {
 			c.log.Debugf("%v: has bad version (received: v%v, current: v%v) error", c.String(), newversion, c.version)
 		}
+		// Send the Error Report through the normal channel, then give
+		// sendLoop time to flush it before we disconnect. The error
+		// report must use the server's highest supported version so the
+		// client knows what to downgrade to (RFC 8210 §7).
+		if !c.versionset {
+			c.SetVersion(PROTOCOL_VERSION_1)
+		}
 		c.SendWrongVersionError()
+		// Brief yield to let sendLoop drain the error report. Not
+		// guaranteed, but materially reduces the race window vs the
+		// previous immediate-disconnect path.
+		time.Sleep(50 * time.Millisecond)
 		c.Disconnect()
 		return fmt.Errorf("%v: has bad version (received: v%v, current: v%v)", c.String(), newversion, c.version)
 	}
@@ -805,7 +828,11 @@ func (c *Client) readLoop(ctx context.Context) error {
 					if c.log != nil {
 						c.log.Debugf("Bad version error")
 					}
+					// c.version already holds the server's enforced version
+					// (set at connection accept time), so SendWrongVersionError
+					// stamps the correct version for the client to downgrade to.
 					c.SendWrongVersionError()
+					time.Sleep(50 * time.Millisecond)
 					c.Disconnect()
 					return fmt.Errorf("%s: bad version error", c.String())
 				}
@@ -948,6 +975,51 @@ func (brk *BgpsecKey) GetFlag() uint8 {
 	return brk.Flags
 }
 
+type VAP struct {
+	Flags       uint8
+	CustomerASN uint32
+	Providers   []uint32
+}
+
+func (vap *VAP) Type() string {
+	return "ASPA"
+}
+
+func (vap *VAP) String() string {
+	return fmt.Sprintf("ASPA AS%v -> Providers: %v", vap.CustomerASN, vap.Providers)
+}
+
+func (vap *VAP) HashKey() string {
+	return fmt.Sprintf("%v-%v", vap.CustomerASN, vap.Providers)
+}
+
+func (r1 *VAP) Equals(r2 SendableData) bool {
+	if r1.Type() != r2.Type() {
+		return false
+	}
+
+	r2True := r2.(*VAP)
+	return r1.CustomerASN == r2True.CustomerASN && fmt.Sprint(r1.Providers) == fmt.Sprint(r2True.Providers) /*This could be made faster*/
+}
+
+func (vap *VAP) Copy() SendableData {
+	cop := VAP{
+		CustomerASN: vap.CustomerASN,
+		Flags:       vap.Flags,
+		Providers:   make([]uint32, 0),
+	}
+	cop.Providers = append(cop.Providers, vap.Providers...)
+	return &cop
+}
+
+func (vap *VAP) SetFlag(f uint8) {
+	vap.Flags = f
+}
+
+func (vap *VAP) GetFlag() uint8 {
+	return vap.Flags
+}
+
 func (c *Client) SendSDs(sessionId uint16, serialNumber uint32, data []SendableData) {
 	pduBegin := &PDUCacheResponse{
 		SessionId: sessionId,
@@ -1001,6 +1073,7 @@ func (c *Client) SendWrongVersionError() {
 		ErrorCode: PDU_ERROR_BADPROTOVERSION,
 		ErrorMsg:  "Bad protocol version",
 	}
+	pdu.SetVersion(PROTOCOL_VERSION_MAX)
 	c.SendPDU(pdu)
 }
 
@@ -1032,13 +1105,28 @@ func (c *Client) SendData(sd SendableData) {
 		}
 
 		pdu := &PDURouterKey{
-			Version:              c.version,
+			Version:              c.version, // The RouterKey PDU is unchanged from rfc8210 to draft-ietf-sidrops-8210bis-25
 			Flags:                t.Flags,
 			SubjectKeyIdentifier: t.Ski,
 			ASN:                  t.ASN,
 			SubjectPublicKeyInfo: t.Pubkey,
 		}
 		c.SendPDU(pdu)
+	case *VAP:
+		if c.version < 2 || c.dontSendASPA {
+			return
+		}
+
+		pdu := &PDUASPA{
+			Flags:             t.Flags,
+			CustomerASNumber:  t.CustomerASN,
+			ProviderASNumbers: t.Providers,
+		}
+		//
+		if t.Flags == FLAG_REMOVED {
+			pdu.ProviderASNumbers = nil
+		}
+		c.SendPDU(pdu)
 	}
 }
 
diff --git a/lib/structs.go b/lib/structs.go
index 0a89c2c..92f9328 100644
--- a/lib/structs.go
+++ b/lib/structs.go
@@ -26,10 +26,16 @@ const (
 	// We ignore the theoretically unbounded length of SKIs for router keys.
 	// RPs should validate that this has the correct length.
 	//
+	// maximum size of ASPA PDU payload:
+	// * 2^16 providers * 32bit = 262144 bytes
+	// * length is inclusive of header: 8 bytes
+	// * flags/afi flags/provider as/customer AS: 16 bytes
 	messageMaxSize = 262168
 
-	PROTOCOL_VERSION_0 = 0
-	PROTOCOL_VERSION_1 = 1
+	PROTOCOL_VERSION_0   = 0
+	PROTOCOL_VERSION_1   = 1
+	PROTOCOL_VERSION_2   = 2
+	PROTOCOL_VERSION_MAX = PROTOCOL_VERSION_2
 
 	PDU_ID_SERIAL_NOTIFY  = 0
 	PDU_ID_SERIAL_QUERY   = 1
@@ -41,18 +47,23 @@ const (
 	PDU_ID_CACHE_RESET    = 8
 	PDU_ID_ROUTER_KEY     = 9
 	PDU_ID_ERROR_REPORT   = 10
+	PDU_ID_ASPA           = 11
 
 	FLAG_ADDED   = 1
 	FLAG_REMOVED = 0
 
-	PDU_ERROR_CORRUPTDATA     = 0
-	PDU_ERROR_INTERNALERR     = 1
-	PDU_ERROR_NODATA          = 2
-	PDU_ERROR_INVALIDREQUEST  = 3
-	PDU_ERROR_BADPROTOVERSION = 4
-	PDU_ERROR_BADPDUTYPE      = 5
-	PDU_ERROR_WITHDRAWUNKNOWN = 6
-	PDU_ERROR_DUPANNOUNCE     = 7
+	PDU_ERROR_CORRUPTDATA            = 0
+	PDU_ERROR_INTERNALERR            = 1
+	PDU_ERROR_NODATA                 = 2
+	PDU_ERROR_INVALIDREQUEST         = 3
+	PDU_ERROR_BADPROTOVERSION        = 4
+	PDU_ERROR_BADPDUTYPE             = 5
+	PDU_ERROR_WITHDRAWUNKNOWN        = 6
+	PDU_ERROR_DUPANNOUNCE            = 7
+	PDU_ERROR_UNEXPECTEDPROTOVERSION = 8
+	PDU_ERROR_ASPAPROVIDERLIST       = 9
+	PDU_ERROR_TRANSPORT              = 10
+	PDU_ERROR_ORDERING               = 11
 
 	AFI_IPv4 = uint8(0)
 	AFI_IPv6 = uint8(1)
@@ -94,6 +105,8 @@ func TypeToString(t uint8) string {
 		return "Router Key"
 	case PDU_ID_ERROR_REPORT:
 		return "Error Report"
+	case PDU_ID_ASPA:
+		return "ASPA PDU"
 	default:
 		return fmt.Sprintf("Unknown type %d", t)
 	}
@@ -515,6 +528,48 @@ func (pdu *PDUErrorReport) Write(wr io.Writer) {
 	}
 }
 
+type PDUASPA struct {
+	Version           uint8
+	Flags             uint8
+	CustomerASNumber  uint32
+	ProviderASNumbers []uint32
+}
+
+func (pdu *PDUASPA) String() string {
+	return fmt.Sprintf("PDU ASPA v%d TODO", pdu.Version) // TODO
+}
+
+func (pdu *PDUASPA) Bytes() []byte {
+	b := bytes.NewBuffer([]byte{})
+	pdu.Write(b)
+	return b.Bytes()
+}
+
+func (pdu *PDUASPA) SetVersion(version uint8) {
+	pdu.Version = version
+}
+
+func (pdu *PDUASPA) GetVersion() uint8 {
+	return pdu.Version
+}
+
+func (pdu *PDUASPA) GetType() uint8 {
+	return PDU_ID_ASPA
+}
+
+func (pdu *PDUASPA) Write(wr io.Writer) {
+	binary.Write(wr, binary.BigEndian, uint8(pdu.Version))
+	binary.Write(wr, binary.BigEndian, uint8(PDU_ID_ASPA))
+	binary.Write(wr, binary.BigEndian, uint8(pdu.Flags))
+	binary.Write(wr, binary.BigEndian, uint8(0))
+	// We add 12 to the len to take in account the headers (8) and the CustomerASNumber (4)
+	binary.Write(wr, binary.BigEndian, uint32(12+len(pdu.ProviderASNumbers)*4))
+	binary.Write(wr, binary.BigEndian, uint32(pdu.CustomerASNumber))
+	for _, pasn := range pdu.ProviderASNumbers {
+		binary.Write(wr, binary.BigEndian, uint32(pasn))
+	}
+}
+
 func DecodeBytes(b []byte) (PDU, error) {
 	buf := bytes.NewBuffer(b)
 	return Decode(buf)
@@ -700,6 +755,50 @@ func Decode(rdr io.Reader) (PDU, error) {
 			PDUCopy:   errPdu,
 			ErrorMsg:  errMsg,
 		}, nil
+	case PDU_ID_ASPA:
+		if len(toread) < 8 {
+			return nil, fmt.Errorf("wrong length for ASPA PDU: %d < 16", len(toread))
+		}
+
+		CASN := binary.BigEndian.Uint32(toread[0:4])
+		PASNs := make([]uint32, 0)
+		rbuf := bytes.NewReader(toread[4:])
+		PDUContainsAS0 := false
+
+		// We substract 12 to the len to remove the headers (8) and the CustomerASNumber (4)
+		// We then divide by 4 because an ASN is 4 bytes
+		var prev_asn uint32
+		var asn uint32
+		for i := 0; i < (int(length)-12)/4; i++ {
+			err := binary.Read(rbuf, binary.BigEndian, &asn)
+			if err != nil {
+				return nil, err
+			}
+			if i == 0 {
+				prev_asn = asn
+			}
+			if asn == 0 {
+				PDUContainsAS0 = true
+			}
+			if i > 0 {
+				if !(asn > prev_asn) {
+					return nil, fmt.Errorf("Sorting issue in ASPA Providers: %d > %d", asn, prev_asn)
+				}
+				prev_asn = asn
+			}
+			PASNs = append(PASNs, asn)
+		}
+
+		if len(PASNs) > 1 && PDUContainsAS0 {
+			return nil, fmt.Errorf("ASPA PDU contains both AS0 and at least another provider")
+		}
+
+		return &PDUASPA{
+			Version:           pver,
+			Flags:             uint8(sessionId >> 8),
+			CustomerASNumber:  CASN,
+			ProviderASNumbers: PASNs,
+		}, nil
 	default:
 		return nil, errors.New("could not decode packet")
 	}
diff --git a/prefixfile/prefixfile.go b/prefixfile/prefixfile.go
index 5f0a2a9..ec14c5a 100644
--- a/prefixfile/prefixfile.go
+++ b/prefixfile/prefixfile.go
@@ -12,10 +12,12 @@ type RPKIList struct {
 	Metadata   MetaData        `json:"metadata,omitempty"`
 	ROA        []VRPJson       `json:"roas"` // for historical reasons this is called 'roas', but should've been called vrps
 	BgpSecKeys []BgpSecKeyJson `json:"bgpsec_keys,omitempty"`
+	ASPA       []VAPJson       `json:"aspas,omitempty"`
 }
 
 type MetaData struct {
 	Counts          int    `json:"vrps"`
+	CountASPAs      int    `json:"aspas"`
 	CountBgpSecKeys int    `json:"bgpsec_pubkeys"`
 	Buildtime       string `json:"buildtime,omitempty"`
 	GeneratedUnix   *int64 `json:"generated,omitempty"`
@@ -44,6 +46,12 @@ type BgpSecKeyJson struct {
 	Ski string `json:"ski"`
 }
 
+type VAPJson struct {
+	CustomerAsid uint32   `json:"customer_asid"`
+	Expires      *int64   `json:"expires,omitempty"`
+	Providers    []uint32 `json:"providers"`
+}
+
 func (md MetaData) GetBuildTime() time.Time {
 	bt, err := time.Parse(time.RFC3339, md.Buildtime)
 	if err != nil {
diff --git a/prefixfile/slurm.go b/prefixfile/slurm.go
index c4e1522..9c0e80a 100644
--- a/prefixfile/slurm.go
+++ b/prefixfile/slurm.go
@@ -1,4 +1,4 @@
-// rfc8416
+// rfc8416 and draft-sidrops-aspa-slurm
 
 package prefixfile
 
@@ -23,6 +23,11 @@ type SlurmBGPsecFilter struct {
 	Comment string  `json:"comment"`
 }
 
+type SlurmASPAFilter struct {
+	Comment      string `json:"comment"`
+	CustomerASid uint32 `json:"customer_asid"`
+}
+
 func (pf *SlurmPrefixFilter) GetASN() (uint32, bool) {
 	if pf.ASN == nil {
 		return 0, true
@@ -39,6 +44,7 @@ func (pf *SlurmPrefixFilter) GetPrefix() netip.Prefix {
 type SlurmValidationOutputFilters struct {
 	PrefixFilters []SlurmPrefixFilter
 	BgpsecFilters []SlurmBGPsecFilter
+	AspaFilters   []SlurmASPAFilter
 }
 
 type SlurmPrefixAssertion struct {
@@ -55,6 +61,12 @@ type SlurmBGPsecAssertion struct {
 	RouterPublicKey []byte `json:"routerPublicKey"`
 }
 
+type SlurmASPAAssertion struct {
+	Comment       string   `json:"comment"`
+	CustomerASNid uint32   `json:"customer_asid"`
+	ProviderSet   []uint32 `json:"provider_set"`
+}
+
 func (pa *SlurmPrefixAssertion) GetASN() uint32 {
 	return pa.ASN
 }
@@ -71,6 +83,7 @@ func (pa *SlurmPrefixAssertion) GetMaxLen() int {
 type SlurmLocallyAddedAssertions struct {
 	PrefixAssertions []SlurmPrefixAssertion
 	BgpsecAssertions []SlurmBGPsecAssertion
+	AspaAssertions   []SlurmASPAAssertion
 }
 
 type SlurmConfig struct {
@@ -191,6 +204,29 @@ func (s *SlurmValidationOutputFilters) FilterOnBRKs(brks []BgpSecKeyJson) (added
 	return added, removed
 }
 
+func (s *SlurmValidationOutputFilters) FilterOnVAPs(vaps []VAPJson) (added, removed []VAPJson) {
+	added = make([]VAPJson, 0)
+	removed = make([]VAPJson, 0)
+	if s.AspaFilters == nil || len(s.AspaFilters) == 0 {
+		return vaps, removed
+	}
+	for _, vap := range vaps {
+		var wasRemoved bool
+		for _, filter := range s.AspaFilters {
+			if vap.CustomerAsid == filter.CustomerASid {
+				removed = append(removed, vap)
+				wasRemoved = true
+				break
+			}
+		}
+
+		if !wasRemoved {
+			added = append(added, vap)
+		}
+	}
+	return added, removed
+}
+
 func (s *SlurmLocallyAddedAssertions) AssertVRPs() []VRPJson {
 	vrps := make([]VRPJson, 0)
 	if s.PrefixAssertions == nil || len(s.PrefixAssertions) == 0 {
@@ -216,6 +252,22 @@ func (s *SlurmLocallyAddedAssertions) AssertVRPs() []VRPJson {
 	return vrps
 }
 
+func (s *SlurmLocallyAddedAssertions) AssertVAPs() []VAPJson {
+	vaps := make([]VAPJson, 0)
+
+	if s.AspaAssertions == nil || len(s.AspaAssertions) == 0 {
+		return vaps
+	}
+	for _, assertion := range s.AspaAssertions {
+		vap := VAPJson{
+			CustomerAsid: assertion.CustomerASNid,
+			Providers:    assertion.ProviderSet,
+		}
+		vaps = append(vaps, vap)
+	}
+	return vaps
+}
+
 func (s *SlurmLocallyAddedAssertions) AssertBRKs() []BgpSecKeyJson {
 	brks := make([]BgpSecKeyJson, 0)
 
@@ -234,21 +286,24 @@ func (s *SlurmLocallyAddedAssertions) AssertBRKs() []BgpSecKeyJson {
 	return brks
 }
 
-func (s *SlurmConfig) GetAssertions() (vrps []VRPJson, BRKs []BgpSecKeyJson) {
+func (s *SlurmConfig) GetAssertions() (vrps []VRPJson, vaps []VAPJson, BRKs []BgpSecKeyJson) {
 	vrps = s.LocallyAddedAssertions.AssertVRPs()
+	vaps = s.LocallyAddedAssertions.AssertVAPs()
 	BRKs = s.LocallyAddedAssertions.AssertBRKs()
 	return
 }
 
-func (s *SlurmConfig) FilterAssert(vrps []VRPJson, BRKs []BgpSecKeyJson, log Logger) (
-	ovrps []VRPJson, oBRKs []BgpSecKeyJson) {
+func (s *SlurmConfig) FilterAssert(vrps []VRPJson, vaps []VAPJson, BRKs []BgpSecKeyJson, log Logger) (
+	ovrps []VRPJson, ovaps []VAPJson, oBRKs []BgpSecKeyJson) {
 	//
 	filteredVRPs, removedVRPs := s.ValidationOutputFilters.FilterOnVRPs(vrps)
+	filteredVAPs, removedVAPs := s.ValidationOutputFilters.FilterOnVAPs(vaps)
 	filteredBRKs, removedBRKs := s.ValidationOutputFilters.FilterOnBRKs(BRKs)
 
-	assertVRPs, assertBRKs := s.GetAssertions()
+	assertVRPs, assertVAPs, assertBRKs := s.GetAssertions()
 
 	ovrps = append(filteredVRPs, assertVRPs...)
+	ovaps = append(filteredVAPs, assertVAPs...)
 	oBRKs = append(filteredBRKs, assertBRKs...)
 
 	if log != nil {
@@ -259,6 +314,10 @@ func (s *SlurmConfig) FilterAssert(vrps []VRPJson, BRKs []BgpSecKeyJson, log Log
 		if len(s.ValidationOutputFilters.BgpsecFilters) != 0 {
 			log.Infof("Slurm Router Key filtering: %v kept, %v removed, %v asserted", len(filteredBRKs), len(removedBRKs), len(oBRKs))
 		}
+
+		if len(s.ValidationOutputFilters.AspaFilters) != 0 {
+			log.Infof("Slurm ASPA filtering: %v kept, %v removed, %v asserted", len(filteredVAPs), len(removedVAPs), len(ovaps))
+		}
 	}
 	return
 }
diff --git a/prefixfile/slurm.json b/prefixfile/slurm.json
index d18d5da..85b85da 100644
--- a/prefixfile/slurm.json
+++ b/prefixfile/slurm.json
@@ -30,6 +30,12 @@
         "SKI": "XC7RBWu3661vfYmhXZwtUw==",
         "comment": "Key for ASN 64497 matching Router SKI"
       }
+    ],
+    "aspaFilters": [
+      {
+        "customer_asid": 64496,
+        "comment": "ASPAs matching Customer ASID"
+      }
     ]
   },
   "locallyAddedAssertions": {
@@ -53,6 +59,13 @@
         "SKI": "NQYXZ0PgL2fdRscxGdVDa+fhAQY=",
         "routerPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhv5HEBGixUjKJTlenvcD1Axyi07rFdVY1KhN4vMPYy5y0Mx6zfaiEqJN27jK/l61xC36Vsaezd7eXAsZ1AEEsQ=="
       }
+    ],
+    "aspaAssertions": [
+      {
+        "customer_asid": 64499,
+        "provider_set": [64497, 64498],
+        "comment": "Pretend 64497 and 64498 are upstream for 64496 in the IPv6 AFI"
+      }
     ]
   }
 }
diff --git a/prefixfile/slurm_test.go b/prefixfile/slurm_test.go
index ad6fa3a..28d96ee 100644
--- a/prefixfile/slurm_test.go
+++ b/prefixfile/slurm_test.go
@@ -188,6 +188,33 @@ func TestFilterOnBSKs(t *testing.T) {
 	assert.Equal(t, "111b485d29a29db7b515f9c471e1ed3cb7bb7dee", removed[2].Ski)
 	assert.Equal(t, uint32(65005), removed[2].Asn)
 }
+func TestFilterOnVAPs(t *testing.T) {
+	vrps := []VAPJson{
+		{
+			CustomerAsid: 65001,
+			Providers:    []uint32{65002, 65003},
+		},
+		{
+			CustomerAsid: 65002,
+			Providers:    []uint32{65001, 65003},
+		},
+	}
+
+	slurm := SlurmValidationOutputFilters{
+		AspaFilters: []SlurmASPAFilter{
+			{
+				CustomerASid: 65001,
+			},
+			{
+				CustomerASid: 65000,
+			},
+		},
+	}
+	added, removed := slurm.FilterOnVAPs(vrps)
+	assert.Len(t, added, 1)
+	assert.Len(t, removed, 1)
+	assert.Equal(t, uint32(65001), removed[0].CustomerAsid)
+}
 
 func TestSlurmEndToEnd(t *testing.T) {
 	slurmfd, err := os.Open("slurm.json")
@@ -212,7 +239,8 @@ func TestSlurmEndToEnd(t *testing.T) {
 		panic(err)
 	}
 
-	finalVRP, finalBgpsec := config.FilterAssert(vrplist.ROA, vrplist.BgpSecKeys, nil)
+	finalVRP, finalASPA, finalBgpsec :=
+		config.FilterAssert(vrplist.ROA, vrplist.ASPA, vrplist.BgpSecKeys, nil)
 
 	foundAssertVRP := false
 	for _, vrps := range finalVRP {
@@ -228,6 +256,19 @@ func TestSlurmEndToEnd(t *testing.T) {
 		t.Fatalf("Did not find asserted VRP")
 	}
 
+	foundAssertVAP := false
+	for _, vaps := range finalASPA {
+		if vaps.CustomerAsid == 64499 {
+			foundAssertVAP = true
+		}
+		if vaps.CustomerAsid == 64496 {
+			t.Fatalf("Found filtered ASPA")
+		}
+	}
+	if !foundAssertVAP {
+		t.Fatalf("Did not find asserted VAP")
+	}
+
 	foundAssertBRK := false
 	for _, brks := range finalBgpsec {
 		if brks.Ski == "510f485d29a29db7b515f9c478f8ed3cb7aa7d23" {
