summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2015-04-02 22:39:32 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2015-04-02 22:39:32 +0900
commit37639e796687e8912a1b51c45de9c4645655abe2 (patch)
tree7e2eb739f83a255574eb4d8d5c3133eefad3797f
parent0f122bd85d683836eb582ca67fd91ac2fb464b13 (diff)
add new cli 'gobgp'
Reimplemented in golang. Easy to install rather than playing with python package dependency. Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--gobgp/main.go527
-rw-r--r--tools/completion/gobgp-completion.bash195
2 files changed, 722 insertions, 0 deletions
diff --git a/gobgp/main.go b/gobgp/main.go
new file mode 100644
index 00000000..6ae9a0e1
--- /dev/null
+++ b/gobgp/main.go
@@ -0,0 +1,527 @@
+// 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 (
+ "encoding/json"
+ "fmt"
+ "github.com/jessevdk/go-flags"
+ "github.com/parnurzeal/gorequest"
+ "net"
+ "os"
+ "sort"
+)
+
+func execute(resource string, callback func(url string, r *gorequest.SuperAgent) *gorequest.SuperAgent) []byte {
+ r := gorequest.New()
+ url := globalOpts.URL + ":" + fmt.Sprint(globalOpts.Port) + "/v1/bgp/" + resource
+ if globalOpts.Debug {
+ fmt.Println(url)
+ }
+ r = callback(url, r)
+ _, body, err := r.End()
+ if err != nil {
+ fmt.Print("Failed to connect to gobgpd. It runs?\n")
+ if globalOpts.Debug {
+ fmt.Println(err)
+ }
+ os.Exit(1)
+ }
+ if globalOpts.Debug {
+ fmt.Println(body)
+ }
+ return []byte(body)
+}
+
+func post(resource string) []byte {
+ f := func(url string, r *gorequest.SuperAgent) *gorequest.SuperAgent {
+ return r.Post(url)
+ }
+ return execute(resource, f)
+}
+
+func get(resource string) []byte {
+ f := func(url string, r *gorequest.SuperAgent) *gorequest.SuperAgent {
+ return r.Get(url)
+ }
+ return execute(resource, f)
+}
+
+func formatTimedelta(d int64) string {
+ u := uint64(d)
+ neg := d < 0
+ if neg {
+ u = -u
+ }
+ secs := u % 60
+ u /= 60
+ mins := u % 60
+ u /= 60
+ hours := u % 60
+ days := u / 24
+
+ if days == 0 {
+ return fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs)
+ } else {
+ hours -= days * 24
+ return fmt.Sprintf("%dd ", days) + fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs)
+ }
+}
+
+type PeerConf struct {
+ RemoteIP string `json:"remote_ip"`
+ Id string `json:"id"`
+ RemoteAS uint32 `json:"remote_as"`
+ CapRefresh bool `json:"cap_refresh"`
+ CapEnhancedRefresh bool `json:"cap_enhanced_refresh"`
+ RemoteCap []int
+ LocalCap []int
+}
+
+type PeerInfo struct {
+ BgpState string `json:"bgp_state"`
+ AdminState string
+ FsmEstablishedTransitions uint32 `json:"fsm_established_transitions"`
+ TotalMessageOut uint32 `json:"total_message_out"`
+ TotalMessageIn uint32 `json:"total_message_in"`
+ UpdateMessageOut uint32 `json:"update_message_out"`
+ UpdateMessageIn uint32 `json:"update_message_in"`
+ KeepAliveMessageOut uint32 `json:"keepalive_message_out"`
+ KeepAliveMessageIn uint32 `json:"keepalive_message_in"`
+ OpenMessageOut uint32 `json:"open_message_out"`
+ OpenMessageIn uint32 `json:"open_message_in"`
+ NotificationOut uint32 `json:"notification_out"`
+ NotificationIn uint32 `json:"notification_in"`
+ RefreshMessageOut uint32 `json:"refresh_message_out"`
+ RefreshMessageIn uint32 `json:"refresh_message_in"`
+ DiscardedOut uint32
+ DiscardedIn uint32
+ Uptime int64 `json:"uptime"`
+ Downtime int64 `json:"downtime"`
+ LastError string `json:"last_error"`
+ Received uint32
+ Accepted uint32
+ Advertized uint32
+ OutQ int
+ Flops uint32
+}
+
+type peer struct {
+ Conf PeerConf
+ Info PeerInfo
+}
+
+type ShowNeighborCommand struct {
+}
+
+func showNeighbor(args []string) {
+ p := peer{}
+ b := get("neighbor/" + args[0])
+ e := json.Unmarshal(b, &p)
+ if e != nil {
+ fmt.Println(e)
+ } else {
+ fmt.Printf("BGP neighbor is %s, remote AS %d\n", p.Conf.RemoteIP, p.Conf.RemoteAS)
+ fmt.Printf(" BGP version 4, remote router ID %s\n", p.Conf.Id)
+ fmt.Printf(" BGP state = %s, up for %s\n", p.Info.BgpState, formatTimedelta(p.Info.Uptime))
+ fmt.Printf(" BGP OutQ = %d, Flops = %d\n", p.Info.OutQ, p.Info.Flops)
+ fmt.Printf(" Neighbor capabilities:\n")
+ caps := []int{}
+ lookup := func(val int, l []int) bool {
+ for _, v := range l {
+ if v == val {
+ return true
+ }
+ }
+ return false
+ }
+ caps = append(caps, p.Conf.LocalCap...)
+ for _, v := range p.Conf.RemoteCap {
+ if !lookup(v, caps) {
+ caps = append(caps, v)
+ }
+ }
+
+ sort.Sort(sort.IntSlice(caps))
+ capdict := map[int]string{1: "MULTIPROTOCOL",
+ 2: "ROUTE_REFRESH",
+ 4: "CARRYING_LABEL_INFO",
+ 64: "GRACEFUL_RESTART",
+ 65: "FOUR_OCTET_AS_NUMBER",
+ 70: "ENHANCED_ROUTE_REFRESH",
+ 128: "ROUTE_REFRESH_CISCO"}
+ for _, c := range caps {
+ k, found := capdict[c]
+ if !found {
+ k = "UNKNOWN (" + fmt.Sprint(c) + ")"
+ }
+ support := ""
+ if lookup(c, p.Conf.LocalCap) {
+ support += "advertised"
+ }
+ if lookup(c, p.Conf.RemoteCap) {
+ if len(support) != 0 {
+ support += " and "
+ }
+ support += "received"
+ }
+ fmt.Printf(" %s: %s\n", k, support)
+ }
+ fmt.Print(" Message statistics:\n")
+ fmt.Print(" Sent Rcvd\n")
+ fmt.Printf(" Opens: %10d %10d\n", p.Info.OpenMessageOut, p.Info.OpenMessageIn)
+ fmt.Printf(" Notifications: %10d %10d\n", p.Info.NotificationOut, p.Info.NotificationIn)
+ fmt.Printf(" Updates: %10d %10d\n", p.Info.UpdateMessageOut, p.Info.UpdateMessageIn)
+ fmt.Printf(" Keepalives: %10d %10d\n", p.Info.KeepAliveMessageOut, p.Info.KeepAliveMessageIn)
+ fmt.Printf(" Route Refesh: %10d %10d\n", p.Info.RefreshMessageOut, p.Info.RefreshMessageIn)
+ fmt.Printf(" Discarded: %10d %10d\n", p.Info.DiscardedOut, p.Info.DiscardedIn)
+ fmt.Printf(" Total: %10d %10d\n", p.Info.TotalMessageOut, p.Info.TotalMessageIn)
+ }
+}
+
+func (x *ShowNeighborCommand) Execute(args []string) error {
+ if len(args) < 1 || len(args) > 3 {
+ // TODO: proper help
+ fmt.Print("syntax error\n")
+ return nil
+ }
+
+ if len(args) == 1 {
+ showNeighbor(args)
+ } else {
+ parser := flags.NewParser(nil, flags.Default)
+ parser.AddCommand("local", "", "", NewShowNeighborRibCommand(args[0], "local-rib"))
+ parser.AddCommand("adj-in", "", "", NewShowNeighborRibCommand(args[0], "adj-rib-in"))
+ parser.AddCommand("adj-out", "", "", NewShowNeighborRibCommand(args[0], "adj-rib-out"))
+ if _, err := parser.ParseArgs(args[1:]); err != nil {
+ os.Exit(1)
+ }
+ }
+ return nil
+}
+
+type ShowNeighborRibCommand struct {
+ remoteIP net.IP
+ resource string
+}
+
+type path struct {
+ Network string
+ Nexthop string
+ Age float64
+ Attrs []map[string]interface{}
+ best bool
+}
+
+func showRoute(pathList []path, showAge bool, showBest bool) {
+ var format string
+ if showAge {
+ format = "%-2s %-18s %-15s %-10s %-10s %-s\n"
+ fmt.Printf(format, "", "Network", "Next Hop", "AS_PATH", "Age", "Attrs")
+ } else {
+ format = "%-2s %-18s %-15s %-10s %-s\n"
+ fmt.Printf(format, "", "Network", "Next Hop", "AS_PATH", "Attrs")
+ }
+
+ for _, p := range pathList {
+ aspath := func(attrs []map[string]interface{}) string {
+ for _, a := range attrs {
+ if a["Type"] == "BGP_ATTR_TYPE_AS_PATH" {
+ return fmt.Sprint(a["AsPath"])
+ }
+ }
+ return ""
+ }
+ formatAttrs := func(attrs []map[string]interface{}) string {
+ s := []string{}
+ for _, a := range attrs {
+ switch a["Type"] {
+ case "BGP_ATTR_TYPE_ORIGIN":
+ s = append(s, fmt.Sprintf("{Origin: %v}", a["Value"]))
+ case "BGP_ATTR_TYPE_MULTI_EXIT_DISC":
+ s = append(s, fmt.Sprintf("{Med: %v}", a["Metric"]))
+ case "BGP_ATTR_TYPE_LOCAL_PREF":
+ s = append(s, fmt.Sprintf("{LocalPref: %v}", a["Pref"]))
+ case "BGP_ATTR_TYPE_ATOMIC_AGGREGATE":
+ s = append(s, "AtomicAggregate")
+ case "BGP_ATTR_TYPE_AGGREGATE":
+ s = append(s, fmt.Sprintf("{Aggregate: {AS: %v, Address: %v}", a["AS"], a["Address"]))
+ case "BGP_ATTR_TYPE_COMMUNITIES":
+ l := []string{}
+ known := map[uint32]string{
+ 0xffff0000: "planned-shut",
+ 0xffff0001: "accept-own",
+ 0xffff0002: "ROUTE_FILTER_TRANSLATED_v4",
+ 0xffff0003: "ROUTE_FILTER_v4",
+ 0xffff0004: "ROUTE_FILTER_TRANSLATED_v6",
+ 0xffff0005: "ROUTE_FILTER_v6",
+ 0xffff0006: "LLGR_STALE",
+ 0xffff0007: "NO_LLGR",
+ 0xFFFFFF01: "NO_EXPORT",
+ 0xFFFFFF02: "NO_ADVERTISE",
+ 0xFFFFFF03: "NO_EXPORT_SUBCONFED",
+ 0xFFFFFF04: "NOPEER"}
+
+ for _, vv := range a["Value"].([]interface{}) {
+ v := uint32(vv.(float64))
+ k, found := known[v]
+ if found {
+ l = append(l, fmt.Sprint(k))
+ } else {
+ l = append(l, fmt.Sprintf("%d:%d", (0xffff0000&v)>>16, 0xffff&v))
+ }
+ }
+ s = append(s, fmt.Sprintf("{Cummunity: %v}", l))
+ case "BGP_ATTR_TYPE_ORIGINATOR_ID":
+ s = append(s, fmt.Sprintf("{Originator: %v|", a["Address"]))
+ case "BGP_ATTR_TYPE_CLUSTER_LIST":
+ s = append(s, fmt.Sprintf("{Cluster: %v|", a["Address"]))
+ case "BGP_ATTR_TYPE_AS4_PATH", "BGP_ATTR_TYPE_MP_UNREACH_NLRI", "BGP_ATTR_TYPE_MP_REACH_NLRI", "BGP_ATTR_TYPE_NEXT_HOP", "BGP_ATTR_TYPE_AS_PATH":
+ default:
+ s = append(s, fmt.Sprintf("{%v: %v}", a["Type"], a["Value"]))
+ }
+ }
+ return fmt.Sprint(s)
+ }
+ best := ""
+ if showBest {
+ if p.best {
+ best = "*>"
+ } else {
+ best = "* "
+ }
+ }
+ if showAge {
+ fmt.Printf(format, best, p.Network, p.Nexthop, aspath(p.Attrs), formatTimedelta(int64(p.Age)), formatAttrs(p.Attrs))
+ } else {
+ fmt.Printf(format, best, p.Network, p.Nexthop, aspath(p.Attrs), formatAttrs(p.Attrs))
+ }
+ }
+}
+
+func showRibCommand(isAdj, showAge, showBest bool, b []byte) {
+ type dest struct {
+ Prefix string
+ Paths []path
+ BestPathIdx int
+ }
+ type local struct {
+ Destinations []dest
+ }
+
+ m := []path{}
+ var e error
+ if isAdj == false {
+ l := local{}
+ e = json.Unmarshal(b, &l)
+ if e == nil {
+ for _, d := range l.Destinations {
+ for i, p := range d.Paths {
+ if i == d.BestPathIdx {
+ p.best = true
+ }
+ m = append(m, p)
+ }
+ }
+ }
+ } else {
+ e = json.Unmarshal(b, &m)
+ }
+ if e != nil {
+ return
+ }
+ showRoute(m, showAge, showBest)
+}
+
+func (x *ShowNeighborRibCommand) Execute(args []string) error {
+ var rt string
+ if len(args) == 0 {
+ if x.remoteIP.To4() != nil {
+ rt = "ipv4"
+ } else {
+ rt = "ipv6"
+ }
+ } else {
+ rt = args[1]
+ }
+ b := get("neighbor/" + x.remoteIP.String() + "/" + x.resource + "/" + rt)
+
+ isAdj := false
+ showBest := false
+ showAge := true
+ if x.resource == "adj-rib-out" || x.resource == "adj-rib-in" {
+ isAdj = true
+ if x.resource == "adj-rib-out" {
+ showAge = false
+ }
+ }
+ if x.resource == "local-rib" {
+ showBest = true
+ }
+ showRibCommand(isAdj, showAge, showBest, b)
+ return nil
+}
+
+func NewShowNeighborRibCommand(addr, resource string) *ShowNeighborRibCommand {
+ return &ShowNeighborRibCommand{
+ remoteIP: net.ParseIP(addr),
+ resource: resource,
+ }
+}
+
+type ShowNeighborsCommand struct {
+}
+
+func (x *ShowNeighborsCommand) Execute(args []string) error {
+ m := []peer{}
+ b := get("neighbors")
+ e := json.Unmarshal(b, &m)
+ if e != nil {
+ fmt.Println(e)
+ } else {
+ if globalOpts.Quiet {
+ for _, p := range m {
+ fmt.Println(p.Conf.RemoteIP)
+ }
+ return nil
+ }
+ maxaddrlen := 0
+ maxaslen := 0
+ maxtimelen := len("Up/Down")
+ timedelta := []string{}
+ for _, p := range m {
+ if len(p.Conf.RemoteIP) > maxaddrlen {
+ maxaddrlen = len(p.Conf.RemoteIP)
+ }
+
+ if len(fmt.Sprint(p.Conf.RemoteAS)) > maxaslen {
+ maxaslen = len(fmt.Sprint(p.Conf.RemoteAS))
+ }
+ var t string
+ if p.Info.Uptime == 0 {
+ t = "never"
+ } else if p.Info.BgpState == "BGP_FSM_ESTABLISHED" {
+ t = formatTimedelta(p.Info.Uptime)
+ } else {
+ t = formatTimedelta(p.Info.Downtime)
+ }
+ if len(t) > maxtimelen {
+ maxtimelen = len(t)
+ }
+ timedelta = append(timedelta, t)
+ }
+ var format string
+ format = "%-" + fmt.Sprint(maxaddrlen) + "s" + " %" + fmt.Sprint(maxaslen) + "s" + " %" + fmt.Sprint(maxtimelen) + "s"
+ format += " %-11s |%11s %8s %8s\n"
+ fmt.Printf(format, "Peer", "AS", "Up/Down", "State", "#Advertised", "Received", "Accepted")
+ format_fsm := func(admin, fsm string) string {
+ if admin == "ADMIN_STATE_DOWN" {
+ return "Idle(Admin)"
+ }
+
+ if fsm == "BGP_FSM_IDLE" {
+ return "Idle"
+ } else if fsm == "BGP_FSM_CONNECT" {
+ return "Connect"
+ } else if fsm == "BGP_FSM_ACTIVE" {
+ return "Active"
+ } else if fsm == "BGP_FSM_OPENSENT" {
+ return "Sent"
+ } else if fsm == "BGP_FSM_OPENCONFIRM" {
+ return "Confirm"
+ } else {
+ return "Establ"
+ }
+ }
+
+ for i, p := range m {
+ fmt.Printf(format, p.Conf.RemoteIP, fmt.Sprint(p.Conf.RemoteAS), timedelta[i], format_fsm(p.Info.AdminState, p.Info.BgpState), fmt.Sprint(p.Info.Advertized), fmt.Sprint(p.Info.Received), fmt.Sprint(p.Info.Accepted))
+ }
+ }
+
+ return nil
+}
+
+type ShowGlobalCommand struct {
+}
+
+func (x *ShowGlobalCommand) Execute(args []string) error {
+ var rt string
+ if len(args) == 0 {
+ rt = "ipv4"
+ } else {
+ rt = args[0]
+ }
+ b := get("global/rib/" + rt)
+ showRibCommand(false, true, true, b)
+ return nil
+}
+
+type ShowCommand struct {
+}
+
+func (x *ShowCommand) Execute(args []string) error {
+ parser := flags.NewParser(nil, flags.Default)
+ parser.AddCommand("neighbor", "", "", &ShowNeighborCommand{})
+ parser.AddCommand("neighbors", "", "", &ShowNeighborsCommand{})
+ parser.AddCommand("global", "", "", &ShowGlobalCommand{})
+ if _, err := parser.ParseArgs(args); err != nil {
+ os.Exit(1)
+ }
+ return nil
+}
+
+type ResetCommand struct {
+ resource string
+}
+
+func (x *ResetCommand) Execute(args []string) error {
+ if len(args) != 2 {
+ return nil
+ }
+ post("neighbor/" + args[1] + "/" + x.resource)
+ return nil
+}
+
+func NewResetCommand(resource string) *ResetCommand {
+ return &ResetCommand{
+ resource: resource,
+ }
+}
+
+var globalOpts struct {
+ URL string `short:"u" long:"url" description:"specifying an url" default:"http://127.0.0.1"`
+ Port int `short:"p" long:"port" description:"specifying a port" default:"8080"`
+ Debug bool `short:"d" long:"debug"`
+ Quiet bool `short:"q" long:"quiet"`
+}
+
+func main() {
+ parser := flags.NewParser(&globalOpts, flags.Default)
+ parser.AddCommand("show", "show stuff", "get information", &ShowCommand{})
+ parser.AddCommand("reset", "show stuff", "get information", NewResetCommand("reset"))
+ parser.AddCommand("softreset", "show stuff", "get information", NewResetCommand("softreset"))
+ parser.AddCommand("softresetin", "show stuff", "get information", NewResetCommand("softresetin"))
+ parser.AddCommand("softresetout", "show stuff", "get information", NewResetCommand("softresetout"))
+ parser.AddCommand("shutdown", "show stuff", "get information", NewResetCommand("shutdown"))
+ parser.AddCommand("enable", "show stuff", "get information", NewResetCommand("enable"))
+ parser.AddCommand("disable", "show stuff", "get information", NewResetCommand("disable"))
+
+ if _, err := parser.Parse(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
diff --git a/tools/completion/gobgp-completion.bash b/tools/completion/gobgp-completion.bash
new file mode 100644
index 00000000..b9e0c483
--- /dev/null
+++ b/tools/completion/gobgp-completion.bash
@@ -0,0 +1,195 @@
+#!bash
+
+__gobgp_q() {
+ gobgp 2>/dev/null "$@"
+}
+
+__search_target() {
+ local word target c=1
+
+ while [ $c -lt $cword ]; do
+ word="${words[c]}"
+ for target in $1; do
+ if [ "$target" = "$word" ]; then
+ echo "$target"
+ return
+ fi
+ done
+ ((c++))
+ done
+}
+
+__gobgp_table_list() {
+ local table_list=("local adj-in adj-out")
+ local table="$(__search_target "${table_list}")"
+ if [ -z "$table" ]; then
+ case "$cur" in
+ *)
+ COMPREPLY=( $( compgen -W "${table_list}" -- "$cur") )
+ ;;
+ esac
+ return
+ fi
+ COMPREPLY=( $( compgen -W "ipv4 ipv6 evpn" -- "$cur") )
+ return
+}
+
+__gobgp_neighbr_list() {
+ local neighbor_list=( $(__gobgp_q --quiet show neighbors) )
+ local target="$(__search_target "${neighbor_list[*]}")"
+ if [ -z "$target" ]; then
+ case "$cur" in
+ *)
+ COMPREPLY=( $( compgen -W "${neighbor_list[*]}" -- "$cur") )
+ ;;
+ esac
+ __ltrim_colon_completions "$cur"
+ return 0
+ fi
+ return 1
+}
+
+_gobgp_show_neighbor() {
+ __gobgp_neighbr_list
+ if [ $? -ne 0 ] ; then
+ __gobgp_table_list
+ fi
+}
+
+_gobgp_show_neighbors() {
+ case "$cur" in
+ *)
+ ;;
+ esac
+ return
+}
+
+_gobgp_show_global() {
+ local targets="ipv4 ipv6 evpn"
+ local target="$(__search_target "$targets")"
+ if [ -z "$target" ]; then
+ case "$cur" in
+ *)
+ COMPREPLY=( $( compgen -W "${targets[*]}" -- "$cur" ) )
+ ;;
+ esac
+ return
+ fi
+}
+
+_gobgp_show() {
+ local targets="neighbor neighbors global"
+ local target="$(__search_target "$targets")"
+ if [ -z "$target" ]; then
+ case "$cur" in
+ *)
+ COMPREPLY=( $( compgen -W "${targets[*]}" -- "$cur" ) )
+ ;;
+ esac
+ return
+ fi
+ _gobgp_show_${target}
+}
+
+__gobgp_generic_reset() {
+ local targets="neighbor"
+ local target="$(__search_target "$targets")"
+ if [ -z "$target" ]; then
+ case "$cur" in
+ *)
+ COMPREPLY=( $( compgen -W "${targets[*]}" -- "$cur" ) )
+ ;;
+ esac
+ return
+ fi
+ __gobgp_neighbr_list
+}
+
+_gobgp_reset() {
+ __gobgp_generic_reset
+}
+
+_gobgp_softreset() {
+ __gobgp_generic_reset
+}
+
+_gobgp_softresetin() {
+ __gobgp_generic_reset
+}
+
+_gobgp_softresetout() {
+ __gobgp_generic_reset
+}
+
+_gobgp_shutdown() {
+ __gobgp_generic_reset
+}
+
+_gobgp_enable() {
+ __gobgp_generic_reset
+}
+
+_gobgp_disable() {
+ __gobgp_generic_reset
+}
+
+_gobgp_gobgp() {
+ case "$prev" in
+ -h)
+ return
+ ;;
+ *)
+ ;;
+ esac
+
+ case "$cur" in
+ -*)
+ COMPREPLY=( $( compgen -W "-h" -- "$cur" ) )
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
+ ;;
+ esac
+}
+
+_gobgp() {
+ local commands=(
+ show
+ reset
+ softreset
+ softresetin
+ softresetout
+ shutdown
+ enable
+ disable
+ )
+
+ COMPREPLY=()
+ local cur prev words cword
+ _get_comp_words_by_ref -n : cur prev words cword
+
+ local command='gobgp'
+ local counter=1
+ while [ $counter -lt $cword ]; do
+ case "${words[$counter]}" in
+ -h)
+ (( counter++ ))
+ ;;
+ -*)
+ ;;
+ *)
+ command="${words[$counter]}"
+ cpos=$counter
+ (( cpos++ ))
+ break
+ ;;
+ esac
+ (( counter++ ))
+ done
+ local completions_func=_gobgp_${command}
+ declare -F $completions_func > /dev/null && $completions_func
+
+ return 0
+}
+
+complete -F _gobgp gobgp