mirror of
https://github.com/moby/moby.git
synced 2026-08-08 00:52:17 +00:00
The stringid.TruncateID utility is used to provide a consistent length
for "short IDs" (containers, networks). While the dummy interfaces need
a short identifier, they use their own format and don't have to follow
the same length as is used for "short IDs" elsewhere.
In addition, stringid.TruncateID has an additional check for the given
ID to contain colons (":"), which won't be the case for network-IDs that
are passed to it, so this check is redundant.
This patch moves the truncating local to the getDummyName function, so
that it can define its own semantics, independent of changes elsewhere.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
231 lines
7.6 KiB
Go
231 lines
7.6 KiB
Go
//go:build linux
|
|
|
|
package ipvlan
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/libnetwork/ns"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
const (
|
|
dummyPrefix = "di-" // prefix for dummy ipvlan parent interfaces.
|
|
dummyIDLength = 12 // length for dummy parent interface IDs.
|
|
ipvlanKernelVer = 4 // minimum ipvlan kernel support
|
|
ipvlanMajorVer = 2 // minimum ipvlan major kernel support
|
|
)
|
|
|
|
// createIPVlan Create the ipvlan slave specifying the source name
|
|
func createIPVlan(containerIfName, parent, ipvlanMode, ipvlanFlag string) (string, error) {
|
|
// Set the ipvlan mode and flag. Default is L2 bridge
|
|
mode, err := setIPVlanMode(ipvlanMode)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unsupported %s ipvlan mode: %v", ipvlanMode, err)
|
|
}
|
|
// Set the ipvlan flag. Default is bridge
|
|
flag, err := setIPVlanFlag(ipvlanFlag)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unsupported %s ipvlan flag: %v", ipvlanFlag, err)
|
|
}
|
|
// verify the Docker host interface acting as the macvlan parent iface exists
|
|
if !parentExists(parent) {
|
|
return "", fmt.Errorf("the requested parent interface %s was not found on the Docker host", parent)
|
|
}
|
|
// Get the link for the master index (Example: the docker host eth iface)
|
|
parentLink, err := ns.NlHandle().LinkByName(parent)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error occurred looking up the ipvlan parent iface %s error: %s", parent, err)
|
|
}
|
|
// Create an ipvlan link
|
|
ipvlan := &netlink.IPVlan{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: containerIfName,
|
|
ParentIndex: parentLink.Attrs().Index,
|
|
},
|
|
Mode: mode,
|
|
Flag: flag,
|
|
}
|
|
if err := ns.NlHandle().LinkAdd(ipvlan); err != nil {
|
|
// If a user creates a macvlan and ipvlan on same parent, only one slave iface can be active at a time.
|
|
return "", fmt.Errorf("failed to create the ipvlan port: %v", err)
|
|
}
|
|
|
|
return ipvlan.Attrs().Name, nil
|
|
}
|
|
|
|
// setIPVlanMode setter for one of the three ipvlan port types
|
|
func setIPVlanMode(mode string) (netlink.IPVlanMode, error) {
|
|
switch mode {
|
|
case modeL2:
|
|
return netlink.IPVLAN_MODE_L2, nil
|
|
case modeL3:
|
|
return netlink.IPVLAN_MODE_L3, nil
|
|
case modeL3S:
|
|
return netlink.IPVLAN_MODE_L3S, nil
|
|
default:
|
|
return 0, fmt.Errorf("Unknown ipvlan mode: %s", mode)
|
|
}
|
|
}
|
|
|
|
// setIPVlanFlag setter for one of the three ipvlan port flags
|
|
func setIPVlanFlag(flag string) (netlink.IPVlanFlag, error) {
|
|
switch flag {
|
|
case flagBridge:
|
|
return netlink.IPVLAN_FLAG_BRIDGE, nil
|
|
case flagPrivate:
|
|
return netlink.IPVLAN_FLAG_PRIVATE, nil
|
|
case flagVepa:
|
|
return netlink.IPVLAN_FLAG_VEPA, nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown ipvlan flag: %s", flag)
|
|
}
|
|
}
|
|
|
|
// parentExists check if the specified interface exists in the default namespace
|
|
func parentExists(ifaceStr string) bool {
|
|
_, err := ns.NlHandle().LinkByName(ifaceStr)
|
|
return err == nil
|
|
}
|
|
|
|
// createVlanLink parses sub-interfaces and vlan id for creation
|
|
func createVlanLink(parentName string) error {
|
|
if strings.Contains(parentName, ".") {
|
|
parent, vidInt, err := parseVlan(parentName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// VLAN identifier or VID is a 12-bit field specifying the VLAN to which the frame belongs
|
|
if vidInt > 4094 || vidInt < 1 {
|
|
return fmt.Errorf("vlan id must be between 1-4094, received: %d", vidInt)
|
|
}
|
|
// get the parent link to attach a vlan subinterface
|
|
parentLink, err := ns.NlHandle().LinkByName(parent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find master interface %s on the Docker host: %v", parent, err)
|
|
}
|
|
vlanLink := &netlink.Vlan{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: parentName,
|
|
ParentIndex: parentLink.Attrs().Index,
|
|
},
|
|
VlanId: vidInt,
|
|
}
|
|
// create the subinterface
|
|
if err := ns.NlHandle().LinkAdd(vlanLink); err != nil {
|
|
return fmt.Errorf("failed to create %s vlan link: %v", vlanLink.Name, err)
|
|
}
|
|
// Bring the new netlink iface up
|
|
if err := ns.NlHandle().LinkSetUp(vlanLink); err != nil {
|
|
return fmt.Errorf("failed to enable %s the ipvlan parent link %v", vlanLink.Name, err)
|
|
}
|
|
log.G(context.TODO()).Debugf("Added a vlan tagged netlink subinterface: %s with a vlan id: %d", parentName, vidInt)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("invalid subinterface vlan name %s, example formatting is eth0.10", parentName)
|
|
}
|
|
|
|
// delVlanLink verifies only sub-interfaces with a vlan id get deleted
|
|
func delVlanLink(linkName string) error {
|
|
if strings.Contains(linkName, ".") {
|
|
_, _, err := parseVlan(linkName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// delete the vlan subinterface
|
|
vlanLink, err := ns.NlHandle().LinkByName(linkName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find interface %s on the Docker host : %v", linkName, err)
|
|
}
|
|
// verify a parent interface isn't being deleted
|
|
if vlanLink.Attrs().ParentIndex == 0 {
|
|
return fmt.Errorf("interface %s does not appear to be a slave device: %v", linkName, err)
|
|
}
|
|
// delete the ipvlan slave device
|
|
if err := ns.NlHandle().LinkDel(vlanLink); err != nil {
|
|
return fmt.Errorf("failed to delete %s link: %v", linkName, err)
|
|
}
|
|
log.G(context.TODO()).Debugf("Deleted a vlan tagged netlink subinterface: %s", linkName)
|
|
}
|
|
// if the subinterface doesn't parse to iface.vlan_id leave the interface in
|
|
// place since it could be a user specified name not created by the driver.
|
|
return nil
|
|
}
|
|
|
|
// parseVlan parses and verifies a slave interface name: -o parent=eth0.10
|
|
func parseVlan(linkName string) (string, int, error) {
|
|
// parse -o parent=eth0.10
|
|
splitName := strings.Split(linkName, ".")
|
|
if len(splitName) != 2 {
|
|
return "", 0, fmt.Errorf("required interface name format is: name.vlan_id, ex. eth0.10 for vlan 10, instead received %s", linkName)
|
|
}
|
|
parent, vidStr := splitName[0], splitName[1]
|
|
// validate type and convert vlan id to int
|
|
vidInt, err := strconv.Atoi(vidStr)
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("unable to parse a valid vlan id from: %s (ex. eth0.10 for vlan 10)", vidStr)
|
|
}
|
|
// Check if the interface exists
|
|
if !parentExists(parent) {
|
|
return "", 0, fmt.Errorf("-o parent interface was not found on the host: %s", parent)
|
|
}
|
|
|
|
return parent, vidInt, nil
|
|
}
|
|
|
|
// createDummyLink creates a dummy0 parent link
|
|
func createDummyLink(dummyName, truncNetID string) error {
|
|
// create a parent interface since one was not specified
|
|
parent := &netlink.Dummy{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: dummyName,
|
|
},
|
|
}
|
|
if err := ns.NlHandle().LinkAdd(parent); err != nil {
|
|
return err
|
|
}
|
|
parentDummyLink, err := ns.NlHandle().LinkByName(dummyName)
|
|
if err != nil {
|
|
return fmt.Errorf("error occurred looking up the ipvlan parent iface %s error: %s", dummyName, err)
|
|
}
|
|
// bring the new netlink iface up
|
|
if err := ns.NlHandle().LinkSetUp(parentDummyLink); err != nil {
|
|
return fmt.Errorf("failed to enable %s the ipvlan parent link: %v", dummyName, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// delDummyLink deletes the link type dummy used when -o parent is not passed
|
|
func delDummyLink(linkName string) error {
|
|
// delete the vlan subinterface
|
|
dummyLink, err := ns.NlHandle().LinkByName(linkName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find link %s on the Docker host : %v", linkName, err)
|
|
}
|
|
// verify a parent interface is being deleted
|
|
if dummyLink.Attrs().ParentIndex != 0 {
|
|
return fmt.Errorf("link %s is not a parent dummy interface", linkName)
|
|
}
|
|
// delete the ipvlan dummy device
|
|
if err := ns.NlHandle().LinkDel(dummyLink); err != nil {
|
|
return fmt.Errorf("failed to delete the dummy %s link: %v", linkName, err)
|
|
}
|
|
log.G(context.TODO()).Debugf("Deleted a dummy parent link: %s", linkName)
|
|
|
|
return nil
|
|
}
|
|
|
|
// getDummyName returns the name of a dummy parent with truncated net ID and driver prefix
|
|
func getDummyName(netID string) string {
|
|
if len(netID) > dummyIDLength {
|
|
netID = netID[:dummyIDLength]
|
|
}
|
|
return dummyPrefix + netID
|
|
}
|