Implement chains support

This patch implements both tc and filter chains.

We also need to align tc filter delition implementation
with iprote2 to delete filters withichain by passing
additional bits during filter deletion call.
This commit is contained in:
Ivan Kolodyazhny 2022-09-15 10:43:11 +03:00 committed by Alessandro Boch
parent e20cb98f77
commit 378a404a26
6 changed files with 318 additions and 17 deletions

22
chain.go Normal file
View File

@ -0,0 +1,22 @@
package netlink
import (
"fmt"
)
// Chain contains the attributes of a Chain
type Chain struct {
Parent uint32
Chain uint32
}
func (c Chain) String() string {
return fmt.Sprintf("{Parent: %d, Chain: %d}", c.Parent, c.Chain)
}
func NewChain(parent uint32, chain uint32) Chain {
return Chain{
Parent: parent,
Chain: chain,
}
}

112
chain_linux.go Normal file
View File

@ -0,0 +1,112 @@
package netlink
import (
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
// ChainDel will delete a chain from the system.
func ChainDel(link Link, chain Chain) error {
// Equivalent to: `tc chain del $chain`
return pkgHandle.ChainDel(link, chain)
}
// ChainDel will delete a chain from the system.
// Equivalent to: `tc chain del $chain`
func (h *Handle) ChainDel(link Link, chain Chain) error {
return h.chainModify(unix.RTM_DELCHAIN, 0, link, chain)
}
// ChainAdd will add a chain to the system.
// Equivalent to: `tc chain add`
func ChainAdd(link Link, chain Chain) error {
return pkgHandle.ChainAdd(link, chain)
}
// ChainAdd will add a chain to the system.
// Equivalent to: `tc chain add`
func (h *Handle) ChainAdd(link Link, chain Chain) error {
return h.chainModify(
unix.RTM_NEWCHAIN,
unix.NLM_F_CREATE|unix.NLM_F_EXCL,
link,
chain)
}
func (h *Handle) chainModify(cmd, flags int, link Link, chain Chain) error {
req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK)
index := int32(0)
if link != nil {
base := link.Attrs()
h.ensureIndex(base)
index = int32(base.Index)
}
msg := &nl.TcMsg{
Family: nl.FAMILY_ALL,
Ifindex: index,
Parent: chain.Parent,
}
req.AddData(msg)
req.AddData(nl.NewRtAttr(nl.TCA_CHAIN, nl.Uint32Attr(chain.Chain)))
_, err := req.Execute(unix.NETLINK_ROUTE, 0)
return err
}
// ChainList gets a list of chains in the system.
// Equivalent to: `tc chain list`.
// The list can be filtered by link.
func ChainList(link Link, parent uint32) ([]Chain, error) {
return pkgHandle.ChainList(link, parent)
}
// ChainList gets a list of chains in the system.
// Equivalent to: `tc chain list`.
// The list can be filtered by link.
func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) {
req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP)
index := int32(0)
if link != nil {
base := link.Attrs()
h.ensureIndex(base)
index = int32(base.Index)
}
msg := &nl.TcMsg{
Family: nl.FAMILY_ALL,
Ifindex: index,
Parent: parent,
}
req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN)
if err != nil {
return nil, err
}
var res []Chain
for _, m := range msgs {
msg := nl.DeserializeTcMsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
// skip chains from other interfaces
if link != nil && msg.Ifindex != index {
continue
}
var chain Chain
for _, attr := range attrs {
switch attr.Attr.Type {
case nl.TCA_CHAIN:
chain.Chain = native.Uint32(attr.Value)
chain.Parent = parent
}
}
res = append(res, chain)
}
return res, nil
}

77
chain_test.go Normal file
View File

