First of all, thank you for this library! I'd like to suggest a modification to etherecho
which I believe would help me and others have more flexibility for smaller targets. In particular I am writing a ENC28J60 library for tinygo and expect to be using etherecho
to test input/output ethernet frames. I was wondering if it is reasonable to think the following modifications to etherecho
would help me:
- Making
etherType=0xcccc (the unused EtherType)
optional. allowing specification of other types through strings (ARP,etc)
- Setting a destination address
I've made my own etherecho
, Here's the result (not tested):
// Command etherecho broadcasts a message to all machines in the same network
// segment, and listens for other messages from other etherecho servers.
//
// etherecho only works on Linux and BSD, and requires root permission or
// CAP_NET_ADMIN on Linux.
package main
import (
"flag"
"log"
"net"
"os"
"time"
"github.com/mdlayher/ethernet"
"github.com/mdlayher/raw"
)
// Make use of an unassigned EtherType for etherecho.
// https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
const UnusedEtherType = 0xcccc
func main() {
var (
ifaceFlag = flag.String("i", "", "network interface to use to send and receive messages")
msgFlag = flag.String("m", "", "message to be sent (default: system's hostname)")
dstAddrFlag = flag.String("d", "", "destination address (default is broadcast: ff:ff:ff:ff:ff:ff)")
etherType = flag.Int("e", UnusedEtherType, "Ether-Type (default is unused EtherType: 0xcccc, see https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml)")
)
flag.Parse()
// Open a raw socket on the specified interface, and configure it to accept
// traffic with etherecho's EtherType.
ifi, err := net.InterfaceByName(*ifaceFlag)
if err != nil {
log.Fatalf("failed to find interface %q: %v", *ifaceFlag, err)
}
dstAddr := ethernet.Broadcast
if *dstAddrFlag != "" {
dstAddr, err = net.ParseMAC(*dstAddrFlag)
if err != nil {
log.Fatalf("failed to find interface %q: %v", *ifaceFlag, err)
}
}
c, err := raw.ListenPacket(ifi, uint16(*etherType), nil)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Default message to system's hostname if empty.
msg := *msgFlag
if msg == "" {
msg, err = os.Hostname()
if err != nil {
log.Fatalf("failed to retrieve hostname: %v", err)
}
}
// Send messages in one goroutine, receive messages in another.
go sendMessages(c, dstAddr, ifi.HardwareAddr, ethernet.EtherType(*etherType), msg)
go receiveMessages(c, ifi.MTU)
// Block forever.
select {}
}
// sendMessages continuously sends a message over a connection at regular intervals,
// sourced from specified hardware address.
func sendMessages(c net.PacketConn, dst, source net.HardwareAddr, etherType ethernet.EtherType, msg string) {
// Message is broadcast to all machines in same network segment.
f := ðernet.Frame{
Destination: dst,
Source: source,
EtherType: etherType,
Payload: []byte(msg),
}
b, err := f.MarshalBinary()
if err != nil {
log.Fatalf("failed to marshal ethernet frame: %v", err)
}
// Required by Linux, even though the Ethernet frame has a destination.
// Unused by BSD.
dstAddr := &raw.Addr{
HardwareAddr: dst,
}
// Send message forever.
t := time.NewTicker(1 * time.Second)
for range t.C {
if _, err := c.WriteTo(b, dstAddr); err != nil {
log.Fatalf("failed to send message: %v", err)
}
}
}
// receiveMessages continuously receives messages over a connection. The messages
// may be up to the interface's MTU in size.
func receiveMessages(c net.PacketConn, mtu int) {
var f ethernet.Frame
b := make([]byte, mtu)
// Keep receiving messages forever.
for {
n, addr, err := c.ReadFrom(b)
if err != nil {
log.Fatalf("failed to receive message: %v", err)
}
// Unpack Ethernet II frame into Go representation.
if err := (&f).UnmarshalBinary(b[:n]); err != nil {
log.Fatalf("failed to unmarshal ethernet frame: %v", err)
}
// Display source of message and message itself.
log.Printf("[%s] %s", addr.String(), string(f.Payload))
}
}