//go:build linux // +build linux package netlink import ( "reflect" "testing" ) func SafeQdiscList(link Link) ([]Qdisc, error) { qdiscs, err := QdiscList(link) if err != nil { return nil, err } result := []Qdisc{} for _, qdisc := range qdiscs { // fmt.Printf("%+v\n", qdisc) // filter default qdisc created by kernel when custom one deleted attrs := qdisc.Attrs() if attrs.Handle == HANDLE_NONE && attrs.Parent == HANDLE_ROOT { continue } result = append(result, qdisc) } return result, nil } func SafeClassList(link Link, handle uint32) ([]Class, error) { classes, err := ClassList(link, handle) if err != nil { return nil, err } result := []Class{} for ind := range classes { double := false for _, class2 := range classes[ind+1:] { if classes[ind].Attrs().Handle == class2.Attrs().Handle { double = true } } if !double { result = append(result, classes[ind]) } } return result, nil } func testClassStats(this, that *ClassStatistics, t *testing.T) { ok := reflect.DeepEqual(this, that) if !ok { t.Fatalf("%#v is expected but it actually was %#v", that, this) } } func TestClassAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); 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(0xffff, 0), Parent: HANDLE_ROOT, } qdisc := NewHtb(attrs) 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") } _, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } classattrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(0xffff, 0), Handle: MakeHandle(0xffff, 2), } htbclassattrs := HtbClassAttrs{ Rate: 1234000, Cbuffer: 1690, Prio: 2, Quantum: 1000, } class := NewHtbClass(classattrs, htbclassattrs) if err := ClassAdd(class); err != nil { t.Fatal(err) } classes, err := SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatal("Failed to add class") } htb, ok := classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } if htb.Rate != class.Rate { t.Fatal("Rate doesn't match") } if htb.Ceil != class.Ceil { t.Fatal("Ceil doesn't match") } if htb.Buffer != class.Buffer { t.Fatal("Buffer doesn't match") } if htb.Cbuffer != class.Cbuffer { t.Fatal("Cbuffer doesn't match") } if htb.Prio != class.Prio { t.Fatal("Prio doesn't match") } if htb.Quantum != class.Quantum { t.Fatal("Quantum doesn't match") } testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t) qattrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0x2, 0), Parent: MakeHandle(0xffff, 2), } nattrs := NetemQdiscAttrs{ Latency: 20000, Loss: 23.4, Duplicate: 14.3, LossCorr: 8.34, Jitter: 1000, DelayCorr: 12.3, ReorderProb: 23.4, CorruptProb: 10.0, CorruptCorr: 10, } qdiscnetem := NewNetem(qattrs, nattrs) if err := QdiscAdd(qdiscnetem); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatal("Failed to add qdisc") } _, ok = qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } netem, ok := qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Compare the record we got from the list with the one we created if netem.Loss != qdiscnetem.Loss { t.Fatal("Loss does not match") } if netem.Latency != qdiscnetem.Latency { t.Fatal("Latency does not match") } if netem.CorruptProb != qdiscnetem.CorruptProb { t.Fatal("CorruptProb does not match") } if netem.Jitter != qdiscnetem.Jitter { t.Fatal("Jitter does not match") } if netem.LossCorr != qdiscnetem.LossCorr { t.Fatal("Loss does not match") } if netem.DuplicateCorr != qdiscnetem.DuplicateCorr { t.Fatal("DuplicateCorr does not match") } // Deletion // automatically removes netem qdisc if err := ClassDel(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 0 { t.Fatal("Failed to remove class") } 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 TestHtbClassAddHtbClassChangeDel(t *testing.T) { /** This test first set up a interface ans set up a Htb qdisc A HTB class is attach to it and a Netem qdisc is attached to that class Next, we test changing the HTB class in place and confirming the Netem is still attached. We also check that invoting ClassChange with a non-existing class will fail. Finally, we test ClassReplace. We confirm it correctly behave like ClassChange when the parent/handle pair exists and that it will create a new class if the handle is modified. */ 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(0xffff, 0), Parent: HANDLE_ROOT, } qdisc := NewHtb(attrs) 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") } _, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } classattrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(0xffff, 0), Handle: MakeHandle(0xffff, 2), } htbclassattrs := HtbClassAttrs{ Rate: uint64(1<<32) + 10, Ceil: uint64(1<<32) + 20, Cbuffer: 1690, } class := NewHtbClass(classattrs, htbclassattrs) if err := ClassAdd(class); err != nil { t.Fatal(err) } classes, err := SafeClassList(link, 0) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatal("Failed to add class") } htb, ok := classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t) qattrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0x2, 0), Parent: MakeHandle(0xffff, 2), } nattrs := NetemQdiscAttrs{ Latency: 20000, Loss: 23.4, Duplicate: 14.3, LossCorr: 8.34, Jitter: 1000, DelayCorr: 12.3, ReorderProb: 23.4, CorruptProb: 10.0, CorruptCorr: 10, } qdiscnetem := NewNetem(qattrs, nattrs) if err := QdiscAdd(qdiscnetem); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatal("Failed to add qdisc") } _, ok = qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Change // For change to work, the handle and parent cannot be changed. // First, test it fails if we change the Handle. oldHandle := classattrs.Handle classattrs.Handle = MakeHandle(0xffff, 3) class = NewHtbClass(classattrs, htbclassattrs) if err := ClassChange(class); err == nil { t.Fatal("ClassChange should not work when using a different handle.") } // It should work with the same handle classattrs.Handle = oldHandle htbclassattrs.Rate = 4321000 class = NewHtbClass(classattrs, htbclassattrs) if err := ClassChange(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatalf( "1 class expected, %d found", len(classes), ) } htb, ok = classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // Check that we still have the netem child qdisc qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatalf("2 qdisc expected, %d found", len(qdiscs)) } _, ok = qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } _, ok = qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Replace // First replace by keeping the same handle, class will be changed. // Then, replace by providing a new handle, n new class will be created. // Replace acting as Change class = NewHtbClass(classattrs, htbclassattrs) if err := ClassReplace(class); err != nil { t.Fatal("Failed to replace class that is existing.") } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatalf( "1 class expected, %d found", len(classes), ) } htb, ok = classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // It should work with the same handle classattrs.Handle = MakeHandle(0xffff, 3) class = NewHtbClass(classattrs, htbclassattrs) if err := ClassReplace(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 2 { t.Fatalf( "2 classes expected, %d found", len(classes), ) } htb, ok = classes[1].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // Deletion for _, class := range classes { if err := ClassDel(class); err != nil { t.Fatal(err) } } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 0 { t.Fatal("Failed to remove class") } 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 TestClassHfsc(t *testing.T) { // New network namespace for tests tearDown := setUpNetlinkTestWithKModule(t, "sch_hfsc") defer tearDown() // Set up testing link and check if succeeded 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) } // Adding HFSC qdisc qdiscAttrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } hfscQdisc := NewHfsc(qdiscAttrs) hfscQdisc.Defcls = 2 err = QdiscAdd(hfscQdisc) if 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") } _, ok := qdiscs[0].(*Hfsc) if !ok { t.Fatal("Qdisc is the wrong type") } // Adding some HFSC classes classAttrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(1, 0), Handle: MakeHandle(1, 1), } hfscClass := NewHfscClass(classAttrs) hfscClass.SetLS(5e6, 10, 5e6) err = ClassAdd(hfscClass) if err != nil { t.Fatal(err) } hfscClass2 := hfscClass hfscClass2.SetLS(0, 0, 0) hfscClass2.Attrs().Parent = MakeHandle(1, 1) hfscClass2.Attrs().Handle = MakeHandle(1, 2) hfscClass2.SetRsc(0, 0, 2e6) err = ClassAdd(hfscClass2) if err != nil { t.Fatal(err) } hfscClass3 := hfscClass hfscClass3.SetLS(0, 0, 0) hfscClass3.Attrs().Parent = MakeHandle(1, 1) hfscClass3.Attrs().Handle = MakeHandle(1, 3) err = ClassAdd(hfscClass3) if err != nil { t.Fatal(err) } // Check the classes classes, err := SafeClassList(link, MakeHandle(1, 0)) if err != nil { t.Fatal(err) } if len(classes) != 4 { t.Fatal("Failed to add classes") } for _, c := range classes { class, ok := c.(*HfscClass) if !ok { t.Fatal("Wrong type of class") } if class.ClassAttrs.Handle == hfscClass.ClassAttrs.Handle { if class.Fsc != hfscClass.Fsc { t.Fatal("HfscClass FSC don't match") } if class.Usc != hfscClass.Usc { t.Fatal("HfscClass USC don't match") } if class.Rsc != hfscClass.Rsc { t.Fatal("HfscClass RSC don't match") } } if class.ClassAttrs.Handle == hfscClass2.ClassAttrs.Handle { if class.Fsc != hfscClass2.Fsc { t.Fatal("HfscClass2 FSC don't match") } if class.Usc != hfscClass2.Usc { t.Fatal("HfscClass2 USC don't match") } if class.Rsc != hfscClass2.Rsc { t.Fatal("HfscClass2 RSC don't match") } } if class.ClassAttrs.Handle == hfscClass3.ClassAttrs.Handle { if class.Fsc != hfscClass3.Fsc { t.Fatal("HfscClass3 FSC don't match") } if class.Usc != hfscClass3.Usc { t.Fatal("HfscClass3 USC don't match") } if class.Rsc != hfscClass3.Rsc { t.Fatal("HfscClass3 RSC don't match") } } } // Terminating the leafs with fq_codel qdiscs fqcodelAttrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(1, 2), Handle: MakeHandle(2, 0), } fqcodel1 := NewFqCodel(fqcodelAttrs) fqcodel1.ECN = 0 fqcodel1.Limit = 1200 fqcodel1.Flows = 65535 fqcodel1.Target = 5 err = QdiscAdd(fqcodel1) if err != nil { t.Fatal(err) } fqcodel2 := fqcodel1 fqcodel2.Attrs().Handle = MakeHandle(3, 0) fqcodel2.Attrs().Parent = MakeHandle(1, 3) err = QdiscAdd(fqcodel2) if err != nil { t.Fatal(err) } // Check the amount of qdiscs qdiscs, _ = SafeQdiscList(link) if len(qdiscs) != 3 { t.Fatal("Failed to add qdisc") } for _, q := range qdiscs[1:] { _, ok = q.(*FqCodel) if !ok { t.Fatal("Qdisc is the wrong type") } } // removing a class if err := ClassDel(hfscClass3); err != nil { t.Fatal(err) } // Check the classes classes, err = SafeClassList(link, MakeHandle(1, 0)) if err != nil { t.Fatal(err) } if len(classes) != 3 { t.Fatal("Failed to delete classes") } // Check qdisc qdiscs, _ = SafeQdiscList(link) if len(qdiscs) != 2 { t.Fatal("Failed to delete qdisc") } // Changing a class hfscClass2.SetRsc(0, 0, 0) hfscClass2.SetSC(5e6, 100, 1e6) hfscClass2.SetUL(6e6, 50, 2e6) hfscClass2.Attrs().Handle = MakeHandle(1, 8) if err := ClassChange(hfscClass2); err == nil { t.Fatal("Class change shouldn't work with a different handle") } hfscClass2.Attrs().Handle = MakeHandle(1, 2) if err := ClassChange(hfscClass2); err != nil { t.Fatal(err) } // Replacing a class // If the handle doesn't exist, create it hfscClass2.SetSC(6e6, 100, 2e6) hfscClass2.SetUL(8e6, 500, 4e6) hfscClass2.Attrs().Handle = MakeHandle(1, 8) if err := ClassReplace(hfscClass2); err != nil { t.Fatal(err) } // If the handle exists, replace it hfscClass.SetLS(5e6, 200, 1e6) if err := ClassChange(hfscClass); err != nil { t.Fatal(err) } }