diff options
Diffstat (limited to 'pkg/server/zclient.go')
-rw-r--r-- | pkg/server/zclient.go | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/pkg/server/zclient.go b/pkg/server/zclient.go new file mode 100644 index 00000000..3ef057ce --- /dev/null +++ b/pkg/server/zclient.go @@ -0,0 +1,450 @@ +// Copyright (C) 2015 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" + "math" + "net" + "strconv" + "strings" + "syscall" + "time" + + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/internal/pkg/zebra" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" +) + +// nexthopStateCache stores a map of nexthop IP to metric value. Especially, +// the metric value of math.MaxUint32 means the nexthop is unreachable. +type nexthopStateCache map[string]uint32 + +func (m nexthopStateCache) applyToPathList(paths []*table.Path) []*table.Path { + updated := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + if path == nil || path.IsWithdraw { + continue + } + metric, ok := m[path.GetNexthop().String()] + if !ok { + continue + } + isNexthopInvalid := metric == math.MaxUint32 + med, err := path.GetMed() + if err == nil && med == metric && path.IsNexthopInvalid == isNexthopInvalid { + // If the nexthop state of the given path is already up to date, + // skips this path. + continue + } + newPath := path.Clone(false) + if isNexthopInvalid { + newPath.IsNexthopInvalid = true + } else { + newPath.IsNexthopInvalid = false + newPath.SetMed(int64(metric), true) + } + updated = append(updated, newPath) + } + return updated +} + +func (m nexthopStateCache) updateByNexthopUpdate(body *zebra.NexthopUpdateBody) (updated bool) { + if len(body.Nexthops) == 0 { + // If NEXTHOP_UPDATE message does not contain any nexthop, the given + // nexthop is unreachable. + if _, ok := m[body.Prefix.String()]; !ok { + // Zebra will send an empty NEXTHOP_UPDATE message as the fist + // response for the NEXTHOP_REGISTER message. Here ignores it. + return false + } + m[body.Prefix.String()] = math.MaxUint32 // means unreachable + } else { + m[body.Prefix.String()] = body.Metric + } + return true +} + +func (m nexthopStateCache) filterPathToRegister(paths []*table.Path) []*table.Path { + filteredPaths := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + // Here filters out: + // - Nil path + // - Withdrawn path + // - External path (advertised from Zebra) in order avoid sending back + // - Unspecified nexthop address + // - Already registered nexthop + if path == nil || path.IsWithdraw || path.IsFromExternal() { + continue + } else if nexthop := path.GetNexthop(); nexthop.IsUnspecified() { + continue + } else if _, ok := m[nexthop.String()]; ok { + continue + } + filteredPaths = append(filteredPaths, path) + } + return filteredPaths +} + +func filterOutExternalPath(paths []*table.Path) []*table.Path { + filteredPaths := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + // Here filters out: + // - Nil path + // - External path (advertised from Zebra) in order avoid sending back + // - Unreachable path because invalidated by Zebra + if path == nil || path.IsFromExternal() || path.IsNexthopInvalid { + continue + } + filteredPaths = append(filteredPaths, path) + } + return filteredPaths +} + +func newIPRouteBody(dst []*table.Path) (body *zebra.IPRouteBody, isWithdraw bool) { + paths := filterOutExternalPath(dst) + if len(paths) == 0 { + return nil, false + } + path := paths[0] + + l := strings.SplitN(path.GetNlri().String(), "/", 2) + var prefix net.IP + nexthops := make([]net.IP, 0, len(paths)) + switch path.GetRouteFamily() { + case bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN: + if path.GetRouteFamily() == bgp.RF_IPv4_UC { + prefix = path.GetNlri().(*bgp.IPAddrPrefix).IPAddrPrefixDefault.Prefix.To4() + } else { + prefix = path.GetNlri().(*bgp.LabeledVPNIPAddrPrefix).IPAddrPrefixDefault.Prefix.To4() + } + for _, p := range paths { + nexthops = append(nexthops, p.GetNexthop().To4()) + } + case bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN: + if path.GetRouteFamily() == bgp.RF_IPv6_UC { + prefix = path.GetNlri().(*bgp.IPv6AddrPrefix).IPAddrPrefixDefault.Prefix.To16() + } else { + prefix = path.GetNlri().(*bgp.LabeledVPNIPv6AddrPrefix).IPAddrPrefixDefault.Prefix.To16() + } + for _, p := range paths { + nexthops = append(nexthops, p.GetNexthop().To16()) + } + default: + return nil, false + } + msgFlags := zebra.MESSAGE_NEXTHOP + plen, _ := strconv.ParseUint(l[1], 10, 8) + med, err := path.GetMed() + if err == nil { + msgFlags |= zebra.MESSAGE_METRIC + } + var flags zebra.FLAG + info := path.GetSource() + if info.AS == info.LocalAS { + flags = zebra.FLAG_IBGP | zebra.FLAG_INTERNAL + } else if info.MultihopTtl > 0 { + flags = zebra.FLAG_INTERNAL + } + return &zebra.IPRouteBody{ + Type: zebra.ROUTE_BGP, + Flags: flags, + SAFI: zebra.SAFI_UNICAST, + Message: msgFlags, + Prefix: prefix, + PrefixLength: uint8(plen), + Nexthops: nexthops, + Metric: med, + }, path.IsWithdraw +} + +func newNexthopRegisterBody(paths []*table.Path, nexthopCache nexthopStateCache) *zebra.NexthopRegisterBody { + paths = nexthopCache.filterPathToRegister(paths) + if len(paths) == 0 { + return nil + } + path := paths[0] + + family := path.GetRouteFamily() + nexthops := make([]*zebra.RegisteredNexthop, 0, len(paths)) + for _, p := range paths { + nexthop := p.GetNexthop() + var nh *zebra.RegisteredNexthop + switch family { + case bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN: + nh = &zebra.RegisteredNexthop{ + Family: syscall.AF_INET, + Prefix: nexthop.To4(), + } + case bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN: + nh = &zebra.RegisteredNexthop{ + Family: syscall.AF_INET6, + Prefix: nexthop.To16(), + } + default: + continue + } + nexthops = append(nexthops, nh) + } + + // If no nexthop needs to be registered or unregistered, skips to send + // message. + if len(nexthops) == 0 { + return nil + } + + return &zebra.NexthopRegisterBody{ + Nexthops: nexthops, + } +} + +func newNexthopUnregisterBody(family uint16, prefix net.IP) *zebra.NexthopRegisterBody { + return &zebra.NexthopRegisterBody{ + Nexthops: []*zebra.RegisteredNexthop{{ + Family: family, + Prefix: prefix, + }}, + } +} + +func newPathFromIPRouteMessage(m *zebra.Message) *table.Path { + header := m.Header + body := m.Body.(*zebra.IPRouteBody) + family := body.RouteFamily() + isWithdraw := body.IsWithdraw() + + var nlri bgp.AddrPrefixInterface + pattr := make([]bgp.PathAttributeInterface, 0) + origin := bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP) + pattr = append(pattr, origin) + + log.WithFields(log.Fields{ + "Topic": "Zebra", + "RouteType": body.Type.String(), + "Flag": body.Flags.String(), + "Message": body.Message, + "Prefix": body.Prefix, + "PrefixLength": body.PrefixLength, + "Nexthop": body.Nexthops, + "IfIndex": body.Ifindexs, + "Metric": body.Metric, + "Distance": body.Distance, + "Mtu": body.Mtu, + "api": header.Command.String(), + }).Debugf("create path from ip route message.") + + switch family { + case bgp.RF_IPv4_UC: + nlri = bgp.NewIPAddrPrefix(body.PrefixLength, body.Prefix.String()) + if len(body.Nexthops) > 0 { + pattr = append(pattr, bgp.NewPathAttributeNextHop(body.Nexthops[0].String())) + } + case bgp.RF_IPv6_UC: + nlri = bgp.NewIPv6AddrPrefix(body.PrefixLength, body.Prefix.String()) + nexthop := "" + if len(body.Nexthops) > 0 { + nexthop = body.Nexthops[0].String() + } + pattr = append(pattr, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) + default: + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Errorf("unsupport address family: %s", family) + return nil + } + + med := bgp.NewPathAttributeMultiExitDisc(body.Metric) + pattr = append(pattr, med) + + path := table.NewPath(nil, nlri, isWithdraw, pattr, time.Now(), false) + path.SetIsFromExternal(true) + return path +} + +type zebraClient struct { + client *zebra.Client + server *BgpServer + nexthopCache nexthopStateCache + dead chan struct{} +} + +func (z *zebraClient) getPathListWithNexthopUpdate(body *zebra.NexthopUpdateBody) []*table.Path { + rib := &table.TableManager{ + Tables: make(map[bgp.RouteFamily]*table.Table), + } + + var rfList []bgp.RouteFamily + switch body.Family { + case uint16(syscall.AF_INET): + rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN} + case uint16(syscall.AF_INET6): + rfList = []bgp.RouteFamily{bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN} + } + + for _, rf := range rfList { + tbl, _, err := z.server.GetRib("", rf, nil) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Family": rf.String(), + "Error": err, + }).Error("failed to get global rib") + continue + } + rib.Tables[rf] = tbl + } + + return rib.GetPathListWithNexthop(table.GLOBAL_RIB_NAME, rfList, body.Prefix) +} + +func (z *zebraClient) updatePathByNexthopCache(paths []*table.Path) { + paths = z.nexthopCache.applyToPathList(paths) + if len(paths) > 0 { + if err := z.server.UpdatePath("", paths); err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "PathList": paths, + }).Error("failed to update nexthop reachability") + } + } +} + +func (z *zebraClient) loop() { + w := z.server.Watch([]WatchOption{ + WatchBestPath(true), + WatchPostUpdate(true), + }...) + defer w.Stop() + + for { + select { + case <-z.dead: + return + case msg := <-z.client.Receive(): + switch body := msg.Body.(type) { + case *zebra.IPRouteBody: + if path := newPathFromIPRouteMessage(msg); path != nil { + if _, err := z.server.AddPath("", []*table.Path{path}); err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Path": path, + "Error": err, + }).Error("failed to add path from zebra") + } + } + case *zebra.NexthopUpdateBody: + if updated := z.nexthopCache.updateByNexthopUpdate(body); !updated { + continue + } + paths := z.getPathListWithNexthopUpdate(body) + if len(paths) == 0 { + // If there is no path bound for the given nexthop, send + // NEXTHOP_UNREGISTER message. + z.client.SendNexthopRegister(msg.Header.VrfId, newNexthopUnregisterBody(body.Family, body.Prefix), true) + delete(z.nexthopCache, body.Prefix.String()) + } + z.updatePathByNexthopCache(paths) + } + case ev := <-w.Event(): + switch msg := ev.(type) { + case *WatchEventBestPath: + if table.UseMultiplePaths.Enabled { + for _, paths := range msg.MultiPathList { + z.updatePathByNexthopCache(paths) + if body, isWithdraw := newIPRouteBody(paths); body != nil { + z.client.SendIPRoute(0, body, isWithdraw) + } + if body := newNexthopRegisterBody(paths, z.nexthopCache); body != nil { + z.client.SendNexthopRegister(0, body, false) + } + } + } else { + z.updatePathByNexthopCache(msg.PathList) + for _, path := range msg.PathList { + vrfs := []uint16{0} + if msg.Vrf != nil { + if v, ok := msg.Vrf[path.GetNlri().String()]; ok { + vrfs = append(vrfs, v) + } + } + for _, i := range vrfs { + if body, isWithdraw := newIPRouteBody([]*table.Path{path}); body != nil { + z.client.SendIPRoute(i, body, isWithdraw) + } + if body := newNexthopRegisterBody([]*table.Path{path}, z.nexthopCache); body != nil { + z.client.SendNexthopRegister(i, body, false) + } + } + } + } + case *WatchEventUpdate: + if body := newNexthopRegisterBody(msg.PathList, z.nexthopCache); body != nil { + vrfID := uint16(0) + for _, vrf := range z.server.GetVrf() { + if vrf.Name == msg.Neighbor.Config.Vrf { + vrfID = uint16(vrf.Id) + } + } + z.client.SendNexthopRegister(vrfID, body, false) + } + } + } + } +} + +func newZebraClient(s *BgpServer, url string, protos []string, version uint8, nhtEnable bool, nhtDelay uint8) (*zebraClient, error) { + l := strings.SplitN(url, ":", 2) + if len(l) != 2 { + return nil, fmt.Errorf("unsupported url: %s", url) + } + var cli *zebra.Client + var err error + for _, ver := range []uint8{version, 2, 3, 4} { + cli, err = zebra.NewClient(l[0], l[1], zebra.ROUTE_BGP, ver) + if err == nil { + break + } + // Retry with another Zebra message version + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Warnf("cannot connect to Zebra with message version %d. going to retry another version...", ver) + } + if cli == nil { + return nil, err + } + // Note: HELLO/ROUTER_ID_ADD messages are automatically sent to negotiate + // the Zebra message version in zebra.NewClient(). + // cli.SendHello() + // cli.SendRouterIDAdd() + cli.SendInterfaceAdd() + for _, typ := range protos { + t, err := zebra.RouteTypeFromString(typ) + if err != nil { + return nil, err + } + cli.SendRedistribute(t, zebra.VRF_DEFAULT) + } + w := &zebraClient{ + client: cli, + server: s, + nexthopCache: make(nexthopStateCache), + dead: make(chan struct{}), + } + go w.loop() + return w, nil +} |