Skip to content

Commit

Permalink
Merge pull request #51 from fako1024/50-source-initialization-require…
Browse files Browse the repository at this point in the history
…s-expensive-lookup-for-network-interfaces

[feature] Remove expensive net.Interface lookups upon link initialization
  • Loading branch information
fako1024 authored Jun 28, 2023
2 parents b410f99 + 583990a commit a567eb3
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 184 deletions.
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{
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

0 comments on commit a567eb3

Please sign in to comment.