github.com/sagernet/netlink@v0.0.0-20240612041022-b9a21c07ac6a/class_linux.go (about)

     1  package netlink
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"syscall"
    10  
    11  	"github.com/sagernet/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 { ... }
    20  type 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
    32  func 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 = Xmittime(rate, buffer)
    47  
    48  	if cbuffer == 0 {
    49  		cbuffer = uint32(float64(ceil)/Hz() + float64(mtu))
    50  	}
    51  	cbuffer = Xmittime(ceil, cbuffer)
    52  
    53  	return &HtbClass{
    54  		ClassAttrs: attrs,
    55  		Rate:       rate,
    56  		Ceil:       ceil,
    57  		Buffer:     buffer,
    58  		Cbuffer:    cbuffer,
    59  		Level:      0,
    60  		Prio:       cattrs.Prio,
    61  		Quantum:    cattrs.Quantum,
    62  	}
    63  }
    64  
    65  // ClassDel will delete a class from the system.
    66  // Equivalent to: `tc class del $class`
    67  func 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`
    73  func (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.
    80  func 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.
    87  func (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.
    96  func 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.
   105  func (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`
   111  func 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`
   117  func (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  
   125  func (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  
   145  func 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  		if htb.Rate >= uint64(1<<32) {
   180  			options.AddRtAttr(nl.TCA_HTB_RATE64, nl.Uint64Attr(htb.Rate))
   181  		}
   182  		if htb.Ceil >= uint64(1<<32) {
   183  			options.AddRtAttr(nl.TCA_HTB_CEIL64, nl.Uint64Attr(htb.Ceil))
   184  		}
   185  	case "hfsc":
   186  		hfsc := class.(*HfscClass)
   187  		opt := nl.HfscCopt{}
   188  		rm1, rd, rm2 := hfsc.Rsc.Attrs()
   189  		opt.Rsc.Set(rm1/8, rd, rm2/8)
   190  		fm1, fd, fm2 := hfsc.Fsc.Attrs()
   191  		opt.Fsc.Set(fm1/8, fd, fm2/8)
   192  		um1, ud, um2 := hfsc.Usc.Attrs()
   193  		opt.Usc.Set(um1/8, ud, um2/8)
   194  		options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc))
   195  		options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc))
   196  		options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc))
   197  	}
   198  	req.AddData(options)
   199  	return nil
   200  }
   201  
   202  // ClassList gets a list of classes in the system.
   203  // Equivalent to: `tc class show`.
   204  // Generally returns nothing if link and parent are not specified.
   205  func ClassList(link Link, parent uint32) ([]Class, error) {
   206  	return pkgHandle.ClassList(link, parent)
   207  }
   208  
   209  // ClassList gets a list of classes in the system.
   210  // Equivalent to: `tc class show`.
   211  // Generally returns nothing if link and parent are not specified.
   212  func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
   213  	req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP)
   214  	msg := &nl.TcMsg{
   215  		Family: nl.FAMILY_ALL,
   216  		Parent: parent,
   217  	}
   218  	if link != nil {
   219  		base := link.Attrs()
   220  		h.ensureIndex(base)
   221  		msg.Ifindex = int32(base.Index)
   222  	}
   223  	req.AddData(msg)
   224  
   225  	msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	var res []Class
   231  	for _, m := range msgs {
   232  		msg := nl.DeserializeTcMsg(m)
   233  
   234  		attrs, err := nl.ParseRouteAttr(m[msg.Len():])
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  
   239  		base := ClassAttrs{
   240  			LinkIndex:  int(msg.Ifindex),
   241  			Handle:     msg.Handle,
   242  			Parent:     msg.Parent,
   243  			Statistics: nil,
   244  		}
   245  
   246  		var class Class
   247  		classType := ""
   248  		for _, attr := range attrs {
   249  			switch attr.Attr.Type {
   250  			case nl.TCA_KIND:
   251  				classType = string(attr.Value[:len(attr.Value)-1])
   252  				switch classType {
   253  				case "htb":
   254  					class = &HtbClass{}
   255  				case "hfsc":
   256  					class = &HfscClass{}
   257  				default:
   258  					class = &GenericClass{ClassType: classType}
   259  				}
   260  			case nl.TCA_OPTIONS:
   261  				switch classType {
   262  				case "htb":
   263  					data, err := nl.ParseRouteAttr(attr.Value)
   264  					if err != nil {
   265  						return nil, err
   266  					}
   267  					_, err = parseHtbClassData(class, data)
   268  					if err != nil {
   269  						return nil, err
   270  					}
   271  				case "hfsc":
   272  					data, err := nl.ParseRouteAttr(attr.Value)
   273  					if err != nil {
   274  						return nil, err
   275  					}
   276  					_, err = parseHfscClassData(class, data)
   277  					if err != nil {
   278  						return nil, err
   279  					}
   280  				}
   281  			// For backward compatibility.
   282  			case nl.TCA_STATS:
   283  				base.Statistics, err = parseTcStats(attr.Value)
   284  				if err != nil {
   285  					return nil, err
   286  				}
   287  			case nl.TCA_STATS2:
   288  				base.Statistics, err = parseTcStats2(attr.Value)
   289  				if err != nil {
   290  					return nil, err
   291  				}
   292  			}
   293  		}
   294  		*class.Attrs() = base
   295  		res = append(res, class)
   296  	}
   297  
   298  	return res, nil
   299  }
   300  
   301  func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
   302  	htb := class.(*HtbClass)
   303  	detailed := false
   304  	for _, datum := range data {
   305  		switch datum.Attr.Type {
   306  		case nl.TCA_HTB_PARMS:
   307  			opt := nl.DeserializeTcHtbCopt(datum.Value)
   308  			htb.Rate = uint64(opt.Rate.Rate)
   309  			htb.Ceil = uint64(opt.Ceil.Rate)
   310  			htb.Buffer = opt.Buffer
   311  			htb.Cbuffer = opt.Cbuffer
   312  			htb.Quantum = opt.Quantum
   313  			htb.Level = opt.Level
   314  			htb.Prio = opt.Prio
   315  		case nl.TCA_HTB_RATE64:
   316  			htb.Rate = native.Uint64(datum.Value[0:8])
   317  		case nl.TCA_HTB_CEIL64:
   318  			htb.Ceil = native.Uint64(datum.Value[0:8])
   319  		}
   320  	}
   321  	return detailed, nil
   322  }
   323  
   324  func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {
   325  	hfsc := class.(*HfscClass)
   326  	detailed := false
   327  	for _, datum := range data {
   328  		m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs()
   329  		switch datum.Attr.Type {
   330  		case nl.TCA_HFSC_RSC:
   331  			hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
   332  		case nl.TCA_HFSC_FSC:
   333  			hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
   334  		case nl.TCA_HFSC_USC:
   335  			hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8}
   336  		}
   337  	}
   338  	return detailed, nil
   339  }
   340  
   341  func parseTcStats(data []byte) (*ClassStatistics, error) {
   342  	buf := &bytes.Buffer{}
   343  	buf.Write(data)
   344  	tcStats := &tcStats{}
   345  	if err := binary.Read(buf, native, tcStats); err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	stats := NewClassStatistics()
   350  	stats.Basic.Bytes = tcStats.Bytes
   351  	stats.Basic.Packets = tcStats.Packets
   352  	stats.Queue.Qlen = tcStats.Qlen
   353  	stats.Queue.Backlog = tcStats.Backlog
   354  	stats.Queue.Drops = tcStats.Drops
   355  	stats.Queue.Overlimits = tcStats.Overlimits
   356  	stats.RateEst.Bps = tcStats.Bps
   357  	stats.RateEst.Pps = tcStats.Pps
   358  
   359  	return stats, nil
   360  }
   361  
   362  func parseGnetStats(data []byte, gnetStats interface{}) error {
   363  	buf := &bytes.Buffer{}
   364  	buf.Write(data)
   365  	return binary.Read(buf, native, gnetStats)
   366  }
   367  
   368  func parseTcStats2(data []byte) (*ClassStatistics, error) {
   369  	rtAttrs, err := nl.ParseRouteAttr(data)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  	stats := NewClassStatistics()
   374  	for _, datum := range rtAttrs {
   375  		switch datum.Attr.Type {
   376  		case nl.TCA_STATS_BASIC:
   377  			if err := parseGnetStats(datum.Value, stats.Basic); err != nil {
   378  				return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s",
   379  					err, hex.Dump(datum.Value))
   380  			}
   381  		case nl.TCA_STATS_QUEUE:
   382  			if err := parseGnetStats(datum.Value, stats.Queue); err != nil {
   383  				return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s",
   384  					err, hex.Dump(datum.Value))
   385  			}
   386  		case nl.TCA_STATS_RATE_EST:
   387  			if err := parseGnetStats(datum.Value, stats.RateEst); err != nil {
   388  				return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s",
   389  					err, hex.Dump(datum.Value))
   390  			}
   391  		}
   392  	}
   393  
   394  	return stats, nil
   395  }