Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
ema committed May 14, 2017
0 parents commit 71b619d
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
language: go
go:
- 1.x
os:
- linux
sudo: required
before_install:
- go get github.com/golang/lint/golint
- go get honnef.co/go/tools/cmd/staticcheck
- go get -d ./...
script:
- go build -tags=gofuzz ./...
- go vet ./...
- staticcheck ./...
#- golint -set_exit_status ./...
- go test -v -race -tags=integration ./...
10 changes: 10 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MIT License
===========

Copyright (C) 2017 Emanuele Rocca

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build:
go build
go vet
staticcheck
#golint -set_exit_status
go test -v -race -tags=integration
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
qdisc [![Build Status](https://travis-ci.org/ema/qdisc.svg?branch=master)](https://travis-ci.org/ema/qdisc)
=====

Package `qdisc` allows to get queuing discipline information via netlink,
similarly to what `tc -s qdisc show` does.

Example usage
-------------

package main

import (
"fmt"

"github.com/ema/qdisc"
)

func main() {
info, err := qdisc.Get()

if err == nil {
for _, msg := range info {
fmt.Printf("%+v\n", msg)
}
}
}
218 changes: 218 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package qdisc

import (
"fmt"
"math"
"net"

"github.com/mdlayher/netlink"
"github.com/mdlayher/netlink/nlenc"
)

const (
TCA_UNSPEC = iota
TCA_KIND
TCA_OPTIONS
TCA_STATS
TCA_XSTATS
TCA_RATE
TCA_FCNT
TCA_STATS2
TCA_STAB
__TCA_MAX
)

const (
TCA_STATS_UNSPEC = iota
TCA_STATS_BASIC
TCA_STATS_RATE_EST
TCA_STATS_QUEUE
TCA_STATS_APP
TCA_STATS_RATE_EST64
__TCA_STATS_MAX
)

// See struct tc_stats in /usr/include/linux/pkt_sched.h
type TC_Stats struct {
Bytes uint64
Packets uint32
Drops uint32
Overlimits uint32
Bps uint32
Pps uint32
Qlen uint32
Backlog uint32
}

// See /usr/include/linux/gen_stats.h
type TC_Stats2 struct {
// struct gnet_stats_basic
Bytes uint64
Packets uint32
// struct gnet_stats_queue
Qlen uint32
Backlog uint32
Drops uint32
Requeues uint32
Overlimits uint32
}

type QdiscInfo struct {
IfaceName string
Parent uint32
Handle uint32
Kind string
Bytes uint64
Packets uint32
Drops uint32
Requeues uint32
Overlimits uint32
}

func parseTCAStats(attr netlink.Attribute) TC_Stats {
var stats TC_Stats
stats.Bytes = nlenc.Uint64(attr.Data[0:8])
stats.Packets = nlenc.Uint32(attr.Data[8:12])
stats.Drops = nlenc.Uint32(attr.Data[12:16])
stats.Overlimits = nlenc.Uint32(attr.Data[16:20])
stats.Bps = nlenc.Uint32(attr.Data[20:24])
stats.Pps = nlenc.Uint32(attr.Data[24:28])
stats.Qlen = nlenc.Uint32(attr.Data[28:32])
stats.Backlog = nlenc.Uint32(attr.Data[32:36])
return stats
}

func parseTCAStats2(attr netlink.Attribute) TC_Stats2 {
var stats TC_Stats2

nested, _ := netlink.UnmarshalAttributes(attr.Data)

for _, a := range nested {
switch a.Type {
case TCA_STATS_BASIC:
stats.Bytes = nlenc.Uint64(a.Data[0:8])
stats.Packets = nlenc.Uint32(a.Data[8:12])
case TCA_STATS_QUEUE:
stats.Qlen = nlenc.Uint32(a.Data[0:4])
stats.Backlog = nlenc.Uint32(a.Data[4:8])
stats.Drops = nlenc.Uint32(a.Data[8:12])
stats.Requeues = nlenc.Uint32(a.Data[12:16])
stats.Overlimits = nlenc.Uint32(a.Data[16:20])
default:
// TODO: TCA_STATS_APP
}
}

return stats
}

func getQdiscMsgs() ([]netlink.Message, error) {
const familyRoute = 0

c, err := netlink.Dial(familyRoute, nil)
if err != nil {
return nil, fmt.Errorf("failed to dial netlink: %v", err)
}
defer c.Close()

req := netlink.Message{
Header: netlink.Header{
Flags: netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump,
Type: 38, // RTM_GETQDISC
},
Data: []byte{0},
}

// Perform a request, receive replies, and validate the replies
msgs, err := c.Execute(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %v", err)
}

return msgs, nil
}

// See https://tools.ietf.org/html/rfc3549#section-3.1.3
func parseMessage(msg netlink.Message) (QdiscInfo, error) {
var m QdiscInfo
var s TC_Stats
var s2 TC_Stats2

/*
struct tcmsg {
unsigned char tcm_family;
unsigned char tcm__pad1;
unsigned short tcm__pad2;
int tcm_ifindex;
__u32 tcm_handle;
__u32 tcm_parent;
__u32 tcm_info;
};
*/

if len(msg.Data) < 20 {
return m, fmt.Errorf("Short message, len=%d", len(msg.Data))
}

ifaceIdx := nlenc.Uint32(msg.Data[4:8])

m.Handle = nlenc.Uint32(msg.Data[8:12])
m.Parent = nlenc.Uint32(msg.Data[12:16])

if m.Parent == math.MaxUint32 {
m.Parent = 0
}

// The first 20 bytes are taken by tcmsg
attrs, err := netlink.UnmarshalAttributes(msg.Data[20:])

if err != nil {
return m, fmt.Errorf("failed to unmarshal attributes: %v", err)
}

for _, attr := range attrs {
switch attr.Type {
case TCA_KIND:
m.Kind = nlenc.String(attr.Data)
case TCA_STATS2:
s2 = parseTCAStats2(attr)
m.Bytes = s2.Bytes
m.Packets = s2.Packets
m.Drops = s2.Drops
// requeues only available in TCA_STATS2, not in TCA_STATS
m.Requeues = s2.Requeues
m.Overlimits = s2.Overlimits
case TCA_STATS:
// Legacy
s = parseTCAStats(attr)
m.Bytes = s.Bytes
m.Packets = s.Packets
m.Drops = s.Drops
m.Overlimits = s.Overlimits
default:
// TODO: TCA_OPTIONS and TCA_XSTATS
}
}

iface, err := net.InterfaceByIndex(int(ifaceIdx))
m.IfaceName = iface.Name

return m, err
}

func Get() ([]QdiscInfo, error) {
var res []QdiscInfo
msgs, _ := getQdiscMsgs()

for _, msg := range msgs {
m, err := parseMessage(msg)

if err != nil {
return nil, err
}

res = append(res, m)
}

return res, nil
}

0 comments on commit 71b619d

Please sign in to comment.