Drop loopback traffic from outside of the stack

Loopback traffic should be stack-local but gVisor has some clients
that depend on the ability to receive loopback traffic that originated
from outside of the stack. Because of this, we guard this change behind
IP protocol options.

Test: integration_test.TestExternalLoopbackTraffic
PiperOrigin-RevId: 363461242
This commit is contained in:
Ghanan Gowripalan
2021-03-17 11:12:06 -07:00
committed by gVisor bot
parent 3dd7ad13b4
commit 4065604e1b
8 changed files with 562 additions and 230 deletions
+68 -48
View File
@@ -593,7 +593,7 @@ func (e *endpoint) forwardPacket(pkt *stack.PacketBuffer) tcpip.Error {
// Check if the destination is owned by the stack.
if ep := e.protocol.findEndpointWithAddress(dstAddr); ep != nil {
ep.handlePacket(pkt)
ep.handleValidatedPacket(h, pkt)
return nil
}
@@ -634,12 +634,25 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
return
}
if !e.protocol.parse(pkt) {
h, ok := e.protocol.parseAndValidate(pkt)
if !ok {
stats.MalformedPacketsReceived.Increment()
return
}
if !e.nic.IsLoopback() {
if e.protocol.options.DropExternalLoopbackTraffic {
if header.IsV4LoopbackAddress(h.SourceAddress()) {
stats.InvalidSourceAddressesReceived.Increment()
return
}
if header.IsV4LoopbackAddress(h.DestinationAddress()) {
stats.InvalidDestinationAddressesReceived.Increment()
return
}
}
if e.protocol.stack.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv4(pkt.NetworkHeader().View()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint)
if addressEndpoint != nil {
@@ -662,62 +675,32 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
e.handlePacket(pkt)
e.handleValidatedPacket(h, pkt)
}
// handleLocalPacket is like HandlePacket except it does not perform the
// prerouting iptables hook or check for loopback traffic that originated from
// outside of the netstack (i.e. martian loopback packets).
func (e *endpoint) handleLocalPacket(pkt *stack.PacketBuffer, canSkipRXChecksum bool) {
stats := e.stats.ip
stats.PacketsReceived.Increment()
pkt = pkt.CloneToInbound()
if e.protocol.parse(pkt) {
pkt.RXTransportChecksumValidated = canSkipRXChecksum
e.handlePacket(pkt)
pkt.RXTransportChecksumValidated = canSkipRXChecksum
h, ok := e.protocol.parseAndValidate(pkt)
if !ok {
stats.MalformedPacketsReceived.Increment()
return
}
stats.MalformedPacketsReceived.Increment()
e.handleValidatedPacket(h, pkt)
}
// handlePacket is like HandlePacket except it does not perform the prerouting
// iptables hook.
func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
func (e *endpoint) handleValidatedPacket(h header.IPv4, pkt *stack.PacketBuffer) {
pkt.NICID = e.nic.ID()
stats := e.stats
h := header.IPv4(pkt.NetworkHeader().View())
if !h.IsValid(pkt.Data().Size() + pkt.NetworkHeader().View().Size() + pkt.TransportHeader().View().Size()) {
stats.ip.MalformedPacketsReceived.Increment()
return
}
// There has been some confusion regarding verifying checksums. We need
// just look for negative 0 (0xffff) as the checksum, as it's not possible to
// get positive 0 (0) for the checksum. Some bad implementations could get it
// when doing entry replacement in the early days of the Internet,
// however the lore that one needs to check for both persists.
//
// RFC 1624 section 1 describes the source of this confusion as:
// [the partial recalculation method described in RFC 1071] computes a
// result for certain cases that differs from the one obtained from
// scratch (one's complement of one's complement sum of the original
// fields).
//
// However RFC 1624 section 5 clarifies that if using the verification method
// "recommended by RFC 1071, it does not matter if an intermediate system
// generated a -0 instead of +0".
//
// RFC1071 page 1 specifies the verification method as:
// (3) To check a checksum, the 1's complement sum is computed over the
// same set of octets, including the checksum field. If the result
// is all 1 bits (-0 in 1's complement arithmetic), the check
// succeeds.
if h.CalculateChecksum() != 0xffff {
stats.ip.MalformedPacketsReceived.Increment()
return
}
srcAddr := h.SourceAddress()
dstAddr := h.DestinationAddress()
@@ -1114,13 +1097,46 @@ func (*protocol) Close() {}
// Wait implements stack.TransportProtocol.Wait.
func (*protocol) Wait() {}
// parse is like Parse but also attempts to parse the transport layer.
// parseAndValidate parses the packet (including its transport layer header) and
// returns the parsed IP header.
//
// Returns true if the network header was successfully parsed.
func (p *protocol) parse(pkt *stack.PacketBuffer) bool {
// Returns true if the IP header was successfully parsed.
func (p *protocol) parseAndValidate(pkt *stack.PacketBuffer) (header.IPv4, bool) {
transProtoNum, hasTransportHdr, ok := p.Parse(pkt)
if !ok {
return false
return nil, false
}
h := header.IPv4(pkt.NetworkHeader().View())
// Do not include the link header's size when calculating the size of the IP
// packet.
if !h.IsValid(pkt.Size() - pkt.LinkHeader().View().Size()) {
return nil, false
}
// There has been some confusion regarding verifying checksums. We need
// just look for negative 0 (0xffff) as the checksum, as it's not possible to
// get positive 0 (0) for the checksum. Some bad implementations could get it
// when doing entry replacement in the early days of the Internet,
// however the lore that one needs to check for both persists.
//
// RFC 1624 section 1 describes the source of this confusion as:
// [the partial recalculation method described in RFC 1071] computes a
// result for certain cases that differs from the one obtained from
// scratch (one's complement of one's complement sum of the original
// fields).
//
// However RFC 1624 section 5 clarifies that if using the verification method
// "recommended by RFC 1071, it does not matter if an intermediate system
// generated a -0 instead of +0".
//
// RFC1071 page 1 specifies the verification method as:
// (3) To check a checksum, the 1's complement sum is computed over the
// same set of octets, including the checksum field. If the result
// is all 1 bits (-0 in 1's complement arithmetic), the check
// succeeds.
if h.CalculateChecksum() != 0xffff {
return nil, false
}
if hasTransportHdr {
@@ -1134,7 +1150,7 @@ func (p *protocol) parse(pkt *stack.PacketBuffer) bool {
}
}
return true
return h, true
}
// Parse implements stack.NetworkProtocol.Parse.
@@ -1213,6 +1229,10 @@ func hashRoute(srcAddr, dstAddr tcpip.Address, protocol tcpip.TransportProtocolN
type Options struct {
// IGMP holds options for IGMP.
IGMP IGMPOptions
// DropExternalLoopbackTraffic indicates that inbound loopback packets (i.e.
// martian loopback packets) should be dropped.
DropExternalLoopbackTraffic bool
}
// NewProtocolWithOptions returns an IPv4 network protocol.
+46 -26
View File
@@ -884,9 +884,8 @@ func (e *endpoint) forwardPacket(pkt *stack.PacketBuffer) tcpip.Error {
dstAddr := h.DestinationAddress()
// Check if the destination is owned by the stack.
if ep := e.protocol.findEndpointWithAddress(dstAddr); ep != nil {
ep.handlePacket(pkt)
ep.handleValidatedPacket(h, pkt)
return nil
}
@@ -925,12 +924,25 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
return
}
if !e.protocol.parse(pkt) {
h, ok := e.protocol.parseAndValidate(pkt)
if !ok {
stats.MalformedPacketsReceived.Increment()
return
}
if !e.nic.IsLoopback() {
if e.protocol.options.DropExternalLoopbackTraffic {
if header.IsV6LoopbackAddress(h.SourceAddress()) {
stats.InvalidSourceAddressesReceived.Increment()
return
}
if header.IsV6LoopbackAddress(h.DestinationAddress()) {
stats.InvalidDestinationAddressesReceived.Increment()
return
}
}
if e.protocol.stack.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv6(pkt.NetworkHeader().View()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint)
if addressEndpoint != nil {
@@ -953,35 +965,31 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
e.handlePacket(pkt)
e.handleValidatedPacket(h, pkt)
}
// handleLocalPacket is like HandlePacket except it does not perform the
// prerouting iptables hook or check for loopback traffic that originated from
// outside of the netstack (i.e. martian loopback packets).
func (e *endpoint) handleLocalPacket(pkt *stack.PacketBuffer, canSkipRXChecksum bool) {
stats := e.stats.ip
stats.PacketsReceived.Increment()
pkt = pkt.CloneToInbound()
if e.protocol.parse(pkt) {
pkt.RXTransportChecksumValidated = canSkipRXChecksum
e.handlePacket(pkt)
return
}
pkt.RXTransportChecksumValidated = canSkipRXChecksum
stats.MalformedPacketsReceived.Increment()
}
// handlePacket is like HandlePacket except it does not perform the prerouting
// iptables hook.
func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
pkt.NICID = e.nic.ID()
stats := e.stats.ip
h := header.IPv6(pkt.NetworkHeader().View())
if !h.IsValid(pkt.Data().Size() + pkt.NetworkHeader().View().Size() + pkt.TransportHeader().View().Size()) {
h, ok := e.protocol.parseAndValidate(pkt)
if !ok {
stats.MalformedPacketsReceived.Increment()
return
}
e.handleValidatedPacket(h, pkt)
}
func (e *endpoint) handleValidatedPacket(h header.IPv6, pkt *stack.PacketBuffer) {
pkt.NICID = e.nic.ID()
stats := e.stats.ip
srcAddr := h.SourceAddress()
dstAddr := h.DestinationAddress()
@@ -1920,13 +1928,21 @@ func (*protocol) Close() {}
// Wait implements stack.TransportProtocol.Wait.
func (*protocol) Wait() {}
// parse is like Parse but also attempts to parse the transport layer.
// parseAndValidate parses the packet (including its transport layer header) and
// returns the parsed IP header.
//
// Returns true if the network header was successfully parsed.
func (p *protocol) parse(pkt *stack.PacketBuffer) bool {
// Returns true if the IP header was successfully parsed.
func (p *protocol) parseAndValidate(pkt *stack.PacketBuffer) (header.IPv6, bool) {
transProtoNum, hasTransportHdr, ok := p.Parse(pkt)
if !ok {
return false
return nil, false
}
h := header.IPv6(pkt.NetworkHeader().View())
// Do not include the link header's size when calculating the size of the IP
// packet.
if !h.IsValid(pkt.Size() - pkt.LinkHeader().View().Size()) {
return nil, false
}
if hasTransportHdr {
@@ -1940,7 +1956,7 @@ func (p *protocol) parse(pkt *stack.PacketBuffer) bool {
}
}
return true
return h, true
}
// Parse implements stack.NetworkProtocol.Parse.
@@ -2054,6 +2070,10 @@ type Options struct {
// DADConfigs holds the default DAD configurations used by IPv6 endpoints.
DADConfigs stack.DADConfigurations
// DropExternalLoopbackTraffic indicates that inbound loopback packets (i.e.
// martian loopback packets) should be dropped.
DropExternalLoopbackTraffic bool
}
// NewProtocolWithOptions returns an IPv6 network protocol.
+2
View File
@@ -72,11 +72,13 @@ go_test(
"//pkg/tcpip/buffer",
"//pkg/tcpip/checker",
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/link/loopback",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/tcpip/tests/utils",
"//pkg/tcpip/transport/icmp",
"//pkg/tcpip/transport/tcp",
"//pkg/tcpip/transport/udp",
"//pkg/waiter",
@@ -24,11 +24,13 @@ import (
"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/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/tests/utils"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
@@ -502,3 +504,262 @@ func TestLoopbackAcceptAllInSubnetTCP(t *testing.T) {
})
}
}
func TestExternalLoopbackTraffic(t *testing.T) {
const (
nicID1 = 1
nicID2 = 2
ipv4Loopback = tcpip.Address("\x7f\x00\x00\x01")
numPackets = 1
)
loopbackSourcedICMPv4 := func(e *channel.Endpoint) {
utils.RxICMPv4EchoRequest(e, ipv4Loopback, utils.Ipv4Addr.Address)
}
loopbackSourcedICMPv6 := func(e *channel.Endpoint) {
utils.RxICMPv6EchoRequest(e, header.IPv6Loopback, utils.Ipv6Addr.Address)
}
loopbackDestinedICMPv4 := func(e *channel.Endpoint) {
utils.RxICMPv4EchoRequest(e, utils.RemoteIPv4Addr, ipv4Loopback)
}
loopbackDestinedICMPv6 := func(e *channel.Endpoint) {
utils.RxICMPv6EchoRequest(e, utils.RemoteIPv6Addr, header.IPv6Loopback)
}
invalidSrcAddrStat := func(s tcpip.IPStats) *tcpip.StatCounter {
return s.InvalidSourceAddressesReceived
}
invalidDestAddrStat := func(s tcpip.IPStats) *tcpip.StatCounter {
return s.InvalidDestinationAddressesReceived
}
tests := []struct {
name string
dropExternalLoopback bool
forwarding bool
rxICMP func(*channel.Endpoint)
invalidAddressStat func(tcpip.IPStats) *tcpip.StatCounter
shouldAccept bool
}{
{
name: "IPv4 external loopback sourced traffic without forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: false,
rxICMP: loopbackSourcedICMPv4,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: true,
},
{
name: "IPv4 external loopback sourced traffic without forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: false,
rxICMP: loopbackSourcedICMPv4,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: false,
},
{
name: "IPv4 external loopback sourced traffic with forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: true,
rxICMP: loopbackSourcedICMPv4,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: true,
},
{
name: "IPv4 external loopback sourced traffic with forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: true,
rxICMP: loopbackSourcedICMPv4,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: false,
},
{
name: "IPv4 external loopback destined traffic without forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: false,
rxICMP: loopbackDestinedICMPv4,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
{
name: "IPv4 external loopback destined traffic without forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: false,
rxICMP: loopbackDestinedICMPv4,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
{
name: "IPv4 external loopback destined traffic with forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: true,
rxICMP: loopbackDestinedICMPv4,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: true,
},
{
name: "IPv4 external loopback destined traffic with forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: true,
rxICMP: loopbackDestinedICMPv4,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
{
name: "IPv6 external loopback sourced traffic without forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: false,
rxICMP: loopbackSourcedICMPv6,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: true,
},
{
name: "IPv6 external loopback sourced traffic without forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: false,
rxICMP: loopbackSourcedICMPv6,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: false,
},
{
name: "IPv6 external loopback sourced traffic with forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: true,
rxICMP: loopbackSourcedICMPv6,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: true,
},
{
name: "IPv6 external loopback sourced traffic with forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: true,
rxICMP: loopbackSourcedICMPv6,
invalidAddressStat: invalidSrcAddrStat,
shouldAccept: false,
},
{
name: "IPv6 external loopback destined traffic without forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: false,
rxICMP: loopbackDestinedICMPv6,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
{
name: "IPv6 external loopback destined traffic without forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: false,
rxICMP: loopbackDestinedICMPv6,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
{
name: "IPv6 external loopback destined traffic with forwarding and drop external loopback disabled",
dropExternalLoopback: false,
forwarding: true,
rxICMP: loopbackDestinedICMPv6,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: true,
},
{
name: "IPv6 external loopback destined traffic with forwarding and drop external loopback enabled",
dropExternalLoopback: true,
forwarding: true,
rxICMP: loopbackDestinedICMPv6,
invalidAddressStat: invalidDestAddrStat,
shouldAccept: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocolWithOptions(ipv4.Options{
DropExternalLoopbackTraffic: test.dropExternalLoopback,
}),
ipv6.NewProtocolWithOptions(ipv6.Options{
DropExternalLoopbackTraffic: test.dropExternalLoopback,
}),
},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
})
e := channel.New(1, header.IPv6MinimumMTU, "")
if err := s.CreateNIC(nicID1, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
}
if err := s.AddAddressWithPrefix(nicID1, ipv4.ProtocolNumber, utils.Ipv4Addr); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID1, ipv4.ProtocolNumber, utils.Ipv4Addr, err)
}
if err := s.AddAddressWithPrefix(nicID1, ipv6.ProtocolNumber, utils.Ipv6Addr); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID1, ipv6.ProtocolNumber, utils.Ipv6Addr, err)
}
if err := s.CreateNIC(nicID2, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
}
if err := s.AddAddress(nicID2, ipv4.ProtocolNumber, ipv4Loopback); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, ipv4.ProtocolNumber, ipv4Loopback, err)
}
if err := s.AddAddress(nicID2, ipv6.ProtocolNumber, header.IPv6Loopback); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, ipv6.ProtocolNumber, header.IPv6Loopback, err)
}
if test.forwarding {
if err := s.SetForwarding(ipv4.ProtocolNumber, true); err != nil {
t.Fatalf("SetForwarding(%d, true): %s", ipv4.ProtocolNumber, err)
}
if err := s.SetForwarding(ipv6.ProtocolNumber, true); err != nil {
t.Fatalf("SetForwarding(%d, true): %s", ipv6.ProtocolNumber, err)
}
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: nicID1,
},
tcpip.Route{
Destination: header.IPv6EmptySubnet,
NIC: nicID1,
},
tcpip.Route{
Destination: ipv4Loopback.WithPrefix().Subnet(),
NIC: nicID2,
},
tcpip.Route{
Destination: header.IPv6Loopback.WithPrefix().Subnet(),
NIC: nicID2,
},
})
stats := s.Stats().IP
invalidAddressStat := test.invalidAddressStat(stats)
deliveredPacketsStat := stats.PacketsDelivered
if got := invalidAddressStat.Value(); got != 0 {
t.Fatalf("got invalidAddressStat.Value() = %d, want = 0", got)
}
if got := deliveredPacketsStat.Value(); got != 0 {
t.Fatalf("got deliveredPacketsStat.Value() = %d, want = 0", got)
}
test.rxICMP(e)
var expectedInvalidPackets uint64
if !test.shouldAccept {
expectedInvalidPackets = numPackets
}
if got := invalidAddressStat.Value(); got != expectedInvalidPackets {
t.Fatalf("got invalidAddressStat.Value() = %d, want = %d", got, expectedInvalidPackets)
}
if got, want := deliveredPacketsStat.Value(), numPackets-expectedInvalidPackets; got != want {
t.Fatalf("got deliveredPacketsStat.Value() = %d, want = %d", got, want)
}
})
}
}
@@ -45,82 +45,61 @@ const (
func TestPingMulticastBroadcast(t *testing.T) {
const nicID = 1
rxIPv4ICMP := func(e *channel.Endpoint, dst tcpip.Address) {
totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize
hdr := buffer.NewPrependable(totalLen)
pkt := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
pkt.SetType(header.ICMPv4Echo)
pkt.SetCode(0)
pkt.SetChecksum(0)
pkt.SetChecksum(^header.Checksum(pkt, 0))
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLen),
Protocol: uint8(icmp.ProtocolNumber4),
TTL: ttl,
SrcAddr: utils.RemoteIPv4Addr,
DstAddr: dst,
})
ip.SetChecksum(^ip.CalculateChecksum())
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: hdr.View().ToVectorisedView(),
}))
}
rxIPv6ICMP := func(e *channel.Endpoint, dst tcpip.Address) {
totalLen := header.IPv6MinimumSize + header.ICMPv6MinimumSize
hdr := buffer.NewPrependable(totalLen)
pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
pkt.SetType(header.ICMPv6EchoRequest)
pkt.SetCode(0)
pkt.SetChecksum(0)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: utils.RemoteIPv6Addr,
Dst: dst,
}))
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: header.ICMPv6MinimumSize,
TransportProtocol: icmp.ProtocolNumber6,
HopLimit: ttl,
SrcAddr: utils.RemoteIPv6Addr,
DstAddr: dst,
})
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: hdr.View().ToVectorisedView(),
}))
}
tests := []struct {
name string
dstAddr tcpip.Address
name string
protoNum tcpip.NetworkProtocolNumber
rxICMP func(*channel.Endpoint, tcpip.Address, tcpip.Address)
srcAddr tcpip.Address
dstAddr tcpip.Address
expectedSrc tcpip.Address
}{
{
name: "IPv4 unicast",
dstAddr: utils.Ipv4Addr.Address,
name: "IPv4 unicast",
protoNum: header.IPv4ProtocolNumber,
dstAddr: utils.Ipv4Addr.Address,
srcAddr: utils.RemoteIPv4Addr,
rxICMP: utils.RxICMPv4EchoRequest,
expectedSrc: utils.Ipv4Addr.Address,
},
{
name: "IPv4 directed broadcast",
dstAddr: utils.Ipv4SubnetBcast,
name: "IPv4 directed broadcast",
protoNum: header.IPv4ProtocolNumber,
rxICMP: utils.RxICMPv4EchoRequest,
srcAddr: utils.RemoteIPv4Addr,
dstAddr: utils.Ipv4SubnetBcast,
expectedSrc: utils.Ipv4Addr.Address,
},
{
name: "IPv4 broadcast",
dstAddr: header.IPv4Broadcast,
name: "IPv4 broadcast",
protoNum: header.IPv4ProtocolNumber,
rxICMP: utils.RxICMPv4EchoRequest,
srcAddr: utils.RemoteIPv4Addr,
dstAddr: header.IPv4Broadcast,
expectedSrc: utils.Ipv4Addr.Address,
},
{
name: "IPv4 all-systems multicast",
dstAddr: header.IPv4AllSystems,
name: "IPv4 all-systems multicast",
protoNum: header.IPv4ProtocolNumber,
rxICMP: utils.RxICMPv4EchoRequest,
srcAddr: utils.RemoteIPv4Addr,
dstAddr: header.IPv4AllSystems,
expectedSrc: utils.Ipv4Addr.Address,
},
{
name: "IPv6 unicast",
dstAddr: utils.Ipv6Addr.Address,
name: "IPv6 unicast",
protoNum: header.IPv6ProtocolNumber,
rxICMP: utils.RxICMPv6EchoRequest,
srcAddr: utils.RemoteIPv6Addr,
dstAddr: utils.Ipv6Addr.Address,
expectedSrc: utils.Ipv6Addr.Address,
},
{
name: "IPv6 all-nodes multicast",
dstAddr: header.IPv6AllNodesMulticastAddress,
name: "IPv6 all-nodes multicast",
protoNum: header.IPv6ProtocolNumber,
rxICMP: utils.RxICMPv6EchoRequest,
srcAddr: utils.RemoteIPv6Addr,
dstAddr: header.IPv6AllNodesMulticastAddress,
expectedSrc: utils.Ipv6Addr.Address,
},
}
@@ -157,44 +136,29 @@ func TestPingMulticastBroadcast(t *testing.T) {
},
})
var rxICMP func(*channel.Endpoint, tcpip.Address)
var expectedSrc tcpip.Address
var expectedDst tcpip.Address
var protoNum tcpip.NetworkProtocolNumber
switch l := len(test.dstAddr); l {
case header.IPv4AddressSize:
rxICMP = rxIPv4ICMP
expectedSrc = utils.Ipv4Addr.Address
expectedDst = utils.RemoteIPv4Addr
protoNum = header.IPv4ProtocolNumber
case header.IPv6AddressSize:
rxICMP = rxIPv6ICMP
expectedSrc = utils.Ipv6Addr.Address
expectedDst = utils.RemoteIPv6Addr
protoNum = header.IPv6ProtocolNumber
default:
t.Fatalf("got unexpected address length = %d bytes", l)
}
rxICMP(e, test.dstAddr)
test.rxICMP(e, test.srcAddr, test.dstAddr)
pkt, ok := e.Read()
if !ok {
t.Fatal("expected ICMP response")
}
if pkt.Route.LocalAddress != expectedSrc {
t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", pkt.Route.LocalAddress, expectedSrc)
if pkt.Route.LocalAddress != test.expectedSrc {
t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", pkt.Route.LocalAddress, test.expectedSrc)
}
if pkt.Route.RemoteAddress != expectedDst {
t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.RemoteAddress, expectedDst)
// The destination of the response packet should be the source of the
// original packet.
if pkt.Route.RemoteAddress != test.srcAddr {
t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.RemoteAddress, test.srcAddr)
}
src, dst := s.NetworkProtocolInstance(protoNum).ParseAddresses(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
if src != expectedSrc {
t.Errorf("got pkt source = %s, want = %s", src, expectedSrc)
src, dst := s.NetworkProtocolInstance(test.protoNum).ParseAddresses(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
if src != test.expectedSrc {
t.Errorf("got pkt source = %s, want = %s", src, test.expectedSrc)
}
if dst != expectedDst {
t.Errorf("got pkt destination = %s, want = %s", dst, expectedDst)
// The destination of the response packet should be the source of the
// original packet.
if dst != test.srcAddr {
t.Errorf("got pkt destination = %s, want = %s", dst, test.srcAddr)
}
})
}
+66 -64
View File
@@ -16,6 +16,7 @@ package route_test
import (
"bytes"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
@@ -161,78 +162,79 @@ func TestLocalPing(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
HandleLocal: true,
})
e := test.linkEndpoint()
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
for _, dropExternalLoopback := range []bool{true, false} {
t.Run(fmt.Sprintf("DropExternalLoopback=%t", dropExternalLoopback), func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocolWithOptions(ipv4.Options{
DropExternalLoopbackTraffic: dropExternalLoopback,
}),
ipv6.NewProtocolWithOptions(ipv6.Options{
DropExternalLoopbackTraffic: dropExternalLoopback,
}),
},
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
HandleLocal: true,
})
e := test.linkEndpoint()
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if len(test.localAddr) != 0 {
if err := s.AddAddress(nicID, test.netProto, test.localAddr); err != nil {
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, test.netProto, test.localAddr, err)
}
}
if len(test.localAddr) != 0 {
if err := s.AddAddress(nicID, test.netProto, test.localAddr); err != nil {
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, test.netProto, test.localAddr, err)
}
}
var wq waiter.Queue
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.EventIn)
ep, err := s.NewEndpoint(test.transProto, test.netProto, &wq)
if err != nil {
t.Fatalf("s.NewEndpoint(%d, %d, _): %s", test.transProto, test.netProto, err)
}
defer ep.Close()
var wq waiter.Queue
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.EventIn)
ep, err := s.NewEndpoint(test.transProto, test.netProto, &wq)
if err != nil {
t.Fatalf("s.NewEndpoint(%d, %d, _): %s", test.transProto, test.netProto, err)
}
defer ep.Close()
connAddr := tcpip.FullAddress{Addr: test.localAddr}
{
err := ep.Connect(connAddr)
if diff := cmp.Diff(test.expectedConnectErr, err); diff != "" {
t.Fatalf("unexpected error from ep.Connect(%#v), (-want, +got):\n%s", connAddr, diff)
}
}
connAddr := tcpip.FullAddress{Addr: test.localAddr}
if err := ep.Connect(connAddr); err != test.expectedConnectErr {
t.Fatalf("got ep.Connect(%#v) = %s, want = %s", connAddr, err, test.expectedConnectErr)
}
if test.expectedConnectErr != nil {
return
}
if test.expectedConnectErr != nil {
return
}
payload := test.icmpBuf(t)
var r bytes.Reader
r.Reset(payload)
var wOpts tcpip.WriteOptions
if n, err := ep.Write(&r, wOpts); err != nil {
t.Fatalf("ep.Write(%#v, %#v): %s", payload, wOpts, err)
} else if n != int64(len(payload)) {
t.Fatalf("got ep.Write(%#v, %#v) = (%d, nil), want = (%d, nil)", payload, wOpts, n, len(payload))
}
var r bytes.Reader
payload := test.icmpBuf(t)
r.Reset(payload)
var wOpts tcpip.WriteOptions
if n, err := ep.Write(&r, wOpts); err != nil {
t.Fatalf("ep.Write(%#v, %#v): %s", payload, wOpts, err)
} else if n != int64(len(payload)) {
t.Fatalf("got ep.Write(%#v, %#v) = (%d, _, nil), want = (%d, _, nil)", payload, wOpts, n, len(payload))
}
// Wait for the endpoint to become readable.
<-ch
// Wait for the endpoint to become readable.
<-ch
var buf bytes.Buffer
opts := tcpip.ReadOptions{NeedRemoteAddr: true}
res, err := ep.Read(&buf, opts)
if err != nil {
t.Fatalf("ep.Read(_, %#v): %s", opts, err)
}
if diff := cmp.Diff(tcpip.ReadResult{
Count: buf.Len(),
Total: buf.Len(),
RemoteAddr: tcpip.FullAddress{Addr: test.localAddr},
}, res, checker.IgnoreCmpPath(
"ControlMessages",
"RemoteAddr.NIC",
"RemoteAddr.Port",
)); diff != "" {
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
}
if diff := cmp.Diff(buf.Bytes()[icmpDataOffset:], []byte(payload[icmpDataOffset:])); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
var w bytes.Buffer
rr, err := ep.Read(&w, tcpip.ReadOptions{
NeedRemoteAddr: true,
})
if err != nil {
t.Fatalf("ep.Read(...): %s", err)
}
if diff := cmp.Diff(buffer.View(w.Bytes()[icmpDataOffset:]), payload[icmpDataOffset:]); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
if rr.RemoteAddr.Addr != test.localAddr {
t.Errorf("got addr.Addr = %s, want = %s", rr.RemoteAddr.Addr, test.localAddr)
}
test.checkLinkEndpoint(t, e)
test.checkLinkEndpoint(t, e)
})
}
})
}
}
+3
View File
@@ -8,12 +8,15 @@ go_library(
visibility = ["//pkg/tcpip/tests:__subpackages__"],
deps = [
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/link/ethernet",
"//pkg/tcpip/link/nested",
"//pkg/tcpip/link/pipe",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport/icmp",
],
)
+60
View File
@@ -20,13 +20,16 @@ import (
"testing"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"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/link/nested"
"gvisor.dev/gvisor/pkg/tcpip/link/pipe"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
)
// Common NIC IDs used by tests.
@@ -45,6 +48,10 @@ const (
LinkAddr4 = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x09")
)
const (
ttl = 255
)
// Common IP addresses used by tests.
var (
Ipv4Addr = tcpip.AddressWithPrefix{
@@ -312,3 +319,56 @@ func SetupRoutedStacks(t *testing.T, host1Stack, routerStack, host2Stack *stack.
},
})
}
// RxICMPv4EchoRequest constructs and injects an ICMPv4 echo request packet on
// the provided endpoint.
func RxICMPv4EchoRequest(e *channel.Endpoint, src, dst tcpip.Address) {
totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize
hdr := buffer.NewPrependable(totalLen)
pkt := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
pkt.SetType(header.ICMPv4Echo)
pkt.SetCode(header.ICMPv4UnusedCode)
pkt.SetChecksum(0)
pkt.SetChecksum(^header.Checksum(pkt, 0))
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(totalLen),
Protocol: uint8(icmp.ProtocolNumber4),
TTL: ttl,
SrcAddr: src,
DstAddr: dst,
})
ip.SetChecksum(^ip.CalculateChecksum())
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: hdr.View().ToVectorisedView(),
}))
}
// RxICMPv6EchoRequest constructs and injects an ICMPv6 echo request packet on
// the provided endpoint.
func RxICMPv6EchoRequest(e *channel.Endpoint, src, dst tcpip.Address) {
totalLen := header.IPv6MinimumSize + header.ICMPv6MinimumSize
hdr := buffer.NewPrependable(totalLen)
pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
pkt.SetType(header.ICMPv6EchoRequest)
pkt.SetCode(header.ICMPv6UnusedCode)
pkt.SetChecksum(0)
pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: pkt,
Src: src,
Dst: dst,
}))
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: header.ICMPv6MinimumSize,
TransportProtocol: icmp.ProtocolNumber6,
HopLimit: ttl,
SrcAddr: src,
DstAddr: dst,
})
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: hdr.View().ToVectorisedView(),
}))
}