<!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>Hi KusakabeShi,</p>
<p>thank you for your patch. We are not going to merge it for several
reasons, not listed in any particular order:</p>
<ul>
<li>it lacks an appropriate test in <code>filter/test.conf</code></li>
<li>it lacks documentation in <code>doc/bird.sgml</code></li>
<li>the commit message is absolutely not sufficient at all</li>
<li>you have obviously not even tried to merge it with BIRD 3</li>
<li>you have either not tried or not documented the behavior of this
feature when fed by IPv4</li>
<li>you are not checking the LL address for actually being
link-local</li>
<li>it is a brutal hack into an already complicated filter system,
perusing the bit item of the attribute specification without saying
anything about that</li>
</ul>
<p>This issue will be handled by an upcoming rework of the filter type
system, which we are expecting to continue on working in several months,
after releasing EVPN support and other more pushing stuff. We do know
that the current implementation is limited. We also have non-triviaĺ
experience maintaining old filter language quirks, and adding a new one
just adds more maintenance work long-term.</p>
<p>If you really need this, we may, theoretically, consider merging a
patch implementing a <em>method</em> atop <code>bgp_next_hop</code>,
e.g.</p>
<pre><code>bgp_next_hop.set_global(3fff::);
bgp_next_hop.set_local(fe80::);</code></pre>
<p>Yet, that has another list of weird problems under the hood, which
will be, again, solved by the type system rework. One of the reasons why
the type system rework takes so long (and didn’t start until recently)
is actually this pre-existing quirk of bgp next hop for IPv6.</p>
<p>Thank you for your understanding.</p>
<p>Maria</p>
<p>On Tue, Apr 21, 2026 at 07:34:17PM +0800, Shi Kusakabe via Bird-users
wrote:</p>
<blockquote>
<p>Hi,</p>
<p> </p>
<p>The current bgp_next_hop filter attribute only accesses nh[0] (the
global address) of the BA_NEXT_HOP eattr. The link-local address stored
in nh[1] is completely invisible to filters. Worse, set bgp_next_hop
allocates a new 16-byte adata, silently discarding any existing
link-local next hop.</p>
<p> </p>
<p>This makes it impossible to:</p>
<ul>
<li><p>Inspect the link-local next hop in a filter</p></li>
<li><p>Modify only the global next hop while preserving the
link-local</p></li>
<li><p>Set a link-local next hop independently</p></li>
</ul>
<p> </p>
<p>This patch adds two new filter attributes, bgp_next_hop_global and
bgp_next_hop_ll, which provide independent read/write access to nh[0]
and nh[1] of the BGP next hop. Setting one does not affect other.</p>
<p> </p>
<p>The existing bgp_next_hop behavior is completely unchanged.</p>
<p> </p>
<p>Implementation:</p>
<p> </p>
<p>Both new attributes map to the same BA_NEXT_HOP ea_code,
differentiated by the bit field of struct f_dynamic_attr (which was
previously unused for EAF_TYPE_IP_ADDRESS):</p>
<p> </p>
<ul>
<li><p>bgp_next_hop — bit=0, original behavior (default from
zero-init)</p></li>
<li><p>bgp_next_hop_global — bit=1, reads/writes nh[0]</p></li>
<li><p>bgp_next_hop_ll — bit=2, reads/writes nh[1]</p></li>
</ul>
<p> </p>
<p>GET (FI_EA_GET):</p>
<ul>
<li><p>bit=2: returns nh[1] if adata->length >= 32, otherwise
IPA_NONE</p></li>
<li><p>bit=0,1: returns nh[0] (same as before)</p></li>
</ul>
<p> </p>
<p>SET (FI_EA_SET):</p>
<ul>
<li><p>bit=0: allocates new 16-byte adata with only the assigned IP
(original behavior, nh[1] is lost)</p></li>
<li><p>bit=1,2: read-modify-write — reads the existing BA_NEXT_HOP from
eattrs, updates only the target slot, preserves the other. If nh[1] is
zero after the update, length is set to 16; otherwise 32.</p></li>
</ul>
<p> </p>
<p>Usage example:</p>
<p> </p>
<h1 id="read">Read</h1>
<p>print bgp_next_hop_global; # 2404:f4c0:f70e:1980::202:881</p>
<p>print bgp_next_hop_ll; # fe80::ff:fe20:2881</p>
<p> </p>
<h1 id="modify-global-only-link-local-preserved">Modify global only,
link-local preserved</h1>
<p>bgp_next_hop_global = 2001:db8::1;</p>
<p> </p>
<h1 id="modify-link-local-only-global-preserved">Modify link-local only,
global preserved</h1>
<p>bgp_next_hop_ll = fe80::99;</p>
<p> </p>
<p>Files changed:</p>
<p>https://github.com/KusakabeShi/bird/commit/b5af9bc2eefbcc9e6f96432dd43fc6422f5f33c8</p>
<p> </p>
<ul>
<li><p>proto/bgp/config.Y — new keywords and dynamic_attr rules</p></li>
<li><p>filter/f-inst.c — GET/SET handling for partial next hop
access</p></li>
</ul>
<p> </p>
<p>The patch applies against the v2.18 release branch. Feedback
welcome.</p>
<p> </p>
<p>Thanks,</p>
<p>KusakabeShi</p>
</blockquote>
<p>On Wed, Apr 22, 2026 at 08:50:25AM +0000, KSKB SHI wrote: > Hi,
><br />
> The current bgp_next_hop filter attribute only accesses nh[0] (the
global address) of the BA_NEXT_HOP eattr. > The link-local address
stored in nh[1] is completely invisible to filters. ><br />
> see this image: > https://upload.cc/i1/2026/04/22/0Nj4E8.png
><br />
> Worse, set bgp_next_hop allocates a new 16-byte adata, silently
discarding any existing link-local next hop. ><br />
> This makes it impossible to: > - Inspect the link-local next hop
in a filter > - Modify only the global next hop while preserving the
link-local > - Set a link-local next hop independently ><br />
> This patch adds two new filter attributes, bgp_next_hop_global and
bgp_next_hop_ll, which provide independent read/write access to nh[0]
and nh[1] of the BGP next hop. Setting one does not affect other.
><br />
> The existing bgp_next_hop behavior is completely unchanged.
><br />
> Implementation: ><br />
> Both new attributes map to the same BA_NEXT_HOP ea_code,
differentiated by the bit field of struct f_dynamic_attr (which was
previously unused for EAF_TYPE_IP_ADDRESS): ><br />
> - bgp_next_hop — bit=0, original behavior (default from zero-init)
> - bgp_next_hop_global — bit=1, reads/writes nh[0] > -
bgp_next_hop_ll — bit=2, reads/writes nh[1] ><br />
> GET (FI_EA_GET): > - bit=2: returns nh[1] if adata->length
>= 32, otherwise IPA_NONE > - bit=0,1: returns nh[0] (same as
before) ><br />
> SET (FI_EA_SET): > - bit=0: allocates new 16-byte adata with
only the assigned IP (original behavior, nh[1] is lost) > - bit=1,2:
read-modify-write — reads the existing BA_NEXT_HOP from eattrs, updates
only the target slot, preserves the other. If nh[1] is zero after the
update, length is set to 16; otherwise 32. ><br />
> Usage example: ><br />
> # Read > print bgp_next_hop_global; #
2404:f4c0:f70e:1980::202:881 > print bgp_next_hop_ll; #
fe80::ff:fe20:2881 ><br />
> # Modify global only, link-local preserved > bgp_next_hop_global
= 2001:db8::1; ><br />
> # Modify link-local only, global preserved > bgp_next_hop_ll =
fe80::99; ><br />
> Files changed: >
https://github.com/KusakabeShi/bird/commit/b5af9bc2eefbcc9e6f96432dd43fc6422f5f33c8
><br />
> - proto/bgp/config.Y — new keywords and dynamic_attr rules > -
filter/f-inst.c — GET/SET handling for partial next hop access
><br />
> The patch applies against the v2.18 release branch. Feedback
welcome. ><br />
> Thanks, > KusakabeShi</p>
<p>–<br />
Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.</p>
</body>
</html>