summaryrefslogtreecommitdiff
path: root/vendor/github.com/vishvananda/netlink/class_linux.go
blob: 029568a3ff90c6af8a4ad1c909e686e8daa1c38f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
package netlink

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"syscall"

	"github.com/vishvananda/netlink/nl"
	"golang.org/x/sys/unix"
)

// Internal tc_stats representation in Go struct.
// This is for internal uses only to deserialize the payload of rtattr.
// After the deserialization, this should be converted into the canonical stats
// struct, ClassStatistics, in case of statistics of a class.
// Ref: struct tc_stats { ... }
type tcStats struct {
	Bytes      uint64 // Number of enqueued bytes
	Packets    uint32 // Number of enqueued packets
	Drops      uint32 // Packets dropped because of lack of resources
	Overlimits uint32 // Number of throttle events when this flow goes out of allocated bandwidth
	Bps        uint32 // Current flow byte rate
	Pps        uint32 // Current flow packet rate
	Qlen       uint32
	Backlog    uint32
}

// NewHtbClass NOTE: function is in here because it uses other linux functions
func NewHtbClass(attrs ClassAttrs, cattrs HtbClassAttrs) *HtbClass {
	mtu := 1600
	rate := cattrs.Rate / 8
	ceil := cattrs.Ceil / 8
	buffer := cattrs.Buffer
	cbuffer := cattrs.Cbuffer

	if ceil == 0 {
		ceil = rate
	}

	if buffer == 0 {
		buffer = uint32(float64(rate)/Hz() + float64(mtu))
	}
	buffer = Xmittime(rate, buffer)

	if cbuffer == 0 {
		cbuffer = uint32(float64(ceil)/Hz() + float64(mtu))
	}
	cbuffer = Xmittime(ceil, cbuffer)

	return &HtbClass{
		ClassAttrs: attrs,
		Rate:       rate,
		Ceil:       ceil,
		Buffer:     buffer,
		Cbuffer:    cbuffer,
		Level:      0,
		Prio:       cattrs.Prio,
		Quantum:    cattrs.Quantum,
	}
}

// ClassDel will delete a class from the system.
// Equivalent to: `tc class del $class`
func ClassDel(class Class) error {
	return pkgHandle.ClassDel(class)
}

// ClassDel will delete a class from the system.
// Equivalent to: `tc class del $class`
func (h *Handle) ClassDel(class Class) error {
	return h.classModify(unix.RTM_DELTCLASS, 0, class)
}

// ClassChange will change a class in place
// Equivalent to: `tc class change $class`
// The parent and handle MUST NOT be changed.
func ClassChange(class Class) error {
	return pkgHandle.ClassChange(class)
}

// ClassChange will change a class in place
// Equivalent to: `tc class change $class`
// The parent and handle MUST NOT be changed.
func (h *Handle) ClassChange(class Class) error {
	return h.classModify(unix.RTM_NEWTCLASS, 0, class)
}

// ClassReplace will replace a class to the system.
// quivalent to: `tc class replace $class`
// The handle MAY be changed.
// If a class already exist with this parent/handle pair, the class is changed.
// If a class does not already exist with this parent/handle, a new class is created.
func ClassReplace(class Class) error {
	return pkgHandle.ClassReplace(class)
}

// ClassReplace will replace a class to the system.
// quivalent to: `tc class replace $class`
// The handle MAY be changed.
// If a class already exist with this parent/handle pair, the class is changed.
// If a class does not already exist with this parent/handle, a new class is created.
func (h *Handle) ClassReplace(class Class) error {
	return h.classModify(unix.RTM_NEWTCLASS, unix.NLM_F_CREATE, class)
}

// ClassAdd will add a class to the system.
// Equivalent to: `tc class add $class`
func ClassAdd(class Class) error {
	return pkgHandle.ClassAdd(class)
}

// ClassAdd will add a class to the system.
// Equivalent to: `tc class add $class`
func (h *Handle) ClassAdd(class Class) error {
	return h.classModify(
		unix.RTM_NEWTCLASS,
		unix.NLM_F_CREATE|unix.NLM_F_EXCL,
		class,
	)
}

