Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added macvlan and ipvlan drivers #964

Merged
merged 2 commits into from
Mar 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ build: ${build_image}.created

build-local:
@mkdir -p "bin"
$(shell which godep) go build -o "bin/dnet" ./cmd/dnet
$(shell which godep) go build -tags experimental -o "bin/dnet" ./cmd/dnet

clean:
@if [ -d bin ]; then \
Expand Down
83 changes: 83 additions & 0 deletions drivers/ipvlan/ipvlan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ipvlan

import (
"net"
"sync"

"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/discoverapi"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/osl"
)

const (
vethLen = 7
containerVethPrefix = "eth"
vethPrefix = "veth"
ipvlanType = "ipvlan" // driver type name
modeL2 = "l2" // ipvlan mode l2 is the default
modeL3 = "l3" // ipvlan L3 mode
parentOpt = "parent" // parent interface -o parent
modeOpt = "_mode" // ipvlan mode ux opt suffix
)

var driverModeOpt = ipvlanType + modeOpt // mode -o ipvlan_mode

type endpointTable map[string]*endpoint

type networkTable map[string]*network

type driver struct {
networks networkTable
sync.Once
sync.Mutex
store datastore.DataStore
}

type endpoint struct {
id string
mac net.HardwareAddr
addr *net.IPNet
addrv6 *net.IPNet
srcName string
}

type network struct {
id string
sbox osl.Sandbox
endpoints endpointTable
driver *driver
config *configuration
sync.Mutex
}

// Init initializes and registers the libnetwork ipvlan driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
c := driverapi.Capability{
DataScope: datastore.LocalScope,
}
d := &driver{
networks: networkTable{},
}
d.initStore(config)

return dc.RegisterDriver(ipvlanType, d, c)
}

func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
return make(map[string]interface{}, 0), nil
}

func (d *driver) Type() string {
return ipvlanType
}

// DiscoverNew is a notification for a new discovery event.
func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
return nil
}

// DiscoverDelete is a notification for a discovery delete event.
func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
return nil
}
82 changes: 82 additions & 0 deletions drivers/ipvlan/ipvlan_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ipvlan

import (
"fmt"

"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)

// CreateEndpoint assigns the mac, ip and endpoint id for the new container
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
epOptions map[string]interface{}) error {

if err := validateID(nid, eid); err != nil {
return err
}
n, err := d.getNetwork(nid)
if err != nil {
return fmt.Errorf("network id %q not found", nid)
}
if ifInfo.MacAddress() != nil {
return fmt.Errorf("%s interfaces do not support custom mac address assigment", ipvlanType)
}
ep := &endpoint{
id: eid,
addr: ifInfo.Address(),
addrv6: ifInfo.AddressIPv6(),
mac: ifInfo.MacAddress(),
}
if ep.addr == nil {
return fmt.Errorf("create endpoint was not passed an IP address")
}
if ep.mac == nil {
ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
if err := ifInfo.SetMacAddress(ep.mac); err != nil {
return err
}
}
// disallow port mapping -p
if opt, ok := epOptions[netlabel.PortMap]; ok {
if _, ok := opt.([]types.PortBinding); ok {
if len(opt.([]types.PortBinding)) > 0 {
logrus.Warnf("%s driver does not support port mappings", ipvlanType)
}
}
}
// disallow port exposure --expose
if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
if _, ok := opt.([]types.TransportPort); ok {
if len(opt.([]types.TransportPort)) > 0 {
logrus.Warnf("%s driver does not support port exposures", ipvlanType)
}
}
}
n.addEndpoint(ep)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see any code for handling the port mapping, in case user starts the container specifying the exposed ports.
If this is not available yet, we should refuse the endpoint creation with proper error message.


return nil
}

// DeleteEndpoint remove the endpoint and associated netlink interface
func (d *driver) DeleteEndpoint(nid, eid string) error {
if err := validateID(nid, eid); err != nil {
return err
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("network id %q not found", nid)
}
ep := n.endpoint(eid)
if ep == nil {
return fmt.Errorf("endpoint id %q not found", eid)
}
if link, err := netlink.LinkByName(ep.srcName); err == nil {
netlink.LinkDel(link)
}

return nil
}
194 changes: 194 additions & 0 deletions drivers/ipvlan/ipvlan_joinleave.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package ipvlan

import (
"fmt"
"net"

"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/osl"
"github.com/docker/libnetwork/types"
)

type staticRoute struct {
Destination *net.IPNet
RouteType int
NextHop net.IP
}

const (
defaultV4RouteCidr = "0.0.0.0/0"
defaultV6RouteCidr = "::/0"
)

// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
defer osl.InitOSContext()()
n, err := d.getNetwork(nid)
if err != nil {
return err
}
endpoint := n.endpoint(eid)
if endpoint == nil {
return fmt.Errorf("could not find endpoint with id %s", eid)
}
// generate a name for the iface that will be renamed to eth0 in the sbox
containerIfName, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
if err != nil {
return fmt.Errorf("error generating an interface name: %v", err)
}
// create the netlink ipvlan interface
vethName, err := createIPVlan(containerIfName, n.config.Parent, n.config.IpvlanMode)
if err != nil {
return err
}
// bind the generated iface name to the endpoint
endpoint.srcName = vethName
ep := n.endpoint(eid)
if ep == nil {
return fmt.Errorf("could not find endpoint with id %s", eid)
}
if n.config.IpvlanMode == modeL3 {
// disable gateway services to add a default gw using dev eth0 only
jinfo.DisableGatewayService()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed if you are anyway adding a default static route to joininfo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point, what I ran into is If I don't disable the service, gateway services will try and add a default route on top of the static default route that is added. The route is added as a static because L3 mode doesn't have a next hop and looks like this to the host default dev eth0.

failed to set gateway while updating gateway: file exists complete logs here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless that check does not work, which seems to be the case... I will take a look at it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, understand now, endpoint only stores the types.NEXT_HOP static routes. This one is of type types.CONNECTED. Thanks.

defaultRoute, err := ifaceGateway(defaultV4RouteCidr)
if err != nil {
return err
}
if err := jinfo.AddStaticRoute(defaultRoute.Destination, defaultRoute.RouteType, defaultRoute.NextHop); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we fail the join in case of this failure ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, ty!

if err := jinfo.AddStaticRoute(defaultRoute.Destination, defaultRoute.RouteType, defaultRoute.NextHop); err != nil {
    return fmt.Errorf("failed to set an ipvlan l3 mode default gateway: %v", err)
}

return fmt.Errorf("failed to set an ipvlan l3 mode ipv4 default gateway: %v", err)
}
logrus.Debugf("Ipvlan Endpoint Joined with IPv4_Addr: %s, Ipvlan_Mode: %s, Parent: %s",
ep.addr.IP.String(), n.config.IpvlanMode, n.config.Parent)
// If the endpoint has a v6 address, set a v6 default route
if ep.addrv6 != nil {
default6Route, err := ifaceGateway(defaultV6RouteCidr)
if err != nil {
return err
}
if err = jinfo.AddStaticRoute(default6Route.Destination, default6Route.RouteType, default6Route.NextHop); err != nil {
return fmt.Errorf("failed to set an ipvlan l3 mode ipv6 default gateway: %v", err)
}
logrus.Debugf("Ipvlan Endpoint Joined with IPv6_Addr: %s, Ipvlan_Mode: %s, Parent: %s",
ep.addrv6.IP.String(), n.config.IpvlanMode, n.config.Parent)
}
}
if n.config.IpvlanMode == modeL2 {
// parse and correlate the endpoint v4 address with the available v4 subnets
if len(n.config.Ipv4Subnets) > 0 {
s := n.getSubnetforIPv4(ep.addr)
if s == nil {
return fmt.Errorf("could not find a valid ipv4 subnet for endpoint %s", eid)
}
v4gw, _, err := net.ParseCIDR(s.GwIP)
if err != nil {
return fmt.Errorf("gatway %s is not a valid ipv4 address: %v", s.GwIP, err)
}
err = jinfo.SetGateway(v4gw)
if err != nil {
return err
}
logrus.Debugf("Ipvlan Endpoint Joined with IPv4_Addr: %s, Gateway: %s, Ipvlan_Mode: %s, Parent: %s",
ep.addr.IP.String(), v4gw.String(), n.config.IpvlanMode, n.config.Parent)
}
// parse and correlate the endpoint v6 address with the available v6 subnets
if len(n.config.Ipv6Subnets) > 0 {
s := n.getSubnetforIPv6(ep.addrv6)
if s == nil {
return fmt.Errorf("could not find a valid ipv6 subnet for endpoint %s", eid)
}
v6gw, _, err := net.ParseCIDR(s.GwIP)
if err != nil {
return fmt.Errorf("gatway %s is not a valid ipv6 address: %v", s.GwIP, err)
}
err = jinfo.SetGatewayIPv6(v6gw)
if err != nil {
return err
}
logrus.Debugf("Ipvlan Endpoint Joined with IPv6_Addr: %s, Gateway: %s, Ipvlan_Mode: %s, Parent: %s",
ep.addrv6.IP.String(), v6gw.String(), n.config.IpvlanMode, n.config.Parent)
}
}
iNames := jinfo.InterfaceName()
err = iNames.SetNames(vethName, containerVethPrefix)
if err != nil {
return err
}

return nil
}

// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
network, err := d.getNetwork(nid)
if err != nil {
return err
}
endpoint, err := network.getEndpoint(eid)
if err != nil {
return err
}
if endpoint == nil {
return fmt.Errorf("could not find endpoint with id %s", eid)
}

return nil
}

// ifaceGateway returns a static route for either v4/v6 to be set to the container eth0
func ifaceGateway(dfNet string) (*staticRoute, error) {
nh, dst, err := net.ParseCIDR(dfNet)
if err != nil {
return nil, fmt.Errorf("unable to parse default route %v", err)
}
defaultRoute := &staticRoute{
Destination: dst,
RouteType: types.CONNECTED,
NextHop: nh,
}

return defaultRoute, nil
}

// getSubnetforIPv4 returns the ipv4 subnet to which the given IP belongs
func (n *network) getSubnetforIPv4(ip *net.IPNet) *ipv4Subnet {
for _, s := range n.config.Ipv4Subnets {
_, snet, err := net.ParseCIDR(s.SubnetIP)
if err != nil {
return nil
}
// first check if the mask lengths are the same
i, _ := snet.Mask.Size()
j, _ := ip.Mask.Size()
if i != j {
continue
}
if snet.Contains(ip.IP) {
return s
}
}

return nil
}

// getSubnetforIPv6 returns the ipv6 subnet to which the given IP belongs
func (n *network) getSubnetforIPv6(ip *net.IPNet) *ipv6Subnet {
for _, s := range n.config.Ipv6Subnets {
_, snet, err := net.ParseCIDR(s.SubnetIP)
if err != nil {
return nil
}
// first check if the mask lengths are the same
i, _ := snet.Mask.Size()
j, _ := ip.Mask.Size()
if i != j {
continue
}
if snet.Contains(ip.IP) {
return s
}
}

return nil
}
Loading