Merge pull request #5 from dcbw/add-cni-plugins
plugins: add plugins from containernetworking/cni
This commit is contained in:
commit
f130db5b61
55
Godeps/Godeps.json
generated
55
Godeps/Godeps.json
generated
@ -9,63 +9,86 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
|
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/ip",
|
"ImportPath": "github.com/containernetworking/cni/pkg/ip",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/ipam",
|
"ImportPath": "github.com/containernetworking/cni/pkg/ipam",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/ns",
|
"ImportPath": "github.com/containernetworking/cni/pkg/ns",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
|
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/testutils",
|
"ImportPath": "github.com/containernetworking/cni/pkg/testutils",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/types",
|
"ImportPath": "github.com/containernetworking/cni/pkg/types",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
|
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
|
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/containernetworking/cni/pkg/utils",
|
||||||
|
"Comment": "v0.5.0",
|
||||||
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/utils/hwaddr",
|
"ImportPath": "github.com/containernetworking/cni/pkg/utils/hwaddr",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/containernetworking/cni/pkg/utils/sysctl",
|
||||||
|
"Comment": "v0.5.0",
|
||||||
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/containernetworking/cni/pkg/version",
|
"ImportPath": "github.com/containernetworking/cni/pkg/version",
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "4ce9b019aab51b28a32ff6549784a69f9b209fe4"
|
"Rev": "1a9288c3c09cea4e580fdb1a636f1c5e185a391f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
||||||
"Comment": "v0.1.0",
|
"Comment": "v0.1.0",
|
||||||
"Rev": "fbb73372b87f6e89951c2b6b31470c2c9d5cfae3"
|
"Rev": "fbb73372b87f6e89951c2b6b31470c2c9d5cfae3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/go-systemd/activation",
|
||||||
|
"Comment": "v2-53-g2688e91",
|
||||||
|
"Rev": "2688e91251d9d8e404e86dd8f096e23b2f086958"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/d2g/dhcp4",
|
||||||
|
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/d2g/dhcp4client",
|
||||||
|
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/onsi/ginkgo",
|
"ImportPath": "github.com/onsi/ginkgo",
|
||||||
"Comment": "v1.2.0-29-g7f8ab55",
|
"Comment": "v1.2.0-29-g7f8ab55",
|
||||||
@ -151,6 +174,16 @@
|
|||||||
"Comment": "v1.0-71-g2152b45",
|
"Comment": "v1.0-71-g2152b45",
|
||||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/onsi/gomega/gbytes",
|
||||||
|
"Comment": "v1.0-71-g2152b45",
|
||||||
|
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/onsi/gomega/gexec",
|
||||||
|
"Comment": "v1.0-71-g2152b45",
|
||||||
|
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/onsi/gomega/internal/assertion",
|
"ImportPath": "github.com/onsi/gomega/internal/assertion",
|
||||||
"Comment": "v1.0-71-g2152b45",
|
"Comment": "v1.0-71-g2152b45",
|
||||||
|
2
build
2
build
@ -15,7 +15,7 @@ export GOPATH=${PWD}/gopath
|
|||||||
mkdir -p "${PWD}/bin"
|
mkdir -p "${PWD}/bin"
|
||||||
|
|
||||||
echo "Building plugins"
|
echo "Building plugins"
|
||||||
PLUGINS="plugins/*"
|
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample"
|
||||||
for d in $PLUGINS; do
|
for d in $PLUGINS; do
|
||||||
if [ -d "$d" ]; then
|
if [ -d "$d" ]; then
|
||||||
plugin="$(basename "$d")"
|
plugin="$(basename "$d")"
|
||||||
|
159
plugins/ipam/dhcp/daemon.go
Normal file
159
plugins/ipam/dhcp/daemon.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/rpc"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/coreos/go-systemd/activation"
|
||||||
|
)
|
||||||
|
|
||||||
|
const listenFdsStart = 3
|
||||||
|
const resendCount = 3
|
||||||
|
|
||||||
|
var errNoMoreTries = errors.New("no more tries")
|
||||||
|
|
||||||
|
type DHCP struct {
|
||||||
|
mux sync.Mutex
|
||||||
|
leases map[string]*DHCPLease
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDHCP() *DHCP {
|
||||||
|
return &DHCP{
|
||||||
|
leases: make(map[string]*DHCPLease),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate acquires an IP from a DHCP server for a specified container.
|
||||||
|
// The acquired lease will be maintained until Release() is called.
|
||||||
|
func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||||
|
conf := types.NetConf{}
|
||||||
|
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||||
|
return fmt.Errorf("error parsing netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID := args.ContainerID + "/" + conf.Name
|
||||||
|
l, err := AcquireLease(clientID, args.Netns, args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipn, err := l.IPNet()
|
||||||
|
if err != nil {
|
||||||
|
l.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.setLease(args.ContainerID, conf.Name, l)
|
||||||
|
|
||||||
|
result.IPs = []*current.IPConfig{{
|
||||||
|
Version: "4",
|
||||||
|
Address: *ipn,
|
||||||
|
Gateway: l.Gateway(),
|
||||||
|
}}
|
||||||
|
result.Routes = l.Routes()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release stops maintenance of the lease acquired in Allocate()
|
||||||
|
// and sends a release msg to the DHCP server.
|
||||||
|
func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error {
|
||||||
|
conf := types.NetConf{}
|
||||||
|
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||||
|
return fmt.Errorf("error parsing netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := d.getLease(args.ContainerID, conf.Name); l != nil {
|
||||||
|
l.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("lease not found: %v/%v", args.ContainerID, conf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DHCP) getLease(contID, netName string) *DHCPLease {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
// TODO(eyakubovich): hash it to avoid collisions
|
||||||
|
l, ok := d.leases[contID+netName]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DHCP) setLease(contID, netName string, l *DHCPLease) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
// TODO(eyakubovich): hash it to avoid collisions
|
||||||
|
d.leases[contID+netName] = l
|
||||||
|
}
|
||||||
|
|
||||||
|
func getListener() (net.Listener, error) {
|
||||||
|
l, err := activation.Listeners(true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(l) == 0:
|
||||||
|
if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.Listen("unix", socketPath)
|
||||||
|
|
||||||
|
case len(l) == 1:
|
||||||
|
if l[0] == nil {
|
||||||
|
return nil, fmt.Errorf("LISTEN_FDS=1 but no FD found")
|
||||||
|
}
|
||||||
|
return l[0], nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Too many (%v) FDs passed through socket activation", len(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDaemon() {
|
||||||
|
// since other goroutines (on separate threads) will change namespaces,
|
||||||
|
// ensure the RPC server does not get scheduled onto those
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
l, err := getListener()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting listener: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dhcp := newDHCP()
|
||||||
|
rpc.Register(dhcp)
|
||||||
|
rpc.HandleHTTP()
|
||||||
|
http.Serve(l, nil)
|
||||||
|
}
|
337
plugins/ipam/dhcp/lease.go
Normal file
337
plugins/ipam/dhcp/lease.go
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
"github.com/d2g/dhcp4client"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC 2131 suggests using exponential backoff, starting with 4sec
|
||||||
|
// and randomized to +/- 1sec
|
||||||
|
const resendDelay0 = 4 * time.Second
|
||||||
|
const resendDelayMax = 32 * time.Second
|
||||||
|
|
||||||
|
const (
|
||||||
|
leaseStateBound = iota
|
||||||
|
leaseStateRenewing
|
||||||
|
leaseStateRebinding
|
||||||
|
)
|
||||||
|
|
||||||
|
// This implementation uses 1 OS thread per lease. This is because
|
||||||
|
// all the network operations have to be done in network namespace
|
||||||
|
// of the interface. This can be improved by switching to the proper
|
||||||
|
// namespace for network ops and using fewer threads. However, this
|
||||||
|
// needs to be done carefully as dhcp4client ops are blocking.
|
||||||
|
|
||||||
|
type DHCPLease struct {
|
||||||
|
clientID string
|
||||||
|
ack *dhcp4.Packet
|
||||||
|
opts dhcp4.Options
|
||||||
|
link netlink.Link
|
||||||
|
renewalTime time.Time
|
||||||
|
rebindingTime time.Time
|
||||||
|
expireTime time.Time
|
||||||
|
stop chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireLease gets an DHCP lease and then maintains it in the background
|
||||||
|
// by periodically renewing it. The acquired lease can be released by
|
||||||
|
// calling DHCPLease.Stop()
|
||||||
|
func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
l := &DHCPLease{
|
||||||
|
clientID: clientID,
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%v: acquiring lease", clientID)
|
||||||
|
|
||||||
|
l.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
errCh <- ns.WithNetNSPath(netns, func(_ ns.NetNS) error {
|
||||||
|
defer l.wg.Done()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error looking up %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.link = link
|
||||||
|
|
||||||
|
if err = l.acquire(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%v: lease acquired, expiration is %v", l.clientID, l.expireTime)
|
||||||
|
|
||||||
|
errCh <- nil
|
||||||
|
|
||||||
|
l.maintain()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := <-errCh; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop terminates the background task that maintains the lease
|
||||||
|
// and issues a DHCP Release
|
||||||
|
func (l *DHCPLease) Stop() {
|
||||||
|
close(l.stop)
|
||||||
|
l.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) acquire() error {
|
||||||
|
c, err := newDHCPClient(l.link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
|
||||||
|
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
|
||||||
|
if err = netlink.LinkSetUp(l.link); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
|
||||||
|
ok, ack, err := c.Request()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
case !ok:
|
||||||
|
return nil, fmt.Errorf("DHCP server NACK'd own offer")
|
||||||
|
default:
|
||||||
|
return &ack, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.commit(pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
|
||||||
|
opts := ack.ParseOptions()
|
||||||
|
|
||||||
|
leaseTime, err := parseLeaseTime(opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rebindingTime, err := parseRebindingTime(opts)
|
||||||
|
if err != nil || rebindingTime > leaseTime {
|
||||||
|
// Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
|
||||||
|
rebindingTime = leaseTime * 85 / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
renewalTime, err := parseRenewalTime(opts)
|
||||||
|
if err != nil || renewalTime > rebindingTime {
|
||||||
|
// Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
|
||||||
|
renewalTime = leaseTime / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
l.expireTime = now.Add(leaseTime)
|
||||||
|
l.renewalTime = now.Add(renewalTime)
|
||||||
|
l.rebindingTime = now.Add(rebindingTime)
|
||||||
|
l.ack = ack
|
||||||
|
l.opts = opts
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) maintain() {
|
||||||
|
state := leaseStateBound
|
||||||
|
|
||||||
|
for {
|
||||||
|
var sleepDur time.Duration
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case leaseStateBound:
|
||||||
|
sleepDur = l.renewalTime.Sub(time.Now())
|
||||||
|
if sleepDur <= 0 {
|
||||||
|
log.Printf("%v: renewing lease", l.clientID)
|
||||||
|
state = leaseStateRenewing
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
case leaseStateRenewing:
|
||||||
|
if err := l.renew(); err != nil {
|
||||||
|
log.Printf("%v: %v", l.clientID, err)
|
||||||
|
|
||||||
|
if time.Now().After(l.rebindingTime) {
|
||||||
|
log.Printf("%v: renawal time expired, rebinding", l.clientID)
|
||||||
|
state = leaseStateRebinding
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("%v: lease renewed, expiration is %v", l.clientID, l.expireTime)
|
||||||
|
state = leaseStateBound
|
||||||
|
}
|
||||||
|
|
||||||
|
case leaseStateRebinding:
|
||||||
|
if err := l.acquire(); err != nil {
|
||||||
|
log.Printf("%v: %v", l.clientID, err)
|
||||||
|
|
||||||
|
if time.Now().After(l.expireTime) {
|
||||||
|
log.Printf("%v: lease expired, bringing interface DOWN", l.clientID)
|
||||||
|
l.downIface()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("%v: lease rebound, expiration is %v", l.clientID, l.expireTime)
|
||||||
|
state = leaseStateBound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(sleepDur):
|
||||||
|
|
||||||
|
case <-l.stop:
|
||||||
|
if err := l.release(); err != nil {
|
||||||
|
log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) downIface() {
|
||||||
|
if err := netlink.LinkSetDown(l.link); err != nil {
|
||||||
|
log.Printf("%v: failed to bring %v interface DOWN: %v", l.clientID, l.link.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) renew() error {
|
||||||
|
c, err := newDHCPClient(l.link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
|
||||||
|
ok, ack, err := c.Renew(*l.ack)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
case !ok:
|
||||||
|
return nil, fmt.Errorf("DHCP server did not renew lease")
|
||||||
|
default:
|
||||||
|
return &ack, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.commit(pkt)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) release() error {
|
||||||
|
log.Printf("%v: releasing lease", l.clientID)
|
||||||
|
|
||||||
|
c, err := newDHCPClient(l.link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
if err = c.Release(*l.ack); err != nil {
|
||||||
|
return fmt.Errorf("failed to send DHCPRELEASE")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) IPNet() (*net.IPNet, error) {
|
||||||
|
mask := parseSubnetMask(l.opts)
|
||||||
|
if mask == nil {
|
||||||
|
return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &net.IPNet{
|
||||||
|
IP: l.ack.YIAddr(),
|
||||||
|
Mask: mask,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) Gateway() net.IP {
|
||||||
|
return parseRouter(l.opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DHCPLease) Routes() []*types.Route {
|
||||||
|
routes := parseRoutes(l.opts)
|
||||||
|
return append(routes, parseCIDRRoutes(l.opts)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jitter returns a random value within [-span, span) range
|
||||||
|
func jitter(span time.Duration) time.Duration {
|
||||||
|
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||||
|
var baseDelay time.Duration = resendDelay0
|
||||||
|
|
||||||
|
for i := 0; i < resendCount; i++ {
|
||||||
|
pkt, err := f()
|
||||||
|
if err == nil {
|
||||||
|
return pkt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print(err)
|
||||||
|
|
||||||
|
time.Sleep(baseDelay + jitter(time.Second))
|
||||||
|
|
||||||
|
if baseDelay < resendDelayMax {
|
||||||
|
baseDelay *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errNoMoreTries
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDHCPClient(link netlink.Link) (*dhcp4client.Client, error) {
|
||||||
|
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhcp4client.New(
|
||||||
|
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
|
||||||
|
dhcp4client.Timeout(5*time.Second),
|
||||||
|
dhcp4client.Broadcast(false),
|
||||||
|
dhcp4client.Connection(pktsock),
|
||||||
|
)
|
||||||
|
}
|
83
plugins/ipam/dhcp/main.go
Normal file
83
plugins/ipam/dhcp/main.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/rpc"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const socketPath = "/run/cni/dhcp.sock"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
||||||
|
runDaemon()
|
||||||
|
} else {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
// Plugin must return result in same version as specified in netconf
|
||||||
|
versionDecoder := &version.ConfigDecoder{}
|
||||||
|
confVersion, err := versionDecoder.Decode(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ¤t.Result{}
|
||||||
|
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.PrintResult(result, confVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
result := struct{}{}
|
||||||
|
if err := rpcCall("DHCP.Release", args, &result); err != nil {
|
||||||
|
return fmt.Errorf("error dialing DHCP daemon: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcCall(method string, args *skel.CmdArgs, result interface{}) error {
|
||||||
|
client, err := rpc.DialHTTP("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error dialing DHCP daemon: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The daemon may be running under a different working dir
|
||||||
|
// so make sure the netns path is absolute.
|
||||||
|
netns, err := filepath.Abs(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to make %q an absolute path: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
args.Netns = netns
|
||||||
|
|
||||||
|
err = client.Call(method, args, result)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error calling %v: %v", method, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
139
plugins/ipam/dhcp/options.go
Normal file
139
plugins/ipam/dhcp/options.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRouter(opts dhcp4.Options) net.IP {
|
||||||
|
if opts, ok := opts[dhcp4.OptionRouter]; ok {
|
||||||
|
if len(opts) == 4 {
|
||||||
|
return net.IP(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func classfulSubnet(sn net.IP) net.IPNet {
|
||||||
|
return net.IPNet{
|
||||||
|
IP: sn,
|
||||||
|
Mask: sn.DefaultMask(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRoutes(opts dhcp4.Options) []*types.Route {
|
||||||
|
// StaticRoutes format: pairs of:
|
||||||
|
// Dest = 4 bytes; Classful IP subnet
|
||||||
|
// Router = 4 bytes; IP address of router
|
||||||
|
|
||||||
|
routes := []*types.Route{}
|
||||||
|
if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
|
||||||
|
for len(opt) >= 8 {
|
||||||
|
sn := opt[0:4]
|
||||||
|
r := opt[4:8]
|
||||||
|
rt := &types.Route{
|
||||||
|
Dst: classfulSubnet(sn),
|
||||||
|
GW: r,
|
||||||
|
}
|
||||||
|
routes = append(routes, rt)
|
||||||
|
opt = opt[8:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
|
||||||
|
// See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
|
||||||
|
|
||||||
|
routes := []*types.Route{}
|
||||||
|
if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
|
||||||
|
for len(opt) >= 5 {
|
||||||
|
width := int(opt[0])
|
||||||
|
if width > 32 {
|
||||||
|
// error: can't have more than /32
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// network bits are compacted to avoid zeros
|
||||||
|
octets := 0
|
||||||
|
if width > 0 {
|
||||||
|
octets = (width-1)/8 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt) < 1+octets+4 {
|
||||||
|
// error: too short
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sn := make([]byte, 4)
|
||||||
|
copy(sn, opt[1:octets+1])
|
||||||
|
|
||||||
|
gw := net.IP(opt[octets+1 : octets+5])
|
||||||
|
|
||||||
|
rt := &types.Route{
|
||||||
|
Dst: net.IPNet{
|
||||||
|
IP: net.IP(sn),
|
||||||
|
Mask: net.CIDRMask(width, 32),
|
||||||
|
},
|
||||||
|
GW: gw,
|
||||||
|
}
|
||||||
|
routes = append(routes, rt)
|
||||||
|
|
||||||
|
opt = opt[octets+5 : len(opt)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubnetMask(opts dhcp4.Options) net.IPMask {
|
||||||
|
mask, ok := opts[dhcp4.OptionSubnetMask]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.IPMask(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
|
||||||
|
val, ok := opts[code]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("option %v not found", optName)
|
||||||
|
}
|
||||||
|
if len(val) != 4 {
|
||||||
|
return 0, fmt.Errorf("option %v is not 4 bytes", optName)
|
||||||
|
}
|
||||||
|
|
||||||
|
secs := binary.BigEndian.Uint32(val)
|
||||||
|
return time.Duration(secs) * time.Second, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
|
||||||
|
return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
|
||||||
|
return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
|
||||||
|
return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
|
||||||
|
}
|
75
plugins/ipam/dhcp/options_test.go
Normal file
75
plugins/ipam/dhcp/options_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateRoutes(t *testing.T, routes []*types.Route) {
|
||||||
|
expected := []*types.Route{
|
||||||
|
&types.Route{
|
||||||
|
Dst: net.IPNet{
|
||||||
|
IP: net.IPv4(10, 0, 0, 0),
|
||||||
|
Mask: net.CIDRMask(8, 32),
|
||||||
|
},
|
||||||
|
GW: net.IPv4(10, 1, 2, 3),
|
||||||
|
},
|
||||||
|
&types.Route{
|
||||||
|
Dst: net.IPNet{
|
||||||
|
IP: net.IPv4(192, 168, 1, 0),
|
||||||
|
Mask: net.CIDRMask(24, 32),
|
||||||
|
},
|
||||||
|
GW: net.IPv4(192, 168, 2, 3),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(routes) != len(expected) {
|
||||||
|
t.Fatalf("wrong length slice; expected %v, got %v", len(expected), len(routes))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(routes); i++ {
|
||||||
|
a := routes[i]
|
||||||
|
e := expected[i]
|
||||||
|
|
||||||
|
if a.Dst.String() != e.Dst.String() {
|
||||||
|
t.Errorf("route.Dst mismatch: expected %v, got %v", e.Dst, a.Dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.GW.Equal(e.GW) {
|
||||||
|
t.Errorf("route.GW mismatch: expected %v, got %v", e.GW, a.GW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRoutes(t *testing.T) {
|
||||||
|
opts := make(dhcp4.Options)
|
||||||
|
opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
|
||||||
|
routes := parseRoutes(opts)
|
||||||
|
|
||||||
|
validateRoutes(t, routes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCIDRRoutes(t *testing.T) {
|
||||||
|
opts := make(dhcp4.Options)
|
||||||
|
opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
|
||||||
|
routes := parseCIDRRoutes(opts)
|
||||||
|
|
||||||
|
validateRoutes(t, routes)
|
||||||
|
}
|
277
plugins/ipam/host-local/backend/allocator/allocator.go
Normal file
277
plugins/ipam/host-local/backend/allocator/allocator.go
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// 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 allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ip"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPAllocator struct {
|
||||||
|
// start is inclusive and may be allocated
|
||||||
|
start net.IP
|
||||||
|
// end is inclusive and may be allocated
|
||||||
|
end net.IP
|
||||||
|
conf *IPAMConfig
|
||||||
|
store backend.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIPAllocator(conf *IPAMConfig, store backend.Store) (*IPAllocator, error) {
|
||||||
|
// Can't create an allocator for a network with no addresses, eg
|
||||||
|
// a /32 or /31
|
||||||
|
ones, masklen := conf.Subnet.Mask.Size()
|
||||||
|
if ones > masklen-2 {
|
||||||
|
return nil, fmt.Errorf("Network %v too small to allocate from", conf.Subnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
start net.IP
|
||||||
|
end net.IP
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
start, end, err = networkRange((*net.IPNet)(&conf.Subnet))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip the .0 address
|
||||||
|
start = ip.NextIP(start)
|
||||||
|
|
||||||
|
if conf.RangeStart != nil {
|
||||||
|
if err := validateRangeIP(conf.RangeStart, (*net.IPNet)(&conf.Subnet), start, end); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
start = conf.RangeStart
|
||||||
|
}
|
||||||
|
if conf.RangeEnd != nil {
|
||||||
|
if err := validateRangeIP(conf.RangeEnd, (*net.IPNet)(&conf.Subnet), start, end); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
end = conf.RangeEnd
|
||||||
|
}
|
||||||
|
return &IPAllocator{start, end, conf, store}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalizeIP(ip net.IP) (net.IP, error) {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return ip.To4(), nil
|
||||||
|
} else if ip.To16() != nil {
|
||||||
|
return ip.To16(), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("IP %s not v4 nor v6", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures @ip is within @ipnet, and (if given) inclusive of @start and @end
|
||||||
|
func validateRangeIP(ip net.IP, ipnet *net.IPNet, start net.IP, end net.IP) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Make sure we can compare IPv4 addresses directly
|
||||||
|
ip, err = canonicalizeIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ipnet.Contains(ip) {
|
||||||
|
return fmt.Errorf("%s not in network: %s", ip, ipnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
if start != nil {
|
||||||
|
start, err = canonicalizeIP(start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(ip) != len(start) {
|
||||||
|
return fmt.Errorf("%s %d not same size IP address as start %s %d", ip, len(ip), start, len(start))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(ip); i++ {
|
||||||
|
if ip[i] > start[i] {
|
||||||
|
break
|
||||||
|
} else if ip[i] < start[i] {
|
||||||
|
return fmt.Errorf("%s outside of network %s with start %s", ip, ipnet, start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if end != nil {
|
||||||
|
end, err = canonicalizeIP(end)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(ip) != len(end) {
|
||||||
|
return fmt.Errorf("%s %d not same size IP address as end %s %d", ip, len(ip), end, len(end))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(ip); i++ {
|
||||||
|
if ip[i] < end[i] {
|
||||||
|
break
|
||||||
|
} else if ip[i] > end[i] {
|
||||||
|
return fmt.Errorf("%s outside of network %s with end %s", ip, ipnet, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns newly allocated IP along with its config
|
||||||
|
func (a *IPAllocator) Get(id string) (*current.IPConfig, []*types.Route, error) {
|
||||||
|
a.store.Lock()
|
||||||
|
defer a.store.Unlock()
|
||||||
|
|
||||||
|
gw := a.conf.Gateway
|
||||||
|
if gw == nil {
|
||||||
|
gw = ip.NextIP(a.conf.Subnet.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestedIP net.IP
|
||||||
|
if a.conf.Args != nil {
|
||||||
|
requestedIP = a.conf.Args.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestedIP != nil {
|
||||||
|
if gw != nil && gw.Equal(a.conf.Args.IP) {
|
||||||
|
return nil, nil, fmt.Errorf("requested IP must differ gateway IP")
|
||||||
|
}
|
||||||
|
|
||||||
|
subnet := net.IPNet{
|
||||||
|
IP: a.conf.Subnet.IP,
|
||||||
|
Mask: a.conf.Subnet.Mask,
|
||||||
|
}
|
||||||
|
err := validateRangeIP(requestedIP, &subnet, a.start, a.end)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reserved, err := a.store.Reserve(id, requestedIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reserved {
|
||||||
|
ipConfig := ¤t.IPConfig{
|
||||||
|
Version: "4",
|
||||||
|
Address: net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask},
|
||||||
|
Gateway: gw,
|
||||||
|
}
|
||||||
|
routes := convertRoutesToCurrent(a.conf.Routes)
|
||||||
|
return ipConfig, routes, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("requested IP address %q is not available in network: %s", requestedIP, a.conf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
startIP, endIP := a.getSearchRange()
|
||||||
|
for cur := startIP; ; cur = a.nextIP(cur) {
|
||||||
|
// don't allocate gateway IP
|
||||||
|
if gw != nil && cur.Equal(gw) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reserved, err := a.store.Reserve(id, cur)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if reserved {
|
||||||
|
ipConfig := ¤t.IPConfig{
|
||||||
|
Version: "4",
|
||||||
|
Address: net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask},
|
||||||
|
Gateway: gw,
|
||||||
|
}
|
||||||
|
routes := convertRoutesToCurrent(a.conf.Routes)
|
||||||
|
return ipConfig, routes, nil
|
||||||
|
}
|
||||||
|
// break here to complete the loop
|
||||||
|
if cur.Equal(endIP) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Releases all IPs allocated for the container with given ID
|
||||||
|
func (a *IPAllocator) Release(id string) error {
|
||||||
|
a.store.Lock()
|
||||||
|
defer a.store.Unlock()
|
||||||
|
|
||||||
|
return a.store.ReleaseByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the start and end IP addresses of a given subnet, excluding
|
||||||
|
// the broadcast address (eg, 192.168.1.255)
|
||||||
|
func networkRange(ipnet *net.IPNet) (net.IP, net.IP, error) {
|
||||||
|
if ipnet.IP == nil {
|
||||||
|
return nil, nil, fmt.Errorf("missing field %q in IPAM configuration", "subnet")
|
||||||
|
}
|
||||||
|
ip, err := canonicalizeIP(ipnet.IP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("IP not v4 nor v6")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ip) != len(ipnet.Mask) {
|
||||||
|
return nil, nil, fmt.Errorf("IPNet IP and Mask version mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
var end net.IP
|
||||||
|
for i := 0; i < len(ip); i++ {
|
||||||
|
end = append(end, ip[i]|^ipnet.Mask[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude the broadcast address for IPv4
|
||||||
|
if ip.To4() != nil {
|
||||||
|
end[3]--
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipnet.IP, end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextIP returns the next ip of curIP within ipallocator's subnet
|
||||||
|
func (a *IPAllocator) nextIP(curIP net.IP) net.IP {
|
||||||
|
if curIP.Equal(a.end) {
|
||||||
|
return a.start
|
||||||
|
}
|
||||||
|
return ip.NextIP(curIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSearchRange returns the start and end ip based on the last reserved ip
|
||||||
|
func (a *IPAllocator) getSearchRange() (net.IP, net.IP) {
|
||||||
|
var startIP net.IP
|
||||||
|
var endIP net.IP
|
||||||
|
startFromLastReservedIP := false
|
||||||
|
lastReservedIP, err := a.store.LastReservedIP()
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
log.Printf("Error retriving last reserved ip: %v", err)
|
||||||
|
} else if lastReservedIP != nil {
|
||||||
|
subnet := net.IPNet{
|
||||||
|
IP: a.conf.Subnet.IP,
|
||||||
|
Mask: a.conf.Subnet.Mask,
|
||||||
|
}
|
||||||
|
err := validateRangeIP(lastReservedIP, &subnet, a.start, a.end)
|
||||||
|
if err == nil {
|
||||||
|
startFromLastReservedIP = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if startFromLastReservedIP {
|
||||||
|
startIP = a.nextIP(lastReservedIP)
|
||||||
|
endIP = lastReservedIP
|
||||||
|
} else {
|
||||||
|
startIP = a.start
|
||||||
|
endIP = a.end
|
||||||
|
}
|
||||||
|
return startIP, endIP
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 allocator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllocator(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Allocator Suite")
|
||||||
|
}
|
378
plugins/ipam/host-local/backend/allocator/allocator_test.go
Normal file
378
plugins/ipam/host-local/backend/allocator/allocator_test.go
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
// Copyright 2016 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 allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AllocatorTestCase struct {
|
||||||
|
subnet string
|
||||||
|
ipmap map[string]string
|
||||||
|
expectResult string
|
||||||
|
lastIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AllocatorTestCase) run() (*current.IPConfig, []*types.Route, error) {
|
||||||
|
subnet, err := types.ParseCIDR(t.subnet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(t.ipmap, net.ParseIP(t.lastIP))
|
||||||
|
alloc, err := NewIPAllocator(&conf, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
res, routes, err := alloc.Get("ID")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("host-local ip allocator", func() {
|
||||||
|
Context("when has free ip", func() {
|
||||||
|
It("should allocate ips in round robin", func() {
|
||||||
|
testCases := []AllocatorTestCase{
|
||||||
|
// fresh start
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
expectResult: "10.0.0.2",
|
||||||
|
lastIP: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/30",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
expectResult: "10.0.0.2",
|
||||||
|
lastIP: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
},
|
||||||
|
expectResult: "10.0.0.3",
|
||||||
|
lastIP: "",
|
||||||
|
},
|
||||||
|
// next ip of last reserved ip
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
expectResult: "10.0.0.6",
|
||||||
|
lastIP: "10.0.0.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.4": "id",
|
||||||
|
"10.0.0.5": "id",
|
||||||
|
},
|
||||||
|
expectResult: "10.0.0.6",
|
||||||
|
lastIP: "10.0.0.3",
|
||||||
|
},
|
||||||
|
// round robin to the beginning
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.6": "id",
|
||||||
|
},
|
||||||
|
expectResult: "10.0.0.2",
|
||||||
|
lastIP: "10.0.0.5",
|
||||||
|
},
|
||||||
|
// lastIP is out of range
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
},
|
||||||
|
expectResult: "10.0.0.3",
|
||||||
|
lastIP: "10.0.0.128",
|
||||||
|
},
|
||||||
|
// wrap around and reserve lastIP
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
"10.0.0.4": "id",
|
||||||
|
"10.0.0.5": "id",
|
||||||
|
"10.0.0.6": "id",
|
||||||
|
},
|
||||||
|
expectResult: "10.0.0.3",
|
||||||
|
lastIP: "10.0.0.3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
res, _, err := tc.run()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res.Address.IP.String()).To(Equal(tc.expectResult))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not allocate the broadcast address", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
alloc, err := NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for i := 1; i < 254; i++ {
|
||||||
|
res, _, err := alloc.Get("ID")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
// i+1 because the gateway address is skipped
|
||||||
|
s := fmt.Sprintf("192.168.1.%d/24", i+1)
|
||||||
|
Expect(s).To(Equal(res.Address.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = alloc.Get("ID")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should allocate RangeStart first", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
RangeStart: net.ParseIP("192.168.1.10"),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
alloc, err := NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
res, _, err := alloc.Get("ID")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res.Address.String()).To(Equal("192.168.1.10/24"))
|
||||||
|
|
||||||
|
res, _, err = alloc.Get("ID")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res.Address.String()).To(Equal("192.168.1.11/24"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should allocate RangeEnd but not past RangeEnd", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
RangeEnd: net.ParseIP("192.168.1.5"),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
alloc, err := NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for i := 1; i < 5; i++ {
|
||||||
|
res, _, err := alloc.Get("ID")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
// i+1 because the gateway address is skipped
|
||||||
|
Expect(res.Address.String()).To(Equal(fmt.Sprintf("192.168.1.%d/24", i+1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = alloc.Get("ID")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when requesting a specific IP", func() {
|
||||||
|
It("must allocate the requested IP", func() {
|
||||||
|
subnet, err := types.ParseCIDR("10.0.0.0/29")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
requestedIP := net.ParseIP("10.0.0.2")
|
||||||
|
ipmap := map[string]string{}
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
Args: &IPAMArgs{IP: requestedIP},
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(ipmap, nil)
|
||||||
|
alloc, _ := NewIPAllocator(&conf, store)
|
||||||
|
res, _, err := alloc.Get("ID")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res.Address.IP.String()).To(Equal(requestedIP.String()))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("must return an error when the requested IP is after RangeEnd", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
ipmap := map[string]string{}
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
Args: &IPAMArgs{IP: net.ParseIP("192.168.1.50")},
|
||||||
|
RangeEnd: net.ParseIP("192.168.1.20"),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(ipmap, nil)
|
||||||
|
alloc, _ := NewIPAllocator(&conf, store)
|
||||||
|
_, _, err = alloc.Get("ID")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("must return an error when the requested IP is before RangeStart", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
ipmap := map[string]string{}
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
Args: &IPAMArgs{IP: net.ParseIP("192.168.1.3")},
|
||||||
|
RangeStart: net.ParseIP("192.168.1.10"),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(ipmap, nil)
|
||||||
|
alloc, _ := NewIPAllocator(&conf, store)
|
||||||
|
_, _, err = alloc.Get("ID")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("RangeStart must be in the given subnet", func() {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
ipnet string
|
||||||
|
start string
|
||||||
|
}{
|
||||||
|
{"outside-subnet", "192.168.1.0/24", "10.0.0.1"},
|
||||||
|
{"zero-ip", "10.1.0.0/16", "10.1.0.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
subnet, err := types.ParseCIDR(tc.ipnet)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: tc.name,
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
RangeStart: net.ParseIP(tc.start),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
_, err = NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("RangeEnd must be in the given subnet", func() {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
ipnet string
|
||||||
|
end string
|
||||||
|
}{
|
||||||
|
{"outside-subnet", "192.168.1.0/24", "10.0.0.1"},
|
||||||
|
{"broadcast-ip", "10.1.0.0/16", "10.1.255.255"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
subnet, err := types.ParseCIDR(tc.ipnet)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: tc.name,
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
RangeEnd: net.ParseIP(tc.end),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
_, err = NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("RangeEnd must be after RangeStart in the given subnet", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/24")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
RangeStart: net.ParseIP("192.168.1.10"),
|
||||||
|
RangeEnd: net.ParseIP("192.168.1.3"),
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
_, err = NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when out of ips", func() {
|
||||||
|
It("returns a meaningful error", func() {
|
||||||
|
testCases := []AllocatorTestCase{
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/30",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
"10.0.0.3": "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnet: "10.0.0.0/29",
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
"10.0.0.3": "id",
|
||||||
|
"10.0.0.4": "id",
|
||||||
|
"10.0.0.5": "id",
|
||||||
|
"10.0.0.6": "id",
|
||||||
|
"10.0.0.7": "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
_, _, err := tc.run()
|
||||||
|
Expect(err).To(MatchError("no IP addresses available in network: test"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when given an invalid subnet", func() {
|
||||||
|
It("returns a meaningful error", func() {
|
||||||
|
subnet, err := types.ParseCIDR("192.168.1.0/31")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conf := IPAMConfig{
|
||||||
|
Name: "test",
|
||||||
|
Type: "host-local",
|
||||||
|
Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask},
|
||||||
|
}
|
||||||
|
store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP(""))
|
||||||
|
_, err = NewIPAllocator(&conf, store)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
84
plugins/ipam/host-local/backend/allocator/config.go
Normal file
84
plugins/ipam/host-local/backend/allocator/config.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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 allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAMConfig represents the IP related network configuration.
|
||||||
|
type IPAMConfig struct {
|
||||||
|
Name string
|
||||||
|
Type string `json:"type"`
|
||||||
|
RangeStart net.IP `json:"rangeStart"`
|
||||||
|
RangeEnd net.IP `json:"rangeEnd"`
|
||||||
|
Subnet types.IPNet `json:"subnet"`
|
||||||
|
Gateway net.IP `json:"gateway"`
|
||||||
|
Routes []types.Route `json:"routes"`
|
||||||
|
DataDir string `json:"dataDir"`
|
||||||
|
ResolvConf string `json:"resolvConf"`
|
||||||
|
Args *IPAMArgs `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPAMArgs struct {
|
||||||
|
types.CommonArgs
|
||||||
|
IP net.IP `json:"ip,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Net struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CNIVersion string `json:"cniVersion"`
|
||||||
|
IPAM *IPAMConfig `json:"ipam"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPAMConfig creates a NetworkConfig from the given network name.
|
||||||
|
func LoadIPAMConfig(bytes []byte, args string) (*IPAMConfig, string, error) {
|
||||||
|
n := Net{}
|
||||||
|
if err := json.Unmarshal(bytes, &n); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.IPAM == nil {
|
||||||
|
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if args != "" {
|
||||||
|
n.IPAM.Args = &IPAMArgs{}
|
||||||
|
err := types.LoadArgs(args, n.IPAM.Args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy net name into IPAM so not to drag Net struct around
|
||||||
|
n.IPAM.Name = n.Name
|
||||||
|
|
||||||
|
return n.IPAM, n.CNIVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRoutesToCurrent(routes []types.Route) []*types.Route {
|
||||||
|
var currentRoutes []*types.Route
|
||||||
|
for _, r := range routes {
|
||||||
|
currentRoutes = append(currentRoutes, &types.Route{
|
||||||
|
Dst: r.Dst,
|
||||||
|
GW: r.GW,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return currentRoutes
|
||||||
|
}
|
115
plugins/ipam/host-local/backend/disk/backend.go
Normal file
115
plugins/ipam/host-local/backend/disk/backend.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 disk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
const lastIPFile = "last_reserved_ip"
|
||||||
|
|
||||||
|
var defaultDataDir = "/var/lib/cni/networks"
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
FileLock
|
||||||
|
dataDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store implements the Store interface
|
||||||
|
var _ backend.Store = &Store{}
|
||||||
|
|
||||||
|
func New(network, dataDir string) (*Store, error) {
|
||||||
|
if dataDir == "" {
|
||||||
|
dataDir = defaultDataDir
|
||||||
|
}
|
||||||
|
dir := filepath.Join(dataDir, network)
|
||||||
|
if err := os.MkdirAll(dir, 0644); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lk, err := NewFileLock(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Store{*lk, dir}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Reserve(id string, ip net.IP) (bool, error) {
|
||||||
|
fname := filepath.Join(s.dataDir, ip.String())
|
||||||
|
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644)
|
||||||
|
if os.IsExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if _, err := f.WriteString(strings.TrimSpace(id)); err != nil {
|
||||||
|
f.Close()
|
||||||
|
os.Remove(f.Name())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// store the reserved ip in lastIPFile
|
||||||
|
ipfile := filepath.Join(s.dataDir, lastIPFile)
|
||||||
|
err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastReservedIP returns the last reserved IP if exists
|
||||||
|
func (s *Store) LastReservedIP() (net.IP, error) {
|
||||||
|
ipfile := filepath.Join(s.dataDir, lastIPFile)
|
||||||
|
data, err := ioutil.ReadFile(ipfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.ParseIP(string(data)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Release(ip net.IP) error {
|
||||||
|
return os.Remove(filepath.Join(s.dataDir, ip.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B. This function eats errors to be tolerant and
|
||||||
|
// release as much as possible
|
||||||
|
func (s *Store) ReleaseByID(id string) error {
|
||||||
|
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(data)) == strings.TrimSpace(id) {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
50
plugins/ipam/host-local/backend/disk/lock.go
Normal file
50
plugins/ipam/host-local/backend/disk/lock.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 disk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileLock wraps os.File to be used as a lock using flock
|
||||||
|
type FileLock struct {
|
||||||
|
f *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileLock opens file/dir at path and returns unlocked FileLock object
|
||||||
|
func NewFileLock(path string) (*FileLock, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileLock{f}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes underlying file
|
||||||
|
func (l *FileLock) Close() error {
|
||||||
|
return l.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock acquires an exclusive lock
|
||||||
|
func (l *FileLock) Lock() error {
|
||||||
|
return syscall.Flock(int(l.f.Fd()), syscall.LOCK_EX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock releases the lock
|
||||||
|
func (l *FileLock) Unlock() error {
|
||||||
|
return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN)
|
||||||
|
}
|
27
plugins/ipam/host-local/backend/store.go
Normal file
27
plugins/ipam/host-local/backend/store.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 backend
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
Lock() error
|
||||||
|
Unlock() error
|
||||||
|
Close() error
|
||||||
|
Reserve(id string, ip net.IP) (bool, error)
|
||||||
|
LastReservedIP() (net.IP, error)
|
||||||
|
Release(ip net.IP) error
|
||||||
|
ReleaseByID(id string) error
|
||||||
|
}
|
77
plugins/ipam/host-local/backend/testing/fake_store.go
Normal file
77
plugins/ipam/host-local/backend/testing/fake_store.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeStore struct {
|
||||||
|
ipMap map[string]string
|
||||||
|
lastReservedIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeStore implements the Store interface
|
||||||
|
var _ backend.Store = &FakeStore{}
|
||||||
|
|
||||||
|
func NewFakeStore(ipmap map[string]string, lastIP net.IP) *FakeStore {
|
||||||
|
return &FakeStore{ipmap, lastIP}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) Lock() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) Unlock() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) Reserve(id string, ip net.IP) (bool, error) {
|
||||||
|
key := ip.String()
|
||||||
|
if _, ok := s.ipMap[key]; !ok {
|
||||||
|
s.ipMap[key] = id
|
||||||
|
s.lastReservedIP = ip
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) LastReservedIP() (net.IP, error) {
|
||||||
|
return s.lastReservedIP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) Release(ip net.IP) error {
|
||||||
|
delete(s.ipMap, ip.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeStore) ReleaseByID(id string) error {
|
||||||
|
toDelete := []string{}
|
||||||
|
for k, v := range s.ipMap {
|
||||||
|
if v == id {
|
||||||
|
toDelete = append(toDelete, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ip := range toDelete {
|
||||||
|
delete(s.ipMap, ip)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
64
plugins/ipam/host-local/dns.go
Normal file
64
plugins/ipam/host-local/dns.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseResolvConf parses an existing resolv.conf in to a DNS struct
|
||||||
|
func parseResolvConf(filename string) (*types.DNS, error) {
|
||||||
|
fp, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dns := types.DNS{}
|
||||||
|
scanner := bufio.NewScanner(fp)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
// Skip comments, empty lines
|
||||||
|
if len(line) == 0 || line[0] == '#' || line[0] == ';' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch fields[0] {
|
||||||
|
case "nameserver":
|
||||||
|
dns.Nameservers = append(dns.Nameservers, fields[1])
|
||||||
|
case "domain":
|
||||||
|
dns.Domain = fields[1]
|
||||||
|
case "search":
|
||||||
|
dns.Search = append(dns.Search, fields[1:]...)
|
||||||
|
case "options":
|
||||||
|
dns.Options = append(dns.Options, fields[1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dns, nil
|
||||||
|
}
|
80
plugins/ipam/host-local/dns_test.go
Normal file
80
plugins/ipam/host-local/dns_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("parsing resolv.conf", func() {
|
||||||
|
It("parses a simple resolv.conf file", func() {
|
||||||
|
contents := `
|
||||||
|
nameserver 192.0.2.0
|
||||||
|
nameserver 192.0.2.1
|
||||||
|
`
|
||||||
|
dns, err := parse(contents)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0", "192.0.2.1"}}))
|
||||||
|
})
|
||||||
|
It("ignores comments", func() {
|
||||||
|
dns, err := parse(`
|
||||||
|
nameserver 192.0.2.0
|
||||||
|
;nameserver 192.0.2.1
|
||||||
|
`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0"}}))
|
||||||
|
})
|
||||||
|
It("parses all fields", func() {
|
||||||
|
dns, err := parse(`
|
||||||
|
nameserver 192.0.2.0
|
||||||
|
nameserver 192.0.2.2
|
||||||
|
domain example.com
|
||||||
|
;nameserver comment
|
||||||
|
#nameserver comment
|
||||||
|
search example.net example.org
|
||||||
|
search example.gov
|
||||||
|
options one two three
|
||||||
|
options four
|
||||||
|
`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(*dns).Should(Equal(types.DNS{
|
||||||
|
Nameservers: []string{"192.0.2.0", "192.0.2.2"},
|
||||||
|
Domain: "example.com",
|
||||||
|
Search: []string{"example.net", "example.org", "example.gov"},
|
||||||
|
Options: []string{"one", "two", "three", "four"},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func parse(contents string) (*types.DNS, error) {
|
||||||
|
f, err := ioutil.TempFile("", "host_local_resolv")
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := f.WriteString(contents); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResolvConf(f.Name())
|
||||||
|
}
|
27
plugins/ipam/host-local/host_local_suite_test.go
Normal file
27
plugins/ipam/host-local/host_local_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHostLocal(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "HostLocal Suite")
|
||||||
|
}
|
292
plugins/ipam/host-local/host_local_test.go
Normal file
292
plugins/ipam/host-local/host_local_test.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// Copyright 2016 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("host-local Operations", func() {
|
||||||
|
It("allocates and releases an address with ADD/DEL", func() {
|
||||||
|
const ifname string = "eth0"
|
||||||
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24",
|
||||||
|
"dataDir": "%s",
|
||||||
|
"resolvConf": "%s/resolv.conf"
|
||||||
|
}
|
||||||
|
}`, tmpDir, tmpDir)
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: nspath,
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the IP
|
||||||
|
r, raw, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||||
|
|
||||||
|
result, err := current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
expectedAddress, err := types.ParseCIDR("10.1.2.2/24")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(result.IPs)).To(Equal(1))
|
||||||
|
expectedAddress.IP = expectedAddress.IP.To16()
|
||||||
|
Expect(result.IPs[0].Address).To(Equal(*expectedAddress))
|
||||||
|
Expect(result.IPs[0].Gateway).To(Equal(net.ParseIP("10.1.2.1")))
|
||||||
|
|
||||||
|
ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
|
||||||
|
contents, err := ioutil.ReadFile(ipFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
|
lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip")
|
||||||
|
contents, err = ioutil.ReadFile(lastFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||||
|
|
||||||
|
// Release the IP
|
||||||
|
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = os.Stat(ipFilePath)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("doesn't error when passed an unknown ID on DEL", func() {
|
||||||
|
const ifname string = "eth0"
|
||||||
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24",
|
||||||
|
"dataDir": "%s"
|
||||||
|
}
|
||||||
|
}`, tmpDir)
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: nspath,
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the IP
|
||||||
|
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("allocates and releases an address with ADD/DEL and 0.1.0 config", func() {
|
||||||
|
const ifname string = "eth0"
|
||||||
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.1.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24",
|
||||||
|
"dataDir": "%s",
|
||||||
|
"resolvConf": "%s/resolv.conf"
|
||||||
|
}
|
||||||
|
}`, tmpDir, tmpDir)
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: nspath,
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the IP
|
||||||
|
r, raw, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(strings.Index(string(raw), "\"ip4\":")).Should(BeNumerically(">", 0))
|
||||||
|
|
||||||
|
result, err := types020.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
expectedAddress, err := types.ParseCIDR("10.1.2.2/24")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
expectedAddress.IP = expectedAddress.IP.To16()
|
||||||
|
Expect(result.IP4.IP).To(Equal(*expectedAddress))
|
||||||
|
Expect(result.IP4.Gateway).To(Equal(net.ParseIP("10.1.2.1")))
|
||||||
|
|
||||||
|
ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
|
||||||
|
contents, err := ioutil.ReadFile(ipFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
|
lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip")
|
||||||
|
contents, err = ioutil.ReadFile(lastFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||||
|
|
||||||
|
Expect(result.DNS).To(Equal(types.DNS{Nameservers: []string{"192.0.2.3"}}))
|
||||||
|
|
||||||
|
// Release the IP
|
||||||
|
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = os.Stat(ipFilePath)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("ignores whitespace in disk files", func() {
|
||||||
|
const ifname string = "eth0"
|
||||||
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24",
|
||||||
|
"dataDir": "%s"
|
||||||
|
}
|
||||||
|
}`, tmpDir)
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: " dummy\n ",
|
||||||
|
Netns: nspath,
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the IP
|
||||||
|
r, _, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
result, err := current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String())
|
||||||
|
contents, err := ioutil.ReadFile(ipFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
|
// Release the IP
|
||||||
|
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = os.Stat(ipFilePath)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("does not output an error message upon initial subnet creation", func() {
|
||||||
|
const ifname string = "eth0"
|
||||||
|
const nspath string = "/some/where"
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.2.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24",
|
||||||
|
"dataDir": "%s"
|
||||||
|
}
|
||||||
|
}`, tmpDir)
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "testing",
|
||||||
|
Netns: nspath,
|
||||||
|
IfName: ifname,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the IP
|
||||||
|
_, out, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(strings.Index(string(out), "Error retriving last reserved ip")).To(Equal(-1))
|
||||||
|
})
|
||||||
|
})
|
86
plugins/ipam/host-local/main.go
Normal file
86
plugins/ipam/host-local/main.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||||
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
ipamConf, confVersion, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ¤t.Result{}
|
||||||
|
|
||||||
|
if ipamConf.ResolvConf != "" {
|
||||||
|
dns, err := parseResolvConf(ipamConf.ResolvConf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result.DNS = *dns
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
allocator, err := allocator.NewIPAllocator(ipamConf, store)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipConf, routes, err := allocator.Get(args.ContainerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result.IPs = []*current.IPConfig{ipConf}
|
||||||
|
result.Routes = routes
|
||||||
|
|
||||||
|
return types.PrintResult(result, confVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
ipAllocator, err := allocator.NewIPAllocator(ipamConf, store)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipAllocator.Release(args.ContainerID)
|
||||||
|
}
|
417
plugins/main/bridge/bridge.go
Normal file
417
plugins/main/bridge/bridge.go
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
// Copyright 2014 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ip"
|
||||||
|
"github.com/containernetworking/cni/pkg/ipam"
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/utils"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultBrName = "cni0"
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
BrName string `json:"bridge"`
|
||||||
|
IsGW bool `json:"isGateway"`
|
||||||
|
IsDefaultGW bool `json:"isDefaultGateway"`
|
||||||
|
ForceAddress bool `json:"forceAddress"`
|
||||||
|
IPMasq bool `json:"ipMasq"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
HairpinMode bool `json:"hairpinMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// this ensures that main runs only on main thread (thread group leader).
|
||||||
|
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||||
|
// must ensure that the goroutine does not jump from OS thread to thread
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNetConf(bytes []byte) (*NetConf, string, error) {
|
||||||
|
n := &NetConf{
|
||||||
|
BrName: defaultBrName,
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
return n, n.CNIVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureBridgeAddr(br *netlink.Bridge, ipn *net.IPNet, forceAddress bool) error {
|
||||||
|
addrs, err := netlink.AddrList(br, syscall.AF_INET)
|
||||||
|
if err != nil && err != syscall.ENOENT {
|
||||||
|
return fmt.Errorf("could not get list of IP addresses: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there're no addresses on the bridge, it's ok -- we'll add one
|
||||||
|
if len(addrs) > 0 {
|
||||||
|
ipnStr := ipn.String()
|
||||||
|
for _, a := range addrs {
|
||||||
|
// string comp is actually easiest for doing IPNet comps
|
||||||
|
if a.IPNet.String() == ipnStr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If forceAddress is set to true then reconfigure IP address otherwise throw error
|
||||||
|
if forceAddress {
|
||||||
|
if err = deleteBridgeAddr(br, a.IPNet); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipn.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||||
|
if err := netlink.AddrAdd(br, addr); err != nil {
|
||||||
|
return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
|
||||||
|
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetDown(br); err != nil {
|
||||||
|
return fmt.Errorf("could not set down bridge %q: %v", br.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.AddrDel(br, addr); err != nil {
|
||||||
|
return fmt.Errorf("could not remove IP address from %q: %v", br.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(br); err != nil {
|
||||||
|
return fmt.Errorf("could not set up bridge %q: %v", br.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bridgeByName(name string) (*netlink.Bridge, error) {
|
||||||
|
l, err := netlink.LinkByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not lookup %q: %v", name, err)
|
||||||
|
}
|
||||||
|
br, ok := l.(*netlink.Bridge)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%q already exists but is not a bridge", name)
|
||||||
|
}
|
||||||
|
return br, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
|
||||||
|
br := &netlink.Bridge{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: brName,
|
||||||
|
MTU: mtu,
|
||||||
|
// Let kernel use default txqueuelen; leaving it unset
|
||||||
|
// means 0, and a zero-length TX queue messes up FIFO
|
||||||
|
// traffic shapers which use TX queue length as the
|
||||||
|
// default packet limit
|
||||||
|
TxQLen: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(br)
|
||||||
|
if err != nil && err != syscall.EEXIST {
|
||||||
|
return nil, fmt.Errorf("could not add %q: %v", brName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch link to read all attributes and if it already existed,
|
||||||
|
// ensure it's really a bridge with similar configuration
|
||||||
|
br, err = bridgeByName(brName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(br); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return br, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error) {
|
||||||
|
contIface := ¤t.Interface{}
|
||||||
|
hostIface := ¤t.Interface{}
|
||||||
|
|
||||||
|
err := netns.Do(func(hostNS ns.NetNS) error {
|
||||||
|
// create the veth pair in the container and move host end into host netns
|
||||||
|
hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contIface.Name = containerVeth.Name
|
||||||
|
contIface.Mac = containerVeth.HardwareAddr.String()
|
||||||
|
contIface.Sandbox = netns.Path()
|
||||||
|
hostIface.Name = hostVeth.Name
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to lookup hostVeth again as its index has changed during ns move
|
||||||
|
hostVeth, err := netlink.LinkByName(hostIface.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to lookup %q: %v", hostIface.Name, err)
|
||||||
|
}
|
||||||
|
hostIface.Mac = hostVeth.Attrs().HardwareAddr.String()
|
||||||
|
|
||||||
|
// connect host veth end to the bridge
|
||||||
|
if err := netlink.LinkSetMaster(hostVeth, br); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to connect %q to bridge %v: %v", hostVeth.Attrs().Name, br.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set hairpin mode
|
||||||
|
if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostIface, contIface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcGatewayIP(ipn *net.IPNet) net.IP {
|
||||||
|
nid := ipn.IP.Mask(ipn.Mask)
|
||||||
|
return ip.NextIP(nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
|
||||||
|
// create bridge if necessary
|
||||||
|
br, err := ensureBridge(n.BrName, n.MTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return br, ¤t.Interface{
|
||||||
|
Name: br.Attrs().Name,
|
||||||
|
Mac: br.Attrs().HardwareAddr.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
n, cniVersion, err := loadNetConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.IsDefaultGW {
|
||||||
|
n.IsGW = true
|
||||||
|
}
|
||||||
|
|
||||||
|
br, brInterface, err := setupBridge(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
netns, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
defer netns.Close()
|
||||||
|
|
||||||
|
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the IPAM plugin and get back the config to apply
|
||||||
|
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
|
result, err := current.NewResultFromResult(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.IPs) == 0 {
|
||||||
|
return errors.New("IPAM plugin returned missing IP config")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface}
|
||||||
|
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
// All IPs currently refer to the container interface
|
||||||
|
ipc.Interface = 2
|
||||||
|
if ipc.Gateway == nil && n.IsGW {
|
||||||
|
ipc.Gateway = calcGatewayIP(&ipc.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netns.Do(func(_ ns.NetNS) error {
|
||||||
|
// set the default gateway if requested
|
||||||
|
if n.IsDefaultGW {
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
defaultNet := &net.IPNet{}
|
||||||
|
switch {
|
||||||
|
case ipc.Address.IP.To4() != nil:
|
||||||
|
defaultNet.IP = net.IPv4zero
|
||||||
|
defaultNet.Mask = net.IPMask(net.IPv4zero)
|
||||||
|
case len(ipc.Address.IP) == net.IPv6len && ipc.Address.IP.To4() == nil:
|
||||||
|
defaultNet.IP = net.IPv6zero
|
||||||
|
defaultNet.Mask = net.IPMask(net.IPv6zero)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown IP object: %v", ipc)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range result.Routes {
|
||||||
|
if defaultNet.String() == route.Dst.String() {
|
||||||
|
if route.GW != nil && !route.GW.Equal(ipc.Gateway) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"isDefaultGateway ineffective because IPAM sets default route via %q",
|
||||||
|
route.GW,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Routes = append(
|
||||||
|
result.Routes,
|
||||||
|
&types.Route{Dst: *defaultNet, GW: ipc.Gateway},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ip.SetHWAddrByIP(args.IfName, result.IPs[0].Address.IP, nil /* TODO IPv6 */); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch the veth since its MAC address may changed
|
||||||
|
link, err := netlink.LinkByName(args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not lookup %q: %v", args.IfName, err)
|
||||||
|
}
|
||||||
|
containerInterface.Mac = link.Attrs().HardwareAddr.String()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.IsGW {
|
||||||
|
var firstV4Addr net.IP
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
gwn := &net.IPNet{
|
||||||
|
IP: ipc.Gateway,
|
||||||
|
Mask: ipc.Address.Mask,
|
||||||
|
}
|
||||||
|
if ipc.Gateway.To4() != nil && firstV4Addr == nil {
|
||||||
|
firstV4Addr = ipc.Gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ensureBridgeAddr(br, gwn, n.ForceAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstV4Addr != nil {
|
||||||
|
if err := ip.SetHWAddrByIP(n.BrName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ip.EnableIP4Forward(); err != nil {
|
||||||
|
return fmt.Errorf("failed to enable forwarding: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.IPMasq {
|
||||||
|
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||||
|
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch the bridge since its MAC address may change when the first
|
||||||
|
// veth is added or after its IP address is set
|
||||||
|
br, err = bridgeByName(n.BrName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
brInterface.Mac = br.Attrs().HardwareAddr.String()
|
||||||
|
|
||||||
|
result.DNS = n.DNS
|
||||||
|
|
||||||
|
return types.PrintResult(result, cniVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
n, _, err := loadNetConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
|
// so don't return an error if the device is already removed.
|
||||||
|
// If the device isn't there then don't try to clean up IP masq either.
|
||||||
|
var ipn *net.IPNet
|
||||||
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
var err error
|
||||||
|
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
||||||
|
if err != nil && err == ip.ErrLinkNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipn != nil && n.IPMasq {
|
||||||
|
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||||
|
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||||
|
err = ip.TeardownIPMasq(ipn, chain, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
27
plugins/main/bridge/bridge_suite_test.go
Normal file
27
plugins/main/bridge/bridge_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBridge(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "bridge Suite")
|
||||||
|
}
|
529
plugins/main/bridge/bridge_test.go
Normal file
529
plugins/main/bridge/bridge_test.go
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/utils/hwaddr"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkBridgeConfig03x(version string, originalNS ns.NetNS) {
|
||||||
|
const BRNAME = "cni0"
|
||||||
|
const IFNAME = "eth0"
|
||||||
|
|
||||||
|
gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "%s",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "bridge",
|
||||||
|
"bridge": "%s",
|
||||||
|
"isDefaultGateway": true,
|
||||||
|
"ipMasq": false,
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "%s"
|
||||||
|
}
|
||||||
|
}`, version, BRNAME, subnet.String())
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *current.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
|
||||||
|
|
||||||
|
result, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(result.Interfaces)).To(Equal(3))
|
||||||
|
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
|
||||||
|
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
|
||||||
|
|
||||||
|
// Make sure bridge link exists
|
||||||
|
link, err := netlink.LinkByName(result.Interfaces[0].Name)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
||||||
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
||||||
|
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
|
||||||
|
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
||||||
|
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
||||||
|
|
||||||
|
// Ensure bridge has gateway address
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(BeNumerically(">", 0))
|
||||||
|
found := false
|
||||||
|
subnetPrefix, subnetBits := subnet.Mask.Size()
|
||||||
|
for _, a := range addrs {
|
||||||
|
aPrefix, aBits := a.IPNet.Mask.Size()
|
||||||
|
if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(found).To(Equal(true))
|
||||||
|
|
||||||
|
// Check for the veth link in the main namespace
|
||||||
|
links, err := netlink.LinkList()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
|
||||||
|
|
||||||
|
link, err = netlink.LinkByName(result.Interfaces[1].Name)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Find the veth peer in the container namespace and the default route
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
|
||||||
|
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
||||||
|
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
||||||
|
|
||||||
|
// Ensure the default route
|
||||||
|
routes, err := netlink.RouteList(link, 0)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var defaultRouteFound bool
|
||||||
|
for _, route := range routes {
|
||||||
|
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
|
||||||
|
if defaultRouteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(defaultRouteFound).To(Equal(true))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure the host veth has been deleted
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure the container veth has been deleted
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(result.Interfaces[1].Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("bridge Operations", func() {
|
||||||
|
var originalNS ns.NetNS
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Create a new NetNS so we don't modify the host
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("creates a bridge", func() {
|
||||||
|
const IFNAME = "bridge0"
|
||||||
|
|
||||||
|
conf := &NetConf{
|
||||||
|
NetConf: types.NetConf{
|
||||||
|
CNIVersion: "0.3.1",
|
||||||
|
Name: "testConfig",
|
||||||
|
Type: "bridge",
|
||||||
|
},
|
||||||
|
BrName: IFNAME,
|
||||||
|
IsGW: false,
|
||||||
|
IPMasq: false,
|
||||||
|
MTU: 5000,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
bridge, _, err := setupBridge(conf)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(bridge.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
|
||||||
|
// Double check that the link was added
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("handles an existing bridge", func() {
|
||||||
|
const IFNAME = "bridge0"
|
||||||
|
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(&netlink.Bridge{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: IFNAME,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
ifindex := link.Attrs().Index
|
||||||
|
|
||||||
|
conf := &NetConf{
|
||||||
|
NetConf: types.NetConf{
|
||||||
|
CNIVersion: "0.3.1",
|
||||||
|
Name: "testConfig",
|
||||||
|
Type: "bridge",
|
||||||
|
},
|
||||||
|
BrName: IFNAME,
|
||||||
|
IsGW: false,
|
||||||
|
IPMasq: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge, _, err := setupBridge(conf)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(bridge.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
Expect(bridge.Attrs().Index).To(Equal(ifindex))
|
||||||
|
|
||||||
|
// Double check that the link has the same ifindex
|
||||||
|
link, err = netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
Expect(link.Attrs().Index).To(Equal(ifindex))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.0 config", func() {
|
||||||
|
checkBridgeConfig03x("0.3.0", originalNS)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
|
||||||
|
checkBridgeConfig03x("0.3.1", originalNS)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("deconfigures an unconfigured bridge with DEL", func() {
|
||||||
|
const BRNAME = "cni0"
|
||||||
|
const IFNAME = "eth0"
|
||||||
|
|
||||||
|
_, subnet, err := net.ParseCIDR("10.1.2.1/24")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "bridge",
|
||||||
|
"bridge": "%s",
|
||||||
|
"isDefaultGateway": true,
|
||||||
|
"ipMasq": false,
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "%s"
|
||||||
|
}
|
||||||
|
}`, BRNAME, subnet.String())
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
|
||||||
|
const BRNAME = "cni0"
|
||||||
|
const IFNAME = "eth0"
|
||||||
|
|
||||||
|
gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.1.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "bridge",
|
||||||
|
"bridge": "%s",
|
||||||
|
"isDefaultGateway": true,
|
||||||
|
"ipMasq": false,
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "%s"
|
||||||
|
}
|
||||||
|
}`, BRNAME, subnet.String())
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *types020.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(strings.Index(string(raw), "\"ip4\":")).Should(BeNumerically(">", 0))
|
||||||
|
|
||||||
|
// We expect a version 0.1.0 result
|
||||||
|
result, err = types020.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure bridge link exists
|
||||||
|
link, err := netlink.LinkByName(BRNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
||||||
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
||||||
|
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
||||||
|
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
||||||
|
|
||||||
|
// Ensure bridge has gateway address
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(BeNumerically(">", 0))
|
||||||
|
found := false
|
||||||
|
subnetPrefix, subnetBits := subnet.Mask.Size()
|
||||||
|
for _, a := range addrs {
|
||||||
|
aPrefix, aBits := a.IPNet.Mask.Size()
|
||||||
|
if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(found).To(Equal(true))
|
||||||
|
|
||||||
|
// Check for the veth link in the main namespace; can't
|
||||||
|
// check the for the specific link since version 0.1.0
|
||||||
|
// doesn't report interfaces
|
||||||
|
links, err := netlink.LinkList()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Find the veth peer in the container namespace and the default route
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
|
||||||
|
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
||||||
|
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
||||||
|
|
||||||
|
// Ensure the default route
|
||||||
|
routes, err := netlink.RouteList(link, 0)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var defaultRouteFound bool
|
||||||
|
for _, route := range routes {
|
||||||
|
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
|
||||||
|
if defaultRouteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(defaultRouteFound).To(Equal(true))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure the container veth has been deleted; cannot check
|
||||||
|
// host veth as version 0.1.0 can't report its name
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("ensure bridge address", func() {
|
||||||
|
const IFNAME = "bridge0"
|
||||||
|
const EXPECTED_IP = "10.0.0.0/8"
|
||||||
|
const CHANGED_EXPECTED_IP = "10.1.2.3/16"
|
||||||
|
|
||||||
|
conf := &NetConf{
|
||||||
|
NetConf: types.NetConf{
|
||||||
|
CNIVersion: "0.3.1",
|
||||||
|
Name: "testConfig",
|
||||||
|
Type: "bridge",
|
||||||
|
},
|
||||||
|
BrName: IFNAME,
|
||||||
|
IsGW: true,
|
||||||
|
IPMasq: false,
|
||||||
|
MTU: 5000,
|
||||||
|
}
|
||||||
|
|
||||||
|
gwnFirst := &net.IPNet{
|
||||||
|
IP: net.IPv4(10, 0, 0, 0),
|
||||||
|
Mask: net.CIDRMask(8, 32),
|
||||||
|
}
|
||||||
|
|
||||||
|
gwnSecond := &net.IPNet{
|
||||||
|
IP: net.IPv4(10, 1, 2, 3),
|
||||||
|
Mask: net.CIDRMask(16, 32),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
bridge, _, err := setupBridge(conf)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
// Check if ForceAddress has default value
|
||||||
|
Expect(conf.ForceAddress).To(Equal(false))
|
||||||
|
|
||||||
|
err = ensureBridgeAddr(bridge, gwnFirst, conf.ForceAddress)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
//Check if IP address is set correctly
|
||||||
|
addrs, err := netlink.AddrList(bridge, syscall.AF_INET)
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
addr := addrs[0].IPNet.String()
|
||||||
|
Expect(addr).To(Equal(EXPECTED_IP))
|
||||||
|
|
||||||
|
//The bridge IP address has been changed. Error expected when ForceAddress is set to false.
|
||||||
|
err = ensureBridgeAddr(bridge, gwnSecond, false)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
//The IP address should stay the same.
|
||||||
|
addrs, err = netlink.AddrList(bridge, syscall.AF_INET)
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
addr = addrs[0].IPNet.String()
|
||||||
|
Expect(addr).To(Equal(EXPECTED_IP))
|
||||||
|
|
||||||
|
//Reconfigure IP when ForceAddress is set to true and IP address has been changed.
|
||||||
|
err = ensureBridgeAddr(bridge, gwnSecond, true)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
//Retrieve the IP address after reconfiguration
|
||||||
|
addrs, err = netlink.AddrList(bridge, syscall.AF_INET)
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
addr = addrs[0].IPNet.String()
|
||||||
|
Expect(addr).To(Equal(CHANGED_EXPECTED_IP))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
210
plugins/main/ipvlan/ipvlan.go
Normal file
210
plugins/main/ipvlan/ipvlan.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ip"
|
||||||
|
"github.com/containernetworking/cni/pkg/ipam"
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"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"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
Master string `json:"master"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// this ensures that main runs only on main thread (thread group leader).
|
||||||
|
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||||
|
// must ensure that the goroutine does not jump from OS thread to thread
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||||
|
n := &NetConf{}
|
||||||
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
if n.Master == "" {
|
||||||
|
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
||||||
|
}
|
||||||
|
return n, n.CNIVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func modeFromString(s string) (netlink.IPVlanMode, error) {
|
||||||
|
switch s {
|
||||||
|
case "", "l2":
|
||||||
|
return netlink.IPVLAN_MODE_L2, nil
|
||||||
|
case "l3":
|
||||||
|
return netlink.IPVLAN_MODE_L3, nil
|
||||||
|
case "l3s":
|
||||||
|
return netlink.IPVLAN_MODE_L3S, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown ipvlan mode: %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||||
|
ipvlan := ¤t.Interface{}
|
||||||
|
|
||||||
|
mode, err := modeFromString(conf.Mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := netlink.LinkByName(conf.Master)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// due to kernel bug we have to create with tmpname or it might
|
||||||
|
// collide with the name on the host and error out
|
||||||
|
tmpName, err := ip.RandomVethName()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mv := &netlink.IPVlan{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
MTU: conf.MTU,
|
||||||
|
Name: tmpName,
|
||||||
|
ParentIndex: m.Attrs().Index,
|
||||||
|
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||||
|
},
|
||||||
|
Mode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkAdd(mv); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create ipvlan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netns.Do(func(_ ns.NetNS) error {
|
||||||
|
err := ip.RenameLink(tmpName, ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
ipvlan.Name = ifName
|
||||||
|
|
||||||
|
// Re-fetch ipvlan to get all properties/attributes
|
||||||
|
contIpvlan, err := netlink.LinkByName(ipvlan.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to refetch ipvlan %q: %v", ipvlan.Name, err)
|
||||||
|
}
|
||||||
|
ipvlan.Mac = contIpvlan.Attrs().HardwareAddr.String()
|
||||||
|
ipvlan.Sandbox = netns.Path()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipvlan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
n, cniVersion, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
netns, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
defer netns.Close()
|
||||||
|
|
||||||
|
ipvlanInterface, err := createIpvlan(n, args.IfName, netns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the IPAM plugin and get back the config to apply
|
||||||
|
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
|
result, err := current.NewResultFromResult(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.IPs) == 0 {
|
||||||
|
return errors.New("IPAM plugin returned missing IP config")
|
||||||
|
}
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
// All addresses belong to the ipvlan interface
|
||||||
|
ipc.Interface = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Interfaces = []*current.Interface{ipvlanInterface}
|
||||||
|
|
||||||
|
err = netns.Do(func(_ ns.NetNS) error {
|
||||||
|
return ipam.ConfigureIface(args.IfName, result)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.DNS = n.DNS
|
||||||
|
|
||||||
|
return types.PrintResult(result, cniVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
n, _, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
|
// so don't return an error if the device is already removed.
|
||||||
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
if _, err := ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4); err != nil {
|
||||||
|
if err != ip.ErrLinkNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
27
plugins/main/ipvlan/ipvlan_suite_test.go
Normal file
27
plugins/main/ipvlan/ipvlan_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIpvlan(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "ipvlan Suite")
|
||||||
|
}
|
227
plugins/main/ipvlan/ipvlan_test.go
Normal file
227
plugins/main/ipvlan/ipvlan_test.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MASTER_NAME = "eth0"
|
||||||
|
|
||||||
|
var _ = Describe("ipvlan Operations", func() {
|
||||||
|
var originalNS ns.NetNS
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Create a new NetNS so we don't modify the host
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
// Add master
|
||||||
|
err = netlink.LinkAdd(&netlink.Dummy{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: MASTER_NAME,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
_, err = netlink.LinkByName(MASTER_NAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("creates an ipvlan link in a non-default namespace", func() {
|
||||||
|
conf := &NetConf{
|
||||||
|
NetConf: types.NetConf{
|
||||||
|
CNIVersion: "0.3.1",
|
||||||
|
Name: "testConfig",
|
||||||
|
Type: "ipvlan",
|
||||||
|
},
|
||||||
|
Master: MASTER_NAME,
|
||||||
|
Mode: "l2",
|
||||||
|
MTU: 1500,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ipvlan in other namespace
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
_, err := createIpvlan(conf, "foobar0", targetNs)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ipvlan link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName("foobar0")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal("foobar0"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures an iplvan link with ADD/DEL", func() {
|
||||||
|
const IFNAME = "ipvl0"
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "%s",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *current.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
result, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(result.Interfaces)).To(Equal(1))
|
||||||
|
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||||
|
Expect(len(result.IPs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ipvlan link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ipvlan link has been deleted
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("deconfigures an unconfigured ipvlan link with DEL", func() {
|
||||||
|
const IFNAME = "ipvl0"
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "%s",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
72
plugins/main/loopback/loopback.go
Normal file
72
plugins/main/loopback/loopback.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
args.IfName = "lo" // ignore config, this only works for loopback
|
||||||
|
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
link, err := netlink.LinkByName(args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netlink.LinkSetUp(link)
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
result := current.Result{}
|
||||||
|
return result.Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
args.IfName = "lo" // ignore config, this only works for loopback
|
||||||
|
err := ns.WithNetNSPath(args.Netns, func(ns.NetNS) error {
|
||||||
|
link, err := netlink.LinkByName(args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netlink.LinkSetDown(link)
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err // not tested
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
41
plugins/main/loopback/loopback_suite_test.go
Normal file
41
plugins/main/loopback/loopback_suite_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2016 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/onsi/gomega/gexec"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pathToLoPlugin string
|
||||||
|
|
||||||
|
func TestLoopback(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Loopback Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
var err error
|
||||||
|
pathToLoPlugin, err = gexec.Build("github.com/containernetworking/plugins/plugins/main/loopback")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
gexec.CleanupBuildArtifacts()
|
||||||
|
})
|
100
plugins/main/loopback/loopback_test.go
Normal file
100
plugins/main/loopback/loopback_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2016 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/onsi/gomega/gexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Loopback", func() {
|
||||||
|
var (
|
||||||
|
networkNS ns.NetNS
|
||||||
|
containerID string
|
||||||
|
command *exec.Cmd
|
||||||
|
environ []string
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
command = exec.Command(pathToLoPlugin)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
networkNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
environ = []string{
|
||||||
|
fmt.Sprintf("CNI_CONTAINERID=%s", containerID),
|
||||||
|
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
|
||||||
|
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
|
||||||
|
fmt.Sprintf("CNI_ARGS=%s", "none"),
|
||||||
|
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
|
||||||
|
}
|
||||||
|
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(networkNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when given a network namespace", func() {
|
||||||
|
It("sets the lo device to UP", func() {
|
||||||
|
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
|
||||||
|
|
||||||
|
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Eventually(session).Should(gbytes.Say(`{.*}`))
|
||||||
|
Eventually(session).Should(gexec.Exit(0))
|
||||||
|
|
||||||
|
var lo *net.Interface
|
||||||
|
err = networkNS.Do(func(ns.NetNS) error {
|
||||||
|
var err error
|
||||||
|
lo, err = net.InterfaceByName("lo")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(lo.Flags & net.FlagUp).To(Equal(net.FlagUp))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("sets the lo device to DOWN", func() {
|
||||||
|
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
|
||||||
|
|
||||||
|
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Eventually(session).Should(gbytes.Say(``))
|
||||||
|
Eventually(session).Should(gexec.Exit(0))
|
||||||
|
|
||||||
|
var lo *net.Interface
|
||||||
|
err = networkNS.Do(func(ns.NetNS) error {
|
||||||
|
var err error
|
||||||
|
lo, err = net.InterfaceByName("lo")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(lo.Flags & net.FlagUp).NotTo(Equal(net.FlagUp))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
251
plugins/main/macvlan/macvlan.go
Normal file
251
plugins/main/macvlan/macvlan.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ip"
|
||||||
|
"github.com/containernetworking/cni/pkg/ipam"
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/utils/sysctl"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPv4InterfaceArpProxySysctlTemplate = "net.ipv4.conf.%s.proxy_arp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
Master string `json:"master"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// this ensures that main runs only on main thread (thread group leader).
|
||||||
|
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||||
|
// must ensure that the goroutine does not jump from OS thread to thread
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||||
|
n := &NetConf{}
|
||||||
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
if n.Master == "" {
|
||||||
|
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
||||||
|
}
|
||||||
|
return n, n.CNIVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func modeFromString(s string) (netlink.MacvlanMode, error) {
|
||||||
|
switch s {
|
||||||
|
case "", "bridge":
|
||||||
|
return netlink.MACVLAN_MODE_BRIDGE, nil
|
||||||
|
case "private":
|
||||||
|
return netlink.MACVLAN_MODE_PRIVATE, nil
|
||||||
|
case "vepa":
|
||||||
|
return netlink.MACVLAN_MODE_VEPA, nil
|
||||||
|
case "passthru":
|
||||||
|
return netlink.MACVLAN_MODE_PASSTHRU, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown macvlan mode: %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||||
|
macvlan := ¤t.Interface{}
|
||||||
|
|
||||||
|
mode, err := modeFromString(conf.Mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := netlink.LinkByName(conf.Master)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// due to kernel bug we have to create with tmpName or it might
|
||||||
|
// collide with the name on the host and error out
|
||||||
|
tmpName, err := ip.RandomVethName()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mv := &netlink.Macvlan{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
MTU: conf.MTU,
|
||||||
|
Name: tmpName,
|
||||||
|
ParentIndex: m.Attrs().Index,
|
||||||
|
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||||
|
},
|
||||||
|
Mode: mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkAdd(mv); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create macvlan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netns.Do(func(_ ns.NetNS) error {
|
||||||
|
// TODO: duplicate following lines for ipv6 support, when it will be added in other places
|
||||||
|
ipv4SysctlValueName := fmt.Sprintf(IPv4InterfaceArpProxySysctlTemplate, tmpName)
|
||||||
|
if _, err := sysctl.Sysctl(ipv4SysctlValueName, "1"); err != nil {
|
||||||
|
// remove the newly added link and ignore errors, because we already are in a failed state
|
||||||
|
_ = netlink.LinkDel(mv)
|
||||||
|
return fmt.Errorf("failed to set proxy_arp on newly added interface %q: %v", tmpName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ip.RenameLink(tmpName, ifName)
|
||||||
|
if err != nil {
|
||||||
|
_ = netlink.LinkDel(mv)
|
||||||
|
return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
macvlan.Name = ifName
|
||||||
|
|
||||||
|
// Re-fetch macvlan to get all properties/attributes
|
||||||
|
contMacvlan, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to refetch macvlan %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
macvlan.Mac = contMacvlan.Attrs().HardwareAddr.String()
|
||||||
|
macvlan.Sandbox = netns.Path()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return macvlan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
n, cniVersion, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
netns, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||||
|
}
|
||||||
|
defer netns.Close()
|
||||||
|
|
||||||
|
macvlanInterface, err := createMacvlan(n, args.IfName, netns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the IPAM plugin and get back the config to apply
|
||||||
|
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
|
result, err := current.NewResultFromResult(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.IPs) == 0 {
|
||||||
|
return errors.New("IPAM plugin returned missing IP config")
|
||||||
|
}
|
||||||
|
result.Interfaces = []*current.Interface{macvlanInterface}
|
||||||
|
|
||||||
|
var firstV4Addr net.IP
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
// All addresses apply to the container macvlan interface
|
||||||
|
ipc.Interface = 0
|
||||||
|
|
||||||
|
if ipc.Address.IP.To4() != nil && firstV4Addr == nil {
|
||||||
|
firstV4Addr = ipc.Address.IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstV4Addr != nil {
|
||||||
|
err = netns.Do(func(_ ns.NetNS) error {
|
||||||
|
if err := ip.SetHWAddrByIP(args.IfName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipam.ConfigureIface(args.IfName, result)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch macvlan interface as its MAC address may have changed
|
||||||
|
err = netns.Do(func(_ ns.NetNS) error {
|
||||||
|
link, err := netlink.LinkByName(args.IfName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to re-fetch macvlan interface: %v", err)
|
||||||
|
}
|
||||||
|
macvlanInterface.Mac = link.Attrs().HardwareAddr.String()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.DNS = n.DNS
|
||||||
|
|
||||||
|
return types.PrintResult(result, cniVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
n, _, err := loadConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
|
// so don't return an error if the device is already removed.
|
||||||
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
if _, err := ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4); err != nil {
|
||||||
|
if err != ip.ErrLinkNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
27
plugins/main/macvlan/macvlan_suite_test.go
Normal file
27
plugins/main/macvlan/macvlan_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMacvlan(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "macvlan Suite")
|
||||||
|
}
|
230
plugins/main/macvlan/macvlan_test.go
Normal file
230
plugins/main/macvlan/macvlan_test.go
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/utils/hwaddr"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MASTER_NAME = "eth0"
|
||||||
|
|
||||||
|
var _ = Describe("macvlan Operations", func() {
|
||||||
|
var originalNS ns.NetNS
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Create a new NetNS so we don't modify the host
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
// Add master
|
||||||
|
err = netlink.LinkAdd(&netlink.Dummy{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: MASTER_NAME,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
_, err = netlink.LinkByName(MASTER_NAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("creates an macvlan link in a non-default namespace", func() {
|
||||||
|
conf := &NetConf{
|
||||||
|
NetConf: types.NetConf{
|
||||||
|
CNIVersion: "0.3.1",
|
||||||
|
Name: "testConfig",
|
||||||
|
Type: "macvlan",
|
||||||
|
},
|
||||||
|
Master: MASTER_NAME,
|
||||||
|
Mode: "bridge",
|
||||||
|
MTU: 1500,
|
||||||
|
}
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
_, err = createMacvlan(conf, "foobar0", targetNs)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure macvlan link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName("foobar0")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal("foobar0"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a macvlan link with ADD/DEL", func() {
|
||||||
|
const IFNAME = "macvl0"
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "macvlan",
|
||||||
|
"master": "%s",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *current.Result
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
r, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
result, err = current.GetResult(r)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(result.Interfaces)).To(Equal(1))
|
||||||
|
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||||
|
Expect(len(result.IPs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure macvlan link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
|
||||||
|
hwaddrString := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
|
||||||
|
Expect(hwaddrString).To(HavePrefix(hwaddr.PrivateMACPrefixString))
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(addrs)).To(Equal(1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure macvlan link has been deleted
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("deconfigures an unconfigured macvlan link with DEL", func() {
|
||||||
|
const IFNAME = "macvl0"
|
||||||
|
|
||||||
|
conf := fmt.Sprintf(`{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "macvlan",
|
||||||
|
"master": "%s",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`, MASTER_NAME)
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
299
plugins/main/ptp/ptp.go
Normal file
299
plugins/main/ptp/ptp.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ip"
|
||||||
|
"github.com/containernetworking/cni/pkg/ipam"
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/cni/pkg/utils"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// this ensures that main runs only on main thread (thread group leader).
|
||||||
|
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||||
|
// must ensure that the goroutine does not jump from OS thread to thread
|
||||||
|
runtime.LockOSThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
IPMasq bool `json:"ipMasq"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
|
||||||
|
// The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1.
|
||||||
|
// What we want is really a point-to-point link but veth does not support IFF_POINTOPONT.
|
||||||
|
// Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and
|
||||||
|
// add a route like "192.168.3.0/24 via 192.168.3.1 dev $ifName".
|
||||||
|
// Unfortunately that won't work as the GW will be outside the interface's subnet.
|
||||||
|
|
||||||
|
// Our solution is to configure the interface with 192.168.3.5/24, then delete the
|
||||||
|
// "192.168.3.0/24 dev $ifName" route that was automatically added. Then we add
|
||||||
|
// "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName".
|
||||||
|
// In other words we force all traffic to ARP via the gateway except for GW itself.
|
||||||
|
|
||||||
|
hostInterface := ¤t.Interface{}
|
||||||
|
containerInterface := ¤t.Interface{}
|
||||||
|
|
||||||
|
err := netns.Do(func(hostNS ns.NetNS) error {
|
||||||
|
hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hostInterface.Name = hostVeth.Name
|
||||||
|
hostInterface.Mac = hostVeth.HardwareAddr.String()
|
||||||
|
containerInterface.Name = contVeth0.Name
|
||||||
|
containerInterface.Mac = contVeth0.HardwareAddr.String()
|
||||||
|
containerInterface.Sandbox = netns.Path()
|
||||||
|
|
||||||
|
var firstV4Addr net.IP
|
||||||
|
for _, ipc := range pr.IPs {
|
||||||
|
// All addresses apply to the container veth interface
|
||||||
|
ipc.Interface = 1
|
||||||
|
|
||||||
|
if ipc.Address.IP.To4() != nil && firstV4Addr == nil {
|
||||||
|
firstV4Addr = ipc.Address.IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.Interfaces = []*current.Interface{hostInterface, containerInterface}
|
||||||
|
|
||||||
|
if firstV4Addr != nil {
|
||||||
|
err = hostNS.Do(func(_ ns.NetNS) error {
|
||||||
|
hostVethName := hostVeth.Name
|
||||||
|
if err := ip.SetHWAddrByIP(hostVethName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
|
||||||
|
return fmt.Errorf("failed to set hardware addr by IP: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipam.ConfigureIface(ifName, pr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ip.SetHWAddrByIP(contVeth0.Name, firstV4Addr, nil /* TODO IPv6 */); err != nil {
|
||||||
|
return fmt.Errorf("failed to set hardware addr by IP: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch container veth to update attributes
|
||||||
|
contVeth, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to look up %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ipc := range pr.IPs {
|
||||||
|
// Delete the route that was automatically added
|
||||||
|
route := netlink.Route{
|
||||||
|
LinkIndex: contVeth.Attrs().Index,
|
||||||
|
Dst: &net.IPNet{
|
||||||
|
IP: ipc.Address.IP.Mask(ipc.Address.Mask),
|
||||||
|
Mask: ipc.Address.Mask,
|
||||||
|
},
|
||||||
|
Scope: netlink.SCOPE_NOWHERE,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.RouteDel(&route); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete route %v: %v", route, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range []netlink.Route{
|
||||||
|
netlink.Route{
|
||||||
|
LinkIndex: contVeth.Attrs().Index,
|
||||||
|
Dst: &net.IPNet{
|
||||||
|
IP: ipc.Gateway,
|
||||||
|
Mask: net.CIDRMask(32, 32),
|
||||||
|
},
|
||||||
|
Scope: netlink.SCOPE_LINK,
|
||||||
|
Src: ipc.Address.IP,
|
||||||
|
},
|
||||||
|
netlink.Route{
|
||||||
|
LinkIndex: contVeth.Attrs().Index,
|
||||||
|
Dst: &net.IPNet{
|
||||||
|
IP: ipc.Address.IP.Mask(ipc.Address.Mask),
|
||||||
|
Mask: ipc.Address.Mask,
|
||||||
|
},
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Gw: ipc.Gateway,
|
||||||
|
Src: ipc.Address.IP,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if err := netlink.RouteAdd(&r); err != nil {
|
||||||
|
return fmt.Errorf("failed to add route %v: %v", r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return hostInterface, containerInterface, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupHostVeth(vethName string, result *current.Result) error {
|
||||||
|
// hostVeth moved namespaces and may have a new ifindex
|
||||||
|
veth, err := netlink.LinkByName(vethName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %q: %v", vethName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
maskLen := 128
|
||||||
|
if ipc.Address.IP.To4() != nil {
|
||||||
|
maskLen = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
ipn := &net.IPNet{
|
||||||
|
IP: ipc.Gateway,
|
||||||
|
Mask: net.CIDRMask(maskLen, maskLen),
|
||||||
|
}
|
||||||
|
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||||
|
if err = netlink.AddrAdd(veth, addr); err != nil {
|
||||||
|
return fmt.Errorf("failed to add IP addr (%#v) to veth: %v", ipn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipn = &net.IPNet{
|
||||||
|
IP: ipc.Address.IP,
|
||||||
|
Mask: net.CIDRMask(maskLen, maskLen),
|
||||||
|
}
|
||||||
|
// dst happens to be the same as IP/net of host veth
|
||||||
|
if err = ip.AddHostRoute(ipn, nil, veth); err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to add route on host: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
conf := NetConf{}
|
||||||
|
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||||
|
return fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ip.EnableIP4Forward(); err != nil {
|
||||||
|
return fmt.Errorf("failed to enable forwarding: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the IPAM plugin and get back the config to apply
|
||||||
|
r, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Convert whatever the IPAM result was into the current Result type
|
||||||
|
result, err := current.NewResultFromResult(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.IPs) == 0 {
|
||||||
|
return errors.New("IPAM plugin returned missing IP config")
|
||||||
|
}
|
||||||
|
|
||||||
|
netns, err := ns.GetNS(args.Netns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||||
|
}
|
||||||
|
defer netns.Close()
|
||||||
|
|
||||||
|
hostInterface, containerInterface, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = setupHostVeth(hostInterface.Name, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.IPMasq {
|
||||||
|
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||||
|
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||||
|
for _, ipc := range result.IPs {
|
||||||
|
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.DNS = conf.DNS
|
||||||
|
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
|
||||||
|
|
||||||
|
return types.PrintResult(result, conf.CNIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
conf := NetConf{}
|
||||||
|
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||||
|
return fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a netns so try to clean up. Delete can be called multiple times
|
||||||
|
// so don't return an error if the device is already removed.
|
||||||
|
// If the device isn't there then don't try to clean up IP masq either.
|
||||||
|
var ipn *net.IPNet
|
||||||
|
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
var err error
|
||||||
|
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
||||||
|
if err != nil && err == ip.ErrLinkNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipn != nil && conf.IPMasq {
|
||||||
|
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||||
|
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||||
|
err = ip.TeardownIPMasq(ipn, chain, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
27
plugins/main/ptp/ptp_suite_test.go
Normal file
27
plugins/main/ptp/ptp_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPtp(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "ptp Suite")
|
||||||
|
}
|
152
plugins/main/ptp/ptp_test.go
Normal file
152
plugins/main/ptp/ptp_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("ptp Operations", func() {
|
||||||
|
var originalNS ns.NetNS
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Create a new NetNS so we don't modify the host
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("configures and deconfigures a ptp link with ADD/DEL", func() {
|
||||||
|
const IFNAME = "ptp0"
|
||||||
|
|
||||||
|
conf := `{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ptp",
|
||||||
|
"ipMasq": true,
|
||||||
|
"mtu": 5000,
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the plugin with the ADD command, creating the veth endpoints
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
_, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ptp link exists in the target namespace
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Call the plugins with the DEL command, deleting the veth endpoints
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Make sure ptp link has been deleted
|
||||||
|
err = targetNs.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(IFNAME)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(link).To(BeNil())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("deconfigures an unconfigured ptp link with DEL", func() {
|
||||||
|
const IFNAME = "ptp0"
|
||||||
|
|
||||||
|
conf := `{
|
||||||
|
"cniVersion": "0.3.0",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ptp",
|
||||||
|
"ipMasq": true,
|
||||||
|
"mtu": 5000,
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": "10.1.2.0/24"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "dummy",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the plugins with the DEL command. It should not error even though the veth doesn't exist.
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
266
plugins/meta/flannel/flannel.go
Normal file
266
plugins/meta/flannel/flannel.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// This is a "meta-plugin". It reads in its own netconf, combines it with
|
||||||
|
// the data from flannel generated subnet file and then invokes a plugin
|
||||||
|
// like bridge or ipvlan to do the real work.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultSubnetFile = "/run/flannel/subnet.env"
|
||||||
|
defaultDataDir = "/var/lib/cni/flannel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetConf struct {
|
||||||
|
types.NetConf
|
||||||
|
SubnetFile string `json:"subnetFile"`
|
||||||
|
DataDir string `json:"dataDir"`
|
||||||
|
Delegate map[string]interface{} `json:"delegate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type subnetEnv struct {
|
||||||
|
nw *net.IPNet
|
||||||
|
sn *net.IPNet
|
||||||
|
mtu *uint
|
||||||
|
ipmasq *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *subnetEnv) missing() string {
|
||||||
|
m := []string{}
|
||||||
|
|
||||||
|
if se.nw == nil {
|
||||||
|
m = append(m, "FLANNEL_NETWORK")
|
||||||
|
}
|
||||||
|
if se.sn == nil {
|
||||||
|
m = append(m, "FLANNEL_SUBNET")
|
||||||
|
}
|
||||||
|
if se.mtu == nil {
|
||||||
|
m = append(m, "FLANNEL_MTU")
|
||||||
|
}
|
||||||
|
if se.ipmasq == nil {
|
||||||
|
m = append(m, "FLANNEL_IPMASQ")
|
||||||
|
}
|
||||||
|
return strings.Join(m, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFlannelNetConf(bytes []byte) (*NetConf, error) {
|
||||||
|
n := &NetConf{
|
||||||
|
SubnetFile: defaultSubnetFile,
|
||||||
|
DataDir: defaultDataDir,
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(bytes, n); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
|
||||||
|
f, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
se := &subnetEnv{}
|
||||||
|
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
for s.Scan() {
|
||||||
|
parts := strings.SplitN(s.Text(), "=", 2)
|
||||||
|
switch parts[0] {
|
||||||
|
case "FLANNEL_NETWORK":
|
||||||
|
_, se.nw, err = net.ParseCIDR(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "FLANNEL_SUBNET":
|
||||||
|
_, se.sn, err = net.ParseCIDR(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "FLANNEL_MTU":
|
||||||
|
mtu, err := strconv.ParseUint(parts[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
se.mtu = new(uint)
|
||||||
|
*se.mtu = uint(mtu)
|
||||||
|
|
||||||
|
case "FLANNEL_IPMASQ":
|
||||||
|
ipmasq := parts[1] == "true"
|
||||||
|
se.ipmasq = &ipmasq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m := se.missing(); m != "" {
|
||||||
|
return nil, fmt.Errorf("%v is missing %v", fn, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return se, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
|
||||||
|
if err := os.MkdirAll(dataDir, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := filepath.Join(dataDir, containerID)
|
||||||
|
return ioutil.WriteFile(path, netconf, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
|
||||||
|
path := filepath.Join(dataDir, containerID)
|
||||||
|
// Ignore errors when removing - Per spec safe to continue during DEL
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
|
return ioutil.ReadFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
|
||||||
|
netconfBytes, err := json.Marshal(netconf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error serializing delegate netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the rendered netconf for cmdDel
|
||||||
|
if err = saveScratchNetConf(cid, dataDir, netconfBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := invoke.DelegateAdd(netconf["type"].(string), netconfBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasKey(m map[string]interface{}, k string) bool {
|
||||||
|
_, ok := m[k]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isString(i interface{}) bool {
|
||||||
|
_, ok := i.(string)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
n, err := loadFlannelNetConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fenv, err := loadFlannelSubnetEnv(n.SubnetFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.Delegate == nil {
|
||||||
|
n.Delegate = make(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
if hasKey(n.Delegate, "type") && !isString(n.Delegate["type"]) {
|
||||||
|
return fmt.Errorf("'delegate' dictionary, if present, must have (string) 'type' field")
|
||||||
|
}
|
||||||
|
if hasKey(n.Delegate, "name") {
|
||||||
|
return fmt.Errorf("'delegate' dictionary must not have 'name' field, it'll be set by flannel")
|
||||||
|
}
|
||||||
|
if hasKey(n.Delegate, "ipam") {
|
||||||
|
return fmt.Errorf("'delegate' dictionary must not have 'ipam' field, it'll be set by flannel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Delegate["name"] = n.Name
|
||||||
|
|
||||||
|
if !hasKey(n.Delegate, "type") {
|
||||||
|
n.Delegate["type"] = "bridge"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasKey(n.Delegate, "ipMasq") {
|
||||||
|
// if flannel is not doing ipmasq, we should
|
||||||
|
ipmasq := !*fenv.ipmasq
|
||||||
|
n.Delegate["ipMasq"] = ipmasq
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasKey(n.Delegate, "mtu") {
|
||||||
|
mtu := fenv.mtu
|
||||||
|
n.Delegate["mtu"] = mtu
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.Delegate["type"].(string) == "bridge" {
|
||||||
|
if !hasKey(n.Delegate, "isGateway") {
|
||||||
|
n.Delegate["isGateway"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Delegate["ipam"] = map[string]interface{}{
|
||||||
|
"type": "host-local",
|
||||||
|
"subnet": fenv.sn.String(),
|
||||||
|
"routes": []types.Route{
|
||||||
|
types.Route{
|
||||||
|
Dst: *fenv.nw,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
nc, err := loadFlannelNetConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
netconfBytes, err := consumeScratchNetConf(args.ContainerID, nc.DataDir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Per spec should ignore error if resources are missing / already removed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &types.NetConf{}
|
||||||
|
if err = json.Unmarshal(netconfBytes, n); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.DelegateDel(n.Type, netconfBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
26
plugins/meta/flannel/flannel_suite_test.go
Normal file
26
plugins/meta/flannel/flannel_suite_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlannel(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Flannel Suite")
|
||||||
|
}
|
211
plugins/meta/flannel/flannel_test.go
Normal file
211
plugins/meta/flannel/flannel_test.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/testutils"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Flannel", func() {
|
||||||
|
var (
|
||||||
|
originalNS ns.NetNS
|
||||||
|
input string
|
||||||
|
subnetFile string
|
||||||
|
dataDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
originalNS, err = ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(originalNS.Close()).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputTemplate = `
|
||||||
|
{
|
||||||
|
"name": "cni-flannel",
|
||||||
|
"type": "flannel",
|
||||||
|
"subnetFile": "%s",
|
||||||
|
"dataDir": "%s"
|
||||||
|
}`
|
||||||
|
|
||||||
|
const flannelSubnetEnv = `
|
||||||
|
FLANNEL_NETWORK=10.1.0.0/16
|
||||||
|
FLANNEL_SUBNET=10.1.17.1/24
|
||||||
|
FLANNEL_MTU=1472
|
||||||
|
FLANNEL_IPMASQ=true
|
||||||
|
`
|
||||||
|
|
||||||
|
var writeSubnetEnv = func(contents string) string {
|
||||||
|
file, err := ioutil.TempFile("", "subnet.env")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
_, err = file.WriteString(contents)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
return file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
// flannel subnet.env
|
||||||
|
subnetFile = writeSubnetEnv(flannelSubnetEnv)
|
||||||
|
|
||||||
|
// flannel state dir
|
||||||
|
dataDir, err = ioutil.TempDir("", "dataDir")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
input = fmt.Sprintf(inputTemplate, subnetFile, dataDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
os.Remove(subnetFile)
|
||||||
|
os.Remove(dataDir)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CNI lifecycle", func() {
|
||||||
|
It("uses dataDir for storing network configuration", func() {
|
||||||
|
const IFNAME = "eth0"
|
||||||
|
|
||||||
|
targetNs, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer targetNs.Close()
|
||||||
|
|
||||||
|
args := &skel.CmdArgs{
|
||||||
|
ContainerID: "some-container-id",
|
||||||
|
Netns: targetNs.Path(),
|
||||||
|
IfName: IFNAME,
|
||||||
|
StdinData: []byte(input),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = originalNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
By("calling ADD")
|
||||||
|
_, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(input), func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("check that plugin writes to net config to dataDir")
|
||||||
|
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
|
||||||
|
Expect(path).Should(BeAnExistingFile())
|
||||||
|
|
||||||
|
netConfBytes, err := ioutil.ReadFile(path)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
expected := `{
|
||||||
|
"ipMasq" : false,
|
||||||
|
"ipam" : {
|
||||||
|
"routes" : [
|
||||||
|
{
|
||||||
|
"dst" : "10.1.0.0/16"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subnet" : "10.1.17.0/24",
|
||||||
|
"type" : "host-local"
|
||||||
|
},
|
||||||
|
"isGateway": true,
|
||||||
|
"mtu" : 1472,
|
||||||
|
"name" : "cni-flannel",
|
||||||
|
"type" : "bridge"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
Expect(netConfBytes).Should(MatchJSON(expected))
|
||||||
|
|
||||||
|
By("calling DEL")
|
||||||
|
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("check that plugin removes net config from state dir")
|
||||||
|
Expect(path).ShouldNot(BeAnExistingFile())
|
||||||
|
|
||||||
|
By("calling DEL again")
|
||||||
|
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
|
||||||
|
return cmdDel(args)
|
||||||
|
})
|
||||||
|
By("check that plugin does not fail due to missing net config")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("loadFlannelNetConf", func() {
|
||||||
|
Context("when subnetFile and dataDir are specified", func() {
|
||||||
|
It("loads flannel network config", func() {
|
||||||
|
conf, err := loadFlannelNetConf([]byte(input))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||||
|
Expect(conf.Type).To(Equal("flannel"))
|
||||||
|
Expect(conf.SubnetFile).To(Equal(subnetFile))
|
||||||
|
Expect(conf.DataDir).To(Equal(dataDir))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when defaulting subnetFile and dataDir", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
input = `{
|
||||||
|
"name": "cni-flannel",
|
||||||
|
"type": "flannel"
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
|
||||||
|
It("loads flannel network config with defaults", func() {
|
||||||
|
conf, err := loadFlannelNetConf([]byte(input))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||||
|
Expect(conf.Type).To(Equal("flannel"))
|
||||||
|
Expect(conf.SubnetFile).To(Equal(defaultSubnetFile))
|
||||||
|
Expect(conf.DataDir).To(Equal(defaultDataDir))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("loadFlannelSubnetEnv", func() {
|
||||||
|
Context("when flannel subnet env is valid", func() {
|
||||||
|
It("loads flannel subnet config", func() {
|
||||||
|
conf, err := loadFlannelSubnetEnv(subnetFile)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(conf.nw.String()).To(Equal("10.1.0.0/16"))
|
||||||
|
Expect(conf.sn.String()).To(Equal("10.1.17.0/24"))
|
||||||
|
var mtu uint = 1472
|
||||||
|
Expect(*conf.mtu).To(Equal(mtu))
|
||||||
|
Expect(*conf.ipmasq).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when flannel subnet env is invalid", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
subnetFile = writeSubnetEnv("foo=bar")
|
||||||
|
})
|
||||||
|
It("returns an error", func() {
|
||||||
|
_, err := loadFlannelSubnetEnv(subnetFile)
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
84
plugins/meta/tuning/tuning.go
Normal file
84
plugins/meta/tuning/tuning.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2016 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 "meta-plugin". It reads in its own netconf, it does not create
|
||||||
|
// any network interface but just changes the network sysctl.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TuningConf represents the network tuning configuration.
|
||||||
|
type TuningConf struct {
|
||||||
|
types.NetConf
|
||||||
|
SysCtl map[string]string `json:"sysctl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
tuningConf := TuningConf{}
|
||||||
|
if err := json.Unmarshal(args.StdinData, &tuningConf); err != nil {
|
||||||
|
return fmt.Errorf("failed to load netconf: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The directory /proc/sys/net is per network namespace. Enter in the
|
||||||
|
// network namespace before writing on it.
|
||||||
|
|
||||||
|
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
|
for key, value := range tuningConf.SysCtl {
|
||||||
|
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
|
||||||
|
fileName = filepath.Clean(fileName)
|
||||||
|
|
||||||
|
// Refuse to modify sysctl parameters that don't belong
|
||||||
|
// to the network subsystem.
|
||||||
|
if !strings.HasPrefix(fileName, "/proc/sys/net/") {
|
||||||
|
return fmt.Errorf("invalid net sysctl key: %q", key)
|
||||||
|
}
|
||||||
|
content := []byte(value)
|
||||||
|
err := ioutil.WriteFile(fileName, content, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := current.Result{}
|
||||||
|
return result.Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
|
// TODO: the settings are not reverted to the previous values. Reverting the
|
||||||
|
// settings is not useful when the whole container goes away but it could be
|
||||||
|
// useful in scenarios where plugins are added and removed at runtime.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||||
|
}
|
23
test
23
test
@ -2,32 +2,15 @@
|
|||||||
#
|
#
|
||||||
# Run CNI plugin tests.
|
# Run CNI plugin tests.
|
||||||
#
|
#
|
||||||
# This needs sudo, as we'll be creating net interfaces. It also needs a valid
|
# This needs sudo, as we'll be creating net interfaces.
|
||||||
# CNI_PATH, with at least the ptp and host-local plugins.
|
|
||||||
#
|
#
|
||||||
# You'll probably run it like this:
|
|
||||||
# CNI_PATH=../cni/bin ./test
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
source ./build
|
source ./build
|
||||||
|
|
||||||
if [ -z "$CNI_PATH" ]; then
|
|
||||||
echo "Need a valid CNI_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check that the plugins we need are available
|
|
||||||
if ! PATH=${CNI_PATH} type -P ptp host-local >& /dev/null; then
|
|
||||||
echo '$CNI_PATH must include ptp and host-local'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#add our build path to CNI_PATH
|
|
||||||
CNI_PATH=$(pwd)/bin:${CNI_PATH}
|
|
||||||
|
|
||||||
echo "Running tests"
|
echo "Running tests"
|
||||||
|
|
||||||
TESTABLE="plugins/sample plugins/vlan"
|
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"
|
||||||
|
|
||||||
# user has not provided PKG override
|
# user has not provided PKG override
|
||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
@ -48,7 +31,7 @@ fi
|
|||||||
split=(${TEST// / })
|
split=(${TEST// / })
|
||||||
TEST=${split[@]/#/${REPO_PATH}/}
|
TEST=${split[@]/#/${REPO_PATH}/}
|
||||||
|
|
||||||
sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${CNI_PATH}:${PATH} go test ${TEST}"
|
sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test ${TEST}"
|
||||||
|
|
||||||
echo "Checking gofmt..."
|
echo "Checking gofmt..."
|
||||||
fmtRes=$(gofmt -l $FMT)
|
fmtRes=$(gofmt -l $FMT)
|
||||||
|
2
vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go
generated
vendored
2
vendor/github.com/containernetworking/cni/pkg/invoke/exec_test.go
generated
vendored
@ -50,7 +50,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||||||
VersionDecoder: versionDecoder,
|
VersionDecoder: versionDecoder,
|
||||||
}
|
}
|
||||||
pluginPath = "/some/plugin/path"
|
pluginPath = "/some/plugin/path"
|
||||||
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.0" }`)
|
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`)
|
||||||
cniargs = &fakes.CNIArgs{}
|
cniargs = &fakes.CNIArgs{}
|
||||||
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
||||||
})
|
})
|
||||||
|
2
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec_test.go
generated
vendored
2
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec_test.go
generated
vendored
@ -58,7 +58,7 @@ var _ = Describe("RawExec", func() {
|
|||||||
"CNI_PATH=/some/bin/path",
|
"CNI_PATH=/some/bin/path",
|
||||||
"CNI_IFNAME=some-eth0",
|
"CNI_IFNAME=some-eth0",
|
||||||
}
|
}
|
||||||
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.0"}`)
|
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.1"}`)
|
||||||
execer = &invoke.RawExec{}
|
execer = &invoke.RawExec{}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
49
vendor/github.com/containernetworking/cni/pkg/ip/link.go
generated
vendored
49
vendor/github.com/containernetworking/cni/pkg/ip/link.go
generated
vendored
@ -16,6 +16,7 @@ package ip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -25,6 +26,10 @@ import (
|
|||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrLinkNotFound = errors.New("link not found")
|
||||||
|
)
|
||||||
|
|
||||||
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
|
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
|
||||||
veth := &netlink.Veth{
|
veth := &netlink.Veth{
|
||||||
LinkAttrs: netlink.LinkAttrs{
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
@ -98,30 +103,38 @@ func RenameLink(curName, newName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupVeth sets up a virtual ethernet link.
|
func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
|
||||||
// Should be in container netns, and will switch back to hostNS to set the host
|
a := l.Attrs()
|
||||||
// veth end up.
|
return net.Interface{
|
||||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (hostVeth, contVeth netlink.Link, err error) {
|
Index: a.Index,
|
||||||
var hostVethName string
|
MTU: a.MTU,
|
||||||
hostVethName, contVeth, err = makeVeth(contVethName, mtu)
|
Name: a.Name,
|
||||||
|
HardwareAddr: a.HardwareAddr,
|
||||||
|
Flags: a.Flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||||
|
// Call SetupVeth from inside the container netns. It will create both veth
|
||||||
|
// devices and move the host-side veth into the provided hostNS namespace.
|
||||||
|
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||||
|
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||||
|
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return net.Interface{}, net.Interface{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = netlink.LinkSetUp(contVeth); err != nil {
|
if err = netlink.LinkSetUp(contVeth); err != nil {
|
||||||
err = fmt.Errorf("failed to set %q up: %v", contVethName, err)
|
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to set %q up: %v", contVethName, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostVeth, err = netlink.LinkByName(hostVethName)
|
hostVeth, err := netlink.LinkByName(hostVethName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
|
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
|
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
|
||||||
err = fmt.Errorf("failed to move veth to host netns: %v", err)
|
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to move veth to host netns: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = hostNS.Do(func(_ ns.NetNS) error {
|
err = hostNS.Do(func(_ ns.NetNS) error {
|
||||||
@ -135,7 +148,10 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (hostVeth, contVet
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return
|
if err != nil {
|
||||||
|
return net.Interface{}, net.Interface{}, err
|
||||||
|
}
|
||||||
|
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelLinkByName removes an interface link.
|
// DelLinkByName removes an interface link.
|
||||||
@ -157,6 +173,9 @@ func DelLinkByName(ifName string) error {
|
|||||||
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
||||||
iface, err := netlink.LinkByName(ifName)
|
iface, err := netlink.LinkByName(ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != nil && err.Error() == "Link not found" {
|
||||||
|
return nil, ErrLinkNotFound
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
vendor/github.com/containernetworking/cni/pkg/ip/link_test.go
generated
vendored
28
vendor/github.com/containernetworking/cni/pkg/ip/link_test.go
generated
vendored
@ -46,8 +46,8 @@ var _ = Describe("Link", func() {
|
|||||||
hostNetNS ns.NetNS
|
hostNetNS ns.NetNS
|
||||||
containerNetNS ns.NetNS
|
containerNetNS ns.NetNS
|
||||||
ifaceCounter int = 0
|
ifaceCounter int = 0
|
||||||
hostVeth netlink.Link
|
hostVeth net.Interface
|
||||||
containerVeth netlink.Link
|
containerVeth net.Interface
|
||||||
hostVethName string
|
hostVethName string
|
||||||
containerVethName string
|
containerVethName string
|
||||||
|
|
||||||
@ -78,8 +78,8 @@ var _ = Describe("Link", func() {
|
|||||||
}
|
}
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
hostVethName = hostVeth.Attrs().Name
|
hostVethName = hostVeth.Name
|
||||||
containerVethName = containerVeth.Attrs().Name
|
containerVethName = containerVeth.Name
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -98,7 +98,7 @@ var _ = Describe("Link", func() {
|
|||||||
|
|
||||||
containerVethFromName, err := netlink.LinkByName(containerVethName)
|
containerVethFromName, err := netlink.LinkByName(containerVethName)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(containerVethFromName.Attrs().Index).To(Equal(containerVeth.Attrs().Index))
|
Expect(containerVethFromName.Attrs().Index).To(Equal(containerVeth.Index))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -108,7 +108,7 @@ var _ = Describe("Link", func() {
|
|||||||
|
|
||||||
hostVethFromName, err := netlink.LinkByName(hostVethName)
|
hostVethFromName, err := netlink.LinkByName(hostVethName)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(hostVethFromName.Attrs().Index).To(Equal(hostVeth.Attrs().Index))
|
Expect(hostVethFromName.Attrs().Index).To(Equal(hostVeth.Index))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -127,6 +127,20 @@ var _ = Describe("Link", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("deleting an non-existent device", func() {
|
||||||
|
It("returns known error", func() {
|
||||||
|
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
|
||||||
|
// This string should match the expected error codes in the cmdDel functions of some of the plugins
|
||||||
|
_, err := ip.DelLinkByNameAddr("THIS_DONT_EXIST", netlink.FAMILY_V4)
|
||||||
|
Expect(err).To(Equal(ip.ErrLinkNotFound))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("when there is no name available for the host-side", func() {
|
Context("when there is no name available for the host-side", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
//adding different interface to container ns
|
//adding different interface to container ns
|
||||||
@ -156,7 +170,7 @@ var _ = Describe("Link", func() {
|
|||||||
|
|
||||||
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
hostVethName = hostVeth.Attrs().Name
|
hostVethName = hostVeth.Name
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
16
vendor/github.com/containernetworking/cni/pkg/types/020/types.go
generated
vendored
16
vendor/github.com/containernetworking/cni/pkg/types/020/types.go
generated
vendored
@ -23,9 +23,9 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const implementedSpecVersion string = "0.2.0"
|
const ImplementedSpecVersion string = "0.2.0"
|
||||||
|
|
||||||
var SupportedVersions = []string{"", "0.1.0", implementedSpecVersion}
|
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
|
||||||
|
|
||||||
// Compatibility types for CNI version 0.1.0 and 0.2.0
|
// Compatibility types for CNI version 0.1.0 and 0.2.0
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ func NewResult(data []byte) (types.Result, error) {
|
|||||||
|
|
||||||
func GetResult(r types.Result) (*Result, error) {
|
func GetResult(r types.Result) (*Result, error) {
|
||||||
// We expect version 0.1.0/0.2.0 results
|
// We expect version 0.1.0/0.2.0 results
|
||||||
result020, err := r.GetAsVersion(implementedSpecVersion)
|
result020, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -52,18 +52,20 @@ func GetResult(r types.Result) (*Result, error) {
|
|||||||
|
|
||||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||||
type Result struct {
|
type Result struct {
|
||||||
IP4 *IPConfig `json:"ip4,omitempty"`
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
IP6 *IPConfig `json:"ip6,omitempty"`
|
IP4 *IPConfig `json:"ip4,omitempty"`
|
||||||
DNS types.DNS `json:"dns,omitempty"`
|
IP6 *IPConfig `json:"ip6,omitempty"`
|
||||||
|
DNS types.DNS `json:"dns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) Version() string {
|
func (r *Result) Version() string {
|
||||||
return implementedSpecVersion
|
return ImplementedSpecVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||||
for _, supportedVersion := range SupportedVersions {
|
for _, supportedVersion := range SupportedVersions {
|
||||||
if version == supportedVersion {
|
if version == supportedVersion {
|
||||||
|
r.CNIVersion = version
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
vendor/github.com/containernetworking/cni/pkg/types/current/types.go
generated
vendored
25
vendor/github.com/containernetworking/cni/pkg/types/current/types.go
generated
vendored
@ -24,9 +24,9 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/types/020"
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
)
|
)
|
||||||
|
|
||||||
const implementedSpecVersion string = "0.3.0"
|
const ImplementedSpecVersion string = "0.3.1"
|
||||||
|
|
||||||
var SupportedVersions = []string{implementedSpecVersion}
|
var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
|
||||||
|
|
||||||
func NewResult(data []byte) (types.Result, error) {
|
func NewResult(data []byte) (types.Result, error) {
|
||||||
result := &Result{}
|
result := &Result{}
|
||||||
@ -37,7 +37,7 @@ func NewResult(data []byte) (types.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetResult(r types.Result) (*Result, error) {
|
func GetResult(r types.Result) (*Result, error) {
|
||||||
resultCurrent, err := r.GetAsVersion(implementedSpecVersion)
|
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -63,8 +63,9 @@ func convertFrom020(result types.Result) (*Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newResult := &Result{
|
newResult := &Result{
|
||||||
DNS: oldResult.DNS,
|
CNIVersion: ImplementedSpecVersion,
|
||||||
Routes: []*types.Route{},
|
DNS: oldResult.DNS,
|
||||||
|
Routes: []*types.Route{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldResult.IP4 != nil {
|
if oldResult.IP4 != nil {
|
||||||
@ -117,6 +118,7 @@ func convertFrom030(result types.Result) (*Result, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to convert result")
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
}
|
}
|
||||||
|
newResult.CNIVersion = ImplementedSpecVersion
|
||||||
return newResult, nil
|
return newResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,11 +131,12 @@ func NewResultFromResult(result types.Result) (*Result, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unsupported CNI result version %q", version)
|
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||||
type Result struct {
|
type Result struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
Interfaces []*Interface `json:"interfaces,omitempty"`
|
Interfaces []*Interface `json:"interfaces,omitempty"`
|
||||||
IPs []*IPConfig `json:"ips,omitempty"`
|
IPs []*IPConfig `json:"ips,omitempty"`
|
||||||
Routes []*types.Route `json:"routes,omitempty"`
|
Routes []*types.Route `json:"routes,omitempty"`
|
||||||
@ -143,7 +146,8 @@ type Result struct {
|
|||||||
// Convert to the older 0.2.0 CNI spec Result type
|
// Convert to the older 0.2.0 CNI spec Result type
|
||||||
func (r *Result) convertTo020() (*types020.Result, error) {
|
func (r *Result) convertTo020() (*types020.Result, error) {
|
||||||
oldResult := &types020.Result{
|
oldResult := &types020.Result{
|
||||||
DNS: r.DNS,
|
CNIVersion: types020.ImplementedSpecVersion,
|
||||||
|
DNS: r.DNS,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range r.IPs {
|
for _, ip := range r.IPs {
|
||||||
@ -189,17 +193,18 @@ func (r *Result) convertTo020() (*types020.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) Version() string {
|
func (r *Result) Version() string {
|
||||||
return implementedSpecVersion
|
return ImplementedSpecVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case implementedSpecVersion:
|
case "0.3.0", ImplementedSpecVersion:
|
||||||
|
r.CNIVersion = version
|
||||||
return r, nil
|
return r, nil
|
||||||
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
|
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
|
||||||
return r.convertTo020()
|
return r.convertTo020()
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot convert version 0.3.0 to %q", version)
|
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) Print() error {
|
func (r *Result) Print() error {
|
||||||
|
56
vendor/github.com/containernetworking/cni/pkg/utils/sysctl/sysctl_linux.go
generated
vendored
Normal file
56
vendor/github.com/containernetworking/cni/pkg/utils/sysctl/sysctl_linux.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2016 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 sysctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sysctl provides a method to set/get values from /proc/sys - in linux systems
|
||||||
|
// new interface to set/get values of variables formerly handled by sysctl syscall
|
||||||
|
// If optional `params` have only one string value - this function will
|
||||||
|
// set this value into corresponding sysctl variable
|
||||||
|
func Sysctl(name string, params ...string) (string, error) {
|
||||||
|
if len(params) > 1 {
|
||||||
|
return "", fmt.Errorf("unexcepted additional parameters")
|
||||||
|
} else if len(params) == 1 {
|
||||||
|
return setSysctl(name, params[0])
|
||||||
|
}
|
||||||
|
return getSysctl(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSysctl(name string) (string, error) {
|
||||||
|
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||||
|
fullName = filepath.Clean(fullName)
|
||||||
|
data, err := ioutil.ReadFile(fullName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data[:len(data)-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSysctl(name, value string) (string, error) {
|
||||||
|
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||||
|
fullName = filepath.Clean(fullName)
|
||||||
|
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSysctl(name)
|
||||||
|
}
|
41
vendor/github.com/containernetworking/cni/pkg/utils/utils.go
generated
vendored
Normal file
41
vendor/github.com/containernetworking/cni/pkg/utils/utils.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2016 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxChainLength = 28
|
||||||
|
chainPrefix = "CNI-"
|
||||||
|
prefixLength = len(chainPrefix)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generates a chain name to be used with iptables.
|
||||||
|
// Ensures that the generated chain name is exactly
|
||||||
|
// maxChainLength chars in length
|
||||||
|
func FormatChainName(name string, id string) string {
|
||||||
|
chainBytes := sha512.Sum512([]byte(name + id))
|
||||||
|
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
|
||||||
|
return chain[:maxChainLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatComment returns a comment used for easier
|
||||||
|
// rule identification within iptables.
|
||||||
|
func FormatComment(name string, id string) string {
|
||||||
|
return fmt.Sprintf("name: %q id: %q", name, id)
|
||||||
|
}
|
27
vendor/github.com/containernetworking/cni/pkg/utils/utils_suite_test.go
generated
vendored
Normal file
27
vendor/github.com/containernetworking/cni/pkg/utils/utils_suite_test.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2016 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 utils_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUtils(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Utils Suite")
|
||||||
|
}
|
51
vendor/github.com/containernetworking/cni/pkg/utils/utils_test.go
generated
vendored
Normal file
51
vendor/github.com/containernetworking/cni/pkg/utils/utils_test.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2016 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Utils", func() {
|
||||||
|
It("must format a short name", func() {
|
||||||
|
chain := FormatChainName("test", "1234")
|
||||||
|
Expect(len(chain)).To(Equal(maxChainLength))
|
||||||
|
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("must truncate a long name", func() {
|
||||||
|
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||||
|
Expect(len(chain)).To(Equal(maxChainLength))
|
||||||
|
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("must be predictable", func() {
|
||||||
|
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||||
|
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||||
|
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||||
|
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||||
|
Expect(chain1).To(Equal(chain2))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("must change when a character changes", func() {
|
||||||
|
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||||
|
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||||
|
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||||
|
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||||
|
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||||
|
Expect(chain1).NotTo(Equal(chain2))
|
||||||
|
})
|
||||||
|
})
|
4
vendor/github.com/containernetworking/cni/pkg/version/version.go
generated
vendored
4
vendor/github.com/containernetworking/cni/pkg/version/version.go
generated
vendored
@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
// Current reports the version of the CNI spec implemented by this library
|
// Current reports the version of the CNI spec implemented by this library
|
||||||
func Current() string {
|
func Current() string {
|
||||||
return "0.3.0"
|
return "0.3.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||||
@ -35,7 +35,7 @@ func Current() string {
|
|||||||
// Any future CNI spec versions which meet this definition should be added to
|
// Any future CNI spec versions which meet this definition should be added to
|
||||||
// this list.
|
// this list.
|
||||||
var Legacy = PluginSupports("0.1.0", "0.2.0")
|
var Legacy = PluginSupports("0.1.0", "0.2.0")
|
||||||
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0")
|
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
|
||||||
|
|
||||||
var resultFactories = []struct {
|
var resultFactories = []struct {
|
||||||
supportedVersions []string
|
supportedVersions []string
|
||||||
|
191
vendor/github.com/coreos/go-systemd/LICENSE
generated
vendored
Normal file
191
vendor/github.com/coreos/go-systemd/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.
|
52
vendor/github.com/coreos/go-systemd/activation/files.go
generated
vendored
Normal file
52
vendor/github.com/coreos/go-systemd/activation/files.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2015 CoreOS, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package activation implements primitives for systemd socket activation.
|
||||||
|
package activation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// based on: https://gist.github.com/alberts/4640792
|
||||||
|
const (
|
||||||
|
listenFdsStart = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func Files(unsetEnv bool) []*os.File {
|
||||||
|
if unsetEnv {
|
||||||
|
defer os.Unsetenv("LISTEN_PID")
|
||||||
|
defer os.Unsetenv("LISTEN_FDS")
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
|
||||||
|
if err != nil || pid != os.Getpid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
|
||||||
|
if err != nil || nfds == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
files := make([]*os.File, 0, nfds)
|
||||||
|
for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
|
||||||
|
syscall.CloseOnExec(fd)
|
||||||
|
files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
37
vendor/github.com/coreos/go-systemd/activation/listeners.go
generated
vendored
Normal file
37
vendor/github.com/coreos/go-systemd/activation/listeners.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2015 CoreOS, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package activation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listeners returns a slice containing a net.Listener for each matching socket type
|
||||||
|
// passed to this process.
|
||||||
|
//
|
||||||
|
// The order of the file descriptors is preserved in the returned slice.
|
||||||
|
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
|
||||||
|
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
|
||||||
|
func Listeners(unsetEnv bool) ([]net.Listener, error) {
|
||||||
|
files := Files(unsetEnv)
|
||||||
|
listeners := make([]net.Listener, len(files))
|
||||||
|
|
||||||
|
for i, f := range files {
|
||||||
|
if pc, err := net.FileListener(f); err == nil {
|
||||||
|
listeners[i] = pc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listeners, nil
|
||||||
|
}
|
37
vendor/github.com/coreos/go-systemd/activation/packetconns.go
generated
vendored
Normal file
37
vendor/github.com/coreos/go-systemd/activation/packetconns.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2015 CoreOS, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package activation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PacketConns returns a slice containing a net.PacketConn for each matching socket type
|
||||||
|
// passed to this process.
|
||||||
|
//
|
||||||
|
// The order of the file descriptors is preserved in the returned slice.
|
||||||
|
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
|
||||||
|
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
|
||||||
|
func PacketConns(unsetEnv bool) ([]net.PacketConn, error) {
|
||||||
|
files := Files(unsetEnv)
|
||||||
|
conns := make([]net.PacketConn, len(files))
|
||||||
|
|
||||||
|
for i, f := range files {
|
||||||
|
if pc, err := net.FilePacketConn(f); err == nil {
|
||||||
|
conns[i] = pc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conns, nil
|
||||||
|
}
|
27
vendor/github.com/d2g/dhcp4/LICENSE
generated
vendored
Normal file
27
vendor/github.com/d2g/dhcp4/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2013 Skagerrak Software Limited. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Skagerrak Software Limited nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
vendor/github.com/d2g/dhcp4/README.md
generated
vendored
Normal file
5
vendor/github.com/d2g/dhcp4/README.md
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# DHCP4 - A DHCP library written in Go.
|
||||||
|
|
||||||
|
Warning: This library is still being developed. Function calls will change.
|
||||||
|
|
||||||
|
I've removed Server Functionality, for me this project supports the underlying DHCP format not the implementation.
|
121
vendor/github.com/d2g/dhcp4/constants.go
generated
vendored
Normal file
121
vendor/github.com/d2g/dhcp4/constants.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package dhcp4
|
||||||
|
|
||||||
|
// OpCodes
|
||||||
|
const (
|
||||||
|
BootRequest OpCode = 1 // From Client
|
||||||
|
BootReply OpCode = 2 // From Server
|
||||||
|
)
|
||||||
|
|
||||||
|
// DHCP Message Type 53
|
||||||
|
const (
|
||||||
|
Discover MessageType = 1 // Broadcast Packet From Client - Can I have an IP?
|
||||||
|
Offer MessageType = 2 // Broadcast From Server - Here's an IP
|
||||||
|
Request MessageType = 3 // Broadcast From Client - I'll take that IP (Also start for renewals)
|
||||||
|
Decline MessageType = 4 // Broadcast From Client - Sorry I can't use that IP
|
||||||
|
ACK MessageType = 5 // From Server, Yes you can have that IP
|
||||||
|
NAK MessageType = 6 // From Server, No you cannot have that IP
|
||||||
|
Release MessageType = 7 // From Client, I don't need that IP anymore
|
||||||
|
Inform MessageType = 8 // From Client, I have this IP and there's nothing you can do about it
|
||||||
|
)
|
||||||
|
|
||||||
|
// DHCP Options
|
||||||
|
const (
|
||||||
|
End OptionCode = 255
|
||||||
|
Pad OptionCode = 0
|
||||||
|
OptionSubnetMask OptionCode = 1
|
||||||
|
OptionTimeOffset OptionCode = 2
|
||||||
|
OptionRouter OptionCode = 3
|
||||||
|
OptionTimeServer OptionCode = 4
|
||||||
|
OptionNameServer OptionCode = 5
|
||||||
|
OptionDomainNameServer OptionCode = 6
|
||||||
|
OptionLogServer OptionCode = 7
|
||||||
|
OptionCookieServer OptionCode = 8
|
||||||
|
OptionLPRServer OptionCode = 9
|
||||||
|
OptionImpressServer OptionCode = 10
|
||||||
|
OptionResourceLocationServer OptionCode = 11
|
||||||
|
OptionHostName OptionCode = 12
|
||||||
|
OptionBootFileSize OptionCode = 13
|
||||||
|
OptionMeritDumpFile OptionCode = 14
|
||||||
|
OptionDomainName OptionCode = 15
|
||||||
|
OptionSwapServer OptionCode = 16
|
||||||
|
OptionRootPath OptionCode = 17
|
||||||
|
OptionExtensionsPath OptionCode = 18
|
||||||
|
|
||||||
|
// IP Layer Parameters per Host
|
||||||
|
OptionIPForwardingEnableDisable OptionCode = 19
|
||||||
|
OptionNonLocalSourceRoutingEnableDisable OptionCode = 20
|
||||||
|
OptionPolicyFilter OptionCode = 21
|
||||||
|
OptionMaximumDatagramReassemblySize OptionCode = 22
|
||||||
|
OptionDefaultIPTimeToLive OptionCode = 23
|
||||||
|
OptionPathMTUAgingTimeout OptionCode = 24
|
||||||
|
OptionPathMTUPlateauTable OptionCode = 25
|
||||||
|
|
||||||
|
// IP Layer Parameters per Interface
|
||||||
|
OptionInterfaceMTU OptionCode = 26
|
||||||
|
OptionAllSubnetsAreLocal OptionCode = 27
|
||||||
|
OptionBroadcastAddress OptionCode = 28
|
||||||
|
OptionPerformMaskDiscovery OptionCode = 29
|
||||||
|
OptionMaskSupplier OptionCode = 30
|
||||||
|
OptionPerformRouterDiscovery OptionCode = 31
|
||||||
|
OptionRouterSolicitationAddress OptionCode = 32
|
||||||
|
OptionStaticRoute OptionCode = 33
|
||||||
|
|
||||||
|
// Link Layer Parameters per Interface
|
||||||
|
OptionTrailerEncapsulation OptionCode = 34
|
||||||
|
OptionARPCacheTimeout OptionCode = 35
|
||||||
|
OptionEthernetEncapsulation OptionCode = 36
|
||||||
|
|
||||||
|
// TCP Parameters
|
||||||
|
OptionTCPDefaultTTL OptionCode = 37
|
||||||
|
OptionTCPKeepaliveInterval OptionCode = 38
|
||||||
|
OptionTCPKeepaliveGarbage OptionCode = 39
|
||||||
|
|
||||||
|
// Application and Service Parameters
|
||||||
|
OptionNetworkInformationServiceDomain OptionCode = 40
|
||||||
|
OptionNetworkInformationServers OptionCode = 41
|
||||||
|
OptionNetworkTimeProtocolServers OptionCode = 42
|
||||||
|
OptionVendorSpecificInformation OptionCode = 43
|
||||||
|
OptionNetBIOSOverTCPIPNameServer OptionCode = 44
|
||||||
|
OptionNetBIOSOverTCPIPDatagramDistributionServer OptionCode = 45
|
||||||
|
OptionNetBIOSOverTCPIPNodeType OptionCode = 46
|
||||||
|
OptionNetBIOSOverTCPIPScope OptionCode = 47
|
||||||
|
OptionXWindowSystemFontServer OptionCode = 48
|
||||||
|
OptionXWindowSystemDisplayManager OptionCode = 49
|
||||||
|
OptionNetworkInformationServicePlusDomain OptionCode = 64
|
||||||
|
OptionNetworkInformationServicePlusServers OptionCode = 65
|
||||||
|
OptionMobileIPHomeAgent OptionCode = 68
|
||||||
|
OptionSimpleMailTransportProtocol OptionCode = 69
|
||||||
|
OptionPostOfficeProtocolServer OptionCode = 70
|
||||||
|
OptionNetworkNewsTransportProtocol OptionCode = 71
|
||||||
|
OptionDefaultWorldWideWebServer OptionCode = 72
|
||||||
|
OptionDefaultFingerServer OptionCode = 73
|
||||||
|
OptionDefaultInternetRelayChatServer OptionCode = 74
|
||||||
|
OptionStreetTalkServer OptionCode = 75
|
||||||
|
OptionStreetTalkDirectoryAssistance OptionCode = 76
|
||||||
|
|
||||||
|
// DHCP Extensions
|
||||||
|
OptionRequestedIPAddress OptionCode = 50
|
||||||
|
OptionIPAddressLeaseTime OptionCode = 51
|
||||||
|
OptionOverload OptionCode = 52
|
||||||
|
OptionDHCPMessageType OptionCode = 53
|
||||||
|
OptionServerIdentifier OptionCode = 54
|
||||||
|
OptionParameterRequestList OptionCode = 55
|
||||||
|
OptionMessage OptionCode = 56
|
||||||
|
OptionMaximumDHCPMessageSize OptionCode = 57
|
||||||
|
OptionRenewalTimeValue OptionCode = 58
|
||||||
|
OptionRebindingTimeValue OptionCode = 59
|
||||||
|
OptionVendorClassIdentifier OptionCode = 60
|
||||||
|
OptionClientIdentifier OptionCode = 61
|
||||||
|
|
||||||
|
OptionTFTPServerName OptionCode = 66
|
||||||
|
OptionBootFileName OptionCode = 67
|
||||||
|
|
||||||
|
OptionUserClass OptionCode = 77
|
||||||
|
|
||||||
|
OptionClientArchitecture OptionCode = 93
|
||||||
|
|
||||||
|
OptionTZPOSIXString OptionCode = 100
|
||||||
|
OptionTZDatabaseString OptionCode = 101
|
||||||
|
|
||||||
|
OptionClasslessRouteFormat OptionCode = 121
|
||||||
|
)
|
58
vendor/github.com/d2g/dhcp4/helpers.go
generated
vendored
Normal file
58
vendor/github.com/d2g/dhcp4/helpers.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package dhcp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPRange returns how many ips in the ip range from start to stop (inclusive)
|
||||||
|
func IPRange(start, stop net.IP) int {
|
||||||
|
//return int(Uint([]byte(stop))-Uint([]byte(start))) + 1
|
||||||
|
return int(binary.BigEndian.Uint32(stop.To4())) - int(binary.BigEndian.Uint32(start.To4())) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAdd returns a copy of start + add.
|
||||||
|
// IPAdd(net.IP{192,168,1,1},30) returns net.IP{192.168.1.31}
|
||||||
|
func IPAdd(start net.IP, add int) net.IP { // IPv4 only
|
||||||
|
start = start.To4()
|
||||||
|
//v := Uvarint([]byte(start))
|
||||||
|
result := make(net.IP, 4)
|
||||||
|
binary.BigEndian.PutUint32(result, binary.BigEndian.Uint32(start)+uint32(add))
|
||||||
|
//PutUint([]byte(result), v+uint64(add))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPLess returns where IP a is less than IP b.
|
||||||
|
func IPLess(a, b net.IP) bool {
|
||||||
|
b = b.To4()
|
||||||
|
for i, ai := range a.To4() {
|
||||||
|
if ai != b[i] {
|
||||||
|
return ai < b[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPInRange returns true if ip is between (inclusive) start and stop.
|
||||||
|
func IPInRange(start, stop, ip net.IP) bool {
|
||||||
|
return !(IPLess(ip, start) || IPLess(stop, ip))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionsLeaseTime - converts a time.Duration to a 4 byte slice, compatible
|
||||||
|
// with OptionIPAddressLeaseTime.
|
||||||
|
func OptionsLeaseTime(d time.Duration) []byte {
|
||||||
|
leaseBytes := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(leaseBytes, uint32(d/time.Second))
|
||||||
|
//PutUvarint(leaseBytes, uint64(d/time.Second))
|
||||||
|
return leaseBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinIPs returns a byte slice of IP addresses, one immediately after the other
|
||||||
|
// This may be useful for creating multiple IP options such as OptionRouter.
|
||||||
|
func JoinIPs(ips []net.IP) (b []byte) {
|
||||||
|
for _, v := range ips {
|
||||||
|
b = append(b, v.To4()...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
40
vendor/github.com/d2g/dhcp4/option.go
generated
vendored
Normal file
40
vendor/github.com/d2g/dhcp4/option.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package dhcp4
|
||||||
|
|
||||||
|
type OptionCode byte
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Code OptionCode
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of DHCP options
|
||||||
|
type Options map[OptionCode][]byte
|
||||||
|
|
||||||
|
// SelectOrderOrAll has same functionality as SelectOrder, except if the order
|
||||||
|
// param is nil, whereby all options are added (in arbitary order).
|
||||||
|
func (o Options) SelectOrderOrAll(order []byte) []Option {
|
||||||
|
if order == nil {
|
||||||
|
opts := make([]Option, 0, len(o))
|
||||||
|
for i, v := range o {
|
||||||
|
opts = append(opts, Option{Code: i, Value: v})
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
return o.SelectOrder(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectOrder returns a slice of options ordered and selected by a byte array
|
||||||
|
// usually defined by OptionParameterRequestList. This result is expected to be
|
||||||
|
// used in ReplyPacket()'s []Option parameter.
|
||||||
|
func (o Options) SelectOrder(order []byte) []Option {
|
||||||
|
opts := make([]Option, 0, len(order))
|
||||||
|
for _, v := range order {
|
||||||
|
if data, ok := o[OptionCode(v)]; ok {
|
||||||
|
opts = append(opts, Option{Code: OptionCode(v), Value: data})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpCode byte
|
||||||
|
type MessageType byte // Option 53
|
149
vendor/github.com/d2g/dhcp4/packet.go
generated
vendored
Normal file
149
vendor/github.com/d2g/dhcp4/packet.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package dhcp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A DHCP packet
|
||||||
|
type Packet []byte
|
||||||
|
|
||||||
|
func (p Packet) OpCode() OpCode { return OpCode(p[0]) }
|
||||||
|
func (p Packet) HType() byte { return p[1] }
|
||||||
|
func (p Packet) HLen() byte { return p[2] }
|
||||||
|
func (p Packet) Hops() byte { return p[3] }
|
||||||
|
func (p Packet) XId() []byte { return p[4:8] }
|
||||||
|
func (p Packet) Secs() []byte { return p[8:10] } // Never Used?
|
||||||
|
func (p Packet) Flags() []byte { return p[10:12] }
|
||||||
|
func (p Packet) CIAddr() net.IP { return net.IP(p[12:16]) }
|
||||||
|
func (p Packet) YIAddr() net.IP { return net.IP(p[16:20]) }
|
||||||
|
func (p Packet) SIAddr() net.IP { return net.IP(p[20:24]) }
|
||||||
|
func (p Packet) GIAddr() net.IP { return net.IP(p[24:28]) }
|
||||||
|
func (p Packet) CHAddr() net.HardwareAddr {
|
||||||
|
hLen := p.HLen()
|
||||||
|
if hLen > 16 { // Prevent chaddr exceeding p boundary
|
||||||
|
hLen = 16
|
||||||
|
}
|
||||||
|
return net.HardwareAddr(p[28 : 28+hLen]) // max endPos 44
|
||||||
|
}
|
||||||
|
|
||||||
|
// 192 bytes of zeros BOOTP legacy
|
||||||
|
func (p Packet) Cookie() []byte { return p[236:240] }
|
||||||
|
func (p Packet) Options() []byte {
|
||||||
|
if len(p) > 240 {
|
||||||
|
return p[240:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Packet) Broadcast() bool { return p.Flags()[0] > 127 }
|
||||||
|
|
||||||
|
func (p Packet) SetBroadcast(broadcast bool) {
|
||||||
|
if p.Broadcast() != broadcast {
|
||||||
|
p.Flags()[0] ^= 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Packet) SetOpCode(c OpCode) { p[0] = byte(c) }
|
||||||
|
func (p Packet) SetCHAddr(a net.HardwareAddr) {
|
||||||
|
copy(p[28:44], a)
|
||||||
|
p[2] = byte(len(a))
|
||||||
|
}
|
||||||
|
func (p Packet) SetHType(hType byte) { p[1] = hType }
|
||||||
|
func (p Packet) SetCookie(cookie []byte) { copy(p.Cookie(), cookie) }
|
||||||
|
func (p Packet) SetHops(hops byte) { p[3] = hops }
|
||||||
|
func (p Packet) SetXId(xId []byte) { copy(p.XId(), xId) }
|
||||||
|
func (p Packet) SetSecs(secs []byte) { copy(p.Secs(), secs) }
|
||||||
|
func (p Packet) SetFlags(flags []byte) { copy(p.Flags(), flags) }
|
||||||
|
func (p Packet) SetCIAddr(ip net.IP) { copy(p.CIAddr(), ip.To4()) }
|
||||||
|
func (p Packet) SetYIAddr(ip net.IP) { copy(p.YIAddr(), ip.To4()) }
|
||||||
|
func (p Packet) SetSIAddr(ip net.IP) { copy(p.SIAddr(), ip.To4()) }
|
||||||
|
func (p Packet) SetGIAddr(ip net.IP) { copy(p.GIAddr(), ip.To4()) }
|
||||||
|
|
||||||
|
// Parses the packet's options into an Options map
|
||||||
|
func (p Packet) ParseOptions() Options {
|
||||||
|
opts := p.Options()
|
||||||
|
options := make(Options, 10)
|
||||||
|
for len(opts) >= 2 && OptionCode(opts[0]) != End {
|
||||||
|
if OptionCode(opts[0]) == Pad {
|
||||||
|
opts = opts[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size := int(opts[1])
|
||||||
|
if len(opts) < 2+size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
options[OptionCode(opts[0])] = opts[2 : 2+size]
|
||||||
|
opts = opts[2+size:]
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacket(opCode OpCode) Packet {
|
||||||
|
p := make(Packet, 241)
|
||||||
|
p.SetOpCode(opCode)
|
||||||
|
p.SetHType(1) // Ethernet
|
||||||
|
p.SetCookie([]byte{99, 130, 83, 99})
|
||||||
|
p[240] = byte(End)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a DHCP option to the end of a packet
|
||||||
|
func (p *Packet) AddOption(o OptionCode, value []byte) {
|
||||||
|
*p = append((*p)[:len(*p)-1], []byte{byte(o), byte(len(value))}...) // Strip off End, Add OptionCode and Length
|
||||||
|
*p = append(*p, value...) // Add Option Value
|
||||||
|
*p = append(*p, byte(End)) // Add on new End
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes all options from packet.
|
||||||
|
func (p *Packet) StripOptions() {
|
||||||
|
*p = append((*p)[:240], byte(End))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a request packet that a Client would send to a server.
|
||||||
|
func RequestPacket(mt MessageType, chAddr net.HardwareAddr, cIAddr net.IP, xId []byte, broadcast bool, options []Option) Packet {
|
||||||
|
p := NewPacket(BootRequest)
|
||||||
|
p.SetCHAddr(chAddr)
|
||||||
|
p.SetXId(xId)
|
||||||
|
if cIAddr != nil {
|
||||||
|
p.SetCIAddr(cIAddr)
|
||||||
|
}
|
||||||
|
p.SetBroadcast(broadcast)
|
||||||
|
p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
|
||||||
|
for _, o := range options {
|
||||||
|
p.AddOption(o.Code, o.Value)
|
||||||
|
}
|
||||||
|
p.PadToMinSize()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyPacket creates a reply packet that a Server would send to a client.
|
||||||
|
// It uses the req Packet param to copy across common/necessary fields to
|
||||||
|
// associate the reply the request.
|
||||||
|
func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDuration time.Duration, options []Option) Packet {
|
||||||
|
p := NewPacket(BootReply)
|
||||||
|
p.SetXId(req.XId())
|
||||||
|
p.SetFlags(req.Flags())
|
||||||
|
p.SetYIAddr(yIAddr)
|
||||||
|
p.SetGIAddr(req.GIAddr())
|
||||||
|
p.SetCHAddr(req.CHAddr())
|
||||||
|
p.SetSecs(req.Secs())
|
||||||
|
p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
|
||||||
|
p.AddOption(OptionServerIdentifier, []byte(serverId))
|
||||||
|
p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration))
|
||||||
|
for _, o := range options {
|
||||||
|
p.AddOption(o.Code, o.Value)
|
||||||
|
}
|
||||||
|
p.PadToMinSize()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// PadToMinSize pads a packet so that when sent over UDP, the entire packet,
|
||||||
|
// is 300 bytes (BOOTP min), to be compatible with really old devices.
|
||||||
|
var padder [272]byte
|
||||||
|
|
||||||
|
func (p *Packet) PadToMinSize() {
|
||||||
|
if n := len(*p); n < 272 {
|
||||||
|
*p = append(*p, padder[:272-n]...)
|
||||||
|
}
|
||||||
|
}
|
354
vendor/github.com/d2g/dhcp4client/LICENSE
generated
vendored
Normal file
354
vendor/github.com/d2g/dhcp4client/LICENSE
generated
vendored
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. “Contributor”
|
||||||
|
|
||||||
|
means each individual or legal entity that creates, contributes to the
|
||||||
|
creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. “Contributor Version”
|
||||||
|
|
||||||
|
means the combination of the Contributions of others (if any) used by a
|
||||||
|
Contributor and that particular Contributor’s Contribution.
|
||||||
|
|
||||||
|
1.3. “Contribution”
|
||||||
|
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. “Covered Software”
|
||||||
|
|
||||||
|
means Source Code Form to which the initial Contributor has attached the
|
||||||
|
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||||
|
Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. “Incompatible With Secondary Licenses”
|
||||||
|
means
|
||||||
|
|
||||||
|
a. that the initial Contributor has attached the notice described in
|
||||||
|
Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
b. that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a
|
||||||
|
Secondary License.
|
||||||
|
|
||||||
|
1.6. “Executable Form”
|
||||||
|
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. “Larger Work”
|
||||||
|
|
||||||
|
means a work that combines Covered Software with other material, in a separate
|
||||||
|
file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. “License”
|
||||||
|
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. “Licensable”
|
||||||
|
|
||||||
|
means having the right to grant, to the maximum extent possible, whether at the
|
||||||
|
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||||
|
this License.
|
||||||
|
|
||||||
|
1.10. “Modifications”
|
||||||
|
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
a. any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
b. any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. “Patent Claims” of a Contributor
|
||||||
|
|
||||||
|
means any patent claim(s), including without limitation, method, process,
|
||||||
|
and apparatus claims, in any patent Licensable by such Contributor that
|
||||||
|
would be infringed, but for the grant of the License, by the making,
|
||||||
|
using, selling, offering for sale, having made, import, or transfer of
|
||||||
|
either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. “Secondary License”
|
||||||
|
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||||
|
General Public License, Version 2.1, the GNU Affero General Public
|
||||||
|
License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. “Source Code Form”
|
||||||
|
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. “You” (or “Your”)
|
||||||
|
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, “You” includes any entity that controls, is
|
||||||
|
controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, “control” means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||||
|
outstanding shares or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
a. under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or as
|
||||||
|
part of a Larger Work; and
|
||||||
|
|
||||||
|
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions
|
||||||
|
or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
a. for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
b. for infringements caused by: (i) Your and any other third party’s
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the
|
||||||
|
notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this License
|
||||||
|
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||||
|
under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the
|
||||||
|
rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under the
|
||||||
|
terms of this License. You must inform recipients that the Source Code Form
|
||||||
|
of the Covered Software is governed by the terms of this License, and how
|
||||||
|
they can obtain a copy of this License. You may not attempt to alter or
|
||||||
|
restrict the recipients’ rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
a. such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the
|
||||||
|
Executable Form how they can obtain a copy of such Source Code Form by
|
||||||
|
reasonable means in a timely manner, at a charge no more than the cost
|
||||||
|
of distribution to the recipient; and
|
||||||
|
|
||||||
|
b. You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for
|
||||||
|
the Executable Form does not attempt to limit or alter the recipients’
|
||||||
|
rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for the
|
||||||
|
Covered Software. If the Larger Work is a combination of Covered Software
|
||||||
|
with a work governed by one or more Secondary Licenses, and the Covered
|
||||||
|
Software is not Incompatible With Secondary Licenses, this License permits
|
||||||
|
You to additionally distribute such Covered Software under the terms of
|
||||||
|
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||||
|
their option, further distribute the Covered Software under the terms of
|
||||||
|
either this License or such Secondary License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered
|
||||||
|
Software, except that You may alter any license notices to the extent
|
||||||
|
required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||||
|
of any Contributor. You must make it absolutely clear that any such
|
||||||
|
warranty, support, indemnity, or liability obligation is offered by You
|
||||||
|
alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with all
|
||||||
|
distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be
|
||||||
|
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||||
|
understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if You
|
||||||
|
fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor
|
||||||
|
are reinstated (a) provisionally, unless and until such Contributor
|
||||||
|
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||||
|
if such Contributor fails to notify You of the non-compliance by some
|
||||||
|
reasonable means prior to 60 days after You have come back into compliance.
|
||||||
|
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||||
|
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||||
|
some reasonable means, this is the first time You have received notice of
|
||||||
|
non-compliance with this License from such Contributor, and You become
|
||||||
|
compliant prior to 30 days after Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||||
|
and cross-claims) alleging that a Contributor Version directly or
|
||||||
|
indirectly infringes any patent, then the rights granted to You by any and
|
||||||
|
all Contributors for the Covered Software under Section 2.1 of this License
|
||||||
|
shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||||
|
license agreements (excluding distributors and resellers) which have been
|
||||||
|
validly granted by You or Your distributors under this License prior to
|
||||||
|
termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an “as is” basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||||
|
risk as to the quality and performance of the Covered Software is with You.
|
||||||
|
Should any Covered Software prove defective in any respect, You (not any
|
||||||
|
Contributor) assume the cost of any necessary servicing, repair, or
|
||||||
|
correction. This disclaimer of warranty constitutes an essential part of this
|
||||||
|
License. No use of any Covered Software is authorized under this License
|
||||||
|
except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any
|
||||||
|
character including, without limitation, damages for lost profits, loss of
|
||||||
|
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses, even if such party shall have been
|
||||||
|
informed of the possibility of such damages. This limitation of liability
|
||||||
|
shall not apply to liability for death or personal injury resulting from such
|
||||||
|
party’s negligence to the extent applicable law prohibits such limitation.
|
||||||
|
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||||
|
consequential damages, so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts of
|
||||||
|
a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it
|
||||||
|
enforceable. Any law or regulation which provides that the language of a
|
||||||
|
contract shall be construed against the drafter shall not be used to construe
|
||||||
|
this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or
|
||||||
|
under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a modified
|
||||||
|
version of this License if you rename the license and remove any
|
||||||
|
references to the name of the license steward (except to note that such
|
||||||
|
modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file, then
|
||||||
|
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||||
|
directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||||
|
|
||||||
|
This Source Code Form is “Incompatible
|
||||||
|
With Secondary Licenses”, as defined by
|
||||||
|
the Mozilla Public License, v. 2.0.
|
||||||
|
|
8
vendor/github.com/d2g/dhcp4client/README.md
generated
vendored
Normal file
8
vendor/github.com/d2g/dhcp4client/README.md
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
dhcp4client [](http://godoc.org/github.com/d2g/dhcp4client) [](https://coveralls.io/r/d2g/dhcp4client?branch=HEAD) [](https://codeship.com/projects/70187)
|
||||||
|
===========
|
||||||
|
|
||||||
|
DHCP Client
|
||||||
|
|
||||||
|
|
||||||
|
###### Thanks to:
|
||||||
|
@eyakubovich For AF_PACKET support.
|
366
vendor/github.com/d2g/dhcp4client/client.go
generated
vendored
Normal file
366
vendor/github.com/d2g/dhcp4client/client.go
generated
vendored
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
package dhcp4client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/d2g/dhcp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxDHCPLen = 576
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
hardwareAddr net.HardwareAddr //The HardwareAddr to send in the request.
|
||||||
|
ignoreServers []net.IP //List of Servers to Ignore requests from.
|
||||||
|
timeout time.Duration //Time before we timeout.
|
||||||
|
broadcast bool //Set the Bcast flag in BOOTP Flags
|
||||||
|
connection connection //The Connection Method to use
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Abstracts the type of underlying socket used
|
||||||
|
*/
|
||||||
|
type connection interface {
|
||||||
|
Close() error
|
||||||
|
Write(packet []byte) error
|
||||||
|
ReadFrom() ([]byte, net.IP, error)
|
||||||
|
SetReadTimeout(t time.Duration) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(options ...func(*Client) error) (*Client, error) {
|
||||||
|
c := Client{
|
||||||
|
timeout: time.Second * 10,
|
||||||
|
broadcast: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.SetOption(options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//if connection hasn't been set as an option create the default.
|
||||||
|
if c.connection == nil {
|
||||||
|
conn, err := NewInetSock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.connection = conn
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetOption(options ...func(*Client) error) error {
|
||||||
|
for _, opt := range options {
|
||||||
|
if err := opt(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Timeout(t time.Duration) func(*Client) error {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.timeout = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IgnoreServers(s []net.IP) func(*Client) error {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.ignoreServers = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HardwareAddr(h net.HardwareAddr) func(*Client) error {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.hardwareAddr = h
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Broadcast(b bool) func(*Client) error {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.broadcast = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connection(conn connection) func(*Client) error {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.connection = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close Connections
|
||||||
|
*/
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
if c.connection != nil {
|
||||||
|
return c.connection.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send the Discovery Packet to the Broadcast Channel
|
||||||
|
*/
|
||||||
|
func (c *Client) SendDiscoverPacket() (dhcp4.Packet, error) {
|
||||||
|
discoveryPacket := c.DiscoverPacket()
|
||||||
|
discoveryPacket.PadToMinSize()
|
||||||
|
|
||||||
|
return discoveryPacket, c.SendPacket(discoveryPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retreive Offer...
|
||||||
|
* Wait for the offer for a specific Discovery Packet.
|
||||||
|
*/
|
||||||
|
func (c *Client) GetOffer(discoverPacket *dhcp4.Packet) (dhcp4.Packet, error) {
|
||||||
|
for {
|
||||||
|
c.connection.SetReadTimeout(c.timeout)
|
||||||
|
readBuffer, source, err := c.connection.ReadFrom()
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offerPacket := dhcp4.Packet(readBuffer)
|
||||||
|
offerPacketOptions := offerPacket.ParseOptions()
|
||||||
|
|
||||||
|
// Ignore Servers in my Ignore list
|
||||||
|
for _, ignoreServer := range c.ignoreServers {
|
||||||
|
if source.Equal(ignoreServer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if offerPacket.SIAddr().Equal(ignoreServer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(offerPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || dhcp4.MessageType(offerPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.Offer || !bytes.Equal(discoverPacket.XId(), offerPacket.XId()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return offerPacket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send Request Based On the offer Received.
|
||||||
|
*/
|
||||||
|
func (c *Client) SendRequest(offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
|
||||||
|
requestPacket := c.RequestPacket(offerPacket)
|
||||||
|
requestPacket.PadToMinSize()
|
||||||
|
|
||||||
|
return requestPacket, c.SendPacket(requestPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retreive Acknowledgement
|
||||||
|
* Wait for the offer for a specific Request Packet.
|
||||||
|
*/
|
||||||
|
func (c *Client) GetAcknowledgement(requestPacket *dhcp4.Packet) (dhcp4.Packet, error) {
|
||||||
|
for {
|
||||||
|
c.connection.SetReadTimeout(c.timeout)
|
||||||
|
readBuffer, source, err := c.connection.ReadFrom()
|
||||||
|
if err != nil {
|
||||||
|
return dhcp4.Packet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acknowledgementPacket := dhcp4.Packet(readBuffer)
|
||||||
|
acknowledgementPacketOptions := acknowledgementPacket.ParseOptions()
|
||||||
|
|
||||||
|
// Ignore Servers in my Ignore list
|
||||||
|
for _, ignoreServer := range c.ignoreServers {
|
||||||
|
if source.Equal(ignoreServer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if acknowledgementPacket.SIAddr().Equal(ignoreServer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(requestPacket.XId(), acknowledgementPacket.XId()) || len(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || (dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK && dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.NAK) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return acknowledgementPacket, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send a DHCP Packet.
|
||||||
|
*/
|
||||||
|
func (c *Client) SendPacket(packet dhcp4.Packet) error {
|
||||||
|
return c.connection.Write(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create Discover Packet
|
||||||
|
*/
|
||||||
|
func (c *Client) DiscoverPacket() dhcp4.Packet {
|
||||||
|
messageid := make([]byte, 4)
|
||||||
|
if _, err := rand.Read(messageid); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := dhcp4.NewPacket(dhcp4.BootRequest)
|
||||||
|
packet.SetCHAddr(c.hardwareAddr)
|
||||||
|
packet.SetXId(messageid)
|
||||||
|
packet.SetBroadcast(c.broadcast)
|
||||||
|
|
||||||
|
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Discover)})
|
||||||
|
//packet.PadToMinSize()
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create Request Packet
|
||||||
|
*/
|
||||||
|
func (c *Client) RequestPacket(offerPacket *dhcp4.Packet) dhcp4.Packet {
|
||||||
|
offerOptions := offerPacket.ParseOptions()
|
||||||
|
|
||||||
|
packet := dhcp4.NewPacket(dhcp4.BootRequest)
|
||||||
|
packet.SetCHAddr(c.hardwareAddr)
|
||||||
|
|
||||||
|
packet.SetXId(offerPacket.XId())
|
||||||
|
packet.SetCIAddr(offerPacket.CIAddr())
|
||||||
|
packet.SetSIAddr(offerPacket.SIAddr())
|
||||||
|
|
||||||
|
packet.SetBroadcast(c.broadcast)
|
||||||
|
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
|
||||||
|
packet.AddOption(dhcp4.OptionRequestedIPAddress, (offerPacket.YIAddr()).To4())
|
||||||
|
packet.AddOption(dhcp4.OptionServerIdentifier, offerOptions[dhcp4.OptionServerIdentifier])
|
||||||
|
|
||||||
|
//packet.PadToMinSize()
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create Request Packet For a Renew
|
||||||
|
*/
|
||||||
|
func (c *Client) RenewalRequestPacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
|
||||||
|
messageid := make([]byte, 4)
|
||||||
|
if _, err := rand.Read(messageid); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
|
||||||
|
packet := dhcp4.NewPacket(dhcp4.BootRequest)
|
||||||
|
packet.SetCHAddr(acknowledgement.CHAddr())
|
||||||
|
|
||||||
|
packet.SetXId(messageid)
|
||||||
|
packet.SetCIAddr(acknowledgement.YIAddr())
|
||||||
|
packet.SetSIAddr(acknowledgement.SIAddr())
|
||||||
|
|
||||||
|
packet.SetBroadcast(c.broadcast)
|
||||||
|
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
|
||||||
|
packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4())
|
||||||
|
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
|
||||||
|
|
||||||
|
//packet.PadToMinSize()
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create Release Packet For a Release
|
||||||
|
*/
|
||||||
|
func (c *Client) ReleasePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
|
||||||
|
messageid := make([]byte, 4)
|
||||||
|
if _, err := rand.Read(messageid); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
|
||||||
|
packet := dhcp4.NewPacket(dhcp4.BootRequest)
|
||||||
|
packet.SetCHAddr(acknowledgement.CHAddr())
|
||||||
|
|
||||||
|
packet.SetXId(messageid)
|
||||||
|
packet.SetCIAddr(acknowledgement.YIAddr())
|
||||||
|
|
||||||
|
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Release)})
|
||||||
|
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
|
||||||
|
|
||||||
|
//packet.PadToMinSize()
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lets do a Full DHCP Request.
|
||||||
|
*/
|
||||||
|
func (c *Client) Request() (bool, dhcp4.Packet, error) {
|
||||||
|
discoveryPacket, err := c.SendDiscoverPacket()
|
||||||
|
if err != nil {
|
||||||
|
return false, discoveryPacket, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offerPacket, err := c.GetOffer(&discoveryPacket)
|
||||||
|
if err != nil {
|
||||||
|
return false, offerPacket, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPacket, err := c.SendRequest(&offerPacket)
|
||||||
|
if err != nil {
|
||||||
|
return false, requestPacket, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
|
||||||
|
if err != nil {
|
||||||
|
return false, acknowledgement, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||||
|
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||||
|
return false, acknowledgement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, acknowledgement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Renew a lease backed on the Acknowledgement Packet.
|
||||||
|
* Returns Sucessfull, The AcknoledgementPacket, Any Errors
|
||||||
|
*/
|
||||||
|
func (c *Client) Renew(acknowledgement dhcp4.Packet) (bool, dhcp4.Packet, error) {
|
||||||
|
renewRequest := c.RenewalRequestPacket(&acknowledgement)
|
||||||
|
renewRequest.PadToMinSize()
|
||||||
|
|
||||||
|
err := c.SendPacket(renewRequest)
|
||||||
|
if err != nil {
|
||||||
|
return false, renewRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
|
||||||
|
if err != nil {
|
||||||
|
return false, newAcknowledgement, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
|
||||||
|
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||||
|
return false, newAcknowledgement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, newAcknowledgement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Release a lease backed on the Acknowledgement Packet.
|
||||||
|
* Returns Any Errors
|
||||||
|
*/
|
||||||
|
func (c *Client) Release(acknowledgement dhcp4.Packet) error {
|
||||||
|
release := c.ReleasePacket(&acknowledgement)
|
||||||
|
release.PadToMinSize()
|
||||||
|
|
||||||
|
return c.SendPacket(release)
|
||||||
|
}
|
75
vendor/github.com/d2g/dhcp4client/inetsock.go
generated
vendored
Normal file
75
vendor/github.com/d2g/dhcp4client/inetsock.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package dhcp4client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inetSock struct {
|
||||||
|
*net.UDPConn
|
||||||
|
|
||||||
|
laddr net.UDPAddr
|
||||||
|
raddr net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInetSock(options ...func(*inetSock) error) (*inetSock, error) {
|
||||||
|
c := &inetSock{
|
||||||
|
laddr: net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 68},
|
||||||
|
raddr: net.UDPAddr{IP: net.IPv4bcast, Port: 67},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.setOption(options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenUDP("udp4", &c.laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UDPConn = conn
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *inetSock) setOption(options ...func(*inetSock) error) error {
|
||||||
|
for _, opt := range options {
|
||||||
|
if err := opt(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLocalAddr(l net.UDPAddr) func(*inetSock) error {
|
||||||
|
return func(c *inetSock) error {
|
||||||
|
c.laddr = l
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRemoteAddr(r net.UDPAddr) func(*inetSock) error {
|
||||||
|
return func(c *inetSock) error {
|
||||||
|
c.raddr = r
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *inetSock) Write(packet []byte) error {
|
||||||
|
_, err := c.WriteToUDP(packet, &c.raddr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *inetSock) ReadFrom() ([]byte, net.IP, error) {
|
||||||
|
readBuffer := make([]byte, MaxDHCPLen)
|
||||||
|
n, source, err := c.ReadFromUDP(readBuffer)
|
||||||
|
if source != nil {
|
||||||
|
return readBuffer[:n], source.IP, err
|
||||||
|
} else {
|
||||||
|
return readBuffer[:n], net.IP{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *inetSock) SetReadTimeout(t time.Duration) error {
|
||||||
|
return c.SetReadDeadline(time.Now().Add(t))
|
||||||
|
}
|
10
vendor/github.com/d2g/dhcp4client/init.go
generated
vendored
Normal file
10
vendor/github.com/d2g/dhcp4client/init.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package dhcp4client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
}
|
147
vendor/github.com/d2g/dhcp4client/pktsock_linux.go
generated
vendored
Normal file
147
vendor/github.com/d2g/dhcp4client/pktsock_linux.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package dhcp4client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minIPHdrLen = 20
|
||||||
|
maxIPHdrLen = 60
|
||||||
|
udpHdrLen = 8
|
||||||
|
ip4Ver = 0x40
|
||||||
|
ttl = 16
|
||||||
|
srcPort = 68
|
||||||
|
dstPort = 67
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bcastMAC = []byte{255, 255, 255, 255, 255, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
// abstracts AF_PACKET
|
||||||
|
type packetSock struct {
|
||||||
|
fd int
|
||||||
|
ifindex int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketSock(ifindex int) (*packetSock, error) {
|
||||||
|
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_DGRAM, int(swap16(unix.ETH_P_IP)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := unix.SockaddrLinklayer{
|
||||||
|
Ifindex: ifindex,
|
||||||
|
Protocol: swap16(unix.ETH_P_IP),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = unix.Bind(fd, &addr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &packetSock{
|
||||||
|
fd: fd,
|
||||||
|
ifindex: ifindex,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetSock) Close() error {
|
||||||
|
return unix.Close(pc.fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetSock) Write(packet []byte) error {
|
||||||
|
lladdr := unix.SockaddrLinklayer{
|
||||||
|
Ifindex: pc.ifindex,
|
||||||
|
Protocol: swap16(unix.ETH_P_IP),
|
||||||
|
Halen: uint8(len(bcastMAC)),
|
||||||
|
}
|
||||||
|
copy(lladdr.Addr[:], bcastMAC)
|
||||||
|
|
||||||
|
pkt := make([]byte, minIPHdrLen+udpHdrLen+len(packet))
|
||||||
|
|
||||||
|
fillIPHdr(pkt[0:minIPHdrLen], udpHdrLen+uint16(len(packet)))
|
||||||
|
fillUDPHdr(pkt[minIPHdrLen:minIPHdrLen+udpHdrLen], uint16(len(packet)))
|
||||||
|
|
||||||
|
// payload
|
||||||
|
copy(pkt[minIPHdrLen+udpHdrLen:len(pkt)], packet)
|
||||||
|
|
||||||
|
return unix.Sendto(pc.fd, pkt, 0, &lladdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetSock) ReadFrom() ([]byte, net.IP, error) {
|
||||||
|
pkt := make([]byte, maxIPHdrLen+udpHdrLen+MaxDHCPLen)
|
||||||
|
n, _, err := unix.Recvfrom(pc.fd, pkt, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP hdr len
|
||||||
|
ihl := int(pkt[0]&0x0F) * 4
|
||||||
|
// Source IP address
|
||||||
|
src := net.IP(pkt[12:16])
|
||||||
|
|
||||||
|
return pkt[ihl+udpHdrLen : n], src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetSock) SetReadTimeout(t time.Duration) error {
|
||||||
|
|
||||||
|
tv := unix.NsecToTimeval(t.Nanoseconds())
|
||||||
|
return unix.SetsockoptTimeval(pc.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute's 1's complement checksum
|
||||||
|
func chksum(p []byte, csum []byte) {
|
||||||
|
cklen := len(p)
|
||||||
|
s := uint32(0)
|
||||||
|
for i := 0; i < (cklen - 1); i += 2 {
|
||||||
|
s += uint32(p[i+1])<<8 | uint32(p[i])
|
||||||
|
}
|
||||||
|
if cklen&1 == 1 {
|
||||||
|
s += uint32(p[cklen-1])
|
||||||
|
}
|
||||||
|
s = (s >> 16) + (s & 0xffff)
|
||||||
|
s = s + (s >> 16)
|
||||||
|
s = ^s
|
||||||
|
|
||||||
|
csum[0] = uint8(s & 0xff)
|
||||||
|
csum[1] = uint8(s >> 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillIPHdr(hdr []byte, payloadLen uint16) {
|
||||||
|
// version + IHL
|
||||||
|
hdr[0] = ip4Ver | (minIPHdrLen / 4)
|
||||||
|
// total length
|
||||||
|
binary.BigEndian.PutUint16(hdr[2:4], uint16(len(hdr))+payloadLen)
|
||||||
|
// identification
|
||||||
|
if _, err := rand.Read(hdr[4:5]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// TTL
|
||||||
|
hdr[8] = 16
|
||||||
|
// Protocol
|
||||||
|
hdr[9] = unix.IPPROTO_UDP
|
||||||
|
// dst IP
|
||||||
|
copy(hdr[16:20], net.IPv4bcast.To4())
|
||||||
|
// compute IP hdr checksum
|
||||||
|
chksum(hdr[0:len(hdr)], hdr[10:12])
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillUDPHdr(hdr []byte, payloadLen uint16) {
|
||||||
|
// src port
|
||||||
|
binary.BigEndian.PutUint16(hdr[0:2], srcPort)
|
||||||
|
// dest port
|
||||||
|
binary.BigEndian.PutUint16(hdr[2:4], dstPort)
|
||||||
|
// length
|
||||||
|
binary.BigEndian.PutUint16(hdr[4:6], udpHdrLen+payloadLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func swap16(x uint16) uint16 {
|
||||||
|
var b [2]byte
|
||||||
|
binary.BigEndian.PutUint16(b[:], x)
|
||||||
|
return binary.LittleEndian.Uint16(b[:])
|
||||||
|
}
|
229
vendor/github.com/onsi/gomega/gbytes/buffer.go
generated
vendored
Normal file
229
vendor/github.com/onsi/gomega/gbytes/buffer.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
Package gbytes provides a buffer that supports incrementally detecting input.
|
||||||
|
|
||||||
|
You use gbytes.Buffer with the gbytes.Say matcher. When Say finds a match, it fastforwards the buffer's read cursor to the end of that match.
|
||||||
|
|
||||||
|
Subsequent matches against the buffer will only operate against data that appears *after* the read cursor.
|
||||||
|
|
||||||
|
The read cursor is an opaque implementation detail that you cannot access. You should use the Say matcher to sift through the buffer. You can always
|
||||||
|
access the entire buffer's contents with Contents().
|
||||||
|
|
||||||
|
*/
|
||||||
|
package gbytes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
gbytes.Buffer implements an io.Writer and can be used with the gbytes.Say matcher.
|
||||||
|
|
||||||
|
You should only use a gbytes.Buffer in test code. It stores all writes in an in-memory buffer - behavior that is inappropriate for production code!
|
||||||
|
*/
|
||||||
|
type Buffer struct {
|
||||||
|
contents []byte
|
||||||
|
readCursor uint64
|
||||||
|
lock *sync.Mutex
|
||||||
|
detectCloser chan interface{}
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewBuffer returns a new gbytes.Buffer
|
||||||
|
*/
|
||||||
|
func NewBuffer() *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BufferWithBytes returns a new gbytes.Buffer seeded with the passed in bytes
|
||||||
|
*/
|
||||||
|
func BufferWithBytes(bytes []byte) *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
contents: bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write implements the io.Writer interface
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
if b.closed {
|
||||||
|
return 0, errors.New("attempt to write to closed buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.contents = append(b.contents, p...)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Read implements the io.Reader interface. It advances the
|
||||||
|
cursor as it reads.
|
||||||
|
|
||||||
|
Returns an error if called after Close.
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Read(d []byte) (int, error) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
if b.closed {
|
||||||
|
return 0, errors.New("attempt to read from closed buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(len(b.contents)) <= b.readCursor {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(d, b.contents[b.readCursor:])
|
||||||
|
b.readCursor += uint64(n)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Close signifies that the buffer will no longer be written to
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Close() error {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
b.closed = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Closed returns true if the buffer has been closed
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Closed() bool {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
return b.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Contents returns all data ever written to the buffer.
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Contents() []byte {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
contents := make([]byte, len(b.contents))
|
||||||
|
copy(contents, b.contents)
|
||||||
|
return contents
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Detect takes a regular expression and returns a channel.
|
||||||
|
|
||||||
|
The channel will receive true the first time data matching the regular expression is written to the buffer.
|
||||||
|
The channel is subsequently closed and the buffer's read-cursor is fast-forwarded to just after the matching region.
|
||||||
|
|
||||||
|
You typically don't need to use Detect and should use the ghttp.Say matcher instead. Detect is useful, however, in cases where your code must
|
||||||
|
be branch and handle different outputs written to the buffer.
|
||||||
|
|
||||||
|
For example, consider a buffer hooked up to the stdout of a client library. You may (or may not, depending on state outside of your control) need to authenticate the client library.
|
||||||
|
|
||||||
|
You could do something like:
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-buffer.Detect("You are not logged in"):
|
||||||
|
//log in
|
||||||
|
case <-buffer.Detect("Success"):
|
||||||
|
//carry on
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
//welp
|
||||||
|
}
|
||||||
|
buffer.CancelDetects()
|
||||||
|
|
||||||
|
You should always call CancelDetects after using Detect. This will close any channels that have not detected and clean up the goroutines that were spawned to support them.
|
||||||
|
|
||||||
|
Finally, you can pass detect a format string followed by variadic arguments. This will construct the regexp using fmt.Sprintf.
|
||||||
|
*/
|
||||||
|
func (b *Buffer) Detect(desired string, args ...interface{}) chan bool {
|
||||||
|
formattedRegexp := desired
|
||||||
|
if len(args) > 0 {
|
||||||
|
formattedRegexp = fmt.Sprintf(desired, args...)
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(formattedRegexp)
|
||||||
|
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
if b.detectCloser == nil {
|
||||||
|
b.detectCloser = make(chan interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
closer := b.detectCloser
|
||||||
|
response := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(10 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
defer close(response)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
b.lock.Lock()
|
||||||
|
data, cursor := b.contents[b.readCursor:], b.readCursor
|
||||||
|
loc := re.FindIndex(data)
|
||||||
|
b.lock.Unlock()
|
||||||
|
|
||||||
|
if loc != nil {
|
||||||
|
response <- true
|
||||||
|
b.lock.Lock()
|
||||||
|
newCursorPosition := cursor + uint64(loc[1])
|
||||||
|
if newCursorPosition >= b.readCursor {
|
||||||
|
b.readCursor = newCursorPosition
|
||||||
|
}
|
||||||
|
b.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-closer:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CancelDetects cancels any pending detects and cleans up their goroutines. You should always call this when you're done with a set of Detect channels.
|
||||||
|
*/
|
||||||
|
func (b *Buffer) CancelDetects() {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
close(b.detectCloser)
|
||||||
|
b.detectCloser = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) didSay(re *regexp.Regexp) (bool, []byte) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
unreadBytes := b.contents[b.readCursor:]
|
||||||
|
copyOfUnreadBytes := make([]byte, len(unreadBytes))
|
||||||
|
copy(copyOfUnreadBytes, unreadBytes)
|
||||||
|
|
||||||
|
loc := re.FindIndex(unreadBytes)
|
||||||
|
|
||||||
|
if loc != nil {
|
||||||
|
b.readCursor += uint64(loc[1])
|
||||||
|
return true, copyOfUnreadBytes
|
||||||
|
} else {
|
||||||
|
return false, copyOfUnreadBytes
|
||||||
|
}
|
||||||
|
}
|
105
vendor/github.com/onsi/gomega/gbytes/say_matcher.go
generated
vendored
Normal file
105
vendor/github.com/onsi/gomega/gbytes/say_matcher.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package gbytes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Objects satisfying the BufferProvider can be used with the Say matcher.
|
||||||
|
type BufferProvider interface {
|
||||||
|
Buffer() *Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Say is a Gomega matcher that operates on gbytes.Buffers:
|
||||||
|
|
||||||
|
Ω(buffer).Should(Say("something"))
|
||||||
|
|
||||||
|
will succeed if the unread portion of the buffer matches the regular expression "something".
|
||||||
|
|
||||||
|
When Say succeeds, it fast forwards the gbytes.Buffer's read cursor to just after the succesful match.
|
||||||
|
Thus, subsequent calls to Say will only match against the unread portion of the buffer
|
||||||
|
|
||||||
|
Say pairs very well with Eventually. To asser that a buffer eventually receives data matching "[123]-star" within 3 seconds you can:
|
||||||
|
|
||||||
|
Eventually(buffer, 3).Should(Say("[123]-star"))
|
||||||
|
|
||||||
|
Ditto with consistently. To assert that a buffer does not receive data matching "never-see-this" for 1 second you can:
|
||||||
|
|
||||||
|
Consistently(buffer, 1).ShouldNot(Say("never-see-this"))
|
||||||
|
|
||||||
|
In addition to bytes.Buffers, Say can operate on objects that implement the gbytes.BufferProvider interface.
|
||||||
|
In such cases, Say simply operates on the *gbytes.Buffer returned by Buffer()
|
||||||
|
|
||||||
|
If the buffer is closed, the Say matcher will tell Eventually to abort.
|
||||||
|
*/
|
||||||
|
func Say(expected string, args ...interface{}) *sayMatcher {
|
||||||
|
formattedRegexp := expected
|
||||||
|
if len(args) > 0 {
|
||||||
|
formattedRegexp = fmt.Sprintf(expected, args...)
|
||||||
|
}
|
||||||
|
return &sayMatcher{
|
||||||
|
re: regexp.MustCompile(formattedRegexp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sayMatcher struct {
|
||||||
|
re *regexp.Regexp
|
||||||
|
receivedSayings []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sayMatcher) buffer(actual interface{}) (*Buffer, bool) {
|
||||||
|
var buffer *Buffer
|
||||||
|
|
||||||
|
switch x := actual.(type) {
|
||||||
|
case *Buffer:
|
||||||
|
buffer = x
|
||||||
|
case BufferProvider:
|
||||||
|
buffer = x.Buffer()
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sayMatcher) Match(actual interface{}) (success bool, err error) {
|
||||||
|
buffer, ok := m.buffer(actual)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("Say must be passed a *gbytes.Buffer or BufferProvider. Got:\n%s", format.Object(actual, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
didSay, sayings := buffer.didSay(m.re)
|
||||||
|
m.receivedSayings = sayings
|
||||||
|
|
||||||
|
return didSay, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sayMatcher) FailureMessage(actual interface{}) (message string) {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Got stuck at:\n%s\nWaiting for:\n%s",
|
||||||
|
format.IndentString(string(m.receivedSayings), 1),
|
||||||
|
format.IndentString(m.re.String(), 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sayMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Saw:\n%s\nWhich matches the unexpected:\n%s",
|
||||||
|
format.IndentString(string(m.receivedSayings), 1),
|
||||||
|
format.IndentString(m.re.String(), 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sayMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
|
||||||
|
switch x := actual.(type) {
|
||||||
|
case *Buffer:
|
||||||
|
return !x.Closed()
|
||||||
|
case BufferProvider:
|
||||||
|
return !x.Buffer().Closed()
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
78
vendor/github.com/onsi/gomega/gexec/build.go
generated
vendored
Normal file
78
vendor/github.com/onsi/gomega/gexec/build.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package gexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tmpDir string
|
||||||
|
|
||||||
|
/*
|
||||||
|
Build uses go build to compile the package at packagePath. The resulting binary is saved off in a temporary directory.
|
||||||
|
A path pointing to this binary is returned.
|
||||||
|
|
||||||
|
Build uses the $GOPATH set in your environment. It passes the variadic args on to `go build`.
|
||||||
|
*/
|
||||||
|
func Build(packagePath string, args ...string) (compiledPath string, err error) {
|
||||||
|
return BuildIn(os.Getenv("GOPATH"), packagePath, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument).
|
||||||
|
*/
|
||||||
|
func BuildIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
|
||||||
|
tmpDir, err := temporaryDirectory()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gopath) == 0 {
|
||||||
|
return "", errors.New("$GOPATH not provided when building " + packagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
executable := filepath.Join(tmpDir, path.Base(packagePath))
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
executable = executable + ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdArgs := append([]string{"build"}, args...)
|
||||||
|
cmdArgs = append(cmdArgs, "-o", executable, packagePath)
|
||||||
|
|
||||||
|
build := exec.Command("go", cmdArgs...)
|
||||||
|
build.Env = append([]string{"GOPATH=" + gopath}, os.Environ()...)
|
||||||
|
|
||||||
|
output, err := build.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
return executable, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
You should call CleanupBuildArtifacts before your test ends to clean up any temporary artifacts generated by
|
||||||
|
gexec. In Ginkgo this is typically done in an AfterSuite callback.
|
||||||
|
*/
|
||||||
|
func CleanupBuildArtifacts() {
|
||||||
|
if tmpDir != "" {
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func temporaryDirectory() (string, error) {
|
||||||
|
var err error
|
||||||
|
if tmpDir == "" {
|
||||||
|
tmpDir, err = ioutil.TempDir("", "gexec_artifacts")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.TempDir(tmpDir, "g")
|
||||||
|
}
|
88
vendor/github.com/onsi/gomega/gexec/exit_matcher.go
generated
vendored
Normal file
88
vendor/github.com/onsi/gomega/gexec/exit_matcher.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package gexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The Exit matcher operates on a session:
|
||||||
|
|
||||||
|
Ω(session).Should(Exit(<optional status code>))
|
||||||
|
|
||||||
|
Exit passes if the session has already exited.
|
||||||
|
|
||||||
|
If no status code is provided, then Exit will succeed if the session has exited regardless of exit code.
|
||||||
|
Otherwise, Exit will only succeed if the process has exited with the provided status code.
|
||||||
|
|
||||||
|
Note that the process must have already exited. To wait for a process to exit, use Eventually:
|
||||||
|
|
||||||
|
Eventually(session, 3).Should(Exit(0))
|
||||||
|
*/
|
||||||
|
func Exit(optionalExitCode ...int) *exitMatcher {
|
||||||
|
exitCode := -1
|
||||||
|
if len(optionalExitCode) > 0 {
|
||||||
|
exitCode = optionalExitCode[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &exitMatcher{
|
||||||
|
exitCode: exitCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exitMatcher struct {
|
||||||
|
exitCode int
|
||||||
|
didExit bool
|
||||||
|
actualExitCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Exiter interface {
|
||||||
|
ExitCode() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *exitMatcher) Match(actual interface{}) (success bool, err error) {
|
||||||
|
exiter, ok := actual.(Exiter)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("Exit must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n%s", format.Object(actual, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
m.actualExitCode = exiter.ExitCode()
|
||||||
|
|
||||||
|
if m.actualExitCode == -1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.exitCode == -1 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return m.exitCode == m.actualExitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *exitMatcher) FailureMessage(actual interface{}) (message string) {
|
||||||
|
if m.actualExitCode == -1 {
|
||||||
|
return "Expected process to exit. It did not."
|
||||||
|
} else {
|
||||||
|
return format.Message(m.actualExitCode, "to match exit code:", m.exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *exitMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||||
|
if m.actualExitCode == -1 {
|
||||||
|
return "you really shouldn't be able to see this!"
|
||||||
|
} else {
|
||||||
|
if m.exitCode == -1 {
|
||||||
|
return "Expected process not to exit. It did."
|
||||||
|
} else {
|
||||||
|
return format.Message(m.actualExitCode, "not to match exit code:", m.exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *exitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
|
||||||
|
session, ok := actual.(*Session)
|
||||||
|
if ok {
|
||||||
|
return session.ExitCode() == -1
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
53
vendor/github.com/onsi/gomega/gexec/prefixed_writer.go
generated
vendored
Normal file
53
vendor/github.com/onsi/gomega/gexec/prefixed_writer.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package gexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
PrefixedWriter wraps an io.Writer, emiting the passed in prefix at the beginning of each new line.
|
||||||
|
This can be useful when running multiple gexec.Sessions concurrently - you can prefix the log output of each
|
||||||
|
session by passing in a PrefixedWriter:
|
||||||
|
|
||||||
|
gexec.Start(cmd, NewPrefixedWriter("[my-cmd] ", GinkgoWriter), NewPrefixedWriter("[my-cmd] ", GinkgoWriter))
|
||||||
|
*/
|
||||||
|
type PrefixedWriter struct {
|
||||||
|
prefix []byte
|
||||||
|
writer io.Writer
|
||||||
|
lock *sync.Mutex
|
||||||
|
atStartOfLine bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrefixedWriter(prefix string, writer io.Writer) *PrefixedWriter {
|
||||||
|
return &PrefixedWriter{
|
||||||
|
prefix: []byte(prefix),
|
||||||
|
writer: writer,
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
atStartOfLine: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *PrefixedWriter) Write(b []byte) (int, error) {
|
||||||
|
w.lock.Lock()
|
||||||
|
defer w.lock.Unlock()
|
||||||
|
|
||||||
|
toWrite := []byte{}
|
||||||
|
|
||||||
|
for _, c := range b {
|
||||||
|
if w.atStartOfLine {
|
||||||
|
toWrite = append(toWrite, w.prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
toWrite = append(toWrite, c)
|
||||||
|
|
||||||
|
w.atStartOfLine = c == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.writer.Write(toWrite)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(b), nil
|
||||||
|
}
|
214
vendor/github.com/onsi/gomega/gexec/session.go
generated
vendored
Normal file
214
vendor/github.com/onsi/gomega/gexec/session.go
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
Package gexec provides support for testing external processes.
|
||||||
|
*/
|
||||||
|
package gexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const INVALID_EXIT_CODE = 254
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
//The wrapped command
|
||||||
|
Command *exec.Cmd
|
||||||
|
|
||||||
|
//A *gbytes.Buffer connected to the command's stdout
|
||||||
|
Out *gbytes.Buffer
|
||||||
|
|
||||||
|
//A *gbytes.Buffer connected to the command's stderr
|
||||||
|
Err *gbytes.Buffer
|
||||||
|
|
||||||
|
//A channel that will close when the command exits
|
||||||
|
Exited <-chan struct{}
|
||||||
|
|
||||||
|
lock *sync.Mutex
|
||||||
|
exitCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Start starts the passed-in *exec.Cmd command. It wraps the command in a *gexec.Session.
|
||||||
|
|
||||||
|
The session pipes the command's stdout and stderr to two *gbytes.Buffers available as properties on the session: session.Out and session.Err.
|
||||||
|
These buffers can be used with the gbytes.Say matcher to match against unread output:
|
||||||
|
|
||||||
|
Ω(session.Out).Should(gbytes.Say("foo-out"))
|
||||||
|
Ω(session.Err).Should(gbytes.Say("foo-err"))
|
||||||
|
|
||||||
|
In addition, Session satisfies the gbytes.BufferProvider interface and provides the stdout *gbytes.Buffer. This allows you to replace the first line, above, with:
|
||||||
|
|
||||||
|
Ω(session).Should(gbytes.Say("foo-out"))
|
||||||
|
|
||||||
|
When outWriter and/or errWriter are non-nil, the session will pipe stdout and/or stderr output both into the session *gybtes.Buffers and to the passed-in outWriter/errWriter.
|
||||||
|
This is useful for capturing the process's output or logging it to screen. In particular, when using Ginkgo it can be convenient to direct output to the GinkgoWriter:
|
||||||
|
|
||||||
|
session, err := Start(command, GinkgoWriter, GinkgoWriter)
|
||||||
|
|
||||||
|
This will log output when running tests in verbose mode, but - otherwise - will only log output when a test fails.
|
||||||
|
|
||||||
|
The session wrapper is responsible for waiting on the *exec.Cmd command. You *should not* call command.Wait() yourself.
|
||||||
|
Instead, to assert that the command has exited you can use the gexec.Exit matcher:
|
||||||
|
|
||||||
|
Ω(session).Should(gexec.Exit())
|
||||||
|
|
||||||
|
When the session exits it closes the stdout and stderr gbytes buffers. This will short circuit any
|
||||||
|
Eventuallys waiting fo the buffers to Say something.
|
||||||
|
*/
|
||||||
|
func Start(command *exec.Cmd, outWriter io.Writer, errWriter io.Writer) (*Session, error) {
|
||||||
|
exited := make(chan struct{})
|
||||||
|
|
||||||
|
session := &Session{
|
||||||
|
Command: command,
|
||||||
|
Out: gbytes.NewBuffer(),
|
||||||
|
Err: gbytes.NewBuffer(),
|
||||||
|
Exited: exited,
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
exitCode: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandOut, commandErr io.Writer
|
||||||
|
|
||||||
|
commandOut, commandErr = session.Out, session.Err
|
||||||
|
|
||||||
|
if outWriter != nil && !reflect.ValueOf(outWriter).IsNil() {
|
||||||
|
commandOut = io.MultiWriter(commandOut, outWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errWriter != nil && !reflect.ValueOf(errWriter).IsNil() {
|
||||||
|
commandErr = io.MultiWriter(commandErr, errWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Stdout = commandOut
|
||||||
|
command.Stderr = commandErr
|
||||||
|
|
||||||
|
err := command.Start()
|
||||||
|
if err == nil {
|
||||||
|
go session.monitorForExit(exited)
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Buffer implements the gbytes.BufferProvider interface and returns s.Out
|
||||||
|
This allows you to make gbytes.Say matcher assertions against stdout without having to reference .Out:
|
||||||
|
|
||||||
|
Eventually(session).Should(gbytes.Say("foo"))
|
||||||
|
*/
|
||||||
|
func (s *Session) Buffer() *gbytes.Buffer {
|
||||||
|
return s.Out
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ExitCode returns the wrapped command's exit code. If the command hasn't exited yet, ExitCode returns -1.
|
||||||
|
|
||||||
|
To assert that the command has exited it is more convenient to use the Exit matcher:
|
||||||
|
|
||||||
|
Eventually(s).Should(gexec.Exit())
|
||||||
|
|
||||||
|
When the process exits because it has received a particular signal, the exit code will be 128+signal-value
|
||||||
|
(See http://www.tldp.org/LDP/abs/html/exitcodes.html and http://man7.org/linux/man-pages/man7/signal.7.html)
|
||||||
|
|
||||||
|
*/
|
||||||
|
func (s *Session) ExitCode() int {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
return s.exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Wait waits until the wrapped command exits. It can be passed an optional timeout.
|
||||||
|
If the command does not exit within the timeout, Wait will trigger a test failure.
|
||||||
|
|
||||||
|
Wait returns the session, making it possible to chain:
|
||||||
|
|
||||||
|
session.Wait().Out.Contents()
|
||||||
|
|
||||||
|
will wait for the command to exit then return the entirety of Out's contents.
|
||||||
|
|
||||||
|
Wait uses eventually under the hood and accepts the same timeout/polling intervals that eventually does.
|
||||||
|
*/
|
||||||
|
func (s *Session) Wait(timeout ...interface{}) *Session {
|
||||||
|
EventuallyWithOffset(1, s, timeout...).Should(Exit())
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Kill sends the running command a SIGKILL signal. It does not wait for the process to exit.
|
||||||
|
|
||||||
|
If the command has already exited, Kill returns silently.
|
||||||
|
|
||||||
|
The session is returned to enable chaining.
|
||||||
|
*/
|
||||||
|
func (s *Session) Kill() *Session {
|
||||||
|
if s.ExitCode() != -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s.Command.Process.Kill()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Interrupt sends the running command a SIGINT signal. It does not wait for the process to exit.
|
||||||
|
|
||||||
|
If the command has already exited, Interrupt returns silently.
|
||||||
|
|
||||||
|
The session is returned to enable chaining.
|
||||||
|
*/
|
||||||
|
func (s *Session) Interrupt() *Session {
|
||||||
|
return s.Signal(syscall.SIGINT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Terminate sends the running command a SIGTERM signal. It does not wait for the process to exit.
|
||||||
|
|
||||||
|
If the command has already exited, Terminate returns silently.
|
||||||
|
|
||||||
|
The session is returned to enable chaining.
|
||||||
|
*/
|
||||||
|
func (s *Session) Terminate() *Session {
|
||||||
|
return s.Signal(syscall.SIGTERM)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Terminate sends the running command the passed in signal. It does not wait for the process to exit.
|
||||||
|
|
||||||
|
If the command has already exited, Signal returns silently.
|
||||||
|
|
||||||
|
The session is returned to enable chaining.
|
||||||
|
*/
|
||||||
|
func (s *Session) Signal(signal os.Signal) *Session {
|
||||||
|
if s.ExitCode() != -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s.Command.Process.Signal(signal)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) monitorForExit(exited chan<- struct{}) {
|
||||||
|
err := s.Command.Wait()
|
||||||
|
s.lock.Lock()
|
||||||
|
s.Out.Close()
|
||||||
|
s.Err.Close()
|
||||||
|
status := s.Command.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
|
if status.Signaled() {
|
||||||
|
s.exitCode = 128 + int(status.Signal())
|
||||||
|
} else {
|
||||||
|
exitStatus := status.ExitStatus()
|
||||||
|
if exitStatus == -1 && err != nil {
|
||||||
|
s.exitCode = INVALID_EXIT_CODE
|
||||||
|
}
|
||||||
|
s.exitCode = exitStatus
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
|
close(exited)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user