From a2af46a09c21dd8b727a0f8912e559b8b484144d Mon Sep 17 00:00:00 2001 From: Sargun Dhillon Date: Tue, 30 Jan 2018 18:37:42 -0800 Subject: [PATCH] Add FQ Codel --- nl/tc_linux.go | 13 ++++++++++++ qdisc.go | 27 +++++++++++++++++++++++++ qdisc_linux.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ qdisc_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+) diff --git a/nl/tc_linux.go b/nl/tc_linux.go index 446b741..94ebc29 100644 --- a/nl/tc_linux.go +++ b/nl/tc_linux.go @@ -695,3 +695,16 @@ const ( TCA_FQ_ORPHAN_MASK // mask applied to orphaned skb hashes TCA_FQ_LOW_RATE_THRESHOLD // per packet delay under this rate ) + +const ( + TCA_FQ_CODEL_UNSPEC = iota + TCA_FQ_CODEL_TARGET + TCA_FQ_CODEL_LIMIT + TCA_FQ_CODEL_INTERVAL + TCA_FQ_CODEL_ECN + TCA_FQ_CODEL_FLOWS + TCA_FQ_CODEL_QUANTUM + TCA_FQ_CODEL_CE_THRESHOLD + TCA_FQ_CODEL_DROP_BATCH_SIZE + TCA_FQ_CODEL_MEMORY_LIMIT +) diff --git a/qdisc.go b/qdisc.go index 10981e1..3df4b5c 100644 --- a/qdisc.go +++ b/qdisc.go @@ -263,3 +263,30 @@ func (qdisc *Fq) Attrs() *QdiscAttrs { func (qdisc *Fq) Type() string { return "fq" } + +// FQ_Codel (Fair Queuing Controlled Delay) is queuing discipline that combines Fair Queuing with the CoDel AQM scheme. +type FqCodel struct { + QdiscAttrs + Target uint32 + Limit uint32 + Interval uint32 + ECN uint32 + Flows uint32 + Quantum uint32 + // There are some more attributes here, but support for them seems not ubiquitous +} + +func NewFqCodel(attrs QdiscAttrs) *FqCodel { + return &FqCodel{ + QdiscAttrs: attrs, + ECN: 1, + } +} + +func (qdisc *FqCodel) Attrs() *QdiscAttrs { + return &qdisc.QdiscAttrs +} + +func (qdisc *FqCodel) Type() string { + return "fq_codel" +} diff --git a/qdisc_linux.go b/qdisc_linux.go index 5758323..3794ac1 100644 --- a/qdisc_linux.go +++ b/qdisc_linux.go @@ -232,6 +232,21 @@ func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { if qdisc.Attrs().Parent != HANDLE_INGRESS { return fmt.Errorf("Ingress filters must set Parent to HANDLE_INGRESS") } + case *FqCodel: + nl.NewRtAttrChild(options, nl.TCA_FQ_CODEL_ECN, nl.Uint32Attr((uint32(qdisc.ECN)))) + if qdisc.Limit > 0 { + nl.NewRtAttrChild(options, nl.TCA_FQ_CODEL_LIMIT, nl.Uint32Attr((uint32(qdisc.Limit)))) + } + if qdisc.Interval > 0 { + nl.NewRtAttrChild(options, nl.TCA_FQ_CODEL_INTERVAL, nl.Uint32Attr((uint32(qdisc.Interval)))) + } + if qdisc.Flows > 0 { + nl.NewRtAttrChild(options, nl.TCA_FQ_CODEL_FLOWS, nl.Uint32Attr((uint32(qdisc.Flows)))) + } + if qdisc.Quantum > 0 { + nl.NewRtAttrChild(options, nl.TCA_FQ_CODEL_QUANTUM, nl.Uint32Attr((uint32(qdisc.Quantum)))) + } + case *Fq: nl.NewRtAttrChild(options, nl.TCA_FQ_RATE_ENABLE, nl.Uint32Attr((uint32(qdisc.Pacing)))) @@ -333,6 +348,8 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { qdisc = &Htb{} case "fq": qdisc = &Fq{} + case "fq_codel": + qdisc = &FqCodel{} case "netem": qdisc = &Netem{} default: @@ -374,6 +391,14 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { if err := parseFqData(qdisc, data); err != nil { return nil, err } + case "fq_codel": + data, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return nil, err + } + if err := parseFqCodelData(qdisc, data); err != nil { + return nil, err + } case "netem": if err := parseNetemData(qdisc, attr.Value); err != nil { return nil, err @@ -426,6 +451,29 @@ func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { return nil } +func parseFqCodelData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { + native = nl.NativeEndian() + fqCodel := qdisc.(*FqCodel) + for _, datum := range data { + + switch datum.Attr.Type { + case nl.TCA_FQ_CODEL_TARGET: + fqCodel.Target = native.Uint32(datum.Value) + case nl.TCA_FQ_CODEL_LIMIT: + fqCodel.Limit = native.Uint32(datum.Value) + case nl.TCA_FQ_CODEL_INTERVAL: + fqCodel.Interval = native.Uint32(datum.Value) + case nl.TCA_FQ_CODEL_ECN: + fqCodel.ECN = native.Uint32(datum.Value) + case nl.TCA_FQ_CODEL_FLOWS: + fqCodel.Flows = native.Uint32(datum.Value) + case nl.TCA_FQ_CODEL_QUANTUM: + fqCodel.Quantum = native.Uint32(datum.Value) + } + } + return nil +} + func parseFqData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { native = nl.NativeEndian() fq := qdisc.(*Fq) diff --git a/qdisc_test.go b/qdisc_test.go index 2a6875b..c8376b4 100644 --- a/qdisc_test.go +++ b/qdisc_test.go @@ -402,3 +402,57 @@ func TestFqAddChangeDel(t *testing.T) { t.Fatal("Failed to remove qdisc") } } + +func TestFqCodelAddChangeDel(t *testing.T) { + minKernelRequired(t, 3, 4) + + 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) + } + qdisc := &FqCodel{ + QdiscAttrs: QdiscAttrs{ + LinkIndex: link.Attrs().Index, + Handle: MakeHandle(1, 0), + Parent: HANDLE_ROOT, + }, + ECN: 1, + Quantum: 9000, + } + if err := QdiscAdd(qdisc); err != nil { + t.Fatal(err) + } + qdiscs, err := SafeQdiscList(link) + if err != nil { + t.Fatal(err) + } + if len(qdiscs) != 1 { + t.Fatal("Failed to add qdisc") + } + fqcodel, ok := qdiscs[0].(*FqCodel) + if !ok { + t.Fatal("Qdisc is the wrong type") + } + if fqcodel.Quantum != qdisc.Quantum { + t.Fatal("Quantum does not match") + } + + if err := QdiscDel(qdisc); err != nil { + t.Fatal(err) + } + qdiscs, err = SafeQdiscList(link) + if err != nil { + t.Fatal(err) + } + if len(qdiscs) != 0 { + t.Fatal("Failed to remove qdisc") + } +}