Repository: metallb/metallb
Pull request #1747 (2022-12-16 08:46:45)

VRF: extend the tests and fix the broken things revealed by the tests
Here we extend the tests to cover all the configuration options with vrf, and we also hammer our way through the failures.


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 58da13f0..42c8cab0 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -198,7 +198,8 @@ jobs:
           echo '/etc/frr/core-%e.%p.%h.%t' | sudo tee /proc/sys/kernel/core_pattern
           if [ ${{ matrix.deployment }} = "helm" ]; then export SPEAKER_SELECTOR="app.kubernetes.io/component=speaker" && export CONTROLLER_SELECTOR="app.kubernetes.io/component=controller"; fi
           SKIP="none"
-          if [ "${{ matrix.bgp-type }}" == "native" ]; then SKIP="$SKIP|FRR|BFD|VRF|DUALSTACK"; fi
+          NATIVE_BGP=""
+          if [ "${{ matrix.bgp-type }}" == "native" ]; then SKIP="$SKIP|FRR|BFD|VRF|DUALSTACK"; NATIVE_BGP="--native-bgp"; fi
           if [ "${{ matrix.ip-family }}" == "ipv4" ]; then SKIP="$SKIP|IPV6|DUALSTACK"; fi
           if [ "${{ matrix.ip-family }}" == "dual" ]; then SKIP="$SKIP|IPV6"; fi
           if [ "${{ matrix.ip-family }}" == "ipv6" ]; then SKIP="$SKIP|IPV4|DUALSTACK"; fi
@@ -206,7 +207,8 @@ jobs:
           if [ "${{ matrix.prometheus }}" == "disabled" ]; then SKIP="$SKIP|metrics"; fi
 
           echo "Skipping $SKIP"
-          sudo -E env "PATH=$PATH" inv e2etest --skip $SKIP -e /tmp/kind_logs
+          echo "Native bgp mode $NATIVE_BGP"
+          sudo -E env "PATH=$PATH" inv e2etest --skip $SKIP $NATIVE_BGP -e /tmp/kind_logs
 
       - name: Collect Logs
         if: ${{ failure() }}
diff --git a/frr-tools/metrics/collector/bfd.go b/frr-tools/metrics/collector/bfd.go
index 58ed5605..2d1b1e55 100644
--- a/frr-tools/metrics/collector/bfd.go
+++ b/frr-tools/metrics/collector/bfd.go
@@ -4,6 +4,7 @@ package collector
 
 import (
 	"encoding/json"
+	"fmt"
 
 	"github.com/go-kit/log"
 	"github.com/go-kit/log/level"
@@ -11,7 +12,7 @@ import (
 
 	"go.universe.tf/metallb/frr-tools/metrics/vtysh"
 	bgpfrr "go.universe.tf/metallb/internal/bgp/frr"
-	bgpstats "go.universe.tf/metallb/internal/bgp/native"
+	bgpmetrics "go.universe.tf/metallb/internal/bgp/metrics"
 )
 
 type bfdPeerCounters struct {
@@ -29,58 +30,58 @@ const subsystem = "bfd"
 
 var (
 	bfdSessionUpDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, bgpstats.SessionUp.Name),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, bgpmetrics.SessionUp.Name),
 		"BFD session state (1 is up, 0 is down)",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	controlPacketInputDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "control_packet_input"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "control_packet_input"),
 		"Number of received BFD control packets",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	controlPacketOutputDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "control_packet_output"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "control_packet_output"),
 		"Number of sent BFD control packets",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	echoPacketInputDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "echo_packet_input"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "echo_packet_input"),
 		"Number of received BFD echo packets",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	echoPacketOutputDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "echo_packet_output"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "echo_packet_output"),
 		"Number of sent BFD echo packets",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	sessionUpEventsDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "session_up_events"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "session_up_events"),
 		"Number of BFD session up events",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	sessionDownEventsDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "session_down_events"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "session_down_events"),
 		"Number of BFD session down events",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	zebraNotificationsDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, subsystem, "zebra_notifications"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, subsystem, "zebra_notifications"),
 		"Number of BFD zebra notifications",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 )
@@ -124,49 +125,72 @@ func (c *bfd) Collect(ch chan<- prometheus.Metric) {
 	updatePeersCountersMetrics(ch, peersCounters)
 }
 
-func updatePeersMetrics(ch chan<- prometheus.Metric, peers map[string]bgpfrr.BFDPeer) {
-	for _, p := range peers {
-		sessionUp := 1
-		if p.Status != "up" {
-			sessionUp = 0
-		}
+func updatePeersMetrics(ch chan<- prometheus.Metric, peersPerVRF map[string][]bgpfrr.BFDPeer) {
+	for vrf, peers := range peersPerVRF {
+		for _, p := range peers {
+			sessionUp := 1
+			if p.Status != "up" {
+				sessionUp = 0
+			}
 
-		ch <- prometheus.MustNewConstMetric(bfdSessionUpDesc, prometheus.GaugeValue, float64(sessionUp), p.Peer)
+			ch <- prometheus.MustNewConstMetric(bfdSessionUpDesc, prometheus.GaugeValue, float64(sessionUp), p.Peer, vrf)
+		}
 	}
 }
 
-func updatePeersCountersMetrics(ch chan<- prometheus.Metric, peersCounters []bfdPeerCounters) {
-	for _, p := range peersCounters {
-		ch <- prometheus.MustNewConstMetric(controlPacketInputDesc, prometheus.CounterValue, float64(p.ControlPacketInput), p.Peer)
-		ch <- prometheus.MustNewConstMetric(controlPacketOutputDesc, prometheus.CounterValue, float64(p.ControlPacketOutput), p.Peer)
-		ch <- prometheus.MustNewConstMetric(echoPacketInputDesc, prometheus.CounterValue, float64(p.EchoPacketInput), p.Peer)
-		ch <- prometheus.MustNewConstMetric(echoPacketOutputDesc, prometheus.CounterValue, float64(p.EchoPacketOutput), p.Peer)
-		ch <- prometheus.MustNewConstMetric(sessionUpEventsDesc, prometheus.CounterValue, float64(p.SessionUpEvents), p.Peer)
-		ch <- prometheus.MustNewConstMetric(sessionDownEventsDesc, prometheus.CounterValue, float64(p.SessionDownEvents), p.Peer)
-		ch <- prometheus.MustNewConstMetric(zebraNotificationsDesc, prometheus.CounterValue, float64(p.ZebraNotifications), p.Peer)
+func updatePeersCountersMetrics(ch chan<- prometheus.Metric, peersCountersPerVRF map[string][]bfdPeerCounters) {
+	for vrf, peersCounters := range peersCountersPerVRF {
+		for _, p := range peersCounters {
+			ch <- prometheus.MustNewConstMetric(controlPacketInputDesc, prometheus.CounterValue, float64(p.ControlPacketInput), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(controlPacketOutputDesc, prometheus.CounterValue, float64(p.ControlPacketOutput), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(echoPacketInputDesc, prometheus.CounterValue, float64(p.EchoPacketInput), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(echoPacketOutputDesc, prometheus.CounterValue, float64(p.EchoPacketOutput), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(sessionUpEventsDesc, prometheus.CounterValue, float64(p.SessionUpEvents), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(sessionDownEventsDesc, prometheus.CounterValue, float64(p.SessionDownEvents), p.Peer, vrf)
+			ch <- prometheus.MustNewConstMetric(zebraNotificationsDesc, prometheus.CounterValue, float64(p.ZebraNotifications), p.Peer, vrf)
+		}
 	}
 }
 
-func getBFDPeers(frrCli vtysh.Cli) (map[string]bgpfrr.BFDPeer, error) {
-	res, err := frrCli("show bfd peers json")
+func getBFDPeers(frrCli vtysh.Cli) (map[string][]bgpfrr.BFDPeer, error) {
+	vrfs, err := vtysh.VRFs(frrCli)
 	if err != nil {
 		return nil, err
 	}
-
-	return bgpfrr.ParseBFDPeers(res)
+	res := make(map[string][]bgpfrr.BFDPeer)
+	for _, vrf := range vrfs {
+		peersJson, err := frrCli(fmt.Sprintf("show bfd vrf %s peers json", vrf))
+		if err != nil {
+			return nil, err
+		}
+		peers, err := bgpfrr.ParseBFDPeers(peersJson)
+		if err != nil {
+			return nil, err
+		}
+		res[vrf] = peers
+	}
+	return res, nil
 }
 
-func getBFDPeersCounters(frrCli vtysh.Cli) ([]bfdPeerCounters, error) {
-	res, err := frrCli("show bfd peers counters json")
+func getBFDPeersCounters(frrCli vtysh.Cli) (map[string][]bfdPeerCounters, error) {
+	vrfs, err := vtysh.VRFs(frrCli)
 	if err != nil {
 		return nil, err
 	}
 
-	parseRes := []bfdPeerCounters{}
-	err = json.Unmarshal([]byte(res), &parseRes)
-	if err != nil {
-		return nil, err
-	}
+	res := make(map[string][]bfdPeerCounters)
+	for _, vrf := range vrfs {
+		countersJson, err := frrCli(fmt.Sprintf("show bfd vrf %s peers counters json", vrf))
+		if err != nil {
+			return nil, err
+		}
 
-	return parseRes, nil
+		parseRes := []bfdPeerCounters{}
+		err = json.Unmarshal([]byte(countersJson), &parseRes)
+		if err != nil {
+			return nil, err
+		}
+		res[vrf] = parseRes
+	}
+	return res, nil
 }
diff --git a/frr-tools/metrics/collector/bgp.go b/frr-tools/metrics/collector/bgp.go
index 0fe3b21d..4b8d0f51 100644
--- a/frr-tools/metrics/collector/bgp.go
+++ b/frr-tools/metrics/collector/bgp.go
@@ -11,91 +11,93 @@ import (
 
 	"go.universe.tf/metallb/frr-tools/metrics/vtysh"
 	bgpfrr "go.universe.tf/metallb/internal/bgp/frr"
-	bgpstats "go.universe.tf/metallb/internal/bgp/native"
+	bgpmetrics "go.universe.tf/metallb/internal/bgp/metrics"
 )
 
+var labels = []string{"peer", "vrf"}
+
 var (
 	sessionUpDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, bgpstats.SessionUp.Name),
-		bgpstats.SessionUp.Help,
-		bgpstats.Labels,
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, bgpmetrics.SessionUp.Name),
+		bgpmetrics.SessionUp.Help,
+		labels,
 		nil,
 	)
 
 	prefixesDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, bgpstats.Prefixes.Name),
-		bgpstats.Prefixes.Help,
-		bgpstats.Labels,
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, bgpmetrics.Prefixes.Name),
+		bgpmetrics.Prefixes.Help,
+		labels,
 		nil,
 	)
 
 	opensSentDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "opens_sent"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "opens_sent"),
 		"Number of BGP open messages sent",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	opensReceivedDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "opens_received"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "opens_received"),
 		"Number of BGP open messages received",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	notificationsSentDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "notifications_sent"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "notifications_sent"),
 		"Number of BGP notification messages sent",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	updatesSentDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, bgpstats.UpdatesSent.Name),
-		bgpstats.UpdatesSent.Help,
-		bgpstats.Labels,
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, bgpmetrics.UpdatesSent.Name),
+		bgpmetrics.UpdatesSent.Help,
+		labels,
 		nil,
 	)
 
 	updatesReceivedDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "updates_total_received"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "updates_total_received"),
 		"Number of BGP UPDATE messages received",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	keepalivesSentDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "keepalives_sent"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "keepalives_sent"),
 		"Number of BGP keepalive messages sent",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	keepalivesReceivedDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "keepalives_received"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "keepalives_received"),
 		"Number of BGP keepalive messages received",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	routeRefreshSentedDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "route_refresh_sent"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "route_refresh_sent"),
 		"Number of BGP route refresh messages sent",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	totalSentDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "total_sent"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "total_sent"),
 		"Number of total BGP messages sent",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 
 	totalReceivedDesc = prometheus.NewDesc(
-		prometheus.BuildFQName(bgpstats.Namespace, bgpstats.Subsystem, "total_received"),
+		prometheus.BuildFQName(bgpmetrics.Namespace, bgpmetrics.Subsystem, "total_received"),
 		"Number of total BGP messages received",
-		bgpstats.Labels,
+		labels,
 		nil,
 	)
 )
@@ -106,7 +108,7 @@ type bgp struct {
 }
 
 func NewBGP(l log.Logger) *bgp {
-	log := log.With(l, "collector", bgpstats.Subsystem)
+	log := log.With(l, "collector", bgpmetrics.Subsystem)
 	return &bgp{Log: log, frrCli: vtysh.Run}
 }
 
