-
Notifications
You must be signed in to change notification settings - Fork 881
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} |
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) | ||
|
||
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 | ||
} |
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should no longer run into that error, see: https://github.com/docker/libnetwork/blob/master/default_gateway.go#L117 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, understand now, endpoint only stores the |
||
defaultRoute, err := ifaceGateway(defaultV4RouteCidr) | ||
if err != nil { | ||
return err | ||
} | ||
if err := jinfo.AddStaticRoute(defaultRoute.Destination, defaultRoute.RouteType, defaultRoute.NextHop); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we fail the join in case of this failure ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added, ty!
|
||
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 | ||
} |
There was a problem hiding this comment.
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.