-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
To allow smooth transition to nftables a new packetfilter component is introduced, packetfilter provides an API to create chains, rules, TCP mss clamping and GlobalNet egress IP rules in a generic way. Since we still want to support platforms using iptables, the packetfilter component should be capable of auto-detecting whether the platform is using nftables or iptables and use the appropriate driver. The clients (e.g: GlobalNet) should be updated to use packetfilter API instead of the iptables,ipsets API, so they will only use the packetfilter APIs and will be abstracted from the underlying implementation. This PR includes: 1. Packetfilter component, auto-detecting hardcoded to iptables. 2. iptables driver, which is based on current iptables driver. 3. Changing clients code to use packetfilter APIs. Signed-off-by: Yossi Boaron <[email protected]>
- Loading branch information
Showing
4 changed files
with
1,009 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/* | ||
SPDX-License-Identifier: Apache-2.0 | ||
Copyright Contributors to the Submariner project. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package iptables | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/coreos/go-iptables/iptables" | ||
"github.com/pkg/errors" | ||
level "github.com/submariner-io/admiral/pkg/log" | ||
"k8s.io/utils/set" | ||
) | ||
|
||
func (a *Adapter) createChainIfNotExists(table, chain string) error { | ||
exists, err := a.ipt.ChainExists(table, chain) | ||
if err == nil && exists { | ||
return nil | ||
} | ||
|
||
if err != nil { | ||
return errors.Wrapf(err, "error finding IP table chain %q in table %q", chain, table) | ||
} | ||
|
||
return errors.Wrap(a.ipt.NewChain(table, chain), "error creating IP table chain") | ||
} | ||
|
||
func (a *Adapter) insertUnique(table, chain string, position int, ruleSpec []string) error { | ||
rules, err := a.ipt.List(table, chain) | ||
if err != nil { | ||
return errors.Wrapf(err, "error listing the rules in %s chain", chain) | ||
} | ||
|
||
isPresentAtRequiredPosition := false | ||
numOccurrences := 0 | ||
|
||
for index, rule := range rules { | ||
if strings.Contains(rule, strings.Join(ruleSpec, " ")) { | ||
logger.V(level.DEBUG).Infof("In %s table, iptables rule \"%s\", exists at index %d.", table, strings.Join(ruleSpec, " "), index) | ||
numOccurrences++ | ||
|
||
if index == position { | ||
isPresentAtRequiredPosition = true | ||
} | ||
} | ||
} | ||
|
||
// The required rule is present in the Chain, but either there are multiple occurrences or its | ||
// not at the desired location | ||
if numOccurrences > 1 || !isPresentAtRequiredPosition { | ||
for i := 0; i < numOccurrences; i++ { | ||
if err = a.ipt.Delete(table, chain, ruleSpec...); err != nil { | ||
return errors.Wrapf(err, "error deleting stale IP table rule %q", strings.Join(ruleSpec, " ")) | ||
} | ||
} | ||
} | ||
|
||
// The required rule is present only once and is at the desired location | ||
if numOccurrences == 1 && isPresentAtRequiredPosition { | ||
logger.V(level.DEBUG).Infof("In %s table, iptables rule \"%s\", already exists.", table, strings.Join(ruleSpec, " ")) | ||
return nil | ||
} else if err := a.ipt.Insert(table, chain, position, ruleSpec...); err != nil { | ||
return errors.Wrapf(err, "error inserting IP table rule %q", strings.Join(ruleSpec, " ")) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (a *Adapter) prependUnique(table, chain string, ruleSpec []string) error { | ||
// Submariner requires certain iptable rules to be programmed at the beginning of an iptables Chain | ||
// so that we can preserve the sourceIP for inter-cluster traffic and avoid K8s SDN making changes | ||
// to the traffic. | ||
// In this API, we check if the required iptable rule is present at the beginning of the chain. | ||
// If the rule is already present and there are no stale[1] flows, we simply return. If not, we create one. | ||
// [1] Sometimes after we program the rule at the beginning of the chain, K8s SDN might insert some | ||
// new rules ahead of the rule that we programmed. In such cases, the rule that we programmed will | ||
// not be the first rule to hit and Submariner behavior might get affected. So, we query the rules | ||
// in the chain to see if the rule slipped its position, and if so, delete all such occurrences. | ||
// We then re-program a new rule at the beginning of the chain as required. | ||
return a.insertUnique(table, chain, 1, ruleSpec) | ||
} | ||
|
||
func (a *Adapter) updateChainRules(table, chain string, rules [][]string) error { | ||
existingRules, err := a.ipt.List(table, chain) | ||
if err != nil { | ||
return errors.Wrapf(err, "error listing the rules in table %q, chain %q", table, chain) | ||
} | ||
|
||
ruleStrings := set.New[string]() | ||
|
||
for _, existingRule := range existingRules { | ||
ruleSpec := strings.Split(existingRule, " ") | ||
if ruleSpec[0] == "-A" { | ||
ruleSpec = ruleSpec[2:] // remove "-A", "$chain" | ||
ruleStrings.Insert(strings.Trim(strings.Join(ruleSpec, " "), " ")) | ||
} | ||
} | ||
|
||
for _, ruleSpec := range rules { | ||
ruleString := strings.Join(ruleSpec, " ") | ||
|
||
if ruleStrings.Has(ruleString) { | ||
ruleStrings.Delete(ruleString) | ||
} else { | ||
logger.V(level.DEBUG).Infof("Adding iptables rule in %q, %q: %q", table, chain, ruleSpec) | ||
|
||
if err := a.ipt.Append(table, chain, ruleSpec...); err != nil { | ||
return errors.Wrapf(err, "error adding rule to %v to %q, %q", ruleSpec, table, chain) | ||
} | ||
} | ||
} | ||
|
||
// remaining elements should not be there, remove them | ||
for _, rule := range ruleStrings.UnsortedList() { | ||
logger.V(level.DEBUG).Infof("Deleting stale iptables rule in %q, %q: %q", table, chain, rule) | ||
ruleSpec := strings.Split(rule, " ") | ||
|
||
if err := a.ipt.Delete(table, chain, ruleSpec...); err != nil { | ||
// Log and let go, as this is not a fatal error, or something that will make real harm, | ||
// it's more harmful to keep retrying. At this point on next update deletion of stale rules | ||
// will happen again | ||
logger.Warningf("Unable to delete iptables entry from table %q, chain %q: %q", table, chain, rule) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (a *Adapter) delete(table, chain string, rulespec ...string) error { | ||
err := a.ipt.Delete(table, chain, rulespec...) | ||
|
||
var iptError *iptables.Error | ||
|
||
ok := errors.As(err, &iptError) | ||
if ok && iptError.IsNotExist() { | ||
return nil | ||
} | ||
|
||
return errors.Wrap(err, "error deleting IP table rule") | ||
} |
Oops, something went wrong.