github.com/vishvananda/netlink@v1.3.1/class_linux.go (about) 1 package netlink 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "syscall" 10 11 "github.com/vishvananda/netlink/nl" 12 "golang.org/x/sys/unix" 13 ) 14 15 // Internal tc_stats representation in Go struct. 16 // This is for internal uses only to deserialize the payload of rtattr. 17 // After the deserialization, this should be converted into the canonical stats 18 // struct, ClassStatistics, in case of statistics of a class. 19 // Ref: struct tc_stats { ... } 20 type tcStats struct { 21 Bytes uint64 // Number of enqueued bytes 22 Packets uint32 // Number of enqueued packets 23 Drops uint32 // Packets dropped because of lack of resources 24 Overlimits uint32 // Number of throttle events when this flow goes out of allocated bandwidth 25 Bps uint32 // Current flow byte rate 26 Pps uint32 // Current flow packet rate 27 Qlen uint32 28 Backlog uint32 29 } 30 31 // NewHtbClass NOTE: function is in here because it uses other linux functions 32 func NewHtbClass(attrs ClassAttrs, cattrs HtbClassAttrs) *HtbClass { 33 mtu := 1600 34 rate := cattrs.Rate / 8 35 ceil := cattrs.Ceil / 8 36 buffer := cattrs.Buffer 37 cbuffer := cattrs.Cbuffer 38 39 if ceil == 0 { 40 ceil = rate 41 } 42 43 if buffer == 0 { 44 buffer = uint32(float64(rate)/Hz() + float64(mtu)) 45 } 46 buffer = Xmittime(rate, buffer) 47 48 if cbuffer == 0 { 49 cbuffer = uint32(float64(ceil)/Hz() + float64(mtu)) 50 } 51 cbuffer = Xmittime(ceil, cbuffer) 52 53 return &HtbClass{ 54 ClassAttrs: attrs, 55 Rate: rate, 56 Ceil: ceil, 57 Buffer: buffer, 58 Cbuffer: cbuffer, 59 Level: 0, 60 Prio: cattrs.Prio, 61 Quantum: cattrs.Quantum, 62 } 63 } 64 65 // ClassDel will delete a class from the system. 66 // Equivalent to: `tc class del $class` 67 func ClassDel(class Class) error { 68 return pkgHandle.ClassDel(class) 69 } 70 71 // ClassDel will delete a class from the system. 72 // Equivalent to: `tc class del $class` 73 func (h *Handle) ClassDel(class Class) error { 74 return h.classModify(unix.RTM_DELTCLASS, 0, class) 75 } 76 77 // ClassChange will change a class in place 78 // Equivalent to: `tc class change $class` 79 // The parent and handle MUST NOT be changed. 80 func ClassChange(class Class) error { 81 return pkgHandle.ClassChange(class) 82 } 83 84 // ClassChange will change a class in place 85 // Equivalent to: `tc class change $class` 86 // The parent and handle MUST NOT be changed. 87 func (h *Handle) ClassChange(class Class) error { 88 return h.classModify(unix.RTM_NEWTCLASS, 0, class) 89 } 90 91 // ClassReplace will replace a class to the system. 92 // quivalent to: `tc class replace $class` 93 // The handle MAY be changed. 94 // If a class already exist with this parent/handle pair, the class is changed. 95 // If a class does not already exist with this parent/handle, a new class is created. 96 func ClassReplace(class Class) error { 97 return pkgHandle.ClassReplace(class) 98 } 99 100 // ClassReplace will replace a class to the system. 101 // quivalent to: `tc class replace $class` 102 // The handle MAY be changed. 103 // If a class already exist with this parent/handle pair, the class is changed. 104 // If a class does not already exist with this parent/handle, a new class is created. 105 func (h *Handle) ClassReplace(class Class) error { 106 return h.classModify(unix.RTM_NEWTCLASS, unix.NLM_F_CREATE, class) 107 } 108 109 // ClassAdd will add a class to the system. 110 // Equivalent to: `tc class add $class` 111 func ClassAdd(class Class) error { 112 return pkgHandle.ClassAdd(class) 113 } 114 115 // ClassAdd will add a class to the system. 116 // Equivalent to: `tc class add $class` 117 func (h *Handle) ClassAdd(class Class) error { 118 return h.classModify( 119 unix.RTM_NEWTCLASS, 120 unix.NLM_F_CREATE|unix.NLM_F_EXCL, 121 class, 122 ) 123 } 124 125 func (h *Handle) classModify(cmd, flags int, class Class) error { 126 req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) 127 base := class.Attrs() 128 msg := &nl.TcMsg{ 129 Family: nl.FAMILY_ALL, 130 Ifindex: int32(base.LinkIndex), 131 Handle: base.Handle, 132 Parent: base.Parent, 133 } 134 req.AddData(msg) 135 136 if cmd != unix.RTM_DELTCLASS { 137 if err := classPayload(req, class); err != nil { 138 return err 139 } 140 } 141 _, err := req.Execute(unix.NETLINK_ROUTE, 0) 142 return err 143 } 144 145 func classPayload(req *nl.NetlinkRequest, class Class) error { 146 req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(class.Type()))) 147 148 options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) 149 switch class.Type() { 150 case "htb": 151 htb := class.(*HtbClass) 152 opt := nl.TcHtbCopt{} 153 opt.Buffer = htb.Buffer 154 opt.Cbuffer = htb.Cbuffer 155 opt.Quantum = htb.Quantum 156 opt.Level = htb.Level 157 opt.Prio = htb.Prio 158 // TODO: Handle Debug properly. For now default to 0 159 /* Calculate {R,C}Tab and set Rate and Ceil */ 160 cellLog := -1 161 ccellLog := -1 162 linklayer := nl.LINKLAYER_ETHERNET 163 mtu := 1600 164 var rtab [256]uint32 165 var ctab [256]uint32 166 tcrate := nl.TcRateSpec{Rate: uint32(htb.Rate)} 167 if CalcRtable(&tcrate, rtab[:], cellLog, uint32(mtu), linklayer) < 0 { 168 return errors.New("HTB: failed to calculate rate table") 169 } 170 opt.Rate = tcrate 171 tcceil := nl.TcRateSpec{Rate: uint32(htb.Ceil)} 172 if CalcRtable(&tcceil, ctab[:], ccellLog, uint32(mtu), linklayer) < 0 { 173 return errors.New("HTB: failed to calculate ceil rate table") 174 } 175 opt.Ceil = tcceil 176 options.AddRtAttr(nl.TCA_HTB_PARMS, opt.Serialize()) 177 options.AddRtAttr(nl.TCA_HTB_RTAB, SerializeRtab(rtab)) 178 options.AddRtAttr(nl.TCA_HTB_CTAB, SerializeRtab(ctab)) 179 if htb.Rate >= uint64(1<<32) { 180 options.AddRtAttr(nl.TCA_HTB_RATE64, nl.Uint64Attr(htb.Rate)) 181 } 182 if htb.Ceil >= uint64(1<<32) { 183 options.AddRtAttr(nl.TCA_HTB_CEIL64, nl.Uint64Attr(htb.Ceil)) 184 } 185 case "hfsc": 186 hfsc := class.(*HfscClass) 187 opt := nl.HfscCopt{} 188 rm1, rd, rm2 := hfsc.Rsc.Attrs() 189 opt.Rsc.Set(rm1/8, rd, rm2/8) 190 fm1, fd, fm2 := hfsc.Fsc.Attrs() 191 opt.Fsc.Set(fm1/8, fd, fm2/8) 192 um1, ud, um2 := hfsc.Usc.Attrs() 193 opt.Usc.Set(um1/8, ud, um2/8) 194 options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc)) 195 options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc)) 196 options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc)) 197 } 198 req.AddData(options) 199 return nil 200 } 201 202 // ClassList gets a list of classes in the system. 203 // Equivalent to: `tc class show`. 204 // 205 // Generally returns nothing if link and parent are not specified. 206 // If the returned error is [ErrDumpInterrupted], results may be inconsistent 207 // or incomplete. 208 func ClassList(link Link, parent uint32) ([]Class, error) { 209 return pkgHandle.ClassList(link, parent) 210 } 211 212 // ClassList gets a list of classes in the system. 213 // Equivalent to: `tc class show`. 214 // 215 // Generally returns nothing if link and parent are not specified. 216 // If the returned error is [ErrDumpInterrupted], results may be inconsistent 217 // or incomplete. 218 func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { 219 req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) 220 msg := &nl.TcMsg{ 221 Family: nl.FAMILY_ALL, 222 Parent: parent, 223 } 224 if link != nil { 225 base := link.Attrs() 226 h.ensureIndex(base) 227 msg.Ifindex = int32(base.Index) 228 } 229 req.AddData(msg) 230 231 msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) 232 if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { 233 return nil, executeErr 234 } 235 236 var res []Class 237 for _, m := range msgs { 238 msg := nl.DeserializeTcMsg(m) 239 240 attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 241 if err != nil { 242 return nil, err 243 } 244 245 base := ClassAttrs{ 246 LinkIndex: int(msg.Ifindex), 247 Handle: msg.Handle, 248 Parent: msg.Parent, 249 Statistics: nil, 250 } 251 252 var class Class 253 classType := "" 254 for _, attr := range attrs { 255 switch attr.Attr.Type { 256 case nl.TCA_KIND: 257 classType = string(attr.Value[:len(attr.Value)-1]) 258 switch classType { 259 case "htb": 260 class = &HtbClass{} 261 case "hfsc": 262 class = &HfscClass{} 263 default: 264 class = &GenericClass{ClassType: classType} 265 } 266 case nl.TCA_OPTIONS: 267 switch classType { 268 case "htb": 269 data, err := nl.ParseRouteAttr(attr.Value) 270 if err != nil { 271 return nil, err 272 } 273 _, err = parseHtbClassData(class, data) 274 if err != nil { 275 return nil, err 276 } 277 case "hfsc": 278 data, err := nl.ParseRouteAttr(attr.Value) 279 if err != nil { 280 return nil, err 281 } 282 _, err = parseHfscClassData(class, data) 283 if err != nil { 284 return nil, err 285 } 286 } 287 // For backward compatibility. 288 case nl.TCA_STATS: 289 base.Statistics, err = parseTcStats(attr.Value) 290 if err != nil { 291 return nil, err 292 } 293 case nl.TCA_STATS2: 294 base.Statistics, err = parseTcStats2(attr.Value) 295 if err != nil { 296 return nil, err 297 } 298 } 299 } 300 *class.Attrs() = base 301 res = append(res, class) 302 } 303 304 return res, executeErr 305 } 306 307 func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 308 htb := class.(*HtbClass) 309 detailed := false 310 for _, datum := range data { 311 switch datum.Attr.Type { 312 case nl.TCA_HTB_PARMS: 313 opt := nl.DeserializeTcHtbCopt(datum.Value) 314 htb.Rate = uint64(opt.Rate.Rate) 315 htb.Ceil = uint64(opt.Ceil.Rate) 316 htb.Buffer = opt.Buffer 317 htb.Cbuffer = opt.Cbuffer 318 htb.Quantum = opt.Quantum 319 htb.Level = opt.Level 320 htb.Prio = opt.Prio 321 case nl.TCA_HTB_RATE64: 322 htb.Rate = native.Uint64(datum.Value[0:8]) 323 case nl.TCA_HTB_CEIL64: 324 htb.Ceil = native.Uint64(datum.Value[0:8]) 325 } 326 } 327 return detailed, nil 328 } 329 330 func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 331 hfsc := class.(*HfscClass) 332 detailed := false 333 for _, datum := range data { 334 m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs() 335 switch datum.Attr.Type { 336 case nl.TCA_HFSC_RSC: 337 hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 338 case nl.TCA_HFSC_FSC: 339 hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 340 case nl.TCA_HFSC_USC: 341 hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 342 } 343 } 344 return detailed, nil 345 } 346 347 func parseTcStats(data []byte) (*ClassStatistics, error) { 348 buf := &bytes.Buffer{} 349 buf.Write(data) 350 tcStats := &tcStats{} 351 if err := binary.Read(buf, native, tcStats); err != nil { 352 return nil, err 353 } 354 355 stats := NewClassStatistics() 356 stats.Basic.Bytes = tcStats.Bytes 357 stats.Basic.Packets = tcStats.Packets 358 stats.Queue.Qlen = tcStats.Qlen 359 stats.Queue.Backlog = tcStats.Backlog 360 stats.Queue.Drops = tcStats.Drops 361 stats.Queue.Overlimits = tcStats.Overlimits 362 stats.RateEst.Bps = tcStats.Bps 363 stats.RateEst.Pps = tcStats.Pps 364 365 return stats, nil 366 } 367 368 func parseGnetStats(data []byte, gnetStats interface{}) error { 369 buf := &bytes.Buffer{} 370 buf.Write(data) 371 return binary.Read(buf, native, gnetStats) 372 } 373 374 func parseTcStats2(data []byte) (*ClassStatistics, error) { 375 rtAttrs, err := nl.ParseRouteAttr(data) 376 if err != nil { 377 return nil, err 378 } 379 stats := NewClassStatistics() 380 for _, datum := range rtAttrs { 381 switch datum.Attr.Type { 382 case nl.TCA_STATS_BASIC: 383 if err := parseGnetStats(datum.Value, stats.Basic); err != nil { 384 return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s", 385 err, hex.Dump(datum.Value)) 386 } 387 case nl.TCA_STATS_QUEUE: 388 if err := parseGnetStats(datum.Value, stats.Queue); err != nil { 389 return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s", 390 err, hex.Dump(datum.Value)) 391 } 392 case nl.TCA_STATS_RATE_EST: 393 if err := parseGnetStats(datum.Value, stats.RateEst); err != nil { 394 return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s", 395 err, hex.Dump(datum.Value)) 396 } 397 case nl.TCA_STATS_BASIC_HW: 398 if err := parseGnetStats(datum.Value, stats.BasicHw); err != nil { 399 return nil, fmt.Errorf("Failed to parse ClassStatistics.BasicHw with: %v\n%s", 400 err, hex.Dump(datum.Value)) 401 } 402 } 403 } 404 405 return stats, nil 406 }