2015-09-10 23:20:18 +00:00
|
|
|
package netlink
|
|
|
|
|
|
|
|
import (
|
2018-03-08 03:09:28 +00:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/hex"
|
2016-01-14 05:48:14 +00:00
|
|
|
"errors"
|
2018-03-08 03:09:28 +00:00
|
|
|
"fmt"
|
2015-09-10 23:20:18 +00:00
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/vishvananda/netlink/nl"
|
2017-10-20 20:38:07 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2015-09-10 23:20:18 +00:00
|
|
|
)
|
|
|
|
|
2018-03-08 03:09:28 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-04-09 20:40:06 +00:00
|
|
|
// NewHtbClass NOTE: function is in here because it uses other linux functions
|
2016-06-15 15:44:14 +00:00
|
|
|
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))
|
|
|
|
}
|
2020-08-24 03:26:04 +00:00
|
|
|
buffer = Xmittime(rate, buffer)
|
2016-06-15 15:44:14 +00:00
|
|
|
|
|
|
|
if cbuffer == 0 {
|
|
|
|
cbuffer = uint32(float64(ceil)/Hz() + float64(mtu))
|
|
|
|
}
|
2020-08-24 03:26:04 +00:00
|
|
|
cbuffer = Xmittime(ceil, cbuffer)
|
2016-06-15 15:44:14 +00:00
|
|
|
|
|
|
|
return &HtbClass{
|
|
|
|
ClassAttrs: attrs,
|
|
|
|
Rate: rate,
|
|
|
|
Ceil: ceil,
|
|
|
|
Buffer: buffer,
|
|
|
|
Cbuffer: cbuffer,
|
|
|
|
Level: 0,
|
2020-08-24 03:26:04 +00:00
|
|
|
Prio: cattrs.Prio,
|
|
|
|
Quantum: cattrs.Quantum,
|
2016-06-15 15:44:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-10 23:20:18 +00:00
|
|
|
// ClassDel will delete a class from the system.
|
|
|
|
// Equivalent to: `tc class del $class`
|
|
|
|
func ClassDel(class Class) error {
|
2016-05-09 23:55:00 +00:00
|
|
|
return pkgHandle.ClassDel(class)
|
2016-05-08 18:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ClassDel will delete a class from the system.
|
|
|
|
// Equivalent to: `tc class del $class`
|
|
|
|
func (h *Handle) ClassDel(class Class) error {
|
2017-10-20 20:38:07 +00:00
|
|
|
return h.classModify(unix.RTM_DELTCLASS, 0, class)
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
|
2015-11-15 04:16:33 +00:00
|
|
|
// 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 {
|
2016-05-09 23:55:00 +00:00
|
|
|
return pkgHandle.ClassChange(class)
|
2016-05-08 18:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2017-10-20 20:38:07 +00:00
|
|
|
return h.classModify(unix.RTM_NEWTCLASS, 0, class)
|
2015-11-15 04:16:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2016-05-09 23:55:00 +00:00
|
|
|
return pkgHandle.ClassReplace(class)
|
2016-05-08 18:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2017-10-20 20:38:07 +00:00
|
|
|
return h.classModify(unix.RTM_NEWTCLASS, unix.NLM_F_CREATE, class)
|
2015-11-15 04:16:33 +00:00
|
|
|
}
|
|
|
|
|
2015-09-10 23:20:18 +00:00
|
|
|
// ClassAdd will add a class to the system.
|
|
|
|
// Equivalent to: `tc class add $class`
|
|
|
|
func ClassAdd(class Class) error {
|
2016-05-09 23:55:00 +00:00
|
|
|
return pkgHandle.ClassAdd(class)
|
2016-05-08 18:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ClassAdd will add a class to the system.
|
|
|
|
// Equivalent to: `tc class add $class`
|
|
|
|
func (h *Handle) ClassAdd(class Class) error {
|
|
|
|
return h.classModify(
|
2017-10-20 20:38:07 +00:00
|
|
|
unix.RTM_NEWTCLASS,
|
|
|
|
unix.NLM_F_CREATE|unix.NLM_F_EXCL,
|
2015-11-14 03:51:07 +00:00
|
|
|
class,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-05-08 18:35:49 +00:00
|
|
|
func (h *Handle) classModify(cmd, flags int, class Class) error {
|
2017-10-20 20:38:07 +00:00
|
|
|
req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK)
|
2015-09-10 23:20:18 +00:00
|
|
|
base := class.Attrs()
|
|
|
|
msg := &nl.TcMsg{
|
|
|
|
Family: nl.FAMILY_ALL,
|
|
|
|
Ifindex: int32(base.LinkIndex),
|
|
|
|
Handle: base.Handle,
|
|
|
|
Parent: base.Parent,
|
|
|
|
}
|
|
|
|
req.AddData(msg)
|
2015-11-14 03:51:07 +00:00
|
|
|
|
2017-10-20 20:38:07 +00:00
|
|
|
if cmd != unix.RTM_DELTCLASS {
|
2015-11-14 03:51:07 +00:00
|
|
|
if err := classPayload(req, class); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-10-20 20:38:07 +00:00
|
|
|
_, err := req.Execute(unix.NETLINK_ROUTE, 0)
|
2015-11-14 03:51:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func classPayload(req *nl.NetlinkRequest, class Class) error {
|
2015-09-10 23:20:18 +00:00
|
|
|
req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(class.Type())))
|
|
|
|
|
|
|
|
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
|
2018-04-09 20:40:06 +00:00
|
|
|
switch class.Type() {
|
|
|
|
case "htb":
|
|
|
|
htb := class.(*HtbClass)
|
2015-09-10 23:20:18 +00:00
|
|
|
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
|
2016-01-14 05:48:14 +00:00
|
|
|
/* Calculate {R,C}Tab and set Rate and Ceil */
|
2016-03-20 00:12:26 +00:00
|
|
|
cellLog := -1
|
|
|
|
ccellLog := -1
|
2016-01-14 05:48:14 +00:00
|
|
|
linklayer := nl.LINKLAYER_ETHERNET
|
|
|
|
mtu := 1600
|
|
|
|
var rtab [256]uint32
|
|
|
|
var ctab [256]uint32
|
|
|
|
tcrate := nl.TcRateSpec{Rate: uint32(htb.Rate)}
|
2017-11-13 04:06:38 +00:00
|
|
|
if CalcRtable(&tcrate, rtab[:], cellLog, uint32(mtu), linklayer) < 0 {
|
2016-03-20 00:12:26 +00:00
|
|
|
return errors.New("HTB: failed to calculate rate table")
|
2016-01-14 05:48:14 +00:00
|
|
|
}
|
|
|
|
opt.Rate = tcrate
|
|
|
|
tcceil := nl.TcRateSpec{Rate: uint32(htb.Ceil)}
|
2017-11-13 04:06:38 +00:00
|
|
|
if CalcRtable(&tcceil, ctab[:], ccellLog, uint32(mtu), linklayer) < 0 {
|
2016-03-20 00:12:26 +00:00
|
|
|
return errors.New("HTB: failed to calculate ceil rate table")
|
2016-01-14 05:48:14 +00:00
|
|
|
}
|
|
|
|
opt.Ceil = tcceil
|
2018-09-30 20:46:00 +00:00
|
|
|
options.AddRtAttr(nl.TCA_HTB_PARMS, opt.Serialize())
|
|
|
|
options.AddRtAttr(nl.TCA_HTB_RTAB, SerializeRtab(rtab))
|
|
|
|
options.AddRtAttr(nl.TCA_HTB_CTAB, SerializeRtab(ctab))
|
2021-02-16 14:14:49 +00:00
|
|
|
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))
|
|
|
|
}
|
2018-04-09 20:40:06 +00:00
|
|
|
case "hfsc":
|
|
|
|
hfsc := class.(*HfscClass)
|
|
|
|
opt := nl.HfscCopt{}
|
2018-08-19 10:37:29 +00:00
|
|
|
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)
|
2021-09-10 02:30:28 +00:00
|
|
|
options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc))
|
|
|
|
options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc))
|
|
|
|
options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc))
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
req.AddData(options)
|
2015-11-14 03:51:07 +00:00
|
|
|
return nil
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ClassList gets a list of classes in the system.
|
|
|
|
// Equivalent to: `tc class show`.
|
2015-11-15 04:16:33 +00:00
|
|
|
// Generally returns nothing if link and parent are not specified.
|
2015-09-10 23:20:18 +00:00
|
|
|
func ClassList(link Link, parent uint32) ([]Class, error) {
|
2016-05-09 23:55:00 +00:00
|
|
|
return pkgHandle.ClassList(link, parent)
|
2016-05-08 18:35:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-10-20 20:38:07 +00:00
|
|
|
req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP)
|
2015-09-10 23:20:18 +00:00
|
|
|
msg := &nl.TcMsg{
|
|
|
|
Family: nl.FAMILY_ALL,
|
|
|
|
Parent: parent,
|
|
|
|
}
|
|
|
|
if link != nil {
|
|
|
|
base := link.Attrs()
|
2016-05-08 18:35:49 +00:00
|
|
|
h.ensureIndex(base)
|
2015-09-10 23:20:18 +00:00
|
|
|
msg.Ifindex = int32(base.Index)
|
|
|
|
}
|
|
|
|
req.AddData(msg)
|
|
|
|
|
2017-10-20 20:38:07 +00:00
|
|
|
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS)
|
2015-09-10 23:20:18 +00:00
|
|
|
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{
|
2018-03-08 03:09:28 +00:00
|
|
|
LinkIndex: int(msg.Ifindex),
|
|
|
|
Handle: msg.Handle,
|
|
|
|
Parent: msg.Parent,
|
|
|
|
Statistics: nil,
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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{}
|
2018-04-09 20:40:06 +00:00
|
|
|
case "hfsc":
|
|
|
|
class = &HfscClass{}
|
2015-09-10 23:20:18 +00:00
|
|
|
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
|
|
|
|
}
|
2018-04-09 20:40:06 +00:00
|
|
|
case "hfsc":
|
|
|
|
data, err := nl.ParseRouteAttr(attr.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = parseHfscClassData(class, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
2018-03-08 03:09:28 +00:00
|
|
|
// 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
|
|
|
|
}
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
*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)
|
2016-01-14 05:48:14 +00:00
|
|
|
htb.Rate = uint64(opt.Rate.Rate)
|
|
|
|
htb.Ceil = uint64(opt.Ceil.Rate)
|
2015-09-10 23:20:18 +00:00
|
|
|
htb.Buffer = opt.Buffer
|
|
|
|
htb.Cbuffer = opt.Cbuffer
|
|
|
|
htb.Quantum = opt.Quantum
|
|
|
|
htb.Level = opt.Level
|
|
|
|
htb.Prio = opt.Prio
|
2021-02-16 14:14:49 +00:00
|
|
|
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])
|
2015-09-10 23:20:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return detailed, nil
|
|
|
|
}
|
2018-03-08 03:09:28 +00:00
|
|
|
|
2018-04-09 20:40:06 +00:00
|
|
|
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:
|
2018-08-19 10:37:29 +00:00
|
|
|
hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
|
2018-04-09 20:40:06 +00:00
|
|
|
case nl.TCA_HFSC_FSC:
|
2018-08-19 10:37:29 +00:00
|
|
|
hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
|
2018-04-09 20:40:06 +00:00
|
|
|
case nl.TCA_HFSC_USC:
|
2018-08-19 10:37:29 +00:00
|
|
|
hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
|
2018-04-09 20:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return detailed, nil
|
|
|
|
}
|
|
|
|
|
2018-03-08 03:09:28 +00:00
|
|
|
func parseTcStats(data []byte) (*ClassStatistics, error) {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
buf.Write(data)
|
|
|
|
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)
|
|
|
|
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))
|
|
|
|
}
|
2023-07-27 06:37:55 +00:00
|
|
|
case nl.TCA_STATS_BASIC_HW:
|
|
|
|
if err := parseGnetStats(datum.Value, stats.BasicHw); err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to parse ClassStatistics.BasicHw with: %v\n%s",
|
|
|
|
err, hex.Dump(datum.Value))
|
|
|
|
}
|
2018-03-08 03:09:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return stats, nil
|
|
|
|
}
|