mirror of
https://github.com/vishvananda/netlink
synced 2024-12-25 08:02:19 +00:00
support basic tbf qdisc
This commit is contained in:
parent
687e6f0b07
commit
a6d6ef6a6a
@ -33,6 +33,8 @@ const (
|
||||
SizeofTcMsg = 0x14
|
||||
SizeofTcActionMsg = 0x04
|
||||
SizeofTcPrioMap = 0x14
|
||||
SizeofTcRateSpec = 0x0c
|
||||
SizeofTcTbfQopt = 2*SizeofTcRateSpec + 0x0c
|
||||
)
|
||||
|
||||
// struct tcmsg {
|
||||
@ -111,23 +113,6 @@ func (x *TcPrioMap) Serialize() []byte {
|
||||
return (*(*[SizeofTcPrioMap]byte)(unsafe.Pointer(x)))[:]
|
||||
}
|
||||
|
||||
// struct tc_ratespec {
|
||||
// unsigned char cell_log;
|
||||
// __u8 linklayer; /* lower 4 bits */
|
||||
// unsigned short overhead;
|
||||
// short cell_align;
|
||||
// unsigned short mpu;
|
||||
// __u32 rate;
|
||||
// };
|
||||
|
||||
// struct tc_tbf_qopt {
|
||||
// struct tc_ratespec rate;
|
||||
// struct tc_ratespec peakrate;
|
||||
// __u32 limit;
|
||||
// __u32 buffer;
|
||||
// __u32 mtu;
|
||||
// };
|
||||
|
||||
const (
|
||||
TCA_TBF_UNSPEC = iota
|
||||
TCA_TBF_PARMS
|
||||
@ -139,3 +124,53 @@ const (
|
||||
TCA_TBF_PBURST
|
||||
TCA_TBF_MAX = TCA_TBF_PBURST
|
||||
)
|
||||
|
||||
// struct tc_ratespec {
|
||||
// unsigned char cell_log;
|
||||
// __u8 linklayer; /* lower 4 bits */
|
||||
// unsigned short overhead;
|
||||
// short cell_align;
|
||||
// unsigned short mpu;
|
||||
// __u32 rate;
|
||||
// };
|
||||
|
||||
type TcRateSpec struct {
|
||||
CellLog uint8
|
||||
Linklayer uint8
|
||||
Overhead uint16
|
||||
CellAlign int16
|
||||
Mpu uint16
|
||||
Rate uint32
|
||||
}
|
||||
|
||||
func DeserializeTcRateSpec(b []byte) *TcRateSpec {
|
||||
return (*TcRateSpec)(unsafe.Pointer(&b[0:SizeofTcRateSpec][0]))
|
||||
}
|
||||
|
||||
func (x *TcRateSpec) Serialize() []byte {
|
||||
return (*(*[SizeofTcRateSpec]byte)(unsafe.Pointer(x)))[:]
|
||||
}
|
||||
|
||||
// struct tc_tbf_qopt {
|
||||
// struct tc_ratespec rate;
|
||||
// struct tc_ratespec peakrate;
|
||||
// __u32 limit;
|
||||
// __u32 buffer;
|
||||
// __u32 mtu;
|
||||
// };
|
||||
|
||||
type TcTbfQopt struct {
|
||||
Rate TcRateSpec
|
||||
Peakrate TcRateSpec
|
||||
Limit uint32
|
||||
Buffer uint32
|
||||
Mtu uint32
|
||||
}
|
||||
|
||||
func DeserializeTcTbfQopt(b []byte) *TcTbfQopt {
|
||||
return (*TcTbfQopt)(unsafe.Pointer(&b[0:SizeofTcTbfQopt][0]))
|
||||
}
|
||||
|
||||
func (x *TcTbfQopt) Serialize() []byte {
|
||||
return (*(*[SizeofTcTbfQopt]byte)(unsafe.Pointer(x)))[:]
|
||||
}
|
||||
|
7
qdisc.go
7
qdisc.go
@ -30,7 +30,7 @@ func (q QdiscAttrs) String() string {
|
||||
}
|
||||
|
||||
func MakeHandle(major, minor uint16) uint32 {
|
||||
return (uint32(major) << 16) & uint32(minor)
|
||||
return (uint32(major) << 16) | uint32(minor)
|
||||
}
|
||||
|
||||
func MajorMinor(handle uint32) (uint16, uint16) {
|
||||
@ -70,6 +70,11 @@ func (qdisc *PfifoFast) Type() string {
|
||||
// TokenBucketFilter is a classful qdisc that rate limits based on tokens
|
||||
type TokenBucketFilter struct {
|
||||
QdiscAttrs
|
||||
// TODO: handle 64bit rate properly
|
||||
Rate uint64
|
||||
Limit uint32
|
||||
Buffer uint32
|
||||
// TODO: handle other settings
|
||||
}
|
||||
|
||||
func (qdisc *TokenBucketFilter) Attrs() *QdiscAttrs {
|
||||
|
222
qdisc_linux.go
222
qdisc_linux.go
@ -1,98 +1,58 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
// // QdiscAdd will add a qdisc to the system.
|
||||
// // Equivalent to: `tc qdisc add $qdisc`
|
||||
// func QdiscAdd(qdisc *Qdisc) error {
|
||||
// req := nl.NewNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||
// return qdiscHandle(qdisc, req, nl.NewRtMsg())
|
||||
// }
|
||||
//
|
||||
// // QdiscAdd will delete a qdisc from the system.
|
||||
// // Equivalent to: `tc qdisc del $qdisc`
|
||||
// func QdiscDel(qdisc *Qdisc) error {
|
||||
// req := nl.NewNetlinkRequest(syscall.RTM_DELROUTE, syscall.NLM_F_ACK)
|
||||
// return qdiscHandle(qdisc, req, nl.NewRtDelMsg())
|
||||
// }
|
||||
//
|
||||
// func qdiscHandle(qdisc *Qdisc, req *nl.NetlinkRequest, msg *nl.RtMsg) error {
|
||||
// if (qdisc.Dst == nil || qdisc.Dst.IP == nil) && qdisc.Src == nil && qdisc.Gw == nil {
|
||||
// return fmt.Errorf("one of Dst.IP, Src, or Gw must not be nil")
|
||||
// }
|
||||
//
|
||||
// msg.Scope = uint8(qdisc.Scope)
|
||||
// family := -1
|
||||
// var rtAttrs []*nl.RtAttr
|
||||
//
|
||||
// if qdisc.Dst != nil && qdisc.Dst.IP != nil {
|
||||
// dstLen, _ := qdisc.Dst.Mask.Size()
|
||||
// msg.Dst_len = uint8(dstLen)
|
||||
// dstFamily := nl.GetIPFamily(qdisc.Dst.IP)
|
||||
// family = dstFamily
|
||||
// var dstData []byte
|
||||
// if dstFamily == FAMILY_V4 {
|
||||
// dstData = qdisc.Dst.IP.To4()
|
||||
// } else {
|
||||
// dstData = qdisc.Dst.IP.To16()
|
||||
// }
|
||||
// rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_DST, dstData))
|
||||
// }
|
||||
//
|
||||
// if qdisc.Src != nil {
|
||||
// srcFamily := nl.GetIPFamily(qdisc.Src)
|
||||
// if family != -1 && family != srcFamily {
|
||||
// return fmt.Errorf("source and destination ip are not the same IP family")
|
||||
// }
|
||||
// family = srcFamily
|
||||
// var srcData []byte
|
||||
// if srcFamily == FAMILY_V4 {
|
||||
// srcData = qdisc.Src.To4()
|
||||
// } else {
|
||||
// srcData = qdisc.Src.To16()
|
||||
// }
|
||||
// // The commonly used src ip for qdiscs is actually PREFSRC
|
||||
// rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_PREFSRC, srcData))
|
||||
// }
|
||||
//
|
||||
// if qdisc.Gw != nil {
|
||||
// gwFamily := nl.GetIPFamily(qdisc.Gw)
|
||||
// if family != -1 && family != gwFamily {
|
||||
// return fmt.Errorf("gateway, source, and destination ip are not the same IP family")
|
||||
// }
|
||||
// family = gwFamily
|
||||
// var gwData []byte
|
||||
// if gwFamily == FAMILY_V4 {
|
||||
// gwData = qdisc.Gw.To4()
|
||||
// } else {
|
||||
// gwData = qdisc.Gw.To16()
|
||||
// }
|
||||
// rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_GATEWAY, gwData))
|
||||
// }
|
||||
//
|
||||
// msg.Family = uint8(family)
|
||||
//
|
||||
// req.AddData(msg)
|
||||
// for _, attr := range rtAttrs {
|
||||
// req.AddData(attr)
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// b = make([]byte, 4)
|
||||
// native = nl.NativeEndian()
|
||||
// )
|
||||
// native.PutUint32(b, uint32(qdisc.LinkIndex))
|
||||
//
|
||||
// req.AddData(nl.NewRtAttr(syscall.RTA_OIF, b))
|
||||
//
|
||||
// _, err := req.Execute(syscall.NETLINK_ROUTE, 0)
|
||||
// return err
|
||||
// }
|
||||
// QdiscDel will delete a qdisc from the system.
|
||||
// Equivalent to: `tc qdisc del $qdisc`
|
||||
func QdiscDel(qdisc Qdisc) error {
|
||||
req := nl.NewNetlinkRequest(syscall.RTM_DELQDISC, syscall.NLM_F_ACK)
|
||||
base := qdisc.Attrs()
|
||||
msg := &nl.TcMsg{
|
||||
Family: nl.FAMILY_ALL,
|
||||
Ifindex: int32(base.LinkIndex),
|
||||
Handle: base.Handle,
|
||||
Parent: base.Parent,
|
||||
}
|
||||
req.AddData(msg)
|
||||
|
||||
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// QdiscAdd will add a qdisc to the system.
|
||||
// Equivalent to: `tc qdisc add $qdisc`
|
||||
func QdiscAdd(qdisc Qdisc) error {
|
||||
req := nl.NewNetlinkRequest(syscall.RTM_NEWQDISC, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||
base := qdisc.Attrs()
|
||||
msg := &nl.TcMsg{
|
||||
Family: nl.FAMILY_ALL,
|
||||
Ifindex: int32(base.LinkIndex),
|
||||
Handle: base.Handle,
|
||||
Parent: base.Parent,
|
||||
}
|
||||
req.AddData(msg)
|
||||
req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.NonZeroTerminated(qdisc.Type())))
|
||||
|
||||
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
|
||||
if tbf, ok := qdisc.(*TokenBucketFilter); ok {
|
||||
opt := nl.TcTbfQopt{}
|
||||
// TODO: handle rate > uint32
|
||||
opt.Rate.Rate = uint32(tbf.Rate)
|
||||
opt.Limit = tbf.Limit
|
||||
opt.Buffer = tbf.Buffer
|
||||
nl.NewRtAttrChild(options, nl.TCA_TBF_PARMS, opt.Serialize())
|
||||
}
|
||||
req.AddData(options)
|
||||
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// QdiscList gets a list of qdiscs in the system.
|
||||
// Equivalent to: `tc qdisc show`.
|
||||
@ -137,22 +97,16 @@ func QdiscList(link Link) ([]Qdisc, error) {
|
||||
qdiscType = string(attr.Value[:len(attr.Value)-1])
|
||||
switch qdiscType {
|
||||
case "pfifo_fast":
|
||||
fmt.Printf("found pfifo_fast\n")
|
||||
qdisc = &PfifoFast{}
|
||||
case "tbf":
|
||||
fmt.Printf("found tbf\n")
|
||||
qdisc = &TokenBucketFilter{}
|
||||
default:
|
||||
fmt.Printf("found generic\n")
|
||||
qdisc = &GenericQdisc{QdiscType: qdiscType}
|
||||
}
|
||||
case nl.TCA_OPTIONS:
|
||||
fmt.Printf("in options\n")
|
||||
fmt.Printf("Value %v\n", attr.Value)
|
||||
switch qdiscType {
|
||||
case "pfifo_fast":
|
||||
// pfifo returns TcPrioMap directly without wrapping it in rtattr
|
||||
fmt.Printf("parsing pfifo_fast\n")
|
||||
if err := parsePfifoFastData(qdisc, attr.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -183,12 +137,90 @@ func parsePfifoFastData(qdisc Qdisc, value []byte) error {
|
||||
}
|
||||
|
||||
func parseTokenBucketFilterData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error {
|
||||
native = nl.NativeEndian()
|
||||
tbf := qdisc.(*TokenBucketFilter)
|
||||
for _, datum := range data {
|
||||
switch datum.Attr.Type {
|
||||
case nl.TCA_TBF_UNSPEC:
|
||||
tbf.Type()
|
||||
case nl.TCA_TBF_PARMS:
|
||||
opt := nl.DeserializeTcTbfQopt(datum.Value)
|
||||
tbf.Rate = uint64(opt.Rate.Rate)
|
||||
tbf.Limit = opt.Limit
|
||||
tbf.Buffer = opt.Buffer
|
||||
case nl.TCA_TBF_RATE64:
|
||||
tbf.Rate = native.Uint64(datum.Value[0:4])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
TIME_UNITS_PER_SEC = 1000000
|
||||
)
|
||||
|
||||
var (
|
||||
tickInUsec float64 = 0.0
|
||||
clockFactor float64 = 0.0
|
||||
)
|
||||
|
||||
func initClock() {
|
||||
data, err := ioutil.ReadFile("/proc/net/psched")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parts := strings.Split(strings.TrimSpace(string(data)), " ")
|
||||
if len(parts) < 3 {
|
||||
return
|
||||
}
|
||||
var vals [3]uint64
|
||||
for i := range vals {
|
||||
val, err := strconv.ParseUint(parts[i], 16, 32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
vals[i] = val
|
||||
}
|
||||
// compatibility
|
||||
if vals[2] == 1000000000 {
|
||||
vals[0] = vals[1]
|
||||
}
|
||||
clockFactor = float64(vals[2]) / TIME_UNITS_PER_SEC
|
||||
tickInUsec = float64(vals[0]) / float64(vals[1]) * clockFactor
|
||||
}
|
||||
|
||||
func TickInUsec() float64 {
|
||||
if tickInUsec == 0.0 {
|
||||
initClock()
|
||||
}
|
||||
return tickInUsec
|
||||
}
|
||||
|
||||
func ClockFactor() float64 {
|
||||
if clockFactor == 0.0 {
|
||||
initClock()
|
||||
}
|
||||
return clockFactor
|
||||
}
|
||||
|
||||
func time2Tick(time uint32) uint32 {
|
||||
return uint32(float64(time) * TickInUsec())
|
||||
}
|
||||
|
||||
func tick2Time(tick uint32) uint32 {
|
||||
return uint32(float64(tick) / TickInUsec())
|
||||
}
|
||||
|
||||
func time2Ktime(time uint32) uint32 {
|
||||
return uint32(float64(time) * ClockFactor())
|
||||
}
|
||||
|
||||
func ktime2Time(ktime uint32) uint32 {
|
||||
return uint32(float64(ktime) / ClockFactor())
|
||||
}
|
||||
|
||||
func burst(rate uint64, buffer uint32) uint32 {
|
||||
return uint32(float64(rate) * float64(tick2Time(buffer)) / TIME_UNITS_PER_SEC)
|
||||
}
|
||||
|
||||
func latency(rate uint64, limit, buffer uint32) float64 {
|
||||
return TIME_UNITS_PER_SEC*(float64(limit)/float64(rate)) - float64(tick2Time(buffer))
|
||||
}
|
||||
|
@ -1,26 +1,63 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQdiscAddDel(t *testing.T) {
|
||||
// eth0, _ := LinkByName("eth0")
|
||||
qdiscs, err := QdiscList(nil)
|
||||
tearDown := setUpNetlinkTest(t)
|
||||
defer tearDown()
|
||||
if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
link, err := LinkByName("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, qdisc := range qdiscs {
|
||||
fmt.Printf("Qdisc: %v\n", qdisc)
|
||||
switch qdisc.Type() {
|
||||
case "pfifo_fast":
|
||||
pfifo := qdisc.(*PfifoFast)
|
||||
fmt.Printf("pfifo: %v %v\n", pfifo.Bands, pfifo.PriorityMap)
|
||||
case "tbf":
|
||||
fmt.Printf("tbf: %v\n", qdisc.(*TokenBucketFilter))
|
||||
}
|
||||
if err := LinkSetUp(link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
qdisc := &TokenBucketFilter{
|
||||
QdiscAttrs: QdiscAttrs{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Handle: MakeHandle(1, 0),
|
||||
Parent: HANDLE_ROOT,
|
||||
},
|
||||
Rate: 131072,
|
||||
Limit: 1220703,
|
||||
Buffer: 16793,
|
||||
}
|
||||
if err := QdiscAdd(qdisc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
qdiscs, err := QdiscList(link)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(qdiscs) != 1 {
|
||||
t.Fatal("Failed to add qdisc")
|
||||
}
|
||||
tbf, ok := qdiscs[0].(*TokenBucketFilter)
|
||||
if !ok {
|
||||
t.Fatal("Qdisc is the wrong type")
|
||||
}
|
||||
if tbf.Rate != qdisc.Rate {
|
||||
t.Fatal("Rate doesn't match")
|
||||
}
|
||||
if tbf.Limit != qdisc.Limit {
|
||||
t.Fatal("Limit doesn't match")
|
||||
}
|
||||
if tbf.Buffer != qdisc.Buffer {
|
||||
t.Fatal("Buffer doesn't match")
|
||||
}
|
||||
if err := QdiscDel(qdisc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
qdiscs, err = QdiscList(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(qdiscs) != 0 {
|
||||
t.Fatal("Failed to remove qdisc")
|
||||
}
|
||||
tearDown := setUpNetlinkTest(t)
|
||||
defer tearDown()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user