Steps to reproduce the behavior.
main.go
package main
import (
"fmt"
"log"
"os"
"github.com/davecgh/go-spew/spew"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
func main() {
ifname := os.Args[1]
ifi, err := netlink.LinkByName(ifname)
if err != nil {
log.Fatalf("Failed to get link info for dev(%s): %v", ifname, err)
}
// open a rtnetlink socket
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
// qdiscs, err := rtnl.Qdisc().Get()
// if err != nil {
// fmt.Fprintf(os.Stderr, "could not get qdiscs: %v\n", err)
// return
// }
// spew.Dump(qdiscs)
filters, err := rtnl.Filter().Get(&tc.Msg{
Family: unix.AF_UNIX,
Ifindex: uint32(ifi.Attrs().Index),
Parent: core.BuildHandle(tc.HandleRoot, tc.HandleIngress),
})
if err != nil {
log.Printf("failed to get filters of %s: %v", ifname, err)
}
spew.Dump(filters)
}
Environment
Provide some information about the system with the issue.
$ uname -r
5.15.0-18-xxx-generic
Describe the issue
I am learning TC recently. I want to do something like $TC class add dev {ifName} parent {parent} classid {classid} htb rate {limit}
, but received netlink receive: invalid argument
as a result.
Minimal code example to reproduce the issue
Because I don't want to change the network config in host, I call AddHTBClass
in Linux network namespace(ip nets add ns1
, BTW, I'm using Ubuntu20.04). I noticed that tc.Config
has NetNS
attribute, but I'm not sure how to deal with it, so I use another way to make the func works in NS correctly. To make the ns environment, use the bash below:
ip netns add ns1
ovs-vsctl add-br br0
ifconfig br0 up
ip link add v-ns1 type veth peer name b-ns1
ovs-vsctl add-port br0 b-ns1
ifconfig b-ns1 up
ip link set v-ns1 netns ns1
ip netns exec ns1 ifconfig v-ns1 up
Here's my main func. in this function, v-ns1
is one side of a veth pair where the other side b-ns1
is set on an ovs in the host .
package main
import (
"net"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"github.com/sirupsen/logrus"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
const (
path_ns = "/var/run/netns"
)
// AddHTBToInterface acts like
// $TC qdisc add dev {ifName} root handle 1:0 htb
func AddHTBToInterface(ifName string) error {
ifByName, err := net.InterfaceByName(ifName)
if err != nil {
return err
}
qdiscHTB := createHTBObject(uint32(ifByName.Index),
core.BuildHandle(0x1, 0x0),
tc.HandleRoot,
nil, &tc.HtbGlob{Version: 0x3, Rate2Quantum: 0xa,})
tcnl, err := tc.Open(&tc.Config{})
if err != nil {
logrus.Errorf("err creating tcnl, err = %s", err)
return err
}
defer tcnl.Close()
if err := tcnl.SetOption(netlink.ExtendedAcknowledge, true); err != nil {
logrus.Warnf("EXT_ACK set failed, err = %v", err)
}
if err := tcnl.Qdisc().Add(qdiscHTB); err != nil {
return err
}
return nil
}
// AddHTBClass acts as
// $TC class add dev {ifName} parent {parent} classid {classid} htb rate {limit}
func AddHTBClass(ifName string, parent uint32, classid uint32, limit uint64) error {
ifByName, err := net.InterfaceByName(ifName)
if err != nil {
logrus.Errorf("cannot find %s interface", ifName)
return err
}
rate := limit
htbObject :=
createHTBObject(uint32(ifByName.Index),
classid,
parent,
&rate, &tc.HtbGlob{Version: 0x3, Rate2Quantum: 0xa,})
tcnl, err := tc.Open(&tc.Config{})
if err != nil {
logrus.Errorf("err creating tcnl, err = %s", err)
return err
}
defer tcnl.Close()
if err := tcnl.SetOption(netlink.ExtendedAcknowledge, true); err != nil {
logrus.Warnf("EXT_ACK set failed, err = %v", err)
}
if err := tcnl.Class().Add(htbObject); err != nil {
return err
}
return nil
}
func createHTBObject(ifIndex uint32, handle uint32, parent uint32, rate *uint64, htbInit *tc.HtbGlob) *tc.Object {
return &tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(ifIndex),
Handle: handle,
Parent: parent,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "htb",
Htb: &tc.Htb{
Init: htbInit,
Rate64: rate,
},
},
}
}
func createHTB() {
ns1, err := ns.GetNS(path_ns + "/ns1")
if err != nil {
logrus.Fatalf("can not open ns1, err=%v", err)
}
err = ns1.Do(func(hostNS ns.NetNS) error {
return AddHTBToInterface("v-ns1")
})
if err != nil {
logrus.Errorf("ns1 tc get err, err = %s", err)
}
}
func createClass() {
ns1, err := ns.GetNS(path_ns + "/ns1")
if err != nil {
logrus.Fatalf("can not open ns1, err=%v", err)
}
err = ns1.Do(func(hostNS ns.NetNS) error {
return AddHTBClass("v-ns1", core.BuildHandle(1,0), core.BuildHandle(1, 1), 100000)
})
if err != nil {
logrus.Errorf("ns1 tc get err, err = %s", err)
}
}
func main() {
//createHTB() // run successfully
createClass()
}
after running createHTB(), I use ip netns exec ns1 tc -s qdisc ls dev v-ns1
to check v-ns1
, the result seems good:
qdisc htb 1: root refcnt 9 r2q 10 default 0 direct_packets_stat 5 direct_qlen 1000
Sent 350 bytes 5 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
but when running createClass(), I get the err err = netlink receive: invalid argument
.
Hello! I'm wondering how to add a priority to a given rule. For example:
tc filter add eth0 ingress flower \
prio 1000 \
action tunnel_key unset \
I don't see where this field would be added. Any help would be appreciated!
Originally posted by kamilpoleszczuk August 18, 2021
@florianl Hi! Creating flower filter with KeyIPv4Dst
set to value crated with eg. net.ParseIP("10.244.1.118")
gives the result:
# tc filter show dev ExampleFlower2 ingress 1 ↵
filter protocol ip pref 99 flower chain 0
filter protocol ip pref 99 flower chain 0 handle 0x1 hw_tc 6
eth_type ipv4
ip_proto udp
dst_ip 118.1.244.10
src_port 4321
so it looks like, tc-go library does not care about required endianness. Does it mean, the inversion should be done on user application side or maybe this could be added to the library itself?
Thanks!
Describe the issue
Can't create u32 filter with nat action.
It seems like all validations before and on tc.con.Send()
has passed, but on tc.con.Receive()
I've got this error:
netlink receive: invalid argument, offset: 0, message: "Filter kind and protocol must be specified"
Can you provide a valid example for creating u32 filter with nat action, please?
Minimal code example to reproduce the issue
package main
import (
"fmt"
"net"
"os"
"syscall"
"github.com/mdlayher/netlink"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
)
const (
linkName = "dummy10"
)
func main() {
// open a rtnetlink socket
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
err = rtnl.SetOption(netlink.ExtendedAcknowledge, true)
if err != nil {
fmt.Fprintf(os.Stderr, "could not set option ExtendedAcknowledge: %v\n", err)
return
}
l, err := net.InterfaceByName(linkName)
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface: %v\n", err)
return
}
actions := &[]*tc.Action{}
*actions = append(*actions, &tc.Action{
Kind: "nat",
Nat: &tc.Nat{
Parms: &tc.NatParms{
OldAddr: 16843135,
NewAddr: 16777343,
Mask: 65535,
},
},
})
filter := &tc.Object{
Msg: tc.Msg{
Family: syscall.AF_UNSPEC,
Ifindex: uint32(l.Index),
Handle: core.BuildHandle(0x1, 0x0000),
Parent: tc.HandleRoot,
},
Attribute: tc.Attribute{
Kind: "u32",
U32: &tc.U32{
Sel: &tc.U32Sel{
NKeys: 1,
Keys: []tc.U32Key{
tc.U32Key{
Mask: 16777215,
Val: 108736,
},
},
},
Actions: actions,
},
},
}
if err := rtnl.Filter().Add(filter); err != nil {
fmt.Println(err)
}
}
Hi Florian,
I am currently working on a project which requires to load ebpf program to the tc ingress hook. I followed the example you provided a while ago: https://gist.github.com/florianl/8f421e57f419fa9a50eb5b085363de66. There are some changes on the go-tc package but I tried to update the sample code to adapt the newest version.
The error I got is: could not assign eBPF: netlink receive: no such file or directory.
Brief introduction on what I did:
bpf program trying to load:
/*
#cgo CFLAGS: -I/usr/include/bcc/compat
#cgo LDFLAGS: -lbcc
#include <bcc/bcc_common.h>
#include <bcc/libbpf.h>
void perf_reader_free(void *ptr);
*/
import "C"
const source string = `
#define KBUILD_MODNAME "tc_eBPF"
#include <uapi/linux/bpf.h>
int tc_eBPF(struct __sk_buff *skb) {
bpf_trace_printk("hello world\n");
return 0;
}
`
then using the gobpf library to load bpf program
module := bpf.NewModule(source, []string{"-w"})
defer module.Close()
fn, err := module.Load("tc_eBPF", C.BPF_PROG_TYPE_SCHED_CLS, 1, 65536)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load ebpf prog: %v\n", err)
return
}
after that, open the rtnl and find the deviceID
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
devID, err := net.InterfaceByName("wlo1")
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface ID: %v\n", err)
return
}
once that part is covered, I added clsact qdisc for adding ebpf filter
qdisc := tc.Object{
tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(tc.HandleIngress, 0x0000),
Parent: tc.HandleIngress,
Info: 0,
},
tc.Attribute{
Kind: "clsact",
},
}
//add clsact qdisc to tc for attaching ebpf
if err := rtnl.Qdisc().Add(&qdisc); err != nil {
fmt.Fprintf(os.Stderr, "could not assign clsact to lo: %v\n", err)
return
}
defer deleteAddedQdisc(rtnl, qdisc)
the delete method provided in sample code seems not working, so I modified it a little bit for testing purpose:
func deleteAddedQdisc(rtnl *tc.Tc, addedQdisc tc.Object) {
qdiscs, err := rtnl.Qdisc().Get()
if err != nil {
fmt.Println(err.Error())
return
}
for _, qdisc := range qdiscs {
if qdisc.Kind == addedQdisc.Kind && qdisc.Ifindex == addedQdisc.Ifindex {
err := rtnl.Qdisc().Delete(&qdisc)
if err != nil {
fmt.Printf(err.Error())
return
}
}
}
}
at the end, I created a tc bpf filter
bpfFn := uint32(fn)
bpfFlag := uint32(0x1)
//
filter := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(tc.HandleIngress, 0x0000),
Parent: tc.HandleIngress,
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &bpfFn,
Flags: &bpfFlag,
},
},
}
if err := rtnl.Filter().Add(&filter); err != nil {
fmt.Fprintf(os.Stderr, "could not assign eBPF: %v\n", err)
return
}
That's pretty much it, I am also gonna to include a full program at the end. After run the program, I got could not assign ebpf: netlink receive: no such file or directory error. My assumption is that when adding the bpf filter, it is trying to find the ebpf file but cannot find it. I am wondering if I did something wrong? Thank you.
package main
import (
"fmt"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
bpf "github.com/iovisor/gobpf/bcc"
"golang.org/x/sys/unix"
"net"
"os"
)
/*
#cgo CFLAGS: -I/usr/include/bcc/compat
#cgo LDFLAGS: -lbcc
#include <bcc/bcc_common.h>
#include <bcc/libbpf.h>
void perf_reader_free(void *ptr);
*/
import "C"
const source string = `
#define KBUILD_MODNAME "tc_eBPF"
#include <uapi/linux/bpf.h>
int tc_eBPF(struct __sk_buff *skb) {
bpf_trace_printk("hello world\n");
return 0;
}
`
func main() {
module := bpf.NewModule(source, []string{"-w"})
defer module.Close()
fn, err := module.Load("tc_eBPF", C.BPF_PROG_TYPE_SCHED_CLS, 1, 65536)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load ebpf prog: %v\n", err)
return
}
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
devID, err := net.InterfaceByName("wlo1")
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface ID: %v\n", err)
return
}
qdisc := tc.Object{
tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(tc.HandleIngress, 0x0000),
Parent: tc.HandleIngress,
Info: 0,
},
tc.Attribute{
Kind: "clsact",
},
}
//add clsact qdisc to tc for attaching ebpf
if err := rtnl.Qdisc().Add(&qdisc); err != nil {
fmt.Fprintf(os.Stderr, "could not assign clsact to lo: %v\n", err)
return
}
defer deleteAddedQdisc(rtnl, qdisc)
//programName := "tc_eBPF"
bpfFn := uint32(fn)
bpfFlag := uint32(0x1)
//
filter := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(tc.HandleIngress, 0x0000),
Parent: tc.HandleIngress,
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &bpfFn,
Flags: &bpfFlag,
},
},
}
if err := rtnl.Filter().Add(&filter); err != nil {
fmt.Fprintf(os.Stderr, "could not assign eBPF: %v\n", err)
return
}
//time.Sleep(30*time.Hour)
// cat sys/kernel/debug/tracing/trace_pipe
}
func deleteAddedQdisc(rtnl *tc.Tc, addedQdisc tc.Object) {
qdiscs, err := rtnl.Qdisc().Get()
if err != nil {
fmt.Println(err.Error())
return
}
for _, qdisc := range qdiscs {
if qdisc.Kind == addedQdisc.Kind && qdisc.Ifindex == addedQdisc.Ifindex {
err := rtnl.Qdisc().Delete(&qdisc)
if err != nil {
fmt.Printf(err.Error())
return
}
}
}
}
Hello! I'm trying to install a filter and subsequently retrieve properties about that filter, such as the MAC that is being matched on, the VLAN tag to be pushed on from an action, etc. I install the flow rule like this:
tunnelKeyAction := tc.TunnelKey{
Parms: &tc.TunnelParms{
Capab: 0x0,
Action: tc.ActPipe,
RefCnt: 0x1,
BindCnt: 0x1, TunnelKeyAction: ActUnset},
}
vlanEndpointAction := tc.VLan{Parms: &tc.VLanParms{
Capab: 0x0,
Action: tc.ActPipe,
RefCnt: 0x1,
BindCnt: 0x1,
VLanAction: ActPush},
PushID: ¶ms.vlanEndpoint}
vlanLinkAction := tc.VLan{Parms: &tc.VLanParms{
Capab: 0x0,
Action: tc.ActPipe,
RefCnt: 0x1,
BindCnt: 0x1,
VLanAction: ActPush},
PushID: ¶ms.vlanLink}
var egressMirror uint32 = 0x2
mirredAction := tc.Mirred{Parms: &tc.MirredParam{
Capab: 0x0,
Action: tc.ActStolen,
RefCnt: 0x1,
BindCnt: 0x1,
Eaction: egressMirror,
IfIndex: params.nfappPrDevIndex}}
actions := []*tc.Action{
{Kind: "tunnel_key", TunnelKey: &tunnelKeyAction},
{Kind: "vlan", VLan: &vlanEndpointAction},
{Kind: "vlan", VLan: &vlanLinkAction},
{Kind: "mirred", Mirred: &mirredAction},
}
/* Create attributes and add rules to qdics */
flower := tc.Flower{Actions: &actions, KeyEncUDPDstPort: ¶ms.dstPort, KeyEncKeyID: ¶ms.vni}
attrFlower := tc.Attribute{Kind: "flower", Flower: &flower}
objFlower := tc.Object{Msg: params.vxlanDev, Attribute: attrFlower}
if err := params.rtnl.Filter().Add(&objFlower); err != nil {
fmt.Fprintf(os.Stderr, "Could not add ingress endpoint filter: %v\n", err)
} else {
fmt.Fprintf(os.Stderr, "Added ingress endpoint filter\n")
}
tcObjects, _ := params.rtnl.Filter().Get(¶ms.vxlanDev)
for _, filter := range tcObjects {
fmt.Printf("%#v\n", filter)
}
The result of printing the filter gives me the following:
Added ingress endpoint filter
tc.Object{Msg:tc.Msg{Family:0x0, Ifindex:0x3e, Handle:0x0, Parent:0xfffffff1, Info:0x10300}, Attribute:tc.Attribute{Kind:"flower", EgressBlock:(*uint32)(nil), IngressBlock:(*uint32)(nil), HwOffload:(*uint8)(nil), Chain:(*uint32)(0xc00001a9a4), Stats:(*tc.Stats)(nil), XStats:(*tc.XStats)(nil), Stats2:(*tc.Stats2)(nil), Stab:(*tc.Stab)(nil), Basic:(*tc.Basic)(nil), BPF:(*tc.Bpf)(nil), U32:(*tc.U32)(nil), Rsvp:(*tc.Rsvp)(nil), Route4:(*tc.Route4)(nil), Fw:(*tc.Fw)(nil), Flow:(*tc.Flow)(nil), Flower:(*tc.Flower)(nil), Matchall:(*tc.Matchall)(nil), Cake:(*tc.Cake)(nil), FqCodel:(*tc.FqCodel)(nil), Codel:(*tc.Codel)(nil), Fq:(*tc.Fq)(nil), Pie:(*tc.Pie)(nil), Hhf:(*tc.Hhf)(nil), Tbf:(*tc.Tbf)(nil), Sfb:(*tc.Sfb)(nil), Red:(*tc.Red)(nil), MqPrio:(*tc.MqPrio)(nil), Pfifo:(*tc.FifoOpt)(nil), Bfifo:(*tc.FifoOpt)(nil), Choke:(*tc.Choke)(nil), Netem:(*tc.Netem)(nil), Htb:(*tc.Htb)(nil), Hfsc:(*tc.Hfsc)(nil), HfscQOpt:(*tc.HfscQOpt)(nil), Dsmark:(*tc.Dsmark)(nil), Drr:(*tc.Drr)(nil), Cbq:(*tc.Cbq)(nil), Atm:(*tc.Atm)(nil), Qfq:(*tc.Qfq)(nil)}}
Here, I would expect the flower attribute to be non-null. Is there something I am doing incorrectly? Thank you!
CAKE landed in linux 4.19. It's probably the easiest AQM to use with a WAN connection. Do you have any plans to add it? 😃
For any of the interface have SFQ qdisc, go-tc failed to fetch it with the error message below:
panic: extractTCAOptions(): unsupported kind: sfq
How can I correctly list SFQ qdisc? Thank you.
Hi, I’m using go-tc to rate limit ingress traffic for an interface using an IFB.
Part of this process is creating a filter on an ingress qdisc that mirrors ingress traffic to the IFB. This command seems to create 3 filters (3 are also seen using Filter.Get()
).
root@box:~# tc filter add dev wg0 parent ffff: protocol all prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifbwg0
root@box:~# tc -d filter show dev wg0 ingress
filter protocol all pref 10 u32 chain 0
filter protocol all pref 10 u32 chain 0 fh 800: ht divisor 1
filter protocol all pref 10 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 not_in_hw
match 00000000/00000000 at 0
action order 1: mirred (Egress Redirect to device ifbwg0) stolen
index 1 ref 1 bind 1
This is my code equivalent for the 2nd filter, as an example:
package main
import(
"fmt"
"net"
"os"
"golang.org/x/sys/unix"
"github.com/florianl/go-tc"
"encoding/json"
)
func main() {
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
devID, err := net.InterfaceByName("wg0")
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface ID: %v\n", err)
return
}
qdiscs, err := rtnl.Qdisc().Get()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get qdiscs: %v\n", err)
return
}
var ingressQdisc tc.Object
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface from id %d: %v", qdisc.Ifindex, err)
return
}
if iface.Name == "wg0" && qdisc.Attribute.Kind == "ingress" {
ingressQdisc = qdisc
}
}
// add filter to ingress qdisc of wg0
newFilter := tc.Object{
tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Parent: ingressQdisc.Msg.Handle,
},
tc.Attribute{
Kind: "u32",
Chain: 0,
U32: &tc.U32{
Divisor: 1,
},
},
}
s, _ := json.MarshalIndent(newFilter, "", "\t")
fmt.Printf("adding filter: %s\n", string(s))
if err := rtnl.Filter().Replace(&newFilter); err != nil {
fmt.Printf("couldn't add filter to wg0: %v\n", err)
return
}
}
Running the program shows:
couldn't add filter to wg0: netlink receive: invalid argument
I can’t seem to create any of the filters shown above (after deleting the existing ones) and the errors provide very little information so I don't know what to correct.
Do you have guidance on how I can debug why the filters I'm creating are invalid (like how to get better errors from netlink) or suggestions about what I can read that will help me understand how a filter should be created?
I have tried:
- creating the filters on the command line and then printing out the
Filter
structs using Filter.Get()
to check that I’m setting required fields. I’m not sure if all the values I chose are correct
- looking at the tests for filters to see if I could create a simple filter and then build incrementally from that
- looking at the referenced C code (for example pkt_sched.h) to see what values I might be missing, or what some flag means.
Thanks!
From documentation, pfifo_fast
seems to be the default qdisc of each interface. Looks like a relatively easy qdisc to implement by the look of the doc, please consider adding support for it.
Hi there,
Firstly, thank you for making this library available to the public. I wanted to know if it is expected that there be no more version tags on github for this project? A project I am working on was using v0.1.1 (5c65c84) but our builds started to fail recently with:
[ 261s] + /usr/bin/go get -d
[ 318s] go: github.com/florianl/[email protected]: invalid pseudo-version: preceding tag (v0.1.0) not found
For now should we just pull from master all the time?
tc-cgroup(8) is a control group based traffic control filter. Supporting it shouldn't be very hard judging by the amount of options this filter takes.
Hi Florian
Using tc
the following are valid commands
tc qdisc add dev eth0 root handle 1: htb
tc filter add dev eth0 parent 1:0 prio 1 protocol ip u32
This creates tc objects that look like:
root:/tmp# ./shape show eth0
****** STARTING WITH QDISCS ******
(tc.Object) {
Msg: (tc.Msg) {
Family: (uint32) 0,
Ifindex: (uint32) 6,
Handle: (uint32) 65536,
Parent: (uint32) 4294967295,
Info: (uint32) 2
},
Attribute: (tc.Attribute) {
Kind: (string) (len=3) "htb",
EgressBlock: (uint32) 0,
IngressBlock: (uint32) 0,
HwOffload: (uint8) 0,
Chain: (uint32) 0,
Stats: (*tc.Stats)(0xc000018750)({
Bytes: (uint64) 0,
Packets: (uint32) 0,
Drops: (uint32) 0,
Overlimits: (uint32) 0,
Bps: (uint32) 0,
Pps: (uint32) 0,
Qlen: (uint32) 0,
Backlog: (uint32) 0
}),
XStats: (*tc.XStats)(<nil>),
Stats2: (*tc.Stats2)(0xc000016a80)({
Bytes: (uint64) 65556,
Packets: (uint32) 0,
Qlen: (uint32) 0,
Backlog: (uint32) 0,
Drops: (uint32) 196632,
Requeues: (uint32) 0,
Overlimits: (uint32) 0
}),
Basic: (*tc.Basic)(<nil>),
BPF: (*tc.Bpf)(<nil>),
U32: (*tc.U32)(<nil>),
Rsvp: (*tc.Rsvp)(<nil>),
Route4: (*tc.Route4)(<nil>),
Fw: (*tc.Fw)(<nil>),
Flow: (*tc.Flow)(<nil>),
FqCodel: (*tc.FqCodel)(<nil>),
Codel: (*tc.Codel)(<nil>),
Fq: (*tc.Fq)(<nil>),
Pie: (*tc.Pie)(<nil>),
Hhf: (*tc.Hhf)(<nil>),
Tbf: (*tc.Tbf)(<nil>),
Sfb: (*tc.Sfb)(<nil>),
Red: (*tc.Red)(<nil>),
MqPrio: (*tc.MqPrio)(<nil>),
Pfifo: (*tc.FifoOpt)(<nil>),
Bfifo: (*tc.FifoOpt)(<nil>),
Choke: (*tc.Choke)(<nil>),
Htb: (*tc.Htb)(0xc000048660)({
Parms: (*tc.HtbOpt)(<nil>),
Init: (*tc.HtbGlob)(0xc000016ae0)({
Version: (uint32) 196625,
Rate2Quantum: (uint32) 10,
Defcls: (uint32) 0,
Debug: (uint32) 0,
DirectPkts: (uint32) 0
}),
Ctab: ([]uint8) <nil>,
Rtab: ([]uint8) <nil>,
DirectQlen: (uint32) 1000,
Rate64: (uint64) 0,
Ceil64: (uint64) 0
}),
Hfsc: (*tc.Hfsc)(<nil>),
Dsmark: (*tc.Dsmark)(<nil>),
Drr: (*tc.Drr)(<nil>),
Cbq: (*tc.Cbq)(<nil>),
Atm: (*tc.Atm)(<nil>),
Qfq: (*tc.Qfq)(<nil>),
Netem: (*tc.Netem)(<nil>)
}
}
****** MOVING ON TO CLASSES ******
****** MOVING ON TO FILTERS ******
(tc.Object) {
Msg: (tc.Msg) {
Family: (uint32) 0,
Ifindex: (uint32) 6,
Handle: (uint32) 0,
Parent: (uint32) 65536,
Info: (uint32) 65544
},
Attribute: (tc.Attribute) {
Kind: (string) (len=3) "u32",
EgressBlock: (uint32) 0,
IngressBlock: (uint32) 0,
HwOffload: (uint8) 0,
Chain: (uint32) 0,
Stats: (*tc.Stats)(<nil>),
XStats: (*tc.XStats)(<nil>),
Stats2: (*tc.Stats2)(<nil>),
Basic: (*tc.Basic)(<nil>),
BPF: (*tc.Bpf)(<nil>),
U32: (*tc.U32)(<nil>),
Rsvp: (*tc.Rsvp)(<nil>),
Route4: (*tc.Route4)(<nil>),
Fw: (*tc.Fw)(<nil>),
Flow: (*tc.Flow)(<nil>),
FqCodel: (*tc.FqCodel)(<nil>),
Codel: (*tc.Codel)(<nil>),
Fq: (*tc.Fq)(<nil>),
Pie: (*tc.Pie)(<nil>),
Hhf: (*tc.Hhf)(<nil>),
Tbf: (*tc.Tbf)(<nil>),
Sfb: (*tc.Sfb)(<nil>),
Red: (*tc.Red)(<nil>),
MqPrio: (*tc.MqPrio)(<nil>),
Pfifo: (*tc.FifoOpt)(<nil>),
Bfifo: (*tc.FifoOpt)(<nil>),
Choke: (*tc.Choke)(<nil>),
Htb: (*tc.Htb)(<nil>),
Hfsc: (*tc.Hfsc)(<nil>),
Dsmark: (*tc.Dsmark)(<nil>),
Drr: (*tc.Drr)(<nil>),
Cbq: (*tc.Cbq)(<nil>),
Atm: (*tc.Atm)(<nil>),
Qfq: (*tc.Qfq)(<nil>),
Netem: (*tc.Netem)(<nil>)
}
}
(tc.Object) {
Msg: (tc.Msg) {
Family: (uint32) 0,
Ifindex: (uint32) 6,
Handle: (uint32) 2147483648,
Parent: (uint32) 65536,
Info: (uint32) 65544
},
Attribute: (tc.Attribute) {
Kind: (string) (len=3) "u32",
EgressBlock: (uint32) 0,
IngressBlock: (uint32) 0,
HwOffload: (uint8) 0,
Chain: (uint32) 0,
Stats: (*tc.Stats)(<nil>),
XStats: (*tc.XStats)(<nil>),
Stats2: (*tc.Stats2)(<nil>),
Basic: (*tc.Basic)(<nil>),
BPF: (*tc.Bpf)(<nil>),
U32: (*tc.U32)(0xc00007b6d0)({
ClassID: (uint32) 0,
Hash: (uint32) 0,
Link: (uint32) 0,
Divisor: (uint32) 1,
Sel: (*tc.U32Sel)(<nil>),
InDev: (string) "",
Pcnt: (uint64) 0,
Mark: (*tc.U32Mark)(<nil>),
Flags: (uint32) 0,
Police: (*tc.Police)(<nil>),
Actions: (*[]*tc.Action)(<nil>)
}),
Rsvp: (*tc.Rsvp)(<nil>),
Route4: (*tc.Route4)(<nil>),
Fw: (*tc.Fw)(<nil>),
Flow: (*tc.Flow)(<nil>),
FqCodel: (*tc.FqCodel)(<nil>),
Codel: (*tc.Codel)(<nil>),
Fq: (*tc.Fq)(<nil>),
Pie: (*tc.Pie)(<nil>),
Hhf: (*tc.Hhf)(<nil>),
Tbf: (*tc.Tbf)(<nil>),
Sfb: (*tc.Sfb)(<nil>),
Red: (*tc.Red)(<nil>),
MqPrio: (*tc.MqPrio)(<nil>),
Pfifo: (*tc.FifoOpt)(<nil>),
Bfifo: (*tc.FifoOpt)(<nil>),
Choke: (*tc.Choke)(<nil>),
Htb: (*tc.Htb)(<nil>),
Hfsc: (*tc.Hfsc)(<nil>),
Dsmark: (*tc.Dsmark)(<nil>),
Drr: (*tc.Drr)(<nil>),
Cbq: (*tc.Cbq)(<nil>),
Atm: (*tc.Atm)(<nil>),
Qfq: (*tc.Qfq)(<nil>),
Netem: (*tc.Netem)(<nil>)
}
}
****** MOVING ON TO INGRESS FILTERS ******
The htb
qdisc can be created using go-tc. However, the filters cannot, neither the first, nor directly the sceond. Basically because both fall foul of the checking of the output of the marshalU32 function, which returns empty on both the structs above, because it appears to not marshall the divisor property. Moving on a level if I then use the following:
tc filter add dev eth0 parent 1:0 prio 1 handle 100: protocol ip u32 divisor 256
It creates another tc object as follows:
(tc.Object) {
Msg: (tc.Msg) {
Family: (uint32) 0,
Ifindex: (uint32) 6,
Handle: (uint32) 2147483648,
Parent: (uint32) 65536,
Info: (uint32) 65544
},
Attribute: (tc.Attribute) {
Kind: (string) (len=3) "u32",
EgressBlock: (uint32) 0,
IngressBlock: (uint32) 0,
HwOffload: (uint8) 0,
Chain: (uint32) 0,
Stats: (*tc.Stats)(<nil>),
XStats: (*tc.XStats)(<nil>),
Stats2: (*tc.Stats2)(<nil>),
Basic: (*tc.Basic)(<nil>),
BPF: (*tc.Bpf)(<nil>),
U32: (*tc.U32)(0xc00007b680)({
ClassID: (uint32) 0,
Hash: (uint32) 0,
Link: (uint32) 0,
Divisor: (uint32) 1,
Sel: (*tc.U32Sel)(<nil>),
InDev: (string) "",
Pcnt: (uint64) 0,
Mark: (*tc.U32Mark)(<nil>),
Flags: (uint32) 0,
Police: (*tc.Police)(<nil>),
Actions: (*[]*tc.Action)(<nil>)
}),
Rsvp: (*tc.Rsvp)(<nil>),
Route4: (*tc.Route4)(<nil>),
Fw: (*tc.Fw)(<nil>),
Flow: (*tc.Flow)(<nil>),
FqCodel: (*tc.FqCodel)(<nil>),
Codel: (*tc.Codel)(<nil>),
Fq: (*tc.Fq)(<nil>),
Pie: (*tc.Pie)(<nil>),
Hhf: (*tc.Hhf)(<nil>),
Tbf: (*tc.Tbf)(<nil>),
Sfb: (*tc.Sfb)(<nil>),
Red: (*tc.Red)(<nil>),
MqPrio: (*tc.MqPrio)(<nil>),
Pfifo: (*tc.FifoOpt)(<nil>),
Bfifo: (*tc.FifoOpt)(<nil>),
Choke: (*tc.Choke)(<nil>),
Htb: (*tc.Htb)(<nil>),
Hfsc: (*tc.Hfsc)(<nil>),
Dsmark: (*tc.Dsmark)(<nil>),
Drr: (*tc.Drr)(<nil>),
Cbq: (*tc.Cbq)(<nil>),
Atm: (*tc.Atm)(<nil>),
Qfq: (*tc.Qfq)(<nil>),
Netem: (*tc.Netem)(<nil>)
}
}
which again cannot be recreated by go-tc for the same reason, divisor being the only non-zero value in U32
possible to make one purely for af_xdp or xdp?
a firewall for ipv6 and ipv4 tcp example
Describe the issue
BPF filter delete fails with error netlink receive: no such file or directory
Expected behavior
Cleanup the filters created by the program.
Minimal code example to reproduce the issue
Steps to reproduce the behavior.
go.mod
module github.com/sanfern/go-tc-example
go 1.20
require (
github.com/cilium/ebpf v0.10.0
github.com/florianl/go-tc v0.4.2
golang.org/x/sys v0.8.0
)
require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.1.1 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
)
main.go
package main
import (
"fmt"
"github.com/cilium/ebpf"
tc "github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"golang.org/x/sys/unix"
"net"
"os"
"time"
)
func main() {
tcIface := "enp0s3"
devID, err := net.InterfaceByName(tcIface)
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface ID: %v\n", err)
return
}
tcgo, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
// get all the qdiscs from all interfaces
qdiscs, err := tcgo.Qdisc().Get()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get qdiscs: %v\n", err)
return
}
clsactFound := false
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface from id %d: %v", qdisc.Ifindex, err)
return
}
if iface.Name == tcIface && qdisc.Kind == "clsact" {
clsactFound = true
}
}
if !clsactFound {
qdisc := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(tc.HandleRoot, 0x0000),
Parent: tc.HandleIngress,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "clsact",
},
}
if err := tcgo.Qdisc().Add(&qdisc); err != nil {
fmt.Printf("could not assign clsact to %s: %v, its already exists", tcIface, err)
}
}
kernFile := "tc_program_kern.o"
prg, err := ebpf.LoadCollection(kernFile)
if err != nil {
fmt.Println("LoadCollection error : ", err)
return
}
defer prg.Close()
bpfProg := prg.Programs["tc_program"]
fd := uint32(bpfProg.FD())
flags := uint32(0x1)
filter := tc.Object{
tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: 0,
Parent: core.BuildHandle(tc.HandleRoot, tc.HandleMinIngress),
Info: 0x300,
},
tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &fd,
Flags: &flags,
},
},
}
if err := tcgo.Filter().Add(&filter); err != nil {
fmt.Fprintf(os.Stderr, "could not attach filter for eBPF program: %v\n", err)
return
}
fmt.Println("Attaching tc program is successful, resting ...")
time.Sleep(2 * time.Second)
if err := tcgo.Filter().Delete(&filter); err != nil {
fmt.Fprintf(os.Stderr, "could not del filter for eBPF program: %v\n", err)
return
}
}
Environment
Provide some information about the system with the issue.
$ uname -r
5.15.0-051500-generic
Ref code
My background are networking diagnosis tools that need beside the tc aspect of NETLINK also many other NETLINK areas, not least the link-related API functions. Is there any chance to maybe team up with https://github.com/vishvananda/netlink and fill in the blanks when it comes to tc functionality?
I'm not affiliated with the https://github.com/vishvananda/netlink project, just evaluating how to deal with the current modules situation.
go-tc version: master (69b0c91)
go version: 1.13
OS: Ubuntu 18.04
I’m creating and deleting qdiscs for an interface using these tc
commands:
tc qdisc add dev eth0 root tbf rate 2mbit burst 32kbit latency 400ms
tc qdisc del dev eth0 root
I’m trying to achieve the same thing using go-tc. After creating the qdisc using the tc qdisc add
command above, and selecting it with Qdisc.Get() in this sample program, I’m unable to delete the qdisc:
package main
import (
"fmt"
"github.com/florianl/go-tc"
"net"
"os"
)
func main() {
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
qdiscs, err := rtnl.Qdisc().Get()
var targetQdiscs []tc.Object
if err != nil {
fmt.Fprintf(os.Stderr, "could not get qdiscs: %v\n", err)
return
}
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface from id %d: %v", qdisc.Ifindex, err)
return
}
if iface.Name == "eth0" && qdisc.Attribute.Kind == "tbf" {
fmt.Printf("%20s\t%+v\n", iface.Name, qdisc)
targetQdiscs = append(targetQdiscs, qdisc)
}
}
for _, qdisc := range targetQdiscs {
if err := rtnl.Qdisc().Delete(&qdisc); err != nil {
fmt.Fprintf(os.Stderr, "could not delete qdisc: %v\n", err)
return
}
}
}
Program output:
root@ip-172-22-0-241:~/go/src/tc-test# go build -o tc-test
root@ip-172-22-0-241:~/go/src/tc-test# ./tc-test
eth0 {Msg:{Family:0 Ifindex:2 Handle:2147549184 Parent:4294967295 Info:2} Attribute:{Kind:tbf EgressBlock:0 IngressBlock:0 HwOffload:0 Chain:0 Stats:0xc00001a2d0 XStats:<nil> Stats2:0xc00001c360 Stab:<nil> Basic:<nil> BPF:<nil> U32:<nil> Rsvp:<nil> Route4:<nil> Fw:<nil> Flow:<nil> Matchall:<nil> Cake:<nil> FqCodel:<nil> Codel:<nil> Fq:<nil> Pie:<nil> Hhf:<nil> Tbf:0xc000086190 Sfb:<nil> Red:<nil> MqPrio:<nil> Pfifo:<nil> Bfifo:<nil> Choke:<nil> Netem:<nil> Htb:<nil> Hfsc:<nil> HfscQOpt:<nil> Dsmark:<nil> Drr:<nil> Cbq:<nil> Atm:<nil> Qfq:<nil>}}
could not delete qdisc: functionality not yet implemented
I suspect this is because some qdisc stats fields are set by the command line tool when creating the qdisc, so this check fails:
https://github.com/florianl/go-tc/blob/master/qdisc.go#L157
Can you describe what should be done with the qdisc when these fields are set (and recommend some resources to read) so I could investigate making a PR? Can you suggest a way I can still delete the qdisc, or let me know if I'm approaching something the wrong way?
If I set the fields I linked to above to nil
, I can remove the qdisc successfully, but there may be some danger in this I’m unaware of.
Hi!
I think the kernel struct below has a 12byte aligment for whatever reason. Do you know?
pkt_cls.h:
struct tc_u32_sel {
unsigned char flags;
unsigned char offshift;
unsigned char nkeys;
__be16 offmask;
__u16 off;
short offoff;
short hoff;
/* <-> above is 12bytes */
__be32 hmask;
struct tc_u32_key keys[0];
};
filter code:
sel := &tc.U32Sel{
Flags: TC_U32_TERMINAL,
Offshift: 0xde,
NKeys: 1,
OffMask: 0x1122,
Off: 0x3344,
Offoff: 0xccdd,
Hoff: 0xeeff,
Hmask: bits.ReverseBytes32(0xaabbccdd),
Keys: []tc.U32Key{
{Mask: bits.ReverseBytes32(0x11223344), Val: bits.ReverseBytes32(0xfacefeed), Off: 0xcc, OffMask: 0x0},
},
}
Current serialization causes funky filters:
greybox test (i3*) # tc -s -d -r f show dev eth1
filter parent 1: protocol ip pref 5000 u32 chain 0
filter parent 1: protocol ip pref 5000 u32 chain 0 fh 800:[80000000] ht divisor 1
filter parent 1: protocol ip pref 5000 u32 chain 0 fh 800::c[8000000c] order 12 key ht 800 bkt 0 flowid 1:1 skip_hw not_in_hw (rule hit 0 success 0)
match cefeedcc/223344fa at 0 (success 0 )
hash mask bbccdd11 at -21778
"Off"-byte 0xcc seen on match value
first "Val"-byte 0xfa seen on mask, etc.
Side note: On a little endian system the explicit __be16 __be32, require flipping in Go-code because everything is serialized as native aka. little.
Using the example code doing Qdisc().Get()
I get the below error when parsing the mqprio shown. Any ideas?
could not get qdiscs: unmarshalMqPrio() 513
[3 4 5 6 7 8 9 10 11 12 13 14]
qdisc mqprio 8001: dev ens785f0 root tc 16 map 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
queues:(0:3) (4:7) (8:11) (12:15) (16:19) (20:23) (24:27) (28:31) (32:35) (36:39) (40:43) (44:47) (48:51) (52:55) (56:59) (60:63)
mode:channel
shaper:dcb
Hi,
How could one test and know how the kernel uses tc structs? Perhaps this is architecture dependent. I think the current code still has some pitfalls where debugging can be somewhat time consuming. I just run into the ones I am using.
eg.
// ConnmarkParam from include/uapi/linux/tc_act/tc_connmark.h
type ConnmarkParam struct {
Index uint32
Capab uint32
Action uint32
RefCnt uint32
BindCnt uint32
Zone uint16
_ [2]byte //pad ???
}
I believe the above struct is indeed 24bytes on "wire" rather than 22. One clue could be 22%4 = 2. Perhaps padding the struct definitions would be the way to go with this? Simple marshalStruct() would then work as expected.
Edit:
On second thought I think 22%4 = 2 thing is garbage, and struct alignment is based on normal C conventions. Whether the padding is on wire is based on if the struct is serialized or memcpy'd by the kernel.
Hello! I'm adding a flower rule to a qdisc like so:
var dst_port_test = uint16(1)
flower := tc.Flower{ClassID: cid, KeyEncUDPDstPort: &dst_port_test}
...
rtnl.Filter().Add(&obj_flower)
I expect to see a tc flower rule that matches on destination port ID of 1. Instead, I get this:
filter protocol all pref 49152 flower chain 0
filter protocol all pref 49152 flower chain 0 handle 0x1
enc_dst_port 256
not_in_hw
The outcome in the flow rules is always dst_port * 256. Perhaps there is a bug in marshalling the data?
So been looking at this for the past few minutes, and I seem to be unable to disable ECN though the library. In the tc
command tool you can turn of ECN 0 with results the following struct for the qdisc object.
❯ sudo tc qdisc add dev test parent root handle 2:3 fq_codel noecn limit 1200 flows 65535 target 5ms
❯ sudo tc qdisc show dev test
qdisc fq_codel 2: root refcnt 2 limit 1200p flows 65535 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb
&{4999 1200 99999 0 65535 1514 0 64 33554432}
But attempting this through the library with the following definition:
qdisc := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(0x1, 0x0),
Parent: tc.HandleRoot,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "fq_codel",
// http://man7.org/linux/man-pages/man8/tc-fq_codel.8.html
// fq_codel limit 2000 target 3ms interval 40ms noecn
FqCodel: &tc.FqCodel{
Target: 0xbb8,
Limit: 0x7d0,
Interval: 0x9c40,
ECN: 0x0,
},
},
}
results into the following outputs:
❯ sudo tc qdisc show dev test
qdisc fq_codel 1: root refcnt 2 limit 2000p flows 1024 quantum 1514 target 3.0ms interval 40.0ms memory_limit 32Mb ecn
&{2999 2000 39999 1 1024 1514 0 64 33554432}
I have the feeling that this might be related to this section https://github.com/florianl/go-tc/blob/master/q_fqCodel.go#L61 . And it does have a TODO to improve the logic. Depending on the spare time (and if that's the problem), I'll probably have a PR done this night or tomorrow to fix this.
example code
I do this on a test
interface that I create beforehand ip l add dev test type #dummy
.
package main
import (
"fmt"
"net"
"os"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"golang.org/x/sys/unix"
)
func main() {
rtnl, err := tc.Open(&tc.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "could not open rtnetlink socket: %v\n", err)
return
}
defer func() {
if err := rtnl.Close(); err != nil {
fmt.Fprintf(os.Stderr, "could not close rtnetlink socket: %v\n", err)
}
}()
devID, err := net.InterfaceByName("test")
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface ID: %v\n", err)
return
}
qdisc := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(devID.Index),
Handle: core.BuildHandle(0x1, 0x0),
Parent: tc.HandleRoot,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "fq_codel",
// http://man7.org/linux/man-pages/man8/tc-fq_codel.8.html
// fq_codel limit 2000 target 3ms interval 40ms noecn
FqCodel: &tc.FqCodel{
Target: 0xbb8,
Limit: 0x7d0,
Interval: 0x9c40,
ECN: 0x0,
},
},
}
if err := rtnl.Qdisc().Add(&qdisc); err != nil {
fmt.Fprintf(os.Stderr, "could not assign htb to lo: %v\n", err)
return
}
defer func() {
if err := rtnl.Qdisc().Delete(&qdisc); err != nil {
fmt.Fprintf(os.Stderr, "could not delete htb qdisc of lo: %v\n", err)
return
}
}()
qdiscs, err := rtnl.Qdisc().Get()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get all qdiscs: %v\n", err)
}
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
fmt.Fprintf(os.Stderr, "could not get interface from id %d: %v", qdisc.Ifindex, err)
return
}
fmt.Printf("%20s\t%s\n", iface.Name, qdisc.Kind)
if qdisc.Msg.Ifindex == uint32(iface.Index) {
fmt.Printf("%v", qdisc.Attribute.FqCodel)
}
}
}
I would like to add support for functionality equivalent to the tc actions
commands to this library, but I wanted to make sure that this is desirable from your point of view before I spend the time.
Summary of functionality I would like to add
tc
allows actions to be created independently of filters.
Once created, these actions may then
- be associated with one or more filters,
- live updated (and updates will be seen by all filters using that action),
- be deleted (only after all filters using that action have been deleted).
For example, consider the following :
for i in x y z; do
ip link add dev "$i" type dummy
tc qdisc add dev "$i" clsact
done
tc actions add action mirred egress redirect dev y
tc actions add action gact drop
At this point, we could
-
list the mirred
actions
$ tc actions list action mirred
total acts 1
action order 0: mirred (Egress Redirect to device y) stolen
index 1 ref 1 bind 0
not_in_hw
used_hw_stats disabled
-
list the gact
actions
$ tc actions list action gact
total acts 1
action order 0: gact action drop
random type none pass val 0
index 1 ref 1 bind 0
not_in_hw
used_hw_stats disabled
-
create any number of filters using either or both of these actions by index
tc filter add dev x ingress pref 1000 proto ip flower dst_ip 8.8.8.8 action mirred index 1
tc filter add dev z ingress pref 1000 proto ip flower dst_ip 8.8.8.8 action mirred index 1 action gact index 1
-
display those filters as normal (with per-action statistics)
$ tc -s filter show dev z ingress
filter protocol ip pref 1000 flower chain 0
filter protocol ip pref 1000 flower chain 0 handle 0x1
eth_type ipv4
dst_ip 8.8.8.8
not_in_hw
action order 1: mirred (Egress Redirect to device y) stolen
index 1 ref 3 bind 2 installed 599 sec used 599 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 2: gact action drop
random type none pass val 0
index 1 ref 2 bind 1 installed 599 sec used 599 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
-
centrally update those actions (e.g., change drop
to pass
)
$ tc actions change action gact pass index 1
$ tc -s filter show dev z ingress
filter protocol ip pref 1000 flower chain 0
filter protocol ip pref 1000 flower chain 0 handle 0x1
eth_type ipv4
dst_ip 8.8.8.8
not_in_hw
action order 1: mirred (Egress Redirect to device y) stolen
index 1 ref 3 bind 2 installed 838 sec used 838 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 2: gact action pass
random type none pass val 0
index 1 ref 2 bind 1 installed 838 sec used 838 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
-
attempts to delete those actions while in use will be rejected (albeit with a buggy error message from iproute2
/tc
)
$ tc actions delete action gact index 1
Error: Failed to delete TC action.
We have an error talking to the kernel
Command "action" is unknown, try "tc actions help"
-
Removing all filters that use an action will allow the action to be deleted
$ tc filter del dev z ingress pref 1000
$ tc actions delete action gact index 1
$ tc filter del dev x ingress pref 1000
$ tc actions delete action mirred index 1
If you are interested I will work on it and submit a patch.
Recommend Projects
-
-
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
An Open Source Machine Learning Framework for Everyone
-
The Web framework for perfectionists with deadlines.
-
A PHP framework for web artisans
-
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
Some thing interesting about web. New door for the world.
-
A server is a program made to process requests and deliver data to clients.
-
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Some thing interesting about visualization, use data art
-
Some thing interesting about game, make everyone happy.
-
Recommend Org
-
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Open source projects and samples from Microsoft.
-
Google ❤️ Open Source for everyone.
-
Alibaba Open Source for everyone
-
Data-Driven Documents codes.
-
China tencent open source team.
-
Jobs
Jooble