| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- // 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
- }
|