github.com/osrg/gobgp/v3@v3.30.0/cmd/gobgpd/main.go (about)

     1  //
     2  // Copyright (C) 2014-2017 Nippon Telegraph and Telephone Corporation.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //    http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    13  // implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"net/http/pprof"
    27  	"os"
    28  	"os/signal"
    29  	"runtime"
    30  	"syscall"
    31  	"time"
    32  
    33  	"github.com/coreos/go-systemd/v22/daemon"
    34  	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
    35  	"github.com/jessevdk/go-flags"
    36  	"github.com/kr/pretty"
    37  	"github.com/prometheus/client_golang/prometheus"
    38  	"github.com/prometheus/client_golang/prometheus/promhttp"
    39  	"github.com/sirupsen/logrus"
    40  	"golang.org/x/time/rate"
    41  	"google.golang.org/grpc"
    42  	"google.golang.org/grpc/credentials"
    43  
    44  	"github.com/osrg/gobgp/v3/internal/pkg/metrics"
    45  	"github.com/osrg/gobgp/v3/internal/pkg/version"
    46  	"github.com/osrg/gobgp/v3/pkg/config"
    47  	"github.com/osrg/gobgp/v3/pkg/server"
    48  )
    49  
    50  var logger = logrus.New()
    51  
    52  func main() {
    53  	sigCh := make(chan os.Signal, 1)
    54  	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
    55  
    56  	var opts struct {
    57  		ConfigFile       string `short:"f" long:"config-file" description:"specifying a config file"`
    58  		ConfigType       string `short:"t" long:"config-type" description:"specifying config type (toml, yaml, json)" default:"toml"`
    59  		ConfigAutoReload bool   `short:"a" long:"config-auto-reload" description:"activate config auto reload on changes"`
    60  		LogLevel         string `short:"l" long:"log-level" description:"specifying log level"`
    61  		LogPlain         bool   `short:"p" long:"log-plain" description:"use plain format for logging (json by default)"`
    62  		UseSyslog        string `short:"s" long:"syslog" description:"use syslogd"`
    63  		Facility         string `long:"syslog-facility" description:"specify syslog facility"`
    64  		DisableStdlog    bool   `long:"disable-stdlog" description:"disable standard logging"`
    65  		CPUs             int    `long:"cpus" description:"specify the number of CPUs to be used"`
    66  		GrpcHosts        string `long:"api-hosts" description:"specify the hosts that gobgpd listens on" default:":50051"`
    67  		GracefulRestart  bool   `short:"r" long:"graceful-restart" description:"flag restart-state in graceful-restart capability"`
    68  		Dry              bool   `short:"d" long:"dry-run" description:"check configuration"`
    69  		PProfHost        string `long:"pprof-host" description:"specify the host that gobgpd listens on for pprof and metrics" default:"localhost:6060"`
    70  		PProfDisable     bool   `long:"pprof-disable" description:"disable pprof profiling"`
    71  		MetricsPath      string `long:"metrics-path" description:"specify path for prometheus metrics, empty value disables them" default:"/metrics"`
    72  		UseSdNotify      bool   `long:"sdnotify" description:"use sd_notify protocol"`
    73  		TLS              bool   `long:"tls" description:"enable TLS authentication for gRPC API"`
    74  		TLSCertFile      string `long:"tls-cert-file" description:"The TLS cert file"`
    75  		TLSKeyFile       string `long:"tls-key-file" description:"The TLS key file"`
    76  		TLSClientCAFile  string `long:"tls-client-ca-file" description:"Optional TLS client CA file to authenticate clients against"`
    77  		Version          bool   `long:"version" description:"show version number"`
    78  	}
    79  	_, err := flags.Parse(&opts)
    80  	if err != nil {
    81  		os.Exit(1)
    82  	}
    83  
    84  	if opts.Version {
    85  		fmt.Println("gobgpd version", version.Version())
    86  		os.Exit(0)
    87  	}
    88  
    89  	if opts.CPUs == 0 {
    90  		runtime.GOMAXPROCS(runtime.NumCPU())
    91  	} else {
    92  		if runtime.NumCPU() < opts.CPUs {
    93  			logger.Errorf("Only %d CPUs are available but %d is specified", runtime.NumCPU(), opts.CPUs)
    94  			os.Exit(1)
    95  		}
    96  		runtime.GOMAXPROCS(opts.CPUs)
    97  	}
    98  
    99  	httpMux := http.NewServeMux()
   100  	if !opts.PProfDisable {
   101  		httpMux.HandleFunc("/debug/pprof/", pprof.Index)
   102  		httpMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
   103  		httpMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
   104  		httpMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
   105  		httpMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
   106  	}
   107  	if opts.MetricsPath != "" {
   108  		httpMux.Handle(opts.MetricsPath, promhttp.Handler())
   109  	}
   110  	if !opts.PProfDisable || opts.MetricsPath != "" {
   111  		go func() {
   112  			logger.Println(http.ListenAndServe(opts.PProfHost, httpMux))
   113  		}()
   114  	}
   115  
   116  	switch opts.LogLevel {
   117  	case "debug":
   118  		logger.SetLevel(logrus.DebugLevel)
   119  	case "info":
   120  		logger.SetLevel(logrus.InfoLevel)
   121  	default:
   122  		logger.SetLevel(logrus.InfoLevel)
   123  	}
   124  
   125  	if opts.DisableStdlog {
   126  		logger.SetOutput(io.Discard)
   127  	} else {
   128  		logger.SetOutput(os.Stdout)
   129  	}
   130  
   131  	if opts.UseSyslog != "" {
   132  		if err := addSyslogHook(opts.UseSyslog, opts.Facility); err != nil {
   133  			logger.Error("Unable to connect to syslog daemon, ", opts.UseSyslog)
   134  		}
   135  	}
   136  
   137  	if opts.LogPlain {
   138  		if opts.DisableStdlog {
   139  			logger.SetFormatter(&logrus.TextFormatter{
   140  				DisableColors: true,
   141  			})
   142  		}
   143  	} else {
   144  		logger.SetFormatter(&logrus.JSONFormatter{})
   145  	}
   146  
   147  	if opts.Dry {
   148  		c, err := config.ReadConfigFile(opts.ConfigFile, opts.ConfigType)
   149  		if err != nil {
   150  			logger.WithFields(logrus.Fields{
   151  				"Topic": "Config",
   152  				"Error": err,
   153  			}).Fatalf("Can't read config file %s", opts.ConfigFile)
   154  		}
   155  		logger.WithFields(logrus.Fields{
   156  			"Topic": "Config",
   157  		}).Info("Finished reading the config file")
   158  		if opts.LogLevel == "debug" {
   159  			pretty.Println(c)
   160  		}
   161  		os.Exit(0)
   162  	}
   163  
   164  	maxSize := 256 << 20
   165  	grpcOpts := []grpc.ServerOption{grpc.MaxRecvMsgSize(maxSize), grpc.MaxSendMsgSize(maxSize)}
   166  	if opts.TLS {
   167  		// server cert/key
   168  		cert, err := tls.LoadX509KeyPair(opts.TLSCertFile, opts.TLSKeyFile)
   169  		if err != nil {
   170  			logger.Fatalf("Failed to load server certificate/key pair: %v", err)
   171  		}
   172  		tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
   173  
   174  		// client CA
   175  		if len(opts.TLSClientCAFile) != 0 {
   176  			tlsConfig.ClientCAs = x509.NewCertPool()
   177  			pemCerts, err := os.ReadFile(opts.TLSClientCAFile)
   178  			if err != nil {
   179  				logger.Fatalf("Failed to load client CA certificates from %q: %v", opts.TLSClientCAFile, err)
   180  			}
   181  			if ok := tlsConfig.ClientCAs.AppendCertsFromPEM(pemCerts); !ok {
   182  				logger.Fatalf("No valid client CA certificates in %q", opts.TLSClientCAFile)
   183  			}
   184  			tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
   185  		}
   186  
   187  		creds := credentials.NewTLS(tlsConfig)
   188  		grpcOpts = append(grpcOpts, grpc.Creds(creds))
   189  	}
   190  
   191  	if opts.MetricsPath != "" {
   192  		grpcOpts = append(
   193  			grpcOpts,
   194  			grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
   195  			grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
   196  		)
   197  	}
   198  
   199  	logger.Info("gobgpd started")
   200  	bgpServer := server.NewBgpServer(server.GrpcListenAddress(opts.GrpcHosts), server.GrpcOption(grpcOpts), server.LoggerOption(&builtinLogger{logger: logger}))
   201  	prometheus.MustRegister(metrics.NewBgpCollector(bgpServer))
   202  	go bgpServer.Serve()
   203  
   204  	if opts.UseSdNotify {
   205  		if status, err := daemon.SdNotify(false, daemon.SdNotifyReady); !status {
   206  			if err != nil {
   207  				logger.Warnf("Failed to send notification via sd_notify(): %s", err)
   208  			} else {
   209  				logger.Warnf("The socket sd_notify() isn't available")
   210  			}
   211  		}
   212  	}
   213  
   214  	if opts.ConfigFile == "" {
   215  		<-sigCh
   216  		stopServer(bgpServer, opts.UseSdNotify)
   217  		return
   218  	}
   219  
   220  	signal.Notify(sigCh, syscall.SIGHUP)
   221  
   222  	initialConfig, err := config.ReadConfigFile(opts.ConfigFile, opts.ConfigType)
   223  	if err != nil {
   224  		logger.WithFields(logrus.Fields{
   225  			"Topic": "Config",
   226  			"Error": err,
   227  		}).Fatalf("Can't read config file %s", opts.ConfigFile)
   228  	}
   229  	logger.WithFields(logrus.Fields{
   230  		"Topic": "Config",
   231  	}).Info("Finished reading the config file")
   232  
   233  	currentConfig, err := config.InitialConfig(context.Background(), bgpServer, initialConfig, opts.GracefulRestart)
   234  	if err != nil {
   235  		logger.WithFields(logrus.Fields{
   236  			"Topic": "Config",
   237  			"Error": err,
   238  		}).Fatalf("Failed to apply initial configuration %s", opts.ConfigFile)
   239  	}
   240  
   241  	if opts.ConfigAutoReload {
   242  		logger.WithFields(logrus.Fields{
   243  			"Topic": "Config",
   244  		}).Info("Watching for config changes to trigger auto-reload")
   245  
   246  		// Writing to the config may trigger many events in quick successions
   247  		// To prevent abusive reloads, we ignore any event in a 100ms window
   248  		rateLimiter := rate.Sometimes{Interval: 100 * time.Millisecond}
   249  
   250  		config.WatchConfigFile(opts.ConfigFile, opts.ConfigType, func() {
   251  			rateLimiter.Do(func() {
   252  				logger.WithFields(logrus.Fields{
   253  					"Topic": "Config",
   254  				}).Info("Config changes detected, reloading configuration")
   255  
   256  				sigCh <- syscall.SIGHUP
   257  			})
   258  		})
   259  	}
   260  
   261  	for sig := range sigCh {
   262  		if sig != syscall.SIGHUP {
   263  			stopServer(bgpServer, opts.UseSdNotify)
   264  			return
   265  		}
   266  
   267  		logger.WithFields(logrus.Fields{
   268  			"Topic": "Config",
   269  		}).Info("Reload the config file")
   270  		newConfig, err := config.ReadConfigFile(opts.ConfigFile, opts.ConfigType)
   271  		if err != nil {
   272  			logger.WithFields(logrus.Fields{
   273  				"Topic": "Config",
   274  				"Error": err,
   275  			}).Warningf("Can't read config file %s", opts.ConfigFile)
   276  			continue
   277  		}
   278  
   279  		currentConfig, err = config.UpdateConfig(context.Background(), bgpServer, currentConfig, newConfig)
   280  		if err != nil {
   281  			logrus.WithFields(logrus.Fields{
   282  				"Topic": "Config",
   283  				"Error": err,
   284  			}).Warningf("Failed to update config %s", opts.ConfigFile)
   285  			continue
   286  		}
   287  	}
   288  }
   289  
   290  func stopServer(bgpServer *server.BgpServer, useSdNotify bool) {
   291  	logger.Info("stopping gobgpd server")
   292  
   293  	bgpServer.Stop()
   294  	if useSdNotify {
   295  		daemon.SdNotify(false, daemon.SdNotifyStopping)
   296  	}
   297  }