github.com/cilium/cilium@v1.16.2/pkg/hubble/parser/sock/parser_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Hubble
     3  
     4  package sock
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  	"net"
    12  	"net/netip"
    13  	"testing"
    14  
    15  	"github.com/sirupsen/logrus"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	flowpb "github.com/cilium/cilium/api/v1/flow"
    20  	"github.com/cilium/cilium/pkg/byteorder"
    21  	cgroupManager "github.com/cilium/cilium/pkg/cgroups/manager"
    22  	parserErrors "github.com/cilium/cilium/pkg/hubble/parser/errors"
    23  	"github.com/cilium/cilium/pkg/hubble/parser/getters"
    24  	"github.com/cilium/cilium/pkg/hubble/testutils"
    25  	"github.com/cilium/cilium/pkg/identity"
    26  	"github.com/cilium/cilium/pkg/ipcache"
    27  	"github.com/cilium/cilium/pkg/labels"
    28  	"github.com/cilium/cilium/pkg/monitor"
    29  	monitorAPI "github.com/cilium/cilium/pkg/monitor/api"
    30  	"github.com/cilium/cilium/pkg/types"
    31  )
    32  
    33  func mustParseIP(s string) (res types.IPv6) {
    34  	ip := net.ParseIP(s)
    35  	if ip == nil {
    36  		panic(fmt.Sprintf("failed to parse ip %q", s))
    37  	}
    38  	if v4 := ip.To4(); v4 != nil {
    39  		copy(res[:4], v4)
    40  	} else {
    41  		copy(res[:], ip)
    42  	}
    43  	return res
    44  }
    45  
    46  func TestDecodeSockEvent(t *testing.T) {
    47  	const (
    48  		xwingIPv4                 = "192.168.10.10"
    49  		xwingIPv6                 = "f00d::a10:0:0:10"
    50  		xwingCgroupId             = 101010
    51  		xwingIdentity             = 1234
    52  		xwingEndpoint             = 110
    53  		xwingPodName              = "xwing"
    54  		xwingPodNamespace         = "default"
    55  		deathstarIPv4             = "192.168.20.20"
    56  		deathstarIPv6             = "f00d::20:20"
    57  		deathstarServiceV4        = "10.10.20.20"
    58  		deathstarServiceV6        = "f00c::20:20"
    59  		deathstarIdentity         = 5678
    60  		deathstarEndpoint         = 220
    61  		deathstarServicePort      = 8080
    62  		deathstarTargetPort       = 80
    63  		deathstarPodName          = "deathstar-1"
    64  		deathstarPodNamespace     = "default"
    65  		deathstarServiceName      = "deathstar"
    66  		deathstarServiceNamespace = "default"
    67  		deathstarServiceDomain    = "deathstar.default.svc.cluster.local"
    68  		deathstarAltIPv4          = "192.168.20.21"
    69  		deathstarAltIPv6          = "f00d::20:21"
    70  		deathstarAltPodName       = "deathstar-2"
    71  		deathstarAltPodNamespace  = "default"
    72  	)
    73  	var (
    74  		xwingLabels     = []string{"k8s:org=alliance"}
    75  		deathstarLabels = []string{"k8s:org=empire"}
    76  	)
    77  
    78  	endpointGetter := &testutils.FakeEndpointGetter{
    79  		OnGetEndpointInfo: func(ip netip.Addr) (endpoint getters.EndpointInfo, ok bool) {
    80  			switch ip.String() {
    81  			case xwingIPv4, xwingIPv6:
    82  				return &testutils.FakeEndpointInfo{
    83  					ID:           xwingEndpoint,
    84  					Identity:     xwingIdentity,
    85  					IPv4:         net.ParseIP(xwingIPv4),
    86  					IPv6:         net.ParseIP(xwingIPv6),
    87  					Labels:       xwingLabels,
    88  					PodName:      xwingPodName,
    89  					PodNamespace: xwingPodNamespace,
    90  				}, true
    91  			case deathstarIPv4, deathstarIPv6:
    92  				return &testutils.FakeEndpointInfo{
    93  					ID:           deathstarEndpoint,
    94  					Identity:     deathstarIdentity,
    95  					IPv4:         net.ParseIP(deathstarIPv4),
    96  					IPv6:         net.ParseIP(deathstarIPv6),
    97  					Labels:       deathstarLabels,
    98  					PodName:      deathstarPodName,
    99  					PodNamespace: deathstarPodNamespace,
   100  				}, true
   101  			}
   102  			return nil, false
   103  		},
   104  	}
   105  	identityGetter := &testutils.FakeIdentityGetter{
   106  		OnGetIdentity: func(securityIdentity uint32) (*identity.Identity, error) {
   107  			switch securityIdentity {
   108  			case xwingIdentity:
   109  				return &identity.Identity{
   110  					ID:     xwingIdentity,
   111  					Labels: labels.NewLabelsFromModel(xwingLabels),
   112  				}, nil
   113  			case deathstarIdentity:
   114  				return &identity.Identity{
   115  					ID:     deathstarIdentity,
   116  					Labels: labels.NewLabelsFromModel(deathstarLabels),
   117  				}, nil
   118  			}
   119  			return nil, errors.New("identity not found")
   120  		},
   121  	}
   122  	dnsGetter := &testutils.FakeFQDNCache{
   123  		OnGetNamesOf: func(epID uint32, ip netip.Addr) (names []string) {
   124  			switch epID {
   125  			case xwingEndpoint:
   126  				switch ip.String() {
   127  				case deathstarServiceV4, deathstarServiceV6:
   128  					return []string{deathstarServiceDomain}
   129  				}
   130  			}
   131  			return nil
   132  		},
   133  	}
   134  	ipGetter := &testutils.FakeIPGetter{
   135  		OnGetK8sMetadata: func(ip netip.Addr) *ipcache.K8sMetadata {
   136  			switch ip.String() {
   137  			case xwingIPv4, xwingIPv6:
   138  				return &ipcache.K8sMetadata{
   139  					PodName:   xwingPodName,
   140  					Namespace: xwingPodNamespace,
   141  				}
   142  			case deathstarIPv4, deathstarIPv6:
   143  				return &ipcache.K8sMetadata{
   144  					PodName:   deathstarPodName,
   145  					Namespace: deathstarPodNamespace,
   146  				}
   147  			case deathstarAltIPv4, deathstarAltIPv6:
   148  				return &ipcache.K8sMetadata{
   149  					PodName:   deathstarAltPodName,
   150  					Namespace: deathstarAltPodNamespace,
   151  				}
   152  			}
   153  			return nil
   154  		},
   155  		OnLookupSecIDByIP: func(ip netip.Addr) (ipcache.Identity, bool) {
   156  			switch ip.String() {
   157  			case xwingIPv4, xwingIPv6:
   158  				return ipcache.Identity{
   159  					ID: xwingIdentity,
   160  				}, true
   161  			case deathstarIPv4, deathstarIPv6, deathstarAltIPv4, deathstarAltIPv6:
   162  				return ipcache.Identity{
   163  					ID: deathstarIdentity,
   164  				}, true
   165  			}
   166  			return ipcache.Identity{}, false
   167  		},
   168  	}
   169  	serviceGetter := &testutils.FakeServiceGetter{
   170  		OnGetServiceByAddr: func(ip netip.Addr, port uint16) *flowpb.Service {
   171  			switch ip.String() {
   172  			case deathstarServiceV4, deathstarServiceV6:
   173  				if port == deathstarServicePort {
   174  					return &flowpb.Service{
   175  						Name:      deathstarServiceName,
   176  						Namespace: deathstarServiceNamespace,
   177  					}
   178  				}
   179  			}
   180  			return nil
   181  		},
   182  	}
   183  	cgroupGetter := &testutils.FakePodMetadataGetter{
   184  		OnGetPodMetadataForContainer: func(cgroupId uint64) *cgroupManager.PodMetadata {
   185  			switch cgroupId {
   186  			case xwingCgroupId:
   187  				return &cgroupManager.PodMetadata{
   188  					Name:      xwingPodName,
   189  					Namespace: xwingPodNamespace,
   190  					IPs:       []string{xwingIPv4, xwingIPv6},
   191  				}
   192  			}
   193  			return nil
   194  		},
   195  	}
   196  	tt := []struct {
   197  		name string
   198  		msg  monitor.TraceSockNotify
   199  
   200  		skipUnknownCGroupIDs bool
   201  
   202  		rawMsg []byte
   203  		flow   *flowpb.Flow
   204  		errMsg string
   205  	}{
   206  		{
   207  			name:   "empty buffer",
   208  			rawMsg: []byte{},
   209  			errMsg: parserErrors.ErrEmptyData.Error(),
   210  		},
   211  		{
   212  			name:   "invalid buffer",
   213  			rawMsg: []byte{monitorAPI.MessageTypeTraceSock},
   214  			errMsg: "failed to parse sock trace event",
   215  		},
   216  		{
   217  			name:   "empty event",
   218  			msg:    monitor.TraceSockNotify{},
   219  			errMsg: parserErrors.NewErrInvalidType(0).Error(),
   220  		},
   221  		{
   222  			name: "invalid cgroup id",
   223  			msg: monitor.TraceSockNotify{
   224  				Type:       monitorAPI.MessageTypeTraceSock,
   225  				XlatePoint: monitor.XlatePointPreDirectionFwd,
   226  				DstIP:      mustParseIP("10.10.10.10"),
   227  				DstPort:    8080,
   228  				L4Proto:    monitor.L4ProtocolUDP,
   229  				SockCookie: 0xc0ffee,
   230  				CgroupId:   1234,
   231  			},
   232  			skipUnknownCGroupIDs: true,
   233  			errMsg:               parserErrors.ErrEventSkipped.Error(),
   234  		},
   235  		{
   236  			name: "minimal",
   237  			msg: monitor.TraceSockNotify{
   238  				Type:       monitorAPI.MessageTypeTraceSock,
   239  				XlatePoint: monitor.XlatePointPreDirectionFwd,
   240  				DstIP:      mustParseIP("10.10.10.10"),
   241  				DstPort:    8080,
   242  				L4Proto:    monitor.L4ProtocolUDP,
   243  				SockCookie: 0xc0ffee,
   244  			},
   245  			skipUnknownCGroupIDs: false,
   246  			flow: &flowpb.Flow{
   247  				Type:    flowpb.FlowType_SOCK,
   248  				Verdict: flowpb.Verdict_TRACED,
   249  				IP: &flowpb.IP{
   250  					Destination: "10.10.10.10",
   251  					IpVersion:   flowpb.IPVersion_IPv4,
   252  				},
   253  				L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_UDP{UDP: &flowpb.UDP{
   254  					DestinationPort: 8080,
   255  				}}},
   256  				Source:      &flowpb.Endpoint{},
   257  				Destination: &flowpb.Endpoint{},
   258  				EventType: &flowpb.CiliumEventType{
   259  					Type:    monitorAPI.MessageTypeTraceSock,
   260  					SubType: monitor.XlatePointPreDirectionFwd,
   261  				},
   262  				SockXlatePoint: monitor.XlatePointPreDirectionFwd,
   263  				SocketCookie:   0xc0ffee,
   264  				Summary:        "UDP",
   265  			},
   266  		},
   267  		{
   268  			name: "pre-translate v4 xwing to service ip",
   269  			msg: monitor.TraceSockNotify{
   270  				Type:       monitorAPI.MessageTypeTraceSock,
   271  				XlatePoint: monitor.XlatePointPreDirectionFwd,
   272  				DstIP:      mustParseIP(deathstarServiceV4),
   273  				DstPort:    deathstarServicePort,
   274  				CgroupId:   xwingCgroupId,
   275  				L4Proto:    monitor.L4ProtocolTCP,
   276  			},
   277  			skipUnknownCGroupIDs: true,
   278  			flow: &flowpb.Flow{
   279  				Type:     flowpb.FlowType_SOCK,
   280  				Verdict:  flowpb.Verdict_TRACED,
   281  				CgroupId: xwingCgroupId,
   282  				IP: &flowpb.IP{
   283  					Source:      xwingIPv4,
   284  					Destination: deathstarServiceV4,
   285  					IpVersion:   flowpb.IPVersion_IPv4,
   286  				},
   287  				L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{
   288  					DestinationPort: deathstarServicePort,
   289  				}}},
   290  				Source: &flowpb.Endpoint{
   291  					ID:        xwingEndpoint,
   292  					Identity:  xwingIdentity,
   293  					PodName:   xwingPodName,
   294  					Namespace: xwingPodNamespace,
   295  					Labels:    xwingLabels,
   296  				},
   297  				Destination:      &flowpb.Endpoint{},
   298  				DestinationNames: []string{deathstarServiceDomain},
   299  				DestinationService: &flowpb.Service{
   300  					Name:      deathstarServiceName,
   301  					Namespace: deathstarServiceNamespace,
   302  				},
   303  				EventType: &flowpb.CiliumEventType{
   304  					Type:    monitorAPI.MessageTypeTraceSock,
   305  					SubType: monitor.XlatePointPreDirectionFwd,
   306  				},
   307  				SockXlatePoint: monitor.XlatePointPreDirectionFwd,
   308  				Summary:        "TCP",
   309  			},
   310  		},
   311  		{
   312  			name: "post-translate v4 xwing to remote pod ip",
   313  			msg: monitor.TraceSockNotify{
   314  				Type:       monitorAPI.MessageTypeTraceSock,
   315  				XlatePoint: monitor.XlatePointPostDirectionFwd,
   316  				DstIP:      mustParseIP(deathstarAltIPv4),
   317  				DstPort:    deathstarTargetPort,
   318  				CgroupId:   xwingCgroupId,
   319  				L4Proto:    monitor.L4ProtocolTCP,
   320  			},
   321  			skipUnknownCGroupIDs: true,
   322  			flow: &flowpb.Flow{
   323  				Type:     flowpb.FlowType_SOCK,
   324  				Verdict:  flowpb.Verdict_TRANSLATED,
   325  				CgroupId: xwingCgroupId,
   326  				IP: &flowpb.IP{
   327  					Source:      xwingIPv4,
   328  					Destination: deathstarAltIPv4,
   329  					IpVersion:   flowpb.IPVersion_IPv4,
   330  				},
   331  				L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{
   332  					DestinationPort: deathstarTargetPort,
   333  				}}},
   334  				Source: &flowpb.Endpoint{
   335  					ID:        xwingEndpoint,
   336  					Identity:  xwingIdentity,
   337  					PodName:   xwingPodName,
   338  					Namespace: xwingPodNamespace,
   339  					Labels:    xwingLabels,
   340  				},
   341  				Destination: &flowpb.Endpoint{
   342  					Identity:  deathstarIdentity,
   343  					PodName:   deathstarAltPodName,
   344  					Namespace: deathstarAltPodNamespace,
   345  					Labels:    deathstarLabels,
   346  				},
   347  				EventType: &flowpb.CiliumEventType{
   348  					Type:    monitorAPI.MessageTypeTraceSock,
   349  					SubType: monitor.XlatePointPostDirectionFwd,
   350  				},
   351  				SockXlatePoint: monitor.XlatePointPostDirectionFwd,
   352  				Summary:        "TCP",
   353  			},
   354  		},
   355  		{
   356  			name: "post-translate rev v6 xwing from service ip",
   357  			msg: monitor.TraceSockNotify{
   358  				Type:       monitorAPI.MessageTypeTraceSock,
   359  				XlatePoint: monitor.XlatePointPostDirectionRev,
   360  				DstIP:      mustParseIP(deathstarServiceV6),
   361  				DstPort:    deathstarServicePort,
   362  				CgroupId:   xwingCgroupId,
   363  				L4Proto:    monitor.L4ProtocolTCP,
   364  				Flags:      monitor.TraceSockNotifyFlagIPv6,
   365  			},
   366  			skipUnknownCGroupIDs: true,
   367  			flow: &flowpb.Flow{
   368  				Type:     flowpb.FlowType_SOCK,
   369  				Verdict:  flowpb.Verdict_TRANSLATED,
   370  				CgroupId: xwingCgroupId,
   371  				IP: &flowpb.IP{
   372  					Source:      deathstarServiceV6,
   373  					Destination: xwingIPv6,
   374  					IpVersion:   flowpb.IPVersion_IPv6,
   375  				},
   376  				L4: &flowpb.Layer4{Protocol: &flowpb.Layer4_TCP{TCP: &flowpb.TCP{
   377  					SourcePort: deathstarServicePort,
   378  				}}},
   379  				Source:      &flowpb.Endpoint{},
   380  				SourceNames: []string{deathstarServiceDomain},
   381  				SourceService: &flowpb.Service{
   382  					Name:      deathstarServiceName,
   383  					Namespace: deathstarServiceNamespace,
   384  				},
   385  				Destination: &flowpb.Endpoint{
   386  					ID:        xwingEndpoint,
   387  					Identity:  xwingIdentity,
   388  					PodName:   xwingPodName,
   389  					Namespace: xwingPodNamespace,
   390  					Labels:    xwingLabels,
   391  				},
   392  				EventType: &flowpb.CiliumEventType{
   393  					Type:    monitorAPI.MessageTypeTraceSock,
   394  					SubType: monitor.XlatePointPostDirectionRev,
   395  				},
   396  				SockXlatePoint: monitor.XlatePointPostDirectionRev,
   397  				Summary:        "TCP",
   398  			},
   399  		},
   400  	}
   401  
   402  	p, err := New(logrus.New(), endpointGetter, identityGetter, dnsGetter, ipGetter, serviceGetter, cgroupGetter)
   403  	assert.Nil(t, err)
   404  
   405  	for _, tc := range tt {
   406  		t.Run(tc.name, func(t *testing.T) {
   407  			p.skipUnknownCGroupIDs = tc.skipUnknownCGroupIDs
   408  			data := tc.rawMsg
   409  			if data == nil {
   410  				buf := &bytes.Buffer{}
   411  				err := binary.Write(buf, byteorder.Native, &tc.msg)
   412  				assert.Nil(t, err)
   413  				data = buf.Bytes()
   414  			}
   415  			flow := &flowpb.Flow{}
   416  			err = p.Decode(data, flow)
   417  			if tc.errMsg != "" {
   418  				assert.ErrorContains(t, err, tc.errMsg)
   419  			} else {
   420  				assert.Nil(t, err)
   421  				require.EqualValues(t, tc.flow, flow)
   422  			}
   423  		})
   424  	}
   425  }