mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 12:32:03 +00:00
pkg/proxy/util/nfacct: utility to interact with nfacct subsystem
nfacct is netfilter's accounting subsystem. This utility allows interactions with the subsystem using lower level netlink API. Signed-off-by: Daman Arora <aroradaman@gmail.com>
This commit is contained in:
parent
7b73ee018c
commit
6b5291654f
2
go.mod
2
go.mod
@ -60,6 +60,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
github.com/vishvananda/netns v0.0.4
|
||||
go.etcd.io/etcd/api/v3 v3.5.13
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13
|
||||
go.etcd.io/etcd/client/v3 v3.5.13
|
||||
@ -202,7 +203,6 @@ require (
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
|
83
pkg/proxy/util/nfacct/handler.go
Normal file
83
pkg/proxy/util/nfacct/handler.go
Normal file
@ -0,0 +1,83 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
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 nfacct
|
||||
|
||||
import (
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"github.com/vishvananda/netns"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// handler is an injectable interface for creating netlink request.
|
||||
type handler interface {
|
||||
newRequest(cmd int, flags uint16) request
|
||||
}
|
||||
|
||||
// request is an injectable interface representing a netlink request.
|
||||
type request interface {
|
||||
Serialize() []byte
|
||||
AddData(data nl.NetlinkRequestData)
|
||||
AddRawData(data []byte)
|
||||
Execute(sockType int, resType uint16) ([][]byte, error)
|
||||
}
|
||||
|
||||
// netlinkHandler is an implementation of the handler interface. It maintains a netlink socket
|
||||
// for communication with the NFAcct subsystem.
|
||||
type netlinkHandler struct {
|
||||
socket *nl.NetlinkSocket
|
||||
}
|
||||
|
||||
// newNetlinkHandler initializes a netlink socket in the current network namespace and returns
|
||||
// an instance of netlinkHandler with the initialized socket.
|
||||
func newNetlinkHandler() (handler, error) {
|
||||
socket, err := nl.GetNetlinkSocketAt(netns.None(), netns.None(), unix.NETLINK_NETFILTER)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &netlinkHandler{socket: socket}, nil
|
||||
}
|
||||
|
||||
// newRequest creates a netlink request tailored for the NFAcct subsystem encapsulating the
|
||||
// specified cmd and flags. It incorporates the netlink header and netfilter generic header
|
||||
// into the resulting request.
|
||||
func (n *netlinkHandler) newRequest(cmd int, flags uint16) request {
|
||||
req := &nl.NetlinkRequest{
|
||||
// netlink message header
|
||||
// (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netlink.h#L44-L58)
|
||||
NlMsghdr: unix.NlMsghdr{
|
||||
Len: uint32(unix.SizeofNlMsghdr),
|
||||
Type: uint16(cmd | (unix.NFNL_SUBSYS_ACCT << 8)),
|
||||
Flags: flags,
|
||||
},
|
||||
Sockets: map[int]*nl.SocketHandle{
|
||||
unix.NETLINK_NETFILTER: {Socket: n.socket},
|
||||
},
|
||||
Data: []nl.NetlinkRequestData{
|
||||
// netfilter generic message
|
||||
// (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netfilter/nfnetlink.h#L32-L38)
|
||||
&nl.Nfgenmsg{
|
||||
NfgenFamily: uint8(unix.AF_NETLINK),
|
||||
Version: nl.NFNETLINK_V0,
|
||||
ResId: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
return req
|
||||
}
|
36
pkg/proxy/util/nfacct/nfacct.go
Normal file
36
pkg/proxy/util/nfacct/nfacct.go
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
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 nfacct
|
||||
|
||||
// Counter represents a nfacct accounting object.
|
||||
type Counter struct {
|
||||
Name string
|
||||
Packets uint64
|
||||
Bytes uint64
|
||||
}
|
||||
|
||||
// Interface is an injectable interface for running nfacct commands.
|
||||
type Interface interface {
|
||||
// Ensure checks the existence of a nfacct counter with the provided name and creates it if absent.
|
||||
Ensure(name string) error
|
||||
// Add creates a nfacct counter with the given name, returning an error if it already exists.
|
||||
Add(name string) error
|
||||
// Get retrieves the nfacct counter with the specified name, returning an error if it doesn't exist.
|
||||
Get(name string) (*Counter, error)
|
||||
// List retrieves all nfacct counters.
|
||||
List() ([]*Counter, error)
|
||||
}
|
307
pkg/proxy/util/nfacct/nfacct_linux.go
Normal file
307
pkg/proxy/util/nfacct/nfacct_linux.go
Normal file
@ -0,0 +1,307 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
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 nfacct
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MaxLength represents the maximum length allowed for the name in a nfacct counter.
|
||||
const MaxLength = 31
|
||||
|
||||
// nf netlink nfacct commands, these should strictly match with the ones defined in kernel headers.
|
||||
// (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netfilter/nfnetlink_acct.h#L9-L16)
|
||||
const (
|
||||
// NFNL_MSG_ACCT_NEW
|
||||
cmdNew = 0
|
||||
// NFNL_MSG_ACCT_GET
|
||||
cmdGet = 1
|
||||
)
|
||||
|
||||
// nf netlink nfacct attribute, these should strictly match with the ones defined in kernel headers.
|
||||
// (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netfilter/nfnetlink_acct.h#L24-L35)
|
||||
const (
|
||||
// NFACCT_NAME
|
||||
attrName = 1
|
||||
// NFACCT_PKTS
|
||||
attrPackets = 2
|
||||
// NFACCT_BYTES
|
||||
attrBytes = 3
|
||||
)
|
||||
|
||||
// runner implements the Interface and depends on the handler for execution.
|
||||
type runner struct {
|
||||
handler handler
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New() (Interface, error) {
|
||||
hndlr, err := newNetlinkHandler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newInternal(hndlr)
|
||||
}
|
||||
|
||||
// newInternal returns a new Interface with the given handler.
|
||||
func newInternal(hndlr handler) (Interface, error) {
|
||||
return &runner{handler: hndlr}, nil
|
||||
|
||||
}
|
||||
|
||||
// Ensure is part of the interface.
|
||||
func (r *runner) Ensure(name string) error {
|
||||
counter, err := r.Get(name)
|
||||
if counter != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil && errors.Is(err, ErrObjectNotFound) {
|
||||
return handleError(r.Add(name))
|
||||
} else if err != nil {
|
||||
return handleError(err)
|
||||
} else {
|
||||
return ErrUnexpected
|
||||
}
|
||||
}
|
||||
|
||||
// Add is part of the interface.
|
||||
func (r *runner) Add(name string) error {
|
||||
if name == "" {
|
||||
return ErrEmptyName
|
||||
}
|
||||
if len(name) > MaxLength {
|
||||
return ErrNameExceedsMaxLength
|
||||
}
|
||||
|
||||
req := r.handler.newRequest(cmdNew, unix.NLM_F_REQUEST|unix.NLM_F_CREATE|unix.NLM_F_ACK)
|
||||
req.AddData(nl.NewRtAttr(attrName, nl.ZeroTerminated(name)))
|
||||
_, err := req.Execute(unix.NETLINK_NETFILTER, 0)
|
||||
if err != nil {
|
||||
return handleError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get is part of the interface.
|
||||
func (r *runner) Get(name string) (*Counter, error) {
|
||||
if len(name) > MaxLength {
|
||||
return nil, ErrNameExceedsMaxLength
|
||||
}
|
||||
|
||||
req := r.handler.newRequest(cmdGet, unix.NLM_F_REQUEST|unix.NLM_F_ACK)
|
||||
req.AddData(nl.NewRtAttr(attrName, nl.ZeroTerminated(name)))
|
||||
msgs, err := req.Execute(unix.NETLINK_NETFILTER, 0)
|
||||
if err != nil {
|
||||
return nil, handleError(err)
|
||||
}
|
||||
|
||||
var counter *Counter
|
||||
for _, msg := range msgs {
|
||||
counter, err = decode(msg, true)
|
||||
if err != nil {
|
||||
return nil, handleError(err)
|
||||
}
|
||||
}
|
||||
return counter, nil
|
||||
}
|
||||
|
||||
// List is part of the interface.
|
||||
func (r *runner) List() ([]*Counter, error) {
|
||||
req := r.handler.newRequest(cmdGet, unix.NLM_F_REQUEST|unix.NLM_F_DUMP)
|
||||
msgs, err := req.Execute(unix.NETLINK_NETFILTER, 0)
|
||||
if err != nil {
|
||||
return nil, handleError(err)
|
||||
}
|
||||
|
||||
counters := make([]*Counter, 0)
|
||||
for _, msg := range msgs {
|
||||
counter, err := decode(msg, true)
|
||||
if err != nil {
|
||||
return nil, handleError(err)
|
||||
}
|
||||
counters = append(counters, counter)
|
||||
}
|
||||
return counters, nil
|
||||
}
|
||||
|
||||
var ErrObjectNotFound = errors.New("object not found")
|
||||
var ErrObjectAlreadyExists = errors.New("object already exists")
|
||||
var ErrNameExceedsMaxLength = fmt.Errorf("object name exceeds the maximum allowed length of %d characters", MaxLength)
|
||||
var ErrEmptyName = errors.New("object name cannot be empty")
|
||||
var ErrUnexpected = errors.New("unexpected error")
|
||||
|
||||
func handleError(err error) error {
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case errors.Is(err, syscall.ENOENT):
|
||||
return ErrObjectNotFound
|
||||
case errors.Is(err, syscall.EBUSY):
|
||||
return ErrObjectAlreadyExists
|
||||
default:
|
||||
return fmt.Errorf("%s: %s", ErrUnexpected.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// decode function processes a byte stream, requiring the 'strict' parameter to be true in production and
|
||||
// false only for testing purposes. If in strict mode and any of the relevant attributes (name, packets, or bytes)
|
||||
// have not been processed, an error is returned indicating a failure to decode the byte stream.
|
||||
//
|
||||
// Parse the netlink message as per the documentation outlined in:
|
||||
// https://docs.kernel.org/userspace-api/netlink/intro.html
|
||||
//
|
||||
// Message Components:
|
||||
// - netfilter generic message [4 bytes]
|
||||
// struct nfgenmsg (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netfilter/nfnetlink.h#L32-L38)
|
||||
// - attributes [variable-sized, must align to 4 bytes from the start of attribute]
|
||||
// struct nlattr (definition: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netlink.h#L220-L232)
|
||||
//
|
||||
// Attribute Components:
|
||||
// - length [2 bytes]
|
||||
// length includes bytes for defining the length itself, bytes for defining the type,
|
||||
// and the actual bytes of data without any padding.
|
||||
// - type [2 bytes]
|
||||
// - data [variable-sized]
|
||||
// - padding [optional]
|
||||
//
|
||||
// Example. Counter{Name: "dummy-metric", Packets: 123, Bytes: 54321} in netlink message:
|
||||
//
|
||||
// struct nfgenmsg{
|
||||
// __u8 nfgen_family: AF_NETLINK
|
||||
// __u8 version: nl.NFNETLINK_V0
|
||||
// __be16 res_id: nl.NFNETLINK_V0
|
||||
// }
|
||||
//
|
||||
// struct nlattr{
|
||||
// __u16 nla_len: 13
|
||||
// __u16 nla_type: NFACCT_NAME
|
||||
// char data: dummy-metric\0
|
||||
// }
|
||||
//
|
||||
// (padding:)
|
||||
// data: \0\0\0
|
||||
//
|
||||
// struct nlattr{
|
||||
// __u16 nla_len: 12
|
||||
// __u16 nla_type: NFACCT_PKTS
|
||||
// __u64: data: 123
|
||||
// }
|
||||
//
|
||||
// struct nlattr{
|
||||
// __u16 nla_len: 12
|
||||
// __u16 nla_type: NFACCT_BYTES
|
||||
// __u64: data: 54321
|
||||
// }
|
||||
func decode(msg []byte, strict bool) (*Counter, error) {
|
||||
counter := &Counter{}
|
||||
reader := bytes.NewReader(msg)
|
||||
// skip the first 4 bytes (netfilter generic message).
|
||||
if _, err := reader.Seek(nl.SizeofNfgenmsg, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// attrsProcessed tracks the number of processed attributes.
|
||||
var attrsProcessed int
|
||||
|
||||
// length and type of netlink attribute.
|
||||
var length, attrType uint16
|
||||
|
||||
// now we are just left with the attributes(struct nlattr) after skipping netlink generic
|
||||
// message; we iterate over all the attributes one by one to construct our Counter object.
|
||||
for reader.Len() > 0 {
|
||||
// netlink attributes are in LTV(length, type and value) format.
|
||||
|
||||
// STEP 1. parse length [2 bytes]
|
||||
if err := binary.Read(reader, binary.NativeEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// STEP 2. parse type [2 bytes]
|
||||
if err := binary.Read(reader, binary.NativeEndian, &attrType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// STEP 3. adjust the length
|
||||
// adjust the length to consider the header bytes read in step(1) and step(2); the actual
|
||||
// length of data will be 4 bytes less than the originally read value.
|
||||
length -= 4
|
||||
|
||||
// STEP 4. parse value [variable sized]
|
||||
// The value can assume any data-type. To read it into the appropriate data structure, we need
|
||||
// to know the data type in advance. We achieve this by switching on the attribute-type, and we
|
||||
// allocate the 'adjusted length' bytes (as done in step(3)) for the data-structure.
|
||||
switch attrType {
|
||||
case attrName:
|
||||
// NFACCT_NAME has a variable size, so we allocate a slice of 'adjusted length' bytes
|
||||
// and read the next 'adjusted length' bytes into this slice.
|
||||
data := make([]byte, length)
|
||||
if err := binary.Read(reader, binary.NativeEndian, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counter.Name = string(data[:length-1])
|
||||
attrsProcessed++
|
||||
case attrPackets:
|
||||
// NFACCT_PKTS holds 8 bytes of data, so we directly read the next 8 bytes into a 64-bit
|
||||
// unsigned integer (counter.Packets).
|
||||
if err := binary.Read(reader, binary.BigEndian, &counter.Packets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrsProcessed++
|
||||
case attrBytes:
|
||||
// NFACCT_BYTES holds 8 bytes of data, so we directly read the next 8 bytes into a 64-bit
|
||||
// unsigned integer (counter.Bytes).
|
||||
if err := binary.Read(reader, binary.BigEndian, &counter.Bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrsProcessed++
|
||||
default:
|
||||
// skip the data part for unknown attribute
|
||||
if _, err := reader.Seek(int64(length), io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Move past the padding to align with the fixed-size length, always a multiple of 4.
|
||||
// If, for instance, the length is 9, skip 3 bytes of padding to reach the start of
|
||||
// the next attribute.
|
||||
// (ref: https://github.com/torvalds/linux/blob/v6.7/include/uapi/linux/netlink.h#L220-L227)
|
||||
if length%4 != 0 {
|
||||
padding := 4 - length%4
|
||||
if _, err := reader.Seek(int64(padding), io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return err if any of the required attribute is not processed.
|
||||
if strict && attrsProcessed != 3 {
|
||||
return nil, errors.New("failed to decode byte-stream")
|
||||
}
|
||||
return counter, nil
|
||||
}
|
628
pkg/proxy/util/nfacct/nfacct_linux_test.go
Normal file
628
pkg/proxy/util/nfacct/nfacct_linux_test.go
Normal file
@ -0,0 +1,628 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
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 nfacct
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// fakeHandler is a mock implementation of the handler interface, designed for testing.
|
||||
type fakeHandler struct {
|
||||
// requests stores instances of fakeRequest, capturing new requests.
|
||||
requests []*fakeRequest
|
||||
// responses holds responses for the subsequent fakeRequest.Execute calls.
|
||||
responses [][][]byte
|
||||
// errs holds errors for the subsequent fakeRequest.Execute calls.
|
||||
errs []error
|
||||
}
|
||||
|
||||
// newRequest creates a request object with the given cmd, flags, predefined response and error.
|
||||
// It additionally records the created request object.
|
||||
func (fh *fakeHandler) newRequest(cmd int, flags uint16) request {
|
||||
var response [][]byte
|
||||
if fh.responses != nil && len(fh.responses) > 0 {
|
||||
response = fh.responses[0]
|
||||
// remove the response from the list of predefined responses and add it to request object for mocking.
|
||||
fh.responses = fh.responses[1:]
|
||||
}
|
||||
|
||||
var err error
|
||||
if fh.errs != nil && len(fh.errs) > 0 {
|
||||
err = fh.errs[0]
|
||||
// remove the error from the list of predefined errors and add it to request object for mocking.
|
||||
fh.errs = fh.errs[1:]
|
||||
}
|
||||
|
||||
req := &fakeRequest{cmd: cmd, flags: flags, response: response, err: err}
|
||||
fh.requests = append(fh.requests, req)
|
||||
return req
|
||||
}
|
||||
|
||||
// fakeRequest records information about the cmd and flags used when creating a new request,
|
||||
// maintains a list for netlink attributes, and stores a predefined response and an optional
|
||||
// error for subsequent execution.
|
||||
type fakeRequest struct {
|
||||
// cmd and flags which were used to create the request.
|
||||
cmd int
|
||||
flags uint16
|
||||
|
||||
// data holds netlink attributes.
|
||||
data []nl.NetlinkRequestData
|
||||
|
||||
// response and err are the predefined output of execution.
|
||||
response [][]byte
|
||||
err error
|
||||
}
|
||||
|
||||
// Serialize is part of request interface.
|
||||
func (fr *fakeRequest) Serialize() []byte { return nil }
|
||||
|
||||
// AddData is part of request interface.
|
||||
func (fr *fakeRequest) AddData(data nl.NetlinkRequestData) {
|
||||
fr.data = append(fr.data, data)
|
||||
}
|
||||
|
||||
// AddRawData is part of request interface.
|
||||
func (fr *fakeRequest) AddRawData(_ []byte) {}
|
||||
|
||||
// Execute is part of request interface.
|
||||
func (fr *fakeRequest) Execute(_ int, _ uint16) ([][]byte, error) {
|
||||
return fr.response, fr.err
|
||||
}
|
||||
|
||||
func TestRunner_Add(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
counterName string
|
||||
handler *fakeHandler
|
||||
err error
|
||||
netlinkCalls int
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
counterName: "metric-1",
|
||||
handler: &fakeHandler{},
|
||||
// expected calls: NFNL_MSG_ACCT_NEW
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "add duplicate counter",
|
||||
counterName: "metric-2",
|
||||
handler: &fakeHandler{
|
||||
errs: []error{syscall.EBUSY},
|
||||
},
|
||||
err: ErrObjectAlreadyExists,
|
||||
// expected calls: NFNL_MSG_ACCT_NEW
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "insufficient privilege",
|
||||
counterName: "metric-2",
|
||||
handler: &fakeHandler{
|
||||
errs: []error{syscall.EPERM},
|
||||
},
|
||||
err: ErrUnexpected,
|
||||
// expected calls: NFNL_MSG_ACCT_NEW
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "exceeds max length",
|
||||
counterName: "this-is-a-string-with-more-than-32-characters",
|
||||
handler: &fakeHandler{},
|
||||
err: ErrNameExceedsMaxLength,
|
||||
// expected calls: zero (the error should be returned by this library)
|
||||
netlinkCalls: 0,
|
||||
},
|
||||
{
|
||||
name: "falls below min length",
|
||||
counterName: "",
|
||||
handler: &fakeHandler{},
|
||||
err: ErrEmptyName,
|
||||
// expected calls: zero (the error should be returned by this library)
|
||||
netlinkCalls: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rnr, err := newInternal(tc.handler)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = rnr.Add(tc.counterName)
|
||||
if tc.err != nil {
|
||||
assert.ErrorContains(t, err, tc.err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// validate number of requests
|
||||
assert.Equal(t, tc.netlinkCalls, len(tc.handler.requests))
|
||||
|
||||
if tc.netlinkCalls > 0 {
|
||||
// validate request
|
||||
assert.Equal(t, cmdNew, tc.handler.requests[0].cmd)
|
||||
assert.Equal(t, uint16(unix.NLM_F_REQUEST|unix.NLM_F_CREATE|unix.NLM_F_ACK), tc.handler.requests[0].flags)
|
||||
|
||||
// validate attribute(NFACCT_NAME)
|
||||
assert.Equal(t, 1, len(tc.handler.requests[0].data))
|
||||
assert.Equal(t,
|
||||
tc.handler.requests[0].data[0].Serialize(),
|
||||
nl.NewRtAttr(attrName, nl.ZeroTerminated(tc.counterName)).Serialize(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestRunner_Get(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
counterName string
|
||||
counter *Counter
|
||||
handler *fakeHandler
|
||||
netlinkCalls int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "valid with padding",
|
||||
counterName: "metric-1",
|
||||
counter: &Counter{Name: "metric-1", Packets: 43214632547, Bytes: 2548697864523217},
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x0f, 0xca, 0xf6, 0x63,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0e, 0x06,
|
||||
0xf6, 0xda, 0xcd, 0xd1, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "valid without padding",
|
||||
counterName: "metrics",
|
||||
counter: &Counter{Name: "metrics", Packets: 12, Bytes: 503},
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x00,
|
||||
0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf7,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "missing netfilter generic header",
|
||||
counterName: "metrics",
|
||||
counter: nil,
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x01, 0x00, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
|
||||
0x73, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xf7, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "incorrect padding",
|
||||
counterName: "metric-1",
|
||||
counter: nil,
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0a, 0x0f, 0xca, 0xf6, 0x63, 0x0c, 0x00, 0x03,
|
||||
0x00, 0x00, 0x09, 0x0e, 0x06, 0xf6, 0xda, 0xcd,
|
||||
0xd1, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "missing bytes attribute",
|
||||
counterName: "metric-1",
|
||||
counter: nil,
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x0f, 0xca, 0xf6, 0x63,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "missing packets attribute",
|
||||
counterName: "metric-1",
|
||||
counter: nil,
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x09, 0x0e, 0x06, 0xf6, 0xda, 0xcd, 0xd1,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "only name attribute",
|
||||
counterName: "metric-1",
|
||||
counter: nil,
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}}},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "get non-existent counter",
|
||||
counterName: "metric-2",
|
||||
handler: &fakeHandler{
|
||||
errs: []error{syscall.ENOENT},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrObjectNotFound,
|
||||
},
|
||||
{
|
||||
name: "unexpected error",
|
||||
counterName: "metric-2",
|
||||
handler: &fakeHandler{
|
||||
errs: []error{syscall.EMFILE},
|
||||
},
|
||||
// expected calls: NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
err: ErrUnexpected,
|
||||
},
|
||||
{
|
||||
name: "exceeds max length",
|
||||
counterName: "this-is-a-string-with-more-than-32-characters",
|
||||
handler: &fakeHandler{},
|
||||
// expected calls: zero (the error should be returned by this library)
|
||||
netlinkCalls: 0,
|
||||
err: ErrNameExceedsMaxLength,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rnr, err := newInternal(tc.handler)
|
||||
assert.NoError(t, err)
|
||||
|
||||
counter, err := rnr.Get(tc.counterName)
|
||||
|
||||
// validate number of requests
|
||||
assert.Equal(t, tc.netlinkCalls, len(tc.handler.requests))
|
||||
if tc.netlinkCalls > 0 {
|
||||
// validate request
|
||||
assert.Equal(t, cmdGet, tc.handler.requests[0].cmd)
|
||||
assert.Equal(t, uint16(unix.NLM_F_REQUEST|unix.NLM_F_ACK), tc.handler.requests[0].flags)
|
||||
|
||||
// validate attribute(NFACCT_NAME)
|
||||
assert.Equal(t, 1, len(tc.handler.requests[0].data))
|
||||
assert.Equal(t,
|
||||
tc.handler.requests[0].data[0].Serialize(),
|
||||
nl.NewRtAttr(attrName, nl.ZeroTerminated(tc.counterName)).Serialize())
|
||||
|
||||
// validate response
|
||||
if tc.err != nil {
|
||||
assert.Nil(t, counter)
|
||||
assert.ErrorContains(t, err, tc.err.Error())
|
||||
} else {
|
||||
assert.NotNil(t, counter)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.counter.Name, counter.Name)
|
||||
assert.Equal(t, tc.counter.Packets, counter.Packets)
|
||||
assert.Equal(t, tc.counter.Bytes, counter.Bytes)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_Ensure(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
counterName string
|
||||
netlinkCalls int
|
||||
handler *fakeHandler
|
||||
}{
|
||||
{
|
||||
name: "counter doesnt exist",
|
||||
counterName: "ct_established_accepted_packets",
|
||||
handler: &fakeHandler{
|
||||
errs: []error{syscall.ENOENT},
|
||||
},
|
||||
// expected calls - NFNL_MSG_ACCT_GET + NFNL_MSG_ACCT_NEW
|
||||
netlinkCalls: 2,
|
||||
},
|
||||
{
|
||||
name: "counter already exists",
|
||||
counterName: "ct_invalid_dropped_packets",
|
||||
handler: &fakeHandler{
|
||||
responses: [][][]byte{{{
|
||||
0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00,
|
||||
0x63, 0x74, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c,
|
||||
0x69, 0x64, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70,
|
||||
0x65, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65,
|
||||
0x74, 0x73, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x02, 0x68, 0xf3, 0x16, 0x58, 0x0e, 0x63,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x12, 0xc5, 0x37, 0xdf,
|
||||
0xe5, 0xa1, 0xcd, 0xd1, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
}}},
|
||||
},
|
||||
// expected calls - NFNL_MSG_ACCT_GET
|
||||
netlinkCalls: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rnr, err := newInternal(tc.handler)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = rnr.Ensure(tc.counterName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// validate number of netlink requests
|
||||
assert.Equal(t, tc.netlinkCalls, len(tc.handler.requests))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRunner_List(t *testing.T) {
|
||||
hndlr := &fakeHandler{
|
||||
responses: [][][]byte{{
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
|
||||
0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x2d, 0x74,
|
||||
0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x74, 0x72,
|
||||
0x69, 0x63, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x08, 0x60, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x01, 0x00,
|
||||
0x6e, 0x66, 0x61, 0x63, 0x63, 0x74, 0x2d, 0x6c,
|
||||
0x69, 0x73, 0x74, 0x2d, 0x74, 0x65, 0x73, 0x74,
|
||||
0x2d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x00,
|
||||
0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x0b, 0x96, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xe6, 0xc5, 0x74,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00,
|
||||
0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x86, 0x8d,
|
||||
0x44, 0xeb, 0xc7, 0x02, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x6e, 0x5f, 0xe2, 0x89, 0x69, 0x3f, 0x9e,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00,
|
||||
0x63, 0x74, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c,
|
||||
0x69, 0x64, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70,
|
||||
0x65, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65,
|
||||
0x74, 0x73, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x01, 0x1e, 0x6e, 0xac, 0x20, 0xe9,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x6d,
|
||||
0x30, 0x11, 0x8a, 0xec, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
expected := []*Counter{
|
||||
{Name: "random-test-metric", Packets: 134, Bytes: 2144},
|
||||
{Name: "nfacct-list-test-metric", Packets: 134038, Bytes: 31901044},
|
||||
{Name: "test", Packets: 147941304813314, Bytes: 31067674010795934},
|
||||
{Name: "ct_invalid_dropped_packets", Packets: 1230217421033, Bytes: 14762609052396},
|
||||
}
|
||||
|
||||
rnr, err := newInternal(hndlr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
counters, err := rnr.List()
|
||||
|
||||
// validate request(NFNL_MSG_ACCT_GET)
|
||||
assert.Equal(t, 1, len(hndlr.requests))
|
||||
assert.Equal(t, cmdGet, hndlr.requests[0].cmd)
|
||||
assert.Equal(t, uint16(unix.NLM_F_REQUEST|unix.NLM_F_DUMP), hndlr.requests[0].flags)
|
||||
|
||||
// validate attributes
|
||||
assert.Equal(t, 0, len(hndlr.requests[0].data))
|
||||
|
||||
// validate response
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, counters)
|
||||
assert.Equal(t, len(expected), len(counters))
|
||||
for i := 0; i < len(expected); i++ {
|
||||
assert.Equal(t, expected[i].Name, counters[i].Name)
|
||||
assert.Equal(t, expected[i].Packets, counters[i].Packets)
|
||||
assert.Equal(t, expected[i].Bytes, counters[i].Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
msg []byte
|
||||
expected *Counter
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x0f, 0xca, 0xf6, 0x63,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0e, 0x06,
|
||||
0xf6, 0xda, 0xcd, 0xd1, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
expected: &Counter{Name: "metric-1", Packets: 43214632547, Bytes: 2548697864523217},
|
||||
},
|
||||
{
|
||||
name: "attribute name missing",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0b, 0x96,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xe6, 0xc5, 0x74, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
expected: &Counter{Packets: 134038, Bytes: 31901044},
|
||||
},
|
||||
{
|
||||
name: "attribute packets missing",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00,
|
||||
0x63, 0x74, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c,
|
||||
0x69, 0x64, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70,
|
||||
0x65, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65,
|
||||
0x74, 0x73, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x60,
|
||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
expected: &Counter{Name: "ct_invalid_dropped_packets", Bytes: 2144},
|
||||
},
|
||||
{
|
||||
name: "attribute bytes missing",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
|
||||
0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x2d, 0x74,
|
||||
0x65, 0x73, 0x74, 0x2d, 0x6d, 0x65, 0x74, 0x72,
|
||||
0x69, 0x63, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf7,
|
||||
},
|
||||
expected: &Counter{Name: "random-test-metric", Packets: 503},
|
||||
},
|
||||
{
|
||||
name: "attribute packets and bytes missing",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00,
|
||||
0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: &Counter{Name: "test"},
|
||||
},
|
||||
{
|
||||
name: "only netfilter generic header present",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: &Counter{},
|
||||
},
|
||||
{
|
||||
name: "only packets attribute",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0b, 0x96,
|
||||
},
|
||||
expected: &Counter{Packets: 134038},
|
||||
},
|
||||
{
|
||||
name: "only bytes attribute",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
|
||||
},
|
||||
expected: &Counter{Bytes: 12},
|
||||
},
|
||||
{
|
||||
name: "new attribute in the beginning",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x0f, 0xca, 0xf6, 0x63,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0e, 0x06,
|
||||
0xf6, 0xda, 0xcd, 0xd1, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
expected: &Counter{Name: "metric-1", Packets: 43214632547, Bytes: 2548697864523217},
|
||||
},
|
||||
{
|
||||
name: "new attribute in the end",
|
||||
msg: []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x0d, 0x00, 0x01, 0x00,
|
||||
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2d, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x0f, 0xca, 0xf6, 0x63,
|
||||
0x0c, 0x00, 0x03, 0x00, 0x00, 0x09, 0x0e, 0x06,
|
||||
0xf6, 0xda, 0xcd, 0xd1, 0x08, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x01,
|
||||
0x02, 0x03, 0x0e, 0x3f, 0xf6, 0xda, 0xcd, 0xd1,
|
||||
},
|
||||
expected: &Counter{Name: "metric-1", Packets: 43214632547, Bytes: 2548697864523217},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
counter, err := decode(tc.msg, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, counter)
|
||||
|
||||
assert.Equal(t, tc.expected.Name, counter.Name)
|
||||
assert.Equal(t, tc.expected.Packets, counter.Packets)
|
||||
assert.Equal(t, tc.expected.Bytes, counter.Bytes)
|
||||
})
|
||||
}
|
||||
}
|
31
pkg/proxy/util/nfacct/nfacct_others.go
Normal file
31
pkg/proxy/util/nfacct/nfacct_others.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
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 nfacct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var unsupportedError = fmt.Errorf(runtime.GOOS + "/" + runtime.GOARCH + "is unsupported")
|
||||
|
||||
func New() (Interface, error) {
|
||||
return nil, unsupportedError
|
||||
}
|
Loading…
Reference in New Issue
Block a user