go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/iptablesplugin/linuxcalls/iptables_linuxcalls.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 linuxcalls 16 17 import ( 18 "bytes" 19 "fmt" 20 "os/exec" 21 "strings" 22 23 "github.com/coreos/go-iptables/iptables" 24 "github.com/pkg/errors" 25 ) 26 27 const ( 28 // prefix of the "append" operation on a rule 29 appendRulePrefix = "-A" 30 31 // prefix of a "new chain" rule 32 newChainRulePrefix = "-N" 33 34 // command names 35 IPv4SaveCmd string = "iptables-save" 36 IPv4RestoreCmd string = "iptables-restore" 37 IPv6RestoreCmd string = "ip6tables-restore" 38 IPv6SaveCmd string = "ip6tables-save" 39 ) 40 41 // IPTablesHandler is a handler for all operations on Linux iptables / ip6tables. 42 type IPTablesHandler struct { 43 v4Handler *iptables.IPTables 44 v6Handler *iptables.IPTables 45 minRuleCountForPerfRuleAddition int 46 } 47 48 // Init initializes an iptables handler. 49 func (h *IPTablesHandler) Init(config *HandlerConfig) error { 50 var err, errv4, errv6 error 51 52 h.v4Handler, errv4 = iptables.NewWithProtocol(iptables.ProtocolIPv4) 53 if errv4 != nil { 54 err = fmt.Errorf("error initializing iptables v4 handler: %v", errv4) 55 // continue, iptables just may not be installed 56 } 57 58 h.v6Handler, errv6 = iptables.NewWithProtocol(iptables.ProtocolIPv6) 59 if errv6 != nil { 60 err = fmt.Errorf("errors: %w; error initializing iptables v6 handler: %v", err, errv6) 61 // continue, ip6tables just may not be installed 62 } 63 64 h.minRuleCountForPerfRuleAddition = config.MinRuleCountForPerfRuleAddition 65 66 return err 67 } 68 69 // CreateChain creates an iptables chain in the specified table. 70 func (h *IPTablesHandler) CreateChain(protocol L3Protocol, table, chain string) error { 71 handler, err := h.getHandler(protocol) 72 if err != nil { 73 return err 74 } 75 return handler.NewChain(table, chain) 76 } 77 78 // DeleteChain deletes an iptables chain in the specified table. 79 func (h *IPTablesHandler) DeleteChain(protocol L3Protocol, table, chain string) error { 80 handler, err := h.getHandler(protocol) 81 if err != nil { 82 return err 83 } 84 return handler.DeleteChain(table, chain) 85 } 86 87 // SetChainDefaultPolicy sets default policy in the specified chain. Should be called only on FILTER tables. 88 func (h *IPTablesHandler) SetChainDefaultPolicy(protocol L3Protocol, table, chain, defaultPolicy string) error { 89 handler, err := h.getHandler(protocol) 90 if err != nil { 91 return err 92 } 93 return handler.ChangePolicy(table, chain, defaultPolicy) 94 } 95 96 // AppendRule appends a rule into the specified chain. 97 func (h *IPTablesHandler) AppendRule(protocol L3Protocol, table, chain string, rule string) error { 98 handler, err := h.getHandler(protocol) 99 if err != nil { 100 return err 101 } 102 ruleSlice := strings.Split(rule, " ") 103 104 return handler.Append(table, chain, ruleSlice[:]...) 105 } 106 107 // AppendRules appends rules into the specified chain. 108 func (h *IPTablesHandler) AppendRules(protocol L3Protocol, table, chain string, rules ...string) error { 109 if len(rules) == 0 { 110 return nil // nothing to do 111 } 112 113 if len(rules) < h.minRuleCountForPerfRuleAddition { // use normal method of addition 114 for _, rule := range rules { 115 err := h.AppendRule(protocol, table, chain, rule) 116 if err != nil { 117 return errors.Errorf("Error by appending iptables rule: %v", err) 118 } 119 } 120 } else { // use performance solution (this makes performance difference with higher count of appended rules) 121 // export existing iptables data 122 data, err := h.saveTable(protocol, table, true) 123 if err != nil { 124 return errors.Errorf(": Can't export all rules due to: %v", err) 125 } 126 127 // add rules to exported data 128 insertPoint := bytes.Index(data, []byte("COMMIT")) 129 if insertPoint == -1 { 130 return errors.Errorf("Error by adding rules: Can't find COMMIT statement in iptables-save data") 131 } 132 var rulesSB strings.Builder 133 for _, rule := range rules { 134 rulesSB.WriteString(fmt.Sprintf("[0:0] -A %s %s\n", chain, rule)) 135 } 136 insertData := []byte(rulesSB.String()) 137 updatedData := make([]byte, len(data)+len(insertData)) 138 copy(updatedData[:insertPoint], data[:insertPoint]) 139 copy(updatedData[insertPoint:insertPoint+len(insertData)], insertData) 140 copy(updatedData[insertPoint+len(insertData):], data[insertPoint:]) 141 142 // import modified data to linux 143 err = h.restoreTable(protocol, table, updatedData, true, true) 144 if err != nil { 145 return errors.Errorf("Error by adding rules: Can't restore modified iptables data due to: %v", err) 146 } 147 } 148 149 return nil 150 } 151 152 // DeleteRule deletes a rule from the specified chain. 153 func (h *IPTablesHandler) DeleteRule(protocol L3Protocol, table, chain string, rule string) error { 154 handler, err := h.getHandler(protocol) 155 if err != nil { 156 return err 157 } 158 ruleSlice := strings.Split(rule, " ") 159 160 return handler.Delete(table, chain, ruleSlice[:]...) 161 } 162 163 // DeleteAllRules deletes all rules within the specified chain. 164 func (h *IPTablesHandler) DeleteAllRules(protocol L3Protocol, table, chain string) error { 165 handler, err := h.getHandler(protocol) 166 if err != nil { 167 return err 168 } 169 return handler.ClearChain(table, chain) 170 } 171 172 // ListRules lists all rules within the specified chain. 173 func (h *IPTablesHandler) ListRules(protocol L3Protocol, table, chain string) (rules []string, err error) { 174 handler, err := h.getHandler(protocol) 175 if err != nil { 176 return nil, err 177 } 178 dumpRules, err := handler.List(table, chain) 179 180 // post-process & filter rules 181 for _, rule := range dumpRules { 182 if strings.HasPrefix(rule, newChainRulePrefix) { 183 // ignore "new chain" rules 184 continue 185 } 186 if strings.HasPrefix(rule, appendRulePrefix) { 187 // trim "-A <CHAIN-NAME>" part 188 rule = strings.TrimPrefix(rule, fmt.Sprintf("%s %s", appendRulePrefix, chain)) 189 } 190 rules = append(rules, strings.TrimSpace(rule)) 191 } 192 193 return 194 } 195 196 // saveTable exports all data for given table in IPTable-save output format 197 func (h *IPTablesHandler) saveTable(protocol L3Protocol, table string, exportCounters bool) ([]byte, error) { 198 // create command with arguments 199 saveCmd := IPv4SaveCmd 200 if protocol == ProtocolIPv6 { 201 saveCmd = IPv6SaveCmd 202 } 203 args := []string{"-t", table} 204 if exportCounters { 205 args = append(args, "-c") 206 } 207 cmd := exec.Command(saveCmd, args...) 208 var stdout, stderr bytes.Buffer 209 cmd.Stdout = &stdout 210 cmd.Stderr = &stderr 211 212 // run command and extract result 213 err := cmd.Run() 214 if err != nil { 215 return nil, errors.Errorf("%s failed due to: %v (%s)", saveCmd, err, stderr.String()) 216 } 217 return stdout.Bytes(), nil 218 } 219 220 // restoreTable import all data (in IPTable-save output format) for given table 221 func (h *IPTablesHandler) restoreTable(protocol L3Protocol, table string, data []byte, flush bool, importCounters bool) error { 222 // create command with arguments 223 restoreCmd := IPv4RestoreCmd 224 if protocol == ProtocolIPv6 { 225 restoreCmd = IPv6RestoreCmd 226 } 227 args := []string{"-T", table} 228 if importCounters { 229 args = append(args, "-c") 230 } 231 if !flush { 232 args = append(args, "-n") 233 } 234 cmd := exec.Command(restoreCmd, args...) 235 cmd.Stdin = bytes.NewReader(data) 236 237 // run command and extract result 238 output, err := cmd.CombinedOutput() 239 if err != nil { 240 return errors.Errorf("%s failed due to: %v (%s)", restoreCmd, err, string(output)) 241 } 242 return nil 243 } 244 245 // getHandler returns the iptables handler for the given protocol. 246 // returns an error if the requested handler is not initialized. 247 func (h *IPTablesHandler) getHandler(protocol L3Protocol) (*iptables.IPTables, error) { 248 var handler *iptables.IPTables 249 250 if protocol == ProtocolIPv4 { 251 handler = h.v4Handler 252 } else { 253 handler = h.v6Handler 254 } 255 256 if handler == nil { 257 return nil, fmt.Errorf("iptables handler for protocol %v is not initialized "+ 258 "(please check that you have installed iptables in host system)", protocol) 259 } 260 return handler, nil 261 }