Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group common sub-sequences of policies into shared iptables chains #8098

Merged
merged 21 commits into from
Dec 20, 2023

Conversation

fasaxc
Copy link
Member

@fasaxc fasaxc commented Oct 10, 2023

Description

  • Group policies in iptables chains based on the selectors used in the policies.
    For example, if there are three policies in a row that use the same selector,
    put them in a group.
  • Render each group as its own chain, named using a hash of the group contents.
  • Share groups chains between endpoints that use the same group.
  • Change iptables refcounting to be recursive so that it can handle the new intermediate chain and still avoid programming unused policy chains.

Related issues/PRs

CORE-10015

Todos

  • Tests
  • Documentation
  • Release note

Release Note

Felix now breaks up "policy jump rules" into new iptables "policy group chains" by selector.  If two endpoints share a common sequence of policies they will share the same group chain, which reduces the number of rules that need to be programmed.

Reminder for the reviewer

Make sure that this PR has the correct labels and milestone set.

Every PR needs one docs-* label.

  • docs-pr-required: This change requires a change to the documentation that has not been completed yet.
  • docs-completed: This change has all necessary documentation completed.
  • docs-not-required: This change has no user-facing impact and requires no docs.

Every PR needs one release-note-* label.

  • release-note-required: This PR has user-facing changes. Most PRs should have this label.
  • release-note-not-required: This PR has no user-facing changes.

Other optional labels:

  • cherry-pick-candidate: This PR should be cherry-picked to an earlier release. For bug fixes only.
  • needs-operator-pr: This PR is related to install and requires a corresponding change to the operator.

@marvin-tigera marvin-tigera added this to the Calico v3.27.0 milestone Oct 10, 2023
@marvin-tigera marvin-tigera added release-note-required Change has user-facing impact (no matter how small) docs-pr-required Change is not yet documented labels Oct 10, 2023
@fasaxc fasaxc force-pushed the policy-grouping branch 2 times, most recently from bd5397a to 663aac1 Compare December 6, 2023 15:24
@fasaxc fasaxc changed the title [WIP] Policy grouping [WIP] Group common sub-sequences of policies into shared iptables chains Dec 6, 2023
Instead of refcounting blindly, trigger incref
of children only when the chain itself becomes referenced.
Trigger decref only for referenced chains.

Only invalidate dataplane if changing a referenced chain.
@fasaxc fasaxc added docs-not-required Docs not required for this change and removed docs-pr-required Change is not yet documented labels Dec 18, 2023
@fasaxc fasaxc changed the title [WIP] Group common sub-sequences of policies into shared iptables chains Group common sub-sequences of policies into shared iptables chains Dec 19, 2023
@fasaxc fasaxc marked this pull request as ready for review December 19, 2023 11:59
@fasaxc fasaxc requested a review from a team as a code owner December 19, 2023 11:59
Copy link
Member

@nelljerram nelljerram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small points, but all up to you and I don't think I will need to re-review. Please LMK if any of the points I've mentioned are not clear.

