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 }