Merge pull request #254 from steveeJ/ip-based-macaddr

pkg/ipam: IP based macaddr
This commit is contained in:
Stefan Junker 2016-08-01 17:43:49 -07:00 committed by GitHub
commit f5ead7969a
13 changed files with 480 additions and 8 deletions

27
pkg/ip/ip_suite_test.go Normal file
View File

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

View File

@ -21,6 +21,7 @@ import (
"os"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/utils/hwaddr"
"github.com/vishvananda/netlink"
)
@ -151,3 +152,30 @@ func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
return addrs[0].IPNet, nil
}
func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
iface, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
switch {
case ip4 == nil && ip6 == nil:
return fmt.Errorf("neither ip4 or ip6 specified")
case ip4 != nil:
{
hwAddr, err := hwaddr.GenerateHardwareAddr4(ip4, hwaddr.PrivateMACPrefix)
if err != nil {
return fmt.Errorf("failed to generate hardware addr: %v", err)
}
if err = netlink.LinkSetHardwareAddr(iface, hwAddr); err != nil {
return fmt.Errorf("failed to add hardware addr to %q: %v", ifName, err)
}
}
case ip6 != nil:
// TODO: IPv6
}
return nil
}

179
pkg/ip/link_test.go Normal file
View File

@ -0,0 +1,179 @@
// 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_test
import (
"fmt"
"net"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/ns"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
func getHwAddr(linkname string) string {
veth, err := netlink.LinkByName(linkname)
Expect(err).NotTo(HaveOccurred())
return fmt.Sprintf("%s", veth.Attrs().HardwareAddr)
}
var _ = Describe("Link", func() {
const (
ifaceFormatString string = "i%d"
mtu int = 1400
ip4onehwaddr = "0a:58:01:01:01:01"
)
var (
hostNetNS ns.NetNS
containerNetNS ns.NetNS
ifaceCounter int = 0
hostVethName string
containerVethName string
ip4one = net.ParseIP("1.1.1.1")
ip4two = net.ParseIP("1.1.1.2")
)
BeforeEach(func() {
var err error
hostNetNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
containerNetNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, containerVeth, err := ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, hostNetNS)
if err != nil {
return err
}
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Attrs().Name
containerVethName = containerVeth.Attrs().Name
return nil
})
})
AfterEach(func() {
Expect(containerNetNS.Close()).To(Succeed())
Expect(hostNetNS.Close()).To(Succeed())
ifaceCounter++
})
It("SetupVeth must put the veth endpoints into the separate namespaces", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(hostVethName)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
It("DelLinkByName must delete the veth endpoints", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// this will delete the host endpoint too
err := ip.DelLinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(containerVethName)
Expect(err).To(HaveOccurred())
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(hostVethName)
Expect(err).To(HaveOccurred())
return nil
})
})
It("DelLinkByNameAddr must throw an error for configured interfaces", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// this will delete the host endpoint too
addr, err := ip.DelLinkByNameAddr(containerVethName, nl.FAMILY_V4)
Expect(err).To(HaveOccurred())
var ipNetNil *net.IPNet
Expect(addr).To(Equal(ipNetNil))
return nil
})
})
It("SetHWAddrByIP must change the interface hwaddr and be predictable", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
hwaddrBefore := getHwAddr(containerVethName)
err = ip.SetHWAddrByIP(containerVethName, ip4one, nil)
Expect(err).NotTo(HaveOccurred())
hwaddrAfter1 := getHwAddr(containerVethName)
Expect(hwaddrBefore).NotTo(Equal(hwaddrAfter1))
Expect(hwaddrAfter1).To(Equal(ip4onehwaddr))
return nil
})
})
It("SetHWAddrByIP must be injective", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := ip.SetHWAddrByIP(containerVethName, ip4one, nil)
Expect(err).NotTo(HaveOccurred())
hwaddrAfter1 := getHwAddr(containerVethName)
err = ip.SetHWAddrByIP(containerVethName, ip4two, nil)
Expect(err).NotTo(HaveOccurred())
hwaddrAfter2 := getHwAddr(containerVethName)
Expect(hwaddrAfter1).NotTo(Equal(hwaddrAfter2))
return nil
})
})
})

View File

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

View File

@ -0,0 +1,63 @@
// 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 hwaddr
import (
"fmt"
"net"
)
const (
ipRelevantByteLen = 4
PrivateMACPrefixString = "0a:58"
)
var (
// private mac prefix safe to use
PrivateMACPrefix = []byte{0x0a, 0x58}
)
type SupportIp4OnlyErr struct{ msg string }
func (e SupportIp4OnlyErr) Error() string { return e.msg }
type MacParseErr struct{ msg string }
func (e MacParseErr) Error() string { return e.msg }
type InvalidPrefixLengthErr struct{ msg string }
func (e InvalidPrefixLengthErr) Error() string { return e.msg }
// GenerateHardwareAddr4 generates 48 bit virtual mac addresses based on the IP4 input.
func GenerateHardwareAddr4(ip net.IP, prefix []byte) (net.HardwareAddr, error) {
switch {
case ip.To4() == nil:
return nil, SupportIp4OnlyErr{msg: "GenerateHardwareAddr4 only supports valid IPv4 address as input"}
case len(prefix) != len(PrivateMACPrefix):
return nil, InvalidPrefixLengthErr{msg: fmt.Sprintf(
"Prefix has length %d instead of %d", len(prefix), len(PrivateMACPrefix)),
}
}
ipByteLen := len(ip)
return (net.HardwareAddr)(
append(
prefix,
ip[ipByteLen-ipRelevantByteLen:ipByteLen]...),
), nil
}

View File

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

View File