@@ -135,34 +137,49 @@ func (c *bgp) Collect(ch chan<- prometheus.Metric) {
 	updateNeighborsMetrics(ch, neighbors)
 }
 
-func updateNeighborsMetrics(ch chan<- prometheus.Metric, neighbors []*bgpfrr.Neighbor) {
-	for _, n := range neighbors {
-		sessionUp := 1
-		if !n.Connected {
-			sessionUp = 0
+func updateNeighborsMetrics(ch chan<- prometheus.Metric, neighbors map[string][]*bgpfrr.Neighbor) {
+	for vrf, nn := range neighbors {
+		for _, n := range nn {
+			sessionUp := 1
+			if !n.Connected {
+				sessionUp = 0
+			}
+			peerLabel := fmt.Sprintf("%s:%d", n.Ip.String(), n.Port)
+
+			ch <- prometheus.MustNewConstMetric(sessionUpDesc, prometheus.GaugeValue, float64(sessionUp), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(prefixesDesc, prometheus.GaugeValue, float64(n.PrefixSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(opensSentDesc, prometheus.CounterValue, float64(n.MsgStats.OpensSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(opensReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.OpensReceived), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(notificationsSentDesc, prometheus.CounterValue, float64(n.MsgStats.NotificationsSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(updatesSentDesc, prometheus.CounterValue, float64(n.MsgStats.UpdatesSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(updatesReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.UpdatesReceived), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(keepalivesSentDesc, prometheus.CounterValue, float64(n.MsgStats.KeepalivesSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(keepalivesReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.KeepalivesReceived), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(routeRefreshSentedDesc, prometheus.CounterValue, float64(n.MsgStats.RouteRefreshSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(totalSentDesc, prometheus.CounterValue, float64(n.MsgStats.TotalSent), peerLabel, vrf)
+			ch <- prometheus.MustNewConstMetric(totalReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.TotalReceived), peerLabel, vrf)
 		}
-		peerLabel := fmt.Sprintf("%s:%d", n.Ip.String(), n.Port)
-
-		ch <- prometheus.MustNewConstMetric(sessionUpDesc, prometheus.GaugeValue, float64(sessionUp), peerLabel)
-		ch <- prometheus.MustNewConstMetric(prefixesDesc, prometheus.GaugeValue, float64(n.PrefixSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(opensSentDesc, prometheus.CounterValue, float64(n.MsgStats.OpensSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(opensReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.OpensReceived), peerLabel)
-		ch <- prometheus.MustNewConstMetric(notificationsSentDesc, prometheus.CounterValue, float64(n.MsgStats.NotificationsSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(updatesSentDesc, prometheus.CounterValue, float64(n.MsgStats.UpdatesSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(updatesReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.UpdatesReceived), peerLabel)
-		ch <- prometheus.MustNewConstMetric(keepalivesSentDesc, prometheus.CounterValue, float64(n.MsgStats.KeepalivesSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(keepalivesReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.KeepalivesReceived), peerLabel)
-		ch <- prometheus.MustNewConstMetric(routeRefreshSentedDesc, prometheus.CounterValue, float64(n.MsgStats.RouteRefreshSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(totalSentDesc, prometheus.CounterValue, float64(n.MsgStats.TotalSent), peerLabel)
-		ch <- prometheus.MustNewConstMetric(totalReceivedDesc, prometheus.CounterValue, float64(n.MsgStats.TotalReceived), peerLabel)
 	}
 }
 
-func getBGPNeighbors(frrCli vtysh.Cli) ([]*bgpfrr.Neighbor, error) {
-	res, err := frrCli("show bgp neighbors json")
+func getBGPNeighbors(frrCli vtysh.Cli) (map[string][]*bgpfrr.Neighbor, error) {
+	vrfs, err := vtysh.VRFs(frrCli)
 	if err != nil {
 		return nil, err
 	}
+	neighbors := make(map[string][]*bgpfrr.Neighbor, 0)
+	for _, vrf := range vrfs {
+		res, err := frrCli(fmt.Sprintf("show bgp vrf %s neighbors json", vrf))
+		if err != nil {
+			return nil, err
+		}
 
-	return bgpfrr.ParseNeighbours(res)
+		neighborsPerVRF, err := bgpfrr.ParseNeighbours(res)
+		if err != nil {
+			return nil, err
+		}
+		neighbors[vrf] = neighborsPerVRF
+
+	}
+	return neighbors, nil
 }
diff --git a/frr-tools/metrics/vtysh/vtysh.go b/frr-tools/metrics/vtysh/vtysh.go
index 46175067..d56f60c1 100644
--- a/frr-tools/metrics/vtysh/vtysh.go
+++ b/frr-tools/metrics/vtysh/vtysh.go
@@ -4,6 +4,8 @@ package vtysh
 
 import (
 	"os/exec"
+
+	bgpfrr "go.universe.tf/metallb/internal/bgp/frr"
 )
 
 type Cli func(args string) (string, error)
@@ -14,3 +16,15 @@ func Run(args string) (string, error) {
 }
 
 var _ Cli = Run
+
+func VRFs(frrCli Cli) ([]string, error) {
+	vrfs, err := frrCli("show bgp vrf all json")
+	if err != nil {
+		return nil, err
+	}
+	parsedVRFs, err := bgpfrr.ParseVRFs(vrfs)
+	if err != nil {
+		return nil, err
+	}
+	return parsedVRFs, nil
+}
diff --git a/internal/bgp/frr/parse.go b/internal/bgp/frr/parse.go
index b8af9cbd..94d27a04 100644
--- a/internal/bgp/frr/parse.go
+++ b/internal/bgp/frr/parse.go
@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net"
+	"sort"
 	"strconv"
 
 	"github.com/pkg/errors"
@@ -13,6 +14,7 @@ import (
 
 type Neighbor struct {
 	Ip             net.IP
+	VRF            string
 	Connected      bool
 	LocalAS        string
 	RemoteAS       string
@@ -39,6 +41,7 @@ type FRRNeighbor struct {
 	BgpState          string       `json:"bgpState"`
 	PortForeign       int          `json:"portForeign"`
 	MsgStats          MessageStats `json:"messageStats"`
+	VRFName           string       `json:"vrf"`
 	AddressFamilyInfo map[string]struct {
 		SentPrefixCounter int `json:"sentPrefixCounter"`
 	} `json:"addressFamilyInfo"`
@@ -219,16 +222,25 @@ func ParseRoutes(vtyshRes string) (map[string]Route, error) {
 	return res, nil
 }
 
-func ParseBFDPeers(vtyshRes string) (map[string]BFDPeer, error) {
+func ParseBFDPeers(vtyshRes string) ([]BFDPeer, error) {
 	parseRes := []BFDPeer{}
 	err := json.Unmarshal([]byte(vtyshRes), &parseRes)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to parse vtysh response")
 	}
-	res := make(map[string]BFDPeer)
-	for _, p := range parseRes {
-		res[p.Peer] = p
+	return parseRes, nil
+}
 
+func ParseVRFs(vtyshRes string) ([]string, error) {
+	vrfs := map[string]interface{}{}
+	err := json.Unmarshal([]byte(vtyshRes), &vrfs)
+	if err != nil {
+		return nil, errors.Wrap(err, "parseVRFs: failed to parse vtysh response")
+	}
+	res := make([]string, 0)
+	for v := range vrfs {
+		res = append(res, v)
 	}
+	sort.Strings(res)
 	return res, nil
 }
diff --git a/internal/bgp/metrics/metrics.go b/internal/bgp/metrics/metrics.go
new file mode 100644
index 00000000..c32fc048
--- /dev/null
+++ b/internal/bgp/metrics/metrics.go
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package metrics
+
+type metric struct {
+	Name string
+	Help string
+}
+
+var (
+	Namespace = "metallb"
+	Subsystem = "bgp"
+
+	SessionUp = metric{
+		Name: "session_up",
+		Help: "BGP session state (1 is up, 0 is down)",
+	}
+
+	UpdatesSent = metric{
+		Name: "updates_total",
+		Help: "Number of BGP UPDATE messages sent",
+	}
+
+	Prefixes = metric{
+		Name: "announced_prefixes_total",
+		Help: "Number of prefixes currently being advertised on the BGP session",
+	}
+)
diff --git a/internal/bgp/native/stats.go b/internal/bgp/native/stats.go
index 10089cba..001d469f 100644
--- a/internal/bgp/native/stats.go
+++ b/internal/bgp/native/stats.go
@@ -2,62 +2,41 @@
 
 package native
 
-import "github.com/prometheus/client_golang/prometheus"
-
-type stat struct {
-	Name string
-	Help string
-}
-
-var (
-	Namespace = "metallb"
-	Subsystem = "bgp"
-	Labels    = []string{"peer"}
-
-	SessionUp = stat{
-		Name: "session_up",
-		Help: "BGP session state (1 is up, 0 is down)",
-	}
-
-	UpdatesSent = stat{
-		Name: "updates_total",
-		Help: "Number of BGP UPDATE messages sent",
-	}
-
-	Prefixes = stat{
-		Name: "announced_prefixes_total",
-		Help: "Number of prefixes currently being advertised on the BGP session",
-	}
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	bgpmetrics "go.universe.tf/metallb/internal/bgp/metrics"
 )
 
+var labels = []string{"peer"}
+
 var stats = metrics{
 	sessionUp: prometheus.NewGaugeVec(prometheus.GaugeOpts{
-		Namespace: Namespace,
-		Subsystem: Subsystem,
-		Name:      SessionUp.Name,
-		Help:      SessionUp.Help,
-	}, Labels),
+		Namespace: bgpmetrics.Namespace,
+		Subsystem: bgpmetrics.Subsystem,
+		Name:      bgpmetrics.SessionUp.Name,
+		Help:      bgpmetrics.SessionUp.Help,
+	}, labels),
 
 	updatesSent: prometheus.NewCounterVec(prometheus.CounterOpts{
-		Namespace: Namespace,
-		Subsystem: Subsystem,
-		Name:      UpdatesSent.Name,
-		Help:      UpdatesSent.Help,
-	}, Labels),
+		Namespace: bgpmetrics.Namespace,
+		Subsystem: bgpmetrics.Subsystem,
+		Name:      bgpmetrics.UpdatesSent.Name,
+		Help:      bgpmetrics.UpdatesSent.Help,
+	}, labels),
 
 	prefixes: prometheus.NewGaugeVec(prometheus.GaugeOpts{
-		Namespace: Namespace,
-		Subsystem: Subsystem,
-		Name:      Prefixes.Name,
-		Help:      Prefixes.Help,
-	}, Labels),
+		Namespace: bgpmetrics.Namespace,
+		Subsystem: bgpmetrics.Subsystem,
+		Name:      bgpmetrics.Prefixes.Name,
+		Help:      bgpmetrics.Prefixes.Help,
+	}, labels),
 
 	pendingPrefixes: prometheus.NewGaugeVec(prometheus.GaugeOpts{
-		Namespace: Namespace,
-		Subsystem: Subsystem,
+		Namespace: bgpmetrics.Namespace,
+		Subsystem: bgpmetrics.Subsystem,
 		Name:      "pending_prefixes_total",
 		Help:      "Number of prefixes that should be advertised on the BGP session",
-	}, Labels),
+	}, labels),
 }
 
 type metrics struct {
diff --git a/tasks.py b/tasks.py
index 0309fb19..c07f57c7 100644
--- a/tasks.py
+++ b/tasks.py
@@ -539,9 +539,7 @@ def dev_env_cleanup(ctx, name="kind", frr_volume_dir=""):
     clusters = run("kind get clusters", hide=True).stdout.strip().splitlines()
     if name in clusters:
         run("kind delete cluster --name={}".format(name), hide=True)
-    else:
-        raise Exit(message="Unable to find cluster named: {}".format(name))
-
+    
     run('for frr in $(docker ps -a -f name=frr --format {{.Names}}) ; do '
         '    docker rm -f $frr ; '
         'done', hide=True)
@@ -560,7 +558,8 @@ def dev_env_cleanup(ctx, name="kind", frr_volume_dir=""):
     run('rm -f "%s"/config.yaml' % dev_env_dir)
 
     # cleanup extra bridge
-    run('docker network rm {bridge_name}'.format(bridge_name=extra_network))
+    run('docker network rm {bridge_name}'.format(bridge_name=extra_network), warn=True)
+    run('docker network rm vrf-net', warn=True)
 
 @task(help={
     "version": "version of MetalLB to release.",
@@ -739,8 +738,9 @@ def helmdocs(ctx, env="container"):
     "node_nics": "a list of node's interfaces separated by comma, default is kind",
     "local_nics": "a list of bridges related node's interfaces separated by comma, default is kind",
     "external_containers": "a comma separated list of external containers names to use for the test. (valid parameters are: ibgp-single-hop / ibgp-multi-hop / ebgp-single-hop / ebgp-multi-hop)",
+    "native_bgp": "tells if the given cluster is deployed using native bgp mode ",
 })
-def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="kube-system,metallb-system", service_pod_port=80, skip_docker=False, focus="", skip="", ipv4_service_range=None, ipv6_service_range=None, prometheus_namespace="", node_nics="kind", local_nics="kind", external_containers=""):
+def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="kube-system,metallb-system", service_pod_port=80, skip_docker=False, focus="", skip="", ipv4_service_range=None, ipv6_service_range=None, prometheus_namespace="", node_nics="kind", local_nics="kind", external_containers="", native_bgp=False,):
     """Run E2E tests against development cluster."""
     if skip_docker:
         opt_skip_docker = "--skip-docker"
@@ -803,7 +803,7 @@ def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="k
         external_containers = "--external-containers="+(external_containers)
 
     testrun = run("cd `git rev-parse --show-toplevel`/e2etest &&"
-            "KUBECONFIG={} go test -timeout 3h {} {} --provider=local --kubeconfig={} --service-pod-port={} {} {} -ipv4-service-range={} -ipv6-service-range={} {} --report-path {} {} -node-nics {} -local-nics {} {}".format(kubeconfig, ginkgo_focus, ginkgo_skip, kubeconfig, service_pod_port, ips_for_containers_v4, ips_for_containers_v6, ipv4_service_range, ipv6_service_range, opt_skip_docker, report_path, prometheus_namespace, node_nics, local_nics, external_containers), warn="True")
+            "KUBECONFIG={} go test -timeout 3h {} {} --provider=local --kubeconfig={} --service-pod-port={} {} {} -ipv4-service-range={} -ipv6-service-range={} {} --report-path {} {} -node-nics {} -local-nics {} {} -bgp-native-mode={}".format(kubeconfig, ginkgo_focus, ginkgo_skip, kubeconfig, service_pod_port, ips_for_containers_v4, ips_for_containers_v6, ipv4_service_range, ipv6_service_range, opt_skip_docker, report_path, prometheus_namespace, node_nics, local_nics, external_containers, native_bgp), warn="True")
 
     if export != None:
         run("kind export logs {}".format(export))


Test patch:
diff --git a/e2etest/bgptests/bgp.go b/e2etest/bgptests/bgp.go
index 6f54b24e..97844895 100644
--- a/e2etest/bgptests/bgp.go
+++ b/e2etest/bgptests/bgp.go
@@ -32,7 +32,6 @@ import (
 	"go.universe.tf/metallb/e2etest/pkg/k8s"
 	"go.universe.tf/metallb/e2etest/pkg/mac"
 	"go.universe.tf/metallb/e2etest/pkg/metallb"
-	"go.universe.tf/metallb/e2etest/pkg/metrics"
 	"go.universe.tf/metallb/e2etest/pkg/service"
 	metallbconfig "go.universe.tf/metallb/internal/config"
 	"go.universe.tf/metallb/internal/pointer"
@@ -257,327 +256,6 @@ var _ = ginkgo.Describe("BGP", func() {
 		table.Entry("IPV6", ipfamily.IPv6, []string{v6PoolAddresses}),
 	)
 
-	ginkgo.Context("metrics", func() {
-		var controllerPod *corev1.Pod
-		var speakerPods []*corev1.Pod
-		var promPod *corev1.Pod
-
-		ginkgo.BeforeEach(func() {
-			var err error
-			controllerPod, err = metallb.ControllerPod(cs)
-			framework.ExpectNoError(err)
-			speakerPods, err = metallb.SpeakerPods(cs)
-			framework.ExpectNoError(err)
-			promPod, err = metrics.PrometheusPod(cs, PrometheusNamespace)
-			framework.ExpectNoError(err)
-		})
-
-		table.DescribeTable("should collect BGP metrics in FRR mode", func(ipFamily ipfamily.Family, poolAddress string, addressTotal int) {
-			poolName := "bgp-test"
-
-			peerAddrToName := make(map[string]string)
-			for _, c := range FRRContainers {
-				address := c.Ipv4
-				if ipFamily == ipfamily.IPv6 {
-					address = c.Ipv6
-				}
-				peerAddr := address + fmt.Sprintf(":%d", c.RouterConfig.BGPPort)
-				peerAddrToName[peerAddr] = c.Name
-			}
-
-			resources := metallbconfig.ClusterResources{
-				Pools: []metallbv1beta1.IPAddressPool{
-					{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: poolName,
-						},
-						Spec: metallbv1beta1.IPAddressPoolSpec{
-							Addresses: []string{poolAddress},
-						},
-					},
-				},
-				Peers:   metallb.PeersForContainers(FRRContainers, ipFamily),
-				BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
-			}
-
-			for _, c := range FRRContainers {
-				err := frrcontainer.PairWithNodes(cs, c, ipFamily)
-				framework.ExpectNoError(err)
-			}
-
-			err := ConfigUpdater.Update(resources)
-			framework.ExpectNoError(err)
-
-			for _, c := range FRRContainers {
-				validateFRRPeeredWithAllNodes(cs, c, ipFamily)
-			}
-
-			ginkgo.By("creating a service")
-			svc, _ := testservice.CreateWithBackend(cs, f.Namespace.Name, "external-local-lb", testservice.TrafficPolicyCluster) // Is a sleep required here?
-			defer testservice.Delete(cs, svc)
-
-			for _, speaker := range speakerPods {
-				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
-				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
-					if err != nil {
-						return err
-					}
-					for addr := range peerAddrToName {
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_opens_sent", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_sent{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_opens_received", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_received{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bgp_updates_total_received", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total_received{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_keepalives_sent", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_sent{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_keepalives_received", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_received{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_route_refresh_sent", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_route_refresh_sent{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_total_sent", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_sent{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_total_received", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_received{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-					}
-					return nil
-				}, 2*time.Minute, 1*time.Second).Should(BeNil())
-			}
-		},
-			table.Entry("IPV4 - Checking service", ipfamily.IPv4, v4PoolAddresses, 256),
-			table.Entry("IPV6 - Checking service", ipfamily.IPv6, v6PoolAddresses, 16),
-		)
-
-		table.DescribeTable("should be exposed by the controller", func(ipFamily ipfamily.Family, poolAddress string, addressTotal int) {
-			poolName := "bgp-test"
-
-			peerAddrToName := make(map[string]string)
-			for _, c := range FRRContainers {
-				address := c.Ipv4
-				if ipFamily == ipfamily.IPv6 {
-					address = c.Ipv6
-				}
-				peerAddr := address + fmt.Sprintf(":%d", c.RouterConfig.BGPPort)
-				peerAddrToName[peerAddr] = c.Name
-			}
-
-			resources := metallbconfig.ClusterResources{
-				Pools: []metallbv1beta1.IPAddressPool{
-					{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: poolName,
-						},
-						Spec: metallbv1beta1.IPAddressPoolSpec{
-							Addresses: []string{poolAddress},
-						},
-					},
-				},
-				Peers:   metallb.PeersForContainers(FRRContainers, ipFamily),
-				BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
-			}
-
-			for _, c := range FRRContainers {
-				err := frrcontainer.PairWithNodes(cs, c, ipFamily)
-				framework.ExpectNoError(err)
-			}
-
-			err := ConfigUpdater.Update(resources)
-			framework.ExpectNoError(err)
-
-			for _, c := range FRRContainers {
-				validateFRRPeeredWithAllNodes(cs, c, ipFamily)
-			}
-
-			ginkgo.By("checking the metrics when no service is added")
-			Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateGaugeValue(0, "metallb_allocator_addresses_in_use_total", map[string]string{"pool": poolName}, controllerMetrics)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_in_use_total{pool="%s"} == 0`, poolName), metrics.There)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateGaugeValue(addressTotal, "metallb_allocator_addresses_total", map[string]string{"pool": poolName}, controllerMetrics)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_total{pool="%s"} == %d`, poolName, addressTotal), metrics.There)
-				if err != nil {
-					return err
-				}
-				return nil
-			}, 2*time.Minute, 1*time.Second).Should(BeNil())
-
-			for _, speaker := range speakerPods {
-				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
-
-				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
-					if err != nil {
-						return err
-					}
-					for peerAddr, peerName := range peerAddrToName {
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", map[string]string{"peer": peerAddr}, speakerMetrics)
-						if err != nil {
-							framework.Logf("frr metrics: %q, neighbor: %s-%s, speaker: %s", speakerMetrics, peerName, peerAddr, speaker.Namespace+"/"+speaker.Name)
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{peer="%s"} == 1`, peerAddr), metrics.There)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateGaugeValue(0, "metallb_bgp_announced_prefixes_total", map[string]string{"peer": peerAddr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{peer="%s"} == 0`, peerAddr), metrics.There)
-						if err != nil {
-							return err
-						}
-					}
-					return nil
-				}, 2*time.Minute, 1*time.Second).Should(BeNil())
-			}
-
-			ginkgo.By("creating a service")
-			svc, _ := testservice.CreateWithBackend(cs, f.Namespace.Name, "external-local-lb", testservice.TrafficPolicyCluster) // Is a sleep required here?
-			defer testservice.Delete(cs, svc)
-
-			ginkgo.By("checking the metrics when a service is added")
-			Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateGaugeValue(1, "metallb_allocator_addresses_in_use_total", map[string]string{"pool": poolName}, controllerMetrics)
-				if err != nil {
-					return err
-				}
-				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_in_use_total{pool="%s"} == 1`, poolName), metrics.There)
-				if err != nil {
-					return err
-				}
-				return nil
-			}, 2*time.Minute, 1*time.Second).Should(BeNil())
-
-			for _, speaker := range speakerPods {
-				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
-
-				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
-					if err != nil {
-						return err
-					}
-					for addr := range peerAddrToName {
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{peer="%s"} == 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_announced_prefixes_total", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{peer="%s"} == 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bgp_updates_total", map[string]string{"peer": addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total{peer="%s"} >= 1`, addr), metrics.There)
-						if err != nil {
-							return err
-						}
-					}
-
-					err = metrics.ValidateGaugeValue(1, "metallb_speaker_announced", map[string]string{"node": speaker.Spec.NodeName, "protocol": "bgp", "service": fmt.Sprintf("%s/%s", f.Namespace.Name, svc.Name)}, speakerMetrics)
-					if err != nil {
-						return err
-					}
-					return nil
-				}, 2*time.Minute, 1*time.Second).Should(BeNil())
-			}
-		},
-			table.Entry("IPV4 - Checking service", ipfamily.IPv4, v4PoolAddresses, 256),
-			table.Entry("IPV6 - Checking service", ipfamily.IPv6, v6PoolAddresses, 16))
-	})
-
 	ginkgo.Context("validate different AddressPools for type=Loadbalancer", func() {
 		ginkgo.AfterEach(func() {
 			// Clean previous configuration.
@@ -717,7 +395,7 @@ var _ = ginkgo.Describe("BGP", func() {
 
 		resources := metallbconfig.ClusterResources{
 			Peers: metallb.PeersForContainers(FRRContainers, ipFamily, func(p *metallbv1beta2.BGPPeer) {
-				p.Spec.PasswordSecret = corev1.SecretReference{Name: metallb.GetBGPPeerSecretName(p.Spec.ASN, p.Spec.Port)}
+				p.Spec.PasswordSecret = corev1.SecretReference{Name: metallb.GetBGPPeerSecretName(p.Spec.ASN, p.Spec.Port, p.Spec.VRFName)}
 				p.Spec.Password = ""
 			}),
 			PasswordSecrets: metallb.BGPPeerSecretReferences(FRRContainers),
@@ -779,6 +457,7 @@ var _ = ginkgo.Describe("BGP", func() {
 			for _, c := range FRRContainers {
 				validateFRRPeeredWithAllNodes(cs, c, pairingFamily)
 			}
+
 			for _, c := range FRRContainers {
 				validateService(cs, svc, allNodes.Items, c)
 			}
@@ -892,346 +571,6 @@ var _ = ginkgo.Describe("BGP", func() {
 					testservice.DualStack(svc)
 				}),
 		)
-
-		ginkgo.It("FRR metrics related to config should be exposed", func() {
-			controllerPod, err := metallb.ControllerPod(cs)
-			framework.ExpectNoError(err)
-
-			speakers, err := metallb.SpeakerPods(cs)
-			framework.ExpectNoError(err)
-			allPods := append(speakers, controllerPod)
-
-			bfdProfile := metallbv1beta1.BFDProfile{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "bfd",
-				},
-			}
-
-			ginkgo.By("Creating an invalid configuration")
-
-			resources := metallbconfig.ClusterResources{
-				Pools: []metallbv1beta1.IPAddressPool{
-					{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "metrics-test",
-						},
-						Spec: metallbv1beta1.IPAddressPoolSpec{
-							Addresses: []string{v4PoolAddresses},
-						},
-					},
-				},
-				Peers:   metallb.WithBFD(metallb.PeersForContainers(FRRContainers, ipfamily.IPv4), "bfd"),
-				BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
-			}
-			err = ConfigUpdater.Update(resources)
-			framework.ExpectNoError(err)
-
-			promPod, err := metrics.PrometheusPod(cs, PrometheusNamespace)
-			framework.ExpectNoError(err)
-
-			ginkgo.By("Checking the config stale metric on the speakers")
-			for _, pod := range speakers {
-				ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
-				Eventually(func() error {
-					podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
-					framework.ExpectNoError(err)
-					err = metrics.ValidateGaugeValue(1, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
-					if err != nil {
-						return err
-					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_stale_bool{pod="%s"} == 1`, pod.Name), metrics.There)
-					if err != nil {
-						return err
-					}
-					return nil
-				}, time.Minute, 1*time.Second).ShouldNot(HaveOccurred(), "on pod", pod.Name)
-			}
-
-			resources.BFDProfiles = []metallbv1beta1.BFDProfile{bfdProfile}
-			err = ConfigUpdater.Update(resources)
-			framework.ExpectNoError(err)
-			for _, pod := range allPods {
-				ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
-				Eventually(func() error {
-					podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
-					framework.ExpectNoError(err)
-					err = metrics.ValidateGaugeValue(0, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
-					if err != nil {
-						return err
-					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_stale_bool{pod="%s"} == 0`, pod.Name), metrics.There)
-					if err != nil {
-						return err
-					}
-					// we don't know how many events we are processing
-					err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_k8s_client_updates_total", map[string]string{}, podMetrics)
-					if err != nil {
-						return err
-					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_updates_total{pod="%s"} > 0`, pod.Name), metrics.There)
-					if err != nil {
-						return err
-					}
-					err = metrics.ValidateGaugeValue(1, "metallb_k8s_client_config_loaded_bool", map[string]string{}, podMetrics)
-					if err != nil {
-						return err
-					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_loaded_bool{pod="%s"} == 1`, pod.Name), metrics.There)
-					if err != nil {
-						return err
-					}
-					return nil
-				}, time.Minute, 5*time.Second).ShouldNot(HaveOccurred(), "on pod", pod.Name)
-			}
-		})
-
-		table.DescribeTable("metrics", func(bfd metallbv1beta1.BFDProfile, pairingFamily ipfamily.Family, poolAddresses []string) {
-			resources := metallbconfig.ClusterResources{
-				Pools: []metallbv1beta1.IPAddressPool{
-					{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "bfd-test",
-						},
-						Spec: metallbv1beta1.IPAddressPoolSpec{
-							Addresses: poolAddresses,
-						},
-					},
-				},
-				Peers:       metallb.WithBFD(metallb.PeersForContainers(FRRContainers, pairingFamily), bfd.Name),
-				BGPAdvs:     []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
-				BFDProfiles: []metallbv1beta1.BFDProfile{bfd},
-			}
-
-			err := ConfigUpdater.Update(resources)
-			framework.ExpectNoError(err)
-
-			for _, c := range FRRContainers {
-				err := frrcontainer.PairWithNodes(cs, c, pairingFamily, func(container *frrcontainer.FRR) {
-					container.NeighborConfig.BFDEnabled = true
-				})
-				framework.ExpectNoError(err)
-			}
-
-			for _, c := range FRRContainers {
-				validateFRRPeeredWithAllNodes(cs, c, pairingFamily)
-			}
-
-			ginkgo.By("checking metrics")
-			controllerPod, err := metallb.ControllerPod(cs)
-			framework.ExpectNoError(err)
-			speakerPods, err := metallb.SpeakerPods(cs)
-			framework.ExpectNoError(err)
-			promPod, err := metrics.PrometheusPod(cs, PrometheusNamespace)
-			framework.ExpectNoError(err)
-
-			var peers []struct {
-				addr     string
-				multihop bool
-			}
-
-			for _, c := range FRRContainers {
-				address := c.Ipv4
-				if pairingFamily == ipfamily.IPv6 {
-					address = c.Ipv6
-				}
-
-				peers = append(peers, struct {
-					addr     string
-					multihop bool
-				}{
-					address,
-					c.NeighborConfig.MultiHop,
-				},
-				)
-			}
-			for _, speaker := range speakerPods {
-				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
-
-				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
-					if err != nil {
-						return err
-					}
-
-					for _, peer := range peers {
-						err = metrics.ValidateGaugeValue(1, "metallb_bfd_session_up", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{peer="%s"} == 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_control_packet_input", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_input{peer="%s"} >= 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_control_packet_output", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_output{peer="%s"} >= 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateGaugeValue(0, "metallb_bfd_session_down_events", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{peer="%s"} == 0`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_session_up_events", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up_events{peer="%s"} >= 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_zebra_notifications", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_zebra_notifications{peer="%s"} >= 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						if bfd.Spec.EchoMode != nil && *bfd.Spec.EchoMode {
-							echoVal := 1
-							if peer.multihop {
-								echoVal = 0
-							}
-							err = metrics.ValidateCounterValue(metrics.GreaterThan(echoVal), "metallb_bfd_echo_packet_input", map[string]string{"peer": peer.addr}, speakerMetrics)
-							if err != nil {
-								return err
-							}
-							err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_input{peer="%s"} >= %d`, peer.addr, echoVal), metrics.There)
-							if err != nil {
-								return err
-							}
-
-							err = metrics.ValidateCounterValue(metrics.GreaterThan(echoVal), "metallb_bfd_echo_packet_output", map[string]string{"peer": peer.addr}, speakerMetrics)
-							if err != nil {
-								return err
-							}
-							err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_output{peer="%s"} >= %d`, peer.addr, echoVal), metrics.There)
-							if err != nil {
-								return err
-							}
-						}
-					}
-					return nil
-				}, 2*time.Minute, 5*time.Second).Should(BeNil())
-			}
-
-			ginkgo.By("disabling BFD in external FRR containers")
-			for _, c := range FRRContainers {
-				err := frrcontainer.PairWithNodes(cs, c, pairingFamily, func(container *frrcontainer.FRR) {
-					container.NeighborConfig.BFDEnabled = false
-				})
-				framework.ExpectNoError(err)
-			}
-
-			ginkgo.By("validating session down metrics")
-			for _, speaker := range speakerPods {
-				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
-
-				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
-					if err != nil {
-						return err
-					}
-
-					for _, peer := range peers {
-						err = metrics.ValidateGaugeValue(0, "metallb_bfd_session_up", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{peer="%s"} == 0`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-
-						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_session_down_events", map[string]string{"peer": peer.addr}, speakerMetrics)
-						if err != nil {
-							return err
-						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{peer="%s"} >= 1`, peer.addr), metrics.There)
-						if err != nil {
-							return err
-						}
-					}
-					return nil
-				}, 2*time.Minute, 5*time.Second).Should(BeNil())
-			}
-		},
-			table.Entry("IPV4 - default",
-				metallbv1beta1.BFDProfile{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "bar",
-					},
-				}, ipfamily.IPv4, []string{v4PoolAddresses}),
-			table.Entry("IPV4 - echo mode enabled",
-				metallbv1beta1.BFDProfile{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "echo",
-					},
-					Spec: metallbv1beta1.BFDProfileSpec{
-						ReceiveInterval:  pointer.Uint32Ptr(80),
-						TransmitInterval: pointer.Uint32Ptr(81),
-						EchoInterval:     pointer.Uint32Ptr(82),
-						EchoMode:         pointer.BoolPtr(true),
-						PassiveMode:      pointer.BoolPtr(false),
-						MinimumTTL:       pointer.Uint32Ptr(254),
-					},
-				}, ipfamily.IPv4, []string{v4PoolAddresses}),
-			table.Entry("IPV6 - default",
-				metallbv1beta1.BFDProfile{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "bar",
-					},
-				}, ipfamily.IPv6, []string{v6PoolAddresses}),
-			table.Entry("IPV6 - echo mode enabled",
-				metallbv1beta1.BFDProfile{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "echo",
-					},
-					Spec: metallbv1beta1.BFDProfileSpec{
-						ReceiveInterval:  pointer.Uint32Ptr(80),
-						TransmitInterval: pointer.Uint32Ptr(81),
-						EchoInterval:     pointer.Uint32Ptr(82),
-						EchoMode:         pointer.BoolPtr(true),
-						PassiveMode:      pointer.BoolPtr(false),
-						MinimumTTL:       pointer.Uint32Ptr(254),
-					},
-				}, ipfamily.IPv6, []string{v6PoolAddresses}),
-			table.Entry("DUALSTACK - full params",
-				metallbv1beta1.BFDProfile{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "full1",
-					},
-					Spec: metallbv1beta1.BFDProfileSpec{
-						ReceiveInterval:  pointer.Uint32Ptr(60),
-						TransmitInterval: pointer.Uint32Ptr(61),
-						EchoInterval:     pointer.Uint32Ptr(62),
-						EchoMode:         pointer.BoolPtr(false),
-						PassiveMode:      pointer.BoolPtr(false),
-						MinimumTTL:       pointer.Uint32Ptr(254),
-					},
-				}, ipfamily.DualStack, []string{v4PoolAddresses, v6PoolAddresses}),
-		)
 	})
 
 	ginkgo.Context("validate configuration changes", func() {
diff --git a/e2etest/bgptests/infra_setup.go b/e2etest/bgptests/infra_setup.go
index 3354c7cf..e9a73465 100644
--- a/e2etest/bgptests/infra_setup.go
+++ b/e2etest/bgptests/infra_setup.go
@@ -172,11 +172,6 @@ func InfraTearDown(cs *clientset.Clientset, containers []*frrcontainer.FRR) erro
 		return err
 	}
 
-	err = vrfTeardown(cs)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
@@ -224,6 +219,12 @@ func vrfSetup(cs *clientset.Clientset) error {
 		if err != nil {
 			return err
 		}
+		// this is required to allow frr to listen for udp connections
+		// in vrfs, in particular to allow bfd to work
+		err = enableL3masterDomains(pod.Spec.NodeName)
+		if err != nil {
+			return err
+		}
 	}
 	return nil
 }
@@ -380,12 +381,14 @@ func vrfContainersConfig() map[string]frrcontainer.Config {
 		Network: vrfNetwork,
 		Neighbor: frrconfig.NeighborConfig{
 			ASN:      metalLBASNVRF,
+			Password: "vrf-test",
 			MultiHop: false,
 		},
 		Router: frrconfig.RouterConfig{
-			ASN:     externalASN,
-			BGPPort: 179,
-			VRF:     vrfName,
+			ASN:      externalASN,
+			Password: "vrf-test",
+			BGPPort:  179,
+			VRF:      vrfName,
 		},
 	}
 	res["ibgp-vrf-single-hop"] = frrcontainer.Config{
@@ -393,12 +396,14 @@ func vrfContainersConfig() map[string]frrcontainer.Config {
 		Network: vrfNetwork,
 		Neighbor: frrconfig.NeighborConfig{
 			ASN:      metalLBASNVRF,
+			Password: "vrf-test",
 			MultiHop: false,
 		},
 		Router: frrconfig.RouterConfig{
-			ASN:     metalLBASNVRF,
-			BGPPort: 179,
-			VRF:     vrfName,
+			ASN:      metalLBASNVRF,
+			BGPPort:  179,
+			Password: "vrf-test",
+			VRF:      vrfName,
 		},
 	}
 
@@ -432,31 +437,6 @@ func multiHopTearDown(cs *clientset.Clientset) error {
 	return nil
 }
 
-func vrfTeardown(cs *clientset.Clientset) error {
-	_, err := executor.Host.Exec(executor.ContainerRuntime, "network", "inspect", vrfNetwork)
-	if err != nil {
-		return nil
-	}
-
-	speakerPods, err := metallb.SpeakerPods(cs)
-	if err != nil {
-		return err
-	}
-
-	for _, pod := range speakerPods {
-		err := removeContainerFromNetwork(pod.Spec.NodeName, vrfNetwork)
-		if err != nil {
-			return err
-		}
-	}
-
-	out, err := executor.Host.Exec(executor.ContainerRuntime, "network", "rm", vrfNetwork)
-	if err != nil {
-		return errors.Wrapf(err, "Failed to remove %s: %s", multiHopNetwork, out)
-	}
-	return nil
-}
-
 // Allow the speaker nodes to reach the multi-hop network containers.
 func addMultiHopToNodes(cs *clientset.Clientset) error {
 	/*
@@ -538,19 +518,12 @@ func addContainerToNetwork(containerName, network string) error {
 	return nil
 }
 
-func removeContainerFromNetwork(containerName, network string) error {
-	networks, err := container.Networks(containerName)
+func enableL3masterDomains(container string) error {
+	nodeExec := executor.ForContainer(container)
+	_, err := nodeExec.Exec("sysctl", "net.ipv4.udp_l3mdev_accept=1")
 	if err != nil {
 		return err
 	}
-	if _, ok := networks[network]; !ok {
-		return nil
-	}
 
-	out, err := executor.Host.Exec(executor.ContainerRuntime, "network", "disconnect",
-		network, containerName)
-	if err != nil {
-		return errors.Wrapf(err, "Failed to disconnect %s from %s: %s", containerName, network, out)
-	}
 	return nil
 }
diff --git a/e2etest/bgptests/metrics.go b/e2etest/bgptests/metrics.go
new file mode 100644
index 00000000..2f819b99
--- /dev/null
+++ b/e2etest/bgptests/metrics.go
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package bgptests
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
+	frrconfig "go.universe.tf/metallb/e2etest/pkg/frr/config"
+	frrcontainer "go.universe.tf/metallb/e2etest/pkg/frr/container"
+	"go.universe.tf/metallb/e2etest/pkg/k8s"
+	"go.universe.tf/metallb/e2etest/pkg/metallb"
+	"go.universe.tf/metallb/e2etest/pkg/metrics"
+	testservice "go.universe.tf/metallb/e2etest/pkg/service"
+	metallbconfig "go.universe.tf/metallb/internal/config"
+	"go.universe.tf/metallb/internal/ipfamily"
+	"go.universe.tf/metallb/internal/pointer"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientset "k8s.io/client-go/kubernetes"
+	"k8s.io/kubernetes/test/e2e/framework"
+	admissionapi "k8s.io/pod-security-admission/api"
+)
+
+var _ = ginkgo.Describe("BGP metrics", func() {
+	var cs clientset.Interface
+	var f *framework.Framework
+
+	emptyBGPAdvertisement := metallbv1beta1.BGPAdvertisement{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "empty",
+		},
+	}
+
+	ginkgo.AfterEach(func() {
+		if ginkgo.CurrentGinkgoTestDescription().Failed {
+			dumpBGPInfo(ReportPath, ginkgo.CurrentGinkgoTestDescription().TestText, cs, f)
+			k8s.DumpInfo(Reporter, ginkgo.CurrentGinkgoTestDescription().TestText)
+		}
+	})
+
+	ginkgo.BeforeEach(func() {
+		ginkgo.By("Clearing any previous configuration")
+
+		err := ConfigUpdater.Clean()
+		framework.ExpectNoError(err)
+
+		for _, c := range FRRContainers {
+			err := c.UpdateBGPConfigFile(frrconfig.Empty)
+			framework.ExpectNoError(err)
+		}
+	})
+
+	f = framework.NewDefaultFramework("bgp")
+	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged
+
+	ginkgo.BeforeEach(func() {
+		cs = f.ClientSet
+		var frrContainersForAdvertisement []*frrcontainer.FRR
+		for _, c := range FRRContainers {
+			// Connectivity between a multi hop FRR container to a BGP peer is going through
+			// the single hop container.
+			// The containers chosen here are the ones a service IP is advertised to.
+			// Since the single hop container might not be familiar with the service IP
+			// (if it wasn't chosen for the advertisement), the connectivity check will fail.
+			if !strings.Contains(c.Name, "multi") {
+				frrContainersForAdvertisement = append(frrContainersForAdvertisement, c)
+			}
+		}
+
+		if len(frrContainersForAdvertisement) < 2 {
+			ginkgo.Skip("This test requires 2 external frr containers")
+		}
+
+	})
+
+	ginkgo.Context("metrics", func() {
+		var controllerPod *corev1.Pod
+		var speakerPods []*corev1.Pod
+		var promPod *corev1.Pod
+
+		ginkgo.BeforeEach(func() {
+			var err error
+			controllerPod, err = metallb.ControllerPod(cs)
+			framework.ExpectNoError(err)
+			speakerPods, err = metallb.SpeakerPods(cs)
+			framework.ExpectNoError(err)
+			promPod, err = metrics.PrometheusPod(cs, PrometheusNamespace)
+			framework.ExpectNoError(err)
+		})
+
+		table.DescribeTable("should collect BGP metrics in FRR mode", func(ipFamily ipfamily.Family, poolAddress string, addressTotal int) {
+			poolName := "bgp-test"
+
+			resources := metallbconfig.ClusterResources{
+				Pools: []metallbv1beta1.IPAddressPool{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: poolName,
+						},
+						Spec: metallbv1beta1.IPAddressPoolSpec{
+							Addresses: []string{poolAddress},
+						},
+					},
+				},
+				Peers:   metallb.PeersForContainers(FRRContainers, ipFamily),
+				BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
+			}
+
+			for _, c := range FRRContainers {
+				err := frrcontainer.PairWithNodes(cs, c, ipFamily)
+				framework.ExpectNoError(err)
+			}
+
+			err := ConfigUpdater.Update(resources)
+			framework.ExpectNoError(err)
+
+			for _, c := range FRRContainers {
+				validateFRRPeeredWithAllNodes(cs, c, ipFamily)
+			}
+
+			ginkgo.By("creating a service")
+			svc, _ := testservice.CreateWithBackend(cs, f.Namespace.Name, "external-local-lb", testservice.TrafficPolicyCluster) // Is a sleep required here?
+			defer testservice.Delete(cs, svc)
+
+			selectors := labelsForPeers(FRRContainers, ipFamily)
+
+			for _, speaker := range speakerPods {
+				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
+				Eventually(func() error {
+					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					if err != nil {
+						return err
+					}
+					for _, selector := range selectors {
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_opens_sent", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_opens_received", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bgp_updates_total_received", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_keepalives_sent", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_keepalives_received", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_route_refresh_sent", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_route_refresh_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_total_sent", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_bgp_total_received", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+					}
+					return nil
+				}, 2*time.Minute, 1*time.Second).Should(BeNil())
+			}
+		},
+			table.Entry("IPV4 - Checking service", ipfamily.IPv4, v4PoolAddresses, 256),
+			table.Entry("IPV6 - Checking service", ipfamily.IPv6, v6PoolAddresses, 16),
+		)
+
+		table.DescribeTable("should be exposed by the controller", func(ipFamily ipfamily.Family, poolAddress string, addressTotal int) {
+			poolName := "bgp-test"
+
+			peerAddrToName := make(map[string]string)
+			for _, c := range FRRContainers {
+				address := c.Ipv4
+				if ipFamily == ipfamily.IPv6 {
+					address = c.Ipv6
+				}
+				peerAddr := address + fmt.Sprintf(":%d", c.RouterConfig.BGPPort)
+				peerAddrToName[peerAddr] = c.Name
+			}
+
+			resources := metallbconfig.ClusterResources{
+				Pools: []metallbv1beta1.IPAddressPool{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: poolName,
+						},
+						Spec: metallbv1beta1.IPAddressPoolSpec{
+							Addresses: []string{poolAddress},
+						},
+					},
+				},
+				Peers:   metallb.PeersForContainers(FRRContainers, ipFamily),
+				BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
+			}
+
+			for _, c := range FRRContainers {
+				err := frrcontainer.PairWithNodes(cs, c, ipFamily)
+				framework.ExpectNoError(err)
+			}
+
+			err := ConfigUpdater.Update(resources)
+			framework.ExpectNoError(err)
+
+			for _, c := range FRRContainers {
+				validateFRRPeeredWithAllNodes(cs, c, ipFamily)
+			}
+
+			ginkgo.By("checking the metrics when no service is added")
+			Eventually(func() error {
+				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateGaugeValue(0, "metallb_allocator_addresses_in_use_total", map[string]string{"pool": poolName}, controllerMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_in_use_total{pool="%s"} == 0`, poolName), metrics.There)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateGaugeValue(addressTotal, "metallb_allocator_addresses_total", map[string]string{"pool": poolName}, controllerMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_total{pool="%s"} == %d`, poolName, addressTotal), metrics.There)
+				if err != nil {
+					return err
+				}
+				return nil
+			}, 2*time.Minute, 1*time.Second).Should(BeNil())
+
+			selectors := labelsForPeers(FRRContainers, ipFamily)
+
+			for _, speaker := range speakerPods {
+				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
+
+				Eventually(func() error {
+					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					if err != nil {
+						return err
+					}
+					for _, selector := range selectors {
+						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{%s} == 1`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateGaugeValue(0, "metallb_bgp_announced_prefixes_total", selector.labelsBGP, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{%s} == 0`, selector.labelsForQueryBGP), metrics.There)
+						if err != nil {
+							return err
+						}
+					}
+					return nil
+				}, 2*time.Minute, 1*time.Second).Should(BeNil())
+			}
+
+			ginkgo.By("creating a service")
+			svc, _ := testservice.CreateWithBackend(cs, f.Namespace.Name, "external-local-lb", testservice.TrafficPolicyCluster) // Is a sleep required here?
+			defer testservice.Delete(cs, svc)
+
+			ginkgo.By("checking the metrics when a service is added")
+			Eventually(func() error {
+				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateGaugeValue(1, "metallb_allocator_addresses_in_use_total", map[string]string{"pool": poolName}, controllerMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_allocator_addresses_in_use_total{pool="%s"} == 1`, poolName), metrics.There)
+				if err != nil {
+					return err
+				}
+				return nil
+			}, 2*time.Minute, 1*time.Second).Should(BeNil())
+
+			for _, speaker := range speakerPods {
+				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
+
+				Eventually(func() error {
+					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					if err != nil {
+						return err
+					}
+					for addr := range peerAddrToName {
+						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", map[string]string{"peer": addr}, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{peer="%s"} == 1`, addr), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateGaugeValue(1, "metallb_bgp_announced_prefixes_total", map[string]string{"peer": addr}, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{peer="%s"} == 1`, addr), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bgp_updates_total", map[string]string{"peer": addr}, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total{peer="%s"} >= 1`, addr), metrics.There)
+						if err != nil {
+							return err
+						}
+					}
+
+					err = metrics.ValidateGaugeValue(1, "metallb_speaker_announced", map[string]string{"node": speaker.Spec.NodeName, "protocol": "bgp", "service": fmt.Sprintf("%s/%s", f.Namespace.Name, svc.Name)}, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					return nil
+				}, 2*time.Minute, 1*time.Second).Should(BeNil())
+			}
+		},
+			table.Entry("IPV4 - Checking service", ipfamily.IPv4, v4PoolAddresses, 256),
+			table.Entry("IPV6 - Checking service", ipfamily.IPv6, v6PoolAddresses, 16))
+	})
+
+	table.DescribeTable("BFD metrics from FRR", func(bfd metallbv1beta1.BFDProfile, pairingFamily ipfamily.Family, poolAddresses []string) {
+		resources := metallbconfig.ClusterResources{
+			Pools: []metallbv1beta1.IPAddressPool{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bfd-test",
+					},
+					Spec: metallbv1beta1.IPAddressPoolSpec{
+						Addresses: poolAddresses,
+					},
+				},
+			},
+			Peers:       metallb.WithBFD(metallb.PeersForContainers(FRRContainers, pairingFamily), bfd.Name),
+			BGPAdvs:     []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
+			BFDProfiles: []metallbv1beta1.BFDProfile{bfd},
+		}
+
+		err := ConfigUpdater.Update(resources)
+		framework.ExpectNoError(err)
+
+		for _, c := range FRRContainers {
+			err := frrcontainer.PairWithNodes(cs, c, pairingFamily, func(container *frrcontainer.FRR) {
+				container.NeighborConfig.BFDEnabled = true
+			})
+			framework.ExpectNoError(err)
+		}
+
+		for _, c := range FRRContainers {
+			validateFRRPeeredWithAllNodes(cs, c, pairingFamily)
+		}
+
+		ginkgo.By("checking metrics")
+		controllerPod, err := metallb.ControllerPod(cs)
+		framework.ExpectNoError(err)
+		speakerPods, err := metallb.SpeakerPods(cs)
+		framework.ExpectNoError(err)
+		promPod, err := metrics.PrometheusPod(cs, PrometheusNamespace)
+		framework.ExpectNoError(err)
+
+		selectors := labelsForPeers(FRRContainers, pairingFamily)
+
+		for _, speaker := range speakerPods {
+			ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
+
+			Eventually(func() error {
+				speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+				if err != nil {
+					return err
+				}
+
+				for _, selector := range selectors {
+					err = metrics.ValidateGaugeValue(1, "metallb_bfd_session_up", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{%s} == 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_control_packet_input", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_input{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_control_packet_output", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_output{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateGaugeValue(0, "metallb_bfd_session_down_events", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{%s} == 0`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_session_up_events", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up_events{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_zebra_notifications", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_zebra_notifications{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					if bfd.Spec.EchoMode != nil && *bfd.Spec.EchoMode {
+						echoVal := 1
+						if selector.noEcho {
+							echoVal = 0
+						}
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(echoVal), "metallb_bfd_echo_packet_input", selector.labelsBFD, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_input{%s} >= %d`, selector.labelsForQueryBFD, echoVal), metrics.There)
+						if err != nil {
+							return err
+						}
+
+						err = metrics.ValidateCounterValue(metrics.GreaterThan(echoVal), "metallb_bfd_echo_packet_output", selector.labelsBFD, speakerMetrics)
+						if err != nil {
+							return err
+						}
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_output{%s} >= %d`, selector.labelsForQueryBFD, echoVal), metrics.There)
+						if err != nil {
+							return err
+						}
+					}
+				}
+				return nil
+			}, time.Minute, 5*time.Second).Should(BeNil())
+		}
+
+		ginkgo.By("disabling BFD in external FRR containers")
+		for _, c := range FRRContainers {
+			err := frrcontainer.PairWithNodes(cs, c, pairingFamily, func(container *frrcontainer.FRR) {
+				container.NeighborConfig.BFDEnabled = false
+			})
+			framework.ExpectNoError(err)
+		}
+
+		ginkgo.By("validating session down metrics")
+		for _, speaker := range speakerPods {
+			ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
+
+			Eventually(func() error {
+				speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+				if err != nil {
+					return err
+				}
+
+				for _, selector := range selectors {
+					err = metrics.ValidateGaugeValue(0, "metallb_bfd_session_up", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{%s} == 0`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+
+					err = metrics.ValidateCounterValue(metrics.GreaterThan(1), "metallb_bfd_session_down_events", selector.labelsBFD, speakerMetrics)
+					if err != nil {
+						return err
+					}
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					if err != nil {
+						return err
+					}
+				}
+				return nil
+			}, 2*time.Minute, 5*time.Second).Should(BeNil())
+		}
+	},
+		table.Entry("IPV4 - default",
+			metallbv1beta1.BFDProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "bar",
+				},
+			}, ipfamily.IPv4, []string{v4PoolAddresses}),
+		table.Entry("IPV4 - echo mode enabled",
+			metallbv1beta1.BFDProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "echo",
+				},
+				Spec: metallbv1beta1.BFDProfileSpec{
+					ReceiveInterval:  pointer.Uint32Ptr(80),
+					TransmitInterval: pointer.Uint32Ptr(81),
+					EchoInterval:     pointer.Uint32Ptr(82),
+					EchoMode:         pointer.BoolPtr(true),
+					PassiveMode:      pointer.BoolPtr(false),
+					MinimumTTL:       pointer.Uint32Ptr(254),
+				},
+			}, ipfamily.IPv4, []string{v4PoolAddresses}),
+		table.Entry("IPV6 - default",
+			metallbv1beta1.BFDProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "bar",
+				},
+			}, ipfamily.IPv6, []string{v6PoolAddresses}),
+		table.Entry("IPV6 - echo mode enabled",
+			metallbv1beta1.BFDProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "echo",
+				},
+				Spec: metallbv1beta1.BFDProfileSpec{
+					ReceiveInterval:  pointer.Uint32Ptr(80),
+					TransmitInterval: pointer.Uint32Ptr(81),
+					EchoInterval:     pointer.Uint32Ptr(82),
+					EchoMode:         pointer.BoolPtr(true),
+					PassiveMode:      pointer.BoolPtr(false),
+					MinimumTTL:       pointer.Uint32Ptr(254),
+				},
+			}, ipfamily.IPv6, []string{v6PoolAddresses}),
+		table.Entry("DUALSTACK - full params",
+			metallbv1beta1.BFDProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "full1",
+				},
+				Spec: metallbv1beta1.BFDProfileSpec{
+					ReceiveInterval:  pointer.Uint32Ptr(60),
+					TransmitInterval: pointer.Uint32Ptr(61),
+					EchoInterval:     pointer.Uint32Ptr(62),
+					EchoMode:         pointer.BoolPtr(false),
+					PassiveMode:      pointer.BoolPtr(false),
+					MinimumTTL:       pointer.Uint32Ptr(254),
+				},
+			}, ipfamily.DualStack, []string{v4PoolAddresses, v6PoolAddresses}),
+	)
+
+	ginkgo.It("FRR metrics related to config should be exposed", func() {
+		controllerPod, err := metallb.ControllerPod(cs)
+		framework.ExpectNoError(err)
+
+		speakers, err := metallb.SpeakerPods(cs)
+		framework.ExpectNoError(err)
+		allPods := append(speakers, controllerPod)
+
+		bfdProfile := metallbv1beta1.BFDProfile{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "bfd",
+			},
+		}
+
+		ginkgo.By("Creating an invalid configuration")
+
+		resources := metallbconfig.ClusterResources{
+			Pools: []metallbv1beta1.IPAddressPool{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "metrics-test",
+					},
+					Spec: metallbv1beta1.IPAddressPoolSpec{
+						Addresses: []string{v4PoolAddresses},
+					},
+				},
+			},
+			Peers:   metallb.WithBFD(metallb.PeersForContainers(FRRContainers, ipfamily.IPv4), "bfd"),
+			BGPAdvs: []metallbv1beta1.BGPAdvertisement{emptyBGPAdvertisement},
+		}
+		err = ConfigUpdater.Update(resources)
+		framework.ExpectNoError(err)
+
+		promPod, err := metrics.PrometheusPod(cs, PrometheusNamespace)
+		framework.ExpectNoError(err)
+
+		ginkgo.By("Checking the config stale metric on the speakers")
+		for _, pod := range speakers {
+			ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
+			Eventually(func() error {
+				podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
+				framework.ExpectNoError(err)
+				err = metrics.ValidateGaugeValue(1, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_stale_bool{pod="%s"} == 1`, pod.Name), metrics.There)
+				if err != nil {
+					return err
+				}
+				return nil
+			}, time.Minute, 1*time.Second).ShouldNot(HaveOccurred(), "on pod", pod.Name)
+		}
+
+		resources.BFDProfiles = []metallbv1beta1.BFDProfile{bfdProfile}
+		err = ConfigUpdater.Update(resources)
+		framework.ExpectNoError(err)
+		for _, pod := range allPods {
+			ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
+			Eventually(func() error {
+				podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
+				framework.ExpectNoError(err)
+				err = metrics.ValidateGaugeValue(0, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_stale_bool{pod="%s"} == 0`, pod.Name), metrics.There)
+				if err != nil {
+					return err
+				}
+				// we don't know how many events we are processing
+				err = metrics.ValidateCounterValue(metrics.GreaterThan(0), "metallb_k8s_client_updates_total", map[string]string{}, podMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_updates_total{pod="%s"} > 0`, pod.Name), metrics.There)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateGaugeValue(1, "metallb_k8s_client_config_loaded_bool", map[string]string{}, podMetrics)
+				if err != nil {
+					return err
+				}
+				err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_k8s_client_config_loaded_bool{pod="%s"} == 1`, pod.Name), metrics.There)
+				if err != nil {
+					return err
+				}
+				return nil
+			}, time.Minute, 5*time.Second).ShouldNot(HaveOccurred(), "on pod", pod.Name)
+		}
+	})
+})
+
+type peerPrometheus struct {
+	labelsForQueryBGP string
+	labelsBGP         map[string]string
+	labelsForQueryBFD string
+	labelsBFD         map[string]string
+	noEcho            bool
+}
+
+func labelsForPeers(peers []*frrcontainer.FRR, ipFamily ipfamily.Family) []peerPrometheus {
+	res := make([]peerPrometheus, 0)
+	for _, c := range peers {
+		address := c.Ipv4
+		if ipFamily == ipfamily.IPv6 {
+			address = c.Ipv6
+		}
+		peerAddr := address + fmt.Sprintf(":%d", c.RouterConfig.BGPPort)
+
+		// Note: we deliberately don't add the vrf label in case of the default vrf to validate that
+		// it is still possible to list the metrics using only the peer label, which is what most users
+		// who don't care about vrfs should do.
+		labelsBGP := map[string]string{"peer": peerAddr}
+		labelsForQueryBGP := fmt.Sprintf(`peer="%s"`, peerAddr)
+		labelsBFD := map[string]string{"peer": address}
+		labelsForQueryBFD := fmt.Sprintf(`peer="%s"`, address)
+
+		noEcho := c.NeighborConfig.MultiHop
+		if c.RouterConfig.VRF != "" {
+			labelsBGP["vrf"] = c.RouterConfig.VRF
+			labelsForQueryBGP = fmt.Sprintf(`peer="%s",vrf="%s"`, peerAddr, c.RouterConfig.VRF)
+			labelsBFD["vrf"] = c.RouterConfig.VRF
+			labelsForQueryBFD = fmt.Sprintf(`peer="%s",vrf="%s"`, address, c.RouterConfig.VRF)
+			noEcho = true // TODO: Need to understand if echo not working across VRFs is expected. If it is, set a webhook to prevent it.
+		}
+		res = append(res, peerPrometheus{
+			labelsBGP:         labelsBGP,
+			labelsForQueryBGP: labelsForQueryBGP,
+			labelsBFD:         labelsBFD,
+			labelsForQueryBFD: labelsForQueryBFD,
+			noEcho:            noEcho,
+		})
+	}
+	return res
+}
diff --git a/e2etest/bgptests/validate.go b/e2etest/bgptests/validate.go
index 9eb83b04..ef66a3fd 100644
--- a/e2etest/bgptests/validate.go
+++ b/e2etest/bgptests/validate.go
@@ -53,7 +53,10 @@ func validateFRRPeeredWithNodes(cs clientset.Interface, nodes []corev1.Node, c *
 		neighbors, err := frr.NeighborsInfo(c)
 		framework.ExpectNoError(err)
 		err = frr.NeighborsMatchNodes(nodes, neighbors, ipFamily, c.RouterConfig.VRF)
-		return err
+		if err != nil {
+			return fmt.Errorf("failed to match neighbors for %s, %w", c.Name, err)
+		}
+		return nil
 	}, 4*time.Minute, 1*time.Second).Should(BeNil())
 }
 
@@ -129,7 +132,11 @@ func frrIsPairedOnPods(cs clientset.Interface, n *frrcontainer.FRR, ipFamily ipf
 	Eventually(func() error {
 		addresses := n.AddressesForFamily(ipFamily)
 		for _, address := range addresses {
-			toParse, err := podExecutor.Exec("vtysh", "-c", fmt.Sprintf("show bgp neighbor %s json", address))
+			vrfSelector := ""
+			if n.RouterConfig.VRF != "" {
+				vrfSelector = fmt.Sprintf("vrf %s", n.RouterConfig.VRF)
+			}
+			toParse, err := podExecutor.Exec("vtysh", "-c", fmt.Sprintf("show bgp %s neighbor %s json", vrfSelector, address))
 			if err != nil {
 				return err
 			}
diff --git a/e2etest/bgptests/vrf.go b/e2etest/bgptests/vrf.go
deleted file mode 100644
index 576f3ed1..00000000
--- a/e2etest/bgptests/vrf.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// SPDX-License-Identifier:Apache-2.0
-
-package bgptests
-
-import (
-	"context"
-
-	"github.com/onsi/ginkgo"
-	"github.com/onsi/ginkgo/extensions/table"
-	frrconfig "go.universe.tf/metallb/e2etest/pkg/frr/config"
-	frrcontainer "go.universe.tf/metallb/e2etest/pkg/frr/container"
-	"go.universe.tf/metallb/e2etest/pkg/k8s"
-	testservice "go.universe.tf/metallb/e2etest/pkg/service"
-	"go.universe.tf/metallb/internal/ipfamily"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	clientset "k8s.io/client-go/kubernetes"
-	"k8s.io/kubernetes/test/e2e/framework"
-	admissionapi "k8s.io/pod-security-admission/api"
-)
-
-var _ = ginkgo.Describe("VRF", func() {
-	var cs clientset.Interface
-	var f *framework.Framework
-	var containers []*frrcontainer.FRR
-
-	ginkgo.AfterEach(func() {
-		if ginkgo.CurrentGinkgoTestDescription().Failed {
-			dumpBGPInfo(ReportPath, ginkgo.CurrentGinkgoTestDescription().TestText, cs, f)
-			k8s.DumpInfo(Reporter, ginkgo.CurrentGinkgoTestDescription().TestText)
-		}
-	})
-
-	ginkgo.BeforeEach(func() {
-		ginkgo.By("Clearing any previous configuration")
-
-		err := ConfigUpdater.Clean()
-		framework.ExpectNoError(err)
-
-		containers = append(VRFFRRContainers, FRRContainers...)
-		for _, c := range containers {
-			err := c.UpdateBGPConfigFile(frrconfig.Empty)
-			framework.ExpectNoError(err)
-		}
-	})
-
-	f = framework.NewDefaultFramework("bgp-vrf")
-	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged
-
-	ginkgo.BeforeEach(func() {
-		cs = f.ClientSet
-	})
-
-	table.DescribeTable("A service of protocol load balancer should work with ETP=cluster", func(pairingIPFamily ipfamily.Family, poolAddresses []string, tweak testservice.Tweak) {
-		_, svc := setupBGPService(f, pairingIPFamily, poolAddresses, containers, func(svc *corev1.Service) {
-			testservice.TrafficPolicyCluster(svc)
-			tweak(svc)
-		})
-		defer testservice.Delete(cs, svc)
-
-		allNodes, err := cs.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
-		framework.ExpectNoError(err)
-		validateDesiredLB(svc)
-
-		for _, c := range containers {
-			validateService(cs, svc, allNodes.Items, c)
-		}
-	},
-		table.FEntry("IPV4", ipfamily.IPv4, []string{v4PoolAddresses}, func(_ *corev1.Service) {}),
-		table.Entry("IPV6", ipfamily.IPv6, []string{v6PoolAddresses}, func(_ *corev1.Service) {}),
-		table.Entry("DUALSTACK", ipfamily.DualStack, []string{v4PoolAddresses, v6PoolAddresses},
-			func(svc *corev1.Service) {
-				testservice.DualStack(svc)
-			}),
-		table.Entry("IPV4 - request IPv4 via custom annotation", ipfamily.IPv4, []string{v4PoolAddresses},
-			func(svc *corev1.Service) {
-				testservice.WithSpecificIPs(svc, "192.168.10.100")
-			}),
-		table.Entry("DUALSTACK - request Dual Stack via custom annotation", ipfamily.DualStack, []string{v4PoolAddresses, v6PoolAddresses},
-			func(svc *corev1.Service) {
-				testservice.DualStack(svc)
-				testservice.WithSpecificIPs(svc, "192.168.10.100", "fc00:f853:ccd:e799::")
-			}),
-	)
-},
-)
diff --git a/e2etest/e2etest_suite_test.go b/e2etest/e2etest_suite_test.go
index 1e89a70a..7812e4b6 100644
--- a/e2etest/e2etest_suite_test.go
+++ b/e2etest/e2etest_suite_test.go
@@ -60,6 +60,7 @@ var (
 	localNics           string
 	externalContainers  string
 	runOnHost           bool
+	bgpNativeMode       bool
 )
 
 // handleFlags sets up all flags and parses the command line.
@@ -88,6 +89,8 @@ func handleFlags() {
 	flag.StringVar(&reportPath, "report-path", "/tmp/report", "the path to be used to dump test failure information")
 	flag.StringVar(&prometheusNamespace, "prometheus-namespace", "monitoring", "the namespace prometheus is running in (if running)")
 	flag.StringVar(&externalContainers, "external-containers", "", "a comma separated list of external containers names to use for the test. (valid parameters are: ibgp-single-hop / ibgp-multi-hop / ebgp-single-hop / ebgp-multi-hop)")
+	flag.BoolVar(&bgpNativeMode, "bgp-native-mode", false, "says if we are testing against a deployment using bgp native mode")
+
 	flag.Parse()
 
 	if _, res := os.LookupEnv("RUN_FRR_CONTAINER_ON_HOST_NETWORK"); res {
@@ -153,8 +156,11 @@ var _ = ginkgo.BeforeSuite(func() {
 	default:
 		bgptests.FRRContainers, err = bgptests.KindnetContainersSetup(v4Addresses, v6Addresses, cs)
 		framework.ExpectNoError(err)
-		bgptests.VRFFRRContainers, err = bgptests.VRFContainersSetup(cs)
-		framework.ExpectNoError(err)
+		if !bgpNativeMode {
+			vrfFRRContainers, err := bgptests.VRFContainersSetup(cs)
+			framework.ExpectNoError(err)
+			bgptests.FRRContainers = append(bgptests.FRRContainers, vrfFRRContainers...)
+		}
 	}
 
 	clientconfig, err := framework.LoadConfig()
diff --git a/e2etest/pkg/frr/bfd.go b/e2etest/pkg/frr/bfd.go
index dfa50628..a62decd2 100644
--- a/e2etest/pkg/frr/bfd.go
+++ b/e2etest/pkg/frr/bfd.go
@@ -11,13 +11,19 @@ import (
 // BFDPeers returns informations for the all the bfd peers in the given
 // executor.
 func BFDPeers(exec executor.Executor) (map[string]bgpfrr.BFDPeer, error) {
-	res, err := exec.Exec("vtysh", "-c", "show bfd peers json")
+	json, err := exec.Exec("vtysh", "-c", "show bfd peers json")
 	if err != nil {
 		return nil, errors.Wrapf(err, "Failed to query neighbours")
 	}
-	peers, err := bgpfrr.ParseBFDPeers(res)
+	peers, err := bgpfrr.ParseBFDPeers(json)
 	if err != nil {
-		return nil, errors.Wrapf(err, "Failed to parse neighbours %s", res)
+		return nil, errors.Wrapf(err, "Failed to parse neighbours %s", json)
 	}
-	return peers, nil
+	// making a map out of the slice so it can
+	// accessed by peer
+	res := make(map[string]bgpfrr.BFDPeer)
+	for _, p := range peers {
+		res[p.Peer] = p
+	}
+	return res, nil
 }
diff --git a/e2etest/pkg/metallb/config.go b/e2etest/pkg/metallb/config.go
index 70601286..8ab67c3b 100644
--- a/e2etest/pkg/metallb/config.go
+++ b/e2etest/pkg/metallb/config.go
@@ -83,7 +83,7 @@ func WithRouterID(peers []metallbv1beta2.BGPPeer, routerID string) []metallbv1be
 func BGPPeerSecretReferences(containers []*frrcontainer.FRR) map[string]corev1.Secret {
 	secretMap := make(map[string]corev1.Secret)
 	for _, c := range containers {
-		name := GetBGPPeerSecretName(c.RouterConfig.ASN, c.RouterConfig.BGPPort)
+		name := GetBGPPeerSecretName(c.RouterConfig.ASN, c.RouterConfig.BGPPort, c.RouterConfig.VRF)
 		secretMap[name] = corev1.Secret{
 			ObjectMeta: metav1.ObjectMeta{
 				Name: name,
@@ -95,6 +95,9 @@ func BGPPeerSecretReferences(containers []*frrcontainer.FRR) map[string]corev1.S
 	return secretMap
 }
 
-func GetBGPPeerSecretName(asn uint32, port uint16) string {
-	return fmt.Sprintf("bgppeer-%d-%d-secret", asn, port)
+func GetBGPPeerSecretName(asn uint32, port uint16, vrf string) string {
+	if vrf != "" {
+		return fmt.Sprintf("%d-%d-%s-secret", asn, port, vrf)
+	}
+	return fmt.Sprintf("%d-%d-secret", asn, port)
 }
diff --git a/frr-tools/metrics/collector/bfd_test.go b/frr-tools/metrics/collector/bfd_test.go
index 46a2e0f2..9c959ad7 100644
--- a/frr-tools/metrics/collector/bfd_test.go
+++ b/frr-tools/metrics/collector/bfd_test.go
@@ -15,28 +15,28 @@ var (
 	bfdMetricsTmpl = `
 	# HELP metallb_bfd_session_up BFD session state (1 is up, 0 is down)
 	# TYPE metallb_bfd_session_up gauge
-	metallb_bfd_session_up{peer="{{ .Peer }}"} {{ .SessionUp }}
+	metallb_bfd_session_up{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .SessionUp }}
 	# HELP metallb_bfd_control_packet_input Number of received BFD control packets
 	# TYPE metallb_bfd_control_packet_input counter
-	metallb_bfd_control_packet_input{peer="{{ .Peer }}"} {{ .ControlPacketInput }}
+	metallb_bfd_control_packet_input{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .ControlPacketInput }}
 	# HELP metallb_bfd_control_packet_output Number of sent BFD control packets
 	# TYPE metallb_bfd_control_packet_output counter
-	metallb_bfd_control_packet_output{peer="{{ .Peer }}"} {{ .ControlPacketOutput }}
+	metallb_bfd_control_packet_output{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .ControlPacketOutput }}
 	# HELP metallb_bfd_echo_packet_input Number of received BFD echo packets
 	# TYPE metallb_bfd_echo_packet_input counter
-	metallb_bfd_echo_packet_input{peer="{{ .Peer }}"} {{ .EchoPacketInput }}
+	metallb_bfd_echo_packet_input{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .EchoPacketInput }}
 	# HELP metallb_bfd_echo_packet_output Number of sent BFD echo packets
 	# TYPE metallb_bfd_echo_packet_output counter
-	metallb_bfd_echo_packet_output{peer="{{ .Peer }}"} {{ .EchoPacketOutput }}
+	metallb_bfd_echo_packet_output{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .EchoPacketOutput }}
 	# HELP metallb_bfd_session_down_events Number of BFD session down events
 	# TYPE metallb_bfd_session_down_events counter
-	metallb_bfd_session_down_events{peer="{{ .Peer }}"} {{ .SessionDownEvents }}
+	metallb_bfd_session_down_events{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .SessionDownEvents }}
 	# HELP metallb_bfd_session_up_events Number of BFD session up events
 	# TYPE metallb_bfd_session_up_events counter
-	metallb_bfd_session_up_events{peer="{{ .Peer }}"} {{ .SessionUpEvents }}
+	metallb_bfd_session_up_events{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .SessionUpEvents }}
 	# HELP metallb_bfd_zebra_notifications Number of BFD zebra notifications
 	# TYPE metallb_bfd_zebra_notifications counter
-	metallb_bfd_zebra_notifications{peer="{{ .Peer }}"} {{ .ZebraNotifications }}
+	metallb_bfd_zebra_notifications{peer="{{ .Peer }}", vrf="{{ .NeighborVRF }}"} {{ .ZebraNotifications }}
 	`
 
 	bfdTests = []struct {
@@ -44,6 +44,7 @@ var (
 		vtyshPeersOutput         string
 		vtyshPeersCountersOutput string
 		peer                     string
+		vrf                      string
 		sessionUp                int
 		controlPacketInput       int
 		controlPacketOutput      int
@@ -58,6 +59,7 @@ var (
 			vtyshPeersOutput:         peersIPv4,
 			vtyshPeersCountersOutput: peersCountersIPv4,
 			peer:                     "172.18.0.4",
+			vrf:                      "default",
 			sessionUp:                1,
 			controlPacketInput:       5,
 			controlPacketOutput:      5,
@@ -72,6 +74,7 @@ var (
 			vtyshPeersOutput:         peersIPv6,
 			vtyshPeersCountersOutput: peersCountersIPv6,
 			peer:                     "fc00:f853:ccd:e793::4",
+			vrf:                      "default",
 			sessionUp:                0,
 			controlPacketInput:       10,
 			controlPacketOutput:      10,
@@ -189,6 +192,7 @@ func TestBFDCollect(t *testing.T) {
 				"SessionUpEvents":     test.sessionUpEvents,
 				"SessionDownEvents":   test.sessionDownEvents,
 				"ZebraNotifications":  test.zebraNotifications,
+				"NeighborVRF":         "default",
 			})
 
 			if err != nil {
@@ -197,11 +201,19 @@ func TestBFDCollect(t *testing.T) {
 
 			l := log.NewNopLogger()
 			collector := NewBFD(l)
+			cmdOutput := map[string]string{
+				"show bgp vrf all json":                    vrfVtysh,
+				"show bfd vrf default peers json":          test.vtyshPeersOutput,
+				"show bfd vrf red peers json":              "[]",
+				"show bfd vrf default peers counters json": test.vtyshPeersCountersOutput,
+				"show bfd vrf red peers counters json":     "[]",
+			}
 			collector.frrCli = func(args string) (string, error) {
-				if args == "show bfd peers json" {
-					return test.vtyshPeersOutput, nil
+				res, ok := cmdOutput[args]
+				if !ok {
+					return "{}", nil
 				}
-				return test.vtyshPeersCountersOutput, nil
+				return res, nil
 			}
 			buf := bytes.NewReader(w.Bytes())
 			err = testutil.CollectAndCompare(collector, buf)
diff --git a/frr-tools/metrics/collector/bgp_test.go b/frr-tools/metrics/collector/bgp_test.go
index bccce7ee..35557a3f 100644
--- a/frr-tools/metrics/collector/bgp_test.go
+++ b/frr-tools/metrics/collector/bgp_test.go
@@ -15,46 +15,47 @@ var (
 	metricsTmpl = `
 	# HELP metallb_bgp_announced_prefixes_total Number of prefixes currently being advertised on the BGP session
 	# TYPE metallb_bgp_announced_prefixes_total gauge
-	metallb_bgp_announced_prefixes_total{peer="{{ .NeighborIP }}"} {{ .AnnouncedPrefixes }}
+	metallb_bgp_announced_prefixes_total{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .AnnouncedPrefixes }}
 	# HELP metallb_bgp_keepalives_received Number of BGP keepalive messages received
 	# TYPE metallb_bgp_keepalives_received counter
-	metallb_bgp_keepalives_received{peer="{{ .NeighborIP }}"} {{ .KeepalivesReceived }}
+	metallb_bgp_keepalives_received{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .KeepalivesReceived }}
 	# HELP metallb_bgp_keepalives_sent Number of BGP keepalive messages sent
 	# TYPE metallb_bgp_keepalives_sent counter
-	metallb_bgp_keepalives_sent{peer="{{ .NeighborIP }}"} {{ .KeepalivesSent }}
+	metallb_bgp_keepalives_sent{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .KeepalivesSent }}
 	# HELP metallb_bgp_notifications_sent Number of BGP notification messages sent
 	# TYPE metallb_bgp_notifications_sent counter
-	metallb_bgp_notifications_sent{peer="{{ .NeighborIP }}"} {{ .NotificationsSent }}
+	metallb_bgp_notifications_sent{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .NotificationsSent }}
 	# HELP metallb_bgp_opens_received Number of BGP open messages received
 	# TYPE metallb_bgp_opens_received counter
-	metallb_bgp_opens_received{peer="{{ .NeighborIP }}"} {{ .OpensReceived }}
+	metallb_bgp_opens_received{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .OpensReceived }}
 	# HELP metallb_bgp_opens_sent Number of BGP open messages sent
 	# TYPE metallb_bgp_opens_sent counter
-	metallb_bgp_opens_sent{peer="{{ .NeighborIP }}"} {{ .OpensSent }}
+	metallb_bgp_opens_sent{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .OpensSent }}
 	# HELP metallb_bgp_route_refresh_sent Number of BGP route refresh messages sent
 	# TYPE metallb_bgp_route_refresh_sent counter
-	metallb_bgp_route_refresh_sent{peer="{{ .NeighborIP }}"} {{ .RouteRefreshSent }}
+	metallb_bgp_route_refresh_sent{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .RouteRefreshSent }}
 	# HELP metallb_bgp_session_up BGP session state (1 is up, 0 is down)
 	# TYPE metallb_bgp_session_up gauge