func (h *Handle) classModify(cmd, flags int, class Class) error {
	req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK)
	base := class.Attrs()
	msg := &nl.TcMsg{
		Family:  nl.FAMILY_ALL,
		Ifindex: int32(base.LinkIndex),
		Handle:  base.Handle,
		Parent:  base.Parent,
	}
	req.AddData(msg)

	if cmd != unix.RTM_DELTCLASS {
		if err := classPayload(req, class); err != nil {
			return err
		}
	}
	_, err := req.Execute(unix.NETLINK_ROUTE, 0)
	return err
}

func classPayload(req *nl.NetlinkRequest, class Class) error {
	req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(class.Type())))

	options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
	switch class.Type() {
	case "htb":
		htb := class.(*HtbClass)
		opt := nl.TcHtbCopt{}
		opt.Buffer = htb.Buffer
		opt.Cbuffer = htb.Cbuffer
		opt.Quantum = htb.Quantum
		opt.Level = htb.Level
		opt.Prio = htb.Prio
		// TODO: Handle Debug properly. For now default to 0
		/* Calculate {R,C}Tab and set Rate and Ceil */
		cellLog := -1
		ccellLog := -1
		linklayer := nl.LINKLAYER_ETHERNET
		mtu := 1600
		var rtab [256]uint32
		var ctab [256]uint32
		tcrate := nl.TcRateSpec{Rate: uint32(htb.Rate)}
		if CalcRtable(&tcrate, rtab[:], cellLog, uint32(mtu), linklayer) < 0 {
			return errors.New("HTB: failed to calculate rate table")
		}
		opt.Rate = tcrate
		tcceil := nl.TcRateSpec{Rate: uint32(htb.Ceil)}
		if CalcRtable(&tcceil, ctab[:], ccellLog, uint32(mtu), linklayer) < 0 {
			return errors.New("HTB: failed to calculate ceil rate table")
		}
		opt.Ceil = tcceil
		options.AddRtAttr(nl.TCA_HTB_PARMS, opt.Serialize())
		options.AddRtAttr(nl.TCA_HTB_RTAB, SerializeRtab(rtab))
		options.AddRtAttr(nl.TCA_HTB_CTAB, SerializeRtab(ctab))
		if htb.Rate >= uint64(1<<32) {
			options.AddRtAttr(nl.TCA_HTB_RATE64, nl.Uint64Attr(htb.Rate))
		}
		if htb.Ceil >= uint64(1<<32) {
			options.AddRtAttr(nl.TCA_HTB_CEIL64, nl.Uint64Attr(htb.Ceil))
		}
	case "hfsc":
		hfsc := class.(*HfscClass)
		opt := nl.HfscCopt{}
		rm1, rd, rm2 := hfsc.Rsc.Attrs()
		opt.Rsc.Set(rm1/8, rd, rm2/8)
		fm1, fd, fm2 := hfsc.Fsc.Attrs()
		opt.Fsc.Set(fm1/8, fd, fm2/8)
		um1, ud, um2 := hfsc.Usc.Attrs()
		opt.Usc.Set(um1/8, ud, um2/8)
		nl.NewRtAttrChild(options, nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc))
		nl.NewRtAttrChild(options, nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc))
		nl.NewRtAttrChild(options, nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc))
	}
	req.AddData(options)
	return nil
}

// ClassList gets a list of classes in the system.
// Equivalent to: `tc class show`.
// Generally returns nothing if link and parent are not specified.
func ClassList(link Link, parent uint32) ([]Class, error) {
	return pkgHandle.ClassList(link, parent)
}

