github.com/cilium/cilium@v1.16.2/pkg/fqdn/dnsproxy/proxy_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package dnsproxy
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net"
    12  	"net/netip"
    13  	"os"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/cilium/dns"
    22  	"github.com/stretchr/testify/require"
    23  	"golang.org/x/exp/maps"
    24  	"sigs.k8s.io/yaml"
    25  
    26  	datapath "github.com/cilium/cilium/pkg/datapath/types"
    27  	"github.com/cilium/cilium/pkg/endpoint"
    28  	"github.com/cilium/cilium/pkg/fqdn/restore"
    29  	"github.com/cilium/cilium/pkg/identity"
    30  	"github.com/cilium/cilium/pkg/identity/cache"
    31  	"github.com/cilium/cilium/pkg/ipcache"
    32  	v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    33  	"github.com/cilium/cilium/pkg/labels"
    34  	monitorAPI "github.com/cilium/cilium/pkg/monitor/api"
    35  	"github.com/cilium/cilium/pkg/option"
    36  	"github.com/cilium/cilium/pkg/policy"
    37  	"github.com/cilium/cilium/pkg/policy/api"
    38  	"github.com/cilium/cilium/pkg/source"
    39  	"github.com/cilium/cilium/pkg/testutils"
    40  	testidentity "github.com/cilium/cilium/pkg/testutils/identity"
    41  	testipcache "github.com/cilium/cilium/pkg/testutils/ipcache"
    42  	"github.com/cilium/cilium/pkg/u8proto"
    43  )
    44  
    45  type DNSProxyTestSuite struct {
    46  	repo         *policy.Repository
    47  	dnsTCPClient *dns.Client
    48  	dnsServer    *dns.Server
    49  	proxy        *DNSProxy
    50  	restoring    bool
    51  }
    52  
    53  func setupDNSProxyTestSuite(tb testing.TB) *DNSProxyTestSuite {
    54  	testutils.PrivilegedTest(tb)
    55  
    56  	s := &DNSProxyTestSuite{}
    57  
    58  	// Add these identities
    59  	wg := &sync.WaitGroup{}
    60  	testSelectorCache.UpdateIdentities(identity.IdentityMap{
    61  		dstID1: labels.Labels{"Dst1": labels.NewLabel("Dst1", "test", labels.LabelSourceK8s)}.LabelArray(),
    62  		dstID2: labels.Labels{"Dst2": labels.NewLabel("Dst2", "test", labels.LabelSourceK8s)}.LabelArray(),
    63  		dstID3: labels.Labels{"Dst3": labels.NewLabel("Dst3", "test", labels.LabelSourceK8s)}.LabelArray(),
    64  		dstID4: labels.Labels{"Dst4": labels.NewLabel("Dst4", "test", labels.LabelSourceK8s)}.LabelArray(),
    65  	}, nil, wg)
    66  	wg.Wait()
    67  
    68  	s.repo = policy.NewPolicyRepository(nil, nil, nil)
    69  	s.dnsTCPClient = &dns.Client{Net: "tcp", Timeout: time.Second, SingleInflight: true}
    70  	s.dnsServer = setupServer(tb)
    71  	require.NotNil(tb, s.dnsServer, "unable to setup DNS server")
    72  	dnsProxyConfig := DNSProxyConfig{
    73  		Address:                "",
    74  		Port:                   0,
    75  		IPv4:                   true,
    76  		IPv6:                   true,
    77  		EnableDNSCompression:   true,
    78  		MaxRestoreDNSIPs:       1000,
    79  		ConcurrencyLimit:       0,
    80  		ConcurrencyGracePeriod: 0,
    81  	}
    82  	proxy, err := StartDNSProxy(dnsProxyConfig, // any address, any port, enable ipv4, enable ipv6, enable compression, max 1000 restore IPs
    83  		// LookupEPByIP
    84  		func(ip netip.Addr) (*endpoint.Endpoint, error) {
    85  			if s.restoring {
    86  				return nil, fmt.Errorf("No EPs available when restoring")
    87  			}
    88  			return endpoint.NewTestEndpointWithState(tb, s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), uint16(epID1), endpoint.StateReady), nil
    89  		},
    90  		// LookupSecIDByIP
    91  		func(ip netip.Addr) (ipcache.Identity, bool) {
    92  			DNSServerListenerAddr := (s.dnsServer.Listener.Addr()).(*net.TCPAddr)
    93  			switch {
    94  			case ip.String() == DNSServerListenerAddr.IP.String():
    95  				ident := ipcache.Identity{
    96  					ID:     dstID1,
    97  					Source: source.Unspec,
    98  				}
    99  				return ident, true
   100  			default:
   101  				ident := ipcache.Identity{
   102  					ID:     dstID2,
   103  					Source: source.Unspec,
   104  				}
   105  				return ident, true
   106  			}
   107  		},
   108  		// LookupIPsBySecID
   109  		func(nid identity.NumericIdentity) []string {
   110  			DNSServerListenerAddr := (s.dnsServer.Listener.Addr()).(*net.TCPAddr)
   111  			switch nid {
   112  			case dstID1:
   113  				return []string{DNSServerListenerAddr.IP.String()}
   114  			case dstID2:
   115  				return []string{"127.0.0.1", "127.0.0.2"}
   116  			default:
   117  				return nil
   118  			}
   119  		},
   120  		// NotifyOnDNSMsg
   121  		func(lookupTime time.Time, ep *endpoint.Endpoint, epIPPort string, serverID identity.NumericIdentity, dstAddr string, msg *dns.Msg, protocol string, allowed bool, stat *ProxyRequestContext) error {
   122  			return nil
   123  		},
   124  	)
   125  	require.Nil(tb, err, "error starting DNS Proxy")
   126  	s.proxy = proxy
   127  
   128  	// This is here because Listener or Listeer.Addr() was nil. The
   129  	// lookupTargetDNSServer function doesn't need to change the target.
   130  	require.NotNil(tb, s.dnsServer.Listener, "DNS server missing a Listener")
   131  	DNSServerListenerAddr := (s.dnsServer.Listener.Addr()).(*net.TCPAddr)
   132  	require.NotNil(tb, DNSServerListenerAddr, "DNS server missing a Listener address")
   133  	s.proxy.lookupTargetDNSServer = func(w dns.ResponseWriter) (serverIP net.IP, serverPortProto restore.PortProto, addrStr string, err error) {
   134  		return DNSServerListenerAddr.IP, restore.MakeV2PortProto(uint16(DNSServerListenerAddr.Port), uint8(u8proto.UDP)), DNSServerListenerAddr.String(), nil
   135  	}
   136  	dstPortProto = restore.MakeV2PortProto(uint16(DNSServerListenerAddr.Port), udpProto)
   137  
   138  	tb.Cleanup(func() {
   139  		for epID := range s.proxy.allowed {
   140  			for pp := range s.proxy.allowed[epID] {
   141  				s.proxy.UpdateAllowed(epID, pp, nil)
   142  			}
   143  		}
   144  		for epID := range s.proxy.restored {
   145  			s.proxy.RemoveRestoredRules(uint16(epID))
   146  		}
   147  		if len(s.proxy.cache) > 0 {
   148  			tb.Error("cache not fully empty after removing all rules. Possible memory leak found.")
   149  		}
   150  		s.proxy.SetRejectReply(option.FQDNProxyDenyWithRefused)
   151  		s.dnsServer.Listener.Close()
   152  		for _, s := range s.proxy.DNSServers {
   153  			s.Shutdown()
   154  		}
   155  	})
   156  
   157  	return s
   158  }
   159  
   160  func (s *DNSProxyTestSuite) GetPolicyRepository() *policy.Repository {
   161  	return s.repo
   162  }
   163  
   164  func (s *DNSProxyTestSuite) GetProxyPort(string) (uint16, error) {
   165  	return 0, nil
   166  }
   167  
   168  func (s *DNSProxyTestSuite) QueueEndpointBuild(ctx context.Context, epID uint64) (func(), error) {
   169  	return nil, nil
   170  }
   171  
   172  func (s *DNSProxyTestSuite) GetCompilationLock() datapath.CompilationLock {
   173  	return nil
   174  }
   175  
   176  func (s *DNSProxyTestSuite) GetCIDRPrefixLengths() (s6, s4 []int) {
   177  	return nil, nil
   178  }
   179  
   180  func (s *DNSProxyTestSuite) SendNotification(msg monitorAPI.AgentNotifyMessage) error {
   181  	return nil
   182  }
   183  
   184  func (s *DNSProxyTestSuite) Datapath() datapath.Datapath {
   185  	return nil
   186  }
   187  
   188  func (s *DNSProxyTestSuite) GetDNSRules(epID uint16) restore.DNSRules {
   189  	return nil
   190  }
   191  
   192  func (s *DNSProxyTestSuite) RemoveRestoredDNSRules(epID uint16) {
   193  }
   194  
   195  func setupServer(tb testing.TB) (dnsServer *dns.Server) {
   196  	waitOnListen := make(chan struct{})
   197  	dnsServer = &dns.Server{Addr: ":0", Net: "tcp", NotifyStartedFunc: func() { close(waitOnListen) }}
   198  	go dnsServer.ListenAndServe()
   199  	dns.HandleFunc(".", serveDNS)
   200  
   201  	select {
   202  	case <-waitOnListen:
   203  		return dnsServer
   204  
   205  	case <-time.After(10 * time.Second):
   206  		tb.Error("DNS server did not start listening")
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func serveDNS(w dns.ResponseWriter, r *dns.Msg) {
   213  	m := new(dns.Msg)
   214  	m.SetReply(r)
   215  
   216  	retARR, err := dns.NewRR(m.Question[0].Name + " 60 IN A 1.1.1.1")
   217  	if err != nil {
   218  		panic(err)
   219  	}
   220  	m.Answer = append(m.Answer, retARR)
   221  
   222  	w.WriteMsg(m)
   223  }
   224  
   225  type DummySelectorCacheUser struct{}
   226  
   227  func (d *DummySelectorCacheUser) IdentitySelectionUpdated(selector policy.CachedSelector, added, deleted []identity.NumericIdentity) {
   228  }
   229  
   230  // Setup identities, ports and endpoint IDs we will need
   231  var (
   232  	cacheAllocator          = cache.NewCachingIdentityAllocator(&testidentity.IdentityAllocatorOwnerMock{})
   233  	testSelectorCache       = policy.NewSelectorCache(cacheAllocator.GetIdentityCache())
   234  	dummySelectorCacheUser  = &DummySelectorCacheUser{}
   235  	DstID1Selector          = api.NewESFromLabels(labels.ParseSelectLabel("k8s:Dst1=test"))
   236  	cachedDstID1Selector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, nil, DstID1Selector)
   237  	DstID2Selector          = api.NewESFromLabels(labels.ParseSelectLabel("k8s:Dst2=test"))
   238  	cachedDstID2Selector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, nil, DstID2Selector)
   239  	DstID3Selector          = api.NewESFromLabels(labels.ParseSelectLabel("k8s:Dst3=test"))
   240  	cachedDstID3Selector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, nil, DstID3Selector)
   241  	DstID4Selector          = api.NewESFromLabels(labels.ParseSelectLabel("k8s:Dst4=test"))
   242  	cachedDstID4Selector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, nil, DstID4Selector)
   243  
   244  	cachedWildcardSelector, _ = testSelectorCache.AddIdentitySelector(dummySelectorCacheUser, nil, api.WildcardEndpointSelector)
   245  
   246  	epID1            = uint64(111)
   247  	epID2            = uint64(222)
   248  	epID3            = uint64(333)
   249  	dstID1           = identity.NumericIdentity(1001)
   250  	dstID2           = identity.NumericIdentity(2002)
   251  	dstID3           = identity.NumericIdentity(3003)
   252  	dstID4           = identity.NumericIdentity(4004)
   253  	dstPortProto     = restore.MakeV2PortProto(53, udpProto) // Set below when we setup the server!
   254  	udpProtoPort53   = dstPortProto
   255  	udpProtoPort54   = restore.MakeV2PortProto(54, udpProto)
   256  	udpProtoPort8053 = restore.MakeV2PortProto(8053, udpProto)
   257  	tcpProtoPort53   = restore.MakeV2PortProto(53, tcpProto)
   258  )
   259  
   260  func TestRejectFromDifferentEndpoint(t *testing.T) {
   261  	s := setupDNSProxyTestSuite(t)
   262  
   263  	name := "cilium.io."
   264  	l7map := policy.L7DataMap{
   265  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   266  			L7Rules: api.L7Rules{
   267  				DNS: []api.PortRuleDNS{{MatchName: name}},
   268  			},
   269  		},
   270  	}
   271  	query := name
   272  
   273  	// Reject a query from not endpoint 1
   274  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   275  	require.NoError(t, err, "Could not update with rules")
   276  	allowed, err := s.proxy.CheckAllowed(epID2, dstPortProto, dstID1, nil, query)
   277  	require.NoError(t, err, "Error when checking allowed")
   278  	require.False(t, allowed, "request was not rejected when it should be blocked")
   279  }
   280  
   281  func TestAcceptFromMatchingEndpoint(t *testing.T) {
   282  	s := setupDNSProxyTestSuite(t)
   283  
   284  	name := "cilium.io."
   285  	l7map := policy.L7DataMap{
   286  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   287  			L7Rules: api.L7Rules{
   288  				DNS: []api.PortRuleDNS{{MatchName: name}},
   289  			},
   290  		},
   291  	}
   292  	query := name
   293  
   294  	// accept a query that matches from endpoint1
   295  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   296  	require.NoError(t, err, "Could not update with rules")
   297  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   298  	require.NoError(t, err, "Error when checking allowed")
   299  	require.True(t, allowed, "request was rejected when it should be allowed")
   300  }
   301  
   302  func TestAcceptNonRegex(t *testing.T) {
   303  	s := setupDNSProxyTestSuite(t)
   304  
   305  	name := "simple.io."
   306  	l7map := policy.L7DataMap{
   307  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   308  			L7Rules: api.L7Rules{
   309  				DNS: []api.PortRuleDNS{{MatchName: name}},
   310  			},
   311  		},
   312  	}
   313  	query := name
   314  
   315  	// accept a query that matches from endpoint1
   316  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   317  	require.Equal(t, nil, err, "Could not update with rules")
   318  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   319  	require.Equal(t, nil, err, "Error when checking allowed")
   320  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   321  }
   322  
   323  func TestRejectNonRegex(t *testing.T) {
   324  	s := setupDNSProxyTestSuite(t)
   325  
   326  	name := "cilium.io."
   327  	l7map := policy.L7DataMap{
   328  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   329  			L7Rules: api.L7Rules{
   330  				DNS: []api.PortRuleDNS{{MatchName: name}},
   331  			},
   332  		},
   333  	}
   334  	query := "ciliumXio."
   335  
   336  	// reject a query for a non-regex where a . is different (i.e. ensure simple FQDNs treat . as .)
   337  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   338  	require.Equal(t, nil, err, "Could not update with rules")
   339  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   340  	require.Equal(t, nil, err, "Error when checking allowed")
   341  	require.Equal(t, false, allowed, "request was not rejected when it should be blocked")
   342  }
   343  
   344  func (s *DNSProxyTestSuite) requestRejectNonMatchingRefusedResponse(t *testing.T) *dns.Msg {
   345  	name := "cilium.io."
   346  	l7map := policy.L7DataMap{
   347  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   348  			L7Rules: api.L7Rules{
   349  				DNS: []api.PortRuleDNS{{MatchName: name}},
   350  			},
   351  		},
   352  	}
   353  	query := "notcilium.io."
   354  
   355  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   356  	require.Equal(t, nil, err, "Could not update with rules")
   357  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   358  	require.Equal(t, nil, err, "Error when checking allowed")
   359  	require.Equal(t, false, allowed, "request was not rejected when it should be blocked")
   360  
   361  	request := new(dns.Msg)
   362  	request.SetQuestion(query, dns.TypeA)
   363  	return request
   364  }
   365  
   366  func TestRejectNonMatchingRefusedResponseWithNameError(t *testing.T) {
   367  	s := setupDNSProxyTestSuite(t)
   368  
   369  	request := s.requestRejectNonMatchingRefusedResponse(t)
   370  
   371  	// reject a query with NXDomain
   372  	s.proxy.SetRejectReply(option.FQDNProxyDenyWithNameError)
   373  	response, _, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
   374  	require.NoError(t, err, "DNS request from test client failed when it should succeed")
   375  	require.Equal(t, dns.RcodeNameError, response.Rcode, "DNS request from test client was not rejected when it should be blocked")
   376  }
   377  
   378  func TestRejectNonMatchingRefusedResponseWithRefused(t *testing.T) {
   379  	s := setupDNSProxyTestSuite(t)
   380  
   381  	request := s.requestRejectNonMatchingRefusedResponse(t)
   382  
   383  	// reject a query with Refused
   384  	s.proxy.SetRejectReply(option.FQDNProxyDenyWithRefused)
   385  	response, _, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
   386  	require.NoError(t, err, "DNS request from test client failed when it should succeed")
   387  	require.Equal(t, dns.RcodeRefused, response.Rcode, "DNS request from test client was not rejected when it should be blocked")
   388  }
   389  
   390  func TestRespondViaCorrectProtocol(t *testing.T) {
   391  	s := setupDNSProxyTestSuite(t)
   392  
   393  	// Respond with an actual answer for the query. This also tests that the
   394  	// connection was forwarded via the correct protocol (tcp/udp) because we
   395  	// connet with TCP, and the server only listens on TCP.
   396  
   397  	name := "cilium.io."
   398  	l7map := policy.L7DataMap{
   399  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   400  			L7Rules: api.L7Rules{
   401  				DNS: []api.PortRuleDNS{{MatchName: name}},
   402  			},
   403  		},
   404  	}
   405  	query := name
   406  
   407  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   408  	require.Equal(t, nil, err, "Could not update with rules")
   409  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   410  	require.Equal(t, nil, err, "Error when checking allowed")
   411  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   412  
   413  	request := new(dns.Msg)
   414  	request.SetQuestion(query, dns.TypeA)
   415  	response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
   416  	require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v)", rtt)
   417  	require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s", response)
   418  	require.Equal(t, "cilium.io.\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
   419  }
   420  
   421  func TestRespondMixedCaseInRequestResponse(t *testing.T) {
   422  	s := setupDNSProxyTestSuite(t)
   423  
   424  	// Test that mixed case query is allowed out and then back in to support
   425  	// high-order-bit query uniqueing schemes (and a data exfiltration
   426  	// vector :( )
   427  	name := "cilium.io."
   428  	l7map := policy.L7DataMap{
   429  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   430  			L7Rules: api.L7Rules{
   431  				DNS: []api.PortRuleDNS{{MatchName: name}},
   432  			},
   433  		},
   434  	}
   435  	query := "CILIUM.io."
   436  
   437  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   438  	require.Equal(t, nil, err, "Could not update with rules")
   439  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   440  	require.Equal(t, nil, err, "Error when checking allowed")
   441  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   442  
   443  	request := new(dns.Msg)
   444  	request.SetQuestion(query, dns.TypeA)
   445  	response, _, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
   446  	require.NoError(t, err, "DNS request from test client failed when it should succeed")
   447  	require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s", response)
   448  	require.Equal(t, "CILIUM.io.\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
   449  
   450  	request.SetQuestion("ciliuM.io.", dns.TypeA)
   451  	response, _, err = s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
   452  	require.NoError(t, err, "DNS request from test client failed when it should succeed")
   453  	require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %+v", response.Answer)
   454  	require.Equal(t, "ciliuM.io.\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
   455  }
   456  
   457  func TestCheckNoRules(t *testing.T) {
   458  	s := setupDNSProxyTestSuite(t)
   459  
   460  	name := "cilium.io."
   461  	l7map := policy.L7DataMap{
   462  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   463  			L7Rules: api.L7Rules{},
   464  		},
   465  	}
   466  	query := name
   467  
   468  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   469  	require.Equal(t, nil, err, "Error when inserting rules")
   470  
   471  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   472  	require.Equal(t, nil, err, "Error when checking allowed")
   473  
   474  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   475  
   476  	l7map = policy.L7DataMap{
   477  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   478  			L7Rules: api.L7Rules{
   479  				DNS: []api.PortRuleDNS{},
   480  			},
   481  		},
   482  	}
   483  	err = s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   484  	require.Equal(t, nil, err, "Error when inserting rules")
   485  
   486  	allowed, err = s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   487  	require.Equal(t, nil, err, "Error when checking allowed")
   488  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   489  }
   490  
   491  func TestCheckAllowedTwiceRemovedOnce(t *testing.T) {
   492  	s := setupDNSProxyTestSuite(t)
   493  
   494  	name := "cilium.io."
   495  	l7map := policy.L7DataMap{
   496  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   497  			L7Rules: api.L7Rules{
   498  				DNS: []api.PortRuleDNS{{MatchName: name}},
   499  			},
   500  		},
   501  	}
   502  	query := name
   503  
   504  	// Add the rule twice
   505  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   506  	require.Equal(t, nil, err, "Could not update with rules")
   507  	err = s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
   508  	require.Equal(t, nil, err, "Could not update with rules")
   509  	allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   510  	require.Equal(t, nil, err, "Error when checking allowed")
   511  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   512  
   513  	// Delete once, it should reject
   514  	err = s.proxy.UpdateAllowed(epID1, dstPortProto, nil)
   515  	require.Equal(t, nil, err, "Could not update with rules")
   516  	allowed, err = s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   517  	require.Equal(t, nil, err, "Error when checking allowed")
   518  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   519  
   520  	// Delete once, it should reject and not crash
   521  	err = s.proxy.UpdateAllowed(epID1, dstPortProto, nil)
   522  	require.Equal(t, nil, err, "Could not update with rules")
   523  	allowed, err = s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
   524  	require.Equal(t, nil, err, "Error when checking allowed")
   525  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   526  }
   527  
   528  func TestFullPathDependence(t *testing.T) {
   529  	s := setupDNSProxyTestSuite(t)
   530  
   531  	// Test that we consider each of endpoint ID, destination SecID (via the
   532  	// selector in L7DataMap), destination port (set in the redirect itself) and
   533  	// the DNS name.
   534  	// The rules approximate:
   535  	// +------+--------+---------+----------+----------------+
   536  	// | From |   To   | DstPort | Protocol |    DNSNames    |
   537  	// +======+========+=========+===========================+
   538  	// | EP1  | DstID1 |      53 |    UDP   | *.ubuntu.com   |
   539  	// | EP1  | DstID1 |      53 |    TCP   | sub.ubuntu.com |
   540  	// | EP1  | DstID1 |      53 |    UDP   | aws.amazon.com |
   541  	// | EP1  | DstID2 |      53 |    UDP   | cilium.io      |
   542  	// | EP1  | *      |      54 |    UDP   | example.com    |
   543  	// | EP3  | DstID1 |      53 |    UDP   | example.com    |
   544  	// | EP3  | DstID3 |      53 |    UDP   | *              |
   545  	// | EP3  | DstID3 |      53 |    TCP   | example.com    |
   546  	// | EP3  | DstID4 |      53 |    UDP   | nil            |
   547  	// +------+--------+---------+---------------------------+
   548  	//
   549  	// Cases:
   550  	// +------+-------+--------+------+---------------------------+----------+----------------------------------------------------------------+
   551  	// | Case | From  |   To   | Port | Protocol |     Query      | Outcome  |                             Reason                             |
   552  	// +------+-------+--------+------+----------+----------------+----------+----------------------------------------------------------------+
   553  	// |    1 | EPID1 | DstID1 |   53 |    UDP   | www.ubuntu.com | Allowed  |                                                                |
   554  	// |    2 | EPID1 | DstID1 |   53 |    TCP   | www.ubuntu.com | Rejected | Protocol TCP only allows "sub.ubuntu.com"                      |
   555  	// |    3 | EPID1 | DstID1 |   53 |    TCP   | sub.ubuntu.com | Allowed  |                                                                |
   556  	// |    4 | EPID1 | DstID1 |   53 |    UDP   | sub.ubuntu.com | Allowed  |                                                                |
   557  	// |    5 | EPID1 | DstID1 |   54 |    UDP   | cilium.io      | Rejected | Port 54 only allows example.com                                |
   558  	// |    6 | EPID1 | DstID2 |   53 |    UDP   | cilium.io      | Allowed  |                                                                |
   559  	// |    7 | EPID1 | DstID2 |   53 |    UDP   | aws.amazon.com | Rejected | Only cilium.io is allowed with DstID2                          |
   560  	// |    8 | EPID1 | DstID1 |   54 |    UDP   | example.com    | Allowed  |                                                                |
   561  	// |    9 | EPID2 | DstID1 |   53 |    UDP   | cilium.io      | Rejected | EPID2 is not allowed as a source by any policy                 |
   562  	// |   10 | EPID3 | DstID1 |   53 |    UDP   | example.com    | Allowed  |                                                                |
   563  	// |   11 | EPID3 | DstID1 |   53 |    UDP   | aws.amazon.com | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com |
   564  	// |   12 | EPID3 | DstID1 |   54 |    UDP   | example.com    | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com |
   565  	// |   13 | EPID3 | DstID2 |   53 |    UDP   | example.com    | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com |
   566  	// |   14 | EPID3 | DstID3 |   53 |    UDP   | example.com    | Allowed  | Allowed due to wildcard match pattern                          |
   567  	// |   15 | EPID3 | DstID3 |   53 |    TCP   | example.com    | Allowed  |                                                                |
   568  	// |   16 | EPID3 | DstID3 |   53 |    TCP   | amazon.com     | Rejected | TCP protocol only allows "example.com"                         |
   569  	// |   17 | EPID3 | DstID4 |   53 |    TCP   | example.com    | Rejected | "example.com" only allowed for DstID3                          |
   570  	// |   18 | EPID3 | DstID4 |   53 |    UDP   | example.com    | Allowed  | Allowed due to a nil rule                                      |
   571  	// +------+-------+--------+------+----------------+----------+----------+----------------------------------------------------------------+
   572  
   573  	// Setup rules
   574  	//	| EP1  | DstID1 |      53 |  UDP  | *.ubuntu.com   |
   575  	//	| EP1  | DstID1 |      53 |  UDP  | aws.amazon.com |
   576  	//	| EP1  | DstID2 |      53 |  UDP  | cilium.io      |
   577  	err := s.proxy.UpdateAllowed(epID1, udpProtoPort53, policy.L7DataMap{
   578  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   579  			L7Rules: api.L7Rules{
   580  				DNS: []api.PortRuleDNS{
   581  					{MatchPattern: "*.ubuntu.com."},
   582  					{MatchPattern: "aws.amazon.com."},
   583  				},
   584  			},
   585  		},
   586  		cachedDstID2Selector: &policy.PerSelectorPolicy{
   587  			L7Rules: api.L7Rules{
   588  				DNS: []api.PortRuleDNS{
   589  					{MatchPattern: "cilium.io."},
   590  				},
   591  			},
   592  		},
   593  	})
   594  	require.Equal(t, nil, err, "Could not update with port 53 rules")
   595  
   596  	//      | EP1  | DstID1 |      53 |  TCP  | sub.ubuntu.com |
   597  	err = s.proxy.UpdateAllowed(epID1, tcpProtoPort53, policy.L7DataMap{
   598  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   599  			L7Rules: api.L7Rules{
   600  				DNS: []api.PortRuleDNS{
   601  					{MatchPattern: "sub.ubuntu.com."},
   602  				},
   603  			},
   604  		},
   605  	})
   606  	require.Equal(t, nil, err, "Could not update with rules")
   607  
   608  	//	| EP1  | DstID1 |      54 |  UDP  | example.com    |
   609  	err = s.proxy.UpdateAllowed(epID1, udpProtoPort54, policy.L7DataMap{
   610  		cachedWildcardSelector: &policy.PerSelectorPolicy{
   611  			L7Rules: api.L7Rules{
   612  				DNS: []api.PortRuleDNS{
   613  					{MatchPattern: "example.com."},
   614  				},
   615  			},
   616  		},
   617  	})
   618  	require.Equal(t, nil, err, "Could not update with rules")
   619  
   620  	// | EP3  | DstID1 |      53 |  UDP  | example.com    |
   621  	// | EP3  | DstID3 |      53 |  UDP  | *              |
   622  	// | EP3  | DstID4 |      53 |  UDP  | nil            |
   623  	err = s.proxy.UpdateAllowed(epID3, udpProtoPort53, policy.L7DataMap{
   624  		cachedDstID1Selector: &policy.PerSelectorPolicy{
   625  			L7Rules: api.L7Rules{
   626  				DNS: []api.PortRuleDNS{
   627  					{MatchPattern: "example.com."},
   628  				},
   629  			},
   630  		},
   631  		cachedDstID3Selector: &policy.PerSelectorPolicy{
   632  			L7Rules: api.L7Rules{
   633  				DNS: []api.PortRuleDNS{
   634  					{MatchPattern: "*"},
   635  				},
   636  			},
   637  		},
   638  		cachedDstID4Selector: nil,
   639  	})
   640  	require.Equal(t, nil, err, "Could not update with rules")
   641  
   642  	// | EP3  | DstID3 |      53 |  TCP  | example.com    |
   643  	err = s.proxy.UpdateAllowed(epID3, tcpProtoPort53, policy.L7DataMap{
   644  		cachedDstID3Selector: &policy.PerSelectorPolicy{
   645  			L7Rules: api.L7Rules{
   646  				DNS: []api.PortRuleDNS{
   647  					{MatchPattern: "example.com"},
   648  				},
   649  			},
   650  		},
   651  	})
   652  	require.Equal(t, nil, err, "Could not update with rules")
   653  
   654  	// Test cases
   655  	// Case 1 | EPID1 | DstID1 |   53 |  UDP  | www.ubuntu.com | Allowed
   656  	allowed, err := s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID1, nil, "www.ubuntu.com")
   657  	require.Equal(t, nil, err, "Error when checking allowed")
   658  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   659  
   660  	// Case 2 | EPID1 | DstID1 |   53 |    TCP   | www.ubuntu.com | Rejected | Protocol TCP only allows "sub.ubuntu.com"
   661  	allowed, err = s.proxy.CheckAllowed(epID1, tcpProtoPort53, dstID1, nil, "www.ubuntu.com")
   662  	require.Equal(t, nil, err, "Error when checking allowed")
   663  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   664  
   665  	// Case 3 | EPID1 | DstID1 |   53 |    TCP   | sub.ubuntu.com | Allowed
   666  	allowed, err = s.proxy.CheckAllowed(epID1, tcpProtoPort53, dstID1, nil, "sub.ubuntu.com")
   667  	require.Equal(t, nil, err, "Error when checking allowed")
   668  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   669  
   670  	// Case 4 | EPID1 | DstID1 |   53 |    UDP   | sub.ubuntu.com | Allowed
   671  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID1, nil, "sub.ubuntu.com")
   672  	require.Equal(t, nil, err, "Error when checking allowed")
   673  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   674  
   675  	// Case 5 | EPID1 | DstID1 |   54 |  UDP  | cilium.io      | Rejected | Port 54 only allows example.com
   676  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, nil, "cilium.io")
   677  	require.Equal(t, nil, err, "Error when checking allowed")
   678  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   679  
   680  	// Case 6 | EPID1 | DstID2 |   53 |  UDP  | cilium.io      | Allowed
   681  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, nil, "cilium.io")
   682  	require.Equal(t, nil, err, "Error when checking allowed")
   683  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   684  
   685  	// Case 7 | EPID1 | DstID2 |   53 |  UDP  | aws.amazon.com | Rejected | Only cilium.io is allowed with DstID2
   686  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, nil, "aws.amazon.com")
   687  	require.Equal(t, nil, err, "Error when checking allowed")
   688  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   689  
   690  	// Case 8 | EPID1 | DstID1 |   54 |  UDP  | example.com    | Allowed
   691  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, nil, "example.com")
   692  	require.Equal(t, nil, err, "Error when checking allowed")
   693  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   694  
   695  	// Case 9 | EPID2 | DstID1 |   53 |  UDP  | cilium.io      | Rejected | EPID2 is not allowed as a source by any policy
   696  	allowed, err = s.proxy.CheckAllowed(epID2, udpProtoPort53, dstID1, nil, "cilium.io")
   697  	require.Equal(t, nil, err, "Error when checking allowed")
   698  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   699  
   700  	// Case 10 | EPID3 | DstID1 |   53 |  UDP  | example.com    | Allowed
   701  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort53, dstID1, nil, "example.com")
   702  	require.Equal(t, nil, err, "Error when checking allowed")
   703  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   704  
   705  	// Case 11 | EPID3 | DstID1 |   53 |  UDP  | aws.amazon.com | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com
   706  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort53, dstID1, nil, "aws.amazon.io")
   707  	require.Equal(t, nil, err, "Error when checking allowed")
   708  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   709  
   710  	// Case 12 | EPID3 | DstID1 |   54 |  UDP  | example.com    | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com
   711  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort54, dstID1, nil, "example.com")
   712  	require.Equal(t, nil, err, "Error when checking allowed")
   713  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   714  
   715  	// Case 13 | EPID3 | DstID2 |   53 |  UDP  | example.com    | Rejected | EPID3 is only allowed to ask DstID1 on Port 53 for example.com
   716  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort53, dstID2, nil, "example.com")
   717  	require.Equal(t, nil, err, "Error when checking allowed")
   718  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   719  
   720  	// Case 14 | EPID3 | DstID3 |   53 |  UDP  | example.com    | Allowed due to wildcard match pattern
   721  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort53, dstID3, nil, "example.com")
   722  	require.Equal(t, nil, err, "Error when checking allowed")
   723  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   724  
   725  	// Case 15 | EPID3 | DstID3 |   53 |    TCP   | example.com    | Allowed
   726  	allowed, err = s.proxy.CheckAllowed(epID3, tcpProtoPort53, dstID3, nil, "example.com")
   727  	require.Equal(t, nil, err, "Error when checking allowed")
   728  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   729  
   730  	// Case 16 | EPID3 | DstID3 |   53 |    TCP   | amazon.com     | Rejected | TCP protocol only allows "example.com"
   731  	allowed, err = s.proxy.CheckAllowed(epID3, tcpProtoPort53, dstID3, nil, "amazon.com")
   732  	require.Equal(t, nil, err, "Error when checking allowed")
   733  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   734  
   735  	// Case 17 | EPID3 | DstID4 |   53 |    TCP   | example.com    | Rejected | "example.com" only allowed for DstID3
   736  	allowed, err = s.proxy.CheckAllowed(epID3, tcpProtoPort53, dstID4, nil, "example.com")
   737  	require.Equal(t, nil, err, "Error when checking allowed")
   738  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   739  
   740  	// Case 18 | EPID3 | DstID4 |   53 |  UDP  | example.com    | Allowed due to a nil rule
   741  	allowed, err = s.proxy.CheckAllowed(epID3, udpProtoPort53, dstID4, nil, "example.com")
   742  	require.Equal(t, nil, err, "Error when checking allowed")
   743  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   744  
   745  	// Get rules for restoration
   746  	expected1 := restore.DNSRules{
   747  		udpProtoPort53: restore.IPRules{
   748  			asIPRule(s.proxy.allowed[epID1][udpProtoPort53][cachedDstID1Selector], map[string]struct{}{"::": {}}),
   749  			asIPRule(s.proxy.allowed[epID1][udpProtoPort53][cachedDstID2Selector], map[string]struct{}{"127.0.0.1": {}, "127.0.0.2": {}}),
   750  		}.Sort(nil),
   751  		udpProtoPort54: restore.IPRules{
   752  			asIPRule(s.proxy.allowed[epID1][udpProtoPort54][cachedWildcardSelector], nil),
   753  		},
   754  		tcpProtoPort53: restore.IPRules{
   755  			asIPRule(s.proxy.allowed[epID1][tcpProtoPort53][cachedDstID1Selector], map[string]struct{}{"::": {}}),
   756  		},
   757  	}
   758  	restored1, _ := s.proxy.GetRules(uint16(epID1))
   759  	restored1.Sort(nil)
   760  	require.EqualValues(t, expected1, restored1)
   761  
   762  	expected2 := restore.DNSRules{}
   763  	restored2, _ := s.proxy.GetRules(uint16(epID2))
   764  	restored2.Sort(nil)
   765  	require.EqualValues(t, expected2, restored2)
   766  
   767  	expected3 := restore.DNSRules{
   768  		udpProtoPort53: restore.IPRules{
   769  			asIPRule(s.proxy.allowed[epID3][udpProtoPort53][cachedDstID1Selector], map[string]struct{}{"::": {}}),
   770  			asIPRule(s.proxy.allowed[epID3][udpProtoPort53][cachedDstID3Selector], map[string]struct{}{}),
   771  			asIPRule(s.proxy.allowed[epID3][udpProtoPort53][cachedDstID4Selector], map[string]struct{}{}),
   772  		}.Sort(nil),
   773  		tcpProtoPort53: restore.IPRules{
   774  			asIPRule(s.proxy.allowed[epID3][tcpProtoPort53][cachedDstID3Selector], map[string]struct{}{}),
   775  		},
   776  	}
   777  	restored3, _ := s.proxy.GetRules(uint16(epID3))
   778  	restored3.Sort(nil)
   779  	require.EqualValues(t, expected3, restored3)
   780  
   781  	// Test with limited set of allowed IPs
   782  	oldUsed := s.proxy.usedServers
   783  	s.proxy.usedServers = map[string]struct{}{"127.0.0.2": {}}
   784  
   785  	expected1b := restore.DNSRules{
   786  		udpProtoPort53: restore.IPRules{
   787  			asIPRule(s.proxy.allowed[epID1][udpProtoPort53][cachedDstID1Selector], map[string]struct{}{}),
   788  			asIPRule(s.proxy.allowed[epID1][udpProtoPort53][cachedDstID2Selector], map[string]struct{}{"127.0.0.2": {}}),
   789  		}.Sort(nil),
   790  		udpProtoPort54: restore.IPRules{
   791  			asIPRule(s.proxy.allowed[epID1][udpProtoPort54][cachedWildcardSelector], nil),
   792  		},
   793  		tcpProtoPort53: restore.IPRules{
   794  			asIPRule(s.proxy.allowed[epID1][tcpProtoPort53][cachedDstID1Selector], map[string]struct{}{}),
   795  		},
   796  	}
   797  	restored1b, _ := s.proxy.GetRules(uint16(epID1))
   798  	restored1b.Sort(nil)
   799  	require.EqualValues(t, expected1b, restored1b)
   800  
   801  	// unlimited again
   802  	s.proxy.usedServers = oldUsed
   803  
   804  	s.proxy.UpdateAllowed(epID1, udpProtoPort53, nil)
   805  	s.proxy.UpdateAllowed(epID1, udpProtoPort54, nil)
   806  	s.proxy.UpdateAllowed(epID1, tcpProtoPort53, nil)
   807  	_, exists := s.proxy.allowed[epID1]
   808  	require.Equal(t, false, exists)
   809  
   810  	_, exists = s.proxy.allowed[epID2]
   811  	require.Equal(t, false, exists)
   812  
   813  	s.proxy.UpdateAllowed(epID3, udpProtoPort53, nil)
   814  	s.proxy.UpdateAllowed(epID3, tcpProtoPort53, nil)
   815  	_, exists = s.proxy.allowed[epID3]
   816  	require.Equal(t, false, exists)
   817  
   818  	dstIP1 := (s.dnsServer.Listener.Addr()).(*net.TCPAddr).IP
   819  	dstIP2a := net.ParseIP("127.0.0.1")
   820  	dstIP2b := net.ParseIP("127.0.0.2")
   821  	dstIPrandom := net.ParseIP("127.0.0.42")
   822  
   823  	// Before restore: all rules removed above, everything is dropped
   824  	// Case 1 | EPID1 | DstID1 |   53 |  UDP  | www.ubuntu.com | Rejected | No rules
   825  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID1, dstIP1, "www.ubuntu.com")
   826  	require.Equal(t, nil, err, "Error when checking allowed")
   827  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   828  
   829  	// Case 2 | EPID1 | DstID1 |   54 |  UDP  | cilium.io      | Rejected | No rules
   830  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, dstIP1, "cilium.io")
   831  	require.Equal(t, nil, err, "Error when checking allowed")
   832  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   833  
   834  	// Case 3 | EPID1 | DstID2 |   53 |  UDP  | cilium.io      | Rejected | No rules
   835  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, dstIP2a, "cilium.io")
   836  	require.Equal(t, nil, err, "Error when checking allowed")
   837  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   838  
   839  	// Case 4 | EPID1 | DstID2 |   53 |  UDP  | aws.amazon.com | Rejected | No rules
   840  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, dstIP2b, "aws.amazon.com")
   841  	require.Equal(t, nil, err, "Error when checking allowed")
   842  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   843  
   844  	// Case 5 | EPID1 | DstID1 |   54 |  UDP  | example.com    | Rejected | No rules
   845  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, dstIP1, "example.com")
   846  	require.Equal(t, nil, err, "Error when checking allowed")
   847  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   848  
   849  	// Restore rules
   850  	ep1 := endpoint.NewTestEndpointWithState(t, s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), uint16(epID1), endpoint.StateReady)
   851  	ep1.DNSRulesV2 = restored1
   852  	s.proxy.RestoreRules(ep1)
   853  	_, exists = s.proxy.restored[epID1]
   854  	require.Equal(t, true, exists)
   855  
   856  	// Same tests with 2 (WORLD) dstID to make sure it is not used, but with correct destination IP
   857  
   858  	// Case 1 | EPID1 | dstIP1 |   53 |  UDP  | www.ubuntu.com | Allowed due to restored rules
   859  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP1, "www.ubuntu.com")
   860  	require.Equal(t, nil, err, "Error when checking allowed")
   861  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   862  
   863  	// Case 2 | EPID1 | dstIP1 |   54 |  UDP  | cilium.io      | Rejected due to restored rules | Port 54 only allows example.com
   864  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIP1, "cilium.io")
   865  	require.Equal(t, nil, err, "Error when checking allowed")
   866  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   867  
   868  	// Case 3 | EPID1 | dstIP2a |   53 |  UDP  | cilium.io      | Allowed due to restored rules
   869  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP2a, "cilium.io")
   870  	require.Equal(t, nil, err, "Error when checking allowed")
   871  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   872  
   873  	// Case 4 | EPID1 | dstIP2b |   53 |  UDP  | aws.amazon.com | Rejected due to restored rules | Only cilium.io is allowed with DstID2
   874  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP2b, "aws.amazon.com")
   875  	require.Equal(t, nil, err, "Error when checking allowed")
   876  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   877  
   878  	// Case 5 | EPID1 | dstIP1 |   54 |  UDP  | example.com    | Allowed due to restored rules
   879  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIP1, "example.com")
   880  	require.Equal(t, nil, err, "Error when checking allowed")
   881  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   882  
   883  	// make sure random IP is not allowed
   884  	// Case 5 | EPID1 | random IP |   53 |  UDP  | example.com    | Rejected due to restored rules
   885  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIPrandom, "example.com")
   886  	require.Equal(t, nil, err, "Error when checking allowed")
   887  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   888  
   889  	// make sure random destination IP is allowed in a wildcard selector
   890  	// Case 5 | EPID1 | random IP |   54 |  UDP  | example.com    | Allowed due to restored rules
   891  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIPrandom, "example.com")
   892  	require.Equal(t, nil, err, "Error when checking allowed")
   893  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   894  
   895  	// Restore rules for epID3
   896  	ep3 := endpoint.NewTestEndpointWithState(t, s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), uint16(epID3), endpoint.StateReady)
   897  	ep3.DNSRulesV2 = restored3
   898  	s.proxy.RestoreRules(ep3)
   899  	_, exists = s.proxy.restored[epID3]
   900  	require.Equal(t, true, exists)
   901  
   902  	// Set empty ruleset, check that restored rules were deleted in epID3
   903  	err = s.proxy.UpdateAllowed(epID3, udpProtoPort53, nil)
   904  	require.Equal(t, nil, err, "Could not update with rules")
   905  
   906  	_, exists = s.proxy.restored[epID3]
   907  	require.Equal(t, false, exists)
   908  
   909  	// epID1 still has restored rules
   910  	_, exists = s.proxy.restored[epID1]
   911  	require.Equal(t, true, exists)
   912  
   913  	// Marshal restored rules to JSON
   914  	jsn, err := json.Marshal(s.proxy.restored[epID1])
   915  	require.Equal(t, nil, err, "Could not marshal restored rules to json")
   916  
   917  	expected := `
   918  	{
   919  		"` + restore.MakeV2PortProto(53, tcpProto).String() + `":[{
   920  			"Re":"^(?:sub[.]ubuntu[.]com[.])$",
   921  			"IPs":{"::":{}}
   922  		}],
   923  		"` + restore.MakeV2PortProto(53, udpProto).String() + `":[{
   924  			"Re":"^(?:[-a-zA-Z0-9_]*[.]ubuntu[.]com[.]|aws[.]amazon[.]com[.])$",
   925  			"IPs":{"::":{}}
   926  		},{
   927  			"Re":"^(?:cilium[.]io[.])$",
   928  			"IPs":{"127.0.0.1":{},"127.0.0.2":{}}
   929  		}],
   930  		"` + restore.MakeV2PortProto(54, udpProto).String() + `":[{
   931  			"Re":"^(?:example[.]com[.])$",
   932  			"IPs":null
   933  		}]
   934  	}`
   935  	pretty := new(bytes.Buffer)
   936  	err = json.Compact(pretty, []byte(expected))
   937  	require.Equal(t, nil, err, "Could not compact expected json")
   938  	require.Equal(t, pretty.String(), string(jsn))
   939  
   940  	s.proxy.RemoveRestoredRules(uint16(epID1))
   941  	_, exists = s.proxy.restored[epID1]
   942  	require.Equal(t, false, exists)
   943  
   944  	// Before restore after marshal: previous restored rules are removed, everything is dropped
   945  	// Case 1 | EPID1 | DstID1 |   53 |  UDP  | www.ubuntu.com | Rejected | No rules
   946  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID1, dstIP1, "www.ubuntu.com")
   947  	require.Equal(t, nil, err, "Error when checking allowed")
   948  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   949  
   950  	// Case 2 | EPID1 | DstID1 |   54 |  UDP  | cilium.io      | Rejected | No rules
   951  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, dstIP1, "cilium.io")
   952  	require.Equal(t, nil, err, "Error when checking allowed")
   953  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   954  
   955  	// Case 3 | EPID1 | DstID2 |   53 |  UDP  | cilium.io      | Rejected | No rules
   956  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, dstIP2a, "cilium.io")
   957  	require.Equal(t, nil, err, "Error when checking allowed")
   958  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   959  
   960  	// Case 4 | EPID1 | DstID2 |   53 |  UDP  | aws.amazon.com | Rejected | No rules
   961  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, dstID2, dstIP2b, "aws.amazon.com")
   962  	require.Equal(t, nil, err, "Error when checking allowed")
   963  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   964  
   965  	// Case 5 | EPID1 | DstID1 |   54 |  UDP  | example.com    | Rejected | No rules
   966  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, dstID1, dstIP1, "example.com")
   967  	require.Equal(t, nil, err, "Error when checking allowed")
   968  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   969  
   970  	// Case 5 | EPID1 | random IP |   54 |  UDP  | example.com    | Rejected
   971  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIPrandom, "example.com")
   972  	require.Equal(t, nil, err, "Error when checking allowed")
   973  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
   974  
   975  	// Restore Unmarshaled rules
   976  	var rules restore.DNSRules
   977  	err = json.Unmarshal(jsn, &rules)
   978  	rules = rules.Sort(nil)
   979  	require.Equal(t, nil, err, "Could not unmarshal restored rules from json")
   980  	require.EqualValues(t, expected1, rules)
   981  
   982  	// Marshal again & compare
   983  	// Marshal restored rules to JSON
   984  	jsn2, err := json.Marshal(rules)
   985  	require.Equal(t, nil, err, "Could not marshal restored rules to json")
   986  	require.Equal(t, pretty.String(), string(jsn2))
   987  
   988  	ep1.DNSRulesV2 = rules
   989  	s.proxy.RestoreRules(ep1)
   990  	_, exists = s.proxy.restored[epID1]
   991  	require.Equal(t, true, exists)
   992  
   993  	// After restoration of JSON marshaled/unmarshaled rules
   994  
   995  	// Case 1 | EPID1 | dstIP1 |   53 |  UDP  | www.ubuntu.com | Allowed due to restored rules
   996  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP1, "www.ubuntu.com")
   997  	require.Equal(t, nil, err, "Error when checking allowed")
   998  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
   999  
  1000  	// Case 2 | EPID1 | dstIP1 |   54 |  UDP  | cilium.io      | Rejected due to restored rules | Port 54 only allows example.com
  1001  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIP1, "cilium.io")
  1002  	require.Equal(t, nil, err, "Error when checking allowed")
  1003  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
  1004  
  1005  	// Case 3 | EPID1 | dstIP2a |   53 |  UDP  | cilium.io      | Allowed due to restored rules
  1006  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP2a, "cilium.io")
  1007  	require.Equal(t, nil, err, "Error when checking allowed")
  1008  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
  1009  
  1010  	// Case 4 | EPID1 | dstIP2b |   53 |  UDP  | aws.amazon.com | Rejected due to restored rules | Only cilium.io is allowed with DstID2
  1011  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIP2b, "aws.amazon.com")
  1012  	require.Equal(t, nil, err, "Error when checking allowed")
  1013  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
  1014  
  1015  	// Case 5 | EPID1 | dstIP1 |   54 |  UDP  | example.com    | Allowed due to restored rules
  1016  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIP1, "example.com")
  1017  	require.Equal(t, nil, err, "Error when checking allowed")
  1018  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
  1019  
  1020  	// make sure random IP is not allowed
  1021  	// Case 5 | EPID1 | random IP |   53 |  UDP  | example.com    | Rejected due to restored rules
  1022  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort53, 2, dstIPrandom, "example.com")
  1023  	require.Equal(t, nil, err, "Error when checking allowed")
  1024  	require.Equal(t, false, allowed, "request was allowed when it should be rejected")
  1025  
  1026  	// make sure random IP is allowed on a wildcard
  1027  	// Case 5 | EPID1 | random IP |   54 |  UDP  | example.com    | Allowed due to restored rules
  1028  	allowed, err = s.proxy.CheckAllowed(epID1, udpProtoPort54, 2, dstIPrandom, "example.com")
  1029  	require.Equal(t, nil, err, "Error when checking allowed")
  1030  	require.Equal(t, true, allowed, "request was rejected when it should be allowed")
  1031  
  1032  	s.proxy.RemoveRestoredRules(uint16(epID1))
  1033  	_, exists = s.proxy.restored[epID1]
  1034  	require.Equal(t, false, exists)
  1035  }
  1036  
  1037  func TestRestoredEndpoint(t *testing.T) {
  1038  	s := setupDNSProxyTestSuite(t)
  1039  
  1040  	// Respond with an actual answer for the query. This also tests that the
  1041  	// connection was forwarded via the correct protocol (tcp/udp) because we
  1042  	// connet with TCP, and the server only listens on TCP.
  1043  
  1044  	name := "cilium.io."
  1045  	pattern := "*.cilium.com."
  1046  	l7map := policy.L7DataMap{
  1047  		cachedDstID1Selector: &policy.PerSelectorPolicy{
  1048  			L7Rules: api.L7Rules{
  1049  				DNS: []api.PortRuleDNS{{MatchName: name}, {MatchPattern: pattern}},
  1050  			},
  1051  		},
  1052  	}
  1053  	queries := []string{name, strings.ReplaceAll(pattern, "*", "sub")}
  1054  
  1055  	err := s.proxy.UpdateAllowed(epID1, dstPortProto, l7map)
  1056  	require.Equal(t, nil, err, "Could not update with rules")
  1057  	for _, query := range queries {
  1058  		allowed, err := s.proxy.CheckAllowed(epID1, dstPortProto, dstID1, nil, query)
  1059  		require.Equal(t, nil, err, "Error when checking allowed query: %q", query)
  1060  		require.Equal(t, true, allowed, "request was rejected when it should be allowed for query: %q", query)
  1061  	}
  1062  
  1063  	// 1st request
  1064  	for _, query := range queries {
  1065  		request := new(dns.Msg)
  1066  		request.SetQuestion(query, dns.TypeA)
  1067  		response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
  1068  		require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v) (query: %q)", rtt, query)
  1069  		require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s (query: %q)", response, query)
  1070  		require.Equal(t, query+"\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
  1071  	}
  1072  
  1073  	for _, query := range queries {
  1074  		request := new(dns.Msg)
  1075  		request.SetQuestion(query, dns.TypeA)
  1076  		response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
  1077  		require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v) (query: %q)", rtt, query)
  1078  		require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s (query: %q)", response, query)
  1079  		require.Equal(t, query+"\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
  1080  	}
  1081  
  1082  	// Get restored rules
  1083  	restored, _ := s.proxy.GetRules(uint16(epID1))
  1084  	restored.Sort(nil)
  1085  
  1086  	// remove rules
  1087  	err = s.proxy.UpdateAllowed(epID1, dstPortProto, nil)
  1088  	require.Equal(t, nil, err, "Could not remove rules")
  1089  
  1090  	// 2nd request, refused due to no rules
  1091  	for _, query := range queries {
  1092  		request := new(dns.Msg)
  1093  		request.SetQuestion(query, dns.TypeA)
  1094  		response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
  1095  		require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v) (query: %q)", rtt, query)
  1096  		require.Equal(t, 0, len(response.Answer), "Proxy returned incorrect number of answer RRs %s (query: %q)", response, query)
  1097  		require.Equal(t, dns.RcodeRefused, response.Rcode, "DNS request from test client was not rejected when it should be blocked (query: %q)", query)
  1098  	}
  1099  
  1100  	// restore rules, set the mock to restoring state
  1101  	s.restoring = true
  1102  	ep1 := endpoint.NewTestEndpointWithState(t, s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), uint16(epID1), endpoint.StateReady)
  1103  	ep1.IPv4 = netip.MustParseAddr("127.0.0.1")
  1104  	ep1.IPv6 = netip.MustParseAddr("::1")
  1105  	ep1.DNSRulesV2 = restored
  1106  	s.proxy.RestoreRules(ep1)
  1107  	_, exists := s.proxy.restored[epID1]
  1108  	require.Equal(t, true, exists)
  1109  
  1110  	// 3nd request, answered due to restored Endpoint and rules being found
  1111  	for _, query := range queries {
  1112  		request := new(dns.Msg)
  1113  		request.SetQuestion(query, dns.TypeA)
  1114  		response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
  1115  		require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v) (query: %q)", rtt, query)
  1116  		require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s (query: %q)", response, query)
  1117  		require.Equal(t, query+"\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
  1118  	}
  1119  	// cleanup
  1120  	s.proxy.RemoveRestoredRules(uint16(epID1))
  1121  	_, exists = s.proxy.restored[epID1]
  1122  	require.Equal(t, false, exists)
  1123  
  1124  	invalidRePattern := "invalid-re-pattern((*"
  1125  	validRePattern := "^this[.]domain[.]com[.]$"
  1126  
  1127  	// extract the port the DNS-server is listening on by looking at the restored rules. The port is non-deterministic
  1128  	// since it's listening on :0
  1129  	require.Equal(t, 1, len(restored), "GetRules is expected to return rules for one port but returned for %d", len(restored))
  1130  	portProto := maps.Keys(restored)[0]
  1131  
  1132  	// Insert one valid and one invalid pattern and ensure that the valid one works
  1133  	// and that the invalid one doesn't interfere with the other rules.
  1134  	restored[portProto] = append(restored[portProto],
  1135  		restore.IPRule{Re: restore.RuleRegex{Pattern: &invalidRePattern}},
  1136  		restore.IPRule{Re: restore.RuleRegex{Pattern: &validRePattern}},
  1137  	)
  1138  	ep1.DNSRulesV2 = restored
  1139  	s.proxy.RestoreRules(ep1)
  1140  	_, exists = s.proxy.restored[epID1]
  1141  	require.Equal(t, true, exists)
  1142  
  1143  	// 4nd request, answered due to restored Endpoint and rules being found, including domain matched by new regex
  1144  	for _, query := range append(queries, "this.domain.com.") {
  1145  		request := new(dns.Msg)
  1146  		request.SetQuestion(query, dns.TypeA)
  1147  		response, rtt, err := s.dnsTCPClient.Exchange(request, s.proxy.DNSServers[0].Listener.Addr().String())
  1148  		require.NoErrorf(t, err, "DNS request from test client failed when it should succeed (RTT: %v) (query: %q)", rtt, query)
  1149  		require.Equal(t, 1, len(response.Answer), "Proxy returned incorrect number of answer RRs %s (query: %q)", response, query)
  1150  		require.Equal(t, query+"\t60\tIN\tA\t1.1.1.1", response.Answer[0].String(), "Proxy returned incorrect RRs")
  1151  	}
  1152  
  1153  	// cleanup
  1154  	s.proxy.RemoveRestoredRules(uint16(epID1))
  1155  	_, exists = s.proxy.restored[epID1]
  1156  	require.Equal(t, false, exists)
  1157  
  1158  	s.restoring = false
  1159  }
  1160  
  1161  func TestProxyRequestContext_IsTimeout(t *testing.T) {
  1162  	p := new(ProxyRequestContext)
  1163  	p.Err = fmt.Errorf("sample err: %w", context.DeadlineExceeded)
  1164  	require.Equal(t, true, p.IsTimeout())
  1165  
  1166  	// Assert that failing to wrap the error properly (by using '%w') causes
  1167  	// IsTimeout() to return the wrong value.
  1168  	//nolint:errorlint
  1169  	p.Err = fmt.Errorf("sample err: %s", context.DeadlineExceeded)
  1170  	require.Equal(t, false, p.IsTimeout())
  1171  
  1172  	p.Err = ErrFailedAcquireSemaphore{}
  1173  	require.Equal(t, true, p.IsTimeout())
  1174  	p.Err = ErrTimedOutAcquireSemaphore{
  1175  		gracePeriod: 1 * time.Second,
  1176  	}
  1177  	require.Equal(t, true, p.IsTimeout())
  1178  }
  1179  
  1180  type selectorMock struct {
  1181  	key string
  1182  }
  1183  
  1184  func (t selectorMock) GetSelections() identity.NumericIdentitySlice {
  1185  	panic("implement me")
  1186  }
  1187  
  1188  func (t selectorMock) GetMetadataLabels() labels.LabelArray {
  1189  	panic("implement me")
  1190  }
  1191  
  1192  func (t selectorMock) Selects(nid identity.NumericIdentity) bool {
  1193  	panic("implement me")
  1194  }
  1195  
  1196  func (t selectorMock) IsWildcard() bool {
  1197  	panic("implement me")
  1198  }
  1199  
  1200  func (t selectorMock) IsNone() bool {
  1201  	panic("implement me")
  1202  }
  1203  
  1204  func (t selectorMock) String() string {
  1205  	return t.key
  1206  }
  1207  
  1208  func Benchmark_perEPAllow_setPortRulesForID(b *testing.B) {
  1209  	const (
  1210  		nEPs              = 10000
  1211  		nEPsAtOnce        = 60
  1212  		nMatchPatterns    = 30
  1213  		nMatchNames       = 600
  1214  		everyNIsEqual     = 10
  1215  		everyNHasWildcard = 20
  1216  	)
  1217  	runtime.GC()
  1218  	initialHeap := getMemStats().HeapInuse
  1219  	rulesPerEP := make([]policy.L7DataMap, 0, nEPs)
  1220  
  1221  	var defaultRules []api.PortRuleDNS
  1222  	for i := 0; i < nMatchPatterns; i++ {
  1223  		defaultRules = append(defaultRules, api.PortRuleDNS{MatchPattern: "*.bar" + strconv.Itoa(i) + "another.very.long.domain.here"})
  1224  	}
  1225  	for i := 0; i < nMatchNames; i++ {
  1226  		defaultRules = append(defaultRules, api.PortRuleDNS{MatchName: strconv.Itoa(i) + "very.long.domain.containing.a.lot.of.chars"})
  1227  	}
  1228  
  1229  	for i := 0; i < nEPs; i++ {
  1230  		commonRules := append([]api.PortRuleDNS{}, defaultRules...)
  1231  		if i%everyNIsEqual != 0 {
  1232  			commonRules = append(
  1233  				commonRules,
  1234  				api.PortRuleDNS{MatchName: "custom-for-this-one" + strconv.Itoa(i) + ".domain.tld"},
  1235  				api.PortRuleDNS{MatchPattern: "custom2-for-this-one*" + strconv.Itoa(i) + ".domain.tld"},
  1236  			)
  1237  		}
  1238  		if (i+1)%everyNHasWildcard == 0 {
  1239  			commonRules = append(commonRules, api.PortRuleDNS{MatchPattern: "*"})
  1240  		}
  1241  		psp := &policy.PerSelectorPolicy{L7Rules: api.L7Rules{DNS: commonRules}}
  1242  		rulesPerEP = append(rulesPerEP, policy.L7DataMap{new(selectorMock): psp, new(selectorMock): psp})
  1243  	}
  1244  
  1245  	pea := perEPAllow{}
  1246  	c := regexCache{}
  1247  	b.ReportAllocs()
  1248  	b.StopTimer()
  1249  	b.ResetTimer()
  1250  	for i := 0; i < b.N; i++ {
  1251  		for epID := uint64(0); epID < nEPs; epID++ {
  1252  			pea.setPortRulesForID(c, epID, udpProtoPort8053, nil)
  1253  		}
  1254  		b.StartTimer()
  1255  		for epID, rules := range rulesPerEP {
  1256  			if epID >= nEPsAtOnce {
  1257  				pea.setPortRulesForID(c, uint64(epID)-nEPsAtOnce, udpProtoPort8053, nil)
  1258  			}
  1259  			pea.setPortRulesForID(c, uint64(epID), udpProtoPort8053, rules)
  1260  		}
  1261  		b.StopTimer()
  1262  	}
  1263  	runtime.GC()
  1264  	// This is a ~proxy metric for the growth of heap per b.N. We call it here instead of the loop to
  1265  	// ensure we also count things like the strings "borrowed" from rulesPerEP
  1266  	b.ReportMetric(float64(getMemStats().HeapInuse-initialHeap), "B(HeapInUse)/op")
  1267  
  1268  	for epID := uint64(0); epID < nEPs; epID++ {
  1269  		pea.setPortRulesForID(c, epID, udpProtoPort8053, nil)
  1270  	}
  1271  	if len(pea) > 0 {
  1272  		b.Fail()
  1273  	}
  1274  	b.StopTimer()
  1275  	// Remove all the inserted rules to ensure the cache goes down to zero entries
  1276  	for epID := uint64(0); epID < 20; epID++ {
  1277  		pea.setPortRulesForID(c, epID, udpProtoPort8053, nil)
  1278  	}
  1279  	if len(pea) > 0 || len(c) > 0 {
  1280  		b.Fail()
  1281  	}
  1282  }
  1283  
  1284  func Benchmark_perEPAllow_setPortRulesForID_large(b *testing.B) {
  1285  	b.Skip()
  1286  	numEPs := uint64(20)
  1287  	cnpFile := "testdata/cnps-large.yaml"
  1288  
  1289  	runtime.GC()
  1290  	m := getMemStats()
  1291  	fmt.Printf("Before Setup (N=%v,EPs=%d)\n", b.N, numEPs)
  1292  
  1293  	fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
  1294  	fmt.Printf("\tHeapInuse = %v MiB", bToMb(m.HeapInuse))
  1295  	fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
  1296  	fmt.Printf("\tNumGC = %v\n", m.NumGC)
  1297  
  1298  	bb, err := os.ReadFile(cnpFile)
  1299  	if err != nil {
  1300  		b.Fatal(err)
  1301  	}
  1302  	var cnpList v2.CiliumNetworkPolicyList
  1303  	if err := yaml.Unmarshal(bb, &cnpList); err != nil {
  1304  		b.Fatal(err)
  1305  	}
  1306  
  1307  	rules := policy.L7DataMap{}
  1308  
  1309  	addEgress := func(e []api.EgressRule) {
  1310  		var portRuleDNS []api.PortRuleDNS
  1311  		for _, egress := range e {
  1312  			if egress.ToPorts != nil {
  1313  				for _, ports := range egress.ToPorts {
  1314  					if ports.Rules != nil {
  1315  						for _, dns := range ports.Rules.DNS {
  1316  							if len(dns.MatchPattern) > 0 {
  1317  								portRuleDNS = append(portRuleDNS, api.PortRuleDNS{
  1318  									MatchPattern: dns.MatchPattern,
  1319  								})
  1320  							}
  1321  							if len(dns.MatchName) > 0 {
  1322  								portRuleDNS = append(portRuleDNS, api.PortRuleDNS{
  1323  									MatchName: dns.MatchName,
  1324  								})
  1325  							}
  1326  						}
  1327  					}
  1328  				}
  1329  			}
  1330  		}
  1331  		rules[new(selectorMock)] = &policy.PerSelectorPolicy{
  1332  			L7Rules: api.L7Rules{
  1333  				DNS: portRuleDNS,
  1334  			},
  1335  		}
  1336  	}
  1337  
  1338  	for _, cnp := range cnpList.Items {
  1339  		if cnp.Specs != nil {
  1340  			for _, spec := range cnp.Specs {
  1341  				if spec.Egress != nil {
  1342  					addEgress(spec.Egress)
  1343  				}
  1344  			}
  1345  		}
  1346  		if cnp.Spec != nil {
  1347  			if cnp.Spec.Egress != nil {
  1348  				addEgress(cnp.Spec.Egress)
  1349  			}
  1350  		}
  1351  	}
  1352  
  1353  	runtime.GC()
  1354  	m = getMemStats()
  1355  	fmt.Printf("Before Test (N=%v,EPs=%d)\n", b.N, numEPs)
  1356  
  1357  	fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
  1358  	fmt.Printf("\tHeapInuse = %v MiB", bToMb(m.HeapInuse))
  1359  	fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
  1360  	fmt.Printf("\tNumGC = %v\n", m.NumGC)
  1361  
  1362  	pea := perEPAllow{}
  1363  	c := regexCache{}
  1364  	b.ReportAllocs()
  1365  	b.ResetTimer()
  1366  	for i := 0; i < b.N; i++ {
  1367  		for epID := uint64(0); epID < numEPs; epID++ {
  1368  			pea.setPortRulesForID(c, epID, udpProtoPort8053, rules)
  1369  		}
  1370  	}
  1371  	b.StopTimer()
  1372  
  1373  	// Uncomment to see the HeapInUse from only the regexp cache
  1374  	// for epID := uint64(0); epID < numEPs; epID++ {
  1375  	//	 pea.setPortRulesForID(epID, udpProtoPort8053, nil)
  1376  	// }
  1377  
  1378  	// Explicitly run gc to ensure we measure what we want
  1379  	runtime.GC()
  1380  	m = getMemStats()
  1381  	// Explicitly keep a reference to "pea" to keep it on the heap
  1382  	// so that we can measure it before it is garbage collected.
  1383  	fmt.Printf("After Test (N=%v,EPs=%d)\n", b.N, len(pea))
  1384  	fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
  1385  	fmt.Printf("\tHeapInuse = %v MiB", bToMb(m.HeapInuse))
  1386  	fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
  1387  	fmt.Printf("\tNumGC = %v\n", m.NumGC)
  1388  	// Remove all the inserted rules to ensure both indexes go to zero entries
  1389  	for epID := uint64(0); epID < numEPs; epID++ {
  1390  		pea.setPortRulesForID(c, epID, udpProtoPort8053, nil)
  1391  	}
  1392  	if len(pea) > 0 || len(c) > 0 {
  1393  		b.Fail()
  1394  	}
  1395  }
  1396  
  1397  func getMemStats() runtime.MemStats {
  1398  	var m runtime.MemStats
  1399  	runtime.ReadMemStats(&m)
  1400  	return m
  1401  }
  1402  
  1403  func bToMb(b uint64) uint64 {
  1404  	return b / 1024 / 1024
  1405  }