Support receiving PKTINFO on icmp endpoints

PiperOrigin-RevId: 428599075
This commit is contained in:
Arthur Sfez
2022-02-14 13:51:49 -08:00
committed by gVisor bot
parent 0f7cbc8ecf
commit 4a94302baf
13 changed files with 532 additions and 253 deletions
+10
View File
@@ -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() {
+8 -2
View File
@@ -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)
+22
View File
@@ -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 {
+25 -1
View File
@@ -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()
+59 -28
View File
@@ -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()
+60 -29
View File
@@ -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()
+60 -134
View File
@@ -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()
+12
View File
@@ -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),
+13 -1
View File
@@ -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<uint8_t*>(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
+14
View File
@@ -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
+110 -7
View File
@@ -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<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 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 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<char*>(&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<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 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 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<char*>(&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;
+19 -51
View File
@@ -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<const sockaddr*>(&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<char*>(&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<unsigned int>(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<const sockaddr*>(&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<char*>(&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) {
+120
View File
@@ -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<in_pktinfo, bool> received_pktinfo;
std::pair<in6_pktinfo, bool> 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<uint8_t*>(&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<uint8_t*>(&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,