diff --git a/.travis.yml b/.travis.yml index 304d1e16a..80219c69d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ before_script: - sudo modprobe nf_conntrack_ipv4 - sudo modprobe nf_conntrack_ipv6 - sudo modprobe sch_hfsc + - sudo modprobe sch_sfq install: - go get -v -t ./... go_import_path: github.com/vishvananda/netlink diff --git a/nl/tc_linux.go b/nl/tc_linux.go index ac3d87a43..3c1f67bb7 100644 --- a/nl/tc_linux.go +++ b/nl/tc_linux.go @@ -94,6 +94,9 @@ const ( SizeofTcTunnelKey = SizeofTcGen + 0x04 SizeofTcSkbEdit = SizeofTcGen SizeofTcPolice = 2*SizeofTcRateSpec + 0x20 + SizeofTcSfqQopt = 0x0b + SizeofTcSfqRedStats = 0x18 + SizeofTcSfqQoptV1 = SizeofTcSfqQopt + SizeofTcSfqRedStats + 0x44 ) // struct tcmsg { @@ -878,3 +881,103 @@ const ( TCA_HFSC_FSC TCA_HFSC_USC ) + +// struct tc_sfq_qopt { +// unsigned quantum; /* Bytes per round allocated to flow */ +// int perturb_period; /* Period of hash perturbation */ +// __u32 limit; /* Maximal packets in queue */ +// unsigned divisor; /* Hash divisor */ +// unsigned flows; /* Maximal number of flows */ +// }; + +type TcSfqQopt struct { + Quantum uint8 // 1 + Perturb int32 // 4 + Limit uint32 // 4 + Divisor uint8 // 1 + Flows uint8 // 1 +} + +func (x *TcSfqQopt) Len() int { + return SizeofTcSfqQopt +} + +func DeserializeTcSfqQopt(b []byte) *TcSfqQopt { + return (*TcSfqQopt)(unsafe.Pointer(&b[0:SizeofTcSfqQopt][0])) +} + +func (x *TcSfqQopt) Serialize() []byte { + return (*(*[SizeofTcSfqQopt]byte)(unsafe.Pointer(x)))[:] +} + +// struct tc_sfqred_stats { +// __u32 prob_drop; /* Early drops, below max threshold */ +// __u32 forced_drop; /* Early drops, after max threshold */ +// __u32 prob_mark; /* Marked packets, below max threshold */ +// __u32 forced_mark; /* Marked packets, after max threshold */ +// __u32 prob_mark_head; /* Marked packets, below max threshold */ +// __u32 forced_mark_head;/* Marked packets, after max threshold */ +// }; +type TcSfqRedStats struct { + ProbDrop uint32 + ForcedDrop uint32 + ProbMark uint32 + ForcedMark uint32 + ProbMarkHead uint32 + ForcedMarkHead uint32 +} + +func (x *TcSfqRedStats) Len() int { + return SizeofTcSfqRedStats +} + +func DeserializeTcSfqRedStats(b []byte) *TcSfqRedStats { + return (*TcSfqRedStats)(unsafe.Pointer(&b[0:SizeofTcSfqRedStats][0])) +} + +func (x *TcSfqRedStats) Serialize() []byte { + return (*(*[SizeofTcSfqRedStats]byte)(unsafe.Pointer(x)))[:] +} + +// struct tc_sfq_qopt_v1 { +// struct tc_sfq_qopt v0; +// unsigned int depth; /* max number of packets per flow */ +// unsigned int headdrop; +// /* SFQRED parameters */ +// __u32 limit; /* HARD maximal flow queue length (bytes) */ +// __u32 qth_min; /* Min average length threshold (bytes) */ +// __u32 qth_max; /* Max average length threshold (bytes) */ +// unsigned char Wlog; /* log(W) */ +// unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ +// unsigned char Scell_log; /* cell size for idle damping */ +// unsigned char flags; +// __u32 max_P; /* probability, high resolution */ +// /* SFQRED stats */ +// struct tc_sfqred_stats stats; +// }; +type TcSfqQoptV1 struct { + TcSfqQopt + Depth uint32 + HeadDrop uint32 + Limit uint32 + QthMin uint32 + QthMax uint32 + Wlog byte + Plog byte + ScellLog byte + Flags byte + MaxP uint32 + TcSfqRedStats +} + +func (x *TcSfqQoptV1) Len() int { + return SizeofTcSfqRedStats +} + +func DeserializeTcSfqQoptV1(b []byte) *TcSfqQoptV1 { + return (*TcSfqQoptV1)(unsafe.Pointer(&b[0:SizeofTcSfqRedStats][0])) +} + +func (x *TcSfqQoptV1) Serialize() []byte { + return (*(*[SizeofTcSfqRedStats]byte)(unsafe.Pointer(x)))[:] +} diff --git a/qdisc.go b/qdisc.go index af78305ac..5171129f0 100644 --- a/qdisc.go +++ b/qdisc.go @@ -338,3 +338,28 @@ func (qdisc *FqCodel) Attrs() *QdiscAttrs { func (qdisc *FqCodel) Type() string { return "fq_codel" } + +type Sfq struct { + QdiscAttrs + // TODO: Only the simplified options for SFQ are handled here. Support for the extended one can be added later. + Quantum uint8 + Perturb uint8 + Limit uint32 + Divisor uint8 + Flows uint8 +} + +func (sfq *Sfq) String() string { + return fmt.Sprintf( + "{%v -- Quantum: %v, Perturb: %v, Limit: %v, Divisor: %v, Flows: %v}", + sfq.Attrs(), sfq.Quantum, sfq.Perturb, sfq.Limit, sfq.Divisor, sfq.Flows, + ) +} + +func (qdisc *Sfq) Attrs() *QdiscAttrs { + return &qdisc.QdiscAttrs +} + +func (qdisc *Sfq) Type() string { + return "sfq" +} diff --git a/qdisc_linux.go b/qdisc_linux.go index fe24239ee..7d9bfda1a 100644 --- a/qdisc_linux.go +++ b/qdisc_linux.go @@ -278,6 +278,15 @@ func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { if qdisc.FlowDefaultRate > 0 { options.AddRtAttr(nl.TCA_FQ_FLOW_DEFAULT_RATE, nl.Uint32Attr((uint32(qdisc.FlowDefaultRate)))) } + case *Sfq: + opt := nl.TcSfqQoptV1{} + opt.TcSfqQopt.Quantum = qdisc.Quantum + opt.TcSfqQopt.Perturb = int32(qdisc.Perturb) + opt.TcSfqQopt.Limit = qdisc.Limit + opt.TcSfqQopt.Divisor = qdisc.Divisor + opt.TcSfqQopt.Flows = qdisc.Flows + + options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) default: options = nil } @@ -362,6 +371,8 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { qdisc = &FqCodel{} case "netem": qdisc = &Netem{} + case "sfq": + qdisc = &Sfq{} default: qdisc = &GenericQdisc{QdiscType: qdiscType} } @@ -417,6 +428,10 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { if err := parseNetemData(qdisc, attr.Value); err != nil { return nil, err } + case "sfq": + if err := parseSfqData(qdisc, attr.Value); err != nil { + return nil, err + } // no options for ingress } @@ -582,6 +597,18 @@ func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { return nil } +func parseSfqData(qdisc Qdisc, value []byte) error { + sfq := qdisc.(*Sfq) + opt := nl.DeserializeTcSfqQoptV1(value) + sfq.Quantum = opt.Quantum + sfq.Perturb = uint8(opt.Perturb) + sfq.Limit = opt.Limit + sfq.Divisor = opt.Divisor + sfq.Flows = opt.Flows + + return nil +} + const ( TIME_UNITS_PER_SEC = 1000000 ) diff --git a/qdisc_test.go b/qdisc_test.go index 825b581ac..8658478d1 100644 --- a/qdisc_test.go +++ b/qdisc_test.go @@ -122,6 +122,75 @@ func TestHtbAddDel(t *testing.T) { } } +func TestSfqAddDel(t *testing.T) { + tearDown := setUpNetlinkTestWithKModule(t, "sch_sfq") + 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 := Sfq{ + QdiscAttrs: attrs, + Quantum: 2, + Perturb: 11, + Limit: 3, + Divisor: 4, + } + 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") + } + sfq, ok := qdiscs[0].(*Sfq) + if !ok { + t.Fatal("Qdisc is the wrong type") + } + if sfq.Quantum != qdisc.Quantum { + t.Fatal("Quantum doesn't match") + } + if sfq.Perturb != qdisc.Perturb { + t.Fatal("Perturb doesn't match") + } + if sfq.Limit != qdisc.Limit { + t.Fatal("Limit doesn't match") + } + if sfq.Divisor != qdisc.Divisor { + t.Fatal("Divisor doesn't match") + } + if sfq.Flows != qdisc.Flows { + t.Fatal("Flows doesn't 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") + } +} + func TestPrioAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown()