GithubHelp home page GithubHelp logo

florianl / go-tc Goto Github PK

View Code? Open in Web Editor NEW
425.0 6.0 46.0 582 KB

traffic control in pure go - it allows to read and alter queues, filters and classes

License: MIT License

Go 100.00%
network traffic-control linux filter class bpf rtnetlink ebpf go qdisc

go-tc's Introduction

tc PkgGoDev Go Report Card GitHub Actions Coverage Status

This is a work in progress version of tc. It provides a C-binding free API to the netlink based traffic control system of rtnetlink.

Example

package main

import (
	"fmt"
	"net"
	"os"

	"github.com/mdlayher/netlink"

	"github.com/florianl/go-tc"
)

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)
		}
	}()

	// For enhanced error messages from the kernel, it is recommended to set
	// option `NETLINK_EXT_ACK`, which is supported since 4.12 kernel.
	//
	// If not supported, `unix.ENOPROTOOPT` is returned.

	err = rtnl.SetOption(netlink.ExtendedAcknowledge, true)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not set option ExtendedAcknowledge: %v\n", err)
		return
	}

	// get all the qdiscs from all interfaces
	qdiscs, err := rtnl.Qdisc().Get()
	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
		}
		fmt.Printf("%20s\t%s\n", iface.Name, qdisc.Kind)
	}
}

Requirements

Privileges

This package processes information directly from the kernel and therefore it requires special privileges. You can provide this privileges by adjusting the CAP_NET_ADMIN capabilities.

	setcap 'cap_net_admin=+ep' /your/executable

go-tc's People

Contributors

asphaltt avatar dennisafa avatar dependabot[bot] avatar fbegyn avatar florianl avatar gizmoguy avatar pombredanne avatar rfyiamcool avatar tanyawong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

go-tc's Issues

Error getting egress filter

Hi Florian

I'm attempting to use go-tc to programmatically apply filtering and shaping banks. My main method for doing this is to use tc to implement the rules and then the relevant go-tc .Get() functions to examine the actual tc.objects created (so that I can recreate these objects with go-tc)

At the moment I'm using the following tc commands to divert ingress traffic to an IFB:

tc qdisc add dev eth0.32 handle ffff: ingress
tc filter add dev eth0.32 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0

When I use tc filter show dev eth0.32 ingress to check the status of the filters created by tc I get the following info:

filter parent ffff: protocol ip pref 49152 u32 chain 0
filter parent ffff: protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
filter parent ffff: protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid ??? not_in_hw
  match 00000000/00000000 at 0
	action order 1: mirred (Egress Redirect to device ifb0) stolen
 	index 1 ref 1 bind 1

All good. I then use the following code in go-tc to try and examine these tc objects:

	filters, err := rtnl.Filter().Get(&tc.Msg{Family: unix.AF_UNSPEC, Ifindex: uint32(iface.Index), Parent: tc.BuildHandle(0xffff, 0)})
	spew.Dump(filters)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not get filter: %v\n", err)
		return err
	}

I can get at two of the filters but the third one errors out, giving me the following message:

could not get filter: unmarshalU32()	7
   [136 0 1 0 11 0 1 0 109 105 114 114 101 100 0 0 48 0 4 0 20 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 24 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 0 2 0 32 0 2 0 1 0 0 0 0 0 0 0 4 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 36 0 1 0 121 184 5 0 0 0 0 0 121 184 5 0 0 0 0 0 0 0 0 0 0 0 0 0 215 182 7 0 1 0 0 0]

At this point I'm stuck!

Can't delete U32 filters

If I use ...Filter().Get(...) to retrieve a filter, I cannot then delete that filter: Filter().Delete(&filter)

From the command line, I am able to add and delete filters as expected:

$ tc filter add dev ens5 parent 0: protocol ip prio 1 u32 match ip dst 199.200.15.170 match ip dport 28145 0xffff action mirred egress redirect dev ifb0
$ tc filter del dev ens5 parent 0: handle 800::801 prio 1 u32

But I can't figure out how to accomplish the same thing in go-tc.

filterQuery := tc.Msg{
	Ifindex: uint32(2),
	Parent:  tc.HandleIngress,
}
filters, _ := tcgo.Filter().Get(&filterQuery)
filter := filters[0]
err := tcgo.Filter().Delete(&filter)  // error: "argument cannot be altered"