@@ -244,6 +244,8 @@ message Policy {
repeated Rule outbound_rules = 2;
bool untracked = 3;
bool pre_dnat = 4;

string original_selector = 6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Assume the numbering here is because there's an = 5 field in Enterprise.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 is used above for namespace.

} else {
break
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be marginally nicer if the coding here made it clear that these rules could not be further appended to. I'm aware that that is in fact the case - by looking at where ProtoRulesToIptablesRules is used - but I wonder if it might be worth changing the signature so that this function here returns the entire &iptables.Chain?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I coded this up but it made the tests uglier. Right now they can treat this as simple one-in-one-out function. Returning a chain means we have to pass in a chain name and then unpack the rules to test them.

const (
PolicyDirectionIngress PolicyDirection = "ingress"
PolicyDirectionEgress PolicyDirection = "egress"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm amazed we don't have a definition like this somewhere already!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find one!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have changed these to match the terminology used in this package inbound/outbound

@@ -254,14 +258,14 @@ func (r *DefaultRuleRenderer) HostEndpointToRawChains(

func (r *DefaultRuleRenderer) HostEndpointToMangleIngressChains(
ifaceName string,
preDNATPolicyNames []string,
preDNATPolicis []*PolicyGroup,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sp, should be "...ies"

polChainPrefix := PolicyInboundPfx
if group.Direction == PolicyDirectionEgress {
polChainPrefix = PolicyOutboundPfx
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For host endpoints, are the "i" and "o" prefixes here the same way round as they currently are for policies applying to a host endpoint? (Might be a bit confusing if not.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and same question (albeit not specifically this bit of code) for the policy group "gi" and "go" prefixes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pi and gi chains line up, as do the po and go chains. I.e. gi chains are groups of pi policies. Policy inbound/outbound aligns with the policy model direction. It's the to/from endpoint chains that are flipped between HEPs and WEPs.

write(strconv.Itoa(len(g.PolicyNames)))
for _, name := range g.PolicyNames {
write(name)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you comment (and remind me) why we include tier and selector here, as well as the direction and policy names? If we had the same group in different tiers, couldn't we reuse the same group?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Tier: needed (in enterprise) to disambiguate tierA/policy1 vs tierB/policy1; different policies with the same policy name.
  • Direction and Selector I found useful to make the object self describing.

Tier needs to be in the hash. Direction and selector aren't strictly needed in the hash (I think) but I was finding it easier to prove to myself that all bases were covered if I included them rather than try to prove tat it works without them In an earlier iteration I didn't have the gi/go prefix, so the direction was needed.

ifaceNameToPolicyGroupChainNames map[string][]string /*chain name*/

activePolicySelectors map[proto.PolicyID]string
policyChainRefCounts map[string] /*chain name*/ int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worry that the separation of int here will be confusing.

break hepLoop
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly worth a common utility definition here:

tierUsesDirtyPolicy := func(t *proto.TierInfo) bool {
        for _, pols := range [][]string{t.IngressPolicies, t.EgressPolicies} {
        	for _, p := range pols {
        		polID := proto.PolicyID{
        			Tier: t.Name,
        			Name: p,
        		}
        		if m.dirtyPolicyIDs.Contains(polID) {
        			return true
        		}
        	}
        }
        return false
}

@@ -975,6 +1080,11 @@ func (m *endpointManager) updateHostEndpoints() {
}
}

ifaceNameToPolicyGroups := map[string][]*rules.PolicyGroup{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not yet entirely following what this is for. Does it matter that different subsets of these groups will need to be programmed in different iptables tables?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, the groups are going to be unconditionally programmed in all tables. I guess that's OK, albeit a touch inelegant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I just copied the old inelegant solution for policies. Could do a bit better here because we do know which groups go in which tables; just seemed like the number of tracking maps would explode a bit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would personally be fine with leaving this to a future tidy up, as this PR is already a good unit on its own.

}],
}
groups := []*rules.PolicyGroup{group}
for _, name := range names[1:] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is names[1:] ok if names only has 1 entry?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I double checked.

@fasaxc fasaxc merged commit e83ae81 into projectcalico:master Dec 20, 2023
2 checks passed
@fasaxc fasaxc mentioned this pull request Dec 20, 2023
3 tasks
VadimEisenberg pushed a commit to neureality-sdk/calico that referenced this pull request Sep 1, 2024
…rojectcalico#8098)

Group policies in iptables chains based on the selectors used in the policies.
For example, if there are three policies in a row that use the same selector,
put them in a group.

- Add protobuf field for policy selector pass through from calc graph.
- Add policy group calculation to endpoint manager.  Does refcounting to
  manage shared policy group chains.
- Add policy group chain rendering to rule renderer. Take advantage of extra
  indirection to implement pass rules more efficiently (return on pass or accept).
- Make iptables reference counting recursive.

  Instead of refcounting blindly, trigger incref
  of children only when the chain itself becomes referenced.
  Trigger decref only for referenced chains.

  Only invalidate dataplane if changing a referenced chain.

CORE-10015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs-not-required Docs not required for this change release-note-required Change has user-facing impact (no matter how small)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants