Merge branch 'master' into noErrorEndpointNotFound

This commit is contained in:
nagiesek 2019-04-17 23:04:04 -07:00 committed by GitHub
commit 635968aaff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
561 changed files with 78062 additions and 13614 deletions

View File

@ -9,6 +9,7 @@ go:
env:
global:
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
- CGO_ENABLED=0
matrix:
- TARGET=amd64
- TARGET=arm
@ -29,6 +30,9 @@ matrix:
install:
- go get github.com/onsi/ginkgo/ginkgo
- go get github.com/containernetworking/cni/cnitool
- go get golang.org/x/tools/cmd/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
script:
- |

61
Godeps/Godeps.json generated
View File

@ -1,7 +1,7 @@
{
"ImportPath": "github.com/containernetworking/plugins",
"GoVersion": "go1.7",
"GodepVersion": "v79",
"GodepVersion": "v80",
"Packages": [
"./..."
],
@ -14,7 +14,7 @@
{
"ImportPath": "github.com/Microsoft/hcsshim",
"Comment": "v0.7.6",
"Rev": "6efef912cc0ecd8778bab95d105662d4f73f8ccd"
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/guid",
@ -81,38 +81,38 @@
},
{
"ImportPath": "github.com/containernetworking/cni/libcni",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/version",
"Comment": "v0.7.0-alpha1",
"Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491"
"Comment": "v0.7.0-rc2",
"Rev": "fbb95fff8a5239a4295c991efa8a397d43118f7e"
},
{
"ImportPath": "github.com/coreos/go-iptables/iptables",
@ -144,6 +144,11 @@
"ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "v4.1.0-6-g885f9cc",
"Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5"
},
{
"ImportPath": "github.com/j-keck/arping",
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
@ -318,39 +323,45 @@
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Rev": "6e453822d85ef5721799774b654d4d02fed62afb"
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netlink/nl",
"Rev": "6e453822d85ef5721799774b654d4d02fed62afb"
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netns",
"Rev": "54f0e4339ce73702a0607f49922aaa1e749b418d"
"Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd"
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "94eea52f7b742c7cbe0b03b22f0c4c8631ece122"
"Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
},
{
"ImportPath": "golang.org/x/net/bpf",
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/ipv4",
"Rev": "e90d6d0afc4c315a0d87a568ae68577cc15149a0"
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "d5840adf789d732bc8b00f37b26ca956a7cc8e79"
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/sys/windows",
"Rev": "d5840adf789d732bc8b00f37b26ca956a7cc8e79"
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/net/internal/socket",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
}
]
}

View File

@ -28,6 +28,7 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
* `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container.
* `bandwidth`: Allows bandwidth-limiting through use of traffic control tbf (ingress/egress).
* `sbr`: A plugin that configures source based routing for an interface (from which it is chained).
* `firewall`: A firewall plugin which uses iptables or firewalld to add rules to allow traffic to/from the container.
### Sample
The sample plugin provides an example for building your own plugin.

View File

@ -20,7 +20,7 @@ export GO="${GO:-go}"
mkdir -p "${PWD}/bin"
echo "Building plugins ${GOOS}"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d")"

120
pkg/ip/utils_linux.go Normal file
View File

@ -0,0 +1,120 @@
// +build linux
// 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 ip
import (
"fmt"
"net"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/vishvananda/netlink"
)
func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
// Ensure ips
for _, ips := range resultIPs {
ourAddr := netlink.Addr{IPNet: &ips.Address}
match := false
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", ifName)
}
addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return fmt.Errorf("Cannot obtain List of IP Addresses")
}
for _, addr := range addrList {
if addr.Equal(ourAddr) {
match = true
break
}
}
if match == false {
return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
}
// Convert the host/prefixlen to just prefix for route lookup.
_, ourPrefix, err := net.ParseCIDR(ourAddr.String())
findGwy := &netlink.Route{Dst: ourPrefix}
routeFilter := netlink.RT_FILTER_DST
var family int
switch {
case ips.Version == "4":
family = netlink.FAMILY_V4
case ips.Version == "6":
family = netlink.FAMILY_V6
default:
return fmt.Errorf("Invalid IP Version %v for interface %v", ips.Version, ifName)
}
gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter)
if err != nil {
return fmt.Errorf("Error %v trying to find Gateway %v for interface %v", err, ips.Gateway, ifName)
}
if gwy == nil {
return fmt.Errorf("Failed to find Gateway %v for interface %v", ips.Gateway, ifName)
}
}
return nil
}
func ValidateExpectedRoute(resultRoutes []*types.Route) error {
// Ensure that each static route in prevResults is found in the routing table
for _, route := range resultRoutes {
find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
var family int
switch {
case route.Dst.IP.To4() != nil:
family = netlink.FAMILY_V4
// Default route needs Dst set to nil
if route.Dst.String() == "0.0.0.0/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
case len(route.Dst.IP) == net.IPv6len:
family = netlink.FAMILY_V6
// Default route needs Dst set to nil
if route.Dst.String() == "::/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
default:
return fmt.Errorf("Invalid static route found %v", route)
}
wasFound, err := netlink.RouteListFiltered(family, find, routeFilter)
if err != nil {
return fmt.Errorf("Expected Route %v not route table lookup error %v", route, err)
}
if wasFound == nil {
return fmt.Errorf("Expected Route %v not found in routing table", route)
}
}
return nil
}

View File

@ -24,6 +24,10 @@ func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
return invoke.DelegateAdd(context.TODO(), plugin, netconf, nil)
}
func ExecCheck(plugin string, netconf []byte) error {
return invoke.DelegateCheck(context.TODO(), plugin, netconf, nil)
}
func ExecDel(plugin string, netconf []byte) error {
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
}

View File

@ -81,6 +81,21 @@ func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, e
return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
}
func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
os.Setenv("CNI_COMMAND", "CHECK")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
defer envCleanup()
return f()
}
func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
}
func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_PATH", os.Getenv("PATH"))

View File

@ -0,0 +1,26 @@
// Copyright 2019 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.
// Buildversion is a destination for the linker trickery so we can auto
// set the build-version
package buildversion
import "fmt"
// This is overridden in the linker script
var BuildVersion = "version unknown"
func BuildString(pluginName string) string {
return fmt.Sprintf("CNI %s plugin %s", pluginName, BuildVersion)
}

View File

@ -18,7 +18,7 @@ $ ./dhcp daemon
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
its PID to the given file.
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for netns as `<prefix>/<original netns>`. It could be used in case of running dhcp daemon as container.
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for DHCP socket as `<prefix>/run/cni/dhcp.sock`. You can use this prefix for references to the host filesystem, e.g. to access netns and the unix socket.
Alternatively, you can use systemd socket activation protocol.
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.

View File

@ -172,7 +172,7 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
}
}
l, err := getListener(socketPath)
l, err := getListener(hostPrefix + socketPath)
if err != nil {
return fmt.Errorf("Error getting listener: %v", err)
}

View File

@ -130,7 +130,7 @@ func (l *DHCPLease) acquire() error {
opts := make(dhcp4.Options)
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter)}
opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter), byte(dhcp4.OptionSubnetMask)}
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRequest(c, opts)

View File

@ -27,6 +27,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const defaultSocketPath = "/run/cni/dhcp.sock"
@ -38,7 +39,7 @@ func main() {
var socketPath string
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to netns")
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
daemonFlags.Parse(os.Args[2:])
@ -51,8 +52,7 @@ func main() {
os.Exit(1)
}
} else {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp"))
}
}
@ -80,9 +80,23 @@ func cmdDel(args *skel.CmdArgs) error {
return nil
}
func cmdGet(args *skel.CmdArgs) error {
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
//return fmt.Errorf("not implemented")
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
//confVersion, err := versionDecoder.Decode(args.StdinData)
_, err := versionDecoder.Decode(args.StdinData)
if err != nil {
return err
}
result := &current.Result{}
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
return err
}
return nil
}
type SocketPathConf struct {

View File

@ -98,7 +98,7 @@ func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
}
routes = append(routes, rt)
opt = opt[octets+5 : len(opt)]
opt = opt[octets+5:]
}
}
return routes

View File

@ -24,14 +24,14 @@ import (
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),

View File

@ -0,0 +1,11 @@
[Unit]
Description=CNI DHCP service
Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp
After=network.target cni-dhcp.socket
Requires=cni-dhcp.socket
[Service]
ExecStart=/opt/cni/bin/dhcp daemon
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,14 @@
[Unit]
Description=CNI DHCP service socket
Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp
PartOf=cni-dhcp.service
[Socket]
ListenStream=/run/cni/dhcp.sock
SocketMode=0660
SocketUser=root
SocketGroup=root
RemoveOnStop=true
[Install]
WantedBy=sockets.target

View File

@ -97,7 +97,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
n.IPAM.IPArgs = append(n.IPAM.IPArgs, n.Args.A.IPs...)
}
for idx, _ := range n.IPAM.IPArgs {
for idx := range n.IPAM.IPArgs {
if err := canonicalizeIP(&n.IPAM.IPArgs[idx]); err != nil {
return nil, "", fmt.Errorf("cannot understand ip: %v", err)
}
@ -122,7 +122,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
// Validate all ranges
numV4 := 0
numV6 := 0
for i, _ := range n.IPAM.Ranges {
for i := range n.IPAM.Ranges {
if err := n.IPAM.Ranges[i].Canonicalize(); err != nil {
return nil, "", fmt.Errorf("invalid range set %d: %s", i, err)
}

View File

@ -45,7 +45,7 @@ var _ = Describe("IPAM config", func() {
Name: "mynet",
Type: "host-local",
Ranges: []RangeSet{
RangeSet{
{
{
RangeStart: net.IP{10, 1, 2, 9},
RangeEnd: net.IP{10, 1, 2, 20},

View File

@ -61,7 +61,7 @@ func (s *RangeSet) Canonicalize() error {
}
fam := 0
for i, _ := range *s {
for i := range *s {
if err := (*s)[i].Canonicalize(); err != nil {
return err
}

View File

@ -98,6 +98,43 @@ func (s *Store) Release(ip net.IP) error {
return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
}
func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) {
found := false
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)) == match {
found = true
}
return nil
})
return found, err
}
func (s *Store) FindByID(id string, ifname string) bool {
s.Lock()
defer s.Unlock()
found := false
match := strings.TrimSpace(id) + LineBreak + ifname
found, err := s.FindByKey(id, ifname, match)
// Match anything created by this id
if !found && err == nil {
match := strings.TrimSpace(id)
found, err = s.FindByKey(id, ifname, match)
}
return found
}
func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) {
found := false
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {

View File

@ -15,10 +15,12 @@
package main
import (
"encoding/json"
"fmt"
"net"
"strings"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
@ -29,13 +31,38 @@ import (
)
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
n := &types.NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
return n, n.CNIVersion, nil
}
func cmdCheck(args *skel.CmdArgs) error {
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}
// Look to see if there is at least one IP address allocated to the container
// in the data dir, irrespective of what that address actually is
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
if err != nil {
return err
}
defer store.Close()
containerIpFound := store.FindByID(args.ContainerID, args.IfName)
if containerIpFound == false {
return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID)
}
return nil
}
func cmdAdd(args *skel.CmdArgs) error {

View File

@ -22,10 +22,10 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/pkg/types/020"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// The top-level network config - IPAM plugins are passed the full configuration
@ -58,13 +58,59 @@ type Address struct {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
n := &types.NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
return n, n.CNIVersion, nil
}
func cmdCheck(args *skel.CmdArgs) error {
ipamConf, _, err := LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}
// Get PrevResult from stdin... store in RawPrevResult
n, _, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(n); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
// Each configured IP should be found in result.IPs
for _, rangeset := range ipamConf.Addresses {
for _, ips := range result.IPs {
// Ensure values are what we expect
if rangeset.Address.IP.Equal(ips.Address.IP) {
if rangeset.Gateway == nil {
break
} else if rangeset.Gateway.Equal(ips.Gateway) {
break
}
return fmt.Errorf("static: Failed to match addr %v on interface %v", ips.Address.IP, args.IfName)
}
}
}
return nil
}
// canonicalizeIP makes sure a provided ip is in standard form

12
plugins/linux_only.txt Normal file
View File

@ -0,0 +1,12 @@
plugins/ipam/dhcp
plugins/main/bridge
plugins/main/host-device
plugins/main/ipvlan
plugins/main/loopback
plugins/main/macvlan
plugins/main/ptp
plugins/main/vlan
plugins/meta/portmap
plugins/meta/tuning
plugins/meta/bandwidth
plugins/meta/firewall

View File

@ -52,3 +52,8 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary.
* `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false.
* `vlan` (int, optional): assign VLAN tag. Defaults to none.
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.

View File

