Skip to content

Commit

Permalink
vrf: Clones VPN paths with different RD and matched RTs
Browse files Browse the repository at this point in the history
When VPN routes are shared between different VPNs (or VRFs) for example,
the RDs assigned for each VRF can differ from others, but the same RT
should be used. In this case, the duplicated IP unicast routes can be
selected as the best path because VPN prefix "<RD>:<Prefix>" is
different and GoBGP calculates the best path on the global table
context. For example,

$ gobgp vrf add 1 rd 1:1 rt both 1:1
$ gobgp global rib -a vpnv4 add 10.0.0.0/24 label 0 rd 1:1 rt 1:1
$ gobgp global rib -a vpnv4 add 10.0.0.0/24 label 0 rd 2:2 rt 1:1
$ gobgp global rib -a vpnv4
   Network              Labels     Next Hop             AS_PATH              Age        Attrs
*> 1:1:10.0.0.0/24      [0]        0.0.0.0                                   00:00:00   [{Origin: ?} {Extcomms: [1:1]}]
*> 2:2:10.0.0.0/24      [0]        0.0.0.0                                   00:00:00   [{Origin: ?} {Extcomms: [1:1]}]
$ gobgp vrf 1 rib -a ipv4
   Network              Next Hop             AS_PATH              Age        Attrs
   10.0.0.0/24          0.0.0.0                                   00:00:10   [{Origin: ?}]  <--- RD 1:1 on global
   10.0.0.0/24          0.0.0.0                                   00:00:10   [{Origin: ?}]  <--- RD 2:2 on global

The both of the above VRF routes are selected as the best path
unexpectedly.

This patch clones the received VPN path before installing the global
table and overwrites the RD of the cloned paths by the RD of the matched
VRFs. Also filters out the duplicated path on each VRF.

$ gobgp vrf add 1 rd 1:1 rt both 1:1
$ gobgp global rib -a vpnv4 add 10.0.0.0/24 label 0 rd 2:2 rt 1:1
$ gobgp global rib -a vpnv4
   Network              Labels     Next Hop             AS_PATH              Age        Attrs
*> 1:1:10.0.0.0/24      [0]        0.0.0.0                                   00:00:00   [{Origin: ?} {Extcomms: [1:1]}]
*> 2:2:10.0.0.0/24      [0]        0.0.0.0                                   00:00:00   [{Origin: ?} {Extcomms: [1:1]}]
$ gobgp vrf 1 rib -a ipv4
   Network              Next Hop             AS_PATH              Age        Attrs
   10.0.0.0/24          0.0.0.0                                   00:00:10   [{Origin: ?}]
$ gobgp global rib -a vpnv4 del 10.0.0.0/24 label 0 rd 2:2
$ gobgp vrf 1 rib -a ipv4
Network not in table
$ gobgp global rib -a vpnv4
Network not in table

Note: This feature only takes effect for only MPLS VPNv4/v6 routes and
does not have any for other address families which have RD on its NLRI
(e.g., EVPN, FlowSpec for VPN).

Signed-off-by: IWASE Yusuke <[email protected]>
  • Loading branch information