@ -0,0 +1,74 @@
// 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 hwaddr_test
import (
"net"
"github.com/containernetworking/cni/pkg/utils/hwaddr"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Hwaddr", func() {
Context("Generate Hardware Address", func() {
It("generate hardware address based on ipv4 address", func() {
testCases := []struct {
ip net.IP
expectedMAC net.HardwareAddr
}{
{
ip: net.ParseIP("10.0.0.2"),
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0x00, 0x00, 0x02)),
},
{
ip: net.ParseIP("10.250.0.244"),
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0xfa, 0x00, 0xf4)),
},
{
ip: net.ParseIP("172.17.0.2"),
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
},
{
ip: net.IPv4(byte(172), byte(17), byte(0), byte(2)),
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
},
}
for _, tc := range testCases {
mac, err := hwaddr.GenerateHardwareAddr4(tc.ip, hwaddr.PrivateMACPrefix)
Expect(err).NotTo(HaveOccurred())
Expect(mac).To(Equal(tc.expectedMAC))
}
})
It("return error if input is not ipv4 address", func() {
testCases := []net.IP{
net.ParseIP(""),
net.ParseIP("2001:db8:0:1:1:1:1:1"),
}
for _, tc := range testCases {
_, err := hwaddr.GenerateHardwareAddr4(tc, hwaddr.PrivateMACPrefix)
Expect(err).To(BeAssignableToTypeOf(hwaddr.SupportIp4OnlyErr{}))
}
})
It("return error if prefix is invalid", func() {
_, err := hwaddr.GenerateHardwareAddr4(net.ParseIP("10.0.0.2"), []byte{0x58})
Expect(err).To(BeAssignableToTypeOf(hwaddr.InvalidPrefixLengthErr{}))
})
})
})

View File

@ -247,7 +247,15 @@ func cmdAdd(args *skel.CmdArgs) error {
// TODO: IPV6
}
return ipam.ConfigureIface(args.IfName, result)
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
return err
}
if err := ip.SetHWAddrByIP(args.IfName, result.IP4.IP.IP, nil /* TODO IPv6 */); err != nil {
return err
}
return nil
}); err != nil {
return err
}
@ -262,6 +270,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if err := ip.SetHWAddrByIP(n.BrName, gwn.IP, nil /* TODO IPv6 */); err != nil {
return err
}
if err := ip.EnableIP4Forward(); err != nil {
return fmt.Errorf("failed to enable forwarding: %v", err)
}

View File

@ -24,6 +24,8 @@ import (
"github.com/containernetworking/cni/pkg/testutils"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils/hwaddr"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
@ -179,9 +181,19 @@ var _ = Describe("bridge Operations", func() {
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
for _, l := range links {
if l.Attrs().Name != BRNAME && l.Attrs().Name != "lo" {
_, isVeth := l.(*netlink.Veth)
Expect(isVeth).To(Equal(true))
switch {
case l.Attrs().Name == BRNAME:
{
_, isBridge := l.(*netlink.Bridge)
Expect(isBridge).To(Equal(true))
hwAddr := fmt.Sprintf("%s", l.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
}
case l.Attrs().Name != BRNAME && l.Attrs().Name != "lo":
{
_, isVeth := l.(*netlink.Veth)
Expect(isVeth).To(Equal(true))
}
}
}
Expect(err).NotTo(HaveOccurred())
@ -197,6 +209,9 @@ var _ = Describe("bridge Operations", func() {
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
// Ensure the default route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())

View File

@ -149,6 +149,10 @@ func cmdAdd(args *skel.CmdArgs) error {
}
err = netns.Do(func(_ ns.NetNS) error {
if err := ip.SetHWAddrByIP(args.IfName, result.IP4.IP.IP, nil /* TODO IPv6 */); err != nil {
return err
}
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {

View File

@ -21,6 +21,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/testutils"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils/hwaddr"
"github.com/vishvananda/netlink"
@ -139,6 +140,10 @@ var _ = Describe("macvlan Operations", func() {
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
return nil
})
Expect(err).NotTo(HaveOccurred())

View File

@ -64,6 +64,15 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string
return err
}
hostNS.Do(func(_ ns.NetNS) error {
hostVethName = hostVeth.Attrs().Name
if err := ip.SetHWAddrByIP(hostVethName, pr.IP4.IP.IP, nil /* TODO IPv6 */); err != nil {
return fmt.Errorf("failed to set hardware addr by IP: %v", err)
}
return nil
})
if err = ipam.ConfigureIface(ifName, pr); err != nil {
return err
}
@ -73,6 +82,10 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string
return fmt.Errorf("failed to look up %q: %v", ifName, err)
}
if err := ip.SetHWAddrByIP(contVeth.Attrs().Name, pr.IP4.IP.IP, nil /* TODO IPv6 */); err != nil {
return fmt.Errorf("failed to set hardware addr by IP: %v", err)
}
// Delete the route that was automatically added
route := netlink.Route{
LinkIndex: contVeth.Attrs().Index,
@ -113,8 +126,6 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string
}
}
hostVethName = hostVeth.Attrs().Name
return nil
})
return hostVethName, err

4
test
View File

@ -11,8 +11,8 @@ set -e
source ./build
TESTABLE="libcni plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/test/noop"
FORMATTABLE="$TESTABLE libcni pkg/ip pkg/ipam pkg/testutils plugins/meta/flannel plugins/meta/tuning"
TESTABLE="libcni plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/test/noop pkg/utils/hwaddr pkg/ip"
FORMATTABLE="$TESTABLE pkg/testutils plugins/meta/flannel plugins/meta/tuning"
# user has not provided PKG override
if [ -z "$PKG" ]; then