@ -18,12 +18,14 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"runtime"
"syscall"
"io/ioutil"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -33,8 +35,7 @@ import (
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// For testcases to force an error after IPAM has been performed
@ -52,6 +53,7 @@ type NetConf struct {
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
PromiscMode bool `json:"promiscMode"`
Vlan int `json:"vlan"`
}
type gwInfo struct {
@ -144,7 +146,7 @@ func calcGateways(result *current.Result, n *NetConf) (*gwInfo, *gwInfo, error)
return gwsV4, gwsV6, nil
}
func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddress bool) error {
func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool) error {
addrs, err := netlink.AddrList(br, family)
if err != nil && err != syscall.ENOENT {
return fmt.Errorf("could not get list of IP addresses: %v", err)
@ -164,34 +166,34 @@ func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddre
// forceAddress is true, otherwise throw an error.
if family == netlink.FAMILY_V4 || a.IPNet.Contains(ipn.IP) || ipn.Contains(a.IPNet.IP) {
if forceAddress {
if err = deleteBridgeAddr(br, a.IPNet); err != nil {
if err = deleteAddr(br, a.IPNet); err != nil {
return err
}
} else {
return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipnStr)
return fmt.Errorf("%q already has an IP address different from %v", br.Attrs().Name, ipnStr)
}
}
}
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 fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
}
// Set the bridge's MAC to itself. Otherwise, the bridge will take the
// lowest-numbered mac on the bridge, and will change as ifs churn
if err := netlink.LinkSetHardwareAddr(br, br.HardwareAddr); err != nil {
if err := netlink.LinkSetHardwareAddr(br, br.Attrs().HardwareAddr); err != nil {
return fmt.Errorf("could not set bridge's mac: %v", err)
}
return nil
}
func deleteBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
func deleteAddr(br netlink.Link, ipn *net.IPNet) error {
addr := &netlink.Addr{IPNet: ipn, Label: ""}
if err := netlink.AddrDel(br, addr); err != nil {
return fmt.Errorf("could not remove IP address from %q: %v", br.Name, err)
return fmt.Errorf("could not remove IP address from %q: %v", br.Attrs().Name, err)
}
return nil
@ -209,7 +211,7 @@ func bridgeByName(name string) (*netlink.Bridge, error) {
return br, nil
}
func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, error) {
func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
br := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: brName,
@ -220,6 +222,7 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er
// default packet limit
TxQLen: -1,
},
VlanFiltering: &vlanFiltering,
}
err := netlink.LinkAdd(br)
@ -247,7 +250,35 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er
return br, nil
}
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error) {
func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) {
name := fmt.Sprintf("%s.%d", br.Name, vlanId)
brGatewayVeth, err := netlink.LinkByName(name)
if err != nil {
if err.Error() != "Link not found" {
return nil, fmt.Errorf("failed to find interface %q: %v", name, err)
}
hostNS, err := ns.GetCurrentNS()
if err != nil {
return nil, fmt.Errorf("faild to find host namespace: %v", err)
}
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanId)
if err != nil {
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
}
brGatewayVeth, err = netlink.LinkByName(brGatewayIface.Name)
if err != nil {
return nil, fmt.Errorf("failed to lookup %q: %v", brGatewayIface.Name, err)
}
}
return brGatewayVeth, nil
}
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int) (*current.Interface, *current.Interface, error) {
contIface := &current.Interface{}
hostIface := &current.Interface{}
@ -284,6 +315,13 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
}
if vlanID != 0 {
err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true)
if err != nil {
return nil, nil, fmt.Errorf("failed to setup vlan tag on interface %q: %v", hostIface.Name, err)
}
}
return hostIface, contIface, nil
}
@ -293,8 +331,12 @@ func calcGatewayIP(ipn *net.IPNet) net.IP {
}
func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
vlanFiltering := false
if n.Vlan != 0 {
vlanFiltering = true
}
// create bridge if necessary
br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode)
br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode, vlanFiltering)
if err != nil {
return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
}
@ -355,7 +397,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer netns.Close()
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan)
if err != nil {
return err
}
@ -435,18 +477,36 @@ func cmdAdd(args *skel.CmdArgs) error {
if n.IsGW {
var firstV4Addr net.IP
var vlanInterface *current.Interface
// Set the IP address(es) on the bridge and enable forwarding
for _, gws := range []*gwInfo{gwsV4, gwsV6} {
for _, gw := range gws.gws {
if gw.IP.To4() != nil && firstV4Addr == nil {
firstV4Addr = gw.IP
}
if n.Vlan != 0 {
vlanIface, err := ensureVlanInterface(br, n.Vlan)
if err != nil {
return fmt.Errorf("failed to create vlan interface: %v", err)
}
err = ensureBridgeAddr(br, gws.family, &gw, n.ForceAddress)
if vlanInterface == nil {
vlanInterface = &current.Interface{Name: vlanIface.Attrs().Name,
Mac: vlanIface.Attrs().HardwareAddr.String()}
result.Interfaces = append(result.Interfaces, vlanInterface)
}
err = ensureAddr(vlanIface, gws.family, &gw, n.ForceAddress)
if err != nil {
return fmt.Errorf("failed to set vlan interface for bridge with addr: %v", err)
}
} else {
err = ensureAddr(br, gws.family, &gw, n.ForceAddress)
if err != nil {
return fmt.Errorf("failed to set bridge addr: %v", err)
}
}
}
if gws.gws != nil {
if err = enableIPForward(gws.family); err != nil {
@ -536,11 +596,269 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
type cniBridgeIf struct {
Name string
ifIndex int
peerIndex int
masterIndex int
found bool
}
func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) {
ifFound := cniBridgeIf{found: false}
if intf.Name == "" {
return ifFound, nil, fmt.Errorf("Interface name missing ")
}
link, err := netlink.LinkByName(intf.Name)
if err != nil {
return ifFound, nil, fmt.Errorf("Interface name %s not found", intf.Name)
}
if expectInSb {
if intf.Sandbox == "" {
return ifFound, nil, fmt.Errorf("Interface %s is expected to be in a sandbox", intf.Name)
}
} else {
if intf.Sandbox != "" {
return ifFound, nil, fmt.Errorf("Interface %s should not be in sandbox", intf.Name)
}
}
return ifFound, link, err
}
func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) {
brFound, link, err := validateInterface(intf, false)
if err != nil {
return brFound, err
}
_, isBridge := link.(*netlink.Bridge)
if !isBridge {
return brFound, fmt.Errorf("Interface %s does not have link type of bridge", intf.Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return brFound, fmt.Errorf("Bridge interface %s Mac doesn't match: %s", intf.Name, intf.Mac)
}
}
linkPromisc := link.Attrs().Promisc != 0
if linkPromisc != n.PromiscMode {
return brFound, fmt.Errorf("Bridge interface %s configured Promisc Mode %v doesn't match current state: %v ",
intf.Name, n.PromiscMode, linkPromisc)
}
brFound.found = true
brFound.Name = link.Attrs().Name
brFound.ifIndex = link.Attrs().Index
brFound.masterIndex = link.Attrs().MasterIndex
return brFound, nil
}
func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(*intf, false)
if err != nil {
return vethFound, err
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
// just skip it, it's not what CNI created
return vethFound, nil
}
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
if err != nil {
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
}
vethFound.ifIndex = link.Attrs().Index
vethFound.masterIndex = link.Attrs().MasterIndex
if vethFound.ifIndex != contIf.peerIndex {
return vethFound, nil
}
if contIf.ifIndex != vethFound.peerIndex {
return vethFound, nil
}
if vethFound.masterIndex != brIf.ifIndex {
return vethFound, nil
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return vethFound, fmt.Errorf("Interface %s Mac doesn't match: %s not found", intf.Name, intf.Mac)
}
}
vethFound.found = true
vethFound.Name = link.Attrs().Name
return vethFound, nil
}
func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(intf, true)
if err != nil {
return vethFound, err
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
return vethFound, fmt.Errorf("Error: Container interface %s not of type veth", link.Attrs().Name)
}
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
if err != nil {
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
}
vethFound.ifIndex = link.Attrs().Index
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return vethFound, fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
vethFound.found = true
vethFound.Name = link.Attrs().Name
return vethFound, nil
}
func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadNetConf(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()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var errLink error
var contCNI, vethCNI cniBridgeIf
var brMap, contMap current.Interface
// Find interfaces for names whe know, CNI Bridge and container
for _, intf := range result.Interfaces {
if n.BrName == intf.Name {
brMap = *intf
continue
} else if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
brCNI, err := validateCniBrInterface(brMap, n)
if err != nil {
return err
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
// Check interface against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
contCNI, errLink = validateCniContainerInterface(contMap)
if errLink != nil {
return errLink
}
return nil
}); err != nil {
return err
}
// Now look for veth that is peer with container interface.
// Anything else wasn't created by CNI, skip it
for _, intf := range result.Interfaces {
// Skip this result if name is the same as cni bridge
// It's either the cni bridge we dealt with above, or something with the
// same name in a different namespace. We just skip since it's not ours
if brMap.Name == intf.Name {
continue
}
// same here for container name
if contMap.Name == intf.Name {
continue
}
vethCNI, errLink = validateCniVethInterface(intf, brCNI, contCNI)
if errLink != nil {
return errLink
}
if vethCNI.found {
// veth with container interface as peer and bridge as master found
break
}
}
if !brCNI.found {
return fmt.Errorf("CNI created bridge %s in host namespace was not found", n.BrName)
}
if !contCNI.found {
return fmt.Errorf("CNI created interface in container %s not found", args.IfName)
}
if !vethCNI.found {
return fmt.Errorf("CNI veth created for bridge %s was not found", n.BrName)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,9 @@
package main
import (
"encoding/json"
"fmt"
"github.com/vishvananda/netlink/nl"
"io/ioutil"
"net"
"os"
@ -30,15 +32,33 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
BRNAME = "bridge0"
BRNAMEVLAN = "bridge0.100"
IFNAME = "eth0"
)
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
BrName string `json:"bridge"`
IPAM *allocator.IPAMConfig `json:"ipam"`
//RuntimeConfig struct { // The capability arg
// IPRanges []RangeSet `json:"ipRanges,omitempty"`
//} `json:"runtimeConfig,omitempty"`
//Args *struct {
// A *IPAMArgs `json:"cni"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
// testCase defines the CNI network configuration and the expected
// bridge addresses for a test case.
type testCase struct {
@ -49,6 +69,7 @@ type testCase struct {
isGW bool
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form
vlan int
}
// Range definition for each entry in the ranges list
@ -78,9 +99,12 @@ const (
"cniVersion": "%s",
"name": "testConfig",
"type": "bridge",
"bridge": "%s",`
"bridge": "%s"`
netDefault = `
vlan = `,
"vlan": %d`
netDefault = `,
"isDefaultGateway": true,
"ipMasq": false`
@ -120,6 +144,10 @@ const (
// for a test case.
func (tc testCase) netConfJSON(dataDir string) string {
conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME)
if tc.vlan != 0 {
conf += fmt.Sprintf(vlan, tc.vlan)
}
if !tc.isLayer2 {
conf += netDefault
if tc.subnet != "" || tc.ranges != nil {
@ -136,7 +164,7 @@ func (tc testCase) netConfJSON(dataDir string) string {
conf += ipamEndStr
}
} else {
conf += `
conf += `,
"ipam": {}`
}
return "{" + conf + "\n}"
@ -171,7 +199,24 @@ var counter uint
// arguments for a test case.
func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs {
conf := tc.netConfJSON(dataDir)
defer func() { counter += 1 }()
//defer func() { counter += 1 }()
return &skel.CmdArgs{
ContainerID: fmt.Sprintf("dummy-%d", counter),
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
}
// createCheckCmdArgs generates network configuration and creates command
// arguments for a Check test case.
func (tc testCase) createCheckCmdArgs(targetNS ns.NetNS, config *Net, dataDir string) *skel.CmdArgs {
conf, err := json.Marshal(config)
Expect(err).NotTo(HaveOccurred())
// TODO Don't we need to use the same counter as before?
//defer func() { counter += 1 }()
return &skel.CmdArgs{
ContainerID: fmt.Sprintf("dummy-%d", counter),
Netns: targetNS.Path(),
@ -218,6 +263,38 @@ func delBridgeAddrs(testNS ns.NetNS) {
}
}
br, err = netlink.LinkByName(BRNAMEVLAN)
if err == nil {
addrs, err = netlink.AddrList(br, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
err = netlink.AddrDel(br, &addr)
Expect(err).NotTo(HaveOccurred())
}
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func delVlanAddrs(testNS ns.NetNS, vlan int) {
err := testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
vlanLink, err := netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, vlan))
Expect(err).NotTo(HaveOccurred())
addrs, err := netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
err = netlink.AddrDel(vlanLink, &addr)
Expect(err).NotTo(HaveOccurred())
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
@ -248,14 +325,27 @@ func countIPAMIPs(path string) (int, error) {
return count, nil
}
func checkVlan(vlanId int, bridgeVlanInfo []*nl.BridgeVlanInfo) bool {
for _, vlan := range bridgeVlanInfo {
if vlan.Vid == uint16(vlanId) {
return true
}
}
return false
}
type cmdAddDelTester interface {
setNS(testNS ns.NetNS, targetNS ns.NetNS)
cmdAddTest(tc testCase, dataDir string)
cmdDelTest(tc testCase)
cmdAddTest(tc testCase, dataDir string) (*current.Result, error)
cmdCheckTest(tc testCase, conf *Net, dataDir string)
cmdDelTest(tc testCase, dataDir string)
}
func testerByVersion(version string) cmdAddDelTester {
switch {
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
case strings.HasPrefix(version, "0.3."):
return &testerV03x{}
default:
@ -263,19 +353,19 @@ func testerByVersion(version string) cmdAddDelTester {
}
}
type testerV03x struct {
type testerV04x struct {
testNS ns.NetNS
targetNS ns.NetNS
args *skel.CmdArgs
vethName string
}
func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
func (tester *testerV04x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.testNS = testNS
tester.targetNS = targetNS
}
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
@ -345,10 +435,83 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
// this check is not relevant for a layer 2 bridge
if !tc.isLayer2 {
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.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{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
foundAddrs++
}
}
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
// Ensure the default route(s)
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound4, defaultRouteFound6 bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
var found *bool
if ipVersion(gwIP) == "4" {
found = &defaultRouteFound4
} else {
found = &defaultRouteFound6
}
if *found == true {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
}
Expect(*found).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
return result, nil
}
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
// Generate network config and command arguments
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf, dataDir)
// Execute cmdCHECK on the plugin
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(tester.args, func() error {
return cmdCheck(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
@ -411,7 +574,244 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
Expect(err).NotTo(HaveOccurred())
}
func (tester *testerV03x) cmdDelTest(tc testCase) {
func (tester *testerV04x) cmdDelTest(tc testCase, dataDir string) {
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(tester.args, func() error {
return cmdDel(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the host veth has been deleted
err = tester.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 = tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(tester.vethName)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
}
type testerV03x struct {
testNS ns.NetNS
targetNS ns.NetNS
args *skel.CmdArgs
vethName string
}
func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.testNS = testNS
tester.targetNS = targetNS
}
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
// Execute cmdADD on the plugin
var result *current.Result
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
return cmdAdd(tester.args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
if !tc.isLayer2 && tc.vlan != 0 {
Expect(len(result.Interfaces)).To(Equal(4))
} else {
Expect(len(result.Interfaces)).To(Equal(3))
}
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
Expect(result.Interfaces[0].Mac).To(HaveLen(17))
Expect(result.Interfaces[1].Name).To(HavePrefix("veth"))
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].Sandbox).To(Equal(tester.targetNS.Path()))
// 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))
bridgeMAC := link.Attrs().HardwareAddr.String()
var vlanLink netlink.Link
if !tc.isLayer2 && tc.vlan != 0 {
// Make sure vlan link exists
vlanLink, err = netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, tc.vlan))
Expect(err).NotTo(HaveOccurred())
Expect(vlanLink.Attrs().Name).To(Equal(fmt.Sprintf("%s.%d", BRNAME, tc.vlan)))
Expect(vlanLink).To(BeAssignableToTypeOf(&netlink.Veth{}))
// Check the bridge dot vlan interface have the vlan tag
peerLink, err := netlink.LinkByIndex(vlanLink.Attrs().Index - 1)
Expect(err).NotTo(HaveOccurred())
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(peerLink.Attrs().Index)]
Expect(isExist).To(BeTrue())
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
}
// Check the bridge vlan filtering equals true
if tc.vlan != 0 {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(true))
} else {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(false))
}
// Ensure bridge has expected gateway address(es)
var addrs []netlink.Addr
if tc.vlan == 0 {
addrs, err = netlink.AddrList(link, netlink.FAMILY_ALL)
} else {
addrs, err = netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
}
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
for _, cidr := range tc.expGWCIDRs {
ip, subnet, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(ip) && 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())
if !tc.isLayer2 && tc.vlan != 0 {
Expect(len(links)).To(Equal(5)) // Bridge, Bridge vlan veth, veth, and loopback
} else {
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{}))
tester.vethName = result.Interfaces[1].Name
// check vlan exist on the veth interface
if tc.vlan != 0 {
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
Expect(isExist).To(BeTrue())
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
}
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
// this check is not relevant for a layer 2 bridge
if !tc.isLayer2 && tc.vlan == 0 {
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.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{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
foundAddrs++
}
}
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
// Ensure the default route(s)
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound4, defaultRouteFound6 bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
var found *bool
if ipVersion(gwIP) == "4" {
found = &defaultRouteFound4
} else {
found = &defaultRouteFound6
}
if *found == true {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
}
Expect(*found).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
return result, nil
}
func (tester *testerV03x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
return
}
func (tester *testerV03x) cmdDelTest(tc testCase, dataDir string) {
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@ -458,7 +858,7 @@ func (tester *testerV01xOr02x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.targetNS = targetNS
}
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) {
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and calculate gateway addresses
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
@ -549,9 +949,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) {
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil, nil
}
func (tester *testerV01xOr02x) cmdDelTest(tc testCase) {
func (tester *testerV01xOr02x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
return
}
func (tester *testerV01xOr02x) cmdDelTest(tc testCase, dataDir string) {
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@ -586,15 +991,107 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) {
tester.setNS(testNS, targetNS)
// Test IP allocation
tester.cmdAddTest(tc, dataDir)
result, err := tester.cmdAddTest(tc, dataDir)
Expect(err).NotTo(HaveOccurred())
if strings.HasPrefix(tc.cniVersion, "0.3.") {
Expect(result).NotTo(BeNil())
} else {
Expect(result).To(BeNil())
}
// Test IP Release
tester.cmdDelTest(tc)
tester.cmdDelTest(tc, dataDir)
// Clean up bridge addresses for next test case
delBridgeAddrs(testNS)
}
func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
func cmdAddDelCheckTest(testNS ns.NetNS, tc testCase, dataDir string) {
Expect(tc.cniVersion).To(Equal("0.4.0"))
// Get a Add/Del tester based on test case version
tester := testerByVersion(tc.cniVersion)
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNS.Close()
tester.setNS(testNS, targetNS)
// Test IP allocation
prevResult, err := tester.cmdAddTest(tc, dataDir)
Expect(err).NotTo(HaveOccurred())
Expect(prevResult).NotTo(BeNil())
confString := tc.netConfJSON(dataDir)
conf := &Net{}
err = json.Unmarshal([]byte(confString), &conf)
Expect(err).NotTo(HaveOccurred())
conf.IPAM, _, err = allocator.LoadIPAMConfig([]byte(confString), "")
Expect(err).NotTo(HaveOccurred())
newConf, err := buildOneConfig("testConfig", tc.cniVersion, conf, prevResult)
Expect(err).NotTo(HaveOccurred())
// Test CHECK
tester.cmdCheckTest(tc, newConf, dataDir)
// Test IP Release
tester.cmdDelTest(tc, dataDir)
// Clean up bridge addresses for next test case
delBridgeAddrs(testNS)
if tc.vlan != 0 && !tc.isLayer2 {
delVlanAddrs(testNS, tc.vlan)
}
}
var _ = Describe("bridge Operations", func() {
var originalNS ns.NetNS
var dataDir string
@ -722,6 +1219,63 @@ var _ = Describe("bridge Operations", func() {
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.0", isLayer2: true, vlan: 100}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.1", isLayer2: true, vlan: 100}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a bridge, veth with default route and vlanID 100 with ADD/DEL for 0.3.0 config", func() {
testCases := []testCase{
{
// IPv4 only
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
vlan: 100,
},
{
// IPv6 only
subnet: "2001:db8::0/64",
expGWCIDRs: []string{"2001:db8::1/64"},
vlan: 100,
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
vlan: 100,
},
{
// 3 Subnets (1 IPv4 and 2 IPv6 subnets)
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
{subnet: "2001:db8::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
"2001:db8::1/64",
},
vlan: 100,
},
}
for _, tc := range testCases {
tc.cniVersion = "0.3.0"
cmdAddDelTest(originalNS, tc, dataDir)
}
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
testCases := []testCase{
{
@ -767,7 +1321,7 @@ var _ = Describe("bridge Operations", func() {
tester.args = tc.createCmdArgs(targetNS, dataDir)
// Execute cmdDEL on the plugin, expect no errors
tester.cmdDelTest(tc)
tester.cmdDelTest(tc, dataDir)
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
@ -870,13 +1424,13 @@ var _ = Describe("bridge Operations", func() {
Expect(conf.ForceAddress).To(Equal(false))
// Set first address on bridge
err = ensureBridgeAddr(bridge, family, &gwnFirst, conf.ForceAddress)
err = ensureAddr(bridge, family, &gwnFirst, conf.ForceAddress)
Expect(err).NotTo(HaveOccurred())
checkBridgeIPs(tc.gwCIDRFirst, "")
// Attempt to set the second address on the bridge
// with ForceAddress set to false.
err = ensureBridgeAddr(bridge, family, &gwnSecond, false)
err = ensureAddr(bridge, family, &gwnSecond, false)
if family == netlink.FAMILY_V4 || subnetsOverlap {
// IPv4 or overlapping IPv6 subnets:
// Expect an error, and address should remain the same
@ -892,7 +1446,7 @@ var _ = Describe("bridge Operations", func() {
// Set the second address on the bridge
// with ForceAddress set to true.
err = ensureBridgeAddr(bridge, family, &gwnSecond, true)
err = ensureAddr(bridge, family, &gwnSecond, true)
Expect(err).NotTo(HaveOccurred())
if family == netlink.FAMILY_V4 || subnetsOverlap {
// IPv4 or overlapping IPv6 subnets:
@ -1007,4 +1561,38 @@ var _ = Describe("bridge Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL/CHECK for 0.4.0 config", func() {
testCases := []testCase{
{
// IPv4 only
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
expGWCIDRs: []string{"10.1.2.1/24"},
},
{
// IPv6 only
ranges: []rangeInfo{{
subnet: "2001:db8::0/64",
}},
expGWCIDRs: []string{"2001:db8::1/64"},
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
},
}
for _, tc := range testCases {
tc.cniVersion = "0.4.0"
cmdAddDelCheckTest(originalNS, tc, dataDir)
}
})
})

View File

@ -25,13 +25,17 @@ import (
"runtime"
"strings"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
//NetConf for host-device config, look the README to learn how to use those parameters
@ -81,6 +85,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("failed to move link %v", err)
}
var result *current.Result
// run the IPAM plugin and get back the config to apply
if cfg.IPAM.Type != "" {
r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData)
@ -96,7 +101,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}()
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r)
result, err = current.NewResultFromResult(r)
if err != nil {
return err
}
@ -124,6 +129,10 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil {
return err
}
result.DNS = cfg.DNS
return types.PrintResult(result, cfg.CNIVersion)
}
return printLink(contDev, cfg.CNIVersion, containerNs)
@ -275,11 +284,109 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
cfg, 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()
// run the IPAM plugin and get back the config to apply
if cfg.IPAM.Type != "" {
err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData)
if err != nil {
return err
}
}
// Parse previous result.
if cfg.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&cfg.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(cfg.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name we know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
//
return nil
}
func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
}

View File

@ -15,19 +15,210 @@
package main
import (
"encoding/json"
"fmt"
"math/rand"
"net"
"strings"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
)
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
KernelPath string `json:"kernelpath"` // Kernelpath of the device
IPAM *IPAMConfig `json:"ipam,omitempty"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
type IPAMConfig struct {
Name string
Type string `json:"type"`
Routes []*types.Route `json:"routes"`
Addresses []Address `json:"addresses,omitempty"`
DNS types.DNS `json:"dns"`
}
type IPAMEnvArgs struct {
types.CommonArgs
IP types.UnmarshallableString `json:"ip,omitempty"`
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
}
type Address struct {
AddressStr string `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
Address net.IPNet
Version string
}
// canonicalizeIP makes sure a provided ip is in standard form
func canonicalizeIP(ip *net.IP) error {
if ip.To4() != nil {
*ip = ip.To4()
return nil
} else if ip.To16() != nil {
*ip = ip.To16()
return nil
}
return fmt.Errorf("IP %s not v4 nor v6", *ip)
}
// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided
// as `bytes`. At the moment values provided in envArgs are ignored so there
// is no possibility to overload the json configuration using envArgs
func LoadIPAMConfig(bytes []byte, envArgs 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")
}
// Validate all ranges
numV4 := 0
numV6 := 0
for i := range n.IPAM.Addresses {
ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err)
}
n.IPAM.Addresses[i].Address = *addr
n.IPAM.Addresses[i].Address.IP = ip
if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil {
return nil, "", fmt.Errorf("invalid address %d: %s", i, err)
}
if n.IPAM.Addresses[i].Address.IP.To4() != nil {
n.IPAM.Addresses[i].Version = "4"
numV4++
} else {
n.IPAM.Addresses[i].Version = "6"
numV6++
}
}
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
if addr.Address.IP.To4() != nil {
addr.Version = "4"
numV4++
} else {
addr.Version = "6"
numV6++
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
// CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 {
for _, v := range types020.SupportedVersions {
if n.CNIVersion == v {
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
}
}
}
// Copy net name into IPAM so not to drag Net struct around
n.IPAM.Name = n.Name
return n.IPAM, n.CNIVersion, nil
}
func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
var _ = Describe("base functionality", func() {
var originalNS ns.NetNS
var ifname string
@ -262,4 +453,255 @@ var _ = Describe("base functionality", func() {
})
It("Works with a valid 0.4.0 config without IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid 0.4.0 config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
targetIP := "10.10.0.1/24"
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"ipam": {
"type": "static",
"addresses": [
{
"address":"`+targetIP+`",
"gateway": "10.10.0.254"
}]
},
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
//get the IP address of the interface in the target namespace
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
addr := addrs[0].IPNet.String()
//assert that IP address is what we set
Expect(addr).To(Equal(targetIP))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -20,24 +20,21 @@ import (
"fmt"
"runtime"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
type NetConf struct {
types.NetConf
// support chaining for master interface and IP decisions
// occurring prior to running ipvlan plugin
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
Master string `json:"master"`
Mode string `json:"mode"`
MTU int `json:"mtu"`
@ -50,33 +47,35 @@ func init() {
runtime.LockOSThread()
}
func loadConf(bytes []byte) (*NetConf, string, error) {
func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
// Parse previous result
if n.RawPrevResult != nil {
resultBytes, err := json.Marshal(n.RawPrevResult)
if err != nil {
return nil, "", fmt.Errorf("could not serialize prevResult: %v", err)
if cmdCheck {
return n, n.CNIVersion, nil
}
res, err := version.NewResult(n.CNIVersion, resultBytes)
if err != nil {
var result *current.Result
var err error
// Parse previous result
if n.NetConf.RawPrevResult != nil {
if err = version.ParsePrevResult(&n.NetConf); err != nil {
return nil, "", fmt.Errorf("could not parse prevResult: %v", err)
}
n.RawPrevResult = nil
n.PrevResult, err = current.NewResultFromResult(res)
result, err = current.NewResultFromResult(n.PrevResult)
if err != nil {
return nil, "", fmt.Errorf("could not convert result to current version: %v", err)
}
}
if n.Master == "" {
if n.PrevResult == nil {
if result == nil {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
}
if len(n.PrevResult.Interfaces) == 1 && n.PrevResult.Interfaces[0].Name != "" {
n.Master = n.PrevResult.Interfaces[0].Name
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
n.Master = result.Interfaces[0].Name
} else {
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
}
@ -97,6 +96,19 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
}
}
func modeToString(mode netlink.IPVlanMode) (string, error) {
switch mode {
case netlink.IPVLAN_MODE_L2:
return "l2", nil
case netlink.IPVLAN_MODE_L3:
return "l3", nil
case netlink.IPVLAN_MODE_L3S:
return "l3s", nil
default:
return "", fmt.Errorf("unknown ipvlan mode: %q", mode)
}
}
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
ipvlan := &current.Interface{}
@ -156,7 +168,7 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
}
func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData)
n, cniVersion, err := loadConf(args.StdinData, false)
if err != nil {
return err
}
@ -175,9 +187,17 @@ func cmdAdd(args *skel.CmdArgs) error {
var result *current.Result
// Configure iface from PrevResult if we have IPs and an IPAM
// block has not been configured
if n.IPAM.Type == "" && n.PrevResult != nil && len(n.PrevResult.IPs) > 0 {
result = n.PrevResult
} else {
haveResult := false
if n.IPAM.Type == "" && n.PrevResult != nil {
result, err = current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
if len(result.IPs) > 0 {
haveResult = true
}
}
if !haveResult {
// run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil {
@ -213,7 +233,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData)
n, _, err := loadConf(args.StdinData, false)
if err != nil {
return err
}
@ -245,11 +265,130 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData, true)
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()
if n.IPAM.Type != "" {
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for names whe know, ipvlan inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(n.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, masterIndex int, modeExpected string) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
ipv, isIPVlan := link.(*netlink.IPVlan)
if !isIPVlan {
return fmt.Errorf("Error: Container interface %s not of type ipvlan", link.Attrs().Name)
}
mode, err := modeFromString(modeExpected)
if ipv.Mode != mode {
currString, err := modeToString(ipv.Mode)
if err != nil {
return err
}
confString, err := modeToString(mode)
if err != nil {
return err
}
return fmt.Errorf("Container IPVlan mode %s does not match expected value: %s", currString, confString)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
}

View File

@ -15,6 +15,7 @@
package main
import (
"encoding/json"
"fmt"
"net"
"syscall"
@ -27,12 +28,71 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
Mode string `json:"mode"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
if orig.IPAM == nil {
inject["master"] = master
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
@ -106,6 +166,109 @@ func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
Expect(err).NotTo(HaveOccurred())
}
func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalNS ns.NetNS) {
targetNs, err := testutils.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.CmdAddWithArgs(args, 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())
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
if n.IPAM != nil {
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
}
cniVersion := "0.4.0"
newConf, err := buildOneConfig(netName, cniVersion, MASTER_NAME, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check on macvlan in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, 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())
}
var _ = Describe("ipvlan Operations", func() {
var originalNS ns.NetNS
@ -256,4 +419,49 @@ var _ = Describe("ipvlan Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL", func() {
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "ipvlanTest1",
"type": "ipvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
ipvlanAddCheckDelTest(conf, "ipvlanTest1", IFNAME, originalNS)
})
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL when chained", func() {
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "ipvlanTest2",
"type": "ipvlan",
"prevResult": {
"interfaces": [
{
"name": "%s"
}
],
"ips": [
{
"version": "4",
"address": "10.1.2.2/24",
"gateway": "10.1.2.1",
"interface": 0
}
],
"routes": []
}
}`, MASTER_NAME)
ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS)
})
})

View File

@ -15,13 +15,14 @@
package main
import (
"fmt"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func cmdAdd(args *skel.CmdArgs) error {
@ -73,11 +74,10 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback"))
}
func cmdGet(args *skel.CmdArgs) error {
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
return nil
}

View File

@ -21,16 +21,19 @@ import (
"net"
"runtime"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
)
const (
@ -77,6 +80,21 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
}
}
func modeToString(mode netlink.MacvlanMode) (string, error) {
switch mode {
case netlink.MACVLAN_MODE_BRIDGE:
return "bridge", nil
case netlink.MACVLAN_MODE_PRIVATE:
return "private", nil
case netlink.MACVLAN_MODE_VEPA:
return "vepa", nil
case netlink.MACVLAN_MODE_PASSTHRU:
return "passthru", nil
default:
return "", fmt.Errorf("unknown macvlan mode: %q", mode)
}
}
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
macvlan := &current.Interface{}
@ -255,11 +273,128 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
n, _, 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()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for names whe know, macvlan device name inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(n.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, parentIndex int, modeExpected string) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
macv, isMacvlan := link.(*netlink.Macvlan)
if !isMacvlan {
return fmt.Errorf("Error: Container interface %s not of type macvlan", link.Attrs().Name)
}
mode, err := modeFromString(modeExpected)
if macv.Mode != mode {
currString, err := modeToString(macv.Mode)
if err != nil {
return err
}
confString, err := modeToString(mode)
if err != nil {
return err
}
return fmt.Errorf("Container macvlan mode %s does not match expected value: %s", currString, confString)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
}

View File

@ -15,6 +15,7 @@
package main
import (
"encoding/json"
"fmt"
"net"
"syscall"
@ -27,12 +28,73 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
Mode string `json:"mode"`
IPAM *allocator.IPAMConfig `json:"ipam"`
//RuntimeConfig struct { // The capability arg
// IPRanges []RangeSet `json:"ipRanges,omitempty"`
//} `json:"runtimeConfig,omitempty"`
//Args *struct {
// A *IPAMArgs `json:"cni"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
var _ = Describe("macvlan Operations", func() {
var originalNS ns.NetNS
@ -223,4 +285,118 @@ var _ = Describe("macvlan Operations", func() {
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() {
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "macvlanTestv4",
"type": "macvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ]]
}
}`, MASTER_NAME)
targetNs, err := testutils.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.CmdAddWithArgs(args, 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))
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())
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("macvlanTestv4", cniVersion, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check on macvlan in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, 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())
})
})

View File

@ -22,16 +22,19 @@ import (
"os"
"runtime"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func init() {
@ -110,7 +113,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
}
for _, r := range []netlink.Route{
netlink.Route{
{
LinkIndex: contVeth.Index,
Dst: &net.IPNet{
IP: ipc.Gateway,
@ -119,7 +122,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
Scope: netlink.SCOPE_LINK,
Src: ipc.Address.IP,
},
netlink.Route{
{
LinkIndex: contVeth.Index,
Dst: &net.IPNet{
IP: ipc.Address.IP.Mask(ipc.Address.Mask),
@ -285,11 +288,108 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %v", 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()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
if conf.NetConf.RawPrevResult == nil {
return fmt.Errorf("ptp: Required prevResult missing")
}
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
return err
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name whe know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
}

View File

@ -15,6 +15,7 @@
package main
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/skel"
@ -25,10 +26,66 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
var _ = Describe("ptp Operations", func() {
var originalNS ns.NetNS
@ -142,6 +199,133 @@ var _ = Describe("ptp Operations", func() {
Expect(err).NotTo(HaveOccurred())
}
doTestv4 := func(conf string, netName string, numIPs int) {
const IFNAME = "ptp0"
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var resI types.Result
var res *current.Result
// Execute the plugin with the ADD command, creating the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
resI, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
res, err = current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
// Make sure ptp link exists in the target namespace
// Then, ping the gateway
seenIPs := 0
wantMac := ""
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
wantMac = link.Attrs().HardwareAddr.String()
for _, ipc := range res.IPs {
if *ipc.Interface != 1 {
continue
}
seenIPs += 1
saddr := ipc.Address.IP.String()
daddr := ipc.Gateway.String()
fmt.Fprintln(GinkgoWriter, "ping", saddr, "->", daddr)
if err := testutils.Ping(saddr, daddr, (ipc.Version == "6"), 30); err != nil {
return fmt.Errorf("ping %s -> %s failed: %s", saddr, daddr, err)
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(seenIPs).To(Equal(numIPs))
// make sure the interfaces are correct
Expect(res.Interfaces).To(HaveLen(2))
Expect(res.Interfaces[0].Name).To(HavePrefix("veth"))
Expect(res.Interfaces[0].Mac).To(HaveLen(17))
Expect(res.Interfaces[0].Sandbox).To(BeEmpty())
Expect(res.Interfaces[1].Name).To(Equal(IFNAME))
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig(netName, cniVersion, n, res)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
args.StdinData = []byte(conf)
// Call the plugins with the DEL command, deleting the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, 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("configures and deconfigures a ptp link with ADD/DEL", func() {
conf := `{
"cniVersion": "0.3.1",
@ -215,4 +399,39 @@ var _ = Describe("ptp Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a CNI V4 ptp link with ADD/DEL", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "ptpNetv4",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
doTestv4(conf, "ptpNetv4", 1)
})
It("configures and deconfigures a CNI V4 dual-stack ptp link with ADD/DEL", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "ptpNetv4ds",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"ranges": [
[{ "subnet": "10.1.2.0/24"}],
[{ "subnet": "2001:db8:1::0/66"}]
]
}
}`
doTestv4(conf, "ptpNetv4ds", 2)
})
})

View File

@ -20,14 +20,17 @@ import (
"fmt"
"runtime"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
type NetConf struct {
@ -192,11 +195,130 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %v", 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()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
if conf.NetConf.RawPrevResult == nil {
return fmt.Errorf("ptp: Required prevResult missing")
}
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
return err
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name whe know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(conf.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, conf.VlanId, conf.MTU)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, masterIndex int, vlanId int, mtu int) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
vlan, isVlan := link.(*netlink.Vlan)
if !isVlan {
return fmt.Errorf("Error: Container interface %s not of type vlan", link.Attrs().Name)
}
// TODO This works when unit testing via cnitool; fails with ./test.sh
//if masterIndex != vlan.Attrs().ParentIndex {
// return fmt.Errorf("Container vlan Master %d does not match expected value: %d", vlan.Attrs().ParentIndex, masterIndex)
//}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("vlan: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
if vlanId != vlan.VlanId {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
intf.Name, vlanId, vlan.VlanId)
}
if mtu != 0 {
if mtu != link.Attrs().MTU {
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
intf.Name, mtu, link.Attrs().MTU)
}
}
return nil
}

View File

@ -15,6 +15,7 @@
package main
import (
"encoding/json"
"fmt"
"net"
"syscall"
@ -27,12 +28,69 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
VlanId int `json:"vlanId"`
MTU int `json:"mtu"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
var _ = Describe("vlan Operations", func() {
var originalNS ns.NetNS
@ -234,4 +292,119 @@ var _ = Describe("vlan Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
const IFNAME = "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "vlanTestv4",
"type": "vlan",
"master": "%s",
"vlanId": 1234,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
targetNs, err := testutils.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.CmdAddWithArgs(args, 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 vlan 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())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("vlanTestv4", cniVersion, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
args.StdinData = []byte(conf)
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure vlan 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())
})
})

View File

@ -21,15 +21,18 @@ import (
"strings"
"os"
"github.com/juju/errors"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/hcn"
"github.com/juju/errors"
"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/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
type NetConf struct {
@ -226,5 +229,5 @@ func cmdGet(_ *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), "TODO")
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-bridge"))
}

View File

@ -21,15 +21,17 @@ import (
"strings"
"os"
"github.com/Microsoft/hcsshim"
"github.com/juju/errors"
"github.com/Microsoft/hcsshim"
"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/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
type NetConf struct {
@ -172,5 +174,5 @@ func cmdGet(_ *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), "TODO")
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-overlay"))
}

View File

@ -35,6 +35,49 @@ import (
"github.com/onsi/gomega/gexec"
)
func buildOneConfig(name, cniVersion string, orig *PluginConf, prevResult types.Result) (*PluginConf, []byte, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, nil, err
}
conf := &PluginConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("bandwidth test", func() {
var (
hostNs ns.NetNS
@ -643,7 +686,6 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf := PluginConf{}
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
@ -654,7 +696,7 @@ var _ = Describe("bandwidth test", func() {
tbfPluginConf.Name = "mynet"
tbfPluginConf.CNIVersion = "0.3.0"
tbfPluginConf.Type = "bandwidth"
tbfPluginConf.RawPrevResult = &map[string]interface{}{
tbfPluginConf.RawPrevResult = map[string]interface{}{
"ips": containerWithTbfResult.IPs,
"interfaces": containerWithTbfResult.Interfaces,
}
@ -663,7 +705,6 @@ var _ = Describe("bandwidth test", func() {
IPs: containerWithTbfResult.IPs,
Interfaces: containerWithTbfResult.Interfaces,
}
conf, err := json.Marshal(tbfPluginConf)
Expect(err).NotTo(HaveOccurred())
@ -725,4 +766,169 @@ var _ = Describe("bandwidth test", func() {
}, 1)
})
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
var portServerWithTbf int
var portServerWithoutTbf int
var containerWithTbfRes types.Result
var containerWithoutTbfRes types.Result
var echoServerWithTbf *gexec.Session
var echoServerWithoutTbf *gexec.Session
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
ptpConf = `{
"cniVersion": "0.4.0",
"name": "myBWnet",
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
containerWithTbfIFName := "ptp0"
containerWithoutTbfIFName := "ptp1"
var err error
containerWithTbfNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
By("create two containers, and use the bandwidth plugin on one of them")
Expect(hostNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf := &PluginConf{}
err = json.Unmarshal([]byte(ptpConf), &tbfPluginConf)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
IngressRate: rateInBits,
EgressBurst: burstInBits,
EgressRate: rateInBits,
}
tbfPluginConf.Type = "bandwidth"
cniVersion := "0.4.0"
_, newConfBytes, err := buildOneConfig("myBWnet", cniVersion, tbfPluginConf, containerWithTbfResult)
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newConfBytes,
}
result, out, err := testutils.CmdAdd(containerWithTbfNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
// Do CNI Check
checkConf := &PluginConf{}
err = json.Unmarshal([]byte(ptpConf), &checkConf)
Expect(err).NotTo(HaveOccurred())
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
IngressRate: rateInBits,
EgressBurst: burstInBits,
EgressRate: rateInBits,
}
checkConf.Type = "bandwidth"
_, newCheckBytes, err := buildOneConfig("myBWnet", cniVersion, checkConf, result)
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newCheckBytes,
}
err = testutils.CmdCheck(containerWithTbfNS.Path(), args.ContainerID, "", newCheckBytes, func() error { return cmdCheck(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})).To(Succeed())
By("starting a tcp server on both containers")
portServerWithTbf, echoServerWithTbf, err = startEchoServerInNamespace(containerWithTbfNS)
Expect(err).NotTo(HaveOccurred())
portServerWithoutTbf, echoServerWithoutTbf, err = startEchoServerInNamespace(containerWithoutTbfNS)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
containerWithTbfNS.Close()
containerWithoutTbfNS.Close()
if echoServerWithoutTbf != nil {
echoServerWithoutTbf.Kill()
}
if echoServerWithTbf != nil {
echoServerWithTbf.Kill()
}
})
Measure("limits ingress traffic on veth device", func(b Benchmarker) {
var runtimeWithLimit time.Duration
var runtimeWithoutLimit time.Duration
By("gather timing statistics about both containers")
By("sending tcp traffic to the container that has traffic shaped", func() {
runtimeWithLimit = b.Time("with tbf", func() {
result, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithTbf, packetInBytes)
})
})
By("sending tcp traffic to the container that does not have traffic shaped", func() {
runtimeWithoutLimit = b.Time("without tbf", func() {
result, err := current.GetResult(containerWithoutTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutTbf, packetInBytes)
})
})
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}, 1)
})
})

View File

@ -20,13 +20,15 @@ import (
"errors"
"fmt"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ip"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ip"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
@ -50,10 +52,6 @@ type PluginConf struct {
Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"`
} `json:"runtimeConfig,omitempty"`
// RuntimeConfig *struct{} `json:"runtimeConfig"`
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
*BandwidthEntry
}
@ -65,21 +63,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
bandwidth := getBandwidth(&conf)
if bandwidth != nil {
err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst)
@ -92,6 +75,18 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
}
}
if conf.RawPrevResult != nil {
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
_, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil
}
@ -167,7 +162,11 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("must be called as chained plugin")
}
hostInterface, err := getHostInterface(conf.PrevResult.Interfaces)
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil {
return err
}
@ -200,7 +199,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
conf.PrevResult.Interfaces = append(conf.PrevResult.Interfaces, &current.Interface{
result.Interfaces = append(result.Interfaces, &current.Interface{
Name: ifbDeviceName,
Mac: ifbDevice.Attrs().HardwareAddr.String(),
})
@ -210,7 +209,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
@ -232,11 +231,125 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), bv.BuildString("bandwidth"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
qdiscs, err := netlink.QdiscList(link)
if err != nil {
return nil, err
}
result := []netlink.Qdisc{}
for _, qdisc := range qdiscs {
// filter out pfifo_fast qdiscs because
// older kernels don't return them
_, pfifo := qdisc.(*netlink.PfifoFast)
if !pfifo {
result = append(result, qdisc)
}
}
return result, nil
}
func cmdCheck(args *skel.CmdArgs) error {
bwConf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
if bwConf.PrevResult == nil {
return fmt.Errorf("must be called as a chained plugin")
}
result, err := current.NewResultFromResult(bwConf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil {
return err
}
link, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
bandwidth := getBandwidth(bwConf)
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
rateInBytes := bandwidth.IngressRate / 8
burstInBytes := bandwidth.IngressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
qdiscs, err := SafeQdiscList(link)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
rateInBytes := bandwidth.EgressRate / 8
burstInBytes := bandwidth.EgressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
}
qdiscs, err := SafeQdiscList(ifbDevice)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
return nil
}

View File

@ -0,0 +1,135 @@
# firewall plugin
## Overview
This plugin creates firewall rules to allow traffic to/from container IP address via the host network .
It does not create any network interfaces and therefore does not set up connectivity by itself.
It is intended to be used as a chained plugins.
## Operation
The following network configuration file
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
}
]
}
```
will allow any IP addresses configured by earlier plugins to send/receive traffic via the host.
A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result.
## Backends
This plugin supports multiple firewall backends that implement the desired functionality.
Available backends include `iptables` and `firewalld` and may be selected with the `backend` key.
If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus.
If no firewalld service is found, it will fall back to iptables.
## firewalld backend rule structure
When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic.
A sample standalone config list (with the file extension .conflist) using firewalld backend might
look like:
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
"backend": "firewalld"
}
]
}
```
`FORWARD_IN_ZONES_SOURCE` chain:
- `-d 10.88.0.2 -j FWDI_trusted`
`CNI_FORWARD_OUT_ZONES_SOURCE` chain:
- `-s 10.88.0.2 -j FWDO_trusted`
## iptables backend rule structure
A sample standalone config list (with the file extension .conflist) using iptables backend might
look like:
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
"backend": "iptables"
}
]
}
```
When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic.
### FORWARD
A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added
when containers are created and from where rules will be removed when containers terminate.
`FORWARD` chain:
- `-j CNI-FORWARD`
CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address
of the container as shown:
`CNI_FORWARD` chain:
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD`
- `-d 10.88.0.2 -j CNI-FORWARD`

View File

@ -0,0 +1,184 @@
// 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"
"net"
"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/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// FirewallNetConf represents the firewall configuration.
type FirewallNetConf struct {
types.NetConf
// Backend is the firewall type to add rules to. Allowed values are
// 'iptables' and 'firewalld'.
Backend string `json:"backend"`
// IptablesAdminChainName is an optional name to use instead of the default
// admin rules override chain name that includes the interface name.
IptablesAdminChainName string `json:"iptablesAdminChainName,omitempty"`
// FirewalldZone is an optional firewalld zone to place the interface into. If
// the firewalld backend is used but the zone is not given, it defaults
// to 'trusted'
FirewalldZone string `json:"firewalldZone,omitempty"`
}
type FirewallBackend interface {
Add(*FirewallNetConf, *current.Result) error
Del(*FirewallNetConf, *current.Result) error
Check(*FirewallNetConf, *current.Result) error
}
func ipString(ip net.IPNet) string {
if ip.IP.To4() == nil {
return ip.IP.String() + "/128"
}
return ip.IP.String() + "/32"
}
func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
conf := FirewallNetConf{}
if err := json.Unmarshal(data, &conf); err != nil {
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
}
// Parse previous result.
if conf.RawPrevResult == nil {
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
}
// Parse previous result.
var result *current.Result
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
}
result, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
return &conf, result, nil
}
func getBackend(conf *FirewallNetConf) (FirewallBackend, error) {
switch conf.Backend {
case "iptables":
return newIptablesBackend(conf)
case "firewalld":
return newFirewalldBackend(conf)
}
// Default to firewalld if it's running
if isFirewalldRunning() {
return newFirewalldBackend(conf)
}
// Otherwise iptables
return newIptablesBackend(conf)
}
func cmdAdd(args *skel.CmdArgs) error {
conf, result, err := parseConf(args.StdinData)
if err != nil {
return err
}
backend, err := getBackend(conf)
if err != nil {
return err
}
if err := backend.Add(conf, result); err != nil {
return err
}
if result == nil {
result = &current.Result{}
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
conf, result, err := parseConf(args.StdinData)
if err != nil {
return err
}
backend, err := getBackend(conf)
if err != nil {
return err
}
// Tolerate errors if the container namespace has been torn down already
containerNS, err := ns.GetNS(args.Netns)
if err == nil {
defer containerNS.Close()
}
// Runtime errors are ignored
if err := backend.Del(conf, result); err != nil {
return err
}
return nil
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.4.0"), bv.BuildString("firewall"))
}
func cmdCheck(args *skel.CmdArgs) error {
conf, result, err := parseConf(args.StdinData)
if err != nil {
return err
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
}
backend, err := getBackend(conf)
if err != nil {
return err
}
if err := backend.Check(conf, result); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,343 @@
// Copyright 2018 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"
"fmt"
"os/exec"
"strings"
"sync"
"syscall"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/godbus/dbus"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
confTmpl = `{
"cniVersion": "0.3.1",
"name": "firewalld-test",
"type": "firewall",
"backend": "firewalld",
"zone": "trusted",
"prevResult": {
"cniVersion": "0.3.0",
"interfaces": [
{"name": "%s", "sandbox": "%s"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`
ifname = "eth0"
)
type fakeFirewalld struct {
zone string
source string
}
func (f *fakeFirewalld) clear() {
f.zone = ""
f.source = ""
}
func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) {
f.zone = zone
f.source = source
return "", nil
}
func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) {
f.zone = zone
f.source = source
return "", nil
}
func (f *fakeFirewalld) QuerySource(zone, source string) (bool, *dbus.Error) {
if f.zone != zone {
return false, nil
}
if f.source != source {
return false, nil
}
return true, nil
}
func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
// Start a private D-Bus session bus
path, err := invoke.FindInPath("dbus-daemon", []string{
"/bin", "/sbin", "/usr/bin", "/usr/sbin",
})
Expect(err).NotTo(HaveOccurred())
cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile")
stdout, err := cmd.StdoutPipe()
Expect(err).NotTo(HaveOccurred())
err = cmd.Start()
Expect(err).NotTo(HaveOccurred())
// Wait for dbus-daemon to print the bus address
bytes, err := bufio.NewReader(stdout).ReadString('\n')
Expect(err).NotTo(HaveOccurred())
busAddr := strings.TrimSpace(string(bytes))
Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())
var startWg sync.WaitGroup
wg.Add(1)
startWg.Add(1)
go func() {
defer GinkgoRecover()
startWg.Done()
err = cmd.Wait()
Expect(err).NotTo(HaveOccurred())
wg.Done()
}()
startWg.Wait()
return busAddr, cmd
}
var _ = Describe("firewalld test", func() {
var (
targetNs ns.NetNS
cmd *exec.Cmd
conn *dbus.Conn
wg sync.WaitGroup
fwd *fakeFirewalld
busAddr string
)
BeforeEach(func() {
var err error
targetNs, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
// Start a private D-Bus session bus
busAddr, cmd = spawnSessionDbus(&wg)
conn, err = dbus.Dial(busAddr)
Expect(err).NotTo(HaveOccurred())
err = conn.Auth(nil)
Expect(err).NotTo(HaveOccurred())
err = conn.Hello()
Expect(err).NotTo(HaveOccurred())
// Start our fake firewalld
reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue)
Expect(err).NotTo(HaveOccurred())
Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner))
fwd = &fakeFirewalld{}
// Because firewalld D-Bus methods start with lower-case, and
// because in Go lower-case methods are private, we need to remap
// Go public methods to the D-Bus name
methods := map[string]string{
"AddSource": firewalldAddSourceMethod,
"QuerySource": firewalldQuerySourceMethod,
"RemoveSource": firewalldRemoveSourceMethod,
}
conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface)
// Make sure the plugin uses our private session bus
testConn = conn
})
AfterEach(func() {
_, err := conn.ReleaseName(firewalldName)
Expect(err).NotTo(HaveOccurred())
err = cmd.Process.Signal(syscall.SIGTERM)
Expect(err).NotTo(HaveOccurred())
wg.Wait()
})
It("works with a 0.3.1 config", func() {
Expect(isFirewalldRunning()).To(BeTrue())
conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: ifname,
StdinData: []byte(conf),
}
_, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(fwd.zone).To(Equal("trusted"))
Expect(fwd.source).To(Equal("10.0.0.2/32"))
fwd.clear()
err = testutils.CmdDel(targetNs.Path(), args.ContainerID, ifname, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(fwd.zone).To(Equal("trusted"))
Expect(fwd.source).To(Equal("10.0.0.2/32"))
})
It("defaults to the firewalld backend", func() {
conf := `{
"cniVersion": "0.3.1",
"name": "firewalld-test",
"type": "firewall",
"zone": "trusted",
"prevResult": {
"cniVersion": "0.3.0",
"interfaces": [
{"name": "eth0", "sandbox": "/foobar"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`
Expect(isFirewalldRunning()).To(BeTrue())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: ifname,
StdinData: []byte(conf),
}
_, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(fwd.zone).To(Equal("trusted"))
Expect(fwd.source).To(Equal("10.0.0.2/32"))
})
It("passes through the prevResult", func() {
conf := `{
"cniVersion": "0.3.1",
"name": "firewalld-test",
"type": "firewall",
"zone": "trusted",
"prevResult": {
"cniVersion": "0.3.0",
"interfaces": [
{"name": "eth0", "sandbox": "/foobar"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`
Expect(isFirewalldRunning()).To(BeTrue())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: ifname,
StdinData: []byte(conf),
}
r, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, 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("eth0"))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
})
It("works with a 0.4.0 config, including Check", func() {
Expect(isFirewalldRunning()).To(BeTrue())
conf := `{
"cniVersion": "0.4.0",
"name": "firewalld-test",
"type": "firewall",
"backend": "firewalld",
"zone": "trusted",
"prevResult": {
"cniVersion": "0.4.0",
"interfaces": [
{"name": "eth0", "sandbox": "/foobar"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: ifname,
StdinData: []byte(conf),
}
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(fwd.zone).To(Equal("trusted"))
Expect(fwd.source).To(Equal("10.0.0.2/32"))
_, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(fwd.zone).To(Equal("trusted"))
Expect(fwd.source).To(Equal("10.0.0.2/32"))
})
})

View File

@ -0,0 +1,512 @@
// Copyright 2017 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"strings"
"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/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func findChains(chains []string) (bool, bool) {
var foundAdmin, foundPriv bool
for _, ch := range chains {
if ch == "CNI-ADMIN" {
foundAdmin = true
} else if ch == "CNI-FORWARD" {
foundPriv = true
}
}
return foundAdmin, foundPriv
}
func findForwardJumpRules(rules []string) (bool, bool) {
var foundAdmin, foundPriv bool
for _, rule := range rules {
if strings.Contains(rule, "-j CNI-ADMIN") {
foundAdmin = true
} else if strings.Contains(rule, "-j CNI-FORWARD") {
foundPriv = true
}
}
return foundAdmin, foundPriv
}
func findForwardAllowRules(rules []string, ip string) (bool, bool) {
var foundOne, foundTwo bool
for _, rule := range rules {
if !strings.HasSuffix(rule, "-j ACCEPT") {
continue
}
if strings.Contains(rule, fmt.Sprintf(" -s %s ", ip)) {
foundOne = true
} else if strings.Contains(rule, fmt.Sprintf(" -d %s ", ip)) && strings.Contains(rule, "RELATED,ESTABLISHED") {
foundTwo = true
}
}
return foundOne, foundTwo
}
func getPrevResult(bytes []byte) *current.Result {
type TmpConf struct {
types.NetConf
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
}
conf := &TmpConf{}
err := json.Unmarshal(bytes, conf)
Expect(err).NotTo(HaveOccurred())
if conf.RawPrevResult == nil {
return nil
}
resultBytes, err := json.Marshal(conf.RawPrevResult)
Expect(err).NotTo(HaveOccurred())
res, err := version.NewResult(conf.CNIVersion, resultBytes)
Expect(err).NotTo(HaveOccurred())
prevResult, err := current.NewResultFromResult(res)
Expect(err).NotTo(HaveOccurred())
return prevResult
}
func validateFullRuleset(bytes []byte) {
prevResult := getPrevResult(bytes)
for _, ip := range prevResult.IPs {
ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address))
Expect(err).NotTo(HaveOccurred())
// Ensure chains
chains, err := ipt.ListChains("filter")
Expect(err).NotTo(HaveOccurred())
foundAdmin, foundPriv := findChains(chains)
Expect(foundAdmin).To(Equal(true))
Expect(foundPriv).To(Equal(true))
// Look for the FORWARD chain jump rules to our custom chains
rules, err := ipt.List("filter", "FORWARD")
Expect(err).NotTo(HaveOccurred())
Expect(len(rules)).Should(BeNumerically(">", 1))
_, foundPriv = findForwardJumpRules(rules)
Expect(foundPriv).To(Equal(true))
// Look for the allow rules in our custom FORWARD chain
rules, err = ipt.List("filter", "CNI-FORWARD")
Expect(err).NotTo(HaveOccurred())
Expect(len(rules)).Should(BeNumerically(">", 1))
foundAdmin, _ = findForwardJumpRules(rules)
Expect(foundAdmin).To(Equal(true))
// Look for the IP allow rules
foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address))
Expect(foundOne).To(Equal(true))
Expect(foundTwo).To(Equal(true))
}
}
func validateCleanedUp(bytes []byte) {
prevResult := getPrevResult(bytes)
for _, ip := range prevResult.IPs {
ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address))
Expect(err).NotTo(HaveOccurred())
// Our private and admin chains don't get cleaned up
chains, err := ipt.ListChains("filter")
Expect(err).NotTo(HaveOccurred())
foundAdmin, foundPriv := findChains(chains)
Expect(foundAdmin).To(Equal(true))
Expect(foundPriv).To(Equal(true))
// Look for the FORWARD chain jump rules to our custom chains
rules, err := ipt.List("filter", "FORWARD")
Expect(err).NotTo(HaveOccurred())
_, foundPriv = findForwardJumpRules(rules)
Expect(foundPriv).To(Equal(true))
// Look for the allow rules in our custom FORWARD chain
rules, err = ipt.List("filter", "CNI-FORWARD")
Expect(err).NotTo(HaveOccurred())
foundAdmin, _ = findForwardJumpRules(rules)
Expect(foundAdmin).To(Equal(true))
// Expect no IP address rules for this IP
foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address))
Expect(foundOne).To(Equal(false))
Expect(foundTwo).To(Equal(false))
}
}
var _ = Describe("firewall plugin iptables backend", func() {
var originalNS, targetNS ns.NetNS
const IFNAME string = "dummy0"
fullConf := []byte(`{
"name": "test",
"type": "firewall",
"backend": "iptables",
"ifName": "dummy0",
"cniVersion": "0.3.1",
"prevResult": {
"interfaces": [
{"name": "dummy0"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"interface": 0
},
{
"version": "6",
"address": "2001:db8:1:2::1/64",
"interface": 0
}
]
}
}`)
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IFNAME,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
targetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(targetNS.Close()).To(Succeed())
})
It("passes prevResult through unchanged", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, 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(2))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
Expect(result.IPs[1].Address.String()).To(Equal("2001:db8:1:2::1/64"))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("installs the right iptables rules on the host", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("correctly handles a custom IptablesAdminChainName", func() {
conf := []byte(`{
"name": "test",
"type": "firewall",
"backend": "iptables",
"ifName": "dummy0",
"cniVersion": "0.3.1",
"iptablesAdminChainName": "CNI-foobar",
"prevResult": {
"interfaces": [
{"name": "dummy0"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"interface": 0
},
{
"version": "6",
"address": "2001:db8:1:2::1/64",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, conf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
var ipt *iptables.IPTables
for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} {
ipt, err = iptables.NewWithProtocol(proto)
Expect(err).NotTo(HaveOccurred())
// Ensure custom admin chain name
chains, err := ipt.ListChains("filter")
Expect(err).NotTo(HaveOccurred())
var foundAdmin bool
for _, ch := range chains {
if ch == "CNI-foobar" {
foundAdmin = true
}
}
Expect(foundAdmin).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("cleans up on delete", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
validateCleanedUp(fullConf)
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("installs the right iptables rules on the host v4.0.x and check is successful", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("cleans up on delete v4.0.x", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
validateCleanedUp(fullConf)
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
var _ = Describe("firewall plugin iptables backend v0.4.x", func() {
var originalNS, targetNS ns.NetNS
const IFNAME string = "dummy0"
fullConf := []byte(`{
"name": "test",
"type": "firewall",
"backend": "iptables",
"ifName": "dummy0",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{"name": "dummy0"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"interface": 0
},
{
"version": "6",
"address": "2001:db8:1:2::1/64",
"interface": 0
}
]
}
}`)
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IFNAME,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
targetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(targetNS.Close()).To(Succeed())
})
It("installs iptables rules, Check rules then cleans up on delete using v4.0.x", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: fullConf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
_, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
validateCleanedUp(fullConf)
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -0,0 +1,27 @@
// Copyright 2017 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestFirewall(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "firewall Suite")
}

View File

@ -0,0 +1,122 @@
// Copyright 2018 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"strings"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/godbus/dbus"
)
const (
dbusName = "org.freedesktop.DBus"
dbusPath = "/org/freedesktop/DBus"
dbusGetNameOwnerMethod = "GetNameOwner"
firewalldName = "org.fedoraproject.FirewallD1"
firewalldPath = "/org/fedoraproject/FirewallD1"
firewalldZoneInterface = "org.fedoraproject.FirewallD1.zone"
firewalldAddSourceMethod = "addSource"
firewalldRemoveSourceMethod = "removeSource"
firewalldQuerySourceMethod = "querySource"
errZoneAlreadySet = "ZONE_ALREADY_SET"
)
// Only used for testcases to override the D-Bus connection
var testConn *dbus.Conn
type fwdBackend struct {
conn *dbus.Conn
}
// fwdBackend implements the FirewallBackend interface
var _ FirewallBackend = &fwdBackend{}
func getConn() (*dbus.Conn, error) {
if testConn != nil {
return testConn, nil
}
return dbus.SystemBus()
}
// isFirewalldRunning checks whether firewalld is running.
func isFirewalldRunning() bool {
conn, err := getConn()
if err != nil {
return false
}
dbusObj := conn.Object(dbusName, dbusPath)
var res string
if err := dbusObj.Call(dbusName+"."+dbusGetNameOwnerMethod, 0, firewalldName).Store(&res); err != nil {
return false
}
return true
}
func newFirewalldBackend(conf *FirewallNetConf) (FirewallBackend, error) {
conn, err := getConn()
if err != nil {
return nil, err
}
backend := &fwdBackend{
conn: conn,
}
return backend, nil
}
func (fb *fwdBackend) Add(conf *FirewallNetConf, result *current.Result) error {
for _, ip := range result.IPs {
ipStr := ipString(ip.Address)
// Add a firewalld rule which assigns the given source IP to the given zone
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
var res string
if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldAddSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil {
if !strings.Contains(err.Error(), errZoneAlreadySet) {
return fmt.Errorf("failed to add the address %v to %v zone: %v", ipStr, conf.FirewalldZone, err)
}
}
}
return nil
}
func (fb *fwdBackend) Del(conf *FirewallNetConf, result *current.Result) error {
for _, ip := range result.IPs {
ipStr := ipString(ip.Address)
// Remove firewalld rules which assigned the given source IP to the given zone
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
var res string
firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res)
}
return nil
}
func (fb *fwdBackend) Check(conf *FirewallNetConf, result *current.Result) error {
for _, ip := range result.IPs {
ipStr := ipString(ip.Address)
// Check for a firewalld rule for the given source IP to the given zone
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
var res bool
if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldQuerySourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil {
return fmt.Errorf("failed to find the address %v in %v zone", ipStr, conf.FirewalldZone)
}
}
return nil
}

View File

@ -0,0 +1,279 @@
// 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 (
"fmt"
"net"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/coreos/go-iptables/iptables"
)
func getPrivChainRules(ip string) [][]string {
var rules [][]string
rules = append(rules, []string{"-d", ip, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"})
rules = append(rules, []string{"-s", ip, "-j", "ACCEPT"})
return rules
}
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
chains, err := ipt.ListChains(table)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
return nil
}
}
return ipt.NewChain(table, chain)
}
func generateFilterRule(privChainName string) []string {
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
}
func generateAdminRule(adminChainName string) []string {
return []string{"-m", "comment", "--comment", "CNI firewall plugin admin overrides", "-j", adminChainName}
}
func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) {
for _, rule := range rules {
ipt.Delete("filter", privChainName, rule...)
}
}
func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) error {
exists, err := ipt.Exists("filter", chain, rule...)
if !exists && err == nil {
err = ipt.Insert("filter", chain, 1, rule...)
}
return err
}
func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
privRule := generateFilterRule(ib.privChainName)
adminRule := generateFilterRule(ib.adminChainName)
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}
// Ensure our filter rule exists in the forward chain
if err := ensureFirstChainRule(ipt, "FORWARD", privRule); err != nil {
return err
}
// Ensure our admin override chain rule exists in our private chain
if err := ensureFirstChainRule(ipt, ib.privChainName, adminRule); err != nil {
return err
}
return nil
}
func protoForIP(ip net.IPNet) iptables.Protocol {
if ip.IP.To4() != nil {
return iptables.ProtocolIPv4
}
return iptables.ProtocolIPv6
}
func (ib *iptablesBackend) addRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
rules := make([][]string, 0)
for _, ip := range result.IPs {
if protoForIP(ip.Address) == proto {
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
}
}
if len(rules) > 0 {
if err := ib.setupChains(ipt); err != nil {
return err
}
// Clean up on any errors
var err error
defer func() {
if err != nil {
cleanupRules(ipt, ib.privChainName, rules)
}
}()
for _, rule := range rules {
err = ipt.AppendUnique("filter", ib.privChainName, rule...)
if err != nil {
return err
}
}
}
return nil
}
func (ib *iptablesBackend) delRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
rules := make([][]string, 0)
for _, ip := range result.IPs {
if protoForIP(ip.Address) == proto {
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
}
}
if len(rules) > 0 {
cleanupRules(ipt, ib.privChainName, rules)
}
return nil
}
func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
rules := make([][]string, 0)
for _, ip := range result.IPs {
if protoForIP(ip.Address) == proto {
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
}
}
if len(rules) <= 0 {
return nil
}
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}
// Ensure our filter rule exists in the forward chain
privRule := generateFilterRule(ib.privChainName)
privExists, err := ipt.Exists("filter", "FORWARD", privRule...)
if err != nil {
return err
}
if !privExists {
return fmt.Errorf("expected %v rule %v not found", "FORWARD", privRule)
}
// Ensure our admin override chain rule exists in our private chain
adminRule := generateFilterRule(ib.adminChainName)
adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...)
if err != nil {
return err
}
if !adminExists {
return fmt.Errorf("expected %v rule %v not found", ib.privChainName, adminRule)
}
// ensure rules for this IP address exist
for _, rule := range rules {
// Ensure our rule exists in our private chain
exists, err := ipt.Exists("filter", ib.privChainName, rule...)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("expected rule %v not found", rule)
}
}
return nil
}
func findProtos(conf *FirewallNetConf) []iptables.Protocol {
protos := []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6}
if conf.PrevResult != nil {
// If PrevResult is given, scan all IP addresses to figure out
// which IP versions to use
protos = []iptables.Protocol{}
result, _ := current.NewResultFromResult(conf.PrevResult)
for _, addr := range result.IPs {
if addr.Address.IP.To4() != nil {
protos = append(protos, iptables.ProtocolIPv4)
} else {
protos = append(protos, iptables.ProtocolIPv6)
}
}
}
return protos
}
type iptablesBackend struct {
protos map[iptables.Protocol]*iptables.IPTables
privChainName string
adminChainName string
ifName string
}
// iptablesBackend implements the FirewallBackend interface
var _ FirewallBackend = &iptablesBackend{}
func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) {
adminChainName := conf.IptablesAdminChainName
if adminChainName == "" {
adminChainName = "CNI-ADMIN"
}
backend := &iptablesBackend{
privChainName: "CNI-FORWARD",
adminChainName: adminChainName,
protos: make(map[iptables.Protocol]*iptables.IPTables),
}
for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} {
ipt, err := iptables.NewWithProtocol(proto)
if err != nil {
return nil, fmt.Errorf("could not initialize iptables protocol %v: %v", proto, err)
}
backend.protos[proto] = ipt
}
return backend, nil
}
func (ib *iptablesBackend) Add(conf *FirewallNetConf, result *current.Result) error {
for proto, ipt := range ib.protos {
if err := ib.addRules(conf, result, ipt, proto); err != nil {
return err
}
}
return nil
}
func (ib *iptablesBackend) Del(conf *FirewallNetConf, result *current.Result) error {
for proto, ipt := range ib.protos {
ib.delRules(conf, result, ipt, proto)
}
return nil
}
func (ib *iptablesBackend) Check(conf *FirewallNetConf, result *current.Result) error {
for proto, ipt := range ib.protos {
if err := ib.checkRules(conf, result, ipt, proto); err != nil {
return err
}
}
return nil
}

