diff options
Diffstat (limited to 'cmd/gobgp/neighbor.go')
-rw-r--r-- | cmd/gobgp/neighbor.go | 1467 |
1 files changed, 1467 insertions, 0 deletions
diff --git a/cmd/gobgp/neighbor.go b/cmd/gobgp/neighbor.go new file mode 100644 index 00000000..d0499950 --- /dev/null +++ b/cmd/gobgp/neighbor.go @@ -0,0 +1,1467 @@ +// 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 main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net" + "sort" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/apiutil" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +// used in showRoute() to determine the width of each column +var ( + columnWidthPrefix = 20 + columnWidthNextHop = 20 + columnWidthAsPath = 20 + columnWidthLabel = 10 +) + +func updateColumnWidth(nlri, nexthop, aspath, label string) { + if prefixLen := len(nlri); columnWidthPrefix < prefixLen { + columnWidthPrefix = prefixLen + } + if columnWidthNextHop < len(nexthop) { + columnWidthNextHop = len(nexthop) + } + if columnWidthAsPath < len(aspath) { + columnWidthAsPath = len(aspath) + } + if columnWidthLabel < len(label) { + columnWidthLabel = len(label) + } +} + +func getNeighbors(vrf string) ([]*api.Peer, error) { + adv := true + if vrf != "" { + adv = false + } else if t := neighborsOpts.Transport; t != "" { + switch t { + case "ipv4", "ipv6": + adv = false + default: + return nil, fmt.Errorf("invalid transport: %s", t) + } + } + stream, err := client.ListPeer(ctx, &api.ListPeerRequest{ + EnableAdvertised: adv, + }) + + l := make([]*api.Peer, 0, 1024) + for { + r, err := stream.Recv() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + l = append(l, r.Peer) + } + return l, err +} + +func getASN(p *api.Peer) string { + asn := "*" + if p.State.PeerAs > 0 { + asn = fmt.Sprint(p.State.PeerAs) + } + return asn +} + +func showNeighbors(vrf string) error { + m, err := getNeighbors(vrf) + if err != nil { + return err + } + if globalOpts.Json { + j, _ := json.Marshal(m) + fmt.Println(string(j)) + return nil + } + + if globalOpts.Quiet { + for _, p := range m { + fmt.Println(p.State.NeighborAddress) + } + return nil + } + maxaddrlen := 0 + maxaslen := 2 + maxtimelen := len("Up/Down") + timedelta := []string{} + + sort.Slice(m, func(i, j int) bool { + p1 := m[i].Conf.NeighborAddress + p2 := m[j].Conf.NeighborAddress + p1Isv4 := !strings.Contains(p1, ":") + p2Isv4 := !strings.Contains(p2, ":") + if p1Isv4 != p2Isv4 { + return p1Isv4 + } + addrlen := 128 + if p1Isv4 { + addrlen = 32 + } + strings := sort.StringSlice{cidr2prefix(fmt.Sprintf("%s/%d", p1, addrlen)), + cidr2prefix(fmt.Sprintf("%s/%d", p2, addrlen))} + return strings.Less(0, 1) + }) + + now := time.Now() + for _, n := range m { + if i := len(n.Conf.NeighborInterface); i > maxaddrlen { + maxaddrlen = i + } else if j := len(n.State.NeighborAddress); j > maxaddrlen { + maxaddrlen = j + } + if l := len(getASN(n)); l > maxaslen { + maxaslen = l + } + timeStr := "never" + if n.Timers.State.Uptime != 0 { + t := int64(n.Timers.State.Downtime) + if n.State.SessionState == api.PeerState_ESTABLISHED { + t = int64(n.Timers.State.Uptime) + } + timeStr = formatTimedelta(int64(now.Sub(time.Unix(int64(t), 0)).Seconds())) + } + if len(timeStr) > maxtimelen { + maxtimelen = len(timeStr) + } + timedelta = append(timedelta, timeStr) + } + + format := "%-" + fmt.Sprint(maxaddrlen) + "s" + " %" + fmt.Sprint(maxaslen) + "s" + " %" + fmt.Sprint(maxtimelen) + "s" + format += " %-11s |%9s %9s\n" + fmt.Printf(format, "Peer", "AS", "Up/Down", "State", "#Received", "Accepted") + formatFsm := func(admin api.PeerState_AdminState, fsm api.PeerState_SessionState) string { + switch admin { + case api.PeerState_DOWN: + return "Idle(Admin)" + case api.PeerState_PFX_CT: + return "Idle(PfxCt)" + } + + switch fsm { + case api.PeerState_UNKNOWN: + // should never happen + return "Unknown" + case api.PeerState_IDLE: + return "Idle" + case api.PeerState_CONNECT: + return "Connect" + case api.PeerState_ACTIVE: + return "Active" + case api.PeerState_OPENSENT: + return "Sent" + case api.PeerState_OPENCONFIRM: + return "Confirm" + case api.PeerState_ESTABLISHED: + return "Establ" + default: + return string(fsm) + } + } + + for i, n := range m { + neigh := n.State.NeighborAddress + if n.Conf.NeighborInterface != "" { + neigh = n.Conf.NeighborInterface + } + fmt.Printf(format, neigh, getASN(n), timedelta[i], formatFsm(n.State.AdminState, n.State.SessionState), fmt.Sprint(n.State.Received), fmt.Sprint(n.State.Accepted)) + } + + return nil +} + +func showNeighbor(args []string) error { + stream, err := client.ListPeer(ctx, &api.ListPeerRequest{ + Address: args[0], + EnableAdvertised: true, + }) + if err != nil { + return err + } + r, err := stream.Recv() + if err != nil && err != io.EOF { + return err + } + p := r.Peer + + if globalOpts.Json { + j, _ := json.Marshal(p) + fmt.Println(string(j)) + return nil + } + + fmt.Printf("BGP neighbor is %s, remote AS %s", p.State.NeighborAddress, getASN(p)) + + if p.RouteReflector.RouteReflectorClient { + fmt.Printf(", route-reflector-client\n") + } else if p.RouteServer.RouteServerClient { + fmt.Printf(", route-server-client\n") + } else { + fmt.Printf("\n") + } + + id := "unknown" + if p.Conf.Id != "" { + id = p.Conf.Id + } + fmt.Printf(" BGP version 4, remote router ID %s\n", id) + fmt.Printf(" BGP state = %s", p.State.SessionState) + if p.Timers.State.Uptime > 0 { + fmt.Printf(", up for %s\n", formatTimedelta(int64(p.Timers.State.Uptime)-time.Now().Unix())) + } else { + fmt.Print("\n") + } + fmt.Printf(" BGP OutQ = %d, Flops = %d\n", p.State.Queues.Output, p.State.Flops) + fmt.Printf(" Hold time is %d, keepalive interval is %d seconds\n", int(p.Timers.State.NegotiatedHoldTime), int(p.Timers.State.KeepaliveInterval)) + fmt.Printf(" Configured hold time is %d, keepalive interval is %d seconds\n", int(p.Timers.Config.HoldTime), int(p.Timers.Config.KeepaliveInterval)) + + elems := make([]string, 0, 3) + if as := p.Conf.AllowOwnAs; as > 0 { + elems = append(elems, fmt.Sprintf("Allow Own AS: %d", as)) + } + switch p.Conf.RemovePrivateAs { + case api.PeerConf_ALL: + elems = append(elems, "Remove private AS: all") + case api.PeerConf_REPLACE: + elems = append(elems, "Remove private AS: replace") + } + if p.Conf.ReplacePeerAs { + elems = append(elems, "Replace peer AS: enabled") + } + + fmt.Printf(" %s\n", strings.Join(elems, ", ")) + + fmt.Printf(" Neighbor capabilities:\n") + caps := []bgp.ParameterCapabilityInterface{} + lookup := func(val bgp.ParameterCapabilityInterface, l []bgp.ParameterCapabilityInterface) bgp.ParameterCapabilityInterface { + for _, v := range l { + if v.Code() == val.Code() { + if v.Code() == bgp.BGP_CAP_MULTIPROTOCOL { + lhs := v.(*bgp.CapMultiProtocol).CapValue + rhs := val.(*bgp.CapMultiProtocol).CapValue + if lhs == rhs { + return v + } + continue + } + return v + } + } + return nil + } + lcaps, _ := apiutil.UnmarshalCapabilities(p.Conf.LocalCap) + caps = append(caps, lcaps...) + + rcaps, _ := apiutil.UnmarshalCapabilities(p.Conf.RemoteCap) + for _, c := range rcaps { + if lookup(c, caps) == nil { + caps = append(caps, c) + } + } + + sort.Slice(caps, func(i, j int) bool { + return caps[i].Code() < caps[j].Code() + }) + + firstMp := true + + for _, c := range caps { + support := "" + if m := lookup(c, lcaps); m != nil { + support += "advertised" + } + if lookup(c, rcaps) != nil { + if len(support) != 0 { + support += " and " + } + support += "received" + } + + switch c.Code() { + case bgp.BGP_CAP_MULTIPROTOCOL: + if firstMp { + fmt.Printf(" %s:\n", c.Code()) + firstMp = false + } + m := c.(*bgp.CapMultiProtocol).CapValue + fmt.Printf(" %s:\t%s\n", m, support) + case bgp.BGP_CAP_GRACEFUL_RESTART: + fmt.Printf(" %s:\t%s\n", c.Code(), support) + grStr := func(g *bgp.CapGracefulRestart) string { + str := "" + if len(g.Tuples) > 0 { + str += fmt.Sprintf("restart time %d sec", g.Time) + } + if g.Flags&0x08 > 0 { + if len(str) > 0 { + str += ", " + } + str += "restart flag set" + } + if g.Flags&0x04 > 0 { + if len(str) > 0 { + str += ", " + } + str += "notification flag set" + } + + if len(str) > 0 { + str += "\n" + } + for _, t := range g.Tuples { + str += fmt.Sprintf(" %s", bgp.AfiSafiToRouteFamily(t.AFI, t.SAFI)) + if t.Flags == 0x80 { + str += ", forward flag set" + } + str += "\n" + } + return str + } + if m := lookup(c, lcaps); m != nil { + g := m.(*bgp.CapGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Local: %s", s) + } + } + if m := lookup(c, rcaps); m != nil { + g := m.(*bgp.CapGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Remote: %s", s) + } + } + case bgp.BGP_CAP_LONG_LIVED_GRACEFUL_RESTART: + fmt.Printf(" %s:\t%s\n", c.Code(), support) + grStr := func(g *bgp.CapLongLivedGracefulRestart) string { + var str string + for _, t := range g.Tuples { + str += fmt.Sprintf(" %s, restart time %d sec", bgp.AfiSafiToRouteFamily(t.AFI, t.SAFI), t.RestartTime) + if t.Flags == 0x80 { + str += ", forward flag set" + } + str += "\n" + } + return str + } + if m := lookup(c, lcaps); m != nil { + g := m.(*bgp.CapLongLivedGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Local:\n%s", s) + } + } + if m := lookup(c, rcaps); m != nil { + g := m.(*bgp.CapLongLivedGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Remote:\n%s", s) + } + } + case bgp.BGP_CAP_EXTENDED_NEXTHOP: + fmt.Printf(" %s:\t%s\n", c.Code(), support) + exnhStr := func(e *bgp.CapExtendedNexthop) string { + lines := make([]string, 0, len(e.Tuples)) + for _, t := range e.Tuples { + var nhafi string + switch int(t.NexthopAFI) { + case bgp.AFI_IP: + nhafi = "ipv4" + case bgp.AFI_IP6: + nhafi = "ipv6" + default: + nhafi = fmt.Sprintf("%d", t.NexthopAFI) + } + line := fmt.Sprintf("nlri: %s, nexthop: %s", bgp.AfiSafiToRouteFamily(t.NLRIAFI, uint8(t.NLRISAFI)), nhafi) + lines = append(lines, line) + } + return strings.Join(lines, "\n") + } + if m := lookup(c, lcaps); m != nil { + e := m.(*bgp.CapExtendedNexthop) + if s := exnhStr(e); len(s) > 0 { + fmt.Printf(" Local: %s\n", s) + } + } + if m := lookup(c, rcaps); m != nil { + e := m.(*bgp.CapExtendedNexthop) + if s := exnhStr(e); len(s) > 0 { + fmt.Printf(" Remote: %s\n", s) + } + } + case bgp.BGP_CAP_ADD_PATH: + fmt.Printf(" %s:\t%s\n", c.Code(), support) + if m := lookup(c, lcaps); m != nil { + fmt.Println(" Local:") + for _, item := range m.(*bgp.CapAddPath).Tuples { + fmt.Printf(" %s:\t%s\n", item.RouteFamily, item.Mode) + } + } + if m := lookup(c, rcaps); m != nil { + fmt.Println(" Remote:") + for _, item := range m.(*bgp.CapAddPath).Tuples { + fmt.Printf(" %s:\t%s\n", item.RouteFamily, item.Mode) + } + } + default: + fmt.Printf(" %s:\t%s\n", c.Code(), support) + } + } + fmt.Print(" Message statistics:\n") + fmt.Print(" Sent Rcvd\n") + fmt.Printf(" Opens: %10d %10d\n", p.State.Messages.Sent.Open, p.State.Messages.Received.Open) + fmt.Printf(" Notifications: %10d %10d\n", p.State.Messages.Sent.Notification, p.State.Messages.Received.Notification) + fmt.Printf(" Updates: %10d %10d\n", p.State.Messages.Sent.Update, p.State.Messages.Received.Update) + fmt.Printf(" Keepalives: %10d %10d\n", p.State.Messages.Sent.Keepalive, p.State.Messages.Received.Keepalive) + fmt.Printf(" Route Refresh: %10d %10d\n", p.State.Messages.Sent.Refresh, p.State.Messages.Received.Refresh) + fmt.Printf(" Discarded: %10d %10d\n", p.State.Messages.Sent.Discarded, p.State.Messages.Received.Discarded) + fmt.Printf(" Total: %10d %10d\n", p.State.Messages.Sent.Total, p.State.Messages.Received.Total) + fmt.Print(" Route statistics:\n") + fmt.Printf(" Advertised: %10d\n", p.State.Advertised) + fmt.Printf(" Received: %10d\n", p.State.Received) + fmt.Printf(" Accepted: %10d\n", p.State.Accepted) + first := true + for _, a := range p.AfiSafis { + limit := a.PrefixLimits + if limit != nil && limit.MaxPrefixes > 0 { + if first { + fmt.Println(" Prefix Limits:") + first = false + } + rf := apiutil.ToRouteFamily(limit.Family) + fmt.Printf(" %s:\tMaximum prefixes allowed %d", bgp.AddressFamilyNameMap[rf], limit.MaxPrefixes) + if limit.ShutdownThresholdPct > 0 { + fmt.Printf(", Threshold for warning message %d%%\n", limit.ShutdownThresholdPct) + } else { + fmt.Printf("\n") + } + } + } + return nil +} + +func getPathSymbolString(p *api.Path, idx int, showBest bool) string { + symbols := "" + if p.Stale { + symbols += "S" + } + if v := p.GetValidationDetail(); v != nil { + switch v.State { + case api.RPKIValidation_STATE_NOT_FOUND: + symbols += "N" + case api.RPKIValidation_STATE_VALID: + symbols += "V" + case api.RPKIValidation_STATE_INVALID: + symbols += "I" + } + + } + if showBest { + if idx == 0 && !p.IsNexthopInvalid { + symbols += "*>" + } else { + symbols += "* " + } + } + return symbols +} + +func getPathAttributeString(nlri bgp.AddrPrefixInterface, attrs []bgp.PathAttributeInterface) string { + s := make([]string, 0) + for _, a := range attrs { + switch a.GetType() { + case bgp.BGP_ATTR_TYPE_NEXT_HOP, bgp.BGP_ATTR_TYPE_MP_REACH_NLRI, bgp.BGP_ATTR_TYPE_AS_PATH, bgp.BGP_ATTR_TYPE_AS4_PATH: + continue + default: + s = append(s, a.String()) + } + } + switch n := nlri.(type) { + case *bgp.EVPNNLRI: + // We print non route key fields like path attributes. + switch route := n.RouteTypeData.(type) { + case *bgp.EVPNMacIPAdvertisementRoute: + s = append(s, fmt.Sprintf("[ESI: %s]", route.ESI.String())) + case *bgp.EVPNIPPrefixRoute: + s = append(s, fmt.Sprintf("[ESI: %s]", route.ESI.String())) + if route.GWIPAddress != nil { + s = append(s, fmt.Sprintf("[GW: %s]", route.GWIPAddress.String())) + } + } + } + return fmt.Sprint(s) +} + +func makeShowRouteArgs(p *api.Path, idx int, now time.Time, showAge, showBest, showLabel bool, showIdentifier bgp.BGPAddPathMode) []interface{} { + nlri, _ := apiutil.GetNativeNlri(p) + + // Path Symbols (e.g. "*>") + args := []interface{}{getPathSymbolString(p, idx, showBest)} + + // Path Identifier + switch showIdentifier { + case bgp.BGP_ADD_PATH_RECEIVE: + args = append(args, fmt.Sprint(p.GetIdentifier())) + case bgp.BGP_ADD_PATH_SEND: + args = append(args, fmt.Sprint(p.GetLocalIdentifier())) + } + + // NLRI + args = append(args, nlri) + + // Label + label := "" + if showLabel { + label = bgp.LabelString(nlri) + args = append(args, label) + } + + attrs, _ := apiutil.GetNativePathAttributes(p) + // Next Hop + nexthop := "fictitious" + if n := getNextHopFromPathAttributes(attrs); n != nil { + nexthop = n.String() + } + args = append(args, nexthop) + + // AS_PATH + aspathstr := func() string { + for _, attr := range attrs { + switch a := attr.(type) { + case *bgp.PathAttributeAsPath: + return bgp.AsPathString(a) + } + } + return "" + }() + args = append(args, aspathstr) + + // Age + if showAge { + t := time.Unix(p.Age, 0) + args = append(args, formatTimedelta(int64(now.Sub(t).Seconds()))) + } + + // Path Attributes + pattrstr := getPathAttributeString(nlri, attrs) + args = append(args, pattrstr) + + updateColumnWidth(nlri.String(), nexthop, aspathstr, label) + + return args +} + +func showRoute(dsts []*api.Destination, showAge, showBest, showLabel bool, showIdentifier bgp.BGPAddPathMode) { + pathStrs := make([][]interface{}, 0, len(dsts)) + now := time.Now() + for _, dst := range dsts { + for idx, p := range dst.Paths { + pathStrs = append(pathStrs, makeShowRouteArgs(p, idx, now, showAge, showBest, showLabel, showIdentifier)) + } + } + + headers := make([]interface{}, 0) + var format string + headers = append(headers, "") // Symbols + format = fmt.Sprintf("%%-3s") + if showIdentifier != bgp.BGP_ADD_PATH_NONE { + headers = append(headers, "ID") + format += "%-3s " + } + headers = append(headers, "Network") + format += fmt.Sprintf("%%-%ds ", columnWidthPrefix) + if showLabel { + headers = append(headers, "Labels") + format += fmt.Sprintf("%%-%ds ", columnWidthLabel) + } + headers = append(headers, "Next Hop", "AS_PATH") + format += fmt.Sprintf("%%-%ds %%-%ds ", columnWidthNextHop, columnWidthAsPath) + if showAge { + headers = append(headers, "Age") + format += "%-10s " + } + headers = append(headers, "Attrs") + format += "%-s\n" + + fmt.Printf(format, headers...) + for _, pathStr := range pathStrs { + fmt.Printf(format, pathStr...) + } +} + +func checkOriginAsWasNotShown(p *api.Path, asPath []bgp.AsPathParamInterface, shownAs map[uint32]struct{}) bool { + // the path was generated in internal + if len(asPath) == 0 { + return false + } + asList := asPath[len(asPath)-1].GetAS() + origin := asList[len(asList)-1] + + if _, ok := shownAs[origin]; ok { + return false + } + shownAs[origin] = struct{}{} + return true +} + +func showValidationInfo(p *api.Path, shownAs map[uint32]struct{}) error { + var asPath []bgp.AsPathParamInterface + attrs, _ := apiutil.GetNativePathAttributes(p) + for _, attr := range attrs { + if attr.GetType() == bgp.BGP_ATTR_TYPE_AS_PATH { + asPath = attr.(*bgp.PathAttributeAsPath).Value + } + } + + nlri, _ := apiutil.GetNativeNlri(p) + if len(asPath) == 0 { + return fmt.Errorf("The path to %s was locally generated.\n", nlri.String()) + } else if !checkOriginAsWasNotShown(p, asPath, shownAs) { + return nil + } + + status := p.GetValidationDetail().State + reason := p.GetValidationDetail().Reason + asList := asPath[len(asPath)-1].GetAS() + origin := asList[len(asList)-1] + + fmt.Printf("Target Prefix: %s, AS: %d\n", nlri.String(), origin) + fmt.Printf(" This route is %s", status) + switch status { + case api.RPKIValidation_STATE_INVALID: + fmt.Printf(" reason: %s\n", reason) + switch reason { + case api.RPKIValidation_REASON_AS: + fmt.Println(" No VRP ASN matches the route origin ASN.") + case api.RPKIValidation_REASON_LENGTH: + fmt.Println(" Route Prefix length is greater than the maximum length allowed by VRP(s) matching this route origin ASN.") + } + case api.RPKIValidation_STATE_NOT_FOUND: + fmt.Println("\n No VRP Covers the Route Prefix") + default: + fmt.Print("\n\n") + } + + printVRPs := func(l []*api.Roa) { + if len(l) == 0 { + fmt.Println(" No Entry") + } else { + var format string + if ip, _, _ := net.ParseCIDR(nlri.String()); ip.To4() != nil { + format = " %-18s %-6s %-10s\n" + } else { + format = " %-42s %-6s %-10s\n" + } + fmt.Printf(format, "Network", "AS", "MaxLen") + for _, m := range l { + fmt.Printf(format, m.Prefix, fmt.Sprint(m.As), fmt.Sprint(m.Maxlen)) + } + } + } + + fmt.Println(" Matched VRPs: ") + printVRPs(p.GetValidationDetail().Matched) + fmt.Println(" Unmatched AS VRPs: ") + printVRPs(p.GetValidationDetail().UnmatchedAs) + fmt.Println(" Unmatched Length VRPs: ") + printVRPs(p.GetValidationDetail().UnmatchedLength) + + return nil +} + +func showRibInfo(r, name string) error { + def := addr2AddressFamily(net.ParseIP(name)) + if r == cmdGlobal { + def = ipv4UC + } + family, err := checkAddressFamily(def) + if err != nil { + return err + } + + var t api.Resource + switch r { + case cmdGlobal: + t = api.Resource_GLOBAL + case cmdLocal: + t = api.Resource_LOCAL + case cmdAdjIn: + t = api.Resource_ADJ_IN + case cmdAdjOut: + t = api.Resource_ADJ_OUT + default: + return fmt.Errorf("invalid resource to show RIB info: %s", r) + } + rsp, err := client.GetTable(ctx, &api.GetTableRequest{ + Type: t, + Family: family, + Name: name, + }) + + if err != nil { + return err + } + + if globalOpts.Json { + j, _ := json.Marshal(rsp) + fmt.Println(string(j)) + return nil + } + fmt.Printf("Table %s\n", family) + fmt.Printf("Destination: %d, Path: %d\n", rsp.NumDestination, rsp.NumPath) + return nil +} + +func parseCIDRorIP(str string) (net.IP, *net.IPNet, error) { + ip, n, err := net.ParseCIDR(str) + if err == nil { + return ip, n, nil + } + ip = net.ParseIP(str) + if ip == nil { + return ip, nil, fmt.Errorf("invalid CIDR/IP") + } + return ip, nil, nil +} + +func showNeighborRib(r string, name string, args []string) error { + showBest := false + showAge := true + showLabel := false + showIdentifier := bgp.BGP_ADD_PATH_NONE + validationTarget := "" + + def := addr2AddressFamily(net.ParseIP(name)) + switch r { + case cmdGlobal: + def = ipv4UC + showBest = true + case cmdLocal: + showBest = true + case cmdAdjOut: + showAge = false + case cmdVRF: + def = ipv4UC + showBest = true + } + family, err := checkAddressFamily(def) + if err != nil { + return err + } + rf := apiutil.ToRouteFamily(family) + switch rf { + case bgp.RF_IPv4_MPLS, bgp.RF_IPv6_MPLS, bgp.RF_IPv4_VPN, bgp.RF_IPv6_VPN, bgp.RF_EVPN: + showLabel = true + } + + var filter []*api.TableLookupPrefix + if len(args) > 0 { + target := args[0] + switch rf { + case bgp.RF_EVPN: + // Uses target as EVPN Route Type string + default: + if _, _, err = parseCIDRorIP(target); err != nil { + return err + } + } + var option api.TableLookupOption + args = args[1:] + for len(args) != 0 { + if args[0] == "longer-prefixes" { + option = api.TableLookupOption_LOOKUP_LONGER + } else if args[0] == "shorter-prefixes" { + option = api.TableLookupOption_LOOKUP_SHORTER + } else if args[0] == "validation" { + if r != cmdAdjIn { + return fmt.Errorf("RPKI information is supported for only adj-in.") + } + validationTarget = target + } else { + return fmt.Errorf("invalid format for route filtering") + } + args = args[1:] + } + filter = []*api.TableLookupPrefix{&api.TableLookupPrefix{ + Prefix: target, + LookupOption: option, + }, + } + } + + var t api.Resource + switch r { + case cmdGlobal: + t = api.Resource_GLOBAL + case cmdLocal: + t = api.Resource_LOCAL + case cmdAdjIn, cmdAccepted, cmdRejected: + t = api.Resource_ADJ_IN + showIdentifier = bgp.BGP_ADD_PATH_RECEIVE + case cmdAdjOut: + t = api.Resource_ADJ_OUT + showIdentifier = bgp.BGP_ADD_PATH_SEND + case cmdVRF: + t = api.Resource_VRF + } + + stream, err := client.ListPath(ctx, &api.ListPathRequest{ + Type: t, + Family: family, + Name: name, + Prefixes: filter, + }) + if err != nil { + return err + } + + rib := make([]*api.Destination, 0) + for { + r, err := stream.Recv() + if err == io.EOF { + break + } else if err != nil { + return err + } + rib = append(rib, r.Destination) + } + + switch r { + case cmdLocal, cmdAdjIn, cmdAccepted, cmdRejected, cmdAdjOut: + if len(rib) == 0 { + stream, err := client.ListPeer(ctx, &api.ListPeerRequest{ + Address: name, + }) + if err != nil { + return err + } + r, err := stream.Recv() + if err != nil && err != io.EOF { + return err + } + if r == nil { + return fmt.Errorf("Neighbor %v is not found", name) + } + if r.Peer.State.SessionState != api.PeerState_ESTABLISHED { + return fmt.Errorf("Neighbor %v's BGP session is not established", name) + } + } + } + + if globalOpts.Json { + d := make(map[string]*apiutil.Destination) + for _, dst := range rib { + d[dst.Prefix] = apiutil.NewDestination(dst) + } + j, _ := json.Marshal(d) + fmt.Println(string(j)) + return nil + } + + if validationTarget != "" { + // show RPKI validation info + d := func() *api.Destination { + for _, dst := range rib { + if dst.Prefix == validationTarget { + return dst + } + } + return nil + }() + if d == nil { + fmt.Println("Network not in table") + return nil + } + shownAs := make(map[uint32]struct{}) + for _, p := range d.GetPaths() { + if err := showValidationInfo(p, shownAs); err != nil { + return err + } + } + } else { + // show RIB + var dsts []*api.Destination + switch rf { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC: + type d struct { + prefix net.IP + dst *api.Destination + } + l := make([]*d, 0, len(rib)) + for _, dst := range rib { + _, p, _ := net.ParseCIDR(dst.Prefix) + l = append(l, &d{prefix: p.IP, dst: dst}) + } + + sort.Slice(l, func(i, j int) bool { + return bytes.Compare(l[i].prefix, l[j].prefix) < 0 + }) + + dsts = make([]*api.Destination, 0, len(rib)) + for _, s := range l { + dsts = append(dsts, s.dst) + } + default: + dsts = append(dsts, rib...) + } + + for _, d := range dsts { + switch r { + case cmdAccepted: + l := make([]*api.Path, 0, len(d.Paths)) + for _, p := range d.GetPaths() { + if !p.Filtered { + l = append(l, p) + } + } + d.Paths = l + case cmdRejected: + // always nothing + d.Paths = []*api.Path{} + default: + } + } + if len(dsts) > 0 { + showRoute(dsts, showAge, showBest, showLabel, showIdentifier) + } else { + fmt.Println("Network not in table") + } + } + return nil +} + +func resetNeighbor(cmd string, remoteIP string, args []string) error { + if reasonLen := len(neighborsOpts.Reason); reasonLen > bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX { + return fmt.Errorf("Too long reason for shutdown communication (max %d bytes)", bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX) + } + var comm string + soft := true + dir := api.ResetPeerRequest_BOTH + switch cmd { + case cmdReset: + soft = false + comm = neighborsOpts.Reason + case cmdSoftReset: + case cmdSoftResetIn: + dir = api.ResetPeerRequest_IN + case cmdSoftResetOut: + dir = api.ResetPeerRequest_OUT + } + _, err := client.ResetPeer(ctx, &api.ResetPeerRequest{ + Address: remoteIP, + Communication: comm, + Soft: soft, + Direction: dir, + }) + return err +} + +func stateChangeNeighbor(cmd string, remoteIP string, args []string) error { + if reasonLen := len(neighborsOpts.Reason); reasonLen > bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX { + return fmt.Errorf("Too long reason for shutdown communication (max %d bytes)", bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX) + } + switch cmd { + case cmdShutdown: + fmt.Printf("WARNING: command `%s` is deprecated. use `%s` instead\n", cmdShutdown, cmdDisable) + _, err := client.ShutdownPeer(ctx, &api.ShutdownPeerRequest{ + Address: remoteIP, + Communication: neighborsOpts.Reason, + }) + return err + case cmdEnable: + _, err := client.EnablePeer(ctx, &api.EnablePeerRequest{ + Address: remoteIP, + }) + return err + case cmdDisable: + _, err := client.DisablePeer(ctx, &api.DisablePeerRequest{ + Address: remoteIP, + }) + return err + } + return nil +} + +func showNeighborPolicy(remoteIP, policyType string, indent int) error { + var assignment *api.PolicyAssignment + var err error + var dir api.PolicyDirection + + switch strings.ToLower(policyType) { + case "import": + dir = api.PolicyDirection_IMPORT + case "export": + dir = api.PolicyDirection_EXPORT + default: + return fmt.Errorf("invalid policy type: choose from (in|import|export)") + } + stream, err := client.ListPolicyAssignment(ctx, &api.ListPolicyAssignmentRequest{ + Name: remoteIP, + Direction: dir, + }) + if err != nil { + return err + } + r, err := stream.Recv() + if err != nil { + return err + } + assignment = r.Assignment + + if globalOpts.Json { + j, _ := json.Marshal(assignment) + fmt.Println(string(j)) + return nil + } + + fmt.Printf("%s policy:\n", strings.Title(policyType)) + fmt.Printf("%sDefault: %s\n", strings.Repeat(" ", indent), assignment.DefaultAction.String()) + for _, p := range assignment.Policies { + fmt.Printf("%sName %s:\n", strings.Repeat(" ", indent), p.Name) + printPolicy(indent+4, p) + } + return nil +} + +func extractDefaultAction(args []string) ([]string, api.RouteAction, error) { + for idx, arg := range args { + if arg == "default" { + if len(args) < (idx + 2) { + return nil, api.RouteAction_NONE, fmt.Errorf("specify default action [accept|reject]") + } + typ := args[idx+1] + switch strings.ToLower(typ) { + case "accept": + return append(args[:idx], args[idx+2:]...), api.RouteAction_ACCEPT, nil + case "reject": + return append(args[:idx], args[idx+2:]...), api.RouteAction_REJECT, nil + default: + return nil, api.RouteAction_NONE, fmt.Errorf("invalid default action") + } + } + } + return args, api.RouteAction_NONE, nil +} + +func modNeighborPolicy(remoteIP, policyType, cmdType string, args []string) error { + if remoteIP == "" { + remoteIP = globalRIBName + } + + assign := &api.PolicyAssignment{ + Name: remoteIP, + } + + switch strings.ToLower(policyType) { + case "import": + assign.Direction = api.PolicyDirection_IMPORT + case "export": + assign.Direction = api.PolicyDirection_EXPORT + } + + usage := fmt.Sprintf("usage: gobgp neighbor %s policy %s %s", remoteIP, policyType, cmdType) + if remoteIP == "" { + usage = fmt.Sprintf("usage: gobgp global policy %s %s", policyType, cmdType) + } + + var err error + switch cmdType { + case cmdAdd, cmdSet: + if len(args) < 1 { + return fmt.Errorf("%s <policy name>... [default {%s|%s}]", usage, "accept", "reject") + } + var err error + var def api.RouteAction + args, def, err = extractDefaultAction(args) + if err != nil { + return fmt.Errorf("%s\n%s <policy name>... [default {%s|%s}]", err, usage, "accept", "reject") + } + assign.DefaultAction = def + } + ps := make([]*api.Policy, 0, len(args)) + for _, name := range args { + ps = append(ps, &api.Policy{Name: name}) + } + assign.Policies = ps + switch cmdType { + case cmdAdd: + _, err = client.AddPolicyAssignment(ctx, &api.AddPolicyAssignmentRequest{ + Assignment: assign, + }) + case cmdSet: + _, err = client.SetPolicyAssignment(ctx, &api.SetPolicyAssignmentRequest{ + Assignment: assign, + }) + case cmdDel: + all := false + if len(args) == 0 { + all = true + } + _, err = client.DeletePolicyAssignment(ctx, &api.DeletePolicyAssignmentRequest{ + Assignment: assign, + All: all, + }) + } + return err +} + +func modNeighbor(cmdType string, args []string) error { + params := map[string]int{ + "interface": paramSingle, + } + usage := fmt.Sprintf("usage: gobgp neighbor %s [ <neighbor-address> | interface <neighbor-interface> ]", cmdType) + if cmdType == cmdAdd { + usage += " as <VALUE>" + } else if cmdType == cmdUpdate { + usage += " [ as <VALUE> ]" + } + if cmdType == cmdAdd || cmdType == cmdUpdate { + params["as"] = paramSingle + params["family"] = paramSingle + params["vrf"] = paramSingle + params["route-reflector-client"] = paramSingle + params["route-server-client"] = paramFlag + params["allow-own-as"] = paramSingle + params["remove-private-as"] = paramSingle + params["replace-peer-as"] = paramFlag + params["ebgp-multihop-ttl"] = paramSingle + usage += " [ family <address-families-list> | vrf <vrf-name> | route-reflector-client [<cluster-id>] | route-server-client | allow-own-as <num> | remove-private-as (all|replace) | replace-peer-as | ebgp-multihop-ttl <ttl>]" + } + + m, err := extractReserved(args, params) + if err != nil || (len(m[""]) != 1 && len(m["interface"]) != 1) { + return fmt.Errorf("%s", usage) + } + + unnumbered := len(m["interface"]) > 0 + if !unnumbered { + if _, err := net.ResolveIPAddr("ip", m[""][0]); err != nil { + return err + } + } + + getNeighborAddress := func() (string, error) { + if unnumbered { + return config.GetIPv6LinkLocalNeighborAddress(m["interface"][0]) + } + return m[""][0], nil + } + + getNeighborConfig := func() (*api.Peer, error) { + addr, err := getNeighborAddress() + if err != nil { + return nil, err + } + var peer *api.Peer + switch cmdType { + case cmdAdd, cmdDel: + peer = &api.Peer{ + Conf: &api.PeerConf{}, + State: &api.PeerState{}, + } + if unnumbered { + peer.Conf.NeighborInterface = m["interface"][0] + } else { + peer.Conf.NeighborAddress = addr + } + peer.State.NeighborAddress = addr + case cmdUpdate: + stream, err := client.ListPeer(ctx, &api.ListPeerRequest{ + Address: addr, + }) + if err != nil { + return nil, err + } + r, err := stream.Recv() + if err != nil { + return nil, err + } + peer = r.Peer + default: + return nil, fmt.Errorf("invalid command: %s", cmdType) + } + return peer, nil + } + + updateNeighborConfig := func(peer *api.Peer) error { + if len(m["as"]) > 0 { + as, err := strconv.ParseUint(m["as"][0], 10, 32) + if err != nil { + return err + } + peer.Conf.PeerAs = uint32(as) + } + if len(m["family"]) == 1 { + peer.AfiSafis = make([]*api.AfiSafi, 0) // for the case of cmdUpdate + for _, f := range strings.Split(m["family"][0], ",") { + rf, err := bgp.GetRouteFamily(f) + if err != nil { + return err + } + afi, safi := bgp.RouteFamilyToAfiSafi(rf) + peer.AfiSafis = append(peer.AfiSafis, &api.AfiSafi{Config: &api.AfiSafiConfig{Family: apiutil.ToApiFamily(afi, safi)}}) + } + } + if len(m["vrf"]) == 1 { + peer.Conf.Vrf = m["vrf"][0] + } + if option, ok := m["route-reflector-client"]; ok { + peer.RouteReflector.RouteReflectorClient = true + if len(option) == 1 { + peer.RouteReflector.RouteReflectorClusterId = option[0] + } + } + if _, ok := m["route-server-client"]; ok { + peer.RouteServer.RouteServerClient = true + } + if option, ok := m["allow-own-as"]; ok { + as, err := strconv.ParseUint(option[0], 10, 8) + if err != nil { + return err + } + peer.Conf.AllowOwnAs = uint32(as) + } + if option, ok := m["remove-private-as"]; ok { + switch option[0] { + case "all": + peer.Conf.RemovePrivateAs = api.PeerConf_ALL + case "replace": + peer.Conf.RemovePrivateAs = api.PeerConf_REPLACE + default: + return fmt.Errorf("invalid remove-private-as value: all or replace") + } + } + if _, ok := m["replace-peer-as"]; ok { + peer.Conf.ReplacePeerAs = true + } + if len(m["ebgp-multihop-ttl"]) == 1 { + ttl, err := strconv.ParseUint(m["ebgp-multihop-ttl"][0], 10, 32) + if err != nil { + return err + } + peer.EbgpMultihop = &api.EbgpMultihop{ + Enabled: true, + MultihopTtl: uint32(ttl), + } + } + return nil + } + + n, err := getNeighborConfig() + if err != nil { + return err + } + + switch cmdType { + case cmdAdd: + if err = updateNeighborConfig(n); err != nil { + return err + } + _, err = client.AddPeer(ctx, &api.AddPeerRequest{ + Peer: n, + }) + case cmdDel: + _, err = client.DeletePeer(ctx, &api.DeletePeerRequest{ + Address: n.Conf.NeighborAddress, + Interface: n.Conf.NeighborInterface, + }) + case cmdUpdate: + if err = updateNeighborConfig(n); err != nil { + return err + } + _, err = client.UpdatePeer(ctx, &api.UpdatePeerRequest{ + Peer: n, + DoSoftResetIn: true, + }) + } + return err +} + +func newNeighborCmd() *cobra.Command { + + neighborCmdImpl := &cobra.Command{} + + type cmds struct { + names []string + f func(string, string, []string) error + } + + c := make([]cmds, 0, 3) + c = append(c, cmds{[]string{cmdLocal, cmdAdjIn, cmdAdjOut, cmdAccepted, cmdRejected}, showNeighborRib}) + c = append(c, cmds{[]string{cmdReset, cmdSoftReset, cmdSoftResetIn, cmdSoftResetOut}, resetNeighbor}) + c = append(c, cmds{[]string{cmdShutdown, cmdEnable, cmdDisable}, stateChangeNeighbor}) + + getPeer := func(addr string) (*api.Peer, error) { + var r *api.ListPeerResponse + stream, err := client.ListPeer(ctx, &api.ListPeerRequest{ + Address: addr, + }) + if err == nil { + r, err = stream.Recv() + } + if err != nil && err != io.EOF { + return nil, err + } + return r.Peer, nil + } + + for _, v := range c { + f := v.f + for _, name := range v.names { + c := &cobra.Command{ + Use: name, + Run: func(cmd *cobra.Command, args []string) { + addr := "" + switch name { + case cmdReset, cmdSoftReset, cmdSoftResetIn, cmdSoftResetOut, cmdShutdown: + if args[len(args)-1] == "all" { + addr = "all" + } + } + if addr == "" { + p, err := getPeer(args[len(args)-1]) + if err != nil { + exitWithError(err) + } + addr = p.State.NeighborAddress + } + err := f(cmd.Use, addr, args[:len(args)-1]) + if err != nil { + exitWithError(err) + } + }, + } + neighborCmdImpl.AddCommand(c) + switch name { + case cmdLocal, cmdAdjIn, cmdAdjOut: + n := name + c.AddCommand(&cobra.Command{ + Use: cmdSummary, + Run: func(cmd *cobra.Command, args []string) { + if err := showRibInfo(n, args[len(args)-1]); err != nil { + exitWithError(err) + } + }, + }) + } + } + } + + policyCmd := &cobra.Command{ + Use: cmdPolicy, + Run: func(cmd *cobra.Command, args []string) { + peer, err := getPeer(args[0]) + if err != nil { + exitWithError(err) + } + remoteIP := peer.State.NeighborAddress + for _, v := range []string{cmdIn, cmdImport, cmdExport} { + if err := showNeighborPolicy(remoteIP, v, 4); err != nil { + exitWithError(err) + } + } + }, + } + + for _, v := range []string{cmdIn, cmdImport, cmdExport} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + peer, err := getPeer(args[0]) + if err != nil { + exitWithError(err) + } + remoteIP := peer.State.NeighborAddress + err = showNeighborPolicy(remoteIP, cmd.Use, 0) + if err != nil { + exitWithError(err) + } + }, + } + + for _, w := range []string{cmdAdd, cmdDel, cmdSet} { + subcmd := &cobra.Command{ + Use: w, + Run: func(subcmd *cobra.Command, args []string) { + peer, err := getPeer(args[len(args)-1]) + if err != nil { + exitWithError(err) + } + remoteIP := peer.State.NeighborAddress + args = args[:len(args)-1] + if err = modNeighborPolicy(remoteIP, cmd.Use, subcmd.Use, args); err != nil { + exitWithError(err) + } + }, + } + cmd.AddCommand(subcmd) + } + + policyCmd.AddCommand(cmd) + + } + + neighborCmdImpl.AddCommand(policyCmd) + + neighborCmd := &cobra.Command{ + Use: cmdNeighbor, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 0 { + err = showNeighbors("") + } else if len(args) == 1 { + err = showNeighbor(args) + } else { + args = append(args[1:], args[0]) + neighborCmdImpl.SetArgs(args) + err = neighborCmdImpl.Execute() + } + if err != nil { + exitWithError(err) + } + }, + } + + for _, v := range []string{cmdAdd, cmdDel, cmdUpdate} { + cmd := &cobra.Command{ + Use: v, + Run: func(c *cobra.Command, args []string) { + if err := modNeighbor(c.Use, args); err != nil { + exitWithError(err) + } + }, + } + neighborCmd.AddCommand(cmd) + } + + neighborCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + neighborCmd.PersistentFlags().StringVarP(&neighborsOpts.Reason, "reason", "", "", "specifying communication field on Cease NOTIFICATION message with Administrative Shutdown subcode") + neighborCmd.PersistentFlags().StringVarP(&neighborsOpts.Transport, "transport", "t", "", "specifying a transport protocol") + return neighborCmd +} |