filter2 := tc.Object{
	tc.Msg{
		Handle:  filter.Handle,
		Parent:  filter.Parent,
		Ifindex: filter.Ifindex,
	},
	tc.Attribute{
		Kind: filter.Kind,
		U32: filter.U32,
	},
}
err = tcgo.Filter().Delete(&filter2)  // error: "netlink receive: no such file or directory"

Do you have any idea how I can delete a filter via go-tc?

Cannot set protocol and pref fields in flower filter

Hello, I'm adding a tc flower filter using the following code:

flower := tc.Flower{
		Actions:         &actions,
		KeyEthType:      ethType,
		KeyVlanID:       params.vlanEndpoint,
		KeyVlanEthType:  ethType,
		KeyCVlanID:      params.vlanLink,
		KeyCVlanEthType: ethType,
	}
	attrFlower := tc.Attribute{Kind: "flower", Flower: &flower}
	objFlower := tc.Object{Msg: params.msg, Attribute: attrFlower}

	if err := params.rtnl.Filter().Add(&objFlower); err != nil {
		fmt.Fprintf(os.Stderr, "could not add filter flower: %v\n", err)
	} else {
		fmt.Fprintf(os.Stderr, "added filter flower\n")
	}

The following is what I get from a tc -s command:

filter protocol all pref 49152 flower chain 0
filter protocol all pref 49152 flower chain 0 handle 0x1
  enc_key_id 2
  enc_dst_port 4789
  not_in_hw
	action order 1: vlan  push id 1 protocol 802.1Q priority 0 pipe
	 index 1 ref 1 bind 1 installed 3 sec used 3 sec
	Action statistics:
	Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
	backlog 0b 0p requeues 0

	action order 2: vlan  push id 1 protocol 802.1Q priority 0 pipe
	 index 2 ref 1 bind 1 installed 3 sec used 3 sec
	Action statistics:
	Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
	backlog 0b 0p requeues 0

	action order 3: mirred (Egress Mirror to device  eth0) stolen
	index 3 ref 1 bind 1 installed 3 sec used 3 sec
	Action statistics:
	Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
	backlog 0b 0p requeues 0

In this particular line: filter protocol all pref 49152 flower chain 0 I would like to be able to set the pref and the protocol, but I do not see the fields in f_flower.go that would allow me to do this. Any help would be appreciated!

How about rtnl.SetOption(netlink.ExtendedAcknowledge, true) by default?

Recently, I use go-tc to set tc rules. But I didn't know to rtnl.SetOption(netlink.ExtendedAcknowledge, true). As a result, I got error message netlink receive: invalid argument. After I set the option, netlink receive: invalid argument, offset: 0, message: "Filter kind and protocol must be specified".

As the Introduction to Netlink — The Linux Kernel documentation doc recommends, Extended ACKs greatly improve the usability of Netlink and should always be enabled, appropriately parsed and reported to the user.

How about rtnl.SetOption(netlink.ExtendedAcknowledge, true) by default?

Since 4.12 kernel, netlink: extended ACK reporting.

In go-tc, it's OK to try rtnl.SetOption(netlink.ExtendedAcknowledge, true), and ignore error ENOPROTOOPT if it fails.

works on centos8, doesn't work on centos7

Describe the issue
as title, it seems that go-tc works on centos8, doesn't work on centos7.
does go-tc has supported kernel version lists doc? i can't not find in repo.

Expected behavior
i would expect the tc-lib will recognize the different kernel verion to be compatible with them

Minimal code example to reproduce the issue
on centos 7, build the example and run will get the error report
could not get qdiscs: extractTCAOptions(): unsupported kind pfifo_fast: unknown kind

Getting filter for clsact qdisc

Describe the issue

Getting direct-action classifier (filter) for clsact qdisc works like this:

$tc filter show dev "${iface}" ingress
filter protocol all pref 49152 bpf chain 0 
filter protocol all pref 49152 bpf chain 0 handle 0x1 tc_ingress:[1576] direct-action not_in_hw id 1576 tag e10556ce972d1ac9 

$tc filter show dev "${iface}" egress
filter protocol all pref 49152 bpf chain 0 
filter protocol all pref 49152 bpf chain 0 handle 0x1 tc_egress:[1578] direct-action not_in_hw id 1578 tag d70ca70156856322 