View File

@ -34,6 +34,8 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const (
@ -217,7 +219,7 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, bv.BuildString("flannel"))
}
func cmdGet(args *skel.CmdArgs) error {

View File

@ -29,6 +29,8 @@ type chain struct {
entryRules [][]string // the rules that "point" to this chain
rules [][]string // the rules this chain contains
prependEntry bool // whether or not the entry rules should be prepended
}
// setup idempotently creates the chain. It will not error if the chain exists.
@ -45,19 +47,19 @@ func (c *chain) setup(ipt *iptables.IPTables) error {
}
// Add the rules to the chain
for i := len(c.rules) - 1; i >= 0; i-- {
if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil {
for _, rule := range c.rules {
if err := insertUnique(ipt, c.table, c.name, false, rule); err != nil {
return err
}
}
// Add the entry rules to the entry chains
for _, entryChain := range c.entryChains {
for i := len(c.entryRules) - 1; i >= 0; i-- {
for _, rule := range c.entryRules {
r := []string{}
r = append(r, c.entryRules[i]...)
r = append(r, rule...)
r = append(r, "-j", c.name)
if err := prependUnique(ipt, c.table, entryChain, r); err != nil {
if err := insertUnique(ipt, c.table, entryChain, c.prependEntry, r); err != nil {
return err
}
}
@ -105,8 +107,9 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
return nil
}
// prependUnique will prepend a rule to a chain, if it does not already exist
func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error {
// insertUnique will add a rule to a chain if it does not already exist.
// By default the rule is appended, unless prepend is true.
func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rule []string) error {
exists, err := ipt.Exists(table, chain, rule...)
if err != nil {
return err
@ -115,7 +118,11 @@ func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) e
return nil
}
if prepend {
return ipt.Insert(table, chain, 1, rule...)
} else {
return ipt.Append(table, chain, rule...)
}
}
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
@ -131,3 +138,44 @@ func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, err
}
return false, nil
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("chain %s not found in iptables table %s", c.name, c.table)
}
for i := len(c.rules) - 1; i >= 0; i-- {
match := checkRule(ipt, c.table, c.name, c.rules[i])
if !match {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.rules, c.name, c.table)
}
}
for _, entryChain := range c.entryChains {
for i := len(c.entryRules) - 1; i >= 0; i-- {
r := []string{}
r = append(r, c.entryRules[i]...)
r = append(r, "-j", c.name)
matchEntryChain := checkRule(ipt, c.table, entryChain, r)
if !matchEntryChain {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.entryRules, entryChain, c.table)
}
}
}
return nil
}
func checkRule(ipt *iptables.IPTables, table, chain string, rule []string) bool {
exists, err := ipt.Exists(table, chain, rule...)
if err != nil {
return false
}
return exists
}

