diff options
Diffstat (limited to 'cmd/gobgpd')
-rw-r--r-- | cmd/gobgpd/main.go | 450 | ||||
-rw-r--r-- | cmd/gobgpd/util.go | 103 | ||||
-rw-r--r-- | cmd/gobgpd/util_windows.go | 24 |
3 files changed, 577 insertions, 0 deletions
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") +} |