github.com/samuelkuklis/utils@v1.0.0/net/ebtables/ebtables.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package ebtables allows to control the ebtables Linux-based bridging firewall. 18 // Both chains and rules can be added, deleted and modified. 19 // For ebtables specific documentation see: http://ebtables.netfilter.org/ 20 package ebtables 21 22 import ( 23 "fmt" 24 "regexp" 25 "strings" 26 27 utilexec "github.com/samuelkuklis/utils/exec" 28 ) 29 30 const ( 31 cmdebtables = "ebtables" 32 33 // Flag to show full mac in output. The default representation omits leading zeroes. 34 fullMac = "--Lmac2" 35 ) 36 37 // RulePosition is the rule position within a table 38 type RulePosition string 39 40 // Relative position for a new rule 41 const ( 42 Prepend RulePosition = "-I" 43 Append RulePosition = "-A" 44 ) 45 46 // Table is an Ebtables table type 47 type Table string 48 49 // Tables available in ebtables by default 50 const ( 51 TableNAT Table = "nat" 52 TableFilter Table = "filter" 53 TableBroute Table = "broute" 54 ) 55 56 // Chain is an Ebtables chain type 57 type Chain string 58 59 // Chains that are built-in in ebtables 60 const ( 61 ChainPostrouting Chain = "POSTROUTING" 62 ChainPrerouting Chain = "PREROUTING" 63 ChainOutput Chain = "OUTPUT" 64 ChainInput Chain = "INPUT" 65 ChainBrouting Chain = "BROUTING" 66 ) 67 68 type operation string 69 70 const ( 71 opCreateChain operation = "-N" 72 opFlushChain operation = "-F" 73 opDeleteChain operation = "-X" 74 opListChain operation = "-L" 75 opAppendRule operation = "-A" 76 opPrependRule operation = "-I" 77 opDeleteRule operation = "-D" 78 ) 79 80 // Interface for running ebtables commands. Implementations must be goroutine-safe. 81 type Interface interface { 82 // GetVersion returns the "X.Y.Z" semver string for ebtables. 83 GetVersion() (string, error) 84 // EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true. 85 // WARNING: ebtables does not provide check operation like iptables do. Hence we have to do a string match of args. 86 // Input args must follow the format and sequence of ebtables list output. Otherwise, EnsureRule will always create 87 // new rules and causing duplicates. 88 EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) 89 // DeleteRule checks if the specified rule is present and, if so, deletes it. 90 DeleteRule(table Table, chain Chain, args ...string) error 91 // EnsureChain checks if the specified chain is present and, if not, creates it. If the rule existed, return true. 92 EnsureChain(table Table, chain Chain) (bool, error) 93 // DeleteChain deletes the specified chain. If the chain did not exist, return error. 94 DeleteChain(table Table, chain Chain) error 95 // FlushChain flush the specified chain. If the chain did not exist, return error. 96 FlushChain(table Table, chain Chain) error 97 } 98 99 // runner implements Interface in terms of exec("ebtables"). 100 type runner struct { 101 exec utilexec.Interface 102 } 103 104 // New returns a new Interface which will exec ebtables. 105 func New(exec utilexec.Interface) Interface { 106 runner := &runner{ 107 exec: exec, 108 } 109 return runner 110 } 111 112 func makeFullArgs(table Table, op operation, chain Chain, args ...string) []string { 113 return append([]string{"-t", string(table), string(op), string(chain)}, args...) 114 } 115 116 // getEbtablesVersionString runs "ebtables --version" to get the version string 117 // in the form "X.X.X" 118 func getEbtablesVersionString(exec utilexec.Interface) (string, error) { 119 // this doesn't access mutable state so we don't need to use the interface / runner 120 bytes, err := exec.Command(cmdebtables, "--version").CombinedOutput() 121 if err != nil { 122 return "", err 123 } 124 return parseVersion(string(bytes)) 125 } 126 127 func parseVersion(version string) (string, error) { 128 // the regular expression contains `v?` at the beginning because 129 // different OS distros have different version format output i.e 130 // either starts with `v` or it doesn't 131 versionMatcher := regexp.MustCompile(`v?([0-9]+\.[0-9]+\.[0-9]+)`) 132 match := versionMatcher.FindStringSubmatch(version) 133 if match == nil { 134 return "", fmt.Errorf("no ebtables version found in string: %s", version) 135 } 136 return match[1], nil 137 } 138 139 func (runner *runner) GetVersion() (string, error) { 140 return getEbtablesVersionString(runner.exec) 141 } 142 143 func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) { 144 var exist bool 145 fullArgs := makeFullArgs(table, opListChain, chain, fullMac) 146 out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 147 if err != nil { 148 exist = false 149 } else { 150 exist = checkIfRuleExists(string(out), args...) 151 } 152 if !exist { 153 fullArgs = makeFullArgs(table, operation(position), chain, args...) 154 out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 155 if err != nil { 156 return exist, fmt.Errorf("Failed to ensure rule: %v, output: %v", err, string(out)) 157 } 158 } 159 return exist, nil 160 } 161 162 func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error { 163 var exist bool 164 fullArgs := makeFullArgs(table, opListChain, chain, fullMac) 165 out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 166 if err != nil { 167 exist = false 168 } else { 169 exist = checkIfRuleExists(string(out), args...) 170 } 171 172 if !exist { 173 return nil 174 } 175 fullArgs = makeFullArgs(table, opDeleteRule, chain, args...) 176 out, err = runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 177 if err != nil { 178 return fmt.Errorf("Failed to delete rule: %v, output: %s", err, out) 179 } 180 return nil 181 } 182 183 func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) { 184 exist := true 185 186 args := makeFullArgs(table, opListChain, chain) 187 _, err := runner.exec.Command(cmdebtables, args...).CombinedOutput() 188 if err != nil { 189 exist = false 190 } 191 if !exist { 192 args = makeFullArgs(table, opCreateChain, chain) 193 out, err := runner.exec.Command(cmdebtables, args...).CombinedOutput() 194 if err != nil { 195 return exist, fmt.Errorf("Failed to ensure %v chain: %v, output: %v", chain, err, string(out)) 196 } 197 } 198 return exist, nil 199 } 200 201 // checkIfRuleExists takes the output of ebtables list chain and checks if the input rules exists 202 // WARNING: checkIfRuleExists expects the input args matches the format and sequence of ebtables list output 203 func checkIfRuleExists(listChainOutput string, args ...string) bool { 204 rule := strings.Join(args, " ") 205 for _, line := range strings.Split(listChainOutput, "\n") { 206 if strings.TrimSpace(line) == rule { 207 return true 208 } 209 } 210 return false 211 } 212 213 func (runner *runner) DeleteChain(table Table, chain Chain) error { 214 fullArgs := makeFullArgs(table, opDeleteChain, chain) 215 out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 216 if err != nil { 217 return fmt.Errorf("Failed to delete %v chain %v: %v, output: %v", string(table), string(chain), err, string(out)) 218 } 219 return nil 220 } 221 222 func (runner *runner) FlushChain(table Table, chain Chain) error { 223 fullArgs := makeFullArgs(table, opFlushChain, chain) 224 out, err := runner.exec.Command(cmdebtables, fullArgs...).CombinedOutput() 225 if err != nil { 226 return fmt.Errorf("Failed to flush %v chain %v: %v, output: %v", string(table), string(chain), err, string(out)) 227 } 228 return nil 229 }