pax_global_header00006660000000000000000000000064140375716540014526gustar00rootroot0000000000000052 comment=14d56d57c892f27a717aa6026fd2d3293221395b go-iptables-0.6.0/000077500000000000000000000000001403757165400137375ustar00rootroot00000000000000go-iptables-0.6.0/.gitignore000066400000000000000000000000061403757165400157230ustar00rootroot00000000000000.idea go-iptables-0.6.0/.travis.yml000066400000000000000000000004751403757165400160560ustar00rootroot00000000000000language: go sudo: required dist: trusty arch: - AMD64 - ppc64le go: - 1.9.x - 1.10.x - tip env: global: - TOOLS_CMD=golang.org/x/tools/cmd - PATH=$GOROOT/bin:$PATH - SUDO_PERMITTED=1 matrix: allow_failures: - go: tip install: - go get golang.org/x/tools/cmd/cover script: - ./test go-iptables-0.6.0/DCO000066400000000000000000000026161403757165400142740ustar00rootroot00000000000000Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. go-iptables-0.6.0/LICENSE000066400000000000000000000240411403757165400147450ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. go-iptables-0.6.0/NOTICE000066400000000000000000000001761403757165400146470ustar00rootroot00000000000000CoreOS Project Copyright 2018 CoreOS, Inc This product includes software developed at CoreOS, Inc. (http://www.coreos.com/). go-iptables-0.6.0/README.md000066400000000000000000000013711403757165400152200ustar00rootroot00000000000000# go-iptables [![GoDoc](https://godoc.org/github.com/coreos/go-iptables/iptables?status.svg)](https://godoc.org/github.com/coreos/go-iptables/iptables) [![Build Status](https://travis-ci.org/coreos/go-iptables.png?branch=master)](https://travis-ci.org/coreos/go-iptables) Go bindings for iptables utility. In-kernel netfilter does not have a good userspace API. The tables are manipulated via setsockopt that sets/replaces the entire table. Changes to existing table need to be resolved by userspace code which is difficult and error-prone. Netfilter developers heavily advocate using iptables utlity for programmatic manipulation. go-iptables wraps invocation of iptables utility with functions to append and delete rules; create, clear and delete chains. go-iptables-0.6.0/build000077500000000000000000000006451403757165400147710ustar00rootroot00000000000000#!/usr/bin/env bash set -e ORG_PATH="github.com/coreos" REPO_PATH="${ORG_PATH}/go-iptables" if [ ! -h gopath/src/${REPO_PATH} ]; then mkdir -p gopath/src/${ORG_PATH} ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255 fi export GOBIN=${PWD}/bin export GOPATH=${PWD}/gopath eval $(go env) if [ ${GOOS} = "linux" ]; then echo "Building go-iptables..." go build ${REPO_PATH}/iptables else echo "Not on Linux" fi go-iptables-0.6.0/code-of-conduct.md000066400000000000000000000057371403757165400172460ustar00rootroot00000000000000## CoreOS Community Code of Conduct ### Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing others' private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer, Brandon Philips , and/or Rithu John . This Code of Conduct is adapted from the Contributor Covenant (http://contributor-covenant.org), version 1.2.0, available at http://contributor-covenant.org/version/1/2/0/ ### CoreOS Events Code of Conduct CoreOS events are working conferences intended for professional networking and collaboration in the CoreOS community. Attendees are expected to behave according to professional standards and in accordance with their employer’s policies on appropriate workplace behavior. While at CoreOS events or related social networking opportunities, attendees should not engage in discriminatory or offensive speech or actions including but not limited to gender, sexuality, race, age, disability, or religion. Speakers should be especially aware of these concerns. CoreOS does not condone any statements by speakers contrary to these standards. CoreOS reserves the right to deny entrance and/or eject from an event (without refund) any individual found to be engaging in discriminatory or offensive speech or actions. Please bring any concerns to the immediate attention of designated on-site staff, Brandon Philips , and/or Rithu John . go-iptables-0.6.0/go.mod000066400000000000000000000000561403757165400150460ustar00rootroot00000000000000module github.com/coreos/go-iptables go 1.16 go-iptables-0.6.0/iptables/000077500000000000000000000000001403757165400155425ustar00rootroot00000000000000go-iptables-0.6.0/iptables/iptables.go000066400000000000000000000436551403757165400177110ustar00rootroot00000000000000// Copyright 2015 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package iptables import ( "bytes" "fmt" "io" "net" "os/exec" "regexp" "strconv" "strings" "syscall" ) // Adds the output of stderr to exec.ExitError type Error struct { exec.ExitError cmd exec.Cmd msg string exitStatus *int //for overriding } func (e *Error) ExitStatus() int { if e.exitStatus != nil { return *e.exitStatus } return e.Sys().(syscall.WaitStatus).ExitStatus() } func (e *Error) Error() string { return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg) } // IsNotExist returns true if the error is due to the chain or rule not existing func (e *Error) IsNotExist() bool { if e.ExitStatus() != 1 { return false } msgNoRuleExist := "Bad rule (does a matching rule exist in that chain?).\n" msgNoChainExist := "No chain/target/match by that name.\n" return strings.Contains(e.msg, msgNoRuleExist) || strings.Contains(e.msg, msgNoChainExist) } // Protocol to differentiate between IPv4 and IPv6 type Protocol byte const ( ProtocolIPv4 Protocol = iota ProtocolIPv6 ) type IPTables struct { path string proto Protocol hasCheck bool hasWait bool waitSupportSecond bool hasRandomFully bool v1 int v2 int v3 int mode string // the underlying iptables operating mode, e.g. nf_tables timeout int // time to wait for the iptables lock, default waits forever } // Stat represents a structured statistic entry. type Stat struct { Packets uint64 `json:"pkts"` Bytes uint64 `json:"bytes"` Target string `json:"target"` Protocol string `json:"prot"` Opt string `json:"opt"` Input string `json:"in"` Output string `json:"out"` Source *net.IPNet `json:"source"` Destination *net.IPNet `json:"destination"` Options string `json:"options"` } type option func(*IPTables) func IPFamily(proto Protocol) option { return func(ipt *IPTables) { ipt.proto = proto } } func Timeout(timeout int) option { return func(ipt *IPTables) { ipt.timeout = timeout } } // New creates a new IPTables configured with the options passed as parameter. // For backwards compatibility, by default always uses IPv4 and timeout 0. // i.e. you can create an IPv6 IPTables using a timeout of 5 seconds passing // the IPFamily and Timeout options as follow: // ip6t := New(IPFamily(ProtocolIPv6), Timeout(5)) func New(opts ...option) (*IPTables, error) { ipt := &IPTables{ proto: ProtocolIPv4, timeout: 0, } for _, opt := range opts { opt(ipt) } path, err := exec.LookPath(getIptablesCommand(ipt.proto)) if err != nil { return nil, err } ipt.path = path vstring, err := getIptablesVersionString(path) if err != nil { return nil, fmt.Errorf("could not get iptables version: %v", err) } v1, v2, v3, mode, err := extractIptablesVersion(vstring) if err != nil { return nil, fmt.Errorf("failed to extract iptables version from [%s]: %v", vstring, err) } ipt.v1 = v1 ipt.v2 = v2 ipt.v3 = v3 ipt.mode = mode checkPresent, waitPresent, waitSupportSecond, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3) ipt.hasCheck = checkPresent ipt.hasWait = waitPresent ipt.waitSupportSecond = waitSupportSecond ipt.hasRandomFully = randomFullyPresent return ipt, nil } // New creates a new IPTables for the given proto. // The proto will determine which command is used, either "iptables" or "ip6tables". func NewWithProtocol(proto Protocol) (*IPTables, error) { return New(IPFamily(proto), Timeout(0)) } // Proto returns the protocol used by this IPTables. func (ipt *IPTables) Proto() Protocol { return ipt.proto } // Exists checks if given rulespec in specified table/chain exists func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) { if !ipt.hasCheck { return ipt.existsForOldIptables(table, chain, rulespec) } cmd := append([]string{"-t", table, "-C", chain}, rulespec...) err := ipt.run(cmd...) eerr, eok := err.(*Error) switch { case err == nil: return true, nil case eok && eerr.ExitStatus() == 1: return false, nil default: return false, err } } // Insert inserts rulespec to specified table/chain (in specified pos) func (ipt *IPTables) Insert(table, chain string, pos int, rulespec ...string) error { cmd := append([]string{"-t", table, "-I", chain, strconv.Itoa(pos)}, rulespec...) return ipt.run(cmd...) } // Append appends rulespec to specified table/chain func (ipt *IPTables) Append(table, chain string, rulespec ...string) error { cmd := append([]string{"-t", table, "-A", chain}, rulespec...) return ipt.run(cmd...) } // AppendUnique acts like Append except that it won't add a duplicate func (ipt *IPTables) AppendUnique(table, chain string, rulespec ...string) error { exists, err := ipt.Exists(table, chain, rulespec...) if err != nil { return err } if !exists { return ipt.Append(table, chain, rulespec...) } return nil } // Delete removes rulespec in specified table/chain func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error { cmd := append([]string{"-t", table, "-D", chain}, rulespec...) return ipt.run(cmd...) } func (ipt *IPTables) DeleteIfExists(table, chain string, rulespec ...string) error { exists, err := ipt.Exists(table, chain, rulespec...) if err == nil && exists { err = ipt.Delete(table, chain, rulespec...) } return err } // List rules in specified table/chain func (ipt *IPTables) List(table, chain string) ([]string, error) { args := []string{"-t", table, "-S", chain} return ipt.executeList(args) } // List rules (with counters) in specified table/chain func (ipt *IPTables) ListWithCounters(table, chain string) ([]string, error) { args := []string{"-t", table, "-v", "-S", chain} return ipt.executeList(args) } // ListChains returns a slice containing the name of each chain in the specified table. func (ipt *IPTables) ListChains(table string) ([]string, error) { args := []string{"-t", table, "-S"} result, err := ipt.executeList(args) if err != nil { return nil, err } // Iterate over rules to find all default (-P) and user-specified (-N) chains. // Chains definition always come before rules. // Format is the following: // -P OUTPUT ACCEPT // -N Custom var chains []string for _, val := range result { if strings.HasPrefix(val, "-P") || strings.HasPrefix(val, "-N") { chains = append(chains, strings.Fields(val)[1]) } else { break } } return chains, nil } // '-S' is fine with non existing rule index as long as the chain exists // therefore pass index 1 to reduce overhead for large chains func (ipt *IPTables) ChainExists(table, chain string) (bool, error) { err := ipt.run("-t", table, "-S", chain, "1") eerr, eok := err.(*Error) switch { case err == nil: return true, nil case eok && eerr.ExitStatus() == 1: return false, nil default: return false, err } } // Stats lists rules including the byte and packet counts func (ipt *IPTables) Stats(table, chain string) ([][]string, error) { args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"} lines, err := ipt.executeList(args) if err != nil { return nil, err } appendSubnet := func(addr string) string { if strings.IndexByte(addr, byte('/')) < 0 { if strings.IndexByte(addr, '.') < 0 { return addr + "/128" } return addr + "/32" } return addr } ipv6 := ipt.proto == ProtocolIPv6 rows := [][]string{} for i, line := range lines { // Skip over chain name and field header if i < 2 { continue } // Fields: // 0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options line = strings.TrimSpace(line) fields := strings.Fields(line) // The ip6tables verbose output cannot be naively split due to the default "opt" // field containing 2 single spaces. if ipv6 { // Check if field 6 is "opt" or "source" address dest := fields[6] ip, _, _ := net.ParseCIDR(dest) if ip == nil { ip = net.ParseIP(dest) } // If we detected a CIDR or IP, the "opt" field is empty.. insert it. if ip != nil { f := []string{} f = append(f, fields[:4]...) f = append(f, " ") // Empty "opt" field for ip6tables f = append(f, fields[4:]...) fields = f } } // Adjust "source" and "destination" to include netmask, to match regular // List output fields[7] = appendSubnet(fields[7]) fields[8] = appendSubnet(fields[8]) // Combine "options" fields 9... into a single space-delimited field. options := fields[9:] fields = fields[:9] fields = append(fields, strings.Join(options, " ")) rows = append(rows, fields) } return rows, nil } // ParseStat parses a single statistic row into a Stat struct. The input should // be a string slice that is returned from calling the Stat method. func (ipt *IPTables) ParseStat(stat []string) (parsed Stat, err error) { // For forward-compatibility, expect at least 10 fields in the stat if len(stat) < 10 { return parsed, fmt.Errorf("stat contained fewer fields than expected") } // Convert the fields that are not plain strings parsed.Packets, err = strconv.ParseUint(stat[0], 0, 64) if err != nil { return parsed, fmt.Errorf(err.Error(), "could not parse packets") } parsed.Bytes, err = strconv.ParseUint(stat[1], 0, 64) if err != nil { return parsed, fmt.Errorf(err.Error(), "could not parse bytes") } _, parsed.Source, err = net.ParseCIDR(stat[7]) if err != nil { return parsed, fmt.Errorf(err.Error(), "could not parse source") } _, parsed.Destination, err = net.ParseCIDR(stat[8]) if err != nil { return parsed, fmt.Errorf(err.Error(), "could not parse destination") } // Put the fields that are strings parsed.Target = stat[2] parsed.Protocol = stat[3] parsed.Opt = stat[4] parsed.Input = stat[5] parsed.Output = stat[6] parsed.Options = stat[9] return parsed, nil } // StructuredStats returns statistics as structured data which may be further // parsed and marshaled. func (ipt *IPTables) StructuredStats(table, chain string) ([]Stat, error) { rawStats, err := ipt.Stats(table, chain) if err != nil { return nil, err } structStats := []Stat{} for _, rawStat := range rawStats { stat, err := ipt.ParseStat(rawStat) if err != nil { return nil, err } structStats = append(structStats, stat) } return structStats, nil } func (ipt *IPTables) executeList(args []string) ([]string, error) { var stdout bytes.Buffer if err := ipt.runWithOutput(args, &stdout); err != nil { return nil, err } rules := strings.Split(stdout.String(), "\n") // strip trailing newline if len(rules) > 0 && rules[len(rules)-1] == "" { rules = rules[:len(rules)-1] } for i, rule := range rules { rules[i] = filterRuleOutput(rule) } return rules, nil } // NewChain creates a new chain in the specified table. // If the chain already exists, it will result in an error. func (ipt *IPTables) NewChain(table, chain string) error { return ipt.run("-t", table, "-N", chain) } const existsErr = 1 // ClearChain flushed (deletes all rules) in the specified table/chain. // If the chain does not exist, a new one will be created func (ipt *IPTables) ClearChain(table, chain string) error { err := ipt.NewChain(table, chain) eerr, eok := err.(*Error) switch { case err == nil: return nil case eok && eerr.ExitStatus() == existsErr: // chain already exists. Flush (clear) it. return ipt.run("-t", table, "-F", chain) default: return err } } // RenameChain renames the old chain to the new one. func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error { return ipt.run("-t", table, "-E", oldChain, newChain) } // DeleteChain deletes the chain in the specified table. // The chain must be empty func (ipt *IPTables) DeleteChain(table, chain string) error { return ipt.run("-t", table, "-X", chain) } func (ipt *IPTables) ClearAndDeleteChain(table, chain string) error { exists, err := ipt.ChainExists(table, chain) if err != nil || !exists { return err } err = ipt.run("-t", table, "-F", chain) if err == nil { err = ipt.run("-t", table, "-X", chain) } return err } func (ipt *IPTables) ClearAll() error { return ipt.run("-F") } func (ipt *IPTables) DeleteAll() error { return ipt.run("-X") } // ChangePolicy changes policy on chain to target func (ipt *IPTables) ChangePolicy(table, chain, target string) error { return ipt.run("-t", table, "-P", chain, target) } // Check if the underlying iptables command supports the --random-fully flag func (ipt *IPTables) HasRandomFully() bool { return ipt.hasRandomFully } // Return version components of the underlying iptables command func (ipt *IPTables) GetIptablesVersion() (int, int, int) { return ipt.v1, ipt.v2, ipt.v3 } // run runs an iptables command with the given arguments, ignoring // any stdout output func (ipt *IPTables) run(args ...string) error { return ipt.runWithOutput(args, nil) } // runWithOutput runs an iptables command with the given arguments, // writing any stdout output to the given writer func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error { args = append([]string{ipt.path}, args...) if ipt.hasWait { args = append(args, "--wait") if ipt.timeout != 0 && ipt.waitSupportSecond { args = append(args, strconv.Itoa(ipt.timeout)) } } else { fmu, err := newXtablesFileLock() if err != nil { return err } ul, err := fmu.tryLock() if err != nil { syscall.Close(fmu.fd) return err } defer ul.Unlock() } var stderr bytes.Buffer cmd := exec.Cmd{ Path: ipt.path, Args: args, Stdout: stdout, Stderr: &stderr, } if err := cmd.Run(); err != nil { switch e := err.(type) { case *exec.ExitError: return &Error{*e, cmd, stderr.String(), nil} default: return err } } return nil } // getIptablesCommand returns the correct command for the given protocol, either "iptables" or "ip6tables". func getIptablesCommand(proto Protocol) string { if proto == ProtocolIPv6 { return "ip6tables" } else { return "iptables" } } // Checks if iptables has the "-C" and "--wait" flag func getIptablesCommandSupport(v1 int, v2 int, v3 int) (bool, bool, bool, bool) { return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), iptablesWaitSupportSecond(v1, v2, v3), iptablesHasRandomFully(v1, v2, v3) } // getIptablesVersion returns the first three components of the iptables version // and the operating mode (e.g. nf_tables or legacy) // e.g. "iptables v1.3.66" would return (1, 3, 66, legacy, nil) func extractIptablesVersion(str string) (int, int, int, string, error) { versionMatcher := regexp.MustCompile(`v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\s+\((\w+))?`) result := versionMatcher.FindStringSubmatch(str) if result == nil { return 0, 0, 0, "", fmt.Errorf("no iptables version found in string: %s", str) } v1, err := strconv.Atoi(result[1]) if err != nil { return 0, 0, 0, "", err } v2, err := strconv.Atoi(result[2]) if err != nil { return 0, 0, 0, "", err } v3, err := strconv.Atoi(result[3]) if err != nil { return 0, 0, 0, "", err } mode := "legacy" if result[4] != "" { mode = result[4] } return v1, v2, v3, mode, nil } // Runs "iptables --version" to get the version string func getIptablesVersionString(path string) (string, error) { cmd := exec.Command(path, "--version") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return "", err } return out.String(), nil } // Checks if an iptables version is after 1.4.11, when --check was added func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool { if v1 > 1 { return true } if v1 == 1 && v2 > 4 { return true } if v1 == 1 && v2 == 4 && v3 >= 11 { return true } return false } // Checks if an iptables version is after 1.4.20, when --wait was added func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool { if v1 > 1 { return true } if v1 == 1 && v2 > 4 { return true } if v1 == 1 && v2 == 4 && v3 >= 20 { return true } return false } //Checks if an iptablse version is after 1.6.0, when --wait support second func iptablesWaitSupportSecond(v1 int, v2 int, v3 int) bool { if v1 > 1 { return true } if v1 == 1 && v2 >= 6 { return true } return false } // Checks if an iptables version is after 1.6.2, when --random-fully was added func iptablesHasRandomFully(v1 int, v2 int, v3 int) bool { if v1 > 1 { return true } if v1 == 1 && v2 > 6 { return true } if v1 == 1 && v2 == 6 && v3 >= 2 { return true } return false } // Checks if a rule specification exists for a table func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) { rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ") args := []string{"-t", table, "-S"} var stdout bytes.Buffer err := ipt.runWithOutput(args, &stdout) if err != nil { return false, err } return strings.Contains(stdout.String(), rs), nil } // counterRegex is the regex used to detect nftables counter format var counterRegex = regexp.MustCompile(`^\[([0-9]+):([0-9]+)\] `) // filterRuleOutput works around some inconsistencies in output. // For example, when iptables is in legacy vs. nftables mode, it produces // different results. func filterRuleOutput(rule string) string { out := rule // work around an output difference in nftables mode where counters // are output in iptables-save format, rather than iptables -S format // The string begins with "[0:0]" // // Fixes #49 if groups := counterRegex.FindStringSubmatch(out); groups != nil { // drop the brackets out = out[len(groups[0]):] out = fmt.Sprintf("%s -c %s %s", out, groups[1], groups[2]) } return out } go-iptables-0.6.0/iptables/iptables_test.go000066400000000000000000000407571403757165400207500ustar00rootroot00000000000000// Copyright 2015 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package iptables import ( "crypto/rand" "fmt" "math/big" "net" "os" "reflect" "testing" ) func TestProto(t *testing.T) { ipt, err := New() if err != nil { t.Fatalf("New failed: %v", err) } if ipt.Proto() != ProtocolIPv4 { t.Fatalf("Expected default protocol IPv4, got %v", ipt.Proto()) } ip4t, err := NewWithProtocol(ProtocolIPv4) if err != nil { t.Fatalf("NewWithProtocol(ProtocolIPv4) failed: %v", err) } if ip4t.Proto() != ProtocolIPv4 { t.Fatalf("Expected protocol IPv4, got %v", ip4t.Proto()) } ip6t, err := NewWithProtocol(ProtocolIPv6) if err != nil { t.Fatalf("NewWithProtocol(ProtocolIPv6) failed: %v", err) } if ip6t.Proto() != ProtocolIPv6 { t.Fatalf("Expected protocol IPv6, got %v", ip6t.Proto()) } } func TestTimeout(t *testing.T) { ipt, err := New() if err != nil { t.Fatalf("New failed: %v", err) } if ipt.timeout != 0 { t.Fatalf("Expected timeout 0 (wait forever), got %v", ipt.timeout) } ipt2, err := New(Timeout(5)) if err != nil { t.Fatalf("New failed: %v", err) } if ipt2.timeout != 5 { t.Fatalf("Expected timeout 5, got %v", ipt.timeout) } } func randChain(t *testing.T) string { n, err := rand.Int(rand.Reader, big.NewInt(1000000)) if err != nil { t.Fatalf("Failed to generate random chain name: %v", err) } return "TEST-" + n.String() } func contains(list []string, value string) bool { for _, val := range list { if val == value { return true } } return false } // mustTestableIptables returns a list of ip(6)tables handles with various // features enabled & disabled, to test compatibility. // We used to test noWait as well, but that was removed as of iptables v1.6.0 func mustTestableIptables() []*IPTables { ipt, err := New() if err != nil { panic(fmt.Sprintf("New failed: %v", err)) } ip6t, err := NewWithProtocol(ProtocolIPv6) if err != nil { panic(fmt.Sprintf("NewWithProtocol(ProtocolIPv6) failed: %v", err)) } ipts := []*IPTables{ipt, ip6t} // ensure we check one variant without built-in checking if ipt.hasCheck { i := *ipt i.hasCheck = false ipts = append(ipts, &i) i6 := *ip6t i6.hasCheck = false ipts = append(ipts, &i6) } else { panic("iptables on this machine is too old -- missing -C") } return ipts } func TestChain(t *testing.T) { for i, ipt := range mustTestableIptables() { t.Run(fmt.Sprint(i), func(t *testing.T) { runChainTests(t, ipt) }) } } func runChainTests(t *testing.T, ipt *IPTables) { t.Logf("testing %s (hasWait=%t, hasCheck=%t)", ipt.path, ipt.hasWait, ipt.hasCheck) chain := randChain(t) // Saving the list of chains before executing tests originaListChain, err := ipt.ListChains("filter") if err != nil { t.Fatalf("ListChains of Initial failed: %v", err) } // chain shouldn't exist, this will create new err = ipt.ClearChain("filter", chain) if err != nil { t.Fatalf("ClearChain (of missing) failed: %v", err) } // chain should be in listChain listChain, err := ipt.ListChains("filter") if err != nil { t.Fatalf("ListChains failed: %v", err) } if !contains(listChain, chain) { t.Fatalf("ListChains doesn't contain the new chain %v", chain) } // ChainExists should find it, too exists, err := ipt.ChainExists("filter", chain) if err != nil { t.Fatalf("ChainExists for existing chain failed: %v", err) } else if !exists { t.Fatalf("ChainExists doesn't find existing chain") } // chain now exists err = ipt.ClearChain("filter", chain) if err != nil { t.Fatalf("ClearChain (of empty) failed: %v", err) } // put a simple rule in err = ipt.Append("filter", chain, "-s", "0/0", "-j", "ACCEPT") if err != nil { t.Fatalf("Append failed: %v", err) } // can't delete non-empty chain err = ipt.DeleteChain("filter", chain) if err == nil { t.Fatalf("DeleteChain of non-empty chain did not fail") } e, ok := err.(*Error) if ok && e.IsNotExist() { t.Fatal("DeleteChain of non-empty chain returned IsNotExist") } err = ipt.ClearChain("filter", chain) if err != nil { t.Fatalf("ClearChain (of non-empty) failed: %v", err) } // rename the chain newChain := randChain(t) err = ipt.RenameChain("filter", chain, newChain) if err != nil { t.Fatalf("RenameChain failed: %v", err) } // chain empty, should be ok err = ipt.DeleteChain("filter", newChain) if err != nil { t.Fatalf("DeleteChain of empty chain failed: %v", err) } // check that chain is fully gone and that state similar to initial one listChain, err = ipt.ListChains("filter") if err != nil { t.Fatalf("ListChains failed: %v", err) } if !reflect.DeepEqual(originaListChain, listChain) { t.Fatalf("ListChains mismatch: \ngot %#v \nneed %#v", originaListChain, listChain) } // ChainExists must not find it anymore exists, err = ipt.ChainExists("filter", chain) if err != nil { t.Fatalf("ChainExists for non-existing chain failed: %v", err) } else if exists { t.Fatalf("ChainExists finds non-existing chain") } // test ClearAndDelete err = ipt.NewChain("filter", chain) if err != nil { t.Fatalf("NewChain failed: %v", err) } err = ipt.Append("filter", chain, "-j", "ACCEPT") if err != nil { t.Fatalf("Append failed: %v", err) } err = ipt.ClearAndDeleteChain("filter", chain) if err != nil { t.Fatalf("ClearAndDelete failed: %v", err) } exists, err = ipt.ChainExists("filter", chain) if err != nil { t.Fatalf("ChainExists failed: %v", err) } if exists { t.Fatalf("ClearAndDelete didn't delete the chain") } err = ipt.ClearAndDeleteChain("filter", chain) if err != nil { t.Fatalf("ClearAndDelete failed for non-existing chain: %v", err) } } func TestRules(t *testing.T) { for i, ipt := range mustTestableIptables() { t.Run(fmt.Sprint(i), func(t *testing.T) { runRulesTests(t, ipt) }) } } func runRulesTests(t *testing.T, ipt *IPTables) { t.Logf("testing %s (hasWait=%t, hasCheck=%t)", getIptablesCommand(ipt.Proto()), ipt.hasWait, ipt.hasCheck) var address1, address2, subnet1, subnet2 string if ipt.Proto() == ProtocolIPv6 { address1 = "2001:db8::1/128" address2 = "2001:db8::2/128" subnet1 = "2001:db8:a::/48" subnet2 = "2001:db8:b::/48" } else { address1 = "203.0.113.1/32" address2 = "203.0.113.2/32" subnet1 = "192.0.2.0/24" subnet2 = "198.51.100.0/24" } chain := randChain(t) // chain shouldn't exist, this will create new err := ipt.ClearChain("filter", chain) if err != nil { t.Fatalf("ClearChain (of missing) failed: %v", err) } err = ipt.Append("filter", chain, "-s", subnet1, "-d", address1, "-j", "ACCEPT") if err != nil { t.Fatalf("Append failed: %v", err) } err = ipt.AppendUnique("filter", chain, "-s", subnet1, "-d", address1, "-j", "ACCEPT") if err != nil { t.Fatalf("AppendUnique failed: %v", err) } err = ipt.Append("filter", chain, "-s", subnet2, "-d", address1, "-j", "ACCEPT") if err != nil { t.Fatalf("Append failed: %v", err) } err = ipt.Insert("filter", chain, 2, "-s", subnet2, "-d", address2, "-j", "ACCEPT") if err != nil { t.Fatalf("Insert failed: %v", err) } err = ipt.Insert("filter", chain, 1, "-s", subnet1, "-d", address2, "-j", "ACCEPT") if err != nil { t.Fatalf("Insert failed: %v", err) } err = ipt.Delete("filter", chain, "-s", subnet1, "-d", address2, "-j", "ACCEPT") if err != nil { t.Fatalf("Delete failed: %v", err) } err = ipt.Append("filter", chain, "-s", address1, "-d", subnet2, "-j", "ACCEPT") if err != nil { t.Fatalf("Append failed: %v", err) } rules, err := ipt.List("filter", chain) if err != nil { t.Fatalf("List failed: %v", err) } expected := []string{ "-N " + chain, "-A " + chain + " -s " + subnet1 + " -d " + address1 + " -j ACCEPT", "-A " + chain + " -s " + subnet2 + " -d " + address2 + " -j ACCEPT", "-A " + chain + " -s " + subnet2 + " -d " + address1 + " -j ACCEPT", "-A " + chain + " -s " + address1 + " -d " + subnet2 + " -j ACCEPT", } if !reflect.DeepEqual(rules, expected) { t.Fatalf("List mismatch: \ngot %#v \nneed %#v", rules, expected) } rules, err = ipt.ListWithCounters("filter", chain) if err != nil { t.Fatalf("ListWithCounters failed: %v", err) } suffix := " -c 0 0 -j ACCEPT" if ipt.mode == "nf_tables" { suffix = " -j ACCEPT -c 0 0" } expected = []string{ "-N " + chain, "-A " + chain + " -s " + subnet1 + " -d " + address1 + suffix, "-A " + chain + " -s " + subnet2 + " -d " + address2 + suffix, "-A " + chain + " -s " + subnet2 + " -d " + address1 + suffix, "-A " + chain + " -s " + address1 + " -d " + subnet2 + suffix, } if !reflect.DeepEqual(rules, expected) { t.Fatalf("ListWithCounters mismatch: \ngot %#v \nneed %#v", rules, expected) } stats, err := ipt.Stats("filter", chain) if err != nil { t.Fatalf("Stats failed: %v", err) } opt := "--" if ipt.proto == ProtocolIPv6 { opt = " " } expectedStats := [][]string{ {"0", "0", "ACCEPT", "all", opt, "*", "*", subnet1, address1, ""}, {"0", "0", "ACCEPT", "all", opt, "*", "*", subnet2, address2, ""}, {"0", "0", "ACCEPT", "all", opt, "*", "*", subnet2, address1, ""}, {"0", "0", "ACCEPT", "all", opt, "*", "*", address1, subnet2, ""}, } if !reflect.DeepEqual(stats, expectedStats) { t.Fatalf("Stats mismatch: \ngot %#v \nneed %#v", stats, expectedStats) } structStats, err := ipt.StructuredStats("filter", chain) if err != nil { t.Fatalf("StructuredStats failed: %v", err) } // It's okay to not check the following errors as they will be evaluated // in the subsequent usage _, address1CIDR, _ := net.ParseCIDR(address1) _, address2CIDR, _ := net.ParseCIDR(address2) _, subnet1CIDR, _ := net.ParseCIDR(subnet1) _, subnet2CIDR, _ := net.ParseCIDR(subnet2) expectedStructStats := []Stat{ {0, 0, "ACCEPT", "all", opt, "*", "*", subnet1CIDR, address1CIDR, ""}, {0, 0, "ACCEPT", "all", opt, "*", "*", subnet2CIDR, address2CIDR, ""}, {0, 0, "ACCEPT", "all", opt, "*", "*", subnet2CIDR, address1CIDR, ""}, {0, 0, "ACCEPT", "all", opt, "*", "*", address1CIDR, subnet2CIDR, ""}, } if !reflect.DeepEqual(structStats, expectedStructStats) { t.Fatalf("StructuredStats mismatch: \ngot %#v \nneed %#v", structStats, expectedStructStats) } for i, stat := range expectedStats { stat, err := ipt.ParseStat(stat) if err != nil { t.Fatalf("ParseStat failed: %v", err) } if !reflect.DeepEqual(stat, expectedStructStats[i]) { t.Fatalf("ParseStat mismatch: \ngot %#v \nneed %#v", stat, expectedStructStats[i]) } } err = ipt.DeleteIfExists("filter", chain, "-s", address1, "-d", subnet2, "-j", "ACCEPT") if err != nil { t.Fatalf("DeleteIfExists failed for existing rule: %v", err) } err = ipt.DeleteIfExists("filter", chain, "-s", address1, "-d", subnet2, "-j", "ACCEPT") if err != nil { t.Fatalf("DeleteIfExists failed for non-existing rule: %v", err) } // Clear the chain that was created. err = ipt.ClearChain("filter", chain) if err != nil { t.Fatalf("Failed to clear test chain: %v", err) } // Delete the chain that was created err = ipt.DeleteChain("filter", chain) if err != nil { t.Fatalf("Failed to delete test chain: %v", err) } } // TestError checks that we're OK when iptables fails to execute func TestError(t *testing.T) { ipt, err := New() if err != nil { t.Fatalf("failed to init: %v", err) } chain := randChain(t) _, err = ipt.List("filter", chain) if err == nil { t.Fatalf("no error with invalid params") } switch e := err.(type) { case *Error: // OK default: t.Fatalf("expected type iptables.Error, got %t", e) } // Now set an invalid binary path ipt.path = "/does-not-exist" _, err = ipt.ListChains("filter") if err == nil { t.Fatalf("no error with invalid ipt binary") } switch e := err.(type) { case *os.PathError: // OK default: t.Fatalf("expected type os.PathError, got %t", e) } } func TestIsNotExist(t *testing.T) { ipt, err := New() if err != nil { t.Fatalf("failed to init: %v", err) } // Create a chain, add a rule chainName := randChain(t) err = ipt.NewChain("filter", chainName) if err != nil { t.Fatal(err) } defer func() { ipt.ClearChain("filter", chainName) ipt.DeleteChain("filter", chainName) }() err = ipt.Append("filter", chainName, "-p", "tcp", "-j", "DROP") if err != nil { t.Fatal(err) } // Delete rule twice err = ipt.Delete("filter", chainName, "-p", "tcp", "-j", "DROP") if err != nil { t.Fatal(err) } err = ipt.Delete("filter", chainName, "-p", "tcp", "-j", "DROP") if err == nil { t.Fatal("delete twice got no error...") } e, ok := err.(*Error) if !ok { t.Fatalf("Got wrong error type, expected iptables.Error, got %T", err) } if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } // Delete chain err = ipt.DeleteChain("filter", chainName) if err != nil { t.Fatal(err) } err = ipt.DeleteChain("filter", chainName) if err == nil { t.Fatal("deletechain twice got no error...") } e, ok = err.(*Error) if !ok { t.Fatalf("Got wrong error type, expected iptables.Error, got %T", err) } if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } // iptables may add more logs to the errors msgs e.msg = "Another app is currently holding the xtables lock; waiting (1s) for it to exit..." + e.msg if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } } func TestIsNotExistForIPv6(t *testing.T) { ipt, err := NewWithProtocol(ProtocolIPv6) if err != nil { t.Fatalf("failed to init: %v", err) } // Create a chain, add a rule chainName := randChain(t) err = ipt.NewChain("filter", chainName) if err != nil { t.Fatal(err) } defer func() { ipt.ClearChain("filter", chainName) ipt.DeleteChain("filter", chainName) }() err = ipt.Append("filter", chainName, "-p", "tcp", "-j", "DROP") if err != nil { t.Fatal(err) } // Delete rule twice err = ipt.Delete("filter", chainName, "-p", "tcp", "-j", "DROP") if err != nil { t.Fatal(err) } err = ipt.Delete("filter", chainName, "-p", "tcp", "-j", "DROP") if err == nil { t.Fatal("delete twice got no error...") } e, ok := err.(*Error) if !ok { t.Fatalf("Got wrong error type, expected iptables.Error, got %T", err) } if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } // Delete chain err = ipt.DeleteChain("filter", chainName) if err != nil { t.Fatal(err) } err = ipt.DeleteChain("filter", chainName) if err == nil { t.Fatal("deletechain twice got no error...") } e, ok = err.(*Error) if !ok { t.Fatalf("Got wrong error type, expected iptables.Error, got %T", err) } if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } // iptables may add more logs to the errors msgs e.msg = "Another app is currently holding the xtables lock; waiting (1s) for it to exit..." + e.msg if !e.IsNotExist() { t.Fatal("IsNotExist returned false, expected true") } } func TestFilterRuleOutput(t *testing.T) { testCases := []struct { name string in string out string }{ { "legacy output", "-A foo1 -p tcp -m tcp --dport 1337 -j ACCEPT", "-A foo1 -p tcp -m tcp --dport 1337 -j ACCEPT", }, { "nft output", "[99:42] -A foo1 -p tcp -m tcp --dport 1337 -j ACCEPT", "-A foo1 -p tcp -m tcp --dport 1337 -j ACCEPT -c 99 42", }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { actual := filterRuleOutput(tt.in) if actual != tt.out { t.Fatalf("expect %s actual %s", tt.out, actual) } }) } } func TestExtractIptablesVersion(t *testing.T) { testCases := []struct { in string v1, v2, v3 int mode string err bool }{ { "iptables v1.8.0 (nf_tables)", 1, 8, 0, "nf_tables", false, }, { "iptables v1.8.0 (legacy)", 1, 8, 0, "legacy", false, }, { "iptables v1.6.2", 1, 6, 2, "legacy", false, }, } for i, tt := range testCases { t.Run(fmt.Sprint(i), func(t *testing.T) { v1, v2, v3, mode, err := extractIptablesVersion(tt.in) if err == nil && tt.err { t.Fatal("expected err, got none") } else if err != nil && !tt.err { t.Fatalf("unexpected err %s", err) } if v1 != tt.v1 || v2 != tt.v2 || v3 != tt.v3 || mode != tt.mode { t.Fatalf("expected %d %d %d %s, got %d %d %d %s", tt.v1, tt.v2, tt.v3, tt.mode, v1, v2, v3, mode) } }) } } go-iptables-0.6.0/iptables/lock.go000066400000000000000000000047131403757165400170260ustar00rootroot00000000000000// Copyright 2015 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package iptables import ( "os" "sync" "syscall" ) const ( // In earlier versions of iptables, the xtables lock was implemented // via a Unix socket, but now flock is used via this lockfile: // http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707 // Note the LSB-conforming "/run" directory does not exist on old // distributions, so assume "/var" is symlinked xtablesLockFilePath = "/var/run/xtables.lock" defaultFilePerm = 0600 ) type Unlocker interface { Unlock() error } type nopUnlocker struct{} func (_ nopUnlocker) Unlock() error { return nil } type fileLock struct { // mu is used to protect against concurrent invocations from within this process mu sync.Mutex fd int } // tryLock takes an exclusive lock on the xtables lock file without blocking. // This is best-effort only: if the exclusive lock would block (i.e. because // another process already holds it), no error is returned. Otherwise, any // error encountered during the locking operation is returned. // The returned Unlocker should be used to release the lock when the caller is // done invoking iptables commands. func (l *fileLock) tryLock() (Unlocker, error) { l.mu.Lock() err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB) switch err { case syscall.EWOULDBLOCK: l.mu.Unlock() return nopUnlocker{}, nil case nil: return l, nil default: l.mu.Unlock() return nil, err } } // Unlock closes the underlying file, which implicitly unlocks it as well. It // also unlocks the associated mutex. func (l *fileLock) Unlock() error { defer l.mu.Unlock() return syscall.Close(l.fd) } // newXtablesFileLock opens a new lock on the xtables lockfile without // acquiring the lock func newXtablesFileLock() (*fileLock, error) { fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm) if err != nil { return nil, err } return &fileLock{fd: fd}, nil } go-iptables-0.6.0/test000077500000000000000000000021101403757165400146360ustar00rootroot00000000000000#!/usr/bin/env bash # # Run all go-iptables tests # ./test # ./test -v # # Run tests for one package # PKG=./unit ./test # PKG=ssh ./test # set -e # Invoke ./cover for HTML output COVER=${COVER:-"-cover"} source ./build TESTABLE="iptables" FORMATTABLE="$TESTABLE" # user has not provided PKG override if [ -z "$PKG" ]; then TEST=$TESTABLE FMT=$FORMATTABLE # user has provided PKG override else # strip out slashes and dots from PKG=./foo/ TEST=${PKG//\//} TEST=${TEST//./} # only run gofmt on packages provided by user FMT="$TEST" fi echo "Checking gofmt..." fmtRes=$(gofmt -l $FMT) if [ -n "${fmtRes}" ]; then echo -e "gofmt checking failed:\n${fmtRes}" exit 255 fi # split TEST into an array and prepend REPO_PATH to each local package split=(${TEST// / }) TEST=${split[@]/#/${REPO_PATH}/} echo "Running tests..." bin=$(mktemp) go test -c -o ${bin} ${COVER} -i ${TEST} if [[ -z "$SUDO_PERMITTED" ]]; then echo "Test aborted for safety reasons. Please set the SUDO_PERMITTED variable." exit 1 fi sudo -E bash -c "${bin} $@ ${TEST}" echo "Success" rm "${bin}"