github.com/sagernet/netlink@v0.0.0-20240612041022-b9a21c07ac6a/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/sagernet/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 // Generally returns nothing if link and parent are not specified. 205 func ClassList(link Link, parent uint32) ([]Class, error) { 206 return pkgHandle.ClassList(link, parent) 207 } 208 209 // ClassList gets a list of classes in the system. 210 // Equivalent to: `tc class show`. 211 // Generally returns nothing if link and parent are not specified. 212 func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { 213 req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) 214 msg := &nl.TcMsg{ 215 Family: nl.FAMILY_ALL, 216 Parent: parent, 217 } 218 if link != nil { 219 base := link.Attrs() 220 h.ensureIndex(base) 221 msg.Ifindex = int32(base.Index) 222 } 223 req.AddData(msg) 224 225 msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) 226 if err != nil { 227 return nil, err 228 } 229 230 var res []Class 231 for _, m := range msgs { 232 msg := nl.DeserializeTcMsg(m) 233 234 attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 235 if err != nil { 236 return nil, err 237 } 238 239 base := ClassAttrs{ 240 LinkIndex: int(msg.Ifindex), 241 Handle: msg.Handle, 242 Parent: msg.Parent, 243 Statistics: nil, 244 } 245 246 var class Class 247 classType := "" 248 for _, attr := range attrs { 249 switch attr.Attr.Type { 250 case nl.TCA_KIND: 251 classType = string(attr.Value[:len(attr.Value)-1]) 252 switch classType { 253 case "htb": 254 class = &HtbClass{} 255 case "hfsc": 256 class = &HfscClass{} 257 default: 258 class = &GenericClass{ClassType: classType} 259 } 260 case nl.TCA_OPTIONS: 261 switch classType { 262 case "htb": 263 data, err := nl.ParseRouteAttr(attr.Value) 264 if err != nil { 265 return nil, err 266 } 267 _, err = parseHtbClassData(class, data) 268 if err != nil { 269 return nil, err 270 } 271 case "hfsc": 272 data, err := nl.ParseRouteAttr(attr.Value) 273 if err != nil { 274 return nil, err 275 } 276 _, err = parseHfscClassData(class, data) 277 if err != nil { 278 return nil, err 279 } 280 } 281 // For backward compatibility. 282 case nl.TCA_STATS: 283 base.Statistics, err = parseTcStats(attr.Value) 284 if err != nil { 285 return nil, err 286 } 287 case nl.TCA_STATS2: 288 base.Statistics, err = parseTcStats2(attr.Value) 289 if err != nil { 290 return nil, err 291 } 292 } 293 } 294 *class.Attrs() = base 295 res = append(res, class) 296 } 297 298 return res, nil 299 } 300 301 func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 302 htb := class.(*HtbClass) 303 detailed := false 304 for _, datum := range data { 305 switch datum.Attr.Type { 306 case nl.TCA_HTB_PARMS: 307 opt := nl.DeserializeTcHtbCopt(datum.Value) 308 htb.Rate = uint64(opt.Rate.Rate) 309 htb.Ceil = uint64(opt.Ceil.Rate) 310 htb.Buffer = opt.Buffer 311 htb.Cbuffer = opt.Cbuffer 312 htb.Quantum = opt.Quantum 313 htb.Level = opt.Level 314 htb.Prio = opt.Prio 315 case nl.TCA_HTB_RATE64: 316 htb.Rate = native.Uint64(datum.Value[0:8]) 317 case nl.TCA_HTB_CEIL64: 318 htb.Ceil = native.Uint64(datum.Value[0:8]) 319 } 320 } 321 return detailed, nil 322 } 323 324 func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { 325 hfsc := class.(*HfscClass) 326 detailed := false 327 for _, datum := range data { 328 m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs() 329 switch datum.Attr.Type { 330 case nl.TCA_HFSC_RSC: 331 hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 332 case nl.TCA_HFSC_FSC: 333 hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 334 case nl.TCA_HFSC_USC: 335 hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} 336 } 337 } 338 return detailed, nil 339 } 340 341 func parseTcStats(data []byte) (*ClassStatistics, error) { 342 buf := &bytes.Buffer{} 343 buf.Write(data) 344 tcStats := &tcStats{} 345 if err := binary.Read(buf, native, tcStats); err != nil { 346 return nil, err 347 } 348 349 stats := NewClassStatistics() 350 stats.Basic.Bytes = tcStats.Bytes 351 stats.Basic.Packets = tcStats.Packets 352 stats.Queue.Qlen = tcStats.Qlen 353 stats.Queue.Backlog = tcStats.Backlog 354 stats.Queue.Drops = tcStats.Drops 355 stats.Queue.Overlimits = tcStats.Overlimits 356 stats.RateEst.Bps = tcStats.Bps 357 stats.RateEst.Pps = tcStats.Pps 358 359 return stats, nil 360 } 361 362 func parseGnetStats(data []byte, gnetStats interface{}) error { 363 buf := &bytes.Buffer{} 364 buf.Write(data) 365 return binary.Read(buf, native, gnetStats) 366 } 367 368 func parseTcStats2(data []byte) (*ClassStatistics, error) { 369 rtAttrs, err := nl.ParseRouteAttr(data) 370 if err != nil { 371 return nil, err 372 } 373 stats := NewClassStatistics() 374 for _, datum := range rtAttrs { 375 switch datum.Attr.Type { 376 case nl.TCA_STATS_BASIC: 377 if err := parseGnetStats(datum.Value, stats.Basic); err != nil { 378 return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s", 379 err, hex.Dump(datum.Value)) 380 } 381 case nl.TCA_STATS_QUEUE: 382 if err := parseGnetStats(datum.Value, stats.Queue); err != nil { 383 return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s", 384 err, hex.Dump(datum.Value)) 385 } 386 case nl.TCA_STATS_RATE_EST: 387 if err := parseGnetStats(datum.Value, stats.RateEst); err != nil { 388 return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s", 389 err, hex.Dump(datum.Value)) 390 } 391 } 392 } 393 394 return stats, nil 395 }