1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
//go:build go1.16
// +build go1.16
// Just because the library builds and runs on older versions of Go doesn't mean
// we have to apply the same restrictions for tests. Go 1.16 is the oldest
// upstream supported version of Go as of February 2022.
package packet_test
import (
"encoding/binary"
"errors"
"net"
"os"
"testing"
"time"
"github.com/mdlayher/packet"
"golang.org/x/sys/unix"
)
func TestConnListen(t *testing.T) {
// Open a connection on an Ethernet interface and begin listening for
// incoming Ethernet frames. We assume that this interface will receive some
// sort of traffic in the next 30 seconds and we will capture that traffic
// by looking for any EtherType value (ETH_P_ALL).
c, ifi := testConn(t)
t.Logf("interface: %q, MTU: %d", ifi.Name, ifi.MTU)
if err := c.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil {
t.Fatalf("failed to set read deadline: %v", err)
}
b := make([]byte, ifi.MTU)
n, addr, err := c.ReadFrom(b)
if err != nil {
t.Fatalf("failed to read Ethernet frame: %v", err)
}
// Received some data, assume some Stats were populated.
stats, err := c.Stats()
if err != nil {
t.Fatalf("failed to fetch stats: %v", err)
}
if stats.Packets == 0 {
t.Fatal("stats indicated 0 received packets")
}
t.Logf(" - packets: %d, drops: %d, freeze queue count: %d",
stats.Packets, stats.Drops, stats.FreezeQueueCount)
// TODO(mdlayher): we could import github.com/mdlayher/ethernet, but parsing
// an Ethernet frame header is fairly easy and this keeps the go.mod tidy.
// Need at least destination MAC, source MAC, and EtherType.
const header = 6 + 6 + 2
if n < header {
t.Fatalf("did not read a complete Ethernet frame from %v, only %d bytes read",
addr, n)
}
// Parse the header to provide tidy log output.
var (
dst = net.HardwareAddr(b[0:6])
src = net.HardwareAddr(b[6:12])
et = binary.BigEndian.Uint16(b[12:14])
)
// Check for the most likely EtherType values.
var ets string
switch et {
case 0x0800:
ets = "IPv4"
case 0x0806:
ets = "ARP"
case 0x86dd:
ets = "IPv6"
default:
ets = "unknown"
}
// And finally print what we found for the user.
t.Log("Ethernet frame:")
t.Logf(" - destination: %s", dst)
t.Logf(" - source: %s", src)
t.Logf(" - ethertype: %#04x (%s)", et, ets)
t.Logf(" - payload: %d bytes", n-header)
}
// testConn produces a *packet.Conn bound to the returned *net.Interface. The
// caller does not need to call Close on the *packet.Conn.
func testConn(t *testing.T) (*packet.Conn, *net.Interface) {
t.Helper()
// TODO(mdlayher): probably parameterize the EtherType.
ifi := testInterface(t)
c, err := packet.Listen(ifi, packet.Raw, unix.ETH_P_ALL, nil)
if err != nil {
if errors.Is(err, os.ErrPermission) {
t.Skipf("skipping, permission denied (try setting CAP_NET_RAW capability): %v", err)
}
t.Fatalf("failed to listen: %v", err)
}
t.Cleanup(func() { c.Close() })
return c, ifi
}
// testInterface looks for a suitable Ethernet interface to bind a *packet.Conn.
func testInterface(t *testing.T) *net.Interface {
ifis, err := net.Interfaces()
if err != nil {
t.Fatalf("failed to get network interfaces: %v", err)
}
if len(ifis) == 0 {
t.Skip("skipping, no network interfaces found")
}
// Try to find a suitable network interface for tests.
var tried []string
for _, ifi := range ifis {
tried = append(tried, ifi.Name)
// true is used to line up other checks.
ok := true &&
// Look for an Ethernet interface.
len(ifi.HardwareAddr) == 6 &&
// Look for up, multicast, broadcast.
ifi.Flags&(net.FlagUp|net.FlagMulticast|net.FlagBroadcast) != 0
if ok {
return &ifi
}
}
t.Skipf("skipping, could not find a usable network interface, tried: %s", tried)
panic("unreachable")
}
|