1package ipvlan
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7
8	"github.com/docker/libnetwork/ns"
9	"github.com/sirupsen/logrus"
10	"github.com/vishvananda/netlink"
11)
12
13const (
14	dummyPrefix     = "di-" // ipvlan prefix for dummy parent interface
15	ipvlanKernelVer = 4     // minimum ipvlan kernel support
16	ipvlanMajorVer  = 2     // minimum ipvlan major kernel support
17)
18
19// createIPVlan Create the ipvlan slave specifying the source name
20func createIPVlan(containerIfName, parent, ipvlanMode string) (string, error) {
21	// Set the ipvlan mode. Default is bridge mode
22	mode, err := setIPVlanMode(ipvlanMode)
23	if err != nil {
24		return "", fmt.Errorf("Unsupported %s ipvlan mode: %v", ipvlanMode, err)
25	}
26	// verify the Docker host interface acting as the macvlan parent iface exists
27	if !parentExists(parent) {
28		return "", fmt.Errorf("the requested parent interface %s was not found on the Docker host", parent)
29	}
30	// Get the link for the master index (Example: the docker host eth iface)
31	parentLink, err := ns.NlHandle().LinkByName(parent)
32	if err != nil {
33		return "", fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", ipvlanType, parent, err)
34	}
35	// Create an ipvlan link
36	ipvlan := &netlink.IPVlan{
37		LinkAttrs: netlink.LinkAttrs{
38			Name:        containerIfName,
39			ParentIndex: parentLink.Attrs().Index,
40		},
41		Mode: mode,
42	}
43	if err := ns.NlHandle().LinkAdd(ipvlan); err != nil {
44		// If a user creates a macvlan and ipvlan on same parent, only one slave iface can be active at a time.
45		return "", fmt.Errorf("failed to create the %s port: %v", ipvlanType, err)
46	}
47
48	return ipvlan.Attrs().Name, nil
49}
50
51// setIPVlanMode setter for one of the two ipvlan port types
52func setIPVlanMode(mode string) (netlink.IPVlanMode, error) {
53	switch mode {
54	case modeL2:
55		return netlink.IPVLAN_MODE_L2, nil
56	case modeL3:
57		return netlink.IPVLAN_MODE_L3, nil
58	default:
59		return 0, fmt.Errorf("Unknown ipvlan mode: %s", mode)
60	}
61}
62
63// parentExists check if the specified interface exists in the default namespace
64func parentExists(ifaceStr string) bool {
65	_, err := ns.NlHandle().LinkByName(ifaceStr)
66	if err != nil {
67		return false
68	}
69
70	return true
71}
72
73// createVlanLink parses sub-interfaces and vlan id for creation
74func createVlanLink(parentName string) error {
75	if strings.Contains(parentName, ".") {
76		parent, vidInt, err := parseVlan(parentName)
77		if err != nil {
78			return err
79		}
80		// VLAN identifier or VID is a 12-bit field specifying the VLAN to which the frame belongs
81		if vidInt > 4094 || vidInt < 1 {
82			return fmt.Errorf("vlan id must be between 1-4094, received: %d", vidInt)
83		}
84		// get the parent link to attach a vlan subinterface
85		parentLink, err := ns.NlHandle().LinkByName(parent)
86		if err != nil {
87			return fmt.Errorf("failed to find master interface %s on the Docker host: %v", parent, err)
88		}
89		vlanLink := &netlink.Vlan{
90			LinkAttrs: netlink.LinkAttrs{
91				Name:        parentName,
92				ParentIndex: parentLink.Attrs().Index,
93			},
94			VlanId: vidInt,
95		}
96		// create the subinterface
97		if err := ns.NlHandle().LinkAdd(vlanLink); err != nil {
98			return fmt.Errorf("failed to create %s vlan link: %v", vlanLink.Name, err)
99		}
100		// Bring the new netlink iface up
101		if err := ns.NlHandle().LinkSetUp(vlanLink); err != nil {
102			return fmt.Errorf("failed to enable %s the ipvlan parent link %v", vlanLink.Name, err)
103		}
104		logrus.Debugf("Added a vlan tagged netlink subinterface: %s with a vlan id: %d", parentName, vidInt)
105		return nil
106	}
107
108	return fmt.Errorf("invalid subinterface vlan name %s, example formatting is eth0.10", parentName)
109}
110
111// delVlanLink verifies only sub-interfaces with a vlan id get deleted
112func delVlanLink(linkName string) error {
113	if strings.Contains(linkName, ".") {
114		_, _, err := parseVlan(linkName)
115		if err != nil {
116			return err
117		}
118		// delete the vlan subinterface
119		vlanLink, err := ns.NlHandle().LinkByName(linkName)
120		if err != nil {
121			return fmt.Errorf("failed to find interface %s on the Docker host : %v", linkName, err)
122		}
123		// verify a parent interface isn't being deleted
124		if vlanLink.Attrs().ParentIndex == 0 {
125			return fmt.Errorf("interface %s does not appear to be a slave device: %v", linkName, err)
126		}
127		// delete the ipvlan slave device
128		if err := ns.NlHandle().LinkDel(vlanLink); err != nil {
129			return fmt.Errorf("failed to delete  %s link: %v", linkName, err)
130		}
131		logrus.Debugf("Deleted a vlan tagged netlink subinterface: %s", linkName)
132	}
133	// if the subinterface doesn't parse to iface.vlan_id leave the interface in
134	// place since it could be a user specified name not created by the driver.
135	return nil
136}
137
138// parseVlan parses and verifies a slave interface name: -o parent=eth0.10
139func parseVlan(linkName string) (string, int, error) {
140	// parse -o parent=eth0.10
141	splitName := strings.Split(linkName, ".")
142	if len(splitName) != 2 {
143		return "", 0, fmt.Errorf("required interface name format is: name.vlan_id, ex. eth0.10 for vlan 10, instead received %s", linkName)
144	}
145	parent, vidStr := splitName[0], splitName[1]
146	// validate type and convert vlan id to int
147	vidInt, err := strconv.Atoi(vidStr)
148	if err != nil {
149		return "", 0, fmt.Errorf("unable to parse a valid vlan id from: %s (ex. eth0.10 for vlan 10)", vidStr)
150	}
151	// Check if the interface exists
152	if !parentExists(parent) {
153		return "", 0, fmt.Errorf("-o parent interface was not found on the host: %s", parent)
154	}
155
156	return parent, vidInt, nil
157}
158
159// createDummyLink creates a dummy0 parent link
160func createDummyLink(dummyName, truncNetID string) error {
161	// create a parent interface since one was not specified
162	parent := &netlink.Dummy{
163		LinkAttrs: netlink.LinkAttrs{
164			Name: dummyName,
165		},
166	}
167	if err := ns.NlHandle().LinkAdd(parent); err != nil {
168		return err
169	}
170	parentDummyLink, err := ns.NlHandle().LinkByName(dummyName)
171	if err != nil {
172		return fmt.Errorf("error occurred looking up the %s parent iface %s error: %s", ipvlanType, dummyName, err)
173	}
174	// bring the new netlink iface up
175	if err := ns.NlHandle().LinkSetUp(parentDummyLink); err != nil {
176		return fmt.Errorf("failed to enable %s the ipvlan parent link: %v", dummyName, err)
177	}
178
179	return nil
180}
181
182// delDummyLink deletes the link type dummy used when -o parent is not passed
183func delDummyLink(linkName string) error {
184	// delete the vlan subinterface
185	dummyLink, err := ns.NlHandle().LinkByName(linkName)
186	if err != nil {
187		return fmt.Errorf("failed to find link %s on the Docker host : %v", linkName, err)
188	}
189	// verify a parent interface is being deleted
190	if dummyLink.Attrs().ParentIndex != 0 {
191		return fmt.Errorf("link %s is not a parent dummy interface", linkName)
192	}
193	// delete the ipvlan dummy device
194	if err := ns.NlHandle().LinkDel(dummyLink); err != nil {
195		return fmt.Errorf("failed to delete the dummy %s link: %v", linkName, err)
196	}
197	logrus.Debugf("Deleted a dummy parent link: %s", linkName)
198
199	return nil
200}
201
202// getDummyName returns the name of a dummy parent with truncated net ID and driver prefix
203func getDummyName(netID string) string {
204	return dummyPrefix + netID
205}
206