github.com/cilium/cilium@v1.16.2/pkg/hubble/metrics/flows-to-world/handler.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package flows_to_world
     5  
     6  import (
     7  	"context"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	flowpb "github.com/cilium/cilium/api/v1/flow"
    14  	v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
    15  	"github.com/cilium/cilium/pkg/hubble/metrics/api"
    16  	pkglabels "github.com/cilium/cilium/pkg/labels"
    17  )
    18  
    19  const (
    20  	reservedWorldLbl     = pkglabels.LabelSourceReserved + ":" + pkglabels.IDNameWorld
    21  	reservedWorldIPv4Lbl = pkglabels.LabelSourceReserved + ":" + pkglabels.IDNameWorldIPv4
    22  	reservedWorldIPv6Lbl = pkglabels.LabelSourceReserved + ":" + pkglabels.IDNameWorldIPv6
    23  )
    24  
    25  type flowsToWorldHandler struct {
    26  	flowsToWorld *prometheus.CounterVec
    27  	context      *api.ContextOptions
    28  	anyDrop      bool
    29  	port         bool
    30  	synOnly      bool
    31  }
    32  
    33  func (h *flowsToWorldHandler) Init(registry *prometheus.Registry, options api.Options) error {
    34  	c, err := api.ParseContextOptions(options)
    35  	if err != nil {
    36  		return err
    37  	}
    38  	h.context = c
    39  	for key := range options {
    40  		switch strings.ToLower(key) {
    41  		case "any-drop":
    42  			h.anyDrop = true
    43  		case "port":
    44  			h.port = true
    45  		case "syn-only":
    46  			h.synOnly = true
    47  		}
    48  	}
    49  	labels := []string{"protocol", "verdict"}
    50  	if h.port {
    51  		labels = append(labels, "port")
    52  	}
    53  	labels = append(labels, h.context.GetLabelNames()...)
    54  
    55  	h.flowsToWorld = prometheus.NewCounterVec(prometheus.CounterOpts{
    56  		Namespace: api.DefaultPrometheusNamespace,
    57  		Name:      "flows_to_world_total",
    58  		Help:      "Total number of flows to reserved:world",
    59  	}, labels)
    60  	registry.MustRegister(h.flowsToWorld)
    61  	return nil
    62  }
    63  
    64  func (h *flowsToWorldHandler) Status() string {
    65  	var status []string
    66  	if h.anyDrop {
    67  		status = append(status, "any-drop")
    68  	}
    69  	if h.port {
    70  		status = append(status, "port")
    71  	}
    72  	if h.synOnly {
    73  		status = append(status, "syn-only")
    74  	}
    75  	return strings.Join(append(status, h.context.Status()), ",")
    76  }
    77  
    78  func (h *flowsToWorldHandler) Context() *api.ContextOptions {
    79  	return h.context
    80  }
    81  
    82  func (h *flowsToWorldHandler) ListMetricVec() []*prometheus.MetricVec {
    83  	return []*prometheus.MetricVec{h.flowsToWorld.MetricVec}
    84  }
    85  
    86  func (h *flowsToWorldHandler) isReservedWorld(endpoint *flowpb.Endpoint) bool {
    87  	for _, label := range endpoint.Labels {
    88  		switch label {
    89  		case reservedWorldLbl, reservedWorldIPv4Lbl, reservedWorldIPv6Lbl:
    90  			return true
    91  		}
    92  	}
    93  	return false
    94  }
    95  
    96  func (h *flowsToWorldHandler) ProcessFlow(_ context.Context, flow *flowpb.Flow) error {
    97  	l4 := flow.GetL4()
    98  	if flow.GetDestination() == nil ||
    99  		!h.isReservedWorld(flow.GetDestination()) ||
   100  		flow.GetEventType() == nil ||
   101  		l4 == nil {
   102  		return nil
   103  	}
   104  	// if "any-drop" option is not set, non-policy drops are ignored.
   105  	if flow.GetVerdict() == flowpb.Verdict_DROPPED && !h.anyDrop && flow.GetDropReasonDesc() != flowpb.DropReason_POLICY_DENIED {
   106  		return nil
   107  	}
   108  	// if this is potentially a forwarded reply packet, ignore it to avoid collecting statistics about ephemeral ports
   109  	isReply := flow.GetIsReply() != nil && flow.GetIsReply().GetValue()
   110  	if flow.GetVerdict() != flowpb.Verdict_DROPPED && isReply {
   111  		return nil
   112  	}
   113  
   114  	// if "syn-only" option is set, only count non-reply SYN packets for TCP.
   115  	if h.synOnly && (!l4.GetTCP().GetFlags().GetSYN() || isReply) {
   116  		return nil
   117  	}
   118  
   119  	labelValues, err := h.context.GetLabelValues(flow)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	labels := []string{v1.FlowProtocol(flow), flow.GetVerdict().String()}
   125  
   126  	// if "port" option is set, add port to the label.
   127  	if h.port {
   128  		port := ""
   129  		if tcp := l4.GetTCP(); tcp != nil {
   130  			port = strconv.Itoa(int(tcp.GetDestinationPort()))
   131  		} else if udp := l4.GetUDP(); udp != nil {
   132  			port = strconv.Itoa(int(udp.GetDestinationPort()))
   133  		} else if sctp := l4.GetSCTP(); sctp != nil {
   134  			port = strconv.Itoa(int(sctp.GetDestinationPort()))
   135  		}
   136  		labels = append(labels, port)
   137  	}
   138  	labels = append(labels, labelValues...)
   139  	h.flowsToWorld.WithLabelValues(labels...).Inc()
   140  	return nil
   141  }