Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions qdisc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
HANDLE_ROOT = 0xFFFFFFFF
PRIORITY_MAP_LEN = 16
)

const (
HANDLE_MIN_INGRESS = 0xFFFFFFF2
HANDLE_MIN_EGRESS = 0xFFFFFFF3
Expand Down Expand Up @@ -68,12 +69,27 @@ func HandleStr(handle uint32) string {
}
}

// Percentage2u32 converts a percentage (0-100) to a kernel format uint32 value.
// This is the inverse of u32ToPercentage.
func Percentage2u32(percentage float32) uint32 {
// FIXME this is most likely not the best way to convert from % to uint32
if percentage == 100 {
if percentage >= 100 {
return math.MaxUint32
}
return uint32(math.MaxUint32 * (percentage / 100))
if percentage <= 0 {
return 0
}

return uint32(float64(percentage) * math.MaxUint32 / 100)
}

// u32ToPercentage converts a kernel format uint32 value back to a percentage.
// This is the inverse of Percentage2u32.
func u32ToPercentage(value uint32) float32 {
percentage := float32(float64(value) * 100 / float64(math.MaxUint32))
if value < math.MaxUint32 && percentage == 100 {
return math.Nextafter32(100, 0)
}
return percentage
}

// PfifoFast is the default qdisc created by the kernel if one has not
Expand Down
22 changes: 22 additions & 0 deletions qdisc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ func NewNetem(attrs QdiscAttrs, nattrs NetemQdiscAttrs) *Netem {
}
}

// ToNetemQdiscAttrs converts the Netem struct back to human-readable NetemQdiscAttrs.
// This is useful when reading qdisc settings from the kernel, as the values stored
// in Netem are in kernel format (ticks for time, uint32 for percentages).
func (netem *Netem) ToNetemQdiscAttrs() NetemQdiscAttrs {
return NetemQdiscAttrs{
Latency: tick2Time(netem.Latency),
DelayCorr: u32ToPercentage(netem.DelayCorr),
Limit: netem.Limit,
Loss: u32ToPercentage(netem.Loss),
LossCorr: u32ToPercentage(netem.LossCorr),
Gap: netem.Gap,
Duplicate: u32ToPercentage(netem.Duplicate),
DuplicateCorr: u32ToPercentage(netem.DuplicateCorr),
Jitter: tick2Time(netem.Jitter),
ReorderProb: u32ToPercentage(netem.ReorderProb),
ReorderCorr: u32ToPercentage(netem.ReorderCorr),
CorruptProb: u32ToPercentage(netem.CorruptProb),
CorruptCorr: u32ToPercentage(netem.CorruptCorr),
Rate64: netem.Rate64,
}
}

// QdiscDel will delete a qdisc from the system.
// Equivalent to: `tc qdisc del $qdisc`
func QdiscDel(qdisc Qdisc) error {
Expand Down
128 changes: 128 additions & 0 deletions qdisc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package netlink

import (
"math"
"testing"
)

Expand Down Expand Up @@ -626,3 +627,130 @@ func TestIngressAddDel(t *testing.T) {
t.Fatal("Failed to remove qdisc")
}
}

