You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.4 KiB
105 lines
3.4 KiB
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package tundevstats
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"os"
|
|
"sync/atomic"
|
|
"testing"
|
|
"testing/synctest"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/mdlayher/netlink"
|
|
"github.com/mdlayher/netlink/nltest"
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func Test_getIfIndex(t *testing.T) {
|
|
ifIndex, err := getIfIndex("lo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ifIndex != 1 {
|
|
// loopback ifIndex is effectively always 1 on Linux, see
|
|
// LOOPBACK_IFINDEX in the kernel (net/flow.h).
|
|
t.Fatalf("expected ifIndex of 1 for loopback, got: %d", ifIndex)
|
|
}
|
|
}
|
|
|
|
type fakeDevice struct {
|
|
name string
|
|
}
|
|
|
|
func (f *fakeDevice) File() *os.File { return nil }
|
|
func (f *fakeDevice) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { return 0, nil }
|
|
func (f *fakeDevice) Write(bufs [][]byte, offset int) (int, error) { return 0, nil }
|
|
func (f *fakeDevice) MTU() (int, error) { return 0, nil }
|
|
func (f *fakeDevice) Name() (string, error) { return f.name, nil }
|
|
func (f *fakeDevice) Events() <-chan tun.Event { return nil }
|
|
func (f *fakeDevice) Close() error { return nil }
|
|
func (f *fakeDevice) BatchSize() int { return 0 }
|
|
|
|
func Test_poller(t *testing.T) {
|
|
getTXQDropsMetric().Set(0) // reset for test count > 1
|
|
|
|
var drops atomic.Uint64
|
|
// dial is a [nltest.Func] that returns an RTM_NEWSTATS response with [drops]
|
|
// at the txDropped offset within the [rtnlLinkStats64] attribute payload.
|
|
dial := func(req []netlink.Message) ([]netlink.Message, error) {
|
|
if len(req) != 1 {
|
|
t.Fatalf("unexpected number of netlink request messages: %d", len(req))
|
|
}
|
|
if req[0].Header.Type != unix.RTM_GETSTATS {
|
|
t.Fatalf("unexpected netlink request message type: %d want: %d", req[0].Header.Type, unix.RTM_GETSTATS)
|
|
}
|
|
data := make([]byte, unsafe.Sizeof(ifStatsMsg{}))
|
|
ae := netlink.NewAttributeEncoder()
|
|
ae.Do(iflaStatsLink64, func() ([]byte, error) {
|
|
ret := make([]byte, unsafe.Sizeof(rtnlLinkStats64{}))
|
|
binary.NativeEndian.PutUint64(ret[56:], drops.Load())
|
|
return ret, nil
|
|
})
|
|
attrs, err := ae.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data = append(data, attrs...)
|
|
return []netlink.Message{
|
|
{
|
|
Header: netlink.Header{
|
|
Type: unix.RTM_NEWSTATS,
|
|
Sequence: req[0].Header.Sequence,
|
|
},
|
|
Data: data,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
lo := &fakeDevice{name: "lo"}
|
|
drops.Store(1)
|
|
synctest.Test(t, func(t *testing.T) {
|
|
closer, err := newPollerWithNetlinkDialer(lo, func(family int, config *netlink.Config) (*netlink.Conn, error) {
|
|
return nltest.Dial(dial), nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
synctest.Wait() // first poll complete, poller.run() durably blocked in select
|
|
if got := getTXQDropsMetric().Value(); got != 1 {
|
|
t.Errorf("got drops: %d want: %d", got, 1)
|
|
}
|
|
drops.Store(2) // increment drops to 2
|
|
time.Sleep(pollInterval)
|
|
synctest.Wait() // second poll complete, poller.run() durably blocked in select again
|
|
if got := getTXQDropsMetric().Value(); got != 2 {
|
|
t.Errorf("got drops: %d want: %d", got, 2)
|
|
}
|
|
closer.Close()
|
|
closer.Close() // multiple calls to Close() shouldn't panic
|
|
})
|
|
|
|
}
|
|
|