Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Remove expensive net.Interface lookups upon link initialization #51

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions capture/afpacket/afpacket/afpacket.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ func NewSource(iface string, options ...Option) (*Source, error) {
func NewSourceFromLink(link *link.Link, options ...Option) (*Source, error) {

// Fail if link is not up
if !link.IsUp() {
if isUp, err := link.IsUp(); err != nil || !isUp {
return nil, fmt.Errorf("link %s is not up", link.Name)
}

// Define new source
src := &Source{
eventHandler: new(event.Handler),
snapLen: DefaultSnapLen,
ipLayerOffset: link.Type.IpHeaderOffset(),
ipLayerOffset: link.Type.IPHeaderOffset(),
link: link,
Mutex: sync.Mutex{},
}
Expand Down
18 changes: 3 additions & 15 deletions capture/afpacket/afpacket/afpacket_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package afpacket
import (
"errors"
"io"
"net"
"sync"
"time"

"github.com/fako1024/slimcap/capture"
Expand Down Expand Up @@ -77,19 +75,9 @@ func NewMockSource(iface string, options ...Option) (*MockSource, error) {

src := &Source{
snapLen: DefaultSnapLen,
ipLayerOffset: link.TypeEthernet.IpHeaderOffset(),
link: &link.Link{
Type: link.TypeEthernet,
Interface: &net.Interface{
Index: 1,
MTU: 1500,
Name: iface,
HardwareAddr: []byte{},
Flags: net.FlagUp,
},
},
Mutex: sync.Mutex{},
eventHandler: mockHandler,
ipLayerOffset: link.TypeEthernet.IPHeaderOffset(),
link: &link.EmptyEthernetLink,
eventHandler: mockHandler,
}

for _, opt := range options {
Expand Down
2 changes: 1 addition & 1 deletion capture/afpacket/afpacket/afpacket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func TestCaptureMethods(t *testing.T) {
t.Run("NextPacketFn", func(t *testing.T) {
testCaptureMethods(t, func(t *testing.T, src *MockSource, i, j uint16) {
err := src.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) error {
require.Equal(t, src.link.Type.IpHeaderOffset(), ipLayerOffset)
require.Equal(t, src.link.Type.IPHeaderOffset(), ipLayerOffset)
require.Equal(t, uint32(i+j), totalLen)
require.Equal(t, byte(i+j)%5, pktType)
require.Equal(t, fmt.Sprintf("1.2.3.%d:%d => 4.5.6.%d:%d (proto: %d)", i%254+1, i, j%254+1, j, 6), capture.IPLayer(payload[ipLayerOffset:]).String())
Expand Down
4 changes: 2 additions & 2 deletions capture/afpacket/afring/afring.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func NewSource(iface string, options ...Option) (*Source, error) {
func NewSourceFromLink(link *link.Link, options ...Option) (*Source, error) {

// Fail if link is not up
if !link.IsUp() {
if isUp, err := link.IsUp(); err != nil || !isUp {
return nil, fmt.Errorf("link %s is not up", link.Name)
}

Expand All @@ -77,7 +77,7 @@ func NewSourceFromLink(link *link.Link, options ...Option) (*Source, error) {
snapLen: DefaultSnapLen,
blockSize: tPacketDefaultBlockSize,
nBlocks: tPacketDefaultBlockNr,
ipLayerOffset: link.Type.IpHeaderOffset(),
ipLayerOffset: link.Type.IPHeaderOffset(),
link: link,
Mutex: sync.Mutex{},
}
Expand Down
16 changes: 2 additions & 14 deletions capture/afpacket/afring/afring_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package afring
import (
"errors"
"io"
"net"
"sync"
"sync/atomic"
"time"
"unsafe"
Expand Down Expand Up @@ -54,18 +52,8 @@ func NewMockSource(iface string, options ...Option) (*MockSource, error) {
blockSize: tPacketDefaultBlockSize,
nBlocks: tPacketDefaultBlockNr,

ipLayerOffset: link.TypeEthernet.IpHeaderOffset(),
link: &link.Link{
Type: link.TypeEthernet,
Interface: &net.Interface{
Index: 1,
MTU: 1500,
Name: iface,
HardwareAddr: []byte{},
Flags: net.FlagUp,
},
},
Mutex: sync.Mutex{},
ipLayerOffset: link.TypeEthernet.IPHeaderOffset(),
link: &link.EmptyEthernetLink,
ringBuffer: ringBuffer{
curTPacketHeader: new(tPacketHeader),
},
Expand Down
2 changes: 1 addition & 1 deletion capture/afpacket/afring/afring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ func TestCaptureMethods(t *testing.T) {
t.Run("NextPacketFn", func(t *testing.T) {
testCaptureMethods(t, func(t *testing.T, src *MockSource, i, j uint16) {
err := src.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) error {
require.Equal(t, src.link.Type.IpHeaderOffset(), ipLayerOffset)
require.Equal(t, src.link.Type.IPHeaderOffset(), ipLayerOffset)
require.Equal(t, int(i*1000+j), int(totalLen))
require.Equal(t, byte(i+j)%5, pktType)
require.Equal(t, fmt.Sprintf("1.2.3.%d:%d => 4.5.6.%d:%d (proto: %d)", i%254+1, i, j%254+1, j, 6), capture.IPLayer(payload[ipLayerOffset:]).String())
Expand Down
10 changes: 4 additions & 6 deletions capture/pcap/pcap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
"unsafe"
Expand Down Expand Up @@ -69,13 +68,12 @@ func NewSource(iface string, r io.Reader) (*Source, error) {

// Populate (fake) link information
obj.link = &link.Link{
Type: link.Type(obj.header.Network),
Interface: &net.Interface{
Name: iface,
Flags: net.FlagUp,
Interface: link.Interface{
Name: iface,
Type: link.Type(obj.header.Network),
},
}
obj.ipLayerOffset = obj.link.Type.IpHeaderOffset()
obj.ipLayerOffset = obj.link.Type.IPHeaderOffset()

return &obj, nil
}
Expand Down
12 changes: 5 additions & 7 deletions capture/pcap/pcap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"embed"
"io"
"io/fs"
"net"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -71,10 +70,9 @@ func TestReader(t *testing.T) {
require.Nil(t, err)

require.Equal(t, &link.Link{
Type: link.TypeEthernet,
Interface: &net.Interface{
Name: "pcap",
Flags: net.FlagUp,
Interface: link.Interface{
fako1024 marked this conversation as resolved.
Show resolved Hide resolved
Name: "pcap",
Type: link.TypeEthernet,
},
}, src.Link())

Expand All @@ -99,7 +97,7 @@ func TestReader(t *testing.T) {
require.Nil(t, payload)
require.Zero(t, totalLen)
require.Equal(t, capture.PacketUnknown, pktType)
require.Equal(t, src.Link().Type.IpHeaderOffset(), ipLayerOffset)
require.Equal(t, src.Link().Type.IPHeaderOffset(), ipLayerOffset)
return nil
})
require.ErrorIs(t, err, io.EOF)
Expand Down Expand Up @@ -232,7 +230,7 @@ func TestCaptureMethods(t *testing.T) {
t.Run("NextPacketFn", func(t *testing.T) {
testCaptureMethods(t, func(t *testing.T, src *Source) {
err := src.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) error {
require.Equal(t, src.link.Type.IpHeaderOffset(), ipLayerOffset)
require.Equal(t, src.link.Type.IPHeaderOffset(), ipLayerOffset)
require.NotNil(t, payload)
require.Equal(t, capture.PacketUnknown, pktType)
require.NotZero(t, totalLen)
Expand Down
4 changes: 2 additions & 2 deletions examples/trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (c *Capture) Run() (err error) {
return err
}
for _, iface := range links {
logger.Infof("Found interface `%s` (idx %d), link type %d, HWAddr `%s`, flags `%s`", iface.Name, iface.Index, iface.Type, iface.HardwareAddr, iface.Flags)
logger.Infof("Found interface `%s` (idx %d), link type %d", iface.Name, iface.Index, iface.Type)
}

// construct list of skipped interfaces
Expand Down Expand Up @@ -168,7 +168,7 @@ func (c *Capture) Run() (err error) {
}
}

if !l.IsUp() {
if isUp, err := l.IsUp(); err != nil || !isUp {
logger.Warnf("skipping listener on non-up interface `%s`", l.Name)
return
}
Expand Down
29 changes: 29 additions & 0 deletions link/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package link

// Interface is the low-level representation of a network interface
type Interface struct {
Name string
Index int
Type Type
}

// NewInterface instantiates a new network interface and obtains its basic parameters
func NewInterface(name string) (iface Interface, err error) {
iface = Interface{
Name: name,
}

if iface.Index, err = getIndex(name); err != nil {
return
}
if iface.Type, err = getLinkType(name); err != nil {
return
}

return
}

// String returns the name of the network interface (Stringer interface)
func (i Interface) String() string {
return i.Name
}
109 changes: 109 additions & 0 deletions link/interface_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//go:build linux
// +build linux

package link

import (
"errors"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)

const (
netBasePath = "/sys/class/net/"
netIndexPath = "/ifindex"
netTypePath = "/type"
netFlagsPath = "/flags"
)

// ErrIndexOutOfBounds denotes the (unlikely) case of an invalid index being outside the range of an int
var ErrIndexOutOfBounds = errors.New("interface index out of bounds")

// Interfaces returns all host interfaces
func Interfaces() ([]Interface, error) {

linkDir, err := os.OpenFile(netBasePath, os.O_RDONLY, 0600)
if err != nil {
return nil, err
}

ifaceNames, err := linkDir.Readdirnames(-1)
if err != nil {
return nil, err
}

ifaces := make([]Interface, len(ifaceNames))
for i, ifaceName := range ifaceNames {
if ifaces[i], err = NewInterface(ifaceName); err != nil {
return nil, err
}
}

return ifaces, nil
}

// IsUp determines if an interface is currently up (at the time of the call)
func (i Interface) IsUp() (bool, error) {

data, err := os.ReadFile(filepath.Clean(netBasePath + i.Name + netFlagsPath))
if err != nil {
return false, err
}

flags, err := strconv.ParseInt(
strings.TrimSpace(string(data)), 0, 64)
if err != nil {
return false, err
}

return flags&syscall.IFF_UP != 0, nil
}

////////////////////////////////////////////////////////////////////////////////

func getIndex(name string) (int, error) {

data, err := os.ReadFile(filepath.Clean(netBasePath + name + netIndexPath))
if err != nil {
return -1, err
}

index, err := strconv.ParseInt(
strings.TrimSpace(string(data)), 0, 64)
if err != nil {
return -1, err
}

// Validate integer upper / lower bounds
if index > 0 && index <= math.MaxInt {
return int(index), nil
}

return -1, ErrIndexOutOfBounds
}

func getLinkType(name string) (Type, error) {

sysPath := netBasePath + name + netTypePath
data, err := os.ReadFile(filepath.Clean(sysPath))
if err != nil {
return -1, err
}

val, err := strconv.Atoi(
strings.TrimSpace(string(data)))
if err != nil {
return -1, err
}

if val < 0 || val > 65535 {
return -1, fmt.Errorf("invalid link type read from `%s`: %d", sysPath, val)
}

return Type(val), nil
}
Loading