1package netlink
2
3import (
4	"bytes"
5	"encoding/binary"
6	"encoding/hex"
7	"errors"
8	"fmt"
9	"syscall"
10
11	"github.com/vishvananda/netlink/nl"
12	"golang.org/x/sys/unix"
13)
14
15// Internal tc_stats representation in Go struct.
16// This is for internal uses only to deserialize the payload of rtattr.
17// After the deserialization, this should be converted into the canonical stats
18// struct, ClassStatistics, in case of statistics of a class.
19// Ref: struct tc_stats { ... }
20type tcStats struct {
21	Bytes      uint64 // Number of enqueued bytes
22	Packets    uint32 // Number of enqueued packets
23	Drops      uint32 // Packets dropped because of lack of resources
24	Overlimits uint32 // Number of throttle events when this flow goes out of allocated bandwidth
25	Bps        uint32 // Current flow byte rate
26	Pps        uint32 // Current flow packet rate
27	Qlen       uint32
28	Backlog    uint32
29}
30
31// NewHtbClass NOTE: function is in here because it uses other linux functions
32func NewHtbClass(attrs ClassAttrs, cattrs HtbClassAttrs) *HtbClass {
33	mtu := 1600
34	rate := cattrs.Rate / 8
35	ceil := cattrs.Ceil / 8
36	buffer := cattrs.Buffer
37	cbuffer := cattrs.Cbuffer
38
39	if ceil == 0 {
40		ceil = rate
41	}
42
43	if buffer == 0 {
44		buffer = uint32(float64(rate)/Hz() + float64(mtu))
45	}
46	buffer = uint32(Xmittime(rate, buffer))
47
48	if cbuffer == 0 {
49		cbuffer = uint32(float64(ceil)/Hz() + float64(mtu))
50	}
51	cbuffer = uint32(Xmittime(ceil, cbuffer))
52
53	return &HtbClass{
54		ClassAttrs: attrs,
55		Rate:       rate,
56		Ceil:       ceil,
57		Buffer:     buffer,
58		Cbuffer:    cbuffer,
59		Quantum:    10,
60		Level:      0,
61		Prio:       0,
62	}
63}
64
65// ClassDel will delete a class from the system.
66// Equivalent to: `tc class del $class`
67func ClassDel(class Class) error {
68	return pkgHandle.ClassDel(class)
69}
70
71// ClassDel will delete a class from the system.
72// Equivalent to: `tc class del $class`
73func (h *Handle) ClassDel(class Class) error {
74	return h.classModify(unix.RTM_DELTCLASS, 0, class)
75}
76
77// ClassChange will change a class in place
78// Equivalent to: `tc class change $class`
79// The parent and handle MUST NOT be changed.
80func ClassChange(class Class) error {
81	return pkgHandle.ClassChange(class)
82}
83
84// ClassChange will change a class in place
85// Equivalent to: `tc class change $class`
86// The parent and handle MUST NOT be changed.
87func (h *Handle) ClassChange(class Class) error {
88	return h.classModify(unix.RTM_NEWTCLASS, 0, class)
89}
90
91// ClassReplace will replace a class to the system.
92// quivalent to: `tc class replace $class`
93// The handle MAY be changed.
94// If a class already exist with this parent/handle pair, the class is changed.
95// If a class does not already exist with this parent/handle, a new class is created.
96func ClassReplace(class Class) error {
97	return pkgHandle.ClassReplace(class)
98}
99
100// ClassReplace will replace a class to the system.
101// quivalent to: `tc class replace $class`
102// The handle MAY be changed.
103// If a class already exist with this parent/handle pair, the class is changed.
104// If a class does not already exist with this parent/handle, a new class is created.
105func (h *Handle) ClassReplace(class Class) error {
106	return h.classModify(unix.RTM_NEWTCLASS, unix.NLM_F_CREATE, class)
107}
108
109// ClassAdd will add a class to the system.
110// Equivalent to: `tc class add $class`
111func ClassAdd(class Class) error {
112	return pkgHandle.ClassAdd(class)
113}
114
115// ClassAdd will add a class to the system.
116// Equivalent to: `tc class add $class`
117func (h *Handle) ClassAdd(class Class) error {
118	return h.classModify(
119		unix.RTM_NEWTCLASS,
120		unix.NLM_F_CREATE|unix.NLM_F_EXCL,
121		class,
122	)
123}
124
125func (h *Handle) classModify(cmd, flags int, class Class) error {
126	req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK)
127	base := class.Attrs()
128	msg := &nl.TcMsg{
129		Family:  nl.FAMILY_ALL,
130		Ifindex: int32(base.LinkIndex),
131		Handle:  base.Handle,
132		Parent:  base.Parent,
133	}
134	req.AddData(msg)
135
136	if cmd != unix.RTM_DELTCLASS {
137		if err := classPayload(req, class); err != nil {
138			return err
139		}
140	}
141	_, err := req.Execute(unix.NETLINK_ROUTE, 0)
142	return err
143}
144
145func classPayload(req *nl.NetlinkRequest, class Class) error {
146	req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(class.Type())))
147
148	options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
149	switch class.Type() {
150	case "htb":
151		htb := class.(*HtbClass)
152		opt := nl.TcHtbCopt{}
153		opt.Buffer = htb.Buffer
154		opt.Cbuffer = htb.Cbuffer
155		opt.Quantum = htb.Quantum
156		opt.Level = htb.Level
157		opt.Prio = htb.Prio
158		// TODO: Handle Debug properly. For now default to 0
159		/* Calculate {R,C}Tab and set Rate and Ceil */
160		cellLog := -1
161		ccellLog := -1
162		linklayer := nl.LINKLAYER_ETHERNET
163		mtu := 1600
164		var rtab [256]uint32
165		var ctab [256]uint32
166		tcrate := nl.TcRateSpec{Rate: uint32(htb.Rate)}
167		if CalcRtable(&tcrate, rtab[:], cellLog, uint32(mtu), linklayer) < 0 {
168			return errors.New("HTB: failed to calculate rate table")
169		}
170		opt.Rate = tcrate
171		tcceil := nl.TcRateSpec{Rate: uint32(htb.Ceil)}
172		if CalcRtable(&tcceil, ctab[:], ccellLog, uint32(mtu), linklayer) < 0 {
173			return errors.New("HTB: failed to calculate ceil rate table")
174		}
175		opt.Ceil = tcceil
176		options.AddRtAttr(nl.TCA_HTB_PARMS, opt.Serialize())
177		options.AddRtAttr(nl.TCA_HTB_RTAB, SerializeRtab(rtab))
178		options.AddRtAttr(nl.TCA_HTB_CTAB, SerializeRtab(ctab))
179	case "hfsc":
180		hfsc := class.(*HfscClass)
181		opt := nl.HfscCopt{}
182		opt.Rsc.Set(hfsc.Rsc.Attrs())
183		opt.Fsc.Set(hfsc.Fsc.Attrs())
184		opt.Usc.Set(hfsc.Usc.Attrs())
185		options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc))
186		options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc))
187		options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc))
188	}
189	req.AddData(options)
190	return nil
191}
192
193// ClassList gets a list of classes in the system.
194// Equivalent to: `tc class show`.
195// Generally returns nothing if link and parent are not specified.
196func ClassList(link Link, parent uint32) ([]Class, error) {
197	return pkgHandle.ClassList(link, parent)
198}
199
200// ClassList gets a list of classes in the system.
201// Equivalent to: `tc class show`.
202// Generally returns nothing if link and parent are not specified.
203func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
204	req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP)
205	msg := &nl.TcMsg{
206		Family: nl.FAMILY_ALL,
207		Parent: parent,
208	}
209	if link != nil {
210		base := link.Attrs()
211		h.ensureIndex(base)
212		msg.Ifindex = int32(base.Index)
213	}
214	req.AddData(msg)
215
216	msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS)
217	if err != nil {
218		return nil, err
219	}
220
221	var res []Class
222	for _, m := range msgs {
223		msg := nl.DeserializeTcMsg(m)
224
225		attrs, err := nl.ParseRouteAttr(m[msg.Len():])
226		if err != nil {
227			return nil, err
228		}
229
230		base := ClassAttrs{
231			LinkIndex:  int(msg.Ifindex),
232			Handle:     msg.Handle,
233			Parent:     msg.Parent,
234			Statistics: nil,
235		}
236
237		var class Class
238		classType := ""
239		for _, attr := range attrs {
240			switch attr.Attr.Type {
241			case nl.TCA_KIND:
242				classType = string(attr.Value[:len(attr.Value)-1])
243				switch classType {
244				case "htb":
245					class = &HtbClass{}
246				case "hfsc":
247					class = &HfscClass{}
248				default:
249					class = &GenericClass{ClassType: classType}
250				}
251			case nl.TCA_OPTIONS:
252				switch classType {
253				case "htb":
254					data, err := nl.ParseRouteAttr(attr.Value)
255					if err != nil {
256						return nil, err
257					}
258					_, err = parseHtbClassData(class, data)
259					if err != nil {
260						return nil, err
261					}
262				case "hfsc":
263					data, err := nl.ParseRouteAttr(attr.Value)
264					if err != nil {
265						return nil, err
266					}
267					_, err = parseHfscClassData(class, data)
268					if err != nil {
269						return nil, err
270					}
271				}
272			// For backward compatibility.
273			case nl.TCA_STATS:
274				base.Statistics, err = parseTcStats(attr.Value)
275				if err != nil {
276					return nil, err
277				}
278			case nl.TCA_STATS2:
279				base.Statistics, err = parseTcStats2(attr.Value)
280				if err != nil {
281					return nil, err
282				}
283			}
284		}
285		*class.Attrs() = base
286		res = append(res, class)
287	}
288
289	return res, nil
290}
291
292func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
293	htb := class.(*HtbClass)
294	detailed := false
295	for _, datum := range data {
296		switch datum.Attr.Type {
297		case nl.TCA_HTB_PARMS:
298			opt := nl.DeserializeTcHtbCopt(datum.Value)
299			htb.Rate = uint64(opt.Rate.Rate)
300			htb.Ceil = uint64(opt.Ceil.Rate)
301			htb.Buffer = opt.Buffer
302			htb.Cbuffer = opt.Cbuffer
303			htb.Quantum = opt.Quantum
304			htb.Level = opt.Level
305			htb.Prio = opt.Prio
306		}
307	}
308	return detailed, nil
309}
310
311func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
312	hfsc := class.(*HfscClass)
313	detailed := false
314	for _, datum := range data {
315		m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs()
316		switch datum.Attr.Type {
317		case nl.TCA_HFSC_RSC:
318			hfsc.Rsc = ServiceCurve{m1: m1, d: d, m2: m2}
319		case nl.TCA_HFSC_FSC:
320			hfsc.Fsc = ServiceCurve{m1: m1, d: d, m2: m2}
321		case nl.TCA_HFSC_USC:
322			hfsc.Usc = ServiceCurve{m1: m1, d: d, m2: m2}
323		}
324	}
325	return detailed, nil
326}
327
328func parseTcStats(data []byte) (*ClassStatistics, error) {
329	buf := &bytes.Buffer{}
330	buf.Write(data)
331	native := nl.NativeEndian()
332	tcStats := &tcStats{}
333	if err := binary.Read(buf, native, tcStats); err != nil {
334		return nil, err
335	}
336
337	stats := NewClassStatistics()
338	stats.Basic.Bytes = tcStats.Bytes
339	stats.Basic.Packets = tcStats.Packets
340	stats.Queue.Qlen = tcStats.Qlen
341	stats.Queue.Backlog = tcStats.Backlog
342	stats.Queue.Drops = tcStats.Drops
343	stats.Queue.Overlimits = tcStats.Overlimits
344	stats.RateEst.Bps = tcStats.Bps
345	stats.RateEst.Pps = tcStats.Pps
346
347	return stats, nil
348}
349
350func parseGnetStats(data []byte, gnetStats interface{}) error {
351	buf := &bytes.Buffer{}
352	buf.Write(data)
353	native := nl.NativeEndian()
354	return binary.Read(buf, native, gnetStats)
355}
356
357func parseTcStats2(data []byte) (*ClassStatistics, error) {
358	rtAttrs, err := nl.ParseRouteAttr(data)
359	if err != nil {
360		return nil, err
361	}
362	stats := NewClassStatistics()
363	for _, datum := range rtAttrs {
364		switch datum.Attr.Type {
365		case nl.TCA_STATS_BASIC:
366			if err := parseGnetStats(datum.Value, stats.Basic); err != nil {
367				return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s",
368					err, hex.Dump(datum.Value))
369			}
370		case nl.TCA_STATS_QUEUE:
371			if err := parseGnetStats(datum.Value, stats.Queue); err != nil {
372				return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s",
373					err, hex.Dump(datum.Value))
374			}
375		case nl.TCA_STATS_RATE_EST:
376			if err := parseGnetStats(datum.Value, stats.RateEst); err != nil {
377				return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s",
378					err, hex.Dump(datum.Value))
379			}
380		}
381	}
382
383	return stats, nil
384}
385