Merge pull request #1 from squeed/portmap-plugin
iptables-based portmapping plugin
This commit is contained in:
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
@ -6,6 +6,11 @@
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/libcni",
|
||||
"Comment": "v0.5.2",
|
||||
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
|
||||
"Comment": "v0.5.2",
|
||||
@ -38,8 +43,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
||||
"Comment": "v0.1.0",
|
||||
"Rev": "fbb73372b87f6e89951c2b6b31470c2c9d5cfae3"
|
||||
"Comment": "v0.1.0-9-g197187d",
|
||||
"Rev": "197187d414d7704f99ea52a692b9672e76f063bf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/activation",
|
||||
@ -54,6 +59,11 @@
|
||||
"ImportPath": "github.com/d2g/dhcp4client",
|
||||
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-shellwords",
|
||||
"Comment": "v1.0.3",
|
||||
"Rev": "02e3cf038dcea8290e44424da473dd12be796a8a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
|
@ -17,7 +17,7 @@ Some CNI network plugins, maintained by the containernetworking team. For more i
|
||||
### Meta: other plugins
|
||||
* `flannel`: generates an interface corresponding to a flannel config file
|
||||
* `tuning`: Tweaks sysctl parameters of an existing interface
|
||||
|
||||
* `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container.
|
||||
|
||||
### Sample
|
||||
The sample plugin provides an example for building your own plugin.
|
||||
|
117
plugins/meta/portmap/README.md
Normal file
117
plugins/meta/portmap/README.md
Normal file
@ -0,0 +1,117 @@
|
||||
## Port-mapping plugin
|
||||
|
||||
This plugin will forward traffic from one or more ports on the host to the
|
||||
container. It expects to be run as a chained plugin.
|
||||
|
||||
## Usage
|
||||
You should use this plugin as part of a network configuration list. It accepts
|
||||
the following configuration options:
|
||||
|
||||
* `snat` - boolean, default true. If true or omitted, set up the SNAT chains
|
||||
* `conditionsV4`, `conditionsV6` - array of strings. A list of arbitrary `iptables`
|
||||
matches to add to the per-container rule. This may be useful if you wish to
|
||||
exclude specific IPs from port-mapping
|
||||
|
||||
The plugin expects to receive the actual list of port mappings via the
|
||||
`portMappings` [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
||||
|
||||
So a sample standalone config list (with the file extension .conflist) might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.16.30.0/24",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {"portMappings": true},
|
||||
"snat": false,
|
||||
"conditionsV4": ["!", "-d", "192.0.2.0/24"],
|
||||
"conditionsV6": ["!", "-d", "fc00::/7"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Rule structure
|
||||
The plugin sets up two sequences of chains and rules - one "primary" DNAT
|
||||
sequence to rewrite the destination, and one additional SNAT sequence that
|
||||
rewrites the source address for packets from localhost. The sequence is somewhat
|
||||
complex to minimize the number of rules non-forwarded packets must traverse.
|
||||
|
||||
|
||||
### DNAT
|
||||
The DNAT rule rewrites the destination port and address of new connections.
|
||||
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
|
||||
never deleted. Each plugin execution creates an additional chain for ease
|
||||
of cleanup. So, if a single container exists on IP 172.16.30.2 with ports
|
||||
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
|
||||
rules look like this:
|
||||
|
||||
`PREROUTING`, `OUTPUT` chains:
|
||||
- `--dst-type LOCAL -j CNI-HOSTPORT-DNAT`
|
||||
|
||||
`CNI-HOSTPORT-DNAT` chain:
|
||||
- `${ConditionsV4/6} -j CNI-DN-xxxxxx` (where xxxxxx is a function of the ContainerID and network name)
|
||||
|
||||
`CNI-DN-xxxxxx` chain:
|
||||
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80`
|
||||
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
||||
|
||||
New connections to the host will have to traverse every rule, so large numbers
|
||||
of port forwards may have a performance impact. This won't affect established
|
||||
connections, just the first packet.
|
||||
|
||||
### SNAT
|
||||
The SNAT rule enables port-forwarding from the localhost IP on the host.
|
||||
This rule rewrites (masquerades) the source address for connections from
|
||||
localhost. If this rule did not exist, a connection to `localhost:80` would
|
||||
still have a source IP of 127.0.0.1 when received by the container, so no
|
||||
packets would respond. Again, it is a sequence of 3 chains. Because SNAT has to
|
||||
occur in the `POSTROUTING` chain, the packet has already been through the DNAT
|
||||
chain.
|
||||
|
||||
`POSTROUTING`:
|
||||
- `-s 127.0.0.1 ! -d 127.0.0.1 -j CNI-HOSTPORT-SNAT`
|
||||
|
||||
`CNI-HOSTPORT-SNAT`:
|
||||
- `-j CNI-SN-xxxxx`
|
||||
|
||||
`CNI-SN-xxxxx`:
|
||||
- `-p tcp -s 127.0.0.1 -d 172.16.30.2 --dport 80 -j MASQUERADE`
|
||||
- `-p tcp -s 127.0.0.1 -d 172.16.30.2 --dport 443 -j MASQUERADE`
|
||||
|
||||
Only new connections from the host, where the source address is 127.0.0.1 but
|
||||
not the destination will traverse this chain. It is unlikely that any packets
|
||||
will reach these rules without being SNATted, so the cost should be minimal.
|
||||
|
||||
Because MASQUERADE happens in POSTROUTING, it means that packets with source ip
|
||||
127.0.0.1 need to pass a routing boundary. By default, that is not allowed
|
||||
in Linux. So, need to enable the sysctl `net.ipv4.conf.IFNAME.route_localnet`,
|
||||
where IFNAME is the name of the host-side interface that routes traffic to the
|
||||
container.
|
||||
|
||||
There is no equivalent to `route_localnet` for ipv6, so SNAT does not work
|
||||
for ipv6. If you need port forwarding from localhost, your container must have
|
||||
an ipv4 address.
|
||||
|
||||
|
||||
## Known issues
|
||||
- ipsets could improve efficiency
|
||||
- SNAT does not work with ipv6.
|
127
plugins/meta/portmap/chain.go
Normal file
127
plugins/meta/portmap/chain.go
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
shellwords "github.com/mattn/go-shellwords"
|
||||
)
|
||||
|
||||
type chain struct {
|
||||
table string
|
||||
name string
|
||||
entryRule []string // the rule that enters this chain
|
||||
entryChains []string // the chains to add the entry rule
|
||||
}
|
||||
|
||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||
func (c *chain) setup(ipt *iptables.IPTables, rules [][]string) error {
|
||||
// create the chain
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := ipt.NewChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the rules to the chain
|
||||
for i := len(rules) - 1; i >= 0; i-- {
|
||||
if err := prependUnique(ipt, c.table, c.name, rules[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the entry rules
|
||||
entryRule := append(c.entryRule, "-j", c.name)
|
||||
for _, entryChain := range c.entryChains {
|
||||
if err := prependUnique(ipt, c.table, entryChain, entryRule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// teardown idempotently deletes a chain. It will not error if the chain doesn't exist.
|
||||
// It will first delete all references to this chain in the entryChains.
|
||||
func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
// flush the chain
|
||||
// This will succeed *and create the chain* if it does not exist.
|
||||
// If the chain doesn't exist, the next checks will fail.
|
||||
if err := ipt.ClearChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entryChain := range c.entryChains {
|
||||
entryChainRules, err := ipt.List(c.table, entryChain)
|
||||
if err != nil {
|
||||
// Swallow error here - probably the chain doesn't exist.
|
||||
// If we miss something the deletion will fail
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entryChainRule := range entryChainRules[1:] {
|
||||
if strings.HasSuffix(entryChainRule, "-j "+c.name) {
|
||||
chainParts, err := shellwords.Parse(entryChainRule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing iptables rule: %s: %v", entryChainRule, err)
|
||||
}
|
||||
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
|
||||
|
||||
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
|
||||
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ipt.DeleteChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prependUnique will prepend a rule to a chain, if it does not already exist
|
||||
func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error {
|
||||
exists, err := ipt.Exists(table, chain, rule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ipt.Insert(table, chain, 1, rule...)
|
||||
}
|
||||
|
||||
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
|
||||
chains, err := ipt.ListChains(tableName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chainName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
203
plugins/meta/portmap/chain_test.go
Normal file
203
plugins/meta/portmap/chain_test.go
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const TABLE = "filter" // We'll monkey around here
|
||||
|
||||
// TODO: run these tests in a new namespace
|
||||
var _ = Describe("chain tests", func() {
|
||||
var testChain chain
|
||||
var ipt *iptables.IPTables
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNs, err := ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||
chainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||
|
||||
testChain = chain{
|
||||
table: TABLE,
|
||||
name: chainName,
|
||||
entryRule: []string{"-d", "203.0.113.1"},
|
||||
entryChains: []string{tlChainName},
|
||||
}
|
||||
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtime.LockOSThread()
|
||||
err = testNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = ipt.ClearChain(TABLE, tlChainName) // This will create the chain
|
||||
if err != nil {
|
||||
currNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
if ipt == nil {
|
||||
return
|
||||
}
|
||||
ipt.ClearChain(TABLE, testChain.name)
|
||||
ipt.ClearChain(TABLE, tlChainName)
|
||||
ipt.DeleteChain(TABLE, testChain.name)
|
||||
ipt.DeleteChain(TABLE, tlChainName)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
It("creates and destroys a chain", func() {
|
||||
defer cleanup()
|
||||
|
||||
tlChainName := testChain.entryChains[0]
|
||||
|
||||
// add an extra rule to the test chain to make sure it's not touched
|
||||
err := ipt.Append(TABLE, tlChainName, "-m", "comment", "--comment",
|
||||
"canary value", "-j", "ACCEPT")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Create the chain
|
||||
chainRules := [][]string{
|
||||
{"-m", "comment", "--comment", "test 1", "-j", "RETURN"},
|
||||
{"-m", "comment", "--comment", "test 2", "-j", "RETURN"},
|
||||
}
|
||||
err = testChain.setup(ipt, chainRules)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify the chain exists
|
||||
ok := false
|
||||
chains, err := ipt.ListChains(TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, chain := range chains {
|
||||
if chain == testChain.name {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
Fail("Could not find created chain")
|
||||
}
|
||||
|
||||
// Check that the entry rule was created
|
||||
haveRules, err := ipt.List(TABLE, tlChainName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(haveRules).To(Equal([]string{
|
||||
"-N " + tlChainName,
|
||||
"-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name,
|
||||
"-A " + tlChainName + ` -m comment --comment "canary value" -j ACCEPT`,
|
||||
}))
|
||||
|
||||
// Check that the chain and rule was created
|
||||
haveRules, err = ipt.List(TABLE, testChain.name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(haveRules).To(Equal([]string{
|
||||
"-N " + testChain.name,
|
||||
"-A " + testChain.name + ` -m comment --comment "test 1" -j RETURN`,
|
||||
"-A " + testChain.name + ` -m comment --comment "test 2" -j RETURN`,
|
||||
}))
|
||||
|
||||
err = testChain.teardown(ipt)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tlRules, err := ipt.List(TABLE, tlChainName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(tlRules).To(Equal([]string{
|
||||
"-N " + tlChainName,
|
||||
"-A " + tlChainName + ` -m comment --comment "canary value" -j ACCEPT`,
|
||||
}))
|
||||
|
||||
chains, err = ipt.ListChains(TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, chain := range chains {
|
||||
if chain == testChain.name {
|
||||
Fail("chain was not deleted")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("creates chains idempotently", func() {
|
||||
defer cleanup()
|
||||
|
||||
// Create the chain
|
||||
chainRules := [][]string{
|
||||
{"-m", "comment", "--comment", "test", "-j", "RETURN"},
|
||||
}
|
||||
err := testChain.setup(ipt, chainRules)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Create it again!
|
||||
err = testChain.setup(ipt, chainRules)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure there are only two rules
|
||||
// (the first rule is an -N because go-iptables
|
||||
rules, err := ipt.List(TABLE, testChain.name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(rules)).To(Equal(2))
|
||||
|
||||
})
|
||||
|
||||
It("deletes chains idempotently", func() {
|
||||
defer cleanup()
|
||||
|
||||
// Create the chain
|
||||
chainRules := [][]string{
|
||||
{"-m", "comment", "--comment", "test", "-j", "RETURN"},
|
||||
}
|
||||
err := testChain.setup(ipt, chainRules)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testChain.teardown(ipt)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
chains, err := ipt.ListChains(TABLE)
|
||||
for _, chain := range chains {
|
||||
if chain == testChain.name {
|
||||
Fail("Chain was not deleted")
|
||||
}
|
||||
}
|
||||
|
||||
err = testChain.teardown(ipt)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
chains, err = ipt.ListChains(TABLE)
|
||||
for _, chain := range chains {
|
||||
if chain == testChain.name {
|
||||
Fail("Chain was not deleted")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
159
plugins/meta/portmap/main.go
Normal file
159
plugins/meta/portmap/main.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This is a post-setup plugin that establishes port forwarding - using iptables,
|
||||
// from the host's network interface(s) to a pod's network interface.
|
||||
//
|
||||
// It is intended to be used as a chained CNI plugin, and determines the container
|
||||
// IP from the previous result. If the result includes an IPv6 address, it will
|
||||
// also be configured. (IPTables will not forward cross-family).
|
||||
//
|
||||
// This has one notable limitation: it does not perform any kind of reservation
|
||||
// of the actual host port. If there is a service on the host, it will have all
|
||||
// its traffic captured by the container. If another container also claims a given
|
||||
// port, it will caputure the traffic - it is last-write-wins.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
// PortMapEntry corresponds to a single entry in the port_mappings argument,
|
||||
// see CONVENTIONS.md
|
||||
type PortMapEntry struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
HostIP string `json:"hostIP,omitempty"`
|
||||
}
|
||||
|
||||
type PortMapConf struct {
|
||||
types.NetConf
|
||||
SNAT *bool `json:"snat,omitempty"`
|
||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||
RuntimeConfig struct {
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
ContainerID string
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
netConf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
|
||||
if netConf.PrevResult == nil {
|
||||
return fmt.Errorf("must be called as chained plugin")
|
||||
}
|
||||
|
||||
if len(netConf.RuntimeConfig.PortMaps) == 0 {
|
||||
return types.PrintResult(netConf.PrevResult, netConf.CNIVersion)
|
||||
}
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
// Loop through IPs, setting up forwarding to the first container IP
|
||||
// per family
|
||||
hasV4 := false
|
||||
hasV6 := false
|
||||
for _, ip := range netConf.PrevResult.IPs {
|
||||
if ip.Version == "6" && hasV6 {
|
||||
continue
|
||||
} else if ip.Version == "4" && hasV4 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip known non-sandbox interfaces
|
||||
intIdx := ip.Interface
|
||||
if intIdx >= 0 && intIdx < len(netConf.PrevResult.Interfaces) && netConf.PrevResult.Interfaces[intIdx].Name != args.IfName {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := forwardPorts(netConf, ip.Address.IP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ip.Version == "6" {
|
||||
hasV6 = true
|
||||
} else {
|
||||
hasV4 = true
|
||||
}
|
||||
}
|
||||
|
||||
// Pass through the previous result
|
||||
return types.PrintResult(netConf.PrevResult, netConf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
netConf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
// We don't need to parse out whether or not we're using v6 or snat,
|
||||
// deletion is idempotent
|
||||
if err := unforwardPorts(netConf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", "0.3.0", version.Current()))
|
||||
}
|
||||
|
||||
// parseConfig parses the supplied configuration (and prevResult) from stdin.
|
||||
func parseConfig(stdin []byte) (*PortMapConf, error) {
|
||||
conf := PortMapConf{}
|
||||
|
||||
if err := json.Unmarshal(stdin, &conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if conf.RawPrevResult != nil {
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
}
|
||||
res, err := version.NewResult(conf.CNIVersion, resultBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
conf.RawPrevResult = nil
|
||||
conf.PrevResult, err = current.NewResultFromResult(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.SNAT == nil {
|
||||
tvar := true
|
||||
conf.SNAT = &tvar
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
294
plugins/meta/portmap/portmap.go
Normal file
294
plugins/meta/portmap/portmap.go
Normal file
@ -0,0 +1,294 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
// This creates the chains to be added to iptables. The basic structure is
|
||||
// a bit complex for efficiencies sake. We create 2 chains: a summary chain
|
||||
// that is shared between invocations, and an invocation (container)-specific
|
||||
// chain. This minimizes the number of operations on the top level, but allows
|
||||
// for easy cleanup.
|
||||
//
|
||||
// We also create DNAT chains to rewrite destinations, and SNAT chains so that
|
||||
// connections to localhost work.
|
||||
//
|
||||
// The basic setup (all operations are on the nat table) is:
|
||||
//
|
||||
// DNAT case (rewrite destination IP and port):
|
||||
// PREROUTING, OUTPUT: --dst-type local -j CNI-HOSTPORT_DNAT
|
||||
// CNI-HOSTPORT-DNAT: -j CNI-DN-abcd123
|
||||
// CNI-DN-abcd123: -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.33:80
|
||||
// CNI-DN-abcd123: -p tcp --dport 8081 -j DNAT ...
|
||||
//
|
||||
// SNAT case (rewrite source IP from localhost after dnat):
|
||||
// POSTROUTING: -s 127.0.0.1 ! -d 127.0.0.1 -j CNI-HOSTPORT-SNAT
|
||||
// CNI-HOSTPORT-SNAT: -j CNI-SN-abcd123
|
||||
// CNI-SN-abcd123: -p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 80 -j MASQUERADE
|
||||
// CNI-SN-abcd123: -p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 90 -j MASQUERADE
|
||||
|
||||
// The names of the top-level summary chains.
|
||||
// These should never be changed, or else upgrading will require manual
|
||||
// intervention.
|
||||
const TopLevelDNATChainName = "CNI-HOSTPORT-DNAT"
|
||||
const TopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerIP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
var conditions *[]string
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
conditions = config.ConditionsV6
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
conditions = config.ConditionsV4
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open iptables: %v", err)
|
||||
}
|
||||
|
||||
toplevelDnatChain := genToplevelDnatChain()
|
||||
if err := toplevelDnatChain.setup(ipt, nil); err != nil {
|
||||
return fmt.Errorf("failed to create top-level DNAT chain: %v", err)
|
||||
}
|
||||
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID, conditions)
|
||||
_ = dnatChain.teardown(ipt) // If we somehow collide on this container ID + network, cleanup
|
||||
|
||||
dnatRules := dnatRules(config.RuntimeConfig.PortMaps, containerIP)
|
||||
if err := dnatChain.setup(ipt, dnatRules); err != nil {
|
||||
return fmt.Errorf("unable to setup DNAT: %v", err)
|
||||
}
|
||||
|
||||
// Enable SNAT for connections to localhost.
|
||||
// This won't work for ipv6, since the kernel doesn't have the equvalent
|
||||
// route_localnet sysctl.
|
||||
if *config.SNAT && !isV6 {
|
||||
toplevelSnatChain := genToplevelSnatChain(isV6)
|
||||
if err := toplevelSnatChain.setup(ipt, nil); err != nil {
|
||||
return fmt.Errorf("failed to create top-level SNAT chain: %v", err)
|
||||
}
|
||||
|
||||
snatChain := genSnatChain(config.Name, config.ContainerID)
|
||||
_ = snatChain.teardown(ipt)
|
||||
|
||||
snatRules := snatRules(config.RuntimeConfig.PortMaps, containerIP)
|
||||
if err := snatChain.setup(ipt, snatRules); err != nil {
|
||||
return fmt.Errorf("unable to setup SNAT: %v", err)
|
||||
}
|
||||
if !isV6 {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(containerIP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genToplevelDnatChain creates the top-level summary chain that we'll
|
||||
// add our chain to. This is easy, because creating chains is idempotent.
|
||||
// IMPORTANT: do not change this, or else upgrading plugins will require
|
||||
// manual intervention.
|
||||
func genToplevelDnatChain() chain {
|
||||
return chain{
|
||||
table: "nat",
|
||||
name: TopLevelDNATChainName,
|
||||
entryRule: []string{
|
||||
"-m", "addrtype",
|
||||
"--dst-type", "LOCAL",
|
||||
},
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
}
|
||||
}
|
||||
|
||||
// genDnatChain creates the per-container chain.
|
||||
// Conditions are any static entry conditions for the chain.
|
||||
func genDnatChain(netName, containerID string, conditions *[]string) chain {
|
||||
name := formatChainName("DN-", netName, containerID)
|
||||
comment := fmt.Sprintf(`dnat name: "%s" id: "%s"`, netName, containerID)
|
||||
|
||||
ch := chain{
|
||||
table: "nat",
|
||||
name: name,
|
||||
entryRule: []string{
|
||||
"-m", "comment",
|
||||
"--comment", comment,
|
||||
},
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}
|
||||
if conditions != nil && len(*conditions) != 0 {
|
||||
ch.entryRule = append(ch.entryRule, *conditions...)
|
||||
}
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// dnatRules generates the destination NAT rules, one per port, to direct
|
||||
// traffic from hostip:hostport to podip:podport
|
||||
func dnatRules(entries []PortMapEntry, containerIP net.IP) [][]string {
|
||||
out := make([][]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
rule := []string{
|
||||
"-p", entry.Protocol,
|
||||
"--dport", strconv.Itoa(entry.HostPort)}
|
||||
|
||||
if entry.HostIP != "" {
|
||||
rule = append(rule,
|
||||
"-d", entry.HostIP)
|
||||
}
|
||||
|
||||
rule = append(rule,
|
||||
"-j", "DNAT",
|
||||
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort))
|
||||
|
||||
out = append(out, rule)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// genToplevelSnatChain creates the top-level summary snat chain.
|
||||
// IMPORTANT: do not change this, or else upgrading plugins will require
|
||||
// manual intervention
|
||||
func genToplevelSnatChain(isV6 bool) chain {
|
||||
return chain{
|
||||
table: "nat",
|
||||
name: TopLevelSNATChainName,
|
||||
entryRule: []string{
|
||||
"-s", localhostIP(isV6),
|
||||
"!", "-d", localhostIP(isV6),
|
||||
},
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
}
|
||||
}
|
||||
|
||||
// genSnatChain creates the snat (localhost) chain for this container.
|
||||
func genSnatChain(netName, containerID string) chain {
|
||||
name := formatChainName("SN-", netName, containerID)
|
||||
comment := fmt.Sprintf(`snat name: "%s" id: "%s"`, netName, containerID)
|
||||
|
||||
return chain{
|
||||
table: "nat",
|
||||
name: name,
|
||||
entryRule: []string{
|
||||
"-m", "comment",
|
||||
"--comment", comment,
|
||||
},
|
||||
entryChains: []string{TopLevelSNATChainName},
|
||||
}
|
||||
}
|
||||
|
||||
// snatRules sets up masquerading for connections to localhost:hostport,
|
||||
// rewriting the source so that returning packets are correct.
|
||||
func snatRules(entries []PortMapEntry, containerIP net.IP) [][]string {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
|
||||
out := make([][]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
out = append(out, []string{
|
||||
"-p", entry.Protocol,
|
||||
"-s", localhostIP(isV6),
|
||||
"-d", containerIP.String(),
|
||||
"--dport", strconv.Itoa(entry.ContainerPort),
|
||||
"-j", "MASQUERADE",
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||
// so that connections with a source ip of 127/8 can cross a routing boundary.
|
||||
func enableLocalnetRouting(ifName string) error {
|
||||
routeLocalnetPath := "net.ipv4.conf." + ifName + ".route_localnet"
|
||||
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
|
||||
return err
|
||||
}
|
||||
|
||||
// unforwardPorts deletes any iptables rules created by this plugin.
|
||||
// It should be idempotent - it will not error if the chain does not exist.
|
||||
//
|
||||
// We also need to be a bit clever about how we handle errors with initializing
|
||||
// iptables. We may be on a system with no ip(6)tables, or no kernel support
|
||||
// for that protocol. The ADD would be successful, since it only adds forwarding
|
||||
// based on the addresses assigned to the container. However, at DELETE time we
|
||||
// don't know which protocols were used.
|
||||
// So, we first check that iptables is "generally OK" by doing a check. If
|
||||
// not, we ignore the error, unless neither v4 nor v6 are OK.
|
||||
func unforwardPorts(config *PortMapConf) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID, nil)
|
||||
snatChain := genSnatChain(config.Name, config.ContainerID)
|
||||
|
||||
ip4t := maybeGetIptables(false)
|
||||
ip6t := maybeGetIptables(true)
|
||||
if ip4t == nil && ip6t == nil {
|
||||
return fmt.Errorf("neither iptables nor ip6tables usable")
|
||||
}
|
||||
|
||||
if ip4t != nil {
|
||||
if err := dnatChain.teardown(ip4t); err != nil {
|
||||
return fmt.Errorf("could not teardown ipv4 dnat: %v", err)
|
||||
}
|
||||
if err := snatChain.teardown(ip4t); err != nil {
|
||||
return fmt.Errorf("could not teardown ipv4 snat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ip6t != nil {
|
||||
if err := dnatChain.teardown(ip6t); err != nil {
|
||||
return fmt.Errorf("could not teardown ipv6 dnat: %v", err)
|
||||
}
|
||||
// no SNAT teardown because it doesn't work for v6
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maybeGetIptables implements the soft error swallowing. If iptables is
|
||||
// usable for the given protocol, returns a handle, otherwise nil
|
||||
func maybeGetIptables(isV6 bool) *iptables.IPTables {
|
||||
proto := iptables.ProtocolIPv4
|
||||
if isV6 {
|
||||
proto = iptables.ProtocolIPv6
|
||||
}
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(proto)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = ipt.List("nat", "OUTPUT")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ipt
|
||||
}
|
225
plugins/meta/portmap/portmap_integ_test.go
Normal file
225
plugins/meta/portmap/portmap_integ_test.go
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
var _ = Describe("portmap integration tests", func() {
|
||||
|
||||
var configList *libcni.NetworkConfigList
|
||||
var cniConf *libcni.CNIConfig
|
||||
var targetNS ns.NetNS
|
||||
var containerPort int
|
||||
var closeChan chan interface{}
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
rawConfig := `{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "cni-portmap-unit-test",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.16.31.0/24"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
configList, err = libcni.ConfListFromBytes([]byte(rawConfig))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// turn PATH in to CNI_PATH
|
||||
dirs := filepath.SplitList(os.Getenv("PATH"))
|
||||
cniConf = &libcni.CNIConfig{Path: dirs}
|
||||
|
||||
targetNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fmt.Fprintln(GinkgoWriter, "namespace:", targetNS.Path())
|
||||
|
||||
// Start an echo server and get the port
|
||||
containerPort, closeChan, err = RunEchoServerInNS(targetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if targetNS != nil {
|
||||
targetNS.Close()
|
||||
}
|
||||
})
|
||||
|
||||
// This needs to be done using Ginkgo's asynchronous testing mode.
|
||||
It("forwards a TCP port on ipv4", func(done Done) {
|
||||
var err error
|
||||
hostPort := 9999
|
||||
runtimeConfig := libcni.RuntimeConf{
|
||||
ContainerID: "unit-test",
|
||||
NetNS: targetNS.Path(),
|
||||
IfName: "eth0",
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": []map[string]interface{}{
|
||||
{
|
||||
"hostPort": hostPort,
|
||||
"containerPort": containerPort,
|
||||
"protocol": "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Make delete idempotent, so we can clean up on failure
|
||||
netDeleted := false
|
||||
deleteNetwork := func() error {
|
||||
if netDeleted {
|
||||
return nil
|
||||
}
|
||||
netDeleted = true
|
||||
return cniConf.DelNetworkList(configList, &runtimeConfig)
|
||||
}
|
||||
|
||||
// we'll also manually check the iptables chains
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dnatChainName := genDnatChain("cni-portmap-unit-test", "unit-test", nil).name
|
||||
|
||||
// Create the network
|
||||
resI, err := cniConf.AddNetworkList(configList, &runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer deleteNetwork()
|
||||
|
||||
// Check the chain exists
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var contIP net.IP
|
||||
|
||||
for _, ip := range result.IPs {
|
||||
if result.Interfaces[ip.Interface].Sandbox == "" {
|
||||
continue
|
||||
}
|
||||
contIP = ip.Address.IP
|
||||
}
|
||||
if contIP == nil {
|
||||
Fail("could not determine container IP")
|
||||
}
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
contOK := testEchoServer(fmt.Sprintf("%s:%d", contIP.String(), containerPort))
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
hostIP := getLocalIP()
|
||||
dnatOK := testEchoServer(fmt.Sprintf("%s:%d", hostIP, hostPort))
|
||||
|
||||
// Verify that a connection to localhost works
|
||||
snatOK := testEchoServer(fmt.Sprintf("%s:%d", "127.0.0.1", hostPort))
|
||||
|
||||
// Cleanup
|
||||
close(closeChan)
|
||||
err = deleteNetwork()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify iptables rules are gone
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
|
||||
|
||||
// Check that everything succeeded *after* we clean up the network
|
||||
if !contOK {
|
||||
Fail("connection direct to " + contIP.String() + " failed")
|
||||
}
|
||||
if !dnatOK {
|
||||
Fail("Connection to " + hostIP + " was not forwarded")
|
||||
}
|
||||
if !snatOK {
|
||||
Fail("connection to 127.0.0.1 was not forwarded")
|
||||
}
|
||||
|
||||
close(done)
|
||||
|
||||
}, 5)
|
||||
})
|
||||
|
||||
// testEchoServer returns true if we found an echo server on the port
|
||||
func testEchoServer(address string) bool {
|
||||
fmt.Fprintln(GinkgoWriter, "dialing", address)
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
fmt.Fprintln(GinkgoWriter, "connection to", address, "failed:", err)
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
fmt.Fprintln(GinkgoWriter, "connected to", address)
|
||||
|
||||
message := "Aliquid melius quam pessimum optimum non est."
|
||||
_, err = fmt.Fprint(conn, message)
|
||||
if err != nil {
|
||||
fmt.Fprintln(GinkgoWriter, "sending message to", address, " failed:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
fmt.Fprintln(GinkgoWriter, "reading...")
|
||||
response := make([]byte, len(message))
|
||||
_, err = conn.Read(response)
|
||||
if err != nil {
|
||||
fmt.Fprintln(GinkgoWriter, "receiving message from", address, " failed:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Fprintln(GinkgoWriter, "read...")
|
||||
if string(response) == message {
|
||||
return true
|
||||
}
|
||||
fmt.Fprintln(GinkgoWriter, "returned message didn't match?")
|
||||
return false
|
||||
}
|
||||
|
||||
func getLocalIP() string {
|
||||
addrs, err := netlink.AddrList(nil, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, addr := range addrs {
|
||||
return addr.IP.String()
|
||||
}
|
||||
Fail("no live addresses")
|
||||
return ""
|
||||
}
|
103
plugins/meta/portmap/portmap_suite_test.go
Normal file
103
plugins/meta/portmap/portmap_suite_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPortmap(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "portmap Suite")
|
||||
}
|
||||
|
||||
// OpenEchoServer opens a server that handles one connection before closing.
|
||||
// It opens on a random port and sends the port number on portChan when
|
||||
// the server is up and running. If an error is encountered, closes portChan.
|
||||
// If closeChan is closed, closes the socket.
|
||||
func OpenEchoServer(portChan chan<- int, closeChan <-chan interface{}) error {
|
||||
laddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
close(portChan)
|
||||
return err
|
||||
}
|
||||
sock, err := net.ListenTCP("tcp", laddr)
|
||||
if err != nil {
|
||||
close(portChan)
|
||||
return err
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
switch addr := sock.Addr().(type) {
|
||||
case *net.TCPAddr:
|
||||
portChan <- addr.Port
|
||||
default:
|
||||
close(portChan)
|
||||
return fmt.Errorf("addr cast failed!")
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-closeChan:
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
sock.SetDeadline(time.Now().Add(time.Second))
|
||||
con, err := sock.AcceptTCP()
|
||||
if err != nil {
|
||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf := make([]byte, 512)
|
||||
con.Read(buf)
|
||||
con.Write(buf)
|
||||
con.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func RunEchoServerInNS(netNS ns.NetNS) (int, chan interface{}, error) {
|
||||
portChan := make(chan int)
|
||||
closeChan := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
err := netNS.Do(func(ns.NetNS) error {
|
||||
OpenEchoServer(portChan, closeChan)
|
||||
return nil
|
||||
})
|
||||
// Somehow the ns.Do failed
|
||||
if err != nil {
|
||||
close(portChan)
|
||||
}
|
||||
}()
|
||||
|
||||
portNum := <-portChan
|
||||
if portNum == 0 {
|
||||
return 0, nil, fmt.Errorf("failed to execute server")
|
||||
}
|
||||
|
||||
return portNum, closeChan, nil
|
||||
}
|
136
plugins/meta/portmap/portmap_test.go
Normal file
136
plugins/meta/portmap/portmap_test.go
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration", func() {
|
||||
netName := "testNetName"
|
||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||
|
||||
mappings := []PortMapEntry{
|
||||
{80, 90, "tcp", ""},
|
||||
{1000, 2000, "udp", ""},
|
||||
}
|
||||
ipv4addr := net.ParseIP("192.2.0.1")
|
||||
ipv6addr := net.ParseIP("2001:db8::1")
|
||||
|
||||
Describe("Generating chains", func() {
|
||||
Context("for DNAT", func() {
|
||||
It("generates a correct container chain", func() {
|
||||
ch := genDnatChain(netName, containerID, &[]string{"-m", "hello"})
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryRule: []string{
|
||||
"-m", "comment",
|
||||
"--comment", `dnat name: "testNetName" id: "` + containerID + `"`,
|
||||
"-m", "hello",
|
||||
},
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
})
|
||||
|
||||
It("generates a correct top-level chain", func() {
|
||||
ch := genToplevelDnatChain()
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-DNAT",
|
||||
entryRule: []string{
|
||||
"-m", "addrtype",
|
||||
"--dst-type", "LOCAL",
|
||||
},
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("for SNAT", func() {
|
||||
It("generates a correct container chain", func() {
|
||||
ch := genSnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-SN-bfd599665540dd91d5d28",
|
||||
entryRule: []string{
|
||||
"-m", "comment",
|
||||
"--comment", `snat name: "testNetName" id: "` + containerID + `"`,
|
||||
},
|
||||
entryChains: []string{TopLevelSNATChainName},
|
||||
}))
|
||||
})
|
||||
|
||||
It("generates a correct top-level chain", func() {
|
||||
Context("for ipv4", func() {
|
||||
ch := genToplevelSnatChain(false)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-SNAT",
|
||||
entryRule: []string{
|
||||
"-s", "127.0.0.1",
|
||||
"!", "-d", "127.0.0.1",
|
||||
},
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Forwarding rules", func() {
|
||||
Context("for DNAT", func() {
|
||||
It("generates correct ipv4 rules", func() {
|
||||
rules := dnatRules(mappings, ipv4addr)
|
||||
Expect(rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "80", "-j", "DNAT", "--to-destination", "192.2.0.1:90"},
|
||||
{"-p", "udp", "--dport", "1000", "-j", "DNAT", "--to-destination", "192.2.0.1:2000"},
|
||||
}))
|
||||
})
|
||||
It("generates correct ipv6 rules", func() {
|
||||
rules := dnatRules(mappings, ipv6addr)
|
||||
Expect(rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "80", "-j", "DNAT", "--to-destination", "[2001:db8::1]:90"},
|
||||
{"-p", "udp", "--dport", "1000", "-j", "DNAT", "--to-destination", "[2001:db8::1]:2000"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("for SNAT", func() {
|
||||
|
||||
It("generates correct ipv4 rules", func() {
|
||||
rules := snatRules(mappings, ipv4addr)
|
||||
Expect(rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "-s", "127.0.0.1", "-d", "192.2.0.1", "--dport", "90", "-j", "MASQUERADE"},
|
||||
{"-p", "udp", "-s", "127.0.0.1", "-d", "192.2.0.1", "--dport", "2000", "-j", "MASQUERADE"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("generates correct ipv6 rules", func() {
|
||||
rules := snatRules(mappings, ipv6addr)
|
||||
Expect(rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "-s", "::1", "-d", "2001:db8::1", "--dport", "90", "-j", "MASQUERADE"},
|
||||
{"-p", "udp", "-s", "::1", "-d", "2001:db8::1", "--dport", "2000", "-j", "MASQUERADE"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
67
plugins/meta/portmap/utils.go
Normal file
67
plugins/meta/portmap/utils.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const maxChainNameLength = 28
|
||||
|
||||
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
|
||||
// need to wrap v6 literals in a []
|
||||
func fmtIpPort(ip net.IP, port int) string {
|
||||
if ip.To4() == nil {
|
||||
return fmt.Sprintf("[%s]:%d", ip.String(), port)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", ip.String(), port)
|
||||
}
|
||||
|
||||
func localhostIP(isV6 bool) string {
|
||||
if isV6 {
|
||||
return "::1"
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// getRoutableHostIF will try and determine which interface routes the container's
|
||||
// traffic. This is the one on which we disable martian filtering.
|
||||
func getRoutableHostIF(containerIP net.IP) string {
|
||||
routes, err := netlink.RouteGet(containerIP)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
link, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return link.Attrs().Name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatChainName(prefix, name, id string) string {
|
||||
chainBytes := sha512.Sum512([]byte(name + id))
|
||||
chain := fmt.Sprintf("CNI-%s%x", prefix, chainBytes)
|
||||
return chain[:maxChainNameLength]
|
||||
}
|
2
test.sh
2
test.sh
@ -10,7 +10,7 @@ source ./build.sh
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/ipam/host-local/backend/allocator plugins/main/loopback plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/meta/flannel plugins/main/vlan plugins/sample pkg/ip pkg/ipam pkg/ns pkg/utils pkg/utils/hwaddr pkg/utils/sysctl"
|
||||
TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/ipam/host-local/backend/allocator plugins/main/loopback plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/meta/flannel plugins/main/vlan plugins/sample pkg/ip pkg/ipam pkg/ns pkg/utils pkg/utils/hwaddr pkg/utils/sysctl plugins/meta/portmap"
|
||||
|
||||
# user has not provided PKG override
|
||||
if [ -z "$PKG" ]; then
|
||||
|
219
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
Normal file
219
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
// Copyright 2015 CNI authors
|
||||
//
|
||||
// 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 libcni
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type RuntimeConf struct {
|
||||
ContainerID string
|
||||
NetNS string
|
||||
IfName string
|
||||
Args [][2]string
|
||||
// A dictionary of capability-specific data passed by the runtime
|
||||
// to plugins as top-level keys in the 'runtimeConfig' dictionary
|
||||
// of the plugin's stdin data. libcni will ensure that only keys
|
||||
// in this map which match the capabilities of the plugin are passed
|
||||
// to the plugin
|
||||
CapabilityArgs map[string]interface{}
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Network *types.NetConf
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type NetworkConfigList struct {
|
||||
Name string
|
||||
CNIVersion string
|
||||
Plugins []*NetworkConfig
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type CNI interface {
|
||||
AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||
DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error
|
||||
|
||||
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
|
||||
}
|
||||
|
||||
type CNIConfig struct {
|
||||
Path []string
|
||||
}
|
||||
|
||||
// CNIConfig implements the CNI interface
|
||||
var _ CNI = &CNIConfig{}
|
||||
|
||||
func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": list.Name,
|
||||
"cniVersion": list.CNIVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
orig, err = InjectConf(orig, inject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return injectRuntimeConfig(orig, rt)
|
||||
}
|
||||
|
||||
// This function takes a libcni RuntimeConf structure and injects values into
|
||||
// a "runtimeConfig" dictionary in the CNI network configuration JSON that
|
||||
// will be passed to the plugin on stdin.
|
||||
//
|
||||
// Only "capabilities arguments" passed by the runtime are currently injected.
|
||||
// These capabilities arguments are filtered through the plugin's advertised
|
||||
// capabilities from its config JSON, and any keys in the CapabilityArgs
|
||||
// matching plugin capabilities are added to the "runtimeConfig" dictionary
|
||||
// sent to the plugin via JSON on stdin. For exmaple, if the plugin's
|
||||
// capabilities include "portMappings", and the CapabilityArgs map includes a
|
||||
// "portMappings" key, that key and its value are added to the "runtimeConfig"
|
||||
// dictionary to be passed to the plugin's stdin.
|
||||
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
var err error
|
||||
|
||||
rc := make(map[string]interface{})
|
||||
for capability, supported := range orig.Network.Capabilities {
|
||||
if !supported {
|
||||
continue
|
||||
}
|
||||
if data, ok := rt.CapabilityArgs[capability]; ok {
|
||||
rc[capability] = data
|
||||
}
|
||||
}
|
||||
|
||||
if len(rc) > 0 {
|
||||
orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return orig, nil
|
||||
}
|
||||
|
||||
// AddNetworkList executes a sequence of plugins with the ADD command
|
||||
func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
|
||||
var prevResult types.Result
|
||||
for _, net := range list.Plugins {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newConf, err := buildOneConfig(list, net, prevResult, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return prevResult, nil
|
||||
}
|
||||
|
||||
// DelNetworkList executes a sequence of plugins with the DEL command
|
||||
func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error {
|
||||
for i := len(list.Plugins) - 1; i >= 0; i-- {
|
||||
net := list.Plugins[i]
|
||||
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newConf, err := buildOneConfig(list, net, nil, rt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNetwork executes the plugin with the ADD command
|
||||
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
net, err = injectRuntimeConfig(net, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
|
||||
}
|
||||
|
||||
// DelNetwork executes the plugin with the DEL command
|
||||
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
net, err = injectRuntimeConfig(net, rt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
|
||||
}
|
||||
|
||||
// GetVersionInfo reports which versions of the CNI spec are supported by
|
||||
// the given plugin.
|
||||
func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
|
||||
pluginPath, err := invoke.FindInPath(pluginType, c.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invoke.GetVersionInfo(pluginPath)
|
||||
}
|
||||
|
||||
// =====
|
||||
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||
return &invoke.Args{
|
||||
Command: action,
|
||||
ContainerID: rt.ContainerID,
|
||||
NetNS: rt.NetNS,
|
||||
PluginArgs: rt.Args,
|
||||
IfName: rt.IfName,
|
||||
Path: strings.Join(c.Path, string(os.PathListSeparator)),
|
||||
}
|
||||
}
|
256
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
Normal file
256
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
// Copyright 2015 CNI authors
|
||||
//
|
||||
// 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 libcni
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type NotFoundError struct {
|
||||
Dir string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e NotFoundError) Error() string {
|
||||
return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
|
||||
}
|
||||
|
||||
type NoConfigsFoundError struct {
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (e NoConfigsFoundError) Error() string {
|
||||
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
|
||||
}
|
||||
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
conf := &NetworkConfig{Bytes: bytes}
|
||||
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
rawList := make(map[string]interface{})
|
||||
if err := json.Unmarshal(bytes, &rawList); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: %s", err)
|
||||
}
|
||||
|
||||
rawName, ok := rawList["name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no name")
|
||||
}
|
||||
name, ok := rawName.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName)
|
||||
}
|
||||
|
||||
var cniVersion string
|
||||
rawVersion, ok := rawList["cniVersion"]
|
||||
if ok {
|
||||
cniVersion, ok = rawVersion.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion)
|
||||
}
|
||||
}
|
||||
|
||||
list := &NetworkConfigList{
|
||||
Name: name,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: bytes,
|
||||
}
|
||||
|
||||
var plugins []interface{}
|
||||
plug, ok := rawList["plugins"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
|
||||
}
|
||||
plugins, ok = plug.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
|
||||
}
|
||||
if len(plugins) == 0 {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
|
||||
}
|
||||
|
||||
for i, conf := range plugins {
|
||||
newBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
|
||||
}
|
||||
netConf, err := ConfFromBytes(newBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
|
||||
}
|
||||
list.Plugins = append(list.Plugins, netConf)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
}
|
||||
return ConfListFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfFiles(dir string, extensions []string) ([]string, error) {
|
||||
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
switch {
|
||||
case err == nil: // break
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confFiles := []string{}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fileExt := filepath.Ext(f.Name())
|
||||
for _, ext := range extensions {
|
||||
if fileExt == ext {
|
||||
confFiles = append(confFiles, filepath.Join(dir, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return confFiles, nil
|
||||
}
|
||||
|
||||
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||
files, err := ConfFiles(dir, []string{".conf", ".json"})
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(files) == 0:
|
||||
return nil, NoConfigsFoundError{Dir: dir}
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Network.Name == name {
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
return nil, NotFoundError{dir, name}
|
||||
}
|
||||
|
||||
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
||||
files, err := ConfFiles(dir, []string{".conflist"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfListFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Name == name {
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Try and load a network configuration file (instead of list)
|
||||
// from the same name, then upconvert.
|
||||
singleConf, err := LoadConf(dir, name)
|
||||
if err != nil {
|
||||
// A little extra logic so the error makes sense
|
||||
if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
|
||||
// Config lists found but no config files found
|
||||
return nil, NotFoundError{dir, name}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
return ConfListFromConf(singleConf)
|
||||
}
|
||||
|
||||
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
|
||||
config := make(map[string]interface{})
|
||||
err := json.Unmarshal(original.Bytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range newValues {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("keys cannot be empty")
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return nil, fmt.Errorf("key '%s' value must not be nil", key)
|
||||
}
|
||||
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ConfFromBytes(newBytes)
|
||||
}
|
||||
|
||||
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
|
||||
// with the single network as the only entry in the list.
|
||||
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
|
||||
// Re-deserialize the config's json, then make a raw map configlist.
|
||||
// This may seem a bit strange, but it's to make the Bytes fields
|
||||
// actually make sense. Otherwise, the generated json is littered with
|
||||
// golang default values.
|
||||
|
||||
rawConfig := make(map[string]interface{})
|
||||
if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawConfigList := map[string]interface{}{
|
||||
"name": original.Network.Name,
|
||||
"cniVersion": original.Network.CNIVersion,
|
||||
"plugins": []interface{}{rawConfig},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(rawConfigList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ConfListFromBytes(b)
|
||||
}
|
191
vendor/github.com/coreos/go-iptables/LICENSE
generated
vendored
Normal file
191
vendor/github.com/coreos/go-iptables/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
Apache 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.
|
81
vendor/github.com/coreos/go-iptables/iptables/iptables.go
generated
vendored
81
vendor/github.com/coreos/go-iptables/iptables/iptables.go
generated
vendored
@ -39,29 +39,52 @@ func (e *Error) Error() string {
|
||||
return fmt.Sprintf("exit status %v: %v", e.ExitStatus(), e.msg)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// New creates a new IPTables.
|
||||
// For backwards compatibility, this always uses IPv4, i.e. "iptables".
|
||||
func New() (*IPTables, error) {
|
||||
path, err := exec.LookPath("iptables")
|
||||
return NewWithProtocol(ProtocolIPv4)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
path, err := exec.LookPath(getIptablesCommand(proto))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkPresent, waitPresent, err := getIptablesCommandSupport()
|
||||
checkPresent, waitPresent, err := getIptablesCommandSupport(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error checking iptables version: %v", err)
|
||||
}
|
||||
ipt := IPTables{
|
||||
path: path,
|
||||
proto: proto,
|
||||
hasCheck: checkPresent,
|
||||
hasWait: waitPresent,
|
||||
}
|
||||
return &ipt, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -116,6 +139,41 @@ func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error {
|
||||
// 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
|
||||
}
|
||||
|
||||
func (ipt *IPTables) executeList(args []string) ([]string, error) {
|
||||
var stdout bytes.Buffer
|
||||
if err := ipt.runWithOutput(args, &stdout); err != nil {
|
||||
return nil, err
|
||||
@ -129,6 +187,8 @@ func (ipt *IPTables) List(table, chain string) ([]string, error) {
|
||||
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)
|
||||
}
|
||||
@ -200,9 +260,18 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
|
||||
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() (bool, bool, error) {
|
||||
vstring, err := getIptablesVersionString()
|
||||
func getIptablesCommandSupport(path string) (bool, bool, error) {
|
||||
vstring, err := getIptablesVersionString(path)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -243,8 +312,8 @@ func extractIptablesVersion(str string) (int, int, int, error) {
|
||||
}
|
||||
|
||||
// Runs "iptables --version" to get the version string
|
||||
func getIptablesVersionString() (string, error) {
|
||||
cmd := exec.Command("iptables", "--version")
|
||||
func getIptablesVersionString(path string) (string, error) {
|
||||
cmd := exec.Command(path, "--version")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
|
8
vendor/github.com/mattn/go-shellwords/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/mattn/go-shellwords/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL
|
21
vendor/github.com/mattn/go-shellwords/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mattn/go-shellwords/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
47
vendor/github.com/mattn/go-shellwords/README.md
generated
vendored
Normal file
47
vendor/github.com/mattn/go-shellwords/README.md
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# go-shellwords
|
||||
|
||||
[](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||
[](https://travis-ci.org/mattn/go-shellwords)
|
||||
|
||||
Parse line as shell words.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
args, err := shellwords.Parse("./foo --bar=baz")
|
||||
// args should be ["./foo", "--bar=baz"]
|
||||
```
|
||||
|
||||
```go
|
||||
os.Setenv("FOO", "bar")
|
||||
p := shellwords.NewParser()
|
||||
p.ParseEnv = true
|
||||
args, err := p.Parse("./foo $FOO")
|
||||
// args should be ["./foo", "bar"]
|
||||
```
|
||||
|
||||
```go
|
||||
p := shellwords.NewParser()
|
||||
p.ParseBacktick = true
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
```go
|
||||
shellwords.ParseBacktick = true
|
||||
p := shellwords.NewParser()
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||
|
||||
# License
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2017
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
145
vendor/github.com/mattn/go-shellwords/shellwords.go
generated
vendored
Normal file
145
vendor/github.com/mattn/go-shellwords/shellwords.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
ParseEnv bool = false
|
||||
ParseBacktick bool = false
|
||||
)
|
||||
|
||||
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceEnv(s string) string {
|
||||
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||
s = s[1:]
|
||||
if s[0] == '{' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
return os.Getenv(s)
|
||||
})
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
ParseEnv bool
|
||||
ParseBacktick bool
|
||||
Position int
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{ParseEnv, ParseBacktick, 0}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(line string) ([]string, error) {
|
||||
args := []string{}
|
||||
buf := ""
|
||||
var escaped, doubleQuoted, singleQuoted, backQuote bool
|
||||
backtick := ""
|
||||
|
||||
pos := -1
|
||||
got := false
|
||||
|
||||
loop:
|
||||
for i, r := range line {
|
||||
if escaped {
|
||||
buf += string(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf += string(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSpace(r) {
|
||||
if singleQuoted || doubleQuoted || backQuote {
|
||||
buf += string(r)
|
||||
backtick += string(r)
|
||||
} else if got {
|
||||
if p.ParseEnv {
|
||||
buf = replaceEnv(buf)
|
||||
}
|
||||
args = append(args, buf)
|
||||
buf = ""
|
||||
got = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '`':
|
||||
if !singleQuoted && !doubleQuoted {
|
||||
if p.ParseBacktick {
|
||||
if backQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
continue
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
}
|
||||
case '"':
|
||||
if !singleQuoted {
|
||||
doubleQuoted = !doubleQuoted
|
||||
continue
|
||||
}
|
||||
case '\'':
|
||||
if !doubleQuoted {
|
||||
singleQuoted = !singleQuoted
|
||||
continue
|
||||
}
|
||||
case ';', '&', '|', '<', '>':
|
||||
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
||||
pos = i
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
got = true
|
||||
buf += string(r)
|
||||
if backQuote {
|
||||
backtick += string(r)
|
||||
}
|
||||
}
|
||||
|
||||
if got {
|
||||
if p.ParseEnv {
|
||||
buf = replaceEnv(buf)
|
||||
}
|
||||
args = append(args, buf)
|
||||
}
|
||||
|
||||
if escaped || singleQuoted || doubleQuoted || backQuote {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
|
||||
p.Position = pos
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func Parse(line string) ([]string, error) {
|
||||
return NewParser().Parse(line)
|
||||
}
|
19
vendor/github.com/mattn/go-shellwords/util_posix.go
generated
vendored
Normal file
19
vendor/github.com/mattn/go-shellwords/util_posix.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// +build !windows
|
||||
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("SHELL")
|
||||
b, err := exec.Command(shell, "-c", line).Output()
|
||||
if err != nil {
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
17
vendor/github.com/mattn/go-shellwords/util_windows.go
generated
vendored
Normal file
17
vendor/github.com/mattn/go-shellwords/util_windows.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("COMSPEC")
|
||||
b, err := exec.Command(shell, "/c", line).Output()
|
||||
if err != nil {
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
Reference in New Issue
Block a user