Repository: metallb/metallb
Pull request #1898 (2023-04-18 22:32:03)

Add support for BGP large communities
This patch only implements large communities but the format is compatible with a future implementation of extended communities.

Community types are distinguished by using Juniper style syntax, thus <AS number>:<community value>,<type>:<administrator>:<assigned-number>, large:<global administrator>:<localdata part 1>:<localdata part 2> for each of the respective types legacy, extended and large communities.

Implements https://issues.redhat.com/browse/RFE-3924

andreaskaris: I moved the removal of native_test.go to https://github.com/metallb/metallb/pull/1915

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 92f3eb9a..7d25661c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -33,7 +33,7 @@ jobs:
           args: -exclude-dir e2etest -severity medium ./...
 
       - name: Golang Vulncheck
-        uses: Templum/govulncheck-action@v0.10.0
+        uses: Templum/govulncheck-action@v0.10.1
         with:
           skip-upload: true
 
diff --git a/.github/workflows/vulncheck_periodic.yaml b/.github/workflows/vulncheck_periodic.yaml
index 86b8d9b0..a9af352a 100644
--- a/.github/workflows/vulncheck_periodic.yaml
+++ b/.github/workflows/vulncheck_periodic.yaml
@@ -13,5 +13,5 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v3
       - name: Golang Vulncheck
-        uses: Templum/govulncheck-action@v0.10.0
+        uses: Templum/govulncheck-action@v0.10.1
 
