diff options
Diffstat (limited to 'pkg/server/peer.go')
-rw-r--r-- | pkg/server/peer.go | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/pkg/server/peer.go b/pkg/server/peer.go new file mode 100644 index 00000000..1f3dd8d8 --- /dev/null +++ b/pkg/server/peer.go @@ -0,0 +1,522 @@ +// Copyright (C) 2014-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. + +package server + +import ( + "fmt" + "net" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/eapache/channels" + log "github.com/sirupsen/logrus" +) + +const ( + FLOP_THRESHOLD = time.Second * 30 + MIN_CONNECT_RETRY = 10 +) + +type PeerGroup struct { + Conf *config.PeerGroup + members map[string]config.Neighbor + dynamicNeighbors map[string]*config.DynamicNeighbor +} + +func NewPeerGroup(c *config.PeerGroup) *PeerGroup { + return &PeerGroup{ + Conf: c, + members: make(map[string]config.Neighbor), + dynamicNeighbors: make(map[string]*config.DynamicNeighbor), + } +} + +func (pg *PeerGroup) AddMember(c config.Neighbor) { + pg.members[c.State.NeighborAddress] = c +} + +func (pg *PeerGroup) DeleteMember(c config.Neighbor) { + delete(pg.members, c.State.NeighborAddress) +} + +func (pg *PeerGroup) AddDynamicNeighbor(c *config.DynamicNeighbor) { + pg.dynamicNeighbors[c.Config.Prefix] = c +} + +func newDynamicPeer(g *config.Global, neighborAddress string, pg *config.PeerGroup, loc *table.TableManager, policy *table.RoutingPolicy) *Peer { + conf := config.Neighbor{ + Config: config.NeighborConfig{ + PeerGroup: pg.Config.PeerGroupName, + }, + State: config.NeighborState{ + NeighborAddress: neighborAddress, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + } + if err := config.OverwriteNeighborConfigWithPeerGroup(&conf, pg); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": neighborAddress, + }).Debugf("Can't overwrite neighbor config: %s", err) + return nil + } + if err := config.SetDefaultNeighborConfigValues(&conf, pg, g); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": neighborAddress, + }).Debugf("Can't set default config: %s", err) + return nil + } + peer := NewPeer(g, &conf, loc, policy) + peer.fsm.state = bgp.BGP_FSM_ACTIVE + return peer +} + +type Peer struct { + tableId string + fsm *FSM + adjRibIn *table.AdjRib + outgoing *channels.InfiniteChannel + policy *table.RoutingPolicy + localRib *table.TableManager + prefixLimitWarned map[bgp.RouteFamily]bool + llgrEndChs []chan struct{} +} + +func NewPeer(g *config.Global, conf *config.Neighbor, loc *table.TableManager, policy *table.RoutingPolicy) *Peer { + peer := &Peer{ + outgoing: channels.NewInfiniteChannel(), + localRib: loc, + policy: policy, + fsm: NewFSM(g, conf, policy), + prefixLimitWarned: make(map[bgp.RouteFamily]bool), + } + if peer.isRouteServerClient() { + peer.tableId = conf.State.NeighborAddress + } else { + peer.tableId = table.GLOBAL_RIB_NAME + } + rfs, _ := config.AfiSafis(conf.AfiSafis).ToRfList() + peer.adjRibIn = table.NewAdjRib(rfs) + return peer +} + +func (peer *Peer) AS() uint32 { + return peer.fsm.pConf.State.PeerAs +} + +func (peer *Peer) ID() string { + return peer.fsm.pConf.State.NeighborAddress +} + +func (peer *Peer) TableID() string { + return peer.tableId +} + +func (peer *Peer) isIBGPPeer() bool { + return peer.fsm.pConf.State.PeerAs == peer.fsm.gConf.Config.As +} + +func (peer *Peer) isRouteServerClient() bool { + return peer.fsm.pConf.RouteServer.Config.RouteServerClient +} + +func (peer *Peer) isRouteReflectorClient() bool { + return peer.fsm.pConf.RouteReflector.Config.RouteReflectorClient +} + +func (peer *Peer) isGracefulRestartEnabled() bool { + return peer.fsm.pConf.GracefulRestart.State.Enabled +} + +func (peer *Peer) getAddPathMode(family bgp.RouteFamily) bgp.BGPAddPathMode { + if mode, y := peer.fsm.rfMap[family]; y { + return mode + } + return bgp.BGP_ADD_PATH_NONE +} + +func (peer *Peer) isAddPathReceiveEnabled(family bgp.RouteFamily) bool { + return (peer.getAddPathMode(family) & bgp.BGP_ADD_PATH_RECEIVE) > 0 +} + +func (peer *Peer) isAddPathSendEnabled(family bgp.RouteFamily) bool { + return (peer.getAddPathMode(family) & bgp.BGP_ADD_PATH_SEND) > 0 +} + +func (peer *Peer) isDynamicNeighbor() bool { + return peer.fsm.pConf.Config.NeighborAddress == "" && peer.fsm.pConf.Config.NeighborInterface == "" +} + +func (peer *Peer) recvedAllEOR() bool { + for _, a := range peer.fsm.pConf.AfiSafis { + if s := a.MpGracefulRestart.State; s.Enabled && !s.EndOfRibReceived { + return false + } + } + return true +} + +func (peer *Peer) configuredRFlist() []bgp.RouteFamily { + rfs, _ := config.AfiSafis(peer.fsm.pConf.AfiSafis).ToRfList() + return rfs +} + +func (peer *Peer) negotiatedRFList() []bgp.RouteFamily { + l := make([]bgp.RouteFamily, 0, len(peer.fsm.rfMap)) + for family, _ := range peer.fsm.rfMap { + l = append(l, family) + } + return l +} + +func (peer *Peer) toGlobalFamilies(families []bgp.RouteFamily) []bgp.RouteFamily { + if peer.fsm.pConf.Config.Vrf != "" { + fs := make([]bgp.RouteFamily, 0, len(families)) + for _, f := range families { + switch f { + case bgp.RF_IPv4_UC: + fs = append(fs, bgp.RF_IPv4_VPN) + case bgp.RF_IPv6_UC: + fs = append(fs, bgp.RF_IPv6_VPN) + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Family": f, + "VRF": peer.fsm.pConf.Config.Vrf, + }).Warn("invalid family configured for neighbor with vrf") + } + } + families = fs + } + return families +} + +func classifyFamilies(all, part []bgp.RouteFamily) ([]bgp.RouteFamily, []bgp.RouteFamily) { + a := []bgp.RouteFamily{} + b := []bgp.RouteFamily{} + for _, f := range all { + p := true + for _, g := range part { + if f == g { + p = false + a = append(a, f) + break + } + } + if p { + b = append(b, f) + } + } + return a, b +} + +func (peer *Peer) forwardingPreservedFamilies() ([]bgp.RouteFamily, []bgp.RouteFamily) { + list := []bgp.RouteFamily{} + for _, a := range peer.fsm.pConf.AfiSafis { + if s := a.MpGracefulRestart.State; s.Enabled && s.Received { + list = append(list, a.State.Family) + } + } + return classifyFamilies(peer.configuredRFlist(), list) +} + +func (peer *Peer) llgrFamilies() ([]bgp.RouteFamily, []bgp.RouteFamily) { + list := []bgp.RouteFamily{} + for _, a := range peer.fsm.pConf.AfiSafis { + if a.LongLivedGracefulRestart.State.Enabled { + list = append(list, a.State.Family) + } + } + return classifyFamilies(peer.configuredRFlist(), list) +} + +func (peer *Peer) isLLGREnabledFamily(family bgp.RouteFamily) bool { + if !peer.fsm.pConf.GracefulRestart.Config.LongLivedEnabled { + return false + } + fs, _ := peer.llgrFamilies() + for _, f := range fs { + if f == family { + return true + } + } + return false +} + +func (peer *Peer) llgrRestartTime(family bgp.RouteFamily) uint32 { + for _, a := range peer.fsm.pConf.AfiSafis { + if a.State.Family == family { + return a.LongLivedGracefulRestart.State.PeerRestartTime + } + } + return 0 +} + +func (peer *Peer) llgrRestartTimerExpired(family bgp.RouteFamily) bool { + all := true + for _, a := range peer.fsm.pConf.AfiSafis { + if a.State.Family == family { + a.LongLivedGracefulRestart.State.PeerRestartTimerExpired = true + } + s := a.LongLivedGracefulRestart.State + if s.Received && !s.PeerRestartTimerExpired { + all = false + } + } + return all +} + +func (peer *Peer) markLLGRStale(fs []bgp.RouteFamily) []*table.Path { + paths := peer.adjRibIn.PathList(fs, true) + for i, p := range paths { + doStale := true + for _, c := range p.GetCommunities() { + if c == uint32(bgp.COMMUNITY_NO_LLGR) { + doStale = false + p = p.Clone(true) + break + } + } + if doStale { + p = p.Clone(false) + p.SetCommunities([]uint32{uint32(bgp.COMMUNITY_LLGR_STALE)}, false) + } + paths[i] = p + } + return paths +} + +func (peer *Peer) stopPeerRestarting() { + peer.fsm.pConf.GracefulRestart.State.PeerRestarting = false + for _, ch := range peer.llgrEndChs { + close(ch) + } + peer.llgrEndChs = make([]chan struct{}, 0) + +} + +func (peer *Peer) filterPathFromSourcePeer(path, old *table.Path) *table.Path { + if peer.ID() != path.GetSource().Address.String() { + return path + } + + // Note: Multiple paths having the same prefix could exist the withdrawals + // list in the case of Route Server setup with import policies modifying + // paths. In such case, gobgp sends duplicated update messages; withdraw + // messages for the same prefix. + if !peer.isRouteServerClient() { + if peer.isRouteReflectorClient() && path.GetRouteFamily() == bgp.RF_RTC_UC { + // When the peer is a Route Reflector client and the given path + // contains the Route Tartget Membership NLRI, the path should not + // be withdrawn in order to signal the client to distribute routes + // with the specific RT to Route Reflector. + return path + } else if !path.IsWithdraw && old != nil && old.GetSource().Address.String() != peer.ID() { + // Say, peer A and B advertized same prefix P, and best path + // calculation chose a path from B as best. When B withdraws prefix + // P, best path calculation chooses the path from A as best. For + // peers other than A, this path should be advertised (as implicit + // withdrawal). However for A, we should advertise the withdrawal + // path. Thing is same when peer A and we advertized prefix P (as + // local route), then, we withdraws the prefix. + return old.Clone(true) + } + } + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Data": path, + }).Debug("From me, ignore.") + return nil +} + +func (peer *Peer) doPrefixLimit(k bgp.RouteFamily, c *config.PrefixLimitConfig) *bgp.BGPMessage { + if maxPrefixes := int(c.MaxPrefixes); maxPrefixes > 0 { + count := peer.adjRibIn.Count([]bgp.RouteFamily{k}) + pct := int(c.ShutdownThresholdPct) + if pct > 0 && !peer.prefixLimitWarned[k] && count > (maxPrefixes*pct/100) { + peer.prefixLimitWarned[k] = true + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": k.String(), + }).Warnf("prefix limit %d%% reached", pct) + } + if count > maxPrefixes { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": k.String(), + }).Warnf("prefix limit reached") + return bgp.NewBGPNotificationMessage(bgp.BGP_ERROR_CEASE, bgp.BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED, nil) + } + } + return nil + +} + +func (peer *Peer) updatePrefixLimitConfig(c []config.AfiSafi) error { + x := peer.fsm.pConf.AfiSafis + y := c + if len(x) != len(y) { + return fmt.Errorf("changing supported afi-safi is not allowed") + } + m := make(map[bgp.RouteFamily]config.PrefixLimitConfig) + for _, e := range x { + m[e.State.Family] = e.PrefixLimit.Config + } + for _, e := range y { + if p, ok := m[e.State.Family]; !ok { + return fmt.Errorf("changing supported afi-safi is not allowed") + } else if !p.Equal(&e.PrefixLimit.Config) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": e.Config.AfiSafiName, + "OldMaxPrefixes": p.MaxPrefixes, + "NewMaxPrefixes": e.PrefixLimit.Config.MaxPrefixes, + "OldShutdownThresholdPct": p.ShutdownThresholdPct, + "NewShutdownThresholdPct": e.PrefixLimit.Config.ShutdownThresholdPct, + }).Warnf("update prefix limit configuration") + peer.prefixLimitWarned[e.State.Family] = false + if msg := peer.doPrefixLimit(e.State.Family, &e.PrefixLimit.Config); msg != nil { + sendFsmOutgoingMsg(peer, nil, msg, true) + } + } + } + peer.fsm.pConf.AfiSafis = c + return nil +} + +func (peer *Peer) handleUpdate(e *FsmMsg) ([]*table.Path, []bgp.RouteFamily, *bgp.BGPMessage) { + m := e.MsgData.(*bgp.BGPMessage) + update := m.Body.(*bgp.BGPUpdate) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + "nlri": update.NLRI, + "withdrawals": update.WithdrawnRoutes, + "attributes": update.PathAttributes, + }).Debug("received update") + peer.fsm.pConf.Timers.State.UpdateRecvTime = time.Now().Unix() + if len(e.PathList) > 0 { + paths := make([]*table.Path, 0, len(e.PathList)) + eor := []bgp.RouteFamily{} + for _, path := range e.PathList { + if path.IsEOR() { + family := path.GetRouteFamily() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": family, + }).Debug("EOR received") + eor = append(eor, family) + continue + } + // RFC4271 9.1.2 Phase 2: Route Selection + // + // If the AS_PATH attribute of a BGP route contains an AS loop, the BGP + // route should be excluded from the Phase 2 decision function. + if aspath := path.GetAsPath(); aspath != nil { + if hasOwnASLoop(peer.fsm.peerInfo.LocalAS, int(peer.fsm.pConf.AsPathOptions.Config.AllowOwnAs), aspath) { + path.SetAsLooped(true) + continue + } + } + paths = append(paths, path) + } + peer.adjRibIn.Update(e.PathList) + for _, af := range peer.fsm.pConf.AfiSafis { + if msg := peer.doPrefixLimit(af.State.Family, &af.PrefixLimit.Config); msg != nil { + return nil, nil, msg + } + } + return paths, eor, nil + } + return nil, nil, nil +} + +func (peer *Peer) startFSMHandler(incoming *channels.InfiniteChannel, stateCh chan *FsmMsg) { + peer.fsm.h = NewFSMHandler(peer.fsm, incoming, stateCh, peer.outgoing) +} + +func (peer *Peer) StaleAll(rfList []bgp.RouteFamily) []*table.Path { + return peer.adjRibIn.StaleAll(rfList) +} + +func (peer *Peer) PassConn(conn *net.TCPConn) { + select { + case peer.fsm.connCh <- conn: + default: + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Warn("accepted conn is closed to avoid be blocked") + } +} + +func (peer *Peer) DropAll(rfList []bgp.RouteFamily) { + peer.adjRibIn.Drop(rfList) +} + +func (peer *Peer) stopFSM() error { + failed := false + addr := peer.fsm.pConf.State.NeighborAddress + t1 := time.AfterFunc(time.Minute*5, func() { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Warnf("Failed to free the fsm.h.t for %s", addr) + failed = true + }) + peer.fsm.h.t.Kill(nil) + peer.fsm.h.t.Wait() + t1.Stop() + if !failed { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Debug("freed fsm.h.t") + cleanInfiniteChannel(peer.outgoing) + } + failed = false + t2 := time.AfterFunc(time.Minute*5, func() { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Warnf("Failed to free the fsm.t for %s", addr) + failed = true + }) + peer.fsm.t.Kill(nil) + peer.fsm.t.Wait() + t2.Stop() + if !failed { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Debug("freed fsm.t") + return nil + } + return fmt.Errorf("Failed to free FSM for %s", addr) +} |