1package netlink 2 3import ( 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 { ... } 20type 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 32func 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 = uint32(Xmittime(rate, buffer)) 47 48 if cbuffer == 0 { 49 cbuffer = uint32(float64(ceil)/Hz() + float64(mtu)) 50 } 51 cbuffer = uint32(Xmittime(ceil, cbuffer)) 52 53 return &HtbClass{ 54 ClassAttrs: attrs, 55 Rate: rate, 56 Ceil: ceil, 57 Buffer: buffer, 58 Cbuffer: cbuffer, 59 Quantum: 10, 60 Level: 0, 61 Prio: 0, 62 } 63} 64 65// ClassDel will delete a class from the system. 66// Equivalent to: `tc class del $class` 67func 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` 73func (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. 80func 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. 87func (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. 96func 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. 105func (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` 111func 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` 117func (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 125func (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 145func 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 case "hfsc": 180 hfsc := class.(*HfscClass) 181 opt := nl.HfscCopt{} 182 opt.Rsc.Set(hfsc.Rsc.Attrs()) 183 opt.Fsc.Set(hfsc.Fsc.Attrs()) 184 opt.Usc.Set(hfsc.Usc.Attrs()) 185 options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc)) 186 options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc)) 187 options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc)) 188 } 189 req.AddData(options) 190 return nil 191} 192 193// ClassList gets a list of classes in the system. 194// Equivalent to: `tc class show`. 195// Generally returns nothing if link and parent are not specified. 196func ClassList(link Link, parent uint32) ([]Class, error) { 197 return pkgHandle.ClassList(link, parent) 198} 199 200// ClassList gets a list of classes in the system. 201// Equivalent to: `tc class show`. 202// Generally returns nothing if link and parent are not specified. 203func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { 204 req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) 205 msg := &nl.TcMsg{ 206 Family: nl.FAMILY_ALL, 207 Parent: parent, 208 } 209 if link != nil { 210 base := link.Attrs() 211 h.ensureIndex(base) 212 msg.Ifindex = int32(base.Index) 213 } 214 req.AddData(msg) 215 216 msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) 217 if err != nil { 218 return nil, err 219 } 220 221 var res []Class 222 for _, m := range msgs { 223 msg := nl.DeserializeTcMsg(m) 224 225 attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 226 if err != nil { 227 return nil, err 228 } 229 230 base := ClassAttrs{ 231 LinkIndex: int(msg.Ifindex), 232 Handle: msg.Handle, 233 Parent: msg.Parent, 234 Statistics: nil, 235 } 236 237 var class Class 238 classType := "" 239 for _, attr := range attrs { 240 switch attr.Attr.Type { 241 case nl.TCA_KIND: 242 classType = string(attr.Value[:len(attr.Value)-1]) 243 switch classType { 244 case "htb": 245 class = &HtbClass{} 246 case "hfsc": 247 class = &HfscClass{} 248 default: 249 class = &GenericClass{ClassType: classType} 250 } 251 case nl.TCA_OPTIONS: 252 switch classType { 253 case "htb": 254 data, err := nl.ParseRouteAttr(attr.Value) 255 if err != nil { 256 return nil, err 257 } 258 _, err = parseHtbClassData(class, data) 259 if err != nil { 260 return nil, err 261 } 262 case "hfsc": 263 data, err := nl.ParseRouteAttr(attr.Value) 264 if err != nil { 265 return nil, err 266 } 267 _, err = parseHfscClassData(class, data) 268 if err != nil { 269 return nil, err 270 } 271 } 272 // For backward compatibility. 273 case nl.TCA_STATS: 274 base.Statistics, err = parseTcStats(attr.Value) 275 if err != nil { 276 return nil, err 277 } 278 case nl.TCA_STATS2: 279 base.Statistics, err = parseTcStats2(attr.Value) 280 if err != nil { 281 return nil, err 282 } 283 } 284 } 285 *class.Attrs() = base 286 res = append(res, class) 287 } 288 289 return res, nil 290} 291 292func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 293 htb := class.(*HtbClass) 294 detailed := false 295 for _, datum := range data { 296 switch datum.Attr.Type { 297 case nl.TCA_HTB_PARMS: 298 opt := nl.DeserializeTcHtbCopt(datum.Value) 299 htb.Rate = uint64(opt.Rate.Rate) 300 htb.Ceil = uint64(opt.Ceil.Rate) 301 htb.Buffer = opt.Buffer 302 htb.Cbuffer = opt.Cbuffer 303 htb.Quantum = opt.Quantum 304 htb.Level = opt.Level 305 htb.Prio = opt.Prio 306 } 307 } 308 return detailed, nil 309} 310 311func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 312 hfsc := class.(*HfscClass) 313 detailed := false 314 for _, datum := range data { 315 m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs() 316 switch datum.Attr.Type { 317 case nl.TCA_HFSC_RSC: 318 hfsc.Rsc = ServiceCurve{m1: m1, d: d, m2: m2} 319 case nl.TCA_HFSC_FSC: 320 hfsc.Fsc = ServiceCurve{m1: m1, d: d, m2: m2} 321 case nl.TCA_HFSC_USC: 322 hfsc.Usc = ServiceCurve{m1: m1, d: d, m2: m2} 323 } 324 } 325 return detailed, nil 326} 327 328func parseTcStats(data []byte) (*ClassStatistics, error) { 329 buf := &bytes.Buffer{} 330 buf.Write(data) 331 native := nl.NativeEndian() 332 tcStats := &tcStats{} 333 if err := binary.Read(buf, native, tcStats); err != nil { 334 return nil, err 335 } 336 337 stats := NewClassStatistics() 338 stats.Basic.Bytes = tcStats.Bytes 339 stats.Basic.Packets = tcStats.Packets 340 stats.Queue.Qlen = tcStats.Qlen 341 stats.Queue.Backlog = tcStats.Backlog 342 stats.Queue.Drops = tcStats.Drops 343 stats.Queue.Overlimits = tcStats.Overlimits 344 stats.RateEst.Bps = tcStats.Bps 345 stats.RateEst.Pps = tcStats.Pps 346 347 return stats, nil 348} 349 350func parseGnetStats(data []byte, gnetStats interface{}) error { 351 buf := &bytes.Buffer{} 352 buf.Write(data) 353 native := nl.NativeEndian() 354 return binary.Read(buf, native, gnetStats) 355} 356 357func parseTcStats2(data []byte) (*ClassStatistics, error) { 358 rtAttrs, err := nl.ParseRouteAttr(data) 359 if err != nil { 360 return nil, err 361 } 362 stats := NewClassStatistics() 363 for _, datum := range rtAttrs { 364 switch datum.Attr.Type { 365 case nl.TCA_STATS_BASIC: 366 if err := parseGnetStats(datum.Value, stats.Basic); err != nil { 367 return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s", 368 err, hex.Dump(datum.Value)) 369 } 370 case nl.TCA_STATS_QUEUE: 371 if err := parseGnetStats(datum.Value, stats.Queue); err != nil { 372 return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s", 373 err, hex.Dump(datum.Value)) 374 } 375 case nl.TCA_STATS_RATE_EST: 376 if err := parseGnetStats(datum.Value, stats.RateEst); err != nil { 377 return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s", 378 err, hex.Dump(datum.Value)) 379 } 380 } 381 } 382 383 return stats, nil 384} 385