// Tests the round-trip conversion of Netem attributes to ensure
// human-readable values are correctly preserved (fixes #480).
func TestNetemQdiscAttrsRoundTrip(t *testing.T) {
initClockMutex.Lock()
oldTickInUsec := tickInUsec
tickInUsec = 15.625
initClockMutex.Unlock()
defer func() {
initClockMutex.Lock()
tickInUsec = oldTickInUsec
initClockMutex.Unlock()
}()

nattrs := NetemQdiscAttrs{
Latency: 5000,
DelayCorr: 12.5,
Limit: 2048,
Loss: 5.0,
LossCorr: 9.5,
Gap: 3,
Duplicate: 4.0,
DuplicateCorr: 7.0,
Jitter: 1300,
ReorderProb: 10.0,
ReorderCorr: 11.0,
CorruptProb: 3.0,
CorruptCorr: 2.0,
Rate64: 123456789,
}
netem := NewNetem(QdiscAttrs{}, nattrs)

if netem.Latency != time2Tick(nattrs.Latency) {
t.Fatalf("kernel-format latency mismatch: got %d, want %d", netem.Latency, time2Tick(nattrs.Latency))
}
if netem.DelayCorr != Percentage2u32(nattrs.DelayCorr) {
t.Fatalf("kernel-format delayCorr mismatch: got %d, want %d", netem.DelayCorr, Percentage2u32(nattrs.DelayCorr))
}
if netem.Limit != nattrs.Limit {
t.Fatalf("kernel-format limit mismatch: got %d, want %d", netem.Limit, nattrs.Limit)
}
if netem.Loss != Percentage2u32(nattrs.Loss) {
t.Fatalf("kernel-format loss mismatch: got %d, want %d", netem.Loss, Percentage2u32(nattrs.Loss))
}
if netem.LossCorr != Percentage2u32(nattrs.LossCorr) {
t.Fatalf("kernel-format lossCorr mismatch: got %d, want %d", netem.LossCorr, Percentage2u32(nattrs.LossCorr))
}
if netem.Gap != nattrs.Gap {
t.Fatalf("kernel-format gap mismatch: got %d, want %d", netem.Gap, nattrs.Gap)
}
if netem.Duplicate != Percentage2u32(nattrs.Duplicate) {
t.Fatalf("kernel-format duplicate mismatch: got %d, want %d", netem.Duplicate, Percentage2u32(nattrs.Duplicate))
}
if netem.DuplicateCorr != Percentage2u32(nattrs.DuplicateCorr) {
t.Fatalf("kernel-format duplicateCorr mismatch: got %d, want %d", netem.DuplicateCorr, Percentage2u32(nattrs.DuplicateCorr))
}
if netem.Jitter != time2Tick(nattrs.Jitter) {
t.Fatalf("kernel-format jitter mismatch: got %d, want %d", netem.Jitter, time2Tick(nattrs.Jitter))
}
if netem.ReorderProb != Percentage2u32(nattrs.ReorderProb) {
t.Fatalf("kernel-format reorderProb mismatch: got %d, want %d", netem.ReorderProb, Percentage2u32(nattrs.ReorderProb))
}
if netem.ReorderCorr != Percentage2u32(nattrs.ReorderCorr) {
t.Fatalf("kernel-format reorderCorr mismatch: got %d, want %d", netem.ReorderCorr, Percentage2u32(nattrs.ReorderCorr))
}
if netem.CorruptProb != Percentage2u32(nattrs.CorruptProb) {
t.Fatalf("kernel-format corruptProb mismatch: got %d, want %d", netem.CorruptProb, Percentage2u32(nattrs.CorruptProb))
}
if netem.CorruptCorr != Percentage2u32(nattrs.CorruptCorr) {
t.Fatalf("kernel-format corruptCorr mismatch: got %d, want %d", netem.CorruptCorr, Percentage2u32(nattrs.CorruptCorr))
}
if netem.Rate64 != nattrs.Rate64 {
t.Fatalf("kernel-format rate64 mismatch: got %d, want %d", netem.Rate64, nattrs.Rate64)
}

human := netem.ToNetemQdiscAttrs()
if human.Latency != tick2Time(time2Tick(nattrs.Latency)) {
t.Fatalf("human-readable latency mismatch: got %d, want %d", human.Latency, tick2Time(time2Tick(nattrs.Latency)))
}
if math.Abs(float64(human.DelayCorr-nattrs.DelayCorr)) > 0.0001 {
t.Fatalf("human-readable delayCorr mismatch: got %f, want %f", human.DelayCorr, nattrs.DelayCorr)
}
if human.Limit != nattrs.Limit {
t.Fatalf("human-readable limit mismatch: got %d, want %d", human.Limit, nattrs.Limit)
}
if math.Abs(float64(human.Loss-nattrs.Loss)) > 0.0001 {
t.Fatalf("human-readable loss mismatch: got %f, want %f", human.Loss, nattrs.Loss)
}
if math.Abs(float64(human.LossCorr-nattrs.LossCorr)) > 0.0001 {
t.Fatalf("human-readable lossCorr mismatch: got %f, want %f", human.LossCorr, nattrs.LossCorr)
}
if human.Gap != nattrs.Gap {
t.Fatalf("human-readable gap mismatch: got %d, want %d", human.Gap, nattrs.Gap)
}
if math.Abs(float64(human.Duplicate-nattrs.Duplicate)) > 0.0001 {
t.Fatalf("human-readable duplicate mismatch: got %f, want %f", human.Duplicate, nattrs.Duplicate)
}
if math.Abs(float64(human.DuplicateCorr-nattrs.DuplicateCorr)) > 0.0001 {
t.Fatalf("human-readable duplicateCorr mismatch: got %f, want %f", human.DuplicateCorr, nattrs.DuplicateCorr)
}
if human.Jitter != tick2Time(time2Tick(nattrs.Jitter)) {
t.Fatalf("human-readable jitter mismatch: got %d, want %d", human.Jitter, tick2Time(time2Tick(nattrs.Jitter)))
}
if math.Abs(float64(human.ReorderProb-nattrs.ReorderProb)) > 0.0001 {
t.Fatalf("human-readable reorderProb mismatch: got %f, want %f", human.ReorderProb, nattrs.ReorderProb)
}
if math.Abs(float64(human.ReorderCorr-nattrs.ReorderCorr)) > 0.0001 {
t.Fatalf("human-readable reorderCorr mismatch: got %f, want %f", human.ReorderCorr, nattrs.ReorderCorr)
}
if math.Abs(float64(human.CorruptProb-nattrs.CorruptProb)) > 0.0001 {
t.Fatalf("human-readable corruptProb mismatch: got %f, want %f", human.CorruptProb, nattrs.CorruptProb)
}
if math.Abs(float64(human.CorruptCorr-nattrs.CorruptCorr)) > 0.0001 {
t.Fatalf("human-readable corruptCorr mismatch: got %f, want %f", human.CorruptCorr, nattrs.CorruptCorr)
}
if human.Rate64 != nattrs.Rate64 {
t.Fatalf("human-readable rate64 mismatch: got %d, want %d", human.Rate64, nattrs.Rate64)
}
}

func TestU32ToPercentagePrecisionNearMax(t *testing.T) {
almostMax := uint32(math.MaxUint32 - 1)
got := u32ToPercentage(almostMax)
if got >= 100 {
t.Fatalf("u32ToPercentage(%d) must be < 100, got %f", almostMax, got)
}
}
Loading