diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/gobgp/cmd/bmp.go | 102 | ||||
-rw-r--r-- | cmd/gobgp/cmd/common.go | 347 | ||||
-rw-r--r-- | cmd/gobgp/cmd/common_test.go | 40 | ||||
-rw-r--r-- | cmd/gobgp/cmd/global.go | 1654 | ||||
-rw-r--r-- | cmd/gobgp/cmd/global_test.go | 38 | ||||
-rw-r--r-- | cmd/gobgp/cmd/monitor.go | 201 | ||||
-rw-r--r-- | cmd/gobgp/cmd/mrt.go | 238 | ||||
-rw-r--r-- | cmd/gobgp/cmd/neighbor.go | 1321 | ||||
-rw-r--r-- | cmd/gobgp/cmd/policy.go | 1081 | ||||
-rw-r--r-- | cmd/gobgp/cmd/root.go | 94 | ||||
-rw-r--r-- | cmd/gobgp/cmd/rpki.go | 162 | ||||
-rw-r--r-- | cmd/gobgp/cmd/rpki_test.go | 76 | ||||
-rw-r--r-- | cmd/gobgp/cmd/vrf.go | 264 | ||||
-rw-r--r-- | cmd/gobgp/lib/lib.go | 78 | ||||
-rw-r--r-- | cmd/gobgp/lib/path.go | 143 | ||||
-rw-r--r-- | cmd/gobgp/main.go | 35 | ||||
-rw-r--r-- | cmd/gobgpd/main.go | 450 | ||||
-rw-r--r-- | cmd/gobgpd/util.go | 103 | ||||
-rw-r--r-- | cmd/gobgpd/util_windows.go | 24 |
19 files changed, 6451 insertions, 0 deletions
diff --git a/cmd/gobgp/cmd/bmp.go b/cmd/gobgp/cmd/bmp.go new file mode 100644 index 00000000..6de8eac4 --- /dev/null +++ b/cmd/gobgp/cmd/bmp.go @@ -0,0 +1,102 @@ +// 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 cmd + +import ( + "fmt" + "net" + "strconv" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bmp" + "github.com/spf13/cobra" +) + +func modBmpServer(cmdType string, args []string) error { + if len(args) < 1 { + return fmt.Errorf("usage: gobgp bmp %s <addr>[:<port>] [{pre|post|both|local-rib|all}]", cmdType) + } + + var address string + port := uint32(bmp.BMP_DEFAULT_PORT) + if host, p, err := net.SplitHostPort(args[0]); err != nil { + ip := net.ParseIP(args[0]) + if ip == nil { + return nil + } + address = args[0] + } else { + address = host + // Note: BmpServerConfig.Port is uint32 type, but the TCP/UDP port is + // 16-bit length. + pn, _ := strconv.ParseUint(p, 10, 16) + port = uint32(pn) + } + + var err error + switch cmdType { + case CMD_ADD: + policyType := config.BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY + if len(args) > 1 { + switch args[1] { + case "pre": + case "post": + policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY + case "both": + policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH + case "local-rib": + policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB + case "all": + policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_ALL + default: + return fmt.Errorf("invalid bmp policy type. valid type is {pre|post|both|local-rib|all}") + } + } + err = client.AddBMP(&config.BmpServerConfig{ + Address: address, + Port: port, + RouteMonitoringPolicy: policyType, + }) + case CMD_DEL: + err = client.DeleteBMP(&config.BmpServerConfig{ + Address: address, + Port: port, + }) + } + return err +} + +func NewBmpCmd() *cobra.Command { + + bmpCmd := &cobra.Command{ + Use: CMD_BMP, + } + + for _, w := range []string{CMD_ADD, CMD_DEL} { + subcmd := &cobra.Command{ + Use: w, + Run: func(cmd *cobra.Command, args []string) { + err := modBmpServer(cmd.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + bmpCmd.AddCommand(subcmd) + } + + return bmpCmd +} diff --git a/cmd/gobgp/cmd/common.go b/cmd/gobgp/cmd/common.go new file mode 100644 index 00000000..fbd75767 --- /dev/null +++ b/cmd/gobgp/cmd/common.go @@ -0,0 +1,347 @@ +// 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 cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "os" + "sort" + "strconv" + "strings" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + api "github.com/osrg/gobgp/api" + cli "github.com/osrg/gobgp/internal/pkg/client" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +const ( + CMD_GLOBAL = "global" + CMD_NEIGHBOR = "neighbor" + CMD_POLICY = "policy" + CMD_RIB = "rib" + CMD_ADD = "add" + CMD_DEL = "del" + CMD_ALL = "all" + CMD_SET = "set" + CMD_LOCAL = "local" + CMD_ADJ_IN = "adj-in" + CMD_ADJ_OUT = "adj-out" + CMD_RESET = "reset" + CMD_SOFT_RESET = "softreset" + CMD_SOFT_RESET_IN = "softresetin" + CMD_SOFT_RESET_OUT = "softresetout" + CMD_SHUTDOWN = "shutdown" + CMD_ENABLE = "enable" + CMD_DISABLE = "disable" + CMD_PREFIX = "prefix" + CMD_ASPATH = "as-path" + CMD_COMMUNITY = "community" + CMD_EXTCOMMUNITY = "ext-community" + CMD_IMPORT = "import" + CMD_EXPORT = "export" + CMD_IN = "in" + CMD_MONITOR = "monitor" + CMD_MRT = "mrt" + CMD_DUMP = "dump" + CMD_INJECT = "inject" + CMD_RPKI = "rpki" + CMD_RPKI_TABLE = "table" + CMD_RPKI_SERVER = "server" + CMD_VRF = "vrf" + CMD_ACCEPTED = "accepted" + CMD_REJECTED = "rejected" + CMD_STATEMENT = "statement" + CMD_CONDITION = "condition" + CMD_ACTION = "action" + CMD_UPDATE = "update" + CMD_ROTATE = "rotate" + CMD_BMP = "bmp" + CMD_LARGECOMMUNITY = "large-community" + CMD_SUMMARY = "summary" + CMD_VALIDATION = "validation" +) + +const ( + PARAM_FLAG = iota + PARAM_SINGLE + PARAM_LIST +) + +var subOpts struct { + AddressFamily string `short:"a" long:"address-family" description:"specifying an address family"` +} + +var neighborsOpts struct { + Reason string `short:"r" long:"reason" description:"specifying communication field on Cease NOTIFICATION message with Administrative Shutdown subcode"` + Transport string `short:"t" long:"transport" description:"specifying a transport protocol"` +} + +var mrtOpts struct { + OutputDir string + FileFormat string + Filename string `long:"filename" description:"MRT file name"` + RecordCount int64 `long:"count" description:"Number of records to inject"` + RecordSkip int64 `long:"skip" description:"Number of records to skip before injecting"` + QueueSize int `long:"batch-size" description:"Maximum number of updates to keep queued"` + Best bool `long:"only-best" description:"only keep best path routes"` + SkipV4 bool `long:"no-ipv4" description:"Skip importing IPv4 routes"` + SkipV6 bool `long:"no-ipv4" description:"Skip importing IPv6 routes"` + NextHop net.IP `long:"nexthop" description:"Rewrite nexthop"` +} + +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 % 24 + days := u / 24 + + if days == 0 { + return fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs) + } + return fmt.Sprintf("%dd ", days) + fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs) +} + +func cidr2prefix(cidr string) string { + _, n, err := net.ParseCIDR(cidr) + if err != nil { + return cidr + } + var buffer bytes.Buffer + for i := 0; i < len(n.IP); i++ { + buffer.WriteString(fmt.Sprintf("%08b", n.IP[i])) + } + ones, _ := n.Mask.Size() + return buffer.String()[:ones] +} + +func extractReserved(args []string, keys map[string]int) (map[string][]string, error) { + m := make(map[string][]string, len(keys)) + var k string + isReserved := func(s string) bool { + for r := range keys { + if s == r { + return true + } + } + return false + } + for _, arg := range args { + if isReserved(arg) { + k = arg + m[k] = make([]string, 0, 1) + } else { + m[k] = append(m[k], arg) + } + } + for k, v := range m { + if k == "" { + continue + } + switch keys[k] { + case PARAM_FLAG: + if len(v) != 0 { + return nil, fmt.Errorf("%s should not have arguments", k) + } + case PARAM_SINGLE: + if len(v) != 1 { + return nil, fmt.Errorf("%s should have one argument", k) + } + case PARAM_LIST: + if len(v) == 0 { + return nil, fmt.Errorf("%s should have one or more arguments", k) + } + } + } + return m, nil +} + +type neighbors []*config.Neighbor + +func (n neighbors) Len() int { + return len(n) +} + +func (n neighbors) Swap(i, j int) { + n[i], n[j] = n[j], n[i] +} + +func (n neighbors) Less(i, j int) bool { + p1 := n[i].State.NeighborAddress + p2 := n[j].State.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) +} + +type capabilities []bgp.ParameterCapabilityInterface + +func (c capabilities) Len() int { + return len(c) +} + +func (c capabilities) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c capabilities) Less(i, j int) bool { + return c[i].Code() < c[j].Code() +} + +type vrfs []*api.Vrf + +func (v vrfs) Len() int { + return len(v) +} + +func (v vrfs) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +func (v vrfs) Less(i, j int) bool { + return v[i].Name < v[j].Name +} + +func newClient() *cli.Client { + var grpcOpts []grpc.DialOption + if globalOpts.TLS { + var creds credentials.TransportCredentials + if globalOpts.CaFile == "" { + creds = credentials.NewClientTLSFromCert(nil, "") + } else { + var err error + creds, err = credentials.NewClientTLSFromFile(globalOpts.CaFile, "") + if err != nil { + exitWithError(err) + } + } + grpcOpts = []grpc.DialOption{ + grpc.WithTimeout(time.Second), + grpc.WithBlock(), + grpc.WithTransportCredentials(creds), + } + } + target := net.JoinHostPort(globalOpts.Host, strconv.Itoa(globalOpts.Port)) + client, err := cli.New(target, grpcOpts...) + if err != nil { + exitWithError(fmt.Errorf("failed to connect to %s over gRPC: %s", target, err)) + } + return client +} + +func addr2AddressFamily(a net.IP) bgp.RouteFamily { + if a.To4() != nil { + return bgp.RF_IPv4_UC + } else if a.To16() != nil { + return bgp.RF_IPv6_UC + } + return bgp.RouteFamily(0) +} + +func checkAddressFamily(def bgp.RouteFamily) (bgp.RouteFamily, error) { + var rf bgp.RouteFamily + var e error + switch subOpts.AddressFamily { + case "ipv4", "v4", "4": + rf = bgp.RF_IPv4_UC + case "ipv6", "v6", "6": + rf = bgp.RF_IPv6_UC + case "ipv4-l3vpn", "vpnv4", "vpn-ipv4": + rf = bgp.RF_IPv4_VPN + case "ipv6-l3vpn", "vpnv6", "vpn-ipv6": + rf = bgp.RF_IPv6_VPN + case "ipv4-labeled", "ipv4-labelled", "ipv4-mpls": + rf = bgp.RF_IPv4_MPLS + case "ipv6-labeled", "ipv6-labelled", "ipv6-mpls": + rf = bgp.RF_IPv6_MPLS + case "evpn": + rf = bgp.RF_EVPN + case "encap", "ipv4-encap": + rf = bgp.RF_IPv4_ENCAP + case "ipv6-encap": + rf = bgp.RF_IPv6_ENCAP + case "rtc": + rf = bgp.RF_RTC_UC + case "ipv4-flowspec", "ipv4-flow", "flow4": + rf = bgp.RF_FS_IPv4_UC + case "ipv6-flowspec", "ipv6-flow", "flow6": + rf = bgp.RF_FS_IPv6_UC + case "ipv4-l3vpn-flowspec", "ipv4vpn-flowspec", "flowvpn4": + rf = bgp.RF_FS_IPv4_VPN + case "ipv6-l3vpn-flowspec", "ipv6vpn-flowspec", "flowvpn6": + rf = bgp.RF_FS_IPv6_VPN + case "l2vpn-flowspec": + rf = bgp.RF_FS_L2_VPN + case "opaque": + rf = bgp.RF_OPAQUE + case "": + rf = def + default: + e = fmt.Errorf("unsupported address family: %s", subOpts.AddressFamily) + } + return rf, e +} + +func printError(err error) { + if globalOpts.Json { + j, _ := json.Marshal(struct { + Error string `json:"error"` + }{Error: err.Error()}) + fmt.Println(string(j)) + } else { + fmt.Println(err) + } +} + +func exitWithError(err error) { + printError(err) + os.Exit(1) +} + +func getNextHopFromPathAttributes(attrs []bgp.PathAttributeInterface) net.IP { + for _, attr := range attrs { + switch a := attr.(type) { + case *bgp.PathAttributeNextHop: + return a.Value + case *bgp.PathAttributeMpReachNLRI: + return a.Nexthop + } + } + return nil +} diff --git a/cmd/gobgp/cmd/common_test.go b/cmd/gobgp/cmd/common_test.go new file mode 100644 index 00000000..d7bb87b2 --- /dev/null +++ b/cmd/gobgp/cmd/common_test.go @@ -0,0 +1,40 @@ +// 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. + +package cmd + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ExtractReserved(t *testing.T) { + assert := assert.New(t) + args := strings.Split("10 rt 100:100 med 10 nexthop 10.0.0.1 aigp metric 10 local-pref 100", " ") + keys := map[string]int{ + "rt": PARAM_LIST, + "med": PARAM_SINGLE, + "nexthop": PARAM_SINGLE, + "aigp": PARAM_LIST, + "local-pref": PARAM_SINGLE} + m, _ := extractReserved(args, keys) + assert.True(len(m["rt"]) == 1) + assert.True(len(m["med"]) == 1) + assert.True(len(m["nexthop"]) == 1) + assert.True(len(m["aigp"]) == 2) + assert.True(len(m["local-pref"]) == 1) +} diff --git a/cmd/gobgp/cmd/global.go b/cmd/gobgp/cmd/global.go new file mode 100644 index 00000000..4559f4ac --- /dev/null +++ b/cmd/gobgp/cmd/global.go @@ -0,0 +1,1654 @@ +// 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 cmd + +import ( + "encoding/json" + "fmt" + "net" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +type ExtCommType int + +const ( + ACCEPT ExtCommType = iota + DISCARD + RATE + REDIRECT + MARK + ACTION + RT + ENCAP + ESI_LABEL + ROUTER_MAC + DEFAULT_GATEWAY + VALID + NOT_FOUND + INVALID +) + +var ExtCommNameMap = map[ExtCommType]string{ + ACCEPT: "accept", + DISCARD: "discard", + RATE: "rate-limit", + REDIRECT: "redirect", + MARK: "mark", + ACTION: "action", + RT: "rt", + ENCAP: "encap", + ESI_LABEL: "esi-label", + ROUTER_MAC: "router-mac", + DEFAULT_GATEWAY: "default-gateway", + VALID: "valid", + NOT_FOUND: "not-found", + INVALID: "invalid", +} + +var ExtCommValueMap = map[string]ExtCommType{ + ExtCommNameMap[ACCEPT]: ACCEPT, + ExtCommNameMap[DISCARD]: DISCARD, + ExtCommNameMap[RATE]: RATE, + ExtCommNameMap[REDIRECT]: REDIRECT, + ExtCommNameMap[MARK]: MARK, + ExtCommNameMap[ACTION]: ACTION, + ExtCommNameMap[RT]: RT, + ExtCommNameMap[ENCAP]: ENCAP, + ExtCommNameMap[ESI_LABEL]: ESI_LABEL, + ExtCommNameMap[ROUTER_MAC]: ROUTER_MAC, + ExtCommNameMap[DEFAULT_GATEWAY]: DEFAULT_GATEWAY, + ExtCommNameMap[VALID]: VALID, + ExtCommNameMap[NOT_FOUND]: NOT_FOUND, + ExtCommNameMap[INVALID]: INVALID, +} + +func rateLimitParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + exp := regexp.MustCompile(fmt.Sprintf("^(%s|(%s) (\\d+)(\\.(\\d+))?)( as (\\d+))?$", ExtCommNameMap[DISCARD], ExtCommNameMap[RATE])) + elems := exp.FindStringSubmatch(strings.Join(args, " ")) + if len(elems) != 8 { + return nil, fmt.Errorf("invalid rate-limit") + } + var rate float32 + var as uint64 + if elems[2] == ExtCommNameMap[RATE] { + f, err := strconv.ParseFloat(elems[3]+elems[4], 32) + if err != nil { + return nil, err + } + rate = float32(f) + } + if elems[7] != "" { + var err error + as, err = strconv.ParseUint(elems[7], 10, 16) + if err != nil { + return nil, err + } + } + return []bgp.ExtendedCommunityInterface{bgp.NewTrafficRateExtended(uint16(as), rate)}, nil +} + +func redirectParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[REDIRECT] { + return nil, fmt.Errorf("invalid redirect") + } + rt, err := bgp.ParseRouteTarget(strings.Join(args[1:], " ")) + if err != nil { + return nil, err + } + switch rt.(type) { + case *bgp.TwoOctetAsSpecificExtended: + r := rt.(*bgp.TwoOctetAsSpecificExtended) + return []bgp.ExtendedCommunityInterface{bgp.NewRedirectTwoOctetAsSpecificExtended(r.AS, r.LocalAdmin)}, nil + case *bgp.IPv4AddressSpecificExtended: + r := rt.(*bgp.IPv4AddressSpecificExtended) + return []bgp.ExtendedCommunityInterface{bgp.NewRedirectIPv4AddressSpecificExtended(r.IPv4.String(), r.LocalAdmin)}, nil + case *bgp.FourOctetAsSpecificExtended: + r := rt.(*bgp.FourOctetAsSpecificExtended) + return []bgp.ExtendedCommunityInterface{bgp.NewRedirectFourOctetAsSpecificExtended(r.AS, r.LocalAdmin)}, nil + case *bgp.IPv6AddressSpecificExtended: + r := rt.(*bgp.IPv6AddressSpecificExtended) + return []bgp.ExtendedCommunityInterface{bgp.NewRedirectIPv6AddressSpecificExtended(r.IPv6.String(), r.LocalAdmin)}, nil + } + return nil, fmt.Errorf("invalid redirect") +} + +func markParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[MARK] { + return nil, fmt.Errorf("invalid mark") + } + dscp, err := strconv.ParseUint(args[1], 10, 8) + if err != nil { + return nil, fmt.Errorf("invalid mark") + } + return []bgp.ExtendedCommunityInterface{bgp.NewTrafficRemarkExtended(uint8(dscp))}, nil +} + +func actionParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[ACTION] { + return nil, fmt.Errorf("invalid action") + } + sample := false + terminal := false + switch args[1] { + case "sample": + sample = true + case "terminal": + terminal = true + case "terminal-sample", "sample-terminal": + sample = true + terminal = true + default: + return nil, fmt.Errorf("invalid action") + } + return []bgp.ExtendedCommunityInterface{bgp.NewTrafficActionExtended(terminal, sample)}, nil +} + +func rtParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[RT] { + return nil, fmt.Errorf("invalid rt") + } + exts := make([]bgp.ExtendedCommunityInterface, 0, len(args[1:])) + for _, arg := range args[1:] { + rt, err := bgp.ParseRouteTarget(arg) + if err != nil { + return nil, err + } + exts = append(exts, rt) + } + return exts, nil +} + +func encapParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[ENCAP] { + return nil, fmt.Errorf("invalid encap") + } + var typ bgp.TunnelType + switch args[1] { + case "l2tpv3": + typ = bgp.TUNNEL_TYPE_L2TP3 + case "gre": + typ = bgp.TUNNEL_TYPE_GRE + case "ip-in-ip": + typ = bgp.TUNNEL_TYPE_IP_IN_IP + case "vxlan": + typ = bgp.TUNNEL_TYPE_VXLAN + case "nvgre": + typ = bgp.TUNNEL_TYPE_NVGRE + case "mpls": + typ = bgp.TUNNEL_TYPE_MPLS + case "mpls-in-gre": + typ = bgp.TUNNEL_TYPE_MPLS_IN_GRE + case "vxlan-gre": + typ = bgp.TUNNEL_TYPE_VXLAN_GRE + default: + return nil, fmt.Errorf("invalid encap type") + } + return []bgp.ExtendedCommunityInterface{bgp.NewEncapExtended(typ)}, nil +} + +func esiLabelParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[ESI_LABEL] { + return nil, fmt.Errorf("invalid esi-label") + } + label, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + return nil, err + } + isSingleActive := false + if len(args) > 2 { + switch args[2] { + case "single-active": + isSingleActive = true + case "all-active": + // isSingleActive = false + default: + return nil, fmt.Errorf("invalid esi-label") + } + } + o := &bgp.ESILabelExtended{ + Label: uint32(label), + IsSingleActive: isSingleActive, + } + return []bgp.ExtendedCommunityInterface{o}, nil +} + +func routerMacParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 2 || args[0] != ExtCommNameMap[ROUTER_MAC] { + return nil, fmt.Errorf("invalid router's mac") + } + hw, err := net.ParseMAC(args[1]) + if err != nil { + return nil, err + } + o := &bgp.RouterMacExtended{Mac: hw} + return []bgp.ExtendedCommunityInterface{o}, nil +} + +func defaultGatewayParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 1 || args[0] != ExtCommNameMap[DEFAULT_GATEWAY] { + return nil, fmt.Errorf("invalid default-gateway") + } + return []bgp.ExtendedCommunityInterface{bgp.NewDefaultGatewayExtended()}, nil +} + +func validationParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { + if len(args) < 1 { + return nil, fmt.Errorf("invalid validation state") + } + var typ bgp.ValidationState + switch args[0] { + case "valid": + typ = bgp.VALIDATION_STATE_VALID + case "not-found": + typ = bgp.VALIDATION_STATE_NOT_FOUND + case "invalid": + typ = bgp.VALIDATION_STATE_INVALID + default: + return nil, fmt.Errorf("invalid validation state") + } + return []bgp.ExtendedCommunityInterface{bgp.NewValidationExtended(typ)}, nil +} + +var ExtCommParserMap = map[ExtCommType]func([]string) ([]bgp.ExtendedCommunityInterface, error){ + ACCEPT: nil, + DISCARD: rateLimitParser, + RATE: rateLimitParser, + REDIRECT: redirectParser, + MARK: markParser, + ACTION: actionParser, + RT: rtParser, + ENCAP: encapParser, + ESI_LABEL: esiLabelParser, + ROUTER_MAC: routerMacParser, + DEFAULT_GATEWAY: defaultGatewayParser, + VALID: validationParser, + NOT_FOUND: validationParser, + INVALID: validationParser, +} + +func ParseExtendedCommunities(args []string) ([]bgp.ExtendedCommunityInterface, error) { + idxs := make([]struct { + t ExtCommType + i int + }, 0, len(ExtCommNameMap)) + for idx, v := range args { + if t, ok := ExtCommValueMap[v]; ok { + idxs = append(idxs, struct { + t ExtCommType + i int + }{t, idx}) + } + } + exts := make([]bgp.ExtendedCommunityInterface, 0, len(idxs)) + for i, idx := range idxs { + var a []string + f := ExtCommParserMap[idx.t] + if i < len(idxs)-1 { + a = args[:idxs[i+1].i-idx.i] + args = args[(idxs[i+1].i - idx.i):] + } else { + a = args + args = nil + } + if f == nil { + continue + } + ext, err := f(a) + if err != nil { + return nil, err + } + exts = append(exts, ext...) + } + if len(args) > 0 { + return nil, fmt.Errorf("failed to parse %v", args) + } + return exts, nil +} + +func ParseFlowSpecArgs(rf bgp.RouteFamily, args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // match <rule>... [then <action>...] [rd <rd>] [rt <rt>...] + req := 3 // match <key1> <arg1> [<key2> <arg2>...] + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "match": PARAM_LIST, + "then": PARAM_LIST, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST}) + if err != nil { + return nil, nil, err + } + if len(m["match"]) == 0 { + return nil, nil, fmt.Errorf("specify filtering rules with keyword 'match'") + } + + var rd bgp.RouteDistinguisherInterface + extcomms := m["then"] + switch rf { + case bgp.RF_FS_IPv4_VPN, bgp.RF_FS_IPv6_VPN, bgp.RF_FS_L2_VPN: + if len(m["rd"]) == 0 { + return nil, nil, fmt.Errorf("specify rd") + } + var err error + if rd, err = bgp.ParseRouteDistinguisher(m["rd"][0]); err != nil { + return nil, nil, fmt.Errorf("invalid rd: %s", m["rd"][0]) + } + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + default: + if len(m["rd"]) > 0 { + return nil, nil, fmt.Errorf("cannot specify rd for %s", rf.String()) + } + if len(m["rt"]) > 0 { + return nil, nil, fmt.Errorf("cannot specify rt for %s", rf.String()) + } + } + + rules, err := bgp.ParseFlowSpecComponents(rf, strings.Join(m["match"], " ")) + if err != nil { + return nil, nil, err + } + + var nlri bgp.AddrPrefixInterface + switch rf { + case bgp.RF_FS_IPv4_UC: + nlri = bgp.NewFlowSpecIPv4Unicast(rules) + case bgp.RF_FS_IPv6_UC: + nlri = bgp.NewFlowSpecIPv6Unicast(rules) + case bgp.RF_FS_IPv4_VPN: + nlri = bgp.NewFlowSpecIPv4VPN(rd, rules) + case bgp.RF_FS_IPv6_VPN: + nlri = bgp.NewFlowSpecIPv6VPN(rd, rules) + case bgp.RF_FS_L2_VPN: + nlri = bgp.NewFlowSpecL2VPN(rd, rules) + default: + return nil, nil, fmt.Errorf("invalid route family") + } + + return nlri, extcomms, nil +} + +func ParseEvpnEthernetAutoDiscoveryArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // esi <esi> etag <etag> label <label> rd <rd> [rt <rt>...] [encap <encap type>] [esi-label <esi-label> [single-active | all-active]] + req := 8 + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "esi": PARAM_LIST, + "etag": PARAM_SINGLE, + "label": PARAM_SINGLE, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "encap": PARAM_SINGLE, + "esi-label": PARAM_SINGLE}) + if err != nil { + return nil, nil, err + } + for _, f := range []string{"esi", "etag", "label", "rd"} { + for len(m[f]) == 0 { + return nil, nil, fmt.Errorf("specify %s", f) + } + } + + esi, err := bgp.ParseEthernetSegmentIdentifier(m["esi"]) + if err != nil { + return nil, nil, err + } + + e, err := strconv.ParseUint(m["etag"][0], 10, 32) + if err != nil { + return nil, nil, err + } + etag := uint32(e) + + l, err := strconv.ParseUint(m["label"][0], 10, 32) + if err != nil { + return nil, nil, err + } + label := uint32(l) + + rd, err := bgp.ParseRouteDistinguisher(m["rd"][0]) + if err != nil { + return nil, nil, err + } + + extcomms := make([]string, 0) + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + if len(m["encap"]) > 0 { + extcomms = append(extcomms, "encap", m["encap"][0]) + } + if len(m["esi-label"]) > 0 { + extcomms = append(extcomms, "esi-label") + extcomms = append(extcomms, m["esi-label"]...) + } + + r := &bgp.EVPNEthernetAutoDiscoveryRoute{ + RD: rd, + ESI: esi, + ETag: etag, + Label: label, + } + return bgp.NewEVPNNLRI(bgp.EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY, r), extcomms, nil +} + +func ParseEvpnMacAdvArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // <mac address> <ip address> [esi <esi>] etag <etag> label <label> rd <rd> [rt <rt>...] [encap <encap type>] [router-mac <mac address>] [default-gateway] + // or + // <mac address> <ip address> <etag> [esi <esi>] label <label> rd <rd> [rt <rt>...] [encap <encap type>] [router-mac <mac address>] [default-gateway] + // or + // <mac address> <ip address> <etag> <label> [esi <esi>] rd <rd> [rt <rt>...] [encap <encap type>] [router-mac <mac address>] [default-gateway] + req := 6 + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "esi": PARAM_LIST, + "etag": PARAM_SINGLE, + "label": PARAM_SINGLE, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "encap": PARAM_SINGLE, + "router-mac": PARAM_SINGLE}) + if err != nil { + return nil, nil, err + } + if len(m[""]) < 2 { + return nil, nil, fmt.Errorf("specify mac and ip address") + } + macStr := m[""][0] + ipStr := m[""][1] + eTagStr := "" + labelStr := "" + if len(m[""]) == 2 { + if len(m["etag"]) == 0 || len(m["label"]) == 0 { + return nil, nil, fmt.Errorf("specify etag and label") + } + eTagStr = m["etag"][0] + labelStr = m["label"][0] + } else if len(m[""]) == 3 { + if len(m["label"]) == 0 { + return nil, nil, fmt.Errorf("specify label") + } + eTagStr = m[""][2] + labelStr = m["label"][0] + } else { + eTagStr = m[""][2] + labelStr = m[""][3] + } + if len(m["rd"]) == 0 { + return nil, nil, fmt.Errorf("specify rd") + } + + mac, err := net.ParseMAC(macStr) + if err != nil { + return nil, nil, fmt.Errorf("invalid mac address: %s", macStr) + } + + ip := net.ParseIP(ipStr) + ipLen := 0 + if ip == nil { + return nil, nil, fmt.Errorf("invalid ip address: %s", ipStr) + } else if ip.IsUnspecified() { + ip = nil + } else if ip.To4() != nil { + ipLen = net.IPv4len * 8 + } else { + ipLen = net.IPv6len * 8 + } + + esi, err := bgp.ParseEthernetSegmentIdentifier(m["esi"]) + if err != nil { + return nil, nil, err + } + + eTag, err := strconv.ParseUint(eTagStr, 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid etag: %s: %s", eTagStr, err) + } + + var labels []uint32 + for _, l := range strings.SplitN(labelStr, ",", 2) { + label, err := strconv.ParseUint(l, 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid label: %s: %s", labelStr, err) + } + labels = append(labels, uint32(label)) + } + + rd, err := bgp.ParseRouteDistinguisher(m["rd"][0]) + if err != nil { + return nil, nil, err + } + + extcomms := make([]string, 0) + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + if len(m["encap"]) > 0 { + extcomms = append(extcomms, "encap", m["encap"][0]) + } + + if len(m["router-mac"]) != 0 { + _, err := net.ParseMAC(m["router-mac"][0]) + if err != nil { + return nil, nil, fmt.Errorf("invalid router-mac address: %s", m["router-mac"][0]) + } + extcomms = append(extcomms, "router-mac", m["router-mac"][0]) + } + + for _, a := range args { + if a == "default-gateway" { + extcomms = append(extcomms, "default-gateway") + break + } + } + + r := &bgp.EVPNMacIPAdvertisementRoute{ + RD: rd, + ESI: esi, + MacAddressLength: 48, + MacAddress: mac, + IPAddressLength: uint8(ipLen), + IPAddress: ip, + Labels: labels, + ETag: uint32(eTag), + } + return bgp.NewEVPNNLRI(bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT, r), extcomms, nil +} + +func ParseEvpnMulticastArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // <ip address> etag <etag> rd <rd> [rt <rt>...] [encap <encap type>] + // or + // <ip address> <etag> rd <rd> [rt <rt>...] [encap <encap type>] + req := 4 + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "etag": PARAM_SINGLE, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "encap": PARAM_SINGLE}) + if err != nil { + return nil, nil, err + } + if len(m[""]) < 1 { + return nil, nil, fmt.Errorf("specify ip address") + } + ipStr := m[""][0] + eTagStr := "" + if len(m[""]) == 1 { + if len(m["etag"]) == 0 { + return nil, nil, fmt.Errorf("specify etag") + } + eTagStr = m["etag"][0] + } else { + eTagStr = m[""][1] + } + if len(m["rd"]) == 0 { + return nil, nil, fmt.Errorf("specify rd") + } + + ip := net.ParseIP(ipStr) + ipLen := 0 + if ip == nil { + return nil, nil, fmt.Errorf("invalid ip address: %s", ipStr) + } else if ip.IsUnspecified() { + ip = nil + } else if ip.To4() != nil { + ipLen = net.IPv4len * 8 + } else { + ipLen = net.IPv6len * 8 + } + + eTag, err := strconv.ParseUint(eTagStr, 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid etag: %s: %s", eTagStr, err) + } + + rd, err := bgp.ParseRouteDistinguisher(m["rd"][0]) + if err != nil { + return nil, nil, err + } + + extcomms := make([]string, 0) + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + if len(m["encap"]) > 0 { + extcomms = append(extcomms, "encap", m["encap"][0]) + } + + r := &bgp.EVPNMulticastEthernetTagRoute{ + RD: rd, + IPAddressLength: uint8(ipLen), + IPAddress: ip, + ETag: uint32(eTag), + } + return bgp.NewEVPNNLRI(bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG, r), extcomms, nil +} + +func ParseEvpnEthernetSegmentArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // <ip address> esi <esi> rd <rd> [rt <rt>...] [encap <encap type>] + req := 5 + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "esi": PARAM_LIST, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "encap": PARAM_SINGLE}) + if err != nil { + return nil, nil, err + } + if len(m[""]) < 1 { + return nil, nil, fmt.Errorf("specify ip address") + } + for _, f := range []string{"esi", "rd"} { + for len(m[f]) == 0 { + return nil, nil, fmt.Errorf("specify %s", f) + } + } + + ip := net.ParseIP(m[""][0]) + ipLen := 0 + if ip == nil { + return nil, nil, fmt.Errorf("invalid ip address: %s", m[""][0]) + } else if ip.IsUnspecified() { + ip = nil + } else if ip.To4() != nil { + ipLen = net.IPv4len * 8 + } else { + ipLen = net.IPv6len * 8 + } + + esi, err := bgp.ParseEthernetSegmentIdentifier(m["esi"]) + if err != nil { + return nil, nil, err + } + + rd, err := bgp.ParseRouteDistinguisher(m["rd"][0]) + if err != nil { + return nil, nil, err + } + + extcomms := make([]string, 0) + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + if len(m["encap"]) > 0 { + extcomms = append(extcomms, "encap", m["encap"][0]) + } + + r := &bgp.EVPNEthernetSegmentRoute{ + RD: rd, + ESI: esi, + IPAddressLength: uint8(ipLen), + IPAddress: ip, + } + return bgp.NewEVPNNLRI(bgp.EVPN_ETHERNET_SEGMENT_ROUTE, r), extcomms, nil +} + +func ParseEvpnIPPrefixArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + // Format: + // <ip prefix> [gw <gateway>] [esi <esi>] etag <etag> [label <label>] rd <rd> [rt <rt>...] [encap <encap type>] + req := 5 + if len(args) < req { + return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) + } + m, err := extractReserved(args, map[string]int{ + "gw": PARAM_SINGLE, + "esi": PARAM_LIST, + "etag": PARAM_SINGLE, + "label": PARAM_SINGLE, + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "encap": PARAM_SINGLE, + "router-mac": PARAM_SINGLE}) + if err != nil { + return nil, nil, err + } + if len(m[""]) < 1 { + return nil, nil, fmt.Errorf("specify prefix") + } + for _, f := range []string{"etag", "rd"} { + for len(m[f]) == 0 { + return nil, nil, fmt.Errorf("specify %s", f) + } + } + + _, nw, err := net.ParseCIDR(m[""][0]) + if err != nil { + return nil, nil, err + } + ones, _ := nw.Mask.Size() + + var gw net.IP + if len(m["gw"]) > 0 { + gw = net.ParseIP(m["gw"][0]) + } + + rd, err := bgp.ParseRouteDistinguisher(m["rd"][0]) + if err != nil { + return nil, nil, err + } + + esi, err := bgp.ParseEthernetSegmentIdentifier(m["esi"]) + if err != nil { + return nil, nil, err + } + + e, err := strconv.ParseUint(m["etag"][0], 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid etag: %s: %s", m["etag"][0], err) + } + etag := uint32(e) + + var label uint32 + if len(m["label"]) > 0 { + e, err := strconv.ParseUint(m["label"][0], 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid label: %s: %s", m["label"][0], err) + } + label = uint32(e) + } + + extcomms := make([]string, 0) + if len(m["rt"]) > 0 { + extcomms = append(extcomms, "rt") + extcomms = append(extcomms, m["rt"]...) + } + if len(m["encap"]) > 0 { + extcomms = append(extcomms, "encap", m["encap"][0]) + } + if len(m["router-mac"]) > 0 { + extcomms = append(extcomms, "router-mac", m["router-mac"][0]) + } + + r := &bgp.EVPNIPPrefixRoute{ + RD: rd, + ESI: esi, + ETag: etag, + IPPrefixLength: uint8(ones), + IPPrefix: nw.IP, + GWIPAddress: gw, + Label: label, + } + return bgp.NewEVPNNLRI(bgp.EVPN_IP_PREFIX, r), extcomms, nil +} + +func ParseEvpnArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { + if len(args) < 1 { + return nil, nil, fmt.Errorf("lack of args. need 1 but %d", len(args)) + } + subtype := args[0] + args = args[1:] + switch subtype { + case "a-d": + return ParseEvpnEthernetAutoDiscoveryArgs(args) + case "macadv": + return ParseEvpnMacAdvArgs(args) + case "multicast": + return ParseEvpnMulticastArgs(args) + case "esi": + return ParseEvpnEthernetSegmentArgs(args) + case "prefix": + return ParseEvpnIPPrefixArgs(args) + } + return nil, nil, fmt.Errorf("invalid subtype. expect [macadv|multicast|prefix] but %s", subtype) +} + +func extractOrigin(args []string) ([]string, bgp.PathAttributeInterface, error) { + typ := bgp.BGP_ORIGIN_ATTR_TYPE_INCOMPLETE + for idx, arg := range args { + if arg == "origin" && len(args) > (idx+1) { + switch args[idx+1] { + case "igp": + typ = bgp.BGP_ORIGIN_ATTR_TYPE_IGP + case "egp": + typ = bgp.BGP_ORIGIN_ATTR_TYPE_EGP + case "incomplete": + default: + return nil, nil, fmt.Errorf("invalid origin type. expect [igp|egp|incomplete] but %s", args[idx+1]) + } + args = append(args[:idx], args[idx+2:]...) + break + } + } + return args, bgp.NewPathAttributeOrigin(uint8(typ)), nil +} + +func toAs4Value(s string) (uint32, error) { + if strings.Contains(s, ".") { + v := strings.Split(s, ".") + upper, err := strconv.ParseUint(v[0], 10, 16) + if err != nil { + return 0, nil + } + lower, err := strconv.ParseUint(v[1], 10, 16) + if err != nil { + return 0, nil + } + return uint32(upper<<16 | lower), nil + } + i, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return 0, err + } + return uint32(i), nil +} + +var ( + _regexpASPathGroups = regexp.MustCompile("[{}]") + _regexpASPathSegment = regexp.MustCompile(`,|\s+`) +) + +func newAsPath(aspath string) (bgp.PathAttributeInterface, error) { + // For the first step, parses "aspath" into a list of uint32 list. + // e.g.) "10 20 {30,40} 50" -> [][]uint32{{10, 20}, {30, 40}, {50}} + segments := _regexpASPathGroups.Split(aspath, -1) + asPathPrams := make([]bgp.AsPathParamInterface, 0, len(segments)) + for idx, segment := range segments { + if segment == "" { + continue + } + nums := _regexpASPathSegment.Split(segment, -1) + asNums := make([]uint32, 0, len(nums)) + for _, n := range nums { + if n == "" { + continue + } + if asn, err := toAs4Value(n); err != nil { + return nil, err + } else { + asNums = append(asNums, uint32(asn)) + } + } + // Assumes "idx" is even, the given "segment" is of type AS_SEQUENCE, + // otherwise AS_SET, because the "segment" enclosed in parentheses is + // of type AS_SET. + if idx%2 == 0 { + asPathPrams = append(asPathPrams, bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, asNums)) + } else { + asPathPrams = append(asPathPrams, bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, asNums)) + } + } + return bgp.NewPathAttributeAsPath(asPathPrams), nil +} + +func extractAsPath(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "aspath" && len(args) > (idx+1) { + attr, err := newAsPath(args[idx+1]) + if err != nil { + return nil, nil, err + } + args = append(args[:idx], args[idx+2:]...) + return args, attr, nil + } + } + return args, nil, nil +} + +func extractNexthop(rf bgp.RouteFamily, args []string) ([]string, string, error) { + afi, _ := bgp.RouteFamilyToAfiSafi(rf) + nexthop := "0.0.0.0" + if afi == bgp.AFI_IP6 { + nexthop = "::" + } + for idx, arg := range args { + if arg == "nexthop" && len(args) > (idx+1) { + if net.ParseIP(args[idx+1]) == nil { + return nil, "", fmt.Errorf("invalid nexthop address") + } + nexthop = args[idx+1] + args = append(args[:idx], args[idx+2:]...) + break + } + } + return args, nexthop, nil +} + +func extractLocalPref(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "local-pref" && len(args) > (idx+1) { + metric, err := strconv.ParseUint(args[idx+1], 10, 32) + if err != nil { + return nil, nil, err + } + args = append(args[:idx], args[idx+2:]...) + return args, bgp.NewPathAttributeLocalPref(uint32(metric)), nil + } + } + return args, nil, nil +} + +func extractMed(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "med" && len(args) > (idx+1) { + med, err := strconv.ParseUint(args[idx+1], 10, 32) + if err != nil { + return nil, nil, err + } + args = append(args[:idx], args[idx+2:]...) + return args, bgp.NewPathAttributeMultiExitDisc(uint32(med)), nil + } + } + return args, nil, nil +} + +func extractCommunity(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "community" && len(args) > (idx+1) { + elems := strings.Split(args[idx+1], ",") + comms := make([]uint32, 0, 1) + for _, elem := range elems { + c, err := table.ParseCommunity(elem) + if err != nil { + return nil, nil, err + } + comms = append(comms, c) + } + args = append(args[:idx], args[idx+2:]...) + return args, bgp.NewPathAttributeCommunities(comms), nil + } + } + return args, nil, nil +} + +func extractLargeCommunity(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "large-community" && len(args) > (idx+1) { + elems := strings.Split(args[idx+1], ",") + comms := make([]*bgp.LargeCommunity, 0, 1) + for _, elem := range elems { + c, err := bgp.ParseLargeCommunity(elem) + if err != nil { + return nil, nil, err + } + comms = append(comms, c) + } + args = append(args[:idx], args[idx+2:]...) + return args, bgp.NewPathAttributeLargeCommunities(comms), nil + } + } + return args, nil, nil +} + +func extractPmsiTunnel(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "pmsi" { + pmsi, err := bgp.ParsePmsiTunnel(args[idx+1:]) + if err != nil { + return nil, nil, err + } + if pmsi.IsLeafInfoRequired { + return append(args[:idx], args[idx+5:]...), pmsi, nil + } + return append(args[:idx], args[idx+4:]...), pmsi, nil + } + } + return args, nil, nil +} + +func extractAigp(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "aigp" { + if len(args) < (idx + 3) { + return nil, nil, fmt.Errorf("invalid aigp format") + } + typ := args[idx+1] + switch typ { + case "metric": + metric, err := strconv.ParseUint(args[idx+2], 10, 64) + if err != nil { + return nil, nil, err + } + aigp := bgp.NewPathAttributeAigp([]bgp.AigpTLVInterface{bgp.NewAigpTLVIgpMetric(uint64(metric))}) + return append(args[:idx], args[idx+3:]...), aigp, nil + default: + return nil, nil, fmt.Errorf("unknown aigp type: %s", typ) + } + } + } + return args, nil, nil +} + +func extractAggregator(args []string) ([]string, bgp.PathAttributeInterface, error) { + for idx, arg := range args { + if arg == "aggregator" { + if len(args) < (idx + 1) { + return nil, nil, fmt.Errorf("invalid aggregator format") + } + v := strings.SplitN(args[idx+1], ":", 2) + if len(v) != 2 { + return nil, nil, fmt.Errorf("invalid aggregator format") + } + as, err := strconv.ParseUint(v[0], 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("invalid aggregator format") + } + attr := bgp.NewPathAttributeAggregator(uint32(as), net.ParseIP(v[1]).String()) + return append(args[:idx], args[idx+2:]...), attr, nil + } + } + return args, nil, nil +} + +func ParsePath(rf bgp.RouteFamily, args []string) (*api.Path, error) { + var nlri bgp.AddrPrefixInterface + var extcomms []string + var err error + attrs := make([]bgp.PathAttributeInterface, 0, 1) + + fns := []func([]string) ([]string, bgp.PathAttributeInterface, error){ + extractOrigin, // 1 ORIGIN + extractAsPath, // 2 AS_PATH + extractMed, // 4 MULTI_EXIT_DISC + extractLocalPref, // 5 LOCAL_PREF + extractAggregator, // 7 AGGREGATOR + extractCommunity, // 8 COMMUNITY + extractPmsiTunnel, // 22 PMSI_TUNNEL + extractAigp, // 26 AIGP + extractLargeCommunity, // 32 LARGE_COMMUNITY + } + + for _, fn := range fns { + var a bgp.PathAttributeInterface + args, a, err = fn(args) + if err != nil { + return nil, err + } + if a != nil { + attrs = append(attrs, a) + } + } + + args, nexthop, err := extractNexthop(rf, args) + if err != nil { + return nil, err + } + + switch rf { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC: + if len(args) < 1 { + return nil, fmt.Errorf("invalid format") + } + ip, nw, err := net.ParseCIDR(args[0]) + if err != nil { + return nil, err + } + ones, _ := nw.Mask.Size() + if rf == bgp.RF_IPv4_UC { + if ip.To4() == nil { + return nil, fmt.Errorf("invalid ipv4 prefix") + } + nlri = bgp.NewIPAddrPrefix(uint8(ones), ip.String()) + } else { + if ip.To16() == nil { + return nil, fmt.Errorf("invalid ipv6 prefix") + } + nlri = bgp.NewIPv6AddrPrefix(uint8(ones), ip.String()) + } + + if len(args) > 2 && args[1] == "identifier" { + id, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid format") + } + nlri.SetPathIdentifier(uint32(id)) + extcomms = args[3:] + } else { + extcomms = args[1:] + } + + case bgp.RF_IPv4_VPN, bgp.RF_IPv6_VPN: + if len(args) < 5 || args[1] != "label" || args[3] != "rd" { + return nil, fmt.Errorf("invalid format") + } + ip, nw, err := net.ParseCIDR(args[0]) + if err != nil { + return nil, err + } + ones, _ := nw.Mask.Size() + + label, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid format") + } + mpls := bgp.NewMPLSLabelStack(uint32(label)) + + rd, err := bgp.ParseRouteDistinguisher(args[4]) + if err != nil { + return nil, err + } + + extcomms = args[5:] + + if rf == bgp.RF_IPv4_VPN { + if ip.To4() == nil { + return nil, fmt.Errorf("invalid ipv4 prefix") + } + nlri = bgp.NewLabeledVPNIPAddrPrefix(uint8(ones), ip.String(), *mpls, rd) + } else { + if ip.To16() == nil { + return nil, fmt.Errorf("invalid ipv6 prefix") + } + nlri = bgp.NewLabeledVPNIPv6AddrPrefix(uint8(ones), ip.String(), *mpls, rd) + } + case bgp.RF_IPv4_MPLS, bgp.RF_IPv6_MPLS: + if len(args) < 2 { + return nil, fmt.Errorf("invalid format") + } + + ip, nw, err := net.ParseCIDR(args[0]) + if err != nil { + return nil, err + } + ones, _ := nw.Mask.Size() + + mpls, err := bgp.ParseMPLSLabelStack(args[1]) + if err != nil { + return nil, err + } + + extcomms = args[2:] + + if rf == bgp.RF_IPv4_MPLS { + if ip.To4() == nil { + return nil, fmt.Errorf("invalid ipv4 prefix") + } + nlri = bgp.NewLabeledIPAddrPrefix(uint8(ones), ip.String(), *mpls) + } else { + if ip.To4() != nil { + return nil, fmt.Errorf("invalid ipv6 prefix") + } + nlri = bgp.NewLabeledIPv6AddrPrefix(uint8(ones), ip.String(), *mpls) + } + case bgp.RF_EVPN: + nlri, extcomms, err = ParseEvpnArgs(args) + case bgp.RF_FS_IPv4_UC, bgp.RF_FS_IPv4_VPN, bgp.RF_FS_IPv6_UC, bgp.RF_FS_IPv6_VPN, bgp.RF_FS_L2_VPN: + nlri, extcomms, err = ParseFlowSpecArgs(rf, args) + case bgp.RF_OPAQUE: + m, err := extractReserved(args, map[string]int{ + "key": PARAM_SINGLE, + "value": PARAM_SINGLE}) + if err != nil { + return nil, err + } + if len(m["key"]) != 1 { + return nil, fmt.Errorf("opaque nlri key missing") + } + if len(m["value"]) > 0 { + nlri = bgp.NewOpaqueNLRI([]byte(m["key"][0]), []byte(m["value"][0])) + } else { + nlri = bgp.NewOpaqueNLRI([]byte(m["key"][0]), nil) + } + default: + return nil, fmt.Errorf("Unsupported route family: %s", rf) + } + if err != nil { + return nil, err + } + + if rf == bgp.RF_IPv4_UC && net.ParseIP(nexthop).To4() != nil { + attrs = append(attrs, bgp.NewPathAttributeNextHop(nexthop)) + } else { + mpreach := bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri}) + attrs = append(attrs, mpreach) + } + + if extcomms != nil { + extcomms, err := ParseExtendedCommunities(extcomms) + if err != nil { + return nil, err + } + normalextcomms := make([]bgp.ExtendedCommunityInterface, 0) + ipv6extcomms := make([]bgp.ExtendedCommunityInterface, 0) + for _, com := range extcomms { + switch com.(type) { + case *bgp.RedirectIPv6AddressSpecificExtended: + ipv6extcomms = append(ipv6extcomms, com) + default: + normalextcomms = append(normalextcomms, com) + } + } + if len(normalextcomms) != 0 { + p := bgp.NewPathAttributeExtendedCommunities(normalextcomms) + attrs = append(attrs, p) + } + if len(ipv6extcomms) != 0 { + ip6p := bgp.NewPathAttributeIP6ExtendedCommunities(ipv6extcomms) + attrs = append(attrs, ip6p) + } + } + sort.Slice(attrs, func(i, j int) bool { return attrs[i].GetType() < attrs[j].GetType() }) + + return api.NewPath(nlri, false, attrs, time.Now()), nil +} + +func showGlobalRib(args []string) error { + return showNeighborRib(CMD_GLOBAL, "", args) +} + +func modPath(resource string, name, modtype string, args []string) error { + rf, err := checkAddressFamily(bgp.RF_IPv4_UC) + if err != nil { + return err + } + + path, err := ParsePath(rf, args) + if err != nil { + cmdstr := "global" + if resource == CMD_VRF { + cmdstr = fmt.Sprintf("vrf %s", name) + } + rdHelpMsgFmt := ` + <RD> : xxx:yyy, xxx.xxx.xxx.xxx:yyy, xxx.xxx:yyy` + ss := make([]string, 0, len(bgp.ProtocolNameMap)) + for _, v := range bgp.ProtocolNameMap { + ss = append(ss, v) + } + sort.SliceStable(ss, func(i, j int) bool { return ss[i] < ss[j] }) + ss = append(ss, "<DEC_NUM>") + ipProtocols := strings.Join(ss, ", ") + ss = make([]string, 0, len(bgp.TCPFlagNameMap)) + for _, v := range bgp.TCPSortedFlags { + ss = append(ss, bgp.TCPFlagNameMap[v]) + } + tcpFlags := strings.Join(ss, ", ") + ss = make([]string, 0, len(bgp.EthernetTypeNameMap)) + for _, v := range bgp.EthernetTypeNameMap { + ss = append(ss, v) + } + sort.SliceStable(ss, func(i, j int) bool { return ss[i] < ss[j] }) + ss = append(ss, "<DEC_NUM>") + etherTypes := strings.Join(ss, ", ") + helpErrMap := map[bgp.RouteFamily]error{} + baseHelpMsgFmt := fmt.Sprintf(`error: %s +usage: %s rib -a %%s %s <PREFIX> %%s [origin { igp | egp | incomplete }] [aspath <ASPATH>] [nexthop <ADDRESS>] [med <NUM>] [local-pref <NUM>] [community <COMMUNITY>] [aigp metric <NUM>] [large-community <LARGE_COMMUNITY>] [aggregator <AGGREGATOR>] + <ASPATH>: <AS>[,<AS>], + <COMMUNITY>: xxx:xxx|internet|planned-shut|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|blackhole|no-export|no-advertise|no-export-subconfed|no-peer, + <LARGE_COMMUNITY>: xxx:xxx:xxx[,<LARGE_COMMUNITY>], + <AGGREGATOR>: <AS>:<ADDRESS>`, + err, + cmdstr, + // <address family> + modtype, + // <label, rd> + ) + helpErrMap[bgp.RF_IPv4_UC] = fmt.Errorf(baseHelpMsgFmt, "ipv4", "[identifier <VALUE>]") + helpErrMap[bgp.RF_IPv6_UC] = fmt.Errorf(baseHelpMsgFmt, "ipv6", "[identifier <VALUE>]") + helpErrMap[bgp.RF_IPv4_VPN] = fmt.Errorf(baseHelpMsgFmt, "vpnv4", "label <LABEL> rd <RD> [rt <RT>]") + helpErrMap[bgp.RF_IPv6_VPN] = fmt.Errorf(baseHelpMsgFmt, "vpnv6", "label <LABEL> rd <RD> [rt <RT>]") + helpErrMap[bgp.RF_IPv4_MPLS] = fmt.Errorf(baseHelpMsgFmt, "ipv4-mpls", "<LABEL>") + helpErrMap[bgp.RF_IPv6_MPLS] = fmt.Errorf(baseHelpMsgFmt, "ipv6-mpls", "<LABEL>") + + fsHelpMsgFmt := fmt.Sprintf(`error: %s +usage: %s rib -a %%s %s%%s match <MATCH> then <THEN>%%s%%s%%s + <THEN> : { %s | + %s | + %s <RATE> [as <AS>] | + %s <RT> | + %s <DEC_NUM> | + %s { sample | terminal | sample-terminal } }... + <RT> : xxx:yyy, xxx.xxx.xxx.xxx:yyy, xxxx::xxxx:yyy, xxx.xxx:yyy`, + err, + cmdstr, + // <address family> + modtype, + // "" or " rd <RD>" + // "" or " [rt <RT>]" + // <help message for RD> + // <MATCH> + ExtCommNameMap[ACCEPT], + ExtCommNameMap[DISCARD], + ExtCommNameMap[RATE], + ExtCommNameMap[REDIRECT], + ExtCommNameMap[MARK], + ExtCommNameMap[ACTION], + ) + baseFsMatchExpr := fmt.Sprintf(` + <MATCH> : { %s <PREFIX> [<OFFSET>] | + %s <PREFIX> [<OFFSET>] | + %s <PROTOCOLS>... | + %s <FRAGMENTS>... | + %s <TCP_FLAGS>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... %%s}... + <PROTOCOLS> : [&] [<|<=|>|>=|==|!=] <PROTOCOL> + <PROTOCOL> : %s + <FRAGMENTS> : [&] [=|!|!=] <FRAGMENT> + <FRAGMENT> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment + <TCP_FLAGS> : [&] [=|!|!=] <TCP_FLAG> + <TCP_FLAG> : %s%%s + <ITEM> : [&] [<|<=|>|>=|==|!=] <DEC_NUM>`, + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_PREFIX], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_PREFIX], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_IP_PROTO], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_FRAGMENT], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_TCP_FLAG], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_PORT], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_PORT], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_PORT], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ICMP_TYPE], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ICMP_CODE], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_PKT_LEN], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DSCP], + // <additional help messages if exists> + ipProtocols, + tcpFlags, + // <additional help messages if exists> + ) + ipv4FsMatchExpr := fmt.Sprintf(baseFsMatchExpr, "", "") + ipv6FsMatchExpr := fmt.Sprintf(baseFsMatchExpr, fmt.Sprintf(`| + %s <ITEM>... `, + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LABEL]), "") + l2vpnFsMatchExpr := fmt.Sprintf(baseFsMatchExpr, fmt.Sprintf(`| + %s <ITEM>... | + %s <MAC_ADDRESS> | + %s <MAC_ADDRESS> | + %s <ETHER_TYPES>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... | + %s <ITEM>... `, + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LABEL], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_MAC], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_MAC], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ETHERNET_TYPE], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_DSAP], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_SSAP], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_CONTROL], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SNAP], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_VID], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_COS], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_INNER_VID], + bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_INNER_COS]), fmt.Sprintf(` + <ETHER_TYPES> : [&] [<|<=|>|>=|==|!=] <ETHER_TYPE> + <ETHER_TYPE> : %s`, + etherTypes)) + helpErrMap[bgp.RF_FS_IPv4_UC] = fmt.Errorf(fsHelpMsgFmt, "ipv4-flowspec", "", "", "", ipv4FsMatchExpr) + helpErrMap[bgp.RF_FS_IPv6_UC] = fmt.Errorf(fsHelpMsgFmt, "ipv6-flowspec", "", "", "", ipv6FsMatchExpr) + helpErrMap[bgp.RF_FS_IPv4_VPN] = fmt.Errorf(fsHelpMsgFmt, "ipv4-l3vpn-flowspec", " rd <RD>", " [rt <RT>]", rdHelpMsgFmt, ipv4FsMatchExpr) + helpErrMap[bgp.RF_FS_IPv6_VPN] = fmt.Errorf(fsHelpMsgFmt, "ipv6-l3vpn-flowspec", " rd <RD>", " [rt <RT>]", rdHelpMsgFmt, ipv6FsMatchExpr) + helpErrMap[bgp.RF_FS_L2_VPN] = fmt.Errorf(fsHelpMsgFmt, "l2vpn-flowspec", " rd <RD>", " [rt <RT>]", rdHelpMsgFmt, l2vpnFsMatchExpr) + helpErrMap[bgp.RF_EVPN] = fmt.Errorf(`error: %s +usage: %s rib %s { a-d <A-D> | macadv <MACADV> | multicast <MULTICAST> | esi <ESI> | prefix <PREFIX> } -a evpn + <A-D> : esi <esi> etag <etag> label <label> rd <rd> [rt <rt>...] [encap <encap type>] [esi-label <esi-label> [single-active | all-active]] + <MACADV> : <mac address> <ip address> [esi <esi>] etag <etag> label <label> rd <rd> [rt <rt>...] [encap <encap type>] [router-mac <mac address>] [default-gateway] + <MULTICAST> : <ip address> etag <etag> rd <rd> [rt <rt>...] [encap <encap type>] [pmsi <type> [leaf-info-required] <label> <tunnel-id>] + <ESI> : <ip address> esi <esi> rd <rd> [rt <rt>...] [encap <encap type>] + <PREFIX> : <ip prefix> [gw <gateway>] [esi <esi>] etag <etag> [label <label>] rd <rd> [rt <rt>...] [encap <encap type>] [router-mac <mac address>]`, + err, + cmdstr, + modtype, + ) + helpErrMap[bgp.RF_OPAQUE] = fmt.Errorf(`error: %s +usage: %s rib %s key <KEY> [value <VALUE>]`, + err, + cmdstr, + modtype, + ) + if err, ok := helpErrMap[rf]; ok { + return err + } + return err + } + + if modtype == CMD_ADD { + if resource == CMD_VRF { + _, err = client.AddVRFPath(name, []*api.Path{path}) + } else { + _, err = client.AddPath([]*api.Path{path}) + } + } else { + if resource == CMD_VRF { + err = client.DeleteVRFPath(name, []*api.Path{path}) + } else { + err = client.DeletePath([]*api.Path{path}) + } + } + return err +} + +func showGlobalConfig() error { + g, err := client.GetServer() + if err != nil { + return err + } + if globalOpts.Json { + j, _ := json.Marshal(g) + fmt.Println(string(j)) + return nil + } + fmt.Println("AS: ", g.Config.As) + fmt.Println("Router-ID:", g.Config.RouterId) + if len(g.Config.LocalAddressList) > 0 { + fmt.Printf("Listening Port: %d, Addresses: %s\n", g.Config.Port, strings.Join(g.Config.LocalAddressList, ", ")) + } + if g.UseMultiplePaths.Config.Enabled { + fmt.Printf("Multipath: enabled") + } + return nil +} + +func modGlobalConfig(args []string) error { + m, err := extractReserved(args, map[string]int{ + "as": PARAM_SINGLE, + "router-id": PARAM_SINGLE, + "listen-port": PARAM_SINGLE, + "listen-addresses": PARAM_LIST, + "use-multipath": PARAM_FLAG}) + if err != nil || len(m["as"]) != 1 || len(m["router-id"]) != 1 { + return fmt.Errorf("usage: gobgp global as <VALUE> router-id <VALUE> [use-multipath] [listen-port <VALUE>] [listen-addresses <VALUE>...]") + } + asn, err := strconv.ParseUint(m["as"][0], 10, 32) + if err != nil { + return err + } + id := net.ParseIP(m["router-id"][0]) + if id.To4() == nil { + return fmt.Errorf("invalid router-id format") + } + var port int64 + if len(m["listen-port"]) > 0 { + // Note: GlobalConfig.Port is uint32 type, but the TCP/UDP port is + // 16-bit length. + port, err = strconv.ParseInt(m["listen-port"][0], 10, 16) + if err != nil { + return err + } + } + useMultipath := false + if _, ok := m["use-multipath"]; ok { + useMultipath = true + } + return client.StartServer(&config.Global{ + Config: config.GlobalConfig{ + As: uint32(asn), + RouterId: id.String(), + Port: int32(port), + LocalAddressList: m["listen-addresses"], + }, + UseMultiplePaths: config.UseMultiplePaths{ + Config: config.UseMultiplePathsConfig{ + Enabled: useMultipath, + }, + }, + }) +} + +func NewGlobalCmd() *cobra.Command { + globalCmd := &cobra.Command{ + Use: CMD_GLOBAL, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) != 0 { + err = modGlobalConfig(args) + } else { + err = showGlobalConfig() + } + if err != nil { + exitWithError(err) + } + }, + } + + ribCmd := &cobra.Command{ + Use: CMD_RIB, + Run: func(cmd *cobra.Command, args []string) { + if err := showGlobalRib(args); err != nil { + exitWithError(err) + } + }, + } + + ribCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + + for _, v := range []string{CMD_ADD, CMD_DEL} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + err := modPath(CMD_GLOBAL, "", cmd.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + ribCmd.AddCommand(cmd) + + if v == CMD_DEL { + subcmd := &cobra.Command{ + Use: CMD_ALL, + Run: func(cmd *cobra.Command, args []string) { + family, err := checkAddressFamily(bgp.RouteFamily(0)) + if err != nil { + exitWithError(err) + } + if err = client.DeletePathByFamily(family); err != nil { + exitWithError(err) + } + }, + } + cmd.AddCommand(subcmd) + } + } + + summaryCmd := &cobra.Command{ + Use: CMD_SUMMARY, + Run: func(cmd *cobra.Command, args []string) { + if err := showRibInfo(CMD_GLOBAL, ""); err != nil { + exitWithError(err) + } + }, + } + ribCmd.AddCommand(summaryCmd) + + policyCmd := &cobra.Command{ + Use: CMD_POLICY, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + exitWithError(fmt.Errorf("usage: gobgp global policy [{ import | export }]")) + } + for _, v := range []string{CMD_IMPORT, CMD_EXPORT} { + if err := showNeighborPolicy("", v, 4); err != nil { + exitWithError(err) + } + } + }, + } + + for _, v := range []string{CMD_IMPORT, CMD_EXPORT} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + if err := showNeighborPolicy("", cmd.Use, 0); err != nil { + exitWithError(err) + } + }, + } + + for _, w := range []string{CMD_ADD, CMD_DEL, CMD_SET} { + subcmd := &cobra.Command{ + Use: w, + Run: func(subcmd *cobra.Command, args []string) { + err := modNeighborPolicy("", cmd.Use, subcmd.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + cmd.AddCommand(subcmd) + } + + policyCmd.AddCommand(cmd) + } + + delCmd := &cobra.Command{ + Use: CMD_DEL, + } + + allCmd := &cobra.Command{ + Use: CMD_ALL, + Run: func(cmd *cobra.Command, args []string) { + if err := client.StopServer(); err != nil { + exitWithError(err) + } + }, + } + delCmd.AddCommand(allCmd) + + globalCmd.AddCommand(ribCmd, policyCmd, delCmd) + return globalCmd +} diff --git a/cmd/gobgp/cmd/global_test.go b/cmd/gobgp/cmd/global_test.go new file mode 100644 index 00000000..7aecb904 --- /dev/null +++ b/cmd/gobgp/cmd/global_test.go @@ -0,0 +1,38 @@ +// 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. + +package cmd + +import ( + "strings" + "testing" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/stretchr/testify/assert" +) + +func Test_ParsePath(t *testing.T) { + assert := assert.New(t) + buf := "10.0.0.0/24 rt 100:100 med 10 nexthop 10.0.0.1 aigp metric 10 local-pref 100" + + path, err := ParsePath(bgp.RF_IPv4_UC, strings.Split(buf, " ")) + assert.Nil(err) + i := 0 + attrs, _ := path.GetNativePathAttributes() + for _, a := range attrs { + assert.True(i < int(a.GetType())) + i = int(a.GetType()) + } +} diff --git a/cmd/gobgp/cmd/monitor.go b/cmd/gobgp/cmd/monitor.go new file mode 100644 index 00000000..6b07eb2d --- /dev/null +++ b/cmd/gobgp/cmd/monitor.go @@ -0,0 +1,201 @@ +// 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 cmd + +import ( + "encoding/json" + "fmt" + "io" + "net" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +func makeMonitorRouteArgs(p *api.Path, showIdentifier bgp.BGPAddPathMode) []interface{} { + pathStr := make([]interface{}, 0) + + // Title + title := "ROUTE" + if p.IsWithdraw { + title = "DELROUTE" + } + pathStr = append(pathStr, title) + + // NLRI + // If Add-Path required, append Path Identifier. + nlri, _ := p.GetNativeNlri() + if showIdentifier != bgp.BGP_ADD_PATH_NONE { + pathStr = append(pathStr, p.GetIdentifier()) + } + pathStr = append(pathStr, nlri) + + attrs, _ := p.GetNativePathAttributes() + // Next Hop + nexthop := "fictitious" + if n := getNextHopFromPathAttributes(attrs); n != nil { + nexthop = n.String() + } + pathStr = append(pathStr, nexthop) + + // AS_PATH + aspathstr := func() string { + for _, attr := range attrs { + switch a := attr.(type) { + case *bgp.PathAttributeAsPath: + return bgp.AsPathString(a) + } + } + return "" + }() + pathStr = append(pathStr, aspathstr) + + // Path Attributes + pathStr = append(pathStr, getPathAttributeString(nlri, attrs)) + + return pathStr +} + +func monitorRoute(pathList []*api.Path, showIdentifier bgp.BGPAddPathMode) { + var pathStrs [][]interface{} + + for _, p := range pathList { + pathStrs = append(pathStrs, makeMonitorRouteArgs(p, showIdentifier)) + } + + format := "[%s] %s via %s aspath [%s] attrs %s\n" + if showIdentifier != bgp.BGP_ADD_PATH_NONE { + format = "[%s] %d:%s via %s aspath [%s] attrs %s\n" + } + for _, pathStr := range pathStrs { + fmt.Printf(format, pathStr...) + } +} + +func NewMonitorCmd() *cobra.Command { + + var current bool + + monitor := func(recver interface { + Recv() (*api.Destination, error) + }, showIdentifier bgp.BGPAddPathMode) { + for { + dst, err := recver.Recv() + if err == io.EOF { + break + } else if err != nil { + exitWithError(err) + } + if globalOpts.Json { + j, _ := json.Marshal(dst.Paths) + fmt.Println(string(j)) + } else { + monitorRoute(dst.Paths, showIdentifier) + } + } + } + + ribCmd := &cobra.Command{ + Use: CMD_RIB, + Run: func(cmd *cobra.Command, args []string) { + family, err := checkAddressFamily(bgp.RouteFamily(0)) + if err != nil { + exitWithError(err) + } + recver, err := client.MonitorRIB(family, current) + if err != nil { + exitWithError(err) + } + monitor(recver, bgp.BGP_ADD_PATH_NONE) + }, + } + ribCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + + globalCmd := &cobra.Command{ + Use: CMD_GLOBAL, + } + globalCmd.AddCommand(ribCmd) + + neighborCmd := &cobra.Command{ + Use: fmt.Sprintf("%s [<neighbor address>]", CMD_NEIGHBOR), + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := "" + if len(args) > 0 { + name = args[0] + } + stream, err := client.MonitorNeighborState(name, current) + if err != nil { + exitWithError(err) + } + for { + s, err := stream.Recv() + if err == io.EOF { + break + } else if err != nil { + exitWithError(err) + } + if globalOpts.Json { + j, _ := json.Marshal(s) + fmt.Println(string(j)) + } else { + addr := s.State.NeighborAddress + if s.Config.NeighborInterface != "" { + addr = fmt.Sprintf("%s(%s)", addr, s.Config.NeighborInterface) + } + fmt.Printf("[NEIGH] %s fsm: %s admin: %s\n", addr, s.State.SessionState, s.State.AdminState) + } + } + }, + } + + adjInCmd := &cobra.Command{ + Use: CMD_ADJ_IN, + Run: func(cmd *cobra.Command, args []string) { + name := "" + if len(args) > 0 { + remoteIP := net.ParseIP(args[0]) + if remoteIP == nil { + exitWithError(fmt.Errorf("invalid ip address: %s", args[0])) + } + name = args[0] + } + family, err := checkAddressFamily(bgp.RouteFamily(0)) + if err != nil { + exitWithError(err) + } + recver, err := client.MonitorAdjRIBIn(name, family, current) + if err != nil { + exitWithError(err) + } + monitor(recver, bgp.BGP_ADD_PATH_RECEIVE) + }, + } + adjInCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + + monitorCmd := &cobra.Command{ + Use: CMD_MONITOR, + } + monitorCmd.AddCommand(globalCmd) + monitorCmd.AddCommand(neighborCmd) + monitorCmd.AddCommand(adjInCmd) + + monitorCmd.PersistentFlags().BoolVarP(¤t, "current", "", false, "dump current contents") + + return monitorCmd +} diff --git a/cmd/gobgp/cmd/mrt.go b/cmd/gobgp/cmd/mrt.go new file mode 100644 index 00000000..32c4fc9b --- /dev/null +++ b/cmd/gobgp/cmd/mrt.go @@ -0,0 +1,238 @@ +// 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 cmd + +import ( + "fmt" + "io" + "os" + "strconv" + "time" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/mrt" +) + +func injectMrt() error { + + file, err := os.Open(mrtOpts.Filename) + if err != nil { + return fmt.Errorf("failed to open file: %s", err) + } + + if mrtOpts.NextHop != nil && !mrtOpts.SkipV4 && !mrtOpts.SkipV6 { + fmt.Println("You should probably specify either --no-ipv4 or --no-ipv6 when overwriting nexthop, unless your dump contains only one type of routes") + } + + var idx int64 + if mrtOpts.QueueSize < 1 { + return fmt.Errorf("Specified queue size is smaller than 1, refusing to run with unbounded memory usage") + } + + ch := make(chan []*api.Path, mrtOpts.QueueSize) + go func() { + + var peers []*mrt.Peer + for { + buf := make([]byte, mrt.MRT_COMMON_HEADER_LEN) + _, err := file.Read(buf) + if err == io.EOF { + break + } else if err != nil { + exitWithError(fmt.Errorf("failed to read: %s", err)) + } + + h := &mrt.MRTHeader{} + err = h.DecodeFromBytes(buf) + if err != nil { + exitWithError(fmt.Errorf("failed to parse")) + } + + buf = make([]byte, h.Len) + _, err = file.Read(buf) + if err != nil { + exitWithError(fmt.Errorf("failed to read")) + } + + msg, err := mrt.ParseMRTBody(h, buf) + if err != nil { + printError(fmt.Errorf("failed to parse: %s", err)) + continue + } + + if globalOpts.Debug { + fmt.Println(msg) + } + + if msg.Header.Type == mrt.TABLE_DUMPv2 { + subType := mrt.MRTSubTypeTableDumpv2(msg.Header.SubType) + switch subType { + case mrt.PEER_INDEX_TABLE: + peers = msg.Body.(*mrt.PeerIndexTable).Peers + continue + case mrt.RIB_IPV4_UNICAST, mrt.RIB_IPV4_UNICAST_ADDPATH: + if mrtOpts.SkipV4 { + continue + } + case mrt.RIB_IPV6_UNICAST, mrt.RIB_IPV6_UNICAST_ADDPATH: + if mrtOpts.SkipV6 { + continue + } + case mrt.GEO_PEER_TABLE: + fmt.Printf("WARNING: Skipping GEO_PEER_TABLE: %s", msg.Body.(*mrt.GeoPeerTable)) + default: + exitWithError(fmt.Errorf("unsupported subType: %v", subType)) + } + + if peers == nil { + exitWithError(fmt.Errorf("not found PEER_INDEX_TABLE")) + } + + rib := msg.Body.(*mrt.Rib) + nlri := rib.Prefix + + paths := make([]*api.Path, 0, len(rib.Entries)) + + for _, e := range rib.Entries { + if len(peers) < int(e.PeerIndex) { + exitWithError(fmt.Errorf("invalid peer index: %d (PEER_INDEX_TABLE has only %d peers)\n", e.PeerIndex, len(peers))) + } + //t := time.Unix(int64(e.OriginatedTime), 0) + + var attrs []bgp.PathAttributeInterface + switch subType { + case mrt.RIB_IPV4_UNICAST, mrt.RIB_IPV4_UNICAST_ADDPATH: + if mrtOpts.NextHop != nil { + for i, attr := range e.PathAttributes { + if attr.GetType() != bgp.BGP_ATTR_TYPE_NEXT_HOP { + e.PathAttributes[i] = bgp.NewPathAttributeNextHop(mrtOpts.NextHop.String()) + break + } + } + } + attrs = e.PathAttributes + default: + attrs = make([]bgp.PathAttributeInterface, 0, len(e.PathAttributes)) + for _, attr := range e.PathAttributes { + if attr.GetType() != bgp.BGP_ATTR_TYPE_MP_REACH_NLRI { + attrs = append(attrs, attr) + } else { + a := attr.(*bgp.PathAttributeMpReachNLRI) + nexthop := a.Nexthop.String() + if mrtOpts.NextHop != nil { + nexthop = mrtOpts.NextHop.String() + } + attrs = append(attrs, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) + } + } + } + + path := api.NewPath(nlri, false, attrs, time.Unix(int64(e.OriginatedTime), 0)) + path.SourceAsn = peers[e.PeerIndex].AS + path.SourceId = peers[e.PeerIndex].BgpId.String() + + // TODO: compare here if mrtOpts.Best is enabled + paths = append(paths, path) + } + + // TODO: calculate properly if necessary. + if mrtOpts.Best { + paths = []*api.Path{paths[0]} + } + + if idx >= mrtOpts.RecordSkip { + ch <- paths + } + + idx += 1 + if idx == mrtOpts.RecordCount+mrtOpts.RecordSkip { + break + } + } + } + + close(ch) + }() + + stream, err := client.AddPathByStream() + if err != nil { + return fmt.Errorf("failed to add path: %s", err) + } + + for paths := range ch { + err = stream.Send(paths...) + if err != nil { + return fmt.Errorf("failed to send: %s", err) + } + } + + if err := stream.Close(); err != nil { + return fmt.Errorf("failed to send: %s", err) + } + return nil +} + +func NewMrtCmd() *cobra.Command { + globalInjectCmd := &cobra.Command{ + Use: CMD_GLOBAL, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + exitWithError(fmt.Errorf("usage: gobgp mrt inject global <filename> [<count> [<skip>]]")) + } + mrtOpts.Filename = args[0] + if len(args) > 1 { + var err error + mrtOpts.RecordCount, err = strconv.ParseInt(args[1], 10, 64) + if err != nil { + exitWithError(fmt.Errorf("invalid count value: %s", args[1])) + } + if len(args) > 2 { + mrtOpts.RecordSkip, err = strconv.ParseInt(args[2], 10, 64) + if err != nil { + exitWithError(fmt.Errorf("invalid skip value: %s", args[2])) + } + } + } else { + mrtOpts.RecordCount = -1 + mrtOpts.RecordSkip = 0 + } + err := injectMrt() + if err != nil { + exitWithError(err) + } + }, + } + + injectCmd := &cobra.Command{ + Use: CMD_INJECT, + } + injectCmd.AddCommand(globalInjectCmd) + + mrtCmd := &cobra.Command{ + Use: CMD_MRT, + } + mrtCmd.AddCommand(injectCmd) + + mrtCmd.PersistentFlags().BoolVarP(&mrtOpts.Best, "only-best", "", false, "inject only best paths") + mrtCmd.PersistentFlags().BoolVarP(&mrtOpts.SkipV4, "no-ipv4", "", false, "Do not import IPv4 routes") + mrtCmd.PersistentFlags().BoolVarP(&mrtOpts.SkipV6, "no-ipv6", "", false, "Do not import IPv6 routes") + mrtCmd.PersistentFlags().IntVarP(&mrtOpts.QueueSize, "queue-size", "", 1<<10, "Maximum number of updates to keep queued") + mrtCmd.PersistentFlags().IPVarP(&mrtOpts.NextHop, "nexthop", "", nil, "Overwrite nexthop") + return mrtCmd +} diff --git a/cmd/gobgp/cmd/neighbor.go b/cmd/gobgp/cmd/neighbor.go new file mode 100644 index 00000000..c6dd8be6 --- /dev/null +++ b/cmd/gobgp/cmd/neighbor.go @@ -0,0 +1,1321 @@ +// 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 cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "sort" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "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) (neighbors, error) { + if vrf != "" { + n, err := client.ListNeighborByVRF(vrf) + return neighbors(n), err + } else if t := neighborsOpts.Transport; t != "" { + switch t { + case "ipv4": + n, err := client.ListNeighborByTransport(bgp.AFI_IP) + return neighbors(n), err + case "ipv6": + n, err := client.ListNeighborByTransport(bgp.AFI_IP6) + return neighbors(n), err + default: + return nil, fmt.Errorf("invalid transport: %s", t) + } + } + n, err := client.ListNeighbor() + return neighbors(n), err +} + +func getASN(p *config.Neighbor) 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.Sort(m) + + now := time.Now() + for _, n := range m { + if i := len(n.Config.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 == config.SESSION_STATE_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 config.AdminState, fsm config.SessionState) string { + switch admin { + case config.ADMIN_STATE_DOWN: + return "Idle(Admin)" + case config.ADMIN_STATE_PFX_CT: + return "Idle(PfxCt)" + } + + switch fsm { + case config.SESSION_STATE_IDLE: + return "Idle" + case config.SESSION_STATE_CONNECT: + return "Connect" + case config.SESSION_STATE_ACTIVE: + return "Active" + case config.SESSION_STATE_OPENSENT: + return "Sent" + case config.SESSION_STATE_OPENCONFIRM: + return "Confirm" + case config.SESSION_STATE_ESTABLISHED: + return "Establ" + default: + return string(fsm) + } + } + + for i, n := range m { + neigh := n.State.NeighborAddress + if n.Config.NeighborInterface != "" { + neigh = n.Config.NeighborInterface + } + fmt.Printf(format, neigh, getASN(n), timedelta[i], formatFsm(n.State.AdminState, n.State.SessionState), fmt.Sprint(n.State.AdjTable.Received), fmt.Sprint(n.State.AdjTable.Accepted)) + } + + return nil +} + +func showNeighbor(args []string) error { + p, e := client.GetNeighbor(args[0], true) + if e != nil { + return e + } + 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.Config.RouteReflectorClient { + fmt.Printf(", route-reflector-client\n") + } else if p.RouteServer.Config.RouteServerClient { + fmt.Printf(", route-server-client\n") + } else { + fmt.Printf("\n") + } + + id := "unknown" + if p.State.RemoteRouterId != "" { + id = p.State.RemoteRouterId + } + 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.AsPathOptions.Config.AllowOwnAs; as > 0 { + elems = append(elems, fmt.Sprintf("Allow Own AS: %d", as)) + } + switch p.Config.RemovePrivateAs { + case config.REMOVE_PRIVATE_AS_OPTION_ALL: + elems = append(elems, "Remove private AS: all") + case config.REMOVE_PRIVATE_AS_OPTION_REPLACE: + elems = append(elems, "Remove private AS: replace") + } + if p.AsPathOptions.Config.ReplacePeerAs { + elems = append(elems, "Replace peer AS: enabled") + } + + fmt.Printf(" %s\n", strings.Join(elems, ", ")) + + fmt.Printf(" Neighbor capabilities:\n") + caps := capabilities{} + lookup := func(val bgp.ParameterCapabilityInterface, l capabilities) 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 + } + for _, c := range p.State.LocalCapabilityList { + caps = append(caps, c) + } + for _, c := range p.State.RemoteCapabilityList { + if lookup(c, caps) == nil { + caps = append(caps, c) + } + } + + sort.Sort(caps) + + firstMp := true + + for _, c := range caps { + support := "" + if m := lookup(c, p.State.LocalCapabilityList); m != nil { + support += "advertised" + } + if lookup(c, p.State.RemoteCapabilityList) != 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, p.State.LocalCapabilityList); m != nil { + g := m.(*bgp.CapGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Local: %s", s) + } + } + if m := lookup(c, p.State.RemoteCapabilityList); 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, p.State.LocalCapabilityList); m != nil { + g := m.(*bgp.CapLongLivedGracefulRestart) + if s := grStr(g); len(s) > 0 { + fmt.Printf(" Local:\n%s", s) + } + } + if m := lookup(c, p.State.RemoteCapabilityList); 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, p.State.LocalCapabilityList); m != nil { + e := m.(*bgp.CapExtendedNexthop) + if s := exnhStr(e); len(s) > 0 { + fmt.Printf(" Local: %s\n", s) + } + } + if m := lookup(c, p.State.RemoteCapabilityList); 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, p.State.LocalCapabilityList); 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, p.State.RemoteCapabilityList); 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.AdjTable.Advertised) + fmt.Printf(" Received: %10d\n", p.State.AdjTable.Received) + fmt.Printf(" Accepted: %10d\n", p.State.AdjTable.Accepted) + first := true + for _, afisafi := range p.AfiSafis { + if afisafi.PrefixLimit.Config.MaxPrefixes > 0 { + if first { + fmt.Println(" Prefix Limits:") + first = false + } + fmt.Printf(" %s:\tMaximum prefixes allowed %d", afisafi.Config.AfiSafiName, afisafi.PrefixLimit.Config.MaxPrefixes) + if afisafi.PrefixLimit.Config.ShutdownThresholdPct > 0 { + fmt.Printf(", Threshold for warning message %d%%\n", afisafi.PrefixLimit.Config.ShutdownThresholdPct) + } else { + fmt.Printf("\n") + } + } + } + return nil +} + +type AsPathFormat struct{} + +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, _ := p.GetNativeNlri() + + // 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, _ := p.GetNativePathAttributes() + // 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, _ := p.GetNativePathAttributes() + for _, attr := range attrs { + if attr.GetType() == bgp.BGP_ATTR_TYPE_AS_PATH { + asPath = attr.(*bgp.PathAttributeAsPath).Value + } + } + + nlri, _ := p.GetNativeNlri() + 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 == CMD_GLOBAL { + def = bgp.RF_IPv4_UC + } + family, err := checkAddressFamily(def) + if err != nil { + return err + } + + var info *api.TableInfo + switch r { + case CMD_GLOBAL: + info, err = client.GetRIBInfo(family) + case CMD_LOCAL: + info, err = client.GetLocalRIBInfo(name, family) + case CMD_ADJ_IN: + info, err = client.GetAdjRIBInInfo(name, family) + case CMD_ADJ_OUT: + info, err = client.GetAdjRIBOutInfo(name, family) + default: + return fmt.Errorf("invalid resource to show RIB info: %s", r) + } + + if err != nil { + return err + } + + if globalOpts.Json { + j, _ := json.Marshal(info) + fmt.Println(string(j)) + return nil + } + fmt.Printf("Table %s\n", family) + fmt.Printf("Destination: %d, Path: %d\n", info.NumDestination, info.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 CMD_GLOBAL: + def = bgp.RF_IPv4_UC + showBest = true + case CMD_LOCAL: + showBest = true + case CMD_ADJ_OUT: + showAge = false + case CMD_VRF: + def = bgp.RF_IPv4_UC + showBest = true + } + family, err := checkAddressFamily(def) + if err != nil { + return err + } + switch family { + 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 family { + 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 != CMD_ADJ_IN { + 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 rib *api.Table + switch r { + case CMD_GLOBAL: + rib, err = client.GetRIB(family, filter) + case CMD_LOCAL: + rib, err = client.GetLocalRIB(name, family, filter) + case CMD_ADJ_IN, CMD_ACCEPTED, CMD_REJECTED: + showIdentifier = bgp.BGP_ADD_PATH_RECEIVE + rib, err = client.GetAdjRIBIn(name, family, filter) + case CMD_ADJ_OUT: + showIdentifier = bgp.BGP_ADD_PATH_SEND + rib, err = client.GetAdjRIBOut(name, family, filter) + case CMD_VRF: + rib, err = client.GetVRFRIB(name, family, filter) + } + + if err != nil { + return err + } + + switch r { + case CMD_LOCAL, CMD_ADJ_IN, CMD_ACCEPTED, CMD_REJECTED, CMD_ADJ_OUT: + if len(rib.Destinations) == 0 { + peer, err := client.GetNeighbor(name, false) + if err != nil { + return err + } + if peer.State.SessionState != config.SESSION_STATE_ESTABLISHED { + return fmt.Errorf("Neighbor %v's BGP session is not established", name) + } + } + } + + if globalOpts.Json { + d := make(map[string]*api.Destination) + for _, dst := range rib.GetDestinations() { + d[dst.Prefix] = 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.GetDestinations() { + 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 + dsts := rib.GetDestinations() + switch family { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC: + type d struct { + prefix net.IP + dst *api.Destination + } + l := make([]*d, 0, len(dsts)) + for _, dst := range dsts { + _, 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(dsts)) + for _, s := range l { + dsts = append(dsts, s.dst) + } + } + + for _, d := range dsts { + switch r { + case CMD_ACCEPTED: + l := make([]*api.Path, 0, len(d.Paths)) + for _, p := range d.GetPaths() { + if !p.Filtered { + l = append(l, p) + } + } + d.Paths = l + case CMD_REJECTED: + // 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 { + family := bgp.RouteFamily(0) + 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 CMD_RESET: + return client.ResetNeighbor(remoteIP, neighborsOpts.Reason) + case CMD_SOFT_RESET: + return client.SoftReset(remoteIP, family) + case CMD_SOFT_RESET_IN: + return client.SoftResetIn(remoteIP, family) + case CMD_SOFT_RESET_OUT: + return client.SoftResetOut(remoteIP, family) + } + return nil +} + +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 CMD_SHUTDOWN: + fmt.Printf("WARNING: command `%s` is deprecated. use `%s` instead\n", CMD_SHUTDOWN, CMD_DISABLE) + return client.ShutdownNeighbor(remoteIP, neighborsOpts.Reason) + case CMD_ENABLE: + return client.EnableNeighbor(remoteIP) + case CMD_DISABLE: + return client.DisableNeighbor(remoteIP, neighborsOpts.Reason) + } + return nil +} + +func showNeighborPolicy(remoteIP, policyType string, indent int) error { + var assignment *api.PolicyAssignment + var err error + + switch strings.ToLower(policyType) { + case "import": + assignment, err = client.GetRouteServerImportPolicy(remoteIP) + case "export": + assignment, err = client.GetRouteServerExportPolicy(remoteIP) + default: + return fmt.Errorf("invalid policy type: choose from (in|import|export)") + } + + if err != nil { + return err + } + + 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.Default.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 { + resource := api.Resource_GLOBAL + if remoteIP != "" { + resource = api.Resource_LOCAL + } + + assign := &api.PolicyAssignment{ + Name: remoteIP, + Resource: resource, + } + + switch strings.ToLower(policyType) { + case "import": + assign.Type = api.PolicyType_IMPORT + case "export": + assign.Type = api.PolicyType_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 CMD_ADD, CMD_SET: + 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.Default = 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 CMD_ADD: + err = client.AddPolicyAssignment(assign) + case CMD_SET: + err = client.ReplacePolicyAssignment(assign) + case CMD_DEL: + all := false + if len(args) == 0 { + all = true + } + err = client.DeletePolicyAssignment(assign, all) + } + return err +} + +func modNeighbor(cmdType string, args []string) error { + params := map[string]int{ + "interface": PARAM_SINGLE, + } + usage := fmt.Sprintf("usage: gobgp neighbor %s [ <neighbor-address> | interface <neighbor-interface> ]", cmdType) + if cmdType == CMD_ADD { + usage += " as <VALUE>" + } else if cmdType == CMD_UPDATE { + usage += " [ as <VALUE> ]" + } + if cmdType == CMD_ADD || cmdType == CMD_UPDATE { + params["as"] = PARAM_SINGLE + params["family"] = PARAM_SINGLE + params["vrf"] = PARAM_SINGLE + params["route-reflector-client"] = PARAM_SINGLE + params["route-server-client"] = PARAM_FLAG + params["allow-own-as"] = PARAM_SINGLE + params["remove-private-as"] = PARAM_SINGLE + params["replace-peer-as"] = PARAM_FLAG + 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 ]" + } + + 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() (*config.Neighbor, error) { + addr, err := getNeighborAddress() + if err != nil { + return nil, err + } + var peer *config.Neighbor + switch cmdType { + case CMD_ADD, CMD_DEL: + peer = &config.Neighbor{} + if unnumbered { + peer.Config.NeighborInterface = m["interface"][0] + } else { + peer.Config.NeighborAddress = addr + } + peer.State.NeighborAddress = addr + case CMD_UPDATE: + peer, err = client.GetNeighbor(addr) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid command: %s", cmdType) + } + return peer, nil + } + + updateNeighborConfig := func(peer *config.Neighbor) error { + if len(m["as"]) > 0 { + as, err := strconv.ParseUint(m["as"][0], 10, 32) + if err != nil { + return err + } + peer.Config.PeerAs = uint32(as) + } + if len(m["family"]) == 1 { + peer.AfiSafis = make([]config.AfiSafi, 0) // for the case of CMD_UPDATE + for _, family := range strings.Split(m["family"][0], ",") { + afiSafiName := config.AfiSafiType(family) + if afiSafiName.ToInt() == -1 { + return fmt.Errorf("invalid family value: %s", family) + } + peer.AfiSafis = append(peer.AfiSafis, config.AfiSafi{Config: config.AfiSafiConfig{AfiSafiName: afiSafiName}}) + } + } + if len(m["vrf"]) == 1 { + peer.Config.Vrf = m["vrf"][0] + } + if option, ok := m["route-reflector-client"]; ok { + peer.RouteReflector.Config = config.RouteReflectorConfig{ + RouteReflectorClient: true, + } + if len(option) == 1 { + peer.RouteReflector.Config.RouteReflectorClusterId = config.RrClusterIdType(option[0]) + } + } + if _, ok := m["route-server-client"]; ok { + peer.RouteServer.Config = config.RouteServerConfig{ + RouteServerClient: true, + } + } + if option, ok := m["allow-own-as"]; ok { + as, err := strconv.ParseUint(option[0], 10, 8) + if err != nil { + return err + } + peer.AsPathOptions.Config.AllowOwnAs = uint8(as) + } + if option, ok := m["remove-private-as"]; ok { + switch option[0] { + case "all": + peer.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_ALL + case "replace": + peer.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_REPLACE + default: + return fmt.Errorf("invalid remove-private-as value: all or replace") + } + } + if _, ok := m["replace-peer-as"]; ok { + peer.AsPathOptions.Config.ReplacePeerAs = true + } + return nil + } + + n, err := getNeighborConfig() + if err != nil { + return err + } + + switch cmdType { + case CMD_ADD: + if err := updateNeighborConfig(n); err != nil { + return err + } + return client.AddNeighbor(n) + case CMD_DEL: + return client.DeleteNeighbor(n) + case CMD_UPDATE: + if err := updateNeighborConfig(n); err != nil { + return err + } + _, err := client.UpdateNeighbor(n, true) + return err + } + return nil +} + +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{CMD_LOCAL, CMD_ADJ_IN, CMD_ADJ_OUT, CMD_ACCEPTED, CMD_REJECTED}, showNeighborRib}) + c = append(c, cmds{[]string{CMD_RESET, CMD_SOFT_RESET, CMD_SOFT_RESET_IN, CMD_SOFT_RESET_OUT}, resetNeighbor}) + c = append(c, cmds{[]string{CMD_SHUTDOWN, CMD_ENABLE, CMD_DISABLE}, stateChangeNeighbor}) + + 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 CMD_RESET, CMD_SOFT_RESET, CMD_SOFT_RESET_IN, CMD_SOFT_RESET_OUT, CMD_SHUTDOWN: + if args[len(args)-1] == "all" { + addr = "all" + } + } + if addr == "" { + peer, err := client.GetNeighbor(args[len(args)-1], false) + if err != nil { + exitWithError(err) + } + addr = peer.State.NeighborAddress + } + err := f(cmd.Use, addr, args[:len(args)-1]) + if err != nil { + exitWithError(err) + } + }, + } + neighborCmdImpl.AddCommand(c) + switch name { + case CMD_LOCAL, CMD_ADJ_IN, CMD_ADJ_OUT: + n := name + c.AddCommand(&cobra.Command{ + Use: CMD_SUMMARY, + Run: func(cmd *cobra.Command, args []string) { + if err := showRibInfo(n, args[len(args)-1]); err != nil { + exitWithError(err) + } + }, + }) + } + } + } + + policyCmd := &cobra.Command{ + Use: CMD_POLICY, + Run: func(cmd *cobra.Command, args []string) { + peer, err := client.GetNeighbor(args[0], false) + if err != nil { + exitWithError(err) + } + remoteIP := peer.State.NeighborAddress + for _, v := range []string{CMD_IN, CMD_IMPORT, CMD_EXPORT} { + if err := showNeighborPolicy(remoteIP, v, 4); err != nil { + exitWithError(err) + } + } + }, + } + + for _, v := range []string{CMD_IN, CMD_IMPORT, CMD_EXPORT} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + peer, err := client.GetNeighbor(args[0], false) + if err != nil { + exitWithError(err) + } + remoteIP := peer.State.NeighborAddress + err = showNeighborPolicy(remoteIP, cmd.Use, 0) + if err != nil { + exitWithError(err) + } + }, + } + + for _, w := range []string{CMD_ADD, CMD_DEL, CMD_SET} { + subcmd := &cobra.Command{ + Use: w, + Run: func(subcmd *cobra.Command, args []string) { + peer, err := client.GetNeighbor(args[len(args)-1], false) + 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: CMD_NEIGHBOR, + 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{CMD_ADD, CMD_DEL, CMD_UPDATE} { + 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 +} diff --git a/cmd/gobgp/cmd/policy.go b/cmd/gobgp/cmd/policy.go new file mode 100644 index 00000000..7faa8648 --- /dev/null +++ b/cmd/gobgp/cmd/policy.go @@ -0,0 +1,1081 @@ +// 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 cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "regexp" + "strconv" + "strings" + + "github.com/spf13/cobra" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +var ( + _regexpCommunity = regexp.MustCompile(`\^\^(\S+)\$\$`) + repexpCommunity = regexp.MustCompile(`(\d+.)*\d+:\d+`) + regexpLargeCommunity = regexp.MustCompile(`\d+:\d+:\d+`) + regexpCommunityString = regexp.MustCompile(`[\^\$]`) +) + +func parseCommunityRegexp(arg string) (*regexp.Regexp, error) { + i, err := strconv.ParseUint(arg, 10, 32) + if err == nil { + return regexp.Compile(fmt.Sprintf("^%d:%d$", i>>16, i&0x0000ffff)) + } + if repexpCommunity.MatchString(arg) { + return regexp.Compile(fmt.Sprintf("^%s$", arg)) + } + for i, v := range bgp.WellKnownCommunityNameMap { + if strings.Replace(strings.ToLower(arg), "_", "-", -1) == v { + return regexp.Compile(fmt.Sprintf("^%d:%d$", i>>16, i&0x0000ffff)) + } + } + exp, err := regexp.Compile(arg) + if err != nil { + return nil, fmt.Errorf("invalid community format: %s", arg) + } + return exp, nil +} + +func parseExtCommunityRegexp(arg string) (bgp.ExtendedCommunityAttrSubType, *regexp.Regexp, error) { + var subtype bgp.ExtendedCommunityAttrSubType + elems := strings.SplitN(arg, ":", 2) + if len(elems) < 2 { + return subtype, nil, fmt.Errorf("invalid ext-community format([rt|soo]:<value>)") + } + switch strings.ToLower(elems[0]) { + case "rt": + subtype = bgp.EC_SUBTYPE_ROUTE_TARGET + case "soo": + subtype = bgp.EC_SUBTYPE_ROUTE_ORIGIN + default: + return subtype, nil, fmt.Errorf("unknown ext-community subtype. rt, soo is supported") + } + exp, err := parseCommunityRegexp(elems[1]) + return subtype, exp, err +} + +func parseLargeCommunityRegexp(arg string) (*regexp.Regexp, error) { + if regexpLargeCommunity.MatchString(arg) { + return regexp.Compile(fmt.Sprintf("^%s$", arg)) + } + exp, err := regexp.Compile(arg) + if err != nil { + return nil, fmt.Errorf("invalid large-community format: %s", arg) + } + return exp, nil +} + +func routeTypePrettyString(s api.Conditions_RouteType) string { + switch s { + case api.Conditions_ROUTE_TYPE_EXTERNAL: + return "external" + case api.Conditions_ROUTE_TYPE_INTERNAL: + return "internal" + case api.Conditions_ROUTE_TYPE_LOCAL: + return "local" + } + return "unknown" +} + +func prettyString(v interface{}) string { + switch a := v.(type) { + case *api.MatchSet: + var typ string + switch a.Type { + case api.MatchType_ALL: + typ = "all" + case api.MatchType_ANY: + typ = "any" + case api.MatchType_INVERT: + typ = "invert" + } + return fmt.Sprintf("%s %s", typ, a.GetName()) + case *api.AsPathLength: + var typ string + switch a.Type { + case api.AsPathLengthType_EQ: + typ = "=" + case api.AsPathLengthType_GE: + typ = ">=" + case api.AsPathLengthType_LE: + typ = "<=" + } + return fmt.Sprintf("%s%d", typ, a.Length) + case *api.CommunityAction: + l := regexpCommunityString.ReplaceAllString(strings.Join(a.Communities, ", "), "") + var typ string + switch a.Type { + case api.CommunityActionType_COMMUNITY_ADD: + typ = "add" + case api.CommunityActionType_COMMUNITY_REMOVE: + typ = "remove" + case api.CommunityActionType_COMMUNITY_REPLACE: + typ = "replace" + } + return fmt.Sprintf("%s[%s]", typ, l) + case *api.MedAction: + if a.Type == api.MedActionType_MED_MOD && a.Value > 0 { + return fmt.Sprintf("+%d", a.Value) + } + return fmt.Sprintf("%d", a.Value) + case *api.LocalPrefAction: + return fmt.Sprintf("%d", a.Value) + case *api.NexthopAction: + if a.Self { + return "self" + } + return a.Address + case *api.AsPrependAction: + return fmt.Sprintf("prepend %d %d times", a.Asn, a.Repeat) + } + return "unknown" +} + +func formatDefinedSet(head bool, typ string, indent int, list []*api.DefinedSet) string { + if len(list) == 0 { + return "Nothing defined yet\n" + } + buff := bytes.NewBuffer(make([]byte, 0, 64)) + sIndent := strings.Repeat(" ", indent) + maxNameLen := 0 + for _, s := range list { + if len(s.GetName()) > maxNameLen { + maxNameLen = len(s.GetName()) + } + } + if head { + if len("NAME") > maxNameLen { + maxNameLen = len("NAME") + } + } + format := fmt.Sprintf("%%-%ds %%s\n", maxNameLen) + if head { + buff.WriteString(fmt.Sprintf(format, "NAME", typ)) + } + for _, s := range list { + l := s.GetList() + if len(l) == 0 { + buff.WriteString(fmt.Sprintf(format, s.GetName(), "")) + } + for i, x := range l { + if typ == "COMMUNITY" || typ == "EXT-COMMUNITY" || typ == "LARGE-COMMUNITY" { + x = _regexpCommunity.ReplaceAllString(x, "$1") + } + if i == 0 { + buff.WriteString(fmt.Sprintf(format, s.GetName(), x)) + } else { + buff.WriteString(fmt.Sprint(sIndent)) + buff.WriteString(fmt.Sprintf(format, "", x)) + } + } + } + return buff.String() +} + +func showDefinedSet(v string, args []string) error { + var typ api.DefinedType + switch v { + case CMD_PREFIX: + typ = api.DefinedType_PREFIX + case CMD_NEIGHBOR: + typ = api.DefinedType_NEIGHBOR + case CMD_ASPATH: + typ = api.DefinedType_AS_PATH + case CMD_COMMUNITY: + typ = api.DefinedType_COMMUNITY + case CMD_EXTCOMMUNITY: + typ = api.DefinedType_EXT_COMMUNITY + case CMD_LARGECOMMUNITY: + typ = api.DefinedType_LARGE_COMMUNITY + default: + return fmt.Errorf("unknown defined type: %s", v) + } + var m []*api.DefinedSet + if len(args) > 0 { + d, err := client.GetDefinedSetByName(typ, args[0]) + if err != nil { + return err + } + m = []*api.DefinedSet{d} + } else { + var err error + m, err = client.GetDefinedSet(typ) + if err != nil { + return err + } + } + if globalOpts.Json { + j, _ := json.Marshal(m) + fmt.Println(string(j)) + return nil + } + if globalOpts.Quiet { + if len(args) > 0 { + fmt.Println(m) + } else { + for _, p := range m { + fmt.Println(p.GetName()) + } + } + return nil + } + var output string + switch v { + case CMD_PREFIX: + output = formatDefinedSet(true, "PREFIX", 0, m) + case CMD_NEIGHBOR: + output = formatDefinedSet(true, "ADDRESS", 0, m) + case CMD_ASPATH: + output = formatDefinedSet(true, "AS-PATH", 0, m) + case CMD_COMMUNITY: + output = formatDefinedSet(true, "COMMUNITY", 0, m) + case CMD_EXTCOMMUNITY: + output = formatDefinedSet(true, "EXT-COMMUNITY", 0, m) + case CMD_LARGECOMMUNITY: + output = formatDefinedSet(true, "LARGE-COMMUNITY", 0, m) + } + fmt.Print(output) + return nil +} + +func parsePrefixSet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty neighbor set name") + } + name := args[0] + args = args[1:] + var list []*api.Prefix + if len(args) > 0 { + mask := "" + if len(args) > 1 { + mask = args[1] + } + min, max, err := config.ParseMaskLength(args[0], mask) + if err != nil { + return nil, err + } + prefix := &api.Prefix{ + IpPrefix: args[0], + MaskLengthMax: uint32(max), + MaskLengthMin: uint32(min), + } + list = []*api.Prefix{prefix} + } + return &api.DefinedSet{ + Type: api.DefinedType_PREFIX, + Name: name, + Prefixes: list, + }, nil +} + +func parseNeighborSet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty neighbor set name") + } + name := args[0] + args = args[1:] + list := make([]string, 0, len(args[1:])) + for _, arg := range args { + address := net.ParseIP(arg) + if address.To4() != nil { + list = append(list, fmt.Sprintf("%s/32", arg)) + } else if address.To16() != nil { + list = append(list, fmt.Sprintf("%s/128", arg)) + } else { + _, _, err := net.ParseCIDR(arg) + if err != nil { + return nil, fmt.Errorf("invalid address or prefix: %s\nplease enter ipv4 or ipv6 format", arg) + } + } + } + return &api.DefinedSet{ + Type: api.DefinedType_NEIGHBOR, + Name: name, + List: list, + }, nil +} + +func parseAsPathSet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty as-path set name") + } + name := args[0] + args = args[1:] + for _, arg := range args { + _, err := regexp.Compile(arg) + if err != nil { + return nil, err + } + } + return &api.DefinedSet{ + Type: api.DefinedType_AS_PATH, + Name: name, + List: args, + }, nil +} + +func parseCommunitySet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty community set name") + } + name := args[0] + args = args[1:] + for _, arg := range args { + if _, err := parseCommunityRegexp(arg); err != nil { + return nil, err + } + } + return &api.DefinedSet{ + Type: api.DefinedType_COMMUNITY, + Name: name, + List: args, + }, nil +} + +func parseExtCommunitySet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty ext-community set name") + } + name := args[0] + args = args[1:] + for _, arg := range args { + if _, _, err := parseExtCommunityRegexp(arg); err != nil { + return nil, err + } + } + return &api.DefinedSet{ + Type: api.DefinedType_EXT_COMMUNITY, + Name: name, + List: args, + }, nil +} + +func parseLargeCommunitySet(args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty large-community set name") + } + name := args[0] + args = args[1:] + for _, arg := range args { + if _, err := parseLargeCommunityRegexp(arg); err != nil { + return nil, err + } + } + return &api.DefinedSet{ + Type: api.DefinedType_LARGE_COMMUNITY, + Name: name, + List: args, + }, nil +} + +func parseDefinedSet(settype string, args []string) (*api.DefinedSet, error) { + if len(args) < 1 { + return nil, fmt.Errorf("empty large-community set name") + } + + switch settype { + case CMD_PREFIX: + return parsePrefixSet(args) + case CMD_NEIGHBOR: + return parseNeighborSet(args) + case CMD_ASPATH: + return parseAsPathSet(args) + case CMD_COMMUNITY: + return parseCommunitySet(args) + case CMD_EXTCOMMUNITY: + return parseExtCommunitySet(args) + case CMD_LARGECOMMUNITY: + return parseLargeCommunitySet(args) + default: + return nil, fmt.Errorf("invalid defined set type: %s", settype) + } +} + +var modPolicyUsageFormat = map[string]string{ + CMD_PREFIX: "usage: policy prefix %s <name> [<prefix> [<mask range>]]", + CMD_NEIGHBOR: "usage: policy neighbor %s <name> [<neighbor address>...]", + CMD_ASPATH: "usage: policy aspath %s <name> [<regexp>...]", + CMD_COMMUNITY: "usage: policy community %s <name> [<regexp>...]", + CMD_EXTCOMMUNITY: "usage: policy extcommunity %s <name> [<regexp>...]", + CMD_LARGECOMMUNITY: "usage: policy large-community %s <name> [<regexp>...]", +} + +func modDefinedSet(settype string, modtype string, args []string) error { + var d *api.DefinedSet + var err error + if len(args) < 1 { + return fmt.Errorf(modPolicyUsageFormat[settype], modtype) + } + if d, err = parseDefinedSet(settype, args); err != nil { + return err + } + switch modtype { + case CMD_ADD: + err = client.AddDefinedSet(d) + case CMD_DEL: + all := false + if len(args) < 2 { + all = true + } + err = client.DeleteDefinedSet(d, all) + case CMD_SET: + err = client.ReplaceDefinedSet(d) + } + return err +} + +func printStatement(indent int, s *api.Statement) { + sIndent := func(indent int) string { + return strings.Repeat(" ", indent) + } + fmt.Printf("%sStatementName %s:\n", sIndent(indent), s.Name) + fmt.Printf("%sConditions:\n", sIndent(indent+2)) + + ind := sIndent(indent + 4) + + c := s.Conditions + if c.PrefixSet != nil { + fmt.Printf("%sPrefixSet: %s \n", ind, prettyString(c.PrefixSet)) + } else if c.NeighborSet != nil { + fmt.Printf("%sNeighborSet: %s\n", ind, prettyString(c.NeighborSet)) + } else if c.AsPathSet != nil { + fmt.Printf("%sAsPathSet: %s \n", ind, prettyString(c.AsPathSet)) + } else if c.CommunitySet != nil { + fmt.Printf("%sCommunitySet: %s\n", ind, prettyString(c.CommunitySet)) + } else if c.ExtCommunitySet != nil { + fmt.Printf("%sExtCommunitySet: %s\n", ind, prettyString(c.ExtCommunitySet)) + } else if c.LargeCommunitySet != nil { + fmt.Printf("%sLargeCommunitySet: %s\n", ind, prettyString(c.LargeCommunitySet)) + } else if c.NextHopInList != nil { + fmt.Printf("%sNextHopInList: %s\n", ind, "[ "+strings.Join(c.NextHopInList, ", ")+" ]") + } else if c.AsPathLength != nil { + fmt.Printf("%sAsPathLength: %s\n", ind, prettyString(c.AsPathLength)) + } else if c.RpkiResult != -1 { + var result string + switch c.RpkiResult { + case 0: + result = "none" + case 1: + result = "valid" + case 2: + result = "invalid" + case 3: + result = "not-found" + } + fmt.Printf("%sRPKI result: %s\n", ind, result) + } else if c.RouteType != api.Conditions_ROUTE_TYPE_NONE { + fmt.Printf("%sRoute Type: %s\n", ind, routeTypePrettyString(c.RouteType)) + } else if c.AfiSafiIn != nil { + fmt.Printf("%sAFI SAFI In: %s\n", ind, c.AfiSafiIn) + } + + fmt.Printf("%sActions:\n", sIndent(indent+2)) + a := s.Actions + if a.Community != nil { + fmt.Println(ind, "Community: ", prettyString(a.Community)) + } else if a.ExtCommunity != nil { + fmt.Println(ind, "ExtCommunity: ", prettyString(a.ExtCommunity)) + } else if a.LargeCommunity != nil { + fmt.Println(ind, "LargeCommunity: ", prettyString(a.LargeCommunity)) + } else if a.Med != nil { + fmt.Println(ind, "MED: ", prettyString(a.Med)) + } else if a.LocalPref != nil { + fmt.Println(ind, "LocalPref: ", prettyString(a.LocalPref)) + } else if a.AsPrepend != nil { + fmt.Println(ind, "ASPathPrepend: ", prettyString(a.AsPrepend)) + } else if a.Nexthop != nil { + fmt.Println(ind, "Nexthop: ", prettyString(a.Nexthop)) + } + + if a.RouteAction != api.RouteAction_NONE { + action := "accept" + if a.RouteAction == api.RouteAction_REJECT { + action = "reject" + } + fmt.Println(ind, action) + } +} + +func printPolicy(indent int, pd *api.Policy) { + for _, s := range pd.Statements { + printStatement(indent, s) + } +} + +func showPolicy(args []string) error { + policies, err := client.GetPolicy() + if err != nil { + return err + } + var m []*api.Policy + if len(args) > 0 { + for _, p := range policies { + if args[0] == p.Name { + m = append(m, p) + break + } + } + if len(m) == 0 { + return fmt.Errorf("not found %s", args[0]) + } + } else { + m = policies + } + if globalOpts.Json { + j, _ := json.Marshal(m) + fmt.Println(string(j)) + return nil + } + if globalOpts.Quiet { + for _, p := range m { + fmt.Println(p.Name) + } + return nil + } + + for _, pd := range m { + fmt.Printf("Name %s:\n", pd.Name) + printPolicy(4, pd) + } + return nil +} + +func showStatement(args []string) error { + stmts, err := client.GetStatement() + if err != nil { + return err + } + var m []*api.Statement + if len(args) > 0 { + for _, s := range stmts { + if args[0] == s.Name { + m = append(m, s) + break + } + } + if len(m) == 0 { + return fmt.Errorf("not found %s", args[0]) + } + } else { + m = stmts + } + if globalOpts.Json { + j, _ := json.Marshal(m) + fmt.Println(string(j)) + return nil + } + if globalOpts.Quiet { + for _, s := range m { + fmt.Println(s.Name) + } + return nil + } + for _, s := range m { + printStatement(0, s) + } + return nil +} + +func modStatement(op string, args []string) error { + if len(args) < 1 { + return fmt.Errorf("usage: gobgp policy statement %s <name>", op) + } + stmt := &api.Statement{ + Name: args[0], + } + var err error + switch op { + case CMD_ADD: + err = client.AddStatement(stmt) + case CMD_DEL: + err = client.DeleteStatement(stmt, false) + default: + return fmt.Errorf("invalid operation: %s", op) + } + return err +} + +func modCondition(name, op string, args []string) error { + stmt := &api.Statement{ + Name: name, + Conditions: &api.Conditions{}, + } + usage := fmt.Sprintf("usage: gobgp policy statement %s %s condition", name, op) + if len(args) < 1 { + return fmt.Errorf("%s { prefix | neighbor | as-path | community | ext-community | large-community | as-path-length | rpki | route-type | next-hop-in-list | afi-safi-in }", usage) + } + typ := args[0] + args = args[1:] + switch typ { + case "prefix": + stmt.Conditions.PrefixSet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s prefix <set-name> [{ any | invert }]", usage) + } + stmt.Conditions.PrefixSet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.PrefixSet.Type = api.MatchType_ANY + case "invert": + stmt.Conditions.PrefixSet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s prefix <set-name> [{ any | invert }]", usage) + } + case "neighbor": + stmt.Conditions.NeighborSet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s neighbor <set-name> [{ any | invert }]", usage) + } + stmt.Conditions.NeighborSet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.NeighborSet.Type = api.MatchType_ANY + case "invert": + stmt.Conditions.NeighborSet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s neighbor <set-name> [{ any | invert }]", usage) + } + case "as-path": + stmt.Conditions.AsPathSet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s as-path <set-name> [{ any | all | invert }]", usage) + } + stmt.Conditions.AsPathSet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.AsPathSet.Type = api.MatchType_ANY + case "all": + stmt.Conditions.AsPathSet.Type = api.MatchType_ALL + case "invert": + stmt.Conditions.AsPathSet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s as-path <set-name> [{ any | all | invert }]", usage) + } + case "community": + stmt.Conditions.CommunitySet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s community <set-name> [{ any | all | invert }]", usage) + } + stmt.Conditions.CommunitySet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.CommunitySet.Type = api.MatchType_ANY + case "all": + stmt.Conditions.CommunitySet.Type = api.MatchType_ALL + case "invert": + stmt.Conditions.CommunitySet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s community <set-name> [{ any | all | invert }]", usage) + } + case "ext-community": + stmt.Conditions.ExtCommunitySet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s ext-community <set-name> [{ any | all | invert }]", usage) + } + stmt.Conditions.ExtCommunitySet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.ExtCommunitySet.Type = api.MatchType_ANY + case "all": + stmt.Conditions.ExtCommunitySet.Type = api.MatchType_ALL + case "invert": + stmt.Conditions.ExtCommunitySet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s ext-community <set-name> [{ any | all | invert }]", usage) + } + case "large-community": + stmt.Conditions.LargeCommunitySet = &api.MatchSet{} + if len(args) < 1 { + return fmt.Errorf("%s large-community <set-name> [{ any | all | invert }]", usage) + } + stmt.Conditions.LargeCommunitySet.Name = args[0] + if len(args) == 1 { + break + } + switch strings.ToLower(args[1]) { + case "any": + stmt.Conditions.LargeCommunitySet.Type = api.MatchType_ANY + case "all": + stmt.Conditions.LargeCommunitySet.Type = api.MatchType_ALL + case "invert": + stmt.Conditions.LargeCommunitySet.Type = api.MatchType_INVERT + default: + return fmt.Errorf("%s large-community <set-name> [{ any | all | invert }]", usage) + } + case "as-path-length": + stmt.Conditions.AsPathLength = &api.AsPathLength{} + if len(args) < 2 { + return fmt.Errorf("%s as-path-length <length> { eq | ge | le }", usage) + } + length, err := strconv.ParseUint(args[0], 10, 32) + if err != nil { + return err + } + stmt.Conditions.AsPathLength.Length = uint32(length) + switch strings.ToLower(args[1]) { + case "eq": + stmt.Conditions.AsPathLength.Type = api.AsPathLengthType_EQ + case "ge": + stmt.Conditions.AsPathLength.Type = api.AsPathLengthType_GE + case "le": + stmt.Conditions.AsPathLength.Type = api.AsPathLengthType_LE + default: + return fmt.Errorf("%s as-path-length <length> { eq | ge | le }", usage) + } + case "rpki": + if len(args) < 1 { + return fmt.Errorf("%s rpki { valid | invalid | not-found }", usage) + } + switch strings.ToLower(args[0]) { + case "valid": + stmt.Conditions.RpkiResult = int32(config.RpkiValidationResultTypeToIntMap[config.RPKI_VALIDATION_RESULT_TYPE_VALID]) + case "invalid": + stmt.Conditions.RpkiResult = int32(config.RpkiValidationResultTypeToIntMap[config.RPKI_VALIDATION_RESULT_TYPE_INVALID]) + case "not-found": + stmt.Conditions.RpkiResult = int32(config.RpkiValidationResultTypeToIntMap[config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND]) + default: + return fmt.Errorf("%s rpki { valid | invalid | not-found }", usage) + } + case "route-type": + err := fmt.Errorf("%s route-type { internal | external | local }", usage) + if len(args) < 1 { + return err + } + switch strings.ToLower(args[0]) { + case "internal": + stmt.Conditions.RouteType = api.Conditions_ROUTE_TYPE_INTERNAL + case "external": + stmt.Conditions.RouteType = api.Conditions_ROUTE_TYPE_EXTERNAL + case "local": + stmt.Conditions.RouteType = api.Conditions_ROUTE_TYPE_LOCAL + default: + return err + } + case "next-hop-in-list": + stmt.Conditions.NextHopInList = args + case "afi-safi-in": + afiSafisInList := make([]api.Family, 0, len(args)) + for _, arg := range args { + afiSafisInList = append(afiSafisInList, api.Family(bgp.AddressFamilyValueMap[arg])) + } + stmt.Conditions.AfiSafiIn = afiSafisInList + default: + return fmt.Errorf("%s { prefix | neighbor | as-path | community | ext-community | large-community | as-path-length | rpki | route-type | next-hop-in-list | afi-safi-in }", usage) + } + + var err error + switch op { + case CMD_ADD: + err = client.AddStatement(stmt) + case CMD_DEL: + err = client.DeleteStatement(stmt, false) + case CMD_SET: + err = client.ReplaceStatement(stmt) + default: + return fmt.Errorf("invalid operation: %s", op) + } + return err +} + +func modAction(name, op string, args []string) error { + stmt := &api.Statement{ + Name: name, + Actions: &api.Actions{}, + } + usage := fmt.Sprintf("usage: gobgp policy statement %s %s action", name, op) + if len(args) < 1 { + return fmt.Errorf("%s { reject | accept | community | ext-community | large-community | med | local-pref | as-prepend | next-hop }", usage) + } + typ := args[0] + args = args[1:] + switch typ { + case "reject": + stmt.Actions.RouteAction = api.RouteAction_REJECT + case "accept": + stmt.Actions.RouteAction = api.RouteAction_ACCEPT + case "community": + stmt.Actions.Community = &api.CommunityAction{} + if len(args) < 1 { + return fmt.Errorf("%s community { add | remove | replace } <value>...", usage) + } + stmt.Actions.Community.Communities = args[1:] + switch strings.ToLower(args[0]) { + case "add": + stmt.Actions.Community.Type = api.CommunityActionType_COMMUNITY_ADD + case "remove": + stmt.Actions.Community.Type = api.CommunityActionType_COMMUNITY_REMOVE + case "replace": + stmt.Actions.Community.Type = api.CommunityActionType_COMMUNITY_REPLACE + default: + return fmt.Errorf("%s community { add | remove | replace } <value>...", usage) + } + case "ext-community": + stmt.Actions.ExtCommunity = &api.CommunityAction{} + if len(args) < 1 { + return fmt.Errorf("%s ext-community { add | remove | replace } <value>...", usage) + } + stmt.Actions.ExtCommunity.Communities = args[1:] + switch strings.ToLower(args[0]) { + case "add": + stmt.Actions.ExtCommunity.Type = api.CommunityActionType_COMMUNITY_ADD + case "remove": + stmt.Actions.ExtCommunity.Type = api.CommunityActionType_COMMUNITY_REMOVE + case "replace": + stmt.Actions.ExtCommunity.Type = api.CommunityActionType_COMMUNITY_REPLACE + default: + return fmt.Errorf("%s ext-community { add | remove | replace } <value>...", usage) + } + case "large-community": + stmt.Actions.LargeCommunity = &api.CommunityAction{} + if len(args) < 1 { + return fmt.Errorf("%s large-community { add | remove | replace } <value>...", usage) + } + stmt.Actions.LargeCommunity.Communities = args[1:] + switch strings.ToLower(args[0]) { + case "add": + stmt.Actions.LargeCommunity.Type = api.CommunityActionType_COMMUNITY_ADD + case "remove": + stmt.Actions.LargeCommunity.Type = api.CommunityActionType_COMMUNITY_REMOVE + case "replace": + stmt.Actions.LargeCommunity.Type = api.CommunityActionType_COMMUNITY_REPLACE + default: + return fmt.Errorf("%s large-community { add | remove | replace } <value>...", usage) + } + case "med": + stmt.Actions.Med = &api.MedAction{} + if len(args) < 2 { + return fmt.Errorf("%s med { add | sub | set } <value>", usage) + } + med, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return err + } + stmt.Actions.Med.Value = int64(med) + switch strings.ToLower(args[0]) { + case "add": + stmt.Actions.Med.Type = api.MedActionType_MED_MOD + case "sub": + stmt.Actions.Med.Type = api.MedActionType_MED_MOD + stmt.Actions.Med.Value = -1 * stmt.Actions.Med.Value + case "set": + stmt.Actions.Med.Type = api.MedActionType_MED_REPLACE + default: + return fmt.Errorf("%s med { add | sub | set } <value>", usage) + } + case "local-pref": + stmt.Actions.LocalPref = &api.LocalPrefAction{} + if len(args) < 1 { + return fmt.Errorf("%s local-pref <value>", usage) + } + value, err := strconv.ParseUint(args[0], 10, 32) + if err != nil { + return err + } + stmt.Actions.LocalPref.Value = uint32(value) + case "as-prepend": + stmt.Actions.AsPrepend = &api.AsPrependAction{} + if len(args) < 2 { + return fmt.Errorf("%s as-prepend { <asn> | last-as } <repeat-value>", usage) + } + asn, _ := strconv.ParseUint(args[0], 10, 32) + stmt.Actions.AsPrepend.Asn = uint32(asn) + repeat, err := strconv.ParseUint(args[1], 10, 8) + if err != nil { + return err + } + stmt.Actions.AsPrepend.Repeat = uint32(repeat) + case "next-hop": + stmt.Actions.Nexthop = &api.NexthopAction{} + if len(args) != 1 { + return fmt.Errorf("%s next-hop { <value> | self }", usage) + } + stmt.Actions.Nexthop.Address = args[0] + } + var err error + switch op { + case CMD_ADD: + err = client.AddStatement(stmt) + case CMD_DEL: + err = client.DeleteStatement(stmt, false) + case CMD_SET: + err = client.ReplaceStatement(stmt) + default: + return fmt.Errorf("invalid operation: %s", op) + } + return err +} + +func modPolicy(modtype string, args []string) error { + if len(args) < 1 { + return fmt.Errorf("usage: gobgp policy %s <name> [<statement name>...]", modtype) + } + name := args[0] + args = args[1:] + stmts := make([]*api.Statement, 0, len(args)) + for _, n := range args { + stmts = append(stmts, &api.Statement{Name: n}) + } + policy := &api.Policy{ + Name: name, + Statements: stmts, + } + + var err error + switch modtype { + case CMD_ADD: + err = client.AddPolicy(policy, true) + case CMD_DEL: + all := false + if len(args) < 1 { + all = true + } + err = client.DeletePolicy(policy, all, true) + case CMD_SET: + err = client.ReplacePolicy(policy, true, true) + } + return err +} + +func NewPolicyCmd() *cobra.Command { + policyCmd := &cobra.Command{ + Use: CMD_POLICY, + Run: func(cmd *cobra.Command, args []string) { + err := showPolicy(args) + if err != nil { + exitWithError(err) + } + }, + } + + for _, v := range []string{CMD_PREFIX, CMD_NEIGHBOR, CMD_ASPATH, CMD_COMMUNITY, CMD_EXTCOMMUNITY, CMD_LARGECOMMUNITY} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + if err := showDefinedSet(cmd.Use, args); err != nil { + exitWithError(err) + } + }, + } + for _, w := range []string{CMD_ADD, CMD_DEL, CMD_SET} { + subcmd := &cobra.Command{ + Use: w, + Run: func(c *cobra.Command, args []string) { + if err := modDefinedSet(cmd.Use, c.Use, args); err != nil { + exitWithError(err) + } + }, + } + cmd.AddCommand(subcmd) + } + policyCmd.AddCommand(cmd) + } + + stmtCmdImpl := &cobra.Command{} + for _, v := range []string{CMD_ADD, CMD_DEL, CMD_SET} { + cmd := &cobra.Command{ + Use: v, + } + for _, w := range []string{CMD_CONDITION, CMD_ACTION} { + subcmd := &cobra.Command{ + Use: w, + Run: func(c *cobra.Command, args []string) { + name := args[len(args)-1] + args = args[:len(args)-1] + var err error + if c.Use == CMD_CONDITION { + err = modCondition(name, cmd.Use, args) + } else { + err = modAction(name, cmd.Use, args) + } + if err != nil { + exitWithError(err) + } + }, + } + cmd.AddCommand(subcmd) + } + stmtCmdImpl.AddCommand(cmd) + } + + stmtCmd := &cobra.Command{ + Use: CMD_STATEMENT, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) < 2 { + err = showStatement(args) + } else { + args = append(args[1:], args[0]) + stmtCmdImpl.SetArgs(args) + err = stmtCmdImpl.Execute() + } + if err != nil { + exitWithError(err) + } + }, + } + for _, v := range []string{CMD_ADD, CMD_DEL} { + cmd := &cobra.Command{ + Use: v, + Run: func(c *cobra.Command, args []string) { + err := modStatement(c.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + stmtCmd.AddCommand(cmd) + } + policyCmd.AddCommand(stmtCmd) + + for _, v := range []string{CMD_ADD, CMD_DEL, CMD_SET} { + cmd := &cobra.Command{ + Use: v, + Run: func(c *cobra.Command, args []string) { + err := modPolicy(c.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + policyCmd.AddCommand(cmd) + } + + return policyCmd +} diff --git a/cmd/gobgp/cmd/root.go b/cmd/gobgp/cmd/root.go new file mode 100644 index 00000000..90df5ee4 --- /dev/null +++ b/cmd/gobgp/cmd/root.go @@ -0,0 +1,94 @@ +// 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 cmd + +import ( + "fmt" + "net/http" + _ "net/http/pprof" + + cli "github.com/osrg/gobgp/internal/pkg/client" + "github.com/spf13/cobra" +) + +var globalOpts struct { + Host string + Port int + Debug bool + Quiet bool + Json bool + GenCmpl bool + BashCmplFile string + PprofPort int + TLS bool + CaFile string +} + +var client *cli.Client + +func NewRootCmd() *cobra.Command { + cobra.EnablePrefixMatching = true + rootCmd := &cobra.Command{ + Use: "gobgp", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if globalOpts.PprofPort > 0 { + go func() { + if err := http.ListenAndServe(fmt.Sprintf("localhost:%d", globalOpts.PprofPort), nil); err != nil { + exitWithError(err) + } + }() + } + + if !globalOpts.GenCmpl { + client = newClient() + } + }, + Run: func(cmd *cobra.Command, args []string) { + if globalOpts.GenCmpl { + cmd.GenBashCompletionFile(globalOpts.BashCmplFile) + } else { + cmd.HelpFunc()(cmd, args) + } + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if client != nil { + client.Close() + } + }, + } + + rootCmd.PersistentFlags().StringVarP(&globalOpts.Host, "host", "u", "127.0.0.1", "host") + rootCmd.PersistentFlags().IntVarP(&globalOpts.Port, "port", "p", 50051, "port") + rootCmd.PersistentFlags().BoolVarP(&globalOpts.Json, "json", "j", false, "use json format to output format") + rootCmd.PersistentFlags().BoolVarP(&globalOpts.Debug, "debug", "d", false, "use debug") + rootCmd.PersistentFlags().BoolVarP(&globalOpts.Quiet, "quiet", "q", false, "use quiet") + rootCmd.PersistentFlags().BoolVarP(&globalOpts.GenCmpl, "gen-cmpl", "c", false, "generate completion file") + rootCmd.PersistentFlags().StringVarP(&globalOpts.BashCmplFile, "bash-cmpl-file", "", "gobgp-completion.bash", "bash cmpl filename") + rootCmd.PersistentFlags().IntVarP(&globalOpts.PprofPort, "pprof-port", "r", 0, "pprof port") + rootCmd.PersistentFlags().BoolVarP(&globalOpts.TLS, "tls", "", false, "connection uses TLS if true, else plain TCP") + rootCmd.PersistentFlags().StringVarP(&globalOpts.CaFile, "tls-ca-file", "", "", "The file containing the CA root cert file") + + globalCmd := NewGlobalCmd() + neighborCmd := NewNeighborCmd() + vrfCmd := NewVrfCmd() + policyCmd := NewPolicyCmd() + monitorCmd := NewMonitorCmd() + mrtCmd := NewMrtCmd() + rpkiCmd := NewRPKICmd() + bmpCmd := NewBmpCmd() + rootCmd.AddCommand(globalCmd, neighborCmd, vrfCmd, policyCmd, monitorCmd, mrtCmd, rpkiCmd, bmpCmd) + return rootCmd +} diff --git a/cmd/gobgp/cmd/rpki.go b/cmd/gobgp/cmd/rpki.go new file mode 100644 index 00000000..853892d4 --- /dev/null +++ b/cmd/gobgp/cmd/rpki.go @@ -0,0 +1,162 @@ +// 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 cmd + +import ( + "fmt" + "net" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/spf13/cobra" +) + +func showRPKIServer(args []string) error { + servers, err := client.GetRPKI() + if err != nil { + fmt.Println(err) + return err + } + if len(args) == 0 { + format := "%-23s %-6s %-10s %s\n" + fmt.Printf(format, "Session", "State", "Uptime", "#IPv4/IPv6 records") + for _, r := range servers { + s := "Down" + uptime := "never" + if r.State.Up { + s = "Up" + uptime = fmt.Sprint(formatTimedelta(int64(time.Since(time.Unix(r.State.Uptime, 0)).Seconds()))) + } + + fmt.Printf(format, net.JoinHostPort(r.Config.Address, fmt.Sprintf("%d", r.Config.Port)), s, uptime, fmt.Sprintf("%d/%d", r.State.RecordsV4, r.State.RecordsV6)) + } + return nil + } + + for _, r := range servers { + if r.Config.Address == args[0] { + up := "Down" + if r.State.Up { + up = "Up" + } + fmt.Printf("Session: %s, State: %s\n", r.Config.Address, up) + fmt.Println(" Port:", r.Config.Port) + fmt.Println(" Serial:", r.State.SerialNumber) + fmt.Printf(" Prefix: %d/%d\n", r.State.PrefixesV4, r.State.PrefixesV6) + fmt.Printf(" Record: %d/%d\n", r.State.RecordsV4, r.State.RecordsV6) + fmt.Println(" Message statistics:") + fmt.Printf(" Receivedv4: %10d\n", r.State.RpkiMessages.RpkiReceived.Ipv4Prefix) + fmt.Printf(" Receivedv6: %10d\n", r.State.RpkiMessages.RpkiReceived.Ipv4Prefix) + fmt.Printf(" SerialNotify: %10d\n", r.State.RpkiMessages.RpkiReceived.SerialNotify) + fmt.Printf(" CacheReset: %10d\n", r.State.RpkiMessages.RpkiReceived.CacheReset) + fmt.Printf(" CacheResponse: %10d\n", r.State.RpkiMessages.RpkiReceived.CacheResponse) + fmt.Printf(" EndOfData: %10d\n", r.State.RpkiMessages.RpkiReceived.EndOfData) + fmt.Printf(" Error: %10d\n", r.State.RpkiMessages.RpkiReceived.Error) + fmt.Printf(" SerialQuery: %10d\n", r.State.RpkiMessages.RpkiSent.SerialQuery) + fmt.Printf(" ResetQuery: %10d\n", r.State.RpkiMessages.RpkiSent.ResetQuery) + } + } + return nil +} + +func showRPKITable(args []string) error { + family, err := checkAddressFamily(bgp.RouteFamily(0)) + if err != nil { + exitWithError(err) + } + roas, err := client.GetROA(family) + if err != nil { + exitWithError(err) + } + + var format string + afi, _ := bgp.RouteFamilyToAfiSafi(family) + if afi == bgp.AFI_IP { + format = "%-18s %-6s %-10s %s\n" + } else { + format = "%-42s %-6s %-10s %s\n" + } + fmt.Printf(format, "Network", "Maxlen", "AS", "Server") + for _, r := range roas { + if len(args) > 0 && args[0] != r.Conf.Address { + continue + } + fmt.Printf(format, r.Prefix, fmt.Sprint(r.Maxlen), fmt.Sprint(r.As), net.JoinHostPort(r.Conf.Address, r.Conf.RemotePort)) + } + return nil +} + +func NewRPKICmd() *cobra.Command { + rpkiCmd := &cobra.Command{ + Use: CMD_RPKI, + } + + serverCmd := &cobra.Command{ + Use: CMD_RPKI_SERVER, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 || len(args) == 1 { + showRPKIServer(args) + return + } else if len(args) != 2 { + exitWithError(fmt.Errorf("usage: gobgp rpki server <ip address> [reset|softreset|enable]")) + } + addr := net.ParseIP(args[0]) + if addr == nil { + exitWithError(fmt.Errorf("invalid ip address: %s", args[0])) + } + var err error + switch args[1] { + case "add": + err = client.AddRPKIServer(addr.String(), 323, 0) + case "reset": + err = client.ResetRPKIServer(addr.String()) + case "softreset": + err = client.SoftResetRPKIServer(addr.String()) + case "enable": + err = client.EnableRPKIServer(addr.String()) + case "disable": + err = client.DisableRPKIServer(addr.String()) + default: + exitWithError(fmt.Errorf("unknown operation: %s", args[1])) + } + if err != nil { + exitWithError(err) + } + }, + } + rpkiCmd.AddCommand(serverCmd) + + tableCmd := &cobra.Command{ + Use: CMD_RPKI_TABLE, + Run: func(cmd *cobra.Command, args []string) { + showRPKITable(args) + }, + } + tableCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + + validateCmd := &cobra.Command{ + Use: "validate", + Run: func(cmd *cobra.Command, args []string) { + if err := client.ValidateRIBWithRPKI(args...); err != nil { + exitWithError(err) + } + }, + } + rpkiCmd.AddCommand(validateCmd) + + rpkiCmd.AddCommand(tableCmd) + return rpkiCmd +} diff --git a/cmd/gobgp/cmd/rpki_test.go b/cmd/gobgp/cmd/rpki_test.go new file mode 100644 index 00000000..af4a9cd1 --- /dev/null +++ b/cmd/gobgp/cmd/rpki_test.go @@ -0,0 +1,76 @@ +// Copyright (C) 2018 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 cmd + +import ( + "testing" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/server" + "github.com/stretchr/testify/assert" +) + +func TestShowRPKITable(test *testing.T) { + assert := assert.New(test) + + s := server.NewBgpServer() + go s.Serve() + + g := server.NewGrpcServer(s, ":50051") + go g.Serve() + + err := s.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: -1, + }, + }) + assert.Nil(err) + defer s.Stop() + + // MF RPKI Project + // http://www.mfeed.ad.jp/rpki/en/roa_cache/technical_info.html + rpki := &config.RpkiServerConfig{ + Address: "210.173.170.254", + Port: 323, + } + err = s.AddRpki(rpki) + assert.Nil(err) + + globalOpts.Host = "127.0.0.1" + globalOpts.Port = 50051 + client = newClient() + defer client.Close() + + // Wait for downloading ROA info + for i := 0; ; i++ { + if servers, err := s.GetRpki(); err == nil && len(servers) > 0 { + if servers[0].State.RecordsV4 > 0 { + break + } + } + if i > 10 { + test.Error("timeout to download ROA info") + break + } + time.Sleep(1 * time.Second) + } + + err = showRPKITable(nil) + assert.Nil(err) +} diff --git a/cmd/gobgp/cmd/vrf.go b/cmd/gobgp/cmd/vrf.go new file mode 100644 index 00000000..fd55dc0a --- /dev/null +++ b/cmd/gobgp/cmd/vrf.go @@ -0,0 +1,264 @@ +// 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 cmd + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/golang/protobuf/ptypes/any" + "github.com/spf13/cobra" +) + +func getVrfs() (vrfs, error) { + ret, err := client.GetVRF() + if err != nil { + return nil, err + } + sort.Sort(vrfs(ret)) + return ret, nil +} + +func showVrfs() error { + maxLens := []int{20, 20, 20, 20, 5} + vrfs, err := getVrfs() + if err != nil { + return err + } + if globalOpts.Json { + j, _ := json.Marshal(vrfs) + fmt.Println(string(j)) + return nil + } + if globalOpts.Quiet { + for _, v := range vrfs { + fmt.Println(v.Name) + } + return nil + } + lines := make([][]string, 0, len(vrfs)) + for _, v := range vrfs { + name := v.Name + rd, err := api.UnmarshalRD(v.Rd) + if err != nil { + return err + } + rdStr := rd.String() + + f := func(rts []*any.Any) (string, error) { + ret := make([]string, 0, len(rts)) + for _, an := range rts { + rt, err := api.UnmarshalRT(an) + if err != nil { + return "", err + } + ret = append(ret, rt.String()) + } + return strings.Join(ret, ", "), nil + } + + importRts, err := f(v.ImportRt) + if err != nil { + return err + } + exportRts, err := f(v.ExportRt) + if err != nil { + return err + } + lines = append(lines, []string{name, rdStr, importRts, exportRts, fmt.Sprintf("%d", v.Id)}) + + for i, v := range []int{len(name), len(rdStr), len(importRts), len(exportRts)} { + if v > maxLens[i] { + maxLens[i] = v + 4 + } + } + + } + format := fmt.Sprintf(" %%-%ds %%-%ds %%-%ds %%-%ds %%-%ds\n", maxLens[0], maxLens[1], maxLens[2], maxLens[3], maxLens[4]) + fmt.Printf(format, "Name", "RD", "Import RT", "Export RT", "ID") + for _, l := range lines { + fmt.Printf(format, l[0], l[1], l[2], l[3], l[4]) + } + return nil +} + +func showVrf(name string) error { + return showNeighborRib(CMD_VRF, name, nil) +} + +func modVrf(typ string, args []string) error { + var err error + switch typ { + case CMD_ADD: + a, err := extractReserved(args, map[string]int{ + "rd": PARAM_SINGLE, + "rt": PARAM_LIST, + "id": PARAM_SINGLE}) + if err != nil || len(a[""]) != 1 || len(a["rd"]) != 1 || len(a["rt"]) < 2 { + return fmt.Errorf("Usage: gobgp vrf add <vrf name> [ id <id> ] rd <rd> rt { import | export | both } <rt>...") + } + name := a[""][0] + var rd bgp.RouteDistinguisherInterface + rd, err = bgp.ParseRouteDistinguisher(a["rd"][0]) + if err != nil { + return err + } + cur := "" + importRt := make([]bgp.ExtendedCommunityInterface, 0) + exportRt := make([]bgp.ExtendedCommunityInterface, 0) + for _, elem := range a["rt"] { + if elem == "import" || elem == "export" || elem == "both" { + cur = elem + continue + } + rt, err := bgp.ParseRouteTarget(elem) + if err != nil { + return err + } + switch cur { + case "import": + importRt = append(importRt, rt) + case "export": + exportRt = append(exportRt, rt) + case "both": + importRt = append(importRt, rt) + exportRt = append(exportRt, rt) + default: + return fmt.Errorf("Usage: gobgp vrf add <vrf name> rd <rd> rt { import | export | both } <rt>...") + } + } + var id uint64 + if len(a["id"]) > 0 { + id, err = strconv.ParseUint(a["id"][0], 10, 32) + if err != nil { + return err + } + } + if err := client.AddVRF(name, int(id), rd, importRt, exportRt); err != nil { + return err + } + case CMD_DEL: + if len(args) != 1 { + return fmt.Errorf("Usage: gobgp vrf del <vrf name>") + } + err = client.DeleteVRF(args[0]) + } + return err +} + +func NewVrfCmd() *cobra.Command { + ribCmd := &cobra.Command{ + Use: CMD_RIB, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 1 { + err = showVrf(args[0]) + } else { + err = fmt.Errorf("usage: gobgp vrf <vrf-name> rib") + } + if err != nil { + exitWithError(err) + } + }, + } + + for _, v := range []string{CMD_ADD, CMD_DEL} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + err := modPath(CMD_VRF, args[len(args)-1], cmd.Use, args[:len(args)-1]) + if err != nil { + exitWithError(err) + } + }, + } + ribCmd.AddCommand(cmd) + } + + neighborCmd := &cobra.Command{ + Use: CMD_NEIGHBOR, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 1 { + var vs vrfs + vs, err = getVrfs() + if err != nil { + exitWithError(err) + } + found := false + for _, v := range vs { + if v.Name == args[0] { + found = true + break + } + } + if !found { + err = fmt.Errorf("vrf %s not found", args[0]) + } else { + err = showNeighbors(args[0]) + } + } else { + err = fmt.Errorf("usage: gobgp vrf <vrf-name> neighbor") + } + if err != nil { + exitWithError(err) + } + }, + } + + vrfCmdImpl := &cobra.Command{} + vrfCmdImpl.AddCommand(ribCmd, neighborCmd) + + vrfCmd := &cobra.Command{ + Use: CMD_VRF, + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 0 { + err = showVrfs() + } else if len(args) == 1 { + } else { + args = append(args[1:], args[0]) + vrfCmdImpl.SetArgs(args) + err = vrfCmdImpl.Execute() + } + if err != nil { + exitWithError(err) + } + }, + } + + for _, v := range []string{CMD_ADD, CMD_DEL} { + cmd := &cobra.Command{ + Use: v, + Run: func(cmd *cobra.Command, args []string) { + err := modVrf(cmd.Use, args) + if err != nil { + exitWithError(err) + } + }, + } + vrfCmd.AddCommand(cmd) + } + vrfCmd.PersistentFlags().StringVarP(&subOpts.AddressFamily, "address-family", "a", "", "address family") + + return vrfCmd +} diff --git a/cmd/gobgp/lib/lib.go b/cmd/gobgp/lib/lib.go new file mode 100644 index 00000000..ee71a859 --- /dev/null +++ b/cmd/gobgp/lib/lib.go @@ -0,0 +1,78 @@ +// 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 + +// #include <stdio.h> +// #include <stdlib.h> +// #include <string.h> +// typedef struct { +// char *value; +// int len; +// } buf; +// +// typedef struct path_t { +// buf nlri; +// buf** path_attributes; +// int path_attributes_len; +// int path_attributes_cap; +// } path; +// +// path* new_path() { +// path* p; +// int cap = 32; +// p = (path*)malloc(sizeof(path)); +// memset(p, 0, sizeof(path)); +// p->nlri.len = 0; +// p->path_attributes_len = 0; +// p->path_attributes_cap = cap; +// p->path_attributes = (buf**)malloc(sizeof(buf)*cap); +// return p; +// } +// +// void free_path(path* p) { +// int i; +// if (p->nlri.value != NULL) { +// free(p->nlri.value); +// } +// for (i = 0; i < p->path_attributes_len; i++) { +// buf* b; +// b = p->path_attributes[i]; +// free(b->value); +// free(b); +// } +// free(p->path_attributes); +// free(p); +// } +// +// int append_path_attribute(path* p, int len, char* value) { +// buf* b; +// if (p->path_attributes_len >= p->path_attributes_cap) { +// return -1; +// } +// b = (buf*)malloc(sizeof(buf)); +// b->value = value; +// b->len = len; +// p->path_attributes[p->path_attributes_len] = b; +// p->path_attributes_len++; +// return 0; +// } +// buf* get_path_attribute(path* p, int idx) { +// if (idx < 0 || idx >= p->path_attributes_len) { +// return NULL; +// } +// return p->path_attributes[idx]; +// } +import "C" diff --git a/cmd/gobgp/lib/path.go b/cmd/gobgp/lib/path.go new file mode 100644 index 00000000..130f5444 --- /dev/null +++ b/cmd/gobgp/lib/path.go @@ -0,0 +1,143 @@ +// 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 + +// typedef struct { +// char *value; +// int len; +// } buf; +// +// typedef struct path_t { +// buf nlri; +// buf** path_attributes; +// int path_attributes_len; +// int path_attributes_cap; +// } path; +// extern path* new_path(); +// extern void free_path(path*); +// extern int append_path_attribute(path*, int, char*); +// extern buf* get_path_attribute(path*, int); +import "C" + +import ( + "encoding/json" + "strings" + + "github.com/osrg/gobgp/cmd/gobgp/cmd" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +//export get_route_family +func get_route_family(input *C.char) C.int { + rf, err := bgp.GetRouteFamily(C.GoString(input)) + if err != nil { + return C.int(-1) + } + return C.int(rf) +} + +//export serialize_path +func serialize_path(rf C.int, input *C.char) *C.path { + args := strings.Split(C.GoString(input), " ") + p, err := cmd.ParsePath(bgp.RouteFamily(rf), args) + if err != nil { + return nil + } + path := C.new_path() + if nlri, err := p.GetNativeNlri(); err != nil { + return nil + } else { + buf, _ := nlri.Serialize() + path.nlri.len = C.int(len(buf)) + path.nlri.value = C.CString(string(buf)) + } + attrs, err := p.GetNativePathAttributes() + if err != nil { + return nil + } + for _, attr := range attrs { + buf, err := attr.Serialize() + if err != nil { + return nil + } + C.append_path_attribute(path, C.int(len(buf)), C.CString(string(buf))) + } + return path +} + +//export decode_path +func decode_path(p *C.path) *C.char { + var buf []byte + var nlri bgp.AddrPrefixInterface + if p.nlri.len > 0 { + buf = []byte(C.GoStringN(p.nlri.value, p.nlri.len)) + nlri = &bgp.IPAddrPrefix{} + err := nlri.DecodeFromBytes(buf) + if err != nil { + return nil + } + } + pattrs := make([]bgp.PathAttributeInterface, 0, int(p.path_attributes_len)) + for i := 0; i < int(p.path_attributes_len); i++ { + b := C.get_path_attribute(p, C.int(i)) + buf = []byte(C.GoStringN(b.value, b.len)) + pattr, err := bgp.GetPathAttribute(buf) + if err != nil { + return nil + } + + err = pattr.DecodeFromBytes(buf) + if err != nil { + return nil + } + + switch pattr.GetType() { + case bgp.BGP_ATTR_TYPE_MP_REACH_NLRI: + mpreach := pattr.(*bgp.PathAttributeMpReachNLRI) + if len(mpreach.Value) != 1 { + return nil + } + nlri = mpreach.Value[0] + } + + pattrs = append(pattrs, pattr) + } + j, _ := json.Marshal(struct { + Nlri bgp.AddrPrefixInterface `json:"nlri"` + PathAttrs []bgp.PathAttributeInterface `json:"attrs"` + }{ + Nlri: nlri, + PathAttrs: pattrs, + }) + return C.CString(string(j)) +} + +//export decode_capabilities +func decode_capabilities(p *C.buf) *C.char { + buf := []byte(C.GoStringN(p.value, p.len)) + c, err := bgp.DecodeCapability(buf) + if err != nil { + return nil + } + j, _ := json.Marshal(c) + return C.CString(string(j)) + +} + +func main() { + // We need the main function to make possible + // CGO compiler to compile the package as C shared library +} diff --git a/cmd/gobgp/main.go b/cmd/gobgp/main.go new file mode 100644 index 00000000..b3ae82f6 --- /dev/null +++ b/cmd/gobgp/main.go @@ -0,0 +1,35 @@ +// 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 ( + "fmt" + "os" + + "github.com/osrg/gobgp/cmd/gobgp/cmd" + "google.golang.org/grpc" +) + +var version = "master" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println("gobgp version", version) + os.Exit(0) + } + grpc.EnableTracing = false + cmd.NewRootCmd().Execute() +} diff --git a/cmd/gobgpd/main.go b/cmd/gobgpd/main.go new file mode 100644 index 00000000..6f1a364d --- /dev/null +++ b/cmd/gobgpd/main.go @@ -0,0 +1,450 @@ +// +// Copyright (C) 2014-2017 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 ( + "fmt" + "io/ioutil" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + "runtime" + "syscall" + + "github.com/golang/protobuf/ptypes/any" + "github.com/jessevdk/go-flags" + "github.com/kr/pretty" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/server" +) + +var version = "master" + +func marshalRouteTargets(l []string) ([]*any.Any, error) { + rtList := make([]*any.Any, 0, len(l)) + for _, rtString := range l { + rt, err := bgp.ParseRouteTarget(rtString) + if err != nil { + return nil, err + } + rtList = append(rtList, api.MarshalRT(rt)) + } + return rtList, nil +} + +func main() { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGTERM) + + var opts struct { + ConfigFile string `short:"f" long:"config-file" description:"specifying a config file"` + ConfigType string `short:"t" long:"config-type" description:"specifying config type (toml, yaml, json)" default:"toml"` + LogLevel string `short:"l" long:"log-level" description:"specifying log level"` + LogPlain bool `short:"p" long:"log-plain" description:"use plain format for logging (json by default)"` + UseSyslog string `short:"s" long:"syslog" description:"use syslogd"` + Facility string `long:"syslog-facility" description:"specify syslog facility"` + DisableStdlog bool `long:"disable-stdlog" description:"disable standard logging"` + CPUs int `long:"cpus" description:"specify the number of CPUs to be used"` + GrpcHosts string `long:"api-hosts" description:"specify the hosts that gobgpd listens on" default:":50051"` + GracefulRestart bool `short:"r" long:"graceful-restart" description:"flag restart-state in graceful-restart capability"` + Dry bool `short:"d" long:"dry-run" description:"check configuration"` + PProfHost string `long:"pprof-host" description:"specify the host that gobgpd listens on for pprof" default:"localhost:6060"` + PProfDisable bool `long:"pprof-disable" description:"disable pprof profiling"` + TLS bool `long:"tls" description:"enable TLS authentication for gRPC API"` + TLSCertFile string `long:"tls-cert-file" description:"The TLS cert file"` + TLSKeyFile string `long:"tls-key-file" description:"The TLS key file"` + Version bool `long:"version" description:"show version number"` + } + _, err := flags.Parse(&opts) + if err != nil { + os.Exit(1) + } + + if opts.Version { + fmt.Println("gobgpd version", version) + os.Exit(0) + } + + if opts.CPUs == 0 { + runtime.GOMAXPROCS(runtime.NumCPU()) + } else { + if runtime.NumCPU() < opts.CPUs { + log.Errorf("Only %d CPUs are available but %d is specified", runtime.NumCPU(), opts.CPUs) + os.Exit(1) + } + runtime.GOMAXPROCS(opts.CPUs) + } + + if !opts.PProfDisable { + go func() { + log.Println(http.ListenAndServe(opts.PProfHost, nil)) + }() + } + + switch opts.LogLevel { + case "debug": + log.SetLevel(log.DebugLevel) + case "info": + log.SetLevel(log.InfoLevel) + default: + log.SetLevel(log.InfoLevel) + } + + if opts.DisableStdlog { + log.SetOutput(ioutil.Discard) + } else { + log.SetOutput(os.Stdout) + } + + if opts.UseSyslog != "" { + if err := addSyslogHook(opts.UseSyslog, opts.Facility); err != nil { + log.Error("Unable to connect to syslog daemon, ", opts.UseSyslog) + } + } + + if opts.LogPlain { + if opts.DisableStdlog { + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + }) + } + } else { + log.SetFormatter(&log.JSONFormatter{}) + } + + configCh := make(chan *config.BgpConfigSet) + if opts.Dry { + go config.ReadConfigfileServe(opts.ConfigFile, opts.ConfigType, configCh) + c := <-configCh + if opts.LogLevel == "debug" { + pretty.Println(c) + } + os.Exit(0) + } + + log.Info("gobgpd started") + bgpServer := server.NewBgpServer() + go bgpServer.Serve() + + var grpcOpts []grpc.ServerOption + if opts.TLS { + creds, err := credentials.NewServerTLSFromFile(opts.TLSCertFile, opts.TLSKeyFile) + if err != nil { + log.Fatalf("Failed to generate credentials: %v", err) + } + grpcOpts = []grpc.ServerOption{grpc.Creds(creds)} + } + // start grpc Server + apiServer := server.NewServer(bgpServer, grpc.NewServer(grpcOpts...), opts.GrpcHosts) + go func() { + if err := apiServer.Serve(); err != nil { + log.Fatalf("failed to listen grpc port: %s", err) + } + }() + + if opts.ConfigFile != "" { + go config.ReadConfigfileServe(opts.ConfigFile, opts.ConfigType, configCh) + } + + loop := func() { + var c *config.BgpConfigSet + for { + select { + case <-sigCh: + apiServer.Shutdown(context.Background(), &api.ShutdownRequest{}) + return + case newConfig := <-configCh: + var added, deleted, updated []config.Neighbor + var addedPg, deletedPg, updatedPg []config.PeerGroup + var updatePolicy bool + + if c == nil { + c = newConfig + if _, err := apiServer.StartServer(context.Background(), &api.StartServerRequest{ + Global: server.NewGlobalFromConfigStruct(&c.Global), + }); err != nil { + log.Fatalf("failed to set global config: %s", err) + } + + if newConfig.Zebra.Config.Enabled { + tps := c.Zebra.Config.RedistributeRouteTypeList + l := make([]string, 0, len(tps)) + for _, t := range tps { + l = append(l, string(t)) + } + if _, err := apiServer.EnableZebra(context.Background(), &api.EnableZebraRequest{ + Url: c.Zebra.Config.Url, + RouteTypes: l, + Version: uint32(c.Zebra.Config.Version), + NexthopTriggerEnable: c.Zebra.Config.NexthopTriggerEnable, + NexthopTriggerDelay: uint32(c.Zebra.Config.NexthopTriggerDelay), + }); err != nil { + log.Fatalf("failed to set zebra config: %s", err) + } + } + + if len(newConfig.Collector.Config.Url) > 0 { + if _, err := apiServer.AddCollector(context.Background(), &api.AddCollectorRequest{ + Url: c.Collector.Config.Url, + DbName: c.Collector.Config.DbName, + TableDumpInterval: c.Collector.Config.TableDumpInterval, + }); err != nil { + log.Fatalf("failed to set collector config: %s", err) + } + } + + for _, c := range newConfig.RpkiServers { + if _, err := apiServer.AddRpki(context.Background(), &api.AddRpkiRequest{ + Address: c.Config.Address, + Port: c.Config.Port, + Lifetime: c.Config.RecordLifetime, + }); err != nil { + log.Fatalf("failed to set rpki config: %s", err) + } + } + for _, c := range newConfig.BmpServers { + if _, err := apiServer.AddBmp(context.Background(), &api.AddBmpRequest{ + Address: c.Config.Address, + Port: c.Config.Port, + Type: api.AddBmpRequest_MonitoringPolicy(c.Config.RouteMonitoringPolicy.ToInt()), + }); err != nil { + log.Fatalf("failed to set bmp config: %s", err) + } + } + for _, vrf := range newConfig.Vrfs { + rd, err := bgp.ParseRouteDistinguisher(vrf.Config.Rd) + if err != nil { + log.Fatalf("failed to load vrf rd config: %s", err) + } + + importRtList, err := marshalRouteTargets(vrf.Config.ImportRtList) + if err != nil { + log.Fatalf("failed to load vrf import rt config: %s", err) + } + exportRtList, err := marshalRouteTargets(vrf.Config.ExportRtList) + if err != nil { + log.Fatalf("failed to load vrf export rt config: %s", err) + } + + if _, err := apiServer.AddVrf(context.Background(), &api.AddVrfRequest{ + Vrf: &api.Vrf{ + Name: vrf.Config.Name, + Rd: api.MarshalRD(rd), + Id: uint32(vrf.Config.Id), + ImportRt: importRtList, + ExportRt: exportRtList, + }, + }); err != nil { + log.Fatalf("failed to set vrf config: %s", err) + } + } + for _, c := range newConfig.MrtDump { + if len(c.Config.FileName) == 0 { + continue + } + if _, err := apiServer.EnableMrt(context.Background(), &api.EnableMrtRequest{ + DumpType: int32(c.Config.DumpType.ToInt()), + Filename: c.Config.FileName, + Interval: c.Config.DumpInterval, + }); err != nil { + log.Fatalf("failed to set mrt config: %s", err) + } + } + p := config.ConfigSetToRoutingPolicy(newConfig) + rp, err := server.NewAPIRoutingPolicyFromConfigStruct(p) + if err != nil { + log.Warn(err) + } else { + apiServer.UpdatePolicy(context.Background(), &api.UpdatePolicyRequest{ + Sets: rp.DefinedSet, + Policies: rp.PolicyDefinition, + }) + } + + added = newConfig.Neighbors + addedPg = newConfig.PeerGroups + if opts.GracefulRestart { + for i, n := range added { + if n.GracefulRestart.Config.Enabled { + added[i].GracefulRestart.State.LocalRestarting = true + } + } + } + + } else { + addedPg, deletedPg, updatedPg = config.UpdatePeerGroupConfig(c, newConfig) + added, deleted, updated = config.UpdateNeighborConfig(c, newConfig) + updatePolicy = config.CheckPolicyDifference(config.ConfigSetToRoutingPolicy(c), config.ConfigSetToRoutingPolicy(newConfig)) + + if updatePolicy { + log.Info("Policy config is updated") + p := config.ConfigSetToRoutingPolicy(newConfig) + rp, err := server.NewAPIRoutingPolicyFromConfigStruct(p) + if err != nil { + log.Warn(err) + } else { + apiServer.UpdatePolicy(context.Background(), &api.UpdatePolicyRequest{ + Sets: rp.DefinedSet, + Policies: rp.PolicyDefinition, + }) + } + } + // global policy update + if !newConfig.Global.ApplyPolicy.Config.Equal(&c.Global.ApplyPolicy.Config) { + a := newConfig.Global.ApplyPolicy.Config + toDefaultTable := func(r config.DefaultPolicyType) table.RouteType { + var def table.RouteType + switch r { + case config.DEFAULT_POLICY_TYPE_ACCEPT_ROUTE: + def = table.ROUTE_TYPE_ACCEPT + case config.DEFAULT_POLICY_TYPE_REJECT_ROUTE: + def = table.ROUTE_TYPE_REJECT + } + return def + } + toPolicies := func(r []string) []*table.Policy { + p := make([]*table.Policy, 0, len(r)) + for _, n := range r { + p = append(p, &table.Policy{ + Name: n, + }) + } + return p + } + + def := toDefaultTable(a.DefaultImportPolicy) + ps := toPolicies(a.ImportPolicyList) + apiServer.ReplacePolicyAssignment(context.Background(), &api.ReplacePolicyAssignmentRequest{ + Assignment: server.NewAPIPolicyAssignmentFromTableStruct(&table.PolicyAssignment{ + Name: "", + Type: table.POLICY_DIRECTION_IMPORT, + Policies: ps, + Default: def, + }), + }) + + def = toDefaultTable(a.DefaultExportPolicy) + ps = toPolicies(a.ExportPolicyList) + apiServer.ReplacePolicyAssignment(context.Background(), &api.ReplacePolicyAssignmentRequest{ + Assignment: server.NewAPIPolicyAssignmentFromTableStruct(&table.PolicyAssignment{ + Name: "", + Type: table.POLICY_DIRECTION_EXPORT, + Policies: ps, + Default: def, + }), + }) + + updatePolicy = true + + } + c = newConfig + } + for _, pg := range addedPg { + log.Infof("PeerGroup %s is added", pg.Config.PeerGroupName) + if _, err := apiServer.AddPeerGroup(context.Background(), &api.AddPeerGroupRequest{ + PeerGroup: server.NewPeerGroupFromConfigStruct(&pg), + }); err != nil { + log.Warn(err) + } + } + for _, pg := range deletedPg { + log.Infof("PeerGroup %s is deleted", pg.Config.PeerGroupName) + if _, err := apiServer.DeletePeerGroup(context.Background(), &api.DeletePeerGroupRequest{ + PeerGroup: server.NewPeerGroupFromConfigStruct(&pg), + }); err != nil { + log.Warn(err) + } + } + for _, pg := range updatedPg { + log.Infof("PeerGroup %v is updated", pg.State.PeerGroupName) + if u, err := apiServer.UpdatePeerGroup(context.Background(), &api.UpdatePeerGroupRequest{ + PeerGroup: server.NewPeerGroupFromConfigStruct(&pg), + }); err != nil { + log.Warn(err) + } else { + updatePolicy = updatePolicy || u.NeedsSoftResetIn + } + } + for _, pg := range updatedPg { + log.Infof("PeerGroup %s is updated", pg.Config.PeerGroupName) + if _, err := apiServer.UpdatePeerGroup(context.Background(), &api.UpdatePeerGroupRequest{ + PeerGroup: server.NewPeerGroupFromConfigStruct(&pg), + }); err != nil { + log.Warn(err) + } + } + for _, dn := range newConfig.DynamicNeighbors { + log.Infof("Dynamic Neighbor %s is added to PeerGroup %s", dn.Config.Prefix, dn.Config.PeerGroup) + if _, err := apiServer.AddDynamicNeighbor(context.Background(), &api.AddDynamicNeighborRequest{ + DynamicNeighbor: &api.DynamicNeighbor{ + Prefix: dn.Config.Prefix, + PeerGroup: dn.Config.PeerGroup, + }, + }); err != nil { + log.Warn(err) + } + } + for _, p := range added { + log.Infof("Peer %v is added", p.State.NeighborAddress) + if _, err := apiServer.AddNeighbor(context.Background(), &api.AddNeighborRequest{ + Peer: server.NewPeerFromConfigStruct(&p), + }); err != nil { + log.Warn(err) + } + } + for _, p := range deleted { + log.Infof("Peer %v is deleted", p.State.NeighborAddress) + if _, err := apiServer.DeleteNeighbor(context.Background(), &api.DeleteNeighborRequest{ + Peer: server.NewPeerFromConfigStruct(&p), + }); err != nil { + log.Warn(err) + } + } + for _, p := range updated { + log.Infof("Peer %v is updated", p.State.NeighborAddress) + if u, err := apiServer.UpdateNeighbor(context.Background(), &api.UpdateNeighborRequest{ + Peer: server.NewPeerFromConfigStruct(&p), + }); err != nil { + log.Warn(err) + } else { + updatePolicy = updatePolicy || u.NeedsSoftResetIn + } + } + + if updatePolicy { + if _, err := apiServer.SoftResetNeighbor(context.Background(), &api.SoftResetNeighborRequest{ + Address: "", + Direction: api.SoftResetNeighborRequest_IN, + }); err != nil { + log.Warn(err) + } + } + } + } + } + + loop() +} diff --git a/cmd/gobgpd/util.go b/cmd/gobgpd/util.go new file mode 100644 index 00000000..1b7885b8 --- /dev/null +++ b/cmd/gobgpd/util.go @@ -0,0 +1,103 @@ +// Copyright (C) 2017 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. + +// +build !windows + +package main + +import ( + "log/syslog" + "os" + "os/signal" + "runtime" + "runtime/debug" + "strings" + "syscall" + + log "github.com/sirupsen/logrus" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" +) + +func init() { + go func() { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGUSR1) + for range sigCh { + runtime.GC() + debug.FreeOSMemory() + } + }() +} + +func addSyslogHook(host, facility string) error { + dst := strings.SplitN(host, ":", 2) + network := "" + addr := "" + if len(dst) == 2 { + network = dst[0] + addr = dst[1] + } + + priority := syslog.Priority(0) + switch facility { + case "kern": + priority = syslog.LOG_KERN + case "user": + priority = syslog.LOG_USER + case "mail": + priority = syslog.LOG_MAIL + case "daemon": + priority = syslog.LOG_DAEMON + case "auth": + priority = syslog.LOG_AUTH + case "syslog": + priority = syslog.LOG_SYSLOG + case "lpr": + priority = syslog.LOG_LPR + case "news": + priority = syslog.LOG_NEWS + case "uucp": + priority = syslog.LOG_UUCP + case "cron": + priority = syslog.LOG_CRON + case "authpriv": + priority = syslog.LOG_AUTHPRIV + case "ftp": + priority = syslog.LOG_FTP + case "local0": + priority = syslog.LOG_LOCAL0 + case "local1": + priority = syslog.LOG_LOCAL1 + case "local2": + priority = syslog.LOG_LOCAL2 + case "local3": + priority = syslog.LOG_LOCAL3 + case "local4": + priority = syslog.LOG_LOCAL4 + case "local5": + priority = syslog.LOG_LOCAL5 + case "local6": + priority = syslog.LOG_LOCAL6 + case "local7": + priority = syslog.LOG_LOCAL7 + } + + hook, err := lSyslog.NewSyslogHook(network, addr, syslog.LOG_INFO|priority, "bgpd") + if err != nil { + return err + } + log.AddHook(hook) + return nil +} diff --git a/cmd/gobgpd/util_windows.go b/cmd/gobgpd/util_windows.go new file mode 100644 index 00000000..56c5bc2f --- /dev/null +++ b/cmd/gobgpd/util_windows.go @@ -0,0 +1,24 @@ +// Copyright (C) 2017 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 ( + "errors" +) + +func addSyslogHook(_, _ string) error { + return errors.New("syslog is not supported on this OS") +} |