From a7d4a785b54a3add5935dd9de963f7de7b5ee3cb Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Wed, 21 Jun 2023 13:38:29 -0700 Subject: [PATCH] Set LinkHeader for pkts sent by raw packet socket Previously, when a packet was sent from a raw packet socket, the packet buffer's link header was left unpopulated and the link header was only found in the packet buffer's payload. This breaks the expectations of LinkEndpoints which expect the link layer header to always be populated when the link requires a header. PiperOrigin-RevId: 542349445 --- pkg/tcpip/link/channel/channel.go | 3 + pkg/tcpip/link/ethernet/ethernet.go | 11 +- pkg/tcpip/link/fdbased/endpoint.go | 14 +++ pkg/tcpip/link/fdbased/packet_dispatchers.go | 5 +- pkg/tcpip/link/loopback/loopback.go | 4 + pkg/tcpip/link/muxed/injectable.go | 3 + pkg/tcpip/link/nested/nested.go | 5 + .../link/packetsocket/packetsocket_test.go | 1 + pkg/tcpip/link/pipe/pipe.go | 3 + pkg/tcpip/link/sharedmem/sharedmem.go | 22 +++- pkg/tcpip/link/sharedmem/sharedmem_server.go | 22 +++- pkg/tcpip/link/waitable/waitable.go | 5 + pkg/tcpip/link/waitable/waitable_test.go | 5 + pkg/tcpip/link/xdp/endpoint.go | 10 +- .../network/internal/testutil/testutil.go | 3 + pkg/tcpip/network/ip_test.go | 5 + pkg/tcpip/stack/forwarding_test.go | 3 + pkg/tcpip/stack/nic.go | 7 ++ pkg/tcpip/stack/registration.go | 6 + pkg/tcpip/stack/stack.go | 2 +- pkg/tcpip/transport/datagram_test.go | 1 + pkg/tcpip/transport/packet/BUILD | 18 ++- pkg/tcpip/transport/packet/packet_test.go | 114 ++++++++++++++++++ 23 files changed, 254 insertions(+), 18 deletions(-) create mode 100644 pkg/tcpip/transport/packet/packet_test.go diff --git a/pkg/tcpip/link/channel/channel.go b/pkg/tcpip/link/channel/channel.go index 1e2843bef..a5d3c5796 100644 --- a/pkg/tcpip/link/channel/channel.go +++ b/pkg/tcpip/link/channel/channel.go @@ -288,3 +288,6 @@ func (*Endpoint) ARPHardwareType() header.ARPHardwareType { // AddHeader implements stack.LinkEndpoint.AddHeader. func (*Endpoint) AddHeader(stack.PacketBufferPtr) {} + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (*Endpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } diff --git a/pkg/tcpip/link/ethernet/ethernet.go b/pkg/tcpip/link/ethernet/ethernet.go index 33a2543c6..e6f1e4d11 100644 --- a/pkg/tcpip/link/ethernet/ethernet.go +++ b/pkg/tcpip/link/ethernet/ethernet.go @@ -60,11 +60,10 @@ func (e *Endpoint) MTU() uint32 { // DeliverNetworkPacket implements stack.NetworkDispatcher. func (e *Endpoint) DeliverNetworkPacket(_ tcpip.NetworkProtocolNumber, pkt stack.PacketBufferPtr) { - hdr, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) - if !ok { + if !e.ParseHeader(pkt) { return } - eth := header.Ethernet(hdr) + eth := header.Ethernet(pkt.LinkHeader().Slice()) dst := eth.DestinationAddress() if dst == header.EthernetBroadcastAddress { pkt.PktType = tcpip.PacketBroadcast @@ -113,3 +112,9 @@ func (*Endpoint) AddHeader(pkt stack.PacketBufferPtr) { } eth.Encode(&fields) } + +// ParseHeader implements stack.LinkEndpoint. +func (*Endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) + return ok +} diff --git a/pkg/tcpip/link/fdbased/endpoint.go b/pkg/tcpip/link/fdbased/endpoint.go index 974fcb0ce..05b3630d3 100644 --- a/pkg/tcpip/link/fdbased/endpoint.go +++ b/pkg/tcpip/link/fdbased/endpoint.go @@ -528,6 +528,20 @@ func (e *endpoint) AddHeader(pkt stack.PacketBufferPtr) { } } +func (e *endpoint) parseHeader(pkt stack.PacketBufferPtr) bool { + _, ok := pkt.LinkHeader().Consume(e.hdrSize) + return ok + +} + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (e *endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + if e.hdrSize > 0 { + return e.parseHeader(pkt) + } + return true +} + // writePacket writes outbound packets to the file descriptor. If it is not // currently writable, the packet is dropped. func (e *endpoint) writePacket(pkt stack.PacketBufferPtr) tcpip.Error { diff --git a/pkg/tcpip/link/fdbased/packet_dispatchers.go b/pkg/tcpip/link/fdbased/packet_dispatchers.go index 7d5ced987..0c2539808 100644 --- a/pkg/tcpip/link/fdbased/packet_dispatchers.go +++ b/pkg/tcpip/link/fdbased/packet_dispatchers.go @@ -189,11 +189,10 @@ func (d *readVDispatcher) dispatch() (bool, tcpip.Error) { var p tcpip.NetworkProtocolNumber if d.e.hdrSize > 0 { - hdr, ok := pkt.LinkHeader().Consume(d.e.hdrSize) - if !ok { + if !d.e.parseHeader(pkt) { return false, nil } - p = header.Ethernet(hdr).Type() + p = header.Ethernet(pkt.LinkHeader().Slice()).Type() } else { // We don't get any indication of what the packet is, so try to guess // if it's an IPv4 or IPv6 packet. diff --git a/pkg/tcpip/link/loopback/loopback.go b/pkg/tcpip/link/loopback/loopback.go index 85089e4e6..563a182c2 100644 --- a/pkg/tcpip/link/loopback/loopback.go +++ b/pkg/tcpip/link/loopback/loopback.go @@ -107,4 +107,8 @@ func (*endpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareLoopback } +// AddHeader implements stack.LinkEndpoint. func (*endpoint) AddHeader(stack.PacketBufferPtr) {} + +// ParseHeader implements stack.LinkEndpoint. +func (*endpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } diff --git a/pkg/tcpip/link/muxed/injectable.go b/pkg/tcpip/link/muxed/injectable.go index d5eaa8239..26990ba28 100644 --- a/pkg/tcpip/link/muxed/injectable.go +++ b/pkg/tcpip/link/muxed/injectable.go @@ -147,6 +147,9 @@ func (*InjectableEndpoint) ARPHardwareType() header.ARPHardwareType { // AddHeader implements stack.LinkEndpoint.AddHeader. func (*InjectableEndpoint) AddHeader(stack.PacketBufferPtr) {} +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (*InjectableEndpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } + // NewInjectableEndpoint creates a new multi-endpoint injectable endpoint. func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) *InjectableEndpoint { return &InjectableEndpoint{ diff --git a/pkg/tcpip/link/nested/nested.go b/pkg/tcpip/link/nested/nested.go index 1a327d84a..b20ce9004 100644 --- a/pkg/tcpip/link/nested/nested.go +++ b/pkg/tcpip/link/nested/nested.go @@ -147,3 +147,8 @@ func (e *Endpoint) ARPHardwareType() header.ARPHardwareType { func (e *Endpoint) AddHeader(pkt stack.PacketBufferPtr) { e.child.AddHeader(pkt) } + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (e *Endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + return e.child.ParseHeader(pkt) +} diff --git a/pkg/tcpip/link/packetsocket/packetsocket_test.go b/pkg/tcpip/link/packetsocket/packetsocket_test.go index 8fcac81ee..655497ca4 100644 --- a/pkg/tcpip/link/packetsocket/packetsocket_test.go +++ b/pkg/tcpip/link/packetsocket/packetsocket_test.go @@ -53,6 +53,7 @@ func (e *nullEndpoint) IsAttached() bool { return e.disp != func (*nullEndpoint) Wait() {} func (*nullEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } func (*nullEndpoint) AddHeader(stack.PacketBufferPtr) {} +func (*nullEndpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } var _ stack.NetworkDispatcher = (*testNetworkDispatcher)(nil) diff --git a/pkg/tcpip/link/pipe/pipe.go b/pkg/tcpip/link/pipe/pipe.go index 789c62a0a..f2c4858ed 100644 --- a/pkg/tcpip/link/pipe/pipe.go +++ b/pkg/tcpip/link/pipe/pipe.go @@ -123,3 +123,6 @@ func (*Endpoint) ARPHardwareType() header.ARPHardwareType { // AddHeader implements stack.LinkEndpoint. func (*Endpoint) AddHeader(stack.PacketBufferPtr) {} + +// ParseHeader implements stack.LinkEndpoint. +func (*Endpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } diff --git a/pkg/tcpip/link/sharedmem/sharedmem.go b/pkg/tcpip/link/sharedmem/sharedmem.go index 324c704a1..2ed7caab7 100644 --- a/pkg/tcpip/link/sharedmem/sharedmem.go +++ b/pkg/tcpip/link/sharedmem/sharedmem.go @@ -354,6 +354,21 @@ func (e *endpoint) AddHeader(pkt stack.PacketBufferPtr) { }) } +func (e *endpoint) parseHeader(pkt stack.PacketBufferPtr) bool { + _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) + return ok +} + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (e *endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + // Add ethernet header if needed. + if len(e.addr) == 0 { + return true + } + + return e.parseHeader(pkt) +} + func (e *endpoint) AddVirtioNetHeader(pkt stack.PacketBufferPtr) { virtio := header.VirtioNetHeader(pkt.VirtioNetHeader().Push(header.VirtioNetHeaderSize)) virtio.Encode(&header.VirtioNetHeaderFields{}) @@ -447,13 +462,12 @@ func (e *endpoint) dispatchLoop(d stack.NetworkDispatcher) { } var proto tcpip.NetworkProtocolNumber - if e.addr != "" { - hdr, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) - if !ok { + if len(e.addr) != 0 { + if !e.parseHeader(pkt) { pkt.DecRef() continue } - proto = header.Ethernet(hdr).Type() + proto = header.Ethernet(pkt.LinkHeader().Slice()).Type() } else { // We don't get any indication of what the packet is, so try to guess // if it's an IPv4 or IPv6 packet. diff --git a/pkg/tcpip/link/sharedmem/sharedmem_server.go b/pkg/tcpip/link/sharedmem/sharedmem_server.go index f29c98da1..c0840bd34 100644 --- a/pkg/tcpip/link/sharedmem/sharedmem_server.go +++ b/pkg/tcpip/link/sharedmem/sharedmem_server.go @@ -217,6 +217,21 @@ func (e *serverEndpoint) AddHeader(pkt stack.PacketBufferPtr) { }) } +func (e *serverEndpoint) parseHeader(pkt stack.PacketBufferPtr) bool { + _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) + return ok +} + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (e *serverEndpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + // Add ethernet header if needed. + if len(e.addr) == 0 { + return true + } + + return e.parseHeader(pkt) +} + func (e *serverEndpoint) AddVirtioNetHeader(pkt stack.PacketBufferPtr) { virtio := header.VirtioNetHeader(pkt.VirtioNetHeader().Push(header.VirtioNetHeaderSize)) virtio.Encode(&header.VirtioNetHeaderFields{}) @@ -304,13 +319,12 @@ func (e *serverEndpoint) dispatchLoop(d stack.NetworkDispatcher) { } } var proto tcpip.NetworkProtocolNumber - if e.addr != "" { - hdr, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) - if !ok { + if len(e.addr) != 0 { + if !e.parseHeader(pkt) { pkt.DecRef() continue } - proto = header.Ethernet(hdr).Type() + proto = header.Ethernet(pkt.LinkHeader().Slice()).Type() } else { // We don't get any indication of what the packet is, so try to guess // if it's an IPv4 or IPv6 packet. diff --git a/pkg/tcpip/link/waitable/waitable.go b/pkg/tcpip/link/waitable/waitable.go index 4000c06f9..7977e51b7 100644 --- a/pkg/tcpip/link/waitable/waitable.go +++ b/pkg/tcpip/link/waitable/waitable.go @@ -161,3 +161,8 @@ func (e *Endpoint) ARPHardwareType() header.ARPHardwareType { func (e *Endpoint) AddHeader(pkt stack.PacketBufferPtr) { e.lower.AddHeader(pkt) } + +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (e *Endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + return e.lower.ParseHeader(pkt) +} diff --git a/pkg/tcpip/link/waitable/waitable_test.go b/pkg/tcpip/link/waitable/waitable_test.go index c62a07cce..b22f2b059 100644 --- a/pkg/tcpip/link/waitable/waitable_test.go +++ b/pkg/tcpip/link/waitable/waitable_test.go @@ -92,6 +92,11 @@ func (*countedEndpoint) AddHeader(stack.PacketBufferPtr) { panic("unimplemented") } +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (*countedEndpoint) ParseHeader(stack.PacketBufferPtr) bool { + panic("unimplemented") +} + func TestWaitWrite(t *testing.T) { ep := &countedEndpoint{} wep := New(ep) diff --git a/pkg/tcpip/link/xdp/endpoint.go b/pkg/tcpip/link/xdp/endpoint.go index 81b938adb..7c34b8791 100644 --- a/pkg/tcpip/link/xdp/endpoint.go +++ b/pkg/tcpip/link/xdp/endpoint.go @@ -246,6 +246,12 @@ func (ep *endpoint) AddHeader(pkt stack.PacketBufferPtr) { }) } +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (ep *endpoint) ParseHeader(pkt stack.PacketBufferPtr) bool { + _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize) + return ok +} + // ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType. func (ep *endpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareEther @@ -361,8 +367,8 @@ func (ep *endpoint) dispatch() (bool, tcpip.Error) { Payload: buffer.MakeWithView(view), }) // AF_XDP packets always have a link header. - if _, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize); !ok { - panic(fmt.Sprintf("LinkHeader().Consume(%d) must succeed", header.EthernetMinimumSize)) + if !ep.ParseHeader(pkt) { + panic("ParseHeader(_) must succeed") } d.DeliverNetworkPacket(netProto, pkt) pkt.DecRef() diff --git a/pkg/tcpip/network/internal/testutil/testutil.go b/pkg/tcpip/network/internal/testutil/testutil.go index 629c6bd60..390eaa50e 100644 --- a/pkg/tcpip/network/internal/testutil/testutil.go +++ b/pkg/tcpip/network/internal/testutil/testutil.go @@ -94,6 +94,9 @@ func (*MockLinkEndpoint) ARPHardwareType() header.ARPHardwareType { return heade // AddHeader implements LinkEndpoint.AddHeader. func (*MockLinkEndpoint) AddHeader(stack.PacketBufferPtr) {} +// ParseHeader implements LinkEndpoint.ParseHeader. +func (*MockLinkEndpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } + // Close releases all resources. func (ep *MockLinkEndpoint) Close() { for _, pkt := range ep.WrittenPackets { diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go index 4611342e3..7d890a9ba 100644 --- a/pkg/tcpip/network/ip_test.go +++ b/pkg/tcpip/network/ip_test.go @@ -229,6 +229,11 @@ func (*testObject) AddHeader(stack.PacketBufferPtr) { panic("not implemented") } +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (*testObject) ParseHeader(stack.PacketBufferPtr) bool { + panic("not implemented") +} + type testContext struct { s *stack.Stack } diff --git a/pkg/tcpip/stack/forwarding_test.go b/pkg/tcpip/stack/forwarding_test.go index 00de02501..c54807b21 100644 --- a/pkg/tcpip/stack/forwarding_test.go +++ b/pkg/tcpip/stack/forwarding_test.go @@ -331,6 +331,9 @@ func (*fwdTestLinkEndpoint) ARPHardwareType() header.ARPHardwareType { // AddHeader implements stack.LinkEndpoint.AddHeader. func (*fwdTestLinkEndpoint) AddHeader(PacketBufferPtr) {} +// ParseHeader implements stack.LinkEndpoint.ParseHeader. +func (*fwdTestLinkEndpoint) ParseHeader(PacketBufferPtr) bool { return true } + func fwdTestNetFactory(t *testing.T, proto *fwdTestNetworkProtocol) (*faketime.ManualClock, *fwdTestLinkEndpoint, *fwdTestLinkEndpoint) { clock := faketime.NewManualClock() // Create a stack with the network protocol and two NICs. diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index bafe633d5..3e8a0a3ea 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -386,6 +386,13 @@ func (n *nic) writePacket(pkt PacketBufferPtr) tcpip.Error { return n.writeRawPacket(pkt) } +func (n *nic) writeRawPacketWithLinkHeaderInPayload(pkt PacketBufferPtr) tcpip.Error { + if !n.NetworkLinkEndpoint.ParseHeader(pkt) { + return &tcpip.ErrMalformedHeader{} + } + return n.writeRawPacket(pkt) +} + func (n *nic) writeRawPacket(pkt PacketBufferPtr) tcpip.Error { // Always an outgoing packet. pkt.PktType = tcpip.PacketOutgoing diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index a76e53287..4cd720820 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -1065,6 +1065,9 @@ type LinkWriter interface { // WritePackets writes packets. Must not be called with an empty list of // packet buffers. // + // Each packet must have the link-layer header set, if the link requires + // one. + // // WritePackets may modify the packet buffers, and takes ownership of the PacketBufferList. // it is not safe to use the PacketBufferList after a call to WritePackets. WritePackets(PacketBufferList) (int, tcpip.Error) @@ -1121,6 +1124,9 @@ type NetworkLinkEndpoint interface { // AddHeader adds a link layer header to the packet if required. AddHeader(PacketBufferPtr) + + // ParseHeader parses the link layer header to the packet. + ParseHeader(PacketBufferPtr) bool } // QueueingDiscipline provides a queueing strategy for outgoing packets (e.g diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index d0b4df7d7..382fcdd49 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -1887,7 +1887,7 @@ func (s *Stack) WriteRawPacket(nicID tcpip.NICID, proto tcpip.NetworkProtocolNum }) defer pkt.DecRef() pkt.NetworkProtocolNumber = proto - return nic.writeRawPacket(pkt) + return nic.writeRawPacketWithLinkHeaderInPayload(pkt) } // NetworkProtocolInstance returns the protocol instance in the stack for the diff --git a/pkg/tcpip/transport/datagram_test.go b/pkg/tcpip/transport/datagram_test.go index 0ff2dbf4b..9879f2149 100644 --- a/pkg/tcpip/transport/datagram_test.go +++ b/pkg/tcpip/transport/datagram_test.go @@ -169,6 +169,7 @@ func (e *mockEndpoint) IsAttached() bool { return e.disp != func (*mockEndpoint) Wait() {} func (*mockEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } func (*mockEndpoint) AddHeader(stack.PacketBufferPtr) {} +func (*mockEndpoint) ParseHeader(stack.PacketBufferPtr) bool { return true } func (e *mockEndpoint) releasePackets() { e.pkts.DecRef() e.pkts = stack.PacketBufferList{} diff --git a/pkg/tcpip/transport/packet/BUILD b/pkg/tcpip/transport/packet/BUILD index c07f25e4a..7b9874a7f 100644 --- a/pkg/tcpip/transport/packet/BUILD +++ b/pkg/tcpip/transport/packet/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "go_library") +load("//tools:defs.bzl", "go_library", "go_test") load("//tools/go_generics:defs.bzl", "go_template_instance") package( @@ -37,3 +37,19 @@ go_library( "//pkg/waiter", ], ) + +go_test( + name = "packet_test", + srcs = ["packet_test.go"], + deps = [ + "//pkg/tcpip", + "//pkg/tcpip/faketime", + "//pkg/tcpip/header", + "//pkg/tcpip/link/channel", + "//pkg/tcpip/link/ethernet", + "//pkg/tcpip/stack", + "//pkg/tcpip/transport/raw", + "//pkg/waiter", + "@com_github_google_go_cmp//cmp:go_default_library", + ], +) diff --git a/pkg/tcpip/transport/packet/packet_test.go b/pkg/tcpip/transport/packet/packet_test.go new file mode 100644 index 000000000..89199e93f --- /dev/null +++ b/pkg/tcpip/transport/packet/packet_test.go @@ -0,0 +1,114 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package packet_test + +import ( + "bytes" + "testing" + + "github.com/google/go-cmp/cmp" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/faketime" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/raw" + "gvisor.dev/gvisor/pkg/waiter" +) + +func TestWriteRaw(t *testing.T) { + const nicID = 1 + + tests := []struct { + name string + len int + expectErr tcpip.Error + }{ + { + name: "small", + len: header.EthernetMinimumSize - 1, + expectErr: &tcpip.ErrMalformedHeader{}, + }, + { + name: "exact", + len: header.EthernetMinimumSize, + expectErr: nil, + }, + { + name: "bigger", + len: header.EthernetMinimumSize + 1, + expectErr: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := stack.New(stack.Options{ + RawFactory: &raw.EndpointFactory{}, + AllowPacketEndpointWrite: true, + Clock: &faketime.NullClock{}, + }) + defer s.Destroy() + + chEP := channel.New(1, header.IPv6MinimumMTU, "") + if err := s.CreateNIC(nicID, ethernet.New(chEP)); err != nil { + t.Errorf("CreateNIC(%d, _) failed: %s", nicID, err) + } + + var wq waiter.Queue + ep, err := s.NewPacketEndpoint(false /* cooked */, 0 /* netProto */, &wq) + if err != nil { + t.Fatalf("s.NewPacketEndpoint(false, 0, _): %s", err) + } + defer ep.Close() + + bindAddr := tcpip.FullAddress{NIC: nicID} + if err := ep.Bind(bindAddr); err != nil { + t.Fatalf("ep.Bind(%#v): %s", bindAddr, err) + } + + data := make([]byte, test.len) + for i := range data { + data[i] = byte(i) + } + + var r bytes.Reader + r.Reset(data) + n, err := ep.Write(&r, tcpip.WriteOptions{}) + if diff := cmp.Diff(test.expectErr, err); diff != "" { + t.Fatalf("ep.Write(..) mismatch:\n%s", diff) + } + if test.expectErr != nil { + return + } + if want := int64(len(data)); n != want { + t.Errorf("got ep.Write(..) = %d, want = %d", n, want) + } + pkt := chEP.Read() + if pkt.IsNil() { + t.Fatal("Packet wasn't written out") + } + defer pkt.DecRef() + + if diff := cmp.Diff(data, stack.PayloadSince(pkt.LinkHeader()).AsSlice()); diff != "" { + t.Errorf("packet data mismatch:\n%s", diff) + } + if len := len(pkt.LinkHeader().Slice()); len != header.EthernetMinimumSize { + t.Errorf("got len(pkt.LinkHeader().Slice()) = %d, want = %d", len, header.EthernetMinimumSize) + } + }) + } +}