github.com/cilium/cilium@v1.16.2/pkg/hubble/metrics/flows-to-world/handler_test.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 "strings" 9 "testing" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/client_golang/prometheus/testutil" 13 "github.com/stretchr/testify/assert" 14 "google.golang.org/protobuf/types/known/wrapperspb" 15 16 flowpb "github.com/cilium/cilium/api/v1/flow" 17 "github.com/cilium/cilium/pkg/hubble/metrics/api" 18 monitorAPI "github.com/cilium/cilium/pkg/monitor/api" 19 ) 20 21 func TestFlowsToWorldHandler_MatchingFlow(t *testing.T) { 22 registry := prometheus.NewRegistry() 23 opts := api.Options{"sourceContext": "namespace", "destinationContext": "dns|ip"} 24 h := &flowsToWorldHandler{} 25 assert.NoError(t, h.Init(registry, opts)) 26 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, strings.NewReader(""))) 27 flow := flowpb.Flow{ 28 Verdict: flowpb.Verdict_DROPPED, 29 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 30 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeDrop}, 31 L4: &flowpb.Layer4{ 32 Protocol: &flowpb.Layer4_TCP{ 33 TCP: &flowpb.TCP{DestinationPort: 80}, 34 }, 35 }, 36 Source: &flowpb.Endpoint{Namespace: "src-a"}, 37 Destination: &flowpb.Endpoint{ 38 Labels: []string{"reserved:world"}, 39 }, 40 DestinationNames: []string{"cilium.io"}, 41 } 42 43 h.ProcessFlow(context.Background(), &flow) 44 flow.L4 = &flowpb.Layer4{ 45 Protocol: &flowpb.Layer4_UDP{UDP: &flowpb.UDP{DestinationPort: 53}}, 46 } 47 h.ProcessFlow(context.Background(), &flow) 48 flow.L4 = &flowpb.Layer4{ 49 Protocol: &flowpb.Layer4_ICMPv4{ICMPv4: &flowpb.ICMPv4{}}, 50 } 51 h.ProcessFlow(context.Background(), &flow) 52 flow.L4 = &flowpb.Layer4{ 53 Protocol: &flowpb.Layer4_ICMPv6{ICMPv6: &flowpb.ICMPv6{}}, 54 } 55 h.ProcessFlow(context.Background(), &flow) 56 expected := strings.NewReader(`# HELP hubble_flows_to_world_total Total number of flows to reserved:world 57 # TYPE hubble_flows_to_world_total counter 58 hubble_flows_to_world_total{destination="cilium.io",protocol="ICMPv4",source="src-a",verdict="DROPPED"} 1 59 hubble_flows_to_world_total{destination="cilium.io",protocol="ICMPv6",source="src-a",verdict="DROPPED"} 1 60 hubble_flows_to_world_total{destination="cilium.io",protocol="UDP",source="src-a",verdict="DROPPED"} 1 61 hubble_flows_to_world_total{destination="cilium.io",protocol="TCP",source="src-a",verdict="DROPPED"} 1 62 `) 63 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, expected)) 64 } 65 66 func TestFlowsToWorldHandler_NonMatchingFlows(t *testing.T) { 67 registry := prometheus.NewRegistry() 68 opts := api.Options{"sourceContext": "namespace", "destinationContext": "dns|ip"} 69 h := &flowsToWorldHandler{} 70 assert.NoError(t, h.Init(registry, opts)) 71 72 // destination is missing. 73 h.ProcessFlow(context.Background(), &flowpb.Flow{ 74 Verdict: flowpb.Verdict_FORWARDED, 75 Source: &flowpb.Endpoint{Namespace: "src-a"}, 76 }) 77 // destination is not reserved:world 78 h.ProcessFlow(context.Background(), &flowpb.Flow{ 79 Verdict: flowpb.Verdict_FORWARDED, 80 Source: &flowpb.Endpoint{Namespace: "src-a"}, 81 Destination: &flowpb.Endpoint{ 82 Labels: []string{"reserved:host"}, 83 }, 84 }) 85 // L4 information is missing. 86 h.ProcessFlow(context.Background(), &flowpb.Flow{ 87 Verdict: flowpb.Verdict_FORWARDED, 88 Source: &flowpb.Endpoint{Namespace: "src-a"}, 89 Destination: &flowpb.Endpoint{ 90 Labels: []string{"reserved:world"}, 91 }, 92 }) 93 // EventType is missing. 94 h.ProcessFlow(context.Background(), &flowpb.Flow{ 95 Verdict: flowpb.Verdict_FORWARDED, 96 Source: &flowpb.Endpoint{Namespace: "src-a"}, 97 Destination: &flowpb.Endpoint{ 98 Labels: []string{"reserved:world"}, 99 }, 100 L4: &flowpb.Layer4{ 101 Protocol: &flowpb.Layer4_TCP{ 102 TCP: &flowpb.TCP{DestinationPort: 80}, 103 }, 104 }, 105 }) 106 // Drop reason is not "Policy denied". 107 h.ProcessFlow(context.Background(), &flowpb.Flow{ 108 Verdict: flowpb.Verdict_DROPPED, 109 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeDrop}, 110 DropReasonDesc: flowpb.DropReason_STALE_OR_UNROUTABLE_IP, 111 L4: &flowpb.Layer4{ 112 Protocol: &flowpb.Layer4_TCP{ 113 TCP: &flowpb.TCP{DestinationPort: 80}, 114 }, 115 }, 116 Source: &flowpb.Endpoint{Namespace: "src-a"}, 117 Destination: &flowpb.Endpoint{ 118 Labels: []string{"reserved:world"}, 119 }, 120 DestinationNames: []string{"cilium.io"}, 121 }) 122 // Flow is a reply. 123 h.ProcessFlow(context.Background(), &flowpb.Flow{ 124 Verdict: flowpb.Verdict_FORWARDED, 125 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeTrace}, 126 L4: &flowpb.Layer4{ 127 Protocol: &flowpb.Layer4_TCP{ 128 TCP: &flowpb.TCP{DestinationPort: 80}, 129 }, 130 }, 131 Source: &flowpb.Endpoint{Namespace: "src-a"}, 132 Destination: &flowpb.Endpoint{ 133 Labels: []string{"reserved:world"}, 134 }, 135 DestinationNames: []string{"cilium.io"}, 136 IsReply: wrapperspb.Bool(true), 137 }) 138 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, strings.NewReader(""))) 139 } 140 141 func TestFlowsToWorldHandler_AnyDrop(t *testing.T) { 142 registry := prometheus.NewRegistry() 143 opts := api.Options{"sourceContext": "namespace", "destinationContext": "dns|ip", "any-drop": ""} 144 h := &flowsToWorldHandler{} 145 assert.NoError(t, h.Init(registry, opts)) 146 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, strings.NewReader(""))) 147 flow := flowpb.Flow{ 148 Verdict: flowpb.Verdict_DROPPED, 149 DropReasonDesc: flowpb.DropReason_STALE_OR_UNROUTABLE_IP, 150 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeDrop}, 151 L4: &flowpb.Layer4{ 152 Protocol: &flowpb.Layer4_TCP{ 153 TCP: &flowpb.TCP{DestinationPort: 80}, 154 }, 155 }, 156 Source: &flowpb.Endpoint{Namespace: "src-a"}, 157 Destination: &flowpb.Endpoint{ 158 Labels: []string{"reserved:world"}, 159 }, 160 DestinationNames: []string{"cilium.io"}, 161 } 162 h.ProcessFlow(context.Background(), &flow) 163 expected := strings.NewReader(`# HELP hubble_flows_to_world_total Total number of flows to reserved:world 164 # TYPE hubble_flows_to_world_total counter 165 hubble_flows_to_world_total{destination="cilium.io",protocol="TCP",source="src-a",verdict="DROPPED"} 1 166 `) 167 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, expected)) 168 } 169 170 func TestFlowsToWorldHandler_IncludePort(t *testing.T) { 171 registry := prometheus.NewRegistry() 172 opts := api.Options{"sourceContext": "namespace", "destinationContext": "dns|ip", "port": ""} 173 h := &flowsToWorldHandler{} 174 assert.NoError(t, h.Init(registry, opts)) 175 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, strings.NewReader(""))) 176 flow := flowpb.Flow{ 177 Verdict: flowpb.Verdict_FORWARDED, 178 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeTrace}, 179 L4: &flowpb.Layer4{ 180 Protocol: &flowpb.Layer4_TCP{ 181 TCP: &flowpb.TCP{DestinationPort: 80}, 182 }, 183 }, 184 Source: &flowpb.Endpoint{Namespace: "src-a"}, 185 Destination: &flowpb.Endpoint{ 186 Labels: []string{"reserved:world"}, 187 }, 188 DestinationNames: []string{"cilium.io"}, 189 IsReply: wrapperspb.Bool(false), 190 } 191 h.ProcessFlow(context.Background(), &flow) 192 flow.L4 = &flowpb.Layer4{ 193 Protocol: &flowpb.Layer4_UDP{ 194 UDP: &flowpb.UDP{DestinationPort: 53}, 195 }, 196 } 197 h.ProcessFlow(context.Background(), &flow) 198 flow.L4 = &flowpb.Layer4{ 199 Protocol: &flowpb.Layer4_SCTP{ 200 SCTP: &flowpb.SCTP{DestinationPort: 2905}, 201 }, 202 } 203 h.ProcessFlow(context.Background(), &flow) 204 expected := strings.NewReader(`# HELP hubble_flows_to_world_total Total number of flows to reserved:world 205 # TYPE hubble_flows_to_world_total counter 206 hubble_flows_to_world_total{destination="cilium.io",port="2905",protocol="SCTP",source="src-a",verdict="FORWARDED"} 1 207 hubble_flows_to_world_total{destination="cilium.io",port="80",protocol="TCP",source="src-a",verdict="FORWARDED"} 1 208 hubble_flows_to_world_total{destination="cilium.io",port="53",protocol="UDP",source="src-a",verdict="FORWARDED"} 1 209 `) 210 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, expected)) 211 } 212 213 func TestFlowsToWorldHandler_SynOnly(t *testing.T) { 214 registry := prometheus.NewRegistry() 215 opts := api.Options{"sourceContext": "namespace", "destinationContext": "dns|ip", "syn-only": ""} 216 h := &flowsToWorldHandler{} 217 assert.NoError(t, h.Init(registry, opts)) 218 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, strings.NewReader(""))) 219 flow := flowpb.Flow{ 220 Verdict: flowpb.Verdict_DROPPED, 221 DropReasonDesc: flowpb.DropReason_POLICY_DENIED, 222 EventType: &flowpb.CiliumEventType{Type: monitorAPI.MessageTypeDrop}, 223 L4: &flowpb.Layer4{ 224 Protocol: &flowpb.Layer4_TCP{ 225 TCP: &flowpb.TCP{DestinationPort: 80, Flags: &flowpb.TCPFlags{SYN: true}}, 226 }, 227 }, 228 Source: &flowpb.Endpoint{Namespace: "src-a"}, 229 Destination: &flowpb.Endpoint{ 230 Labels: []string{"reserved:world"}, 231 }, 232 DestinationNames: []string{"cilium.io"}, 233 IsReply: wrapperspb.Bool(false), 234 } 235 h.ProcessFlow(context.Background(), &flow) 236 237 // flows without is_reply field should be counted. 238 flow.IsReply = nil 239 h.ProcessFlow(context.Background(), &flow) 240 241 // reply flows should not be counted 242 flow.IsReply = wrapperspb.Bool(true) 243 h.ProcessFlow(context.Background(), &flow) 244 245 // Non-SYN should not be counted 246 flow.IsReply = wrapperspb.Bool(false) 247 flow.L4.GetTCP().Flags = &flowpb.TCPFlags{ACK: true} 248 h.ProcessFlow(context.Background(), &flow) 249 250 expected := strings.NewReader(`# HELP hubble_flows_to_world_total Total number of flows to reserved:world 251 # TYPE hubble_flows_to_world_total counter 252 hubble_flows_to_world_total{destination="cilium.io",protocol="TCP",source="src-a",verdict="DROPPED"} 2 253 `) 254 assert.NoError(t, testutil.CollectAndCompare(h.flowsToWorld, expected)) 255 } 256 257 func Test_flowsToWorldHandler_Status(t *testing.T) { 258 h := &flowsToWorldHandler{ 259 context: &api.ContextOptions{ 260 Destination: api.ContextIdentifierList{api.ContextNamespace}, 261 Source: api.ContextIdentifierList{api.ContextReservedIdentity}, 262 }, 263 anyDrop: true, 264 port: true, 265 synOnly: true, 266 } 267 assert.Equal(t, "any-drop,port,syn-only,destination=namespace,source=reserved-identity", h.Status()) 268 h.anyDrop = false 269 h.port = false 270 assert.Equal(t, "syn-only,destination=namespace,source=reserved-identity", h.Status()) 271 }