@ -0,0 +1,77 @@
//go:build linux
// +build linux
package netlink
import (
"testing"
)
func TestChainAddDel(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
t.Fatal(err)
}
if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
t.Fatal(err)
}
link, err := LinkByName("foo")
if err != nil {
t.Fatal(err)
}
if err := LinkSetUp(link); err != nil {
t.Fatal(err)
}
qdisc := &Ingress{
QdiscAttrs: QdiscAttrs{
LinkIndex: link.Attrs().Index,
Handle: MakeHandle(0xffff, 0),
Parent: HANDLE_INGRESS,
},
}
if err := QdiscAdd(qdisc); err != nil {
t.Fatal(err)
}
qdiscs, err := SafeQdiscList(link)
if err != nil {
t.Fatal(err)
}
if len(qdiscs) != 1 {
t.Fatal("Failed to add qdisc")
}
_, ok := qdiscs[0].(*Ingress)
if !ok {
t.Fatal("Qdisc is the wrong type")
}
chainVal := new(uint32)
*chainVal = 20
chain := NewChain(HANDLE_INGRESS, *chainVal)
err = ChainAdd(link, chain)
if err != nil {
t.Fatal(err)
}
chains, err := ChainList(link, HANDLE_INGRESS)
if err != nil {
t.Fatal(err)
}
if len(chains) != 1 {
t.Fatal("Failed to add chain")
}
if chains[0].Chain != *chainVal {
t.Fatal("Incorrect chain added")
}
if chains[0].Parent != HANDLE_INGRESS {
t.Fatal("Incorrect chain parent")
}
if err := ChainDel(link, chain); err != nil {
t.Fatal(err)
}
chains, err = ChainList(link, HANDLE_INGRESS)
if err != nil {
t.Fatal(err)
}
if len(chains) != 0 {
t.Fatal("Failed to remove chain")
}
}

View File

@ -19,6 +19,7 @@ type FilterAttrs struct {
Parent uint32 Parent uint32
Priority uint16 // lower is higher priority Priority uint16 // lower is higher priority
Protocol uint16 // unix.ETH_P_* Protocol uint16 // unix.ETH_P_*
Chain *uint32
} }
func (q FilterAttrs) String() string { func (q FilterAttrs) String() string {

View File

@ -206,19 +206,7 @@ func FilterDel(filter Filter) error {
// FilterDel will delete a filter from the system. // FilterDel will delete a filter from the system.
// Equivalent to: `tc filter del $filter` // Equivalent to: `tc filter del $filter`
func (h *Handle) FilterDel(filter Filter) error { func (h *Handle) FilterDel(filter Filter) error {
req := h.newNetlinkRequest(unix.RTM_DELTFILTER, unix.NLM_F_ACK) return h.filterModify(filter, unix.RTM_DELTFILTER, 0)
base := filter.Attrs()
msg := &nl.TcMsg{
Family: nl.FAMILY_ALL,
Ifindex: int32(base.LinkIndex),
Handle: base.Handle,
Parent: base.Parent,
Info: MakeHandle(base.Priority, nl.Swap16(base.Protocol)),
}
req.AddData(msg)
_, err := req.Execute(unix.NETLINK_ROUTE, 0)
return err
} }
// FilterAdd will add a filter to the system. // FilterAdd will add a filter to the system.
@ -230,7 +218,7 @@ func FilterAdd(filter Filter) error {
// FilterAdd will add a filter to the system. // FilterAdd will add a filter to the system.
// Equivalent to: `tc filter add $filter` // Equivalent to: `tc filter add $filter`
func (h *Handle) FilterAdd(filter Filter) error { func (h *Handle) FilterAdd(filter Filter) error {
return h.filterModify(filter, unix.NLM_F_CREATE|unix.NLM_F_EXCL) return h.filterModify(filter, unix.RTM_NEWTFILTER, unix.NLM_F_CREATE|unix.NLM_F_EXCL)
} }
// FilterReplace will replace a filter. // FilterReplace will replace a filter.
@ -242,11 +230,11 @@ func FilterReplace(filter Filter) error {
// FilterReplace will replace a filter. // FilterReplace will replace a filter.
// Equivalent to: `tc filter replace $filter` // Equivalent to: `tc filter replace $filter`
func (h *Handle) FilterReplace(filter Filter) error { func (h *Handle) FilterReplace(filter Filter) error {
return h.filterModify(filter, unix.NLM_F_CREATE) return h.filterModify(filter, unix.RTM_NEWTFILTER, unix.NLM_F_CREATE)
} }
func (h *Handle) filterModify(filter Filter, flags int) error { func (h *Handle) filterModify(filter Filter, proto, flags int) error {
req := h.newNetlinkRequest(unix.RTM_NEWTFILTER, flags|unix.NLM_F_ACK) req := h.newNetlinkRequest(proto, flags|unix.NLM_F_ACK)
base := filter.Attrs() base := filter.Attrs()
msg := &nl.TcMsg{ msg := &nl.TcMsg{
Family: nl.FAMILY_ALL, Family: nl.FAMILY_ALL,
@ -256,6 +244,9 @@ func (h *Handle) filterModify(filter Filter, flags int) error {
Info: MakeHandle(base.Priority, nl.Swap16(base.Protocol)), Info: MakeHandle(base.Priority, nl.Swap16(base.Protocol)),
} }
req.AddData(msg) req.AddData(msg)
if filter.Attrs().Chain != nil {
req.AddData(nl.NewRtAttr(nl.TCA_CHAIN, nl.Uint32Attr(*filter.Attrs().Chain)))
}
req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(filter.Type()))) req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(filter.Type())))
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
@ -470,6 +461,10 @@ func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) {
default: default:
detailed = true detailed = true
} }
case nl.TCA_CHAIN:
val := new(uint32)
*val = native.Uint32(attr.Value)
base.Chain = val
} }
} }
// only return the detailed version of the filter // only return the detailed version of the filter

