github.com/zhyoulun/cilium@v1.6.12/pkg/policy/rule.go (about)

     1  // Copyright 2016-2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package policy
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  
    21  	"github.com/cilium/cilium/pkg/identity"
    22  	"github.com/cilium/cilium/pkg/labels"
    23  	"github.com/cilium/cilium/pkg/lock"
    24  	"github.com/cilium/cilium/pkg/option"
    25  	"github.com/cilium/cilium/pkg/policy/api"
    26  
    27  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  )
    29  
    30  type rule struct {
    31  	api.Rule
    32  
    33  	metadata *ruleMetadata
    34  }
    35  
    36  type ruleMetadata struct {
    37  	// mutex protects all fields in this type.
    38  	Mutex lock.RWMutex
    39  
    40  	// IdentitySelected is a cache that maps from an identity to whether
    41  	// this rule selects that identity.
    42  	IdentitySelected map[identity.NumericIdentity]bool
    43  }
    44  
    45  func newRuleMetadata() *ruleMetadata {
    46  	return &ruleMetadata{
    47  		IdentitySelected: make(map[identity.NumericIdentity]bool),
    48  	}
    49  }
    50  
    51  func (m *ruleMetadata) delete(identity *identity.Identity) {
    52  	m.Mutex.Lock()
    53  	defer m.Mutex.Unlock()
    54  	delete(m.IdentitySelected, identity.ID)
    55  }
    56  
    57  func (r *rule) String() string {
    58  	return fmt.Sprintf("%v", r.EndpointSelector)
    59  }
    60  
    61  func (l4 *L4Filter) mergeCachedSelectors(from *L4Filter, selectorCache *SelectorCache) {
    62  	for _, cs := range from.CachedSelectors {
    63  		if l4.CachedSelectors.Insert(cs) {
    64  			// Update selector owner to the existingFilter
    65  			selectorCache.ChangeUser(cs, from, l4)
    66  		} else {
    67  			// selector was already in existingFilter.CachedSelectors, release
    68  			selectorCache.RemoveSelector(cs, from)
    69  		}
    70  	}
    71  	from.CachedSelectors = nil
    72  }
    73  
    74  func mergePortProto(ctx *SearchContext, existingFilter, filterToMerge *L4Filter, selectorCache *SelectorCache) error {
    75  	// Handle cases where filter we are merging new rule with, new rule itself
    76  	// allows all traffic on L3, or both rules allow all traffic on L3.
    77  	if existingFilter.AllowsAllAtL3() {
    78  		// Case 1: existing filter selects all endpoints, which means that this filter
    79  		// can now simply select all endpoints.
    80  		//
    81  		// Release references held by filterToMerge.CachedSelectors
    82  		if !filterToMerge.HasL3DependentL7Rules() {
    83  			selectorCache.RemoveSelectors(filterToMerge.CachedSelectors, filterToMerge)
    84  			filterToMerge.CachedSelectors = nil
    85  		}
    86  	} else {
    87  		// Case 2: Merge selectors from filterToMerge to the existingFilter.
    88  		if filterToMerge.AllowsAllAtL3() {
    89  			// Allowing all, release selectors from existingFilter
    90  			if !existingFilter.HasL3DependentL7Rules() {
    91  				selectorCache.RemoveSelectors(existingFilter.CachedSelectors, existingFilter)
    92  				existingFilter.CachedSelectors = nil
    93  			}
    94  			existingFilter.allowsAllAtL3 = true
    95  		}
    96  	}
    97  	existingFilter.mergeCachedSelectors(filterToMerge, selectorCache)
    98  
    99  	// Merge the L7-related data from the arguments provided to this function
   100  	// with the existing L7-related data already in the filter.
   101  	if filterToMerge.L7Parser != ParserTypeNone {
   102  		if existingFilter.L7Parser == ParserTypeNone {
   103  			existingFilter.L7Parser = filterToMerge.L7Parser
   104  		} else if filterToMerge.L7Parser != existingFilter.L7Parser {
   105  			ctx.PolicyTrace("   Merge conflict: mismatching parsers %s/%s\n", filterToMerge.L7Parser, existingFilter.L7Parser)
   106  			return fmt.Errorf("cannot merge conflicting L7 parsers (%s/%s)", filterToMerge.L7Parser, existingFilter.L7Parser)
   107  		}
   108  	}
   109  
   110  	for cs, newL7Rules := range filterToMerge.L7RulesPerEp {
   111  		if l7Rules, ok := existingFilter.L7RulesPerEp[cs]; ok {
   112  			switch {
   113  			case len(newL7Rules.HTTP) > 0:
   114  				if len(l7Rules.Kafka) > 0 || len(l7Rules.DNS) > 0 || l7Rules.L7Proto != "" {
   115  					ctx.PolicyTrace("   Merge conflict: mismatching L7 rule types.\n")
   116  					return fmt.Errorf("cannot merge conflicting L7 rule types")
   117  				}
   118  
   119  				for _, newRule := range newL7Rules.HTTP {
   120  					if !newRule.Exists(l7Rules) {
   121  						l7Rules.HTTP = append(l7Rules.HTTP, newRule)
   122  					}
   123  				}
   124  			case len(newL7Rules.Kafka) > 0:
   125  				if len(l7Rules.HTTP) > 0 || len(l7Rules.DNS) > 0 || l7Rules.L7Proto != "" {
   126  					ctx.PolicyTrace("   Merge conflict: mismatching L7 rule types.\n")
   127  					return fmt.Errorf("cannot merge conflicting L7 rule types")
   128  				}
   129  
   130  				for _, newRule := range newL7Rules.Kafka {
   131  					if !newRule.Exists(l7Rules) {
   132  						l7Rules.Kafka = append(l7Rules.Kafka, newRule)
   133  					}
   134  				}
   135  			case newL7Rules.L7Proto != "":
   136  				if len(l7Rules.Kafka) > 0 || len(l7Rules.HTTP) > 0 || len(l7Rules.DNS) > 0 || (l7Rules.L7Proto != "" && l7Rules.L7Proto != newL7Rules.L7Proto) {
   137  					ctx.PolicyTrace("   Merge conflict: mismatching L7 rule types.\n")
   138  					return fmt.Errorf("cannot merge conflicting L7 rule types")
   139  				}
   140  				if l7Rules.L7Proto == "" {
   141  					l7Rules.L7Proto = newL7Rules.L7Proto
   142  				}
   143  
   144  				for _, newRule := range newL7Rules.L7 {
   145  					if !newRule.Exists(l7Rules) {
   146  						l7Rules.L7 = append(l7Rules.L7, newRule)
   147  					}
   148  				}
   149  			case len(newL7Rules.DNS) > 0:
   150  				if len(l7Rules.HTTP) > 0 || len(l7Rules.Kafka) > 0 || len(l7Rules.L7) > 0 {
   151  					ctx.PolicyTrace("   Merge conflict: mismatching L7 rule types.\n")
   152  					return fmt.Errorf("cannot merge conflicting L7 rule types")
   153  				}
   154  
   155  				for _, newRule := range newL7Rules.DNS {
   156  					if !newRule.Exists(l7Rules) {
   157  						l7Rules.DNS = append(l7Rules.DNS, newRule)
   158  					}
   159  				}
   160  
   161  			default:
   162  				ctx.PolicyTrace("   No L7 rules to merge.\n")
   163  			}
   164  			existingFilter.L7RulesPerEp[cs] = l7Rules
   165  		} else {
   166  			existingFilter.L7RulesPerEp[cs] = newL7Rules
   167  		}
   168  	}
   169  	return nil
   170  }
   171  
   172  // mergeIngressPortProto merges all rules which share the same port & protocol that
   173  // select a given set of endpoints. It updates the L4Filter mapped to by the specified
   174  // port and protocol with the contents of the provided PortRule. If the rule
   175  // being merged has conflicting L7 rules with those already in the provided
   176  // L4PolicyMap for the specified port-protocol tuple, it returns an error.
   177  //
   178  // If any rules contain L7 rules that select Host and we should accept
   179  // all traffic from host (hostWildcardL7 == true) the L7 rules will be
   180  // translated into L7 wildcards (ie, traffic will be forwarded to the
   181  // proxy for endpoints matching those labels, but the proxy will allow
   182  // all such traffic).
   183  func mergeIngressPortProto(ctx *SearchContext, endpoints api.EndpointSelectorSlice, hostWildcardL7 bool, r api.PortRule, p api.PortProtocol,
   184  	proto api.L4Proto, ruleLabels labels.LabelArray, resMap L4PolicyMap, selectorCache *SelectorCache) (int, error) {
   185  
   186  	key := p.Port + "/" + string(proto)
   187  	existingFilter, ok := resMap[key]
   188  	if !ok {
   189  		resMap[key] = createL4IngressFilter(endpoints, hostWildcardL7, r, p, proto, ruleLabels, selectorCache)
   190  		return 1, nil
   191  	}
   192  
   193  	// Create a new L4Filter based off of the arguments provided to this function
   194  	// for merging with the filter which is already in the policy map.
   195  	filterToMerge := createL4IngressFilter(endpoints, hostWildcardL7, r, p, proto, ruleLabels, selectorCache)
   196  
   197  	if err := mergePortProto(ctx, existingFilter, filterToMerge, selectorCache); err != nil {
   198  		filterToMerge.detach(selectorCache)
   199  		return 0, err
   200  	}
   201  	existingFilter.DerivedFromRules = append(existingFilter.DerivedFromRules, ruleLabels)
   202  	resMap[key] = existingFilter
   203  
   204  	return 1, nil
   205  }
   206  
   207  func traceL3(ctx *SearchContext, peerEndpoints api.EndpointSelectorSlice, direction string) {
   208  	var result bytes.Buffer
   209  
   210  	// Requirements will be cloned into every selector, only trace them once.
   211  	if len(peerEndpoints[0].MatchExpressions) > 0 {
   212  		sel := peerEndpoints[0]
   213  		result.WriteString("    Enforcing requirements ")
   214  		result.WriteString(fmt.Sprintf("%+v", sel.MatchExpressions))
   215  		result.WriteString("\n")
   216  	}
   217  	// EndpointSelector
   218  	for _, sel := range peerEndpoints {
   219  		if len(sel.MatchLabels) > 0 {
   220  			result.WriteString("    Allows ")
   221  			result.WriteString(direction)
   222  			result.WriteString(" labels ")
   223  			result.WriteString(sel.String())
   224  			result.WriteString("\n")
   225  		}
   226  	}
   227  	ctx.PolicyTrace(result.String())
   228  }
   229  
   230  // portRulesCoverContext determines whether L4 portions of rules cover the
   231  // specified port models.
   232  //
   233  // Returns true if the list of ports is 0, or the rules match the ports.
   234  func rulePortsCoverSearchContext(ports []api.PortProtocol, ctx *SearchContext) bool {
   235  	if len(ctx.DPorts) == 0 {
   236  		return true
   237  	}
   238  	for _, p := range ports {
   239  		for _, dp := range ctx.DPorts {
   240  			tracePort := api.PortProtocol{
   241  				Port:     fmt.Sprintf("%d", dp.Port),
   242  				Protocol: api.L4Proto(dp.Protocol),
   243  			}
   244  			if p.Covers(tracePort) {
   245  				return true
   246  			}
   247  		}
   248  	}
   249  	return false
   250  }
   251  
   252  func mergeIngress(ctx *SearchContext, fromEndpoints api.EndpointSelectorSlice, toPorts []api.PortRule, ruleLabels labels.LabelArray, resMap L4PolicyMap, selectorCache *SelectorCache) (int, error) {
   253  	found := 0
   254  
   255  	if ctx.From != nil && len(fromEndpoints) > 0 {
   256  		if ctx.TraceEnabled() {
   257  			traceL3(ctx, fromEndpoints, "from")
   258  		}
   259  		if !fromEndpoints.Matches(ctx.From) {
   260  			ctx.PolicyTrace("      No label match for %s", ctx.From)
   261  			return 0, nil
   262  		}
   263  		ctx.PolicyTrace("      Found all required labels")
   264  	}
   265  
   266  	// Daemon options may induce L3 allows for host/world. In this case, if
   267  	// we find any L7 rules matching host/world then we need to turn any L7
   268  	// restrictions on these endpoints into L7 allow-all so that the
   269  	// traffic is always allowed, but is also always redirected through the
   270  	// proxy
   271  	hostWildcardL7 := option.Config.AlwaysAllowLocalhost()
   272  
   273  	var (
   274  		cnt int
   275  		err error
   276  	)
   277  
   278  	// L3-only rule (with requirements folded into fromEndpoints).
   279  	if len(toPorts) == 0 && len(fromEndpoints) > 0 {
   280  		cnt, err = mergeIngressPortProto(ctx, fromEndpoints, hostWildcardL7, api.PortRule{}, api.PortProtocol{Port: "0", Protocol: api.ProtoAny}, api.ProtoAny, ruleLabels, resMap, selectorCache)
   281  		if err != nil {
   282  			return found, err
   283  		}
   284  	}
   285  
   286  	found += cnt
   287  
   288  	for _, r := range toPorts {
   289  		// For L4 Policy, an empty slice of EndpointSelector indicates that the
   290  		// rule allows all at L3 - explicitly specify this by creating a slice
   291  		// with the WildcardEndpointSelector.
   292  		if len(fromEndpoints) == 0 {
   293  			fromEndpoints = api.EndpointSelectorSlice{api.WildcardEndpointSelector}
   294  		}
   295  
   296  		ctx.PolicyTrace("      Allows port %v\n", r.Ports)
   297  		if !rulePortsCoverSearchContext(r.Ports, ctx) {
   298  			ctx.PolicyTrace("        No port match found\n")
   299  			continue
   300  		}
   301  		if r.Rules != nil && r.Rules.L7Proto != "" {
   302  			ctx.PolicyTrace("        l7proto: \"%s\"\n", r.Rules.L7Proto)
   303  		}
   304  		if !r.Rules.IsEmpty() {
   305  			for _, l7 := range r.Rules.HTTP {
   306  				ctx.PolicyTrace("          %+v\n", l7)
   307  			}
   308  			for _, l7 := range r.Rules.Kafka {
   309  				ctx.PolicyTrace("          %+v\n", l7)
   310  			}
   311  			for _, l7 := range r.Rules.L7 {
   312  				ctx.PolicyTrace("          %+v\n", l7)
   313  			}
   314  		}
   315  
   316  		for _, p := range r.Ports {
   317  			if p.Protocol != api.ProtoAny {
   318  				cnt, err := mergeIngressPortProto(ctx, fromEndpoints, hostWildcardL7, r, p, p.Protocol, ruleLabels, resMap, selectorCache)
   319  				if err != nil {
   320  					return found, err
   321  				}
   322  				found += cnt
   323  			} else {
   324  				cnt, err := mergeIngressPortProto(ctx, fromEndpoints, hostWildcardL7, r, p, api.ProtoTCP, ruleLabels, resMap, selectorCache)
   325  				if err != nil {
   326  					return found, err
   327  				}
   328  				found += cnt
   329  
   330  				cnt, err = mergeIngressPortProto(ctx, fromEndpoints, hostWildcardL7, r, p, api.ProtoUDP, ruleLabels, resMap, selectorCache)
   331  				if err != nil {
   332  					return found, err
   333  				}
   334  				found += cnt
   335  			}
   336  		}
   337  	}
   338  
   339  	return found, nil
   340  }
   341  
   342  func (state *traceState) selectRule(ctx *SearchContext, r *rule) {
   343  	ctx.PolicyTrace("* Rule %s: selected\n", r)
   344  	state.selectedRules++
   345  }
   346  
   347  func (state *traceState) unSelectRule(ctx *SearchContext, labels labels.LabelArray, r *rule) {
   348  	ctx.PolicyTraceVerbose("  Rule %s: did not select %+v\n", r, labels)
   349  }
   350  
   351  // resolveIngressPolicy analyzes the rule against the given SearchContext, and
   352  // merges it with any prior-generated policy within the provided L4Policy.
   353  // Requirements based off of all Ingress requirements (set in FromRequires) in
   354  // other rules are stored in the specified slice of LabelSelectorRequirement.
   355  // These requirements are dynamically inserted into a copy of the receiver rule,
   356  // as requirements form conjunctions across all rules.
   357  func (r *rule) resolveIngressPolicy(ctx *SearchContext, state *traceState, result L4PolicyMap, requirements []v1.LabelSelectorRequirement, selectorCache *SelectorCache) (L4PolicyMap, error) {
   358  	if !ctx.rulesSelect {
   359  		if !r.EndpointSelector.Matches(ctx.To) {
   360  			state.unSelectRule(ctx, ctx.To, r)
   361  			return nil, nil
   362  		}
   363  	}
   364  
   365  	state.selectRule(ctx, r)
   366  	found := 0
   367  
   368  	if len(r.Ingress) == 0 {
   369  		ctx.PolicyTrace("    No ingress rules\n")
   370  	}
   371  	for _, ingressRule := range r.Ingress {
   372  		fromEndpoints := ingressRule.GetSourceEndpointSelectorsWithRequirements(requirements)
   373  		cnt, err := mergeIngress(ctx, fromEndpoints, ingressRule.ToPorts, r.Rule.Labels.DeepCopy(), result, selectorCache)
   374  		if err != nil {
   375  			return nil, err
   376  		}
   377  		if cnt > 0 {
   378  			found += cnt
   379  		}
   380  	}
   381  
   382  	if found > 0 {
   383  		return result, nil
   384  	}
   385  
   386  	return nil, nil
   387  }
   388  
   389  // ********************** CIDR POLICY **********************
   390  
   391  // mergeCIDR inserts all of the CIDRs in ipRules to resMap. Returns the number
   392  // of CIDRs added to resMap.
   393  func mergeCIDR(ctx *SearchContext, dir string, ipRules []api.CIDR, ruleLabels labels.LabelArray, resMap *CIDRPolicyMap) int {
   394  	found := 0
   395  
   396  	for _, r := range ipRules {
   397  		strCIDR := string(r)
   398  		ctx.PolicyTrace("  Allows %s IP %s\n", dir, strCIDR)
   399  
   400  		found += resMap.Insert(strCIDR, ruleLabels)
   401  	}
   402  
   403  	return found
   404  }
   405  
   406  // resolveCIDRPolicy inserts the CIDRs from the specified rule into result if
   407  // the rule corresponds to the current SearchContext. It returns the resultant
   408  // CIDRPolicy containing the added ingress and egress CIDRs. If no CIDRs are
   409  // added to result, a nil CIDRPolicy is returned.
   410  func (r *rule) resolveCIDRPolicy(ctx *SearchContext, state *traceState, result *CIDRPolicy) *CIDRPolicy {
   411  	// Don't select rule if it doesn't apply to the given context.
   412  	if !ctx.rulesSelect {
   413  		if !r.EndpointSelector.Matches(ctx.To) {
   414  			state.unSelectRule(ctx, ctx.To, r)
   415  			return nil
   416  		}
   417  	}
   418  
   419  	state.selectRule(ctx, r)
   420  	found := 0
   421  
   422  	for _, ingressRule := range r.Ingress {
   423  		// TODO (ianvernon): GH-1658
   424  		var allCIDRs []api.CIDR
   425  		allCIDRs = append(allCIDRs, ingressRule.FromCIDR...)
   426  		allCIDRs = append(allCIDRs, api.ComputeResultantCIDRSet(ingressRule.FromCIDRSet)...)
   427  
   428  		// CIDR + L4 rules are handled via mergeIngress(),
   429  		// skip them here.
   430  		if len(allCIDRs) > 0 && len(ingressRule.ToPorts) > 0 {
   431  			continue
   432  		}
   433  
   434  		if cnt := mergeCIDR(ctx, "Ingress", allCIDRs, r.Labels, &result.Ingress); cnt > 0 {
   435  			found += cnt
   436  		}
   437  	}
   438  
   439  	// CIDR egress policy is used for visibility of desired state in
   440  	// the API and for determining which prefix lengths are available,
   441  	// however it does not determine the actual CIDRs in the BPF maps
   442  	// for allowing traffic by CIDR!
   443  	for _, egressRule := range r.Egress {
   444  		var allCIDRs []api.CIDR
   445  		allCIDRs = append(allCIDRs, egressRule.ToCIDR...)
   446  		allCIDRs = append(allCIDRs, api.ComputeResultantCIDRSet(egressRule.ToCIDRSet)...)
   447  
   448  		// Unlike the Ingress policy which only counts L3 policy in
   449  		// this function, we count the CIDR+L4 policy in the
   450  		// desired egress CIDR policy here as well. This ensures
   451  		// proper computation of IPcache prefix lengths.
   452  		if cnt := mergeCIDR(ctx, "Egress", allCIDRs, r.Labels, &result.Egress); cnt > 0 {
   453  			found += cnt
   454  		}
   455  	}
   456  
   457  	if found > 0 {
   458  		return result
   459  	}
   460  
   461  	ctx.PolicyTrace("    No L3 rules\n")
   462  	return nil
   463  }
   464  
   465  func (r *rule) matches(securityIdentity *identity.Identity) bool {
   466  	r.metadata.Mutex.Lock()
   467  	defer r.metadata.Mutex.Unlock()
   468  	var ruleMatches bool
   469  
   470  	if ruleMatches, cached := r.metadata.IdentitySelected[securityIdentity.ID]; cached {
   471  		return ruleMatches
   472  	}
   473  	// Fall back to costly matching.
   474  	if ruleMatches = r.EndpointSelector.Matches(securityIdentity.LabelArray); ruleMatches {
   475  		// Update cache so we don't have to do costly matching again.
   476  		r.metadata.IdentitySelected[securityIdentity.ID] = true
   477  	} else {
   478  		r.metadata.IdentitySelected[securityIdentity.ID] = false
   479  	}
   480  
   481  	return ruleMatches
   482  }
   483  
   484  // ****************** EGRESS POLICY ******************
   485  
   486  func mergeEgress(ctx *SearchContext, toEndpoints api.EndpointSelectorSlice, toPorts []api.PortRule, ruleLabels labels.LabelArray, resMap L4PolicyMap, selectorCache *SelectorCache, fqdns api.FQDNSelectorSlice) (int, error) {
   487  	found := 0
   488  
   489  	if ctx.To != nil && len(toEndpoints) > 0 {
   490  		if ctx.TraceEnabled() {
   491  			traceL3(ctx, toEndpoints, "to")
   492  		}
   493  		if !toEndpoints.Matches(ctx.To) {
   494  			ctx.PolicyTrace("      No label match for %s", ctx.To)
   495  			return 0, nil
   496  		}
   497  		ctx.PolicyTrace("      Found all required labels")
   498  	}
   499  
   500  	var (
   501  		cnt int
   502  		err error
   503  	)
   504  
   505  	// L3-only rule (with requirements folded into toEndpoints).
   506  	if len(toPorts) == 0 && len(toEndpoints) > 0 {
   507  		cnt, err = mergeEgressPortProto(ctx, toEndpoints, api.PortRule{}, api.PortProtocol{Port: "0", Protocol: api.ProtoAny}, api.ProtoAny, ruleLabels, resMap, selectorCache, fqdns)
   508  		if err != nil {
   509  			return found, err
   510  		}
   511  	}
   512  
   513  	found += cnt
   514  
   515  	for _, r := range toPorts {
   516  		// For L4 Policy, an empty slice of EndpointSelector indicates that the
   517  		// rule allows all at L3 - explicitly specify this by creating a slice
   518  		// with the WildcardEndpointSelector.
   519  		if len(toEndpoints) == 0 {
   520  			toEndpoints = api.EndpointSelectorSlice{api.WildcardEndpointSelector}
   521  		}
   522  		ctx.PolicyTrace("      Allows port %v\n", r.Ports)
   523  		if r.Rules != nil && r.Rules.L7Proto != "" {
   524  			ctx.PolicyTrace("        l7proto: \"%s\"\n", r.Rules.L7Proto)
   525  		}
   526  		if !r.Rules.IsEmpty() {
   527  			for _, l7 := range r.Rules.HTTP {
   528  				ctx.PolicyTrace("          %+v\n", l7)
   529  			}
   530  			for _, l7 := range r.Rules.Kafka {
   531  				ctx.PolicyTrace("          %+v\n", l7)
   532  			}
   533  			for _, l7 := range r.Rules.L7 {
   534  				ctx.PolicyTrace("          %+v\n", l7)
   535  			}
   536  		}
   537  
   538  		for _, p := range r.Ports {
   539  			if p.Protocol != api.ProtoAny {
   540  				cnt, err := mergeEgressPortProto(ctx, toEndpoints, r, p, p.Protocol, ruleLabels, resMap, selectorCache, fqdns)
   541  				if err != nil {
   542  					return found, err
   543  				}
   544  				found += cnt
   545  			} else {
   546  				cnt, err := mergeEgressPortProto(ctx, toEndpoints, r, p, api.ProtoTCP, ruleLabels, resMap, selectorCache, fqdns)
   547  				if err != nil {
   548  					return found, err
   549  				}
   550  				found += cnt
   551  
   552  				cnt, err = mergeEgressPortProto(ctx, toEndpoints, r, p, api.ProtoUDP, ruleLabels, resMap, selectorCache, fqdns)
   553  				if err != nil {
   554  					return found, err
   555  				}
   556  				found += cnt
   557  			}
   558  		}
   559  	}
   560  
   561  	return found, nil
   562  }
   563  
   564  // mergeEgressPortProto merges all rules which share the same port & protocol that
   565  // select a given set of endpoints. It updates the L4Filter mapped to by the specified
   566  // port and protocol with the contents of the provided PortRule. If the rule
   567  // being merged has conflicting L7 rules with those already in the provided
   568  // L4PolicyMap for the specified port-protocol tuple, it returns an error.
   569  func mergeEgressPortProto(ctx *SearchContext, endpoints api.EndpointSelectorSlice, r api.PortRule, p api.PortProtocol,
   570  	proto api.L4Proto, ruleLabels labels.LabelArray, resMap L4PolicyMap, selectorCache *SelectorCache, fqdns api.FQDNSelectorSlice) (int, error) {
   571  
   572  	key := p.Port + "/" + string(proto)
   573  	existingFilter, ok := resMap[key]
   574  	if !ok {
   575  		resMap[key] = createL4EgressFilter(endpoints, r, p, proto, ruleLabels, selectorCache, fqdns)
   576  		return 1, nil
   577  	}
   578  
   579  	// Create a new L4Filter based off of the arguments provided to this function
   580  	// for merging with the filter which is already in the policy map.
   581  	filterToMerge := createL4EgressFilter(endpoints, r, p, proto, ruleLabels, selectorCache, fqdns)
   582  
   583  	if err := mergePortProto(ctx, existingFilter, filterToMerge, selectorCache); err != nil {
   584  		filterToMerge.detach(selectorCache)
   585  		return 0, err
   586  	}
   587  	existingFilter.DerivedFromRules = append(existingFilter.DerivedFromRules, ruleLabels)
   588  	resMap[key] = existingFilter
   589  	return 1, nil
   590  }
   591  
   592  func (r *rule) resolveEgressPolicy(ctx *SearchContext, state *traceState, result L4PolicyMap, requirements []v1.LabelSelectorRequirement, selectorCache *SelectorCache) (L4PolicyMap, error) {
   593  	if !ctx.rulesSelect {
   594  		if !r.EndpointSelector.Matches(ctx.From) {
   595  			state.unSelectRule(ctx, ctx.From, r)
   596  			return nil, nil
   597  		}
   598  	}
   599  
   600  	state.selectRule(ctx, r)
   601  	found := 0
   602  
   603  	if len(r.Egress) == 0 {
   604  		ctx.PolicyTrace("    No L4 rules\n")
   605  	}
   606  	for _, egressRule := range r.Egress {
   607  		toEndpoints := egressRule.GetDestinationEndpointSelectorsWithRequirements(requirements)
   608  		cnt, err := mergeEgress(ctx, toEndpoints, egressRule.ToPorts, r.Rule.Labels.DeepCopy(), result, selectorCache, egressRule.ToFQDNs)
   609  		if err != nil {
   610  			return nil, err
   611  		}
   612  		if cnt > 0 {
   613  			found += cnt
   614  		}
   615  	}
   616  
   617  	if found > 0 {
   618  		return result, nil
   619  	}
   620  
   621  	return nil, nil
   622  }