Support SOL_ICMPV6 -> ICMPV6_FILTER

PiperOrigin-RevId: 417696519
This commit is contained in:
Ghanan Gowripalan
2021-12-21 15:04:13 -08:00
committed by gVisor bot
parent e57939e24a
commit beaecd1e3c
6 changed files with 318 additions and 9 deletions
+5
View File
@@ -159,3 +159,8 @@ const (
IPV6_RECVFRAGSIZE = 77
IPV6_FREEBIND = 78
)
// Socket options from uapi/linux/icmpv6.h
const (
ICMPV6_FILTER = 1
)
+8
View File
@@ -586,3 +586,11 @@ const SCM_MAX_FD = 253
// socket option for querying whether a socket is in a listening
// state.
const SO_ACCEPTCON = 1 << 16
// ICMP6Filter represents struct icmp6_filter from linux/icmpv6.h.
//
// +marshal
// +stateify savable
type ICMP6Filter struct {
Filter [8]uint32
}
+67 -2
View File
@@ -65,6 +65,8 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)
const bitsPerUint32 = 32
func mustCreateMetric(name, description string) *tcpip.StatCounter {
var cm tcpip.StatCounter
metric.MustRegisterCustomUint64Metric(name, true /* cumulative */, false /* sync */, description, cm.Value)
@@ -858,8 +860,10 @@ func GetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, family in
case linux.SOL_IP:
return getSockOptIP(t, s, ep, name, outPtr, outLen, family)
case linux.SOL_ICMPV6:
return getSockOptICMPv6(t, s, ep, name, outLen)
case linux.SOL_UDP,
linux.SOL_ICMPV6,
linux.SOL_RAW,
linux.SOL_PACKET:
@@ -1293,6 +1297,39 @@ func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name,
return nil, syserr.ErrProtocolNotAvailable
}
func getSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outLen int) (marshal.Marshallable, *syserr.Error) {
if _, ok := ep.(tcpip.Endpoint); !ok {
log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name)
return nil, syserr.ErrUnknownProtocolOption
}
if family, _, _ := s.Type(); family != linux.AF_INET6 {
return nil, syserr.ErrNotSupported
}
switch name {
case linux.ICMPV6_FILTER:
var v tcpip.ICMPv6Filter
if err := ep.GetSockOpt(&v); err != nil {
return nil, syserr.TranslateNetstackError(err)
}
filter := linux.ICMP6Filter{Filter: v.DenyType}
// Linux truncates the output to outLen.
buf := t.CopyScratchBuffer(filter.SizeBytes())
filter.MarshalUnsafe(buf)
if len(buf) > outLen {
buf = buf[:outLen]
}
bufP := primitive.ByteSlice(buf)
return &bufP, nil
default:
t.Kernel().EmitUnimplementedEvent(t)
}
return nil, syserr.ErrProtocolNotAvailable
}
// getSockOptIPv6 implements GetSockOpt when level is SOL_IPV6.
func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) {
if _, ok := ep.(tcpip.Endpoint); !ok {
@@ -1686,6 +1723,9 @@ func SetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, level int
case linux.SOL_TCP:
return setSockOptTCP(t, s, ep, name, optVal)
case linux.SOL_ICMPV6:
return setSockOptICMPv6(t, s, ep, name, optVal)
case linux.SOL_IPV6:
return setSockOptIPv6(t, s, ep, name, optVal)
@@ -1700,7 +1740,6 @@ func SetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, level int
return syserr.ErrProtocolNotAvailable
case linux.SOL_UDP,
linux.SOL_ICMPV6,
linux.SOL_RAW:
t.Kernel().EmitUnimplementedEvent(t)
@@ -2051,6 +2090,32 @@ func setSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name i
return nil
}
func setSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error {
if _, ok := ep.(tcpip.Endpoint); !ok {
log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name)
return syserr.ErrUnknownProtocolOption
}
if family, _, _ := s.Type(); family != linux.AF_INET6 {
return syserr.ErrUnknownProtocolOption
}
switch name {
case linux.ICMPV6_FILTER:
var req linux.ICMP6Filter
if len(optVal) < req.SizeBytes() {
return syserr.ErrInvalidArgument
}
req.UnmarshalUnsafe(optVal)
return syserr.TranslateNetstackError(ep.SetSockOpt(&tcpip.ICMPv6Filter{DenyType: req.Filter}))
default:
t.Kernel().EmitUnimplementedEvent(t)
}
return nil
}
// setSockOptIPv6 implements SetSockOpt when level is SOL_IPV6.
func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error {
if _, ok := ep.(tcpip.Endpoint); !ok {
+23
View File
@@ -897,6 +897,29 @@ type SettableSocketOption interface {
isSettableSocketOption()
}
// ICMPv6Filter specifes a filter for ICMPv6 types.
//
// +stateify savable
type ICMPv6Filter struct {
// DenyType indicates if an ICMP type should be blocked.
//
// The ICMPv6 type field is 8 bits so there are up to 256 different ICMPv6
// types.
DenyType [8]uint32
}
// ShouldDeny returns true iff the ICMPv6 Type should be denied.
func (f *ICMPv6Filter) ShouldDeny(icmpType uint8) bool {
const bitsInUint32 = 32
i := icmpType / bitsInUint32
b := icmpType % bitsInUint32
return f.DenyType[i]&(1<<b) != 0
}
func (*ICMPv6Filter) isGettableSocketOption() {}
func (*ICMPv6Filter) isSettableSocketOption() {}
// EndpointState represents the state of an endpoint.
type EndpointState uint8
+54 -6
View File
@@ -85,6 +85,10 @@ type endpoint struct {
rcvDisabled bool
mu sync.RWMutex `state:"nosave"`
// icmp6Filter holds the filter for ICMPv6 packets.
//
// +checklocks:mu
icmpv6Filter tcpip.ICMPv6Filter
}
// NewEndpoint returns a raw endpoint for the given protocols.
@@ -388,10 +392,23 @@ func (e *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
// SetSockOpt implements tcpip.Endpoint.SetSockOpt.
func (e *endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
switch opt.(type) {
switch opt := opt.(type) {
case *tcpip.SocketDetachFilterOption:
return nil
case *tcpip.ICMPv6Filter:
if e.net.NetProto() != header.IPv6ProtocolNumber {
return &tcpip.ErrUnknownProtocolOption{}
}
if e.transProto != header.ICMPv6ProtocolNumber {
return &tcpip.ErrInvalidOptionValue{}
}
e.mu.Lock()
defer e.mu.Unlock()
e.icmpv6Filter = *opt
return nil
default:
return e.net.SetSockOpt(opt)
}
@@ -403,7 +420,24 @@ func (e *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
func (e *endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error {
return e.net.GetSockOpt(opt)
switch opt := opt.(type) {
case *tcpip.ICMPv6Filter:
if e.net.NetProto() != header.IPv6ProtocolNumber {
return &tcpip.ErrUnknownProtocolOption{}
}
if e.transProto != header.ICMPv6ProtocolNumber {
return &tcpip.ErrInvalidOptionValue{}
}
e.mu.RLock()
defer e.mu.RUnlock()
*opt = e.icmpv6Filter
return nil
default:
return e.net.GetSockOpt(opt)
}
}
// GetSockOptInt implements tcpip.Endpoint.GetSockOptInt.
@@ -509,15 +543,29 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
//
// TODO(https://gvisor.dev/issue/6517): Avoid the copy once S/R supports
// overlapping slices.
transportHeader := pkt.TransportHeader().View()
var combinedVV buffer.VectorisedView
if info.NetProto == header.IPv4ProtocolNumber {
networkHeader, transportHeader := pkt.NetworkHeader().View(), pkt.TransportHeader().View()
switch info.NetProto {
case header.IPv4ProtocolNumber:
networkHeader := pkt.NetworkHeader().View()
headers := make(buffer.View, 0, len(networkHeader)+len(transportHeader))
headers = append(headers, networkHeader...)
headers = append(headers, transportHeader...)
combinedVV = headers.ToVectorisedView()
} else {
combinedVV = append(buffer.View(nil), pkt.TransportHeader().View()...).ToVectorisedView()
case header.IPv6ProtocolNumber:
if e.transProto == header.ICMPv6ProtocolNumber {
if len(transportHeader) < header.ICMPv6MinimumSize {
return false
}
if e.icmpv6Filter.ShouldDeny(uint8(header.ICMPv6(transportHeader).Type())) {
return false
}
}
combinedVV = append(buffer.View(nil), transportHeader...).ToVectorisedView()
default:
panic(fmt.Sprintf("unrecognized protocol number = %d", info.NetProto))
}
combinedVV.Append(pkt.Data().ExtractVV())
packet.data = combinedVV
+161 -1
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
@@ -34,6 +35,15 @@ namespace testing {
namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::FieldsAre;
using ::testing::Not;
using ::testing::Test;
using ::testing::Values;
using ::testing::WithParamInterface;
// The size of an empty ICMP packet and IP header together.
constexpr size_t kEmptyICMPSize = 28;
@@ -41,7 +51,7 @@ constexpr size_t kEmptyICMPSize = 28;
// responds to ICMP echo requests, and thus a single echo request sent via
// loopback leads to 2 received ICMP packets.
class RawSocketICMPTest : public ::testing::Test {
class RawSocketICMPTest : public Test {
protected:
// Creates a socket to be used in tests.
void SetUp() override;
@@ -109,6 +119,16 @@ TEST_F(RawSocketICMPTest, SockOptIPv6Checksum) {
EXPECT_EQ(len, sizeof(v));
}
TEST_F(RawSocketICMPTest, ICMPv6FilterNotSupported) {
icmp6_filter v;
EXPECT_THAT(setsockopt(s_, SOL_ICMPV6, ICMP6_FILTER, &v, sizeof(v)),
SyscallFailsWithErrno(ENOPROTOOPT));
socklen_t len = sizeof(v);
EXPECT_THAT(getsockopt(s_, SOL_ICMPV6, ICMP6_FILTER, &v, &len),
SyscallFailsWithErrno(EOPNOTSUPP));
EXPECT_EQ(len, sizeof(v));
}
// We'll only read an echo in this case, as the kernel won't respond to the
// malformed ICMP checksum.
TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) {
@@ -552,6 +572,146 @@ void RawSocketICMPTest::ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len,
SyscallSucceedsWithValue(expected_size + sizeof(struct iphdr)));
}
class RawSocketICMPv6Test : public Test {
public:
void SetUp() override {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
fd_ = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6));
}
void TearDown() override {
if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) {
return;
}
EXPECT_THAT(close(fd_.release()), SyscallSucceeds());
}
protected:
const FileDescriptor& fd() { return fd_; }
private:
FileDescriptor fd_;
};
TEST_F(RawSocketICMPv6Test, InitialFilterPassesAll) {
icmp6_filter got_filter;
socklen_t got_filter_len = sizeof(got_filter);
ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter,
&got_filter_len),
SyscallSucceeds());
ASSERT_EQ(got_filter_len, sizeof(got_filter));
icmp6_filter expected_filter;
ICMP6_FILTER_SETPASSALL(&expected_filter);
EXPECT_THAT(got_filter,
FieldsAre(ElementsAreArray(expected_filter.icmp6_filt)));
}
TEST_F(RawSocketICMPv6Test, GetPartialFilterSucceeds) {
icmp6_filter set_filter;
ICMP6_FILTER_SETBLOCKALL(&set_filter);
ASSERT_THAT(setsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter,
sizeof(set_filter)),
SyscallSucceeds());
icmp6_filter got_filter = {};
// We use a length smaller than a full filter length and expect that
// only the bytes up to the provided length are modified. The last element
// should be unmodified when getsockopt returns.
constexpr socklen_t kShortFilterLen =
sizeof(got_filter) - sizeof(got_filter.icmp6_filt[0]);
socklen_t got_filter_len = kShortFilterLen;
ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter,
&got_filter_len),
SyscallSucceeds());
ASSERT_EQ(got_filter_len, kShortFilterLen);
icmp6_filter expected_filter = set_filter;
expected_filter.icmp6_filt[std::size(expected_filter.icmp6_filt) - 1] = 0;
EXPECT_THAT(got_filter,
FieldsAre(ElementsAreArray(expected_filter.icmp6_filt)));
}
class RawSocketICMPv6TypeTest : public RawSocketICMPv6Test,
public WithParamInterface<uint8_t> {};
TEST_P(RawSocketICMPv6TypeTest, FilterDeliveredPackets) {
const sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_LOOPBACK_INIT,
};
const uint8_t allowed_type = GetParam();
// Pass only the allowed type.
{
icmp6_filter set_filter;
ICMP6_FILTER_SETBLOCKALL(&set_filter);
ICMP6_FILTER_SETPASS(allowed_type, &set_filter);
ASSERT_THAT(setsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter,
sizeof(set_filter)),
SyscallSucceeds());
icmp6_filter got_filter;
socklen_t got_filter_len = sizeof(got_filter);
ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter,
&got_filter_len),
SyscallSucceeds());
ASSERT_EQ(got_filter_len, sizeof(got_filter));
EXPECT_THAT(got_filter, FieldsAre(ElementsAreArray(set_filter.icmp6_filt)));
}
// Send an ICMP packet for each type.
uint8_t icmp_type = 0;
constexpr uint8_t kUnusedICMPCode = 0;
do {
const icmp6_hdr packet = {
.icmp6_type = icmp_type,
.icmp6_code = kUnusedICMPCode,
// The stack will calculate the checksum.
.icmp6_cksum = 0,
};
ASSERT_THAT(RetryEINTR(sendto)(fd().get(), &packet, sizeof(packet), 0,
reinterpret_cast<const sockaddr*>(&addr),
sizeof(addr)),
SyscallSucceedsWithValue(sizeof(packet)));
} while (icmp_type++ != std::numeric_limits<uint8_t>::max());
// Make sure only the allowed type was received.
{
icmp6_hdr got_packet;
sockaddr_in6 sender;
socklen_t sender_len = sizeof(sender);
ASSERT_THAT(RetryEINTR(recvfrom)(
fd().get(), &got_packet, sizeof(got_packet), 0 /* flags */,
reinterpret_cast<sockaddr*>(&sender), &sender_len),
SyscallSucceedsWithValue(sizeof(got_packet)));
ASSERT_EQ(sender_len, sizeof(sender));
EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0);
// The stack should have populated the checksum.
if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
// TODO(https://github.com/google/gvisor/pull/6957): Use same check as
// Linux.
EXPECT_THAT(got_packet,
FieldsAre(allowed_type, kUnusedICMPCode, 0 /* icmp6_cksum */,
_ /* icmp6_dataun */
));
} else {
EXPECT_THAT(got_packet,
FieldsAre(allowed_type, kUnusedICMPCode,
Not(0) /* icmp6_cksum */, _ /* icmp6_dataun */
));
}
EXPECT_THAT(got_packet.icmp6_data32, ElementsAre(0));
}
}
INSTANTIATE_TEST_SUITE_P(AllRawSocketTests, RawSocketICMPv6TypeTest,
Values(uint8_t{0},
std::numeric_limits<uint8_t>::max()));
} // namespace
} // namespace testing