summaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 13:48:38 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 20:44:25 +0900
commitc4775c42510d1f1ddd55036dc19e982712fa6a0b (patch)
tree6ec8b61d4338c809e239e3003a2d32d480898e22 /cmd
parentb3079759aa13172fcb548a83da9a9653d8d5fed4 (diff)
follow Standard Go Project Layout
https://github.com/golang-standards/project-layout Now you can see clearly what are private and public library code. Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/gobgp/cmd/bmp.go102
-rw-r--r--cmd/gobgp/cmd/common.go347
-rw-r--r--cmd/gobgp/cmd/common_test.go40
-rw-r--r--cmd/gobgp/cmd/global.go1654
-rw-r--r--cmd/gobgp/cmd/global_test.go38
-rw-r--r--cmd/gobgp/cmd/monitor.go201
-rw-r--r--cmd/gobgp/cmd/mrt.go238
-rw-r--r--cmd/gobgp/cmd/neighbor.go1321
-rw-r--r--cmd/gobgp/cmd/policy.go1081
-rw-r--r--cmd/gobgp/cmd/root.go94
-rw-r--r--cmd/gobgp/cmd/rpki.go162
-rw-r--r--cmd/gobgp/cmd/rpki_test.go76
-rw-r--r--cmd/gobgp/cmd/vrf.go264
-rw-r--r--cmd/gobgp/lib/lib.go78
-rw-r--r--cmd/gobgp/lib/path.go143
-rw-r--r--cmd/gobgp/main.go35
-rw-r--r--cmd/gobgpd/main.go450
-rw-r--r--cmd/gobgpd/util.go103
-rw-r--r--cmd/gobgpd/util_windows.go24
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(&current, "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")
+}