Expected behavior

I would like to get these filters using your code.

Minimal code example to reproduce the issue

This code does not work, len(filters)=0

info := tc.Msg{
		Family:  unix.AF_UNSPEC,
		Ifindex: uint32(devID.Index),
		Handle:  0x1,
		Parent:  clsact.Handle,
		Info:    0,
	}
filters, err := tcnl.Filter().Get(&info)

where clsact.Handle=ffff:, cause

$tc qdisc show dev enp0s3 clsact 
qdisc clsact ffff: parent ffff:fff1

cannot get simple qdisc creation to work

Hi, this might just be me being dense but I can't get simple HTB qdisc to work. My code:

	devID, err := net.InterfaceByName("eth0.100")
	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:  tc.BuildHandle(0x1, 0x0000),
			Parent:  0xFFFFFFFF,
			Info:    0,
		},
		tc.Attribute{
			Kind: "htb",
			Htb: &tc.Htb{
				Init: &tc.HtbGlob{
				},
			},
		},
	}

	if err := rtnl.Qdisc().Add(&qdisc); err != nil {
		fmt.Fprintf(os.Stderr, "could not assign htb to eth0.100: %v\n", err)
		return
	}

This simply results in could not assign htb to eth0.100: netlink receive: invalid argument. I think my values above should be equivalent to the tc command:

tc qdisc add dev eth0.100 root handle 1: htb

which does work.

filter: failed with error unmarshalMatchall() 4 [8 0 0 0 0 0 0 0]

Describe the issue

I have tc-filter rule:

$ tc filter show dev veth_delay_out ingress
filter root protocol all pref 49148 matchall chain 0
filter root protocol all pref 49148 matchall chain 0 handle 0x1
  not_in_hw
	action order 1: mirred (Egress Redirect to device *) stolen
	index 10 ref 1 bind 1

But when I get filters for veth_delay_out, it fails with error unmarshalMatchall() 4 [8 0 0 0 0 0 0 0].

Expected behavior

It expects to get matchall information.

Minimal code example to reproduce the issue

Steps to reproduce the behavior.

  1. tc filter replace dev veth_delay_out ingress protocol all matchall action mirred egress redirect dev ifb0
  2. ./tcfilter veth_delay_out
go.mod

module tcfilter

go 1.19

require (
github.com/davecgh/go-spew v1.1.1
github.com/florianl/go-tc v0.4.2
github.com/vishvananda/netlink v1.1.0
golang.org/x/sys v0.6.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
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // 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"
"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

use tcnl.Class().Add(), but received "netlink receive: invalid argument"

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.

Setting the priority of a rule

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!

KeyIPv4Dst endianness

Discussed in https://github.com/florianl/go-tc/discussions/34

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!

Filter kind and protocol must be specified

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)
	}

}

Attaching EBPF program returns no such file or directory

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
			}
		}
	}
}

Filter().Get() does not retrieve flower or actions object

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: &params.vlanEndpoint}
vlanLinkAction := tc.VLan{Parms: &tc.VLanParms{
	Capab:      0x0,
	Action:     tc.ActPipe,
	RefCnt:     0x1,
	BindCnt:    0x1,
	VLanAction: ActPush},
	PushID: &params.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: &params.dstPort, KeyEncKeyID: &params.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(&params.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!

Feature request: CAKE qdisc

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? 😃

Cannot list SFQ qdisc

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.

Guidance for creating filters with go-tc

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!

pfifo_fast support

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.

Version tag no longer available

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?

Cgroup filter support

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.

marshalU32 returns empty for valid u32 rule

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

Can't delete BPF filters

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

Can't delete qdiscs, functionality not yet implemented?

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.

tc_u32_sel struct alignment

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.

mqprio parse error

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

general struct alignment

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.

Adding flower rule results in unexpected output in `tc -s filter show`

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?

fq_codel: unable to disable ECN

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)
		}
	}
}

RFC: Support for `tc actions` like functionality

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

  1. be associated with one or more filters,
  2. live updated (and updates will be seen by all filters using that action),
  3. 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

  1. 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
  2. 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
  3. 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   
  4. 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
  5. 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
  6. 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"
  7. 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

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.