View File

@ -117,8 +117,8 @@ var _ = Describe("chain tests", func() {
Expect(err).NotTo(HaveOccurred())
Expect(haveRules).To(Equal([]string{
"-N " + tlChainName,
"-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name,
"-A " + tlChainName + ` -m comment --comment "canary value" -j ACCEPT`,
"-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name,
}))
// Check that the chain and rule was created

View File

@ -34,6 +34,8 @@ import (
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// PortMapEntry corresponds to a single entry in the port_mappings argument,
@ -55,8 +57,6 @@ type PortMapConf struct {
RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
} `json:"runtimeConfig,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
// These are fields parsed out of the config or the environment;
// included here for convenience
@ -70,7 +70,7 @@ type PortMapConf struct {
const DefaultMarkBit = 13
func cmdAdd(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName)
netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return fmt.Errorf("failed to parse config: %v", err)
}
@ -102,7 +102,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName)
netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return fmt.Errorf("failed to parse config: %v", err)
}
@ -118,37 +118,60 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
conf, result, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return err
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
}
if len(conf.RuntimeConfig.PortMaps) == 0 {
return nil
}
conf.ContainerID = args.ContainerID
if conf.ContIPv4 != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}
if conf.ContIPv6 != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
}
return nil
}
// parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, error) {
conf := PortMapConf{}
if err := json.Unmarshal(stdin, &conf); err != nil {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
return nil, nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
// Parse previous result.
var result *current.Result
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
result, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
@ -158,7 +181,7 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil {
return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
return nil, nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
}
if conf.MarkMasqBit == nil {
@ -167,21 +190,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 {
return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
}
// Reject invalid port numbers
for _, pm := range conf.RuntimeConfig.PortMaps {
if pm.ContainerPort <= 0 {
return nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
return nil, nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
}
if pm.HostPort <= 0 {
return nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
return nil, nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
}
}
if conf.PrevResult != nil {
for _, ip := range conf.PrevResult.IPs {
for _, ip := range result.IPs {
if ip.Version == "6" && conf.ContIPv6 != nil {
continue
} else if ip.Version == "4" && conf.ContIPv4 != nil {
@ -192,9 +215,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
if ip.Interface != nil {
intIdx := *ip.Interface
if intIdx >= 0 &&
intIdx < len(conf.PrevResult.Interfaces) &&
(conf.PrevResult.Interfaces[intIdx].Name != ifName ||
conf.PrevResult.Interfaces[intIdx].Sandbox == "") {
intIdx < len(result.Interfaces) &&
(result.Interfaces[intIdx].Name != ifName ||
result.Interfaces[intIdx].Sandbox == "") {
continue
}
}
@ -207,5 +230,5 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
}
return &conf, nil
return &conf, result, nil
}

View File

@ -111,6 +111,46 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
return nil
}
func checkPorts(config *PortMapConf, containerIP net.IP) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerIP)
ip4t := maybeGetIptables(false)
ip6t := maybeGetIptables(true)
if ip4t == nil && ip6t == nil {
return fmt.Errorf("neither iptables nor ip6tables usable")
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip4t); err != nil {
return fmt.Errorf("could not check ipv4 dnat: %v", err)
}
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip6t); err != nil {
return fmt.Errorf("could not check ipv6 dnat: %v", err)
}
}
return nil
}
// genToplevelDnatChain creates the top-level summary chain that we'll
// add our chain to. This is easy, because creating chains is idempotent.
// IMPORTANT: do not change this, or else upgrading plugins will require
@ -255,6 +295,10 @@ func genMarkMasqChain(markBit int) chain {
table: "nat",
name: MarkMasqChainName,
entryChains: []string{"POSTROUTING"},
// Only this entry chain needs to be prepended, because otherwise it is
// stomped on by the masquerading rules created by the CNI ptp and bridge
// plugins.
prependEntry: true,
entryRules: [][]string{{
"-m", "comment",
"--comment", "CNI portfwd requiring masquerade",

View File

@ -165,6 +165,12 @@ var _ = Describe("portmap integration tests", func() {
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")

View File

@ -68,7 +68,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
c, err := parseConfig(configBytes, "container")
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -91,7 +91,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"]
}`)
c, err := parseConfig(configBytes, "container")
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -115,7 +115,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
_, err := parseConfig(configBytes, "container")
_, _, err := parseConfig(configBytes, "container")
Expect(err).To(MatchError("Invalid host port number: 0"))
})
@ -143,7 +143,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
_, err := parseConfig(configBytes, "container")
_, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
})
})
@ -175,7 +175,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"]
}`)
conf, err := parseConfig(configBytes, "foo")
conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID
@ -271,7 +271,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"]
}`)
conf, err := parseConfig(configBytes, "foo")
conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID
@ -323,6 +323,7 @@ var _ = Describe("portmapping configuration", func() {
"--mark", "0x20/0x20",
"-j", "MASQUERADE",
}},
prependEntry: true,
}))
})
})

View File

@ -22,13 +22,15 @@ import (
"net"
"github.com/alexflint/go-filemutex"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const firstTableID = 100
@ -370,7 +372,7 @@ RULE_LOOP:
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, bv.BuildString("sbr"))
}
func cmdGet(args *skel.CmdArgs) error {

View File

@ -25,20 +25,21 @@ import (
"path/filepath"
"strings"
"github.com/vishvananda/netlink"
"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/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// TuningConf represents the network tuning configuration.
type TuningConf struct {
types.NetConf
SysCtl map[string]string `json:"sysctl"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
Mac string `json:"mac,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
@ -68,23 +69,6 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
}
}
// Parse previous result.
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil
}
@ -111,7 +95,15 @@ func changeMacAddr(ifName string, newMacAddr string) error {
}
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
for _, i := range config.PrevResult.Interfaces {
// Parse previous result.
if config.PrevResult == nil {
return
}
version.ParsePrevResult(&config.NetConf)
result, _ := current.NewResultFromResult(config.PrevResult)
for _, i := range result.Interfaces {
if i.Name == ifName {
i.Mac = newMacAddr
}
@ -144,6 +136,20 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
// The directory /proc/sys/net is per network namespace. Enter in the
// network namespace before writing on it.
@ -199,11 +205,80 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning"))
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
tuningConf, err := parseConf(args.StdinData, args.Args)
if err != nil {
return err
}
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
// Check each configured value vs what's currently in the container
for key, conf_value := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
cur_value := strings.TrimSuffix(string(contents), "\n")
if conf_value != cur_value {
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
}
}
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", args.IfName)
}
if tuningConf.Mac != "" {
if tuningConf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Error: Tuning configured Ethernet of %s is %s, current value is %s",
args.IfName, tuningConf.Mac, link.Attrs().HardwareAddr)
}
}
if tuningConf.Promisc {
if link.Attrs().Promisc == 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
} else {
if link.Attrs().Promisc != 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
}
if tuningConf.Mtu != 0 {
if tuningConf.Mtu != link.Attrs().MTU {
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
args.IfName, tuningConf.Mtu, link.Attrs().MTU)
}
}
return nil
})
if err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,11 @@
package main
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -27,6 +31,49 @@ import (
. "github.com/onsi/gomega"
)
func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.Result) (*TuningConf, []byte, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, nil, err
}
conf := &TuningConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("tuning plugin", func() {
var originalNS ns.NetNS
const IFNAME string = "dummy0"
@ -342,4 +389,296 @@ var _ = Describe("tuning plugin", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures promiscuous mode with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"promisc": true,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Promisc).To(Equal(1))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mtu with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mtu": 1454,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().MTU).To(Equal(1454))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from conf file) with CNI v4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mac": "c2:11:22:33:44:55",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66",
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, 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))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:66")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -25,6 +25,8 @@ import (
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
// PluginConf is whatever you expect your configuration json to be. This is whatever
@ -92,13 +94,22 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
// Remove this if this is an "originating" plugin
if conf.PrevResult == nil {
return fmt.Errorf("must be called as chained plugin")
}
// Uncomment if this is an "originating" plugin
//if conf.PrevResult != nil {
// return fmt.Errorf("must be called as the first plugin")
// }
// This is some sample code to generate the list of container-side IPs.
// We're casting the prevResult to a 0.3.0 response, which can also include
// host-side IPs (but doesn't when converted from a 0.2.0 response).
//
// You don't need this if you are writing an "originating" plugin.
containerIPs := make([]net.IP, 0, len(conf.PrevResult.IPs))
if conf.CNIVersion != "0.3.0" {
for _, ip := range conf.PrevResult.IPs {
@ -123,6 +134,8 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("got no container IPs")
}
// Implement your plugin here
// Pass through the result for the next plugin
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
}
@ -141,11 +154,11 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
// replace TODO with your plugin name
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO"))
}
func cmdGet(args *skel.CmdArgs) error {
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
}