View File

@ -2068,3 +2068,97 @@ func TestFilterU32PoliceAddDel(t *testing.T) {
t.Fatal("Failed to remove qdisc") t.Fatal("Failed to remove qdisc")
} }
} }
func TestFilterChainAddDel(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
t.Fatal(err)
}
if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
t.Fatal(err)
}
link, err := LinkByName("foo")
if err != nil {
t.Fatal(err)
}
if err := LinkSetUp(link); err != nil {
t.Fatal(err)
}
redir, err := LinkByName("bar")
if err != nil {
t.Fatal(err)
}
if err := LinkSetUp(redir); err != nil {
t.Fatal(err)
}
qdisc := &Ingress{
QdiscAttrs: QdiscAttrs{
LinkIndex: link.Attrs().Index,
Handle: MakeHandle(0xffff, 0),
Parent: HANDLE_INGRESS,
},
}
if err := QdiscAdd(qdisc); err != nil {
t.Fatal(err)
}
qdiscs, err := SafeQdiscList(link)
if err != nil {
t.Fatal(err)
}
if len(qdiscs) != 1 {
t.Fatal("Failed to add qdisc")
}
_, ok := qdiscs[0].(*Ingress)
if !ok {
t.Fatal("Qdisc is the wrong type")
}
classId := MakeHandle(1, 1)
chainVal := new(uint32)
*chainVal = 20
filter := &U32{
FilterAttrs: FilterAttrs{
LinkIndex: link.Attrs().Index,
Parent: MakeHandle(0xffff, 0),
Priority: 1,
Protocol: unix.ETH_P_IP,
Chain: chainVal,
},
RedirIndex: redir.Attrs().Index,
ClassId: classId,
}
if err := FilterAdd(filter); err != nil {
t.Fatal(err)
}
filters, err := FilterList(link, MakeHandle(0xffff, 0))
if err != nil {
t.Fatal(err)
}
if len(filters) != 1 {
t.Fatal("Failed to add filter")
}
filterChain := filters[0].Attrs().Chain
if filterChain != nil && *filterChain != *chainVal {
t.Fatalf("Chain of the filter is the wrong value")
}
if err := FilterDel(filter); err != nil {
t.Fatal(err)
}
filters, err = FilterList(link, MakeHandle(0xffff, 0))
if err != nil {
t.Fatal(err)
}
if len(filters) != 0 {
t.Fatal("Failed to remove filter")
}
if err := QdiscDel(qdisc); err != nil {
t.Fatal(err)
}
qdiscs, err = SafeQdiscList(link)
if err != nil {
t.Fatal(err)
}
if len(qdiscs) != 0 {
t.Fatal("Failed to remove qdisc")
}
}