istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/istio-iptables/pkg/builder/iptables_builder_impl.go (about)

     1  // Copyright Istio Authors
     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 builder
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"istio.io/istio/pkg/util/sets"
    22  	"istio.io/istio/tools/istio-iptables/pkg/config"
    23  	"istio.io/istio/tools/istio-iptables/pkg/constants"
    24  	"istio.io/istio/tools/istio-iptables/pkg/log"
    25  )
    26  
    27  // Rule represents iptables rule - chain, table and options
    28  type Rule struct {
    29  	chain  string
    30  	table  string
    31  	params []string
    32  }
    33  
    34  // Rules represents iptables for V4 and V6
    35  type Rules struct {
    36  	rulesv4 []*Rule
    37  	rulesv6 []*Rule
    38  }
    39  
    40  // IptablesRuleBuilder is an implementation for IptablesRuleBuilder interface
    41  type IptablesRuleBuilder struct {
    42  	rules Rules
    43  	cfg   *config.Config
    44  }
    45  
    46  // NewIptablesBuilders creates a new IptablesRuleBuilder
    47  func NewIptablesRuleBuilder(cfg *config.Config) *IptablesRuleBuilder {
    48  	if cfg == nil {
    49  		cfg = &config.Config{}
    50  	}
    51  	return &IptablesRuleBuilder{
    52  		rules: Rules{
    53  			rulesv4: []*Rule{},
    54  			rulesv6: []*Rule{},
    55  		},
    56  		cfg: cfg,
    57  	}
    58  }
    59  
    60  func (rb *IptablesRuleBuilder) InsertRule(command log.Command, chain string, table string, position int, params ...string) *IptablesRuleBuilder {
    61  	rb.InsertRuleV4(command, chain, table, position, params...)
    62  	rb.InsertRuleV6(command, chain, table, position, params...)
    63  	return rb
    64  }
    65  
    66  // nolint lll
    67  func (rb *IptablesRuleBuilder) insertInternal(ipt *[]*Rule, command log.Command, chain string, table string, position int, params ...string) *IptablesRuleBuilder {
    68  	rules := params
    69  	*ipt = append(*ipt, &Rule{
    70  		chain:  chain,
    71  		table:  table,
    72  		params: append([]string{"-I", chain, fmt.Sprint(position)}, rules...),
    73  	})
    74  	idx := indexOf("-j", params)
    75  	// We have identified the type of command this is and logging is enabled. Insert a rule to log this chain was hit.
    76  	// Since this is insert we do this *after* the real chain, which will result in it bumping it forward
    77  	if rb.cfg.TraceLogging && idx >= 0 && command != log.UndefinedCommand {
    78  		match := params[:idx]
    79  		// 1337 group is just a random constant to be matched on the log reader side
    80  		// Size of 20 allows reading the IPv4 IP header.
    81  		match = append(match, "-j", "NFLOG", "--nflog-prefix", fmt.Sprintf(`%q`, command.Identifier), "--nflog-group", "1337", "--nflog-size", "20")
    82  		*ipt = append(*ipt, &Rule{
    83  			chain:  chain,
    84  			table:  table,
    85  			params: append([]string{"-I", chain, fmt.Sprint(position)}, match...),
    86  		})
    87  	}
    88  	return rb
    89  }
    90  
    91  func (rb *IptablesRuleBuilder) InsertRuleV4(command log.Command, chain string, table string, position int, params ...string) *IptablesRuleBuilder {
    92  	return rb.insertInternal(&rb.rules.rulesv4, command, chain, table, position, params...)
    93  }
    94  
    95  func (rb *IptablesRuleBuilder) InsertRuleV6(command log.Command, chain string, table string, position int, params ...string) *IptablesRuleBuilder {
    96  	if !rb.cfg.EnableIPv6 {
    97  		return rb
    98  	}
    99  	return rb.insertInternal(&rb.rules.rulesv6, command, chain, table, position, params...)
   100  }
   101  
   102  func indexOf(element string, data []string) int {
   103  	for k, v := range data {
   104  		if element == v {
   105  			return k
   106  		}
   107  	}
   108  	return -1 // not found.
   109  }
   110  
   111  func (rb *IptablesRuleBuilder) appendInternal(ipt *[]*Rule, command log.Command, chain string, table string, params ...string) *IptablesRuleBuilder {
   112  	idx := indexOf("-j", params)
   113  	// We have identified the type of command this is and logging is enabled. Appending a rule to log this chain will be hit
   114  	if rb.cfg.TraceLogging && idx >= 0 && command != log.UndefinedCommand {
   115  		match := params[:idx]
   116  		// 1337 group is just a random constant to be matched on the log reader side
   117  		// Size of 20 allows reading the IPv4 IP header.
   118  		match = append(match, "-j", "NFLOG", "--nflog-prefix", fmt.Sprintf(`%q`, command.Identifier), "--nflog-group", "1337", "--nflog-size", "20")
   119  		*ipt = append(*ipt, &Rule{
   120  			chain:  chain,
   121  			table:  table,
   122  			params: append([]string{"-A", chain}, match...),
   123  		})
   124  	}
   125  	rules := params
   126  	*ipt = append(*ipt, &Rule{
   127  		chain:  chain,
   128  		table:  table,
   129  		params: append([]string{"-A", chain}, rules...),
   130  	})
   131  	return rb
   132  }
   133  
   134  func (rb *IptablesRuleBuilder) AppendRuleV4(command log.Command, chain string, table string, params ...string) *IptablesRuleBuilder {
   135  	return rb.appendInternal(&rb.rules.rulesv4, command, chain, table, params...)
   136  }
   137  
   138  func (rb *IptablesRuleBuilder) AppendRule(command log.Command, chain string, table string, params ...string) *IptablesRuleBuilder {
   139  	rb.AppendRuleV4(command, chain, table, params...)
   140  	rb.AppendRuleV6(command, chain, table, params...)
   141  	return rb
   142  }
   143  
   144  func (rb *IptablesRuleBuilder) AppendRuleV6(command log.Command, chain string, table string, params ...string) *IptablesRuleBuilder {
   145  	if !rb.cfg.EnableIPv6 {
   146  		return rb
   147  	}
   148  	return rb.appendInternal(&rb.rules.rulesv6, command, chain, table, params...)
   149  }
   150  
   151  func (rb *IptablesRuleBuilder) buildRules(rules []*Rule) [][]string {
   152  	output := make([][]string, 0)
   153  	chainTableLookupSet := sets.New[string]()
   154  	for _, r := range rules {
   155  		chainTable := fmt.Sprintf("%s:%s", r.chain, r.table)
   156  		// Create new chain if key: `chainTable` isn't present in map
   157  		if !chainTableLookupSet.Contains(chainTable) {
   158  			// Ignore chain creation for built-in chains for iptables
   159  			if _, present := constants.BuiltInChainsMap[r.chain]; !present {
   160  				cmd := []string{"-t", r.table, "-N", r.chain}
   161  				output = append(output, cmd)
   162  				chainTableLookupSet.Insert(chainTable)
   163  			}
   164  		}
   165  	}
   166  	for _, r := range rules {
   167  		cmd := append([]string{"-t", r.table}, r.params...)
   168  		output = append(output, cmd)
   169  	}
   170  	return output
   171  }
   172  
   173  func (rb *IptablesRuleBuilder) BuildV4() [][]string {
   174  	return rb.buildRules(rb.rules.rulesv4)
   175  }
   176  
   177  func (rb *IptablesRuleBuilder) BuildV6() [][]string {
   178  	return rb.buildRules(rb.rules.rulesv6)
   179  }
   180  
   181  func (rb *IptablesRuleBuilder) constructIptablesRestoreContents(tableRulesMap map[string][]string) string {
   182  	var b strings.Builder
   183  	for table, rules := range tableRulesMap {
   184  		if len(rules) > 0 {
   185  			_, _ = fmt.Fprintln(&b, "*", table)
   186  			for _, r := range rules {
   187  				_, _ = fmt.Fprintln(&b, r)
   188  			}
   189  			_, _ = fmt.Fprintln(&b, "COMMIT")
   190  		}
   191  	}
   192  	return b.String()
   193  }
   194  
   195  func (rb *IptablesRuleBuilder) buildRestore(rules []*Rule) string {
   196  	tableRulesMap := map[string][]string{
   197  		constants.FILTER: {},
   198  		constants.NAT:    {},
   199  		constants.MANGLE: {},
   200  	}
   201  
   202  	chainTableLookupMap := sets.New[string]()
   203  	for _, r := range rules {
   204  		chainTable := fmt.Sprintf("%s:%s", r.chain, r.table)
   205  		// Create new chain if key: `chainTable` isn't present in map
   206  		if !chainTableLookupMap.Contains(chainTable) {
   207  			// Ignore chain creation for built-in chains for iptables
   208  			if _, present := constants.BuiltInChainsMap[r.chain]; !present {
   209  				tableRulesMap[r.table] = append(tableRulesMap[r.table], fmt.Sprintf("-N %s", r.chain))
   210  				chainTableLookupMap.Insert(chainTable)
   211  			}
   212  		}
   213  	}
   214  
   215  	for _, r := range rules {
   216  		tableRulesMap[r.table] = append(tableRulesMap[r.table], strings.Join(r.params, " "))
   217  	}
   218  	return rb.constructIptablesRestoreContents(tableRulesMap)
   219  }
   220  
   221  func (rb *IptablesRuleBuilder) BuildV4Restore() string {
   222  	return rb.buildRestore(rb.rules.rulesv4)
   223  }
   224  
   225  func (rb *IptablesRuleBuilder) BuildV6Restore() string {
   226  	return rb.buildRestore(rb.rules.rulesv6)
   227  }
   228  
   229  // AppendVersionedRule is a wrapper around AppendRule that substitutes an ipv4/ipv6 specific value
   230  // in place in the params. This allows appending a dual-stack rule that has an IP value in it.
   231  func (rb *IptablesRuleBuilder) AppendVersionedRule(ipv4 string, ipv6 string, command log.Command, chain string, table string, params ...string) {
   232  	rb.AppendRuleV4(command, chain, table, replaceVersionSpecific(ipv4, params...)...)
   233  	rb.AppendRuleV6(command, chain, table, replaceVersionSpecific(ipv6, params...)...)
   234  }
   235  
   236  func replaceVersionSpecific(contents string, inputs ...string) []string {
   237  	res := make([]string, 0, len(inputs))
   238  	for _, i := range inputs {
   239  		if i == constants.IPVersionSpecific {
   240  			res = append(res, contents)
   241  		} else {
   242  			res = append(res, i)
   243  		}
   244  	}
   245  	return res
   246  }