View File

@ -2,11 +2,12 @@
set -xe
SRC_DIR="${SRC_DIR:-$PWD}"
DOCKER="${DOCKER:-docker}"
TAG=$(git describe --tags --dirty)
RELEASE_DIR=release-${TAG}
BUILDFLAGS="-ldflags '-extldflags -static -X main._buildVersion=${TAG}'"
BUILDFLAGS="-ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${TAG}'"
OUTPUT_DIR=bin
@ -15,7 +16,7 @@ rm -Rf ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${OUTPUT_DIR}
docker run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \
$DOCKER run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \
/bin/sh -xe -c "\
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;

View File

@ -6,35 +6,53 @@
#
set -e
# switch into the repo root directory
cd "$(dirname $0)"
# Build all plugins before testing
source ./build_linux.sh
echo "Running tests"
GINKGO_FLAGS="-p --randomizeAllSpecs --randomizeSuites --failOnPending --progress --skipPackage=pkg/hns"
function testrun {
sudo -E bash -c "umask 0; cd ${GOPATH}/src; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test $@"
}
# user has not provided PKG override
if [ -z "$PKG" ]; then
GINKGO_FLAGS="$GINKGO_FLAGS -r ."
LINT_TARGETS="./..."
COVERALLS=${COVERALLS:-""}
# user has provided PKG override
if [ -n "${COVERALLS}" ]; then
echo "with coverage profile generation..."
else
GINKGO_FLAGS="$GINKGO_FLAGS $PKG"
LINT_TARGETS="$PKG"
echo "without coverage profile generation..."
fi
sudo -E bash -c "umask 0; cd ${GOPATH}/src/${REPO_PATH}; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} ginkgo ${GINKGO_FLAGS}"
PKG=${PKG:-$(cd ${GOPATH}/src/${REPO_PATH}; go list ./... | xargs echo)}
# coverage profile only works per-package
i=0
for t in ${PKG}; do
if [ -n "${COVERALLS}" ]; then
COVERFLAGS="-covermode set -coverprofile ${i}.coverprofile"
fi
testrun "${COVERFLAGS:-""} ${t}"
i=$((i+1))
done
# Submit coverage information
if [ -n "${COVERALLS}" ]; then
gover
goveralls -service=travis-ci -coverprofile=gover.coverprofile
fi
cd ${GOPATH}/src/${REPO_PATH};
echo "Checking gofmt..."
fmtRes=$(go fmt $LINT_TARGETS)
fmtRes=$(go fmt $PKG)
if [ -n "${fmtRes}" ]; then
echo -e "go fmt checking failed:\n${fmtRes}"
exit 255
fi
echo "Checking govet..."
vetRes=$(go vet $LINT_TARGETS)
vetRes=$(go vet $PKG)
if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255

View File

@ -126,7 +126,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
// These capabilities arguments are filtered through the plugin's advertised
// capabilities from its config JSON, and any keys in the CapabilityArgs
// matching plugin capabilities are added to the "runtimeConfig" dictionary
// sent to the plugin via JSON on stdin. For exmaple, if the plugin's
// sent to the plugin via JSON on stdin. For example, if the plugin's
// capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin.

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin dragonfly freebsd linux netbsd opensbd solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package invoke

View File

@ -291,7 +291,7 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio
// The caller must also specify what CNI spec versions the plugin supports.
//
// The caller can specify an "about" string, which is printed on stderr
// when no CNI_COMMAND is specified. The reccomended output is "CNI plugin <foo> v<version>"
// when no CNI_COMMAND is specified. The recommended output is "CNI plugin <foo> v<version>"
//
// When an error occurs in either cmdAdd, cmdCheck, or cmdDel, PluginMain will print the error
// as JSON to stdout and call os.Exit(1).

View File

@ -17,6 +17,7 @@ package types020
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
@ -73,11 +74,15 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) {
}
func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
_, err = writer.Write(data)
return err
}

View File

@ -17,6 +17,7 @@ package current
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
@ -75,13 +76,9 @@ func convertFrom020(result types.Result) (*Result, error) {
Gateway: oldResult.IP4.Gateway,
})
for _, route := range oldResult.IP4.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP4.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
GW: route.GW,
})
}
}
@ -93,13 +90,9 @@ func convertFrom020(result types.Result) (*Result, error) {
Gateway: oldResult.IP6.Gateway,
})
for _, route := range oldResult.IP6.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP6.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
GW: route.GW,
})
}
}
@ -202,11 +195,15 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) {
}
func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
_, err = writer.Write(data)
return err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"os"
)
@ -87,7 +88,7 @@ type ResultFactoryFunc func([]byte) (Result, error)
// Result is an interface that provides the result of plugin execution
type Result interface {
// The highest CNI specification result verison the result supports
// The highest CNI specification result version the result supports
// without having to convert
Version() string
@ -98,6 +99,9 @@ type Result interface {
// Prints the result in JSON format to stdout
Print() error
// Prints the result in JSON format to provided writer
PrintTo(writer io.Writer) error
// Returns a JSON string representation of the result
String() string
}

View File

@ -86,9 +86,13 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
// minor, and micro numbers or returns an error
func ParseVersion(version string) (int, int, int, error) {
var major, minor, micro int
if version == "" {
return -1, -1, -1, fmt.Errorf("invalid version %q: the version is empty", version)
}
parts := strings.Split(version, ".")
if len(parts) == 0 || len(parts) >= 4 {
return -1, -1, -1, fmt.Errorf("invalid version %q: too many or too few parts", version)
if len(parts) >= 4 {
return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version)
}
major, err := strconv.Atoi(parts[0])
@ -114,7 +118,7 @@ func ParseVersion(version string) (int, int, int, error) {
}
// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro
// nubmers, and compares them to determine whether the first version is greater
// numbers, and compares them to determine whether the first version is greater
// than or equal to the second
func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)

40
vendor/github.com/godbus/dbus/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,40 @@
dist: precise
language: go
go_import_path: github.com/godbus/dbus
sudo: true
go:
- 1.6.3
- 1.7.3
- tip
env:
global:
matrix:
- TARGET=amd64
- TARGET=arm64
- TARGET=arm
- TARGET=386
- TARGET=ppc64le
matrix:
fast_finish: true
allow_failures:
- go: tip
exclude:
- go: tip
env: TARGET=arm
- go: tip
env: TARGET=arm64
- go: tip
env: TARGET=386
- go: tip
env: TARGET=ppc64le
addons:
apt:
packages:
- dbus
- dbus-x11
before_install:

50
vendor/github.com/godbus/dbus/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,50 @@
# How to Contribute
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.markdown) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

25
vendor/github.com/godbus/dbus/LICENSE generated vendored Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. 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.
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
HOLDER 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.

3
vendor/github.com/godbus/dbus/MAINTAINERS generated vendored Normal file
View File

@ -0,0 +1,3 @@
Brandon Philips <brandon@ifup.org> (@philips)
Brian Waldon <brian@waldon.cc> (@bcwaldon)
John Southworth <jsouthwo@brocade.com> (@jsouthworth)

44
vendor/github.com/godbus/dbus/README.markdown generated vendored Normal file
View File

@ -0,0 +1,44 @@
[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus)
dbus
----
dbus is a simple library that implements native Go client bindings for the
D-Bus message bus system.
### Features
* Complete native implementation of the D-Bus message protocol
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
* Subpackages that help with the introspection / property interfaces
### Installation
This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
```
go get github.com/godbus/dbus
```
If you want to use the subpackages, you can install them the same way.
### Usage
The complete package documentation and some simple examples are available at
[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
gives a short overview over the basic usage.
#### Projects using godbus
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
Please note that the API is considered unstable for now and may change without
further notice.
### License
go.dbus is available under the Simplified BSD License; see LICENSE for the full
text.
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.

252
vendor/github.com/godbus/dbus/auth.go generated vendored Normal file
View File

@ -0,0 +1,252 @@
package dbus
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"strconv"
)
// AuthStatus represents the Status of an authentication mechanism.
type AuthStatus byte
const (
// AuthOk signals that authentication is finished; the next command
// from the server should be an OK.
AuthOk AuthStatus = iota
// AuthContinue signals that additional data is needed; the next command
// from the server should be a DATA.
AuthContinue
// AuthError signals an error; the server sent invalid data or some
// other unexpected thing happened and the current authentication
// process should be aborted.
AuthError
)
type authState byte
const (
waitingForData authState = iota
waitingForOk
waitingForReject
)
// Auth defines the behaviour of an authentication mechanism.
type Auth interface {
// Return the name of the mechnism, the argument to the first AUTH command
// and the next status.
FirstData() (name, resp []byte, status AuthStatus)
// Process the given DATA command, and return the argument to the DATA
// command and the next status. If len(resp) == 0, no DATA command is sent.
HandleData(data []byte) (resp []byte, status AuthStatus)
}
// Auth authenticates the connection, trying the given list of authentication
// mechanisms (in that order). If nil is passed, the EXTERNAL and
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
// connections, this method must be called before sending any messages to the
// bus. Auth must not be called on shared connections.
func (conn *Conn) Auth(methods []Auth) error {
if methods == nil {
uid := strconv.Itoa(os.Getuid())
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
}
in := bufio.NewReader(conn.transport)
err := conn.transport.SendNullByte()
if err != nil {
return err
}
err = authWriteLine(conn.transport, []byte("AUTH"))
if err != nil {
return err
}
s, err := authReadLine(in)
if err != nil {
return err
}
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
return errors.New("dbus: authentication protocol error")
}
s = s[1:]
for _, v := range s {
for _, m := range methods {
if name, data, status := m.FirstData(); bytes.Equal(v, name) {
var ok bool
err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data)
if err != nil {
return err
}
switch status {
case AuthOk:
err, ok = conn.tryAuth(m, waitingForOk, in)
case AuthContinue:
err, ok = conn.tryAuth(m, waitingForData, in)
default:
panic("dbus: invalid authentication status")
}
if err != nil {
return err
}
if ok {
if conn.transport.SupportsUnixFDs() {
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
if err != nil {
return err
}
line, err := authReadLine(in)
if err != nil {
return err
}
switch {
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
conn.EnableUnixFDs()
conn.unixFD = true
case bytes.Equal(line[0], []byte("ERROR")):
default:
return errors.New("dbus: authentication protocol error")
}
}
err = authWriteLine(conn.transport, []byte("BEGIN"))
if err != nil {
return err
}
go conn.inWorker()
return nil
}
}
}
}
return errors.New("dbus: authentication failed")
}
// tryAuth tries to authenticate with m as the mechanism, using state as the
// initial authState and in for reading input. It returns (nil, true) on
// success, (nil, false) on a REJECTED and (someErr, false) if some other
// error occured.
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
for {
s, err := authReadLine(in)
if err != nil {
return err, false
}
switch {
case state == waitingForData && string(s[0]) == "DATA":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
continue
}
data, status := m.HandleData(s[1])
switch status {
case AuthOk, AuthContinue:
if len(data) != 0 {
err = authWriteLine(conn.transport, []byte("DATA"), data)
if err != nil {
return err, false
}
}
if status == AuthOk {
state = waitingForOk
}
case AuthError:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
}
case state == waitingForData && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForData && string(s[0]) == "ERROR":
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForData && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForData:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForOk && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForOk && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForOk && (string(s[0]) == "DATA" ||
string(s[0]) == "ERROR"):
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForOk:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForReject && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForReject:
return errors.New("dbus: authentication protocol error"), false
default:
panic("dbus: invalid auth state")
}
}
}
// authReadLine reads a line and separates it into its fields.
func authReadLine(in *bufio.Reader) ([][]byte, error) {
data, err := in.ReadBytes('\n')
if err != nil {
return nil, err
}
data = bytes.TrimSuffix(data, []byte("\r\n"))
return bytes.Split(data, []byte{' '}), nil
}
// authWriteLine writes the given line in the authentication protocol format
// (elements of data separated by a " " and terminated by "\r\n").
func authWriteLine(out io.Writer, data ...[]byte) error {
buf := make([]byte, 0)
for i, v := range data {
buf = append(buf, v...)
if i != len(data)-1 {
buf = append(buf, ' ')
}
}
buf = append(buf, '\r')
buf = append(buf, '\n')
n, err := out.Write(buf)
if err != nil {
return err
}
if n != len(buf) {
return io.ErrUnexpectedEOF
}
return nil
}

26
vendor/github.com/godbus/dbus/auth_external.go generated vendored Normal file
View File

@ -0,0 +1,26 @@
package dbus
import (
"encoding/hex"
)
// AuthExternal returns an Auth that authenticates as the given user with the
// EXTERNAL mechanism.
func AuthExternal(user string) Auth {
return authExternal{user}
}
// AuthExternal implements the EXTERNAL authentication mechanism.
type authExternal struct {
user string
}
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("EXTERNAL"), b, AuthOk
}
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
return nil, AuthError
}

102
vendor/github.com/godbus/dbus/auth_sha1.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
package dbus
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"os"
)
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
// directory of the user.
func AuthCookieSha1(user, home string) Auth {
return authCookieSha1{user, home}
}
type authCookieSha1 struct {
user, home string
}
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
}
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
challenge := make([]byte, len(data)/2)
_, err := hex.Decode(challenge, data)
if err != nil {
return nil, AuthError
}
b := bytes.Split(challenge, []byte{' '})
if len(b) != 3 {
return nil, AuthError
}
context := b[0]
id := b[1]
svchallenge := b[2]
cookie := a.getCookie(context, id)
if cookie == nil {
return nil, AuthError
}
clchallenge := a.generateChallenge()
if clchallenge == nil {
return nil, AuthError
}
hash := sha1.New()
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
hexhash := make([]byte, 2*hash.Size())
hex.Encode(hexhash, hash.Sum(nil))
data = append(clchallenge, ' ')
data = append(data, hexhash...)
resp := make([]byte, 2*len(data))
hex.Encode(resp, data)
return resp, AuthOk
}
// getCookie searches for the cookie identified by id in context and returns
// the cookie content or nil. (Since HandleData can't return a specific error,
// but only whether an error occured, this function also doesn't bother to
// return an error.)
func (a authCookieSha1) getCookie(context, id []byte) []byte {
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
if err != nil {
return nil
}
defer file.Close()
rd := bufio.NewReader(file)
for {
line, err := rd.ReadBytes('\n')
if err != nil {
return nil
}
line = line[:len(line)-1]
b := bytes.Split(line, []byte{' '})
if len(b) != 3 {
return nil
}
if bytes.Equal(b[0], id) {
return b[2]
}
}
}
// generateChallenge returns a random, hex-encoded challenge, or nil on error
// (see above).
func (a authCookieSha1) generateChallenge() []byte {
b := make([]byte, 16)
n, err := rand.Read(b)
if err != nil {
return nil
}
if n != 16 {
return nil
}
enc := make([]byte, 32)
hex.Encode(enc, b)
return enc
}

36
vendor/github.com/godbus/dbus/call.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package dbus
import (
"errors"
)
// Call represents a pending or completed method call.
type Call struct {
Destination string
Path ObjectPath
Method string
Args []interface{}
// Strobes when the call is complete.
Done chan *Call
// After completion, the error status. If this is non-nil, it may be an
// error message from the peer (with Error as its type) or some other error.
Err error
// Holds the response once the call is done.
Body []interface{}
}
var errSignature = errors.New("dbus: mismatched signature")
// Store stores the body of the reply into the provided pointers. It returns
// an error if the signatures of the body and retvalues don't match, or if
// the error status is not nil.
func (c *Call) Store(retvalues ...interface{}) error {
if c.Err != nil {
return c.Err
}
return Store(c.Body, retvalues...)
}

767
vendor/github.com/godbus/dbus/conn.go generated vendored Normal file
View File