-	metallb_bgp_session_up{peer="{{ .NeighborIP }}"} {{ .SessionUp }}
+	metallb_bgp_session_up{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .SessionUp }}
 	# HELP metallb_bgp_total_received Number of total BGP messages received
 	# TYPE metallb_bgp_total_received counter
-	metallb_bgp_total_received{peer="{{ .NeighborIP }}"} {{ .TotalReceived }}
+	metallb_bgp_total_received{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .TotalReceived }}
 	# HELP metallb_bgp_total_sent Number of total BGP messages sent
 	# TYPE metallb_bgp_total_sent counter
-	metallb_bgp_total_sent{peer="{{ .NeighborIP }}"} {{ .TotalSent }}
+	metallb_bgp_total_sent{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .TotalSent }}
 	# HELP metallb_bgp_updates_total Number of BGP UPDATE messages sent
 	# TYPE metallb_bgp_updates_total counter
-	metallb_bgp_updates_total{peer="{{ .NeighborIP }}"} {{ .UpdatesTotal }}
+	metallb_bgp_updates_total{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .UpdatesTotal }}
 	# HELP metallb_bgp_updates_total_received Number of BGP UPDATE messages received
 	# TYPE metallb_bgp_updates_total_received counter
-	metallb_bgp_updates_total_received{peer="{{ .NeighborIP }}"} {{ .UpdatesTotalReceived }}
+	metallb_bgp_updates_total_received{peer="{{ .NeighborIP }}", vrf="{{ .NeighborVRF }}"} {{ .UpdatesTotalReceived }}
 	`
 
 	tests = []struct {
 		desc                 string
 		vtyshOutput          string
 		neighborIP           string
+		neighborVRF          string
 		announcedPrefixes    int
 		sessionUp            int
 		updatesTotal         int
@@ -72,6 +73,7 @@ var (
 			desc:                 "Output contains only IPv4 advertisements",
 			vtyshOutput:          neighborsIPv4Only,
 			neighborIP:           "172.18.0.4:179",
+			neighborVRF:          "default",
 			announcedPrefixes:    3,
 			sessionUp:            1,
 			updatesTotal:         3,
@@ -89,6 +91,7 @@ var (
 			desc:                 "Output contains mixed IPv4 and IPv6 advertisements",
 			vtyshOutput:          neighborsDual,
 			neighborIP:           "172.18.0.4:180",
+			neighborVRF:          "default",
 			announcedPrefixes:    6,
 			sessionUp:            1,
 			updatesTotal:         3,
@@ -367,6 +370,69 @@ var (
 		}
 	  }	  
 	`
