<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <style>
html {
  line-height: 1.2;
  font-family: serif;
  font-size: 0.9em;
  color: black; 
  background-color: white;
}
body {
  margin: 0;
  margin-right: auto;
  max-width: 36em;
  padding: 1em;
  hyphens: auto;
  overflow-wrap: break-word;
  text-rendering: optimizeLegibility;
  font-kerning: normal;
}
@media print {
  body {
    background-color: transparent;
    color: black;
    font-size: 11pt;
  }
  p, h2, h3 {
    orphans: 3;
    widows: 3;
  }
  h2, h3, h4 {
    page-break-after: avoid;
  }
}
p {
  margin: 1em 0;
}
a {
  color: black;
}
a:visited {
  color: black;
}
img {
  max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
  margin-top: 1.4em;
}
h5, h6 {
  font-size: 1em;
  font-style: italic;
}
h6 {
  font-weight: normal;
}
ol, ul {
  padding-left: 1.7em;
  margin-top: 1em;
}
li > ol, li > ul {
  margin-top: 0;
}
blockquote {
  margin: 0.5em;
  padding-left: 0.5em;
  border-left: 2px solid #e6e6e6;
  color: #444;
}
code {
  font-family: 'Lucida Console', monospace;
  font-size: 95%;
  margin: 0;
}
pre {
  margin: 1em 0;
  overflow: auto;
  max-width: unset;
  width: fit-content;
}
pre code {
  padding: 0;
  overflow: visible;
  overflow-wrap: normal;
  max-width: unset;
  white-space: pre-wrap;
}
pre code span {
  white-space: pre;
}
.sourceCode {
 background-color: transparent;
 overflow: visible;
}

code.diff span.kw,
code.diff span.dt {
  font-weight: bold;
}

code.diff span.va {
  background-color: rgba(192, 255, 192, 64);
  color: rgb(0, 64, 0);
}

code.diff span.st {
  background-color: rgba(255, 192, 192, 64);
  color: rgb(64, 0, 0);
}

pre.diff {
  background-color: rgb(240, 240, 240);
  padding: 0.4em;
  border: 1pt solid grey;
}

hr {
  background-color: black;
  border: none;
  height: 1px;
  margin: 1em 0;
}
table {
  margin: 1em 0;
  border-collapse: collapse;
  width: 100%;
  overflow-x: auto;
  display: block;
  font-variant-numeric: lining-nums tabular-nums;
}
table caption {
  margin-bottom: 0.75em;
}
tbody {
  margin-top: 0.5em;
  border-top: 1px solid black;
  border-bottom: 1px solid black;
}
th {
  border-top: 1px solid black;
  padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
  padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
  margin-bottom: 4em;
  text-align: center;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
q { quotes: "„" "”" "»" "«"; }
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
  </style>
</head>
<body>
<p>Hello Ondřej,</p>
<p>thank you for catching this bug. It probably applies to v2.18 as
well. Replicated, gonna fix soon.</p>
<p>Maria</p>
<p>On Thu, Feb 05, 2026 at 06:33:51PM +0100, Ondřej Caletka wrote:</p>
<blockquote>
<p>Hello again!</p>
<p>On 29/01/2026 11:49, Ondřej Caletka wrote: > I wonder whether
there is a way how to see a ASPA table entry for > particular
customer AS number. Something like: ><br />
> bird> show route table aspas all for 2121 > syntax error,
unexpected NUM, expecting IP4 or IP6 or VPN_RD or > CF_SYM_KNOWN
></p>
<p>Found it!</p>
<p>bird> show route all aspa 2121 Table aspas: 2121 [rpki_validator
2026-01-29] * (100) preference: 100 source: RPKI aspa_providers: 3333
Internal route handling values: 0L 16G 1S id 10</p>
<blockquote>
<p>Regarding the validation itself, a random trivial example where
aspa_check_downstream fails and I don’t know why is this:</p>
<p>80.254.230.0/24         bgp_path: 3333 12859 42695</p>
<p>(My ASN is 2121 and there is an ASPA stating that 3333 is provider
for 2121)</p>
<p>There is no ASPA for 3333 There is ASPA for 12859 not stating 42695
as provider There is ASPA for 42695 not stating 12859 as provider</p>
<p>So the up ramp should be 42695 and the down ramp should be 2121 3333
12859</p>
<p>I don’t see any valleys here yet it is rejected.</p>
<p>Am I doing it wrong?</p>
</blockquote>
<p>I believe there is a logic error not covered by the tests in the
validation logic around here</p>
<p>https://gitlab.nic.cz/labs/bird/-/blob/master/nest/rt-table.c?ref_type=heads#L428</p>
<p>I did some pen-and-paper tracing of this algorithm:</p>
<p>First ASN: 3333 ap = 0, found = up = down = false; set max_down = 1,
min_up = 0;</p>
<p>So far so good, no ASPA so the min_down ramp ends here, the max_down
goes further. If there is no ASPA everywhere, min_up can end here
too.</p>
<p>Second ASN: 12859 ap = 1, found = true, up = down = false; set min_up
= max_up = 1; set force_upstream = true;</p>
<p>There is ASPA but neither on the left or on the right is a provider.
We don’t touch the down ramp (it pointed here from the previous step)
and we clamp the up ramp for this position. However, we also force
upstream validation from now on. I believe this is wrong because if this
is the apex of the down ramp, the upstream validation should start from
the next ASN.</p>
<p>Third ASN: 42695 ap = 2, found = true, up = down = false,
force_upstream = true; Bacause ap>0 and the ASN on the left is not
provider, this ends up with ASPA_INVALID, because in previous iteration,
the algorithm was switched to the upstream one.</p>
<p>Should we not switched to upstream validation at this point this
would end up with min_up = max_up = 2.</p>
<p>I believe that this is expected result for the algorithm: both
minimum and maximum up ramps end on AS with index 2, minimum down ramp
is 0 since the leftmost ASN does not have ASPA, but maximum down ramp is
1.</p>
<p>Thus having max_up = 2 and max_down = 1 says that the up and down
ramps are meeting on adjacent peers. This should be allowed.</p>
<p>However the logic in the code requires max_up <= max_down for
Unknown result. This means it requires up and down ramps to be touching
or overlapping.</p>
<p>I don’t think this is what the
draft-ietf-sidrops-aspa-verification-24 is prescribing. It operates with
ramp lengths in place of positions so for this particular case:
max_up_ramp = 1, min_up_ramp =1, max_down_ramp = 2, min_down_ramp = 1, N
= 3.</p>
<p>The draft says that: > the sum of max_up_ramp and max_down_ramp is
less than N, the AS_PATH is Invalid.</p>
<p>Here 1 + 2 = 3 so this is not invalid.</p>
<blockquote>
<p>Else, if the sum of min_up_ramp and min_down_ramp is less than N,
enough information is not available to perform full AS_PATH
verification, and the outcome is set to Unknown.</p>
</blockquote>
<p>Here 1 + 1 < 3 so this should be unknown.</p>
<p>Am I still doing it wrong? :)</p>
<p>– Best regards,</p>
<p>Ondřej Caletka</p>
</blockquote>
<p>–<br />
Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.</p>
</body>
</html>