Add TOS control message for ICMP & RAW sockets

PiperOrigin-RevId: 426208980
This commit is contained in:
Arthur Sfez
2022-02-03 12:19:06 -08:00
committed by gVisor bot
parent e31c3f18da
commit 2d9f7fc7ea
17 changed files with 886 additions and 229 deletions
+27 -5
View File
@@ -292,32 +292,54 @@ func ReceiveTClass(want uint32) ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if !cm.HasTClass {
t.Errorf("got cm.HasTClass = %t, want = true", cm.HasTClass)
t.Error("got cm.HasTClass = false, want = true")
} else if got := cm.TClass; got != want {
t.Errorf("got cm.TClass = %d, want %d", got, want)
}
}
}
// NoTClassReceived creates a checker that checks the absence of the TCLASS
// field in ControlMessages.
func NoTClassReceived() ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if cm.HasTClass {
t.Error("got cm.HasTClass = true, want = false")
}
}
}
// ReceiveTOS creates a checker that checks the TOS field in ControlMessages.
func ReceiveTOS(want uint8) ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if !cm.HasTOS {
t.Errorf("got cm.HasTOS = %t, want = true", cm.HasTOS)
t.Error("got cm.HasTOS = false, want = true")
} else if got := cm.TOS; got != want {
t.Errorf("got cm.TOS = %d, want %d", got, want)
}
}
}
// NoTOSReceived creates a checker that checks the absence of the TOS field in
// ControlMessages.
func NoTOSReceived() ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if cm.HasTOS {
t.Error("got cm.HasTOS = true, want = false")
}
}
}
// ReceiveIPPacketInfo creates a checker that checks the PacketInfo field in
// ControlMessages.
func ReceiveIPPacketInfo(want tcpip.IPPacketInfo) ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if !cm.HasIPPacketInfo {
t.Errorf("got cm.HasIPPacketInfo = %t, want = true", cm.HasIPPacketInfo)
t.Error("got cm.HasIPPacketInfo = false, want = true")
} else if diff := cmp.Diff(want, cm.PacketInfo); diff != "" {
t.Errorf("IPPacketInfo mismatch (-want +got):\n%s", diff)
}
@@ -330,7 +352,7 @@ func ReceiveIPv6PacketInfo(want tcpip.IPv6PacketInfo) ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if !cm.HasIPv6PacketInfo {
t.Errorf("got cm.HasIPv6PacketInfo = %t, want = true", cm.HasIPv6PacketInfo)
t.Error("got cm.HasIPv6PacketInfo = false, want = true")
} else if diff := cmp.Diff(want, cm.IPv6PacketInfo); diff != "" {
t.Errorf("IPv6PacketInfo mismatch (-want +got):\n%s", diff)
}
@@ -343,7 +365,7 @@ func ReceiveOriginalDstAddr(want tcpip.FullAddress) ControlMessagesChecker {
return func(t *testing.T, cm tcpip.ControlMessages) {
t.Helper()
if !cm.HasOriginalDstAddress {
t.Errorf("got cm.HasOriginalDstAddress = %t, want = true", cm.HasOriginalDstAddress)
t.Error("got cm.HasOriginalDstAddress = false, want = true")
} else if diff := cmp.Diff(want, cm.OriginalDstAddress); diff != "" {
t.Errorf("OriginalDstAddress mismatch (-want +got):\n%s", diff)
}
+5 -1
View File
@@ -689,10 +689,14 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
PayloadCsum: dataRange.Checksum(),
PayloadLen: dataRange.Size(),
}))
replyTClass, _ := iph.TOS()
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: r.DefaultTTL(),
TOS: stack.DefaultTOS,
// Even though RFC 4443 does not mention anything about it, Linux uses the
// TrafficClass of the received echo request when replying.
// https://github.com/torvalds/linux/blob/0280e3c58f9/net/ipv6/icmp.c#L797
TOS: replyTClass,
}, replyPkt); err != nil {
sent.dropped.Increment()
return
+1
View File
@@ -59,6 +59,7 @@ go_test(
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/stack",
"//pkg/tcpip/testutil",
"//pkg/tcpip/transport/testing/context",
"//pkg/waiter",
],
)
+32 -5
View File
@@ -37,6 +37,10 @@ type icmpPacket struct {
senderAddress tcpip.FullAddress
data buffer.VectorisedView `state:".(buffer.VectorisedView)"`
receivedAt time.Time `state:".(int64)"`
// tosOrTClass stores either the Type of Service for IPv4 or the Traffic Class
// for IPv6.
tosOrTClass uint8
}
// endpoint represents an ICMP endpoint. This struct serves as the interface
@@ -177,12 +181,32 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
e.rcvMu.Unlock()
// Control Messages
// TODO(https://gvisor.dev/issue/7012): Share control message code with other
// network endpoints.
cm := tcpip.ControlMessages{
HasTimestamp: true,
Timestamp: p.receivedAt,
}
switch netProto := e.net.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if e.ops.GetReceiveTOS() {
cm.HasTOS = true
cm.TOS = p.tosOrTClass
}
case header.IPv6ProtocolNumber:
if e.ops.GetReceiveTClass() {
cm.HasTClass = true
// Although TClass is an 8-bit value it's read in the CMsg as a uint32.
cm.TClass = uint32(p.tosOrTClass)
}
default:
panic(fmt.Sprintf("unrecognized network protocol = %d", netProto))
}
res := tcpip.ReadResult{
Total: p.data.Size(),
ControlMessages: tcpip.ControlMessages{
HasTimestamp: true,
Timestamp: p.receivedAt,
},
Total: p.data.Size(),
ControlMessages: cm,
}
if opts.NeedRemoteAddr {
res.RemoteAddr = p.senderAddress
@@ -680,6 +704,9 @@ func (e *endpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketB
},
}
// Save any useful information from the network header to the packet.
packet.tosOrTClass, _ = pkt.Network().TOS()
// ICMP socket's data includes ICMP header.
packet.data = pkt.TransportHeader().View().ToVectorisedView()
packet.data.Append(pkt.Data().ExtractVV())
+167 -15
View File
@@ -30,6 +30,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/testutil"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/testing/context"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -42,6 +43,8 @@ var (
remoteV4Addr = testutil.MustParse4("10.0.0.3")
)
const testTOS = 0x80
func addNICWithDefaultRoute(t *testing.T, s *stack.Stack, id tcpip.NICID, name string, addrV4 tcpip.Address) *channel.Endpoint {
t.Helper()
@@ -80,18 +83,6 @@ func writePayload(buf []byte) {
}
}
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.
//
@@ -117,10 +108,22 @@ func TestWriteUnboundWithBindToDevice(t *testing.T) {
echoPayloadSize := defaultEP.MTU() - header.IPv4MinimumSize - header.ICMPv4MinimumSize
newICMPv4EchoRequest := func() buffer.View {
buf := buffer.NewView(header.ICMPv4MinimumSize + int(echoPayloadSize))
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
}
// 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)
buf := newICMPv4EchoRequest()
r := buf.Reader()
n, err := socket.Write(&r, tcpip.WriteOptions{
To: &tcpip.FullAddress{Addr: remoteV4Addr},
@@ -162,7 +165,7 @@ func TestWriteUnboundWithBindToDevice(t *testing.T) {
// Use SO_BINDTODEVICE to send over the alternate NIC by default.
socket.SocketOptions().SetBindToDevice(2)
buf := newICMPv4EchoRequest(echoPayloadSize)
buf := newICMPv4EchoRequest()
r := buf.Reader()
n, err := socket.Write(&r, tcpip.WriteOptions{
To: &tcpip.FullAddress{Addr: remoteV4Addr},
@@ -204,7 +207,7 @@ func TestWriteUnboundWithBindToDevice(t *testing.T) {
{
socket.SocketOptions().SetBindToDevice(0)
buf := newICMPv4EchoRequest(echoPayloadSize)
buf := newICMPv4EchoRequest()
r := buf.Reader()
n, err := socket.Write(&r, tcpip.WriteOptions{
To: &tcpip.FullAddress{Addr: remoteV4Addr},
@@ -241,6 +244,155 @@ func TestWriteUnboundWithBindToDevice(t *testing.T) {
}
}
func buildV4EchoReplyPacket(payload []byte, h context.Header4Tuple) (buffer.View, buffer.View) {
const ttl = 65
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.IPv4MinimumSize + header.ICMPv4MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv4(buf)
ip.Encode(&header.IPv4Fields{
TOS: testTOS,
TotalLength: uint16(len(buf)),
TTL: ttl,
Protocol: uint8(icmp.ProtocolNumber4),
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
ip.SetChecksum(^ip.CalculateChecksum())
// Initialize the ICMP header.
icmp := header.ICMPv4(buf[header.IPv4MinimumSize:])
icmp.SetType(header.ICMPv4EchoReply)
icmp.SetCode(header.ICMPv4UnusedCode)
icmp.SetIdent(h.Dst.Port)
icmp.SetChecksum(^header.Checksum(icmp, 0))
return buf, buffer.View(icmp)
}
func buildV6EchoReplyPacket(payload []byte, h context.Header4Tuple) (buffer.View, buffer.View) {
const hoplimit = 65
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.IPv6MinimumSize + header.ICMPv6EchoMinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv6(buf)
ip.Encode(&header.IPv6Fields{
TrafficClass: testTOS,
PayloadLength: uint16(header.ICMPv6EchoMinimumSize + len(payload)),
TransportProtocol: icmp.ProtocolNumber6,
HopLimit: hoplimit,
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
// Initialize the ICMPv6 header.
icmpv6 := header.ICMPv6(buf[header.IPv6MinimumSize:])
icmpv6.SetType(header.ICMPv6EchoReply)
icmpv6.SetCode(header.ICMPv6UnusedCode)
icmpv6.SetIdent(h.Dst.Port)
icmpv6.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpv6[:header.ICMPv6EchoMinimumSize],
Src: h.Src.Addr,
Dst: h.Dst.Addr,
PayloadCsum: header.Checksum(payload, 0),
PayloadLen: len(payload),
}))
return buf, buffer.View(icmpv6)
}
// buildEchoReplyPacket builds an ICMPv4 or ICMPv6 echo reply packet, and
// returns the full packet and the ICMP portion of the packet.
func buildEchoReplyPacket(payload []byte, flow context.TestFlow) (buffer.View, buffer.View) {
h := flow.MakeHeader4Tuple(context.Incoming)
if flow.IsV4() {
return buildV4EchoReplyPacket(payload, h)
}
return buildV6EchoReplyPacket(payload, h)
}
func TestReceiveControlMessages(t *testing.T) {
var payload = [...]byte{0, 1, 2, 3, 4, 5}
for _, test := range []struct {
name string
optionProtocol tcpip.NetworkProtocolNumber
getReceiveOption func(tcpip.Endpoint) bool
setReceiveOption func(tcpip.Endpoint, bool)
presenceChecker checker.ControlMessagesChecker
absenceChecker checker.ControlMessagesChecker
}{
{
name: "TOS",
optionProtocol: header.IPv4ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTOS() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTOS(value) },
presenceChecker: checker.ReceiveTOS(testTOS),
absenceChecker: checker.NoTOSReceived(),
},
{
name: "TClass",
optionProtocol: header.IPv6ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTClass() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTClass(value) },
presenceChecker: checker.ReceiveTClass(testTOS),
absenceChecker: checker.NoTClassReceived(),
},
} {
t.Run(test.name, func(t *testing.T) {
for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV6, context.UnicastV6Only, context.MulticastV4, context.MulticastV6, context.MulticastV6Only, context.Broadcast} {
t.Run(flow.String(), func(t *testing.T) {
c := context.New(t, []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6})
defer c.Cleanup()
icmpProto := func() tcpip.TransportProtocolNumber {
if flow.IsV4() {
return icmp.ProtocolNumber4
}
return icmp.ProtocolNumber6
}()
c.CreateEndpointForFlow(flow, icmpProto)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
c.T.Fatalf("Bind failed: %s", err)
}
if flow.IsMulticast() {
netProto := flow.NetProto()
addr := flow.GetMulticastAddr()
if err := c.Stack.JoinGroup(netProto, context.NICID, addr); err != nil {
c.T.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, context.NICID, addr, err)
}
}
buf, icmp := buildEchoReplyPacket(payload[:], flow)
if test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = true, want = false")
}
test.setReceiveOption(c.EP, true)
if !test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = false, want = true")
}
c.InjectPacket(flow.NetProto(), buf)
if flow.NetProto() == test.optionProtocol {
c.ReadFromEndpointExpectSuccess(icmp, flow, test.presenceChecker)
} else {
c.ReadFromEndpointExpectSuccess(icmp, flow, test.absenceChecker)
}
})
}
})
}
}
func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()
+17 -1
View File
@@ -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(licenses = ["notice"])
@@ -40,3 +40,19 @@ go_library(
"//pkg/waiter",
],
)
go_test(
name = "raw_x_test",
size = "small",
srcs = ["raw_test.go"],
deps = [
"//pkg/refs",
"//pkg/refsvfs2",
"//pkg/tcpip",
"//pkg/tcpip/checker",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport/testing/context",
"//pkg/tcpip/transport/udp",
],
)
+34 -13
View File
@@ -50,6 +50,10 @@ type rawPacket struct {
// senderAddr is the network address of the sender.
senderAddr tcpip.FullAddress
packetInfo tcpip.IPPacketInfo
// tosOrTClass stores either the Type of Service for IPv4 or the Traffic Class
// for IPv6.
tosOrTClass uint8
}
// endpoint is the raw socket implementation of tcpip.Endpoint. It is legal to
@@ -222,26 +226,32 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
e.rcvMu.Unlock()
res := tcpip.ReadResult{
Total: pkt.data.Size(),
ControlMessages: tcpip.ControlMessages{
HasTimestamp: true,
Timestamp: pkt.receivedAt,
},
}
if opts.NeedRemoteAddr {
res.RemoteAddr = pkt.senderAddr
// Control Messages
// TODO(https://gvisor.dev/issue/7012): Share control message code with other
// network endpoints.
cm := tcpip.ControlMessages{
HasTimestamp: true,
Timestamp: pkt.receivedAt,
}
switch netProto := e.net.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if e.ops.GetReceiveTOS() {
cm.HasTOS = true
cm.TOS = pkt.tosOrTClass
}
if e.ops.GetReceivePacketInfo() {
res.ControlMessages.HasIPPacketInfo = true
res.ControlMessages.PacketInfo = pkt.packetInfo
cm.HasIPPacketInfo = true
cm.PacketInfo = pkt.packetInfo
}
case header.IPv6ProtocolNumber:
if e.ops.GetReceiveTClass() {
cm.HasTClass = true
// Although TClass is an 8-bit value it's read in the CMsg as a uint32.
cm.TClass = uint32(pkt.tosOrTClass)
}
if e.ops.GetIPv6ReceivePacketInfo() {
res.ControlMessages.HasIPv6PacketInfo = true
res.ControlMessages.IPv6PacketInfo = tcpip.IPv6PacketInfo{
cm.HasIPv6PacketInfo = true
cm.IPv6PacketInfo = tcpip.IPv6PacketInfo{
NIC: pkt.packetInfo.NIC,
Addr: pkt.packetInfo.DestinationAddr,
}
@@ -250,6 +260,14 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
panic(fmt.Sprintf("unrecognized network protocol = %d", netProto))
}
res := tcpip.ReadResult{
Total: pkt.data.Size(),
ControlMessages: cm,
}
if opts.NeedRemoteAddr {
res.RemoteAddr = pkt.senderAddr
}
n, err := pkt.data.ReadTo(dst, opts.Peek)
if n == 0 && err != nil {
return res, &tcpip.ErrBadBuffer{}
@@ -604,6 +622,9 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
},
}
// Save any useful information from the network header to the packet.
packet.tosOrTClass, _ = pkt.Network().TOS()
// Raw IPv4 endpoints return the IP header, but IPv6 endpoints do not.
// We copy headers' underlying bytes because pkt.*Header may point to
// the middle of a slice, and another struct may point to the "outer"
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2022 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 raw_test
import (
"os"
"testing"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/refsvfs2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/testing/context"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
const (
testTOS = 0x80
testTTL = 65
)
func TestReceiveControlMessage(t *testing.T) {
var payload = [...]byte{0, 1, 2, 3, 4, 5}
for _, test := range []struct {
name string
optionProtocol tcpip.NetworkProtocolNumber
getReceiveOption func(tcpip.Endpoint) bool
setReceiveOption func(tcpip.Endpoint, bool)
presenceChecker checker.ControlMessagesChecker
absenceChecker checker.ControlMessagesChecker
}{
{
name: "TOS",
optionProtocol: header.IPv4ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTOS() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTOS(value) },
presenceChecker: checker.ReceiveTOS(testTOS),
absenceChecker: checker.NoTOSReceived(),
},
{
name: "TClass",
optionProtocol: header.IPv6ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTClass() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTClass(value) },
presenceChecker: checker.ReceiveTClass(testTOS),
absenceChecker: checker.NoTClassReceived(),
},
} {
t.Run(test.name, func(t *testing.T) {
for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV6, context.UnicastV6Only, context.MulticastV4, context.MulticastV6, context.MulticastV6Only, context.Broadcast} {
t.Run(flow.String(), func(t *testing.T) {
c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol})
defer c.Cleanup()
c.CreateRawEndpointForFlow(flow, header.UDPProtocolNumber)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
c.T.Fatalf("Bind failed: %s", err)
}
if flow.IsMulticast() {
netProto := flow.NetProto()
addr := flow.GetMulticastAddr()
if err := c.Stack.JoinGroup(netProto, context.NICID, addr); err != nil {
c.T.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, context.NICID, addr, err)
}
}
buf := context.BuildUDPPacket(payload[:], flow, context.Incoming, testTOS, testTTL, false)
expectedReadData := buf
if flow.IsV6() {
// Raw IPv6 endpoints do not return the network header.
expectedReadData = expectedReadData[header.IPv6MinimumSize:]
}
if test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = true, want = false")
}
test.setReceiveOption(c.EP, true)
if !test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = false, want = true")
}
c.InjectPacket(flow.NetProto(), buf)
if flow.NetProto() == test.optionProtocol {
c.ReadFromEndpointExpectSuccess(expectedReadData, flow, test.presenceChecker)
} else {
c.ReadFromEndpointExpectSuccess(expectedReadData, flow, test.absenceChecker)
}
})
}
})
}
}
func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()
refsvfs2.DoLeakCheck()
os.Exit(code)
}
@@ -23,6 +23,8 @@ go_library(
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/tcpip/transport/raw",
"//pkg/tcpip/transport/udp",
"//pkg/waiter",
"@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_time//rate:go_default_library",
@@ -32,6 +32,7 @@ import (
"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/raw"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -96,6 +97,7 @@ func NewWithOptions(t *testing.T, transportProtocols []stack.TransportProtocolFa
TransportProtocols: transportProtocols,
HandleLocal: options.HandleLocal,
Clock: &faketime.NullClock{},
RawFactory: &raw.EndpointFactory{},
}
s := stack.New(stackOptions)
@@ -178,6 +180,30 @@ func (c *Context) CreateEndpointForFlow(flow TestFlow, transport tcpip.Transport
}
}
// CreateRawEndpoint creates the Context's Endpoint.
func (c *Context) CreateRawEndpoint(network tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber) {
c.T.Helper()
var err tcpip.Error
c.EP, err = c.Stack.NewRawEndpoint(transport, network, &c.WQ, true /* associated */)
if err != nil {
c.T.Fatal("c.Stack.NewRawEndpoint failed: ", err)
}
}
// CreateRawEndpointForFlow creates the Context's Endpoint and configured it
// according to the given TestFlow.
func (c *Context) CreateRawEndpointForFlow(flow TestFlow, transport tcpip.TransportProtocolNumber) {
c.T.Helper()
c.CreateRawEndpoint(flow.SockProto(), transport)
if flow.isV6Only() {
c.EP.SocketOptions().SetV6Only(true)
} else if flow.isBroadcast() {
c.EP.SocketOptions().SetBroadcast(true)
}
}
// CheckEndpointWriteStats checks that the write statistic related to the given
// error has been incremented as expected.
func (c *Context) CheckEndpointWriteStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) {
+103
View File
@@ -19,10 +19,12 @@ 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/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
const (
@@ -338,3 +340,104 @@ func (flow TestFlow) isReverseMulticast() bool {
return false
}
}
// BuildV4UDPPacket builds an IPv4 UDP packet.
func BuildV4UDPPacket(payload []byte, h Header4Tuple, tos, ttl uint8, badChecksum bool) buffer.View {
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv4(buf)
ip.Encode(&header.IPv4Fields{
TOS: tos,
TotalLength: uint16(len(buf)),
TTL: ttl,
Protocol: uint8(udp.ProtocolNumber),
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
ip.SetChecksum(^ip.CalculateChecksum())
// Initialize the UDP header.
u := header.UDP(buf[header.IPv4MinimumSize:])
u.Encode(&header.UDPFields{
SrcPort: h.Src.Port,
DstPort: h.Dst.Port,
Length: uint16(header.UDPMinimumSize + len(payload)),
})
// Calculate the UDP pseudo-header checksum.
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u)))
// Calculate the UDP checksum and set it.
xsum = header.Checksum(payload, xsum)
u.SetChecksum(^u.CalculateChecksum(xsum))
if badChecksum {
// Invalidate the UDP header checksum field, taking care to avoid overflow
// to zero, which would disable checksum validation.
for {
u.SetChecksum(u.Checksum() + 1)
if u.Checksum() != 0 {
break
}
}
}
return buf
}
// BuildV6UDPPacket builds an IPv6 UDP packet.
func BuildV6UDPPacket(payload []byte, h Header4Tuple, tclass, hoplimit uint8, badChecksum bool) buffer.View {
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv6(buf)
ip.Encode(&header.IPv6Fields{
TrafficClass: tclass,
PayloadLength: uint16(header.UDPMinimumSize + len(payload)),
TransportProtocol: udp.ProtocolNumber,
HopLimit: hoplimit,
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
// Initialize the UDP header.
u := header.UDP(buf[header.IPv6MinimumSize:])
u.Encode(&header.UDPFields{
SrcPort: h.Src.Port,
DstPort: h.Dst.Port,
Length: uint16(header.UDPMinimumSize + len(payload)),
})
// Calculate the UDP pseudo-header checksum.
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u)))
// Calculate the UDP checksum and set it.
xsum = header.Checksum(payload, xsum)
u.SetChecksum(^u.CalculateChecksum(xsum))
if badChecksum {
// Invalidate the UDP header checksum field (Unlike IPv4, zero is a valid
// checksum value for IPv6 so no need to avoid it).
u := header.UDP(buf[header.IPv6MinimumSize:])
u.SetChecksum(u.Checksum() + 1)
}
return buf
}
// BuildUDPPacket builds an IPv4 or IPv6 UDP packet, depending on the specified
// TestFlow.
func BuildUDPPacket(payload []byte, flow TestFlow, direction PacketDirection, tosOrTclass, ttlOrHopLimit uint8, badChecksum bool) buffer.View {
h := flow.MakeHeader4Tuple(direction)
if flow.IsV4() {
return BuildV4UDPPacket(payload, h, tosOrTclass, ttlOrHopLimit, badChecksum)
}
return BuildV6UDPPacket(payload, h, tosOrTclass, ttlOrHopLimit, badChecksum)
}
+8 -13
View File
@@ -39,8 +39,9 @@ type udpPacket struct {
packetInfo tcpip.IPPacketInfo
pkt *stack.PacketBuffer
receivedAt time.Time `state:".(int64)"`
// tos stores either the receiveTOS or receiveTClass value.
tos uint8
// tosOrTClass stores either the Type of Service for IPv4 or the Traffic Class
// for IPv6.
tosOrTClass uint8
}
// endpoint represents a UDP endpoint. This struct serves as the interface
@@ -233,18 +234,18 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
e.rcvMu.Unlock()
// Control Messages
// TODO(https://gvisor.dev/issue/7012): Share control message code with other
// network endpoints.
cm := tcpip.ControlMessages{
HasTimestamp: true,
Timestamp: p.receivedAt,
}
switch p.netProto {
case header.IPv4ProtocolNumber:
if e.ops.GetReceiveTOS() {
cm.HasTOS = true
cm.TOS = p.tos
cm.TOS = p.tosOrTClass
}
if e.ops.GetReceivePacketInfo() {
cm.HasIPPacketInfo = true
cm.PacketInfo = p.packetInfo
@@ -253,9 +254,8 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
if e.ops.GetReceiveTClass() {
cm.HasTClass = true
// Although TClass is an 8-bit value it's read in the CMsg as a uint32.
cm.TClass = uint32(p.tos)
cm.TClass = uint32(p.tosOrTClass)
}
if e.ops.GetIPv6ReceivePacketInfo() {
cm.HasIPv6PacketInfo = true
cm.IPv6PacketInfo = tcpip.IPv6PacketInfo{
@@ -926,12 +926,7 @@ func (e *endpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketB
e.rcvBufSize += pkt.Data().Size()
// Save any useful information from the network header to the packet.
switch pkt.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
packet.tos, _ = header.IPv4(pkt.NetworkHeader().View()).TOS()
case header.IPv6ProtocolNumber:
packet.tos, _ = header.IPv6(pkt.NetworkHeader().View()).TOS()
}
packet.tosOrTClass, _ = pkt.Network().TOS()
// TODO(gvisor.dev/issue/3556): r.LocalAddress may be a multicast or broadcast
// address. packetInfo.LocalAddr should hold a unicast address that can be
+68 -176
View File
@@ -44,6 +44,7 @@ import (
const (
testTOS = 0x80
testTTL = 65
arbitraryPayloadSize = 30
)
@@ -57,115 +58,18 @@ func newRandomPayload(size int) []byte {
return b
}
func buildV4Packet(payload []byte, h context.Header4Tuple, badChecksum bool) buffer.View {
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv4(buf)
ip.Encode(&header.IPv4Fields{
TOS: testTOS,
TotalLength: uint16(len(buf)),
TTL: 65,
Protocol: uint8(udp.ProtocolNumber),
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
ip.SetChecksum(^ip.CalculateChecksum())
// Initialize the UDP header.
u := header.UDP(buf[header.IPv4MinimumSize:])
u.Encode(&header.UDPFields{
SrcPort: h.Src.Port,
DstPort: h.Dst.Port,
Length: uint16(header.UDPMinimumSize + len(payload)),
})
// Calculate the UDP pseudo-header checksum.
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u)))
// Calculate the UDP checksum and set it.
xsum = header.Checksum(payload, xsum)
u.SetChecksum(^u.CalculateChecksum(xsum))
if badChecksum {
// Invalidate the UDP header checksum field, taking care to avoid overflow
// to zero, which would disable checksum validation.
for {
u.SetChecksum(u.Checksum() + 1)
if u.Checksum() != 0 {
break
}
}
}
return buf
}
func buildV6Packet(payload []byte, h context.Header4Tuple, badChecksum bool) buffer.View {
// Allocate a buffer for data and headers.
buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
// Initialize the IP header.
ip := header.IPv6(buf)
ip.Encode(&header.IPv6Fields{
TrafficClass: testTOS,
PayloadLength: uint16(header.UDPMinimumSize + len(payload)),
TransportProtocol: udp.ProtocolNumber,
HopLimit: 65,
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
// Initialize the UDP header.
u := header.UDP(buf[header.IPv6MinimumSize:])
u.Encode(&header.UDPFields{
SrcPort: h.Src.Port,
DstPort: h.Dst.Port,
Length: uint16(header.UDPMinimumSize + len(payload)),
})
// Calculate the UDP pseudo-header checksum.
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u)))
// Calculate the UDP checksum and set it.
xsum = header.Checksum(payload, xsum)
u.SetChecksum(^u.CalculateChecksum(xsum))
if badChecksum {
// Invalidate the UDP header checksum field (Unlike IPv4, zero is a valid
// checksum value for IPv6 so no need to avoid it).
u := header.UDP(buf[header.IPv6MinimumSize:])
u.SetChecksum(u.Checksum() + 1)
}
return buf
}
func buildPacket(payload []byte, flow context.TestFlow, direction context.PacketDirection, badChecksum bool) buffer.View {
h := flow.MakeHeader4Tuple(direction)
if flow.IsV4() {
return buildV4Packet(payload, h, badChecksum)
}
return buildV6Packet(payload, h, badChecksum)
}
func testRead(c *context.Context, flow context.TestFlow, checkers ...checker.ControlMessagesChecker) {
c.T.Helper()
payload := newRandomPayload(arbitraryPayloadSize)
c.InjectPacket(flow.NetProto(), buildPacket(payload, flow, context.Incoming, false))
c.InjectPacket(flow.NetProto(), context.BuildUDPPacket(payload, flow, context.Incoming, testTOS, testTTL, false))
c.ReadFromEndpointExpectSuccess(payload, flow, checkers...)
}
func testFailingRead(c *context.Context, flow context.TestFlow, expectReadError bool) {
c.T.Helper()
c.InjectPacket(flow.NetProto(), buildPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, false))
c.InjectPacket(flow.NetProto(), context.BuildUDPPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, testTOS, testTTL, false))
if expectReadError {
c.ReadFromEndpointExpectError()
} else {
@@ -312,7 +216,7 @@ func TestV4ReadOnV6(t *testing.T) {
}
payload := newRandomPayload(arbitraryPayloadSize)
buf := buildPacket(payload, context.UnicastV4in6, context.Incoming, false)
buf := context.BuildUDPPacket(payload, context.UnicastV4in6, context.Incoming, testTOS, testTTL, false)
c.InjectPacket(header.IPv4ProtocolNumber, buf)
c.ReadFromEndpointExpectSuccess(payload, context.UnicastV4in6)
}
@@ -390,7 +294,7 @@ func TestV4ReadSelfSource(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV4.MakeHeader4Tuple(context.Incoming)
h.Src = h.Dst
c.InjectPacket(header.IPv4ProtocolNumber, buildV4Packet(payload, h, false))
c.InjectPacket(header.IPv4ProtocolNumber, context.BuildV4UDPPacket(payload, h, testTOS, testTTL, false))
if got := c.Stack.Stats().IP.InvalidSourceAddressesReceived.Value(); got != tt.wantInvalidSource {
t.Errorf("c.Stack.Stats().IP.InvalidSourceAddressesReceived got %d, want %d", got, tt.wantInvalidSource)
@@ -1341,83 +1245,71 @@ func TestSetTClass(t *testing.T) {
}
}
func TestReceiveTosTClass(t *testing.T) {
const RcvTOSOpt = "ReceiveTosOption"
const RcvTClassOpt = "ReceiveTClassOption"
testCases := []struct {
name string
tests []context.TestFlow
func TestReceiveControlMessage(t *testing.T) {
for _, test := range []struct {
name string
optionProtocol tcpip.NetworkProtocolNumber
getReceiveOption func(tcpip.Endpoint) bool
setReceiveOption func(tcpip.Endpoint, bool)
presenceChecker checker.ControlMessagesChecker
absenceChecker checker.ControlMessagesChecker
}{
{
name: RcvTOSOpt,
tests: v4PacketFlows[:],
name: "TOS",
optionProtocol: header.IPv4ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTOS() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTOS(value) },
presenceChecker: checker.ReceiveTOS(testTOS),
absenceChecker: checker.NoTOSReceived(),
},
{
name: RcvTClassOpt,
tests: v6PacketFlows[:],
name: "TClass",
optionProtocol: header.IPv6ProtocolNumber,
getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceiveTClass() },
setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceiveTClass(value) },
presenceChecker: checker.ReceiveTClass(testTOS),
absenceChecker: checker.NoTClassReceived(),
},
}
for _, testCase := range testCases {
for _, flow := range testCase.tests {
t.Run(fmt.Sprintf("%s:flow:%s", testCase.name, flow), func(t *testing.T) {
c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4})
defer c.Cleanup()
} {
t.Run(test.name, func(t *testing.T) {
for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV6, context.UnicastV6Only, context.MulticastV4, context.MulticastV6, context.MulticastV6Only, context.Broadcast} {
t.Run(flow.String(), func(t *testing.T) {
c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol})
defer c.Cleanup()
c.CreateEndpointForFlow(flow, udp.ProtocolNumber)
name := testCase.name
if flow.IsMulticast() {
netProto := flow.NetProto()
addr := flow.GetMulticastAddr()
if err := c.Stack.JoinGroup(netProto, context.NICID, addr); err != nil {
c.T.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, context.NICID, addr, err)
c.CreateEndpointForFlow(flow, udp.ProtocolNumber)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
c.T.Fatalf("Bind failed: %s", err)
}
if flow.IsMulticast() {
netProto := flow.NetProto()
addr := flow.GetMulticastAddr()
if err := c.Stack.JoinGroup(netProto, context.NICID, addr); err != nil {
c.T.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, context.NICID, addr, err)
}
}
}
var optionGetter func() bool
var optionSetter func(bool)
switch name {
case RcvTOSOpt:
optionGetter = c.EP.SocketOptions().GetReceiveTOS
optionSetter = c.EP.SocketOptions().SetReceiveTOS
case RcvTClassOpt:
optionGetter = c.EP.SocketOptions().GetReceiveTClass
optionSetter = c.EP.SocketOptions().SetReceiveTClass
default:
t.Fatalf("unkown test variant: %s", name)
}
payload := newRandomPayload(arbitraryPayloadSize)
buf := context.BuildUDPPacket(payload, flow, context.Incoming, testTOS, testTTL, false)
// Verify that setting and reading the option works.
v := optionGetter()
// Test for expected default value.
if v != false {
c.T.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, v, false)
}
if test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = true, want = false")
}
const want = true
optionSetter(want)
test.setReceiveOption(c.EP, true)
if !test.getReceiveOption(c.EP) {
t.Fatal("got getReceiveOption() = false, want = true")
}
got := optionGetter()
if got != want {
c.T.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, got, want)
}
// Verify that the correct received TOS or TClass is handed through as
// ancillary data to the ControlMessages struct.
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
c.T.Fatalf("Bind failed: %s", err)
}
switch name {
case RcvTClassOpt:
testRead(c, flow, checker.ReceiveTClass(testTOS))
case RcvTOSOpt:
testRead(c, flow, checker.ReceiveTOS(testTOS))
default:
t.Fatalf("unknown test variant: %s", name)
}
})
}
c.InjectPacket(flow.NetProto(), buf)
if flow.NetProto() == test.optionProtocol {
c.ReadFromEndpointExpectSuccess(payload, flow, test.presenceChecker)
} else {
c.ReadFromEndpointExpectSuccess(payload, flow, test.absenceChecker)
}
})
}
})
}
}
@@ -1518,7 +1410,7 @@ func TestV4UnknownDestination(t *testing.T) {
payloadSize += header.IPv4MinimumProcessableDatagramSize
}
payload := newRandomPayload(payloadSize)
c.InjectPacket(tc.flow.NetProto(), buildPacket(payload, tc.flow, context.Incoming, tc.badChecksum))
c.InjectPacket(tc.flow.NetProto(), context.BuildUDPPacket(payload, tc.flow, context.Incoming, testTOS, testTTL, tc.badChecksum))
if tc.badChecksum {
checksumErrors++
if got, want := c.Stack.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want {
@@ -1612,7 +1504,7 @@ func TestV6UnknownDestination(t *testing.T) {
payloadSize += header.IPv6MinimumMTU
}
payload := newRandomPayload(payloadSize)
c.InjectPacket(tc.flow.NetProto(), buildPacket(payload, tc.flow, context.Incoming, tc.badChecksum))
c.InjectPacket(tc.flow.NetProto(), context.BuildUDPPacket(payload, tc.flow, context.Incoming, testTOS, testTTL, tc.badChecksum))
if tc.badChecksum {
checksumErrors++
if got, want := c.Stack.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want {
@@ -1678,7 +1570,7 @@ func TestIncrementMalformedPacketsReceived(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV6.MakeHeader4Tuple(context.Incoming)
buf := buildV6Packet(payload, h, false)
buf := context.BuildV6UDPPacket(payload, h, testTOS, testTTL, false)
// Invalidate the UDP header length field.
u := header.UDP(buf[header.IPv6MinimumSize:])
@@ -1717,7 +1609,7 @@ func TestShortHeader(t *testing.T) {
TrafficClass: testTOS,
PayloadLength: uint16(udpSize),
TransportProtocol: udp.ProtocolNumber,
HopLimit: 65,
HopLimit: testTTL,
SrcAddr: h.Src.Addr,
DstAddr: h.Dst.Addr,
})
@@ -1757,7 +1649,7 @@ func TestBadChecksumErrors(t *testing.T) {
c.T.Fatalf("Bind failed: %s", err)
}
c.InjectPacket(flow.NetProto(), buildPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, true))
c.InjectPacket(flow.NetProto(), context.BuildUDPPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, testTOS, testTTL, true))
const want = 1
if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want {
@@ -1784,7 +1676,7 @@ func TestPayloadModifiedV4(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV4.MakeHeader4Tuple(context.Incoming)
buf := buildV4Packet(payload, h, false)
buf := context.BuildV4UDPPacket(payload, h, testTOS, testTTL, false)
// Modify the payload so that the checksum value in the UDP header will be
// incorrect.
buf[len(buf)-1]++
@@ -1813,7 +1705,7 @@ func TestPayloadModifiedV6(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV6.MakeHeader4Tuple(context.Incoming)
buf := buildV6Packet(payload, h, false)
buf := context.BuildV6UDPPacket(payload, h, testTOS, testTTL, false)
// Modify the payload so that the checksum value in the UDP header will be
// incorrect.
buf[len(buf)-1]++
@@ -1842,7 +1734,7 @@ func TestChecksumZeroV4(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV4.MakeHeader4Tuple(context.Incoming)
buf := buildV4Packet(payload, h, false)
buf := context.BuildV4UDPPacket(payload, h, testTOS, testTTL, false)
// Set the checksum field in the UDP header to zero.
u := header.UDP(buf[header.IPv4MinimumSize:])
u.SetChecksum(0)
@@ -1871,7 +1763,7 @@ func TestChecksumZeroV6(t *testing.T) {
payload := newRandomPayload(arbitraryPayloadSize)
h := context.UnicastV6.MakeHeader4Tuple(context.Incoming)
buf := buildV6Packet(payload, h, false)
buf := context.BuildV6UDPPacket(payload, h, testTOS, testTTL, false)
// Set the checksum field in the UDP header to zero.
u := header.UDP(buf[header.IPv6MinimumSize:])
u.SetChecksum(0)
@@ -246,5 +246,45 @@ std::string GetAddrStr(const sockaddr* a) {
}
}
namespace {
template <typename T>
void RecvCmsg(int sock, int cmsg_level, int cmsg_type, char buf[],
size_t* buf_size, T* out_cmsg_value) {
struct iovec iov = {
iov.iov_base = buf,
iov.iov_len = *buf_size,
};
// Add an extra byte to confirm we only read what we expected.
char control[CMSG_SPACE(sizeof(*out_cmsg_value)) + 1];
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
ASSERT_THAT(*buf_size = RetryEINTR(recvmsg)(sock, &msg, /*flags=*/0),
SyscallSucceeds());
ASSERT_EQ(msg.msg_controllen, CMSG_SPACE(sizeof(*out_cmsg_value)));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(*out_cmsg_value)));
ASSERT_EQ(cmsg->cmsg_level, cmsg_level);
ASSERT_EQ(cmsg->cmsg_type, cmsg_type);
std::copy_n(CMSG_DATA(cmsg), sizeof(*out_cmsg_value),
reinterpret_cast<uint8_t*>(out_cmsg_value));
}
} // namespace
void RecvTOS(int sock, char buf[], size_t* buf_size, uint8_t* out_tos) {
RecvCmsg(sock, SOL_IP, IP_TOS, buf, buf_size, out_tos);
}
void RecvTClass(int sock, char buf[], size_t* buf_size, int* out_tclass) {
RecvCmsg(sock, SOL_IPV6, IPV6_TCLASS, buf, buf_size, out_tclass);
}
} // namespace testing
} // namespace gvisor
+11
View File
@@ -125,6 +125,17 @@ std::string GetAddr6Str(const in6_addr* a);
// string.
std::string GetAddrStr(const sockaddr* a);
// RecvTOS attempts to read buf_size bytes into buf, and then update buf_size
// with the numbers of bytes actually read. It expects the IP_TOS cmsg to be
// received. The buffer must already be allocated with at least buf_size size.
void RecvTOS(int sock, char buf[], size_t* buf_size, uint8_t* out_tos);
// RecvTClass attempts to read buf_size bytes into buf, and then update buf_size
// with the numbers of bytes actually read. It expects the IPV6_TCLASS cmsg to
// be received. The buffer must already be allocated with at least buf_size
// size.
void RecvTClass(int sock, char buf[], size_t* buf_size, int* out_tclass);
} // namespace testing
} // namespace gvisor
+111
View File
@@ -13,6 +13,7 @@
// limitations under the License.
#include <errno.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
@@ -80,6 +81,116 @@ TEST(PingSocket, ICMPPortExhaustion) {
}
}
TEST(PingSocket, ReceiveTOS) {
PosixErrorOr<FileDescriptor> result =
Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (!result.ok()) {
int errno_value = result.error().errno_value();
ASSERT_EQ(errno_value, EACCES) << strerror(errno_value);
GTEST_SKIP() << "ping socket not supported";
}
FileDescriptor& ping = result.ValueOrDie();
const sockaddr_in kAddr = {
.sin_family = AF_INET,
.sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)},
};
ASSERT_THAT(bind(ping.get(), reinterpret_cast<const sockaddr*>(&kAddr),
sizeof(kAddr)),
SyscallSucceeds());
constexpr int kArbitraryTOS = 42;
ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IP, IP_TOS, &kArbitraryTOS,
sizeof(kArbitraryTOS)),
SyscallSucceeds());
constexpr icmphdr kSendIcmp = {
.type = ICMP_ECHO,
};
ASSERT_THAT(sendto(ping.get(), &kSendIcmp, sizeof(kSendIcmp), 0,
reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceedsWithValue(sizeof(kSendIcmp)));
// Register to receive TOS.
constexpr int kOne = 1;
ASSERT_THAT(
setsockopt(ping.get(), IPPROTO_IP, IP_RECVTOS, &kOne, sizeof(kOne)),
SyscallSucceeds());
struct {
icmphdr icmp;
// Add an extra byte to confirm we did not read unexpected bytes.
char unused;
} ABSL_ATTRIBUTE_PACKED recv_buf;
size_t recv_buf_len = sizeof(recv_buf);
uint8_t received_tos;
ASSERT_NO_FATAL_FAILURE(RecvTOS(ping.get(),
reinterpret_cast<char*>(&recv_buf),
&recv_buf_len, &received_tos));
ASSERT_EQ(recv_buf_len, sizeof(icmphdr));
EXPECT_EQ(recv_buf.icmp.type, ICMP_ECHOREPLY);
EXPECT_EQ(recv_buf.icmp.code, 0);
EXPECT_EQ(received_tos, kArbitraryTOS);
}
TEST(PingSocket, ReceiveTClass) {
PosixErrorOr<FileDescriptor> result =
Socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
if (!result.ok()) {
int errno_value = result.error().errno_value();
ASSERT_EQ(errno_value, EACCES) << strerror(errno_value);
GTEST_SKIP() << "ping socket not supported";
}
FileDescriptor& ping = result.ValueOrDie();
const sockaddr_in6 kAddr = {
.sin6_family = AF_INET6,
.sin6_addr = in6addr_loopback,
};
ASSERT_THAT(bind(ping.get(), reinterpret_cast<const sockaddr*>(&kAddr),
sizeof(kAddr)),
SyscallSucceeds());
constexpr int kArbitraryTClass = 42;
ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IPV6, IPV6_TCLASS,
&kArbitraryTClass, sizeof(kArbitraryTClass)),
SyscallSucceeds());
constexpr icmp6_hdr kSendIcmp = {
.icmp6_type = ICMP6_ECHO_REQUEST,
};
ASSERT_THAT(sendto(ping.get(), &kSendIcmp, sizeof(kSendIcmp), 0,
reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceedsWithValue(sizeof(kSendIcmp)));
// Register to receive TCLASS.
constexpr int kOne = 1;
ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IPV6, IPV6_RECVTCLASS, &kOne,
sizeof(kOne)),
SyscallSucceeds());
struct {
icmp6_hdr icmpv6;
// Add an extra byte to confirm we did not read unexpected bytes.
char unused;
} ABSL_ATTRIBUTE_PACKED recv_buf;
size_t recv_buf_len = sizeof(recv_buf);
int received_tclass;
ASSERT_NO_FATAL_FAILURE(RecvTClass(ping.get(),
reinterpret_cast<char*>(&recv_buf),
&recv_buf_len, &received_tclass));
ASSERT_EQ(recv_buf_len, sizeof(kSendIcmp));
EXPECT_EQ(recv_buf.icmpv6.icmp6_type, ICMP6_ECHO_REPLY);
EXPECT_EQ(recv_buf.icmpv6.icmp6_code, 0);
EXPECT_EQ(received_tclass, kArbitraryTClass);
}
struct BindTestCase {
TestAddress bind_to;
int want = 0;
+118
View File
@@ -1186,6 +1186,124 @@ TEST(RawSocketTest, ReceiveIPv6PacketInfo) {
EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull());
}
TEST(RawSocketTest, ReceiveTOS) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor raw =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
const sockaddr_in kAddr = {
.sin_family = AF_INET,
.sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)},
};
ASSERT_THAT(
bind(raw.get(), reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceeds());
constexpr int kArbitraryTOS = 42;
ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IP, IP_TOS, &kArbitraryTOS,
sizeof(kArbitraryTOS)),
SyscallSucceeds());
constexpr char kSendBuf[] = "malformed UDP";
ASSERT_THAT(sendto(raw.get(), kSendBuf, sizeof(kSendBuf), 0 /* flags */,
reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceedsWithValue(sizeof(kSendBuf)));
// Register to receive TOS.
constexpr int kOne = 1;
ASSERT_THAT(
setsockopt(raw.get(), IPPROTO_IP, IP_RECVTOS, &kOne, sizeof(kOne)),
SyscallSucceeds());
struct {
iphdr ip;
char data[sizeof(kSendBuf)];
// Extra space in the receive buffer should be unused.
char unused_space;
} ABSL_ATTRIBUTE_PACKED recv_buf;
uint8_t recv_tos;
size_t recv_buf_len = sizeof(recv_buf);
ASSERT_NO_FATAL_FAILURE(RecvTOS(raw.get(), reinterpret_cast<char*>(&recv_buf),
&recv_buf_len, &recv_tos));
ASSERT_EQ(recv_buf_len, sizeof(iphdr) + sizeof(kSendBuf));
EXPECT_EQ(recv_buf.ip.version, static_cast<unsigned int>(IPVERSION));
// IHL holds the number of header bytes in 4 byte units.
EXPECT_EQ(recv_buf.ip.ihl, sizeof(iphdr) / 4);
EXPECT_EQ(ntohs(recv_buf.ip.tot_len), sizeof(iphdr) + sizeof(kSendBuf));
EXPECT_EQ(recv_buf.ip.protocol, IPPROTO_UDP);
EXPECT_EQ(ntohl(recv_buf.ip.saddr), INADDR_LOOPBACK);
EXPECT_EQ(ntohl(recv_buf.ip.daddr), INADDR_LOOPBACK);
EXPECT_EQ(memcmp(kSendBuf, &recv_buf.data, sizeof(kSendBuf)), 0);
if (const char* val = getenv("TOS_TCLASS_EXPECT_DEFAULT");
val != nullptr && strcmp(val, "1") == 0) {
// TODO(https://issuetracker.google.com/issues/217448626): As of writing, it
// seems like at least one Linux environment does not allow setting a custom
// TOS. In this case, we expect the default instead of the TOS that was set
// above.
EXPECT_EQ(recv_buf.ip.tos, 0u);
EXPECT_EQ(recv_tos, 0u);
} else {
EXPECT_EQ(recv_buf.ip.tos, static_cast<uint8_t>(kArbitraryTOS));
EXPECT_EQ(recv_tos, kArbitraryTOS);
}
}
TEST(RawSocketTest, ReceiveTClass) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor raw =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
const sockaddr_in6 kAddr = {
.sin6_family = AF_INET6,
.sin6_addr = in6addr_loopback,
};
ASSERT_THAT(
bind(raw.get(), reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceeds());
constexpr int kArbitraryTClass = 42;
ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IPV6, IPV6_TCLASS,
&kArbitraryTClass, sizeof(kArbitraryTClass)),
SyscallSucceeds());
constexpr char send_buf[] = "malformed UDP";
ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */,
reinterpret_cast<const sockaddr*>(&kAddr), sizeof(kAddr)),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Register to receive TClass.
constexpr int kOne = 1;
ASSERT_THAT(
setsockopt(raw.get(), IPPROTO_IPV6, IPV6_RECVTCLASS, &kOne, sizeof(kOne)),
SyscallSucceeds());
char recv_buf[sizeof(send_buf) + 1];
size_t recv_buf_len = sizeof(recv_buf);
int recv_tclass;
ASSERT_NO_FATAL_FAILURE(
RecvTClass(raw.get(), recv_buf, &recv_buf_len, &recv_tclass));
ASSERT_EQ(recv_buf_len, sizeof(send_buf));
EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0);
if (const char* val = getenv("TOS_TCLASS_EXPECT_DEFAULT");
val != nullptr && strcmp(val, "1") == 0) {
// TODO(https://issuetracker.google.com/issues/217448626): As of writing, it
// seems like at least one Linux environment does not allow setting a custom
// TCLASS. In this case, we expect the default instead of the TCLASS that
// was set above.
EXPECT_EQ(recv_tclass, 0);
} else {
EXPECT_EQ(recv_tclass, kArbitraryTClass);
}
}
TEST(RawSocketTest, SetIPv6ChecksumError_MultipleOf2) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));