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  }