blob: d150d8e222bbe3aee7f59c78c59fc2a4f250011b [file] [log] [blame]
// Package ethernet implements marshaling and unmarshaling of IEEE 802.3
// Ethernet II frames and IEEE 802.1Q VLAN tags.
package ethernet
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io"
"net"
)
//go:generate stringer -output=string.go -type=EtherType
const (
// minPayload is the minimum payload size for an Ethernet frame, assuming
// that no 802.1Q VLAN tags are present.
minPayload = 46
)
var (
// Broadcast is a special hardware address which indicates a Frame should
// be sent to every device on a given LAN segment.
Broadcast = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
)
var (
// ErrInvalidFCS is returned when Frame.UnmarshalFCS detects an incorrect
// Ethernet frame check sequence in a byte slice for a Frame.
ErrInvalidFCS = errors.New("invalid frame check sequence")
)
// An EtherType is a value used to identify an upper layer protocol
// encapsulated in a Frame.
//
// A list of IANA-assigned EtherType values may be found here:
// http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml.
type EtherType uint16
// Common EtherType values frequently used in a Frame.
const (
EtherTypeIPv4 EtherType = 0x0800
EtherTypeARP EtherType = 0x0806
EtherTypeIPv6 EtherType = 0x86DD
// EtherTypeVLAN and EtherTypeServiceVLAN are used as 802.1Q Tag Protocol
// Identifiers (TPIDs).
EtherTypeVLAN EtherType = 0x8100
EtherTypeServiceVLAN EtherType = 0x88a8
)
// A Frame is an IEEE 802.3 Ethernet II frame. A Frame contains information
// such as source and destination hardware addresses, zero or more optional
// 802.1Q VLAN tags, an EtherType, and payload data.
type Frame struct {
// Destination specifies the destination hardware address for this Frame.
//
// If this address is set to Broadcast, the Frame will be sent to every
// device on a given LAN segment.
Destination net.HardwareAddr
// Source specifies the source hardware address for this Frame.
//
// Typically, this is the hardware address of the network interface used to
// send this Frame.
Source net.HardwareAddr
// ServiceVLAN specifies an optional 802.1Q service VLAN tag, for use with
// 802.1ad double tagging, or "Q-in-Q". If ServiceVLAN is not nil, VLAN must
// not be nil as well.
//
// Most users should leave this field set to nil and use VLAN instead.
ServiceVLAN *VLAN
// VLAN specifies an optional 802.1Q customer VLAN tag, which may or may
// not be present in a Frame. It is important to note that the operating
// system may automatically strip VLAN tags before they can be parsed.
VLAN *VLAN
// EtherType is a value used to identify an upper layer protocol
// encapsulated in this Frame.
EtherType EtherType
// Payload is a variable length data payload encapsulated by this Frame.
Payload []byte
}
// MarshalBinary allocates a byte slice and marshals a Frame into binary form.
func (f *Frame) MarshalBinary() ([]byte, error) {
b := make([]byte, f.length())
_, err := f.read(b)
return b, err
}
// MarshalFCS allocates a byte slice, marshals a Frame into binary form, and
// finally calculates and places a 4-byte IEEE CRC32 frame check sequence at
// the end of the slice.
//
// Most users should use MarshalBinary instead. MarshalFCS is provided as a
// convenience for rare occasions when the operating system cannot
// automatically generate a frame check sequence for an Ethernet frame.
func (f *Frame) MarshalFCS() ([]byte, error) {
// Frame length with 4 extra bytes for frame check sequence
b := make([]byte, f.length()+4)
if _, err := f.read(b); err != nil {
return nil, err
}
// Compute IEEE CRC32 checksum of frame bytes and place it directly
// in the last four bytes of the slice
binary.BigEndian.PutUint32(b[len(b)-4:], crc32.ChecksumIEEE(b[0:len(b)-4]))
return b, nil
}
// read reads data from a Frame into b. read is used to marshal a Frame
// into binary form, but does not allocate on its own.
func (f *Frame) read(b []byte) (int, error) {
// S-VLAN must also have accompanying C-VLAN.
if f.ServiceVLAN != nil && f.VLAN == nil {
return 0, ErrInvalidVLAN
}
copy(b[0:6], f.Destination)
copy(b[6:12], f.Source)
// Marshal each non-nil VLAN tag into bytes, inserting the appropriate
// EtherType/TPID before each, so devices know that one or more VLANs
// are present.
vlans := []struct {
vlan *VLAN
tpid EtherType
}{
{vlan: f.ServiceVLAN, tpid: EtherTypeServiceVLAN},
{vlan: f.VLAN, tpid: EtherTypeVLAN},
}
n := 12
for _, vt := range vlans {
if vt.vlan == nil {
continue
}
// Add VLAN EtherType and VLAN bytes.
binary.BigEndian.PutUint16(b[n:n+2], uint16(vt.tpid))
if _, err := vt.vlan.read(b[n+2 : n+4]); err != nil {
return 0, err
}
n += 4
}
// Marshal actual EtherType after any VLANs, copy payload into
// output bytes.
binary.BigEndian.PutUint16(b[n:n+2], uint16(f.EtherType))
copy(b[n+2:], f.Payload)
return len(b), nil
}
// UnmarshalBinary unmarshals a byte slice into a Frame.
func (f *Frame) UnmarshalBinary(b []byte) error {
// Verify that both hardware addresses and a single EtherType are present
if len(b) < 14 {
return io.ErrUnexpectedEOF
}
// Track offset in packet for reading data
n := 14
// Continue looping and parsing VLAN tags until no more VLAN EtherType
// values are detected
et := EtherType(binary.BigEndian.Uint16(b[n-2 : n]))
switch et {
case EtherTypeServiceVLAN, EtherTypeVLAN:
// VLAN type is hinted for further parsing. An index is returned which
// indicates how many bytes were consumed by VLAN tags.
nn, err := f.unmarshalVLANs(et, b[n:])
if err != nil {
return err
}
n += nn
default:
// No VLANs detected.
f.EtherType = et
}
// Allocate single byte slice to store destination and source hardware
// addresses, and payload
bb := make([]byte, 6+6+len(b[n:]))
copy(bb[0:6], b[0:6])
f.Destination = bb[0:6]
copy(bb[6:12], b[6:12])
f.Source = bb[6:12]
// There used to be a minimum payload length restriction here, but as
// long as two hardware addresses and an EtherType are present, it
// doesn't really matter what is contained in the payload. We will
// follow the "robustness principle".
copy(bb[12:], b[n:])
f.Payload = bb[12:]
return nil
}
// UnmarshalFCS computes the IEEE CRC32 frame check sequence of a Frame,
// verifies it against the checksum present in the byte slice, and finally,
// unmarshals a byte slice into a Frame.
//
// Most users should use UnmarshalBinary instead. UnmarshalFCS is provided as
// a convenience for rare occasions when the operating system cannot
// automatically verify a frame check sequence for an Ethernet frame.
func (f *Frame) UnmarshalFCS(b []byte) error {
// Must contain enough data for FCS, to avoid panics
if len(b) < 4 {
return io.ErrUnexpectedEOF
}
// Verify checksum in slice versus newly computed checksum
want := binary.BigEndian.Uint32(b[len(b)-4:])
got := crc32.ChecksumIEEE(b[0 : len(b)-4])
if want != got {
return ErrInvalidFCS
}
return f.UnmarshalBinary(b[0 : len(b)-4])
}
// length calculates the number of bytes required to store a Frame.
func (f *Frame) length() int {
// If payload is less than the required minimum length, we zero-pad up to
// the required minimum length
pl := len(f.Payload)
if pl < minPayload {
pl = minPayload
}
// Add additional length if VLAN tags are needed.
var vlanLen int
switch {
case f.ServiceVLAN != nil && f.VLAN != nil:
vlanLen = 8
case f.VLAN != nil:
vlanLen = 4
}
// 6 bytes: destination hardware address
// 6 bytes: source hardware address
// N bytes: VLAN tags (if present)
// 2 bytes: EtherType
// N bytes: payload length (may be padded)
return 6 + 6 + vlanLen + 2 + pl
}
// unmarshalVLANs unmarshals S/C-VLAN tags. It is assumed that tpid
// is a valid S/C-VLAN TPID.
func (f *Frame) unmarshalVLANs(tpid EtherType, b []byte) (int, error) {
// 4 or more bytes must remain for valid S/C-VLAN tag and EtherType.
if len(b) < 4 {
return 0, io.ErrUnexpectedEOF
}
// Track how many bytes are consumed by VLAN tags.
var n int
switch tpid {
case EtherTypeServiceVLAN:
vlan := new(VLAN)
if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil {
return 0, err
}
f.ServiceVLAN = vlan
// Assume that a C-VLAN immediately trails an S-VLAN.
if EtherType(binary.BigEndian.Uint16(b[n+2:n+4])) != EtherTypeVLAN {
return 0, ErrInvalidVLAN
}
// 4 or more bytes must remain for valid C-VLAN tag and EtherType.
n += 4
if len(b[n:]) < 4 {
return 0, io.ErrUnexpectedEOF
}
// Continue to parse the C-VLAN.
fallthrough
case EtherTypeVLAN:
vlan := new(VLAN)
if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil {
return 0, err
}
f.VLAN = vlan
f.EtherType = EtherType(binary.BigEndian.Uint16(b[n+2 : n+4]))
n += 4
default:
panic(fmt.Sprintf("unknown VLAN TPID: %04x", tpid))
}
return n, nil
}