iwaseyusuke committed May 11, 2018
1 parent 2b0f16d commit 8d0fb84
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 4 deletions.
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ func (server *BgpServer) notifyBestWatcher(best []*table.Path, multipath [][]*ta
switch p.GetRouteFamily() {
case bgp.RF_IPv4_VPN, bgp.RF_IPv6_VPN:
for _, vrf := range server.globalRib.Vrfs {
if vrf.Id != 0 && table.CanImportToVrf(vrf, p) {
if vrf.Id != 0 && vrf.IsImported(p) {
m[p.GetNlri().String()] = uint16(vrf.Id)
}
}
Expand Down
7 changes: 6 additions & 1 deletion table/destination.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ func (lhs *PeerInfo) Equal(rhs *PeerInfo) bool {
return false
}

if lhs.Address == nil && rhs.Address == nil {
// The both peers are "local"
return true
}

if (lhs.AS == rhs.AS) && lhs.ID.Equal(rhs.ID) && lhs.LocalID.Equal(rhs.LocalID) && lhs.Address.Equal(rhs.Address) {
return true
}
Expand Down Expand Up @@ -996,7 +1001,7 @@ func (d *Destination) Select(option ...DestinationSelectOption) *Destination {
if vrf != nil {
ps := make([]*Path, 0, len(paths))
for _, p := range paths {
if CanImportToVrf(vrf, p) {
if vrf.IsImported(p) {
ps = append(ps, p.ToLocal())
}
}
Expand Down
26 changes: 25 additions & 1 deletion table/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ type originInfo struct {
stale bool
}

func (info *originInfo) Clone(nlri bgp.AddrPrefixInterface) *originInfo {
return &originInfo{
nlri: nlri,
source: info.source,
timestamp: info.timestamp,
validation: info.validation,
noImplicitWithdraw: info.noImplicitWithdraw,
isFromExternal: info.isFromExternal,
eor: info.eor,
stale: info.stale,
}
}

type RpkiValidationReasonType string

const (
Expand Down Expand Up @@ -335,9 +348,20 @@ func (path *Path) Clone(isWithdraw bool) *Path {
}
}

func (path *Path) CloneWithNewNlri(isWithdraw bool, nlri bgp.AddrPrefixInterface) *Path {
cloned := path.Clone(isWithdraw)
cloned.info = path.OriginInfo().Clone(nlri)
attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI)
if attr != nil {
oldNlri := attr.(*bgp.PathAttributeMpReachNLRI)
cloned.setPathAttr(bgp.NewPathAttributeMpReachNLRI(oldNlri.Nexthop.String(), []bgp.AddrPrefixInterface{nlri}))
}
return cloned
}

func (path *Path) root() *Path {
p := path
for p.parent != nil {
for p.info == nil && p.parent != nil {
p = p.parent
}
return p
Expand Down
57 changes: 56 additions & 1 deletion table/table_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,15 @@ func (manager *TableManager) Update(newPath *Path) []*Update {
return nil
}

// Except for a special case with EVPN, we'll have one destination.
// Except for a special case with MPLS-VPN and EVPN, we'll have one
// destination.
updates := make([]*Update, 0, 1)
family := newPath.GetRouteFamily()
if _, ok := manager.Tables[family]; ok {
for _, p := range manager.clonePathForVrf(newPath) {
updates = append(updates, manager.update(p))
}

updates = append(updates, manager.update(newPath))

if family == bgp.RF_EVPN {
Expand All @@ -226,6 +231,56 @@ func (manager *TableManager) Update(newPath *Path) []*Update {
return updates
}

// clonePathForVrf returns a cloned path list if the given path has RTs which
// match some import RTs on VRFs, but has the different RD with those VRFs. The
// returned paths have the same attributes other than the RD and whose RD is
// overwritten by the RD of the matched VRFs.
// Please note that the returned list does not include the original path and
// currently this function clones only VPNv4 or VPNv6 paths for MPLS VPN.
func (manager *TableManager) clonePathForVrf(path *Path) []*Path {
if path == nil {
return nil
}
var rdString string
var v4 *bgp.LabeledVPNIPAddrPrefix
var v6 *bgp.LabeledVPNIPv6AddrPrefix
switch nlri := path.GetNlri().(type) {
case *bgp.LabeledVPNIPAddrPrefix:
rdString = nlri.RD.String()
v4 = nlri
case *bgp.LabeledVPNIPv6AddrPrefix:
rdString = nlri.RD.String()
v6 = nlri
default:
return nil
}
if path.IsWithdraw {
// The withdrawn path can have no path attribute, then look up global
// table to get the path attributes from the existing path.
src := path.GetSource()
if dest := manager.GetDestination(path); dest != nil {
for _, p := range dest.knownPathList {
if src.Equal(p.GetSource()) {
path = p.Clone(true)
}
}
}
}
pathList := make([]*Path, 0, len(manager.Vrfs))
for _, vrf := range manager.Vrfs {
if CanImportToVrf(vrf, path) && vrf.Rd.String() != rdString {
var nlri bgp.AddrPrefixInterface
if v4 != nil {
nlri = bgp.NewLabeledVPNIPAddrPrefix(v4.IPPrefixLen(), v4.Prefix.String(), v4.Labels, vrf.Rd)
} else { // if v6 != nil
nlri = bgp.NewLabeledVPNIPv6AddrPrefix(v6.IPPrefixLen(), v6.Prefix.String(), v6.Labels, vrf.Rd)
}
pathList = append(pathList, path.CloneWithNewNlri(path.IsWithdraw, nlri))
}
}
return pathList
}

// EVPN MAC MOBILITY HANDLING
//
// RFC7432 15. MAC Mobility
Expand Down
18 changes: 18 additions & 0 deletions table/vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ func (v *Vrf) Clone() *Vrf {
}
}

func (v *Vrf) IsImported(path *Path) bool {
if path == nil {
return false
}
// When VPNv4/VPNv6 address family, a path having the different RD with the
// matched VRF might have cloned paths whose RD is overwritten by the RD
// of the matched VRF. Then, here compares the RD of the given path and
// this VRF instead of comparing the RTs of the given path and the import
// RTs of this VRF. If with other address families, compares the RTs.
switch nlri := path.GetNlri().(type) {
case *bgp.LabeledVPNIPAddrPrefix:
return v.Rd.String() == nlri.RD.String()
case *bgp.LabeledVPNIPv6AddrPrefix:
return v.Rd.String() == nlri.RD.String()
}
return CanImportToVrf(v, path)
}

func isLastTargetUser(vrfs map[string]*Vrf, target bgp.ExtendedCommunityInterface) bool {
for _, vrf := range vrfs {
for _, rt := range vrf.ImportRt {
Expand Down

0 comments on commit 8d0fb84

Please sign in to comment.