mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-14 06:15:45 +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/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/vishvananda/netlink v1.1.0
|
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/api/v3 v3.5.13
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13
|
go.etcd.io/etcd/client/pkg/v3 v3.5.13
|
||||||
go.etcd.io/etcd/client/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/stoewer/go-strcase v1.2.0 // indirect
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // 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/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/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||||
github.com/xlab/treeprint v1.2.0 // 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