mirror of
https://github.com/vishvananda/netlink
synced 2025-01-12 09:59:25 +00:00
[netem] minimalist support for netem
Support for tc_netem_qopt options, e.g: latency, limit, loss, gap, duplicate and jitter
This commit is contained in:
parent
9168c87284
commit
9b7c60d6bd
@ -79,6 +79,37 @@ func TestClassAddDel(t *testing.T) {
|
||||
if htb.Cbuffer != class.Cbuffer {
|
||||
t.Fatal("Cbuffer doesn't match")
|
||||
}
|
||||
|
||||
qattrs := QdiscAttrs{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Handle: MakeHandle(0x2, 0),
|
||||
Parent: MakeHandle(0xffff, 2),
|
||||
}
|
||||
nattrs := NetemQdiscAttrs{
|
||||
Latency: 20000,
|
||||
Loss: 23.4,
|
||||
Duplicate: 14.3,
|
||||
Jitter: 1000,
|
||||
}
|
||||
netem := NewNetem(qattrs, nattrs)
|
||||
if err := QdiscAdd(netem); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qdiscs, err = QdiscList(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")
|
||||
}
|
||||
|
||||
// Deletion
|
||||
|
||||
if err := ClassDel(class); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ const (
|
||||
SizeofTcActionMsg = 0x04
|
||||
SizeofTcPrioMap = 0x14
|
||||
SizeofTcRateSpec = 0x0c
|
||||
SizeofTcNetemQopt = 0x18
|
||||
SizeofTcTbfQopt = 2*SizeofTcRateSpec + 0x0c
|
||||
SizeofTcHtbCopt = 2*SizeofTcRateSpec + 0x14
|
||||
SizeofTcHtbGlob = 0x14
|
||||
@ -191,6 +192,53 @@ func (x *TcRateSpec) Serialize() []byte {
|
||||
return (*(*[SizeofTcRateSpec]byte)(unsafe.Pointer(x)))[:]
|
||||
}
|
||||
|
||||
/**
|
||||
* NETEM
|
||||
*/
|
||||
|
||||
const (
|
||||
TCA_NETEM_UNSPEC = iota
|
||||
TCA_NETEM_CORR
|
||||
TCA_NETEM_DELAY_DIST
|
||||
TCA_NETEM_REORDER
|
||||
TCA_NETEM_CORRUPT
|
||||
TCA_NETEM_LOSS
|
||||
TCA_NETEM_RATE
|
||||
TCA_NETEM_ECN
|
||||
TCA_NETEM_RATE64
|
||||
TCA_NETEM_MAX = TCA_NETEM_RATE64
|
||||
)
|
||||
|
||||
// struct tc_netem_qopt {
|
||||
// __u32 latency; /* added delay (us) */
|
||||
// __u32 limit; /* fifo limit (packets) */
|
||||
// __u32 loss; /* random packet loss (0=none ~0=100%) */
|
||||
// __u32 gap; /* re-ordering gap (0 for none) */
|
||||
// __u32 duplicate; /* random packet dup (0=none ~0=100%) */
|
||||
// __u32 jitter; /* random jitter in latency (us) */
|
||||
// };
|
||||
|
||||
type TcNetemQopt struct {
|
||||
Latency uint32
|
||||
Limit uint32
|
||||
Loss uint32
|
||||
Gap uint32
|
||||
Duplicate uint32
|
||||
Jitter uint32
|
||||
}
|
||||
|
||||
func (msg *TcNetemQopt) Len() int {
|
||||
return SizeofTcNetemQopt
|
||||
}
|
||||
|
||||
func DeserializeTcNetemQopt(b []byte) *TcNetemQopt {
|
||||
return (*TcNetemQopt)(unsafe.Pointer(&b[0:SizeofTcNetemQopt][0]))
|
||||
}
|
||||
|
||||
func (x *TcNetemQopt) Serialize() []byte {
|
||||
return (*(*[SizeofTcNetemQopt]byte)(unsafe.Pointer(x)))[:]
|
||||
}
|
||||
|
||||
// struct tc_tbf_qopt {
|
||||
// struct tc_ratespec rate;
|
||||
// struct tc_ratespec peakrate;
|
||||
|
72
qdisc.go
72
qdisc.go
@ -2,6 +2,7 @@ package netlink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -52,6 +53,10 @@ func HandleStr(handle uint32) string {
|
||||
}
|
||||
}
|
||||
|
||||
func Percentage(percentage float32) uint32 {
|
||||
return uint32(math.MaxUint32 * (percentage / 100))
|
||||
}
|
||||
|
||||
// PfifoFast is the default qdisc created by the kernel if one has not
|
||||
// been defined for the interface
|
||||
type PfifoFast struct {
|
||||
@ -120,6 +125,73 @@ func (qdisc *Htb) Type() string {
|
||||
return "htb"
|
||||
}
|
||||
|
||||
// Netem is a classless qdisc that rate limits based on tokens
|
||||
|
||||
type NetemQdiscAttrs struct {
|
||||
Latency uint32 // in us
|
||||
Limit uint32
|
||||
Loss float32 // in %
|
||||
Gap uint32
|
||||
Duplicate float32 // in %
|
||||
Jitter uint32 // in us
|
||||
}
|
||||
|
||||
func (q NetemQdiscAttrs) String() string {
|
||||
return fmt.Sprintf(
|
||||
"{Latency: %d, Limit: %d, Loss: %d, Gap: %d, Duplicate: %d, Jitter: %d}",
|
||||
q.Latency, q.Limit, q.Loss, q.Gap, q.Duplicate, q.Jitter,
|
||||
)
|
||||
}
|
||||
|
||||
type Netem struct {
|
||||
QdiscAttrs
|
||||
Latency uint32
|
||||
Limit uint32
|
||||
Loss uint32
|
||||
Gap uint32
|
||||
Duplicate uint32
|
||||
Jitter uint32
|
||||
}
|
||||
|
||||
func NewNetem(attrs QdiscAttrs, nattrs NetemQdiscAttrs) *Netem {
|
||||
var limit uint32 = 1000
|
||||
|
||||
latency := nattrs.Latency
|
||||
loss := Percentage(nattrs.Loss)
|
||||
gap := nattrs.Gap
|
||||
duplicate := Percentage(nattrs.Duplicate)
|
||||
jitter := nattrs.Jitter
|
||||
|
||||
// FIXME should validate values(like loss/duplicate are percentages...)
|
||||
latency = time2Tick(latency)
|
||||
|
||||
if nattrs.Limit != 0 {
|
||||
limit = nattrs.Limit
|
||||
}
|
||||
// Jitter is only value if latency is > 0
|
||||
if latency > 0 {
|
||||
jitter = time2Tick(jitter)
|
||||
}
|
||||
|
||||
return &Netem{
|
||||
QdiscAttrs: attrs,
|
||||
Latency: latency,
|
||||
Limit: limit,
|
||||
Loss: loss,
|
||||
Gap: gap,
|
||||
Duplicate: duplicate,
|
||||
Jitter: jitter,
|
||||
}
|
||||
}
|
||||
|
||||
func (qdisc *Netem) Attrs() *QdiscAttrs {
|
||||
return &qdisc.QdiscAttrs
|
||||
}
|
||||
|
||||
func (qdisc *Netem) Type() string {
|
||||
return "netem"
|
||||
}
|
||||
|
||||
// Tbf is a classless qdisc that rate limits based on tokens
|
||||
type Tbf struct {
|
||||
QdiscAttrs
|
||||
|
@ -65,6 +65,15 @@ func QdiscAdd(qdisc Qdisc) error {
|
||||
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 netem, ok := qdisc.(*Netem); ok {
|
||||
opt := nl.TcNetemQopt{}
|
||||
opt.Latency = netem.Latency
|
||||
opt.Limit = netem.Limit
|
||||
opt.Loss = netem.Loss
|
||||
opt.Gap = netem.Gap
|
||||
opt.Duplicate = netem.Duplicate
|
||||
opt.Jitter = netem.Jitter
|
||||
options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize())
|
||||
} else if _, ok := qdisc.(*Ingress); ok {
|
||||
// ingress filters must use the proper handle
|
||||
if msg.Parent != HANDLE_INGRESS {
|
||||
@ -135,6 +144,8 @@ func QdiscList(link Link) ([]Qdisc, error) {
|
||||
qdisc = &Ingress{}
|
||||
case "htb":
|
||||
qdisc = &Htb{}
|
||||
case "netem":
|
||||
qdisc = &Netem{}
|
||||
default:
|
||||
qdisc = &GenericQdisc{QdiscType: qdiscType}
|
||||
}
|
||||
@ -166,6 +177,10 @@ func QdiscList(link Link) ([]Qdisc, error) {
|
||||
if err := parseHtbData(qdisc, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "netem":
|
||||
if err := parseNetemData(qdisc, attr.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// no options for ingress
|
||||
}
|
||||
@ -213,6 +228,19 @@ func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseNetemData(qdisc Qdisc, value []byte) error {
|
||||
netem := qdisc.(*Netem)
|
||||
opt := nl.DeserializeTcNetemQopt(value)
|
||||
netem.Latency = opt.Latency
|
||||
netem.Limit = opt.Limit
|
||||
netem.Loss = opt.Loss
|
||||
netem.Gap = opt.Gap
|
||||
netem.Duplicate = opt.Duplicate
|
||||
netem.Jitter = opt.Jitter
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error {
|
||||
native = nl.NativeEndian()
|
||||
tbf := qdisc.(*Tbf)
|
||||
|
@ -88,13 +88,6 @@ func TestHtbAddDel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
/*
|
||||
cmd := exec.Command("tc", "qdisc")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
fmt.Printf("%s\n", out)
|
||||
}
|
||||
*/
|
||||
qdiscs, err := QdiscList(link)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -126,6 +119,7 @@ func TestHtbAddDel(t *testing.T) {
|
||||
t.Fatal("Failed to remove qdisc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioAddDel(t *testing.T) {
|
||||
tearDown := setUpNetlinkTest(t)
|
||||
defer tearDown()
|
||||
|
Loading…
Reference in New Issue
Block a user