diff options
-rw-r--r-- | config/bgp_configs.go | 21 | ||||
-rw-r--r-- | config/util.go | 9 | ||||
-rw-r--r-- | gobgp/cmd/common.go | 4 | ||||
-rw-r--r-- | server/server.go | 230 | ||||
-rw-r--r-- | test/lib/base.py | 4 | ||||
-rw-r--r-- | test/lib/gobgp.py | 8 | ||||
-rw-r--r-- | test/scenario_test/evpn_test.py | 6 | ||||
-rw-r--r-- | test/scenario_test/rtc_test.py | 94 | ||||
-rw-r--r-- | tools/pyang_plugins/gobgp.yang | 8 |
9 files changed, 320 insertions, 64 deletions
diff --git a/config/bgp_configs.go b/config/bgp_configs.go index 47d070b4..1b3d2a73 100644 --- a/config/bgp_configs.go +++ b/config/bgp_configs.go @@ -2597,6 +2597,22 @@ func (lhs *Collector) Equal(rhs *Collector) bool { return true } +//struct for container gobgp:route-target-membership +type RouteTargetMembership struct { + // original -> gobgp:deferral-time + DeferralTime uint16 `mapstructure:"deferral-time"` +} + +func (lhs *RouteTargetMembership) Equal(rhs *RouteTargetMembership) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.DeferralTime != rhs.DeferralTime { + return false + } + return true +} + //struct for container bgp-mp:l2vpn-evpn type L2vpnEvpn struct { // original -> bgp-mp:prefix-limit @@ -3224,6 +3240,8 @@ type AfiSafi struct { UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths"` // original -> bgp-mp:prefix-limit PrefixLimit PrefixLimit `mapstructure:"prefix-limit"` + // original -> gobgp:route-target-membership + RouteTargetMembership RouteTargetMembership `mapstructure:"route-target-membership"` } func (lhs *AfiSafi) Equal(rhs *AfiSafi) bool { @@ -3284,6 +3302,9 @@ func (lhs *AfiSafi) Equal(rhs *AfiSafi) bool { if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } + if !lhs.RouteTargetMembership.Equal(&(rhs.RouteTargetMembership)) { + return false + } return true } diff --git a/config/util.go b/config/util.go index 47edc98f..8d7d2546 100644 --- a/config/util.go +++ b/config/util.go @@ -57,3 +57,12 @@ func CreateRfMap(p *Neighbor) map[bgp.RouteFamily]bool { } return rfMap } + +func GetAfiSafi(p *Neighbor, family bgp.RouteFamily) *AfiSafi { + for _, a := range p.AfiSafis { + if string(a.AfiSafiName) == family.String() { + return &a + } + } + return nil +} diff --git a/gobgp/cmd/common.go b/gobgp/cmd/common.go index 1a04a3aa..98e493af 100644 --- a/gobgp/cmd/common.go +++ b/gobgp/cmd/common.go @@ -440,9 +440,9 @@ func checkAddressFamily(def bgp.RouteFamily) (bgp.RouteFamily, error) { rf = bgp.RF_IPv4_UC case "ipv6", "v6", "6": rf = bgp.RF_IPv6_UC - case "vpnv4", "vpn-ipv4": + case "ipv4-l3vpn", "vpnv4", "vpn-ipv4": rf = bgp.RF_IPv4_VPN - case "vpnv6", "vpn-ipv6": + case "ipv6-l3vpn", "vpnv6", "vpn-ipv6": rf = bgp.RF_IPv6_VPN case "ipv4-labeled", "ipv4-labelled", "ipv4-mpls": rf = bgp.RF_IPv4_MPLS diff --git a/server/server.go b/server/server.go index 1c73f4d9..a182c26b 100644 --- a/server/server.go +++ b/server/server.go @@ -399,64 +399,81 @@ func filterpath(peer *Peer, path *table.Path) *table.Path { return nil } - remoteAddr := peer.fsm.pConf.Config.NeighborAddress - //iBGP handling - if !path.IsLocal() && peer.isIBGPPeer() { - ignore := true - info := path.GetSource() - - //if the path comes from eBGP peer - if info.AS != peer.fsm.pConf.Config.PeerAs { - ignore = false - } - // RFC4456 8. Avoiding Routing Information Loops - // A router that recognizes the ORIGINATOR_ID attribute SHOULD - // ignore a route received with its BGP Identifier as the ORIGINATOR_ID. - if id := path.GetOriginatorID(); peer.fsm.gConf.Config.RouterId == id.String() { - log.WithFields(log.Fields{ - "Topic": "Peer", - "Key": remoteAddr, - "OriginatorID": id, - "Data": path, - }).Debug("Originator ID is mine, ignore") - return nil - } - if info.RouteReflectorClient { - ignore = false + if peer.isIBGPPeer() { + ignore := false + //RFC4684 Constrained Route Distribution + if peer.fsm.rfMap[bgp.RF_RTC_UC] && path.GetRouteFamily() != bgp.RF_RTC_UC { + ignore = true + for _, ext := range path.GetExtCommunities() { + for _, path := range peer.adjRibIn.PathList([]bgp.RouteFamily{bgp.RF_RTC_UC}, true) { + rt := path.GetNlri().(*bgp.RouteTargetMembershipNLRI).RouteTarget + if ext.String() == rt.String() { + ignore = false + break + } + } + if !ignore { + break + } + } } - if peer.isRouteReflectorClient() { + + if !path.IsLocal() { + ignore = true + info := path.GetSource() + //if the path comes from eBGP peer + if info.AS != peer.fsm.pConf.Config.PeerAs { + ignore = false + } // RFC4456 8. Avoiding Routing Information Loops - // If the local CLUSTER_ID is found in the CLUSTER_LIST, - // the advertisement received SHOULD be ignored. - for _, clusterId := range path.GetClusterList() { - if clusterId.Equal(peer.fsm.peerInfo.RouteReflectorClusterID) { - log.WithFields(log.Fields{ - "Topic": "Peer", - "Key": remoteAddr, - "ClusterID": clusterId, - "Data": path, - }).Debug("cluster list path attribute has local cluster id, ignore") - return nil + // A router that recognizes the ORIGINATOR_ID attribute SHOULD + // ignore a route received with its BGP Identifier as the ORIGINATOR_ID. + if id := path.GetOriginatorID(); peer.fsm.gConf.Config.RouterId == id.String() { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "OriginatorID": id, + "Data": path, + }).Debug("Originator ID is mine, ignore") + return nil + } + if info.RouteReflectorClient { + ignore = false + } + if peer.isRouteReflectorClient() { + // RFC4456 8. Avoiding Routing Information Loops + // If the local CLUSTER_ID is found in the CLUSTER_LIST, + // the advertisement received SHOULD be ignored. + for _, clusterId := range path.GetClusterList() { + if clusterId.Equal(peer.fsm.peerInfo.RouteReflectorClusterID) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "ClusterID": clusterId, + "Data": path, + }).Debug("cluster list path attribute has local cluster id, ignore") + return nil + } } + ignore = false } - ignore = false } if ignore { log.WithFields(log.Fields{ "Topic": "Peer", - "Key": remoteAddr, + "Key": peer.ID(), "Data": path, }).Debug("From same AS, ignore.") return nil } } - if remoteAddr == path.GetSource().Address.String() { + if peer.ID() == path.GetSource().Address.String() { log.WithFields(log.Fields{ "Topic": "Peer", - "Key": remoteAddr, + "Key": peer.ID(), "Data": path, }).Debug("From me, ignore.") return nil @@ -717,6 +734,7 @@ func (server *BgpServer) propagateUpdate(peer *Peer, pathList []*table.Path) ([] rib := server.globalRib var alteredPathList, newly, withdrawn []*table.Path var best map[string][]*table.Path + msgs := make([]*SenderMsg, 0, len(server.neighborMap)) if peer != nil && peer.isRouteServerClient() { for _, path := range pathList { @@ -744,7 +762,48 @@ func (server *BgpServer) propagateUpdate(peer *Peer, pathList []*table.Path) ([] server.validatePaths(newly, withdrawn, false) } else { for idx, path := range pathList { - pathList[idx] = server.policy.ApplyPolicy(table.GLOBAL_RIB_NAME, table.POLICY_DIRECTION_IMPORT, path, nil) + path = server.policy.ApplyPolicy(table.GLOBAL_RIB_NAME, table.POLICY_DIRECTION_IMPORT, path, nil) + pathList[idx] = path + // RFC4684 Constrained Route Distribution 6. Operation + // + // When a BGP speaker receives a BGP UPDATE that advertises or withdraws + // a given Route Target membership NLRI, it should examine the RIB-OUTs + // of VPN NLRIs and re-evaluate the advertisement status of routes that + // match the Route Target in question. + // + // A BGP speaker should generate the minimum set of BGP VPN route + // updates (advertisements and/or withdrawls) necessary to transition + // between the previous and current state of the route distribution + // graph that is derived from Route Target membership information. + if peer != nil && path != nil && path.GetRouteFamily() == bgp.RF_RTC_UC { + rt := path.GetNlri().(*bgp.RouteTargetMembershipNLRI).RouteTarget + fs := make([]bgp.RouteFamily, 0, len(peer.configuredRFlist())) + for _, f := range peer.configuredRFlist() { + if f != bgp.RF_RTC_UC { + fs = append(fs, f) + } + } + var candidates []*table.Path + if path.IsWithdraw { + candidates = peer.adjRibOut.PathList(fs, false) + } else { + candidates = rib.GetBestPathList(peer.TableID(), fs) + } + paths := make([]*table.Path, 0, len(pathList)) + for _, p := range candidates { + t := false + for _, ext := range p.GetExtCommunities() { + if ext.String() == rt.String() { + t = true + break + } + } + if t { + paths = append(paths, p.Clone(path.IsWithdraw)) + } + } + msgs = append(msgs, newSenderMsg(peer, paths, nil, false)) + } } alteredPathList = pathList best, newly, withdrawn = rib.ProcessPaths([]string{table.GLOBAL_RIB_NAME}, pathList) @@ -757,7 +816,6 @@ func (server *BgpServer) propagateUpdate(peer *Peer, pathList []*table.Path) ([] } } - msgs := make([]*SenderMsg, 0, len(server.neighborMap)) for _, targetPeer := range server.neighborMap { if (peer == nil && targetPeer.isRouteServerClient()) || (peer != nil && peer.isRouteServerClient() != targetPeer.isRouteServerClient()) { continue @@ -811,8 +869,36 @@ func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) []*SenderMsg { // update for export policy laddr, _ := peer.fsm.LocalHostPort() peer.fsm.pConf.Transport.Config.LocalAddress = laddr + deferralExpiredFunc := func(family bgp.RouteFamily) func() { + return func() { + req := NewGrpcRequest(REQ_DEFERRAL_TIMER_EXPIRED, peer.ID(), family, nil) + server.GrpcReqCh <- req + <-req.ResponseCh + } + } if !peer.fsm.pConf.GracefulRestart.State.LocalRestarting { - pathList, _ := peer.getBestFromLocal(peer.configuredRFlist()) + // When graceful-restart cap (which means intention + // of sending EOR) and route-target address family are negotiated, + // send route-target NLRIs first, and wait to send others + // till receiving EOR of route-target address family. + // This prevents sending uninterested routes to peers. + // + // However, when the peer is graceful restarting, give up + // waiting sending non-route-target NLRIs since the peer won't send + // any routes (and EORs) before we send ours (or deferral-timer expires). + var pathList []*table.Path + if c := config.GetAfiSafi(peer.fsm.pConf, bgp.RF_RTC_UC); !peer.fsm.pConf.GracefulRestart.State.PeerRestarting && peer.fsm.rfMap[bgp.RF_RTC_UC] && c.RouteTargetMembership.DeferralTime > 0 { + pathList, _ = peer.getBestFromLocal([]bgp.RouteFamily{bgp.RF_RTC_UC}) + t := c.RouteTargetMembership.DeferralTime + for _, f := range peer.configuredRFlist() { + if f != bgp.RF_RTC_UC { + time.AfterFunc(time.Second*time.Duration(t), deferralExpiredFunc(f)) + } + } + } else { + pathList, _ = peer.getBestFromLocal(peer.configuredRFlist()) + } + if len(pathList) > 0 { peer.adjRibOut.Update(pathList) msgs = []*SenderMsg{newSenderMsg(peer, pathList, nil, false)} @@ -827,13 +913,9 @@ func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) []*SenderMsg { deferral := peer.fsm.pConf.GracefulRestart.Config.DeferralTime log.WithFields(log.Fields{ "Topic": "Peer", - "Key": peer.fsm.pConf.Config.NeighborAddress, + "Key": peer.ID(), }).Debugf("now syncing, suppress sending updates. start deferral timer(%d)", deferral) - time.AfterFunc(time.Second*time.Duration(deferral), func() { - req := NewGrpcRequest(REQ_DEFERRAL_TIMER_EXPIRED, peer.fsm.pConf.Config.NeighborAddress, bgp.RouteFamily(0), nil) - server.GrpcReqCh <- req - <-req.ResponseCh - }) + time.AfterFunc(time.Second*time.Duration(deferral), deferralExpiredFunc(bgp.RouteFamily(0))) } } else { if server.shutdown && nextState == bgp.BGP_FSM_IDLE { @@ -915,7 +997,11 @@ func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) []*SenderMsg { } if len(eor) > 0 { + rtc := false for _, f := range eor { + if f == bgp.RF_RTC_UC { + rtc = true + } for i, a := range peer.fsm.pConf.AfiSafis { if g, _ := bgp.GetRouteFamily(string(a.AfiSafiName)); f == g { peer.fsm.pConf.AfiSafis[i].MpGracefulRestart.State.EndOfRibReceived = true @@ -954,7 +1040,11 @@ func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) []*SenderMsg { log.WithFields(log.Fields{ "Topic": "Server", }).Info("sync finished") + } + + // we don't delay non-route-target NLRIs when local-restarting + rtc = false } if peer.fsm.pConf.GracefulRestart.State.PeerRestarting { if peer.recvedAllEOR() { @@ -967,6 +1057,28 @@ func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) []*SenderMsg { m, _ := server.propagateUpdate(peer, pathList) msgs = append(msgs, m...) } + + // we don't delay non-route-target NLRIs when peer is restarting + rtc = false + } + + // received EOR of route-target address family + // outbound filter is now ready, let's flash non-route-target NLRIs + if c := config.GetAfiSafi(peer.fsm.pConf, bgp.RF_RTC_UC); rtc && c != nil && c.RouteTargetMembership.DeferralTime > 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Debug("received route-target eor. flash non-route-target NLRIs") + families := make([]bgp.RouteFamily, 0, len(peer.configuredRFlist())) + for _, f := range peer.configuredRFlist() { + if f != bgp.RF_RTC_UC { + families = append(families, f) + } + } + if paths, _ := peer.getBestFromLocal(families); len(paths) > 0 { + peer.adjRibOut.Update(paths) + msgs = append(msgs, newSenderMsg(peer, paths, nil, false)) + } } } default: @@ -2057,22 +2169,30 @@ func (server *BgpServer) handleGrpc(grpcReq *GrpcRequest) []*SenderMsg { continue } + families := []bgp.RouteFamily{grpcReq.RouteFamily} + if families[0] == bgp.RouteFamily(0) { + families = peer.configuredRFlist() + } + if grpcReq.RequestType == REQ_DEFERRAL_TIMER_EXPIRED { if peer.fsm.pConf.GracefulRestart.State.LocalRestarting { peer.fsm.pConf.GracefulRestart.State.LocalRestarting = false log.WithFields(log.Fields{ - "Topic": "Peer", - "Key": peer.fsm.pConf.Config.NeighborAddress, + "Topic": "Peer", + "Key": peer.ID(), + "Families": families, }).Debug("deferral timer expired") + } else if c := config.GetAfiSafi(peer.fsm.pConf, bgp.RF_RTC_UC); peer.fsm.rfMap[bgp.RF_RTC_UC] && !c.MpGracefulRestart.State.EndOfRibReceived { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Families": families, + }).Debug("route-target deferral timer expired") } else { continue } } - families := []bgp.RouteFamily{grpcReq.RouteFamily} - if families[0] == bgp.RouteFamily(0) { - families = peer.configuredRFlist() - } sentPathList := peer.adjRibOut.PathList(families, false) peer.adjRibOut.Drop(families) pathList, filtered := peer.getBestFromLocal(families) diff --git a/test/lib/base.py b/test/lib/base.py index d5dfc221..41807cbf 100644 --- a/test/lib/base.py +++ b/test/lib/base.py @@ -250,7 +250,7 @@ class BGPContainer(Container): super(BGPContainer, self).run() return self.WAIT_FOR_BOOT - def add_peer(self, peer, passwd=None, evpn=False, is_rs_client=False, + def add_peer(self, peer, passwd=None, vpn=False, is_rs_client=False, policies=None, passive=False, is_rr_client=False, cluster_id=None, flowspec=False, bridge='', reload_config=True, as2=False, @@ -273,7 +273,7 @@ class BGPContainer(Container): self.peers[peer] = {'neigh_addr': neigh_addr, 'passwd': passwd, - 'evpn': evpn, + 'vpn': vpn, 'flowspec': flowspec, 'is_rs_client': is_rs_client, 'is_rr_client': is_rr_client, diff --git a/test/lib/gobgp.py b/test/lib/gobgp.py index 36b4b86e..0b352fa5 100644 --- a/test/lib/gobgp.py +++ b/test/lib/gobgp.py @@ -212,10 +212,12 @@ class GoBGPContainer(BGPContainer): else: Exception('invalid ip address version. {0}'.format(version)) - if info['evpn']: + if info['vpn']: + afi_safi_list.append({'afi-safi-name': 'l3vpn-ipv4-unicast'}) + afi_safi_list.append({'afi-safi-name': 'l3vpn-ipv6-unicast'}) afi_safi_list.append({'afi-safi-name': 'l2vpn-evpn'}) - afi_safi_list.append({'afi-safi-name': 'encap'}) - afi_safi_list.append({'afi-safi-name': 'rtc'}) + afi_safi_list.append({'afi-safi-name': 'rtc', + 'route-target-membership': {'deferral-time': 10}}) if info['flowspec']: afi_safi_list.append({'afi-safi-name': 'ipv4-flowspec'}) diff --git a/test/scenario_test/evpn_test.py b/test/scenario_test/evpn_test.py index 7920bf2b..01827805 100644 --- a/test/scenario_test/evpn_test.py +++ b/test/scenario_test/evpn_test.py @@ -56,10 +56,12 @@ class GoBGPTestBase(unittest.TestCase): initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) + g1.local("gobgp vrf add vrf1 rd 10:10 rt both 10:10") + g2.local("gobgp vrf add vrf1 rd 10:10 rt both 10:10") for a, b in combinations(ctns, 2): - a.add_peer(b, evpn=True, passwd='evpn') - b.add_peer(a, evpn=True, passwd='evpn') + a.add_peer(b, vpn=True, passwd='evpn') + b.add_peer(a, vpn=True, passwd='evpn') cls.g1 = g1 cls.g2 = g2 diff --git a/test/scenario_test/rtc_test.py b/test/scenario_test/rtc_test.py new file mode 100644 index 00000000..f1f417c7 --- /dev/null +++ b/test/scenario_test/rtc_test.py @@ -0,0 +1,94 @@ +# Copyright (C) 2016 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. + +import unittest +from fabric.api import local +from lib import base +from lib.gobgp import * +from lib.quagga import * +import sys +import os +import time +import nose +from noseplugin import OptionParser, parser_option +from itertools import combinations + + +class GoBGPTestBase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + gobgp_ctn_image_name = parser_option.gobgp_image + base.TEST_PREFIX = parser_option.test_prefix + + g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', + ctn_image_name=gobgp_ctn_image_name, + log_level=parser_option.gobgp_log_level) + g2 = GoBGPContainer(name='g2', asn=65000, router_id='192.168.0.2', + ctn_image_name=gobgp_ctn_image_name, + log_level=parser_option.gobgp_log_level) + ctns = [g1, g2] + + initial_wait_time = max(ctn.run() for ctn in ctns) + + time.sleep(initial_wait_time) + + g1.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") + g1.local("gobgp vrf add vrf2 rd 200:200 rt both 200:200") + g2.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") + g2.local("gobgp vrf add vrf3 rd 300:300 rt both 300:300") + + g1.local("gobgp vrf vrf1 rib add 10.0.0.0/24") + g1.local("gobgp vrf vrf2 rib add 10.0.0.0/24") + g2.local("gobgp vrf vrf1 rib add 20.0.0.0/24") + g2.local("gobgp vrf vrf3 rib add 20.0.0.0/24") + + for a, b in combinations(ctns, 2): + a.add_peer(b, vpn=True, passwd='rtc', graceful_restart=True) + b.add_peer(a, vpn=True, passwd='rtc', graceful_restart=True) + + cls.g1 = g1 + cls.g2 = g2 + + # test each neighbor state is turned establish + def test_01_neighbor_established(self): + self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) + + def test_02_check_gobgp_adj_rib_out(self): + time.sleep(10) + self.assertTrue(len(self.g1.get_adj_rib_out(self.g2, rf='ipv4-l3vpn')) == 1) + self.assertTrue(len(self.g2.get_adj_rib_out(self.g1, rf='ipv4-l3vpn')) == 1) + + def test_03_add_vrf(self): + self.g1.local("gobgp vrf add vrf3 rd 300:300 rt both 300:300") + time.sleep(10) + self.assertTrue(len(self.g1.get_adj_rib_in(self.g2, rf='ipv4-l3vpn')) == 2) + + def test_04_del_vrf(self): + self.g1.local("gobgp vrf del vrf1") + time.sleep(10) + self.assertTrue(len(self.g1.get_adj_rib_in(self.g2, rf='ipv4-l3vpn')) == 1) + +if __name__ == '__main__': + if os.geteuid() is not 0: + print "you are not root." + sys.exit(1) + output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) + if int(output) is not 0: + print "docker not found" + sys.exit(1) + + nose.main(argv=sys.argv, addplugins=[OptionParser()], + defaultTest=sys.argv[0]) diff --git a/tools/pyang_plugins/gobgp.yang b/tools/pyang_plugins/gobgp.yang index 911de3bc..30cc9d30 100644 --- a/tools/pyang_plugins/gobgp.yang +++ b/tools/pyang_plugins/gobgp.yang @@ -826,4 +826,12 @@ module gobgp { augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { uses bgp-mp:all-afi-safi-common; } + + augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { + container route-target-membership { + leaf deferral-time { + type uint16; + } + } + } } |