Hello.

This is not directly related to BIRD itself. BIRD appears to work correctly in this case. The question is about how bird_exporter exposes metrics for a BIRD 2 BGP protocol with multiple address-family channels.

I am using bird_exporter with BIRD 2 and noticed confusing behavior when a single BGP protocol has more than one address-family channel enabled, for example both ipv4 and ipv6.

Versions:

bird_exporter: 1.5.0
BIRD: 2.19.0

Exporter is started like this:

bird_exporter \
  -web.listen-address=localhost:9325 \
  -bird.v2 \
  -bird.socket=/path/to/bird.ctl

BIRD shows one established BGP session with both IPv4 and IPv6 channels up:

Name              Proto  State  Info
example_peer      BGP    up     Established

BGP state: Established
Neighbor address: 2001:db8::1
Source address:   2001:db8::2

Local capabilities:
  Multiprotocol
    AF announced: ipv4 ipv6

Neighbor capabilities:
  Multiprotocol
    AF announced: ipv4 ipv6

Channel ipv4
  State: UP
  Table: master4
  Routes: 10 imported, 0 filtered, 6000 exported, 10 preferred

Channel ipv6
  State: UP
  Table: master6
  Routes: 0 imported, 0 filtered, 200 exported, 0 preferred

Actual bird_exporter output:

bird_protocol_up{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="4",name="example_peer",proto="BGP",state="Established"} 1
bird_protocol_up{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="6",name="example_peer",proto="BGP",state=""} 1

bird_protocol_uptime{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="4",name="example_peer",proto="BGP"} 397
bird_protocol_uptime{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="6",name="example_peer",proto="BGP"} 397

bird_protocol_prefix_export_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="4",name="example_peer",proto="BGP"} 6000
bird_protocol_prefix_export_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="6",name="example_peer",proto="BGP"} 200

bird_protocol_prefix_import_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="4",name="example_peer",proto="BGP"} 10
bird_protocol_prefix_import_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="6",name="example_peer",proto="BGP"} 0

bird_protocol_prefix_preferred_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="4",name="example_peer",proto="BGP"} 10
bird_protocol_prefix_preferred_count{export_filter="(unnamed)",import_filter="(unnamed)",ip_version="6",name="example_peer",proto="BGP"} 0

The important part is that both ip_version="4" and ip_version="6" are exported for the same BGP protocol, but only one series has the BGP FSM state:

bird_protocol_up{ip_version="4",name="example_peer",proto="BGP",state="Established"} 1
bird_protocol_up{ip_version="6",name="example_peer",proto="BGP",state=""} 1

The IPv6 series has state="", although the BGP protocol is established and the IPv6 channel is UP.

Expected behavior:

Either state="Established" should be present on all bird_protocol_up series generated from the same established BGP protocol:

bird_protocol_up{ip_version="4",name="example_peer",proto="BGP",state="Established"} 1
bird_protocol_up{ip_version="6",name="example_peer",proto="BGP",state="Established"} 1

or state should not be attached to per-AF bird_protocol_up series at all, and a separate protocol-level metric should expose the BGP FSM state.

The current behavior is confusing for monitoring systems, because the same BGP protocol appears as partly established and partly without state, although both address-family channels are UP.

It would also be useful to distinguish clearly between:

A cleaner model could be something like:

bird_protocol_up{name="example_peer",proto="BGP",state="Established"} 1

bird_protocol_channel_up{name="example_peer",proto="BGP",channel="ipv4"} 1
bird_protocol_channel_up{name="example_peer",proto="BGP",channel="ipv6"} 1

This would avoid ambiguity when one BIRD 2 protocol serves multiple address families.

Thank you.