pkg/tc/tc.go (292 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//limitations under the License.
package tc
import (
"errors"
"fmt"
"strings"
constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants"
"github.com/aws/aws-ebpf-sdk-go/pkg/logger"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
const (
FILTER_CLEANUP_FAILED = "filter cleanup failed"
)
var log = logger.Get()
type BpfTc interface {
TCIngressAttach(interfaceName string, progFD int, funcName string) error
TCIngressDetach(interfaceName string) error
TCEgressAttach(interfaceName string, progFD int, funcName string) error
TCEgressDetach(interfaceName string) error
CleanupQdiscs(ingressCleanup bool, egressCleanup bool) error
GetAllAttachedProgIds() (map[string]int, map[string]int, error)
}
var _ BpfTc = &bpfTc{}
type bpfTc struct {
InterfacePrefix []string
}
func New(interfacePrefix []string) BpfTc {
return &bpfTc{
InterfacePrefix: interfacePrefix,
}
}
func enableQdisc(link netlink.Link) bool {
qdiscs, err := netlink.QdiscList(link)
if err != nil {
log.Infof("Unable to check qdisc hence try installing")
return true
}
qdiscHandle := netlink.MakeHandle(constdef.QDISC_HANDLE, 0)
for _, qdisc := range qdiscs {
attrs := qdisc.Attrs()
if attrs.LinkIndex != link.Attrs().Index {
continue
}
if (attrs.Handle&qdiscHandle) == qdiscHandle && attrs.Parent == netlink.HANDLE_CLSACT {
log.Infof("Found qdisc hence don't install again")
return false
}
}
log.Infof("Qdisc is not enabled hence install")
return true
}
func mismatchedInterfacePrefix(interfaceName string, interfacePrefix []string) error {
for _, prefix := range interfacePrefix {
if strings.HasPrefix(interfaceName, prefix) {
return nil
}
}
log.Errorf("expected prefix - %s but got %s", interfacePrefix, interfaceName)
return errors.New("Mismatched initialized prefix name and passed interface name")
}
func (m *bpfTc) TCIngressAttach(interfaceName string, progFD int, funcName string) error {
if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil {
return err
}
intf, err := netlink.LinkByName(interfaceName)
if err != nil {
log.Errorf("failed to find device by name %s: %v", interfaceName, err)
return err
}
attrs := netlink.QdiscAttrs{
LinkIndex: intf.Attrs().Index,
Handle: netlink.MakeHandle(constdef.QDISC_HANDLE, 0),
Parent: netlink.HANDLE_CLSACT,
}
if enableQdisc(intf) {
qdisc := &netlink.GenericQdisc{
QdiscAttrs: attrs,
QdiscType: "clsact",
}
if err := netlink.QdiscAdd(qdisc); err != nil {
log.Errorf("cannot add clsact qdisc: %v", err)
return err
}
}
// construct the filter
filter := &netlink.BpfFilter{
FilterAttrs: netlink.FilterAttrs{
LinkIndex: attrs.LinkIndex,
Parent: uint32(netlink.HANDLE_MIN_INGRESS),
Handle: constdef.DEFAULT_BPF_FILTER_HANDLE,
Protocol: unix.ETH_P_ALL,
Priority: 1,
},
Fd: progFD,
Name: funcName,
DirectAction: true,
}
if err = netlink.FilterAdd(filter); err != nil {
log.Errorf("while loading ingress program %q on fd %d: %v", "handle ingress", progFD, err)
return err
}
log.Infof("TC ingress filter add done %s", interfaceName)
return nil
}
func (m *bpfTc) TCIngressDetach(interfaceName string) error {
if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil {
return err
}
intf, err := netlink.LinkByName(interfaceName)
if err != nil {
log.Errorf("failed to find device by name %s: %v", interfaceName, err)
return err
}
//Currently supports only one handle, in future we might need to cache the handle
filterHandle := uint32(constdef.DEFAULT_BPF_FILTER_HANDLE)
filterParent := uint32(netlink.HANDLE_MIN_INGRESS)
filters, err := netlink.FilterList(intf, filterParent)
if err != nil {
log.Errorf("failed to get filter list: %v", err)
return err
}
for _, filter := range filters {
if filter.Attrs().Handle == filterHandle {
err = netlink.FilterDel(filter)
if err != nil {
log.Errorf("delete filter failed on intf %s : %v", interfaceName, err)
return errors.New(FILTER_CLEANUP_FAILED)
}
log.Infof("TC ingress filter detach done")
return nil
}
}
return fmt.Errorf("no active filter to detach-%s", interfaceName)
}
func (m *bpfTc) TCEgressAttach(interfaceName string, progFD int, funcName string) error {
if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil {
return err
}
intf, err := netlink.LinkByName(interfaceName)
if err != nil {
log.Errorf("failed to find device by name %s: %w", interfaceName, err)
return err
}
attrs := netlink.QdiscAttrs{
LinkIndex: intf.Attrs().Index,
Handle: netlink.MakeHandle(constdef.QDISC_HANDLE, 0),
Parent: netlink.HANDLE_CLSACT,
}
if enableQdisc(intf) {
qdisc := &netlink.GenericQdisc{
QdiscAttrs: attrs,
QdiscType: "clsact",
}
if err := netlink.QdiscAdd(qdisc); err != nil {
log.Errorf("cannot add clsact qdisc: %v", err)
return err
}
}
// construct the filter
filter := &netlink.BpfFilter{
FilterAttrs: netlink.FilterAttrs{
LinkIndex: attrs.LinkIndex,
Parent: uint32(netlink.HANDLE_MIN_EGRESS),
Handle: constdef.DEFAULT_BPF_FILTER_HANDLE,
Protocol: unix.ETH_P_ALL,
Priority: 1,
},
Fd: progFD,
Name: funcName,
DirectAction: true,
}
if err = netlink.FilterAdd(filter); err != nil {
log.Errorf("while loading egress program %q on fd %d: %v", "handle egress", progFD, err)
return err
}
log.Infof("TC filter egress add done %s", interfaceName)
return nil
}
func (m *bpfTc) TCEgressDetach(interfaceName string) error {
if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil {
return err
}
intf, err := netlink.LinkByName(interfaceName)
if err != nil {
log.Errorf("failed to find device by name %s: %w", interfaceName, err)
return err
}
//Currently supports only one handle, in future we might need to cache the handle
filterHandle := uint32(0x1)
filterParent := uint32(netlink.HANDLE_MIN_EGRESS)
filters, err := netlink.FilterList(intf, filterParent)
if err != nil {
log.Errorf("failed to get filter list: %v", err)
return err
}
for _, filter := range filters {
if filter.Attrs().Handle == filterHandle {
err = netlink.FilterDel(filter)
if err != nil {
log.Errorf("delete filter failed on intf %s : %v", interfaceName, err)
return errors.New(FILTER_CLEANUP_FAILED)
}
log.Infof("TC egress filter detach done")
return nil
}
}
return fmt.Errorf("no active filter to detach-%s", interfaceName)
}
func (m *bpfTc) CleanupQdiscs(ingressCleanup bool, egressCleanup bool) error {
if len(m.InterfacePrefix) == 0 {
log.Errorf("invalid empty prefix")
return nil
}
linkList, err := netlink.LinkList()
if err != nil {
log.Errorf("unable to get link list")
return err
}
for _, link := range linkList {
linkName := link.Attrs().Name
if err := mismatchedInterfacePrefix(linkName, m.InterfacePrefix); err == nil {
if ingressCleanup {
log.Infof("Trying to cleanup ingress on %s", linkName)
err = m.TCIngressDetach(linkName)
if err != nil {
if err.Error() == FILTER_CLEANUP_FAILED {
log.Errorf("failed to detach ingress, might not be present so moving on")
}
}
}
if egressCleanup {
log.Infof("Trying to cleanup egress on %s", linkName)
err = m.TCEgressDetach(linkName)
if err != nil {
if err.Error() == FILTER_CLEANUP_FAILED {
log.Errorf("failed to detach egress, might not be present so moving on")
}
}
}
}
}
return nil
}
func (m *bpfTc) getAttachedProgId(link netlink.Link, filterParent uint32) int {
linkName := link.Attrs().Name
filters, err := netlink.FilterList(link, filterParent)
if err != nil {
log.Errorf("failed to list filters for: %s ", linkName, err)
}
progId := 0
filterHandle := uint32(constdef.DEFAULT_BPF_FILTER_HANDLE)
// You will only have one filter for a handle
for _, filter := range filters {
if filter.Attrs().Handle == filterHandle {
bpf, ok := filter.(*netlink.BpfFilter)
if !ok {
continue
}
progId = int(bpf.Id)
}
}
return progId
}
func (m *bpfTc) GetAllAttachedProgIds() (map[string]int, map[string]int, error) {
if len(m.InterfacePrefix) == 0 {
log.Errorf("invalid empty prefix")
return nil, nil, fmt.Errorf("Invalid empty prefix")
}
linkList, err := netlink.LinkList()
if err != nil {
log.Errorf("unable to get link list")
return nil, nil, err
}
interfaceToIngressProgId := make(map[string]int)
interfaceToEgressProgId := make(map[string]int)
for _, link := range linkList {
linkName := link.Attrs().Name
log.Infof("link name %s", linkName)
ingressProgId := 0
egressProgId := 0
if err := mismatchedInterfacePrefix(linkName, m.InterfacePrefix); err == nil {
// Get ingress ID attached
filterParent := uint32(netlink.HANDLE_MIN_INGRESS)
ingressProgId = m.getAttachedProgId(link, filterParent)
log.Infof("Got ingress progId %d", ingressProgId)
if ingressProgId > 0 {
interfaceToIngressProgId[linkName] = ingressProgId
}
// Get egress ID attached
filterParent = uint32(netlink.HANDLE_MIN_EGRESS)
egressProgId = m.getAttachedProgId(link, filterParent)
log.Infof("Got egress progId %d", egressProgId)
if egressProgId > 0 {
interfaceToEgressProgId[linkName] = egressProgId
}
}
}
return interfaceToIngressProgId, interfaceToEgressProgId, nil
}