diff --git a/go.mod b/go.mod index 55bae04..0e496c1 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,14 @@ require ( github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.3.0 github.com/onsi/ginkgo/v2 v2.1.4 github.com/onsi/gomega v1.19.0 + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 k8s.io/api v0.24.2 k8s.io/apimachinery v0.24.2 k8s.io/client-go v0.24.2 k8s.io/klog/v2 v2.70.1 + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 ) require ( @@ -40,7 +42,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/spf13/afero v1.4.1 // indirect github.com/vishvananda/netlink v1.1.1-0.20211101163509-b10eb8fe5cf6 // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect @@ -56,7 +57,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect diff --git a/pkg/tc/driver/cmdline/objects.go b/pkg/tc/driver/cmdline/objects.go new file mode 100644 index 0000000..9d5b868 --- /dev/null +++ b/pkg/tc/driver/cmdline/objects.go @@ -0,0 +1,42 @@ +package driver + +type cQDisc struct { + Kind string `json:"kind"` + Handle string `json:"handle"` + Parent string `json:"parent"` +} + +type cChain struct { + Parent string `json:"parent"` + Chain uint16 `json:"chain"` +} + +type cFilter struct { + Protocol string `json:"protocol"` + Priority uint16 `json:"pref"` + Kind string `json:"kind"` + Chain uint16 `json:"chain"` + Options *cFilterOptions `json:"options,omitempty"` +} + +type cFilterOptions struct { + Handle uint32 `json:"handle"` + Keys cFlowerKeys `json:"keys"` + Actions []cAction `json:"actions"` +} + +type cFlowerKeys struct { + IpProto *string `json:"ip_proto,omitempty"` + DstIp *string `json:"dst_ip,omitempty"` + DstPort *uint16 `json:"dst_port,omitempty"` +} + +type cAction struct { + Order uint `json:"order"` + Kind string `json:"kind"` + ControlAction cControlAction `json:"control_action"` +} + +type cControlAction struct { + Type string `json:"type"` +} diff --git a/pkg/tc/driver/cmdline/tc_cmdline.go b/pkg/tc/driver/cmdline/tc_cmdline.go new file mode 100644 index 0000000..1f955d8 --- /dev/null +++ b/pkg/tc/driver/cmdline/tc_cmdline.go @@ -0,0 +1,247 @@ +package driver + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/pkg/errors" + "k8s.io/utils/exec" + "k8s.io/klog/v2" + + "github.com/Mellanox/multi-networkpolicy-tc/pkg/tc/types" +) + +// NewTcCmdLineImpl creates a new instance of TcCmdLineImpl +func NewTcCmdLineImpl(dev string, log klog.Logger, executor exec.Interface) *TcCmdLineImpl { + return &TcCmdLineImpl{ + netDev: dev, + log: log, + executor: executor, + cmdline: "tc", + options: []string{"-json"}, + } +} + +// TcCmdLineImpl is a concrete implementation of TC interface utilizing TC command line +type TcCmdLineImpl struct { + netDev string + log klog.Logger + executor exec.Interface + + cmdline string + options []string +} + +// execTcCmdNoOutput executes tc command with args, returning error if occurred +func (t *TcCmdLineImpl) execTcCmdNoOutput(args []string) error { + finalArgs := append(t.options, args...) + t.log.V(10).Info("executing", "cmd", "tc", "args", finalArgs) + cmd := t.executor.Command("tc", finalArgs...) + err := cmd.Run() + t.log.V(10).Info("exec result", "err", err) + return err +} + +// execTcCmd executes tc command with args, returning stdout output and error +func (t *TcCmdLineImpl) execTcCmd(args []string) ([]byte, error) { + finalArgs := append(t.options, args...) + t.log.V(10).Info("executing", "cmd", "tc", "args", finalArgs) + cmd := t.executor.Command("tc", finalArgs...) + out, err := cmd.Output() + t.log.V(10).Info("exec result", "err", err, "out", out) + return out, err +} + +// QDiscAdd implements TC interface +func (t *TcCmdLineImpl) QDiscAdd(qdisc types.QDisc) error { + args := []string{"qdisc", "add", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// QDiscDel implements TC interface +func (t *TcCmdLineImpl) QDiscDel(qdisc types.QDisc) error { + args := []string{"qdisc", "del", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// QDiscList implements TC interface +func (t *TcCmdLineImpl) QDiscList() ([]types.QDisc, error) { + args := []string{"qdisc", "list", "dev", t.netDev} + out, err := t.execTcCmd(args) + if err != nil { + return nil, err + } + // parse output and return objects + var cQdiscs []cQDisc + err = json.Unmarshal(out, &cQdiscs) + if err != nil { + return nil, err + } + + var objs []types.QDisc + for _, q := range cQdiscs { + if q.Kind != types.QDiscIngressType { + // skip non-ingress qdiscs + continue + } + qdisc := types.NewIngressQdisc() + handle, err := parseMajorMinor(q.Handle) + if err != nil { + return nil, errors.Wrap(err, "Failed to parse qdisc Handle") + } + parent, err := parseMajorMinor(q.Parent) + if err != nil { + return nil, errors.Wrap(err, "Failed to parse qdisc Parent") + } + qdisc.Handle = &handle + qdisc.Parent = &parent + objs = append(objs, qdisc) + } + return objs, nil +} + +// FilterAdd implements TC interface +func (t *TcCmdLineImpl) FilterAdd(qdisc types.QDisc, filter types.Filter) error { + args := []string{"filter", "add", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + args = append(args, filter.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// FilterDel implements TC interface +func (t *TcCmdLineImpl) FilterDel(qdisc types.QDisc, filterAttr *types.FilterAttrs) error { + args := []string{"filter", "del", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + args = append(args, filterAttr.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// FilterList implements TC interface +func (t *TcCmdLineImpl) FilterList(qdisc types.QDisc) ([]types.Filter, error) { + args := []string{"filter", "list", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + out, err := t.execTcCmd(args) + if err != nil { + return nil, err + } + // parse output and return objects + var cFilters []cFilter + err = json.Unmarshal(out, &cFilters) + if err != nil { + return nil, err + } + + var objs []types.Filter + for _, f := range cFilters { + // skip filters with no Options + if f.Options == nil { + continue + } + if f.Kind != types.FilterKindFlower { + return nil, fmt.Errorf("unexpected filter Kind: %s", f.Kind) + } + + fb := types.NewFlowerFilterBuilder(). + WithChain(f.Chain). + WithProtocol(f.Protocol). + WithPriority(f.Priority). + WithHandle(f.Options.Handle) + + if f.Options.Keys.IpProto != nil { + fb.WithMatchKeyIPProto(*f.Options.Keys.IpProto) + } + if f.Options.Keys.DstIp != nil { + fb.WithMatchKeyDstIP(*f.Options.Keys.DstIp) + } + if f.Options.Keys.DstPort != nil { + fb.WithMatchKeyDstPort(*f.Options.Keys.DstPort) + } + + for _, a := range f.Options.Actions { + // TODO(adrianc): sort first by Order, ATM only one action is expected + if a.Kind != types.ActionTypeGeneric { + return nil, fmt.Errorf("unexpected action: %s", a.Kind) + } + act := types.NewGenericAction(a.ControlAction.Type) + fb.WithAction(act) + } + objs = append(objs, fb.Build()) + } + return objs, nil +} + +// ChainAdd implements TC interface +func (t *TcCmdLineImpl) ChainAdd(qdisc types.QDisc, chain types.Chain) error { + args := []string{"chain", "add", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + args = append(args, chain.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// ChainDel implements TC interface +func (t *TcCmdLineImpl) ChainDel(qdisc types.QDisc, chain types.Chain) error { + args := []string{"chain", "del", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + args = append(args, chain.GenCmdLineArgs()...) + return t.execTcCmdNoOutput(args) +} + +// ChainList implements TC interface +func (t *TcCmdLineImpl) ChainList(qdisc types.QDisc) ([]types.Chain, error) { + args := []string{"chain", "list", "dev", t.netDev} + args = append(args, qdisc.GenCmdLineArgs()...) + out, err := t.execTcCmd(args) + if err != nil { + return nil, err + } + // parse output and return objects + var cChains []cChain + err = json.Unmarshal(out, &cChains) + if err != nil { + return nil, err + } + + var objs []types.Chain + for _, c := range cChains { + parent, err := parseMajorMinor(c.Parent) + if err != nil { + return nil, errors.Wrap(err, "Failed to parse Chain Parent") + } + objs = append(objs, types.NewChainBuilder().WithChain(c.Chain).WithParent(parent).Build()) + } + return objs, nil +} + +// parseMajorMinor parses TC string Handle and Parent. for a given format the following output is expected as depicted +// below. +// "abcd" -> int32(0xabcd) +// "abcdef01" -> int32(0xabcdef01) +// "abcd:" -> int32(0xabcd) +// "abcd:ef01" -> int32(0xabcdef01) +func parseMajorMinor(mm string) (uint32, error) { + parsedMm := strings.Split(mm, ":") + if len(parsedMm) == 1 { + p, err := strconv.ParseUint(parsedMm[0], 16, 32) + return uint32(p), err + } else if len(parsedMm) == 2 { + major, err := strconv.ParseUint(parsedMm[0], 16, 32) + if err != nil { + return 0, err + } + if len(parsedMm[1]) > 0 { + // we have minor + minor, err := strconv.ParseUint(parsedMm[1], 16, 32) + if err != nil { + return 0, err + } + return ((uint32(major) & 0xffff) << 16) | (uint32(minor) & 0xffff), nil + } + return uint32(major), nil + } else { + return 0, fmt.Errorf("failed to parse MajorMinor string: %s", mm) + } +} diff --git a/pkg/tc/interface.go b/pkg/tc/interface.go new file mode 100644 index 0000000..7c8338d --- /dev/null +++ b/pkg/tc/interface.go @@ -0,0 +1,30 @@ +package tc + +import ( + tctypes "github.com/Mellanox/multi-networkpolicy-tc/pkg/tc/types" +) + +// TC defines an interface to interact with Linux Traffic Control subsystem +// an implementation should be associated with a specific network interface (netdev). +type TC interface { + // QDiscAdd adds the specified Qdisc + QDiscAdd(qdisc tctypes.QDisc) error + // QDiscDel deletes the specified Qdisc + QDiscDel(qdisc tctypes.QDisc) error + // QDiscList lists QDiscs + QDiscList() ([]tctypes.QDisc, error) + + // FilterAdd adds filter to qdisc + FilterAdd(qdisc tctypes.QDisc, filter tctypes.Filter) error + // FilterDel deletes filter identified by filterAttr from qdisc + FilterDel(qdisc tctypes.QDisc, filterAttr *tctypes.FilterAttrs) error + // FilterList lists Filters on qdisc + FilterList(qdisc tctypes.QDisc) ([]tctypes.Filter, error) + + // ChainAdd adds chain to qdiscss + ChainAdd(qdisc tctypes.QDisc, chain tctypes.Chain) error + // ChainDel deletes chain from qdisc + ChainDel(qdisc tctypes.QDisc, chain tctypes.Chain) error + // ChainList lists chains on qdisc + ChainList(qdisc tctypes.QDisc) ([]tctypes.Chain, error) +} diff --git a/pkg/tc/types/action.go b/pkg/tc/types/action.go new file mode 100644 index 0000000..9840209 --- /dev/null +++ b/pkg/tc/types/action.go @@ -0,0 +1,89 @@ +package types + +const ( + // Action type + ActionTypeGeneric = "gact" + + // Generic control actions + ActionGenericPass = "pass" + ActionGenericDrop = "drop" +) + +// Action is an interface which represents a TC action +type Action interface { + CmdLineGenerator + // Type returns the action type + Type() string + // Spec returns Action Specification + Spec() map[string]string + // Equals compares this Action with other, returns true if they are equal or false otherwise + Equals(other Action) bool +} + +// NewGenericAction creates a new GenericAction +func NewGenericAction(controlAction string) *GenericAction { + return &GenericAction{controlAction: controlAction} +} + +// GenericAction is a struct representing TC generic action (gact) +type GenericAction struct { + controlAction string +} + +// Type implements Action interface, it returns the type of the action +func (a *GenericAction) Type() string { + return ActionTypeGeneric +} + +// Spec implements Action interface, it returns the specification of the action +func (a *GenericAction) Spec() map[string]string { + m := make(map[string]string) + m["control_action"] = a.controlAction + return m +} + +// Equals implements Action interface, it returns true if this and other Action are equal +func (a *GenericAction) Equals(other Action) bool { + otherGenericAction, ok := other.(*GenericAction) + if !ok { + return false + } + if a.controlAction != otherGenericAction.controlAction { + return false + } + return true +} + +// GenCmdLineArgs implements CmdLineGenerator interface +func (a *GenericAction) GenCmdLineArgs() []string { + return []string{"action", ActionTypeGeneric, a.controlAction} +} + +// Builer + +// NewGenericActionBuiler creates a new GenericActionBuilder +func NewGenericActionBuiler() *GenericActionBuilder { + return &GenericActionBuilder{} +} + +// GenericActionBuilder is a GenericAction builer +type GenericActionBuilder struct { + genericAction GenericAction +} + +// WithDrop adds ActionGenericDrop control action to GenericActionBuilder +func (gb *GenericActionBuilder) WithDrop() *GenericActionBuilder { + gb.genericAction.controlAction = ActionGenericDrop + return gb +} + +// WithPass adds ActionGenericPass control action to GenericActionBuilder +func (gb *GenericActionBuilder) WithPass() *GenericActionBuilder { + gb.genericAction.controlAction = ActionGenericPass + return gb +} + +// Build builds and returns a new GenericAction instance +func (gb *GenericActionBuilder) Build() *GenericAction { + return NewGenericAction(gb.genericAction.controlAction) +} diff --git a/pkg/tc/types/chain.go b/pkg/tc/types/chain.go new file mode 100644 index 0000000..7b98992 --- /dev/null +++ b/pkg/tc/types/chain.go @@ -0,0 +1,95 @@ +package types + +import ( + "fmt" + "strconv" +) + +const ( + // ChainDefaultParent is the default parent of a chain which is the ingress qdisc + ChainDefaultParent = 0xfffffff1 + // ChainDefaultChain is the default chain number + ChainDefaultChain = 0 +) + +// Chain is an interface which represents a TC chain +type Chain interface { + CmdLineGenerator + // Attrs returns chain attributes + Attrs() *ChainAttrs +} + +// ChainAttrs are the attributes of a Chain +type ChainAttrs struct { + Parent *uint32 + Chain *uint16 +} + +// ChainImpl is a concrete implementation of Chain +type ChainImpl struct { + ChainAttrs +} + +// Attrs implements Chain interface +func (c *ChainImpl) Attrs() *ChainAttrs { + return &c.ChainAttrs +} + +// GenCmdLineArgs implements CmdLineGenerator interface +func (c *ChainImpl) GenCmdLineArgs() []string { + args := []string{} + + if c.Parent != nil { + parent := fmt.Sprintf("%x:%x", uint16(*c.Parent>>16), uint16(*c.Parent)) + args = append(args, "parent", parent) + } + + if c.Chain != nil { + args = append(args, "chain", strconv.FormatUint(uint64(*c.Chain), 10)) + } + return args +} + +func NewChainImpl(parent *uint32, chain *uint16) *ChainImpl { + return &ChainImpl{ChainAttrs{ + Parent: parent, + Chain: chain, + }} +} + +// builder + +// NewChainBuilder creates a new ChainBuilder +func NewChainBuilder() *ChainBuilder { + return &ChainBuilder{} +} + +// ChainBuilder is a Chain builder +type ChainBuilder struct { + chain ChainImpl +} + +// WithParent adds Chain Parent to ChainBuilder +func (cb *ChainBuilder) WithParent(parent uint32) *ChainBuilder { + cb.chain.Parent = &parent + return cb +} + +// WithChain adds Chain chain number to ChainBuilder +func (cb *ChainBuilder) WithChain(chain uint16) *ChainBuilder { + cb.chain.Chain = &chain + return cb +} + +// Build builds and returns a new Chain instance +// Note: calling Build() multiple times will not return a completely +// new object on each call. that is, pointer/slice/map types will not be deep copied. +// to create several objects, different builders should be used. +func (cb *ChainBuilder) Build() *ChainImpl { + if cb.chain.Chain == nil { + defChain := uint16(ChainDefaultChain) + cb.chain.Chain = &defChain + + } + return NewChainImpl(cb.chain.Parent, cb.chain.Chain) +} diff --git a/pkg/tc/types/common.go b/pkg/tc/types/common.go new file mode 100644 index 0000000..08bc047 --- /dev/null +++ b/pkg/tc/types/common.go @@ -0,0 +1,8 @@ +package types + +// CmdLineGenerator is an interface for generating tc command line args for a tc object +type CmdLineGenerator interface { + // GenCmdLineArgs returns tc command line arguments which can be incorporated + // when invoking tc command via shell + GenCmdLineArgs() []string +} diff --git a/pkg/tc/types/filter.go b/pkg/tc/types/filter.go new file mode 100644 index 0000000..de97c7f --- /dev/null +++ b/pkg/tc/types/filter.go @@ -0,0 +1,360 @@ +package types + +import ( + "strconv" + "strings" +) + +const ( + // Values for FilterAttrs.Protocol + FilterProtocolAll = "all" + FilterProtocolIP = "ip" // note ip == ipv4 + + // FlowerFilter.Kind + FilterKindFlower = "flower" + + // FlowerKeys + FlowerKeyIPProto = "ip_proto" + FlowerKeyDstIP = "dst_ip" + FlowerKeyDstPort = "dst_port" +) + +// Filter represent a tc filter object +type Filter interface { + CmdLineGenerator + // Attrs returns FilterAttrs + Attrs() *FilterAttrs + // Equals compares this Filter with other, returns true if they are equal or false otherwise + Equals(other Filter) bool +} + +// FilterAttrs holds filter object attributes +type FilterAttrs struct { + Kind string + Protocol string + Chain *uint16 + Handle *uint32 + Priority *uint16 +} + +// NewFilterAttrs creates new FilterAttrs instance +func NewFilterAttrs(kind string, protocol string, chain *uint16, handle *uint32, priority *uint16) *FilterAttrs { + return &FilterAttrs{ + Kind: kind, + Protocol: protocol, + Chain: chain, + Handle: handle, + Priority: priority, + } +} + +// GenCmdLineArgs implements CmdLineGenerator interface, it generates the needed tc command line args for FilterAttrs +func (fa *FilterAttrs) GenCmdLineArgs() []string { + args := []string{} + + if fa.Protocol != "" { + args = append(args, "protocol", fa.Protocol) + } + + if fa.Handle != nil { + args = append(args, "handle", strconv.FormatUint(uint64(*fa.Handle), 10)) + } + + if fa.Chain != nil { + args = append(args, "chain", strconv.FormatUint(uint64(*fa.Chain), 10)) + } + + if fa.Priority != nil { + args = append(args, "pref", strconv.FormatUint(uint64(*fa.Priority), 10)) + } + + // must be last as next are filter type specific params + args = append(args, fa.Kind) + + return args +} + +// Equals compares this FilterAttrs with other, returns true if they are equal or false otherwise +func (fa *FilterAttrs) Equals(other *FilterAttrs) bool { + if fa == other { + return true + } + + if (fa == nil && other != nil) || (fa != nil && other == nil) { + return false + } + + if fa.Kind != other.Kind { + return false + } + if fa.Protocol != other.Protocol { + return false + } + defChain := uint16(ChainDefaultChain) + if !compare(fa.Chain, other.Chain, &defChain) { + return false + } + if !compare(fa.Priority, other.Priority, nil) { + return false + } + return true +} + +// FlowerSpec holds flower filter specification (which consists of a list of Match) +type FlowerSpec struct { + IpProto *string + DstIP *string + DstPort *uint16 +} + +// GenCmdLineArgs implements CmdLineGenerator interface, it generates the needed tc command line args for FlowerSpec +func (ff *FlowerSpec) GenCmdLineArgs() []string { + args := []string{} + + if ff == nil { + return args + } + + if ff.IpProto != nil { + args = append(args, FlowerKeyIPProto, *ff.IpProto) + } + + if ff.DstIP != nil { + args = append(args, FlowerKeyDstIP, *ff.DstIP) + } + + if ff.DstPort != nil { + args = append(args, FlowerKeyDstPort, strconv.FormatUint(uint64(*ff.DstPort), 10)) + } + + return args +} + +// Equals compares this FlowerSpec with other, returns true if they are equal or false otherwise +func (ff *FlowerSpec) Equals(other *FlowerSpec) bool { + if ff == other { + return true + } + + if (ff == nil && other != nil) || (ff != nil && other == nil) { + return false + } + + // same Key/val + if !compare(ff.IpProto, other.IpProto, nil) { + return false + } + if !compare(ff.DstIP, other.DstIP, nil) { + return false + } + if !compare(ff.DstPort, other.DstPort, nil) { + return false + } + + return true +} + +// FlowerFilter is a concrete implementation of Filter of kind Flower +type FlowerFilter struct { + FilterAttrs + // Flower Match keys, only valid if Kind == FilterKindFlower + Flower *FlowerSpec + // Actions + Actions []Action +} + +// Attrs implements Filter interface, it returns FilterAttrs +func (f *FlowerFilter) Attrs() *FilterAttrs { + return &f.FilterAttrs +} + +// Equals implements Filter interface +func (f *FlowerFilter) Equals(other Filter) bool { + // types equal + otherFlower, ok := other.(*FlowerFilter) + if !ok { + return false + } + + // FilterAttr equal + if !f.Attrs().Equals(other.Attrs()) { + return false + } + + // FlowerSpec Equal + if !f.Flower.Equals(otherFlower.Flower) { + return false + } + + // Actions Equal (order matters) + if len(f.Actions) != len(otherFlower.Actions) { + return false + } + for i := range f.Actions { + if !f.Actions[i].Equals(otherFlower.Actions[i]) { + return false + } + } + + return true +} + +// GenCmdLineArgs implements CmdLineGenerator interface, it generates the needed tc command line args for FlowerFilter +func (f *FlowerFilter) GenCmdLineArgs() []string { + args := []string{} + + args = append(args, f.FilterAttrs.GenCmdLineArgs()...) + + if f.Flower != nil { + args = append(args, f.Flower.GenCmdLineArgs()...) + } + + for _, action := range f.Actions { + args = append(args, action.GenCmdLineArgs()...) + } + + return args +} + +// Builders + +// NewFilterAttrsBuilder returns a new FilterAttrsBuilder +func NewFilterAttrsBuilder() *FilterAttrsBuilder { + return &FilterAttrsBuilder{} +} + +// FilterAttrsBuilder is a FilterAttr builder +type FilterAttrsBuilder struct { + filterAttrs FilterAttrs +} + +// WithKind adds Kind to FilterAttrsBuilder +func (fb *FilterAttrsBuilder) WithKind(k string) *FilterAttrsBuilder { + fb.filterAttrs.Kind = k + return fb +} + +// WithProtocol adds Protocol to FilterAttrsBuilder +func (fb *FilterAttrsBuilder) WithProtocol(p string) *FilterAttrsBuilder { + fb.filterAttrs.Protocol = p + return fb +} + +// WithChain adds Chain index to FilterAttrsBuilder +func (fb *FilterAttrsBuilder) WithChain(c uint16) *FilterAttrsBuilder { + fb.filterAttrs.Chain = &c + return fb +} + +// WithHandle adds Handle to FilterAttrsBuilder +func (fb *FilterAttrsBuilder) WithHandle(h uint32) *FilterAttrsBuilder { + fb.filterAttrs.Handle = &h + return fb +} + +// WithPriority adds Priority to FilterAttrsBuilder +func (fb *FilterAttrsBuilder) WithPriority(p uint16) *FilterAttrsBuilder { + fb.filterAttrs.Priority = &p + return fb +} + +// Build builds and returns a new FilterAttrs instance +// Note: calling Build() multiple times will not return a completely +// new object on each call. that is, pointer/slice/map types will not be deep copied. +// to create several objects, different builders should be used. +func (fb *FilterAttrsBuilder) Build() *FilterAttrs { + return NewFilterAttrs(fb.filterAttrs.Kind, fb.filterAttrs.Protocol, fb.filterAttrs.Chain, fb.filterAttrs.Handle, fb.filterAttrs.Priority) +} + +// NewFlowerFilterBuilder returns a new instance of FlowerFilterBuilder +func NewFlowerFilterBuilder() *FlowerFilterBuilder { + return &FlowerFilterBuilder{ + filterAttrsBuilder: NewFilterAttrsBuilder(), + flowerFilter: FlowerFilter{ + Flower: &FlowerSpec{}, + Actions: make([]Action, 0), + }, + } +} + +// FlowerFilterBuilder is a FlowerFilter builder +type FlowerFilterBuilder struct { + filterAttrsBuilder *FilterAttrsBuilder + flowerFilter FlowerFilter +} + +// WithKind adds Kind to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithKind(k string) *FlowerFilterBuilder { + fb.filterAttrsBuilder = fb.filterAttrsBuilder.WithKind(k) + return fb +} + +// WithProtocol adds Protocol to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithProtocol(p string) *FlowerFilterBuilder { + fb.filterAttrsBuilder = fb.filterAttrsBuilder.WithProtocol(p) + return fb +} + +// WithChain adds Chain number to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithChain(c uint16) *FlowerFilterBuilder { + fb.filterAttrsBuilder = fb.filterAttrsBuilder.WithChain(c) + return fb +} + +// WithHandle adds Handle to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithHandle(h uint32) *FlowerFilterBuilder { + fb.filterAttrsBuilder = fb.filterAttrsBuilder.WithHandle(h) + return fb +} + +// WithPriority adds Priority to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithPriority(p uint16) *FlowerFilterBuilder { + fb.filterAttrsBuilder = fb.filterAttrsBuilder.WithPriority(p) + return fb +} + +// WithMatchKeyIPProto adds Match with FlowerKeyIPProto key and specified value to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithMatchKeyIPProto(val string) *FlowerFilterBuilder { + lower := strings.ToLower(val) + fb.flowerFilter.Flower.IpProto = &lower + return fb +} + +// WithMatchKeyDstIP adds Match with FlowerKeyDstIP key and specified value to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithMatchKeyDstIP(val string) *FlowerFilterBuilder { + // if its a CIDR with /32 take only the IP else take the full CIDR + if strings.HasSuffix(val, "/32") { + valNoMask := val[:len(val)-3] + fb.flowerFilter.Flower.DstIP = &valNoMask + } else { + fb.flowerFilter.Flower.DstIP = &val + } + return fb +} + +// WithMatchKeyDstPort adds Match with FlowerKeyDstPort key and specified value to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithMatchKeyDstPort(val uint16) *FlowerFilterBuilder { + fb.flowerFilter.Flower.DstPort = &val + return fb +} + +// WithAction adds specified Action to FlowerFilterBuilder +func (fb *FlowerFilterBuilder) WithAction(a Action) *FlowerFilterBuilder { + fb.flowerFilter.Actions = append(fb.flowerFilter.Actions, a) + return fb +} + +// Build builds and creates a new FlowerFilter instance +// Note: calling Build() multiple times will not return a completely +// new object on each call. that is, pointer/slice/map types will not be deep copied. +// to create several objects, different builders should be used. +func (fb *FlowerFilterBuilder) Build() *FlowerFilter { + fb.flowerFilter.FilterAttrs = *fb.filterAttrsBuilder.Build() + fb.flowerFilter.Kind = FilterKindFlower + + return &FlowerFilter{ + FilterAttrs: *fb.flowerFilter.Attrs(), + Flower: fb.flowerFilter.Flower, + Actions: fb.flowerFilter.Actions, + } +} diff --git a/pkg/tc/types/qdisc.go b/pkg/tc/types/qdisc.go new file mode 100644 index 0000000..e4e906d --- /dev/null +++ b/pkg/tc/types/qdisc.go @@ -0,0 +1,53 @@ +package types + +const ( + QDiscIngressType = "ingress" +) + +// QDiscAttr holds QDisc object attributes +type QDiscAttr struct { + Handle *uint32 + Parent *uint32 +} + +// QDisc is an interface which represents a TC qdisc object +type QDisc interface { + CmdLineGenerator + // Attrs returns QDiscAttr for a qdisc + Attrs() *QDiscAttr + // Type returns the QDisc type + Type() string +} + +// GenericQDisc is a generic qdisc of an arbitrary type +type GenericQDisc struct { + QDiscAttr + QdiscType string +} + +// Attrs implements QDisc interface +func (g *GenericQDisc) Attrs() *QDiscAttr { + return &g.QDiscAttr +} + +// Type implements QDisc interface +func (g *GenericQDisc) Type() string { + return g.QdiscType +} + +// GenCmdLineArgs implements CmdLineGenerator interface +func (g *GenericQDisc) GenCmdLineArgs() []string { + // for now we can just use qdisc type without attrs (parent, handle) + return []string{QDiscIngressType} +} + +// NewIngressQdisc creates a new Ingress QDisc object +func NewIngressQdisc() *GenericQDisc { + return &GenericQDisc{ + QDiscAttr: QDiscAttr{ + Handle: nil, + Parent: nil, + }, + QdiscType: QDiscIngressType, + } +} diff --git a/pkg/tc/types/utils.go b/pkg/tc/types/utils.go new file mode 100644 index 0000000..ed60ceb --- /dev/null +++ b/pkg/tc/types/utils.go @@ -0,0 +1,27 @@ +package types + +// compare first with second. They are equal if: +// 1. first and second point to the same address (nil or otherwise) +// 2. first and second contain the same value +// 3. if nilVal != nil +// 3.1 first is not nil and *nilVal equals to *first +// 3.2 second is not nil and *nilVal equals to *second +func compare[C comparable](first *C, second *C, nilVal *C) bool { + if first == second { + return true + } + + if first != nil && second != nil { + return *first == *second + } + + if nilVal != nil { + if first != nil && *first == *nilVal { + return true + } + if second != nil && *second == *nilVal { + return true + } + } + return false +}