mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement {g,s}etsockopt(IP_RECVTOS) for UDP sockets
PiperOrigin-RevId: 289718534
This commit is contained in:
committed by
gVisor bot
parent
1ad8381eac
commit
50625cee59
@@ -327,7 +327,7 @@ func PackInq(t *kernel.Task, inq int32, buf []byte) []byte {
|
||||
}
|
||||
|
||||
// PackTOS packs an IP_TOS socket control message.
|
||||
func PackTOS(t *kernel.Task, tos int8, buf []byte) []byte {
|
||||
func PackTOS(t *kernel.Task, tos uint8, buf []byte) []byte {
|
||||
return putCmsgStruct(
|
||||
buf,
|
||||
linux.SOL_IP,
|
||||
|
||||
@@ -1268,11 +1268,11 @@ func getSockOptIPv6(t *kernel.Task, ep commonEndpoint, name, outLen int) (interf
|
||||
if err != nil {
|
||||
return nil, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
var o uint32
|
||||
var o int32
|
||||
if v {
|
||||
o = 1
|
||||
}
|
||||
return int32(o), nil
|
||||
return o, nil
|
||||
|
||||
case linux.IPV6_PATHMTU:
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
@@ -1377,6 +1377,21 @@ func getSockOptIP(t *kernel.Task, ep commonEndpoint, name, outLen int, family in
|
||||
}
|
||||
return int32(v), nil
|
||||
|
||||
case linux.IP_RECVTOS:
|
||||
if outLen < sizeOfInt32 {
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
v, err := ep.GetSockOptBool(tcpip.ReceiveTOSOption)
|
||||
if err != nil {
|
||||
return nil, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
var o int32
|
||||
if v {
|
||||
o = 1
|
||||
}
|
||||
return o, nil
|
||||
|
||||
default:
|
||||
emitUnimplementedEventIP(t, name)
|
||||
}
|
||||
@@ -1895,6 +1910,13 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s
|
||||
}
|
||||
return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.IPv4TOSOption(v)))
|
||||
|
||||
case linux.IP_RECVTOS:
|
||||
v, err := parseIntOrChar(optVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syserr.TranslateNetstackError(ep.SetSockOptBool(tcpip.ReceiveTOSOption, v != 0))
|
||||
|
||||
case linux.IP_ADD_SOURCE_MEMBERSHIP,
|
||||
linux.IP_BIND_ADDRESS_NO_PORT,
|
||||
linux.IP_BLOCK_SOURCE,
|
||||
@@ -1915,7 +1937,6 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s
|
||||
linux.IP_RECVFRAGSIZE,
|
||||
linux.IP_RECVOPTS,
|
||||
linux.IP_RECVORIGDSTADDR,
|
||||
linux.IP_RECVTOS,
|
||||
linux.IP_RECVTTL,
|
||||
linux.IP_RETOPTS,
|
||||
linux.IP_TRANSPARENT,
|
||||
@@ -2335,7 +2356,14 @@ func (s *SocketOperations) nonBlockingRead(ctx context.Context, dst usermem.IOSe
|
||||
}
|
||||
|
||||
func (s *SocketOperations) controlMessages() socket.ControlMessages {
|
||||
return socket.ControlMessages{IP: tcpip.ControlMessages{HasTimestamp: s.readCM.HasTimestamp && s.sockOptTimestamp, Timestamp: s.readCM.Timestamp}}
|
||||
return socket.ControlMessages{
|
||||
IP: tcpip.ControlMessages{
|
||||
HasTimestamp: s.readCM.HasTimestamp && s.sockOptTimestamp,
|
||||
Timestamp: s.readCM.Timestamp,
|
||||
HasTOS: s.readCM.HasTOS,
|
||||
TOS: s.readCM.TOS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// updateTimestamp sets the timestamp for SIOCGSTAMP. It should be called after
|
||||
|
||||
@@ -33,6 +33,9 @@ type NetworkChecker func(*testing.T, []header.Network)
|
||||
// TransportChecker is a function to check a property of a transport packet.
|
||||
type TransportChecker func(*testing.T, header.Transport)
|
||||
|
||||
// ControlMessagesChecker is a function to check a property of ancillary data.
|
||||
type ControlMessagesChecker func(*testing.T, tcpip.ControlMessages)
|
||||
|
||||
// IPv4 checks the validity and properties of the given IPv4 packet. It is
|
||||
// expected to be used in conjunction with other network checkers for specific
|
||||
// properties. For example, to check the source and destination address, one
|
||||
@@ -158,6 +161,19 @@ func FragmentFlags(flags uint8) NetworkChecker {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Fatalf("got cm.HasTOS = %t, want cm.TOS = %d", cm.HasTOS, want)
|
||||
}
|
||||
if got := cm.TOS; got != want {
|
||||
t.Fatalf("got cm.TOS = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TOS creates a checker that checks the TOS field.
|
||||
func TOS(tos uint8, label uint32) NetworkChecker {
|
||||
return func(t *testing.T, h []header.Network) {
|
||||
|
||||
@@ -763,7 +763,7 @@ func (n *NIC) RemoveAddressRange(subnet tcpip.Subnet) {
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
// Subnets returns the Subnets associated with this NIC.
|
||||
// AddressRanges returns the Subnets associated with this NIC.
|
||||
func (n *NIC) AddressRanges() []tcpip.Subnet {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
@@ -912,7 +912,7 @@ func (s *Stack) CheckNIC(id tcpip.NICID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// NICSubnets returns a map of NICIDs to their associated subnets.
|
||||
// NICAddressRanges returns a map of NICIDs to their associated subnets.
|
||||
func (s *Stack) NICAddressRanges() map[tcpip.NICID][]tcpip.Subnet {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
+6
-2
@@ -322,7 +322,7 @@ type ControlMessages struct {
|
||||
HasTOS bool
|
||||
|
||||
// TOS is the IPv4 type of service of the associated packet.
|
||||
TOS int8
|
||||
TOS uint8
|
||||
|
||||
// HasTClass indicates whether Tclass is valid/set.
|
||||
HasTClass bool
|
||||
@@ -500,9 +500,13 @@ type WriteOptions struct {
|
||||
type SockOptBool int
|
||||
|
||||
const (
|
||||
// ReceiveTOSOption is used by SetSockOpt/GetSockOpt to specify if the TOS
|
||||
// ancillary message is passed with incoming packets.
|
||||
ReceiveTOSOption SockOptBool = iota
|
||||
|
||||
// V6OnlyOption is used by {G,S}etSockOptBool to specify whether an IPv6
|
||||
// socket is to be restricted to sending and receiving IPv6 packets only.
|
||||
V6OnlyOption SockOptBool = iota
|
||||
V6OnlyOption
|
||||
)
|
||||
|
||||
// SockOptInt represents socket options which values have the int type.
|
||||
|
||||
@@ -31,6 +31,7 @@ type udpPacket struct {
|
||||
senderAddress tcpip.FullAddress
|
||||
data buffer.VectorisedView `state:".(buffer.VectorisedView)"`
|
||||
timestamp int64
|
||||
tos uint8
|
||||
}
|
||||
|
||||
// EndpointState represents the state of a UDP endpoint.
|
||||
@@ -113,6 +114,10 @@ type endpoint struct {
|
||||
// applied while sending packets. Defaults to 0 as on Linux.
|
||||
sendTOS uint8
|
||||
|
||||
// receiveTOS determines if the incoming IPv4 TOS header field is passed
|
||||
// as ancillary data to ControlMessages on Read.
|
||||
receiveTOS bool
|
||||
|
||||
// shutdownFlags represent the current shutdown state of the endpoint.
|
||||
shutdownFlags tcpip.ShutdownFlags
|
||||
|
||||
@@ -243,7 +248,18 @@ func (e *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMess
|
||||
*addr = p.senderAddress
|
||||
}
|
||||
|
||||
return p.data.ToView(), tcpip.ControlMessages{HasTimestamp: true, Timestamp: p.timestamp}, nil
|
||||
cm := tcpip.ControlMessages{
|
||||
HasTimestamp: true,
|
||||
Timestamp: p.timestamp,
|
||||
}
|
||||
e.mu.RLock()
|
||||
receiveTOS := e.receiveTOS
|
||||
e.mu.RUnlock()
|
||||
if receiveTOS {
|
||||
cm.HasTOS = true
|
||||
cm.TOS = p.tos
|
||||
}
|
||||
return p.data.ToView(), cm, nil
|
||||
}
|
||||
|
||||
// prepareForWrite prepares the endpoint for sending data. In particular, it
|
||||
@@ -458,6 +474,12 @@ func (e *endpoint) Peek([][]byte) (int64, tcpip.ControlMessages, *tcpip.Error) {
|
||||
// SetSockOptBool implements tcpip.Endpoint.SetSockOptBool.
|
||||
func (e *endpoint) SetSockOptBool(opt tcpip.SockOptBool, v bool) *tcpip.Error {
|
||||
switch opt {
|
||||
case tcpip.ReceiveTOSOption:
|
||||
e.mu.Lock()
|
||||
e.receiveTOS = v
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case tcpip.V6OnlyOption:
|
||||
// We only recognize this option on v6 endpoints.
|
||||
if e.NetProto != header.IPv6ProtocolNumber {
|
||||
@@ -664,15 +686,21 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
|
||||
// GetSockOptBool implements tcpip.Endpoint.GetSockOptBool.
|
||||
func (e *endpoint) GetSockOptBool(opt tcpip.SockOptBool) (bool, *tcpip.Error) {
|
||||
switch opt {
|
||||
case tcpip.ReceiveTOSOption:
|
||||
e.mu.RLock()
|
||||
v := e.receiveTOS
|
||||
e.mu.RUnlock()
|
||||
return v, nil
|
||||
|
||||
case tcpip.V6OnlyOption:
|
||||
// We only recognize this option on v6 endpoints.
|
||||
if e.NetProto != header.IPv6ProtocolNumber {
|
||||
return false, tcpip.ErrUnknownProtocolOption
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
e.mu.RLock()
|
||||
v := e.v6only
|
||||
e.mu.Unlock()
|
||||
e.mu.RUnlock()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
@@ -1215,6 +1243,12 @@ func (e *endpoint) HandlePacket(r *stack.Route, id stack.TransportEndpointID, pk
|
||||
e.rcvList.PushBack(packet)
|
||||
e.rcvBufSize += pkt.Data.Size()
|
||||
|
||||
// Save any useful information from the network header to the packet.
|
||||
switch r.NetProto {
|
||||
case header.IPv4ProtocolNumber:
|
||||
packet.tos, _ = header.IPv4(pkt.NetworkHeader).TOS()
|
||||
}
|
||||
|
||||
packet.timestamp = e.stack.NowNanoseconds()
|
||||
|
||||
e.rcvMu.Unlock()
|
||||
|
||||
@@ -56,6 +56,7 @@ const (
|
||||
multicastAddr = "\xe8\x2b\xd3\xea"
|
||||
multicastV6Addr = "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
broadcastAddr = header.IPv4Broadcast
|
||||
testTOS = 0x80
|
||||
|
||||
// defaultMTU is the MTU, in bytes, used throughout the tests, except
|
||||
// where another value is explicitly used. It is chosen to match the MTU
|
||||
@@ -453,6 +454,7 @@ func (c *testContext) injectV4Packet(payload []byte, h *header4Tuple, valid bool
|
||||
ip := header.IPv4(buf)
|
||||
ip.Encode(&header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: testTOS,
|
||||
TotalLength: uint16(len(buf)),
|
||||
TTL: 65,
|
||||
Protocol: uint8(udp.ProtocolNumber),
|
||||
@@ -552,8 +554,8 @@ func TestBindToDeviceOption(t *testing.T) {
|
||||
// testReadInternal sends a packet of the given test flow into the stack by
|
||||
// injecting it into the link endpoint. It then attempts to read it from the
|
||||
// UDP endpoint and depending on if this was expected to succeed verifies its
|
||||
// correctness.
|
||||
func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expectReadError bool) {
|
||||
// correctness including any additional checker functions provided.
|
||||
func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expectReadError bool, checkers ...checker.ControlMessagesChecker) {
|
||||
c.t.Helper()
|
||||
|
||||
payload := newPayload()
|
||||
@@ -568,12 +570,12 @@ func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expe
|
||||
epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
v, _, err := c.ep.Read(&addr)
|
||||
v, cm, err := c.ep.Read(&addr)
|
||||
if err == tcpip.ErrWouldBlock {
|
||||
// Wait for data to become available.
|
||||
select {
|
||||
case <-ch:
|
||||
v, _, err = c.ep.Read(&addr)
|
||||
v, cm, err = c.ep.Read(&addr)
|
||||
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
if packetShouldBeDropped {
|
||||
@@ -606,15 +608,21 @@ func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expe
|
||||
if !bytes.Equal(payload, v) {
|
||||
c.t.Fatalf("bad payload: got %x, want %x", v, payload)
|
||||
}
|
||||
|
||||
// Run any checkers against the ControlMessages.
|
||||
for _, f := range checkers {
|
||||
f(c.t, cm)
|
||||
}
|
||||
|
||||
c.checkEndpointReadStats(1, epstats, err)
|
||||
}
|
||||
|
||||
// testRead sends a packet of the given test flow into the stack by injecting it
|
||||
// into the link endpoint. It then reads it from the UDP endpoint and verifies
|
||||
// its correctness.
|
||||
func testRead(c *testContext, flow testFlow) {
|
||||
// its correctness including any additional checker functions provided.
|
||||
func testRead(c *testContext, flow testFlow, checkers ...checker.ControlMessagesChecker) {
|
||||
c.t.Helper()
|
||||
testReadInternal(c, flow, false /* packetShouldBeDropped */, false /* expectReadError */)
|
||||
testReadInternal(c, flow, false /* packetShouldBeDropped */, false /* expectReadError */, checkers...)
|
||||
}
|
||||
|
||||
// testFailingRead sends a packet of the given test flow into the stack by
|
||||
@@ -1282,7 +1290,7 @@ func TestTOSV4(t *testing.T) {
|
||||
|
||||
c.createEndpointForFlow(flow)
|
||||
|
||||
const tos = 0xC0
|
||||
const tos = testTOS
|
||||
var v tcpip.IPv4TOSOption
|
||||
if err := c.ep.GetSockOpt(&v); err != nil {
|
||||
c.t.Errorf("GetSockopt failed: %s", err)
|
||||
@@ -1317,7 +1325,7 @@ func TestTOSV6(t *testing.T) {
|
||||
|
||||
c.createEndpointForFlow(flow)
|
||||
|
||||
const tos = 0xC0
|
||||
const tos = testTOS
|
||||
var v tcpip.IPv6TrafficClassOption
|
||||
if err := c.ep.GetSockOpt(&v); err != nil {
|
||||
c.t.Errorf("GetSockopt failed: %s", err)
|
||||
@@ -1344,6 +1352,47 @@ func TestTOSV6(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiveTOSV4(t *testing.T) {
|
||||
for _, flow := range []testFlow{unicastV4, broadcast} {
|
||||
t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
|
||||
c := newDualTestContext(t, defaultMTU)
|
||||
defer c.cleanup()
|
||||
|
||||
c.createEndpointForFlow(flow)
|
||||
|
||||
// Verify that setting and reading the option works.
|
||||
v, err := c.ep.GetSockOptBool(tcpip.ReceiveTOSOption)
|
||||
if err != nil {
|
||||
c.t.Fatal("GetSockOptBool(tcpip.ReceiveTOSOption) failed:", err)
|
||||
}
|
||||
// Test for expected default value.
|
||||
if v != false {
|
||||
c.t.Errorf("got GetSockOptBool(tcpip.ReceiveTOSOption) = %t, want = %t", v, false)
|
||||
}
|
||||
|
||||
want := true
|
||||
if err := c.ep.SetSockOptBool(tcpip.ReceiveTOSOption, want); err != nil {
|
||||
c.t.Fatalf("SetSockOptBool(tcpip.ReceiveTOSOption, %t) failed: %s", want, err)
|
||||
}
|
||||
|
||||
got, err := c.ep.GetSockOptBool(tcpip.ReceiveTOSOption)
|
||||
if err != nil {
|
||||
c.t.Fatal("GetSockOptBool(tcpip.ReceiveTOSOption) failed:", err)
|
||||
}
|
||||
if got != want {
|
||||
c.t.Fatalf("got GetSockOptBool(tcpip.ReceiveTOSOption) = %t, want = %t", got, want)
|
||||
}
|
||||
|
||||
// Verify that the correct received TOS is handed through as
|
||||
// ancillary data to the ControlMessages struct.
|
||||
if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
|
||||
c.t.Fatal("Bind failed:", err)
|
||||
}
|
||||
testRead(c, flow, checker.ReceiveTOS(testTOS))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMulticastInterfaceOption(t *testing.T) {
|
||||
for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6, multicastV6Only} {
|
||||
t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
|
||||
|
||||
@@ -209,6 +209,46 @@ TEST_P(UDPSocketPairTest, SetMulticastLoopChar) {
|
||||
EXPECT_EQ(get, kSockOptOn);
|
||||
}
|
||||
|
||||
// Ensure that Receiving TOS is off by default.
|
||||
TEST_P(UDPSocketPairTest, RecvTosDefault) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
int get = -1;
|
||||
socklen_t get_len = sizeof(get);
|
||||
ASSERT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_IP, IP_RECVTOS, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kSockOptOff);
|
||||
}
|
||||
|
||||
// Test that setting and getting IP_RECVTOS works as expected.
|
||||
TEST_P(UDPSocketPairTest, SetRecvTos) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_RECVTOS,
|
||||
&kSockOptOff, sizeof(kSockOptOff)),
|
||||
SyscallSucceeds());
|
||||
|
||||
int get = -1;
|
||||
socklen_t get_len = sizeof(get);
|
||||
ASSERT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_IP, IP_RECVTOS, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kSockOptOff);
|
||||
|
||||
ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_RECVTOS,
|
||||
&kSockOptOn, sizeof(kSockOptOn)),
|
||||
SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_IP, IP_RECVTOS, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kSockOptOn);
|
||||
}
|
||||
|
||||
TEST_P(UDPSocketPairTest, ReuseAddrDefault) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
|
||||
@@ -1349,8 +1349,9 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
|
||||
// outgoing packets, and that a receiving socket with IP_RECVTOS or
|
||||
// IPV6_RECVTCLASS will create the corresponding control message.
|
||||
TEST_P(UdpSocketTest, SetAndReceiveTOS) {
|
||||
// TODO(b/68320120): IP_RECVTOS/IPV6_RECVTCLASS not supported for netstack.
|
||||
SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet());
|
||||
// TODO(b/68320120): IPV6_RECVTCLASS not supported for netstack.
|
||||
SKIP_IF((GetParam() != AddressFamily::kIpv4) && IsRunningOnGvisor() &&
|
||||
!IsRunningWithHostinet());
|
||||
ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
|
||||
ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
|
||||
|
||||
@@ -1421,7 +1422,8 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
|
||||
// TOS byte on outgoing packets, and that a receiving socket with IP_RECVTOS or
|
||||
// IPV6_RECVTCLASS will create the corresponding control message.
|
||||
TEST_P(UdpSocketTest, SendAndReceiveTOS) {
|
||||
// TODO(b/68320120): IP_RECVTOS/IPV6_RECVTCLASS not supported for netstack.
|
||||
// TODO(b/68320120): IPV6_RECVTCLASS not supported for netstack.
|
||||
// TODO(b/146661005): Setting TOS via cmsg not supported for netstack.
|
||||
SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet());
|
||||
ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
|
||||
ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
|
||||
|
||||
Reference in New Issue
Block a user