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:
		
							
								
								
									
										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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user