diff --git a/README.md b/README.md
index 833b7dd..79d7939 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,9 @@ Flags:
--[no-]collector.route Enable the route collector (default: enabled, to disable use
--no-collector.route).
--[no-]collector.vrrp Enable the vrrp collector (default: disabled).
+ --[no-]collector.mpls_ldp Enable the mpls_ldp collector (default: disabled).
+ --[no-]collector.mpls_ldp.binding-in-use
+ Enable the frr_mpls_ldp_binding_in_use metric (default: disabled).
--web.telemetry-path="/metrics"
Path under which to expose metrics.
--web.listen-address=:9342 ...
@@ -136,6 +139,7 @@ BGP IPv6 | Per VRF and address family (currently support unicast only) BGP IPv6
BGP L2VPN | Per VRF and address family (currently support EVPN only) BGP L2VPN EVPN metrics:
- RIB entries
- RIB memory usage
- Configured peer count
- Peer memory usage
- Configure peer group count
- Peer group memory usage
- Peer messages in
- Peer messages out
- Peer active prfixes
- Peer state (established/down)
- Peer uptime
VRRP | Per VRRP Interface, VrID and Protocol:
- Rx and TX statistics
- VRRP Status
- VRRP State Transitions
PIM | PIM metrics:
- Neighbor count
- Neighbor uptime
+MPLS LDP | MPLS LDP metrics:
- Binding count
- Binding in use (if enabled)
- IGP Sync state
- Interface state
- Interface hello interval
- Interface hello holdtime
- Interface adjacency count
- Neighbor state
- Neighbor uptime
- Discovery adjacency count
### Sending commands to FRR
diff --git a/collector/command.go b/collector/command.go
index 50a9133..113f94b 100644
--- a/collector/command.go
+++ b/collector/command.go
@@ -64,6 +64,13 @@ func executeVRRPCommand(cmd string) ([]byte, error) {
return socketConn.ExecVRRPCmd(cmd)
}
+func executeMPLSLDPCommand(cmd string) ([]byte, error) {
+ if *vtyshEnable {
+ return execVtyshCommand(cmd)
+ }
+ return socketConn.ExecLDPDCmd(cmd)
+}
+
func execVtyshCommand(vtyshCmd string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), *vtyshTimeout)
defer cancel()
diff --git a/collector/mpls_ldp.go b/collector/mpls_ldp.go
new file mode 100644
index 0000000..c0916f2
--- /dev/null
+++ b/collector/mpls_ldp.go
@@ -0,0 +1,326 @@
+package collector
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "strings"
+
+ "github.com/alecthomas/kingpin/v2"
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+ mplsLdpSubsystem = "mpls_ldp"
+
+ mplsLDPBindingInUse = kingpin.Flag("collector.mpls_ldp.binding-in-use", "Enable the frr_mpls_ldp_binding_in_use metric (default: disabled).").Default("False").Bool()
+)
+
+func init() {
+ registerCollector(mplsLdpSubsystem, disabledByDefault, NewMPLSLDPCollector)
+}
+
+type mplsLDPCollector struct {
+ logger *slog.Logger
+ descriptions map[string]*prometheus.Desc
+}
+
+func NewMPLSLDPCollector(logger *slog.Logger) (Collector, error) {
+ return &mplsLDPCollector{logger: logger, descriptions: getMPLSLDPDesc()}, nil
+}
+
+func getMPLSLDPDesc() map[string]*prometheus.Desc {
+ bindingsLabels := []string{"address_family"}
+ bindingLabels := []string{"address_family", "prefix", "neighbor_id", "local_label", "remote_label"}
+ igpSyncLabels := []string{"interface", "peer_ldp_id"}
+ interfaceLabels := []string{"name", "address_family"}
+ neighborLabels := []string{"address_family", "neighbor_id"}
+ discoveryLabels := []string{"address_family", "neighbor_id", "interface", "type"}
+
+ return map[string]*prometheus.Desc{
+ "bindingCount": colPromDesc(mplsLdpSubsystem, "binding_count", "Number of MPLS LDP bindings.", bindingsLabels),
+ "bindingInUse": colPromDesc(mplsLdpSubsystem, "binding_in_use", "Usage status of the MPLS LDP binding (1=In Use, 0=Not In Use).", bindingLabels),
+ "igpSyncState": colPromDesc(mplsLdpSubsystem, "igp_sync_state", "State of MPLS LDP IGP sync (1=Ready/Complete, 0=Not Complete).", igpSyncLabels),
+ "interfaceState": colPromDesc(mplsLdpSubsystem, "interface_state", "State of MPLS LDP interface (1=Active, 0=Inactive).", interfaceLabels),
+ "interfaceHelloInterval": colPromDesc(mplsLdpSubsystem, "interface_hello_interval_seconds", "Hello interval for the interface.", interfaceLabels),
+ "interfaceHelloHoldtime": colPromDesc(mplsLdpSubsystem, "interface_hello_holdtime_seconds", "Hello holdtime for the interface.", interfaceLabels),
+ "interfaceAdjacencyCount": colPromDesc(mplsLdpSubsystem, "interface_adjacency_count", "Number of adjacencies on the interface.", interfaceLabels),
+ "neighborState": colPromDesc(mplsLdpSubsystem, "neighbor_state", "State of MPLS LDP neighbor (1=Operational, 0=Other).", neighborLabels),
+ "neighborUptime": colPromDesc(mplsLdpSubsystem, "neighbor_uptime_seconds", "Uptime of MPLS LDP neighbor in seconds.", neighborLabels),
+ "discoveryAdjacencyCount": colPromDesc(mplsLdpSubsystem, "discovery_adjacency_count", "Number of discovery adjacencies.", discoveryLabels),
+ }
+}
+
+func (c *mplsLDPCollector) Update(ch chan<- prometheus.Metric) error {
+ // 1. Bindings
+ if err := c.collectBindings(ch); err != nil {
+ return err
+ }
+ // 2. IGP Sync
+ if err := c.collectIGPSync(ch); err != nil {
+ return err
+ }
+ // 3. Interface
+ if err := c.collectInterface(ch); err != nil {
+ return err
+ }
+ // 4. Neighbor
+ if err := c.collectNeighbor(ch); err != nil {
+ return err
+ }
+ // 5. Discovery
+ if err := c.collectDiscovery(ch); err != nil {
+ return err
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------
+// Bindings
+// ----------------------------------------------------------------------
+
+type mplsLdpBindings struct {
+ Bindings []struct {
+ AddressFamily string `json:"addressFamily"`
+ Prefix string `json:"prefix"`
+ NeighborID string `json:"neighborId"`
+ LocalLabel string `json:"localLabel"`
+ RemoteLabel string `json:"remoteLabel"`
+ InUse int `json:"inUse"`
+ } `json:"bindings"`
+}
+
+func (c *mplsLDPCollector) collectBindings(ch chan<- prometheus.Metric) error {
+ cmd := "show mpls ldp binding json"
+ output, err := executeMPLSLDPCommand(cmd)
+ if err != nil {
+ return err
+ }
+ if err := processBindings(ch, output, c.descriptions); err != nil {
+ return cmdOutputProcessError(cmd, string(output), err)
+ }
+ return nil
+}
+
+func processBindings(ch chan<- prometheus.Metric, output []byte, descs map[string]*prometheus.Desc) error {
+ var data mplsLdpBindings
+ if err := json.Unmarshal(output, &data); err != nil {
+ return fmt.Errorf("cannot unmarshal mpls ldp binding json: %w", err)
+ }
+
+ counts := make(map[string]float64)
+ for _, b := range data.Bindings {
+ counts[b.AddressFamily]++
+ if *mplsLDPBindingInUse {
+ newGauge(ch, descs["bindingInUse"], float64(b.InUse), b.AddressFamily, b.Prefix, b.NeighborID, b.LocalLabel, b.RemoteLabel)
+ }
+ }
+
+ for af, count := range counts {
+ newGauge(ch, descs["bindingCount"], count, af)
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------
+// IGP Sync
+// ----------------------------------------------------------------------
+
+type mplsLdpIGPSync struct {
+ State string `json:"state"`
+ WaitTime int `json:"waitTime"`
+ PeerLdpID string `json:"peerLdpId"`
+}
+
+type mplsLdpIGPSyncOutput map[string]mplsLdpIGPSync
+
+func (c *mplsLDPCollector) collectIGPSync(ch chan<- prometheus.Metric) error {
+ cmd := "show mpls ldp igp-sync json"
+ output, err := executeMPLSLDPCommand(cmd)
+ if err != nil {
+ return err
+ }
+ if err := processIGPSync(ch, output, c.descriptions); err != nil {
+ return cmdOutputProcessError(cmd, string(output), err)
+ }
+ return nil
+}
+
+func processIGPSync(ch chan<- prometheus.Metric, output []byte, descs map[string]*prometheus.Desc) error {
+ var data mplsLdpIGPSyncOutput
+ if err := json.Unmarshal(output, &data); err != nil {
+ return fmt.Errorf("cannot unmarshal mpls ldp igp-sync json: %w", err)
+ }
+
+ for iface, info := range data {
+ stateVal := 0.0
+ if !strings.Contains(strings.ToLower(info.State), "notcomplete") {
+ stateVal = 1.0
+ }
+ newGauge(ch, descs["igpSyncState"], stateVal, iface, info.PeerLdpID)
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------
+// Interface
+// ----------------------------------------------------------------------
+
+type mplsLdpInterface struct {
+ Name string `json:"name"`
+ AddressFamily string `json:"addressFamily"`
+ State string `json:"state"`
+ UpTime string `json:"upTime"`
+ HelloInterval float64 `json:"helloInterval"`
+ HelloHoldtime float64 `json:"helloHoldtime"`
+ AdjacencyCount float64 `json:"adjacencyCount"`
+}
+
+type mplsLdpInterfaceOutput map[string]mplsLdpInterface
+
+func (c *mplsLDPCollector) collectInterface(ch chan<- prometheus.Metric) error {
+ cmd := "show mpls ldp interface json"
+ output, err := executeMPLSLDPCommand(cmd)
+ if err != nil {
+ return err
+ }
+ if err := processInterface(ch, output, c.descriptions); err != nil {
+ return cmdOutputProcessError(cmd, string(output), err)
+ }
+ return nil
+}
+
+func processInterface(ch chan<- prometheus.Metric, output []byte, descs map[string]*prometheus.Desc) error {
+ var data mplsLdpInterfaceOutput
+ if err := json.Unmarshal(output, &data); err != nil {
+ return fmt.Errorf("cannot unmarshal mpls ldp interface json: %w", err)
+ }
+
+ for _, info := range data {
+ stateVal := 0.0
+ if strings.EqualFold(info.State, "active") {
+ stateVal = 1.0
+ }
+ newGauge(ch, descs["interfaceState"], stateVal, info.Name, info.AddressFamily)
+ newGauge(ch, descs["interfaceHelloInterval"], info.HelloInterval, info.Name, info.AddressFamily)
+ newGauge(ch, descs["interfaceHelloHoldtime"], info.HelloHoldtime, info.Name, info.AddressFamily)
+ newGauge(ch, descs["interfaceAdjacencyCount"], info.AdjacencyCount, info.Name, info.AddressFamily)
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------
+// Neighbor
+// ----------------------------------------------------------------------
+
+type mplsLdpNeighbor struct {
+ AddressFamily string `json:"addressFamily"`
+ NeighborID string `json:"neighborId"`
+ State string `json:"state"`
+ UpTime string `json:"upTime"`
+}
+
+type mplsLdpNeighborOutput struct {
+ Neighbors []mplsLdpNeighbor `json:"neighbors"`
+}
+
+func (c *mplsLDPCollector) collectNeighbor(ch chan<- prometheus.Metric) error {
+ cmd := "show mpls ldp neighbor json"
+ output, err := executeMPLSLDPCommand(cmd)
+ if err != nil {
+ return err
+ }
+ if err := processNeighbor(ch, output, c.descriptions); err != nil {
+ return cmdOutputProcessError(cmd, string(output), err)
+ }
+ return nil
+}
+
+func processNeighbor(ch chan<- prometheus.Metric, output []byte, descs map[string]*prometheus.Desc) error {
+ var data mplsLdpNeighborOutput
+ if err := json.Unmarshal(output, &data); err != nil {
+ return fmt.Errorf("cannot unmarshal mpls ldp neighbor json: %w", err)
+ }
+
+ for _, n := range data.Neighbors {
+ stateVal := 0.0
+ if strings.EqualFold(n.State, "operational") {
+ stateVal = 1.0
+ }
+ newGauge(ch, descs["neighborState"], stateVal, n.AddressFamily, n.NeighborID)
+
+ uptimeSeconds, err := parseUptime(n.UpTime)
+ if err == nil {
+ newGauge(ch, descs["neighborUptime"], uptimeSeconds, n.AddressFamily, n.NeighborID)
+ }
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------
+// Discovery
+// ----------------------------------------------------------------------
+
+type mplsLdpDiscovery struct {
+ AddressFamily string `json:"addressFamily"`
+ NeighborID string `json:"neighborId"`
+ Type string `json:"type"`
+ Interface string `json:"interface"`
+}
+
+type mplsLdpDiscoveryOutput struct {
+ Adjacencies []mplsLdpDiscovery `json:"adjacencies"`
+}
+
+func (c *mplsLDPCollector) collectDiscovery(ch chan<- prometheus.Metric) error {
+ cmd := "show mpls ldp discovery json"
+ output, err := executeMPLSLDPCommand(cmd)
+ if err != nil {
+ return err
+ }
+ if err := processDiscovery(ch, output, c.descriptions); err != nil {
+ return cmdOutputProcessError(cmd, string(output), err)
+ }
+ return nil
+}
+
+func processDiscovery(ch chan<- prometheus.Metric, output []byte, descs map[string]*prometheus.Desc) error {
+ var data mplsLdpDiscoveryOutput
+ if err := json.Unmarshal(output, &data); err != nil {
+ return fmt.Errorf("cannot unmarshal mpls ldp discovery json: %w", err)
+ }
+
+ counts := make(map[string]float64)
+ for _, d := range data.Adjacencies {
+ key := strings.Join([]string{d.AddressFamily, d.NeighborID, d.Interface, d.Type}, "|")
+ counts[key]++
+ }
+
+ for key, count := range counts {
+ parts := strings.Split(key, "|")
+ if len(parts) == 4 {
+ newGauge(ch, descs["discoveryAdjacencyCount"], count, parts[0], parts[1], parts[2], parts[3])
+ }
+ }
+ return nil
+}
+
+// Helper to parse uptime string "HH:MM:SS"
+func parseUptime(uptime string) (float64, error) {
+ parts := strings.Split(uptime, ":")
+ if len(parts) == 3 {
+ h, err1 := parseInt(parts[0])
+ m, err2 := parseInt(parts[1])
+ s, err3 := parseInt(parts[2])
+ if err1 == nil && err2 == nil && err3 == nil {
+ return float64(h*3600 + m*60 + s), nil
+ }
+ }
+ return 0, fmt.Errorf("invalid uptime format: %s", uptime)
+}
+
+func parseInt(s string) (int, error) {
+ var val int
+ _, err := fmt.Sscanf(s, "%d", &val)
+ return val, err
+}
diff --git a/collector/mpls_ldp_test.go b/collector/mpls_ldp_test.go
new file mode 100644
index 0000000..eab2827
--- /dev/null
+++ b/collector/mpls_ldp_test.go
@@ -0,0 +1,90 @@
+package collector
+
+import (
+ "testing"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+func TestProcessMPLSLDPBindings(t *testing.T) {
+ // Enable processing of bindings
+ *mplsLDPBindingInUse = true
+ defer func() { *mplsLDPBindingInUse = false }()
+
+ ch := make(chan prometheus.Metric, 1024)
+ if err := processBindings(ch, readTestFixture(t, "show_mpls_ldp_binding.json"), getMPLSLDPDesc()); err != nil {
+ t.Fatalf("error calling processBindings: %s", err)
+ }
+ close(ch)
+
+ expected := map[string]float64{
+ "frr_mpls_ldp_binding_count{address_family=ipv4}": 5,
+ "frr_mpls_ldp_binding_in_use{address_family=ipv4,local_label=imp-null,neighbor_id=0.0.0.0,prefix=2.2.2.2/32,remote_label=-}": 0,
+ "frr_mpls_ldp_binding_in_use{address_family=ipv4,local_label=imp-null,neighbor_id=0.0.0.0,prefix=10.0.0.0/24,remote_label=-}": 0,
+ "frr_mpls_ldp_binding_in_use{address_family=ipv4,local_label=18,neighbor_id=0.0.0.0,prefix=10.1.0.0/24,remote_label=-}": 0,
+ "frr_mpls_ldp_binding_in_use{address_family=ipv4,local_label=16,neighbor_id=0.0.0.0,prefix=192.168.200.0/24,remote_label=-}": 0,
+ "frr_mpls_ldp_binding_in_use{address_family=ipv4,local_label=17,neighbor_id=0.0.0.0,prefix=192.168.201.0/24,remote_label=-}": 0,
+ }
+ got := collectMetrics(t, ch)
+ compareMetrics(t, got, expected)
+}
+
+func TestProcessMPLSLDPIGPSync(t *testing.T) {
+ ch := make(chan prometheus.Metric, 1024)
+ if err := processIGPSync(ch, readTestFixture(t, "show_mpls_ldp_igp_sync.json"), getMPLSLDPDesc()); err != nil {
+ t.Fatalf("error calling processIGPSync: %s", err)
+ }
+ close(ch)
+
+ expected := map[string]float64{
+ "frr_mpls_ldp_igp_sync_state{interface=eth0,peer_ldp_id=}": 0,
+ }
+ got := collectMetrics(t, ch)
+ compareMetrics(t, got, expected)
+}
+
+func TestProcessMPLSLDPInterface(t *testing.T) {
+ ch := make(chan prometheus.Metric, 1024)
+ if err := processInterface(ch, readTestFixture(t, "show_mpls_ldp_interface.json"), getMPLSLDPDesc()); err != nil {
+ t.Fatalf("error calling processInterface: %s", err)
+ }
+ close(ch)
+
+ expected := map[string]float64{
+ "frr_mpls_ldp_interface_state{address_family=ipv4,name=eth0}": 1,
+ "frr_mpls_ldp_interface_hello_interval_seconds{address_family=ipv4,name=eth0}": 5,
+ "frr_mpls_ldp_interface_hello_holdtime_seconds{address_family=ipv4,name=eth0}": 15,
+ "frr_mpls_ldp_interface_adjacency_count{address_family=ipv4,name=eth0}": 0,
+ }
+ got := collectMetrics(t, ch)
+ compareMetrics(t, got, expected)
+}
+
+func TestProcessMPLSLDPNeighbor(t *testing.T) {
+ ch := make(chan prometheus.Metric, 1024)
+ if err := processNeighbor(ch, readTestFixture(t, "show_mpls_ldp_neighbor.json"), getMPLSLDPDesc()); err != nil {
+ t.Fatalf("error calling processNeighbor: %s", err)
+ }
+ close(ch)
+
+ expected := map[string]float64{
+ "frr_mpls_ldp_neighbor_state{address_family=ipv4,neighbor_id=1.1.1.1}": 1,
+ "frr_mpls_ldp_neighbor_uptime_seconds{address_family=ipv4,neighbor_id=1.1.1.1}": 141, // 2m21s = 141s
+ }
+ got := collectMetrics(t, ch)
+ compareMetrics(t, got, expected)
+}
+
+func TestProcessMPLSLDPDiscovery(t *testing.T) {
+ ch := make(chan prometheus.Metric, 1024)
+ if err := processDiscovery(ch, readTestFixture(t, "show_mpls_ldp_discovery.json"), getMPLSLDPDesc()); err != nil {
+ t.Fatalf("error calling processDiscovery: %s", err)
+ }
+ close(ch)
+
+ expected := map[string]float64{
+ "frr_mpls_ldp_discovery_adjacency_count{address_family=ipv4,interface=eth0,neighbor_id=1.1.1.1,type=link}": 1,
+ }
+ got := collectMetrics(t, ch)
+ compareMetrics(t, got, expected)
+}
diff --git a/collector/testdata/show_mpls_ldp_binding.json b/collector/testdata/show_mpls_ldp_binding.json
new file mode 100644
index 0000000..c61cc52
--- /dev/null
+++ b/collector/testdata/show_mpls_ldp_binding.json
@@ -0,0 +1,44 @@
+{
+ "bindings":[
+ {
+ "addressFamily":"ipv4",
+ "prefix":"2.2.2.2/32",
+ "neighborId":"0.0.0.0",
+ "localLabel":"imp-null",
+ "remoteLabel":"-",
+ "inUse":0
+ },
+ {
+ "addressFamily":"ipv4",
+ "prefix":"10.0.0.0/24",
+ "neighborId":"0.0.0.0",
+ "localLabel":"imp-null",
+ "remoteLabel":"-",
+ "inUse":0
+ },
+ {
+ "addressFamily":"ipv4",
+ "prefix":"10.1.0.0/24",
+ "neighborId":"0.0.0.0",
+ "localLabel":"18",
+ "remoteLabel":"-",
+ "inUse":0
+ },
+ {
+ "addressFamily":"ipv4",
+ "prefix":"192.168.200.0/24",
+ "neighborId":"0.0.0.0",
+ "localLabel":"16",
+ "remoteLabel":"-",
+ "inUse":0
+ },
+ {
+ "addressFamily":"ipv4",
+ "prefix":"192.168.201.0/24",
+ "neighborId":"0.0.0.0",
+ "localLabel":"17",
+ "remoteLabel":"-",
+ "inUse":0
+ }
+ ]
+}
diff --git a/collector/testdata/show_mpls_ldp_discovery.json b/collector/testdata/show_mpls_ldp_discovery.json
new file mode 100644
index 0000000..3d6cbac
--- /dev/null
+++ b/collector/testdata/show_mpls_ldp_discovery.json
@@ -0,0 +1,11 @@
+{
+ "adjacencies":[
+ {
+ "addressFamily":"ipv4",
+ "neighborId":"1.1.1.1",
+ "type":"link",
+ "interface":"eth0",
+ "helloHoldtime":15
+ }
+ ]
+}
diff --git a/collector/testdata/show_mpls_ldp_igp_sync.json b/collector/testdata/show_mpls_ldp_igp_sync.json
new file mode 100644
index 0000000..48d62cc
--- /dev/null
+++ b/collector/testdata/show_mpls_ldp_igp_sync.json
@@ -0,0 +1,9 @@
+{
+ "eth0":{
+ "state":"labelExchangeNotComplete",
+ "waitTime":10,
+ "waitTimeRemaining":0,
+ "timerRunning":false,
+ "peerLdpId":""
+ }
+}
diff --git a/collector/testdata/show_mpls_ldp_interface.json b/collector/testdata/show_mpls_ldp_interface.json
new file mode 100644
index 0000000..4d2cda0
--- /dev/null
+++ b/collector/testdata/show_mpls_ldp_interface.json
@@ -0,0 +1,11 @@
+{
+ "eth0: ipv4":{
+ "name":"eth0",
+ "addressFamily":"ipv4",
+ "state":"ACTIVE",
+ "upTime":"00:03:11",
+ "helloInterval":5,
+ "helloHoldtime":15,
+ "adjacencyCount":0
+ }
+}
diff --git a/collector/testdata/show_mpls_ldp_neighbor.json b/collector/testdata/show_mpls_ldp_neighbor.json
new file mode 100644
index 0000000..be2ef23
--- /dev/null
+++ b/collector/testdata/show_mpls_ldp_neighbor.json
@@ -0,0 +1,11 @@
+{
+ "neighbors":[
+ {
+ "addressFamily":"ipv4",
+ "neighborId":"1.1.1.1",
+ "state":"OPERATIONAL",
+ "transportAddress":"1.1.1.1",
+ "upTime":"00:02:21"
+ }
+ ]
+}
diff --git a/dev/Makefile b/dev/Makefile
index 5c8b937..17c1814 100644
--- a/dev/Makefile
+++ b/dev/Makefile
@@ -44,6 +44,7 @@ help:
@echo " make show-bfd - Show BFD status"
@echo " make show-pim - Show PIM status"
@echo " make show-vrrp - Show VRRP status"
+ @echo " make show-mpls-ldp - Show MPLS LDP status"
@echo " make show-routes - Show route summary"
@echo " make show-version - Show FRR version"
@echo " make logs - View FRR container logs"
@@ -90,7 +91,7 @@ restart: down up
# Build frr_exporter
build:
@echo "Building frr_exporter for Linux:"
- cd .. && go build -o $(EXPORTER_BIN) .
+ cd .. && CGO_ENABLED=0 go build -o $(EXPORTER_BIN) .
@echo "Built: $(EXPORTER_BIN)"
# Deploy exporter to containers and start it
@@ -108,6 +109,8 @@ deploy: $(EXPORTER_BIN)
--collector.bfd \
--collector.pim \
--collector.vrrp \
+ --collector.mpls_ldp \
+ --collector.mpls_ldp.binding-in-use \
--collector.route \
--collector.route.detailed-routes \
--collector.bgp.peer-descriptions \
@@ -120,6 +123,8 @@ deploy: $(EXPORTER_BIN)
--collector.bfd \
--collector.pim \
--collector.vrrp \
+ --collector.mpls_ldp \
+ --collector.mpls_ldp.binding-in-use \
--collector.route \
--collector.route.detailed-routes \
--collector.bgp.peer-descriptions \
@@ -185,7 +190,7 @@ vtysh2:
docker exec -it frr-router2 vtysh
# Show overall status
-status: show-bgp show-ospf show-bfd show-pim show-vrrp
+status: show-bgp show-ospf show-bfd show-pim show-vrrp show-mpls-ldp
# Show BGP status
show-bgp:
@@ -221,6 +226,14 @@ show-vrrp:
@echo "=== VRRP Status (Router1) ==="
@docker exec frr-router1 vtysh -c "show vrrp" 2>/dev/null
+# Show MPLS LDP status
+show-mpls-ldp:
+ @echo "=== MPLS LDP Neighbors (Router1) ==="
+ @docker exec frr-router1 vtysh -c "show mpls ldp neighbor"
+ @echo "=== MPLS LDP Bindings (Router1) ==="
+ @docker exec frr-router1 vtysh -c "show mpls ldp binding"
+
+
# Show routes
show-routes:
@echo "=== IP Routes (Router1) ==="
diff --git a/dev/configs/daemons b/dev/configs/daemons
index 5e95470..6214e07 100644
--- a/dev/configs/daemons
+++ b/dev/configs/daemons
@@ -10,7 +10,7 @@ ripngd=no
isisd=no
pimd=yes
pim6d=no
-ldpd=no
+ldpd=yes
nhrpd=no
eigrpd=no
babeld=no
@@ -29,3 +29,4 @@ ospfd_options=" -A 127.0.0.1"
pimd_options=" -A 127.0.0.1"
bfdd_options=" -A 127.0.0.1"
vrrpd_options=" -A 127.0.0.1"
+ldpd_options=" -A 127.0.0.1"
diff --git a/dev/configs/router1.conf b/dev/configs/router1.conf
index 51da1a0..541c211 100644
--- a/dev/configs/router1.conf
+++ b/dev/configs/router1.conf
@@ -131,4 +131,20 @@ exit
ip route 192.168.100.0/24 10.0.0.11
ip route 192.168.101.0/24 10.0.0.11
+! ============================================
+! MPLS LDP Configuration
+! ============================================
+mpls ldp
+ router-id 1.1.1.1
+ !
+ address-family ipv4
+ discovery transport-address 1.1.1.1
+ !
+ interface eth0
+ exit
+ !
+ exit-address-family
+ !
+exit
+
end
diff --git a/dev/configs/router2.conf b/dev/configs/router2.conf
index ab33cf4..b3ef84e 100644
--- a/dev/configs/router2.conf
+++ b/dev/configs/router2.conf
@@ -131,4 +131,19 @@ exit
ip route 192.168.200.0/24 10.0.0.10
ip route 192.168.201.0/24 10.0.0.10
+! ============================================
+! MPLS LDP Configuration
+! ============================================
+mpls ldp
+ router-id 2.2.2.2
+ !
+ address-family ipv4
+ discovery transport-address 2.2.2.2
+ !
+ interface eth0
+ exit
+ !
+ exit-address-family
+ !
+exit
end
diff --git a/dev/configs/startup.sh b/dev/configs/startup.sh
index 28f553c..21feafc 100755
--- a/dev/configs/startup.sh
+++ b/dev/configs/startup.sh
@@ -7,6 +7,12 @@ set -e
# Enable IP forwarding
sysctl -w net.ipv4.conf.all.forwarding=1 &>/dev/null || true
+# Enable MPLS
+modprobe mpls_router &>/dev/null || true
+modprobe mpls_iptunnel &>/dev/null || true
+sysctl -w net.mpls.platform_labels=1000 &>/dev/null || true
+sysctl -w net.mpls.conf.eth0.input=1 &>/dev/null || true
+
# Create VRF if kernel supports it
if ip link add vrf-red type vrf table 10 2>/dev/null; then
echo "Created VRF vrf-red"
diff --git a/internal/frrsockets/frrsockets.go b/internal/frrsockets/frrsockets.go
index 3c4e619..0528d46 100644
--- a/internal/frrsockets/frrsockets.go
+++ b/internal/frrsockets/frrsockets.go
@@ -45,6 +45,10 @@ func (c Connection) ExecZebraCmd(cmd string) ([]byte, error) {
return executeCmd(filepath.Join(c.dirPath, "zebra.vty"), cmd, c.timeout)
}
+func (c Connection) ExecLDPDCmd(cmd string) ([]byte, error) {
+ return executeCmd(filepath.Join(c.dirPath, "ldpd.vty"), cmd, c.timeout)
+}
+
func executeCmd(socketPath, cmd string, timeout time.Duration) ([]byte, error) {
var response bytes.Buffer