diff --git a/api/v1beta1/bgpadvertisement_types.go b/api/v1beta1/bgpadvertisement_types.go
index 0f430ff8..c94ac779 100644
--- a/api/v1beta1/bgpadvertisement_types.go
+++ b/api/v1beta1/bgpadvertisement_types.go
@@ -41,8 +41,9 @@ type BGPAdvertisementSpec struct {
 	// +optional
 	LocalPref uint32 `json:"localPref,omitempty"`
 
-	// The BGP communities to be associated with the announcement. Each item can be a
-	// community of the form 1234:1234 or the name of an alias defined in the Community CRD.
+	// The BGP communities to be associated with the announcement. Each item can be a standard community of the
+	// form 1234:1234, a large community of the form large:1234:1234:1234 or the name of an alias defined in the
+	// Community CRD.
 	// +optional
 	Communities []string `json:"communities,omitempty"`
 
diff --git a/api/v1beta1/community_types.go b/api/v1beta1/community_types.go
index 4ac9c68e..f506d17d 100644
--- a/api/v1beta1/community_types.go
+++ b/api/v1beta1/community_types.go
@@ -23,7 +23,8 @@ import (
 type CommunityAlias struct {
 	// The name of the alias for the community.
 	Name string `json:"name,omitempty"`
-	// The BGP community value corresponding to the given name.
+	// The BGP community value corresponding to the given name. Can be a standard community of the form 1234:1234
+	// or a large community of the form large:1234:1234:1234.
 	Value string `json:"value,omitempty"`
 }
 
diff --git a/config/crd/bases/metallb.io_bgpadvertisements.yaml b/config/crd/bases/metallb.io_bgpadvertisements.yaml
index e04c385e..7fc6cc3a 100644
--- a/config/crd/bases/metallb.io_bgpadvertisements.yaml
+++ b/config/crd/bases/metallb.io_bgpadvertisements.yaml
@@ -67,8 +67,9 @@ spec:
                 type: integer
               communities:
                 description: The BGP communities to be associated with the announcement.
-                  Each item can be a community of the form 1234:1234 or the name of
-                  an alias defined in the Community CRD.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
                 items:
                   type: string
                 type: array
diff --git a/config/crd/bases/metallb.io_communities.yaml b/config/crd/bases/metallb.io_communities.yaml
index 01d2c796..0b559baa 100644
--- a/config/crd/bases/metallb.io_communities.yaml
+++ b/config/crd/bases/metallb.io_communities.yaml
@@ -44,7 +44,8 @@ spec:
                       type: string
                     value:
                       description: The BGP community value corresponding to the given
-                        name.
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
                       type: string
                   type: object
                 type: array
diff --git a/config/manifests/metallb-frr-prometheus.yaml b/config/manifests/metallb-frr-prometheus.yaml
index 652404cf..a2263605 100644
--- a/config/manifests/metallb-frr-prometheus.yaml
+++ b/config/manifests/metallb-frr-prometheus.yaml
@@ -389,8 +389,9 @@ spec:
                 type: integer
               communities:
                 description: The BGP communities to be associated with the announcement.
-                  Each item can be a community of the form 1234:1234 or the name of
-                  an alias defined in the Community CRD.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
                 items:
                   type: string
                 type: array
@@ -883,7 +884,8 @@ spec:
                       type: string
                     value:
                       description: The BGP community value corresponding to the given
-                        name.
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
                       type: string
                   type: object
                 type: array
diff --git a/config/manifests/metallb-frr.yaml b/config/manifests/metallb-frr.yaml
index fc1f59c5..8f740c68 100644
--- a/config/manifests/metallb-frr.yaml
+++ b/config/manifests/metallb-frr.yaml
@@ -389,8 +389,9 @@ spec:
                 type: integer
               communities:
                 description: The BGP communities to be associated with the announcement.
-                  Each item can be a community of the form 1234:1234 or the name of
-                  an alias defined in the Community CRD.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
                 items:
                   type: string
                 type: array
@@ -883,7 +884,8 @@ spec:
                       type: string
                     value:
                       description: The BGP community value corresponding to the given
-                        name.
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
                       type: string
                   type: object
                 type: array
diff --git a/config/manifests/metallb-native-prometheus.yaml b/config/manifests/metallb-native-prometheus.yaml
index 82199930..643236f4 100644
--- a/config/manifests/metallb-native-prometheus.yaml
+++ b/config/manifests/metallb-native-prometheus.yaml
@@ -389,8 +389,9 @@ spec:
                 type: integer
               communities:
                 description: The BGP communities to be associated with the announcement.
-                  Each item can be a community of the form 1234:1234 or the name of
-                  an alias defined in the Community CRD.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
                 items:
                   type: string
                 type: array
@@ -883,7 +884,8 @@ spec:
                       type: string
                     value:
                       description: The BGP community value corresponding to the given
-                        name.
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
                       type: string
                   type: object
                 type: array
diff --git a/config/manifests/metallb-native.yaml b/config/manifests/metallb-native.yaml
index 2165f1a4..238d6036 100644
--- a/config/manifests/metallb-native.yaml
+++ b/config/manifests/metallb-native.yaml
@@ -389,8 +389,9 @@ spec:
                 type: integer
               communities:
                 description: The BGP communities to be associated with the announcement.
-                  Each item can be a community of the form 1234:1234 or the name of
-                  an alias defined in the Community CRD.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
                 items:
                   type: string
                 type: array
@@ -883,7 +884,8 @@ spec:
                       type: string
                     value:
                       description: The BGP community value corresponding to the given
-                        name.
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
                       type: string
                   type: object
                 type: array
diff --git a/internal/bgp/bgp.go b/internal/bgp/bgp.go
index c5950755..d62c23ce 100644
--- a/internal/bgp/bgp.go
+++ b/internal/bgp/bgp.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/go-kit/log"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/config"
 )
 
@@ -20,7 +21,7 @@ type Advertisement struct {
 	// peers (i.e. where the peer ASN matches the local ASN).
 	LocalPref uint32
 	// BGP communities to attach to the path.
-	Communities []uint32
+	Communities []community.BGPCommunity
 	// Used to declare the intent of announcing IPs
 	// only to the BGPPeers in this list.
 	Peers []string
diff --git a/internal/bgp/community/community.go b/internal/bgp/community/community.go
new file mode 100644
index 00000000..793ee687
--- /dev/null
+++ b/internal/bgp/community/community.go
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package community
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+var (
+	ErrInvalidCommunityValue  = errors.New("invalid community value")
+	ErrInvalidCommunityFormat = errors.New("invalid community format")
+)
+
+// largeBGPCommunityMarker is the prefix that shall be used to indicate that a given community value is of type large
+// community. If and when extended BGP communities will be supported, the largeBGPCommunityMarker allows us to
+// distinguish between extended and large communities.
+const largeBGPCommunityMarker = "large"
+
+// BGPCommunity represents a BGP community.
+type BGPCommunity interface {
+	LessThan(BGPCommunity) bool
+	String() string
+}
+
+// New parses the provided string and returns a newly created BGPCommunity object.
+// Strings are parsed according to Juniper style  syntax (https://www.juniper.net/documentation/us/en/software/\
+// junos/routing-policy/bgp/topics/concept/policy-bgp-communities-extended-communities-match-conditions-overview.html
+// Legacy communities are of format "<AS number>:<community value>".
+// Extended communities (support of which is yet to be implemented) are of format "<type>:<administrator>:<assigned-number>".
+// Large communities are of format large:<global administrator>:<localdata part 1>:<localdata part 2>.
+func New(c string) (BGPCommunity, error) {
+	var bgpCommunity BGPCommunity
+
+	fs := strings.Split(c, ":")
+	switch l := len(fs); l {
+	case 2:
+		var fields [2]uint16
+		for i := 0; i < 2; i++ {
+			b, err := strconv.ParseUint(fs[i], 10, 16)
+			if err != nil {
+				return bgpCommunity, fmt.Errorf("%w: invalid section %q of community %q, err: %q",
+					ErrInvalidCommunityValue, fs[i], c, err)
+			}
+			fields[i] = uint16(b)
+		}
+		return BGPCommunityLegacy{
+			upperVal: fields[0],
+			lowerVal: fields[1],
+		}, nil
+	case 4:
+		if fs[0] != largeBGPCommunityMarker {
+			return bgpCommunity, fmt.Errorf("%w: invalid marker for large community, expected community to be of "+
+				"format %s:<uint32>:<uint32>:<uint32> but got %q instead",
+				ErrInvalidCommunityValue, largeBGPCommunityMarker, c)
+		}
+		var fields [3]uint32
+		for i := 1; i < 4; i++ {
+			b, err := strconv.ParseUint(fs[i], 10, 32)
+			if err != nil {
+				return bgpCommunity, fmt.Errorf("%w: invalid section %q of community %q, err: %q",
+					ErrInvalidCommunityValue, fs[i], c, err)
+			}
+			fields[i-1] = uint32(b)
+		}
+		return BGPCommunityLarge{
+			globalAdministrator: fields[0],
+			localDataPart1:      fields[1],
+			localDataPart2:      fields[2],
+		}, nil
+	}
+
+	return bgpCommunity, fmt.Errorf("%w: %s", ErrInvalidCommunityFormat, c)
+}
+
+// BGPCommunityLegacy holds the internal representation of a BGP legacy community.
+type BGPCommunityLegacy struct {
+	upperVal uint16
+	lowerVal uint16
+}
+
+// LessThan makes 2 different BGPCommunity objects comparable. For the sake of comparison, legacy communities are
+// considered to be large communities in format <legacy community>:0:0 and thus can be compared with large communities.
+func (b BGPCommunityLegacy) LessThan(c BGPCommunity) bool {
+	return lessThan(b, c)
+}
+
+// String returns the string representation of this community. Legacy communities will be printed in new-format,
+// "<AS number>:<community value>".
+func (b BGPCommunityLegacy) String() string {
+	return fmt.Sprintf("%d:%d", b.upperVal, b.lowerVal)
+}
+
+// ToUint32 returns the uint32 representation of this legacy community.
+func (b BGPCommunityLegacy) ToUint32() uint32 {
+	return (uint32(b.upperVal) << 16) + uint32(b.lowerVal)
+}
+
+// BGPCommunity holds the internal representation of a large BGP community.
+type BGPCommunityLarge struct {
+	globalAdministrator uint32
+	localDataPart1      uint32
+	localDataPart2      uint32
+}
+
+// LessThan makes 2 different BGPCommunity objects comparable. For the sake of comparison, legacy communities are
+// considered to be large communities in format <legacy community>:0:0 and thus can be compared with large communities.
+func (b BGPCommunityLarge) LessThan(c BGPCommunity) bool {
+	return lessThan(b, c)
+}
+
+// String returns the string representation of this community. Large communities will be printed as
+// ("<global administrator>:<localdata part 1>:<localdata part 2>").
+func (b BGPCommunityLarge) String() string {
+	return fmt.Sprintf("%d:%d:%d", b.globalAdministrator, b.localDataPart1, b.localDataPart2)
+}
+
+// IsLegacy returns true if this is a Legacy community.
+func IsLegacy(c BGPCommunity) bool {
+	_, ok := c.(BGPCommunityLegacy)
+	return ok
+}
+
+// IsLarge returns true if this is a Legacy community.
+func IsLarge(c BGPCommunity) bool {
+	_, ok := c.(BGPCommunityLarge)
+	return ok
+}
+
+// lessThan is a helper function that compares two communities regardless of their type.
+func lessThan(b BGPCommunity, c BGPCommunity) bool {
+	var bl BGPCommunityLarge
+	var cl BGPCommunityLarge
+	switch v := b.(type) {
+	case BGPCommunityLegacy:
+		bl = BGPCommunityLarge{
+			globalAdministrator: v.ToUint32(),
+			localDataPart1:      0,
+			localDataPart2:      0,
+		}
+	case BGPCommunityLarge:
+		bl = v
+	}
+
+	switch v := c.(type) {
+	case BGPCommunityLegacy:
+		cl = BGPCommunityLarge{
+			globalAdministrator: v.ToUint32(),
+			localDataPart1:      0,
+			localDataPart2:      0,
+		}
+	case BGPCommunityLarge:
+		cl = v
+	}
+	return bl.globalAdministrator < cl.globalAdministrator ||
+		(bl.globalAdministrator == cl.globalAdministrator && (bl.localDataPart1 < cl.localDataPart1 ||
+			(bl.localDataPart1 == cl.localDataPart1 && bl.localDataPart2 < cl.localDataPart2)))
+}
diff --git a/internal/bgp/frr/config.go b/internal/bgp/frr/config.go
index 818dcdda..119edfb4 100644
--- a/internal/bgp/frr/config.go
+++ b/internal/bgp/frr/config.go
@@ -88,10 +88,11 @@ func (n *neighborConfig) ID() string {
 }
 
 type advertisementConfig struct {
-	IPFamily    ipfamily.Family
-	Prefix      string
-	Communities []string
-	LocalPref   uint32
+	IPFamily         ipfamily.Family
+	Prefix           string
+	Communities      []string
+	LargeCommunities []string
+	LocalPref        uint32
 }
 
 // routerName() defines the format of the key of the "Routers" map in the
@@ -133,6 +134,9 @@ func templateConfig(data interface{}) (string, error) {
 			"communityPrefixList": func(neighbor *neighborConfig, community string) string {
 				return fmt.Sprintf("%s-%s-%s-community-prefixes", neighbor.ID(), community, neighbor.IPFamily)
 			},
+			"largeCommunityPrefixList": func(neighbor *neighborConfig, community string) string {
+				return fmt.Sprintf("%s-large:%s-%s-community-prefixes", neighbor.ID(), community, neighbor.IPFamily)
+			},
 			"allowedPrefixList": func(neighbor *neighborConfig) string {
 				return fmt.Sprintf("%s-pl-%s", neighbor.ID(), neighbor.IPFamily)
 			},
diff --git a/internal/bgp/frr/frr.go b/internal/bgp/frr/frr.go
index 49e7899c..d22e1e46 100644
--- a/internal/bgp/frr/frr.go
+++ b/internal/bgp/frr/frr.go
@@ -15,6 +15,7 @@ import (
 	"github.com/go-kit/log"
 	"github.com/go-kit/log/level"
 	"go.universe.tf/metallb/internal/bgp"
+	"go.universe.tf/metallb/internal/bgp/community"
 	metallbconfig "go.universe.tf/metallb/internal/config"
 	"go.universe.tf/metallb/internal/ipfamily"
 	"go.universe.tf/metallb/internal/logging"
@@ -282,19 +283,24 @@ func (sm *sessionManager) createConfig() (*frrConfig, error) {
 			family := ipfamily.ForAddress(adv.Prefix.IP)
 
 			communities := make([]string, 0)
+			largeCommunities := make([]string, 0)
 
 			// Convert community 32bits value to : format
 			for _, c := range adv.Communities {
-				community := metallbconfig.CommunityToString(c)
-				communities = append(communities, community)
+				if community.IsLarge(c) {
+					largeCommunities = append(largeCommunities, c.String())
+					continue
+				}
+				communities = append(communities, c.String())
 			}
 
 			prefix := adv.Prefix.String()
 			advConfig := advertisementConfig{
-				IPFamily:    family,
-				Prefix:      prefix,
-				Communities: communities,
-				LocalPref:   adv.LocalPref,
+				IPFamily:         family,
+				Prefix:           prefix,
+				Communities:      communities,
+				LargeCommunities: largeCommunities,
+				LocalPref:        adv.LocalPref,
 			}
 
 			neighbor.Advertisements = append(neighbor.Advertisements, &advConfig)
diff --git a/internal/bgp/frr/templates/filters.tmpl b/internal/bgp/frr/templates/filters.tmpl
index 7cf35a07..1e26f0b4 100644
--- a/internal/bgp/frr/templates/filters.tmpl
+++ b/internal/bgp/frr/templates/filters.tmpl
@@ -14,6 +14,14 @@ route-map {{.neighbor.ID}}-out permit {{counter .neighbor.ID}}
   on-match next
 {{- end -}}
 
+{{- define "largecommunityfilter" -}}
+{{frrIPFamily .advertisement.IPFamily}} prefix-list {{largeCommunityPrefixList .neighbor .largecommunity}} permit {{.advertisement.Prefix}}
+route-map {{.neighbor.ID}}-out permit {{counter .neighbor.ID}}
+  match {{frrIPFamily .advertisement.IPFamily}} address prefix-list {{largeCommunityPrefixList .neighbor .largecommunity}}
+  set large-community {{.largecommunity}} additive
+  on-match next
+{{- end -}}
+
 {{- /* The prefixes are per router in FRR, but MetalLB api allows to associate a given BGPAdvertisement to a service IP,
      and a given advertisement contains both the properties of the announcement (i.e. community) and the list of peers
      we may want to advertise to. Because of this, for each neighbor we must opt-in and allow the advertisement, and
@@ -31,6 +39,9 @@ route-map {{.neighbor.ID}}-in deny 20
 {{- range $c := $a.Communities }}
 {{template "communityfilter" dict "advertisement" $a "neighbor" $.neighbor "community" $c}}
 {{- end }}
+{{- range $lc := $a.LargeCommunities }}
+{{template "largecommunityfilter" dict "advertisement" $a "neighbor" $.neighbor "largecommunity" $lc}}
+{{- end }}
 {{/* this advertisement is allowed to the specific neighbor  */}}
 {{frrIPFamily $a.IPFamily}} prefix-list {{allowedPrefixList $.neighbor}} permit {{$a.Prefix}}
 {{- end }}
diff --git a/internal/bgp/native/messages.go b/internal/bgp/native/messages.go
index c301efd8..62b29249 100644
--- a/internal/bgp/native/messages.go
+++ b/internal/bgp/native/messages.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"go.universe.tf/metallb/internal/bgp"
+	"go.universe.tf/metallb/internal/bgp/community"
 )
 
 func sendOpen(w io.Writer, asn uint32, routerID net.IP, holdTime time.Duration) error {
@@ -383,13 +384,24 @@ func encodePathAttrs(b *bytes.Buffer, asn uint32, ibgp, fbasn bool, nextHop net.
 	}
 
 	if len(adv.Communities) > 0 {
+		// Convert communities to legacy communities uint32 representation. Throw an error if any non legacy type
+		// communities were found.
+		var legacyCommunities []uint32
+		for _, c := range adv.Communities {
+			legacyCommunity, ok := c.(community.BGPCommunityLegacy)
+			if !ok {
+				return fmt.Errorf("invalid community type for BGP native mode, community %s is not a legacy BGP "+
+					"Community", c)
+			}
+			legacyCommunities = append(legacyCommunities, legacyCommunity.ToUint32())
+		}
 		b.Write([]byte{
 			0xc0, 8, // optional transitive, communities
 		})
-		if err := binary.Write(b, binary.BigEndian, uint8(len(adv.Communities)*4)); err != nil {
+		if err := binary.Write(b, binary.BigEndian, uint8(len(legacyCommunities)*4)); err != nil {
 			return err
 		}
-		for _, c := range adv.Communities {
+		for _, c := range legacyCommunities {
 			if err := binary.Write(b, binary.BigEndian, c); err != nil {
 				return err
 			}
diff --git a/internal/config/config.go b/internal/config/config.go
index 7df5f8a5..fc041907 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -20,7 +20,6 @@ import (
 	"net"
 	"reflect"
 	"sort"
-	"strconv"
 	"strings"
 	"time"
 
@@ -28,6 +27,7 @@ import (
 	"github.com/pkg/errors"
 	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
 	metallbv1beta2 "go.universe.tf/metallb/api/v1beta2"
+	"go.universe.tf/metallb/internal/bgp/community"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -175,7 +175,7 @@ type BGPAdvertisement struct {
 	// advertising to IBGP peers (i.e. Peer.MyASN == Peer.ASN).
 	LocalPref uint32
 	// Value of the COMMUNITIES path attribute.
-	Communities map[uint32]bool
+	Communities map[community.BGPCommunity]bool
 	// The map of nodes allowed for this advertisement
 	Nodes map[string]bool
 	// Used to declare the intent of announcing IPs
@@ -369,11 +369,11 @@ func bgpExtrasFor(resources ClusterResources) string {
 	return resources.BGPExtras.Data[bgpExtrasField]
 }
 
-func communitiesFromCrs(cs []metallbv1beta1.Community) (map[string]uint32, error) {
-	communities := map[string]uint32{}
+func communitiesFromCrs(cs []metallbv1beta1.Community) (map[string]community.BGPCommunity, error) {
+	communities := map[string]community.BGPCommunity{}
 	for _, c := range cs {
 		for _, communityAlias := range c.Spec.Communities {
-			v, err := ParseCommunity(communityAlias.Value)
+			v, err := community.New(communityAlias.Value)
 			if err != nil {
 				return nil, fmt.Errorf("parsing community %q: %s", communityAlias.Name, err)
 			}
@@ -608,7 +608,7 @@ func poolsByServiceSelector(pools map[string]*Pool) []string {
 	return poolsByServiceSelector
 }
 
-func addressPoolFromLegacyCR(p metallbv1beta1.AddressPool, bgpCommunities map[string]uint32, allNodes map[string]bool) (*Pool, error) {
+func addressPoolFromLegacyCR(p metallbv1beta1.AddressPool, bgpCommunities map[string]community.BGPCommunity, allNodes map[string]bool) (*Pool, error) {
 	if p.Name == "" {
 		return nil, errors.New("missing pool name")
 	}
@@ -728,7 +728,7 @@ func setL2AdvertisementsToPools(ipPools []metallbv1beta1.IPAddressPool, l2Advs [
 }
 
 func setBGPAdvertisementsToPools(ipPools []metallbv1beta1.IPAddressPool, bgpAdvs []metallbv1beta1.BGPAdvertisement,
-	nodes []corev1.Node, ipPoolMap map[string]*Pool, communities map[string]uint32) error {
+	nodes []corev1.Node, ipPoolMap map[string]*Pool, communities map[string]community.BGPCommunity) error {
 	for _, bgpAdv := range bgpAdvs {
 		adv, err := bgpAdvertisementFromCR(bgpAdv, communities, nodes)
 		if err != nil {
@@ -789,7 +789,7 @@ func l2AdvertisementFromCR(crdAd metallbv1beta1.L2Advertisement, nodes []corev1.
 	return l2, nil
 }
 
-func bgpAdvertisementFromCR(crdAd metallbv1beta1.BGPAdvertisement, communities map[string]uint32, nodes []corev1.Node) (*BGPAdvertisement, error) {
+func bgpAdvertisementFromCR(crdAd metallbv1beta1.BGPAdvertisement, communities map[string]community.BGPCommunity, nodes []corev1.Node) (*BGPAdvertisement, error) {
 	err := validateDuplicate(crdAd.Spec.IPAddressPools, "ipAddressPools")
 	if err != nil {
 		return nil, err
@@ -816,7 +816,7 @@ func bgpAdvertisementFromCR(crdAd metallbv1beta1.BGPAdvertisement, communities m
 		AggregationLength:   32,
 		AggregationLengthV6: 128,
 		LocalPref:           0,
-		Communities:         map[uint32]bool{},
+		Communities:         map[community.BGPCommunity]bool{},
 	}
 
 	if crdAd.Spec.AggregationLength != nil {
@@ -855,14 +855,14 @@ func bgpAdvertisementFromCR(crdAd metallbv1beta1.BGPAdvertisement, communities m
 	return ad, nil
 }
 
-func bgpAdvertisementsFromLegacyCR(ads []metallbv1beta1.LegacyBgpAdvertisement, cidrsPerAddresses map[string][]*net.IPNet, communities map[string]uint32, allNodes map[string]bool) ([]*BGPAdvertisement, error) {
+func bgpAdvertisementsFromLegacyCR(ads []metallbv1beta1.LegacyBgpAdvertisement, cidrsPerAddresses map[string][]*net.IPNet, communities map[string]community.BGPCommunity, allNodes map[string]bool) ([]*BGPAdvertisement, error) {
 	if len(ads) == 0 {
 		return []*BGPAdvertisement{
 			{
 				AggregationLength:   32,
 				AggregationLengthV6: 128,
 				LocalPref:           0,
-				Communities:         map[uint32]bool{},
+				Communities:         map[community.BGPCommunity]bool{},
 				Nodes:               allNodes,
 			},
 		}, nil
@@ -879,7 +879,7 @@ func bgpAdvertisementsFromLegacyCR(ads []metallbv1beta1.LegacyBgpAdvertisement,
 			AggregationLength:   32,
 			AggregationLengthV6: 128,
 			LocalPref:           0,
-			Communities:         map[uint32]bool{},
+			Communities:         map[community.BGPCommunity]bool{},
 			Nodes:               allNodes,
 		}
 
@@ -929,23 +929,24 @@ func bgpAdvertisementsFromLegacyCR(ads []metallbv1beta1.LegacyBgpAdvertisement,
 	return ret, nil
 }
 
-func getCommunityValue(community string, communities map[string]uint32) (uint32, error) {
-	if v, ok := communities[community]; ok {
+// getCommunityValue returns the BGPCommunity from the communities map if it exists there. Otherwise, it creates a
+// new BGP community object from the provided communityString.
+func getCommunityValue(communityString string, communities map[string]community.BGPCommunity) (community.BGPCommunity, error) {
+	if v, ok := communities[communityString]; ok {
 		return v, nil
 	}
 
-	v, err := ParseCommunity(community)
-	if errors.Is(err, errInvalidaCommunityValue) {
-		return 0, err
+	var c community.BGPCommunity
+	c, err := community.New(communityString)
+	if errors.Is(err, community.ErrInvalidCommunityValue) {
+		return c, err
 	}
-
 	// Return TransientError on invalidCommunityFormat, in case it refers
 	// a Community resource that doesn't exist yet.
-	if errors.Is(err, errInvalidCommunityFormat) {
-		return 0, TransientError{err.Error()}
+	if errors.Is(err, community.ErrInvalidCommunityFormat) {
+		return c, TransientError{err.Error()}
 	}
-
-	return v, nil
+	return c, nil
 }
 
 func validateHoldTime(ht time.Duration) error {
@@ -1018,32 +1019,6 @@ func advertisementsAreCompatible(newAdv, adv *BGPAdvertisement) bool {
 	return true
 }
 
-var errInvalidaCommunityValue = errors.New("invalid community value")
-var errInvalidCommunityFormat = errors.New("invalid community format")
-
-func ParseCommunity(c string) (uint32, error) {
-	fs := strings.Split(c, ":")
-	if len(fs) != 2 {
-		return 0, fmt.Errorf("%w: %s", errInvalidCommunityFormat, c)
-	}
-	a, err := strconv.ParseUint(fs[0], 10, 16)
-	if err != nil {
-		return 0, fmt.Errorf("%w: invalid first section of community %q: %s", errInvalidaCommunityValue, fs[0], err)
-	}
-	b, err := strconv.ParseUint(fs[1], 10, 16)
-	if err != nil {
-		return 0, fmt.Errorf("%w: invalid second section of community %q: %s", errInvalidaCommunityValue, fs[1], err)
-	}
-
-	return (uint32(a) << 16) + uint32(b), nil
-}
-
-func CommunityToString(c uint32) string {
-	upperVal := c >> 16
-	lowerVal := c & 0xFFFF
-	return fmt.Sprintf("%d:%d", upperVal, lowerVal)
-}
-
 func ParseCIDR(cidr string) ([]*net.IPNet, error) {
 	if !strings.Contains(cidr, "-") {
 		_, n, err := net.ParseCIDR(cidr)
diff --git a/internal/config/validation.go b/internal/config/validation.go
index 27d0c00f..2eeadeea 100644
--- a/internal/config/validation.go
+++ b/internal/config/validation.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/pkg/errors"
 	metallbv1beta2 "go.universe.tf/metallb/api/v1beta2"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/ipfamily"
 )
 
@@ -39,11 +40,20 @@ func DiscardFRROnly(c ClusterResources) error {
 	if len(c.BFDProfiles) > 0 {
 		return errors.New("bfd profiles section set")
 	}
+	// Only IPv4 BGP advertisements are supported in native mode.
+	if err := findIPv6BGPAdvertisement(c); err != nil {
+		return err
+	}
+	// Only legacy type communities are supported in native mode.
+	return findNonLegacyCommunity(c)
+}
+
+// findIPv6BGPAdvertisement checks for IPv6 addresses. If it finds at least one IPv6 BGP advertisement, it will throw
+// and error as it's not supported in native mode.
+func findIPv6BGPAdvertisement(c ClusterResources) error {
 	if len(c.BGPAdvs) == 0 {
 		return nil
 	}
-	// we check for ipv6 addresses if we have at least one bgp advertisement, as it's
-	// not supported in native mode.
 	for _, p := range c.Pools {
 		for _, cidr := range p.Spec.Addresses {
 			nets, err := ParseCIDR(cidr)
@@ -60,6 +70,54 @@ func DiscardFRROnly(c ClusterResources) error {
 	return nil
 }
 
+// findNonLegacyCommunity returns an error if it can find a non legacy community. If a community string can not be
+// parsed, the string will be ignored.
+func findNonLegacyCommunity(c ClusterResources) error {
+	for _, p := range c.LegacyAddressPools {
+		for _, adv := range p.Spec.BGPAdvertisements {
+			for _, cs := range adv.Communities {
+				c, err := community.New(cs)
+				if err != nil {
+					// Skip aliases.
+					continue
+				}
+				if !community.IsLegacy(c) {
+					return fmt.Errorf("native BGP mode only supports legacy communities, legacy address pool %q "+
+						"has non legacy community %q", p.Name, cs)
+				}
+			}
+		}
+	}
+	for _, adv := range c.BGPAdvs {
+		for _, cs := range adv.Spec.Communities {
+			c, err := community.New(cs)
+			if err != nil {
+				// Skip aliases.
+				continue
+			}
+			if !community.IsLegacy(c) {
+				return fmt.Errorf("native BGP mode only supports legacy communities, BGP advertisement %q "+
+					"has non legacy community %q", adv.Name, cs)
+			}
+		}
+	}
+	for _, co := range c.Communities {
+		for _, cs := range co.Spec.Communities {
+			c, err := community.New(cs.Value)
+			if err != nil {
+				// Skip it if we cannot parse it - the purpose of this very verification is not to make sure that
+				// a string can be parsed or not.
+				continue
+			}
+			if !community.IsLegacy(c) {
+				return fmt.Errorf("native BGP mode only supports legacy communities, BGP community CR %q "+
+					"has non legacy community %q", co.Name, cs)
+			}
+		}
+	}
+	return nil
+}
+
 // DontValidate is a Validate function that always returns
 // success.
 func DontValidate(c ClusterResources) error {
diff --git a/speaker/bgp_controller.go b/speaker/bgp_controller.go
index b4ffbfef..de058fec 100644
--- a/speaker/bgp_controller.go
+++ b/speaker/bgp_controller.go
@@ -301,7 +301,7 @@ func (c *bgpController) SetBalancer(l log.Logger, name string, lbIPs []net.IP, p
 			for comm := range adCfg.Communities {
 				ad.Communities = append(ad.Communities, comm)
 			}
-			sort.Slice(ad.Communities, func(i, j int) bool { return ad.Communities[i] < ad.Communities[j] })
+			sort.Slice(ad.Communities, func(i, j int) bool { return ad.Communities[i].LessThan(ad.Communities[j]) })
 			c.svcAds[name] = append(c.svcAds[name], ad)
 		}
 	}
diff --git a/website/content/apis/_index.md b/website/content/apis/_index.md
index 2738b4cb..b6927912 100644
--- a/website/content/apis/_index.md
+++ b/website/content/apis/_index.md
@@ -260,8 +260,9 @@ Path with higher localpref is preferred over one with lower localpref.</p>
 </td>
 <td>
 <em>(Optional)</em>
-<p>The BGP communities to be associated with the announcement. Each item can be a
-community of the form 1234:1234 or the name of an alias defined in the Community CRD.</p>
+<p>The BGP communities to be associated with the announcement. Each item can be a standard community of the
+form 1234:1234, a large community of the form large:1234:1234:1234 or the name of an alias defined in the
+Community CRD.</p>
 </td>
 </tr>
 <tr>
@@ -436,7 +437,8 @@ string
 </em>
 </td>
 <td>
-<p>The BGP community value corresponding to the given name.</p>
+<p>The BGP community value corresponding to the given name. Can be a standard community of the form 1234:1234
+or a large community of the form large:1234:1234:1234.</p>
 </td>
 </tr>
 </tbody>


Test patch:
diff --git a/e2etest/.gitignore b/e2etest/.gitignore
new file mode 100644
index 00000000..74be4abc
--- /dev/null
+++ b/e2etest/.gitignore
@@ -0,0 +1,1 @@
+e2etest.test
diff --git a/e2etest/bgptests/bgp.go b/e2etest/bgptests/bgp.go
index a4ce2b33..12b6c39f 100644
--- a/e2etest/bgptests/bgp.go
+++ b/e2etest/bgptests/bgp.go
@@ -846,6 +846,20 @@ var _ = ginkgo.Describe("BGP", func() {
 				false,
 				ipfamily.IPv4,
 				[]metallbv1beta1.Community{}),
+			ginkgo.Entry("FRR - IPV4 - large community and localpref",
+				"192.168.10.0/24",
+				"192.168.16.0/24",
+				metallbv1beta1.BGPAdvertisement{
+					ObjectMeta: metav1.ObjectMeta{Name: "advertisement"},
+					Spec: metallbv1beta1.BGPAdvertisementSpec{
+						Communities:    []string{"large:123:456:7890"},
+						LocalPref:      50,
+						IPAddressPools: []string{"bgp-with-advertisement"},
+					},
+				},
+				false,
+				ipfamily.IPv4,
+				[]metallbv1beta1.Community{}),
 			ginkgo.Entry("IPV4 - localpref",
 				"192.168.10.0/24",
 				"192.168.16.0/24",
@@ -1011,8 +1025,21 @@ var _ = ginkgo.Describe("BGP", func() {
 				},
 				false,
 				ipfamily.IPv6,
+				[]metallbv1beta1.Community{}),
+			ginkgo.Entry("FRR - IPV6 - large community and localpref",
+				"fc00:f853:0ccd:e799::0-fc00:f853:0ccd:e799::18",
+				"fc00:f853:0ccd:e799::19-fc00:f853:0ccd:e799::26",
+				metallbv1beta1.BGPAdvertisement{
+					ObjectMeta: metav1.ObjectMeta{Name: "advertisement"},
+					Spec: metallbv1beta1.BGPAdvertisementSpec{
+						Communities:    []string{"large:123:456:7890"},
+						LocalPref:      50,
+						IPAddressPools: []string{"bgp-with-advertisement"},
+					},
+				},
+				false,
+				ipfamily.IPv6,
 				[]metallbv1beta1.Community{}))
-
 	})
 
 	ginkgo.Context("MetalLB FRR rejects", func() {
diff --git a/e2etest/pkg/frr/bgp.go b/e2etest/pkg/frr/bgp.go
index 413665ea..74dfd6dc 100644
--- a/e2etest/pkg/frr/bgp.go
+++ b/e2etest/pkg/frr/bgp.go
@@ -9,6 +9,7 @@ import (
 	"github.com/pkg/errors"
 	"go.universe.tf/metallb/e2etest/pkg/executor"
 
+	"go.universe.tf/metallb/internal/bgp/community"
 	bgpfrr "go.universe.tf/metallb/internal/bgp/frr"
 	"go.universe.tf/metallb/internal/ipfamily"
 )
@@ -97,7 +98,17 @@ func RoutesForFamily(exec executor.Executor, family ipfamily.Family) (map[string
 }
 
 // RoutesForCommunity returns informations about routes in the given executor related to the given community.
-func RoutesForCommunity(exec executor.Executor, community string, family ipfamily.Family) (map[string]bgpfrr.Route, error) {
+func RoutesForCommunity(exec executor.Executor, communityString string, family ipfamily.Family) (map[string]bgpfrr.Route, error) {
+	// Parse c string to BGP c and determine if this is a legacy or large c.
+	c, err := community.New(communityString)
+	if err != nil {
+		return nil, err
+	}
+	communityType := "community"
+	if community.IsLarge(c) {
+		communityType = "large-community"
+	}
+
 	families := []string{family.String()}
 	if family == ipfamily.DualStack {
 		families = []string{ipfamily.IPv4.String(), ipfamily.IPv6.String()}
@@ -105,9 +116,9 @@ func RoutesForCommunity(exec executor.Executor, community string, family ipfamil
 
 	routes := map[string]bgpfrr.Route{}
 	for _, f := range families {
-		res, err := exec.Exec("vtysh", "-c", fmt.Sprintf("show bgp %s community %s json", f, community))
+		res, err := exec.Exec("vtysh", "-c", fmt.Sprintf("show bgp %s %s %s json", f, communityType, c))
 		if err != nil {
-			return nil, errors.Wrapf(err, "Failed to query routes for family %s community %s", f, community)
+			return nil, errors.Wrapf(err, "Failed to query routes for family %s %s %s", f, communityType, c)
 		}
 
 		r, err := bgpfrr.ParseRoutes(res)
diff --git a/e2etest/webhookstests/webhooks.go b/e2etest/webhookstests/webhooks.go
index a160e01c..7acde7cc 100644
--- a/e2etest/webhookstests/webhooks.go
+++ b/e2etest/webhookstests/webhooks.go
@@ -224,7 +224,7 @@ var _ = ginkgo.Describe("Webhooks", func() {
 	})
 
 	ginkgo.Context("For Community", func() {
-		ginkgo.It("Should reject a new invalid Community", func() {
+		ginkgo.DescribeTable("reject a new invalid Community", func(community, expectedError string) {
 			ginkgo.By("Creating invalid Community")
 			resources := metallbconfig.ClusterResources{
 				Communities: []metallbv1beta1.Community{
@@ -236,7 +236,7 @@ var _ = ginkgo.Describe("Webhooks", func() {
 							Communities: []metallbv1beta1.CommunityAlias{
 								{
 									Name:  "INVALID_COMMUNITY",
-									Value: "99999999:1",
+									Value: community,
 								},
 							},
 						},
@@ -245,10 +245,12 @@ var _ = ginkgo.Describe("Webhooks", func() {
 			}
 			err := ConfigUpdater.Update(resources)
 			framework.ExpectError(err)
-			Expect(err.Error()).To(ContainSubstring("invalid first section of community"))
-		})
+			Expect(err.Error()).To(ContainSubstring(expectedError))
+		},
+			ginkgo.Entry("in legacy format", "99999999:1", "invalid community value: invalid section"),
+			ginkgo.Entry("in large format", "lar:123:9999:123", "expected community to be of format large"))
 
-		ginkgo.It("Should reject an update to an invalid Community", func() {
+		ginkgo.DescribeTable("reject an update to an invalid Community", func(community, expectedError string) {
 			ginkgo.By("Creating Community")
 			resources := metallbconfig.ClusterResources{
 				Communities: []metallbv1beta1.Community{
@@ -267,16 +269,18 @@ var _ = ginkgo.Describe("Webhooks", func() {
 				Communities: []metallbv1beta1.CommunityAlias{
 					{
 						Name:  "INVALID_COMMUNITY",
-						Value: "99999999:1",
+						Value: community,
 					},
 				},
 			}
 			err = ConfigUpdater.Update(resources)
 			framework.ExpectError(err)
-			Expect(err.Error()).To(ContainSubstring("invalid first section of community"))
-		})
+			Expect(err.Error()).To(ContainSubstring(expectedError))
+		},
+			ginkgo.Entry("in legacy format", "99999999:1", "invalid community value: invalid section"),
+			ginkgo.Entry("in large format", "lar:123:9999:123", "expected community to be of format large"))
 
-		ginkgo.It("Should reject Community duplications", func() {
+		ginkgo.DescribeTable("reject Community duplications", func(community string) {
 			ginkgo.By("Creating duplicates in the same Community")
 			resources := metallbconfig.ClusterResources{
 				Communities: []metallbv1beta1.Community{
@@ -288,11 +292,11 @@ var _ = ginkgo.Describe("Webhooks", func() {
 							Communities: []metallbv1beta1.CommunityAlias{
 								{
 									Name:  "DUP_COMMUNITY",
-									Value: "1111:2222",
+									Value: community,
 								},
 								{
 									Name:  "DUP_COMMUNITY",
-									Value: "1111:2222",
+									Value: community,
 								},
 							},
 						},
@@ -314,7 +318,7 @@ var _ = ginkgo.Describe("Webhooks", func() {
 							Communities: []metallbv1beta1.CommunityAlias{
 								{
 									Name:  "DUP_COMMUNITY",
-									Value: "1111:2222",
+									Value: community,
 								},
 							},
 						},
@@ -327,7 +331,7 @@ var _ = ginkgo.Describe("Webhooks", func() {
 							Communities: []metallbv1beta1.CommunityAlias{
 								{
 									Name:  "DUP_COMMUNITY",
-									Value: "1111:2222",
+									Value: community,
 								},
 							},
 						},
@@ -337,7 +341,9 @@ var _ = ginkgo.Describe("Webhooks", func() {
 			err = ConfigUpdater.Update(resources)
 			framework.ExpectError(err)
 			Expect(err.Error()).To(ContainSubstring("duplicate definition of community"))
-		})
+		},
+			ginkgo.Entry("in legacy format", "1111:2222"),
+			ginkgo.Entry("FRR - in large format", "large:123:9999:123"))
 	})
 
 	ginkgo.Context("For BFDProfile", func() {
diff --git a/internal/bgp/community/community_test.go b/internal/bgp/community/community_test.go
new file mode 100644
index 00000000..2acef146
--- /dev/null
+++ b/internal/bgp/community/community_test.go
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package community
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestNewBGPCommunity(t *testing.T) {
+	tcs := map[string]struct {
+		input       string
+		output      BGPCommunity
+		errorString string
+	}{
+		"valid legacy community in new-format": {
+			input:  "12345:12345",
+			output: BGPCommunityLegacy{upperVal: 12345, lowerVal: 12345},
+		},
+		"legacy community in old-format not supported": {
+			input:       "12345",
+			errorString: "invalid community format: 12345",
+		},
+		"valid large community": {
+			input: "large:12345:12345:12345",
+			output: BGPCommunityLarge{
+				globalAdministrator: 12345,
+				localDataPart1:      12345,
+				localDataPart2:      12345,
+			},
+		},
+		"invalid large community 1": {
+			input:       "larg:12345:12345:12345",
+			errorString: "invalid community value: invalid marker for large community",
+		},
+		"invalid large community 2": {
+			input:       "large:12345:wrong:12345",
+			errorString: "invalid community value: invalid section",
+		},
+		"invalid extended community not yet implemented": {
+			input:       "12345:12345:12345",
+			errorString: "invalid community format: 12345:12345:12345",
+		},
+	}
+	for d, tc := range tcs {
+		c, err := New(tc.input)
+		if tc.errorString != "" {
+			if err == nil {
+				t.Fatalf("%s(%s): Expected to see an error but got <nil> instead", t.Name(), d)
+			}
+			if !strings.Contains(err.Error(), tc.errorString) {
+				t.Fatalf("%s(%s): Expected returned error to contain '%s', but got %q instead",
+					t.Name(), d, tc.errorString, err)
+			}
+			continue
+		}
+		if err != nil {
+			t.Fatalf("%s(%s): Expected to see no error, but got %q instead", t.Name(), d, err)
+		}
+		if c != tc.output {
+			t.Fatalf("%s(%s): Parsed community and expected community do not match.\n"+
+				"Expected:\n%v\nGot:\n%v\n", t.Name(), d, tc.output, c)
+		}
+	}
+}
+
+func TestBGPCommunityLessThan(t *testing.T) {
+	tcs := map[string]struct {
+		left            string
+		right           string
+		expectedOutcome bool
+	}{
+		"compares legacy communities":           {left: "0:1234", right: "0:2345", expectedOutcome: true},
+		"compares large communities 1":          {left: "large:1234:0:0", right: "large:1234:0:1", expectedOutcome: true},
+		"compares large communities 2":          {left: "large:1235:0:0", right: "large:1234:1:0", expectedOutcome: false},
+		"compares legacy and large communities": {left: "0:1234", right: "large:123:456:789", expectedOutcome: false},
+	}
+	for d, tc := range tcs {
+		leftCommunity, _ := New(tc.left)
+		rightCommunity, _ := New(tc.right)
+		if leftCommunity.LessThan(rightCommunity) != tc.expectedOutcome {
+			t.Fatalf("%s(%s): did not get expected outcome %t when comparing communities %q and %q",
+				t.Name(), d, tc.expectedOutcome, tc.left, tc.right)
+		}
+	}
+}
+
+func TestBGPCommunityString(t *testing.T) {
+	tcs := map[string]struct {
+		input  string
+		output string
+	}{
+		"legacy community": {input: "0:1234", output: "0:1234"},
+		"large community":  {input: "large:1:2:3", output: "1:2:3"},
+	}
+	for d, tc := range tcs {
+		community, _ := New(tc.input)
+		if community.String() != tc.output {
+			t.Fatalf("%s(%s): did not get expected output string %q, got: %q", t.Name(), d, tc.output, community)
+		}
+	}
+}
diff --git a/internal/bgp/frr/frr_test.go b/internal/bgp/frr/frr_test.go
index b3526274..8a9e4244 100644
--- a/internal/bgp/frr/frr_test.go
+++ b/internal/bgp/frr/frr_test.go
@@ -15,7 +15,7 @@ import (
 
 	"github.com/go-kit/log"
 	"go.universe.tf/metallb/internal/bgp"
-	"go.universe.tf/metallb/internal/config"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/logging"
 	"k8s.io/apimachinery/pkg/util/wait"
 )
@@ -473,11 +473,11 @@ func TestSingleAdvertisement(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
-	communities = append(communities, community)
-	community, _ = config.ParseCommunity("3333:4444")
-	communities = append(communities, community)
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("1111:2222")
+	communities = append(communities, community1)
+	community2, _ := community.New("3333:4444")
+	communities = append(communities, community2)
 	adv := &bgp.Advertisement{
 		Prefix:      prefix,
 		Communities: communities,
@@ -693,11 +693,11 @@ func TestSingleAdvertisementWithPeerSelector(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
-	communities = append(communities, community)
-	community, _ = config.ParseCommunity("3333:4444")
-	communities = append(communities, community)
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("1111:2222")
+	communities = append(communities, community1)
+	community2, _ := community.New("3333:4444")
+	communities = append(communities, community2)
 	adv := &bgp.Advertisement{
 		Prefix:      prefix,
 		Communities: communities,
@@ -741,11 +741,11 @@ func TestSingleAdvertisementNonExistingPeer(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
-	communities = append(communities, community)
-	community, _ = config.ParseCommunity("3333:4444")
-	communities = append(communities, community)
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("1111:2222")
+	communities = append(communities, community1)
+	community2, _ := community.New("3333:4444")
+	communities = append(communities, community2)
 	adv := &bgp.Advertisement{
 		Prefix:      prefix,
 		Communities: communities,
@@ -789,8 +789,8 @@ func TestTwoAdvertisements(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -860,8 +860,8 @@ func TestTwoAdvertisementsTwoSessions(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -937,8 +937,8 @@ func TestTwoAdvertisementsTwoSessionsOneWithPeerSelector(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -1052,3 +1052,52 @@ func TestLoggingConfigurationOverrideByEnvironmentVar(t *testing.T) {
 	sessionManager.reloadConfig <- reloadEvent{config: config}
 	testCheckConfigFile(t)
 }
+
+func TestLargeCommunities(t *testing.T) {
+	testSetup(t)
+
+	l := log.NewNopLogger()
+	sessionManager := mockNewSessionManager(l, logging.LevelInfo)
+	defer close(sessionManager.reloadConfig)
+	session, err := sessionManager.NewSession(l,
+		bgp.SessionParameters{
+			PeerAddress:   "10.2.2.254:179",
+			SourceAddress: net.ParseIP("10.1.1.254"),
+			MyASN:         100,
+			RouterID:      net.ParseIP("10.1.1.254"),
+			PeerASN:       200,
+			HoldTime:      time.Second,
+			KeepAliveTime: time.Second,
+			Password:      "password",
+			CurrentNode:   "hostname",
+			EBGPMultiHop:  true,
+			SessionName:   "test-peer"})
+	if err != nil {
+		t.Fatalf("Could not create session: %s", err)
+	}
+	defer session.Close()
+
+	prefix := &net.IPNet{
+		IP:   net.ParseIP("172.16.1.10"),
+		Mask: classCMask,
+	}
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("large:1111:2222:3333")
+	communities = append(communities, community1)
+	community2, _ := community.New("large:2222:3333:4444")
+	communities = append(communities, community2)
+	community3, _ := community.New("3333:4444")
+	communities = append(communities, community3)
+	adv := &bgp.Advertisement{
+		Prefix:      prefix,
+		Communities: communities,
+		LocalPref:   300,
+	}
+
+	err = session.Set(adv)
+	if err != nil {
+		t.Fatalf("Could not advertise prefix: %s", err)
+	}
+
+	testCheckConfigFile(t)
+}
diff --git a/internal/bgp/frr/frr_vrf_test.go b/internal/bgp/frr/frr_vrf_test.go
index 95d38dea..d092aef0 100644
--- a/internal/bgp/frr/frr_vrf_test.go
+++ b/internal/bgp/frr/frr_vrf_test.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/go-kit/log"
 	"go.universe.tf/metallb/internal/bgp"
-	"go.universe.tf/metallb/internal/config"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/logging"
 )
 
@@ -236,11 +236,11 @@ func TestSingleAdvertisementVRF(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
-	communities = append(communities, community)
-	community, _ = config.ParseCommunity("3333:4444")
-	communities = append(communities, community)
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("1111:2222")
+	communities = append(communities, community1)
+	community2, _ := community.New("3333:4444")
+	communities = append(communities, community2)
 	adv := &bgp.Advertisement{
 		Prefix:      prefix,
 		Communities: communities,
@@ -341,11 +341,11 @@ func TestSingleAdvertisementWithPeerSelectorVRF(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
-	communities = append(communities, community)
-	community, _ = config.ParseCommunity("3333:4444")
-	communities = append(communities, community)
+	communities := []community.BGPCommunity{}
+	community1, _ := community.New("1111:2222")
+	communities = append(communities, community1)
+	community2, _ := community.New("3333:4444")
+	communities = append(communities, community2)
 	adv := &bgp.Advertisement{
 		Prefix:      prefix,
 		Communities: communities,
@@ -390,8 +390,8 @@ func TestTwoAdvertisementsVRF(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -462,8 +462,8 @@ func TestTwoAdvertisementsTwoSessionsOneVRF(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -541,8 +541,8 @@ func TestTwoAdvertisementsTwoSessionsOneWithPeerSelectorAndVRF(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
@@ -622,8 +622,8 @@ func TestTwoAdvertisementsTwoSessionsVRFWithPeerSelector(t *testing.T) {
 		IP:   net.ParseIP("172.16.1.10"),
 		Mask: classCMask,
 	}
-	communities := []uint32{}
-	community, _ := config.ParseCommunity("1111:2222")
+	communities := []community.BGPCommunity{}
+	community, _ := community.New("1111:2222")
 	communities = append(communities, community)
 	adv1 := &bgp.Advertisement{
 		Prefix:      prefix1,
diff --git a/internal/bgp/frr/testdata/TestLargeCommunities.golden b/internal/bgp/frr/testdata/TestLargeCommunities.golden
new file mode 100644
index 00000000..33b1141f
--- /dev/null
+++ b/internal/bgp/frr/testdata/TestLargeCommunities.golden
@@ -0,0 +1,66 @@
+log file /etc/frr/frr.log 
+log timestamp precision 3
+hostname dummyhostname
+ip nht resolve-via-default
+ipv6 nht resolve-via-default
+route-map 10.2.2.254-in deny 20
+
+ip prefix-list 10.2.2.254-300-ipv4-localpref-prefixes permit 172.16.1.10/24
+route-map 10.2.2.254-out permit 1
+  match ip address prefix-list 10.2.2.254-300-ipv4-localpref-prefixes
+  set local-preference 300
+  on-match next
+ip prefix-list 10.2.2.254-3333:4444-ipv4-community-prefixes permit 172.16.1.10/24
+route-map 10.2.2.254-out permit 2
+  match ip address prefix-list 10.2.2.254-3333:4444-ipv4-community-prefixes
+  set community 3333:4444 additive
+  on-match next
+ip prefix-list 10.2.2.254-large:1111:2222:3333-ipv4-community-prefixes permit 172.16.1.10/24
+route-map 10.2.2.254-out permit 3
+  match ip address prefix-list 10.2.2.254-large:1111:2222:3333-ipv4-community-prefixes
+  set large-community 1111:2222:3333 additive
+  on-match next
+ip prefix-list 10.2.2.254-large:2222:3333:4444-ipv4-community-prefixes permit 172.16.1.10/24
+route-map 10.2.2.254-out permit 4
+  match ip address prefix-list 10.2.2.254-large:2222:3333:4444-ipv4-community-prefixes
+  set large-community 2222:3333:4444 additive
+  on-match next
+
+ip prefix-list 10.2.2.254-pl-ipv4 permit 172.16.1.10/24
+
+route-map 10.2.2.254-out permit 5
+  match ip address prefix-list 10.2.2.254-pl-ipv4
+route-map 10.2.2.254-out permit 6
+  match ipv6 address prefix-list 10.2.2.254-pl-ipv4
+
+
+ipv6 prefix-list 10.2.2.254-pl-ipv4 deny any
+
+router bgp 100
+  no bgp ebgp-requires-policy
+  no bgp network import-check
+  no bgp default ipv4-unicast
+
+  bgp router-id 10.1.1.254
+  neighbor 10.2.2.254 remote-as 200
+  neighbor 10.2.2.254 ebgp-multihop
+  neighbor 10.2.2.254 port 179
+  neighbor 10.2.2.254 timers 1 1
+  neighbor 10.2.2.254 password password
+  neighbor 10.2.2.254 update-source 10.1.1.254
+
+  address-family ipv4 unicast
+    neighbor 10.2.2.254 activate
+    neighbor 10.2.2.254 route-map 10.2.2.254-in in
+    neighbor 10.2.2.254 route-map 10.2.2.254-out out
+  exit-address-family
+  address-family ipv6 unicast
+    neighbor 10.2.2.254 activate
+    neighbor 10.2.2.254 route-map 10.2.2.254-in in
+    neighbor 10.2.2.254 route-map 10.2.2.254-out out
+  exit-address-family
+  address-family ipv4 unicast
+    network 172.16.1.10/24
+  exit-address-family
+
+
diff --git a/internal/bgp/native/messages_test.go b/internal/bgp/native/messages_test.go
index 4728023b..2815b943 100644
--- a/internal/bgp/native/messages_test.go
+++ b/internal/bgp/native/messages_test.go
@@ -7,8 +7,12 @@ import (
 	"net"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 	"time"
+
+	"go.universe.tf/metallb/internal/bgp"
+	"go.universe.tf/metallb/internal/bgp/community"
 )
 
 // Just test that sendOpen and readOpen can at least talk to each other.
@@ -94,3 +98,67 @@ func TestOpenFourByteASN(t *testing.T) {
 		}
 	}
 }
+
+// TestSendUpdate makes sure that sendUpdate fails if large communities are provided. The E2E tests take care of
+// testing further functionality. A decodeUpdate method would be needed for a more complete unit test.
+func TestSendUpdate(t *testing.T) {
+	tcs := map[string]struct {
+		asn         uint32
+		ibgp        bool
+		fbasn       bool
+		nextHop     net.IP
+		adv         *bgp.Advertisement
+		errorString string
+	}{
+		"send update with legacy communities should succeed": {
+			asn:     65000,
+			ibgp:    false,
+			fbasn:   false,
+			nextHop: net.ParseIP("192.168.123.10"),
+			adv: &bgp.Advertisement{
+				Prefix: func() *net.IPNet {
+					_, ipnet, _ := net.ParseCIDR("172.16.0.0/24")
+					return ipnet
+				}(),
+				LocalPref: 100,
+				Communities: func() []community.BGPCommunity {
+					c1, _ := community.New("0:1234")
+					c2, _ := community.New("0:2345")
+					return []community.BGPCommunity{c1, c2}
+				}(),
+				Peers: []string{},
+			},
+			errorString: "",
+		},
+		"send update with large communities should result in an error": {
+			asn:     65000,
+			ibgp:    false,
+			fbasn:   false,
+			nextHop: net.ParseIP("192.168.123.10"),
+			adv: &bgp.Advertisement{
+				Prefix: func() *net.IPNet {
+					_, ipnet, _ := net.ParseCIDR("172.16.0.0/24")
+					return ipnet
+				}(),
+				LocalPref: 100,
+				Communities: func() []community.BGPCommunity {
+					c1, _ := community.New("0:1234")
+					c2, _ := community.New("large:123:234:567")
+					return []community.BGPCommunity{c1, c2}
+				}(),
+				Peers: []string{},
+			},
+			errorString: "invalid community type for BGP native mode",
+		},
+	}
+	for d, tc := range tcs {
+		var b bytes.Buffer
+		err := sendUpdate(&b, tc.asn, tc.ibgp, tc.fbasn, tc.nextHop, tc.adv)
+		if tc.errorString == "" && err != nil {
+			t.Fatalf("%s(%s): send update, err: %q", t.Name(), d, err)
+		}
+		if tc.errorString != "" && (err == nil || !strings.Contains(err.Error(), tc.errorString)) {
+			t.Fatalf("%s(%s): send update expected to see error %q but got %q instead", t.Name(), d, tc.errorString, err)
+		}
+	}
+}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index edc53a26..0c04fe23 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"go.universe.tf/metallb/api/v1beta1"
 	"go.universe.tf/metallb/api/v1beta2"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/pointer"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -255,9 +256,12 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities: map[uint32]bool{
-									0xfc0004d2: true,
-								},
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("64512:1234")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
 								Nodes: map[string]bool{},
 								Peers: []string{"peer1"},
 							},
@@ -265,7 +269,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv2",
 								AggregationLength:   24,
 								AggregationLengthV6: 64,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -283,7 +287,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv3",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -1019,7 +1023,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv3",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -1061,7 +1065,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv3",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -1248,14 +1252,14 @@ func TestParse(t *testing.T) {
 								AggregationLength:   24,
 								AggregationLengthV6: 120,
 								LocalPref:           100,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node1": true, "node2": true},
 							},
 							{
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           200,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node1": true, "node2": true},
 							},
 						},
@@ -1328,14 +1332,14 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node1": true},
 							},
 							{
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           200,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node2": true},
 							},
 						},
@@ -1399,7 +1403,7 @@ func TestParse(t *testing.T) {
 								AggregationLengthV6: 128,
 								LocalPref:           100,
 								Peers:               []string{"peer1"},
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node1": true, "node2": true},
 							},
 							{
@@ -1407,7 +1411,7 @@ func TestParse(t *testing.T) {
 								AggregationLengthV6: 128,
 								LocalPref:           200,
 								Peers:               []string{"peer2"},
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"node1": true, "node2": true},
 							},
 						},
@@ -1462,7 +1466,7 @@ func TestParse(t *testing.T) {
 							{
 								AggregationLength:   26,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -1872,7 +1876,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv3",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -2116,7 +2120,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv3",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -2297,7 +2301,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -2423,9 +2427,12 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities: map[uint32]bool{
-									0x04D20929: true,
-								},
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("1234:2345")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
 								Nodes: map[string]bool{},
 							},
 						},
@@ -2438,9 +2445,12 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities: map[uint32]bool{
-									0x04D20929: true,
-								},
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("1234:2345")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
 								Nodes: map[string]bool{},
 							},
 						},
@@ -2509,9 +2519,213 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities: map[uint32]bool{
-									0xfc0004d2: true,
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("64512:1234")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
+								Nodes: map[string]bool{},
+							},
+						},
+					},
+				}},
+				BFDProfiles: map[string]*BFDProfile{},
+				Peers:       map[string]*Peer{},
+			},
+		},
+		{
+			desc: "config IPAddressPool with large communities CR",
+			crs: ClusterResources{
+				Peers: []v1beta2.BGPPeer{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "peer1",
+						},
+						Spec: v1beta2.BGPPeerSpec{
+							MyASN:        42,
+							ASN:          142,
+							Address:      "1.2.3.4",
+							Port:         1179,
+							HoldTime:     metav1.Duration{Duration: 180 * time.Second},
+							RouterID:     "10.20.30.40",
+							SrcAddress:   "10.20.30.40",
+							EBGPMultiHop: true,
+							VRFName:      "foo",
+						},
+					},
+				},
+				Pools: []v1beta1.IPAddressPool{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "pool1",
+						},
+						Spec: v1beta1.IPAddressPoolSpec{
+							Addresses: []string{
+								"10.20.0.0/16",
+								"10.50.0.0/24",
+							},
+							AvoidBuggyIPs: true,
+							AutoAssign:    pointer.BoolPtr(false),
+						},
+					},
+				},
+				BGPAdvs: []v1beta1.BGPAdvertisement{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "adv1",
+						},
+						Spec: v1beta1.BGPAdvertisementSpec{
+							AggregationLength: pointer.Int32Ptr(32),
+							LocalPref:         uint32(100),
+							Communities:       []string{"bar"},
+							IPAddressPools:    []string{"pool1"},
+							Peers:             []string{"peer1"},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "adv2",
+						},
+						Spec: v1beta1.BGPAdvertisementSpec{
+							AggregationLength:   pointer.Int32Ptr(24),
+							AggregationLengthV6: pointer.Int32Ptr(64),
+							IPAddressPools:      []string{"pool1"},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "adv3",
+						},
+						Spec: v1beta1.BGPAdvertisementSpec{
+							IPAddressPools: []string{"pool2"},
+						},
+					},
+				},
+				Communities: []v1beta1.Community{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "community",
+						},
+						Spec: v1beta1.CommunitySpec{
+							Communities: []v1beta1.CommunityAlias{
+								{
+									Name:  "bar",
+									Value: "large:123:64512:1234",
+								},
+							},
+						},
+					},
+				},
+			},
+			want: &Config{
+				Peers: map[string]*Peer{
+					"peer1": {
+						Name:          "peer1",
+						MyASN:         42,
+						ASN:           142,
+						Addr:          net.ParseIP("1.2.3.4"),
+						SrcAddr:       net.ParseIP("10.20.30.40"),
+						Port:          1179,
+						HoldTime:      180 * time.Second,
+						KeepaliveTime: 60 * time.Second,
+						RouterID:      net.ParseIP("10.20.30.40"),
+						NodeSelectors: []labels.Selector{labels.Everything()},
+						EBGPMultiHop:  true,
+						VRF:           "foo",
+					},
+				},
+				Pools: &Pools{ByName: map[string]*Pool{
+					"pool1": {
+						Name:          "pool1",
+						CIDR:          []*net.IPNet{ipnet("10.20.0.0/16"), ipnet("10.50.0.0/24")},
+						AvoidBuggyIPs: true,
+						AutoAssign:    false,
+						BGPAdvertisements: []*BGPAdvertisement{
+							{
+								Name:                "adv1",
+								AggregationLength:   32,
+								AggregationLengthV6: 128,
+								LocalPref:           100,
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("large:123:64512:1234")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
+								Nodes: map[string]bool{},
+								Peers: []string{"peer1"},
+							},
+							{
+								Name:                "adv2",
+								AggregationLength:   24,
+								AggregationLengthV6: 64,
+								Communities:         map[community.BGPCommunity]bool{},
+								Nodes:               map[string]bool{},
+							},
+						},
+					},
+				}},
+				BFDProfiles: map[string]*BFDProfile{},
+			},
+		},
+		{
+			desc: "config legacy pool with BGP Communities CR and large communities",
+			crs: ClusterResources{
+				LegacyAddressPools: []v1beta1.AddressPool{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "legacybgppool1",
+						},
+						Spec: v1beta1.AddressPoolSpec{
+							Addresses: []string{
+								"10.40.0.0/16",
+								"10.60.0.0/24",
+							},
+							Protocol:   string(BGP),
+							AutoAssign: pointer.BoolPtr(false),
+							BGPAdvertisements: []v1beta1.LegacyBgpAdvertisement{
+								{
+									AggregationLength: pointer.Int32Ptr(32),
+									LocalPref:         uint32(100),
+									Communities:       []string{"bar"},
+								},
+							},
+						},
+					},
+				},
+				Communities: []v1beta1.Community{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "community",
+						},
+						Spec: v1beta1.CommunitySpec{
+							Communities: []v1beta1.CommunityAlias{
+								{
+									Name:  "bar",
+									Value: "large:123:64512:1234",
 								},
+							},
+						},
+					},
+				},
+			},
+			want: &Config{
+				Pools: &Pools{ByName: map[string]*Pool{
+					"legacybgppool1": {
+						Name: "legacybgppool1",
+						CIDR: []*net.IPNet{ipnet("10.40.0.0/16"), ipnet("10.60.0.0/24")},
+						BGPAdvertisements: []*BGPAdvertisement{
+							{
+								AggregationLength:   32,
+								AggregationLengthV6: 128,
+								LocalPref:           100,
+								Communities: func() map[community.BGPCommunity]bool {
+									c, _ := community.New("large:123:64512:1234")
+									return map[community.BGPCommunity]bool{
+										c: true,
+									}
+								}(),
 								Nodes: map[string]bool{},
 							},
 						},
@@ -2658,7 +2872,7 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -2677,7 +2891,7 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           200,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 							},
 						},
@@ -3087,7 +3301,7 @@ func TestParse(t *testing.T) {
 							{
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes: map[string]bool{
 									"first":  true,
 									"second": true,
@@ -3117,7 +3331,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv1",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"second": true},
 							},
 						},
@@ -3137,7 +3351,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv2",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{"first": true},
 							},
 						},
@@ -3365,7 +3579,7 @@ func TestParse(t *testing.T) {
 								Name:                "adv1",
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes: map[string]bool{
 									"first":  true,
 									"second": true,
@@ -3450,7 +3664,7 @@ func TestParse(t *testing.T) {
 								AggregationLength:   32,
 								AggregationLengthV6: 128,
 								LocalPref:           100,
-								Communities:         map[uint32]bool{},
+								Communities:         map[community.BGPCommunity]bool{},
 								Nodes:               map[string]bool{},
 								Peers:               []string{"peer1"},
 							},
diff --git a/internal/config/validation_test.go b/internal/config/validation_test.go
index eaa9fbed..b287ea79 100644
--- a/internal/config/validation_test.go
+++ b/internal/config/validation_test.go
@@ -82,6 +82,53 @@ func TestValidate(t *testing.T) {
 			},
 			mustFail: true,
 		},
+		{
+			desc: "large BGP community inside legacy pool",
+			config: ClusterResources{
+				LegacyAddressPools: []v1beta1.AddressPool{
+					{
+						Spec: v1beta1.AddressPoolSpec{
+							BGPAdvertisements: []v1beta1.LegacyBgpAdvertisement{
+								{
+									Communities: []string{"large:123:456:789"},
+								},
+							},
+						},
+					},
+				},
+			},
+			mustFail: true,
+		},
+		{
+			desc: "large BGP community inside BGP Advertisement",
+			config: ClusterResources{
+				BGPAdvs: []v1beta1.BGPAdvertisement{
+					{
+						Spec: v1beta1.BGPAdvertisementSpec{
+							Communities: []string{"large:123:456:789"},
+						},
+					},
+				},
+			},
+			mustFail: true,
+		},
+		{
+			desc: "large BGP community inside Community CR",
+			config: ClusterResources{
+				Communities: []v1beta1.Community{
+					{
+						Spec: v1beta1.CommunitySpec{
+							Communities: []v1beta1.CommunityAlias{
+								{
+									Value: "large:123:456:789",
+								},
+							},
+						},
+					},
+				},
+			},
+			mustFail: true,
+		},
 		{
 			desc: "should pass",
 			config: ClusterResources{
diff --git a/speaker/bgp_controller_test.go b/speaker/bgp_controller_test.go
index a1a10833..af324184 100644
--- a/speaker/bgp_controller_test.go
+++ b/speaker/bgp_controller_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	"go.universe.tf/metallb/internal/bgp"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/config"
 	"go.universe.tf/metallb/internal/k8s/controllers"
 	"go.universe.tf/metallb/internal/k8s/epslices"
@@ -73,11 +74,11 @@ func sortAds(ads map[string][]*bgp.Advertisement) {
 			if len(a.Communities) != len(b.Communities) {
 				return len(a.Communities) < len(b.Communities)
 			}
-			sort.Slice(a.Communities, func(i, j int) bool { return a.Communities[i] < a.Communities[j] })
-			sort.Slice(b.Communities, func(i, j int) bool { return b.Communities[i] < b.Communities[j] })
+			sort.Slice(a.Communities, func(i, j int) bool { return a.Communities[i].LessThan(a.Communities[j]) })
+			sort.Slice(b.Communities, func(i, j int) bool { return b.Communities[i].LessThan(b.Communities[j]) })
 			for k := range a.Communities {
 				if a.Communities[k] != b.Communities[k] {
-					return a.Communities[k] < b.Communities[k]
+					return a.Communities[k].LessThan(b.Communities[k])
 				}
 			}
 			return false
@@ -586,8 +587,14 @@ func TestBGPSpeaker(t *testing.T) {
 							{
 								AggregationLength: 32,
 								LocalPref:         100,
-								Communities:       map[uint32]bool{1234: true, 2345: true},
-								Nodes:             map[string]bool{"pandora": true},
+								Communities: func() map[community.BGPCommunity]bool {
+									community1, _ := community.New("0:1234")
+									community2, _ := community.New("0:2345")
+									community3, _ := community.New("large:123:456:789")
+									return map[community.BGPCommunity]bool{community1: true, community2: true,
+										community3: true}
+								}(),
+								Nodes: map[string]bool{"pandora": true},
 							},
 							{
 								AggregationLength: 24,
@@ -624,9 +631,14 @@ func TestBGPSpeaker(t *testing.T) {
 			wantAds: map[string][]*bgp.Advertisement{
 				"1.2.3.4:0": {
 					{
-						Prefix:      ipnet("10.20.30.1/32"),
-						LocalPref:   100,
-						Communities: []uint32{1234, 2345},
+						Prefix:    ipnet("10.20.30.1/32"),
+						LocalPref: 100,
+						Communities: func() []community.BGPCommunity {
+							community1, _ := community.New("0:1234")
+							community2, _ := community.New("0:2345")
+							community3, _ := community.New("large:123:456:789")
+							return []community.BGPCommunity{community3, community1, community2}
+						}(),
 					},
 					{
 						Prefix:    ipnet("10.20.30.0/24"),
@@ -653,8 +665,12 @@ func TestBGPSpeaker(t *testing.T) {
 							{
 								AggregationLength: 32,
 								LocalPref:         100,
-								Communities:       map[uint32]bool{1234: true, 2345: true},
-								Nodes:             map[string]bool{"pandora": true},
+								Communities: func() map[community.BGPCommunity]bool {
+									community1, _ := community.New("0:1234")
+									community2, _ := community.New("0:2345")
+									return map[community.BGPCommunity]bool{community1: true, community2: true}
+								}(),
+								Nodes: map[string]bool{"pandora": true},
 							},
 							{
 								AggregationLength: 24,
@@ -691,9 +707,13 @@ func TestBGPSpeaker(t *testing.T) {
 			wantAds: map[string][]*bgp.Advertisement{
 				"1.2.3.4:0": {
 					{
-						Prefix:      ipnet("10.20.30.1/32"),
-						LocalPref:   100,
-						Communities: []uint32{1234, 2345},
+						Prefix:    ipnet("10.20.30.1/32"),
+						LocalPref: 100,
+						Communities: func() []community.BGPCommunity {
+							community1, _ := community.New("0:1234")
+							community2, _ := community.New("0:2345")
+							return []community.BGPCommunity{community1, community2}
+						}(),
 					},
 				},
 			},
@@ -718,9 +738,13 @@ func TestBGPSpeaker(t *testing.T) {
 							{
 								AggregationLength: 32,
 								LocalPref:         100,
-								Communities:       map[uint32]bool{1234: true, 2345: true},
-								Peers:             []string{"peer1"},
-								Nodes:             map[string]bool{"pandora": true},
+								Communities: func() map[community.BGPCommunity]bool {
+									community1, _ := community.New("0:1234")
+									community2, _ := community.New("0:2345")
+									return map[community.BGPCommunity]bool{community1: true, community2: true}
+								}(),
+								Peers: []string{"peer1"},
+								Nodes: map[string]bool{"pandora": true},
 							},
 							{
 								AggregationLength: 24,
@@ -757,10 +781,14 @@ func TestBGPSpeaker(t *testing.T) {
 			wantAds: map[string][]*bgp.Advertisement{
 				"1.2.3.4:0": {
 					{
-						Prefix:      ipnet("10.20.30.1/32"),
-						LocalPref:   100,
-						Communities: []uint32{1234, 2345},
-						Peers:       []string{"peer1"},
+						Prefix:    ipnet("10.20.30.1/32"),
+						LocalPref: 100,
+						Communities: func() []community.BGPCommunity {
+							community1, _ := community.New("0:1234")
+							community2, _ := community.New("0:2345")
+							return []community.BGPCommunity{community1, community2}
+						}(),
+						Peers: []string{"peer1"},
 					},
 					{
 						Prefix:    ipnet("10.20.30.0/24"),
@@ -1493,8 +1521,12 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 							{
 								AggregationLength: 32,
 								LocalPref:         100,
-								Communities:       map[uint32]bool{1234: true, 2345: true},
-								Nodes:             map[string]bool{"pandora": true},
+								Communities: func() map[community.BGPCommunity]bool {
+									community1, _ := community.New("0:1234")
+									community2, _ := community.New("0:2345")
+									return map[community.BGPCommunity]bool{community1: true, community2: true}
+								}(),
+								Nodes: map[string]bool{"pandora": true},
 							},
 							{
 								AggregationLength: 24,
@@ -1534,9 +1566,13 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 			wantAds: map[string][]*bgp.Advertisement{
 				"1.2.3.4:0": {
 					{
-						Prefix:      ipnet("10.20.30.1/32"),
-						LocalPref:   100,
-						Communities: []uint32{1234, 2345},
+						Prefix:    ipnet("10.20.30.1/32"),
+						LocalPref: 100,
+						Communities: func() []community.BGPCommunity {
+							community1, _ := community.New("0:1234")
+							community2, _ := community.New("0:2345")
+							return []community.BGPCommunity{community1, community2}
+						}(),
 					},
 					{
 						Prefix:    ipnet("10.20.30.0/24"),