@ -0,0 +1,767 @@
package dbus
import (
"errors"
"io"
"os"
"reflect"
"strings"
"sync"
)
var (
systemBus *Conn
systemBusLck sync.Mutex
sessionBus *Conn
sessionBusLck sync.Mutex
sessionEnvLck sync.Mutex
)
// ErrClosed is the error returned by calls on a closed connection.
var ErrClosed = errors.New("dbus: connection closed by user")
// Conn represents a connection to a message bus (usually, the system or
// session bus).
//
// Connections are either shared or private. Shared connections
// are shared between calls to the functions that return them. As a result,
// the methods Close, Auth and Hello must not be called on them.
//
// Multiple goroutines may invoke methods on a connection simultaneously.
type Conn struct {
transport
busObj BusObject
unixFD bool
uuid string
names *nameTracker
serialGen *serialGenerator
calls *callTracker
handler Handler
outHandler *outputHandler
signalHandler SignalHandler
eavesdropped chan<- *Message
eavesdroppedLck sync.Mutex
}
// SessionBus returns a shared connection to the session bus, connecting to it
// if not already done.
func SessionBus() (conn *Conn, err error) {
sessionBusLck.Lock()
defer sessionBusLck.Unlock()
if sessionBus != nil {
return sessionBus, nil
}
defer func() {
if conn != nil {
sessionBus = conn
}
}()
conn, err = SessionBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
func getSessionBusAddress() (string, error) {
sessionEnvLck.Lock()
defer sessionEnvLck.Unlock()
address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
if address != "" && address != "autolaunch:" {
return address, nil
}
return getSessionBusPlatformAddress()
}
// SessionBusPrivate returns a new private connection to the session bus.
func SessionBusPrivate() (*Conn, error) {
address, err := getSessionBusAddress()
if err != nil {
return nil, err
}
return Dial(address)
}
// SessionBusPrivate returns a new private connection to the session bus.
func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
address, err := getSessionBusAddress()
if err != nil {
return nil, err
}
return DialHandler(address, handler, signalHandler)
}
// SystemBus returns a shared connection to the system bus, connecting to it if
// not already done.
func SystemBus() (conn *Conn, err error) {
systemBusLck.Lock()
defer systemBusLck.Unlock()
if systemBus != nil {
return systemBus, nil
}
defer func() {
if conn != nil {
systemBus = conn
}
}()
conn, err = SystemBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
// SystemBusPrivate returns a new private connection to the system bus.
func SystemBusPrivate() (*Conn, error) {
return Dial(getSystemBusPlatformAddress())
}
// SystemBusPrivateHandler returns a new private connection to the system bus, using the provided handlers.
func SystemBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
return DialHandler(getSystemBusPlatformAddress(), handler, signalHandler)
}
// Dial establishes a new private connection to the message bus specified by address.
func Dial(address string) (*Conn, error) {
tr, err := getTransport(address)
if err != nil {
return nil, err
}
return newConn(tr, NewDefaultHandler(), NewDefaultSignalHandler())
}
// DialHandler establishes a new private connection to the message bus specified by address, using the supplied handlers.
func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) {
tr, err := getTransport(address)
if err != nil {
return nil, err
}
return newConn(tr, handler, signalHandler)
}
// NewConn creates a new private *Conn from an already established connection.
func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
return NewConnHandler(conn, NewDefaultHandler(), NewDefaultSignalHandler())
}
// NewConnHandler creates a new private *Conn from an already established connection, using the supplied handlers.
func NewConnHandler(conn io.ReadWriteCloser, handler Handler, signalHandler SignalHandler) (*Conn, error) {
return newConn(genericTransport{conn}, handler, signalHandler)
}
// newConn creates a new *Conn from a transport.
func newConn(tr transport, handler Handler, signalHandler SignalHandler) (*Conn, error) {
conn := new(Conn)
conn.transport = tr
conn.calls = newCallTracker()
conn.handler = handler
conn.signalHandler = signalHandler
conn.outHandler = &outputHandler{conn: conn}
conn.serialGen = newSerialGenerator()
conn.names = newNameTracker()
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
return conn, nil
}
// BusObject returns the object owned by the bus daemon which handles
// administrative requests.
func (conn *Conn) BusObject() BusObject {
return conn.busObj
}
// Close closes the connection. Any blocked operations will return with errors
// and the channels passed to Eavesdrop and Signal are closed. This method must
// not be called on shared connections.
func (conn *Conn) Close() error {
conn.outHandler.close()
if term, ok := conn.signalHandler.(Terminator); ok {
term.Terminate()
}
if term, ok := conn.handler.(Terminator); ok {
term.Terminate()
}
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
close(conn.eavesdropped)
}
conn.eavesdroppedLck.Unlock()
return conn.transport.Close()
}
// Eavesdrop causes conn to send all incoming messages to the given channel
// without further processing. Method replies, errors and signals will not be
// sent to the appropiate channels and method calls will not be handled. If nil
// is passed, the normal behaviour is restored.
//
// The caller has to make sure that ch is sufficiently buffered;
// if a message arrives when a write to ch is not possible, the message is
// discarded.
func (conn *Conn) Eavesdrop(ch chan<- *Message) {
conn.eavesdroppedLck.Lock()
conn.eavesdropped = ch
conn.eavesdroppedLck.Unlock()
}
// getSerial returns an unused serial.
func (conn *Conn) getSerial() uint32 {
return conn.serialGen.getSerial()
}
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
// called after authentication, but before sending any other messages to the
// bus. Hello must not be called for shared connections.
func (conn *Conn) Hello() error {
var s string
err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
if err != nil {
return err
}
conn.names.acquireUniqueConnectionName(s)
return nil
}
// inWorker runs in an own goroutine, reading incoming messages from the
// transport and dispatching them appropiately.
func (conn *Conn) inWorker() {
for {
msg, err := conn.ReadMessage()
if err != nil {
if _, ok := err.(InvalidMessageError); !ok {
// Some read error occured (usually EOF); we can't really do
// anything but to shut down all stuff and returns errors to all
// pending replies.
conn.Close()
conn.calls.finalizeAllWithError(err)
return
}
// invalid messages are ignored
continue
}
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
select {
case conn.eavesdropped <- msg:
default:
}
conn.eavesdroppedLck.Unlock()
continue
}
conn.eavesdroppedLck.Unlock()
dest, _ := msg.Headers[FieldDestination].value.(string)
found := dest == "" ||
!conn.names.uniqueNameIsKnown() ||
conn.names.isKnownName(dest)
if !found {
// Eavesdropped a message, but no channel for it is registered.
// Ignore it.
continue
}
switch msg.Type {
case TypeError:
conn.serialGen.retireSerial(conn.calls.handleDBusError(msg))
case TypeMethodReply:
conn.serialGen.retireSerial(conn.calls.handleReply(msg))
case TypeSignal:
conn.handleSignal(msg)
case TypeMethodCall:
go conn.handleCall(msg)
}
}
}
func (conn *Conn) handleSignal(msg *Message) {
iface := msg.Headers[FieldInterface].value.(string)
member := msg.Headers[FieldMember].value.(string)
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
// sender is optional for signals.
sender, _ := msg.Headers[FieldSender].value.(string)
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
if member == "NameLost" {
// If we lost the name on the bus, remove it from our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the lost name")
}
conn.names.loseName(name)
} else if member == "NameAcquired" {
// If we acquired the name on the bus, add it to our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the acquired name")
}
conn.names.acquireName(name)
}
}
signal := &Signal{
Sender: sender,
Path: msg.Headers[FieldPath].value.(ObjectPath),
Name: iface + "." + member,
Body: msg.Body,
}
conn.signalHandler.DeliverSignal(iface, member, signal)
}
// Names returns the list of all names that are currently owned by this
// connection. The slice is always at least one element long, the first element
// being the unique name of the connection.
func (conn *Conn) Names() []string {
return conn.names.listKnownNames()
}
// Object returns the object identified by the given destination name and path.
func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
return &Object{conn, dest, path}
}
// outWorker runs in an own goroutine, encoding and sending messages that are
// sent to conn.out.
func (conn *Conn) sendMessage(msg *Message) {
conn.sendMessageAndIfClosed(msg, func() {})
}
func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
conn.calls.handleSendError(msg, err)
if err != nil {
conn.serialGen.retireSerial(msg.serial)
} else if msg.Type != TypeMethodCall {
conn.serialGen.retireSerial(msg.serial)
}
}
// Send sends the given message to the message bus. You usually don't need to
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
// call is returned and the same value is sent to ch (which must be buffered)
// once the call is complete. Otherwise, ch is ignored and a Call structure is
// returned of which only the Err member is valid.
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
var call *Call
msg.serial = conn.getSerial()
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 5)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
call = new(Call)
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
iface, _ := msg.Headers[FieldInterface].value.(string)
member, _ := msg.Headers[FieldMember].value.(string)
call.Method = iface + "." + member
call.Args = msg.Body
call.Done = ch
conn.calls.track(msg.serial, call)
conn.sendMessageAndIfClosed(msg, func() {
call.Err = ErrClosed
call.Done <- call
})
} else {
call = &Call{Err: nil}
conn.sendMessageAndIfClosed(msg, func() {
call = &Call{Err: ErrClosed}
})
}
return call
}
// sendError creates an error message corresponding to the parameters and sends
// it to conn.out.
func (conn *Conn) sendError(err error, dest string, serial uint32) {
var e *Error
switch em := err.(type) {
case Error:
e = &em
case *Error:
e = em
case DBusError:
name, body := em.DBusError()
e = NewError(name, body)
default:
e = MakeFailedError(err)
}
msg := new(Message)
msg.Type = TypeError
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = e.Body
if len(e.Body) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
}
conn.sendMessage(msg)
}
// sendReply creates a method reply message corresponding to the parameters and
// sends it to conn.out.
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
msg := new(Message)
msg.Type = TypeMethodReply
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.sendMessage(msg)
}
func (conn *Conn) defaultSignalAction(fn func(h *defaultSignalHandler, ch chan<- *Signal), ch chan<- *Signal) {
if !isDefaultSignalHandler(conn.signalHandler) {
return
}
handler := conn.signalHandler.(*defaultSignalHandler)
fn(handler, ch)
}
// Signal registers the given channel to be passed all received signal messages.
// The caller has to make sure that ch is sufficiently buffered; if a message
// arrives when a write to c is not possible, it is discarded.
//
// Multiple of these channels can be registered at the same time.
//
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
// channel for eavesdropped messages, this channel receives all signals, and
// none of the channels passed to Signal will receive any signals.
func (conn *Conn) Signal(ch chan<- *Signal) {
conn.defaultSignalAction((*defaultSignalHandler).addSignal, ch)
}
// RemoveSignal removes the given channel from the list of the registered channels.
func (conn *Conn) RemoveSignal(ch chan<- *Signal) {
conn.defaultSignalAction((*defaultSignalHandler).removeSignal, ch)
}
// SupportsUnixFDs returns whether the underlying transport supports passing of
// unix file descriptors. If this is false, method calls containing unix file
// descriptors will return an error and emitted signals containing them will
// not be sent.
func (conn *Conn) SupportsUnixFDs() bool {
return conn.unixFD
}
// Error represents a D-Bus message of type Error.
type Error struct {
Name string
Body []interface{}
}
func NewError(name string, body []interface{}) *Error {
return &Error{name, body}
}
func (e Error) Error() string {
if len(e.Body) >= 1 {
s, ok := e.Body[0].(string)
if ok {
return s
}
}
return e.Name
}
// Signal represents a D-Bus message of type Signal. The name member is given in
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
type Signal struct {
Sender string
Path ObjectPath
Name string
Body []interface{}
}
// transport is a D-Bus transport.
type transport interface {
// Read and Write raw data (for example, for the authentication protocol).
io.ReadWriteCloser
// Send the initial null byte used for the EXTERNAL mechanism.
SendNullByte() error
// Returns whether this transport supports passing Unix FDs.
SupportsUnixFDs() bool
// Signal the transport that Unix FD passing is enabled for this connection.
EnableUnixFDs()
// Read / send a message, handling things like Unix FDs.
ReadMessage() (*Message, error)
SendMessage(*Message) error
}
var (
transports = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
var err error
var t transport
addresses := strings.Split(address, ";")
for _, v := range addresses {
i := strings.IndexRune(v, ':')
if i == -1 {
err = errors.New("dbus: invalid bus address (no transport)")
continue
}
f := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
continue
}
t, err = f(v[i+1:])
if err == nil {
return t, nil
}
}
return nil, err
}
// dereferenceAll returns a slice that, assuming that vs is a slice of pointers
// of arbitrary types, containes the values that are obtained from dereferencing
// all elements in vs.
func dereferenceAll(vs []interface{}) []interface{} {
for i := range vs {
v := reflect.ValueOf(vs[i])
v = v.Elem()
vs[i] = v.Interface()
}
return vs
}
// getKey gets a key from a the list of keys. Returns "" on error / not found...
func getKey(s, key string) string {
for _, keyEqualsValue := range strings.Split(s, ",") {
keyValue := strings.SplitN(keyEqualsValue, "=", 2)
if len(keyValue) == 2 && keyValue[0] == key {
return keyValue[1]
}
}
return ""
}
type outputHandler struct {
conn *Conn
sendLck sync.Mutex
closed struct {
isClosed bool
lck sync.RWMutex
}
}
func (h *outputHandler) sendAndIfClosed(msg *Message, ifClosed func()) error {
h.closed.lck.RLock()
defer h.closed.lck.RUnlock()
if h.closed.isClosed {
ifClosed()
return nil
}
h.sendLck.Lock()
defer h.sendLck.Unlock()
return h.conn.SendMessage(msg)
}
func (h *outputHandler) close() {
h.closed.lck.Lock()
defer h.closed.lck.Unlock()
h.closed.isClosed = true
}
type serialGenerator struct {
lck sync.Mutex
nextSerial uint32
serialUsed map[uint32]bool
}
func newSerialGenerator() *serialGenerator {
return &serialGenerator{
serialUsed: map[uint32]bool{0: true},
nextSerial: 1,
}
}
func (gen *serialGenerator) getSerial() uint32 {
gen.lck.Lock()
defer gen.lck.Unlock()
n := gen.nextSerial
for gen.serialUsed[n] {
n++
}
gen.serialUsed[n] = true
gen.nextSerial = n + 1
return n
}
func (gen *serialGenerator) retireSerial(serial uint32) {
gen.lck.Lock()
defer gen.lck.Unlock()
delete(gen.serialUsed, serial)
}
type nameTracker struct {
lck sync.RWMutex
unique string
names map[string]struct{}
}
func newNameTracker() *nameTracker {
return &nameTracker{names: map[string]struct{}{}}
}
func (tracker *nameTracker) acquireUniqueConnectionName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
tracker.unique = name
}
func (tracker *nameTracker) acquireName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
tracker.names[name] = struct{}{}
}
func (tracker *nameTracker) loseName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
delete(tracker.names, name)
}
func (tracker *nameTracker) uniqueNameIsKnown() bool {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
return tracker.unique != ""
}
func (tracker *nameTracker) isKnownName(name string) bool {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
_, ok := tracker.names[name]
return ok || name == tracker.unique
}
func (tracker *nameTracker) listKnownNames() []string {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
out := make([]string, 0, len(tracker.names)+1)
out = append(out, tracker.unique)
for k := range tracker.names {
out = append(out, k)
}
return out
}
type callTracker struct {
calls map[uint32]*Call
lck sync.RWMutex
}
func newCallTracker() *callTracker {
return &callTracker{calls: map[uint32]*Call{}}
}
func (tracker *callTracker) track(sn uint32, call *Call) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
tracker.calls[sn] = call
}
func (tracker *callTracker) handleReply(msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock()
c, ok := tracker.calls[serial]
tracker.lck.RUnlock()
if !ok {
return serial
}
c.Body = msg.Body
c.Done <- c
tracker.finalize(serial)
return serial
}
func (tracker *callTracker) handleDBusError(msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock()
c, ok := tracker.calls[serial]
tracker.lck.RUnlock()
if !ok {
return serial
}
name, _ := msg.Headers[FieldErrorName].value.(string)
c.Err = Error{name, msg.Body}
c.Done <- c
tracker.finalize(serial)
return serial
}
func (tracker *callTracker) handleSendError(msg *Message, err error) {
if err == nil {
return
}
tracker.lck.RLock()
c, ok := tracker.calls[msg.serial]
tracker.lck.RUnlock()
if !ok {
return
}
c.Err = err
c.Done <- c
tracker.finalize(msg.serial)
}
func (tracker *callTracker) finalize(sn uint32) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
delete(tracker.calls, sn)
}
func (tracker *callTracker) finalizeAllWithError(err error) {
closedCalls := make(map[uint32]*Call)
tracker.lck.RLock()
for sn, v := range tracker.calls {
v.Err = err
v.Done <- v
closedCalls[sn] = v
}
tracker.lck.RUnlock()
for sn := range closedCalls {
tracker.finalize(sn)
}
}

33
vendor/github.com/godbus/dbus/conn_darwin.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package dbus
import (
"errors"
"fmt"
"os"
"os/exec"
)
const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket"
func getSessionBusPlatformAddress() (string, error) {
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
b, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
if len(b) == 0 {
return "", errors.New("dbus: couldn't determine address of session bus")
}
return "unix:path=" + string(b[:len(b)-1]), nil
}
func getSystemBusPlatformAddress() string {
address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")
if address != "" {
return fmt.Sprintf("unix:path=%s", address)
}
return defaultSystemBusAddress
}

42
vendor/github.com/godbus/dbus/conn_other.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// +build !darwin
package dbus
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
)
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
func getSessionBusPlatformAddress() (string, error) {
cmd := exec.Command("dbus-launch")
b, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
i := bytes.IndexByte(b, '=')
j := bytes.IndexByte(b, '\n')
if i == -1 || j == -1 {
return "", errors.New("dbus: couldn't determine address of session bus")
}
env, addr := string(b[0:i]), string(b[i+1:j])
os.Setenv(env, addr)
return addr, nil
}
func getSystemBusPlatformAddress() string {
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
if address != "" {
return fmt.Sprintf("unix:path=%s", address)
}
return defaultSystemBusAddress
}

427
vendor/github.com/godbus/dbus/dbus.go generated vendored Normal file
View File

@ -0,0 +1,427 @@
package dbus
import (
"errors"
"fmt"
"reflect"
"strings"
)
var (
byteType = reflect.TypeOf(byte(0))
boolType = reflect.TypeOf(false)
uint8Type = reflect.TypeOf(uint8(0))
int16Type = reflect.TypeOf(int16(0))
uint16Type = reflect.TypeOf(uint16(0))
intType = reflect.TypeOf(int(0))
uintType = reflect.TypeOf(uint(0))
int32Type = reflect.TypeOf(int32(0))
uint32Type = reflect.TypeOf(uint32(0))
int64Type = reflect.TypeOf(int64(0))
uint64Type = reflect.TypeOf(uint64(0))
float64Type = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
signatureType = reflect.TypeOf(Signature{""})
objectPathType = reflect.TypeOf(ObjectPath(""))
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
interfacesType = reflect.TypeOf([]interface{}{})
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
unixFDType = reflect.TypeOf(UnixFD(0))
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
)
// An InvalidTypeError signals that a value which cannot be represented in the
// D-Bus wire format was passed to a function.
type InvalidTypeError struct {
Type reflect.Type
}
func (e InvalidTypeError) Error() string {
return "dbus: invalid type " + e.Type.String()
}
// Store copies the values contained in src to dest, which must be a slice of
// pointers. It converts slices of interfaces from src to corresponding structs
// in dest. An error is returned if the lengths of src and dest or the types of
// their elements don't match.
func Store(src []interface{}, dest ...interface{}) error {
if len(src) != len(dest) {
return errors.New("dbus.Store: length mismatch")
}
for i := range src {
if err := storeInterfaces(src[i], dest[i]); err != nil {
return err
}
}
return nil
}
func storeInterfaces(src, dest interface{}) error {
return store(reflect.ValueOf(dest), reflect.ValueOf(src))
}
func store(dest, src reflect.Value) error {
if dest.Kind() == reflect.Ptr {
return store(dest.Elem(), src)
}
switch src.Kind() {
case reflect.Slice:
return storeSlice(dest, src)
case reflect.Map:
return storeMap(dest, src)
default:
return storeBase(dest, src)
}
}
func storeBase(dest, src reflect.Value) error {
return setDest(dest, src)
}
func setDest(dest, src reflect.Value) error {
if !isVariant(src.Type()) && isVariant(dest.Type()) {
//special conversion for dbus.Variant
dest.Set(reflect.ValueOf(MakeVariant(src.Interface())))
return nil
}
if isVariant(src.Type()) && !isVariant(dest.Type()) {
src = getVariantValue(src)
}
if !src.Type().ConvertibleTo(dest.Type()) {
return fmt.Errorf(
"dbus.Store: type mismatch: cannot convert %s to %s",
src.Type(), dest.Type())
}
dest.Set(src.Convert(dest.Type()))
return nil
}
func kindsAreCompatible(dest, src reflect.Type) bool {
switch {
case isVariant(dest):
return true
case dest.Kind() == reflect.Interface:
return true
default:
return dest.Kind() == src.Kind()
}
}
func isConvertibleTo(dest, src reflect.Type) bool {
switch {
case isVariant(dest):
return true
case dest.Kind() == reflect.Interface:
return true
case dest.Kind() == reflect.Slice:
return src.Kind() == reflect.Slice &&
isConvertibleTo(dest.Elem(), src.Elem())
case dest.Kind() == reflect.Struct:
return src == interfacesType
default:
return src.ConvertibleTo(dest)
}
}
func storeMap(dest, src reflect.Value) error {
switch {
case !kindsAreCompatible(dest.Type(), src.Type()):
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"map: cannot store a value of %s into %s",
src.Type(), dest.Type())
case isVariant(dest.Type()):
return storeMapIntoVariant(dest, src)
case dest.Kind() == reflect.Interface:
return storeMapIntoInterface(dest, src)
case isConvertibleTo(dest.Type().Key(), src.Type().Key()) &&
isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
return storeMapIntoMap(dest, src)
default:
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"map: cannot convert a value of %s into %s",
src.Type(), dest.Type())
}
}
func storeMapIntoVariant(dest, src reflect.Value) error {
dv := reflect.MakeMap(src.Type())
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeMapIntoInterface(dest, src reflect.Value) error {
var dv reflect.Value
if isVariant(src.Type().Elem()) {
//Convert variants to interface{} recursively when converting
//to interface{}
dv = reflect.MakeMap(
reflect.MapOf(src.Type().Key(), interfaceType))
} else {
dv = reflect.MakeMap(src.Type())
}
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeMapIntoMap(dest, src reflect.Value) error {
if dest.IsNil() {
dest.Set(reflect.MakeMap(dest.Type()))
}
keys := src.MapKeys()
for _, key := range keys {
dkey := key.Convert(dest.Type().Key())
dval := reflect.New(dest.Type().Elem()).Elem()
err := store(dval, getVariantValue(src.MapIndex(key)))
if err != nil {
return err
}
dest.SetMapIndex(dkey, dval)
}
return nil
}
func storeSlice(dest, src reflect.Value) error {
switch {
case src.Type() == interfacesType && dest.Kind() == reflect.Struct:
//The decoder always decodes structs as slices of interface{}
return storeStruct(dest, src)
case !kindsAreCompatible(dest.Type(), src.Type()):
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"slice: cannot store a value of %s into %s",
src.Type(), dest.Type())
case isVariant(dest.Type()):
return storeSliceIntoVariant(dest, src)
case dest.Kind() == reflect.Interface:
return storeSliceIntoInterface(dest, src)
case isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
return storeSliceIntoSlice(dest, src)
default:
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"slice: cannot convert a value of %s into %s",
src.Type(), dest.Type())
}
}
func storeStruct(dest, src reflect.Value) error {
if isVariant(dest.Type()) {
return storeBase(dest, src)
}
dval := make([]interface{}, 0, dest.NumField())
dtype := dest.Type()
for i := 0; i < dest.NumField(); i++ {
field := dest.Field(i)
ftype := dtype.Field(i)
if ftype.PkgPath != "" {
continue
}
if ftype.Tag.Get("dbus") == "-" {
continue
}
dval = append(dval, field.Addr().Interface())
}
if src.Len() != len(dval) {
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"destination struct does not have "+
"enough fields need: %d have: %d",
src.Len(), len(dval))
}
return Store(src.Interface().([]interface{}), dval...)
}
func storeSliceIntoVariant(dest, src reflect.Value) error {
dv := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeSliceIntoInterface(dest, src reflect.Value) error {
var dv reflect.Value
if isVariant(src.Type().Elem()) {
//Convert variants to interface{} recursively when converting
//to interface{}
dv = reflect.MakeSlice(reflect.SliceOf(interfaceType),
src.Len(), src.Cap())
} else {
dv = reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
}
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeSliceIntoSlice(dest, src reflect.Value) error {
if dest.IsNil() || dest.Len() < src.Len() {
dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap()))
}
if dest.Len() != src.Len() {
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"slices are different lengths "+
"need: %d have: %d",
src.Len(), dest.Len())
}
for i := 0; i < src.Len(); i++ {
err := store(dest.Index(i), getVariantValue(src.Index(i)))
if err != nil {
return err
}
}
return nil
}
func getVariantValue(in reflect.Value) reflect.Value {
if isVariant(in.Type()) {
return reflect.ValueOf(in.Interface().(Variant).Value())
}
return in
}
func isVariant(t reflect.Type) bool {
return t == variantType
}
// An ObjectPath is an object path as defined by the D-Bus spec.
type ObjectPath string
// IsValid returns whether the object path is valid.
func (o ObjectPath) IsValid() bool {
s := string(o)
if len(s) == 0 {
return false
}
if s[0] != '/' {
return false
}
if s[len(s)-1] == '/' && len(s) != 1 {
return false
}
// probably not used, but technically possible
if s == "/" {
return true
}
split := strings.Split(s[1:], "/")
for _, v := range split {
if len(v) == 0 {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
// documentation for more information about Unix file descriptor passsing.
type UnixFD int32
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
type UnixFDIndex uint32
// alignment returns the alignment of values of type t.
func alignment(t reflect.Type) int {
switch t {
case variantType:
return 1
case objectPathType:
return 4
case signatureType:
return 1
case interfacesType:
return 4
}
switch t.Kind() {
case reflect.Uint8:
return 1
case reflect.Uint16, reflect.Int16:
return 2
case reflect.Uint, reflect.Int, reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
return 4
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
return 8
case reflect.Ptr:
return alignment(t.Elem())
}
return 1
}
// isKeyType returns whether t is a valid type for a D-Bus dict.
func isKeyType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
reflect.String, reflect.Uint, reflect.Int:
return true
}
return false
}
// isValidInterface returns whether s is a valid name for an interface.
func isValidInterface(s string) bool {
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
return false
}
elem := strings.Split(s, ".")
if len(elem) < 2 {
return false
}
for _, v := range elem {
if len(v) == 0 {
return false
}
if v[0] >= '0' && v[0] <= '9' {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// isValidMember returns whether s is a valid name for a member.
func isValidMember(s string) bool {
if len(s) == 0 || len(s) > 255 {
return false
}
i := strings.Index(s, ".")
if i != -1 {
return false
}
if s[0] >= '0' && s[0] <= '9' {
return false
}
for _, c := range s {
if !isMemberChar(c) {
return false
}
}
return true
}
func isMemberChar(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_'
}

228
vendor/github.com/godbus/dbus/decoder.go generated vendored Normal file
View File

@ -0,0 +1,228 @@
package dbus
import (
"encoding/binary"
"io"
"reflect"
)
type decoder struct {
in io.Reader
order binary.ByteOrder
pos int
}
// newDecoder returns a new decoder that reads values from in. The input is
// expected to be in the given byte order.
func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
dec := new(decoder)
dec.in = in
dec.order = order
return dec
}
// align aligns the input to the given boundary and panics on error.
func (dec *decoder) align(n int) {
if dec.pos%n != 0 {
newpos := (dec.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-dec.pos)
if _, err := io.ReadFull(dec.in, empty); err != nil {
panic(err)
}
dec.pos = newpos
}
}
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
func (dec *decoder) binread(v interface{}) {
if err := binary.Read(dec.in, dec.order, v); err != nil {
panic(err)
}
}
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
defer func() {
var ok bool
v := recover()
if err, ok = v.(error); ok {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = FormatError("unexpected EOF")
}
}
}()
vs = make([]interface{}, 0)
s := sig.str
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
return nil, err
}
v := dec.decode(s[:len(s)-len(rem)], 0)
vs = append(vs, v)
s = rem
}
return vs, nil
}
func (dec *decoder) decode(s string, depth int) interface{} {
dec.align(alignment(typeFor(s)))
switch s[0] {
case 'y':
var b [1]byte
if _, err := dec.in.Read(b[:]); err != nil {
panic(err)
}
dec.pos++
return b[0]
case 'b':
i := dec.decode("u", depth).(uint32)
switch {
case i == 0:
return false
case i == 1:
return true
default:
panic(FormatError("invalid value for boolean"))
}
case 'n':
var i int16
dec.binread(&i)
dec.pos += 2
return i
case 'i':
var i int32
dec.binread(&i)
dec.pos += 4
return i
case 'x':
var i int64
dec.binread(&i)
dec.pos += 8
return i
case 'q':
var i uint16
dec.binread(&i)
dec.pos += 2
return i
case 'u':
var i uint32
dec.binread(&i)
dec.pos += 4
return i
case 't':
var i uint64
dec.binread(&i)
dec.pos += 8
return i
case 'd':
var f float64
dec.binread(&f)
dec.pos += 8
return f
case 's':
length := dec.decode("u", depth).(uint32)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
return string(b[:len(b)-1])
case 'o':
return ObjectPath(dec.decode("s", depth).(string))
case 'g':
length := dec.decode("y", depth).(byte)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
sig, err := ParseSignature(string(b[:len(b)-1]))
if err != nil {
panic(err)
}
return sig
case 'v':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var variant Variant
sig := dec.decode("g", depth).(Signature)
if len(sig.str) == 0 {
panic(FormatError("variant signature is empty"))
}
err, rem := validSingle(sig.str, 0)
if err != nil {
panic(err)
}
if rem != "" {
panic(FormatError("variant signature has multiple types"))
}
variant.sig = sig
variant.value = dec.decode(sig.str, depth+1)
return variant
case 'h':
return UnixFDIndex(dec.decode("u", depth).(uint32))
case 'a':
if len(s) > 1 && s[1] == '{' {
ksig := s[2:3]
vsig := s[3 : len(s)-1]
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
// Even for empty maps, the correct padding must be included
dec.align(8)
spos := dec.pos
for dec.pos < spos+int(length) {
dec.align(8)
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
kv := dec.decode(ksig, depth+2)
vv := dec.decode(vsig, depth+2)
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return v.Interface()
}
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
// Even for empty arrays, the correct padding must be included
dec.align(alignment(typeFor(s[1:])))
spos := dec.pos
for dec.pos < spos+int(length) {
ev := dec.decode(s[1:], depth+1)
v = reflect.Append(v, reflect.ValueOf(ev))
}
return v.Interface()
case '(':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
dec.align(8)
v := make([]interface{}, 0)
s = s[1 : len(s)-1]
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
panic(err)
}
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
v = append(v, ev)
s = rem
}
return v
default:
panic(SignatureError{Sig: s})
}
}
// A FormatError is an error in the wire format.
type FormatError string
func (e FormatError) Error() string {
return "dbus: wire format error: " + string(e)
}

