diff options
Diffstat (limited to 'pkg/tcpip/transport/icmp')
-rw-r--r-- | pkg/tcpip/transport/icmp/BUILD | 61 | ||||
-rw-r--r-- | pkg/tcpip/transport/icmp/icmp_packet_list.go | 221 | ||||
-rw-r--r-- | pkg/tcpip/transport/icmp/icmp_state_autogen.go | 167 | ||||
-rw-r--r-- | pkg/tcpip/transport/icmp/icmp_test.go | 239 |
4 files changed, 388 insertions, 300 deletions
diff --git a/pkg/tcpip/transport/icmp/BUILD b/pkg/tcpip/transport/icmp/BUILD deleted file mode 100644 index 4718ec4ec..000000000 --- a/pkg/tcpip/transport/icmp/BUILD +++ /dev/null @@ -1,61 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -package(licenses = ["notice"]) - -go_template_instance( - name = "icmp_packet_list", - out = "icmp_packet_list.go", - package = "icmp", - prefix = "icmpPacket", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*icmpPacket", - "Linker": "*icmpPacket", - }, -) - -go_library( - name = "icmp", - srcs = [ - "endpoint.go", - "endpoint_state.go", - "icmp_packet_list.go", - "protocol.go", - ], - imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"], - visibility = ["//visibility:public"], - deps = [ - "//pkg/sleep", - "//pkg/sync", - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/ports", - "//pkg/tcpip/stack", - "//pkg/tcpip/transport", - "//pkg/tcpip/transport/internal/network", - "//pkg/tcpip/transport/raw", - "//pkg/tcpip/transport/tcp", - "//pkg/waiter", - ], -) - -go_test( - name = "icmp_x_test", - size = "small", - srcs = ["icmp_test.go"], - deps = [ - ":icmp", - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/checker", - "//pkg/tcpip/header", - "//pkg/tcpip/link/channel", - "//pkg/tcpip/link/sniffer", - "//pkg/tcpip/network/ipv4", - "//pkg/tcpip/stack", - "//pkg/tcpip/testutil", - "//pkg/waiter", - ], -) diff --git a/pkg/tcpip/transport/icmp/icmp_packet_list.go b/pkg/tcpip/transport/icmp/icmp_packet_list.go new file mode 100644 index 000000000..0aacdad3f --- /dev/null +++ b/pkg/tcpip/transport/icmp/icmp_packet_list.go @@ -0,0 +1,221 @@ +package icmp + +// ElementMapper provides an identity mapping by default. +// +// This can be replaced to provide a struct that maps elements to linker +// objects, if they are not the same. An ElementMapper is not typically +// required if: Linker is left as is, Element is left as is, or Linker and +// Element are the same type. +type icmpPacketElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (icmpPacketElementMapper) linkerFor(elem *icmpPacket) *icmpPacket { return elem } + +// List is an intrusive list. Entries can be added to or removed from the list +// in O(1) time and with no additional memory allocations. +// +// The zero value for List is an empty list ready to use. +// +// To iterate over a list (where l is a List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e. +// } +// +// +stateify savable +type icmpPacketList struct { + head *icmpPacket + tail *icmpPacket +} + +// Reset resets list l to the empty state. +func (l *icmpPacketList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +// +//go:nosplit +func (l *icmpPacketList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +// +//go:nosplit +func (l *icmpPacketList) Front() *icmpPacket { + return l.head +} + +// Back returns the last element of list l or nil. +// +//go:nosplit +func (l *icmpPacketList) Back() *icmpPacket { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +// +//go:nosplit +func (l *icmpPacketList) Len() (count int) { + for e := l.Front(); e != nil; e = (icmpPacketElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +// +//go:nosplit +func (l *icmpPacketList) PushFront(e *icmpPacket) { + linker := icmpPacketElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + icmpPacketElementMapper{}.linkerFor(l.head).SetPrev(e) + } else { + l.tail = e + } + + l.head = e +} + +// PushBack inserts the element e at the back of list l. +// +//go:nosplit +func (l *icmpPacketList) PushBack(e *icmpPacket) { + linker := icmpPacketElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(e) + } else { + l.head = e + } + + l.tail = e +} + +// PushBackList inserts list m at the end of list l, emptying m. +// +//go:nosplit +func (l *icmpPacketList) PushBackList(m *icmpPacketList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head) + icmpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail) + + l.tail = m.tail + } + m.head = nil + m.tail = nil +} + +// InsertAfter inserts e after b. +// +//go:nosplit +func (l *icmpPacketList) InsertAfter(b, e *icmpPacket) { + bLinker := icmpPacketElementMapper{}.linkerFor(b) + eLinker := icmpPacketElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + icmpPacketElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +// +//go:nosplit +func (l *icmpPacketList) InsertBefore(a, e *icmpPacket) { + aLinker := icmpPacketElementMapper{}.linkerFor(a) + eLinker := icmpPacketElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + icmpPacketElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +// +//go:nosplit +func (l *icmpPacketList) Remove(e *icmpPacket) { + linker := icmpPacketElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + icmpPacketElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + icmpPacketElementMapper{}.linkerFor(next).SetPrev(prev) + } else if l.tail == e { + l.tail = prev + } + + linker.SetNext(nil) + linker.SetPrev(nil) +} + +// Entry is a default implementation of Linker. Users can add anonymous fields +// of this type to their structs to make them automatically implement the +// methods needed by List. +// +// +stateify savable +type icmpPacketEntry struct { + next *icmpPacket + prev *icmpPacket +} + +// Next returns the entry that follows e in the list. +// +//go:nosplit +func (e *icmpPacketEntry) Next() *icmpPacket { + return e.next +} + +// Prev returns the entry that precedes e in the list. +// +//go:nosplit +func (e *icmpPacketEntry) Prev() *icmpPacket { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +// +//go:nosplit +func (e *icmpPacketEntry) SetNext(elem *icmpPacket) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +// +//go:nosplit +func (e *icmpPacketEntry) SetPrev(elem *icmpPacket) { + e.prev = elem +} diff --git a/pkg/tcpip/transport/icmp/icmp_state_autogen.go b/pkg/tcpip/transport/icmp/icmp_state_autogen.go new file mode 100644 index 000000000..5ba2d9925 --- /dev/null +++ b/pkg/tcpip/transport/icmp/icmp_state_autogen.go @@ -0,0 +1,167 @@ +// automatically generated by stateify. + +package icmp + +import ( + "gvisor.dev/gvisor/pkg/state" + "gvisor.dev/gvisor/pkg/tcpip/buffer" +) + +func (p *icmpPacket) StateTypeName() string { + return "pkg/tcpip/transport/icmp.icmpPacket" +} + +func (p *icmpPacket) StateFields() []string { + return []string{ + "icmpPacketEntry", + "senderAddress", + "data", + "receivedAt", + } +} + +func (p *icmpPacket) beforeSave() {} + +// +checklocksignore +func (p *icmpPacket) StateSave(stateSinkObject state.Sink) { + p.beforeSave() + var dataValue buffer.VectorisedView + dataValue = p.saveData() + stateSinkObject.SaveValue(2, dataValue) + var receivedAtValue int64 + receivedAtValue = p.saveReceivedAt() + stateSinkObject.SaveValue(3, receivedAtValue) + stateSinkObject.Save(0, &p.icmpPacketEntry) + stateSinkObject.Save(1, &p.senderAddress) +} + +func (p *icmpPacket) afterLoad() {} + +// +checklocksignore +func (p *icmpPacket) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &p.icmpPacketEntry) + stateSourceObject.Load(1, &p.senderAddress) + stateSourceObject.LoadValue(2, new(buffer.VectorisedView), func(y interface{}) { p.loadData(y.(buffer.VectorisedView)) }) + stateSourceObject.LoadValue(3, new(int64), func(y interface{}) { p.loadReceivedAt(y.(int64)) }) +} + +func (e *endpoint) StateTypeName() string { + return "pkg/tcpip/transport/icmp.endpoint" +} + +func (e *endpoint) StateFields() []string { + return []string{ + "DefaultSocketOptionsHandler", + "transProto", + "waiterQueue", + "uniqueID", + "net", + "stats", + "ops", + "rcvReady", + "rcvList", + "rcvBufSize", + "rcvClosed", + "frozen", + "ident", + } +} + +// +checklocksignore +func (e *endpoint) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.DefaultSocketOptionsHandler) + stateSinkObject.Save(1, &e.transProto) + stateSinkObject.Save(2, &e.waiterQueue) + stateSinkObject.Save(3, &e.uniqueID) + stateSinkObject.Save(4, &e.net) + stateSinkObject.Save(5, &e.stats) + stateSinkObject.Save(6, &e.ops) + stateSinkObject.Save(7, &e.rcvReady) + stateSinkObject.Save(8, &e.rcvList) + stateSinkObject.Save(9, &e.rcvBufSize) + stateSinkObject.Save(10, &e.rcvClosed) + stateSinkObject.Save(11, &e.frozen) + stateSinkObject.Save(12, &e.ident) +} + +// +checklocksignore +func (e *endpoint) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.DefaultSocketOptionsHandler) + stateSourceObject.Load(1, &e.transProto) + stateSourceObject.Load(2, &e.waiterQueue) + stateSourceObject.Load(3, &e.uniqueID) + stateSourceObject.Load(4, &e.net) + stateSourceObject.Load(5, &e.stats) + stateSourceObject.Load(6, &e.ops) + stateSourceObject.Load(7, &e.rcvReady) + stateSourceObject.Load(8, &e.rcvList) + stateSourceObject.Load(9, &e.rcvBufSize) + stateSourceObject.Load(10, &e.rcvClosed) + stateSourceObject.Load(11, &e.frozen) + stateSourceObject.Load(12, &e.ident) + stateSourceObject.AfterLoad(e.afterLoad) +} + +func (l *icmpPacketList) StateTypeName() string { + return "pkg/tcpip/transport/icmp.icmpPacketList" +} + +func (l *icmpPacketList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (l *icmpPacketList) beforeSave() {} + +// +checklocksignore +func (l *icmpPacketList) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.head) + stateSinkObject.Save(1, &l.tail) +} + +func (l *icmpPacketList) afterLoad() {} + +// +checklocksignore +func (l *icmpPacketList) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.head) + stateSourceObject.Load(1, &l.tail) +} + +func (e *icmpPacketEntry) StateTypeName() string { + return "pkg/tcpip/transport/icmp.icmpPacketEntry" +} + +func (e *icmpPacketEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (e *icmpPacketEntry) beforeSave() {} + +// +checklocksignore +func (e *icmpPacketEntry) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.next) + stateSinkObject.Save(1, &e.prev) +} + +func (e *icmpPacketEntry) afterLoad() {} + +// +checklocksignore +func (e *icmpPacketEntry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.next) + stateSourceObject.Load(1, &e.prev) +} + +func init() { + state.Register((*icmpPacket)(nil)) + state.Register((*endpoint)(nil)) + state.Register((*icmpPacketList)(nil)) + state.Register((*icmpPacketEntry)(nil)) +} diff --git a/pkg/tcpip/transport/icmp/icmp_test.go b/pkg/tcpip/transport/icmp/icmp_test.go deleted file mode 100644 index 729f50e9a..000000000 --- a/pkg/tcpip/transport/icmp/icmp_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2021 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 icmp_test - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/buffer" - "gvisor.dev/gvisor/pkg/tcpip/checker" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/link/channel" - "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/testutil" - "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" - "gvisor.dev/gvisor/pkg/waiter" -) - -// TODO(https://gvisor.dev/issues/5623): Finish unit testing the icmp package. -// See the issue for remaining areas of work. - -var ( - localV4Addr1 = testutil.MustParse4("10.0.0.1") - localV4Addr2 = testutil.MustParse4("10.0.0.2") - remoteV4Addr = testutil.MustParse4("10.0.0.3") -) - -func addNICWithDefaultRoute(t *testing.T, s *stack.Stack, id tcpip.NICID, name string, addrV4 tcpip.Address) *channel.Endpoint { - t.Helper() - - ep := channel.New(1 /* size */, header.IPv4MinimumMTU, "" /* linkAddr */) - t.Cleanup(ep.Close) - - wep := stack.LinkEndpoint(ep) - if testing.Verbose() { - wep = sniffer.New(ep) - } - - opts := stack.NICOptions{Name: name} - if err := s.CreateNICWithOptions(id, wep, opts); err != nil { - t.Fatalf("s.CreateNIC(%d, _) = %s", id, err) - } - - protocolAddr := tcpip.ProtocolAddress{ - Protocol: ipv4.ProtocolNumber, - AddressWithPrefix: addrV4.WithPrefix(), - } - if err := s.AddProtocolAddress(id, protocolAddr, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", id, protocolAddr, err) - } - - s.AddRoute(tcpip.Route{ - Destination: header.IPv4EmptySubnet, - NIC: id, - }) - - return ep -} - -func writePayload(buf []byte) { - for i := range buf { - buf[i] = byte(i) - } -} - -func newICMPv4EchoRequest(payloadSize uint32) buffer.View { - buf := buffer.NewView(header.ICMPv4MinimumSize + int(payloadSize)) - writePayload(buf[header.ICMPv4MinimumSize:]) - - icmp := header.ICMPv4(buf) - icmp.SetType(header.ICMPv4Echo) - // No need to set the checksum; it is reset by the socket before the packet - // is sent. - - return buf -} - -// TestWriteUnboundWithBindToDevice exercises writing to an unbound ICMP socket -// when SO_BINDTODEVICE is set to the non-default NIC for that subnet. -// -// Only IPv4 is tested. The logic to determine which NIC to use is agnostic to -// the version of IP. -func TestWriteUnboundWithBindToDevice(t *testing.T) { - s := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4}, - HandleLocal: true, - }) - - // Add two NICs, both with default routes on the same subnet. The first NIC - // added will be the default NIC for that subnet. - defaultEP := addNICWithDefaultRoute(t, s, 1, "default", localV4Addr1) - alternateEP := addNICWithDefaultRoute(t, s, 2, "alternate", localV4Addr2) - - socket, err := s.NewEndpoint(icmp.ProtocolNumber4, ipv4.ProtocolNumber, &waiter.Queue{}) - if err != nil { - t.Fatalf("s.NewEndpoint(%d, %d, _) = %s", icmp.ProtocolNumber4, ipv4.ProtocolNumber, err) - } - defer socket.Close() - - echoPayloadSize := defaultEP.MTU() - header.IPv4MinimumSize - header.ICMPv4MinimumSize - - // Send a packet without SO_BINDTODEVICE. This verifies that the first NIC - // to be added is the default NIC to send packets when not explicitly bound. - { - buf := newICMPv4EchoRequest(echoPayloadSize) - r := buf.Reader() - n, err := socket.Write(&r, tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: remoteV4Addr}, - }) - if err != nil { - t.Fatalf("socket.Write(_, {To:%s}) = %s", remoteV4Addr, err) - } - if n != int64(len(buf)) { - t.Fatalf("got n = %d, want n = %d", n, len(buf)) - } - - // Verify the packet was sent out the default NIC. - p, ok := defaultEP.Read() - if !ok { - t.Fatalf("got defaultEP.Read(_) = _, false; want = _, true (packet wasn't written out)") - } - - vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views()) - b := vv.ToView() - - checker.IPv4(t, b, []checker.NetworkChecker{ - checker.SrcAddr(localV4Addr1), - checker.DstAddr(remoteV4Addr), - checker.ICMPv4( - checker.ICMPv4Type(header.ICMPv4Echo), - checker.ICMPv4Payload(buf[header.ICMPv4MinimumSize:]), - ), - }...) - - // Verify the packet was not sent out the alternate NIC. - if p, ok := alternateEP.Read(); ok { - t.Fatalf("got alternateEP.Read(_) = %+v, true; want = _, false", p) - } - } - - // Send a packet with SO_BINDTODEVICE. This exercises reliance on - // SO_BINDTODEVICE to route the packet to the alternate NIC. - { - // Use SO_BINDTODEVICE to send over the alternate NIC by default. - socket.SocketOptions().SetBindToDevice(2) - - buf := newICMPv4EchoRequest(echoPayloadSize) - r := buf.Reader() - n, err := socket.Write(&r, tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: remoteV4Addr}, - }) - if err != nil { - t.Fatalf("socket.Write(_, {To:%s}) = %s", tcpip.Address(remoteV4Addr), err) - } - if n != int64(len(buf)) { - t.Fatalf("got n = %d, want n = %d", n, len(buf)) - } - - // Verify the packet was not sent out the default NIC. - if p, ok := defaultEP.Read(); ok { - t.Fatalf("got defaultEP.Read(_) = %+v, true; want = _, false", p) - } - - // Verify the packet was sent out the alternate NIC. - p, ok := alternateEP.Read() - if !ok { - t.Fatalf("got alternateEP.Read(_) = _, false; want = _, true (packet wasn't written out)") - } - - vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views()) - b := vv.ToView() - - checker.IPv4(t, b, []checker.NetworkChecker{ - checker.SrcAddr(localV4Addr2), - checker.DstAddr(remoteV4Addr), - checker.ICMPv4( - checker.ICMPv4Type(header.ICMPv4Echo), - checker.ICMPv4Payload(buf[header.ICMPv4MinimumSize:]), - ), - }...) - } - - // Send a packet with SO_BINDTODEVICE cleared. This verifies that clearing - // the device binding will fallback to using the default NIC to send - // packets. - { - socket.SocketOptions().SetBindToDevice(0) - - buf := newICMPv4EchoRequest(echoPayloadSize) - r := buf.Reader() - n, err := socket.Write(&r, tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: remoteV4Addr}, - }) - if err != nil { - t.Fatalf("socket.Write(_, {To:%s}) = %s", tcpip.Address(remoteV4Addr), err) - } - if n != int64(len(buf)) { - t.Fatalf("got n = %d, want n = %d", n, len(buf)) - } - - // Verify the packet was sent out the default NIC. - p, ok := defaultEP.Read() - if !ok { - t.Fatalf("got defaultEP.Read(_) = _, false; want = _, true (packet wasn't written out)") - } - - vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views()) - b := vv.ToView() - - checker.IPv4(t, b, []checker.NetworkChecker{ - checker.SrcAddr(localV4Addr1), - checker.DstAddr(remoteV4Addr), - checker.ICMPv4( - checker.ICMPv4Type(header.ICMPv4Echo), - checker.ICMPv4Payload(buf[header.ICMPv4MinimumSize:]), - ), - }...) - - // Verify the packet was not sent out the alternate NIC. - if p, ok := alternateEP.Read(); ok { - t.Fatalf("got alternateEP.Read(_) = %+v, true; want = _, false", p) - } - } -} |