github.com/fafucoder/cilium@v1.6.11/cilium/cmd/monitor.go (about)

     1  // Copyright 2017-2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"encoding/gob"
    19  	"fmt"
    20  	"io"
    21  	"net"
    22  	"os"
    23  	"os/signal"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/cilium/cilium/api/v1/models"
    28  	"github.com/cilium/cilium/pkg/defaults"
    29  	"github.com/cilium/cilium/pkg/monitor"
    30  	"github.com/cilium/cilium/pkg/monitor/agent/listener"
    31  	"github.com/cilium/cilium/pkg/monitor/format"
    32  	"github.com/cilium/cilium/pkg/monitor/payload"
    33  
    34  	"github.com/spf13/cobra"
    35  	"github.com/spf13/viper"
    36  )
    37  
    38  const (
    39  	connTimeout = 12 * time.Second
    40  )
    41  
    42  // monitorCmd represents the monitor command
    43  var (
    44  	monitorCmd = &cobra.Command{
    45  		Use:   "monitor",
    46  		Short: "Display BPF program events",
    47  		Long: `The monitor displays notifications and events emitted by the BPF
    48  programs attached to endpoints and devices. This includes:
    49    * Dropped packet notifications
    50    * Captured packet traces
    51    * Debugging information`,
    52  		Run: func(cmd *cobra.Command, args []string) {
    53  			runMonitor(args)
    54  		},
    55  	}
    56  	printer    = format.NewMonitorFormatter(format.INFO)
    57  	socketPath = ""
    58  )
    59  
    60  func init() {
    61  	rootCmd.AddCommand(monitorCmd)
    62  	monitorCmd.Flags().BoolVar(&printer.Hex, "hex", false, "Do not dissect, print payload in HEX")
    63  	monitorCmd.Flags().VarP(&printer.EventTypes, "type", "t", fmt.Sprintf("Filter by event types %v", monitor.GetAllTypes()))
    64  	monitorCmd.Flags().Var(&printer.FromSource, "from", "Filter by source endpoint id")
    65  	monitorCmd.Flags().Var(&printer.ToDst, "to", "Filter by destination endpoint id")
    66  	monitorCmd.Flags().Var(&printer.Related, "related-to", "Filter by either source or destination endpoint id")
    67  	monitorCmd.Flags().BoolVarP(&printer.Verbose, "verbose", "v", false, "Enable verbose output")
    68  	monitorCmd.Flags().BoolVarP(&printer.JSONOutput, "json", "j", false, "Enable json output. Shadows -v flag")
    69  	monitorCmd.Flags().StringVar(&socketPath, "monitor-socket", "", "Configure monitor socket path")
    70  	viper.BindEnv("monitor-socket", "CILIUM_MONITOR_SOCK")
    71  	viper.BindPFlags(monitorCmd.Flags())
    72  }
    73  
    74  func setVerbosity() {
    75  	if printer.JSONOutput {
    76  		printer.Verbosity = format.JSON
    77  	} else if printer.Verbose {
    78  		printer.Verbosity = format.DEBUG
    79  	} else {
    80  		printer.Verbosity = format.INFO
    81  	}
    82  }
    83  
    84  func setupSigHandler() {
    85  	signalChan := make(chan os.Signal, 1)
    86  	signal.Notify(signalChan, os.Interrupt)
    87  	go func() {
    88  		for range signalChan {
    89  			fmt.Printf("\nReceived an interrupt, disconnecting from monitor...\n\n")
    90  			os.Exit(0)
    91  		}
    92  	}()
    93  }
    94  
    95  // openMonitorSock attempts to open a version specific monitor socket It
    96  // returns a connection, with a version, or an error.
    97  func openMonitorSock(path string) (conn net.Conn, version listener.Version, err error) {
    98  	errors := make([]string, 0)
    99  
   100  	// try the user-provided socket
   101  	if path != "" {
   102  		conn, err = net.Dial("unix", path)
   103  		if err == nil {
   104  			version = listener.Version1_2
   105  			if strings.HasSuffix(path, "monitor.sock") {
   106  				version = listener.Version1_0
   107  			}
   108  			return conn, version, nil
   109  		}
   110  		errors = append(errors, path+": "+err.Error())
   111  	}
   112  
   113  	// try the 1.2 socket
   114  	conn, err = net.Dial("unix", defaults.MonitorSockPath1_2)
   115  	if err == nil {
   116  		return conn, listener.Version1_2, nil
   117  	}
   118  	errors = append(errors, defaults.MonitorSockPath1_2+": "+err.Error())
   119  
   120  	// try the 1.1 socket
   121  	conn, err = net.Dial("unix", defaults.MonitorSockPath1_0)
   122  	if err == nil {
   123  		return conn, listener.Version1_0, nil
   124  	}
   125  	errors = append(errors, defaults.MonitorSockPath1_0+": "+err.Error())
   126  
   127  	return nil, listener.VersionUnsupported, fmt.Errorf("Cannot find or open a supported node-monitor socket. %s", strings.Join(errors, ","))
   128  }
   129  
   130  // consumeMonitorEvents handles and prints events on a monitor connection. It
   131  // calls getMonitorParsed to construct a monitor-version appropriate parser.
   132  // It closes conn on return, and returns on error, including io.EOF
   133  func consumeMonitorEvents(conn net.Conn, version listener.Version) error {
   134  	defer conn.Close()
   135  
   136  	getParsedPayload, err := getMonitorParser(conn, version)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	for {
   142  		pl, err := getParsedPayload()
   143  		if err != nil {
   144  			return err
   145  		}
   146  		if !printer.FormatEvent(pl) {
   147  			// earlier code used an else to handle this case, along with pl.Type ==
   148  			// payload.RecordLost above. It should be safe to call lostEvent to match
   149  			// the earlier behaviour, despite it not being wholly correct.
   150  			log.WithError(err).WithField("type", pl.Type).Warn("Unknown payload type")
   151  			format.LostEvent(pl.Lost, pl.CPU)
   152  		}
   153  	}
   154  }
   155  
   156  // eventParseFunc is a convenience function type used as a version-specific
   157  // parser of monitor events
   158  type eventParserFunc func() (*payload.Payload, error)
   159  
   160  // getMonitorParser constructs and returns an eventParserFunc. It is
   161  // appropriate for the monitor API version passed in.
   162  func getMonitorParser(conn net.Conn, version listener.Version) (parser eventParserFunc, err error) {
   163  	switch version {
   164  	case listener.Version1_0:
   165  		var (
   166  			meta payload.Meta
   167  			pl   payload.Payload
   168  		)
   169  		// This implements the older API. Always encode a Meta and Payload object,
   170  		// both with full gob type information
   171  		return func() (*payload.Payload, error) {
   172  			if err := payload.ReadMetaPayload(conn, &meta, &pl); err != nil {
   173  				return nil, err
   174  			}
   175  			return &pl, nil
   176  		}, nil
   177  
   178  	case listener.Version1_2:
   179  		var (
   180  			pl  payload.Payload
   181  			dec = gob.NewDecoder(conn)
   182  		)
   183  		// This implemenents the newer 1.2 API. Each listener maintains its own gob
   184  		// session, and type information is only ever sent once.
   185  		return func() (*payload.Payload, error) {
   186  			if err := pl.DecodeBinary(dec); err != nil {
   187  				return nil, err
   188  			}
   189  			return &pl, nil
   190  		}, nil
   191  
   192  	default:
   193  		return nil, fmt.Errorf("unsupported version %s", version)
   194  	}
   195  }
   196  
   197  func endpointsExist(endpoints format.Uint16Flags, existingEndpoints []*models.Endpoint) bool {
   198  
   199  	endpointsFound := format.Uint16Flags{}
   200  	for _, ep := range existingEndpoints {
   201  		if endpoints.Has(uint16(ep.ID)) {
   202  			endpointsFound = append(endpointsFound, uint16(ep.ID))
   203  		}
   204  	}
   205  
   206  	if len(endpointsFound) < len(endpoints) {
   207  		for _, endpoint := range endpoints {
   208  			if !endpointsFound.Has(endpoint) {
   209  				fmt.Fprintf(os.Stderr, "endpoint %d not found\n", endpoint)
   210  			}
   211  		}
   212  	}
   213  
   214  	if len(endpointsFound) == 0 {
   215  		return false
   216  	}
   217  
   218  	return true
   219  }
   220  
   221  func validateEndpointsFilters() {
   222  	if !(len(printer.FromSource) > 0) ||
   223  		!(len(printer.ToDst) > 0) ||
   224  		!(len(printer.Related) > 0) {
   225  		return
   226  	}
   227  
   228  	existingEndpoints, err := client.EndpointList()
   229  	if err != nil {
   230  		Fatalf("cannot get endpoint list: %s\n", err)
   231  	}
   232  
   233  	validFilter := false
   234  	if len(printer.FromSource) > 0 {
   235  		if endpointsExist(printer.FromSource, existingEndpoints) {
   236  			validFilter = true
   237  		}
   238  	}
   239  	if len(printer.ToDst) > 0 {
   240  		if endpointsExist(printer.ToDst, existingEndpoints) {
   241  			validFilter = true
   242  		}
   243  	}
   244  
   245  	if len(printer.Related) > 0 {
   246  		if endpointsExist(printer.Related, existingEndpoints) {
   247  			validFilter = true
   248  		}
   249  	}
   250  
   251  	// exit if all filters are not not found
   252  	if !validFilter {
   253  		os.Exit(1)
   254  	}
   255  }
   256  
   257  func runMonitor(args []string) {
   258  	if len(args) > 0 {
   259  		fmt.Println("Error: arguments not recognized")
   260  		os.Exit(1)
   261  	}
   262  
   263  	validateEndpointsFilters()
   264  	setVerbosity()
   265  	setupSigHandler()
   266  	if resp, err := client.Daemon.GetHealthz(nil); err == nil {
   267  		if nm := resp.Payload.NodeMonitor; nm != nil {
   268  			fmt.Printf("Listening for events on %d CPUs with %dx%d of shared memory\n",
   269  				nm.Cpus, nm.Npages, nm.Pagesize)
   270  		}
   271  	}
   272  	fmt.Printf("Press Ctrl-C to quit\n")
   273  
   274  	// On EOF, retry
   275  	// On other errors, exit
   276  	// always wait connTimeout when retrying
   277  	for ; ; time.Sleep(connTimeout) {
   278  		conn, version, err := openMonitorSock(viper.GetString("monitor-socket"))
   279  		if err != nil {
   280  			log.WithError(err).Error("Cannot open monitor socket")
   281  			return
   282  		}
   283  
   284  		err = consumeMonitorEvents(conn, version)
   285  		switch {
   286  		case err == nil:
   287  		// no-op
   288  
   289  		case err == io.EOF, err == io.ErrUnexpectedEOF:
   290  			log.WithError(err).Warn("connection closed")
   291  			continue
   292  
   293  		default:
   294  			log.WithError(err).Fatal("decoding error")
   295  		}
   296  	}
   297  }