mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add TOS control message for ICMP & RAW sockets
PiperOrigin-RevId: 426208980
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,6 +59,7 @@ go_test(
|
||||
"//pkg/tcpip/network/ipv4",
|
||||
"//pkg/tcpip/stack",
|
||||
"//pkg/tcpip/testutil",
|
||||
"//pkg/tcpip/transport/testing/context",
|
||||
"//pkg/waiter",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user