Skip to content

Commit fe92719

Browse files
committed
added optional performance strategy for adding chain rule with many rules into linux netfilter (iptables)
Signed-off-by: Filip Gschwandtner <[email protected]>
1 parent 615f9fd commit fe92719

File tree

5 files changed

+135
-18
lines changed

5 files changed

+135
-18
lines changed

plugins/linux/iptablesplugin/descriptor/rulechain.go

+48-12
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
package descriptor
1616

1717
import (
18+
"bytes"
19+
"fmt"
1820
"strings"
1921

2022
"github.com/golang/protobuf/proto"
2123
"github.com/pkg/errors"
2224
"go.ligato.io/cn-infra/v2/logging"
23-
2425
kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
2526
ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor"
2627
"go.ligato.io/vpp-agent/v3/plugins/linux/iptablesplugin/descriptor/adapter"
@@ -69,19 +70,22 @@ type RuleChainDescriptor struct {
6970

7071
// parallelization of the Retrieve operation
7172
goRoutinesCnt int
73+
// performance solution threshold
74+
minRuleCountForPerfRuleAddition int
7275
}
7376

7477
// NewRuleChainDescriptor creates a new instance of the iptables RuleChain descriptor.
7578
func NewRuleChainDescriptor(
7679
scheduler kvs.KVScheduler, ipTablesHandler linuxcalls.IPTablesAPI, nsPlugin nsplugin.API,
77-
log logging.PluginLogger, goRoutinesCnt int) *kvs.KVDescriptor {
80+
log logging.PluginLogger, goRoutinesCnt int, minRuleCountForPerfRuleAddition int) *kvs.KVDescriptor {
7881

7982
descrCtx := &RuleChainDescriptor{
80-
scheduler: scheduler,
81-
ipTablesHandler: ipTablesHandler,
82-
nsPlugin: nsPlugin,
83-
goRoutinesCnt: goRoutinesCnt,
84-
log: log.NewLogger("ipt-rulechain-descriptor"),
83+
scheduler: scheduler,
84+
ipTablesHandler: ipTablesHandler,
85+
nsPlugin: nsPlugin,
86+
goRoutinesCnt: goRoutinesCnt,
87+
minRuleCountForPerfRuleAddition: minRuleCountForPerfRuleAddition,
88+
log: log.NewLogger("ipt-rulechain-descriptor"),
8589
}
8690

8791
typedDescr := &adapter.RuleChainDescriptor{
@@ -208,11 +212,43 @@ func (d *RuleChainDescriptor) Create(key string, rch *linux_iptables.RuleChain)
208212
}
209213

210214
// append all rules
211-
for _, rule := range rch.Rules {
212-
err := d.ipTablesHandler.AppendRule(protocolType(rch), tableNameStr(rch), chainNameStr(rch), rule)
213-
if err != nil {
214-
d.log.Errorf("Error by appending iptables rule: %v", err)
215-
break
215+
if len(rch.Rules) > 0 {
216+
if len(rch.Rules) < d.minRuleCountForPerfRuleAddition { // use normal method of addition
217+
for _, rule := range rch.Rules {
218+
err := d.ipTablesHandler.AppendRule(protocolType(rch), tableNameStr(rch), chainNameStr(rch), rule)
219+
if err != nil {
220+
d.log.Errorf("Error by appending iptables rule: %v", err)
221+
break
222+
}
223+
}
224+
} else { // use performance solution (this makes performance difference with higher count of appended rules)
225+
// export existing iptables data
226+
data, err := d.ipTablesHandler.SaveTable(protocolType(rch), tableNameStr(rch), true)
227+
if err != nil {
228+
return nil, errors.Errorf("Error by adding rules: Can't export all rules due to: %v", err)
229+
}
230+
231+
// add rules to exported data
232+
insertPoint := bytes.Index(data, []byte("COMMIT"))
233+
if insertPoint == -1 {
234+
return nil, errors.Errorf("Error by adding rules: Can't find COMMIT statement in iptables-save data")
235+
}
236+
var rules strings.Builder
237+
chain := chainNameStr(rch)
238+
for _, rule := range rch.Rules {
239+
rules.WriteString(fmt.Sprintf("[0:0] -A %s %s\n", chain, rule))
240+
}
241+
insertData := []byte(rules.String())
242+
updatedData := make([]byte, len(data)+len(insertData))
243+
copy(updatedData[:insertPoint], data[:insertPoint])
244+
copy(updatedData[insertPoint:insertPoint+len(insertData)], insertData)
245+
copy(updatedData[insertPoint+len(insertData):], data[insertPoint:])
246+
247+
// import modified data to linux
248+
err = d.ipTablesHandler.RestoreTable(protocolType(rch), tableNameStr(rch), updatedData, true, true)
249+
if err != nil {
250+
return nil, errors.Errorf("Error by adding rules: Can't restore modified iptables data due to: %v", err)
251+
}
216252
}
217253
}
218254

plugins/linux/iptablesplugin/iptablesplugin.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
package iptablesplugin
1818

1919
import (
20-
"go.ligato.io/cn-infra/v2/infra"
20+
"math"
2121

22+
"go.ligato.io/cn-infra/v2/infra"
2223
kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
23-
2424
"go.ligato.io/vpp-agent/v3/plugins/linux/iptablesplugin/descriptor"
2525
"go.ligato.io/vpp-agent/v3/plugins/linux/iptablesplugin/linuxcalls"
2626
"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin"
@@ -30,6 +30,13 @@ const (
3030
// by default, at most 10 go routines will split the configured rule chains
3131
// to execute the Retrieve operation in parallel.
3232
defaultGoRoutinesCnt = 10
33+
34+
// by default, no rules will be added by alternative performance strategy using
35+
// iptables-save/modify data/iptables-store technique
36+
// If this performance technique is needed, then the minimum rule limit should be lowered
37+
// by configuration to some lower value (0 means that the permance strategy is
38+
// always used)
39+
defaultMinRuleCountForPerfRuleAddition = math.MaxInt32
3340
)
3441

3542
// IPTablesPlugin configures Linux iptables rules.
@@ -56,8 +63,9 @@ type Deps struct {
5663

5764
// Config holds the plugin configuration.
5865
type Config struct {
59-
Disabled bool `json:"disabled"`
60-
GoRoutinesCnt int `json:"go-routines-count"`
66+
Disabled bool `json:"disabled"`
67+
GoRoutinesCnt int `json:"go-routines-count"`
68+
MinRuleCountForPerfRuleAddition int `json:"min-rule-count-for-performance-rule-addition"`
6169
}
6270

6371
// Init initializes and registers descriptors and handlers for Linux iptables rules.
@@ -85,7 +93,7 @@ func (p *IPTablesPlugin) Init() error {
8593

8694
// init & register the descriptor
8795
ruleChainDescriptor := descriptor.NewRuleChainDescriptor(
88-
p.KVScheduler, p.iptHandler, p.NsPlugin, p.Log, config.GoRoutinesCnt)
96+
p.KVScheduler, p.iptHandler, p.NsPlugin, p.Log, config.GoRoutinesCnt, config.MinRuleCountForPerfRuleAddition)
8997

9098
err = p.Deps.KVScheduler.RegisterKVDescriptor(ruleChainDescriptor)
9199
if err != nil {
@@ -104,7 +112,8 @@ func (p *IPTablesPlugin) Close() error {
104112
func (p *IPTablesPlugin) retrieveConfig() (*Config, error) {
105113
config := &Config{
106114
// default configuration
107-
GoRoutinesCnt: defaultGoRoutinesCnt,
115+
GoRoutinesCnt: defaultGoRoutinesCnt,
116+
MinRuleCountForPerfRuleAddition: defaultMinRuleCountForPerfRuleAddition,
108117
}
109118
found, err := p.Cfg.LoadValue(config)
110119
if !found {

plugins/linux/iptablesplugin/linux-iptablesplugin.conf

+8
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ disabled: false
44
# How many go routines (at most) will split configured network namespaces to execute
55
# the Retrieve operation in parallel.
66
go-routines-count: 10
7+
8+
# Minimal rule count needed to perform alternative rule additions by creating RuleChain.
9+
# Alternative method is based on exporting iptables data(iptables-save), adding rules
10+
# into that exported data and import them back (iptables-restore). This can very efficient
11+
# in case of filling many rules at once.
12+
# By default off (rule count to activate alternative method is super high and therefore
13+
# practically turned off)
14+
min-rule-count-for-performance-rule-addition: 2147483647

plugins/linux/iptablesplugin/linuxcalls/iptables_api.go

+6
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ type IPTablesAPIWrite interface {
5252

5353
// DeleteAllRules deletes all rules within the specified chain.
5454
DeleteAllRules(protocol L3Protocol, table, chain string) error
55+
56+
// RestoreTable import all data (in IPTable-save output format) for given table
57+
RestoreTable(protocol L3Protocol, table string, data []byte, flush bool, importCounters bool) error
5558
}
5659

5760
// IPTablesAPIRead interface covers read methods inside linux calls package
5861
// needed to manage linux iptables rules.
5962
type IPTablesAPIRead interface {
6063
// ListRules lists all rules within the specified chain.
6164
ListRules(protocol L3Protocol, table, chain string) (rules []string, err error)
65+
66+
// SaveTable exports all data for given table in IPTable-save output format
67+
SaveTable(protocol L3Protocol, table string, exportCounters bool) ([]byte, error)
6268
}
6369

6470
// NewIPTablesHandler creates new instance of iptables handler.

plugins/linux/iptablesplugin/linuxcalls/iptables_linuxcalls.go

+58
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
package linuxcalls
1616

1717
import (
18+
"bytes"
1819
"fmt"
20+
"os/exec"
1921
"strings"
2022

2123
"github.com/coreos/go-iptables/iptables"
24+
"github.com/pkg/errors"
2225
)
2326

2427
const (
@@ -27,6 +30,12 @@ const (
2730

2831
// prefix of a "new chain" rule
2932
newChainRulePrefix = "-N"
33+
34+
// command names
35+
IPv4SaveCmd string = "iptables-save"
36+
IPv4RestoreCmd string = "iptables-restore"
37+
IPv6RestoreCmd string = "ip6tables-restore"
38+
IPv6SaveCmd string = "ip6tables-save"
3039
)
3140

3241
// IPTablesHandler is a handler for all operations on Linux iptables / ip6tables.
@@ -136,6 +145,55 @@ func (h *IPTablesHandler) ListRules(protocol L3Protocol, table, chain string) (r
136145
return
137146
}
138147

148+
// SaveTable exports all data for given table in IPTable-save output format
149+
func (h *IPTablesHandler) SaveTable(protocol L3Protocol, table string, exportCounters bool) ([]byte, error) {
150+
// create command with arguments
151+
saveCmd := IPv4SaveCmd
152+
if protocol == ProtocolIPv6 {
153+
saveCmd = IPv6SaveCmd
154+
}
155+
args := []string{"-t", table}
156+
if exportCounters {
157+
args = append(args, "-c")
158+
}
159+
cmd := exec.Command(saveCmd, args...)
160+
var stdout, stderr bytes.Buffer
161+
cmd.Stdout = &stdout
162+
cmd.Stderr = &stderr
163+
164+
// run command and extract result
165+
err := cmd.Run()
166+
if err != nil {
167+
return nil, errors.Errorf("%s failed due to: %v (%s)", saveCmd, err, stderr.String())
168+
}
169+
return stdout.Bytes(), nil
170+
}
171+
172+
// RestoreTable import all data (in IPTable-save output format) for given table
173+
func (h *IPTablesHandler) RestoreTable(protocol L3Protocol, table string, data []byte, flush bool, importCounters bool) error {
174+
// create command with arguments
175+
restoreCmd := IPv4RestoreCmd
176+
if protocol == ProtocolIPv6 {
177+
restoreCmd = IPv6RestoreCmd
178+
}
179+
args := []string{"-T", table}
180+
if importCounters {
181+
args = append(args, "-c")
182+
}
183+
if !flush {
184+
args = append(args, "-n")
185+
}
186+
cmd := exec.Command(restoreCmd, args...)
187+
cmd.Stdin = bytes.NewReader(data)
188+
189+
// run command and extract result
190+
output, err := cmd.CombinedOutput()
191+
if err != nil {
192+
return errors.Errorf("%s failed due to: %v (%s)", restoreCmd, err, string(output))
193+
}
194+
return nil
195+
}
196+
139197
// getHandler returns the iptables handler for the given protocol.
140198
// returns an error if the requested handler is not initialized.
141199
func (h *IPTablesHandler) getHandler(protocol L3Protocol) (*iptables.IPTables, error) {

0 commit comments

Comments
 (0)