hostinet: Support ping and raw sockets.

PiperOrigin-RevId: 513902015
This commit is contained in:
Nicolas Lacasse
2023-03-04 00:40:01 -08:00
committed by gVisor bot
parent da3c2bbb82
commit f37b20c011
16 changed files with 148 additions and 55 deletions
+3 -3
View File
@@ -286,8 +286,8 @@ swgso-tests: load-basic $(RUNTIME_BIN)
.PHONY: swgso-tests
hostnet-tests: load-basic $(RUNTIME_BIN)
@$(call install_runtime,$(RUNTIME),--network=host)
@$(call test_runtime,$(RUNTIME),--test_env=CHECKPOINT=false --test_env=HOSTNET=true $(INTEGRATION_TARGETS))
@$(call install_runtime,$(RUNTIME),--network=host --net-raw)
@$(call test_runtime,$(RUNTIME),--test_env=TEST_CHECKPOINT=false --test_env=TEST_HOSTNET=true --test_env=TEST_NET_RAW=true $(INTEGRATION_TARGETS))
.PHONY: hostnet-tests
kvm-tests: load-basic $(RUNTIME_BIN)
@@ -311,7 +311,7 @@ iptables-tests: load-iptables $(RUNTIME_BIN)
@# FIXME(b/218923513): Need to fix permissions issues.
@#$(call test,--test_env=RUNTIME=runc //test/iptables:iptables_test)
@$(call install_runtime,$(RUNTIME),--net-raw)
@$(call test_runtime,$(RUNTIME),//test/iptables:iptables_test)
@$(call test_runtime,$(RUNTIME),--test_env=TEST_NET_RAW=true //test/iptables:iptables_test)
.PHONY: iptables-tests
packetdrill-tests: load-packetdrill $(RUNTIME_BIN)
+3
View File
@@ -603,3 +603,6 @@ const SO_ACCEPTCON = 1 << 16
type ICMP6Filter struct {
Filter [8]uint32
}
// SizeOfICMP6Filter is the size of ICMP6Filter struct.
var SizeOfICMP6Filter = uint32((*ICMP6Filter)(nil).SizeBytes())
+1
View File
@@ -34,6 +34,7 @@ go_library(
"//pkg/sentry/hostfd",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/time",
"//pkg/sentry/socket",
"//pkg/sentry/socket/control",
+30 -5
View File
@@ -29,6 +29,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs"
"gvisor.dev/gvisor/pkg/sentry/hostfd"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/socket"
"gvisor.dev/gvisor/pkg/sentry/socket/control"
@@ -66,6 +67,21 @@ var AllowedSocketTypes = []AllowedSocketType{
{unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP},
{unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP},
{unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6},
}
// AllowedRawSocketTypes are the socket types which are supported by hostinet
// with raw sockets enabled.
var AllowedRawSocketTypes = []AllowedSocketType{
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_TCP},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_UDP},
{unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_ICMP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_RAW},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_UDP},
{unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_ICMPV6},
}
// Socket implements socket.Socket (and by extension, vfs.FileDescriptionImpl)
@@ -185,16 +201,24 @@ type socketProvider struct {
// Socket implements socket.Provider.Socket.
func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) {
// Check that we are using the host network stack.
stack := t.NetworkContext()
if stack == nil {
netCtx := t.NetworkContext()
if netCtx == nil {
return nil, nil
}
if _, ok := stack.(*Stack); !ok {
stack, ok := netCtx.(*Stack)
if !ok {
return nil, nil
}
stype := stypeflags & linux.SOCK_TYPE_MASK
// Raw sockets require CAP_NET_RAW.
if stype == linux.SOCK_RAW {
if creds := auth.CredentialsFromContext(t); !creds.HasCapability(linux.CAP_NET_RAW) {
return nil, syserr.ErrNotPermitted
}
}
// Convert generic IPPROTO_IP protocol to the actual protocol depending
// on family and type.
if protocol == linux.IPPROTO_IP && (p.family == linux.AF_INET || p.family == linux.AF_INET6) {
@@ -208,7 +232,7 @@ func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, proto
// Validate the socket based on family, type, and protocol.
var supported bool
for _, allowed := range AllowedSocketTypes {
for _, allowed := range stack.allowedSocketTypes {
if p.family == allowed.Family && int(stype) == allowed.Type && protocol == allowed.Protocol {
supported = true
break
@@ -222,7 +246,8 @@ func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, proto
// Conservatively ignore all flags specified by the application and add
// SOCK_NONBLOCK since socketOperations requires it.
fd, err := unix.Socket(p.family, int(stype)|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, protocol)
st := int(stype) | unix.SOCK_NONBLOCK | unix.SOCK_CLOEXEC
fd, err := unix.Socket(p.family, st, protocol)
if err != nil {
return nil, syserr.FromError(err)
}
+5
View File
@@ -59,6 +59,7 @@ type SockOpt struct {
var SockOpts = []SockOpt{
{linux.SOL_IP, linux.IP_ADD_MEMBERSHIP, 0, false, true},
{linux.SOL_IP, linux.IP_DROP_MEMBERSHIP, 0, false, true},
{linux.SOL_IP, linux.IP_HDRINCL, sizeofInt32, true, true},
{linux.SOL_IP, linux.IP_MULTICAST_IF, uint64(linux.SizeOfInetAddr), true, true},
{linux.SOL_IP, linux.IP_MULTICAST_LOOP, 0 /* can be 32-bit int or 8-bit uint */, true, true},
{linux.SOL_IP, linux.IP_MULTICAST_TTL, 0 /* can be 32-bit int or 8-bit uint */, true, true},
@@ -70,6 +71,7 @@ var SockOpts = []SockOpt{
{linux.SOL_IP, linux.IP_TOS, 0 /* Can be 32, 16, or 8 bits */, true, true},
{linux.SOL_IP, linux.IP_TTL, sizeofInt32, true, true},
{linux.SOL_IPV6, linux.IPV6_CHECKSUM, sizeofInt32, true, true},
{linux.SOL_IPV6, linux.IPV6_MULTICAST_HOPS, sizeofInt32, true, true},
{linux.SOL_IPV6, linux.IPV6_RECVERR, sizeofInt32, true, true},
{linux.SOL_IPV6, linux.IPV6_RECVHOPLIMIT, sizeofInt32, true, true},
@@ -81,6 +83,7 @@ var SockOpts = []SockOpt{
{linux.SOL_IPV6, linux.IPV6_V6ONLY, sizeofInt32, true, true},
{linux.SOL_SOCKET, linux.SO_ACCEPTCONN, sizeofInt32, true, true},
{linux.SOL_SOCKET, linux.SO_BINDTODEVICE, 0, true, true},
{linux.SOL_SOCKET, linux.SO_BROADCAST, sizeofInt32, true, true},
{linux.SOL_SOCKET, linux.SO_ERROR, sizeofInt32, true, false},
{linux.SOL_SOCKET, linux.SO_KEEPALIVE, sizeofInt32, true, true},
@@ -111,6 +114,8 @@ var SockOpts = []SockOpt{
{linux.SOL_TCP, linux.TCP_SYNCNT, sizeofInt32, true, true},
{linux.SOL_TCP, linux.TCP_USER_TIMEOUT, sizeofInt32, true, true},
{linux.SOL_TCP, linux.TCP_WINDOW_CLAMP, sizeofInt32, true, true},
{linux.SOL_ICMPV6, linux.ICMPV6_FILTER, uint64(linux.SizeOfICMP6Filter), true, true},
}
// sockOptMap is a map of {level, name} -> SockOpts. It is an optimization for
+8 -1
View File
@@ -64,6 +64,8 @@ type Stack struct {
tcpSACKEnabled bool
netDevFile *os.File
netSNMPFile *os.File
// allowedSocketTypes is the list of allowed socket types
allowedSocketTypes []AllowedSocketType
}
// Destroy implements inet.Stack.Destroy.
@@ -79,7 +81,7 @@ func NewStack() *Stack {
}
// Configure sets up the stack using the current state of the host network.
func (s *Stack) Configure() error {
func (s *Stack) Configure(allowRawSockets bool) error {
if err := addHostInterfaces(s); err != nil {
return err
}
@@ -127,6 +129,11 @@ func (s *Stack) Configure() error {
s.netSNMPFile = f
}
s.allowedSocketTypes = AllowedSocketTypes
if allowRawSockets {
s.allowedSocketTypes = append(s.allowedSocketTypes, AllowedRawSocketTypes...)
}
return nil
}
+1
View File
@@ -101,6 +101,7 @@ var SocketFlagSet = abi.FlagSet{
var ipProtocol = abi.ValueSet{
linux.IPPROTO_IP: "IPPROTO_IP",
linux.IPPROTO_ICMP: "IPPROTO_ICMP",
linux.IPPROTO_ICMPV6: "IPPROTO_ICMPV6",
linux.IPPROTO_IGMP: "IPPROTO_IGMP",
linux.IPPROTO_IPIP: "IPPROTO_IPIP",
linux.IPPROTO_TCP: "IPPROTO_TCP",
+15 -7
View File
@@ -49,13 +49,16 @@ import (
)
var (
checkpoint = flag.Bool("checkpoint", BoolFromEnv("CHECKPOINT", true), "control checkpoint/restore support")
partition = flag.Int("partition", IntFromEnv("PARTITION", 1), "partition number, this is 1-indexed")
totalPartitions = flag.Int("total_partitions", IntFromEnv("TOTAL_PARTITIONS", 1), "total number of partitions")
isRunningWithHostNet = flag.Bool("hostnet", BoolFromEnv("HOSTNET", false), "whether test is running with hostnet")
runscPath = flag.String("runsc", os.Getenv("RUNTIME"), "path to runsc binary")
// Note: flag overlay is already taken by runsc.
partition = flag.Int("partition", IntFromEnv("PARTITION", 1), "partition number, this is 1-indexed")
totalPartitions = flag.Int("total_partitions", IntFromEnv("TOTAL_PARTITIONS", 1), "total number of partitions")
runscPath = flag.String("runsc", os.Getenv("RUNTIME"), "path to runsc binary")
// Flags controlling features for sandbox under test, prefixed with
// "test-" to avoid potential conflicts with runsc flags.
checkpointSupported = flag.Bool("test-checkpoint", BoolFromEnv("TEST_CHECKPOINT", true), "control checkpoint/restore support")
isRunningWithOverlay = flag.Bool("test-overlay", BoolFromEnv("TEST_OVERLAY", false), "whether test is running with --overlay2")
isRunningWithNetRaw = flag.Bool("test-net-raw", BoolFromEnv("TEST_NET_RAW", false), "whether test is running with raw socket support")
isRunningWithHostNet = flag.Bool("test-hostnet", BoolFromEnv("TEST_HOSTNET", false), "whether test is running with hostnet")
// TestEnvSupportsRawSockets indicates whether a test sandbox can
// create raw sockets.
@@ -117,7 +120,7 @@ func DurationFromEnv(name string, def time.Duration) time.Duration {
// IsCheckpointSupported returns the relevant command line flag.
func IsCheckpointSupported() bool {
return *checkpoint
return *checkpointSupported
}
// IsRunningWithHostNet returns the relevant command line flag.
@@ -125,6 +128,11 @@ func IsRunningWithHostNet() bool {
return *isRunningWithHostNet
}
// IsRunningWithNetRaw returns the relevant command line flag.
func IsRunningWithNetRaw() bool {
return *isRunningWithNetRaw
}
// IsRunningWithOverlay returns the relevant command line flag.
func IsRunningWithOverlay() bool {
return *isRunningWithOverlay
+6 -2
View File
@@ -22,7 +22,7 @@ import (
)
// hostInetFilters contains syscalls that are needed by sentry/socket/hostinet.
func hostInetFilters() seccomp.SyscallRules {
func hostInetFilters(allowRawSockets bool) seccomp.SyscallRules {
rules := seccomp.SyscallRules{
unix.SYS_ACCEPT4: []seccomp.Rule{
{
@@ -80,7 +80,11 @@ func hostInetFilters() seccomp.SyscallRules {
// Generate rules for socket creation based on hostinet's supported
// socket types.
socketRules := []seccomp.Rule{}
for _, sock := range hostinet.AllowedSocketTypes {
stypes := hostinet.AllowedSocketTypes
if allowRawSockets {
stypes = append(stypes, hostinet.AllowedRawSocketTypes...)
}
for _, sock := range stypes {
socketRules = append(socketRules, seccomp.Rule{
seccomp.EqualTo(sock.Family),
// We always set SOCK_NONBLOCK and SOCK_CLOEXEC
+14 -9
View File
@@ -25,13 +25,14 @@ import (
// Options are seccomp filter related options.
type Options struct {
Platform platform.Platform
HostNetwork bool
HostFilesystem bool
HostSocketCreate bool
HostSocketOpen bool
ProfileEnable bool
ControllerFD int
Platform platform.Platform
HostNetwork bool
HostNetworkRawSockets bool
HostFilesystem bool
HostSocketCreate bool
HostSocketOpen bool
ProfileEnable bool
ControllerFD int
}
// Install seccomp filters based on the given platform.
@@ -44,8 +45,12 @@ func Install(opt Options) error {
s.Merge(instrumentationFilters())
if opt.HostNetwork {
Report("host networking enabled: syscall filters less restrictive!")
s.Merge(hostInetFilters())
if opt.HostNetworkRawSockets {
Report("host networking (with raw sockets) enabled: syscall filters less restrictive!")
} else {
Report("host networking enabled: syscall filters less restrictive!")
}
s.Merge(hostInetFilters(opt.HostNetworkRawSockets))
}
if opt.ProfileEnable {
Report("profile enabled: syscall filters less restrictive!")
+10 -8
View File
@@ -569,14 +569,16 @@ func (l *Loader) installSeccompFilters() error {
filter.Report("syscall filter is DISABLED. Running in less secure mode.")
} else {
hostUDS := l.root.conf.GetHostUDS()
hostnet := l.root.conf.Network == config.NetworkHost
opts := filter.Options{
Platform: l.k.Platform,
HostNetwork: l.root.conf.Network == config.NetworkHost,
HostFilesystem: l.root.conf.DirectFS,
HostSocketCreate: l.root.conf.DirectFS && hostUDS.AllowCreate(),
HostSocketOpen: l.root.conf.DirectFS && hostUDS.AllowOpen(),
ProfileEnable: l.root.conf.ProfileEnable,
ControllerFD: l.ctrl.srv.FD(),
Platform: l.k.Platform,
HostNetwork: hostnet,
HostNetworkRawSockets: hostnet && l.root.conf.EnableRaw,
HostFilesystem: l.root.conf.DirectFS,
HostSocketCreate: l.root.conf.DirectFS && hostUDS.AllowCreate(),
HostSocketOpen: l.root.conf.DirectFS && hostUDS.AllowOpen(),
ProfileEnable: l.root.conf.ProfileEnable,
ControllerFD: l.ctrl.srv.FD(),
}
if err := filter.Install(opts); err != nil {
return fmt.Errorf("installing seccomp filters: %w", err)
@@ -606,7 +608,7 @@ func (l *Loader) run() error {
// is configured after the loader is created and before Run() is called.
log.Debugf("Configuring host network")
s := l.k.RootNetworkNamespace().Stack().(*hostinet.Stack)
if err := s.Configure(); err != nil {
if err := s.Configure(l.root.conf.EnableRaw); err != nil {
return err
}
}
+28 -9
View File
@@ -32,6 +32,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bits"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/specutils"
)
@@ -77,15 +78,24 @@ func TestExecPrivileged(t *testing.T) {
d := dockerutil.MakeContainer(ctx, t)
defer d.CleanUp(ctx)
// Start the container with all capabilities dropped.
if err := d.Spawn(ctx, dockerutil.RunOpts{
// Container will drop all capabilities.
opts := dockerutil.RunOpts{
Image: "basic/alpine",
CapDrop: []string{"all"},
}, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil {
}
// But if we are running with host network stack and raw sockets, then
// we require CAP_NET_RAW, so add that back.
if testutil.IsRunningWithHostNet() && testutil.IsRunningWithNetRaw() {
opts.CapAdd = []string{"NET_RAW"}
}
// Start the container.
if err := d.Spawn(ctx, opts, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Check that all capabilities where dropped from container.
// Grab the capabilities from inside container.
matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second)
if err != nil {
t.Fatalf("WaitForOutputSubmatch() timeout: %v", err)
@@ -98,12 +108,18 @@ func TestExecPrivileged(t *testing.T) {
t.Fatalf("failed to convert capabilities %q: %v", matches[1], err)
}
t.Logf("Container capabilities: %#x", containerCaps)
if containerCaps != 0 {
t.Fatalf("Container should have no capabilities: %x", containerCaps)
// Expect no capabilities, unless raw sockets configured.
var wantContainerCaps uint64
if testutil.IsRunningWithNetRaw() {
wantContainerCaps |= bits.MaskOf64(int(linux.CAP_NET_RAW))
}
if containerCaps != wantContainerCaps {
t.Fatalf("Container caps got %x want %x", containerCaps, wantContainerCaps)
}
// Check that 'exec --privileged' adds all capabilities, except for
// CAP_NET_RAW.
// Check that 'exec --privileged' adds all capabilities except
// CAP_NET_RAW, unless raw sockets configured.
got, err := d.Exec(ctx, dockerutil.ExecOpts{
Privileged: true,
}, "grep", "CapEff:", "/proc/self/status")
@@ -111,7 +127,10 @@ func TestExecPrivileged(t *testing.T) {
t.Fatalf("docker exec failed: %v", err)
}
t.Logf("Exec CapEff: %v", got)
wantCaps := specutils.AllCapabilitiesUint64() &^ bits.MaskOf64(int(linux.CAP_NET_RAW))
wantCaps := specutils.AllCapabilitiesUint64()
if !testutil.IsRunningWithNetRaw() {
wantCaps &= ^bits.MaskOf64(int(linux.CAP_NET_RAW))
}
wantStr := fmt.Sprintf("CapEff:\t%016x\n", wantCaps)
if got == wantStr {
// All good.
-6
View File
@@ -568,12 +568,6 @@ func TestLink(t *testing.T) {
// This test ensures we can run ping without errors.
func TestPing4Loopback(t *testing.T) {
if testutil.IsRunningWithHostNet() {
// TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable
// this test.
t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.")
}
runIntegrationTest(t, nil, "./ping4.sh")
}
+4
View File
@@ -447,6 +447,7 @@ syscall_test(
syscall_test(
size = "medium",
add_hostinet = True,
# Takes too long under gotsan to run.
tags = ["nogotsan"],
test = "//test/syscalls/linux:ping_socket_test",
@@ -557,14 +558,17 @@ syscall_test(
)
syscall_test(
add_hostinet = True,
test = "//test/syscalls/linux:raw_socket_hdrincl_test",
)
syscall_test(
add_hostinet = True,
test = "//test/syscalls/linux:raw_socket_icmp_test",
)
syscall_test(
add_hostinet = True,
shard_count = more_shards,
test = "//test/syscalls/linux:raw_socket_test",
)
+2 -2
View File
@@ -797,7 +797,7 @@ TEST_P(RawSocketTest, RecvBufLimits) {
ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
int sent = 4;
if (IsRunningOnGvisor()) {
if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
// Linux seems to drop the 4th packet even though technically it should
// fit in the receive buffer.
ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
@@ -1451,7 +1451,7 @@ TEST(RawSocketTest, SetIPv6ChecksumError_ReadShort) {
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
int intV = 2;
if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
if (IsRunningOnGvisor()) {
// TODO(https://gvisor.dev/issue/6982): This is a deviation from Linux. We
// should determine if we want to match the behaviour or handle the error
// more gracefully.
+18 -3
View File
@@ -244,7 +244,12 @@ TEST_F(RawSocketICMPTest, MultipleSocketReceive) {
// A raw ICMP socket and ping socket should both receive the ICMP packets
// intended for the ping socket.
TEST_F(RawSocketICMPTest, RawAndPingSockets) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
// By default, ping sockets cannot be created on Linux, even with root privs.
// So we only run the test with gVisor and not hostinet, and even then require
// CAP_NET_RAW.
// See https://lwn.net/Articles/443051/
SKIP_IF(!IsRunningOnGvisor() || IsRunningWithHostinet() ||
ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor ping_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
@@ -294,7 +299,12 @@ TEST_F(RawSocketICMPTest, RawAndPingSockets) {
// while a ping socket should not. Neither should be able to receieve a short
// malformed packet.
TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
// By default, ping sockets cannot be created on Linux, even with root privs.
// So we only run the test with gVisor and not hostinet, and even then require
// CAP_NET_RAW.
// See https://lwn.net/Articles/443051/
SKIP_IF(!IsRunningOnGvisor() || IsRunningWithHostinet() ||
ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor ping_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
@@ -335,7 +345,12 @@ TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) {
// while ping socket should not.
// Neither should be able to receieve a short malformed packet.
TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
// By default, ping sockets cannot be created on Linux, even with root privs.
// So we only run the test with gVisor and not hostinet, and even then require
// CAP_NET_RAW.
// See https://lwn.net/Articles/443051/
SKIP_IF(!IsRunningOnGvisor() || IsRunningWithHostinet() ||
ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor ping_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));