+	vrfVtysh = `{
+		"default":{
+		 "vrfId": 0,
+		 "vrfName": "default",
+		 "tableVersion": 1,
+		 "routerId": "172.18.0.3",
+		 "defaultLocPrf": 100,
+		 "localAS": 64512,
+		 "routes": { "192.168.10.0/32": [
+		  {
+			"valid":true,
+			"bestpath":true,
+			"pathFrom":"external",
+			"prefix":"192.168.10.0",
+			"prefixLen":32,
+			"network":"192.168.10.0\/32",
+			"metric":0,
+			"weight":32768,
+			"peerId":"(unspec)",
+			"path":"",
+			"origin":"IGP",
+			"nexthops":[
+			  {
+				"ip":"0.0.0.0",
+				"hostname":"kind-control-plane",
+				"afi":"ipv4",
+				"used":true
+			  }
+			]
+		  }
+		] }  }
+		,
+		"red":{
+		 "vrfId": 5,
+		 "vrfName": "red",
+		 "tableVersion": 1,
+		 "routerId": "172.31.0.4",
+		 "defaultLocPrf": 100,
+		 "localAS": 64512,
+		 "routes": { "192.168.10.0/32": [
+		  {
+			"valid":true,
+			"bestpath":true,
+			"pathFrom":"external",
+			"prefix":"192.168.10.0",
+			"prefixLen":32,
+			"network":"192.168.10.0\/32",
+			"metric":0,
+			"weight":32768,
+			"peerId":"(unspec)",
+			"path":"",
+			"origin":"IGP",
+			"nexthops":[
+			  {
+				"ip":"0.0.0.0",
+				"hostname":"kind-control-plane",
+				"afi":"ipv4",
+				"used":true
+			  }
+			]
+		  }
+		] }  }
+		}`
 )
 
 func TestCollect(t *testing.T) {
@@ -380,6 +446,7 @@ func TestCollect(t *testing.T) {
 			var w bytes.Buffer
 			err = tmpl.Execute(&w, map[string]interface{}{
 				"NeighborIP":           tc.neighborIP,
+				"NeighborVRF":          tc.neighborVRF,
 				"AnnouncedPrefixes":    tc.announcedPrefixes,
 				"SessionUp":            tc.sessionUp,
 				"UpdatesTotal":         tc.updatesTotal,
@@ -400,8 +467,16 @@ func TestCollect(t *testing.T) {
 
 			l := log.NewNopLogger()
 			collector := NewBGP(l)
+			cmdOutput := map[string]string{
+				"show bgp vrf all json":               vrfVtysh,
+				"show bgp vrf default neighbors json": tc.vtyshOutput,
+			}
 			collector.frrCli = func(args string) (string, error) {
-				return tc.vtyshOutput, nil
+				res, ok := cmdOutput[args]
+				if !ok {
+					return "{}", nil
+				}
+				return res, nil
 			}
 			buf := bytes.NewReader(w.Bytes())
 			err = testutil.CollectAndCompare(collector, buf)
diff --git a/internal/bgp/frr/parse_test.go b/internal/bgp/frr/parse_test.go
index 39969333..c1b7a42f 100644
--- a/internal/bgp/frr/parse_test.go
+++ b/internal/bgp/frr/parse_test.go
@@ -694,14 +694,91 @@ func TestBFDPeers(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Failed to parse %s", err)
 	}
-	p, ok := peers["172.18.0.3"]
-	if !ok {
+	if len(peers) != 3 {
+		t.Fatal("Unexpected peer number", len(peers))
+	}
+	if peers[2].Peer != "172.18.0.3" {
 		t.Fatal("Peer not found")
 	}
-	if p.Status != "up" {
+	if peers[2].Status != "up" {
 		t.Fatal("wrong status")
 	}
-	if p.RemoteEchoInterval != 50 {
+	if peers[2].RemoteEchoInterval != 50 {
 		t.Fatal("wrong echo interval")
 	}
 }
+
+const vrfs = `{
+"default":{
+ "vrfId": 0,
+ "vrfName": "default",
+ "tableVersion": 1,
+ "routerId": "172.18.0.3",
+ "defaultLocPrf": 100,
+ "localAS": 64512,
+ "routes": { "192.168.10.0/32": [
+  {
+    "valid":true,
+    "bestpath":true,
+    "pathFrom":"external",
+    "prefix":"192.168.10.0",
+    "prefixLen":32,
+    "network":"192.168.10.0\/32",
+    "metric":0,
+    "weight":32768,
+    "peerId":"(unspec)",
+    "path":"",
+    "origin":"IGP",
+    "nexthops":[
+      {
+        "ip":"0.0.0.0",
+        "hostname":"kind-control-plane",
+        "afi":"ipv4",
+        "used":true
+      }
+    ]
+  }
+] }  }
+,
+"red":{
+ "vrfId": 5,
+ "vrfName": "red",
+ "tableVersion": 1,
+ "routerId": "172.31.0.4",
+ "defaultLocPrf": 100,
+ "localAS": 64512,
+ "routes": { "192.168.10.0/32": [
+  {
+    "valid":true,
+    "bestpath":true,
+    "pathFrom":"external",
+    "prefix":"192.168.10.0",
+    "prefixLen":32,
+    "network":"192.168.10.0\/32",
+    "metric":0,
+    "weight":32768,
+    "peerId":"(unspec)",
+    "path":"",
+    "origin":"IGP",
+    "nexthops":[
+      {
+        "ip":"0.0.0.0",
+        "hostname":"kind-control-plane",
+        "afi":"ipv4",
+        "used":true
+      }
+    ]
+  }
+] }  }
+}`
+
+func TestVRFs(t *testing.T) {
+	parsed, err := ParseVRFs(vrfs)
+	if err != nil {
+		t.Fatalf("Failed to parse %s", err)
+	}
+	expected := []string{"default", "red"}
+	if !cmp.Equal(parsed, expected) {
+		t.Fatalf("unexpected vrf list: %s", cmp.Diff(parsed, expected))
+	}
+}