xref: /openbsd-src/usr.sbin/rpki-client/aspa.c (revision 30a085025d55af7cc5799cf5d05a8d03f20d20ee)
1*30a08502Stb /*	$OpenBSD: aspa.c,v 1.32 2024/11/13 12:51:03 tb Exp $ */
2a29ddfd5Sjob /*
3a29ddfd5Sjob  * Copyright (c) 2022 Job Snijders <job@fastly.com>
4a29ddfd5Sjob  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
5a29ddfd5Sjob  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
6a29ddfd5Sjob  *
7a29ddfd5Sjob  * Permission to use, copy, modify, and distribute this software for any
8a29ddfd5Sjob  * purpose with or without fee is hereby granted, provided that the above
9a29ddfd5Sjob  * copyright notice and this permission notice appear in all copies.
10a29ddfd5Sjob  *
11a29ddfd5Sjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12a29ddfd5Sjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13a29ddfd5Sjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14a29ddfd5Sjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a29ddfd5Sjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a29ddfd5Sjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a29ddfd5Sjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18a29ddfd5Sjob  */
19a29ddfd5Sjob 
20a29ddfd5Sjob #include <assert.h>
21a29ddfd5Sjob #include <err.h>
22a29ddfd5Sjob #include <stdint.h>
23a29ddfd5Sjob #include <stdlib.h>
24a29ddfd5Sjob #include <string.h>
25a29ddfd5Sjob #include <unistd.h>
26a29ddfd5Sjob 
27a29ddfd5Sjob #include <openssl/asn1.h>
28a29ddfd5Sjob #include <openssl/asn1t.h>
29a29ddfd5Sjob #include <openssl/stack.h>
30a29ddfd5Sjob #include <openssl/safestack.h>
31a29ddfd5Sjob #include <openssl/x509.h>
32a29ddfd5Sjob 
33a29ddfd5Sjob #include "extern.h"
34a29ddfd5Sjob 
35a29ddfd5Sjob extern ASN1_OBJECT	*aspa_oid;
36a29ddfd5Sjob 
37a29ddfd5Sjob /*
384dc25303Sjob  * Types and templates for ASPA eContent draft-ietf-sidrops-aspa-profile-15
39a29ddfd5Sjob  */
40a29ddfd5Sjob 
41d3d26873Sjob ASN1_ITEM_EXP ASProviderAttestation_it;
42d3d26873Sjob 
43a29ddfd5Sjob typedef struct {
44a29ddfd5Sjob 	ASN1_INTEGER		*version;
45a29ddfd5Sjob 	ASN1_INTEGER		*customerASID;
464b5fc138Sjob 	STACK_OF(ASN1_INTEGER)	*providers;
47a29ddfd5Sjob } ASProviderAttestation;
48a29ddfd5Sjob 
49a29ddfd5Sjob ASN1_SEQUENCE(ASProviderAttestation) = {
50889cefa2Sjob 	ASN1_EXP_OPT(ASProviderAttestation, version, ASN1_INTEGER, 0),
51a29ddfd5Sjob 	ASN1_SIMPLE(ASProviderAttestation, customerASID, ASN1_INTEGER),
524b5fc138Sjob 	ASN1_SEQUENCE_OF(ASProviderAttestation, providers, ASN1_INTEGER),
53a29ddfd5Sjob } ASN1_SEQUENCE_END(ASProviderAttestation);
54a29ddfd5Sjob 
55a29ddfd5Sjob DECLARE_ASN1_FUNCTIONS(ASProviderAttestation);
56a29ddfd5Sjob IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation);
57a29ddfd5Sjob 
58a29ddfd5Sjob /*
59a29ddfd5Sjob  * Parse the ProviderASSet sequence.
60a29ddfd5Sjob  * Return zero on failure, non-zero on success.
61a29ddfd5Sjob  */
62a29ddfd5Sjob static int
63be6e5ad5Stb aspa_parse_providers(const char *fn, struct aspa *aspa,
64be6e5ad5Stb     const STACK_OF(ASN1_INTEGER) *providers)
65a29ddfd5Sjob {
664b5fc138Sjob 	const ASN1_INTEGER	*pa;
674b5fc138Sjob 	uint32_t		 provider;
68a29ddfd5Sjob 	size_t			 providersz, i;
69a29ddfd5Sjob 
704b5fc138Sjob 	if ((providersz = sk_ASN1_INTEGER_num(providers)) == 0) {
71be6e5ad5Stb 		warnx("%s: ASPA: ProviderASSet needs at least one entry", fn);
72a29ddfd5Sjob 		return 0;
73a29ddfd5Sjob 	}
74a29ddfd5Sjob 
75a29ddfd5Sjob 	if (providersz >= MAX_ASPA_PROVIDERS) {
76be6e5ad5Stb 		warnx("%s: ASPA: too many providers (more than %d)", fn,
77a29ddfd5Sjob 		    MAX_ASPA_PROVIDERS);
78a29ddfd5Sjob 		return 0;
79a29ddfd5Sjob 	}
80a29ddfd5Sjob 
81be6e5ad5Stb 	aspa->providers = calloc(providersz, sizeof(provider));
82be6e5ad5Stb 	if (aspa->providers == NULL)
83a29ddfd5Sjob 		err(1, NULL);
84a29ddfd5Sjob 
85a29ddfd5Sjob 	for (i = 0; i < providersz; i++) {
864b5fc138Sjob 		pa = sk_ASN1_INTEGER_value(providers, i);
87a29ddfd5Sjob 
88041f5afeStb 		memset(&provider, 0, sizeof(provider));
89041f5afeStb 
904b5fc138Sjob 		if (!as_id_parse(pa, &provider)) {
91be6e5ad5Stb 			warnx("%s: ASPA: malformed ProviderAS", fn);
92a29ddfd5Sjob 			return 0;
93a29ddfd5Sjob 		}
94a29ddfd5Sjob 
95be6e5ad5Stb 		if (aspa->custasid == provider) {
96a29ddfd5Sjob 			warnx("%s: ASPA: CustomerASID can't also be Provider",
97be6e5ad5Stb 			    fn);
98a29ddfd5Sjob 			return 0;
99a29ddfd5Sjob 		}
100a29ddfd5Sjob 
101a29ddfd5Sjob 		if (i > 0) {
102be6e5ad5Stb 			if (aspa->providers[i - 1] > provider) {
103a29ddfd5Sjob 				warnx("%s: ASPA: invalid ProviderASSet order",
104be6e5ad5Stb 				    fn);
105a29ddfd5Sjob 				return 0;
106a29ddfd5Sjob 			}
107be6e5ad5Stb 			if (aspa->providers[i - 1] == provider) {
108be6e5ad5Stb 				warnx("%s: ASPA: duplicate ProviderAS", fn);
109a29ddfd5Sjob 				return 0;
110a29ddfd5Sjob 			}
111a29ddfd5Sjob 		}
112a29ddfd5Sjob 
113*30a08502Stb 		aspa->providers[aspa->num_providers++] = provider;
114a29ddfd5Sjob 	}
115a29ddfd5Sjob 
116a29ddfd5Sjob 	return 1;
117a29ddfd5Sjob }
118a29ddfd5Sjob 
119a29ddfd5Sjob /*
120a29ddfd5Sjob  * Parse the eContent of an ASPA file.
121a29ddfd5Sjob  * Returns zero on failure, non-zero on success.
122a29ddfd5Sjob  */
123a29ddfd5Sjob static int
124be6e5ad5Stb aspa_parse_econtent(const char *fn, struct aspa *aspa, const unsigned char *d,
125be6e5ad5Stb     size_t dsz)
126a29ddfd5Sjob {
127d115f50dSjob 	const unsigned char	*oder;
128cd9dc441Stb 	ASProviderAttestation	*aspa_asn1;
129a29ddfd5Sjob 	int			 rc = 0;
130a29ddfd5Sjob 
131d115f50dSjob 	oder = d;
132cd9dc441Stb 	if ((aspa_asn1 = d2i_ASProviderAttestation(NULL, &d, dsz)) == NULL) {
133be6e5ad5Stb 		warnx("%s: ASPA: failed to parse ASProviderAttestation", fn);
134a29ddfd5Sjob 		goto out;
135a29ddfd5Sjob 	}
136d115f50dSjob 	if (d != oder + dsz) {
137be6e5ad5Stb 		warnx("%s: %td bytes trailing garbage in eContent", fn,
138d115f50dSjob 		    oder + dsz - d);
139d115f50dSjob 		goto out;
140d115f50dSjob 	}
141a29ddfd5Sjob 
142be6e5ad5Stb 	if (!valid_econtent_version(fn, aspa_asn1->version, 1))
143a29ddfd5Sjob 		goto out;
144a29ddfd5Sjob 
145be6e5ad5Stb 	if (!as_id_parse(aspa_asn1->customerASID, &aspa->custasid)) {
146be6e5ad5Stb 		warnx("%s: malformed CustomerASID", fn);
147a29ddfd5Sjob 		goto out;
148a29ddfd5Sjob 	}
149a29ddfd5Sjob 
150be6e5ad5Stb 	if (!aspa_parse_providers(fn, aspa, aspa_asn1->providers))
151a29ddfd5Sjob 		goto out;
152a29ddfd5Sjob 
153a29ddfd5Sjob 	rc = 1;
154a29ddfd5Sjob  out:
155cd9dc441Stb 	ASProviderAttestation_free(aspa_asn1);
156a29ddfd5Sjob 	return rc;
157a29ddfd5Sjob }
158a29ddfd5Sjob 
159a29ddfd5Sjob /*
160a29ddfd5Sjob  * Parse a full ASPA file.
161a29ddfd5Sjob  * Returns the payload or NULL if the file was malformed.
162a29ddfd5Sjob  */
163a29ddfd5Sjob struct aspa *
1640636c4d0Stb aspa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
1650636c4d0Stb     size_t len)
166a29ddfd5Sjob {
167be6e5ad5Stb 	struct aspa	*aspa;
168a29ddfd5Sjob 	size_t		 cmsz;
169a29ddfd5Sjob 	unsigned char	*cms;
170a29ddfd5Sjob 	struct cert	*cert = NULL;
171beccb378Stb 	time_t		 signtime = 0;
172a29ddfd5Sjob 	int		 rc = 0;
173a29ddfd5Sjob 
1741bb1e509Sjob 	cms = cms_parse_validate(x509, fn, der, len, aspa_oid, &cmsz,
1751bb1e509Sjob 	    &signtime);
176a29ddfd5Sjob 	if (cms == NULL)
177a29ddfd5Sjob 		return NULL;
178a29ddfd5Sjob 
179be6e5ad5Stb 	if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
180a29ddfd5Sjob 		err(1, NULL);
181a29ddfd5Sjob 
182be6e5ad5Stb 	aspa->signtime = signtime;
1831bb1e509Sjob 
184be6e5ad5Stb 	if (!x509_get_aia(*x509, fn, &aspa->aia))
185a29ddfd5Sjob 		goto out;
186be6e5ad5Stb 	if (!x509_get_aki(*x509, fn, &aspa->aki))
187a29ddfd5Sjob 		goto out;
188be6e5ad5Stb 	if (!x509_get_sia(*x509, fn, &aspa->sia))
1892cf0e122Sjob 		goto out;
190be6e5ad5Stb 	if (!x509_get_ski(*x509, fn, &aspa->ski))
191a29ddfd5Sjob 		goto out;
192be6e5ad5Stb 	if (aspa->aia == NULL || aspa->aki == NULL || aspa->sia == NULL ||
193be6e5ad5Stb 	    aspa->ski == NULL) {
194a29ddfd5Sjob 		warnx("%s: RFC 6487 section 4.8: "
1952cf0e122Sjob 		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
196a29ddfd5Sjob 		goto out;
197a29ddfd5Sjob 	}
198a29ddfd5Sjob 
199a29ddfd5Sjob 	if (X509_get_ext_by_NID(*x509, NID_sbgp_ipAddrBlock, -1) != -1) {
200a29ddfd5Sjob 		warnx("%s: superfluous IP Resources extension present", fn);
201a29ddfd5Sjob 		goto out;
202a29ddfd5Sjob 	}
203a29ddfd5Sjob 
204be6e5ad5Stb 	if (!x509_get_notbefore(*x509, fn, &aspa->notbefore))
205a29ddfd5Sjob 		goto out;
206be6e5ad5Stb 	if (!x509_get_notafter(*x509, fn, &aspa->notafter))
207a29ddfd5Sjob 		goto out;
208a29ddfd5Sjob 
209c9e39c95Sjob 	if (x509_any_inherits(*x509)) {
210ef8bdb37Sjob 		warnx("%s: inherit elements not allowed in EE cert", fn);
211c9e39c95Sjob 		goto out;
212c9e39c95Sjob 	}
213c9e39c95Sjob 
214be6e5ad5Stb 	if (!aspa_parse_econtent(fn, aspa, cms, cmsz))
215a29ddfd5Sjob 		goto out;
216a29ddfd5Sjob 
217891d6bceSjob 	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
218a29ddfd5Sjob 		goto out;
219a29ddfd5Sjob 
220be6e5ad5Stb 	aspa->valid = valid_aspa(fn, cert, aspa);
221a29ddfd5Sjob 
222a29ddfd5Sjob 	rc = 1;
223a29ddfd5Sjob  out:
224a29ddfd5Sjob 	if (rc == 0) {
225be6e5ad5Stb 		aspa_free(aspa);
226be6e5ad5Stb 		aspa = NULL;
227a29ddfd5Sjob 		X509_free(*x509);
228a29ddfd5Sjob 		*x509 = NULL;
229a29ddfd5Sjob 	}
23079f83670Stb 	cert_free(cert);
231a29ddfd5Sjob 	free(cms);
232be6e5ad5Stb 	return aspa;
233a29ddfd5Sjob }
234a29ddfd5Sjob 
235a29ddfd5Sjob /*
236a29ddfd5Sjob  * Free an ASPA pointer.
237a29ddfd5Sjob  * Safe to call with NULL.
238a29ddfd5Sjob  */
239a29ddfd5Sjob void
240a29ddfd5Sjob aspa_free(struct aspa *p)
241a29ddfd5Sjob {
242a29ddfd5Sjob 	if (p == NULL)
243a29ddfd5Sjob 		return;
244a29ddfd5Sjob 
245a29ddfd5Sjob 	free(p->aia);
246a29ddfd5Sjob 	free(p->aki);
2472cf0e122Sjob 	free(p->sia);
248a29ddfd5Sjob 	free(p->ski);
249a29ddfd5Sjob 	free(p->providers);
250a29ddfd5Sjob 	free(p);
251a29ddfd5Sjob }
252a29ddfd5Sjob 
253a29ddfd5Sjob /*
254a29ddfd5Sjob  * Serialise parsed ASPA content.
255a29ddfd5Sjob  * See aspa_read() for the reader on the other side.
256a29ddfd5Sjob  */
257a29ddfd5Sjob void
258a29ddfd5Sjob aspa_buffer(struct ibuf *b, const struct aspa *p)
259a29ddfd5Sjob {
260a29ddfd5Sjob 	io_simple_buffer(b, &p->valid, sizeof(p->valid));
261a29ddfd5Sjob 	io_simple_buffer(b, &p->custasid, sizeof(p->custasid));
2621fc2657fSclaudio 	io_simple_buffer(b, &p->talid, sizeof(p->talid));
263534b6674Sjob 	io_simple_buffer(b, &p->expires, sizeof(p->expires));
264a29ddfd5Sjob 
265*30a08502Stb 	io_simple_buffer(b, &p->num_providers, sizeof(size_t));
266a29ddfd5Sjob 	io_simple_buffer(b, p->providers,
267*30a08502Stb 	    p->num_providers * sizeof(p->providers[0]));
268a29ddfd5Sjob 
269a29ddfd5Sjob 	io_str_buffer(b, p->aia);
270a29ddfd5Sjob 	io_str_buffer(b, p->aki);
271a29ddfd5Sjob 	io_str_buffer(b, p->ski);
272a29ddfd5Sjob }
273a29ddfd5Sjob 
274a29ddfd5Sjob /*
275a29ddfd5Sjob  * Read parsed ASPA content from descriptor.
276a29ddfd5Sjob  * See aspa_buffer() for writer.
277a29ddfd5Sjob  * Result must be passed to aspa_free().
278a29ddfd5Sjob  */
279a29ddfd5Sjob struct aspa *
280a29ddfd5Sjob aspa_read(struct ibuf *b)
281a29ddfd5Sjob {
282a29ddfd5Sjob 	struct aspa	*p;
283a29ddfd5Sjob 
284a29ddfd5Sjob 	if ((p = calloc(1, sizeof(struct aspa))) == NULL)
285a29ddfd5Sjob 		err(1, NULL);
286a29ddfd5Sjob 
287a29ddfd5Sjob 	io_read_buf(b, &p->valid, sizeof(p->valid));
288a29ddfd5Sjob 	io_read_buf(b, &p->custasid, sizeof(p->custasid));
2891fc2657fSclaudio 	io_read_buf(b, &p->talid, sizeof(p->talid));
290534b6674Sjob 	io_read_buf(b, &p->expires, sizeof(p->expires));
291a29ddfd5Sjob 
292*30a08502Stb 	io_read_buf(b, &p->num_providers, sizeof(size_t));
293f814cda1Stb 
294*30a08502Stb 	if (p->num_providers > 0) {
295*30a08502Stb 		if ((p->providers = calloc(p->num_providers,
296f814cda1Stb 		    sizeof(p->providers[0]))) == NULL)
297a29ddfd5Sjob 			err(1, NULL);
298f814cda1Stb 		io_read_buf(b, p->providers,
299*30a08502Stb 		    p->num_providers * sizeof(p->providers[0]));
300f814cda1Stb 	}
301a29ddfd5Sjob 
302a29ddfd5Sjob 	io_read_str(b, &p->aia);
303a29ddfd5Sjob 	io_read_str(b, &p->aki);
304a29ddfd5Sjob 	io_read_str(b, &p->ski);
305a29ddfd5Sjob 	assert(p->aia && p->aki && p->ski);
306a29ddfd5Sjob 
307a29ddfd5Sjob 	return p;
308a29ddfd5Sjob }
309a29ddfd5Sjob 
310a29ddfd5Sjob /*
3114b5fc138Sjob  * Insert a new uint32_t at index idx in the struct vap v.
312acb55ac2Sclaudio  * All elements in the provider array from idx are moved up by one
313acb55ac2Sclaudio  * to make space for the new element.
314a29ddfd5Sjob  */
315acb55ac2Sclaudio static void
3164b5fc138Sjob insert_vap(struct vap *v, uint32_t idx, uint32_t *p)
317a29ddfd5Sjob {
318*30a08502Stb 	if (idx < v->num_providers)
319acb55ac2Sclaudio 		memmove(v->providers + idx + 1, v->providers + idx,
320*30a08502Stb 		    (v->num_providers - idx) * sizeof(v->providers[0]));
321acb55ac2Sclaudio 	v->providers[idx] = *p;
322*30a08502Stb 	v->num_providers++;
323a29ddfd5Sjob }
324a29ddfd5Sjob 
325a29ddfd5Sjob /*
326a29ddfd5Sjob  * Add each ProviderAS entry into the Validated ASPA Providers (VAP) tree.
327acb55ac2Sclaudio  * Duplicated entries are merged.
328a29ddfd5Sjob  */
329a29ddfd5Sjob void
330cd55b6bdSjob aspa_insert_vaps(char *fn, struct vap_tree *tree, struct aspa *aspa,
331cd55b6bdSjob     struct repo *rp)
332a29ddfd5Sjob {
333acb55ac2Sclaudio 	struct vap	*v, *found;
334acb55ac2Sclaudio 	size_t		 i, j;
335a29ddfd5Sjob 
336acb55ac2Sclaudio 	if ((v = calloc(1, sizeof(*v))) == NULL)
337acb55ac2Sclaudio 		err(1, NULL);
338acb55ac2Sclaudio 	v->custasid = aspa->custasid;
3391fc2657fSclaudio 	v->talid = aspa->talid;
3401fc2657fSclaudio 	if (rp != NULL)
3411fc2657fSclaudio 		v->repoid = repo_id(rp);
3421fc2657fSclaudio 	else
3431fc2657fSclaudio 		v->repoid = 0;
344534b6674Sjob 	v->expires = aspa->expires;
345a29ddfd5Sjob 
346acb55ac2Sclaudio 	if ((found = RB_INSERT(vap_tree, tree, v)) != NULL) {
3477e284d50Stb 		if (found->overflowed) {
348cd55b6bdSjob 			free(v);
349cd55b6bdSjob 			return;
350cd55b6bdSjob 		}
3511fc2657fSclaudio 		if (found->expires > v->expires) {
3521fc2657fSclaudio 			/* decrement found */
3531fc2657fSclaudio 			repo_stat_inc(repo_byid(found->repoid), found->talid,
3541fc2657fSclaudio 			    RTYPE_ASPA, STYPE_DEC_UNIQUE);
355acb55ac2Sclaudio 			found->expires = v->expires;
3561fc2657fSclaudio 			found->talid = v->talid;
3571fc2657fSclaudio 			found->repoid = v->repoid;
3581fc2657fSclaudio 			repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
3591fc2657fSclaudio 		}
360acb55ac2Sclaudio 		free(v);
361acb55ac2Sclaudio 		v = found;
362acb55ac2Sclaudio 	} else
3631fc2657fSclaudio 		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
3641fc2657fSclaudio 
3651fc2657fSclaudio 	repo_stat_inc(rp, aspa->talid, RTYPE_ASPA, STYPE_TOTAL);
366acb55ac2Sclaudio 
367acb55ac2Sclaudio 	v->providers = reallocarray(v->providers,
368*30a08502Stb 	    v->num_providers + aspa->num_providers, sizeof(v->providers[0]));
369acb55ac2Sclaudio 	if (v->providers == NULL)
370acb55ac2Sclaudio 		err(1, NULL);
371acb55ac2Sclaudio 
372acb55ac2Sclaudio 	/*
373acb55ac2Sclaudio 	 * Merge all data from aspa into v: loop over all aspa providers,
374acb55ac2Sclaudio 	 * insert them in the right place in v->providers while keeping the
375acb55ac2Sclaudio 	 * order of the providers array.
376acb55ac2Sclaudio 	 */
377*30a08502Stb 	for (i = 0, j = 0; i < aspa->num_providers; ) {
378*30a08502Stb 		if (j == v->num_providers ||
3794b5fc138Sjob 		    aspa->providers[i] < v->providers[j]) {
380acb55ac2Sclaudio 			/* merge provider from aspa into v */
3811fc2657fSclaudio 			repo_stat_inc(rp, v->talid, RTYPE_ASPA,
38274a82ef4Stb 			    STYPE_PROVIDERS);
383acb55ac2Sclaudio 			insert_vap(v, j, &aspa->providers[i]);
384acb55ac2Sclaudio 			i++;
3854b5fc138Sjob 		} else if (aspa->providers[i] == v->providers[j])
386acb55ac2Sclaudio 			i++;
3874b5fc138Sjob 
388*30a08502Stb 		if (j < v->num_providers)
389acb55ac2Sclaudio 			j++;
390a29ddfd5Sjob 	}
3917e284d50Stb 
392*30a08502Stb 	if (v->num_providers >= MAX_ASPA_PROVIDERS) {
3937e284d50Stb 		v->overflowed = 1;
3947e284d50Stb 		free(v->providers);
3957e284d50Stb 		v->providers = NULL;
396*30a08502Stb 		v->num_providers = 0;
3977e284d50Stb 		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_OVERFLOW);
3987e284d50Stb 		warnx("%s: too many providers for ASPA Customer ASID %u "
3997e284d50Stb 		    "(more than %d)", fn, v->custasid, MAX_ASPA_PROVIDERS);
4007e284d50Stb 		return;
4017e284d50Stb 	}
402a29ddfd5Sjob }
403a29ddfd5Sjob 
404a29ddfd5Sjob static inline int
405a29ddfd5Sjob vapcmp(struct vap *a, struct vap *b)
406a29ddfd5Sjob {
407a29ddfd5Sjob 	if (a->custasid > b->custasid)
408a29ddfd5Sjob 		return 1;
409a29ddfd5Sjob 	if (a->custasid < b->custasid)
410a29ddfd5Sjob 		return -1;
411a29ddfd5Sjob 
412a29ddfd5Sjob 	return 0;
413a29ddfd5Sjob }
414a29ddfd5Sjob 
415a29ddfd5Sjob RB_GENERATE(vap_tree, vap, entry, vapcmp);
416