From 82c1b44edc7df95dbf0cc80594c33383ad008dfa Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Wed, 6 Nov 2024 11:35:13 -0800 Subject: [PATCH 1/5] X-Smart-Branch-Parent: master From 585d64821740da570e0642b3883010680a3835a0 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 22 Oct 2024 19:51:29 -0700 Subject: [PATCH 2/5] All networking connections are now maintained. Two connections with different non nil timestamps are considered to be equivalent --- .../pkg/mock_sensor/expect_conn.go | 45 ++++++++++++++++++- integration-tests/pkg/mock_sensor/server.go | 44 +++++++++++------- integration-tests/pkg/types/network.go | 40 +++++++++++++++++ 3 files changed, 111 insertions(+), 18 deletions(-) diff --git a/integration-tests/pkg/mock_sensor/expect_conn.go b/integration-tests/pkg/mock_sensor/expect_conn.go index bf9b597183..aaf284063a 100644 --- a/integration-tests/pkg/mock_sensor/expect_conn.go +++ b/integration-tests/pkg/mock_sensor/expect_conn.go @@ -33,7 +33,7 @@ loop: case <-timer: // we know they don't match at this point, but by using // ElementsMatch we get much better logging about the differences - return assert.ElementsMatch(t, expected, s.Connections(containerID), "timed out waiting for networks") + return assert.ElementsMatch(t, expected, s.Connections(containerID), "timed out waiting for network connections") case network := <-s.LiveConnections(): if network.GetContainerId() != containerID { continue loop @@ -72,7 +72,6 @@ loop: if conn.GetContainerId() != containerID { continue loop } - if len(s.Connections(containerID)) == n { return s.Connections(containerID) } @@ -80,6 +79,48 @@ loop: } } +func (s *MockSensor) checkIfConnectionsMatchExpected(t *testing.T, connections []types.NetworkInfo, expected []types.NetworkInfo) bool { + if len(connections) > len(expected) { + return assert.ElementsMatch(t, expected, connections, "networking connections do not match") + } + + if len(connections) == len(expected) { + types.SortConnections(connections) + for i, _ := range expected { + if !expected[i].Equal(connections[i]) { + return assert.ElementsMatch(t, expected, connections, "networking connections do not match") + } + } + return true + } + return false +} + +func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { + types.SortConnections(expected) + timer := time.NewTimer(timeout) + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-timer.C: + connections := s.Connections(containerID) + types.SortConnections(connections) + return s.checkIfConnectionsMatchExpected(t, connections, expected) + case <-ticker.C: + connections := s.Connections(containerID) + types.SortConnections(connections) + success := s.checkIfConnectionsMatchExpected(t, connections, expected) + if success { + return true + } else if len(connections) >= len(expected) { + return assert.ElementsMatch(t, expected, connections, "networking connections do not match") + } + } + } +} + // ExpectEndpoints waits up to the timeout for the gRPC server to receive // the list of expected Endpoints. It will first check to see if the endpoints // have been received already, and then monitor the live feed of endpoints diff --git a/integration-tests/pkg/mock_sensor/server.go b/integration-tests/pkg/mock_sensor/server.go index 5b99e5fcda..d19445dba5 100644 --- a/integration-tests/pkg/mock_sensor/server.go +++ b/integration-tests/pkg/mock_sensor/server.go @@ -32,7 +32,8 @@ const ( // us to use any comparable type as the key) type ProcessMap map[types.ProcessInfo]interface{} type LineageMap map[types.ProcessLineage]interface{} -type ConnMap map[types.NetworkInfo]interface{} + +// type ConnMap map[types.NetworkInfo]interface{} type EndpointMap map[types.EndpointInfo]interface{} type MockSensor struct { @@ -47,7 +48,8 @@ type MockSensor struct { processLineages map[string]LineageMap processMutex sync.Mutex - connections map[string]ConnMap + connections map[string][]types.NetworkInfo + //connections map[string]ConnMap endpoints map[string]EndpointMap networkMutex sync.Mutex @@ -65,8 +67,9 @@ func NewMockSensor(test string) *MockSensor { testName: test, processes: make(map[string]ProcessMap), processLineages: make(map[string]LineageMap), - connections: make(map[string]ConnMap), - endpoints: make(map[string]EndpointMap), + connections: make(map[string][]types.NetworkInfo), + //connections: make(map[string]ConnMap), + endpoints: make(map[string]EndpointMap), } } @@ -155,11 +158,12 @@ func (m *MockSensor) Connections(containerID string) []types.NetworkInfo { defer m.networkMutex.Unlock() if connections, ok := m.connections[containerID]; ok { - keys := make([]types.NetworkInfo, 0, len(connections)) - for k := range connections { - keys = append(keys, k) - } - return keys + //keys := make([]types.NetworkInfo, 0, len(connections)) + //for k := range connections { + // keys = append(keys, k) + //} + //return keys + return connections } return make([]types.NetworkInfo, 0) } @@ -171,8 +175,11 @@ func (m *MockSensor) HasConnection(containerID string, conn types.NetworkInfo) b defer m.networkMutex.Unlock() if conns, ok := m.connections[containerID]; ok { - _, exists := conns[conn] - return exists + for _, connection := range conns { + if connection.Equal(conn) { + return true + } + } } return false @@ -271,7 +278,7 @@ func (m *MockSensor) Stop() { m.processes = make(map[string]ProcessMap) m.processLineages = make(map[string]LineageMap) - m.connections = make(map[string]ConnMap) + m.connections = make(map[string][]types.NetworkInfo) m.endpoints = make(map[string]EndpointMap) m.processChannel.Stop() @@ -432,11 +439,16 @@ func (m *MockSensor) pushConnection(containerID string, connection *sensorAPI.Ne CloseTimestamp: connection.GetCloseTimestamp().String(), } - if connections, ok := m.connections[containerID]; ok { - connections[conn] = true + //if connections, ok := m.connections[containerID]; ok { + // connections[conn] = true + //} else { + // connections := ConnMap{conn: true} + // m.connections[containerID] = connections + //} + if _, ok := m.connections[containerID]; ok { + m.connections[containerID] = append(m.connections[containerID], conn) } else { - connections := ConnMap{conn: true} - m.connections[containerID] = connections + m.connections[containerID] = []types.NetworkInfo{conn} } } diff --git a/integration-tests/pkg/types/network.go b/integration-tests/pkg/types/network.go index dbe260ca4d..1f688e24cc 100644 --- a/integration-tests/pkg/types/network.go +++ b/integration-tests/pkg/types/network.go @@ -1,5 +1,9 @@ package types +import ( + "sort" +) + const ( NilTimestamp = "" ) @@ -16,3 +20,39 @@ func (n *NetworkInfo) IsActive() bool { // no close timestamp means the connection is open, and active return n.CloseTimestamp == NilTimestamp } + +func (n *NetworkInfo) Equal(other NetworkInfo) bool { + return n.LocalAddress == other.LocalAddress && + n.RemoteAddress == other.RemoteAddress && + n.Role == other.Role && + n.SocketFamily == other.SocketFamily && + n.IsActive() == other.IsActive() +} + +func (n *NetworkInfo) Less(other NetworkInfo) bool { + if n.LocalAddress != other.LocalAddress { + return n.LocalAddress < other.LocalAddress + } + + if n.RemoteAddress != other.RemoteAddress { + return n.RemoteAddress < other.RemoteAddress + } + + if n.Role != other.Role { + return n.Role < other.Role + } + + if n.SocketFamily != other.SocketFamily { + return n.SocketFamily < other.SocketFamily + } + + if n.IsActive() != other.IsActive() { + return n.IsActive() + } + + return false +} + +func SortConnections(connections []NetworkInfo) { + sort.Slice(connections, func(i, j int) bool { return connections[i].Less(connections[j]) }) +} From eca4ddb7b432d8fa5a7fae10f89cad68bfcdf0f0 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 22 Oct 2024 20:06:16 -0700 Subject: [PATCH 3/5] Cleanup --- integration-tests/pkg/mock_sensor/server.go | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/integration-tests/pkg/mock_sensor/server.go b/integration-tests/pkg/mock_sensor/server.go index d19445dba5..44291d6e1f 100644 --- a/integration-tests/pkg/mock_sensor/server.go +++ b/integration-tests/pkg/mock_sensor/server.go @@ -32,8 +32,6 @@ const ( // us to use any comparable type as the key) type ProcessMap map[types.ProcessInfo]interface{} type LineageMap map[types.ProcessLineage]interface{} - -// type ConnMap map[types.NetworkInfo]interface{} type EndpointMap map[types.EndpointInfo]interface{} type MockSensor struct { @@ -48,8 +46,7 @@ type MockSensor struct { processLineages map[string]LineageMap processMutex sync.Mutex - connections map[string][]types.NetworkInfo - //connections map[string]ConnMap + connections map[string][]types.NetworkInfo endpoints map[string]EndpointMap networkMutex sync.Mutex @@ -68,8 +65,7 @@ func NewMockSensor(test string) *MockSensor { processes: make(map[string]ProcessMap), processLineages: make(map[string]LineageMap), connections: make(map[string][]types.NetworkInfo), - //connections: make(map[string]ConnMap), - endpoints: make(map[string]EndpointMap), + endpoints: make(map[string]EndpointMap), } } @@ -158,11 +154,6 @@ func (m *MockSensor) Connections(containerID string) []types.NetworkInfo { defer m.networkMutex.Unlock() if connections, ok := m.connections[containerID]; ok { - //keys := make([]types.NetworkInfo, 0, len(connections)) - //for k := range connections { - // keys = append(keys, k) - //} - //return keys return connections } return make([]types.NetworkInfo, 0) @@ -439,12 +430,6 @@ func (m *MockSensor) pushConnection(containerID string, connection *sensorAPI.Ne CloseTimestamp: connection.GetCloseTimestamp().String(), } - //if connections, ok := m.connections[containerID]; ok { - // connections[conn] = true - //} else { - // connections := ConnMap{conn: true} - // m.connections[containerID] = connections - //} if _, ok := m.connections[containerID]; ok { m.connections[containerID] = append(m.connections[containerID], conn) } else { From 03c775eeb3393250eb9fc2b5e125dc983b3de1a6 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 22 Oct 2024 21:58:11 -0700 Subject: [PATCH 4/5] Able to control if connections should be ordered or not. Also added comments --- .../pkg/mock_sensor/expect_conn.go | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/integration-tests/pkg/mock_sensor/expect_conn.go b/integration-tests/pkg/mock_sensor/expect_conn.go index aaf284063a..edfeb7cc4e 100644 --- a/integration-tests/pkg/mock_sensor/expect_conn.go +++ b/integration-tests/pkg/mock_sensor/expect_conn.go @@ -79,6 +79,7 @@ loop: } } +// checkIfConnectionsMatchExpected compares a list of expected and observed connection match exactly. func (s *MockSensor) checkIfConnectionsMatchExpected(t *testing.T, connections []types.NetworkInfo, expected []types.NetworkInfo) bool { if len(connections) > len(expected) { return assert.ElementsMatch(t, expected, connections, "networking connections do not match") @@ -86,7 +87,7 @@ func (s *MockSensor) checkIfConnectionsMatchExpected(t *testing.T, connections [ if len(connections) == len(expected) { types.SortConnections(connections) - for i, _ := range expected { + for i := range expected { if !expected[i].Equal(connections[i]) { return assert.ElementsMatch(t, expected, connections, "networking connections do not match") } @@ -96,8 +97,14 @@ func (s *MockSensor) checkIfConnectionsMatchExpected(t *testing.T, connections [ return false } -func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { - types.SortConnections(expected) +// CompareConnections compares a list of expected connections to the observed connections. This comparison is done at the beginning, when a new +// connection arrives, and after a timeout period. The number of connections must match and it can be specified if the order of the connections +// must match or not. The difference between this function and ExpectConnections is that ExpectConnections tolerates extra observed connections +// that are not expected. +func (s *MockSensor) CompareConnections(t *testing.T, containerID string, timeout time.Duration, order bool, expected ...types.NetworkInfo) bool { + if order { + types.SortConnections(expected) + } timer := time.NewTimer(timeout) ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() @@ -106,11 +113,15 @@ func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, ti select { case <-timer.C: connections := s.Connections(containerID) - types.SortConnections(connections) + if order { + types.SortConnections(connections) + } return s.checkIfConnectionsMatchExpected(t, connections, expected) case <-ticker.C: connections := s.Connections(containerID) - types.SortConnections(connections) + if order { + types.SortConnections(connections) + } success := s.checkIfConnectionsMatchExpected(t, connections, expected) if success { return true @@ -121,6 +132,18 @@ func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, ti } } +// ExpectExactConnections requires that within a timeout period the networking connections involving containerID match a list of expected +// netwoking connections. +func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { + return s.CompareConnections(t, containerID, timeout, true, expected...) +} + +// ExpectSameElementsConnections requires that within a timeout period the networking connections involving containerID match a list of expected +// netwoking connections, but the order of those connections do not have to match. +func (s *MockSensor) ExpectSameElementsConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { + return s.CompareConnections(t, containerID, timeout, false, expected...) +} + // ExpectEndpoints waits up to the timeout for the gRPC server to receive // the list of expected Endpoints. It will first check to see if the endpoints // have been received already, and then monitor the live feed of endpoints From d1a1847f6fd6ab9b748347c533c47766940fb987 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Mon, 28 Oct 2024 20:17:00 -0700 Subject: [PATCH 5/5] Listening for new connection events instead of using a ticker --- .../pkg/mock_sensor/expect_conn.go | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/integration-tests/pkg/mock_sensor/expect_conn.go b/integration-tests/pkg/mock_sensor/expect_conn.go index edfeb7cc4e..7811db0cb5 100644 --- a/integration-tests/pkg/mock_sensor/expect_conn.go +++ b/integration-tests/pkg/mock_sensor/expect_conn.go @@ -97,6 +97,21 @@ func (s *MockSensor) checkIfConnectionsMatchExpected(t *testing.T, connections [ return false } +// getConnectionsAndCompare gets the connections for a container, sorts them if order is set to true, and compares with a set of expected +// connections. If asssertMismatch is true and the set of observed connections does not match the set of expected connections an assert is +// triggered. If assertMismatch is not set then just return if the observed and expected connections match. +func (s *MockSensor) getConnectionsAndCompare(t *testing.T, containerID string, order bool, assertMismatch bool, expected ...types.NetworkInfo) bool { + connections := s.Connections(containerID) + if order { + types.SortConnections(connections) + } + success := s.checkIfConnectionsMatchExpected(t, connections, expected) + if assertMismatch && !success { + return assert.ElementsMatch(t, expected, connections, "networking connections do not match") + } + return success +} + // CompareConnections compares a list of expected connections to the observed connections. This comparison is done at the beginning, when a new // connection arrives, and after a timeout period. The number of connections must match and it can be specified if the order of the connections // must match or not. The difference between this function and ExpectConnections is that ExpectConnections tolerates extra observed connections @@ -105,28 +120,25 @@ func (s *MockSensor) CompareConnections(t *testing.T, containerID string, timeou if order { types.SortConnections(expected) } - timer := time.NewTimer(timeout) - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() + + success := s.getConnectionsAndCompare(t, containerID, order, false, expected...) + if success { + return true + } + + timer := time.After(timeout) for { select { - case <-timer.C: - connections := s.Connections(containerID) - if order { - types.SortConnections(connections) - } - return s.checkIfConnectionsMatchExpected(t, connections, expected) - case <-ticker.C: - connections := s.Connections(containerID) - if order { - types.SortConnections(connections) + case <-timer: + return s.getConnectionsAndCompare(t, containerID, order, true, expected...) + case conn := <-s.LiveConnections(): + if conn.GetContainerId() != containerID { + continue } - success := s.checkIfConnectionsMatchExpected(t, connections, expected) + success := s.getConnectionsAndCompare(t, containerID, order, false, expected...) if success { return true - } else if len(connections) >= len(expected) { - return assert.ElementsMatch(t, expected, connections, "networking connections do not match") } } } @@ -135,13 +147,13 @@ func (s *MockSensor) CompareConnections(t *testing.T, containerID string, timeou // ExpectExactConnections requires that within a timeout period the networking connections involving containerID match a list of expected // netwoking connections. func (s *MockSensor) ExpectExactConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { - return s.CompareConnections(t, containerID, timeout, true, expected...) + return s.CompareConnections(t, containerID, timeout, false, expected...) } // ExpectSameElementsConnections requires that within a timeout period the networking connections involving containerID match a list of expected // netwoking connections, but the order of those connections do not have to match. func (s *MockSensor) ExpectSameElementsConnections(t *testing.T, containerID string, timeout time.Duration, expected ...types.NetworkInfo) bool { - return s.CompareConnections(t, containerID, timeout, false, expected...) + return s.CompareConnections(t, containerID, timeout, true, expected...) } // ExpectEndpoints waits up to the timeout for the gRPC server to receive