| package netlink |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "strconv" |
| "strings" |
| "syscall" |
| |
| "github.com/vishvananda/netlink/nl" |
| ) |
| |
| // NOTE function is here because it uses other linux functions |
| func NewNetem(attrs QdiscAttrs, nattrs NetemQdiscAttrs) *Netem { |
| var limit uint32 = 1000 |
| var lossCorr, delayCorr, duplicateCorr uint32 |
| var reorderProb, reorderCorr uint32 |
| var corruptProb, corruptCorr uint32 |
| |
| latency := nattrs.Latency |
| loss := Percentage2u32(nattrs.Loss) |
| gap := nattrs.Gap |
| duplicate := Percentage2u32(nattrs.Duplicate) |
| jitter := nattrs.Jitter |
| |
| // Correlation |
| if latency > 0 && jitter > 0 { |
| delayCorr = Percentage2u32(nattrs.DelayCorr) |
| } |
| if loss > 0 { |
| lossCorr = Percentage2u32(nattrs.LossCorr) |
| } |
| if duplicate > 0 { |
| duplicateCorr = Percentage2u32(nattrs.DuplicateCorr) |
| } |
| // 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) |
| } |
| |
| reorderProb = Percentage2u32(nattrs.ReorderProb) |
| reorderCorr = Percentage2u32(nattrs.ReorderCorr) |
| |
| if reorderProb > 0 { |
| // ERROR if lantency == 0 |
| if gap == 0 { |
| gap = 1 |
| } |
| } |
| |
| corruptProb = Percentage2u32(nattrs.CorruptProb) |
| corruptCorr = Percentage2u32(nattrs.CorruptCorr) |
| |
| return &Netem{ |
| QdiscAttrs: attrs, |
| Latency: latency, |
| DelayCorr: delayCorr, |
| Limit: limit, |
| Loss: loss, |
| LossCorr: lossCorr, |
| Gap: gap, |
| Duplicate: duplicate, |
| DuplicateCorr: duplicateCorr, |
| Jitter: jitter, |
| ReorderProb: reorderProb, |
| ReorderCorr: reorderCorr, |
| CorruptProb: corruptProb, |
| CorruptCorr: corruptCorr, |
| } |
| } |
| |
| // QdiscDel will delete a qdisc from the system. |
| // Equivalent to: `tc qdisc del $qdisc` |
| func QdiscDel(qdisc Qdisc) error { |
| return pkgHandle.QdiscDel(qdisc) |
| } |
| |
| // QdiscDel will delete a qdisc from the system. |
| // Equivalent to: `tc qdisc del $qdisc` |
| func (h *Handle) QdiscDel(qdisc Qdisc) error { |
| return h.qdiscModify(syscall.RTM_DELQDISC, 0, qdisc) |
| } |
| |
| // QdiscChange will change a qdisc in place |
| // Equivalent to: `tc qdisc change $qdisc` |
| // The parent and handle MUST NOT be changed. |
| func QdiscChange(qdisc Qdisc) error { |
| return pkgHandle.QdiscChange(qdisc) |
| } |
| |
| // QdiscChange will change a qdisc in place |
| // Equivalent to: `tc qdisc change $qdisc` |
| // The parent and handle MUST NOT be changed. |
| func (h *Handle) QdiscChange(qdisc Qdisc) error { |
| return h.qdiscModify(syscall.RTM_NEWQDISC, 0, qdisc) |
| } |
| |
| // QdiscReplace will replace a qdisc to the system. |
| // Equivalent to: `tc qdisc replace $qdisc` |
| // The handle MUST change. |
| func QdiscReplace(qdisc Qdisc) error { |
| return pkgHandle.QdiscReplace(qdisc) |
| } |
| |
| // QdiscReplace will replace a qdisc to the system. |
| // Equivalent to: `tc qdisc replace $qdisc` |
| // The handle MUST change. |
| func (h *Handle) QdiscReplace(qdisc Qdisc) error { |
| return h.qdiscModify( |
| syscall.RTM_NEWQDISC, |
| syscall.NLM_F_CREATE|syscall.NLM_F_REPLACE, |
| qdisc) |
| } |
| |
| // QdiscAdd will add a qdisc to the system. |
| // Equivalent to: `tc qdisc add $qdisc` |
| func QdiscAdd(qdisc Qdisc) error { |
| return pkgHandle.QdiscAdd(qdisc) |
| } |
| |
| // QdiscAdd will add a qdisc to the system. |
| // Equivalent to: `tc qdisc add $qdisc` |
| func (h *Handle) QdiscAdd(qdisc Qdisc) error { |
| return h.qdiscModify( |
| syscall.RTM_NEWQDISC, |
| syscall.NLM_F_CREATE|syscall.NLM_F_EXCL, |
| qdisc) |
| } |
| |
| func (h *Handle) qdiscModify(cmd, flags int, qdisc Qdisc) error { |
| req := h.newNetlinkRequest(cmd, flags|syscall.NLM_F_ACK) |
| base := qdisc.Attrs() |
| msg := &nl.TcMsg{ |
| Family: nl.FAMILY_ALL, |
| Ifindex: int32(base.LinkIndex), |
| Handle: base.Handle, |
| Parent: base.Parent, |
| } |
| req.AddData(msg) |
| |
| // When deleting don't bother building the rest of the netlink payload |
| if cmd != syscall.RTM_DELQDISC { |
| if err := qdiscPayload(req, qdisc); err != nil { |
| return err |
| } |
| } |
| |
| _, err := req.Execute(syscall.NETLINK_ROUTE, 0) |
| return err |
| } |
| |
| func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { |
| |
| req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(qdisc.Type()))) |
| |
| options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) |
| |
| switch qdisc := qdisc.(type) { |
| case *Prio: |
| tcmap := nl.TcPrioMap{ |
| Bands: int32(qdisc.Bands), |
| Priomap: qdisc.PriorityMap, |
| } |
| options = nl.NewRtAttr(nl.TCA_OPTIONS, tcmap.Serialize()) |
| case *Tbf: |
| opt := nl.TcTbfQopt{} |
| opt.Rate.Rate = uint32(qdisc.Rate) |
| opt.Peakrate.Rate = uint32(qdisc.Peakrate) |
| opt.Limit = qdisc.Limit |
| opt.Buffer = qdisc.Buffer |
| nl.NewRtAttrChild(options, nl.TCA_TBF_PARMS, opt.Serialize()) |
| if qdisc.Rate >= uint64(1<<32) { |
| nl.NewRtAttrChild(options, nl.TCA_TBF_RATE64, nl.Uint64Attr(qdisc.Rate)) |
| } |
| if qdisc.Peakrate >= uint64(1<<32) { |
| nl.NewRtAttrChild(options, nl.TCA_TBF_PRATE64, nl.Uint64Attr(qdisc.Peakrate)) |
| } |
| if qdisc.Peakrate > 0 { |
| nl.NewRtAttrChild(options, nl.TCA_TBF_PBURST, nl.Uint32Attr(qdisc.Minburst)) |
| } |
| case *Htb: |
| opt := nl.TcHtbGlob{} |
| opt.Version = qdisc.Version |
| opt.Rate2Quantum = qdisc.Rate2Quantum |
| opt.Defcls = qdisc.Defcls |
| // TODO: Handle Debug properly. For now default to 0 |
| opt.Debug = qdisc.Debug |
| opt.DirectPkts = qdisc.DirectPkts |
| nl.NewRtAttrChild(options, nl.TCA_HTB_INIT, opt.Serialize()) |
| // nl.NewRtAttrChild(options, nl.TCA_HTB_DIRECT_QLEN, opt.Serialize()) |
| case *Netem: |
| opt := nl.TcNetemQopt{} |
| opt.Latency = qdisc.Latency |
| opt.Limit = qdisc.Limit |
| opt.Loss = qdisc.Loss |
| opt.Gap = qdisc.Gap |
| opt.Duplicate = qdisc.Duplicate |
| opt.Jitter = qdisc.Jitter |
| options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) |
| // Correlation |
| corr := nl.TcNetemCorr{} |
| corr.DelayCorr = qdisc.DelayCorr |
| corr.LossCorr = qdisc.LossCorr |
| corr.DupCorr = qdisc.DuplicateCorr |
| |
| if corr.DelayCorr > 0 || corr.LossCorr > 0 || corr.DupCorr > 0 { |
| nl.NewRtAttrChild(options, nl.TCA_NETEM_CORR, corr.Serialize()) |
| } |
| // Corruption |
| corruption := nl.TcNetemCorrupt{} |
| corruption.Probability = qdisc.CorruptProb |
| corruption.Correlation = qdisc.CorruptCorr |
| if corruption.Probability > 0 { |
| nl.NewRtAttrChild(options, nl.TCA_NETEM_CORRUPT, corruption.Serialize()) |
| } |
| // Reorder |
| reorder := nl.TcNetemReorder{} |
| reorder.Probability = qdisc.ReorderProb |
| reorder.Correlation = qdisc.ReorderCorr |
| if reorder.Probability > 0 { |
| nl.NewRtAttrChild(options, nl.TCA_NETEM_REORDER, reorder.Serialize()) |
| } |
| case *Ingress: |
| // ingress filters must use the proper handle |
| if qdisc.Attrs().Parent != HANDLE_INGRESS { |
| return fmt.Errorf("Ingress filters must set Parent to HANDLE_INGRESS") |
| } |
| } |
| |
| req.AddData(options) |
| return nil |
| } |
| |
| // QdiscList gets a list of qdiscs in the system. |
| // Equivalent to: `tc qdisc show`. |
| // The list can be filtered by link. |
| func QdiscList(link Link) ([]Qdisc, error) { |
| return pkgHandle.QdiscList(link) |
| } |
| |
| // QdiscList gets a list of qdiscs in the system. |
| // Equivalent to: `tc qdisc show`. |
| // The list can be filtered by link. |
| func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { |
| req := h.newNetlinkRequest(syscall.RTM_GETQDISC, syscall.NLM_F_DUMP) |
| index := int32(0) |
| if link != nil { |
| base := link.Attrs() |
| h.ensureIndex(base) |
| index = int32(base.Index) |
| } |
| msg := &nl.TcMsg{ |
| Family: nl.FAMILY_ALL, |
| Ifindex: index, |
| } |
| req.AddData(msg) |
| |
| msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWQDISC) |
| if err != nil { |
| return nil, err |
| } |
| |
| var res []Qdisc |
| for _, m := range msgs { |
| msg := nl.DeserializeTcMsg(m) |
| |
| attrs, err := nl.ParseRouteAttr(m[msg.Len():]) |
| if err != nil { |
| return nil, err |
| } |
| |
| // skip qdiscs from other interfaces |
| if link != nil && msg.Ifindex != index { |
| continue |
| } |
| |
| base := QdiscAttrs{ |
| LinkIndex: int(msg.Ifindex), |
| Handle: msg.Handle, |
| Parent: msg.Parent, |
| Refcnt: msg.Info, |
| } |
| var qdisc Qdisc |
| qdiscType := "" |
| for _, attr := range attrs { |
| switch attr.Attr.Type { |
| case nl.TCA_KIND: |
| qdiscType = string(attr.Value[:len(attr.Value)-1]) |
| switch qdiscType { |
| case "pfifo_fast": |
| qdisc = &PfifoFast{} |
| case "prio": |
| qdisc = &Prio{} |
| case "tbf": |
| qdisc = &Tbf{} |
| case "ingress": |
| qdisc = &Ingress{} |
| case "htb": |
| qdisc = &Htb{} |
| case "netem": |
| qdisc = &Netem{} |
| default: |
| qdisc = &GenericQdisc{QdiscType: qdiscType} |
| } |
| case nl.TCA_OPTIONS: |
| switch qdiscType { |
| case "pfifo_fast": |
| // pfifo returns TcPrioMap directly without wrapping it in rtattr |
| if err := parsePfifoFastData(qdisc, attr.Value); err != nil { |
| return nil, err |
| } |
| case "prio": |
| // prio returns TcPrioMap directly without wrapping it in rtattr |
| if err := parsePrioData(qdisc, attr.Value); err != nil { |
| return nil, err |
| } |
| case "tbf": |
| data, err := nl.ParseRouteAttr(attr.Value) |
| if err != nil { |
| return nil, err |
| } |
| if err := parseTbfData(qdisc, data); err != nil { |
| return nil, err |
| } |
| case "htb": |
| data, err := nl.ParseRouteAttr(attr.Value) |
| if err != nil { |
| return nil, err |
| } |
| 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 |
| } |
| } |
| } |
| *qdisc.Attrs() = base |
| res = append(res, qdisc) |
| } |
| |
| return res, nil |
| } |
| |
| func parsePfifoFastData(qdisc Qdisc, value []byte) error { |
| pfifo := qdisc.(*PfifoFast) |
| tcmap := nl.DeserializeTcPrioMap(value) |
| pfifo.PriorityMap = tcmap.Priomap |
| pfifo.Bands = uint8(tcmap.Bands) |
| return nil |
| } |
| |
| func parsePrioData(qdisc Qdisc, value []byte) error { |
| prio := qdisc.(*Prio) |
| tcmap := nl.DeserializeTcPrioMap(value) |
| prio.PriorityMap = tcmap.Priomap |
| prio.Bands = uint8(tcmap.Bands) |
| return nil |
| } |
| |
| func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { |
| native = nl.NativeEndian() |
| htb := qdisc.(*Htb) |
| for _, datum := range data { |
| switch datum.Attr.Type { |
| case nl.TCA_HTB_INIT: |
| opt := nl.DeserializeTcHtbGlob(datum.Value) |
| htb.Version = opt.Version |
| htb.Rate2Quantum = opt.Rate2Quantum |
| htb.Defcls = opt.Defcls |
| htb.Debug = opt.Debug |
| htb.DirectPkts = opt.DirectPkts |
| case nl.TCA_HTB_DIRECT_QLEN: |
| // TODO |
| //htb.DirectQlen = native.uint32(datum.Value) |
| } |
| } |
| 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 |
| data, err := nl.ParseRouteAttr(value[nl.SizeofTcNetemQopt:]) |
| if err != nil { |
| return err |
| } |
| for _, datum := range data { |
| switch datum.Attr.Type { |
| case nl.TCA_NETEM_CORR: |
| opt := nl.DeserializeTcNetemCorr(datum.Value) |
| netem.DelayCorr = opt.DelayCorr |
| netem.LossCorr = opt.LossCorr |
| netem.DuplicateCorr = opt.DupCorr |
| case nl.TCA_NETEM_CORRUPT: |
| opt := nl.DeserializeTcNetemCorrupt(datum.Value) |
| netem.CorruptProb = opt.Probability |
| netem.CorruptCorr = opt.Correlation |
| case nl.TCA_NETEM_REORDER: |
| opt := nl.DeserializeTcNetemReorder(datum.Value) |
| netem.ReorderProb = opt.Probability |
| netem.ReorderCorr = opt.Correlation |
| } |
| } |
| return nil |
| } |
| |
| func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { |
| native = nl.NativeEndian() |
| tbf := qdisc.(*Tbf) |
| for _, datum := range data { |
| switch datum.Attr.Type { |
| case nl.TCA_TBF_PARMS: |
| opt := nl.DeserializeTcTbfQopt(datum.Value) |
| tbf.Rate = uint64(opt.Rate.Rate) |
| tbf.Peakrate = uint64(opt.Peakrate.Rate) |
| tbf.Limit = opt.Limit |
| tbf.Buffer = opt.Buffer |
| case nl.TCA_TBF_RATE64: |
| tbf.Rate = native.Uint64(datum.Value[0:8]) |
| case nl.TCA_TBF_PRATE64: |
| tbf.Peakrate = native.Uint64(datum.Value[0:8]) |
| case nl.TCA_TBF_PBURST: |
| tbf.Minburst = native.Uint32(datum.Value[0:4]) |
| } |
| } |
| return nil |
| } |
| |
| const ( |
| TIME_UNITS_PER_SEC = 1000000 |
| ) |
| |
| var ( |
| tickInUsec float64 |
| clockFactor float64 |
| hz float64 |
| ) |
| |
| func initClock() { |
| data, err := ioutil.ReadFile("/proc/net/psched") |
| if err != nil { |
| return |
| } |
| parts := strings.Split(strings.TrimSpace(string(data)), " ") |
| if len(parts) < 3 { |
| return |
| } |
| var vals [3]uint64 |
| for i := range vals { |
| val, err := strconv.ParseUint(parts[i], 16, 32) |
| if err != nil { |
| return |
| } |
| vals[i] = val |
| } |
| // compatibility |
| if vals[2] == 1000000000 { |
| vals[0] = vals[1] |
| } |
| clockFactor = float64(vals[2]) / TIME_UNITS_PER_SEC |
| tickInUsec = float64(vals[0]) / float64(vals[1]) * clockFactor |
| hz = float64(vals[0]) |
| } |
| |
| func TickInUsec() float64 { |
| if tickInUsec == 0.0 { |
| initClock() |
| } |
| return tickInUsec |
| } |
| |
| func ClockFactor() float64 { |
| if clockFactor == 0.0 { |
| initClock() |
| } |
| return clockFactor |
| } |
| |
| func Hz() float64 { |
| if hz == 0.0 { |
| initClock() |
| } |
| return hz |
| } |
| |
| func time2Tick(time uint32) uint32 { |
| return uint32(float64(time) * TickInUsec()) |
| } |
| |
| func tick2Time(tick uint32) uint32 { |
| return uint32(float64(tick) / TickInUsec()) |
| } |
| |
| func time2Ktime(time uint32) uint32 { |
| return uint32(float64(time) * ClockFactor()) |
| } |
| |
| func ktime2Time(ktime uint32) uint32 { |
| return uint32(float64(ktime) / ClockFactor()) |
| } |
| |
| func burst(rate uint64, buffer uint32) uint32 { |
| return uint32(float64(rate) * float64(tick2Time(buffer)) / TIME_UNITS_PER_SEC) |
| } |
| |
| func latency(rate uint64, limit, buffer uint32) float64 { |
| return TIME_UNITS_PER_SEC*(float64(limit)/float64(rate)) - float64(tick2Time(buffer)) |
| } |
| |
| func Xmittime(rate uint64, size uint32) float64 { |
| return TickInUsec() * TIME_UNITS_PER_SEC * (float64(size) / float64(rate)) |
| } |