istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/istio-iptables/pkg/dependencies/implementation.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 dependencies 16 17 import ( 18 "fmt" 19 "io" 20 "os/exec" 21 "regexp" 22 "strings" 23 24 utilversion "k8s.io/apimachinery/pkg/util/version" 25 26 "istio.io/istio/pkg/log" 27 "istio.io/istio/tools/istio-iptables/pkg/constants" 28 ) 29 30 // XTablesExittype is the exit type of xtables commands. 31 type XTablesExittype int 32 33 // Learn from `xtables_exittype` of iptables. 34 // `XTF_ONLY_ONCE`, `XTF_NO_INVERT`, `XTF_BAD_VALUE`, `XTF_ONE_ACTION` will eventually turned out to be a 35 // parameter problem with explicit error message. Thus, we do not need to support them here. 36 const ( 37 // XTablesOtherProblem indicates a problem of other type in xtables 38 XTablesOtherProblem XTablesExittype = iota + 1 39 // XTablesParameterProblem indicates a parameter problem in xtables 40 XTablesParameterProblem 41 // XTablesVersionProblem indicates a version problem in xtables 42 XTablesVersionProblem 43 // XTablesResourceProblem indicates a resource problem in xtables 44 XTablesResourceProblem 45 ) 46 47 var exittypeToString = map[XTablesExittype]string{ 48 XTablesOtherProblem: "xtables other problem", 49 XTablesParameterProblem: "xtables parameter problem", 50 XTablesVersionProblem: "xtables version problem", 51 XTablesResourceProblem: "xtables resource problem", 52 } 53 54 // RealDependencies implementation of interface Dependencies, which is used in production 55 type RealDependencies struct { 56 NetworkNamespace string 57 CNIMode bool 58 } 59 60 const iptablesVersionPattern = `v([0-9]+(\.[0-9]+)+)` 61 62 type IptablesVersion struct { 63 DetectedBinary string 64 DetectedSaveBinary string 65 DetectedRestoreBinary string 66 // the actual version 67 Version *utilversion.Version 68 // true if legacy mode, false if nf_tables 69 Legacy bool 70 // true if we detected that existing rules are present for this variant (legacy, nft, v6) 71 ExistingRules bool 72 } 73 74 func (v IptablesVersion) CmdToString(cmd constants.IptablesCmd) string { 75 switch cmd { 76 case constants.IPTables: 77 return v.DetectedBinary 78 case constants.IPTablesSave: 79 return v.DetectedSaveBinary 80 case constants.IPTablesRestore: 81 return v.DetectedRestoreBinary 82 default: 83 return "" 84 } 85 } 86 87 // IsWriteCmd returns true for all command types that do write actions (and thus need a lock) 88 func (v IptablesVersion) IsWriteCmd(cmd constants.IptablesCmd) bool { 89 switch cmd { 90 case constants.IPTables: 91 return true 92 case constants.IPTablesRestore: 93 return true 94 default: 95 return false 96 } 97 } 98 99 // Constants for iptables commands 100 // These should not be used directly/assumed to be present, but should be contextually detected 101 const ( 102 iptablesBin = "iptables" 103 iptablesNftBin = "iptables-nft" 104 iptablesLegacyBin = "iptables-legacy" 105 ip6tablesBin = "ip6tables" 106 ip6tablesNftBin = "ip6tables-nft" 107 ip6tablesLegacyBin = "ip6tables-legacy" 108 iptablesRestoreBin = "iptables-restore" 109 ip6tablesRestoreBin = "ip6tables-restore" 110 ) 111 112 // It is not sufficient to check for the presence of one binary or the other in $PATH - 113 // we must choose a binary that is 114 // 1. Available in our $PATH 115 // 2. Matches where rules are actually defined in the netns we're operating in 116 // (legacy or nft, with a preference for the latter if both present) 117 // 118 // This is designed to handle situations where, for instance, the host has nft-defined rules, and our default container 119 // binary is `legacy`, or vice-versa - we must match the binaries we have in our $PATH to what rules are actually defined 120 // in our current netns context. 121 // 122 // Q: Why not simply "use the host default binary" at $PATH/iptables? 123 // A: Because we are running in our own container and do not have access to the host default binary. 124 // We are using our local binaries to update host rules, and we must pick the right match. 125 // 126 // Basic selection logic is as follows: 127 // 1. see if we have `nft` binary set in our $PATH 128 // 2. see if we have existing rules in `nft` in our netns 129 // 3. If so, use `nft` binary set 130 // 4. Otherwise, see if we have `legacy` binary set, and use that. 131 // 5. Otherwise, see if we have `iptables` binary set, and use that (detecting whether it's nft or legacy). 132 func (r *RealDependencies) DetectIptablesVersion(ipV6 bool) (IptablesVersion, error) { 133 // Begin detecting 134 // 135 // iptables variants all have ipv6 variants, so decide which set we're looking for 136 var nftBin, legacyBin, plainBin string 137 if ipV6 { 138 nftBin = ip6tablesNftBin 139 legacyBin = ip6tablesLegacyBin 140 plainBin = ip6tablesBin 141 } else { 142 nftBin = iptablesNftBin 143 legacyBin = iptablesLegacyBin 144 plainBin = iptablesBin 145 } 146 147 // 1. What binaries we have 148 // 2. What binary we should use, based on existing rules defined in our current context. 149 // does the nft binary set exist, and are nft rules present? 150 nftVer, err := shouldUseBinaryForCurrentContext(nftBin) 151 if err == nil && nftVer.ExistingRules { 152 // if so, immediately use it. 153 return nftVer, nil 154 } 155 // not critical, may find another. 156 log.Debugf("did not find (or cannot use) iptables binary, error was %w: %+v", err, nftVer) 157 158 // Check again 159 // does the legacy binary set exist, and are legacy rules present? 160 legVer, err := shouldUseBinaryForCurrentContext(legacyBin) 161 if err == nil && legVer.ExistingRules { 162 // if so, immediately use it 163 return legVer, nil 164 } 165 // not critical, may find another. 166 log.Debugf("did not find (or cannot use) iptables binary, error was %w: %+v", err, legVer) 167 168 // regular non-suffixed binary set is our last resort. 169 // 170 // If it's there, and rules do not already exist for a specific variant, 171 // we should use the default non-suffixed binary. 172 // If it's NOT there, just propagate the error, we can't do anything, no iptables here 173 return shouldUseBinaryForCurrentContext(plainBin) 174 } 175 176 // TODO BML verify this won't choke on "-save" binaries having slightly diff. version string prefixes 177 func parseIptablesVer(rawVer string) (*utilversion.Version, error) { 178 versionMatcher := regexp.MustCompile(iptablesVersionPattern) 179 match := versionMatcher.FindStringSubmatch(rawVer) 180 if match == nil { 181 return nil, fmt.Errorf("no iptables version found for: %q", rawVer) 182 } 183 version, err := utilversion.ParseGeneric(match[1]) 184 if err != nil { 185 return nil, fmt.Errorf("iptables version %q is not a valid version string: %v", match[1], err) 186 } 187 return version, nil 188 } 189 190 // transformToXTablesErrorMessage returns an updated error message with explicit xtables error hints, if applicable. 191 func transformToXTablesErrorMessage(stderr string, err error) string { 192 ee, ok := err.(*exec.ExitError) 193 if !ok { 194 // Not common, but can happen if file not found error, etc 195 return err.Error() 196 } 197 exitcode := ee.ExitCode() 198 if errtypeStr, ok := exittypeToString[XTablesExittype(exitcode)]; ok { 199 // The original stderr is something like: 200 // `prog_name + prog_vers: error hints` 201 // `(optional) try help information`. 202 // e.g., 203 // `iptables 1.8.4 (legacy): Couldn't load target 'ISTIO_OUTPUT':No such file or directory` 204 // `Try 'iptables -h' or 'iptables --help' for more information.` 205 // Reusing the `error hints` and optional `try help information` parts of the original stderr to form 206 // an error message with explicit xtables error information. 207 errStrParts := strings.SplitN(stderr, ":", 2) 208 errStr := stderr 209 if len(errStrParts) > 1 { 210 errStr = errStrParts[1] 211 } 212 return fmt.Sprintf("%v: %v", errtypeStr, strings.TrimSpace(errStr)) 213 } 214 215 return stderr 216 } 217 218 // Run runs a command 219 func (r *RealDependencies) Run(cmd constants.IptablesCmd, iptVer *IptablesVersion, stdin io.ReadSeeker, args ...string) error { 220 return r.executeXTables(cmd, iptVer, false, stdin, args...) 221 } 222 223 // RunQuietlyAndIgnore runs a command quietly and ignores errors 224 func (r *RealDependencies) RunQuietlyAndIgnore(cmd constants.IptablesCmd, iptVer *IptablesVersion, stdin io.ReadSeeker, args ...string) { 225 _ = r.executeXTables(cmd, iptVer, true, stdin, args...) 226 }