aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rcynic/rcynic.c379
-rw-r--r--rcynic/rcynic.py53
-rw-r--r--rpkid/examples/rpki.conf4
-rw-r--r--rpkid/irbe_cli.py7
-rw-r--r--rpkid/rpki/__doc__.py2
-rw-r--r--rpkid/rpki/rootd.py2
-rw-r--r--rpkid/rpki/rpkid.py2
-rw-r--r--rpkid/rpki/up_down.py2
-rw-r--r--rpkid/rpki/x509.py52
-rw-r--r--rpkid/tests/Makefile.in2
-rw-r--r--rpkid/tests/left-right-protocol-samples.xml2
-rw-r--r--rpkid/tests/publication-protocol-samples.xml8
-rw-r--r--rpkid/tests/smoketest.3.yaml4
-rw-r--r--rpkid/tests/smoketest.py4
-rw-r--r--rpkid/tests/testpoke.py2
-rw-r--r--scripts/analyze-rcynic-history.py223
16 files changed, 392 insertions, 356 deletions
diff --git a/rcynic/rcynic.c b/rcynic/rcynic.c
index 3dc3c044..d345dc9f 100644
--- a/rcynic/rcynic.c
+++ b/rcynic/rcynic.c
@@ -219,6 +219,7 @@ static const struct {
QB(bad_key_usage, "Bad keyUsage") \
QB(bad_manifest_digest_length, "Bad manifest digest length") \
QB(bad_public_key, "Bad public key") \
+ QB(bad_roa_asID, "Bad ROA asID") \
QB(bad_serial_number, "Bad serialNumber") \
QB(certificate_bad_signature, "Bad certificate signature") \
QB(certificate_failed_validation, "Certificate failed validation") \
@@ -253,6 +254,7 @@ static const struct {
QB(nonconformant_signature_algorithm, "Nonconformant signature algorithm")\
QB(nonconformant_digest_algorithm, "Nonconformant digest algorithm") \
QB(object_rejected, "Object rejected") \
+ QB(rfc3779_inheritance_required, "RFC 3779 inheritance required") \
QB(roa_contains_bad_afi_value, "ROA contains bad AFI value") \
QB(roa_resource_not_in_ee, "ROA resource not in EE") \
QB(roa_resources_malformed, "ROA resources malformed") \
@@ -272,6 +274,7 @@ static const struct {
QW(crldp_names_newer_crl, "CRLDP names newer CRL") \
QW(digest_mismatch, "Digest mismatch") \
QW(issuer_uses_multiple_crldp_values, "Issuer uses multiple CRLDP values")\
+ QW(multiple_rsync_uris_in_extension, "Multiple rsync URIs in extension") \
QW(nonconformant_issuer_name, "Nonconformant X.509 issuer name") \
QW(nonconformant_subject_name, "Nonconformant X.509 subject name") \
QW(rsync_transfer_skipped, "rsync transfer skipped") \
@@ -2781,7 +2784,7 @@ static int extract_crldp_uri(rcynic_ctx_t *rc,
DIST_POINT *d;
int i;
- assert(crldp);
+ assert(rc && uri && crldp && result);
if (sk_DIST_POINT_num(crldp) != 1)
goto bad;
@@ -2795,16 +2798,18 @@ static int extract_crldp_uri(rcynic_ctx_t *rc,
GENERAL_NAME *n = sk_GENERAL_NAME_value(d->distpoint->name.fullname, i);
if (n == NULL || n->type != GEN_URI)
goto bad;
- if (!is_rsync((char *) n->d.uniformResourceIdentifier->data)) {
+ if (!is_rsync((char *) n->d.uniformResourceIdentifier->data))
log_validation_status(rc, uri, non_rsync_uri_in_extension, generation);
- } else if (sizeof(result->s) <= n->d.uniformResourceIdentifier->length) {
+ else if (sizeof(result->s) <= n->d.uniformResourceIdentifier->length)
log_validation_status(rc, uri, uri_too_long, generation);
- } else {
+ else if (result->s[0])
+ log_validation_status(rc, uri, multiple_rsync_uris_in_extension, generation);
+ else
strcpy(result->s, (char *) n->d.uniformResourceIdentifier->data);
- return 1;
- }
}
+ return result->s[0];
+
bad:
log_validation_status(rc, uri, malformed_crldp_extension, generation);
return 0;
@@ -2819,11 +2824,12 @@ static int extract_access_uri(rcynic_ctx_t *rc,
const AUTHORITY_INFO_ACCESS *xia,
const unsigned char *oid,
const int oidlen,
- uri_t *result)
+ uri_t *result,
+ int *count)
{
int i;
- assert(xia);
+ assert(rc && uri && xia && oid && result && count);
for (i = 0; i < sk_ACCESS_DESCRIPTION_num(xia); i++) {
ACCESS_DESCRIPTION *a = sk_ACCESS_DESCRIPTION_value(xia, i);
@@ -2831,14 +2837,15 @@ static int extract_access_uri(rcynic_ctx_t *rc,
return 0;
if (oid_cmp(a->method, oid, oidlen))
continue;
- if (!is_rsync((char *) a->location->d.uniformResourceIdentifier->data)) {
+ ++*count;
+ if (!is_rsync((char *) a->location->d.uniformResourceIdentifier->data))
log_validation_status(rc, uri, non_rsync_uri_in_extension, generation);
- } else if (sizeof(result->s) <= a->location->d.uniformResourceIdentifier->length) {
+ else if (sizeof(result->s) <= a->location->d.uniformResourceIdentifier->length)
log_validation_status(rc, uri, uri_too_long, generation);
- } else {
- strcpy(result->s, (char *) a->location->d.uniformResourceIdentifier->data);
- return 1;
- }
+ else if (result->s[0])
+ log_validation_status(rc, uri, multiple_rsync_uris_in_extension, generation);
+ else
+ strcpy(result->s, (char *) a->location->d.uniformResourceIdentifier->data);
}
return 1;
}
@@ -3014,21 +3021,6 @@ static X509_CRL *check_crl_1(rcynic_ctx_t *rc,
}
}
-#if 0
- /*
- * Might need to generalize this to check cert AKI as well. Haven't
- * handled cert SKI check yet either. Do we want to call
- * X509_check_akid() here or just compare the OCTET STRINGs
- * directly? 99% of X509_check_akid() is irrelevant to our profile.
- */
- if (!crl->akid ||
- !crl->akid->keyid ||
- crl->akid->serial ||
- crl->akid->issuer ||
- X509_check_akid(issuer, crl->akid) != X509_V_OK)
- bad_crl_akid;
-#endif
-
if ((pkey = X509_get_pubkey(issuer)) == NULL)
goto punt;
ret = X509_CRL_verify(crl, pkey);
@@ -3227,11 +3219,9 @@ static int check_x509(rcynic_ctx_t *rc,
ASN1_BIT_STRING *ski_pubkey = NULL;
STACK_OF(DIST_POINT) *crldp = NULL;
BASIC_CONSTRAINTS *bc = NULL;
- ASIdentifiers *asid = NULL;
- IPAddrBlocks *addr = NULL;
hashbuf_t ski_hashbuf;
unsigned ski_hashlen;
- int ok, crit, ex_count, ret = 0;
+ int ok, crit, loc, ex_count, ret = 0;
assert(rc && wsk && w && uri && x && w->cert);
@@ -3297,10 +3287,13 @@ static int check_x509(rcynic_ctx_t *rc,
}
if ((aia = X509_get_ext_d2i(x, NID_info_access, NULL, NULL)) != NULL) {
+ int n_caIssuers = 0;
ex_count--;
if (!extract_access_uri(rc, uri, generation, aia,
- id_ad_caIssuers, sizeof(id_ad_caIssuers), &certinfo->aia) ||
- !certinfo->aia.s[0]) {
+ id_ad_caIssuers, sizeof(id_ad_caIssuers),
+ &certinfo->aia, &n_caIssuers) ||
+ !certinfo->aia.s[0] ||
+ sk_ACCESS_DESCRIPTION_num(aia) != n_caIssuers) {
log_validation_status(rc, uri, malformed_aia_extension, generation);
goto done;
}
@@ -3317,18 +3310,19 @@ static int check_x509(rcynic_ctx_t *rc,
}
if ((sia = X509_get_ext_d2i(x, NID_sinfo_access, NULL, NULL)) != NULL) {
- int got_caDirectory, got_rpkiManifest, got_signedObject;
+ int got_caDirectory, got_rpkiManifest, got_signedObject;
+ int n_caDirectory = 0, n_rpkiManifest = 0, n_signedObject = 0;
ex_count--;
ok = (extract_access_uri(rc, uri, generation, sia, id_ad_caRepository,
- sizeof(id_ad_caRepository), &certinfo->sia) &&
+ sizeof(id_ad_caRepository), &certinfo->sia, &n_caDirectory) &&
extract_access_uri(rc, uri, generation, sia, id_ad_rpkiManifest,
- sizeof(id_ad_rpkiManifest), &certinfo->manifest) &&
+ sizeof(id_ad_rpkiManifest), &certinfo->manifest, &n_rpkiManifest) &&
extract_access_uri(rc, uri, generation, sia, id_ad_signedObject,
- sizeof(id_ad_signedObject), &certinfo->signedobject));
+ sizeof(id_ad_signedObject), &certinfo->signedobject, &n_signedObject));
got_caDirectory = certinfo->sia.s[0] != '\0';
got_rpkiManifest = certinfo->manifest.s[0] != '\0';
got_signedObject = certinfo->signedobject.s[0] != '\0';
- ok &= sk_ACCESS_DESCRIPTION_num(sia) == got_caDirectory + got_rpkiManifest + got_signedObject;
+ ok &= sk_ACCESS_DESCRIPTION_num(sia) == n_caDirectory + n_rpkiManifest + n_signedObject;
if (certinfo->ca)
ok &= got_caDirectory && got_rpkiManifest && !got_signedObject;
else if (rc->allow_ee_without_signedObject)
@@ -3435,23 +3429,28 @@ static int check_x509(rcynic_ctx_t *rc,
}
}
- if ((addr = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, &crit, NULL)) != NULL) {
+ if (x->rfc3779_addr) {
ex_count--;
- if (!crit || !v3_addr_is_canonical(addr)) {
+ if ((loc = X509_get_ext_by_NID(x, NID_sbgp_ipAddrBlock, -1)) < 0 ||
+ !X509_EXTENSION_get_critical(X509_get_ext(x, loc)) ||
+ !v3_addr_is_canonical(x->rfc3779_addr)) {
log_validation_status(rc, uri, bad_ipaddrblocks, generation);
goto done;
}
}
- if ((asid = X509_get_ext_d2i(x, NID_sbgp_autonomousSysNum, &crit, NULL)) != NULL) {
+ if (x->rfc3779_asid) {
ex_count--;
- if (!crit || !v3_asid_is_canonical(asid)) {
+ if ((loc = X509_get_ext_by_NID(x, NID_sbgp_autonomousSysNum, -1)) < 0 ||
+ !X509_EXTENSION_get_critical(X509_get_ext(x, loc)) ||
+ !v3_asid_is_canonical(x->rfc3779_asid) ||
+ x->rfc3779_asid->rdi != NULL) {
log_validation_status(rc, uri, bad_asidentifiers, generation);
goto done;
}
}
- if (!addr && !asid) {
+ if (!x->rfc3779_addr && !x->rfc3779_asid) {
log_validation_status(rc, uri, missing_resources, generation);
goto done;
}
@@ -3500,6 +3499,17 @@ static int check_x509(rcynic_ctx_t *rc,
goto done;
}
+ if (x->akid) {
+ ex_count--;
+ if (!check_aki(rc, uri, w->cert, x->akid, generation))
+ goto done;
+ }
+
+ if (!x->akid && !certinfo->ta) {
+ log_validation_status(rc, uri, aki_extension_missing, generation);
+ goto done;
+ }
+
if (certinfo->ta) {
if (certinfo->crldp.s[0]) {
@@ -3509,11 +3519,6 @@ static int check_x509(rcynic_ctx_t *rc,
} else {
- if (check_aki(rc, uri, w->cert, x->akid, generation))
- ex_count--;
- else
- goto done;
-
if (!certinfo->crldp.s[0]) {
log_validation_status(rc, uri, crldp_uri_missing, generation);
goto done;
@@ -3598,6 +3603,130 @@ static int check_x509(rcynic_ctx_t *rc,
}
/**
+ * Check a signed CMS object.
+ */
+static int check_cms(rcynic_ctx_t *rc,
+ STACK_OF(walk_ctx_t) *wsk,
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
+ CMS_ContentInfo **pcms,
+ X509 **px,
+ certinfo_t *certinfo,
+ BIO *bio,
+ const unsigned char *hash,
+ const size_t hashlen,
+ const unsigned char *expected_eContentType,
+ const size_t expected_eContentType_len,
+ const int require_inheritance,
+ const object_generation_t generation)
+{
+ const ASN1_OBJECT *eContentType = NULL;
+ STACK_OF(CMS_SignerInfo) *signer_infos = NULL;
+ STACK_OF(X509) *signers = NULL;
+ CMS_ContentInfo *cms = NULL;
+ CMS_SignerInfo *si = NULL;
+ ASN1_OCTET_STRING *sid = NULL;
+ X509_NAME *si_issuer = NULL;
+ ASN1_INTEGER *si_serial = NULL;
+ hashbuf_t hashbuf;
+ X509 *x = NULL;
+ certinfo_t certinfo_;
+ int i, result = 0;
+
+ assert(rc && wsk && uri && path && prefix && expected_eContentType);
+
+ if (!certinfo)
+ certinfo = &certinfo_;
+
+ if (!uri_to_filename(rc, uri, path, prefix))
+ goto error;
+
+ if (hash)
+ cms = read_cms(path, &hashbuf);
+ else
+ cms = read_cms(path, NULL);
+
+ if (!cms)
+ goto error;
+
+ if (hash && (hashlen > sizeof(hashbuf.h) ||
+ memcmp(hashbuf.h, hash, hashlen))) {
+ log_validation_status(rc, uri, digest_mismatch, generation);
+ if (!rc->allow_digest_mismatch)
+ goto error;
+ }
+
+ if (!(eContentType = CMS_get0_eContentType(cms)) ||
+ oid_cmp(eContentType, expected_eContentType,
+ expected_eContentType_len)) {
+ log_validation_status(rc, uri, bad_cms_econtenttype, generation);
+ goto error;
+ }
+
+ if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
+ log_validation_status(rc, uri, cms_validation_failure, generation);
+ goto error;
+ }
+
+ if (!(signers = CMS_get0_signers(cms)) || sk_X509_num(signers) != 1 ||
+ (x = sk_X509_value(signers, 0)) == NULL) {
+ log_validation_status(rc, uri, cms_signer_missing, generation);
+ goto error;
+ }
+
+ if ((signer_infos = CMS_get0_SignerInfos(cms)) == NULL ||
+ sk_CMS_SignerInfo_num(signer_infos) != 1 ||
+ (si = sk_CMS_SignerInfo_value(signer_infos, 0)) == NULL ||
+ !CMS_SignerInfo_get0_signer_id(si, &sid, &si_issuer, &si_serial) ||
+ sid == NULL || si_issuer != NULL || si_serial != NULL) {
+ log_validation_status(rc, uri, bad_cms_signer_infos, generation);
+ goto error;
+ }
+
+ if (CMS_SignerInfo_cert_cmp(si, x)) {
+ log_validation_status(rc, uri, cms_ski_mismatch, generation);
+ goto error;
+ }
+
+ if (!check_x509(rc, wsk, uri, x, certinfo, generation))
+ goto error;
+
+ if (require_inheritance && x->rfc3779_addr) {
+ for (i = 0; i < sk_IPAddressFamily_num(x->rfc3779_addr); i++) {
+ IPAddressFamily *f = sk_IPAddressFamily_value(x->rfc3779_addr, i);
+ if (f->ipAddressChoice->type != IPAddressChoice_inherit) {
+ log_validation_status(rc, uri, rfc3779_inheritance_required, generation);
+ goto error;
+ }
+ }
+ }
+
+ if (require_inheritance && x->rfc3779_asid && x->rfc3779_asid->asnum &&
+ x->rfc3779_asid->asnum->type != ASIdentifierChoice_inherit) {
+ log_validation_status(rc, uri, rfc3779_inheritance_required, generation);
+ goto error;
+ }
+
+ if (pcms) {
+ *pcms = cms;
+ cms = NULL;
+ }
+
+ if (px)
+ *px = x;
+
+ result = 1;
+
+ error:
+ CMS_ContentInfo_free(cms);
+
+ return result;
+}
+
+
+
+/**
* Load certificate, check against manifest, then run it through all
* the check_x509() tests.
*/
@@ -3707,43 +3836,21 @@ static Manifest *check_manifest_1(rcynic_ctx_t *rc,
const object_generation_t generation)
{
Manifest *manifest = NULL, *result = NULL;
- const ASN1_OBJECT *eContentType = NULL;
- STACK_OF(X509) *signers = NULL;
CMS_ContentInfo *cms = NULL;
FileAndHash *fah = NULL;
BIO *bio = NULL;
- X509 *ee;
+ X509 *x;
int i;
assert(rc && wsk && uri && path && prefix);
- if (!uri_to_filename(rc, uri, path, prefix) ||
- (cms = read_cms(path, NULL)) == NULL)
- goto done;
-
- if ((eContentType = CMS_get0_eContentType(cms)) == NULL ||
- oid_cmp(eContentType, id_ct_rpkiManifest, sizeof(id_ct_rpkiManifest))) {
- log_validation_status(rc, uri, bad_cms_econtenttype, generation);
- goto done;
- }
-
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
logmsg(rc, log_sys_err, "Couldn't allocate BIO for manifest %s", uri->s);
goto done;
}
- if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- log_validation_status(rc, uri, cms_validation_failure, generation);
- goto done;
- }
-
- if ((signers = CMS_get0_signers(cms)) == NULL || sk_X509_num(signers) != 1 ||
- (ee = sk_X509_value(signers, 0)) == NULL) {
- log_validation_status(rc, uri, cms_signer_missing, generation);
- goto done;
- }
-
- if (!check_x509(rc, wsk, uri, ee, certinfo, generation))
+ if (!check_cms(rc, wsk, uri, path, prefix, &cms, &x, certinfo, bio, NULL, 0,
+ id_ct_rpkiManifest, sizeof(id_ct_rpkiManifest), 1, generation))
goto done;
if ((manifest = ASN1_item_d2i_bio(ASN1_ITEM_rptr(Manifest), bio, NULL)) == NULL) {
@@ -3793,7 +3900,6 @@ static Manifest *check_manifest_1(rcynic_ctx_t *rc,
BIO_free(bio);
Manifest_free(manifest);
CMS_ContentInfo_free(cms);
- sk_X509_free(signers);
return result;
}
@@ -3949,21 +4055,12 @@ static int check_roa_1(rcynic_ctx_t *rc,
const size_t hashlen,
const object_generation_t generation)
{
- unsigned char addrbuf[ADDR_RAW_BUF_LEN];
- const ASN1_OBJECT *eContentType = NULL;
STACK_OF(IPAddressFamily) *roa_resources = NULL, *ee_resources = NULL;
- STACK_OF(CMS_SignerInfo) *signer_infos = NULL;
- STACK_OF(X509) *signers = NULL;
+ unsigned char addrbuf[ADDR_RAW_BUF_LEN];
CMS_ContentInfo *cms = NULL;
- CMS_SignerInfo *si = NULL;
- ASN1_OCTET_STRING *sid = NULL;
- X509_NAME *si_issuer = NULL;
- ASN1_INTEGER *si_serial = NULL;
- hashbuf_t hashbuf;
- ROA *roa = NULL;
BIO *bio = NULL;
+ ROA *roa = NULL;
X509 *x = NULL;
- certinfo_t certinfo;
int i, j, result = 0;
unsigned afi, *safi = NULL, safi_, prefixlen;
ROAIPAddressFamily *rf;
@@ -3971,62 +4068,14 @@ static int check_roa_1(rcynic_ctx_t *rc,
assert(rc && wsk && uri && path && prefix);
- if (!uri_to_filename(rc, uri, path, prefix))
- goto error;
-
- if (hash)
- cms = read_cms(path, &hashbuf);
- else
- cms = read_cms(path, NULL);
-
- if (!cms)
- goto error;
-
- if (hash && (hashlen > sizeof(hashbuf.h) ||
- memcmp(hashbuf.h, hash, hashlen))) {
- log_validation_status(rc, uri, digest_mismatch, generation);
- if (!rc->allow_digest_mismatch)
- goto error;
- }
-
- if (!(eContentType = CMS_get0_eContentType(cms)) ||
- oid_cmp(eContentType, id_ct_routeOriginAttestation,
- sizeof(id_ct_routeOriginAttestation))) {
- log_validation_status(rc, uri, bad_cms_econtenttype, generation);
- goto error;
- }
-
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
logmsg(rc, log_sys_err, "Couldn't allocate BIO for ROA %s", uri->s);
goto error;
}
- if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- log_validation_status(rc, uri, cms_validation_failure, generation);
- goto error;
- }
-
- if (!(signers = CMS_get0_signers(cms)) || sk_X509_num(signers) != 1 ||
- (x = sk_X509_value(signers, 0)) == NULL) {
- log_validation_status(rc, uri, cms_signer_missing, generation);
- goto error;
- }
-
- if ((signer_infos = CMS_get0_SignerInfos(cms)) == NULL ||
- sk_CMS_SignerInfo_num(signer_infos) != 1 ||
- (si = sk_CMS_SignerInfo_value(signer_infos, 0)) == NULL ||
- !CMS_SignerInfo_get0_signer_id(si, &sid, &si_issuer, &si_serial) ||
- sid == NULL || si_issuer != NULL || si_serial != NULL) {
- log_validation_status(rc, uri, bad_cms_signer_infos, generation);
- goto error;
- }
-
- if (CMS_SignerInfo_cert_cmp(si, x)) {
- log_validation_status(rc, uri, cms_ski_mismatch, generation);
- goto error;
- }
-
- if (!check_x509(rc, wsk, uri, x, &certinfo, generation))
+ if (!check_cms(rc, wsk, uri, path, prefix, &cms, &x, NULL, bio, NULL, 0,
+ id_ct_routeOriginAttestation, sizeof(id_ct_routeOriginAttestation),
+ 0, generation))
goto error;
if (!(roa = ASN1_item_d2i_bio(ASN1_ITEM_rptr(ROA), bio, NULL))) {
@@ -4039,12 +4088,12 @@ static int check_roa_1(rcynic_ctx_t *rc,
goto error;
}
- /*
- * ROA issuer doesn't need rights to the ASN, so we don't need to
- * check the asID field.
- */
+ if (ASN1_INTEGER_cmp(roa->asID, asn1_zero) < 0) {
+ log_validation_status(rc, uri, bad_roa_asID, generation);
+ goto error;
+ }
- ee_resources = X509_get_ext_d2i(sk_X509_value(signers, 0), NID_sbgp_ipAddrBlock, NULL, NULL);
+ ee_resources = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, NULL, NULL);
/*
* Extract prefixes from ROA and convert them into a resource set.
@@ -4134,7 +4183,6 @@ static int check_roa_1(rcynic_ctx_t *rc,
BIO_free(bio);
ROA_free(roa);
CMS_ContentInfo_free(cms);
- sk_X509_free(signers);
sk_IPAddressFamily_pop_free(roa_resources, IPAddressFamily_free);
sk_IPAddressFamily_pop_free(ee_resources, IPAddressFamily_free);
@@ -4199,41 +4247,13 @@ static int check_ghostbuster_1(rcynic_ctx_t *rc,
const size_t hashlen,
const object_generation_t generation)
{
- const ASN1_OBJECT *eContentType = NULL;
- STACK_OF(X509) *signers = NULL;
CMS_ContentInfo *cms = NULL;
- hashbuf_t hashbuf;
BIO *bio = NULL;
- certinfo_t certinfo;
+ X509 *x;
int result = 0;
assert(rc && wsk && uri && path && prefix);
- if (!uri_to_filename(rc, uri, path, prefix))
- goto error;
-
- if (hash)
- cms = read_cms(path, &hashbuf);
- else
- cms = read_cms(path, NULL);
-
- if (!cms)
- goto error;
-
- if (hash && (hashlen > sizeof(hashbuf.h) ||
- memcmp(hashbuf.h, hash, hashlen))) {
- log_validation_status(rc, uri, digest_mismatch, generation);
- if (!rc->allow_digest_mismatch)
- goto error;
- }
-
- if (!(eContentType = CMS_get0_eContentType(cms)) ||
- oid_cmp(eContentType, id_ct_rpkiGhostbusters,
- sizeof(id_ct_rpkiGhostbusters))) {
- log_validation_status(rc, uri, bad_cms_econtenttype, generation);
- goto error;
- }
-
#if 0
/*
* May want this later if we're going to inspect the VCard. For now,
@@ -4245,15 +4265,10 @@ static int check_ghostbuster_1(rcynic_ctx_t *rc,
}
#endif
- if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- log_validation_status(rc, uri, cms_validation_failure, generation);
- goto error;
- }
-
- if (!(signers = CMS_get0_signers(cms)) || sk_X509_num(signers) != 1) {
- log_validation_status(rc, uri, cms_signer_missing, generation);
+ if (!check_cms(rc, wsk, uri, path, prefix, &cms, &x, NULL, bio, NULL, 0,
+ id_ct_rpkiGhostbusters, sizeof(id_ct_rpkiGhostbusters),
+ 1, generation))
goto error;
- }
#if 0
/*
@@ -4262,15 +4277,11 @@ static int check_ghostbuster_1(rcynic_ctx_t *rc,
*/
#endif
- if (!check_x509(rc, wsk, uri, sk_X509_value(signers, 0), &certinfo, generation))
- goto error;
-
result = 1;
error:
BIO_free(bio);
CMS_ContentInfo_free(cms);
- sk_X509_free(signers);
return result;
}
diff --git a/rcynic/rcynic.py b/rcynic/rcynic.py
index 8bdb7786..b7ddb6fc 100644
--- a/rcynic/rcynic.py
+++ b/rcynic/rcynic.py
@@ -24,13 +24,14 @@ import sys, urlparse, os, getopt
from xml.etree.ElementTree import (ElementTree, Element, SubElement, Comment)
opt = {
- "refresh" : 1800,
- "suppress_zero_columns" : True,
- "use_colors" : True,
- "show_detailed_status" : True,
- "show_problems" : False,
- "show_summary" : True,
- "one_file_per_section" : False }
+ "refresh" : 1800,
+ "suppress_zero_columns" : True,
+ "use_colors" : True,
+ "show_detailed_status" : True,
+ "show_problems" : False,
+ "show_summary" : True,
+ "suppress_backup_whining" : True,
+ "one_file_per_section" : False }
def usage(msg = 0):
f = sys.stderr if msg else sys.stdout
@@ -79,13 +80,17 @@ class Label(object):
class Validation_Status(object):
- def __init__(self, elt, map):
+ label_map = None
+
+ def __init__(self, elt):
self.uri = elt.text.strip()
self.timestamp = elt.get("timestamp")
self.generation = elt.get("generation")
self.hostname = urlparse.urlparse(self.uri).hostname or None
self.fn2 = os.path.splitext(self.uri)[1] or None if self.generation else None
- self.label = map[elt.get("status")]
+ self.label = self.label_map[elt.get("status")]
+
+ def stand_up_and_be_counted(self):
self.label.sum += 1
@property
@@ -96,6 +101,22 @@ class Validation_Status(object):
def mood(self):
return self.label.mood
+ @property
+ def accepted(self):
+ return self.label.code == "object_accepted"
+
+ @property
+ def rejected(self):
+ return self.label.code == "object_rejected"
+
+ @property
+ def is_current(self):
+ return self.generation == "current"
+
+ @property
+ def is_backup(self):
+ return self.generation == "backup"
+
html = None
body = None
@@ -150,9 +171,17 @@ def finish_html(name = None):
input = ElementTree(file = sys.stdin if input_file is None else input_file)
labels = [Label(elt) for elt in input.find("labels")]
-label_map = dict((l.code, l) for l in labels)
-validation_status = [Validation_Status(elt, label_map) for elt in input.findall("validation_status")]
-del label_map
+Validation_Status.label_map = dict((l.code, l) for l in labels)
+validation_status = [Validation_Status(elt) for elt in input.findall("validation_status")]
+
+if opt["suppress_backup_whining"]:
+
+ accepted_current = set(v.uri for v in validation_status if v.is_current and v.accepted)
+ validation_status = [v for v in validation_status if not v.is_backup or v.uri not in accepted_current]
+
+for v in validation_status:
+ v.stand_up_and_be_counted()
+
if opt["suppress_zero_columns"]:
labels = [l for l in labels if l.sum > 0]
diff --git a/rpkid/examples/rpki.conf b/rpkid/examples/rpki.conf
index 53216b97..84d7109c 100644
--- a/rpkid/examples/rpki.conf
+++ b/rpkid/examples/rpki.conf
@@ -331,7 +331,7 @@ rpki-root-crl = root.crl
# Filename (relative to rootd-base-uri and rpki-root-dir) of the
# manifest for rootd's root RPKI certificate
-rpki-root-manifest = root.mnf
+rpki-root-manifest = root.mft
# Up-down protocol class name for RPKI certificate rootd issues to its
# one (and only) child
@@ -364,7 +364,7 @@ root_cert_sia = rsync://${myrpki::publication_rsync_server}/${myrpki::publicat
# root_cert_sia + rpki-root-manifest
-root_cert_manifest = rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/root.mnf
+root_cert_manifest = rsync://${myrpki::publication_rsync_server}/${myrpki::publication_rsync_module}/root.mft
#################################################################
diff --git a/rpkid/irbe_cli.py b/rpkid/irbe_cli.py
index f7593a75..637ad720 100644
--- a/rpkid/irbe_cli.py
+++ b/rpkid/irbe_cli.py
@@ -227,9 +227,14 @@ class roa_elt(cmd_elt_mixin, rpki.publication.roa_elt):
class report_error_elt(reply_elt_mixin, rpki.publication.report_error_elt):
pass
+class ghostbuster_elt(cmd_elt_mixin, rpki.publication.ghostbuster_elt):
+ pass
+
class publication_msg(cmd_msg_mixin, rpki.publication.msg):
pdus = dict((x.element_name, x)
- for x in (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt))
+ for x in (config_elt, client_elt, certificate_elt, crl_elt,
+ manifest_elt, roa_elt, report_error_elt,
+ ghostbuster_elt))
class publication_sax_handler(rpki.publication.sax_handler):
pdu = publication_msg
diff --git a/rpkid/rpki/__doc__.py b/rpkid/rpki/__doc__.py
index 1f9a7ec2..c53de51e 100644
--- a/rpkid/rpki/__doc__.py
+++ b/rpkid/rpki/__doc__.py
@@ -1382,7 +1382,7 @@
#
# @par @c rpki-root-manifest:
# Name of file to which rootd should save its
-# RPKI manifest. Default is "Root.mnf".
+# RPKI manifest. Default is "Root.mft".
#
# @par @c rpki-subject-pkcs10:
# Name of file that rootd should use when saving
diff --git a/rpkid/rpki/rootd.py b/rpkid/rpki/rootd.py
index 26553b33..44e6af83 100644
--- a/rpkid/rpki/rootd.py
+++ b/rpkid/rpki/rootd.py
@@ -306,7 +306,7 @@ class main(object):
self.rpki_root_cert_file = self.cfg.get("rpki-root-cert")
self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "Root.cer")
- self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "Root.mnf")
+ self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "Root.mft")
self.rpki_root_crl = self.cfg.get("rpki-root-crl", "Root.crl")
self.rpki_subject_cert = self.cfg.get("rpki-subject-cert", "Child.cer")
self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10", "Child.pkcs10")
diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py
index 9a9be46e..715a8aa2 100644
--- a/rpkid/rpki/rpkid.py
+++ b/rpkid/rpki/rpkid.py
@@ -742,7 +742,7 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
Return publication URI for this ca_detail's manifest.
"""
- return self.ca.sia_uri + self.public_key.gSKI() + ".mnf"
+ return self.ca.sia_uri + self.public_key.gSKI() + ".mft"
def has_expired(self):
"""
diff --git a/rpkid/rpki/up_down.py b/rpkid/rpki/up_down.py
index 009818cb..0eba6b52 100644
--- a/rpkid/rpki/up_down.py
+++ b/rpkid/rpki/up_down.py
@@ -704,3 +704,5 @@ class cms_msg(rpki.x509.XML_CMS_object):
encoding = "UTF-8"
schema = rpki.relaxng.up_down
saxify = sax_handler.saxify
+ allow_extra_certs = True
+ allow_extra_crls = True
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 7bbb47bc..955b8d97 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -940,11 +940,12 @@ class RSA(DER_object):
return self.POW
@classmethod
- def generate(cls, keylength = 2048):
+ def generate(cls, keylength = 2048, quiet = False):
"""
Generate a new keypair.
"""
- rpki.log.debug("Generating new %d-bit RSA key" % keylength)
+ if not quiet:
+ rpki.log.debug("Generating new %d-bit RSA key" % keylength)
return cls(POW = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, keylength))
def get_public_DER(self):
@@ -1052,6 +1053,16 @@ class CMS_object(DER_object):
require_crls = False
+ ## @var allow_extra_certs
+ # Set this to True to allow CMS messages to contain CA certificates.
+
+ allow_extra_certs = False
+
+ ## @var allow_extra_crls
+ # Set this to True to allow CMS messages to contain multiple CRLs.
+
+ allow_extra_crls = False
+
## @var print_on_der_error
# Set this to True to log alleged DER when we have trouble parsing
# it, in case it's really a Perl backtrace or something.
@@ -1136,36 +1147,41 @@ class CMS_object(DER_object):
if self.debug_cms_certs:
rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
if x.getNotAfter() < now:
- raise rpki.exceptions.TrustedCMSCertHasExpired
+ raise rpki.exceptions.TrustedCMSCertHasExpired("Trusted CMS certificate has expired", "%s (%s)" % (x.getSubject(), x.hSKI()))
if not x.is_CA():
- if trusted_ee is not None:
- raise rpki.exceptions.MultipleCMSEECert
- trusted_ee = x
+ if trusted_ee is None:
+ trusted_ee = x
+ else:
+ raise rpki.exceptions.MultipleCMSEECert("Multiple CMS EE certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in ta if not x.is_CA()))
store.addTrust(x.get_POW())
if trusted_ee:
if self.debug_cms_certs:
rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
- if certs and (len(certs) > 1 or certs[0].getSubject() != trusted_ee.getSubject() or certs[0].getPublicKey() != trusted_ee.getPublicKey()):
- raise rpki.exceptions.UnexpectedCMSCerts # , certs
+ if len(certs) > 1 or (len(certs) == 1 and
+ (certs[0].getSubject() != trusted_ee.getSubject() or
+ certs[0].getPublicKey() != trusted_ee.getPublicKey())):
+ raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in certs))
if crls:
- rpki.log.warn("Ignoring unexpected CMS CRL%s from trusted peer" % ("" if len(crls) == 1 else "s"))
+ raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (c.getIssuer(), c.hAKI()) for c in crls))
+
else:
- if not certs:
- raise rpki.exceptions.MissingCMSEEcert # , certs
- if len(certs) > 1 or certs[0].is_CA():
- raise rpki.exceptions.UnexpectedCMSCerts # , certs
- if not crls:
+ untrusted_ee = [x for x in certs if not x.is_CA()]
+ if len(untrusted_ee) < 1:
+ raise rpki.exceptions.MissingCMSEEcert
+ if len(untrusted_ee) > 1 or (not self.allow_extra_certs and len(certs) > len(untrusted_ee)):
+ raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in certs))
+ if len(crls) < 1:
if self.require_crls:
- raise rpki.exceptions.MissingCMSCRL # , crls
+ raise rpki.exceptions.MissingCMSCRL
else:
rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting")
- if len(crls) > 1:
- raise rpki.exceptions.UnexpectedCMSCRLs # , crls
+ if len(crls) > 1 and not self.allow_extra_crls:
+ raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (c.getIssuer(), c.hAKI()) for c in crls))
for x in certs:
if x.getNotAfter() < now:
- raise rpki.exceptions.CMSCertHasExpired # , x
+ raise rpki.exceptions.CMSCertHasExpired("CMS certificate has expired", "%s (%s)" % (x.getSubject(), x.hSKI()))
try:
content = cms.verify(store)
diff --git a/rpkid/tests/Makefile.in b/rpkid/tests/Makefile.in
index f86573da..35cd70c3 100644
--- a/rpkid/tests/Makefile.in
+++ b/rpkid/tests/Makefile.in
@@ -6,7 +6,7 @@ abs_top_builddir = @abs_top_builddir@
all: protocol-samples
clean:
- rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples yamltest.dir
+ rm -rf smoketest.dir left-right-protocol-samples publication-protocol-samples yamltest.dir rcynic.xml rcynic-data
protocol-samples: left-right-protocol-samples/.stamp publication-protocol-samples/.stamp
diff --git a/rpkid/tests/left-right-protocol-samples.xml b/rpkid/tests/left-right-protocol-samples.xml
index 94727558..7b97386d 100644
--- a/rpkid/tests/left-right-protocol-samples.xml
+++ b/rpkid/tests/left-right-protocol-samples.xml
@@ -973,7 +973,7 @@
fBk4i7H945v/zs7bLLMJxTs8+ao4iCDuknjbGhjWmi9xrTXDtcCXx607rPDkJQcJE2WnRS/U
HIA=
</list_published_objects>
- <list_published_objects self_handle="42" uri="rsync://rpki.example.org/rpki/DEMEtlxZrZes7TNGbe7XwVSMgW0.mnf">
+ <list_published_objects self_handle="42" uri="rsync://rpki.example.org/rpki/DEMEtlxZrZes7TNGbe7XwVSMgW0.mft">
MIIHBQYJKoZIhvcNAQcCoIIG9jCCBvICAQMxDTALBglghkgBZQMEAgEwggEfBgsqhkiG9w0B
CRABGqCCAQ4EggEKMIIBBgICAWoYDzIwMDkwOTI4MjA1MTQ5WhgPMjAwOTA5MjgyMTUxNDla
BglghkgBZQMEAgEwgdIwRBYfREVNRXRseFpyWmVzN1ROR2JlN1h3VlNNZ1cwLmNybAMhAPgd
diff --git a/rpkid/tests/publication-protocol-samples.xml b/rpkid/tests/publication-protocol-samples.xml
index 12df2785..96b095a7 100644
--- a/rpkid/tests/publication-protocol-samples.xml
+++ b/rpkid/tests/publication-protocol-samples.xml
@@ -256,7 +256,7 @@
<!-- === -->
<msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mnf">
+ <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft">
MIIHCgYJKoZIhvcNAQcCoIIG+zCCBvcCAQMxDTALBglghkgBZQMEAgEwggEeBgsqhkiG9w0B
CRABGqCCAQ0EggEJMIIBBQIBEhgPMjAwODA1MjIxODA1MTVaGA8yMDA4MDUyMjE4MDYxNVoG
CWCGSAFlAwQCATCB0jBEFh9ZbTVUTzRJYnlDb0pNZ3E2R2o4dG41Mng5U0UuY2VyAyEA4L8Z
@@ -295,15 +295,15 @@
</msg>
<msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mnf"/>
+ <manifest action="publish" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
</msg>
<msg version="1" type="query" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mnf"/>
+ <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
</msg>
<msg version="1" type="reply" xmlns="http://www.hactrn.net/uris/rpki/publication-spec/">
- <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mnf"/>
+ <manifest action="withdraw" uri="rsync://wombat.invalid/testbed/RIR/R0/1/j7ghjwblCrcCp9ltyPDNzYKPfxc.mft"/>
</msg>
<!-- === -->
diff --git a/rpkid/tests/smoketest.3.yaml b/rpkid/tests/smoketest.3.yaml
index 06ab25ea..f7e4d2a9 100644
--- a/rpkid/tests/smoketest.3.yaml
+++ b/rpkid/tests/smoketest.3.yaml
@@ -51,10 +51,10 @@ kids:
ipv4: 10.3.0.0/23
---
#- shell find publication -type f -name '*.roa' -print -exec ../../../utils/print_roa/print_roa {} \;
-#- shell find publication -type f -name '*.mnf' -print -exec ../../../utils/print_manifest/print_manifest {} \;
+#- shell find publication -type f -name '*.mft' -print -exec ../../../utils/print_manifest/print_manifest {} \;
#---
#- shell find publication -type f -name '*.roa' -print -exec ../../../utils/print_roa/print_roa {} \;
-#- shell find publication -type f -name '*.mnf' -print -exec ../../../utils/print_manifest/print_manifest {} \;
+#- shell find publication -type f -name '*.mft' -print -exec ../../../utils/print_manifest/print_manifest {} \;
#---
- shell set -x; rtr_origin=../../../rtr-origin/rtr-origin; $rtr_origin --cronjob rcynic-data/authenticated && $rtr_origin --show
---
diff --git a/rpkid/tests/smoketest.py b/rpkid/tests/smoketest.py
index 7dfc584c..fa686afd 100644
--- a/rpkid/tests/smoketest.py
+++ b/rpkid/tests/smoketest.py
@@ -1433,7 +1433,7 @@ rpki-subject-pkcs10 = %(rootd_name)s.subject.pkcs10
rpki-subject-lifetime = %(lifetime)s
rpki-root-crl = Bandicoot.crl
-rpki-root-manifest = Bandicoot.mnf
+rpki-root-manifest = Bandicoot.mft
rpki-class-name = Wombat
rpki-subject-cert = Wombat.cer
@@ -1459,7 +1459,7 @@ authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
keyUsage = critical,keyCertSign,cRLSign
-subjectInfoAccess = 1.3.6.1.5.5.7.48.5;URI:%(rootd_sia)s,1.3.6.1.5.5.7.48.10;URI:%(rootd_sia)sBandicoot.mnf
+subjectInfoAccess = 1.3.6.1.5.5.7.48.5;URI:%(rootd_sia)s,1.3.6.1.5.5.7.48.10;URI:%(rootd_sia)sBandicoot.mft
sbgp-autonomousSysNum = critical,AS:0-4294967295
sbgp-ipAddrBlock = critical,IPv4:0.0.0.0/0,IPv6:0::/0
certificatePolicies = critical, @rpki_certificate_policy
diff --git a/rpkid/tests/testpoke.py b/rpkid/tests/testpoke.py
index 26cd29aa..8851a821 100644
--- a/rpkid/tests/testpoke.py
+++ b/rpkid/tests/testpoke.py
@@ -137,7 +137,7 @@ def do_issue():
q_pdu = rpki.up_down.issue_pdu()
req_key = get_PEM("cert-request-key", rpki.x509.RSA, yaml_req) or cms_key
sia = ((rpki.oids.name2oid["id-ad-caRepository"], ("uri", yaml_req["sia"][0])),
- (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", yaml_req["sia"][0] + req_key.gSKI() + ".mnf")))
+ (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", yaml_req["sia"][0] + req_key.gSKI() + ".mft")))
q_pdu.class_name = yaml_req["class"]
q_pdu.pkcs10 = rpki.x509.PKCS10.create_ca(req_key, sia)
query_up_down(q_pdu)
diff --git a/scripts/analyze-rcynic-history.py b/scripts/analyze-rcynic-history.py
index 1713e7ce..7d918198 100644
--- a/scripts/analyze-rcynic-history.py
+++ b/scripts/analyze-rcynic-history.py
@@ -4,7 +4,7 @@ summaries and run gnuplot to draw some pictures.
$Id$
-Copyright (C) 2011 Internet Systems Consortium ("ISC")
+Copyright (C) 2011-2012 Internet Systems Consortium ("ISC")
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -19,77 +19,58 @@ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
"""
-show_summary = True
-show_sessions = True
-show_plot = True
-plot_all_hosts = False
-plot_to_one = True
-plot_to_many = True
+plot_all_hosts = False
+plot_to_one = True
+plot_to_many = True
+write_rcynic_xml = True
import mailbox, sys, urlparse, os, getopt, datetime, subprocess
from xml.etree.cElementTree import (ElementTree as ElementTree,
fromstring as ElementTreeFromString)
-class Rsync_History(object):
+def parse_utc(s):
+ return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ")
- timestamp_format = "%Y-%m-%dT%H:%M:%SZ"
+class Rsync_History(object):
+ """
+ An Rsync_History object represents one rsync connection.
+ """
def __init__(self, elt):
- self.started = datetime.datetime.strptime(elt.get("started"), self.timestamp_format)
- self.finished = datetime.datetime.strptime(elt.get("finished"), self.timestamp_format)
self.error = elt.get("error")
self.uri = elt.text.strip()
self.hostname = urlparse.urlparse(self.uri).hostname or None
- self.elapsed = self.finished - self.started
-
- def __cmp__(self, other):
- return (cmp(self.started, other.started) or
- cmp(self.finished, other.finished) or
- cmp(self.hostname, other.hostname))
+ self.elapsed = parse_utc(elt.get("finished")) - parse_utc(elt.get("started"))
class Host(object):
+ """
+ A host object represents all the data collected for one host. Note
+ that it (usually) contains a list of all the sessions in which this
+ host appears.
+ """
- def __init__(self, hostname, session_id = None):
+ def __init__(self, hostname, session_id):
self.hostname = hostname
- self.session_ids = []
- if session_id is not None:
- self.session_ids.append(session_id)
+ self.session_id = session_id
self.elapsed = datetime.timedelta(0)
self.connection_count = 0
self.dead_connections = 0
self.uris = set()
- self.connections = []
-
- def __add__(self, other):
- assert self.hostname == other.hostname
- result = self.__class__(self.hostname)
- for a in ("elapsed", "connection_count", "dead_connections", "session_ids", "connections"):
- setattr(result, a, getattr(self, a) + getattr(other, a))
- result.uris = self.uris | other.uris
- return result
+ self.total_connection_time = datetime.timedelta(0)
def add_rsync_history(self, h):
- self.connection_count += 1
- self.elapsed += h.elapsed
- self.dead_connections += int(h.error is not None)
- self.connections.append(h)
+ self.connection_count += 1
+ self.elapsed += h.elapsed
+ self.dead_connections += int(h.error is not None)
+ self.total_connection_time += h.elapsed
def add_uri(self, u):
self.uris.add(u)
- @property
- def session_id(self):
- assert len(self.session_ids) == 1
- return self.session_ids[0]
-
- @property
- def session_count(self):
- return len(self.session_ids)
-
- @property
- def object_count(self):
- return len(self.uris)
+ def finalize(self):
+ self.object_count = len(self.uris)
+ del self.uris
@property
def failure_rate_percentage(self):
@@ -97,28 +78,15 @@ class Host(object):
@property
def seconds_per_object(self):
- return (float((self.elapsed.days * 24 * 3600 + self.elapsed.seconds) * 10**6 +
- self.elapsed.microseconds) /
- float(self.object_count * self.session_count * 10**6))
+ return float(self.elapsed.total_seconds()) / float(self.object_count)
@property
def objects_per_connection(self):
- return (float(self.object_count * self.session_count) /
- float(self.connection_count))
-
- @property
- def scaled_connections(self):
- return float(self.connection_count) / float(self.session_count)
-
- @property
- def scaled_elapsed(self):
- return self.elapsed / self.session_count
+ return float(self.object_count) / float(self.connection_count)
@property
def average_connection_time(self):
- return (float(sum(((c.elapsed.days * 24 * 3600 + c.elapsed.seconds) * 10**6 + c.elapsed.microseconds)
- for c in self.connections)) /
- float(self.connection_count * 10**6))
+ return float(self.total_connection_time.total_seconds()) / float(self.connection_count)
class Format(object):
@@ -135,8 +103,7 @@ class Host(object):
except ZeroDivisionError:
return self.oops
- format = (Format("scaled_elapsed", "Rsync Time", ".10s"),
- Format("scaled_connections", "Connections", "d"),
+ format = (Format("connection_count", "Connections", "d"),
Format("object_count", "Objects", "d"),
Format("objects_per_connection", "Objects/Connection", ".3f"),
Format("seconds_per_object", "Seconds/Object", ".3f"),
@@ -157,9 +124,14 @@ class Host(object):
return self.format_dict[name](self).strip()
class Session(dict):
+ """
+ A session corresponds to one XML file. This is a dictionary of Host
+ objects, keyed by hostname.
+ """
- def __init__(self, session_id = None):
+ def __init__(self, session_id, msg_key):
self.session_id = session_id
+ self.msg_key = msg_key
@property
def hostnames(self):
@@ -168,17 +140,6 @@ class Session(dict):
def get_plot_row(self, name, hostnames):
return (self.session_id,) + tuple(self[h].format_field(name) if h in self else "" for h in hostnames)
- def __add__(self, other):
- result = self.__class__()
- for h in self.hostnames | other.hostnames:
- if h in self and h in other:
- result[h] = self[h] + other[h]
- elif h in self:
- result[h] = self[h]
- else:
- result[h] = other[h]
- return result
-
def add_rsync_history(self, h):
if h.hostname not in self:
self[h.hostname] = Host(h.hostname, self.session_id)
@@ -189,43 +150,9 @@ class Session(dict):
if h and h in self:
self[h].add_uri(u)
- def dump(self, title, f = sys.stdout):
- f.write("\n" + title + "\n" + Host.header + "\n")
- for h in sorted(self):
- f.write(str(self[h]) + "\n")
-
-mb = mailbox.Maildir("/u/sra/rpki/rcynic-xml", factory = None, create = False)
-
-sessions = []
-
-for msg in mb.itervalues():
-
- sys.stderr.write(".")
-
- assert not msg.is_multipart()
-
- input = ElementTreeFromString(msg.get_payload())
-
- session = Session(input.get("date"))
- sessions.append(session)
-
- for elt in input.findall("rsync_history"):
- session.add_rsync_history(Rsync_History(elt))
-
- for elt in input.findall("validation_status"):
- if elt.get("generation") == "current":
- session.add_uri(elt.text.strip())
-
-sys.stderr.write("\n")
-
-summary = sum(sessions, Session())
-
-if show_summary:
- summary.dump("Summary (%d sessions)" % len(sessions))
-
-if show_sessions:
- for i, session in enumerate(sessions, 1):
- session.dump("Session #%d (%s)" % (i, session.session_id))
+ def finalize(self):
+ for h in self.itervalues():
+ h.finalize()
def plotter(f, hostnames, field, logscale = False):
plotlines = sorted(session.get_plot_row(field, hostnames) for session in sessions)
@@ -246,7 +173,7 @@ def plotter(f, hostnames, field, logscale = False):
#set format x '%m/%d'
set format x '%b%d'
#set title '""" + title + """'
- plot""" + ",".join(" '-' using 1:2 with lines title '%s'" % h for h in hostnames) + "\n")
+ plot""" + ",".join(" '-' using 1:2 with linespoints pointinterval 500 title '%s'" % h for h in hostnames) + "\n")
for i in xrange(1, n):
for plotline in plotlines:
f.write("%s %s\n" % (plotline[0], plotline[i].rstrip("%")))
@@ -267,19 +194,65 @@ def plot_one(hostnames, fields):
gnuplot.stdin.write("set terminal pdf\n")
gnuplot.stdin.write("set output 'analyze-rcynic-history.pdf'\n")
for field in fields:
- if field not in ("scaled_elapsed", "hostname"):
+ if field != "hostname":
plotter(gnuplot.stdin, hostnames, field, logscale = False)
plotter(gnuplot.stdin, hostnames, field, logscale = True)
gnuplot.stdin.close()
gnuplot.wait()
-if show_plot:
- if plot_all_hosts:
- hostnames = sorted(summary.hostnames)
- else:
- hostnames = ("rpki.apnic.net", "rpki.ripe.net", "repository.lacnic.net", "rpki.afrinic.net", "arin.rpki.net", "rgnet.rpki.net")
- fields = [fmt.attr for fmt in Host.format if fmt.attr not in ("scaled_elapsed", "hostname")]
- if plot_to_one:
- plot_one(hostnames, fields)
- if plot_to_many:
- plot_many(hostnames, fields)
+mb = mailbox.Maildir("/u/sra/rpki/rcynic-xml", factory = None, create = False)
+
+sessions = []
+
+latest = None
+
+for i, key in enumerate(mb.iterkeys(), 1):
+
+ sys.stderr.write("\r%s %d/%d..." % ("|\\-/"[i & 3], i, len(mb)))
+
+ assert not mb[key].is_multipart()
+
+ input = ElementTreeFromString(mb[key].get_payload())
+
+ date = input.get("date")
+
+ sys.stderr.write("%s..." % date)
+
+ session = Session(date, key)
+ sessions.append(session)
+
+ if latest is None or session.session_id > latest.session_id:
+ latest = session
+
+ for elt in input.findall("rsync_history"):
+ session.add_rsync_history(Rsync_History(elt))
+
+ for elt in input.findall("validation_status"):
+ if elt.get("generation") == "current":
+ session.add_uri(elt.text.strip())
+
+ session.finalize()
+
+sys.stderr.write("\n")
+
+if plot_all_hosts:
+ hostnames = set()
+ for session in sessions:
+ hostnames.update(session.hostnames)
+ hostnames = sorted(hostnames)
+
+else:
+ hostnames = ("rpki.apnic.net", "rpki.ripe.net", "repository.lacnic.net",
+ "rpki.afrinic.net", "arin.rpki.net", "rgnet.rpki.net",
+ "rpki-pilot.arin.net")
+
+fields = [fmt.attr for fmt in Host.format if fmt.attr != "hostname"]
+if plot_to_one:
+ plot_one(hostnames, fields)
+if plot_to_many:
+ plot_many(hostnames, fields)
+
+if write_rcynic_xml and latest is not None:
+ f = open("rcynic.xml", "wb")
+ f.write(mb[latest.msg_key].get_payload())
+ f.close()