From 21dffa8f4c8a6d9c744f3629782eab26890d7187 Mon Sep 17 00:00:00 2001 From: Arthur Sfez Date: Wed, 23 Feb 2022 15:38:20 -0800 Subject: [PATCH] Support sending TTL and HopLimit PiperOrigin-RevId: 430554985 --- pkg/sentry/socket/control/control.go | 15 ++- pkg/sentry/socket/netstack/netstack.go | 22 +++- pkg/tcpip/tcpip.go | 29 ++++- .../transport/internal/network/endpoint.go | 13 +- test/syscalls/linux/raw_socket.cc | 116 ++++++++++++------ test/syscalls/linux/udp_socket.cc | 4 - 6 files changed, 142 insertions(+), 57 deletions(-) diff --git a/pkg/sentry/socket/control/control.go b/pkg/sentry/socket/control/control.go index edb85b5a6..fa79f9c94 100644 --- a/pkg/sentry/socket/control/control.go +++ b/pkg/sentry/socket/control/control.go @@ -17,6 +17,7 @@ package control import ( + "math" "time" "gvisor.dev/gvisor/pkg/abi/linux" @@ -522,6 +523,10 @@ func CmsgsSpace(t *kernel.Task, cmsgs socket.ControlMessages) int { } // Parse parses a raw socket control message into portable objects. +// TODO(https://gvisor.dev/issue/7188): Parse is only called on raw cmsg that +// are used when sending a messages. We should fail with EINVAL when we find a +// non-sendable control messages (such as IP_RECVERR). And the function should +// be renamed to reflect that. func Parse(t *kernel.Task, socketOrEndpoint interface{}, buf []byte, width uint) (socket.ControlMessages, error) { var ( cmsgs socket.ControlMessages @@ -601,10 +606,13 @@ func Parse(t *kernel.Task, socketOrEndpoint interface{}, buf []byte, width uint) if length < linux.SizeOfControlMessageTTL { return socket.ControlMessages{}, linuxerr.EINVAL } - cmsgs.IP.HasTTL = true var ttl primitive.Uint32 ttl.UnmarshalUnsafe(buf) + if ttl == 0 || ttl > math.MaxUint8 { + return socket.ControlMessages{}, linuxerr.EINVAL + } cmsgs.IP.TTL = uint32(ttl) + cmsgs.IP.HasTTL = true case linux.IP_PKTINFO: if length < linux.SizeOfControlMessageIPPacketInfo { @@ -661,9 +669,12 @@ func Parse(t *kernel.Task, socketOrEndpoint interface{}, buf []byte, width uint) if length < linux.SizeOfControlMessageHopLimit { return socket.ControlMessages{}, linuxerr.EINVAL } - cmsgs.IP.HasHopLimit = true var hoplimit primitive.Uint32 hoplimit.UnmarshalUnsafe(buf) + if hoplimit > math.MaxUint8 { + return socket.ControlMessages{}, linuxerr.EINVAL + } + cmsgs.IP.HasHopLimit = true cmsgs.IP.HopLimit = uint32(hoplimit) case linux.IPV6_RECVORIGDSTADDR: diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index 1537cff43..80779299a 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -2869,7 +2869,7 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq flags |= linux.MSG_TRUNC } - return msgLen, flags, addr, addrLen, s.controlMessages(res.ControlMessages), nil + return msgLen, flags, addr, addrLen, s.netstackToLinuxControlMessages(res.ControlMessages), nil } if peek { @@ -2892,12 +2892,12 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq s.Endpoint.ModerateRecvBuf(n) } - cmsg := s.controlMessages(res.ControlMessages) + cmsg := s.netstackToLinuxControlMessages(res.ControlMessages) s.fillCmsgInq(&cmsg) return res.Count, 0, nil, 0, cmsg, syserr.TranslateNetstackError(err) } -func (s *socketOpsCommon) controlMessages(cm tcpip.ReceivableControlMessages) socket.ControlMessages { +func (s *socketOpsCommon) netstackToLinuxControlMessages(cm tcpip.ReceivableControlMessages) socket.ControlMessages { readCM := socket.NewIPControlMessages(s.family, cm) return socket.ControlMessages{ IP: socket.IPControlMessages{ @@ -2923,6 +2923,15 @@ func (s *socketOpsCommon) controlMessages(cm tcpip.ReceivableControlMessages) so } } +func (s *socketOpsCommon) linuxToNetstackControlMessages(cm socket.ControlMessages) tcpip.SendableControlMessages { + return tcpip.SendableControlMessages{ + HasTTL: cm.IP.HasTTL, + TTL: uint8(cm.IP.TTL), + HasHopLimit: cm.IP.HasHopLimit, + HopLimit: uint8(cm.IP.HopLimit), + } +} + // updateTimestamp sets the timestamp for SIOCGSTAMP. It should be called after // successfully writing packet data out to userspace. // @@ -3083,9 +3092,10 @@ func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []b } opts := tcpip.WriteOptions{ - To: addr, - More: flags&linux.MSG_MORE != 0, - EndOfRecord: flags&linux.MSG_EOR != 0, + To: addr, + More: flags&linux.MSG_MORE != 0, + EndOfRecord: flags&linux.MSG_EOR != 0, + ControlMessages: s.linuxToNetstackControlMessages(controlMessages), } r := src.Reader(t) diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 726d3ac6c..da6546a07 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -416,13 +416,28 @@ func (l *LimitedWriter) Write(p []byte) (int, error) { return n, err } -// ReceivableControlMessages holds control messages that can be received. +// SendableControlMessages contains socket control messages that can be written. +// +// +stateify savable +type SendableControlMessages struct { + // HasTTL indicates whether TTL is valid/set. + HasTTL bool + + // TTL is the IPv4 Time To Live of the associated packet. + TTL uint8 + + // HasHopLimit indicates whether HopLimit is valid/set. + HasHopLimit bool + + // HopLimit is the IPv6 Hop Limit of the associated packet. + HopLimit uint8 +} + +// ReceivableControlMessages contains socket control messages that can be +// received. // // +stateify savable type ReceivableControlMessages struct { - // HasTimestamp indicates whether Timestamp is valid/set. - HasTimestamp bool - // Timestamp is the time that the last packet used to create the read data // was received. Timestamp time.Time `state:".(int64)"` @@ -451,6 +466,9 @@ type ReceivableControlMessages struct { // HopLimit is the IPv6 Hop Limit of the associated packet. HopLimit uint8 + // HasTimestamp indicates whether Timestamp is valid/set. + HasTimestamp bool + // HasTClass indicates whether TClass is valid/set. HasTClass bool @@ -699,6 +717,9 @@ type WriteOptions struct { // endpoint. If Atomic is false, then data fetched from the Payloader may be // discarded if available endpoint buffer space is unsufficient. Atomic bool + + // ControlMessages contains optional overrides used when writing a packet. + ControlMessages SendableControlMessages } // SockOptInt represents socket options which values have the int type. diff --git a/pkg/tcpip/transport/internal/network/endpoint.go b/pkg/tcpip/transport/internal/network/endpoint.go index 55f518a86..63c0eed3f 100644 --- a/pkg/tcpip/transport/internal/network/endpoint.go +++ b/pkg/tcpip/transport/internal/network/endpoint.go @@ -329,11 +329,22 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext } var tos uint8 + var ttl uint8 switch netProto := route.NetProto(); netProto { case header.IPv4ProtocolNumber: tos = e.ipv4TOS + if opts.ControlMessages.HasTTL { + ttl = opts.ControlMessages.TTL + } else { + ttl = e.calculateTTL(route) + } case header.IPv6ProtocolNumber: tos = e.ipv6TClass + if opts.ControlMessages.HasHopLimit { + ttl = opts.ControlMessages.HopLimit + } else { + ttl = e.calculateTTL(route) + } default: panic(fmt.Sprintf("invalid protocol number = %d", netProto)) } @@ -341,7 +352,7 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext return WriteContext{ transProto: e.transProto, route: route, - ttl: e.calculateTTL(route), + ttl: ttl, tos: tos, owner: e.owner, }, nil diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index aefa0dc80..2ab90cf25 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -1300,44 +1300,63 @@ TEST(RawSocketTest, ReceiveTTL) { ASSERT_THAT( bind(raw.get(), reinterpret_cast(&kAddr), sizeof(kAddr)), SyscallSucceeds()); + ASSERT_THAT(connect(raw.get(), reinterpret_cast(&kAddr), + sizeof(kAddr)), + SyscallSucceeds()); constexpr int kArbitraryTTL = 42; ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IP, IP_TTL, &kArbitraryTTL, sizeof(kArbitraryTTL)), SyscallSucceeds()); - constexpr char kSendBuf[] = "malformed UDP"; - ASSERT_THAT(sendto(raw.get(), kSendBuf, sizeof(kSendBuf), 0 /* flags */, - reinterpret_cast(&kAddr), sizeof(kAddr)), - SyscallSucceedsWithValue(sizeof(kSendBuf))); + char send_buf[] = "malformed UDP"; + auto test_recv_ttl = [&](int expected_ttl) { + // Register to receive TTL. + constexpr int kOne = 1; + ASSERT_THAT( + setsockopt(raw.get(), IPPROTO_IP, IP_RECVTTL, &kOne, sizeof(kOne)), + SyscallSucceeds()); - // Register to receive TTL. - constexpr int kOne = 1; - ASSERT_THAT( - setsockopt(raw.get(), IPPROTO_IP, IP_RECVTTL, &kOne, sizeof(kOne)), - SyscallSucceeds()); + struct { + iphdr ip; + char data[sizeof(send_buf)]; + } ABSL_ATTRIBUTE_PACKED recv_buf; - struct { - iphdr ip; - char data[sizeof(kSendBuf)]; - } ABSL_ATTRIBUTE_PACKED recv_buf; - int recv_ttl; - size_t recv_buf_len = sizeof(recv_buf); - ASSERT_NO_FATAL_FAILURE(RecvTTL(raw.get(), reinterpret_cast(&recv_buf), - &recv_buf_len, &recv_ttl)); - ASSERT_EQ(recv_buf_len, sizeof(iphdr) + sizeof(kSendBuf)); + int recv_ttl; + size_t recv_buf_len = sizeof(recv_buf); + ASSERT_NO_FATAL_FAILURE(RecvTTL(raw.get(), + reinterpret_cast(&recv_buf), + &recv_buf_len, &recv_ttl)); + ASSERT_EQ(recv_buf_len, sizeof(iphdr) + sizeof(send_buf)); - EXPECT_EQ(recv_buf.ip.version, static_cast(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(recv_buf.ip.version, static_cast(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(send_buf)); + 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(recv_buf.ip.ttl, static_cast(expected_ttl)); - EXPECT_EQ(memcmp(kSendBuf, &recv_buf.data, sizeof(kSendBuf)), 0); + EXPECT_EQ(memcmp(send_buf, &recv_buf.data, sizeof(send_buf)), 0); - EXPECT_EQ(recv_ttl, kArbitraryTTL); + EXPECT_EQ(recv_ttl, expected_ttl); + }; + + ASSERT_THAT(send(raw.get(), send_buf, sizeof(send_buf), /*flags=*/0), + SyscallSucceedsWithValue(sizeof(send_buf))); + { + SCOPED_TRACE("receive ttl set by option"); + ASSERT_NO_FATAL_FAILURE(test_recv_ttl(kArbitraryTTL)); + } + + constexpr int kArbitrarySendmsgTTL = kArbitraryTTL + 1; + ASSERT_NO_FATAL_FAILURE(SendTTL(raw.get(), send_buf, size_t(sizeof(send_buf)), + kArbitrarySendmsgTTL)); + { + SCOPED_TRACE("receive ttl set by cmsg"); + ASSERT_NO_FATAL_FAILURE(test_recv_ttl(kArbitrarySendmsgTTL)); + } } TEST(RawSocketTest, ReceiveHopLimit) { @@ -1353,32 +1372,49 @@ TEST(RawSocketTest, ReceiveHopLimit) { ASSERT_THAT( bind(raw.get(), reinterpret_cast(&kAddr), sizeof(kAddr)), SyscallSucceeds()); + ASSERT_THAT(connect(raw.get(), reinterpret_cast(&kAddr), + sizeof(kAddr)), + SyscallSucceeds()); constexpr int kArbitraryHopLimit = 42; ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kArbitraryHopLimit, sizeof(kArbitraryHopLimit)), SyscallSucceeds()); - constexpr char send_buf[] = "malformed UDP"; - ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */, - reinterpret_cast(&kAddr), sizeof(kAddr)), - SyscallSucceedsWithValue(sizeof(send_buf))); - // Register to receive HOPLIMIT. constexpr int kOne = 1; ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kOne, sizeof(kOne)), SyscallSucceeds()); - char recv_buf[sizeof(send_buf) + 1]; - size_t recv_buf_len = sizeof(recv_buf); - int recv_hoplimit; - ASSERT_NO_FATAL_FAILURE( - RecvHopLimit(raw.get(), recv_buf, &recv_buf_len, &recv_hoplimit)); - ASSERT_EQ(recv_buf_len, sizeof(send_buf)); + char send_buf[] = "malformed UDP"; + auto test_recv_hoplimit = [&](int expected_hoplimit) { + char recv_buf[sizeof(send_buf)]; + size_t recv_buf_len = sizeof(recv_buf); + int recv_hoplimit; + ASSERT_NO_FATAL_FAILURE( + RecvHopLimit(raw.get(), recv_buf, &recv_buf_len, &recv_hoplimit)); + ASSERT_EQ(recv_buf_len, sizeof(send_buf)); - EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0); - EXPECT_EQ(recv_hoplimit, kArbitraryHopLimit); + EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0); + EXPECT_EQ(recv_hoplimit, expected_hoplimit); + }; + + ASSERT_THAT(send(raw.get(), send_buf, sizeof(send_buf), /*flags=*/0), + SyscallSucceedsWithValue(sizeof(send_buf))); + { + SCOPED_TRACE("receive hoplimit set by option"); + ASSERT_NO_FATAL_FAILURE(test_recv_hoplimit(kArbitraryHopLimit)); + } + + constexpr int kArbitrarySendmsgHopLimit = kArbitraryHopLimit + 1; + ASSERT_NO_FATAL_FAILURE(SendHopLimit(raw.get(), send_buf, + size_t(sizeof(send_buf)), + kArbitrarySendmsgHopLimit)); + { + SCOPED_TRACE("receive hoplimit set by cmsg"); + ASSERT_NO_FATAL_FAILURE(test_recv_hoplimit(kArbitrarySendmsgHopLimit)); + } } TEST(RawSocketTest, SetIPv6ChecksumError_MultipleOf2) { diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index ba2d83ac3..a1265ec36 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -2189,10 +2189,6 @@ TEST_P(UdpSocketControlMessagesTest, SetAndReceiveTTLOrHopLimit) { } TEST_P(UdpSocketControlMessagesTest, SendAndReceiveTTLOrHopLimit) { - // TODO(b/146661005): Setting TTL/HopLimit via sendmsg is not supported by - // netstack. - SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet()); - // Enable receiving TTL and maybe HOPLIMIT on the receiver. ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTTL, &kSockOptOn, sizeof(kSockOptOn)),