291
vendor/github.com/godbus/dbus/default_handler.go generated vendored Normal file
View File

@ -0,0 +1,291 @@
package dbus
import (
"bytes"
"reflect"
"strings"
"sync"
)
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
methods := make(map[string]Method)
methods["Introspect"] = exportedMethod{
reflect.ValueOf(func(msg Message) (string, *Error) {
path := msg.Headers[FieldPath].value.(ObjectPath)
return h.introspectPath(path), nil
}),
}
return newExportedIntf(methods, true)
}
//NewDefaultHandler returns an instance of the default
//call handler. This is useful if you want to implement only
//one of the two handlers but not both.
func NewDefaultHandler() *defaultHandler {
h := &defaultHandler{
objects: make(map[ObjectPath]*exportedObj),
defaultIntf: make(map[string]*exportedIntf),
}
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
return h
}
type defaultHandler struct {
sync.RWMutex
objects map[ObjectPath]*exportedObj
defaultIntf map[string]*exportedIntf
}
func (h *defaultHandler) PathExists(path ObjectPath) bool {
_, ok := h.objects[path]
return ok
}
func (h *defaultHandler) introspectPath(path ObjectPath) string {
subpath := make(map[string]struct{})
var xml bytes.Buffer
xml.WriteString("<node>")
for obj, _ := range h.objects {
p := string(path)
if p != "/" {
p += "/"
}
if strings.HasPrefix(string(obj), p) {
node_name := strings.Split(string(obj[len(p):]), "/")[0]
subpath[node_name] = struct{}{}
}
}
for s, _ := range subpath {
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
}
xml.WriteString("\n</node>")
return xml.String()
}
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
h.RLock()
defer h.RUnlock()
object, ok := h.objects[path]
if ok {
return object, ok
}
// If an object wasn't found for this exact path,
// look for a matching subtree registration
subtreeObject := newExportedObject()
path = path[:strings.LastIndex(string(path), "/")]
for len(path) > 0 {
object, ok = h.objects[path]
if ok {
for name, iface := range object.interfaces {
// Only include this handler if it registered for the subtree
if iface.isFallbackInterface() {
subtreeObject.interfaces[name] = iface
}
}
break
}
path = path[:strings.LastIndex(string(path), "/")]
}
for name, intf := range h.defaultIntf {
if _, exists := subtreeObject.interfaces[name]; exists {
continue
}
subtreeObject.interfaces[name] = intf
}
return subtreeObject, true
}
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
h.Lock()
h.objects[path] = object
h.Unlock()
}
func (h *defaultHandler) DeleteObject(path ObjectPath) {
h.Lock()
delete(h.objects, path)
h.Unlock()
}
type exportedMethod struct {
reflect.Value
}
func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
t := m.Type()
params := make([]reflect.Value, len(args))
for i := 0; i < len(args); i++ {
params[i] = reflect.ValueOf(args[i]).Elem()
}
ret := m.Value.Call(params)
err := ret[t.NumOut()-1].Interface().(*Error)
ret = ret[:t.NumOut()-1]
out := make([]interface{}, len(ret))
for i, val := range ret {
out[i] = val.Interface()
}
if err == nil {
//concrete type to interface nil is a special case
return out, nil
}
return out, err
}
func (m exportedMethod) NumArguments() int {
return m.Value.Type().NumIn()
}
func (m exportedMethod) ArgumentValue(i int) interface{} {
return reflect.Zero(m.Type().In(i)).Interface()
}
func (m exportedMethod) NumReturns() int {
return m.Value.Type().NumOut()
}
func (m exportedMethod) ReturnValue(i int) interface{} {
return reflect.Zero(m.Type().Out(i)).Interface()
}
func newExportedObject() *exportedObj {
return &exportedObj{
interfaces: make(map[string]*exportedIntf),
}
}
type exportedObj struct {
interfaces map[string]*exportedIntf
}
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
if name == "" {
return obj, true
}
intf, exists := obj.interfaces[name]
return intf, exists
}
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
obj.interfaces[name] = iface
}
func (obj *exportedObj) DeleteInterface(name string) {
delete(obj.interfaces, name)
}
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
for _, intf := range obj.interfaces {
method, exists := intf.LookupMethod(name)
if exists {
return method, exists
}
}
return nil, false
}
func (obj *exportedObj) isFallbackInterface() bool {
return false
}
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
return &exportedIntf{
methods: methods,
includeSubtree: includeSubtree,
}
}
type exportedIntf struct {
methods map[string]Method
// Whether or not this export is for the entire subtree
includeSubtree bool
}
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
out, exists := obj.methods[name]
return out, exists
}
func (obj *exportedIntf) isFallbackInterface() bool {
return obj.includeSubtree
}
//NewDefaultSignalHandler returns an instance of the default
//signal handler. This is useful if you want to implement only
//one of the two handlers but not both.
func NewDefaultSignalHandler() *defaultSignalHandler {
return &defaultSignalHandler{}
}
func isDefaultSignalHandler(handler SignalHandler) bool {
_, ok := handler.(*defaultSignalHandler)
return ok
}
type defaultSignalHandler struct {
sync.RWMutex
closed bool
signals []chan<- *Signal
}
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
go func() {
sh.RLock()
defer sh.RUnlock()
if sh.closed {
return
}
for _, ch := range sh.signals {
ch <- signal
}
}()
}
func (sh *defaultSignalHandler) Init() error {
sh.Lock()
sh.signals = make([]chan<- *Signal, 0)
sh.Unlock()
return nil
}
func (sh *defaultSignalHandler) Terminate() {
sh.Lock()
sh.closed = true
for _, ch := range sh.signals {
close(ch)
}
sh.signals = nil
sh.Unlock()
}
func (sh *defaultSignalHandler) addSignal(ch chan<- *Signal) {
sh.Lock()
defer sh.Unlock()
if sh.closed {
return
}
sh.signals = append(sh.signals, ch)
}
func (sh *defaultSignalHandler) removeSignal(ch chan<- *Signal) {
sh.Lock()
defer sh.Unlock()
if sh.closed {
return
}
for i := len(sh.signals) - 1; i >= 0; i-- {
if ch == sh.signals[i] {
copy(sh.signals[i:], sh.signals[i+1:])
sh.signals[len(sh.signals)-1] = nil
sh.signals = sh.signals[:len(sh.signals)-1]
}
}
}

69
vendor/github.com/godbus/dbus/doc.go generated vendored Normal file
View File

@ -0,0 +1,69 @@
/*
Package dbus implements bindings to the D-Bus message bus system.
To use the message bus API, you first need to connect to a bus (usually the
session or system bus). The acquired connection then can be used to call methods
on remote objects and emit or receive signals. Using the Export method, you can
arrange D-Bus methods calls to be directly translated to method calls on a Go
value.
Conversion Rules
For outgoing messages, Go types are automatically converted to the
corresponding D-Bus types. The following types are directly encoded as their
respective D-Bus equivalents:
Go type | D-Bus type
------------+-----------
byte | BYTE
bool | BOOLEAN
int16 | INT16
uint16 | UINT16
int | INT32
uint | UINT32
int32 | INT32
uint32 | UINT32
int64 | INT64
uint64 | UINT64
float64 | DOUBLE
string | STRING
ObjectPath | OBJECT_PATH
Signature | SIGNATURE
Variant | VARIANT
interface{} | VARIANT
UnixFDIndex | UNIX_FD
Slices and arrays encode as ARRAYs of their element type.
Maps encode as DICTs, provided that their key type can be used as a key for
a DICT.
Structs other than Variant and Signature encode as a STRUCT containing their
exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will
be skipped.
Pointers encode as the value they're pointed to.
Types convertible to one of the base types above will be mapped as the
base type.
Trying to encode any other type or a slice, map or struct containing an
unsupported type will result in an InvalidTypeError.
For incoming messages, the inverse of these rules are used, with the exception
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
containing the struct fields in the correct order. The Store function can be
used to convert such values to Go structs.
Unix FD passing
Handling Unix file descriptors deserves special mention. To use them, you should
first check that they are supported on a connection by calling SupportsUnixFDs.
If it returns true, all method of Connection will translate messages containing
UnixFD's to messages that are accompanied by the given file descriptors with the
UnixFD values being substituted by the correct indices. Similarily, the indices
of incoming messages are automatically resolved. It shouldn't be necessary to use
UnixFDIndex.
*/
package dbus

210
vendor/github.com/godbus/dbus/encoder.go generated vendored Normal file
View File

@ -0,0 +1,210 @@
package dbus
import (
"bytes"
"encoding/binary"
"io"
"reflect"
)
// An encoder encodes values to the D-Bus wire format.
type encoder struct {
out io.Writer
order binary.ByteOrder
pos int
}
// NewEncoder returns a new encoder that writes to out in the given byte order.
func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
return newEncoderAtOffset(out, 0, order)
}
// newEncoderAtOffset returns a new encoder that writes to out in the given
// byte order. Specify the offset to initialize pos for proper alignment
// computation.
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
enc.pos = offset
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
pad := enc.padding(0, n)
if pad > 0 {
empty := make([]byte, pad)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos += pad
}
}
// pad returns the number of bytes of padding, based on current position and additional offset.
// and alignment.
func (enc *encoder) padding(offset, algn int) int {
abs := enc.pos + offset
if abs%algn != 0 {
newabs := (abs + algn - 1) & ^(algn - 1)
return newabs - abs
}
return 0
}
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
func (enc *encoder) binwrite(v interface{}) {
if err := binary.Write(enc.out, enc.order, v); err != nil {
panic(err)
}
}
// Encode encodes the given values to the underyling reader. All written values
// are aligned properly as required by the D-Bus spec.
func (enc *encoder) Encode(vs ...interface{}) (err error) {
defer func() {
err, _ = recover().(error)
}()
for _, v := range vs {
enc.encode(reflect.ValueOf(v), 0)
}
return nil
}
// encode encodes the given value to the writer and panics on error. depth holds
// the depth of the container nesting.
func (enc *encoder) encode(v reflect.Value, depth int) {
enc.align(alignment(v.Type()))
switch v.Kind() {
case reflect.Uint8:
var b [1]byte
b[0] = byte(v.Uint())
if _, err := enc.out.Write(b[:]); err != nil {
panic(err)
}
enc.pos++
case reflect.Bool:
if v.Bool() {
enc.encode(reflect.ValueOf(uint32(1)), depth)
} else {
enc.encode(reflect.ValueOf(uint32(0)), depth)
}
case reflect.Int16:
enc.binwrite(int16(v.Int()))
enc.pos += 2
case reflect.Uint16:
enc.binwrite(uint16(v.Uint()))
enc.pos += 2
case reflect.Int, reflect.Int32:
enc.binwrite(int32(v.Int()))
enc.pos += 4
case reflect.Uint, reflect.Uint32:
enc.binwrite(uint32(v.Uint()))
enc.pos += 4
case reflect.Int64:
enc.binwrite(v.Int())
enc.pos += 8
case reflect.Uint64:
enc.binwrite(v.Uint())
enc.pos += 8
case reflect.Float64:
enc.binwrite(v.Float())
enc.pos += 8
case reflect.String:
enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth)
b := make([]byte, v.Len()+1)
copy(b, v.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case reflect.Ptr:
enc.encode(v.Elem(), depth)
case reflect.Slice, reflect.Array:
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus alignment for elements.
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
for i := 0; i < v.Len(); i++ {
bufenc.encode(v.Index(i), depth+1)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(alignment(v.Type().Elem()))
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Struct:
if depth >= 64 && v.Type() != signatureType {
panic(FormatError("input exceeds container depth limit"))
}
switch t := v.Type(); t {
case signatureType:
str := v.Field(0)
enc.encode(reflect.ValueOf(byte(str.Len())), depth+1)
b := make([]byte, str.Len()+1)
copy(b, str.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case variantType:
variant := v.Interface().(Variant)
enc.encode(reflect.ValueOf(variant.sig), depth+1)
enc.encode(reflect.ValueOf(variant.value), depth+1)
default:
for i := 0; i < v.Type().NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
enc.encode(v.Field(i), depth+1)
}
}
}
case reflect.Map:
// Maps are arrays of structures, so they actually increase the depth by
// 2.
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
keys := v.MapKeys()
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus 8-byte alignment
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, 8)
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
for _, k := range keys {
bufenc.align(8)
bufenc.encode(k, depth+2)
bufenc.encode(v.MapIndex(k), depth+2)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(8)
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Interface:
enc.encode(reflect.ValueOf(MakeVariant(v.Interface())), depth)
default:
panic(InvalidTypeError{v.Type()})
}
}

412
vendor/github.com/godbus/dbus/export.go generated vendored Normal file
View File

@ -0,0 +1,412 @@
package dbus
import (
"errors"
"fmt"
"reflect"
"strings"
)
var (
ErrMsgInvalidArg = Error{
"org.freedesktop.DBus.Error.InvalidArgs",
[]interface{}{"Invalid type / number of args"},
}
ErrMsgNoObject = Error{
"org.freedesktop.DBus.Error.NoSuchObject",
[]interface{}{"No such object"},
}
ErrMsgUnknownMethod = Error{
"org.freedesktop.DBus.Error.UnknownMethod",
[]interface{}{"Unknown / invalid method"},
}
ErrMsgUnknownInterface = Error{
"org.freedesktop.DBus.Error.UnknownInterface",
[]interface{}{"Object does not implement the interface"},
}
)
func MakeFailedError(err error) *Error {
return &Error{
"org.freedesktop.DBus.Error.Failed",
[]interface{}{err.Error()},
}
}
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func computeMethodName(name string, mapping map[string]string) string {
newname, ok := mapping[name]
if ok {
name = newname
}
return name
}
func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
if in == nil {
return nil
}
methods := make(map[string]reflect.Value)
val := reflect.ValueOf(in)
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
methtype := typ.Method(i)
method := val.Method(i)
t := method.Type()
// only track valid methods must return *Error as last arg
// and must be exported
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
methtype.PkgPath != "" {
continue
}
// map names while building table
methods[computeMethodName(methtype.Name, mapping)] = method
}
return methods
}
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
pointers := make([]interface{}, m.NumArguments())
decode := make([]interface{}, 0, len(body))
for i := 0; i < m.NumArguments(); i++ {
tp := reflect.TypeOf(m.ArgumentValue(i))
val := reflect.New(tp)
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
val.Elem().Set(reflect.ValueOf(*msg))
} else {
decode = append(decode, pointers[i])
}
}
if len(decode) != len(body) {
return nil, ErrMsgInvalidArg
}
if err := Store(body, decode...); err != nil {
return nil, ErrMsgInvalidArg
}
return pointers, nil
}
func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
if decoder, ok := m.(ArgumentDecoder); ok {
return decoder.DecodeArguments(conn, sender, msg, msg.Body)
}
return standardMethodArgumentDecode(m, sender, msg, msg.Body)
}
// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
name := msg.Headers[FieldMember].value.(string)
path := msg.Headers[FieldPath].value.(ObjectPath)
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
sender, hasSender := msg.Headers[FieldSender].value.(string)
serial := msg.serial
if ifaceName == "org.freedesktop.DBus.Peer" {
switch name {
case "Ping":
conn.sendReply(sender, serial)
case "GetMachineId":
conn.sendReply(sender, serial, conn.uuid)
default:
conn.sendError(ErrMsgUnknownMethod, sender, serial)
}
return
}
if len(name) == 0 {
conn.sendError(ErrMsgUnknownMethod, sender, serial)
}
object, ok := conn.handler.LookupObject(path)
if !ok {
conn.sendError(ErrMsgNoObject, sender, serial)
return
}
iface, exists := object.LookupInterface(ifaceName)
if !exists {
conn.sendError(ErrMsgUnknownInterface, sender, serial)
return
}
m, exists := iface.LookupMethod(name)
if !exists {
conn.sendError(ErrMsgUnknownMethod, sender, serial)
return
}
args, err := conn.decodeArguments(m, sender, msg)
if err != nil {
conn.sendError(err, sender, serial)
return
}
ret, err := m.Call(args...)
if err != nil {
conn.sendError(err, sender, serial)
return
}
if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
reply.serial = conn.getSerial()
reply.Headers = make(map[HeaderField]Variant)
if hasSender {
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
}
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
reply.Body = make([]interface{}, len(ret))
for i := 0; i < len(ret); i++ {
reply.Body[i] = ret[i]
}
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
conn.sendMessage(reply)
}
}
// Emit emits the given signal on the message bus. The name parameter must be
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
if !path.IsValid() {
return errors.New("dbus: invalid object path")
}
i := strings.LastIndex(name, ".")
if i == -1 {
return errors.New("dbus: invalid method name")
}
iface := name[:i]
member := name[i+1:]
if !isValidMember(member) {
return errors.New("dbus: invalid method name")
}
if !isValidInterface(iface) {
return errors.New("dbus: invalid interface name")
}
msg := new(Message)
msg.Type = TypeSignal
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldInterface] = MakeVariant(iface)
msg.Headers[FieldMember] = MakeVariant(member)
msg.Headers[FieldPath] = MakeVariant(path)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
var closed bool
conn.sendMessageAndIfClosed(msg, func() {
closed = true
})
if closed {
return ErrClosed
}
return nil
}
// Export registers the given value to be exported as an object on the
// message bus.
//
// If a method call on the given path and interface is received, an exported
// method with the same name is called with v as the receiver if the
// parameters match and the last return value is of type *Error. If this
// *Error is not nil, it is sent back to the caller as an error.
// Otherwise, a method reply is sent with the other return values as its body.
//
// Any parameters with the special type Sender are set to the sender of the
// dbus message when the method is called. Parameters of this type do not
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Similarly, any parameters with the type Message are set to the raw message
// received on the bus. Again, parameters of this type do not contribute to the
// dbus signature of the method.
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
// handled for every object.
//
// Passing nil as the first parameter will cause conn to cease handling calls on
// the given combination of path and interface.
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
return conn.ExportWithMap(v, nil, path, iface)
}
// ExportWithMap works exactly like Export but provides the ability to remap
// method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, false)
}
// ExportSubtree works exactly like Export but registers the given value for
// an entire subtree rather under the root path provided.
//
// In order to make this useful, one parameter in each of the value's exported
// methods should be a Message, in which case it will contain the raw message
// (allowing one to get access to the path that caused the method to be called).
//
// Note that more specific export paths take precedence over less specific. For
// example, a method call using the ObjectPath /foo/bar/baz will call a method
// exported on /foo/bar before a method exported on /foo.
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
return conn.ExportSubtreeWithMap(v, nil, path, iface)
}
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
// ability to remap method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, true)
}
// ExportMethodTable like Export registers the given methods as an object
// on the message bus. Unlike Export the it uses a method table to define
// the object instead of a native go object.
//
// The method table is a map from method name to function closure
// representing the method. This allows an object exported on the bus to not
// necessarily be a native go object. It can be useful for generating exposed
// methods on the fly.
//
// Any non-function objects in the method table are ignored.
func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, false)
}
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, true)
}
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
out := make(map[string]reflect.Value)
for name, method := range methods {
rval := reflect.ValueOf(method)
if rval.Kind() != reflect.Func {
continue
}
t := rval.Type()
// only track valid methods must return *Error as last arg
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
continue
}
out[name] = rval
}
return conn.export(out, path, iface, includeSubtree)
}
func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
if h.PathExists(path) {
obj := h.objects[path]
obj.DeleteInterface(iface)
if len(obj.interfaces) == 0 {
h.DeleteObject(path)
}
}
return nil
}
// exportWithMap is the worker function for all exports/registrations.
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
h, ok := conn.handler.(*defaultHandler)
if !ok {
return fmt.Errorf(
`dbus: export only allowed on the default hander handler have %T"`,
conn.handler)
}
if !path.IsValid() {
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}
// Remove a previous export if the interface is nil
if methods == nil {
return conn.unexport(h, path, iface)
}
// If this is the first handler for this path, make a new map to hold all
// handlers for this path.
if !h.PathExists(path) {
h.AddObject(path, newExportedObject())
}
exportedMethods := make(map[string]Method)
for name, method := range methods {
exportedMethods[name] = exportedMethod{method}
}
// Finally, save this handler
obj := h.objects[path]
obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
if err != nil {
return 0, err
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
if err != nil {
return 0, err
}
return RequestNameReply(r), nil
}
// ReleaseNameReply is the reply to a ReleaseName call.
type ReleaseNameReply uint32
const (
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
ReleaseNameReplyNonExistent
ReleaseNameReplyNotOwner
)
// RequestNameFlags represents the possible flags for a RequestName call.
type RequestNameFlags uint32
const (
NameFlagAllowReplacement RequestNameFlags = 1 << iota
NameFlagReplaceExisting
NameFlagDoNotQueue
)
// RequestNameReply is the reply to a RequestName call.
type RequestNameReply uint32
const (
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
RequestNameReplyInQueue
RequestNameReplyExists
RequestNameReplyAlreadyOwner
)

28
vendor/github.com/godbus/dbus/homedir.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package dbus
import (
"os"
"sync"
)
var (
homeDir string
homeDirLock sync.Mutex
)
func getHomeDir() string {
homeDirLock.Lock()
defer homeDirLock.Unlock()
if homeDir != "" {
return homeDir
}
homeDir = os.Getenv("HOME")
if homeDir != "" {
return homeDir
}
homeDir = lookupHomeDir()
return homeDir
}

15
vendor/github.com/godbus/dbus/homedir_dynamic.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// +build !static_build
package dbus
import (
"os/user"
)
func lookupHomeDir() string {
u, err := user.Current()
if err != nil {
return "/"
}
return u.HomeDir
}

45
vendor/github.com/godbus/dbus/homedir_static.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// +build static_build
package dbus
import (
"bufio"
"os"
"strconv"
"strings"
)
func lookupHomeDir() string {
myUid := os.Getuid()
f, err := os.Open("/etc/passwd")
if err != nil {
return "/"
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
break
}
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
parts := strings.Split(line, ":")
if len(parts) >= 6 {
uid, err := strconv.Atoi(parts[2])
if err == nil && uid == myUid {
return parts[5]
}
}
}
// Default to / if we can't get a better value
return "/"
}

353
vendor/github.com/godbus/dbus/message.go generated vendored Normal file
View File

