go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/iptablesplugin/descriptor/rulechain.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 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 descriptor 16 17 import ( 18 "strings" 19 20 "github.com/pkg/errors" 21 "go.ligato.io/cn-infra/v2/logging" 22 "google.golang.org/protobuf/proto" 23 24 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 25 ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor" 26 "go.ligato.io/vpp-agent/v3/plugins/linux/iptablesplugin/descriptor/adapter" 27 "go.ligato.io/vpp-agent/v3/plugins/linux/iptablesplugin/linuxcalls" 28 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin" 29 nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" 30 ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" 31 linux_iptables "go.ligato.io/vpp-agent/v3/proto/ligato/linux/iptables" 32 linux_namespace "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace" 33 ) 34 35 const ( 36 // RuleChainDescriptorName is the name of the descriptor for Linux iptables rule chains. 37 RuleChainDescriptorName = "linux-ipt-rulechain-descriptor" 38 39 // dependency labels 40 ruleChainInterfaceDep = "interface-exists" 41 microserviceDep = "microservice-available" 42 43 // minimum number of namespaces to be given to a single Go routine for processing 44 // in the Retrieve operation 45 minWorkForGoRoutine = 3 46 ) 47 48 // A list of non-retriable errors: 49 var ( 50 // ErrCustomChainWithoutName is returned when the chain name is not provided for the custom iptables chain. 51 ErrCustomChainWithoutName = errors.New("iptables chain of type CUSTOM defined without chain name") 52 53 // ErrInvalidChainForTable is returned when the chain is not valid for the provided table. 54 ErrInvalidChainForTable = errors.New("provided chain is not valid for the provided table") 55 56 // ErrDefaultPolicyOnNonFilterRule is returned when a default policy is applied on a table different to FILTER. 57 ErrDefaultPolicyOnNonFilterRule = errors.New("iptables default policy can be only applied on FILTER tables") 58 59 // ErrDefaultPolicyOnCustomChain is returned when a default policy is applied on a custom chain, which is not allowed in iptables. 60 ErrDefaultPolicyOnCustomChain = errors.New("iptables default policy cannot be applied on custom chains") 61 ) 62 63 // RuleChainDescriptor teaches KVScheduler how to configure Linux iptables rule chains. 64 type RuleChainDescriptor struct { 65 log logging.Logger 66 nsPlugin nsplugin.API 67 scheduler kvs.KVScheduler 68 ipTablesHandler linuxcalls.IPTablesAPI 69 70 // parallelization of the Retrieve operation 71 goRoutinesCnt int 72 // performance solution threshold 73 minRuleCountForPerfRuleAddition int 74 } 75 76 // NewRuleChainDescriptor creates a new instance of the iptables RuleChain descriptor. 77 func NewRuleChainDescriptor( 78 scheduler kvs.KVScheduler, ipTablesHandler linuxcalls.IPTablesAPI, nsPlugin nsplugin.API, 79 log logging.PluginLogger, goRoutinesCnt int, minRuleCountForPerfRuleAddition int) *kvs.KVDescriptor { 80 81 descrCtx := &RuleChainDescriptor{ 82 scheduler: scheduler, 83 ipTablesHandler: ipTablesHandler, 84 nsPlugin: nsPlugin, 85 goRoutinesCnt: goRoutinesCnt, 86 minRuleCountForPerfRuleAddition: minRuleCountForPerfRuleAddition, 87 log: log.NewLogger("ipt-rulechain-descriptor"), 88 } 89 90 typedDescr := &adapter.RuleChainDescriptor{ 91 Name: RuleChainDescriptorName, 92 NBKeyPrefix: linux_iptables.ModelRuleChain.KeyPrefix(), 93 ValueTypeName: linux_iptables.ModelRuleChain.ProtoName(), 94 KeySelector: linux_iptables.ModelRuleChain.IsKeyValid, 95 KeyLabel: linux_iptables.ModelRuleChain.StripKeyPrefix, 96 ValueComparator: descrCtx.EquivalentRuleChains, 97 Validate: descrCtx.Validate, 98 Create: descrCtx.Create, 99 Delete: descrCtx.Delete, 100 Retrieve: descrCtx.Retrieve, 101 Dependencies: descrCtx.Dependencies, 102 RetrieveDependencies: []string{ifdescriptor.InterfaceDescriptorName}, 103 } 104 return adapter.NewRuleChainDescriptor(typedDescr) 105 } 106 107 // EquivalentRuleChains is a comparison function for two RuleChain entries. 108 func (d *RuleChainDescriptor) EquivalentRuleChains(key string, oldRCh, newRch *linux_iptables.RuleChain) bool { 109 110 // first, compare everything except the rules 111 oldRules := oldRCh.Rules 112 newRules := newRch.Rules 113 114 oldRCh.Rules = nil 115 newRch.Rules = nil 116 defer func() { 117 oldRCh.Rules = oldRules 118 newRch.Rules = newRules 119 }() 120 121 if !proto.Equal(oldRCh, newRch) { 122 return false 123 } 124 125 // compare rule count 126 if len(oldRules) != len(newRules) { 127 return false 128 } 129 130 // compare individual rules one by one 131 // note that the rules can have individual parts reordered, e.g. the rule 132 // "-i eth0 -s 192.168.0.1 -j ACCEPT" is equivalent to 133 // "-s 192.168.0.1 -i eth0 -j ACCEPT" 134 135 for i := range oldRules { 136 // tokenize the matching rules based on space separator 137 oldTokens := strings.Split(oldRules[i], " ") 138 newTokens := strings.Split(newRules[i], " ") 139 // compare token counts first 140 if len(oldTokens) != len(newTokens) { 141 return false 142 } 143 // check if each token exists in the matching rule 144 for j := range oldTokens { 145 if !sliceContains(newTokens, oldTokens[j]) { 146 return false 147 } 148 } 149 } 150 151 return true 152 } 153 154 // Validate validates iptables rule chain. 155 func (d *RuleChainDescriptor) Validate(key string, rch *linux_iptables.RuleChain) (err error) { 156 if rch.ChainType == linux_iptables.RuleChain_CUSTOM && rch.ChainName == "" { 157 return kvs.NewInvalidValueError(ErrCustomChainWithoutName, "chain_name") 158 } 159 if !isAllowedChain(rch.Table, rch.ChainType) { 160 return kvs.NewInvalidValueError(ErrInvalidChainForTable, "chain_type") 161 } 162 if rch.Table != linux_iptables.RuleChain_FILTER && rch.DefaultPolicy != linux_iptables.RuleChain_NONE { 163 return kvs.NewInvalidValueError(ErrDefaultPolicyOnNonFilterRule, "default_policy") 164 } 165 if rch.ChainType == linux_iptables.RuleChain_CUSTOM && rch.DefaultPolicy != linux_iptables.RuleChain_NONE { 166 return kvs.NewInvalidValueError(ErrDefaultPolicyOnCustomChain, "default_policy") 167 } 168 return nil 169 } 170 171 // Create creates iptables rule chain. 172 func (d *RuleChainDescriptor) Create(key string, rch *linux_iptables.RuleChain) (metadata interface{}, err error) { 173 174 d.log.Debugf("CREATE IPT rule chain %s: %v", key, rch) 175 176 // switch network namespace 177 nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() 178 nsRevert, err := d.nsPlugin.SwitchToNamespace(nsCtx, rch.Namespace) 179 if err != nil { 180 d.log.WithFields(logging.Fields{ 181 "err": err, 182 "namespace": rch.Namespace, 183 }).Warn("Failed to switch the namespace") 184 return nil, err 185 } 186 // revert network namespace after returning 187 defer nsRevert() 188 189 // create custom chain if needed 190 if rch.ChainType == linux_iptables.RuleChain_CUSTOM { 191 err := d.ipTablesHandler.CreateChain(protocolType(rch), tableNameStr(rch), chainNameStr(rch)) 192 if err != nil { 193 d.log.Warnf("Error by creating iptables chain: %v", err) 194 // try to continue, the chain may already exist 195 } 196 } 197 198 // for FILTER tables, change the default policy if it is set 199 if rch.Table == linux_iptables.RuleChain_FILTER && rch.DefaultPolicy != linux_iptables.RuleChain_NONE { 200 err = d.ipTablesHandler.SetChainDefaultPolicy(protocolType(rch), tableNameStr(rch), chainNameStr(rch), chainPolicyStr(rch)) 201 if err != nil { 202 d.log.Errorf("Error by setting iptables default policy: %v", err) 203 return nil, err 204 } 205 } 206 207 // wipe all rules in the chain that may have existed before 208 err = d.ipTablesHandler.DeleteAllRules(protocolType(rch), tableNameStr(rch), chainNameStr(rch)) 209 if err != nil { 210 return nil, errors.Errorf("Error by wiping iptables rules: %v", err) 211 } 212 213 // append all rules 214 err = d.ipTablesHandler.AppendRules(protocolType(rch), tableNameStr(rch), chainNameStr(rch), rch.Rules...) 215 if err != nil { 216 return nil, errors.Errorf("Error by adding rules: %v", err) 217 } 218 219 return nil, err 220 } 221 222 // Delete removes iptables rule chain. 223 func (d *RuleChainDescriptor) Delete(key string, rch *linux_iptables.RuleChain, metadata interface{}) error { 224 225 d.log.Debugf("DELETE IPT rule chain %s: %v", key, rch) 226 227 // switch network namespace 228 nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() 229 nsRevert, err := d.nsPlugin.SwitchToNamespace(nsCtx, rch.Namespace) 230 if err != nil { 231 d.log.WithFields(logging.Fields{ 232 "err": err, 233 "namespace": rch.Namespace, 234 }).Warn("Failed to switch the namespace") 235 return err 236 } 237 // revert network namespace after returning 238 defer nsRevert() 239 240 // delete all rules in the chain 241 err = d.ipTablesHandler.DeleteAllRules(protocolType(rch), tableNameStr(rch), chainNameStr(rch)) 242 if err != nil { 243 d.log.Errorf("Error by deleting iptables rules: %v", err) 244 } 245 246 // delete the chain if it was custom-defined 247 if rch.ChainType == linux_iptables.RuleChain_CUSTOM { 248 err := d.ipTablesHandler.DeleteChain(protocolType(rch), tableNameStr(rch), chainNameStr(rch)) 249 if err != nil { 250 d.log.Errorf("Error by deleting iptables chain: %v", err) 251 return err 252 } 253 } 254 255 return nil 256 } 257 258 // Dependencies lists dependencies for a iptables rule chain. 259 func (d *RuleChainDescriptor) Dependencies(key string, rch *linux_iptables.RuleChain) []kvs.Dependency { 260 var deps []kvs.Dependency 261 262 // the associated interfaces must exist 263 if len(rch.Interfaces) > 0 { 264 for _, i := range rch.Interfaces { 265 deps = append(deps, kvs.Dependency{ 266 Label: ruleChainInterfaceDep + "-" + i, 267 Key: ifmodel.InterfaceKey(i), 268 }) 269 } 270 } 271 272 // microservice must be available 273 if rch.Namespace != nil && rch.Namespace.Type == linux_namespace.NetNamespace_MICROSERVICE { 274 deps = append(deps, kvs.Dependency{ 275 Label: microserviceDep + "-" + rch.Namespace.Reference, 276 Key: linux_namespace.MicroserviceKey(rch.Namespace.Reference), 277 }) 278 } 279 280 return deps 281 } 282 283 // retrievedRuleChains is used as the return value sent via channel by retrieveRuleChains(). 284 type retrievedRuleChains struct { 285 chains []adapter.RuleChainKVWithMetadata 286 err error 287 } 288 289 // Retrieve returns all iptables rule chain entries managed by this agent. 290 func (d *RuleChainDescriptor) Retrieve(correlate []adapter.RuleChainKVWithMetadata) ([]adapter.RuleChainKVWithMetadata, error) { 291 var values []adapter.RuleChainKVWithMetadata 292 293 if len(correlate) == 0 { 294 return values, nil 295 } 296 297 goRoutinesCnt := len(correlate) / minWorkForGoRoutine 298 299 if goRoutinesCnt > d.goRoutinesCnt { 300 goRoutinesCnt = d.goRoutinesCnt 301 } 302 303 ch := make(chan retrievedRuleChains, goRoutinesCnt) 304 305 // invoke multiple go routines for more efficient parallel chain retrieval 306 for idx := 0; idx < goRoutinesCnt; idx++ { 307 if goRoutinesCnt > 1 { 308 go d.retrieveRuleChains(correlate, idx, goRoutinesCnt, ch) 309 } else { 310 d.retrieveRuleChains(correlate, idx, goRoutinesCnt, ch) 311 } 312 } 313 314 // collect results from the go routines 315 for idx := 0; idx < goRoutinesCnt; idx++ { 316 retrieved := <-ch 317 if retrieved.err != nil { 318 return values, retrieved.err 319 } 320 values = append(values, retrieved.chains...) 321 } 322 323 return values, nil 324 } 325 326 // retrieveRuleChains is run by a separate go routine to retrieve all iptables rule chains associated 327 // with every <goRoutineIdx>-th correlation input. 328 func (d *RuleChainDescriptor) retrieveRuleChains( 329 correlate []adapter.RuleChainKVWithMetadata, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedRuleChains) { 330 331 var retrieved retrievedRuleChains 332 nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() 333 334 for i := goRoutineIdx; i < len(correlate); i += goRoutinesCnt { 335 corrrelRule := correlate[i].Value 336 337 // switch to the namespace 338 nsRevert, err := d.nsPlugin.SwitchToNamespace(nsCtx, corrrelRule.Namespace) 339 if err != nil { 340 d.log.WithFields(logging.Fields{ 341 "err": err, 342 "namespace": corrrelRule.Namespace, 343 }).Warn("Failed to switch the namespace") 344 continue // continue with the item 345 } 346 347 // TODO: we are not able to dump the default policy of a chain 348 349 // list rules in provided table & chain 350 rules, err := d.ipTablesHandler.ListRules(protocolType(corrrelRule), tableNameStr(corrrelRule), chainNameStr(corrrelRule)) 351 352 // switch back to the default namespace 353 nsRevert() 354 355 if err != nil { 356 d.log.Warnf("Error by listing iptables rules: %v", err) 357 continue // continue with the item 358 } 359 360 // build key-value pair for the retrieved rules 361 val := proto.Clone(corrrelRule).(*linux_iptables.RuleChain) 362 val.Rules = rules 363 retrieved.chains = append(retrieved.chains, adapter.RuleChainKVWithMetadata{ 364 Key: linux_iptables.RuleChainKey(val.Name), 365 Value: val, 366 Origin: kvs.FromNB, 367 }) 368 } 369 370 ch <- retrieved 371 } 372 373 // sliceContains returns true if provided slice contains provided value, false otherwise. 374 func sliceContains(slice []string, value string) bool { 375 for _, i := range slice { 376 if i == value { 377 return true 378 } 379 } 380 return false 381 } 382 383 // isAllowedChain returns true if provided chain is valid for the provided table, false otherwise. 384 func isAllowedChain(table linux_iptables.RuleChain_Table, chain linux_iptables.RuleChain_ChainType) bool { 385 switch table { 386 case linux_iptables.RuleChain_FILTER: 387 // Input / Forward / Output / Custom 388 switch chain { 389 case linux_iptables.RuleChain_PREROUTING: 390 return false 391 case linux_iptables.RuleChain_POSTROUTING: 392 return false 393 default: 394 return true 395 } 396 case linux_iptables.RuleChain_NAT: 397 // Prerouting / Output / Postrouting / Custom 398 switch chain { 399 case linux_iptables.RuleChain_INPUT: 400 return false 401 case linux_iptables.RuleChain_FORWARD: 402 return false 403 default: 404 return true 405 } 406 case linux_iptables.RuleChain_MANGLE: 407 // all chains 408 return true 409 case linux_iptables.RuleChain_RAW: 410 // Prerouting / Output / Custom 411 switch chain { 412 case linux_iptables.RuleChain_INPUT: 413 return false 414 case linux_iptables.RuleChain_FORWARD: 415 return false 416 case linux_iptables.RuleChain_POSTROUTING: 417 return false 418 default: 419 return true 420 } 421 case linux_iptables.RuleChain_SECURITY: 422 // Input / Output / Forward 423 switch chain { 424 case linux_iptables.RuleChain_PREROUTING: 425 return false 426 case linux_iptables.RuleChain_POSTROUTING: 427 return false 428 default: 429 return true 430 } 431 } 432 return false 433 } 434 435 // protocolType returns protocol of the given rule chain in the NB API format. 436 func protocolType(rch *linux_iptables.RuleChain) linuxcalls.L3Protocol { 437 switch rch.Protocol { 438 case linux_iptables.RuleChain_IPV6: 439 return linuxcalls.ProtocolIPv6 440 default: 441 return linuxcalls.ProtocolIPv4 442 } 443 } 444 445 // tableNameStr returns iptables table name of the given rule chain in the NB API format. 446 func tableNameStr(rch *linux_iptables.RuleChain) string { 447 switch rch.Table { 448 case linux_iptables.RuleChain_NAT: 449 return "nat" 450 case linux_iptables.RuleChain_MANGLE: 451 return "mangle" 452 case linux_iptables.RuleChain_RAW: 453 return "raw" 454 case linux_iptables.RuleChain_SECURITY: 455 return "security" 456 default: 457 return "filter" 458 } 459 } 460 461 // chainNameStr returns iptables chain name of the given rule chain in the NB API format. 462 func chainNameStr(rch *linux_iptables.RuleChain) string { 463 switch rch.ChainType { 464 case linux_iptables.RuleChain_CUSTOM: 465 return rch.ChainName 466 case linux_iptables.RuleChain_OUTPUT: 467 return "OUTPUT" 468 case linux_iptables.RuleChain_FORWARD: 469 return "FORWARD" 470 case linux_iptables.RuleChain_PREROUTING: 471 return "PREROUTING" 472 case linux_iptables.RuleChain_POSTROUTING: 473 return "POSTROUTING" 474 default: 475 return "INPUT" 476 } 477 } 478 479 // chainPolicyStr returns iptables policy name of the given rule chain in the NB API format. 480 func chainPolicyStr(rch *linux_iptables.RuleChain) string { 481 switch rch.DefaultPolicy { 482 case linux_iptables.RuleChain_DROP: 483 return "DROP" 484 case linux_iptables.RuleChain_QUEUE: 485 return "QUEUE" 486 case linux_iptables.RuleChain_RETURN: 487 return "RETURN" 488 default: 489 return "ACCEPT" 490 } 491 }