├── .travis.yml ├── LICENSE.md ├── README.md ├── cmd └── etherecho │ ├── README.md │ └── main.go ├── ethernet.go ├── ethernet_test.go ├── fuzz.go ├── go.mod ├── go.sum ├── string.go ├── vlan.go └── vlan_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.x 4 | os: 5 | - linux 6 | before_install: 7 | - go get golang.org/x/lint/golint 8 | - go get honnef.co/go/tools/cmd/staticcheck 9 | - go get -d ./... 10 | script: 11 | - go build -tags=gofuzz ./... 12 | - go vet ./... 13 | - staticcheck ./... 14 | - golint -set_exit_status ./... 15 | - go test -v -race ./... 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (C) 2015 Matt Layher 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ethernet [![Build Status](https://travis-ci.org/mdlayher/ethernet.svg?branch=master)](https://travis-ci.org/mdlayher/ethernet) [![GoDoc](https://godoc.org/github.com/mdlayher/ethernet?status.svg)](https://godoc.org/github.com/mdlayher/ethernet) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/ethernet)](https://goreportcard.com/report/github.com/mdlayher/ethernet) 2 | ======== 3 | 4 | Package `ethernet` implements marshaling and unmarshaling of IEEE 802.3 5 | Ethernet II frames and IEEE 802.1Q VLAN tags. MIT Licensed. 6 | 7 | For more information about using Ethernet frames in Go, check out my blog 8 | post: [Network Protocol Breakdown: Ethernet and Go](https://medium.com/@mdlayher/network-protocol-breakdown-ethernet-and-go-de985d726cc1). -------------------------------------------------------------------------------- /cmd/etherecho/README.md: -------------------------------------------------------------------------------- 1 | etherecho 2 | ========= 3 | 4 | Command `etherecho` broadcasts a message to all machines in the same network 5 | segment, and listens for other messages from other `etherecho` servers. 6 | 7 | `etherecho` only works on Linux and BSD, and requires root permission or 8 | `CAP_NET_ADMIN` on Linux. 9 | 10 | Usage 11 | ----- 12 | 13 | ``` 14 | $ etherecho -h 15 | Usage of etherecho: 16 | -i string 17 | network interface to use to send and receive messages 18 | -m string 19 | message to be sent (default: system's hostname) 20 | ``` 21 | 22 | Example 23 | ------- 24 | 25 | Start an instance of `etherecho` on two machines on the same network segment: 26 | 27 | ``` 28 | foo $ etherecho -i eth0 29 | ``` 30 | ``` 31 | bar $ etherecho -i eth0 32 | ``` 33 | 34 | Both machines should begin seeing messages from each other at regular intervals: 35 | 36 | ``` 37 | foo $ etherecho -i eth0 38 | 2017/06/14 00:03:13 [aa:aa:aa:aa:aa:aa] bar 39 | 2017/06/14 00:03:14 [aa:aa:aa:aa:aa:aa] bar 40 | 2017/06/14 00:03:15 [aa:aa:aa:aa:aa:aa] bar 41 | ``` 42 | ``` 43 | bar $ etherecho -i eth0 44 | 2017/06/14 00:03:13 [bb:bb:bb:bb:bb:bb] foo 45 | 2017/06/14 00:03:14 [bb:bb:bb:bb:bb:bb] foo 46 | 2017/06/14 00:03:15 [bb:bb:bb:bb:bb:bb] foo 47 | ``` 48 | 49 | Additional machines can be added, so long as they reside on the same network 50 | segment. -------------------------------------------------------------------------------- /cmd/etherecho/main.go: -------------------------------------------------------------------------------- 1 | // Command etherecho broadcasts a message to all machines in the same network 2 | // segment, and listens for other messages from other etherecho servers. 3 | // 4 | // etherecho only works on Linux and BSD, and requires root permission or 5 | // CAP_NET_ADMIN on Linux. 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "log" 11 | "net" 12 | "os" 13 | "time" 14 | 15 | "github.com/mdlayher/ethernet" 16 | "github.com/mdlayher/packet" 17 | ) 18 | 19 | // Make use of an unassigned EtherType for etherecho. 20 | // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml 21 | const etherType = 0xcccc 22 | 23 | func main() { 24 | var ( 25 | ifaceFlag = flag.String("i", "", "network interface to use to send and receive messages") 26 | msgFlag = flag.String("m", "", "message to be sent (default: system's hostname)") 27 | ) 28 | 29 | flag.Parse() 30 | 31 | // Open a raw socket on the specified interface, and configure it to accept 32 | // traffic with etherecho's EtherType. 33 | ifi, err := net.InterfaceByName(*ifaceFlag) 34 | if err != nil { 35 | log.Fatalf("failed to find interface %q: %v", *ifaceFlag, err) 36 | } 37 | 38 | c, err := packet.Listen(ifi, packet.Raw, etherType, nil) 39 | if err != nil { 40 | log.Fatalf("failed to listen: %v", err) 41 | } 42 | 43 | // Default message to system's hostname if empty. 44 | msg := *msgFlag 45 | if msg == "" { 46 | msg, err = os.Hostname() 47 | if err != nil { 48 | log.Fatalf("failed to retrieve hostname: %v", err) 49 | } 50 | } 51 | 52 | // Send messages in one goroutine, receive messages in another. 53 | go sendMessages(c, ifi.HardwareAddr, msg) 54 | go receiveMessages(c, ifi.MTU) 55 | 56 | // Block forever. 57 | select {} 58 | } 59 | 60 | // sendMessages continuously sends a message over a connection at regular intervals, 61 | // sourced from specified hardware address. 62 | func sendMessages(c net.PacketConn, source net.HardwareAddr, msg string) { 63 | // Message is broadcast to all machines in same network segment. 64 | f := ðernet.Frame{ 65 | Destination: ethernet.Broadcast, 66 | Source: source, 67 | EtherType: etherType, 68 | Payload: []byte(msg), 69 | } 70 | 71 | b, err := f.MarshalBinary() 72 | if err != nil { 73 | log.Fatalf("failed to marshal ethernet frame: %v", err) 74 | } 75 | 76 | // Required by Linux, even though the Ethernet frame has a destination. 77 | // Unused by BSD. 78 | addr := &packet.Addr{ 79 | HardwareAddr: ethernet.Broadcast, 80 | } 81 | 82 | // Send message forever. 83 | t := time.NewTicker(1 * time.Second) 84 | for range t.C { 85 | if _, err := c.WriteTo(b, addr); err != nil { 86 | log.Fatalf("failed to send message: %v", err) 87 | } 88 | } 89 | } 90 | 91 | // receiveMessages continuously receives messages over a connection. The messages 92 | // may be up to the interface's MTU in size. 93 | func receiveMessages(c net.PacketConn, mtu int) { 94 | var f ethernet.Frame 95 | b := make([]byte, mtu) 96 | 97 | // Keep receiving messages forever. 98 | for { 99 | n, addr, err := c.ReadFrom(b) 100 | if err != nil { 101 | log.Fatalf("failed to receive message: %v", err) 102 | } 103 | 104 | // Unpack Ethernet II frame into Go representation. 105 | if err := (&f).UnmarshalBinary(b[:n]); err != nil { 106 | log.Fatalf("failed to unmarshal ethernet frame: %v", err) 107 | } 108 | 109 | // Display source of message and message itself. 110 | log.Printf("[%s] %s", addr.String(), string(f.Payload)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ethernet.go: -------------------------------------------------------------------------------- 1 | // Package ethernet implements marshaling and unmarshaling of IEEE 802.3 2 | // Ethernet II frames and IEEE 802.1Q VLAN tags. 3 | package ethernet 4 | 5 | import ( 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "hash/crc32" 10 | "io" 11 | "net" 12 | ) 13 | 14 | //go:generate stringer -output=string.go -type=EtherType 15 | 16 | const ( 17 | // minPayload is the minimum payload size for an Ethernet frame, assuming 18 | // that no 802.1Q VLAN tags are present. 19 | minPayload = 46 20 | ) 21 | 22 | // Broadcast is a special hardware address which indicates a Frame should 23 | // be sent to every device on a given LAN segment. 24 | var Broadcast = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} 25 | 26 | // ErrInvalidFCS is returned when Frame.UnmarshalFCS detects an incorrect 27 | // Ethernet frame check sequence in a byte slice for a Frame. 28 | var ErrInvalidFCS = errors.New("invalid frame check sequence") 29 | 30 | // An EtherType is a value used to identify an upper layer protocol 31 | // encapsulated in a Frame. 32 | // 33 | // A list of IANA-assigned EtherType values may be found here: 34 | // http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml. 35 | type EtherType uint16 36 | 37 | // Common EtherType values frequently used in a Frame. 38 | const ( 39 | EtherTypeIPv4 EtherType = 0x0800 40 | EtherTypeARP EtherType = 0x0806 41 | EtherTypeIPv6 EtherType = 0x86DD 42 | 43 | // EtherTypeVLAN and EtherTypeServiceVLAN are used as 802.1Q Tag Protocol 44 | // Identifiers (TPIDs). 45 | EtherTypeVLAN EtherType = 0x8100 46 | EtherTypeServiceVLAN EtherType = 0x88a8 47 | ) 48 | 49 | // A Frame is an IEEE 802.3 Ethernet II frame. A Frame contains information 50 | // such as source and destination hardware addresses, zero or more optional 51 | // 802.1Q VLAN tags, an EtherType, and payload data. 52 | type Frame struct { 53 | // Destination specifies the destination hardware address for this Frame. 54 | // 55 | // If this address is set to Broadcast, the Frame will be sent to every 56 | // device on a given LAN segment. 57 | Destination net.HardwareAddr 58 | 59 | // Source specifies the source hardware address for this Frame. 60 | // 61 | // Typically, this is the hardware address of the network interface used to 62 | // send this Frame. 63 | Source net.HardwareAddr 64 | 65 | // ServiceVLAN specifies an optional 802.1Q service VLAN tag, for use with 66 | // 802.1ad double tagging, or "Q-in-Q". If ServiceVLAN is not nil, VLAN must 67 | // not be nil as well. 68 | // 69 | // Most users should leave this field set to nil and use VLAN instead. 70 | ServiceVLAN *VLAN 71 | 72 | // VLAN specifies an optional 802.1Q customer VLAN tag, which may or may 73 | // not be present in a Frame. It is important to note that the operating 74 | // system may automatically strip VLAN tags before they can be parsed. 75 | VLAN *VLAN 76 | 77 | // EtherType is a value used to identify an upper layer protocol 78 | // encapsulated in this Frame. 79 | EtherType EtherType 80 | 81 | // Payload is a variable length data payload encapsulated by this Frame. 82 | Payload []byte 83 | } 84 | 85 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 86 | func (f *Frame) MarshalBinary() ([]byte, error) { 87 | b := make([]byte, f.length()) 88 | _, err := f.read(b) 89 | return b, err 90 | } 91 | 92 | // MarshalFCS allocates a byte slice, marshals a Frame into binary form, and 93 | // finally calculates and places a 4-byte IEEE CRC32 frame check sequence at 94 | // the end of the slice. 95 | // 96 | // Most users should use MarshalBinary instead. MarshalFCS is provided as a 97 | // convenience for rare occasions when the operating system cannot 98 | // automatically generate a frame check sequence for an Ethernet frame. 99 | func (f *Frame) MarshalFCS() ([]byte, error) { 100 | // Frame length with 4 extra bytes for frame check sequence 101 | b := make([]byte, f.length()+4) 102 | if _, err := f.read(b); err != nil { 103 | return nil, err 104 | } 105 | 106 | // Compute IEEE CRC32 checksum of frame bytes and place it directly 107 | // in the last four bytes of the slice 108 | binary.BigEndian.PutUint32(b[len(b)-4:], crc32.ChecksumIEEE(b[0:len(b)-4])) 109 | return b, nil 110 | } 111 | 112 | // read reads data from a Frame into b. read is used to marshal a Frame 113 | // into binary form, but does not allocate on its own. 114 | func (f *Frame) read(b []byte) (int, error) { 115 | // S-VLAN must also have accompanying C-VLAN. 116 | if f.ServiceVLAN != nil && f.VLAN == nil { 117 | return 0, ErrInvalidVLAN 118 | } 119 | 120 | copy(b[0:6], f.Destination) 121 | copy(b[6:12], f.Source) 122 | 123 | // Marshal each non-nil VLAN tag into bytes, inserting the appropriate 124 | // EtherType/TPID before each, so devices know that one or more VLANs 125 | // are present. 126 | vlans := []struct { 127 | vlan *VLAN 128 | tpid EtherType 129 | }{ 130 | {vlan: f.ServiceVLAN, tpid: EtherTypeServiceVLAN}, 131 | {vlan: f.VLAN, tpid: EtherTypeVLAN}, 132 | } 133 | 134 | n := 12 135 | for _, vt := range vlans { 136 | if vt.vlan == nil { 137 | continue 138 | } 139 | 140 | // Add VLAN EtherType and VLAN bytes. 141 | binary.BigEndian.PutUint16(b[n:n+2], uint16(vt.tpid)) 142 | if _, err := vt.vlan.read(b[n+2 : n+4]); err != nil { 143 | return 0, err 144 | } 145 | n += 4 146 | } 147 | 148 | // Marshal actual EtherType after any VLANs, copy payload into 149 | // output bytes. 150 | binary.BigEndian.PutUint16(b[n:n+2], uint16(f.EtherType)) 151 | copy(b[n+2:], f.Payload) 152 | 153 | return len(b), nil 154 | } 155 | 156 | // UnmarshalBinary unmarshals a byte slice into a Frame. 157 | func (f *Frame) UnmarshalBinary(b []byte) error { 158 | // Verify that both hardware addresses and a single EtherType are present 159 | if len(b) < 14 { 160 | return io.ErrUnexpectedEOF 161 | } 162 | 163 | // Track offset in packet for reading data 164 | n := 14 165 | 166 | // Continue looping and parsing VLAN tags until no more VLAN EtherType 167 | // values are detected 168 | et := EtherType(binary.BigEndian.Uint16(b[n-2 : n])) 169 | switch et { 170 | case EtherTypeServiceVLAN, EtherTypeVLAN: 171 | // VLAN type is hinted for further parsing. An index is returned which 172 | // indicates how many bytes were consumed by VLAN tags. 173 | nn, err := f.unmarshalVLANs(et, b[n:]) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | n += nn 179 | default: 180 | // No VLANs detected. 181 | f.EtherType = et 182 | } 183 | 184 | // Allocate single byte slice to store destination and source hardware 185 | // addresses, and payload 186 | bb := make([]byte, 6+6+len(b[n:])) 187 | copy(bb[0:6], b[0:6]) 188 | f.Destination = bb[0:6] 189 | copy(bb[6:12], b[6:12]) 190 | f.Source = bb[6:12] 191 | 192 | // There used to be a minimum payload length restriction here, but as 193 | // long as two hardware addresses and an EtherType are present, it 194 | // doesn't really matter what is contained in the payload. We will 195 | // follow the "robustness principle". 196 | copy(bb[12:], b[n:]) 197 | f.Payload = bb[12:] 198 | 199 | return nil 200 | } 201 | 202 | // UnmarshalFCS computes the IEEE CRC32 frame check sequence of a Frame, 203 | // verifies it against the checksum present in the byte slice, and finally, 204 | // unmarshals a byte slice into a Frame. 205 | // 206 | // Most users should use UnmarshalBinary instead. UnmarshalFCS is provided as 207 | // a convenience for rare occasions when the operating system cannot 208 | // automatically verify a frame check sequence for an Ethernet frame. 209 | func (f *Frame) UnmarshalFCS(b []byte) error { 210 | // Must contain enough data for FCS, to avoid panics 211 | if len(b) < 4 { 212 | return io.ErrUnexpectedEOF 213 | } 214 | 215 | // Verify checksum in slice versus newly computed checksum 216 | want := binary.BigEndian.Uint32(b[len(b)-4:]) 217 | got := crc32.ChecksumIEEE(b[0 : len(b)-4]) 218 | if want != got { 219 | return ErrInvalidFCS 220 | } 221 | 222 | return f.UnmarshalBinary(b[0 : len(b)-4]) 223 | } 224 | 225 | // length calculates the number of bytes required to store a Frame. 226 | func (f *Frame) length() int { 227 | // If payload is less than the required minimum length, we zero-pad up to 228 | // the required minimum length 229 | pl := len(f.Payload) 230 | if pl < minPayload { 231 | pl = minPayload 232 | } 233 | 234 | // Add additional length if VLAN tags are needed. 235 | var vlanLen int 236 | switch { 237 | case f.ServiceVLAN != nil && f.VLAN != nil: 238 | vlanLen = 8 239 | case f.VLAN != nil: 240 | vlanLen = 4 241 | } 242 | 243 | // 6 bytes: destination hardware address 244 | // 6 bytes: source hardware address 245 | // N bytes: VLAN tags (if present) 246 | // 2 bytes: EtherType 247 | // N bytes: payload length (may be padded) 248 | return 6 + 6 + vlanLen + 2 + pl 249 | } 250 | 251 | // unmarshalVLANs unmarshals S/C-VLAN tags. It is assumed that tpid 252 | // is a valid S/C-VLAN TPID. 253 | func (f *Frame) unmarshalVLANs(tpid EtherType, b []byte) (int, error) { 254 | // 4 or more bytes must remain for valid S/C-VLAN tag and EtherType. 255 | if len(b) < 4 { 256 | return 0, io.ErrUnexpectedEOF 257 | } 258 | 259 | // Track how many bytes are consumed by VLAN tags. 260 | var n int 261 | 262 | switch tpid { 263 | case EtherTypeServiceVLAN: 264 | vlan := new(VLAN) 265 | if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil { 266 | return 0, err 267 | } 268 | f.ServiceVLAN = vlan 269 | 270 | // Assume that a C-VLAN immediately trails an S-VLAN. 271 | if EtherType(binary.BigEndian.Uint16(b[n+2:n+4])) != EtherTypeVLAN { 272 | return 0, ErrInvalidVLAN 273 | } 274 | 275 | // 4 or more bytes must remain for valid C-VLAN tag and EtherType. 276 | n += 4 277 | if len(b[n:]) < 4 { 278 | return 0, io.ErrUnexpectedEOF 279 | } 280 | 281 | // Continue to parse the C-VLAN. 282 | fallthrough 283 | case EtherTypeVLAN: 284 | vlan := new(VLAN) 285 | if err := vlan.UnmarshalBinary(b[n : n+2]); err != nil { 286 | return 0, err 287 | } 288 | 289 | f.VLAN = vlan 290 | f.EtherType = EtherType(binary.BigEndian.Uint16(b[n+2 : n+4])) 291 | n += 4 292 | default: 293 | panic(fmt.Sprintf("unknown VLAN TPID: %04x", tpid)) 294 | } 295 | 296 | return n, nil 297 | } 298 | -------------------------------------------------------------------------------- /ethernet_test.go: -------------------------------------------------------------------------------- 1 | package ethernet 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestFrameMarshalBinary(t *testing.T) { 12 | tests := []struct { 13 | desc string 14 | f *Frame 15 | b []byte 16 | err error 17 | }{ 18 | { 19 | desc: "S-VLAN, no C-VLAN", 20 | f: &Frame{ 21 | // Contents don't matter. 22 | ServiceVLAN: &VLAN{}, 23 | }, 24 | err: ErrInvalidVLAN, 25 | }, 26 | { 27 | desc: "IPv4, no VLANs", 28 | f: &Frame{ 29 | Destination: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 30 | Source: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 31 | EtherType: EtherTypeIPv4, 32 | Payload: bytes.Repeat([]byte{0}, 50), 33 | }, 34 | b: append([]byte{ 35 | 0, 1, 0, 1, 0, 1, 36 | 1, 0, 1, 0, 1, 0, 37 | 0x08, 0x00, 38 | }, bytes.Repeat([]byte{0}, 50)...), 39 | }, 40 | { 41 | desc: "IPv6, C-VLAN: (PRI 1, ID 101)", 42 | f: &Frame{ 43 | Destination: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 44 | Source: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 45 | VLAN: &VLAN{ 46 | Priority: 1, 47 | ID: 101, 48 | }, 49 | EtherType: EtherTypeIPv6, 50 | Payload: bytes.Repeat([]byte{0}, 50), 51 | }, 52 | b: append([]byte{ 53 | 1, 0, 1, 0, 1, 0, 54 | 0, 1, 0, 1, 0, 1, 55 | 0x81, 0x00, 56 | 0x20, 0x65, 57 | 0x86, 0xDD, 58 | }, bytes.Repeat([]byte{0}, 50)...), 59 | }, 60 | { 61 | desc: "ARP, S-VLAN: (PRI 0, DROP, ID 100), C-VLAN: (PRI 1, ID 101)", 62 | f: &Frame{ 63 | Destination: Broadcast, 64 | Source: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 65 | ServiceVLAN: &VLAN{ 66 | DropEligible: true, 67 | ID: 100, 68 | }, 69 | VLAN: &VLAN{ 70 | Priority: 1, 71 | ID: 101, 72 | }, 73 | EtherType: EtherTypeARP, 74 | Payload: bytes.Repeat([]byte{0}, 50), 75 | }, 76 | b: append([]byte{ 77 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 78 | 0, 1, 0, 1, 0, 1, 79 | 0x88, 0xa8, 80 | 0x10, 0x64, 81 | 0x81, 0x00, 82 | 0x20, 0x65, 83 | 0x08, 0x06, 84 | }, bytes.Repeat([]byte{0}, 50)...), 85 | }, 86 | } 87 | 88 | for _, tt := range tests { 89 | t.Run(tt.desc, func(t *testing.T) { 90 | b, err := tt.f.MarshalBinary() 91 | if err != nil { 92 | if want, got := tt.err, err; want != got { 93 | t.Fatalf("unexpected error: %v != %v", want, got) 94 | } 95 | 96 | return 97 | } 98 | 99 | if want, got := tt.b, b; !bytes.Equal(want, got) { 100 | t.Fatalf("unexpected Frame bytes:\n- want: %v\n- got: %v", want, got) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | func TestFrameMarshalFCS(t *testing.T) { 107 | tests := []struct { 108 | desc string 109 | f *Frame 110 | b []byte 111 | err error 112 | }{ 113 | { 114 | desc: "IPv4, no VLANs", 115 | f: &Frame{ 116 | Destination: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 117 | Source: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 118 | EtherType: EtherTypeIPv4, 119 | Payload: bytes.Repeat([]byte{0}, 50), 120 | }, 121 | b: append( 122 | append( 123 | []byte{ 124 | 0, 1, 0, 1, 0, 1, 125 | 1, 0, 1, 0, 1, 0, 126 | 0x08, 0x00, 127 | }, 128 | bytes.Repeat([]byte{0}, 50)..., 129 | ), 130 | []byte{159, 205, 24, 60}..., 131 | ), 132 | }, 133 | } 134 | 135 | for _, tt := range tests { 136 | t.Run(tt.desc, func(t *testing.T) { 137 | b, err := tt.f.MarshalFCS() 138 | if err != nil { 139 | if want, got := tt.err, err; want != got { 140 | t.Fatalf("unexpected error: %v != %v", want, got) 141 | } 142 | 143 | return 144 | } 145 | 146 | if want, got := tt.b, b; !bytes.Equal(want, got) { 147 | t.Fatalf("unexpected Frame bytes:\n- want: %v\n- got: %v", want, got) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func TestFrameUnmarshalBinary(t *testing.T) { 154 | tests := []struct { 155 | desc string 156 | b []byte 157 | f *Frame 158 | err error 159 | }{ 160 | { 161 | desc: "nil buffer", 162 | err: io.ErrUnexpectedEOF, 163 | }, 164 | { 165 | desc: "short buffer", 166 | b: bytes.Repeat([]byte{0}, 13), 167 | err: io.ErrUnexpectedEOF, 168 | }, 169 | { 170 | desc: "1 short S-VLAN", 171 | b: []byte{ 172 | 0, 0, 0, 0, 0, 0, 173 | 0, 0, 0, 0, 0, 0, 174 | 0x88, 0xa8, 175 | 0x00, 176 | }, 177 | err: io.ErrUnexpectedEOF, 178 | }, 179 | { 180 | desc: "1 short C-VLAN", 181 | b: []byte{ 182 | 0, 0, 0, 0, 0, 0, 183 | 0, 0, 0, 0, 0, 0, 184 | 0x81, 0x00, 185 | 0x00, 186 | }, 187 | err: io.ErrUnexpectedEOF, 188 | }, 189 | { 190 | desc: "VLAN ID too large", 191 | b: []byte{ 192 | 0, 0, 0, 0, 0, 0, 193 | 0, 0, 0, 0, 0, 0, 194 | 0x81, 0x00, 195 | 0xff, 0xff, 196 | 0x00, 0x00, 197 | }, 198 | err: ErrInvalidVLAN, 199 | }, 200 | { 201 | desc: "no C-VLAN after S-VLAN", 202 | b: []byte{ 203 | 0, 0, 0, 0, 0, 0, 204 | 0, 0, 0, 0, 0, 0, 205 | 0x88, 0xa8, 206 | 0x20, 0x65, 207 | 0x08, 0x06, 208 | 0x00, 0x00, 0x00, 0x00, 209 | }, 210 | err: ErrInvalidVLAN, 211 | }, 212 | { 213 | desc: "short C-VLAN after S-VLAN", 214 | b: []byte{ 215 | 0, 0, 0, 0, 0, 0, 216 | 0, 0, 0, 0, 0, 0, 217 | 0x88, 0xa8, 218 | 0x20, 0x65, 219 | 0x81, 0x00, 220 | 0x00, 0x00, 221 | }, 222 | err: io.ErrUnexpectedEOF, 223 | }, 224 | { 225 | desc: "go-fuzz crasher: VLAN tag without enough bytes for trailing EtherType", 226 | b: []byte("190734863281\x81\x0032"), 227 | err: io.ErrUnexpectedEOF, 228 | }, 229 | { 230 | desc: "0 VLANs detected, but 1 may have been present", 231 | b: bytes.Repeat([]byte{0}, 56), 232 | f: &Frame{ 233 | Destination: net.HardwareAddr{0, 0, 0, 0, 0, 0}, 234 | Source: net.HardwareAddr{0, 0, 0, 0, 0, 0}, 235 | Payload: bytes.Repeat([]byte{0}, 42), 236 | }, 237 | }, 238 | { 239 | desc: "IPv4, no VLANs", 240 | b: append([]byte{ 241 | 0, 1, 0, 1, 0, 1, 242 | 1, 0, 1, 0, 1, 0, 243 | 0x08, 0x00, 244 | }, bytes.Repeat([]byte{0}, 50)...), 245 | f: &Frame{ 246 | Destination: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 247 | Source: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 248 | EtherType: EtherTypeIPv4, 249 | Payload: bytes.Repeat([]byte{0}, 50), 250 | }, 251 | }, 252 | { 253 | desc: "IPv6, C-VLAN: (PRI 1, ID 101)", 254 | b: append([]byte{ 255 | 1, 0, 1, 0, 1, 0, 256 | 0, 1, 0, 1, 0, 1, 257 | 0x81, 0x00, 258 | 0x20, 0x65, 259 | 0x86, 0xDD, 260 | }, bytes.Repeat([]byte{0}, 50)...), 261 | f: &Frame{ 262 | Destination: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 263 | Source: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 264 | VLAN: &VLAN{ 265 | Priority: 1, 266 | ID: 101, 267 | }, 268 | EtherType: EtherTypeIPv6, 269 | Payload: bytes.Repeat([]byte{0}, 50), 270 | }, 271 | }, 272 | { 273 | desc: "ARP, S-VLAN: (PRI 0, DROP, ID 100), C-VLAN: (PRI 1, ID 101)", 274 | b: append([]byte{ 275 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 276 | 0, 1, 0, 1, 0, 1, 277 | 0x88, 0xa8, 278 | 0x10, 0x64, 279 | 0x81, 0x00, 280 | 0x20, 0x65, 281 | 0x08, 0x06, 282 | }, bytes.Repeat([]byte{0}, 50)...), 283 | f: &Frame{ 284 | Destination: Broadcast, 285 | Source: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 286 | ServiceVLAN: &VLAN{ 287 | DropEligible: true, 288 | ID: 100, 289 | }, 290 | VLAN: &VLAN{ 291 | Priority: 1, 292 | ID: 101, 293 | }, 294 | EtherType: EtherTypeARP, 295 | Payload: bytes.Repeat([]byte{0}, 50), 296 | }, 297 | }, 298 | } 299 | 300 | for _, tt := range tests { 301 | t.Run(tt.desc, func(t *testing.T) { 302 | f := new(Frame) 303 | if err := f.UnmarshalBinary(tt.b); err != nil { 304 | if want, got := tt.err, err; want != got { 305 | t.Fatalf("unexpected error: %v != %v", want, got) 306 | } 307 | 308 | return 309 | } 310 | 311 | if want, got := tt.f, f; !reflect.DeepEqual(want, got) { 312 | t.Fatalf("unexpected Frame:\n- want: %v\n- got: %v", want, got) 313 | } 314 | }) 315 | } 316 | } 317 | 318 | func TestFrameUnmarshalFCS(t *testing.T) { 319 | tests := []struct { 320 | desc string 321 | b []byte 322 | f *Frame 323 | err error 324 | }{ 325 | { 326 | desc: "too short for FCS", 327 | b: []byte{1, 2, 3}, 328 | err: io.ErrUnexpectedEOF, 329 | }, 330 | { 331 | desc: "invalid FCS", 332 | b: []byte{1, 2, 3, 4}, 333 | err: ErrInvalidFCS, 334 | }, 335 | { 336 | desc: "IPv4, no VLANs", 337 | b: append( 338 | append( 339 | []byte{ 340 | 0, 1, 0, 1, 0, 1, 341 | 1, 0, 1, 0, 1, 0, 342 | 0x08, 0x00, 343 | }, 344 | bytes.Repeat([]byte{0}, 50)..., 345 | ), 346 | []byte{159, 205, 24, 60}..., 347 | ), 348 | f: &Frame{ 349 | Destination: net.HardwareAddr{0, 1, 0, 1, 0, 1}, 350 | Source: net.HardwareAddr{1, 0, 1, 0, 1, 0}, 351 | EtherType: EtherTypeIPv4, 352 | Payload: bytes.Repeat([]byte{0}, 50), 353 | }, 354 | }, 355 | } 356 | 357 | for _, tt := range tests { 358 | t.Run(tt.desc, func(t *testing.T) { 359 | f := new(Frame) 360 | if err := f.UnmarshalFCS(tt.b); err != nil { 361 | if want, got := tt.err, err; want != got { 362 | t.Fatalf("unexpected error: %v != %v", want, got) 363 | } 364 | 365 | return 366 | } 367 | 368 | if want, got := tt.f, f; !reflect.DeepEqual(want, got) { 369 | t.Fatalf("unexpected Frame:\n- want: %v\n- got: %v", want, got) 370 | } 371 | }) 372 | } 373 | } 374 | 375 | // Benchmarks for Frame.MarshalBinary with varying VLAN tags and payloads 376 | 377 | func BenchmarkFrameMarshalBinary(b *testing.B) { 378 | f := &Frame{ 379 | Payload: []byte{0, 1, 2, 3, 4}, 380 | } 381 | 382 | benchmarkFrameMarshalBinary(b, f) 383 | } 384 | 385 | func BenchmarkFrameMarshalBinaryCVLAN(b *testing.B) { 386 | f := &Frame{ 387 | VLAN: &VLAN{ 388 | Priority: PriorityBackground, 389 | ID: 10, 390 | }, 391 | Payload: []byte{0, 1, 2, 3, 4}, 392 | } 393 | 394 | benchmarkFrameMarshalBinary(b, f) 395 | } 396 | 397 | func BenchmarkFrameMarshalBinarySVLANCVLAN(b *testing.B) { 398 | f := &Frame{ 399 | ServiceVLAN: &VLAN{ 400 | Priority: PriorityBackground, 401 | ID: 10, 402 | }, 403 | VLAN: &VLAN{ 404 | Priority: PriorityBestEffort, 405 | ID: 20, 406 | }, 407 | Payload: []byte{0, 1, 2, 3, 4}, 408 | } 409 | 410 | benchmarkFrameMarshalBinary(b, f) 411 | } 412 | 413 | func BenchmarkFrameMarshalBinaryJumboPayload(b *testing.B) { 414 | f := &Frame{ 415 | Payload: make([]byte, 8192), 416 | } 417 | 418 | benchmarkFrameMarshalBinary(b, f) 419 | } 420 | 421 | func benchmarkFrameMarshalBinary(b *testing.B, f *Frame) { 422 | f.Destination = net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad} 423 | f.Source = net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde} 424 | 425 | b.ResetTimer() 426 | b.ReportAllocs() 427 | for i := 0; i < b.N; i++ { 428 | if _, err := f.MarshalBinary(); err != nil { 429 | b.Fatal(err) 430 | } 431 | } 432 | } 433 | 434 | // Benchmarks for Frame.MarshalFCS 435 | 436 | func BenchmarkFrameMarshalFCS(b *testing.B) { 437 | f := &Frame{ 438 | Payload: []byte{0, 1, 2, 3, 4}, 439 | } 440 | 441 | benchmarkFrameMarshalFCS(b, f) 442 | } 443 | 444 | func benchmarkFrameMarshalFCS(b *testing.B, f *Frame) { 445 | f.Destination = net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad} 446 | f.Source = net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde} 447 | 448 | b.ResetTimer() 449 | b.ReportAllocs() 450 | for i := 0; i < b.N; i++ { 451 | if _, err := f.MarshalFCS(); err != nil { 452 | b.Fatal(err) 453 | } 454 | } 455 | } 456 | 457 | // Benchmarks for Frame.UnmarshalBinary with varying VLAN tags and payloads 458 | 459 | func BenchmarkFrameUnmarshalBinary(b *testing.B) { 460 | f := &Frame{ 461 | Payload: []byte{0, 1, 2, 3, 4}, 462 | } 463 | 464 | benchmarkFrameUnmarshalBinary(b, f) 465 | } 466 | 467 | func BenchmarkFrameUnmarshalBinaryCVLAN(b *testing.B) { 468 | f := &Frame{ 469 | VLAN: &VLAN{ 470 | Priority: PriorityBackground, 471 | ID: 10, 472 | }, 473 | 474 | Payload: []byte{0, 1, 2, 3, 4}, 475 | } 476 | 477 | benchmarkFrameUnmarshalBinary(b, f) 478 | } 479 | 480 | func BenchmarkFrameUnmarshalBinarySVLANCVLAN(b *testing.B) { 481 | f := &Frame{ 482 | ServiceVLAN: &VLAN{ 483 | Priority: PriorityBackground, 484 | ID: 10, 485 | }, 486 | VLAN: &VLAN{ 487 | Priority: PriorityBestEffort, 488 | ID: 20, 489 | }, 490 | Payload: []byte{0, 1, 2, 3, 4}, 491 | } 492 | 493 | benchmarkFrameUnmarshalBinary(b, f) 494 | } 495 | 496 | func BenchmarkFrameUnmarshalBinaryJumboPayload(b *testing.B) { 497 | f := &Frame{ 498 | Payload: make([]byte, 8192), 499 | } 500 | 501 | benchmarkFrameUnmarshalBinary(b, f) 502 | } 503 | 504 | func benchmarkFrameUnmarshalBinary(b *testing.B, f *Frame) { 505 | f.Destination = net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad} 506 | f.Source = net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde} 507 | 508 | fb, err := f.MarshalBinary() 509 | if err != nil { 510 | b.Fatal(err) 511 | } 512 | 513 | b.ResetTimer() 514 | b.ReportAllocs() 515 | for i := 0; i < b.N; i++ { 516 | if err := f.UnmarshalBinary(fb); err != nil { 517 | b.Fatal(err) 518 | } 519 | } 520 | } 521 | 522 | // Benchmarks for Frame.UnmarshalFCS 523 | 524 | func BenchmarkFrameUnmarshalFCS(b *testing.B) { 525 | f := &Frame{ 526 | Payload: []byte{0, 1, 2, 3, 4}, 527 | } 528 | 529 | benchmarkFrameUnmarshalFCS(b, f) 530 | } 531 | 532 | func benchmarkFrameUnmarshalFCS(b *testing.B, f *Frame) { 533 | f.Destination = net.HardwareAddr{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad} 534 | f.Source = net.HardwareAddr{0xad, 0xbe, 0xef, 0xde, 0xad, 0xde} 535 | 536 | fb, err := f.MarshalFCS() 537 | if err != nil { 538 | b.Fatal(err) 539 | } 540 | 541 | b.ResetTimer() 542 | b.ReportAllocs() 543 | for i := 0; i < b.N; i++ { 544 | if err := f.UnmarshalFCS(fb); err != nil { 545 | b.Fatal(err) 546 | } 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | //go:build gofuzz 2 | // +build gofuzz 3 | 4 | package ethernet 5 | 6 | func Fuzz(data []byte) int { 7 | f := new(Frame) 8 | if err := f.UnmarshalBinary(data); err != nil { 9 | return 0 10 | } 11 | 12 | if _, err := f.MarshalBinary(); err != nil { 13 | panic(err) 14 | } 15 | 16 | if err := f.UnmarshalFCS(data); err != nil { 17 | return 0 18 | } 19 | 20 | if _, err := f.MarshalFCS(); err != nil { 21 | panic(err) 22 | } 23 | 24 | return 1 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mdlayher/ethernet 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/mdlayher/packet v1.0.0 7 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 2 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 3 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 4 | github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= 5 | github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 6 | github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= 7 | github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= 8 | github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w= 9 | github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 12 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= 13 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 14 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 15 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 16 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 17 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 18 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= 19 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 21 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 22 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 23 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output=string.go -type=EtherType"; DO NOT EDIT. 2 | 3 | package ethernet 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _EtherType_name_0 = "EtherTypeIPv4" 9 | _EtherType_name_1 = "EtherTypeARP" 10 | _EtherType_name_2 = "EtherTypeVLAN" 11 | _EtherType_name_3 = "EtherTypeIPv6" 12 | _EtherType_name_4 = "EtherTypeServiceVLAN" 13 | ) 14 | 15 | var ( 16 | _EtherType_index_0 = [...]uint8{0, 13} 17 | _EtherType_index_1 = [...]uint8{0, 12} 18 | _EtherType_index_2 = [...]uint8{0, 13} 19 | _EtherType_index_3 = [...]uint8{0, 13} 20 | _EtherType_index_4 = [...]uint8{0, 20} 21 | ) 22 | 23 | func (i EtherType) String() string { 24 | switch { 25 | case i == 2048: 26 | return _EtherType_name_0 27 | case i == 2054: 28 | return _EtherType_name_1 29 | case i == 33024: 30 | return _EtherType_name_2 31 | case i == 34525: 32 | return _EtherType_name_3 33 | case i == 34984: 34 | return _EtherType_name_4 35 | default: 36 | return fmt.Sprintf("EtherType(%d)", i) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vlan.go: -------------------------------------------------------------------------------- 1 | package ethernet 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | // VLANNone is a special VLAN ID which indicates that no VLAN is being 11 | // used in a Frame. In this case, the VLAN's other fields may be used 12 | // to indicate a Frame's priority. 13 | VLANNone = 0x000 14 | 15 | // VLANMax is a reserved VLAN ID which may indicate a wildcard in some 16 | // management systems, but may not be configured or transmitted in a 17 | // VLAN tag. 18 | VLANMax = 0xfff 19 | ) 20 | 21 | // ErrInvalidVLAN is returned when a VLAN tag is invalid due to one of the 22 | // following reasons: 23 | // - Priority of greater than 7 is detected 24 | // - ID of greater than 4094 (0xffe) is detected 25 | // - A customer VLAN does not follow a service VLAN (when using Q-in-Q) 26 | var ErrInvalidVLAN = errors.New("invalid VLAN") 27 | 28 | // Priority is an IEEE P802.1p priority level. Priority can be any value from 29 | // 0 to 7. 30 | // 31 | // It is important to note that priority 1 (PriorityBackground) actually has 32 | // a lower priority than 0 (PriorityBestEffort). All other Priority constants 33 | // indicate higher priority as the integer values increase. 34 | type Priority uint8 35 | 36 | // IEEE P802.1p recommended priority levels. Note that PriorityBackground has 37 | // a lower priority than PriorityBestEffort. 38 | const ( 39 | PriorityBackground Priority = 1 40 | PriorityBestEffort Priority = 0 41 | PriorityExcellentEffort Priority = 2 42 | PriorityCriticalApplications Priority = 3 43 | PriorityVideo Priority = 4 44 | PriorityVoice Priority = 5 45 | PriorityInternetworkControl Priority = 6 46 | PriorityNetworkControl Priority = 7 47 | ) 48 | 49 | // A VLAN is an IEEE 802.1Q Virtual LAN (VLAN) tag. A VLAN contains 50 | // information regarding traffic priority and a VLAN identifier for 51 | // a given Frame. 52 | type VLAN struct { 53 | // Priority specifies a IEEE P802.1p priority level. Priority can be any 54 | // value from 0 to 7. 55 | Priority Priority 56 | 57 | // DropEligible indicates if a Frame is eligible to be dropped in the 58 | // presence of network congestion. 59 | DropEligible bool 60 | 61 | // ID specifies the VLAN ID for a Frame. ID can be any value from 0 to 62 | // 4094 (0x000 to 0xffe), allowing up to 4094 VLANs. 63 | // 64 | // If ID is 0 (0x000, VLANNone), no VLAN is specified, and the other fields 65 | // simply indicate a Frame's priority. 66 | ID uint16 67 | } 68 | 69 | // MarshalBinary allocates a byte slice and marshals a VLAN into binary form. 70 | func (v *VLAN) MarshalBinary() ([]byte, error) { 71 | b := make([]byte, 2) 72 | _, err := v.read(b) 73 | return b, err 74 | } 75 | 76 | // read reads data from a VLAN into b. read is used to marshal a VLAN into 77 | // binary form, but does not allocate on its own. 78 | func (v *VLAN) read(b []byte) (int, error) { 79 | // Check for VLAN priority in valid range 80 | if v.Priority > PriorityNetworkControl { 81 | return 0, ErrInvalidVLAN 82 | } 83 | 84 | // Check for VLAN ID in valid range 85 | if v.ID >= VLANMax { 86 | return 0, ErrInvalidVLAN 87 | } 88 | 89 | // 3 bits: priority 90 | ub := uint16(v.Priority) << 13 91 | 92 | // 1 bit: drop eligible 93 | var drop uint16 94 | if v.DropEligible { 95 | drop = 1 96 | } 97 | ub |= drop << 12 98 | 99 | // 12 bits: VLAN ID 100 | ub |= v.ID 101 | 102 | binary.BigEndian.PutUint16(b, ub) 103 | return 2, nil 104 | } 105 | 106 | // UnmarshalBinary unmarshals a byte slice into a VLAN. 107 | func (v *VLAN) UnmarshalBinary(b []byte) error { 108 | // VLAN tag is always 2 bytes 109 | if len(b) != 2 { 110 | return io.ErrUnexpectedEOF 111 | } 112 | 113 | // 3 bits: priority 114 | // 1 bit : drop eligible 115 | // 12 bits: VLAN ID 116 | ub := binary.BigEndian.Uint16(b[0:2]) 117 | v.Priority = Priority(uint8(ub >> 13)) 118 | v.DropEligible = ub&0x1000 != 0 119 | v.ID = ub & 0x0fff 120 | 121 | // Check for VLAN ID in valid range 122 | if v.ID >= VLANMax { 123 | return ErrInvalidVLAN 124 | } 125 | 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /vlan_test.go: -------------------------------------------------------------------------------- 1 | package ethernet 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestVLANMarshalBinary(t *testing.T) { 11 | tests := []struct { 12 | desc string 13 | v *VLAN 14 | b []byte 15 | err error 16 | }{ 17 | { 18 | desc: "VLAN priority too large", 19 | v: &VLAN{ 20 | Priority: 8, 21 | }, 22 | err: ErrInvalidVLAN, 23 | }, 24 | { 25 | desc: "VLAN ID too large", 26 | v: &VLAN{ 27 | ID: 4095, 28 | }, 29 | err: ErrInvalidVLAN, 30 | }, 31 | { 32 | desc: "empty VLAN", 33 | v: &VLAN{}, 34 | b: []byte{0x00, 0x00}, 35 | }, 36 | { 37 | desc: "VLAN: PRI 1, ID 101", 38 | v: &VLAN{ 39 | Priority: 1, 40 | ID: 101, 41 | }, 42 | b: []byte{0x20, 0x65}, 43 | }, 44 | { 45 | desc: "VLANs: PRI 0, DROP, ID 100", 46 | v: &VLAN{ 47 | DropEligible: true, 48 | ID: 100, 49 | }, 50 | b: []byte{0x10, 0x64}, 51 | }, 52 | } 53 | 54 | for _, tt := range tests { 55 | t.Run(tt.desc, func(t *testing.T) { 56 | b, err := tt.v.MarshalBinary() 57 | if err != nil { 58 | if want, got := tt.err, err; want != got { 59 | t.Fatalf("unexpected error: %v != %v", want, got) 60 | } 61 | 62 | return 63 | } 64 | 65 | if want, got := tt.b, b; !bytes.Equal(want, got) { 66 | t.Fatalf("unexpected VLAN bytes:\n- want: %v\n- got: %v", want, got) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | func TestVLANUnmarshalBinary(t *testing.T) { 73 | tests := []struct { 74 | desc string 75 | b []byte 76 | v *VLAN 77 | err error 78 | }{ 79 | { 80 | desc: "nil buffer", 81 | err: io.ErrUnexpectedEOF, 82 | }, 83 | { 84 | desc: "short buffer", 85 | b: []byte{0}, 86 | err: io.ErrUnexpectedEOF, 87 | }, 88 | { 89 | desc: "VLAN ID too large", 90 | b: []byte{0xff, 0xff}, 91 | err: ErrInvalidVLAN, 92 | }, 93 | { 94 | desc: "VLAN: PRI 1, ID 101", 95 | b: []byte{0x20, 0x65}, 96 | v: &VLAN{ 97 | Priority: 1, 98 | ID: 101, 99 | }, 100 | }, 101 | { 102 | desc: "VLAN: PRI 0, DROP, ID 100", 103 | b: []byte{0x10, 0x64}, 104 | v: &VLAN{ 105 | DropEligible: true, 106 | ID: 100, 107 | }, 108 | }, 109 | } 110 | 111 | for _, tt := range tests { 112 | t.Run(tt.desc, func(t *testing.T) { 113 | v := new(VLAN) 114 | if err := v.UnmarshalBinary(tt.b); err != nil { 115 | if want, got := tt.err, err; want != got { 116 | t.Fatalf("unexpected error: %v != %v", want, got) 117 | } 118 | 119 | return 120 | } 121 | 122 | if want, got := tt.v, v; !reflect.DeepEqual(want, got) { 123 | t.Fatalf("unexpected VLAN:\n- want: %v\n- got: %v", want, got) 124 | } 125 | }) 126 | } 127 | } 128 | 129 | // Benchmarks for VLAN.MarshalBinary 130 | 131 | func BenchmarkVLANMarshalBinary(b *testing.B) { 132 | v := &VLAN{ 133 | Priority: PriorityBackground, 134 | ID: 10, 135 | } 136 | 137 | b.ResetTimer() 138 | b.ReportAllocs() 139 | for i := 0; i < b.N; i++ { 140 | if _, err := v.MarshalBinary(); err != nil { 141 | b.Fatal(err) 142 | } 143 | } 144 | } 145 | 146 | // Benchmarks for VLAN.UnmarshalBinary 147 | 148 | func BenchmarkVLANUnmarshalBinary(b *testing.B) { 149 | v := &VLAN{ 150 | Priority: PriorityBestEffort, 151 | ID: 20, 152 | } 153 | 154 | vb, err := v.MarshalBinary() 155 | if err != nil { 156 | b.Fatal(err) 157 | } 158 | 159 | b.ResetTimer() 160 | b.ReportAllocs() 161 | for i := 0; i < b.N; i++ { 162 | if err := v.UnmarshalBinary(vb); err != nil { 163 | b.Fatal(err) 164 | } 165 | } 166 | } 167 | --------------------------------------------------------------------------------