mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support SOL_ICMPV6 -> ICMPV6_FILTER
PiperOrigin-RevId: 417696519
This commit is contained in:
committed by
gVisor bot
parent
e57939e24a
commit
beaecd1e3c
@@ -159,3 +159,8 @@ const (
|
||||
IPV6_RECVFRAGSIZE = 77
|
||||
IPV6_FREEBIND = 78
|
||||
)
|
||||
|
||||
// Socket options from uapi/linux/icmpv6.h
|
||||
const (
|
||||
ICMPV6_FILTER = 1
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user