xref: /openbsd-src/usr.sbin/rpki-client/roa.c (revision 381ee5995072b060a824e758e1421220f03cdcc0)
1*381ee599Stb /*	$OpenBSD: roa.c,v 1.80 2024/11/12 09:23:07 tb Exp $ */
29a7e9e7fSjob /*
3740e9a54Stb  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
49a7e9e7fSjob  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
59a7e9e7fSjob  *
69a7e9e7fSjob  * Permission to use, copy, modify, and distribute this software for any
79a7e9e7fSjob  * purpose with or without fee is hereby granted, provided that the above
89a7e9e7fSjob  * copyright notice and this permission notice appear in all copies.
99a7e9e7fSjob  *
109a7e9e7fSjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
119a7e9e7fSjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
129a7e9e7fSjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
139a7e9e7fSjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
149a7e9e7fSjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
159a7e9e7fSjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
169a7e9e7fSjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
179a7e9e7fSjob  */
189a7e9e7fSjob 
199a7e9e7fSjob #include <assert.h>
209a7e9e7fSjob #include <err.h>
219a7e9e7fSjob #include <stdint.h>
229a7e9e7fSjob #include <stdlib.h>
239a7e9e7fSjob #include <string.h>
249a7e9e7fSjob #include <unistd.h>
259a7e9e7fSjob 
26a1753de6Sclaudio #include <openssl/asn1.h>
2723e50b68Stb #include <openssl/asn1t.h>
2823e50b68Stb #include <openssl/stack.h>
2923e50b68Stb #include <openssl/safestack.h>
30a1753de6Sclaudio #include <openssl/x509.h>
31a1753de6Sclaudio 
329a7e9e7fSjob #include "extern.h"
339a7e9e7fSjob 
34de9b6f5dSclaudio extern ASN1_OBJECT	*roa_oid;
35d2e465bbSclaudio 
369a7e9e7fSjob /*
3723e50b68Stb  * Types and templates for the ROA eContent, RFC 6482, section 3.
389a7e9e7fSjob  */
399a7e9e7fSjob 
40d3d26873Sjob ASN1_ITEM_EXP ROAIPAddress_it;
41d3d26873Sjob ASN1_ITEM_EXP ROAIPAddressFamily_it;
42d3d26873Sjob ASN1_ITEM_EXP RouteOriginAttestation_it;
43d3d26873Sjob 
4423e50b68Stb typedef struct {
4523e50b68Stb 	ASN1_BIT_STRING		*address;
4623e50b68Stb 	ASN1_INTEGER		*maxLength;
4723e50b68Stb } ROAIPAddress;
489a7e9e7fSjob 
4923e50b68Stb DECLARE_STACK_OF(ROAIPAddress);
509a7e9e7fSjob 
5123e50b68Stb typedef struct {
5223e50b68Stb 	ASN1_OCTET_STRING	*addressFamily;
5323e50b68Stb 	STACK_OF(ROAIPAddress)	*addresses;
5423e50b68Stb } ROAIPAddressFamily;
559a7e9e7fSjob 
5623e50b68Stb DECLARE_STACK_OF(ROAIPAddressFamily);
579a7e9e7fSjob 
589395df6fStb #ifndef DEFINE_STACK_OF
5923e50b68Stb #define sk_ROAIPAddress_num(st)		SKM_sk_num(ROAIPAddress, (st))
6023e50b68Stb #define sk_ROAIPAddress_value(st, i)	SKM_sk_value(ROAIPAddress, (st), (i))
619a7e9e7fSjob 
6223e50b68Stb #define sk_ROAIPAddressFamily_num(st)	SKM_sk_num(ROAIPAddressFamily, (st))
6323e50b68Stb #define sk_ROAIPAddressFamily_value(st, i) \
6423e50b68Stb     SKM_sk_value(ROAIPAddressFamily, (st), (i))
6523e50b68Stb #endif
669a7e9e7fSjob 
6723e50b68Stb typedef struct {
6823e50b68Stb 	ASN1_INTEGER			*version;
6923e50b68Stb 	ASN1_INTEGER			*asid;
7023e50b68Stb 	STACK_OF(ROAIPAddressFamily)	*ipAddrBlocks;
7123e50b68Stb } RouteOriginAttestation;
729a7e9e7fSjob 
7323e50b68Stb ASN1_SEQUENCE(ROAIPAddress) = {
7423e50b68Stb 	ASN1_SIMPLE(ROAIPAddress, address, ASN1_BIT_STRING),
7523e50b68Stb 	ASN1_OPT(ROAIPAddress, maxLength, ASN1_INTEGER),
7623e50b68Stb } ASN1_SEQUENCE_END(ROAIPAddress);
779a7e9e7fSjob 
7823e50b68Stb ASN1_SEQUENCE(ROAIPAddressFamily) = {
7923e50b68Stb 	ASN1_SIMPLE(ROAIPAddressFamily, addressFamily, ASN1_OCTET_STRING),
8023e50b68Stb 	ASN1_SEQUENCE_OF(ROAIPAddressFamily, addresses, ROAIPAddress),
8123e50b68Stb } ASN1_SEQUENCE_END(ROAIPAddressFamily);
829a7e9e7fSjob 
8323e50b68Stb ASN1_SEQUENCE(RouteOriginAttestation) = {
84889cefa2Sjob 	ASN1_EXP_OPT(RouteOriginAttestation, version, ASN1_INTEGER, 0),
8523e50b68Stb 	ASN1_SIMPLE(RouteOriginAttestation, asid, ASN1_INTEGER),
8623e50b68Stb 	ASN1_SEQUENCE_OF(RouteOriginAttestation, ipAddrBlocks,
8723e50b68Stb 	    ROAIPAddressFamily),
8823e50b68Stb } ASN1_SEQUENCE_END(RouteOriginAttestation);
899a7e9e7fSjob 
9023e50b68Stb DECLARE_ASN1_FUNCTIONS(RouteOriginAttestation);
9123e50b68Stb IMPLEMENT_ASN1_FUNCTIONS(RouteOriginAttestation);
929a7e9e7fSjob 
939a7e9e7fSjob /*
949a7e9e7fSjob  * Parses the eContent section of an ROA file, RFC 6482, section 3.
959a7e9e7fSjob  * Returns zero on failure, non-zero on success.
969a7e9e7fSjob  */
979a7e9e7fSjob static int
98be6e5ad5Stb roa_parse_econtent(const char *fn, struct roa *roa, const unsigned char *d,
99be6e5ad5Stb     size_t dsz)
1009a7e9e7fSjob {
101d115f50dSjob 	const unsigned char		*oder;
102cd9dc441Stb 	RouteOriginAttestation		*roa_asn1;
10323e50b68Stb 	const ROAIPAddressFamily	*addrfam;
10423e50b68Stb 	const STACK_OF(ROAIPAddress)	*addrs;
105de494ec3Stb 	int				 addrsz, ipv4_seen = 0, ipv6_seen = 0;
10623e50b68Stb 	enum afi			 afi;
10723e50b68Stb 	const ROAIPAddress		*addr;
108331e816cStb 	uint64_t			 maxlen;
10923e50b68Stb 	struct ip_addr			 ipaddr;
11023e50b68Stb 	struct roa_ip			*res;
111b9ea95bbSjob 	int				 ipaddrblocksz;
11223e50b68Stb 	int				 i, j, rc = 0;
1139a7e9e7fSjob 
114d115f50dSjob 	oder = d;
115cd9dc441Stb 	if ((roa_asn1 = d2i_RouteOriginAttestation(NULL, &d, dsz)) == NULL) {
116c0528901Stb 		warnx("%s: RFC 6482 section 3: failed to parse "
117be6e5ad5Stb 		    "RouteOriginAttestation", fn);
1189a7e9e7fSjob 		goto out;
1199a7e9e7fSjob 	}
120d115f50dSjob 	if (d != oder + dsz) {
121be6e5ad5Stb 		warnx("%s: %td bytes trailing garbage in eContent", fn,
122d115f50dSjob 		    oder + dsz - d);
123d115f50dSjob 		goto out;
124d115f50dSjob 	}
1259a7e9e7fSjob 
126be6e5ad5Stb 	if (!valid_econtent_version(fn, roa_asn1->version, 0))
1279a7e9e7fSjob 		goto out;
1289a7e9e7fSjob 
129be6e5ad5Stb 	if (!as_id_parse(roa_asn1->asid, &roa->asid)) {
1309a7e9e7fSjob 		warnx("%s: RFC 6482 section 3.2: asID: "
131be6e5ad5Stb 		    "malformed AS identifier", fn);
1329a7e9e7fSjob 		goto out;
1339a7e9e7fSjob 	}
1349a7e9e7fSjob 
135cd9dc441Stb 	ipaddrblocksz = sk_ROAIPAddressFamily_num(roa_asn1->ipAddrBlocks);
136de494ec3Stb 	if (ipaddrblocksz != 1 && ipaddrblocksz != 2) {
137c7a965b3Stb 		warnx("%s: RFC 9582: unexpected number of ipAddrBlocks "
138be6e5ad5Stb 		    "(got %d, expected 1 or 2)", fn, ipaddrblocksz);
139b9ea95bbSjob 		goto out;
140b9ea95bbSjob 	}
141b9ea95bbSjob 
142b9ea95bbSjob 	for (i = 0; i < ipaddrblocksz; i++) {
143be6e5ad5Stb 		addrfam = sk_ROAIPAddressFamily_value(roa_asn1->ipAddrBlocks,
144be6e5ad5Stb 		    i);
14523e50b68Stb 		addrs = addrfam->addresses;
14623e50b68Stb 		addrsz = sk_ROAIPAddress_num(addrs);
1479a7e9e7fSjob 
148be6e5ad5Stb 		if (!ip_addr_afi_parse(fn, addrfam->addressFamily, &afi)) {
14923e50b68Stb 			warnx("%s: RFC 6482 section 3.3: addressFamily: "
150be6e5ad5Stb 			    "invalid", fn);
1519a7e9e7fSjob 			goto out;
152820799d0Sjob 		}
15323e50b68Stb 
154de494ec3Stb 		switch (afi) {
155de494ec3Stb 		case AFI_IPV4:
156de494ec3Stb 			if (ipv4_seen++ > 0) {
157c7a965b3Stb 				warnx("%s: RFC 9582 section 4.3.2: "
158be6e5ad5Stb 				    "IPv4 appears twice", fn);
159de494ec3Stb 				goto out;
160de494ec3Stb 			}
161de494ec3Stb 			break;
162de494ec3Stb 		case AFI_IPV6:
163de494ec3Stb 			if (ipv6_seen++ > 0) {
164c7a965b3Stb 				warnx("%s: RFC 9582 section 4.3.2: "
165be6e5ad5Stb 				    "IPv6 appears twice", fn);
166de494ec3Stb 				goto out;
167de494ec3Stb 			}
168de494ec3Stb 			break;
169de494ec3Stb 		}
170de494ec3Stb 
171de494ec3Stb 		if (addrsz == 0) {
172c7a965b3Stb 			warnx("%s: RFC 9582, section 4.3.2: "
173be6e5ad5Stb 			    "empty ROAIPAddressFamily", fn);
174de494ec3Stb 			goto out;
175de494ec3Stb 		}
176de494ec3Stb 
177*381ee599Stb 		if (roa->num_ips + addrsz >= MAX_IP_SIZE) {
17816fbb15cSjob 			warnx("%s: too many ROAIPAddress entries: limit %d",
179be6e5ad5Stb 			    fn, MAX_IP_SIZE);
1809a7e9e7fSjob 			goto out;
18123e50b68Stb 		}
182*381ee599Stb 		roa->ips = recallocarray(roa->ips, roa->num_ips,
183*381ee599Stb 		    roa->num_ips + addrsz, sizeof(struct roa_ip));
184be6e5ad5Stb 		if (roa->ips == NULL)
18523e50b68Stb 			err(1, NULL);
18623e50b68Stb 
18723e50b68Stb 		for (j = 0; j < addrsz; j++) {
18823e50b68Stb 			addr = sk_ROAIPAddress_value(addrs, j);
18923e50b68Stb 
190be6e5ad5Stb 			if (!ip_addr_parse(addr->address, afi, fn, &ipaddr)) {
19123e50b68Stb 				warnx("%s: RFC 6482 section 3.3: address: "
192be6e5ad5Stb 				    "invalid IP address", fn);
19323e50b68Stb 				goto out;
19423e50b68Stb 			}
19523e50b68Stb 			maxlen = ipaddr.prefixlen;
19623e50b68Stb 
19723e50b68Stb 			if (addr->maxLength != NULL) {
198331e816cStb 				if (!ASN1_INTEGER_get_uint64(&maxlen,
199331e816cStb 				    addr->maxLength)) {
20023e50b68Stb 					warnx("%s: RFC 6482 section 3.2: "
201331e816cStb 					    "ASN1_INTEGER_get_uint64 failed",
202be6e5ad5Stb 					    fn);
20323e50b68Stb 					goto out;
20423e50b68Stb 				}
20523e50b68Stb 				if (ipaddr.prefixlen > maxlen) {
20623e50b68Stb 					warnx("%s: prefixlen (%d) larger than "
207be6e5ad5Stb 					    "maxLength (%llu)", fn,
208331e816cStb 					    ipaddr.prefixlen,
209331e816cStb 					    (unsigned long long)maxlen);
21023e50b68Stb 					goto out;
21123e50b68Stb 				}
21223e50b68Stb 				if (maxlen > ((afi == AFI_IPV4) ? 32 : 128)) {
213331e816cStb 					warnx("%s: maxLength (%llu) too large",
214be6e5ad5Stb 					    fn, (unsigned long long)maxlen);
21523e50b68Stb 					goto out;
21623e50b68Stb 				}
21723e50b68Stb 			}
21823e50b68Stb 
219*381ee599Stb 			res = &roa->ips[roa->num_ips++];
22023e50b68Stb 			res->addr = ipaddr;
22123e50b68Stb 			res->afi = afi;
22223e50b68Stb 			res->maxlength = maxlen;
22323e50b68Stb 			ip_roa_compose_ranges(res);
22423e50b68Stb 		}
22523e50b68Stb 	}
2269a7e9e7fSjob 
2279a7e9e7fSjob 	rc = 1;
2289a7e9e7fSjob  out:
229cd9dc441Stb 	RouteOriginAttestation_free(roa_asn1);
2309a7e9e7fSjob 	return rc;
2319a7e9e7fSjob }
2329a7e9e7fSjob 
2339a7e9e7fSjob /*
234fc5c0efeSclaudio  * Parse a full RFC 6482 file.
2359a7e9e7fSjob  * Returns the ROA or NULL if the document was malformed.
2369a7e9e7fSjob  */
2379a7e9e7fSjob struct roa *
2380636c4d0Stb roa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
2390636c4d0Stb     size_t len)
2409a7e9e7fSjob {
241be6e5ad5Stb 	struct roa	*roa;
2429a7e9e7fSjob 	size_t		 cmsz;
2439a7e9e7fSjob 	unsigned char	*cms;
24499dbdb7fStb 	struct cert	*cert = NULL;
245beccb378Stb 	time_t		 signtime = 0;
24699dbdb7fStb 	int		 rc = 0;
2479a7e9e7fSjob 
2481330c972Stb 	cms = cms_parse_validate(x509, fn, der, len, roa_oid, &cmsz, &signtime);
2499a7e9e7fSjob 	if (cms == NULL)
2509a7e9e7fSjob 		return NULL;
2519a7e9e7fSjob 
252be6e5ad5Stb 	if ((roa = calloc(1, sizeof(struct roa))) == NULL)
2537fe44250Sbenno 		err(1, NULL);
254be6e5ad5Stb 	roa->signtime = signtime;
2551f25fa5dStb 
256be6e5ad5Stb 	if (!x509_get_aia(*x509, fn, &roa->aia))
257f999fe57Sclaudio 		goto out;
258be6e5ad5Stb 	if (!x509_get_aki(*x509, fn, &roa->aki))
259f999fe57Sclaudio 		goto out;
260be6e5ad5Stb 	if (!x509_get_sia(*x509, fn, &roa->sia))
2612cf0e122Sjob 		goto out;
262be6e5ad5Stb 	if (!x509_get_ski(*x509, fn, &roa->ski))
263f999fe57Sclaudio 		goto out;
264be6e5ad5Stb 	if (roa->aia == NULL || roa->aki == NULL || roa->sia == NULL ||
265be6e5ad5Stb 	    roa->ski == NULL) {
2661f25fa5dStb 		warnx("%s: RFC 6487 section 4.8: "
2672cf0e122Sjob 		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
2689a7e9e7fSjob 		goto out;
2691f25fa5dStb 	}
2701f25fa5dStb 
271be6e5ad5Stb 	if (!x509_get_notbefore(*x509, fn, &roa->notbefore))
272a66158d7Sjob 		goto out;
273be6e5ad5Stb 	if (!x509_get_notafter(*x509, fn, &roa->notafter))
274a66158d7Sjob 		goto out;
275a66158d7Sjob 
276be6e5ad5Stb 	if (!roa_parse_econtent(fn, roa, cms, cmsz))
2779a7e9e7fSjob 		goto out;
2789a7e9e7fSjob 
279c9e39c95Sjob 	if (x509_any_inherits(*x509)) {
280ef8bdb37Sjob 		warnx("%s: inherit elements not allowed in EE cert", fn);
281c9e39c95Sjob 		goto out;
282c9e39c95Sjob 	}
283c9e39c95Sjob 
284891d6bceSjob 	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
28599dbdb7fStb 		goto out;
28699dbdb7fStb 
287*381ee599Stb 	if (cert->num_ases > 0) {
28899dbdb7fStb 		warnx("%s: superfluous AS Resources extension present", fn);
28999dbdb7fStb 		goto out;
29099dbdb7fStb 	}
29199dbdb7fStb 
292*381ee599Stb 	if (cert->num_ips == 0) {
293f814cda1Stb 		warnx("%s: no IP address present", fn);
294f814cda1Stb 		goto out;
295f814cda1Stb 	}
296f814cda1Stb 
29799dbdb7fStb 	/*
29899dbdb7fStb 	 * If the ROA isn't valid, we accept it anyway and depend upon
29999dbdb7fStb 	 * the code around roa_read() to check the "valid" field itself.
30099dbdb7fStb 	 */
301be6e5ad5Stb 	roa->valid = valid_roa(fn, cert, roa);
30299dbdb7fStb 
3039a7e9e7fSjob 	rc = 1;
3049a7e9e7fSjob out:
3059a7e9e7fSjob 	if (rc == 0) {
306be6e5ad5Stb 		roa_free(roa);
307be6e5ad5Stb 		roa = NULL;
3089a7e9e7fSjob 		X509_free(*x509);
3099a7e9e7fSjob 		*x509 = NULL;
3109a7e9e7fSjob 	}
31199dbdb7fStb 	cert_free(cert);
3129a7e9e7fSjob 	free(cms);
313be6e5ad5Stb 	return roa;
3149a7e9e7fSjob }
3159a7e9e7fSjob 
3169a7e9e7fSjob /*
3179a7e9e7fSjob  * Free an ROA pointer.
3189a7e9e7fSjob  * Safe to call with NULL.
3199a7e9e7fSjob  */
3209a7e9e7fSjob void
3219a7e9e7fSjob roa_free(struct roa *p)
3229a7e9e7fSjob {
3239a7e9e7fSjob 
3249a7e9e7fSjob 	if (p == NULL)
3259a7e9e7fSjob 		return;
326ebd55816Sjob 	free(p->aia);
3279a7e9e7fSjob 	free(p->aki);
3282cf0e122Sjob 	free(p->sia);
3299a7e9e7fSjob 	free(p->ski);
3309a7e9e7fSjob 	free(p->ips);
3319a7e9e7fSjob 	free(p);
3329a7e9e7fSjob }
3339a7e9e7fSjob 
3349a7e9e7fSjob /*
3359a7e9e7fSjob  * Serialise parsed ROA content.
3369a7e9e7fSjob  * See roa_read() for reader.
3379a7e9e7fSjob  */
3389a7e9e7fSjob void
33908db1177Sclaudio roa_buffer(struct ibuf *b, const struct roa *p)
3409a7e9e7fSjob {
341dc508150Sclaudio 	io_simple_buffer(b, &p->valid, sizeof(p->valid));
342dc508150Sclaudio 	io_simple_buffer(b, &p->asid, sizeof(p->asid));
343dc508150Sclaudio 	io_simple_buffer(b, &p->talid, sizeof(p->talid));
344*381ee599Stb 	io_simple_buffer(b, &p->num_ips, sizeof(p->num_ips));
345534b6674Sjob 	io_simple_buffer(b, &p->expires, sizeof(p->expires));
3469a7e9e7fSjob 
347*381ee599Stb 	io_simple_buffer(b, p->ips, p->num_ips * sizeof(p->ips[0]));
3489a7e9e7fSjob 
349ebd55816Sjob 	io_str_buffer(b, p->aia);
35008db1177Sclaudio 	io_str_buffer(b, p->aki);
35108db1177Sclaudio 	io_str_buffer(b, p->ski);
3529a7e9e7fSjob }
3539a7e9e7fSjob 
3549a7e9e7fSjob /*
3559a7e9e7fSjob  * Read parsed ROA content from descriptor.
3569a7e9e7fSjob  * See roa_buffer() for writer.
3579a7e9e7fSjob  * Result must be passed to roa_free().
3589a7e9e7fSjob  */
3599a7e9e7fSjob struct roa *
3607eb79a4aSclaudio roa_read(struct ibuf *b)
3619a7e9e7fSjob {
3629a7e9e7fSjob 	struct roa	*p;
3639a7e9e7fSjob 
3649a7e9e7fSjob 	if ((p = calloc(1, sizeof(struct roa))) == NULL)
3657fe44250Sbenno 		err(1, NULL);
3669a7e9e7fSjob 
367dc508150Sclaudio 	io_read_buf(b, &p->valid, sizeof(p->valid));
368dc508150Sclaudio 	io_read_buf(b, &p->asid, sizeof(p->asid));
369dc508150Sclaudio 	io_read_buf(b, &p->talid, sizeof(p->talid));
370*381ee599Stb 	io_read_buf(b, &p->num_ips, sizeof(p->num_ips));
371534b6674Sjob 	io_read_buf(b, &p->expires, sizeof(p->expires));
3729a7e9e7fSjob 
373*381ee599Stb 	if (p->num_ips > 0) {
374*381ee599Stb 		if ((p->ips = calloc(p->num_ips, sizeof(p->ips[0]))) == NULL)
3757fe44250Sbenno 			err(1, NULL);
376*381ee599Stb 		io_read_buf(b, p->ips, p->num_ips * sizeof(p->ips[0]));
377f814cda1Stb 	}
3789a7e9e7fSjob 
3797eb79a4aSclaudio 	io_read_str(b, &p->aia);
3807eb79a4aSclaudio 	io_read_str(b, &p->aki);
3817eb79a4aSclaudio 	io_read_str(b, &p->ski);
382dc508150Sclaudio 	assert(p->aia && p->aki && p->ski);
38352c8fec2Sclaudio 
3849a7e9e7fSjob 	return p;
3859a7e9e7fSjob }
386a382efa2Sclaudio 
3879a6cbf4dSclaudio /*
3889a6cbf4dSclaudio  * Add each IP address in the ROA into the VRP tree.
3899a6cbf4dSclaudio  * Updates "vrps" to be the number of VRPs and "uniqs" to be the unique
3909a6cbf4dSclaudio  * number of addresses.
3919a6cbf4dSclaudio  */
392a382efa2Sclaudio void
3934f5f25cbSclaudio roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, struct repo *rp)
394a382efa2Sclaudio {
395a66158d7Sjob 	struct vrp	*v, *found;
396a382efa2Sclaudio 	size_t		 i;
397a382efa2Sclaudio 
398*381ee599Stb 	for (i = 0; i < roa->num_ips; i++) {
399a382efa2Sclaudio 		if ((v = malloc(sizeof(*v))) == NULL)
4007fe44250Sbenno 			err(1, NULL);
401a382efa2Sclaudio 		v->afi = roa->ips[i].afi;
402a382efa2Sclaudio 		v->addr = roa->ips[i].addr;
403a382efa2Sclaudio 		v->maxlength = roa->ips[i].maxlength;
404a382efa2Sclaudio 		v->asid = roa->asid;
405dc508150Sclaudio 		v->talid = roa->talid;
4064f5f25cbSclaudio 		if (rp != NULL)
4074f5f25cbSclaudio 			v->repoid = repo_id(rp);
4084f5f25cbSclaudio 		else
4094f5f25cbSclaudio 			v->repoid = 0;
410534b6674Sjob 		v->expires = roa->expires;
411a66158d7Sjob 
412a66158d7Sjob 		/*
413a66158d7Sjob 		 * Check if a similar VRP already exists in the tree.
414a66158d7Sjob 		 * If the found VRP expires sooner, update it to this
415a66158d7Sjob 		 * ROAs later expiry moment.
416a66158d7Sjob 		 */
417a66158d7Sjob 		if ((found = RB_INSERT(vrp_tree, tree, v)) != NULL) {
418a66158d7Sjob 			/* already exists */
419a66158d7Sjob 			if (found->expires < v->expires) {
420a66158d7Sjob 				/* update found with preferred data */
4214f5f25cbSclaudio 				/* adjust unique count */
4224f5f25cbSclaudio 				repo_stat_inc(repo_byid(found->repoid),
4231fc2657fSclaudio 				    found->talid, RTYPE_ROA, STYPE_DEC_UNIQUE);
4241fc2657fSclaudio 				found->expires = v->expires;
4251fc2657fSclaudio 				found->talid = v->talid;
4264f5f25cbSclaudio 				found->repoid = v->repoid;
4271fc2657fSclaudio 				repo_stat_inc(rp, v->talid, RTYPE_ROA,
4281fc2657fSclaudio 				    STYPE_UNIQUE);
429a66158d7Sjob 			}
430a382efa2Sclaudio 			free(v);
431a66158d7Sjob 		} else
4321fc2657fSclaudio 			repo_stat_inc(rp, v->talid, RTYPE_ROA, STYPE_UNIQUE);
433a66158d7Sjob 
4341fc2657fSclaudio 		repo_stat_inc(rp, roa->talid, RTYPE_ROA, STYPE_TOTAL);
435a382efa2Sclaudio 	}
436a382efa2Sclaudio }
437a382efa2Sclaudio 
438a382efa2Sclaudio static inline int
439a382efa2Sclaudio vrpcmp(struct vrp *a, struct vrp *b)
440a382efa2Sclaudio {
441a382efa2Sclaudio 	int rv;
442a382efa2Sclaudio 
443a382efa2Sclaudio 	if (a->afi > b->afi)
444a382efa2Sclaudio 		return 1;
445a382efa2Sclaudio 	if (a->afi < b->afi)
446a382efa2Sclaudio 		return -1;
447a382efa2Sclaudio 	switch (a->afi) {
448a382efa2Sclaudio 	case AFI_IPV4:
449a382efa2Sclaudio 		rv = memcmp(&a->addr.addr, &b->addr.addr, 4);
450a382efa2Sclaudio 		if (rv)
451a382efa2Sclaudio 			return rv;
452a382efa2Sclaudio 		break;
453a382efa2Sclaudio 	case AFI_IPV6:
454a382efa2Sclaudio 		rv = memcmp(&a->addr.addr, &b->addr.addr, 16);
455a382efa2Sclaudio 		if (rv)
456a382efa2Sclaudio 			return rv;
457a382efa2Sclaudio 		break;
458a29ddfd5Sjob 	default:
459a29ddfd5Sjob 		break;
460a382efa2Sclaudio 	}
461a382efa2Sclaudio 	/* a smaller prefixlen is considered bigger, e.g. /8 vs /10 */
462a382efa2Sclaudio 	if (a->addr.prefixlen < b->addr.prefixlen)
463a382efa2Sclaudio 		return 1;
464a382efa2Sclaudio 	if (a->addr.prefixlen > b->addr.prefixlen)
465a382efa2Sclaudio 		return -1;
466a382efa2Sclaudio 	if (a->maxlength < b->maxlength)
467a382efa2Sclaudio 		return 1;
468a382efa2Sclaudio 	if (a->maxlength > b->maxlength)
469a382efa2Sclaudio 		return -1;
470a382efa2Sclaudio 
471a382efa2Sclaudio 	if (a->asid > b->asid)
472a382efa2Sclaudio 		return 1;
473a382efa2Sclaudio 	if (a->asid < b->asid)
474a382efa2Sclaudio 		return -1;
475a382efa2Sclaudio 
476a382efa2Sclaudio 	return 0;
477a382efa2Sclaudio }
478a382efa2Sclaudio 
479a382efa2Sclaudio RB_GENERATE(vrp_tree, vrp, entry, vrpcmp);
480