diff --git a/nl/tc_linux.go b/nl/tc_linux.go index c9bfe8d..3217f60 100644 --- a/nl/tc_linux.go +++ b/nl/tc_linux.go @@ -35,6 +35,8 @@ const ( SizeofTcPrioMap = 0x14 SizeofTcRateSpec = 0x0c SizeofTcTbfQopt = 2*SizeofTcRateSpec + 0x0c + SizeofTcHtbQopt = 2*SizeofTcRateSpec + 0x14 + SizeofTcHtbGlob = 0x14 SizeofTcU32Key = 0x10 SizeofTcU32Sel = 0x10 // without keys SizeofTcMirred = 0x1c @@ -190,6 +192,38 @@ func (x *TcTbfQopt) Serialize() []byte { return (*(*[SizeofTcTbfQopt]byte)(unsafe.Pointer(x)))[:] } +const ( + TCA_HTB_UNSPEC = iota + TCA_HTB_PARMS + TCA_HTB_INIT + TCA_HTB_CTAB + TCA_HTB_RTAB + TCA_HTB_DIRECT_QLEN + TCA_HTB_RATE64 + TCA_HTB_CEIL64 + TCA_HTB_MAX = TCA_HTB_CEIL64 +) + +type TcHtbGlob struct { + Version uint32 + Rate2Quantum uint32 + Defcls uint32 + Debug uint32 + DirectPkts uint32 +} + +func (msg *TcHtbGlob) Len() int { + return SizeofTcHtbGlob +} + +func DeserializeTcHtbGlob(b []byte) *TcHtbGlob { + return (*TcHtbGlob)(unsafe.Pointer(&b[0:SizeofTcHtbGlob][0])) +} + +func (x *TcHtbGlob) Serialize() []byte { + return (*(*[SizeofTcHtbGlob]byte)(unsafe.Pointer(x)))[:] +} + const ( TCA_U32_UNSPEC = iota TCA_U32_CLASSID diff --git a/qdisc.go b/qdisc.go index 8e3d020..41a4aa8 100644 --- a/qdisc.go +++ b/qdisc.go @@ -91,7 +91,36 @@ func (qdisc *Prio) Type() string { return "prio" } -// Tbf is a classful qdisc that rate limits based on tokens +// Htb is a classful qdisc that rate limits based on tokens +type Htb struct { + QdiscAttrs + Version uint32 + Rate2Quantum uint32 + Defcls uint32 + Debug uint32 + DirectPkts uint32 +} + +func NewHtb(attrs QdiscAttrs) *Htb { + return &Htb{ + QdiscAttrs: attrs, + Version: 3, + Defcls: 0, + Rate2Quantum: 10, + Debug: 0, + DirectPkts: 0, + } +} + +func (qdisc *Htb) Attrs() *QdiscAttrs { + return &qdisc.QdiscAttrs +} + +func (qdisc *Htb) Type() string { + return "htb" +} + +// Tbf is a classless qdisc that rate limits based on tokens type Tbf struct { QdiscAttrs // TODO: handle 64bit rate properly diff --git a/qdisc_linux.go b/qdisc_linux.go index 2531c9d..f289e1b 100644 --- a/qdisc_linux.go +++ b/qdisc_linux.go @@ -55,6 +55,16 @@ func QdiscAdd(qdisc Qdisc) error { opt.Limit = tbf.Limit opt.Buffer = tbf.Buffer nl.NewRtAttrChild(options, nl.TCA_TBF_PARMS, opt.Serialize()) + } else if htb, ok := qdisc.(*Htb); ok { + opt := nl.TcHtbGlob{} + opt.Version = htb.Version + opt.Rate2Quantum = htb.Rate2Quantum + opt.Defcls = htb.Defcls + // TODO: Handle Debug properly. For now default to 0 + opt.Debug = htb.Debug + opt.DirectPkts = htb.DirectPkts + nl.NewRtAttrChild(options, nl.TCA_HTB_INIT, opt.Serialize()) + // nl.NewRtAttrChild(options, nl.TCA_HTB_DIRECT_QLEN, opt.Serialize()) } else if _, ok := qdisc.(*Ingress); ok { // ingress filters must use the proper handle if msg.Parent != HANDLE_INGRESS { @@ -123,6 +133,8 @@ func QdiscList(link Link) ([]Qdisc, error) { qdisc = &Tbf{} case "ingress": qdisc = &Ingress{} + case "htb": + qdisc = &Htb{} default: qdisc = &GenericQdisc{QdiscType: qdiscType} } @@ -146,6 +158,15 @@ func QdiscList(link Link) ([]Qdisc, error) { if err := parseTbfData(qdisc, data); err != nil { return nil, err } + case "htb": + data, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return nil, err + } + if err := parseHtbData(qdisc, data); err != nil { + return nil, err + } + // no options for ingress } } @@ -173,6 +194,25 @@ func parsePrioData(qdisc Qdisc, value []byte) error { return nil } +func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { + native = nl.NativeEndian() + htb := qdisc.(*Htb) + for _, datum := range data { + switch datum.Attr.Type { + case nl.TCA_HTB_INIT: + opt := nl.DeserializeTcHtbGlob(datum.Value) + htb.Version = opt.Version + htb.Rate2Quantum = opt.Rate2Quantum + htb.Defcls = opt.Defcls + htb.Debug = opt.Debug + htb.DirectPkts = opt.DirectPkts + case nl.TCA_HTB_DIRECT_QLEN: + // TODO + //htb.DirectQlen = native.uint32(datum.Value) + } + } + return nil +} func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { native = nl.NativeEndian() tbf := qdisc.(*Tbf) diff --git a/qdisc_test.go b/qdisc_test.go index 94e92e7..3864b30 100644 --- a/qdisc_test.go +++ b/qdisc_test.go @@ -62,6 +62,63 @@ func TestTbfAddDel(t *testing.T) { } } +func TestHtbAddDel(t *testing.T) { + 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) + } + if err := LinkSetUp(link); err != nil { + t.Fatal(err) + } + + attrs := QdiscAttrs{ + LinkIndex: link.Attrs().Index, + Handle: MakeHandle(1, 0), + Parent: HANDLE_ROOT, + } + + qdisc := NewHtb(attrs) + qdisc.Rate2Quantum = 5 + 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") + } + htb, ok := qdiscs[0].(*Htb) + if !ok { + t.Fatal("Qdisc is the wrong type") + } + if htb.Defcls != qdisc.Defcls { + t.Fatal("Defcls doesn't match") + } + if htb.Rate2Quantum != qdisc.Rate2Quantum { + t.Fatal("Rate2Quantum doesn't match") + } + if htb.Debug != qdisc.Debug { + t.Fatal("Debug doesn't match") + } + if err := QdiscDel(qdisc); err != nil { + t.Fatal(err) + } + qdiscs, err = QdiscList(link) + if err != nil { + t.Fatal(err) + } + if len(qdiscs) != 0 { + t.Fatal("Failed to remove qdisc") + } +} func TestPrioAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown()