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  }