diff --git a/pkg/sentry/socket/control/control.go b/pkg/sentry/socket/control/control.go index c40141a1d..c82e59337 100644 --- a/pkg/sentry/socket/control/control.go +++ b/pkg/sentry/socket/control/control.go @@ -600,6 +600,16 @@ func Parse(t *kernel.Task, socketOrEndpoint interface{}, buf []byte, width uint) tclass.UnmarshalUnsafe(buf) cmsgs.IP.TClass = uint32(tclass) + case linux.IPV6_PKTINFO: + if length < linux.SizeOfControlMessageIPv6PacketInfo { + return socket.ControlMessages{}, linuxerr.EINVAL + } + + cmsgs.IP.HasIPv6PacketInfo = true + var packetInfo linux.ControlMessageIPv6PacketInfo + packetInfo.UnmarshalUnsafe(buf) + cmsgs.IP.IPv6PacketInfo = packetInfo + case linux.IPV6_RECVORIGDSTADDR: var addr linux.SockAddrInet6 if length < addr.SizeBytes() { diff --git a/pkg/sentry/socket/hostinet/socket.go b/pkg/sentry/socket/hostinet/socket.go index 8b19a75f4..547faba75 100644 --- a/pkg/sentry/socket/hostinet/socket.go +++ b/pkg/sentry/socket/hostinet/socket.go @@ -392,7 +392,7 @@ func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, optVal } case linux.SOL_IPV6: switch name { - case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR: + case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVPKTINFO, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR: optlen = sizeofInt32 } case linux.SOL_SOCKET: @@ -449,7 +449,7 @@ func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt [] } case linux.SOL_IPV6: switch name { - case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR: + case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVPKTINFO, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR: optlen = sizeofInt32 } case linux.SOL_SOCKET: @@ -632,6 +632,12 @@ func parseUnixControlMessages(unixControlMessages []unix.SocketControlMessage) s tclass.UnmarshalUnsafe(unixCmsg.Data) controlMessages.IP.TClass = uint32(tclass) + case linux.IPV6_PKTINFO: + controlMessages.IP.HasIPv6PacketInfo = true + var packetInfo linux.ControlMessageIPv6PacketInfo + packetInfo.UnmarshalUnsafe(unixCmsg.Data) + controlMessages.IP.IPv6PacketInfo = packetInfo + case linux.IPV6_RECVORIGDSTADDR: var addr linux.SockAddrInet6 addr.UnmarshalUnsafe(unixCmsg.Data) diff --git a/pkg/tcpip/checker/checker.go b/pkg/tcpip/checker/checker.go index 52a9a19fb..622939bc3 100644 --- a/pkg/tcpip/checker/checker.go +++ b/pkg/tcpip/checker/checker.go @@ -346,6 +346,17 @@ func ReceiveIPPacketInfo(want tcpip.IPPacketInfo) ControlMessagesChecker { } } +// NoIPPacketInfoReceived creates a checker that checks the PacketInfo field in +// ControlMessages. +func NoIPPacketInfoReceived() ControlMessagesChecker { + return func(t *testing.T, cm tcpip.ControlMessages) { + t.Helper() + if cm.HasIPPacketInfo { + t.Error("got cm.HasIPPacketInfo = true, want = false") + } + } +} + // ReceiveIPv6PacketInfo creates a checker that checks the IPv6PacketInfo field // in ControlMessages. func ReceiveIPv6PacketInfo(want tcpip.IPv6PacketInfo) ControlMessagesChecker { @@ -359,6 +370,17 @@ func ReceiveIPv6PacketInfo(want tcpip.IPv6PacketInfo) ControlMessagesChecker { } } +// NoIPv6PacketInfoReceived creates a checker that checks the PacketInfo field +// in ControlMessages. +func NoIPv6PacketInfoReceived() ControlMessagesChecker { + return func(t *testing.T, cm tcpip.ControlMessages) { + t.Helper() + if cm.HasIPv6PacketInfo { + t.Error("got cm.HasIPv6PacketInfo = true, want = false") + } + } +} + // ReceiveOriginalDstAddr creates a checker that checks the OriginalDstAddress // field in ControlMessages. func ReceiveOriginalDstAddr(want tcpip.FullAddress) ControlMessagesChecker { diff --git a/pkg/tcpip/transport/icmp/endpoint.go b/pkg/tcpip/transport/icmp/endpoint.go index 8f1422e5b..df95438d5 100644 --- a/pkg/tcpip/transport/icmp/endpoint.go +++ b/pkg/tcpip/transport/icmp/endpoint.go @@ -35,6 +35,7 @@ import ( type icmpPacket struct { icmpPacketEntry senderAddress tcpip.FullAddress + packetInfo tcpip.IPPacketInfo data buffer.VectorisedView `state:".(buffer.VectorisedView)"` receivedAt time.Time `state:".(int64)"` @@ -194,12 +195,23 @@ func (e *endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult cm.HasTOS = true cm.TOS = p.tosOrTClass } + if e.ops.GetReceivePacketInfo() { + cm.HasIPPacketInfo = true + cm.PacketInfo = p.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(p.tosOrTClass) } + if e.ops.GetIPv6ReceivePacketInfo() { + cm.HasIPv6PacketInfo = true + cm.IPv6PacketInfo = tcpip.IPv6PacketInfo{ + NIC: p.packetInfo.NIC, + Addr: p.packetInfo.DestinationAddr, + } + } default: panic(fmt.Sprintf("unrecognized network protocol = %d", netProto)) } @@ -696,16 +708,28 @@ func (e *endpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketB wasEmpty := e.rcvBufSize == 0 + net := pkt.Network() + dstAddr := net.DestinationAddress() // Push new packet into receive list and increment the buffer size. packet := &icmpPacket{ senderAddress: tcpip.FullAddress{ NIC: pkt.NICID, Addr: id.RemoteAddress, }, + packetInfo: tcpip.IPPacketInfo{ + // Linux does not 'prepare' [1] in_pktinfo on socket buffers destined to + // ping sockets (unlike UDP/RAW sockets). However the interface index [2] + // and the Header Destination Address [3] are always filled. + // [1] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_sockglue.c#L1392 + // [2] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_input.c#L510 + // [3] https://github.com/torvalds/linux/blob/dcb85f85fa6/net/ipv4/ip_sockglue.c#L60 + NIC: pkt.NICID, + DestinationAddr: dstAddr, + }, } // Save any useful information from the network header to the packet. - packet.tosOrTClass, _ = pkt.Network().TOS() + packet.tosOrTClass, _ = net.TOS() // ICMP socket's data includes ICMP header. packet.data = pkt.TransportHeader().View().ToVectorisedView() diff --git a/pkg/tcpip/transport/icmp/icmp_test.go b/pkg/tcpip/transport/icmp/icmp_test.go index c88c88d97..daf4ed496 100644 --- a/pkg/tcpip/transport/icmp/icmp_test.go +++ b/pkg/tcpip/transport/icmp/icmp_test.go @@ -320,34 +320,65 @@ func buildEchoReplyPacket(payload []byte, flow context.TestFlow) (buffer.View, b 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) { + 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) { + 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(), + }, + { + name: "IPPacketInfo", + optionProtocol: header.IPv4ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ + NIC: context.NICID, + // TODO(https://gvisor.dev/issue/3556): Expect the NIC's address + // instead of the header destination address for the LocalAddr + // field. + DestinationAddr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPPacketInfoReceived(), + }, + { + name: "IPv6PacketInfo", + optionProtocol: header.IPv6ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetIPv6ReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetIPv6ReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ + NIC: context.NICID, + Addr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPv6PacketInfoReceived(), + }, + } { + t.Run(test.name, func(t *testing.T) { c := context.New(t, []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6}) defer c.Cleanup() diff --git a/pkg/tcpip/transport/raw/raw_test.go b/pkg/tcpip/transport/raw/raw_test.go index 92bb3ed77..e1dd5b25b 100644 --- a/pkg/tcpip/transport/raw/raw_test.go +++ b/pkg/tcpip/transport/raw/raw_test.go @@ -36,35 +36,66 @@ const ( 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) { - + 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) { + 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(), + }, + { + name: "IPPacketInfo", + optionProtocol: header.IPv4ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ + NIC: context.NICID, + // TODO(https://gvisor.dev/issue/3556): Expect the NIC's address + // instead of the header destination address for the LocalAddr + // field. + LocalAddr: h.Dst.Addr, + DestinationAddr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPPacketInfoReceived(), + }, + { + name: "IPv6PacketInfo", + optionProtocol: header.IPv6ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetIPv6ReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetIPv6ReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ + NIC: context.NICID, + Addr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPv6PacketInfoReceived(), + }, + } { + t.Run(test.name, func(t *testing.T) { c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol}) defer c.Cleanup() diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go index 3c5aabac7..3599066bc 100644 --- a/pkg/tcpip/transport/udp/udp_test.go +++ b/pkg/tcpip/transport/udp/udp_test.go @@ -844,112 +844,6 @@ func TestReadIncrementsPacketsReceived(t *testing.T) { } } -func TestReadIPPacketInfo(t *testing.T) { - tests := []struct { - name string - proto tcpip.NetworkProtocolNumber - flow context.TestFlow - checker func(tcpip.NICID) checker.ControlMessagesChecker - }{ - { - name: "IPv4 unicast", - proto: header.IPv4ProtocolNumber, - flow: context.UnicastV4, - checker: func(id tcpip.NICID) checker.ControlMessagesChecker { - return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ - NIC: id, - LocalAddr: context.StackAddr, - DestinationAddr: context.StackAddr, - }) - }, - }, - { - name: "IPv4 multicast", - proto: header.IPv4ProtocolNumber, - flow: context.MulticastV4, - checker: func(id tcpip.NICID) checker.ControlMessagesChecker { - return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ - NIC: id, - // TODO(gvisor.dev/issue/3556): Check for a unicast address. - LocalAddr: context.MulticastAddr, - DestinationAddr: context.MulticastAddr, - }) - }, - }, - { - name: "IPv4 broadcast", - proto: header.IPv4ProtocolNumber, - flow: context.Broadcast, - checker: func(id tcpip.NICID) checker.ControlMessagesChecker { - return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ - NIC: id, - // TODO(gvisor.dev/issue/3556): Check for a unicast address. - LocalAddr: context.BroadcastAddr, - DestinationAddr: context.BroadcastAddr, - }) - }, - }, - { - name: "IPv6 unicast", - proto: header.IPv6ProtocolNumber, - flow: context.UnicastV6, - checker: func(id tcpip.NICID) checker.ControlMessagesChecker { - return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ - NIC: id, - Addr: context.StackV6Addr, - }) - }, - }, - { - name: "IPv6 multicast", - proto: header.IPv6ProtocolNumber, - flow: context.MulticastV6, - checker: func(id tcpip.NICID) checker.ControlMessagesChecker { - return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ - NIC: id, - Addr: context.MulticastV6Addr, - }) - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) - defer c.Cleanup() - - c.CreateEndpoint(test.proto, udp.ProtocolNumber) - - bindAddr := tcpip.FullAddress{Port: context.StackPort} - if err := c.EP.Bind(bindAddr); err != nil { - t.Fatalf("Bind(%+v): %s", bindAddr, err) - } - - if test.flow.IsMulticast() { - ifoptSet := tcpip.AddMembershipOption{NIC: context.NICID, MulticastAddr: test.flow.GetMulticastAddr()} - if err := c.EP.SetSockOpt(&ifoptSet); err != nil { - c.T.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err) - } - } - - switch f := test.flow.NetProto(); f { - case header.IPv4ProtocolNumber: - c.EP.SocketOptions().SetReceivePacketInfo(true) - case header.IPv6ProtocolNumber: - c.EP.SocketOptions().SetIPv6ReceivePacketInfo(true) - default: - t.Fatalf("unhandled protocol number = %d", f) - } - - testRead(c, test.flow, test.checker(context.NICID)) - - if got := c.Stack.Stats().UDP.PacketsReceived.Value(); got != 1 { - t.Fatalf("Read did not increment PacketsReceived: got = %d, want = 1", got) - } - }) - } -} - func TestReadRecvOriginalDstAddr(t *testing.T) { tests := []struct { name string @@ -1246,34 +1140,66 @@ func TestSetTClass(t *testing.T) { } 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: "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) { + 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) { + 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(), + }, + { + name: "PacketInfo", + optionProtocol: header.IPv4ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ + NIC: context.NICID, + // TODO(https://gvisor.dev/issue/3556): Expect the NIC's address + // instead of the header destination address for the LocalAddr + // field. + LocalAddr: h.Dst.Addr, + DestinationAddr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPPacketInfoReceived(), + }, + { + name: "IPv6PacketInfo", + optionProtocol: header.IPv6ProtocolNumber, + getReceiveOption: func(ep tcpip.Endpoint) bool { return ep.SocketOptions().GetIPv6ReceivePacketInfo() }, + setReceiveOption: func(ep tcpip.Endpoint, value bool) { ep.SocketOptions().SetIPv6ReceivePacketInfo(value) }, + presenceChecker: func() checker.ControlMessagesChecker { + h := flow.MakeHeader4Tuple(context.Incoming) + return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ + NIC: context.NICID, + Addr: h.Dst.Addr, + }) + }(), + absenceChecker: checker.NoIPv6PacketInfoReceived(), + }, + } { + t.Run(test.name, func(t *testing.T) { c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol}) defer c.Cleanup() diff --git a/runsc/boot/filter/config.go b/runsc/boot/filter/config.go index fa1095887..bc34dd3eb 100644 --- a/runsc/boot/filter/config.go +++ b/runsc/boot/filter/config.go @@ -389,6 +389,11 @@ func hostInetFilters() seccomp.SyscallRules { seccomp.EqualTo(unix.SOL_IPV6), seccomp.EqualTo(unix.IPV6_RECVTCLASS), }, + { + seccomp.MatchAny{}, + seccomp.EqualTo(unix.SOL_IPV6), + seccomp.EqualTo(unix.IPV6_RECVPKTINFO), + }, { seccomp.MatchAny{}, seccomp.EqualTo(unix.SOL_IPV6), @@ -548,6 +553,13 @@ func hostInetFilters() seccomp.SyscallRules { seccomp.MatchAny{}, seccomp.EqualTo(4), }, + { + seccomp.MatchAny{}, + seccomp.EqualTo(unix.SOL_IPV6), + seccomp.EqualTo(unix.IPV6_RECVPKTINFO), + seccomp.MatchAny{}, + seccomp.EqualTo(4), + }, { seccomp.MatchAny{}, seccomp.EqualTo(unix.SOL_IP), diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index dc9635cae..ca3cd7343 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -24,6 +24,7 @@ namespace gvisor { namespace testing { +using ::testing::IsNull; using ::testing::NotNull; uint32_t IPFromInetSockaddr(const struct sockaddr* addr) { @@ -270,10 +271,11 @@ void RecvCmsg(int sock, int cmsg_level, int cmsg_type, char buf[], ASSERT_EQ(msg.msg_controllen, CMSG_SPACE(sizeof(*out_cmsg_value))); struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); + ASSERT_THAT(cmsg, NotNull()); 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); + ASSERT_THAT(CMSG_NXTHDR(&msg, cmsg), IsNull()); std::copy_n(CMSG_DATA(cmsg), sizeof(*out_cmsg_value), reinterpret_cast(out_cmsg_value)); @@ -325,5 +327,15 @@ void SendTClass(int sock, char buf[], size_t buf_size, int tclass) { SendCmsg(sock, SOL_IPV6, IPV6_TCLASS, buf, buf_size, tclass); } +void RecvPktInfo(int sock, char buf[], size_t* buf_size, + in_pktinfo* out_pktinfo) { + RecvCmsg(sock, SOL_IP, IP_PKTINFO, buf, buf_size, out_pktinfo); +} + +void RecvIPv6PktInfo(int sock, char buf[], size_t* buf_size, + in6_pktinfo* out_pktinfo) { + RecvCmsg(sock, SOL_IPV6, IPV6_PKTINFO, buf, buf_size, out_pktinfo); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h index d8a727d43..a2e81e4d6 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -144,6 +144,20 @@ void RecvTClass(int sock, char buf[], size_t* buf_size, int* out_tclass); // message. void SendTClass(int sock, char buf[], size_t buf_size, int tclass); +// RecvPktInfo attempts to read buf_size bytes into buf, and then update +// buf_size with the numbers of bytes actually read. It expects the +// IP_PKTINFO cmsg to be received. The buffer must already be allocated with +// at least buf_size size. +void RecvPktInfo(int sock, char buf[], size_t* buf_size, + in_pktinfo* out_pktinfo); + +// RecvIPv6PktInfo attempts to read buf_size bytes into buf, and then update +// buf_size with the numbers of bytes actually read. It expects the +// IPV6_PKTINFO cmsg to be received. The buffer must already be allocated with +// at least buf_size size. +void RecvIPv6PktInfo(int sock, char buf[], size_t* buf_size, + in6_pktinfo* out_pktinfo); + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc index 285b44dc4..49604dc38 100644 --- a/test/syscalls/linux/ping_socket.cc +++ b/test/syscalls/linux/ping_socket.cc @@ -112,10 +112,9 @@ TEST(PingSocket, ReceiveTOS) { SyscallSucceedsWithValue(sizeof(kSendIcmp))); // Register to receive TOS. - constexpr int kOne = 1; - ASSERT_THAT( - setsockopt(ping.get(), IPPROTO_IP, IP_RECVTOS, &kOne, sizeof(kOne)), - SyscallSucceeds()); + ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IP, IP_RECVTOS, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); struct { icmphdr icmp; @@ -167,9 +166,8 @@ TEST(PingSocket, ReceiveTClass) { SyscallSucceedsWithValue(sizeof(kSendIcmp))); // Register to receive TCLASS. - constexpr int kOne = 1; - ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IPV6, IPV6_RECVTCLASS, &kOne, - sizeof(kOne)), + ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IPV6, IPV6_RECVTCLASS, &kSockOptOn, + sizeof(kSockOptOn)), SyscallSucceeds()); struct { @@ -191,6 +189,111 @@ TEST(PingSocket, ReceiveTClass) { EXPECT_EQ(received_tclass, kArbitraryTClass); } +TEST(PingSocket, ReceiveIPPacketInfo) { + PosixErrorOr 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(&kAddr), + sizeof(kAddr)), + SyscallSucceeds()); + + constexpr icmphdr kSendIcmp = { + .type = ICMP_ECHO, + }; + ASSERT_THAT(sendto(ping.get(), &kSendIcmp, sizeof(kSendIcmp), 0, + reinterpret_cast(&kAddr), sizeof(kAddr)), + SyscallSucceedsWithValue(sizeof(kSendIcmp))); + + // Register to receive PKTINFO. + ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IP, IP_PKTINFO, &kSockOptOn, + sizeof(kSockOptOn)), + 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); + in_pktinfo received_pktinfo; + ASSERT_NO_FATAL_FAILURE(RecvPktInfo(ping.get(), + reinterpret_cast(&recv_buf), + &recv_buf_len, &received_pktinfo)); + 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_pktinfo.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_EQ(ntohl(received_pktinfo.ipi_spec_dst.s_addr), INADDR_ANY); + EXPECT_EQ(ntohl(received_pktinfo.ipi_addr.s_addr), INADDR_LOOPBACK); +} + +TEST(PingSocket, ReceiveIPv6PktInfo) { + PosixErrorOr 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(&kAddr), + sizeof(kAddr)), + SyscallSucceeds()); + + constexpr icmp6_hdr kSendIcmp = { + .icmp6_type = ICMP6_ECHO_REQUEST, + }; + ASSERT_THAT(sendto(ping.get(), &kSendIcmp, sizeof(kSendIcmp), 0, + reinterpret_cast(&kAddr), sizeof(kAddr)), + SyscallSucceedsWithValue(sizeof(kSendIcmp))); + + // Register to receive PKTINFO. + ASSERT_THAT(setsockopt(ping.get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, + &kSockOptOn, sizeof(kSockOptOn)), + 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); + in6_pktinfo received_pktinfo; + ASSERT_NO_FATAL_FAILURE(RecvIPv6PktInfo(ping.get(), + reinterpret_cast(&recv_buf), + &recv_buf_len, &received_pktinfo)); + 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_pktinfo.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + ASSERT_EQ(memcmp(&received_pktinfo.ipi6_addr, &in6addr_loopback, + sizeof(in6addr_loopback)), + 0); +} + struct BindTestCase { TestAddress bind_to; int want = 0; diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index 058681411..fd2ad93a1 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -1091,12 +1091,12 @@ TEST(RawSocketTest, ReceiveIPPacketInfo) { SyscallSucceeds()); // Register to receive IP packet info. - constexpr int one = 1; - ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)), + ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IP, IP_PKTINFO, &kSockOptOn, + sizeof(kSockOptOn)), SyscallSucceeds()); constexpr char send_buf[] = "malformed UDP"; - ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */, + ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), /*flags=*/0, reinterpret_cast(&addr_), sizeof(addr_)), SyscallSucceedsWithValue(sizeof(send_buf))); @@ -1107,20 +1107,14 @@ TEST(RawSocketTest, ReceiveIPPacketInfo) { // Extra space in the receive buffer should be unused. char unused_space; } ABSL_ATTRIBUTE_PACKED recv_buf; - iovec recv_iov = { - .iov_base = &recv_buf, - .iov_len = sizeof(recv_buf), - }; + + size_t recv_buf_len = sizeof(recv_buf); in_pktinfo received_pktinfo; - char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; - msghdr recv_msg = { - .msg_iov = &recv_iov, - .msg_iovlen = 1, - .msg_control = recv_cmsg_buf, - .msg_controllen = CMSG_LEN(sizeof(received_pktinfo)), - }; - ASSERT_THAT(RetryEINTR(recvmsg)(raw.get(), &recv_msg, 0), - SyscallSucceedsWithValue(sizeof(iphdr) + sizeof(send_buf))); + ASSERT_NO_FATAL_FAILURE(RecvPktInfo(raw.get(), + reinterpret_cast(&recv_buf), + &recv_buf_len, &received_pktinfo)); + + EXPECT_EQ(recv_buf_len, sizeof(iphdr) + sizeof(send_buf)); EXPECT_EQ(memcmp(send_buf, &recv_buf.data, sizeof(send_buf)), 0); EXPECT_EQ(recv_buf.ip.version, static_cast(IPVERSION)); // IHL holds the number of header bytes in 4 byte units. @@ -1130,18 +1124,10 @@ TEST(RawSocketTest, ReceiveIPPacketInfo) { EXPECT_EQ(ntohl(recv_buf.ip.saddr), INADDR_LOOPBACK); EXPECT_EQ(ntohl(recv_buf.ip.daddr), INADDR_LOOPBACK); - cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); - ASSERT_THAT(cmsg, NotNull()); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(received_pktinfo))); - EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IP); - EXPECT_EQ(cmsg->cmsg_type, IP_PKTINFO); - memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); EXPECT_EQ(received_pktinfo.ipi_ifindex, ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); EXPECT_EQ(ntohl(received_pktinfo.ipi_spec_dst.s_addr), INADDR_LOOPBACK); EXPECT_EQ(ntohl(received_pktinfo.ipi_addr.s_addr), INADDR_LOOPBACK); - - EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); } TEST(RawSocketTest, ReceiveIPv6PacketInfo) { @@ -1159,46 +1145,28 @@ TEST(RawSocketTest, ReceiveIPv6PacketInfo) { SyscallSucceeds()); // Register to receive IPv6 packet info. - constexpr int one = 1; - ASSERT_THAT( - setsockopt(raw.get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)), - SyscallSucceeds()); + ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); constexpr char send_buf[] = "malformed UDP"; - ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */, + ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), /*flags=*/0, reinterpret_cast(&addr_), sizeof(addr_)), SyscallSucceedsWithValue(sizeof(send_buf))); char recv_buf[sizeof(send_buf) + 1]; - iovec recv_iov = { - .iov_base = recv_buf, - .iov_len = sizeof(recv_buf), - }; + size_t recv_buf_len = sizeof(recv_buf); in6_pktinfo received_pktinfo; - char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; - msghdr recv_msg = { - .msg_iov = &recv_iov, - .msg_iovlen = 1, - .msg_control = recv_cmsg_buf, - .msg_controllen = CMSG_LEN(sizeof(received_pktinfo)), - }; - ASSERT_THAT(RetryEINTR(recvmsg)(raw.get(), &recv_msg, 0), - SyscallSucceedsWithValue(sizeof(send_buf))); + ASSERT_NO_FATAL_FAILURE(RecvIPv6PktInfo(raw.get(), + reinterpret_cast(&recv_buf), + &recv_buf_len, &received_pktinfo)); + EXPECT_EQ(recv_buf_len, sizeof(send_buf)); EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0); - - cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); - ASSERT_THAT(cmsg, NotNull()); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(received_pktinfo))); - EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IPV6); - EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO); - memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); EXPECT_EQ(received_pktinfo.ipi6_ifindex, ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); ASSERT_EQ(memcmp(&received_pktinfo.ipi6_addr, &in6addr_loopback, sizeof(in6addr_loopback)), 0); - - EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); } TEST(RawSocketTest, ReceiveTOS) { diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 99a1b06bf..7c7f7753a 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -2142,6 +2142,126 @@ TEST_P(UdpSocketControlMessagesTest, SendAndReceiveTOSorTClass) { EXPECT_EQ(recv_data_len, sizeof(sent_data)); } +TEST_P(UdpSocketControlMessagesTest, SetAndReceivePktInfo) { + // Enable receiving IP_PKTINFO and maybe IPV6_PKTINFO on the receiver. + ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_PKTINFO, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + if (ServerAddressFamily() == AF_INET6) { + ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVPKTINFO, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + } + + constexpr size_t kArbitrarySendSize = 1042; + constexpr char sent_data[kArbitrarySendSize] = {}; + ASSERT_THAT(RetryEINTR(send)(client_.get(), sent_data, sizeof(sent_data), 0), + SyscallSucceedsWithValue(sizeof(sent_data))); + + char recv_data[sizeof(sent_data) + 1]; + size_t recv_data_len = sizeof(recv_data); + switch (GetParam()) { + case AddressFamily::kIpv4: { + in_pktinfo received_pktinfo; + ASSERT_NO_FATAL_FAILURE(RecvPktInfo(server_.get(), recv_data, + &recv_data_len, &received_pktinfo)); + EXPECT_EQ(recv_data_len, sizeof(sent_data)); + EXPECT_EQ(received_pktinfo.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_EQ(ntohl(received_pktinfo.ipi_spec_dst.s_addr), INADDR_LOOPBACK); + EXPECT_EQ(ntohl(received_pktinfo.ipi_addr.s_addr), INADDR_LOOPBACK); + break; + } + + case AddressFamily::kIpv6: { + in6_pktinfo received_pktinfo; + ASSERT_NO_FATAL_FAILURE(RecvIPv6PktInfo( + server_.get(), recv_data, &recv_data_len, &received_pktinfo)); + EXPECT_EQ(recv_data_len, sizeof(sent_data)); + EXPECT_EQ(received_pktinfo.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + ASSERT_EQ(memcmp(&received_pktinfo.ipi6_addr, &in6addr_loopback, + sizeof(in6addr_loopback)), + 0); + break; + } + + case AddressFamily::kDualStack: { + // TODO(https://gvisor.dev/issue/7144): On dual stack sockets, Linux can + // receive both the IPv4 and IPv6 packet info. gVisor should do the same. + iovec iov = { + iov.iov_base = recv_data, + iov.iov_len = recv_data_len, + }; + // Add an extra byte to confirm we only read what we expected. + char control[CMSG_SPACE(sizeof(in_pktinfo)) + + CMSG_SPACE(sizeof(in6_pktinfo)) + 1]; + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control, + .msg_controllen = sizeof(control), + }; + + ASSERT_THAT( + recv_data_len = RetryEINTR(recvmsg)(server_.get(), &msg, /*flags=*/0), + SyscallSucceeds()); + EXPECT_EQ(recv_data_len, sizeof(sent_data)); + size_t expected_controllen = CMSG_SPACE(sizeof(in_pktinfo)); + if (!IsRunningOnGvisor() || IsRunningWithHostinet()) { + expected_controllen += CMSG_SPACE(sizeof(in6_pktinfo)); + } + EXPECT_EQ(msg.msg_controllen, expected_controllen); + + std::pair received_pktinfo; + std::pair received_pktinfo6; + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + while (cmsg != nullptr) { + ASSERT_TRUE(cmsg->cmsg_level == SOL_IP || cmsg->cmsg_level == SOL_IPV6); + if (cmsg->cmsg_level == SOL_IP) { + ASSERT_FALSE(received_pktinfo.second); + ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in_pktinfo))); + ASSERT_EQ(cmsg->cmsg_type, IP_PKTINFO); + received_pktinfo.second = true; + std::copy_n(CMSG_DATA(cmsg), sizeof(received_pktinfo.first), + reinterpret_cast(&received_pktinfo.first)); + } else { // SOL_IPV6 + ASSERT_FALSE(received_pktinfo6.second); + ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in6_pktinfo))); + ASSERT_EQ(cmsg->cmsg_type, IPV6_PKTINFO); + received_pktinfo6.second = true; + std::copy_n(CMSG_DATA(cmsg), sizeof(received_pktinfo6.first), + reinterpret_cast(&received_pktinfo6.first)); + } + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + + ASSERT_TRUE(received_pktinfo.second); + EXPECT_EQ(received_pktinfo.first.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_EQ(ntohl(received_pktinfo.first.ipi_spec_dst.s_addr), + INADDR_LOOPBACK); + EXPECT_EQ(ntohl(received_pktinfo.first.ipi_addr.s_addr), INADDR_LOOPBACK); + + if (!IsRunningOnGvisor() || IsRunningWithHostinet()) { + ASSERT_TRUE(received_pktinfo6.second); + EXPECT_EQ(received_pktinfo6.first.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + struct in6_addr expected; + inet_pton(AF_INET6, "::ffff:127.0.0.1", &expected); + EXPECT_EQ(memcmp(&received_pktinfo6.first.ipi6_addr, &expected, + sizeof(expected)), + 0); + } else { + ASSERT_FALSE(received_pktinfo6.second); + } + + break; + } + } +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketControlMessagesTest, ::testing::Values(AddressFamily::kIpv4, AddressFamily::kIpv6,