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 }