summaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/table/destination.go
diff options
context:
space:
mode:
authorFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 13:48:38 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 20:44:25 +0900
commitc4775c42510d1f1ddd55036dc19e982712fa6a0b (patch)
tree6ec8b61d4338c809e239e3003a2d32d480898e22 /internal/pkg/table/destination.go
parentb3079759aa13172fcb548a83da9a9653d8d5fed4 (diff)
follow Standard Go Project Layout
https://github.com/golang-standards/project-layout Now you can see clearly what are private and public library code. Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Diffstat (limited to 'internal/pkg/table/destination.go')
-rw-r--r--internal/pkg/table/destination.go1041
1 files changed, 1041 insertions, 0 deletions
diff --git a/internal/pkg/table/destination.go b/internal/pkg/table/destination.go
new file mode 100644
index 00000000..fa61572e
--- /dev/null
+++ b/internal/pkg/table/destination.go
@@ -0,0 +1,1041 @@
+// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package table
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "net"
+ "sort"
+
+ "github.com/osrg/gobgp/internal/pkg/config"
+ "github.com/osrg/gobgp/pkg/packet/bgp"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var SelectionOptions config.RouteSelectionOptionsConfig
+var UseMultiplePaths config.UseMultiplePathsConfig
+
+type BestPathReason uint8
+
+const (
+ BPR_UNKNOWN BestPathReason = iota
+ BPR_DISABLED
+ BPR_ONLY_PATH
+ BPR_REACHABLE_NEXT_HOP
+ BPR_HIGHEST_WEIGHT
+ BPR_LOCAL_PREF
+ BPR_LOCAL_ORIGIN
+ BPR_ASPATH
+ BPR_ORIGIN
+ BPR_MED
+ BPR_ASN
+ BPR_IGP_COST
+ BPR_ROUTER_ID
+ BPR_OLDER
+ BPR_NON_LLGR_STALE
+)
+
+var BestPathReasonStringMap = map[BestPathReason]string{
+ BPR_UNKNOWN: "Unknown",
+ BPR_DISABLED: "Bestpath selection disabled",
+ BPR_ONLY_PATH: "Only Path",
+ BPR_REACHABLE_NEXT_HOP: "Reachable Next Hop",
+ BPR_HIGHEST_WEIGHT: "Highest Weight",
+ BPR_LOCAL_PREF: "Local Pref",
+ BPR_LOCAL_ORIGIN: "Local Origin",
+ BPR_ASPATH: "AS Path",
+ BPR_ORIGIN: "Origin",
+ BPR_MED: "MED",
+ BPR_ASN: "ASN",
+ BPR_IGP_COST: "IGP Cost",
+ BPR_ROUTER_ID: "Router ID",
+ BPR_OLDER: "Older",
+ BPR_NON_LLGR_STALE: "no LLGR Stale",
+}
+
+func (r *BestPathReason) String() string {
+ return BestPathReasonStringMap[*r]
+}
+
+func IpToRadixkey(b []byte, max uint8) string {
+ var buffer bytes.Buffer
+ for i := 0; i < len(b) && i < int(max); i++ {
+ fmt.Fprintf(&buffer, "%08b", b[i])
+ }
+ return buffer.String()[:max]
+}
+
+func CidrToRadixkey(cidr string) string {
+ _, n, _ := net.ParseCIDR(cidr)
+ ones, _ := n.Mask.Size()
+ return IpToRadixkey(n.IP, uint8(ones))
+}
+
+func AddrToRadixkey(addr bgp.AddrPrefixInterface) string {
+ var (
+ ip net.IP
+ size uint8
+ )
+ switch T := addr.(type) {
+ case *bgp.IPAddrPrefix:
+ mask := net.CIDRMask(int(T.Length), net.IPv4len*8)
+ ip, size = T.Prefix.Mask(mask).To4(), uint8(T.Length)
+ case *bgp.IPv6AddrPrefix:
+ mask := net.CIDRMask(int(T.Length), net.IPv6len*8)
+ ip, size = T.Prefix.Mask(mask).To16(), uint8(T.Length)
+ default:
+ return CidrToRadixkey(addr.String())
+ }
+ return IpToRadixkey(ip, size)
+}
+
+type PeerInfo struct {
+ AS uint32
+ ID net.IP
+ LocalAS uint32
+ LocalID net.IP
+ Address net.IP
+ LocalAddress net.IP
+ RouteReflectorClient bool
+ RouteReflectorClusterID net.IP
+ MultihopTtl uint8
+ Confederation bool
+}
+
+func (lhs *PeerInfo) Equal(rhs *PeerInfo) bool {
+ if lhs == rhs {
+ return true
+ }
+
+ if rhs == nil {
+ return false
+ }
+
+ if (lhs.AS == rhs.AS) && lhs.ID.Equal(rhs.ID) && lhs.LocalID.Equal(rhs.LocalID) && lhs.Address.Equal(rhs.Address) {
+ return true
+ }
+ return false
+}
+
+func (i *PeerInfo) String() string {
+ if i.Address == nil {
+ return "local"
+ }
+ s := bytes.NewBuffer(make([]byte, 0, 64))
+ s.WriteString(fmt.Sprintf("{ %s | ", i.Address))
+ s.WriteString(fmt.Sprintf("as: %d", i.AS))
+ s.WriteString(fmt.Sprintf(", id: %s", i.ID))
+ if i.RouteReflectorClient {
+ s.WriteString(fmt.Sprintf(", cluster-id: %s", i.RouteReflectorClusterID))
+ }
+ s.WriteString(" }")
+ return s.String()
+}
+
+func NewPeerInfo(g *config.Global, p *config.Neighbor) *PeerInfo {
+ clusterID := net.ParseIP(string(p.RouteReflector.State.RouteReflectorClusterId)).To4()
+ // exclude zone info
+ naddr, _ := net.ResolveIPAddr("ip", p.State.NeighborAddress)
+ return &PeerInfo{
+ AS: p.Config.PeerAs,
+ LocalAS: g.Config.As,
+ LocalID: net.ParseIP(g.Config.RouterId).To4(),
+ RouteReflectorClient: p.RouteReflector.Config.RouteReflectorClient,
+ Address: naddr.IP,
+ RouteReflectorClusterID: clusterID,
+ MultihopTtl: p.EbgpMultihop.Config.MultihopTtl,
+ Confederation: p.IsConfederationMember(g),
+ }
+}
+
+type Destination struct {
+ routeFamily bgp.RouteFamily
+ nlri bgp.AddrPrefixInterface
+ knownPathList []*Path
+ localIdMap *Bitmap
+}
+
+func NewDestination(nlri bgp.AddrPrefixInterface, mapSize int, known ...*Path) *Destination {
+ d := &Destination{
+ routeFamily: bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI()),
+ nlri: nlri,
+ knownPathList: known,
+ localIdMap: NewBitmap(mapSize),
+ }
+ // the id zero means id is not allocated yet.
+ if mapSize != 0 {
+ d.localIdMap.Flag(0)
+ }
+ return d
+}
+
+func (dd *Destination) Family() bgp.RouteFamily {
+ return dd.routeFamily
+}
+
+func (dd *Destination) setRouteFamily(routeFamily bgp.RouteFamily) {
+ dd.routeFamily = routeFamily
+}
+
+func (dd *Destination) GetNlri() bgp.AddrPrefixInterface {
+ return dd.nlri
+}
+
+func (dd *Destination) setNlri(nlri bgp.AddrPrefixInterface) {
+ dd.nlri = nlri
+}
+
+func (dd *Destination) GetAllKnownPathList() []*Path {
+ return dd.knownPathList
+}
+
+func rsFilter(id string, as uint32, path *Path) bool {
+ isASLoop := func(as uint32, path *Path) bool {
+ for _, v := range path.GetAsList() {
+ if as == v {
+ return true
+ }
+ }
+ return false
+ }
+
+ if id != GLOBAL_RIB_NAME && (path.GetSource().Address.String() == id || isASLoop(as, path)) {
+ return true
+ }
+ return false
+}
+
+func (dd *Destination) GetKnownPathList(id string, as uint32) []*Path {
+ list := make([]*Path, 0, len(dd.knownPathList))
+ for _, p := range dd.knownPathList {
+ if rsFilter(id, as, p) {
+ continue
+ }
+ list = append(list, p)
+ }
+ return list
+}
+
+func getBestPath(id string, as uint32, pathList []*Path) *Path {
+ for _, p := range pathList {
+ if rsFilter(id, as, p) {
+ continue
+ }
+ return p
+ }
+ return nil
+}
+
+func (dd *Destination) GetBestPath(id string, as uint32) *Path {
+ p := getBestPath(id, as, dd.knownPathList)
+ if p == nil || p.IsNexthopInvalid {
+ return nil
+ }
+ return p
+}
+
+func (dd *Destination) GetMultiBestPath(id string) []*Path {
+ return getMultiBestPath(id, dd.knownPathList)
+}
+
+// Calculates best-path among known paths for this destination.
+//
+// Modifies destination's state related to stored paths. Removes withdrawn
+// paths from known paths. Also, adds new paths to known paths.
+func (dest *Destination) Calculate(newPath *Path) *Update {
+ oldKnownPathList := make([]*Path, len(dest.knownPathList))
+ copy(oldKnownPathList, dest.knownPathList)
+
+ if newPath.IsWithdraw {
+ p := dest.explicitWithdraw(newPath)
+ if p != nil {
+ if id := p.GetNlri().PathLocalIdentifier(); id != 0 {
+ dest.localIdMap.Unflag(uint(id))
+ }
+ }
+ } else {
+ dest.implicitWithdraw(newPath)
+ dest.knownPathList = append(dest.knownPathList, newPath)
+ }
+
+ for _, path := range dest.knownPathList {
+ if path.GetNlri().PathLocalIdentifier() == 0 {
+ id, err := dest.localIdMap.FindandSetZeroBit()
+ if err != nil {
+ dest.localIdMap.Expand()
+ id, _ = dest.localIdMap.FindandSetZeroBit()
+ }
+ path.GetNlri().SetPathLocalIdentifier(uint32(id))
+ }
+ }
+ // Compute new best path
+ dest.computeKnownBestPath()
+
+ l := make([]*Path, len(dest.knownPathList))
+ copy(l, dest.knownPathList)
+ return &Update{
+ KnownPathList: l,
+ OldKnownPathList: oldKnownPathList,
+ }
+}
+
+// Removes withdrawn paths.
+//
+// Note:
+// We may have disproportionate number of withdraws compared to know paths
+// since not all paths get installed into the table due to bgp policy and
+// we can receive withdraws for such paths and withdrawals may not be
+// stopped by the same policies.
+//
+func (dest *Destination) explicitWithdraw(withdraw *Path) *Path {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": dest.GetNlri().String(),
+ }).Debug("Removing withdrawals")
+
+ // If we have some withdrawals and no know-paths, it means it is safe to
+ // delete these withdraws.
+ if len(dest.knownPathList) == 0 {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": dest.GetNlri().String(),
+ }).Debug("Found withdrawals for path(s) that did not get installed")
+ return nil
+ }
+
+ // Match all withdrawals from destination paths.
+ isFound := -1
+ for i, path := range dest.knownPathList {
+ // We have a match if the source and path-id are same.
+ if path.GetSource().Equal(withdraw.GetSource()) && path.GetNlri().PathIdentifier() == withdraw.GetNlri().PathIdentifier() {
+ isFound = i
+ withdraw.GetNlri().SetPathLocalIdentifier(path.GetNlri().PathLocalIdentifier())
+ }
+ }
+
+ // We do no have any match for this withdraw.
+ if isFound == -1 {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": dest.GetNlri().String(),
+ "Path": withdraw,
+ }).Warn("No matching path for withdraw found, may be path was not installed into table")
+ return nil
+ } else {
+ p := dest.knownPathList[isFound]
+ dest.knownPathList = append(dest.knownPathList[:isFound], dest.knownPathList[isFound+1:]...)
+ return p
+ }
+}
+
+// Identifies which of known paths are old and removes them.
+//
+// Known paths will no longer have paths whose new version is present in
+// new paths.
+func (dest *Destination) implicitWithdraw(newPath *Path) {
+ found := -1
+ for i, path := range dest.knownPathList {
+ if newPath.NoImplicitWithdraw() {
+ continue
+ }
+ // Here we just check if source is same and not check if path
+ // version num. as newPaths are implicit withdrawal of old
+ // paths and when doing RouteRefresh (not EnhancedRouteRefresh)
+ // we get same paths again.
+ if newPath.GetSource().Equal(path.GetSource()) && newPath.GetNlri().PathIdentifier() == path.GetNlri().PathIdentifier() {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": dest.GetNlri().String(),
+ "Path": path,
+ }).Debug("Implicit withdrawal of old path, since we have learned new path from the same peer")
+
+ found = i
+ newPath.GetNlri().SetPathLocalIdentifier(path.GetNlri().PathLocalIdentifier())
+ break
+ }
+ }
+ if found != -1 {
+ dest.knownPathList = append(dest.knownPathList[:found], dest.knownPathList[found+1:]...)
+ }
+}
+
+func (dest *Destination) computeKnownBestPath() (*Path, BestPathReason, error) {
+ if SelectionOptions.DisableBestPathSelection {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("computeKnownBestPath skipped")
+ return nil, BPR_DISABLED, nil
+ }
+
+ // If we do not have any paths to this destination, then we do not have
+ // new best path.
+ if len(dest.knownPathList) == 0 {
+ return nil, BPR_UNKNOWN, nil
+ }
+
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("computeKnownBestPath knownPathList: %d", len(dest.knownPathList))
+
+ // We pick the first path as current best path. This helps in breaking
+ // tie between two new paths learned in one cycle for which best-path
+ // calculation steps lead to tie.
+ if len(dest.knownPathList) == 1 {
+ // If the first path has the invalidated next-hop, which evaluated by
+ // IGP, returns no path with the reason of the next-hop reachability.
+ if dest.knownPathList[0].IsNexthopInvalid {
+ return nil, BPR_REACHABLE_NEXT_HOP, nil
+ }
+ return dest.knownPathList[0], BPR_ONLY_PATH, nil
+ }
+ dest.sort()
+ newBest := dest.knownPathList[0]
+ // If the first path has the invalidated next-hop, which evaluated by IGP,
+ // returns no path with the reason of the next-hop reachability.
+ if dest.knownPathList[0].IsNexthopInvalid {
+ return nil, BPR_REACHABLE_NEXT_HOP, nil
+ }
+ return newBest, newBest.reason, nil
+}
+
+func (dst *Destination) sort() {
+ sort.SliceStable(dst.knownPathList, func(i, j int) bool {
+ //Compares given paths and returns best path.
+ //
+ //Parameters:
+ // -`path1`: first path to compare
+ // -`path2`: second path to compare
+ //
+ // Best path processing will involve following steps:
+ // 1. Select a path with a reachable next hop.
+ // 2. Select the path with the highest weight.
+ // 3. If path weights are the same, select the path with the highest
+ // local preference value.
+ // 4. Prefer locally originated routes (network routes, redistributed
+ // routes, or aggregated routes) over received routes.
+ // 5. Select the route with the shortest AS-path length.
+ // 6. If all paths have the same AS-path length, select the path based
+ // on origin: IGP is preferred over EGP; EGP is preferred over
+ // Incomplete.
+ // 7. If the origins are the same, select the path with lowest MED
+ // value.
+ // 8. If the paths have the same MED values, select the path learned
+ // via EBGP over one learned via IBGP.
+ // 9. Select the route with the lowest IGP cost to the next hop.
+ // 10. Select the route received from the peer with the lowest BGP
+ // router ID.
+ //
+ // Returns None if best-path among given paths cannot be computed else best
+ // path.
+ // Assumes paths from NC has source equal to None.
+ //
+
+ path1 := dst.knownPathList[i]
+ path2 := dst.knownPathList[j]
+
+ var better *Path
+ reason := BPR_UNKNOWN
+
+ // draft-uttaro-idr-bgp-persistence-02
+ if better == nil {
+ better = compareByLLGRStaleCommunity(path1, path2)
+ reason = BPR_NON_LLGR_STALE
+ }
+ // Follow best path calculation algorithm steps.
+ // compare by reachability
+ if better == nil {
+ better = compareByReachableNexthop(path1, path2)
+ reason = BPR_REACHABLE_NEXT_HOP
+ }
+ if better == nil {
+ better = compareByHighestWeight(path1, path2)
+ reason = BPR_HIGHEST_WEIGHT
+ }
+ if better == nil {
+ better = compareByLocalPref(path1, path2)
+ reason = BPR_LOCAL_PREF
+ }
+ if better == nil {
+ better = compareByLocalOrigin(path1, path2)
+ reason = BPR_LOCAL_ORIGIN
+ }
+ if better == nil {
+ better = compareByASPath(path1, path2)
+ reason = BPR_ASPATH
+ }
+ if better == nil {
+ better = compareByOrigin(path1, path2)
+ reason = BPR_ORIGIN
+ }
+ if better == nil {
+ better = compareByMED(path1, path2)
+ reason = BPR_MED
+ }
+ if better == nil {
+ better = compareByASNumber(path1, path2)
+ reason = BPR_ASN
+ }
+ if better == nil {
+ better = compareByIGPCost(path1, path2)
+ reason = BPR_IGP_COST
+ }
+ if better == nil {
+ better = compareByAge(path1, path2)
+ reason = BPR_OLDER
+ }
+ if better == nil {
+ var e error = nil
+ better, e = compareByRouterID(path1, path2)
+ if e != nil {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Error": e,
+ }).Error("Could not get best path by comparing router ID")
+ }
+ reason = BPR_ROUTER_ID
+ }
+ if better == nil {
+ reason = BPR_UNKNOWN
+ better = path1
+ }
+
+ better.reason = reason
+
+ return better == path1
+ })
+}
+
+type Update struct {
+ KnownPathList []*Path
+ OldKnownPathList []*Path
+}
+
+func getMultiBestPath(id string, pathList []*Path) []*Path {
+ list := make([]*Path, 0, len(pathList))
+ var best *Path
+ for _, p := range pathList {
+ if !p.IsNexthopInvalid {
+ if best == nil {
+ best = p
+ list = append(list, p)
+ } else if best.Compare(p) == 0 {
+ list = append(list, p)
+ }
+ }
+ }
+ return list
+}
+
+func (u *Update) GetWithdrawnPath() []*Path {
+ if len(u.KnownPathList) == len(u.OldKnownPathList) {
+ return nil
+ }
+
+ l := make([]*Path, 0, len(u.OldKnownPathList))
+
+ for _, p := range u.OldKnownPathList {
+ y := func() bool {
+ for _, old := range u.KnownPathList {
+ if p == old {
+ return true
+ }
+ }
+ return false
+ }()
+ if !y {
+ l = append(l, p.Clone(true))
+ }
+ }
+ return l
+}
+
+func (u *Update) GetChanges(id string, as uint32, peerDown bool) (*Path, *Path, []*Path) {
+ best, old := func(id string) (*Path, *Path) {
+ old := getBestPath(id, as, u.OldKnownPathList)
+ best := getBestPath(id, as, u.KnownPathList)
+ if best != nil && best.Equal(old) {
+ // RFC4684 3.2. Intra-AS VPN Route Distribution
+ // When processing RT membership NLRIs received from internal iBGP
+ // peers, it is necessary to consider all available iBGP paths for a
+ // given RT prefix, for building the outbound route filter, and not just
+ // the best path.
+ if best.GetRouteFamily() == bgp.RF_RTC_UC {
+ return best, old
+ }
+ // For BGP Nexthop Tracking, checks if the nexthop reachability
+ // was changed or not.
+ if best.IsNexthopInvalid != old.IsNexthopInvalid {
+ // If the nexthop of the best path became unreachable, we need
+ // to withdraw that path.
+ if best.IsNexthopInvalid {
+ return best.Clone(true), old
+ }
+ return best, old
+ }
+ return nil, old
+ }
+ if best == nil {
+ if old == nil {
+ return nil, nil
+ }
+ if peerDown {
+ // withdraws were generated by peer
+ // down so paths are not in knowpath
+ // or adjin.
+ old.IsWithdraw = true
+ return old, old
+ }
+ return old.Clone(true), old
+ }
+ return best, old
+ }(id)
+
+ var multi []*Path
+
+ if id == GLOBAL_RIB_NAME && UseMultiplePaths.Enabled {
+ diff := func(lhs, rhs []*Path) bool {
+ if len(lhs) != len(rhs) {
+ return true
+ }
+ for idx, l := range lhs {
+ if !l.Equal(rhs[idx]) {
+ return true
+ }
+ }
+ return false
+ }
+ oldM := getMultiBestPath(id, u.OldKnownPathList)
+ newM := getMultiBestPath(id, u.KnownPathList)
+ if diff(oldM, newM) {
+ multi = newM
+ if len(newM) == 0 {
+ multi = []*Path{best}
+ }
+ }
+ }
+ return best, old, multi
+}
+
+func compareByLLGRStaleCommunity(path1, path2 *Path) *Path {
+ p1 := path1.IsLLGRStale()
+ p2 := path2.IsLLGRStale()
+ if p1 == p2 {
+ return nil
+ } else if p1 {
+ return path2
+ }
+ return path1
+}
+
+func compareByReachableNexthop(path1, path2 *Path) *Path {
+ // Compares given paths and selects best path based on reachable next-hop.
+ //
+ // If no path matches this criteria, return nil.
+ // For BGP Nexthop Tracking, evaluates next-hop is validated by IGP.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("enter compareByReachableNexthop -- path1: %s, path2: %s", path1, path2)
+
+ if path1.IsNexthopInvalid && !path2.IsNexthopInvalid {
+ return path2
+ } else if !path1.IsNexthopInvalid && path2.IsNexthopInvalid {
+ return path1
+ }
+
+ return nil
+}
+
+func compareByHighestWeight(path1, path2 *Path) *Path {
+ // Selects a path with highest weight.
+ //
+ // Weight is BGPS specific parameter. It is local to the router on which it
+ // is configured.
+ // Return:
+ // nil if best path among given paths cannot be decided, else best path.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("enter compareByHighestWeight -- path1: %s, path2: %s", path1, path2)
+ return nil
+}
+
+func compareByLocalPref(path1, path2 *Path) *Path {
+ // Selects a path with highest local-preference.
+ //
+ // Unlike the weight attribute, which is only relevant to the local
+ // router, local preference is an attribute that routers exchange in the
+ // same AS. Highest local-pref is preferred. If we cannot decide,
+ // we return None.
+ //
+ // # Default local-pref values is 100
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByLocalPref")
+ localPref1, _ := path1.GetLocalPref()
+ localPref2, _ := path2.GetLocalPref()
+ // Highest local-preference value is preferred.
+ if localPref1 > localPref2 {
+ return path1
+ } else if localPref1 < localPref2 {
+ return path2
+ } else {
+ return nil
+ }
+}
+
+func compareByLocalOrigin(path1, path2 *Path) *Path {
+
+ // Select locally originating path as best path.
+ // Locally originating routes are network routes, redistributed routes,
+ // or aggregated routes.
+ // Returns None if given paths have same source.
+ //
+ // If both paths are from same sources we cannot compare them here.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByLocalOrigin")
+ if path1.GetSource().Equal(path2.GetSource()) {
+ return nil
+ }
+
+ // Here we consider prefix from NC as locally originating static route.
+ // Hence it is preferred.
+ if path1.IsLocal() {
+ return path1
+ }
+
+ if path2.IsLocal() {
+ return path2
+ }
+ return nil
+}
+
+func compareByASPath(path1, path2 *Path) *Path {
+ // Calculated the best-paths by comparing as-path lengths.
+ //
+ // Shortest as-path length is preferred. If both path have same lengths,
+ // we return None.
+ if SelectionOptions.IgnoreAsPathLength {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("compareByASPath -- skip")
+ return nil
+ }
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByASPath")
+ attribute1 := path1.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH)
+ attribute2 := path2.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH)
+
+ // With addpath support, we could compare paths from API don't
+ // AS_PATH. No need to warn here.
+ if !path1.IsLocal() && !path2.IsLocal() && (attribute1 == nil || attribute2 == nil) {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": "compareByASPath",
+ "ASPath1": attribute1,
+ "ASPath2": attribute2,
+ }).Warn("can't compare ASPath because it's not present")
+ }
+
+ l1 := path1.GetAsPathLen()
+ l2 := path2.GetAsPathLen()
+
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("compareByASPath -- l1: %d, l2: %d", l1, l2)
+ if l1 > l2 {
+ return path2
+ } else if l1 < l2 {
+ return path1
+ } else {
+ return nil
+ }
+}
+
+func compareByOrigin(path1, path2 *Path) *Path {
+ // Select the best path based on origin attribute.
+ //
+ // IGP is preferred over EGP; EGP is preferred over Incomplete.
+ // If both paths have same origin, we return None.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByOrigin")
+ attribute1 := path1.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN)
+ attribute2 := path2.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN)
+
+ if attribute1 == nil || attribute2 == nil {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ "Key": "compareByOrigin",
+ "Origin1": attribute1,
+ "Origin2": attribute2,
+ }).Error("can't compare origin because it's not present")
+ return nil
+ }
+
+ origin1 := attribute1.(*bgp.PathAttributeOrigin).Value
+ origin2 := attribute2.(*bgp.PathAttributeOrigin).Value
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("compareByOrigin -- origin1: %d, origin2: %d", origin1, origin2)
+
+ // If both paths have same origins
+ if origin1 == origin2 {
+ return nil
+ } else if origin1 < origin2 {
+ return path1
+ } else {
+ return path2
+ }
+}
+
+func compareByMED(path1, path2 *Path) *Path {
+ // Select the path based with lowest MED value.
+ //
+ // If both paths have same MED, return None.
+ // By default, a route that arrives with no MED value is treated as if it
+ // had a MED of 0, the most preferred value.
+ // RFC says lower MED is preferred over higher MED value.
+ // compare MED among not only same AS path but also all path,
+ // like bgp always-compare-med
+
+ isInternal := func() bool { return path1.GetAsPathLen() == 0 && path2.GetAsPathLen() == 0 }()
+
+ isSameAS := func() bool {
+ firstAS := func(path *Path) uint32 {
+ if asPath := path.GetAsPath(); asPath != nil {
+ for _, v := range asPath.Value {
+ segType := v.GetType()
+ asList := v.GetAS()
+ if len(asList) == 0 {
+ continue
+ }
+ switch segType {
+ case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ:
+ continue
+ }
+ return asList[0]
+ }
+ }
+ return 0
+ }
+ return firstAS(path1) != 0 && firstAS(path1) == firstAS(path2)
+ }()
+
+ if SelectionOptions.AlwaysCompareMed || isInternal || isSameAS {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByMED")
+ getMed := func(path *Path) uint32 {
+ attribute := path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC)
+ if attribute == nil {
+ return 0
+ }
+ med := attribute.(*bgp.PathAttributeMultiExitDisc).Value
+ return med
+ }
+
+ med1 := getMed(path1)
+ med2 := getMed(path2)
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("compareByMED -- med1: %d, med2: %d", med1, med2)
+ if med1 == med2 {
+ return nil
+ } else if med1 < med2 {
+ return path1
+ }
+ return path2
+ } else {
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("skip compareByMED %v %v %v", SelectionOptions.AlwaysCompareMed, isInternal, isSameAS)
+ return nil
+ }
+}
+
+func compareByASNumber(path1, path2 *Path) *Path {
+
+ //Select the path based on source (iBGP/eBGP) peer.
+ //
+ //eBGP path is preferred over iBGP. If both paths are from same kind of
+ //peers, return None.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByASNumber")
+
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("compareByASNumber -- p1Asn: %d, p2Asn: %d", path1.GetSource().AS, path2.GetSource().AS)
+ // Path from confederation member should be treated as internal (IBGP learned) path.
+ isIBGP1 := path1.GetSource().Confederation || path1.IsIBGP()
+ isIBGP2 := path2.GetSource().Confederation || path2.IsIBGP()
+ // If one path is from ibgp peer and another is from ebgp peer, take the ebgp path.
+ if isIBGP1 != isIBGP2 {
+ if isIBGP1 {
+ return path2
+ }
+ return path1
+ }
+
+ // If both paths are from ebgp or ibpg peers, we cannot decide.
+ return nil
+}
+
+func compareByIGPCost(path1, path2 *Path) *Path {
+ // Select the route with the lowest IGP cost to the next hop.
+ //
+ // Return None if igp cost is same.
+ // Currently BGPS has no concept of IGP and IGP cost.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debugf("enter compareByIGPCost -- path1: %v, path2: %v", path1, path2)
+ return nil
+}
+
+func compareByRouterID(path1, path2 *Path) (*Path, error) {
+ // Select the route received from the peer with the lowest BGP router ID.
+ //
+ // If both paths are eBGP paths, then we do not do any tie breaking, i.e we do
+ // not pick best-path based on this criteria.
+ // RFC: http://tools.ietf.org/html/rfc5004
+ // We pick best path between two iBGP paths as usual.
+ log.WithFields(log.Fields{
+ "Topic": "Table",
+ }).Debug("enter compareByRouterID")
+
+ // If both paths are from NC we have same router Id, hence cannot compare.
+ if path1.IsLocal() && path2.IsLocal() {
+ return nil, nil
+ }
+
+ // If both paths are from eBGP peers, then according to RFC we need
+ // not tie break using router id.
+ if !SelectionOptions.ExternalCompareRouterId && !path1.IsIBGP() && !path2.IsIBGP() {
+ return nil, nil
+ }
+
+ if !SelectionOptions.ExternalCompareRouterId && path1.IsIBGP() != path2.IsIBGP() {
+ return nil, fmt.Errorf("This method does not support comparing ebgp with ibgp path")
+ }
+
+ // At least one path is not coming from NC, so we get local bgp id.
+ id1 := binary.BigEndian.Uint32(path1.GetSource().ID)
+ id2 := binary.BigEndian.Uint32(path2.GetSource().ID)
+
+ // If both router ids are same/equal we cannot decide.
+ // This case is possible since router ids are arbitrary.
+ if id1 == id2 {
+ return nil, nil
+ } else if id1 < id2 {
+ return path1, nil
+ } else {
+ return path2, nil
+ }
+}
+
+func compareByAge(path1, path2 *Path) *Path {
+ if !path1.IsIBGP() && !path2.IsIBGP() && !SelectionOptions.ExternalCompareRouterId {
+ age1 := path1.GetTimestamp().UnixNano()
+ age2 := path2.GetTimestamp().UnixNano()
+ if age1 == age2 {
+ return nil
+ } else if age1 < age2 {
+ return path1
+ }
+ return path2
+ }
+ return nil
+}
+
+func (dest *Destination) String() string {
+ return fmt.Sprintf("Destination NLRI: %s", dest.nlri.String())
+}
+
+type DestinationSelectOption struct {
+ ID string
+ AS uint32
+ VRF *Vrf
+ adj bool
+ Best bool
+ MultiPath bool
+}
+
+func (d *Destination) MarshalJSON() ([]byte, error) {
+ return json.Marshal(d.GetAllKnownPathList())
+}
+
+func (d *Destination) Select(option ...DestinationSelectOption) *Destination {
+ id := GLOBAL_RIB_NAME
+ var vrf *Vrf
+ adj := false
+ best := false
+ mp := false
+ as := uint32(0)
+ for _, o := range option {
+ if o.ID != "" {
+ id = o.ID
+ }
+ if o.VRF != nil {
+ vrf = o.VRF
+ }
+ adj = o.adj
+ best = o.Best
+ mp = o.MultiPath
+ as = o.AS
+ }
+ var paths []*Path
+ if adj {
+ paths = make([]*Path, len(d.knownPathList))
+ copy(paths, d.knownPathList)
+ } else {
+ paths = d.GetKnownPathList(id, as)
+ if vrf != nil {
+ ps := make([]*Path, 0, len(paths))
+ for _, p := range paths {
+ if CanImportToVrf(vrf, p) {
+ ps = append(ps, p.ToLocal())
+ }
+ }
+ paths = ps
+ }
+ if len(paths) == 0 {
+ return nil
+ }
+ if best {
+ if !mp {
+ paths = []*Path{paths[0]}
+ } else {
+ ps := make([]*Path, 0, len(paths))
+ var best *Path
+ for _, p := range paths {
+ if best == nil {
+ best = p
+ ps = append(ps, p)
+ } else if best.Compare(p) == 0 {
+ ps = append(ps, p)
+ }
+ }
+ paths = ps
+ }
+ }
+ }
+ return NewDestination(d.nlri, 0, paths...)
+}