1package netlink
2
3import (
4	"syscall"
5
6	"fmt"
7	"github.com/vishvananda/netlink/nl"
8	"golang.org/x/sys/unix"
9)
10
11// DevlinkDevEswitchAttr represents device's eswitch attributes
12type DevlinkDevEswitchAttr struct {
13	Mode       string
14	InlineMode string
15	EncapMode  string
16}
17
18// DevlinkDevAttrs represents device attributes
19type DevlinkDevAttrs struct {
20	Eswitch DevlinkDevEswitchAttr
21}
22
23// DevlinkDevice represents device and its attributes
24type DevlinkDevice struct {
25	BusName    string
26	DeviceName string
27	Attrs      DevlinkDevAttrs
28}
29
30func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) {
31	devices := make([]*DevlinkDevice, 0, len(msgs))
32	for _, m := range msgs {
33		attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:])
34		if err != nil {
35			return nil, err
36		}
37		dev := &DevlinkDevice{}
38		if err = dev.parseAttributes(attrs); err != nil {
39			return nil, err
40		}
41		devices = append(devices, dev)
42	}
43	return devices, nil
44}
45
46func eswitchStringToMode(modeName string) (uint16, error) {
47	if modeName == "legacy" {
48		return nl.DEVLINK_ESWITCH_MODE_LEGACY, nil
49	} else if modeName == "switchdev" {
50		return nl.DEVLINK_ESWITCH_MODE_SWITCHDEV, nil
51	} else {
52		return 0xffff, fmt.Errorf("invalid switchdev mode")
53	}
54}
55
56func parseEswitchMode(mode uint16) string {
57	var eswitchMode = map[uint16]string{
58		nl.DEVLINK_ESWITCH_MODE_LEGACY:    "legacy",
59		nl.DEVLINK_ESWITCH_MODE_SWITCHDEV: "switchdev",
60	}
61	if eswitchMode[mode] == "" {
62		return "unknown"
63	} else {
64		return eswitchMode[mode]
65	}
66}
67
68func parseEswitchInlineMode(inlinemode uint8) string {
69	var eswitchInlineMode = map[uint8]string{
70		nl.DEVLINK_ESWITCH_INLINE_MODE_NONE:      "none",
71		nl.DEVLINK_ESWITCH_INLINE_MODE_LINK:      "link",
72		nl.DEVLINK_ESWITCH_INLINE_MODE_NETWORK:   "network",
73		nl.DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT: "transport",
74	}
75	if eswitchInlineMode[inlinemode] == "" {
76		return "unknown"
77	} else {
78		return eswitchInlineMode[inlinemode]
79	}
80}
81
82func parseEswitchEncapMode(encapmode uint8) string {
83	var eswitchEncapMode = map[uint8]string{
84		nl.DEVLINK_ESWITCH_ENCAP_MODE_NONE:  "disable",
85		nl.DEVLINK_ESWITCH_ENCAP_MODE_BASIC: "enable",
86	}
87	if eswitchEncapMode[encapmode] == "" {
88		return "unknown"
89	} else {
90		return eswitchEncapMode[encapmode]
91	}
92}
93
94func (d *DevlinkDevice) parseAttributes(attrs []syscall.NetlinkRouteAttr) error {
95	for _, a := range attrs {
96		switch a.Attr.Type {
97		case nl.DEVLINK_ATTR_BUS_NAME:
98			d.BusName = string(a.Value)
99		case nl.DEVLINK_ATTR_DEV_NAME:
100			d.DeviceName = string(a.Value)
101		case nl.DEVLINK_ATTR_ESWITCH_MODE:
102			d.Attrs.Eswitch.Mode = parseEswitchMode(native.Uint16(a.Value))
103		case nl.DEVLINK_ATTR_ESWITCH_INLINE_MODE:
104			d.Attrs.Eswitch.InlineMode = parseEswitchInlineMode(uint8(a.Value[0]))
105		case nl.DEVLINK_ATTR_ESWITCH_ENCAP_MODE:
106			d.Attrs.Eswitch.EncapMode = parseEswitchEncapMode(uint8(a.Value[0]))
107		}
108	}
109	return nil
110}
111
112func (dev *DevlinkDevice) parseEswitchAttrs(msgs [][]byte) {
113	m := msgs[0]
114	attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:])
115	if err != nil {
116		return
117	}
118	dev.parseAttributes(attrs)
119}
120
121func (h *Handle) getEswitchAttrs(family *GenlFamily, dev *DevlinkDevice) {
122	msg := &nl.Genlmsg{
123		Command: nl.DEVLINK_CMD_ESWITCH_GET,
124		Version: nl.GENL_DEVLINK_VERSION,
125	}
126	req := h.newNetlinkRequest(int(family.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK)
127	req.AddData(msg)
128
129	b := make([]byte, len(dev.BusName))
130	copy(b, dev.BusName)
131	data := nl.NewRtAttr(nl.DEVLINK_ATTR_BUS_NAME, b)
132	req.AddData(data)
133
134	b = make([]byte, len(dev.DeviceName))
135	copy(b, dev.DeviceName)
136	data = nl.NewRtAttr(nl.DEVLINK_ATTR_DEV_NAME, b)
137	req.AddData(data)
138
139	msgs, err := req.Execute(unix.NETLINK_GENERIC, 0)
140	if err != nil {
141		return
142	}
143	dev.parseEswitchAttrs(msgs)
144}
145
146// DevLinkGetDeviceList provides a pointer to devlink devices and nil error,
147// otherwise returns an error code.
148func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
149	f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME)
150	if err != nil {
151		return nil, err
152	}
153	msg := &nl.Genlmsg{
154		Command: nl.DEVLINK_CMD_GET,
155		Version: nl.GENL_DEVLINK_VERSION,
156	}
157	req := h.newNetlinkRequest(int(f.ID),
158		unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP)
159	req.AddData(msg)
160	msgs, err := req.Execute(unix.NETLINK_GENERIC, 0)
161	if err != nil {
162		return nil, err
163	}
164	devices, err := parseDevLinkDeviceList(msgs)
165	if err != nil {
166		return nil, err
167	}
168	for _, d := range devices {
169		h.getEswitchAttrs(f, d)
170	}
171	return devices, nil
172}
173
174// DevLinkGetDeviceList provides a pointer to devlink devices and nil error,
175// otherwise returns an error code.
176func DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
177	return pkgHandle.DevLinkGetDeviceList()
178}
179
180func parseDevlinkDevice(msgs [][]byte) (*DevlinkDevice, error) {
181	m := msgs[0]
182	attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:])
183	if err != nil {
184		return nil, err
185	}
186	dev := &DevlinkDevice{}
187	if err = dev.parseAttributes(attrs); err != nil {
188		return nil, err
189	}
190	return dev, nil
191}
192
193func (h *Handle) createCmdReq(cmd uint8, bus string, device string) (*GenlFamily, *nl.NetlinkRequest, error) {
194	f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME)
195	if err != nil {
196		return nil, nil, err
197	}
198
199	msg := &nl.Genlmsg{
200		Command: cmd,
201		Version: nl.GENL_DEVLINK_VERSION,
202	}
203	req := h.newNetlinkRequest(int(f.ID),
204		unix.NLM_F_REQUEST|unix.NLM_F_ACK)
205	req.AddData(msg)
206
207	b := make([]byte, len(bus)+1)
208	copy(b, bus)
209	data := nl.NewRtAttr(nl.DEVLINK_ATTR_BUS_NAME, b)
210	req.AddData(data)
211
212	b = make([]byte, len(device)+1)
213	copy(b, device)
214	data = nl.NewRtAttr(nl.DEVLINK_ATTR_DEV_NAME, b)
215	req.AddData(data)
216
217	return f, req, nil
218}
219
220// DevlinkGetDeviceByName provides a pointer to devlink device and nil error,
221// otherwise returns an error code.
222func (h *Handle) DevLinkGetDeviceByName(Bus string, Device string) (*DevlinkDevice, error) {
223	f, req, err := h.createCmdReq(nl.DEVLINK_CMD_GET, Bus, Device)
224	if err != nil {
225		return nil, err
226	}
227
228	respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0)
229	if err != nil {
230		return nil, err
231	}
232	dev, err := parseDevlinkDevice(respmsg)
233	if err == nil {
234		h.getEswitchAttrs(f, dev)
235	}
236	return dev, err
237}
238
239// DevlinkGetDeviceByName provides a pointer to devlink device and nil error,
240// otherwise returns an error code.
241func DevLinkGetDeviceByName(Bus string, Device string) (*DevlinkDevice, error) {
242	return pkgHandle.DevLinkGetDeviceByName(Bus, Device)
243}
244
245// DevLinkSetEswitchMode sets eswitch mode if able to set successfully or
246// returns an error code.
247// Equivalent to: `devlink dev eswitch set $dev mode switchdev`
248// Equivalent to: `devlink dev eswitch set $dev mode legacy`
249func (h *Handle) DevLinkSetEswitchMode(Dev *DevlinkDevice, NewMode string) error {
250	mode, err := eswitchStringToMode(NewMode)
251	if err != nil {
252		return err
253	}
254
255	_, req, err := h.createCmdReq(nl.DEVLINK_CMD_ESWITCH_SET, Dev.BusName, Dev.DeviceName)
256	if err != nil {
257		return err
258	}
259
260	req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_ESWITCH_MODE, nl.Uint16Attr(mode)))
261
262	_, err = req.Execute(unix.NETLINK_GENERIC, 0)
263	return err
264}
265
266// DevLinkSetEswitchMode sets eswitch mode if able to set successfully or
267// returns an error code.
268// Equivalent to: `devlink dev eswitch set $dev mode switchdev`
269// Equivalent to: `devlink dev eswitch set $dev mode legacy`
270func DevLinkSetEswitchMode(Dev *DevlinkDevice, NewMode string) error {
271	return pkgHandle.DevLinkSetEswitchMode(Dev, NewMode)
272}
273