github.com/fafucoder/cilium@v1.6.11/cilium/cmd/helpers.go (about) 1 // Copyright 2016-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 "bytes" 19 "encoding/json" 20 "fmt" 21 "os" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "text/tabwriter" 27 28 "github.com/cilium/cilium/pkg/bpf" 29 "github.com/cilium/cilium/pkg/color" 30 endpointid "github.com/cilium/cilium/pkg/endpoint/id" 31 "github.com/cilium/cilium/pkg/identity" 32 "github.com/cilium/cilium/pkg/maps/policymap" 33 "github.com/cilium/cilium/pkg/option" 34 "github.com/cilium/cilium/pkg/policy/trafficdirection" 35 "github.com/cilium/cilium/pkg/u8proto" 36 37 "github.com/spf13/cobra" 38 ) 39 40 // Fatalf prints the Printf formatted message to stderr and exits the program 41 // Note: os.Exit(1) is not recoverable 42 func Fatalf(msg string, args ...interface{}) { 43 fmt.Fprintf(os.Stderr, "Error: %s\n", fmt.Sprintf(msg, args...)) 44 os.Exit(1) 45 } 46 47 // Usagef prints the Printf formatted message to stderr, prints usage help and 48 // exits the program 49 // Note: os.Exit(1) is not recoverable 50 func Usagef(cmd *cobra.Command, msg string, args ...interface{}) { 51 txt := fmt.Sprintf(msg, args...) 52 fmt.Fprintf(os.Stderr, "Error: %s\n\n", txt) 53 cmd.Help() 54 os.Exit(1) 55 } 56 57 func requireEndpointID(cmd *cobra.Command, args []string) { 58 if len(args) < 1 { 59 Usagef(cmd, "Missing endpoint id argument") 60 } 61 62 if id := identity.GetReservedID(args[0]); id == identity.IdentityUnknown { 63 _, _, err := endpointid.Parse(args[0]) 64 65 if err != nil { 66 Fatalf("Cannot parse endpoint id \"%s\": %s", args[0], err) 67 } 68 } 69 } 70 71 func requireEndpointIDorGlobal(cmd *cobra.Command, args []string) { 72 if len(args) < 1 { 73 Usagef(cmd, "Missing endpoint id or 'global' argument") 74 } 75 76 if args[0] != "global" { 77 requireEndpointID(cmd, args) 78 } 79 } 80 81 func requirePath(cmd *cobra.Command, args []string) { 82 if len(args) < 1 { 83 Usagef(cmd, "Missing path argument") 84 } 85 86 if args[0] == "" { 87 Usagef(cmd, "Empty path argument") 88 } 89 } 90 91 func requireServiceID(cmd *cobra.Command, args []string) { 92 if len(args) < 1 { 93 Usagef(cmd, "Missing service id argument") 94 } 95 96 if args[0] == "" { 97 Usagef(cmd, "Empty service id argument") 98 } 99 } 100 101 // TablePrinter prints the map[string][]string, which is an usual representation 102 // of dumped BPF map, using tabwriter. 103 func TablePrinter(firstTitle, secondTitle string, data map[string][]string) { 104 w := tabwriter.NewWriter(os.Stdout, 5, 0, 3, ' ', 0) 105 106 fmt.Fprintf(w, "%s\t%s\t\n", firstTitle, secondTitle) 107 108 for key, value := range data { 109 for k, v := range value { 110 if k == 0 { 111 fmt.Fprintf(w, "%s\t%s\t\n", key, v) 112 } else { 113 fmt.Fprintf(w, "%s\t%s\t\n", "", v) 114 } 115 } 116 } 117 118 w.Flush() 119 } 120 121 // Search 'result' for strings with escaped JSON inside, and expand the JSON. 122 func expandNestedJSON(result bytes.Buffer) (bytes.Buffer, error) { 123 reStringWithJSON := regexp.MustCompile(`"[^"\\{]*{.*[^\\]"`) 124 reJSON := regexp.MustCompile(`{(.|\n)*}`) 125 for { 126 var ( 127 loc []int 128 indent string 129 ) 130 131 // Search for nested JSON; if we don't find any, then break. 132 resBytes := result.Bytes() 133 if loc = reStringWithJSON.FindIndex(resBytes); loc == nil { 134 break 135 } 136 137 // Determine the current indentation 138 for i := 0; i < loc[0]-1; i++ { 139 idx := loc[0] - i - 1 140 if resBytes[idx] != ' ' { 141 break 142 } 143 indent = fmt.Sprintf("\t%s\t", indent) 144 } 145 146 stringStart := loc[0] 147 stringEnd := loc[1] 148 149 // Unquote the string with the nested json. 150 quotedBytes := resBytes[stringStart:stringEnd] 151 unquoted, err := strconv.Unquote(string(quotedBytes)) 152 if err != nil { 153 return bytes.Buffer{}, fmt.Errorf("Failed to Unquote string: %s\n%s", err.Error(), string(quotedBytes)) 154 } 155 156 // Find the JSON within the unquoted string. 157 nestedStart := 0 158 nestedEnd := 0 159 // Find the left-most match 160 if loc = reJSON.FindStringIndex(unquoted); loc != nil { 161 nestedStart = loc[0] 162 nestedEnd = loc[1] 163 } 164 165 // Decode the nested JSON 166 decoded := "" 167 if nestedEnd != 0 { 168 m := make(map[string]interface{}) 169 nested := bytes.NewBufferString(unquoted[nestedStart:nestedEnd]) 170 if err := json.NewDecoder(nested).Decode(&m); err != nil { 171 return bytes.Buffer{}, fmt.Errorf("Failed to decode nested JSON: %s (\n%s\n)", err.Error(), unquoted[nestedStart:nestedEnd]) 172 } 173 decodedBytes, err := json.MarshalIndent(m, indent, " ") 174 if err != nil { 175 return bytes.Buffer{}, fmt.Errorf("Cannot marshal nested JSON: %s", err.Error()) 176 } 177 decoded = string(decodedBytes) 178 } 179 180 // Serialize 181 nextResult := bytes.Buffer{} 182 nextResult.Write(resBytes[0:stringStart]) 183 nextResult.WriteString(string(unquoted[:nestedStart])) 184 nextResult.WriteString(string(decoded)) 185 nextResult.WriteString(string(unquoted[nestedEnd:])) 186 nextResult.Write(resBytes[stringEnd:]) 187 result = nextResult 188 } 189 190 return result, nil 191 } 192 193 // PolicyUpdateArgs is the parsed representation of a 194 // bpf policy {add,delete} command. 195 type PolicyUpdateArgs struct { 196 // path is the basename of the BPF map for this policy update. 197 path string 198 199 // trafficDirection represents the traffic direction provided 200 // as an argument e.g. `ingress` 201 trafficDirection trafficdirection.TrafficDirection 202 203 // label represents the identity of the label provided as argument. 204 label uint32 205 206 // port represents the port associated with the command, if specified. 207 port uint16 208 209 // protocols represents the set of protocols associated with the 210 // command, if specified. 211 protocols []uint8 212 } 213 214 // parseTrafficString converts the provided string to its corresponding 215 // TrafficDirection. If the string does not correspond to a valid TrafficDirection 216 // type, returns Invalid and a corresponding error. 217 func parseTrafficString(td string) (trafficdirection.TrafficDirection, error) { 218 lowered := strings.ToLower(td) 219 220 switch lowered { 221 case "ingress": 222 return trafficdirection.Ingress, nil 223 case "egress": 224 return trafficdirection.Egress, nil 225 default: 226 return trafficdirection.Invalid, fmt.Errorf("invalid direction %q provided", td) 227 } 228 229 } 230 231 // parsePolicyUpdateArgs parses the arguments to a bpf policy {add,delete} 232 // command, provided as a list containing the endpoint ID, traffic direction, 233 // identity and optionally, a list of ports. 234 // Returns a parsed representation of the command arguments. 235 func parsePolicyUpdateArgs(cmd *cobra.Command, args []string) *PolicyUpdateArgs { 236 if len(args) < 3 { 237 Usagef(cmd, "<endpoint id>, <traffic-direction>, and <identity> required") 238 } 239 240 pa, err := parsePolicyUpdateArgsHelper(args) 241 if err != nil { 242 Fatalf("%s", err) 243 } 244 245 return pa 246 } 247 248 func endpointToPolicyMapPath(endpointID string) (string, error) { 249 if endpointID == "" { 250 return "", fmt.Errorf("Need ID or label") 251 } 252 253 var mapName string 254 id, err := strconv.Atoi(endpointID) 255 if err == nil { 256 mapName = bpf.LocalMapName(policymap.MapName, uint16(id)) 257 } else if numericIdentity := identity.GetReservedID(endpointID); numericIdentity != identity.IdentityUnknown { 258 mapSuffix := "reserved_" + strconv.FormatUint(uint64(numericIdentity), 10) 259 mapName = fmt.Sprintf("%s%s", policymap.MapName, mapSuffix) 260 } else { 261 return "", err 262 } 263 264 return bpf.MapPath(mapName), nil 265 } 266 267 func parsePolicyUpdateArgsHelper(args []string) (*PolicyUpdateArgs, error) { 268 trafficDirection := args[1] 269 parsedTd, err := parseTrafficString(trafficDirection) 270 if err != nil { 271 return nil, fmt.Errorf("Failed to convert %s to a valid traffic direction: %s", args[1], err) 272 } 273 274 mapName, err := endpointToPolicyMapPath(args[0]) 275 if err != nil { 276 return nil, fmt.Errorf("Failed to parse endpointID %q", args[0]) 277 } 278 279 peerLbl, err := strconv.ParseUint(args[2], 10, 32) 280 if err != nil { 281 return nil, fmt.Errorf("Failed to convert %s", args[2]) 282 } 283 label := uint32(peerLbl) 284 285 port := uint16(0) 286 protos := []uint8{} 287 if len(args) > 3 { 288 pp, err := parseL4PortsSlice([]string{args[3]}) 289 if err != nil { 290 return nil, fmt.Errorf("Failed to parse L4: %s", err) 291 } 292 port = pp[0].Port 293 if port != 0 { 294 proto, _ := u8proto.ParseProtocol(pp[0].Protocol) 295 if proto == 0 { 296 for _, proto := range u8proto.ProtoIDs { 297 protos = append(protos, uint8(proto)) 298 } 299 } else { 300 protos = append(protos, uint8(proto)) 301 } 302 } 303 } 304 if len(protos) == 0 { 305 protos = append(protos, 0) 306 } 307 308 pa := &PolicyUpdateArgs{ 309 path: mapName, 310 trafficDirection: parsedTd, 311 label: label, 312 port: port, 313 protocols: protos, 314 } 315 316 return pa, nil 317 } 318 319 // updatePolicyKey updates an entry in the PolicyMap for the provided 320 // PolicyUpdateArgs argument. 321 // Adds the entry to the PolicyMap if add is true, otherwise the entry is 322 // deleted. 323 func updatePolicyKey(pa *PolicyUpdateArgs, add bool) { 324 // The map needs not to be transparently initialized here even if 325 // it's not present for some reason. Triggering map recreation with 326 // OpenOrCreate when some map attribute had changed would be much worse. 327 policyMap, err := policymap.Open(pa.path) 328 if err != nil { 329 Fatalf("Cannot open policymap %q : %s", pa.path, err) 330 } 331 332 for _, proto := range pa.protocols { 333 u8p := u8proto.U8proto(proto) 334 entry := fmt.Sprintf("%d %d/%s", pa.label, pa.port, u8p.String()) 335 if add { 336 var proxyPort uint16 337 if err := policyMap.Allow(pa.label, pa.port, u8p, pa.trafficDirection, proxyPort); err != nil { 338 Fatalf("Cannot add policy key '%s': %s\n", entry, err) 339 } 340 } else { 341 if err := policyMap.Delete(pa.label, pa.port, u8p, pa.trafficDirection); err != nil { 342 Fatalf("Cannot delete policy key '%s': %s\n", entry, err) 343 } 344 } 345 } 346 } 347 348 // dumpConfig pretty prints boolean options 349 func dumpConfig(Opts map[string]string) { 350 opts := []string{} 351 for k := range Opts { 352 opts = append(opts, k) 353 } 354 sort.Strings(opts) 355 356 for _, k := range opts { 357 // XXX: Reuse the format function from *option.Library 358 value = Opts[k] 359 if enabled, err := option.NormalizeBool(value); err != nil { 360 // If it cannot be parsed as a bool, just format the value. 361 fmt.Printf("%-24s %s\n", k, color.Green(value)) 362 } else if enabled == option.OptionDisabled { 363 fmt.Printf("%-24s %s\n", k, color.Red("Disabled")) 364 } else { 365 fmt.Printf("%-24s %s\n", k, color.Green("Enabled")) 366 } 367 } 368 }