@ -0,0 +1,353 @@
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"reflect"
"strconv"
)
const protoVersion byte = 1
// Flags represents the possible flags of a D-Bus message.
type Flags byte
const (
// FlagNoReplyExpected signals that the message is not expected to generate
// a reply. If this flag is set on outgoing messages, any possible reply
// will be discarded.
FlagNoReplyExpected Flags = 1 << iota
// FlagNoAutoStart signals that the message bus should not automatically
// start an application when handling this message.
FlagNoAutoStart
// FlagAllowInteractiveAuthorization may be set on a method call
// message to inform the receiving side that the caller is prepared
// to wait for interactive authorization, which might take a
// considerable time to complete. For instance, if this flag is set,
// it would be appropriate to query the user for passwords or
// confirmation via Polkit or a similar framework.
FlagAllowInteractiveAuthorization
)
// Type represents the possible types of a D-Bus message.
type Type byte
const (
TypeMethodCall Type = 1 + iota
TypeMethodReply
TypeError
TypeSignal
typeMax
)
func (t Type) String() string {
switch t {
case TypeMethodCall:
return "method call"
case TypeMethodReply:
return "reply"
case TypeError:
return "error"
case TypeSignal:
return "signal"
}
return "invalid"
}
// HeaderField represents the possible byte codes for the headers
// of a D-Bus message.
type HeaderField byte
const (
FieldPath HeaderField = 1 + iota
FieldInterface
FieldMember
FieldErrorName
FieldReplySerial
FieldDestination
FieldSender
FieldSignature
FieldUnixFDs
fieldMax
)
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
// invalid.
type InvalidMessageError string
func (e InvalidMessageError) Error() string {
return "dbus: invalid message: " + string(e)
}
// fieldType are the types of the various header fields.
var fieldTypes = [fieldMax]reflect.Type{
FieldPath: objectPathType,
FieldInterface: stringType,
FieldMember: stringType,
FieldErrorName: stringType,
FieldReplySerial: uint32Type,
FieldDestination: stringType,
FieldSender: stringType,
FieldSignature: signatureType,
FieldUnixFDs: uint32Type,
}
// requiredFields lists the header fields that are required by the different
// message types.
var requiredFields = [typeMax][]HeaderField{
TypeMethodCall: {FieldPath, FieldMember},
TypeMethodReply: {FieldReplySerial},
TypeError: {FieldErrorName, FieldReplySerial},
TypeSignal: {FieldPath, FieldInterface, FieldMember},
}
// Message represents a single D-Bus message.
type Message struct {
Type
Flags
Headers map[HeaderField]Variant
Body []interface{}
serial uint32
}
type header struct {
Field byte
Variant
}
// DecodeMessage tries to decode a single message in the D-Bus wire format
// from the given reader. The byte order is figured out from the first byte.
// The possibly returned error can be an error of the underlying reader, an
// InvalidMessageError or a FormatError.
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
var order binary.ByteOrder
var hlength, length uint32
var typ, flags, proto byte
var headers []header
b := make([]byte, 1)
_, err = rd.Read(b)
if err != nil {
return
}
switch b[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
dec := newDecoder(rd, order)
dec.pos = 1
msg = new(Message)
vs, err := dec.Decode(Signature{"yyyuu"})
if err != nil {
return nil, err
}
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
return nil, err
}
msg.Type = Type(typ)
msg.Flags = Flags(flags)
// get the header length separately because we need it later
b = make([]byte, 4)
_, err = io.ReadFull(rd, b)
if err != nil {
return nil, err
}
binary.Read(bytes.NewBuffer(b), order, &hlength)
if hlength+length+16 > 1<<27 {
return nil, InvalidMessageError("message is too long")
}
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
dec.pos = 12
vs, err = dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
if err = Store(vs, &headers); err != nil {
return nil, err
}
msg.Headers = make(map[HeaderField]Variant)
for _, v := range headers {
msg.Headers[HeaderField(v.Field)] = v.Variant
}
dec.align(8)
body := make([]byte, int(length))
if length != 0 {
_, err := io.ReadFull(rd, body)
if err != nil {
return nil, err
}
}
if err = msg.IsValid(); err != nil {
return nil, err
}
sig, _ := msg.Headers[FieldSignature].value.(Signature)
if sig.str != "" {
buf := bytes.NewBuffer(body)
dec = newDecoder(buf, order)
vs, err := dec.Decode(sig)
if err != nil {
return nil, err
}
msg.Body = vs
}
return
}
// EncodeTo encodes and sends a message to the given writer. The byte order must
// be either binary.LittleEndian or binary.BigEndian. If the message is not
// valid or an error occurs when writing, an error is returned.
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
if err := msg.IsValid(); err != nil {
return err
}
var vs [7]interface{}
switch order {
case binary.LittleEndian:
vs[0] = byte('l')
case binary.BigEndian:
vs[0] = byte('B')
default:
return errors.New("dbus: invalid byte order")
}
body := new(bytes.Buffer)
enc := newEncoder(body, order)
if len(msg.Body) != 0 {
enc.Encode(msg.Body...)
}
vs[1] = msg.Type
vs[2] = msg.Flags
vs[3] = protoVersion
vs[4] = uint32(len(body.Bytes()))
vs[5] = msg.serial
headers := make([]header, 0, len(msg.Headers))
for k, v := range msg.Headers {
headers = append(headers, header{byte(k), v})
}
vs[6] = headers
var buf bytes.Buffer
enc = newEncoder(&buf, order)
enc.Encode(vs[:]...)
enc.align(8)
body.WriteTo(&buf)
if buf.Len() > 1<<27 {
return InvalidMessageError("message is too long")
}
if _, err := buf.WriteTo(out); err != nil {
return err
}
return nil
}
// IsValid checks whether msg is a valid message and returns an
// InvalidMessageError if it is not.
func (msg *Message) IsValid() error {
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 {
return InvalidMessageError("invalid flags")
}
if msg.Type == 0 || msg.Type >= typeMax {
return InvalidMessageError("invalid message type")
}
for k, v := range msg.Headers {
if k == 0 || k >= fieldMax {
return InvalidMessageError("invalid header")
}
if reflect.TypeOf(v.value) != fieldTypes[k] {
return InvalidMessageError("invalid type of header field")
}
}
for _, v := range requiredFields[msg.Type] {
if _, ok := msg.Headers[v]; !ok {
return InvalidMessageError("missing required header")
}
}
if path, ok := msg.Headers[FieldPath]; ok {
if !path.value.(ObjectPath).IsValid() {
return InvalidMessageError("invalid path name")
}
}
if iface, ok := msg.Headers[FieldInterface]; ok {
if !isValidInterface(iface.value.(string)) {
return InvalidMessageError("invalid interface name")
}
}
if member, ok := msg.Headers[FieldMember]; ok {
if !isValidMember(member.value.(string)) {
return InvalidMessageError("invalid member name")
}
}
if errname, ok := msg.Headers[FieldErrorName]; ok {
if !isValidInterface(errname.value.(string)) {
return InvalidMessageError("invalid error name")
}
}
if len(msg.Body) != 0 {
if _, ok := msg.Headers[FieldSignature]; !ok {
return InvalidMessageError("missing signature")
}
}
return nil
}
// Serial returns the message's serial number. The returned value is only valid
// for messages received by eavesdropping.
func (msg *Message) Serial() uint32 {
return msg.serial
}
// String returns a string representation of a message similar to the format of
// dbus-monitor.
func (msg *Message) String() string {
if err := msg.IsValid(); err != nil {
return "<invalid>"
}
s := msg.Type.String()
if v, ok := msg.Headers[FieldSender]; ok {
s += " from " + v.value.(string)
}
if v, ok := msg.Headers[FieldDestination]; ok {
s += " to " + v.value.(string)
}
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
if v, ok := msg.Headers[FieldReplySerial]; ok {
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldUnixFDs]; ok {
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldPath]; ok {
s += " path " + string(v.value.(ObjectPath))
}
if v, ok := msg.Headers[FieldInterface]; ok {
s += " interface " + v.value.(string)
}
if v, ok := msg.Headers[FieldErrorName]; ok {
s += " error " + v.value.(string)
}
if v, ok := msg.Headers[FieldMember]; ok {
s += " member " + v.value.(string)
}
if len(msg.Body) != 0 {
s += "\n"
}
for i, v := range msg.Body {
s += " " + MakeVariant(v).String()
if i != len(msg.Body)-1 {
s += "\n"
}
}
return s
}

137
vendor/github.com/godbus/dbus/object.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package dbus
import (
"errors"
"strings"
)
// BusObject is the interface of a remote object on which methods can be
// invoked.
type BusObject interface {
Call(method string, flags Flags, args ...interface{}) *Call
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
GetProperty(p string) (Variant, error)
Destination() string
Path() ObjectPath
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
}
// AddMatchSignal subscribes BusObject to signals from specified interface and
// method (member).
func (o *Object) AddMatchSignal(iface, member string) *Call {
return o.Call(
"org.freedesktop.DBus.AddMatch",
0,
"type='signal',interface='"+iface+"',member='"+member+"'",
)
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned with any error in Err and a closed channel in Done containing
// the returned Call as it's one entry.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
if msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 10)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
}
o.conn.calls.track(msg.serial, call)
o.conn.sendMessageAndIfClosed(msg, func() {
call.Err = ErrClosed
call.Done <- call
})
return call
}
done := make(chan *Call, 1)
call := &Call{
Err: nil,
Done: done,
}
defer func() {
call.Done <- call
close(done)
}()
o.conn.sendMessageAndIfClosed(msg, func() {
call.Err = ErrClosed
})
return call
}
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
result := Variant{}
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
if err != nil {
return Variant{}, err
}
return result, nil
}
// Destination returns the destination that calls on (o *Object) are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on (o *Object") are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

89
vendor/github.com/godbus/dbus/server_interfaces.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
package dbus
// Terminator allows a handler to implement a shutdown mechanism that
// is called when the connection terminates.
type Terminator interface {
Terminate()
}
// Handler is the representation of a D-Bus Application.
//
// The Handler must have a way to lookup objects given
// an ObjectPath. The returned object must implement the
// ServerObject interface.
type Handler interface {
LookupObject(path ObjectPath) (ServerObject, bool)
}
// ServerObject is the representation of an D-Bus Object.
//
// Objects are registered at a path for a given Handler.
// The Objects implement D-Bus interfaces. The semantics
// of Interface lookup is up to the implementation of
// the ServerObject. The ServerObject implementation may
// choose to implement empty string as a valid interface
// represeting all methods or not per the D-Bus specification.
type ServerObject interface {
LookupInterface(name string) (Interface, bool)
}
// An Interface is the representation of a D-Bus Interface.
//
// Interfaces are a grouping of methods implemented by the Objects.
// Interfaces are responsible for routing method calls.
type Interface interface {
LookupMethod(name string) (Method, bool)
}
// A Method represents the exposed methods on D-Bus.
type Method interface {
// Call requires that all arguments are decoded before being passed to it.
Call(args ...interface{}) ([]interface{}, error)
NumArguments() int
NumReturns() int
// ArgumentValue returns a representative value for the argument at position
// it should be of the proper type. reflect.Zero would be a good mechanism
// to use for this Value.
ArgumentValue(position int) interface{}
// ReturnValue returns a representative value for the return at position
// it should be of the proper type. reflect.Zero would be a good mechanism
// to use for this Value.
ReturnValue(position int) interface{}
}
// An Argument Decoder can decode arguments using the non-standard mechanism
//
// If a method implements this interface then the non-standard
// decoder will be used.
//
// Method arguments must be decoded from the message.
// The mechanism for doing this will vary based on the
// implementation of the method. A normal approach is provided
// as part of this library, but may be replaced with
// any other decoding scheme.
type ArgumentDecoder interface {
// To decode the arguments of a method the sender and message are
// provided incase the semantics of the implementer provides access
// to these as part of the method invocation.
DecodeArguments(conn *Conn, sender string, msg *Message, args []interface{}) ([]interface{}, error)
}
// A SignalHandler is responsible for delivering a signal.
//
// Signal delivery may be changed from the default channel
// based approach by Handlers implementing the SignalHandler
// interface.
type SignalHandler interface {
DeliverSignal(iface, name string, signal *Signal)
}
// A DBusError is used to convert a generic object to a D-Bus error.
//
// Any custom error mechanism may implement this interface to provide
// a custom encoding of the error on D-Bus. By default if a normal
// error is returned, it will be encoded as the generic
// "org.freedesktop.DBus.Error.Failed" error. By implementing this
// interface as well a custom encoding may be provided.
type DBusError interface {
DBusError() (string, []interface{})
}

259
vendor/github.com/godbus/dbus/sig.go generated vendored Normal file
View File

@ -0,0 +1,259 @@
package dbus
import (
"fmt"
"reflect"
"strings"
)
var sigToType = map[byte]reflect.Type{
'y': byteType,
'b': boolType,
'n': int16Type,
'q': uint16Type,
'i': int32Type,
'u': uint32Type,
'x': int64Type,
't': uint64Type,
'd': float64Type,
's': stringType,
'g': signatureType,
'o': objectPathType,
'v': variantType,
'h': unixFDIndexType,
}
// Signature represents a correct type signature as specified by the D-Bus
// specification. The zero value represents the empty signature, "".
type Signature struct {
str string
}
// SignatureOf returns the concatenation of all the signatures of the given
// values. It panics if one of them is not representable in D-Bus.
func SignatureOf(vs ...interface{}) Signature {
var s string
for _, v := range vs {
s += getSignature(reflect.TypeOf(v))
}
return Signature{s}
}
// SignatureOfType returns the signature of the given type. It panics if the
// type is not representable in D-Bus.
func SignatureOfType(t reflect.Type) Signature {
return Signature{getSignature(t)}
}
// getSignature returns the signature of the given type and panics on unknown types.
func getSignature(t reflect.Type) string {
// handle simple types first
switch t.Kind() {
case reflect.Uint8:
return "y"
case reflect.Bool:
return "b"
case reflect.Int16:
return "n"
case reflect.Uint16:
return "q"
case reflect.Int, reflect.Int32:
if t == unixFDType {
return "h"
}
return "i"
case reflect.Uint, reflect.Uint32:
if t == unixFDIndexType {
return "h"
}
return "u"
case reflect.Int64:
return "x"
case reflect.Uint64:
return "t"
case reflect.Float64:
return "d"
case reflect.Ptr:
return getSignature(t.Elem())
case reflect.String:
if t == objectPathType {
return "o"
}
return "s"
case reflect.Struct:
if t == variantType {
return "v"
} else if t == signatureType {
return "g"
}
var s string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
s += getSignature(t.Field(i).Type)
}
}
return "(" + s + ")"
case reflect.Array, reflect.Slice:
return "a" + getSignature(t.Elem())
case reflect.Map:
if !isKeyType(t.Key()) {
panic(InvalidTypeError{t})
}
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
case reflect.Interface:
return "v"
}
panic(InvalidTypeError{t})
}
// ParseSignature returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func ParseSignature(s string) (sig Signature, err error) {
if len(s) == 0 {
return
}
if len(s) > 255 {
return Signature{""}, SignatureError{s, "too long"}
}
sig.str = s
for err == nil && len(s) != 0 {
err, s = validSingle(s, 0)
}
if err != nil {
sig = Signature{""}
}
return
}
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
// is not valid.
func ParseSignatureMust(s string) Signature {
sig, err := ParseSignature(s)
if err != nil {
panic(err)
}
return sig
}
// Empty retruns whether the signature is the empty signature.
func (s Signature) Empty() bool {
return s.str == ""
}
// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
err, r := validSingle(s.str, 0)
return err != nil && r == ""
}
// String returns the signature's string representation.
func (s Signature) String() string {
return s.str
}
// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
Sig string
Reason string
}
func (e SignatureError) Error() string {
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
}
// Try to read a single type from this string. If it was successful, err is nil
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
// SignatureError and rem is "". depth is the current recursion depth which may
// not be greater than 64 and should be given as 0 on the first call.
func validSingle(s string, depth int) (err error, rem string) {
if s == "" {
return SignatureError{Sig: s, Reason: "empty signature"}, ""
}
if depth > 64 {
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
}
switch s[0] {
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
return nil, s[1:]
case 'a':
if len(s) > 1 && s[1] == '{' {
i := findMatching(s[1:], '{', '}')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
}
i++
rem = s[i+1:]
s = s[2:i]
if err, _ = validSingle(s[:1], depth+1); err != nil {
return err, ""
}
err, nr := validSingle(s[1:], depth+1)
if err != nil {
return err, ""
}
if nr != "" {
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
}
return nil, rem
}
return validSingle(s[1:], depth+1)
case '(':
i := findMatching(s, '(', ')')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
}
rem = s[i+1:]
s = s[1:i]
for err == nil && s != "" {
err, s = validSingle(s, depth+1)
}
if err != nil {
rem = ""
}
return
}
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}
func findMatching(s string, left, right rune) int {
n := 0
for i, v := range s {
if v == left {
n++
} else if v == right {
n--
}
if n == 0 {
return i
}
}
return -1
}
// typeFor returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func typeFor(s string) (t reflect.Type) {
err, _ := validSingle(s, 0)
if err != nil {
panic(err)
}
if t, ok := sigToType[s[0]]; ok {
return t
}
switch s[0] {
case 'a':
if s[1] == '{' {
i := strings.LastIndex(s, "}")
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
} else {
t = reflect.SliceOf(typeFor(s[1:]))
}
case '(':
t = interfacesType
}
return
}

6
vendor/github.com/godbus/dbus/transport_darwin.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

50
vendor/github.com/godbus/dbus/transport_generic.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package dbus
import (
"encoding/binary"
"errors"
"io"
"unsafe"
)
var nativeEndian binary.ByteOrder
func detectEndianness() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
func init() {
nativeEndian = detectEndianness()
}
type genericTransport struct {
io.ReadWriteCloser
}
func (t genericTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}
func (t genericTransport) SupportsUnixFDs() bool {
return false
}
func (t genericTransport) EnableUnixFDs() {}
func (t genericTransport) ReadMessage() (*Message, error) {
return DecodeMessage(t)
}
func (t genericTransport) SendMessage(msg *Message) error {
for _, v := range msg.Body {
if _, ok := v.(UnixFD); ok {
return errors.New("dbus: unix fd passing not enabled")
}
}
return msg.EncodeTo(t, nativeEndian)
}

43
vendor/github.com/godbus/dbus/transport_tcp.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
//+build !windows
package dbus
import (
"errors"
"net"
)
func init() {
transports["tcp"] = newTcpTransport
}
func tcpFamily(keys string) (string, error) {
switch getKey(keys, "family") {
case "":
return "tcp", nil
case "ipv4":
return "tcp4", nil
case "ipv6":
return "tcp6", nil
default:
return "", errors.New("dbus: invalid tcp family (must be ipv4 or ipv6)")
}
}
func newTcpTransport(keys string) (transport, error) {
host := getKey(keys, "host")
port := getKey(keys, "port")
if host == "" || port == "" {
return nil, errors.New("dbus: unsupported address (must set host and port)")
}
protocol, err := tcpFamily(keys)
if err != nil {
return nil, err
}
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
if err != nil {
return nil, err
}
return NewConn(socket)
}

196
vendor/github.com/godbus/dbus/transport_unix.go generated vendored Normal file
View File

@ -0,0 +1,196 @@
//+build !windows,!solaris
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"syscall"
)
type oobReader struct {
conn *net.UnixConn
oob []byte
buf [4096]byte
}
func (o *oobReader) Read(b []byte) (n int, err error) {
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
if err != nil {
return n, err
}
if flags&syscall.MSG_CTRUNC != 0 {
return n, errors.New("dbus: control data truncated (too many fds received)")
}
o.oob = append(o.oob, o.buf[:oobn]...)
return n, nil
}
type unixTransport struct {
*net.UnixConn
hasUnixFDs bool
}
func newUnixTransport(keys string) (transport, error) {
var err error
t := new(unixTransport)
abstract := getKey(keys, "abstract")
path := getKey(keys, "path")
switch {
case abstract == "" && path == "":
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
case abstract != "" && path == "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
case abstract == "" && path != "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
default:
return nil, errors.New("dbus: invalid address (both path and abstract set)")
}
}
func init() {
transports["unix"] = newUnixTransport
}
func (t *unixTransport) EnableUnixFDs() {
t.hasUnixFDs = true
}
func (t *unixTransport) ReadMessage() (*Message, error) {
var (
blen, hlen uint32
csheader [16]byte
headers []header
order binary.ByteOrder
unixfds uint32
)
// To be sure that all bytes of out-of-band data are read, we use a special
// reader that uses ReadUnix on the underlying connection instead of Read
// and gathers the out-of-band data in a buffer.
rd := &oobReader{conn: t.UnixConn}
// read the first 16 bytes (the part of the header that has a constant size),
// from which we can figure out the length of the rest of the message
if _, err := io.ReadFull(rd, csheader[:]); err != nil {
return nil, err
}
switch csheader[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
// csheader[4:8] -> length of message body, csheader[12:16] -> length of
// header fields (without alignment)
binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
if hlen%8 != 0 {
hlen += 8 - (hlen % 8)
}
// decode headers and look for unix fds
headerdata := make([]byte, hlen+4)
copy(headerdata, csheader[12:])
if _, err := io.ReadFull(t, headerdata[4:]); err != nil {
return nil, err
}
dec := newDecoder(bytes.NewBuffer(headerdata), order)
dec.pos = 12
vs, err := dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
Store(vs, &headers)
for _, v := range headers {
if v.Field == byte(FieldUnixFDs) {
unixfds, _ = v.Variant.value.(uint32)
}
}
all := make([]byte, 16+hlen+blen)
copy(all, csheader[:])
copy(all[16:], headerdata[4:])
if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil {
return nil, err
}
if unixfds != 0 {
if !t.hasUnixFDs {
return nil, errors.New("dbus: got unix fds on unsupported transport")
}
// read the fds from the OOB data
scms, err := syscall.ParseSocketControlMessage(rd.oob)
if err != nil {
return nil, err
}
if len(scms) != 1 {
return nil, errors.New("dbus: received more than one socket control message")
}
fds, err := syscall.ParseUnixRights(&scms[0])
if err != nil {
return nil, err
}
msg, err := DecodeMessage(bytes.NewBuffer(all))
if err != nil {
return nil, err
}
// substitute the values in the message body (which are indices for the
// array receiver via OOB) with the actual values
for i, v := range msg.Body {
if j, ok := v.(UnixFDIndex); ok {
if uint32(j) >= unixfds {
return nil, InvalidMessageError("invalid index for unix fd")
}
msg.Body[i] = UnixFD(fds[j])
}
}
return msg, nil
}
return DecodeMessage(bytes.NewBuffer(all))
}
func (t *unixTransport) SendMessage(msg *Message) error {
fds := make([]int, 0)
for i, v := range msg.Body {
if fd, ok := v.(UnixFD); ok {
msg.Body[i] = UnixFDIndex(len(fds))
fds = append(fds, int(fd))
}
}
if len(fds) != 0 {
if !t.hasUnixFDs {
return errors.New("dbus: unix fd passing not enabled")
}
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
oob := syscall.UnixRights(fds...)
buf := new(bytes.Buffer)
msg.EncodeTo(buf, nativeEndian)
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
if err != nil {
return err
}
if n != buf.Len() || oobn != len(oob) {
return io.ErrShortWrite
}
} else {
if err := msg.EncodeTo(t, nativeEndian); err != nil {
return nil
}
}
return nil
}
func (t *unixTransport) SupportsUnixFDs() bool {
return true
}

View File

@ -0,0 +1,95 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for DragonFly BSD
package dbus
/*
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// http://golang.org/src/pkg/syscall/types_dragonfly.go
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
//salign := sizeofPtr
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
// still require 32-bit aligned access to network subsystem.
//if darwin64Bit || dragonfly64Bit {
// salign = 4
//}
salign := 4
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@ -0,0 +1,91 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for FreeBSD
package dbus
/*
const int sizeofPtr = sizeof(void*);
#define _WANT_UCRED
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// https://golang.org/src/syscall/ztypes_freebsd_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// https://golang.org/src/syscall/types_freebsd.go
// https://github.com/freebsd/freebsd/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
salign := C.sizeofPtr
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@ -0,0 +1,25 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
package dbus
import (
"io"
"os"
"syscall"
)
func (t *unixTransport) SendNullByte() error {
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := syscall.UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More