// ClassList gets a list of classes in the system.
// Equivalent to: `tc class show`.
// Generally returns nothing if link and parent are not specified.
func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
	req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP)
	msg := &nl.TcMsg{
		Family: nl.FAMILY_ALL,
		Parent: parent,
	}
	if link != nil {
		base := link.Attrs()
		h.ensureIndex(base)
		msg.Ifindex = int32(base.Index)
	}
	req.AddData(msg)

	msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS)
	if err != nil {
		return nil, err
	}

	var res []Class
	for _, m := range msgs {
		msg := nl.DeserializeTcMsg(m)

		attrs, err := nl.ParseRouteAttr(m[msg.Len():])
		if err != nil {
			return nil, err
		}

		base := ClassAttrs{
			LinkIndex:  int(msg.Ifindex),
			Handle:     msg.Handle,
			Parent:     msg.Parent,
			Statistics: nil,
		}

		var class Class
		classType := ""
		for _, attr := range attrs {
			switch attr.Attr.Type {
			case nl.TCA_KIND:
				classType = string(attr.Value[:len(attr.Value)-1])
				switch classType {
				case "htb":
					class = &HtbClass{}
				case "hfsc":
					class = &HfscClass{}
				default:
					class = &GenericClass{ClassType: classType}
				}
			case nl.TCA_OPTIONS:
				switch classType {
				case "htb":
					data, err := nl.ParseRouteAttr(attr.Value)
					if err != nil {
						return nil, err
					}
					_, err = parseHtbClassData(class, data)
					if err != nil {
						return nil, err
					}
				case "hfsc":
					data, err := nl.ParseRouteAttr(attr.Value)
					if err != nil {
						return nil, err
					}
					_, err = parseHfscClassData(class, data)
					if err != nil {
						return nil, err
					}
				}
			// For backward compatibility.
			case nl.TCA_STATS:
				base.Statistics, err = parseTcStats(attr.Value)
				if err != nil {
					return nil, err
				}
			case nl.TCA_STATS2:
				base.Statistics, err = parseTcStats2(attr.Value)
				if err != nil {
					return nil, err
				}
			}
		}
		*class.Attrs() = base
		res = append(res, class)
	}

	return res, nil
}

func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
	htb := class.(*HtbClass)
	detailed := false
	for _, datum := range data {
		switch datum.Attr.Type {
		case nl.TCA_HTB_PARMS:
			opt := nl.DeserializeTcHtbCopt(datum.Value)
			htb.Rate = uint64(opt.Rate.Rate)
			htb.Ceil = uint64(opt.Ceil.Rate)
			htb.Buffer = opt.Buffer
			htb.Cbuffer = opt.Cbuffer
			htb.Quantum = opt.Quantum
			htb.Level = opt.Level
			htb.Prio = opt.Prio
		case nl.TCA_HTB_RATE64:
			htb.Rate = native.Uint64(datum.Value[0:8])
		case nl.TCA_HTB_CEIL64:
			htb.Ceil = native.Uint64(datum.Value[0:8])
		}
	}
	return detailed, nil
}

func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
	hfsc := class.(*HfscClass)
	detailed := false
	for _, datum := range data {
		m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs()
		switch datum.Attr.Type {
		case nl.TCA_HFSC_RSC:
			hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
		case nl.TCA_HFSC_FSC:
			hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
		case nl.TCA_HFSC_USC:
			hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
		}
	}
	return detailed, nil
}

func parseTcStats(data []byte) (*ClassStatistics, error) {
	buf := &bytes.Buffer{}
	buf.Write(data)
	native := nl.NativeEndian()
	tcStats := &tcStats{}
	if err := binary.Read(buf, native, tcStats); err != nil {
		return nil, err
	}

	stats := NewClassStatistics()
	stats.Basic.Bytes = tcStats.Bytes
	stats.Basic.Packets = tcStats.Packets
	stats.Queue.Qlen = tcStats.Qlen
	stats.Queue.Backlog = tcStats.Backlog
	stats.Queue.Drops = tcStats.Drops
	stats.Queue.Overlimits = tcStats.Overlimits
	stats.RateEst.Bps = tcStats.Bps
	stats.RateEst.Pps = tcStats.Pps

	return stats, nil
}

func parseGnetStats(data []byte, gnetStats interface{}) error {
	buf := &bytes.Buffer{}
	buf.Write(data)
	native := nl.NativeEndian()
	return binary.Read(buf, native, gnetStats)
}

func parseTcStats2(data []byte) (*ClassStatistics, error) {
	rtAttrs, err := nl.ParseRouteAttr(data)
	if err != nil {
		return nil, err
	}
	stats := NewClassStatistics()
	for _, datum := range rtAttrs {
		switch datum.Attr.Type {
		case nl.TCA_STATS_BASIC:
			if err := parseGnetStats(datum.Value, stats.Basic); err != nil {
				return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s",
					err, hex.Dump(datum.Value))
			}
		case nl.TCA_STATS_QUEUE:
			if err := parseGnetStats(datum.Value, stats.Queue); err != nil {
				return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s",
					err, hex.Dump(datum.Value))
			}
		case nl.TCA_STATS_RATE_EST:
			if err := parseGnetStats(datum.Value, stats.RateEst); err != nil {
				return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s",
					err, hex.Dump(datum.Value))
			}
		}
	}

	return stats, nil
}