Merge pull request #9551 from amysaq2023:support-external-stack

PiperOrigin-RevId: 677933413
This commit is contained in:
gVisor bot
2024-09-23 14:12:32 -07:00
49 changed files with 2811 additions and 8 deletions
+3
View File
@@ -29,6 +29,9 @@ test:race --@io_bazel_rules_go//go/config:race --@io_bazel_rules_go//go/config:p
build --@io_bazel_rules_go//go/config:pure
test --@io_bazel_rules_go//go/config:pure
# Set bazel_rule as non-pure when cgo is used.
build:plugin-tldk --@io_bazel_rules_go//go/config:pure=false --define=plugin_tldk=true --define=network_plugins=true
# By default, exclude nogo targets from building. They will still be included
# by default for all tests.
build --build_tag_filters=-nogo
+3 -3
View File
@@ -87,7 +87,7 @@ steps:
label: ":world_map: Build runsc and pkg (AMD64)"
commands:
- "make build TARGETS=//pkg/..."
- "make build TARGETS=//runsc/..."
- "make build TARGETS='--build_tag_filters=-network_plugins //runsc/...'"
agents:
arch: "amd64"
@@ -96,7 +96,7 @@ steps:
label: ":world_map: Build runsc and pkg (ARM64)"
commands:
- "make build TARGETS=//pkg/..."
- "make build TARGETS=//runsc/..."
- "make build TARGETS='--build_tag_filters=-network_plugins //runsc/...'"
agents:
arch: "arm64"
@@ -105,7 +105,7 @@ steps:
<<: *source_test_continuous
label: ":world_map: Build everything"
commands:
- "make build TARGETS=//..."
- "make build TARGETS='--build_tag_filters=-network_plugins //...'"
# Check that the Go branch builds. This is not technically required, as this build is maintained
# as a GitHub action in order to preserve this maintaince across forks. However, providing the
+1
View File
@@ -139,6 +139,7 @@ go_path(
# Packages that are not dependencies of the above.
"//pkg/sentry/kernel/memevent",
"//pkg/sentry/socket/plugin/stack",
"//pkg/tcpip/adapters/gonet",
"//pkg/tcpip/faketime",
"//pkg/tcpip/link/channel",
+1 -1
View File
@@ -221,7 +221,7 @@ nogo-tests:
#
# FIXME(gvisor.dev/issue/10045): Need to fix broken tests.
unit-tests: ## Local package unit tests in pkg/..., tools/.., etc.
@$(call test,--test_tag_filters=-nogo$(COMMA)-requires-kvm -- //:all pkg/... tools/... runsc/... vdso/... test/trace/... -//pkg/metric:metric_test -//pkg/coretag:coretag_test -//runsc/config:config_test -//tools/tracereplay:tracereplay_test -//test/trace:trace_test)
@$(call test,--test_tag_filters=-nogo$(COMMA)-requires-kvm --build_tag_filters=-network_plugins -- //:all pkg/... tools/... runsc/... vdso/... test/trace/... -//pkg/metric:metric_test -//pkg/coretag:coretag_test -//runsc/config:config_test -//tools/tracereplay:tracereplay_test -//test/trace:trace_test)
.PHONY: unit-tests
# See unit-tests: this includes runsc/container.
+7
View File
@@ -52,6 +52,7 @@ http_archive(
# Allow for patching of the go_sdk.
"//tools:rules_go_sdk.patch",
"//tools:rules_go_facts.patch",
"//tools:rules_cgo.patch",
],
sha256 = "80a98277ad1311dacd837f9b16db62887702e9f1d1c4c9f796d0121a46c8e184",
urls = [
@@ -3351,3 +3352,9 @@ go_repository(
sum = "h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg=",
version = "v2.3.3",
)
new_local_repository(
name = "libpluginstack",
path = "tools/plugin-stack",
build_file = "tools/plugin-stack/plugin-stack.BUILD",
)
+3
View File
@@ -85,6 +85,9 @@ type IFConf struct {
Ptr uint64
}
// SizeOfIFConf is the binary size of an IFConf struct (16 bytes).
var SizeOfIFConf = (*IFConf)(nil).SizeBytes()
// EthtoolCmd is a marshallable type to be able to easily copyin the
// the command for an SIOCETHTOOL ioctl.
//
+21
View File
@@ -0,0 +1,21 @@
load("//tools:defs.bzl", "go_library")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_library(
name = "plugin",
srcs = [
"config.go",
"plugin.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/seccomp",
"//pkg/sentry/inet",
"//pkg/waiter",
"@org_golang_x_sys//unix:go_default_library",
],
)
+41
View File
@@ -0,0 +1,41 @@
load("//tools:defs.bzl", "go_library")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
config_setting(
name = "network_plugins",
values = {"define": "network_plugins=true"},
)
go_library(
name = "cgo",
srcs = [
"cgo.go",
"nocgo_stub_unsafe.go",
"socket_unsafe.go",
"stack_unsafe.go",
"util_unsafe.go",
],
bazel_cdeps = [
"@libpluginstack//:libpluginstack",
],
bazel_cgo = select({
":network_plugins": True,
"//conditions:default": False,
}),
bazel_clinkopts = [
"-L external/libpluginstack",
],
bazel_copts = [
"-march=native",
"-I external/libpluginstack/lib/libtle_glue",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/abi/linux",
"//pkg/abi/linux/errno",
],
)
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cgo provides interfaces definition to interact with third-party
// network stack. It also implements CGO wrappers to handle Golang arguments
// to CGO and CGO return values to Golang.
//
// Third-party external network stack will implement interfaces defined in this
// package in order to be used by gVisor.
package cgo
@@ -0,0 +1,158 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !network_plugins
// +build !network_plugins
package cgo
import (
"syscall"
"unsafe"
)
// GetPtr is a non-cgo stub function.
func GetPtr(bs []byte) unsafe.Pointer {
panic("unimplemented")
}
// EpollCreate is a non-cgo stub function.
func EpollCreate() int {
panic("unimplemented")
}
// EpollCtl is a non-cgo stub function.
func EpollCtl(epfd int32, op int, handle, events uint32) {
panic("unimplemented")
}
// EpollWait is a non-cgo stub function.
func EpollWait(epfd int32, events []syscall.EpollEvent, n int, us int) int {
panic("unimplemented")
}
// Socket is a non-cgo stub function.
func Socket(domain, skType, protocol int) int64 {
panic("unimplemented")
}
// Bind is a non-cgo stub function.
func Bind(handle uint32, sa []byte) int64 {
panic("unimplemented")
}
// Listen is a non-cgo stub function.
func Listen(handle uint32, backlog int) int64 {
panic("unimplemented")
}
// Accept is a non-cgo stub function.
func Accept(handle uint32, addrPtr *byte, lenPtr *uint32) int64 {
panic("unimplemented")
}
// Ioctl is a non-cgo stub function.
func Ioctl(handle uint32, cmd uint32, buf []byte) int64 {
panic("unimplemented")
}
// Connect is a non-cgo stub function.
func Connect(handle uint32, addr []byte) int64 {
panic("unimplemented")
}
// Getsockopt is a non-cgo stub function.
func Getsockopt(handle uint32, l int, n int, val []byte, s int) (int64, int) {
panic("unimplemented")
}
// Setsockopt is a non-cgo stub function.
func Setsockopt(handle uint32, l int, n int, val []byte) int64 {
panic("unimplemented")
}
// Shutdown is a non-cgo stub function.
func Shutdown(handle uint32, how int) int64 {
panic("unimplemented")
}
// Close is a non-cgo stub function.
func Close(handle uint32) {
panic("unimplemented")
}
// Getsockname is a non-cgo stub function.
func Getsockname(handle uint32, addr []byte, addrlen *uint32) int64 {
panic("unimplemented")
}
// GetPeername is a non-cgo stub function.
func GetPeername(handle uint32, addr []byte, addrlen *uint32) int64 {
panic("unimplemented")
}
// Readiness is a non-cgo stub function.
func Readiness(handle uint32, mask uint64) int64 {
panic("unimplemented")
}
// Read is a non-cgo stub function.
func Read(handle uint32, buf uintptr, count int) int64 {
panic("unimplemented")
}
// Readv is a non-cgo stub function.
func Readv(handle uint32, iovs []syscall.Iovec) int64 {
panic("unimplemented")
}
// Recvfrom is a non-cgo stub function.
func Recvfrom(handle uint32, buf, addr []byte, flags int) (int64, int) {
panic("unimplemented")
}
// Recvmsg is a non-cgo stub function.
func Recvmsg(handle uint32, iovs []syscall.Iovec, addr, control []byte, flags int) (int64, int, int, int) {
panic("unimplemented")
}
// Write is a non-cgo stub function.
func Write(handle uint32, buf uintptr, count int) int64 {
panic("unimplemented")
}
// Writev is a non-cgo stub function.
func Writev(handle uint32, iovs []syscall.Iovec) int64 {
panic("unimplemented")
}
// Sendto is a non-cgo stub function.
func Sendto(handle uint32, buf uintptr, count int, flags int, addr []byte) int64 {
panic("unimplemented")
}
// Sendmsg is a non-cgo stub function.
func Sendmsg(handle uint32, iovs []syscall.Iovec, addr []byte, flags int) int64 {
panic("unimplemented")
}
// InitStack is a non-cgo stub function.
func InitStack(initStr string, fds []int) error {
panic("unimplemented")
}
// PreInitStack is a non-cgo stub function.
func PreInitStack(pid int) (string, []int, error) {
panic("unimplemented")
}
@@ -0,0 +1,422 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build network_plugins
// +build network_plugins
package cgo
/*
#include <stdint.h>
#include <sys/epoll.h>
#include <sys/socket.h>
// socket event-related operations
int plugin_epoll_create(void);
int plugin_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int plugin_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// socket control-path operations
int plugin_socket(int domain, int type, int protocol, uint64_t *err);
int plugin_listen(int sockfd, int backlog, uint64_t *err);
int plugin_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen, uint64_t *err);
int plugin_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t *err);
int plugin_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, uint64_t *err);
int plugin_getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen, uint64_t *err);
int plugin_setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen, uint64_t *err);
int plugin_getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t *err);
int plugin_getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t *err);
int plugin_ioctl(int fd, uint64_t *err, unsigned long int request, void *buf);
int plugin_shutdown(int sockfd, int how, uint64_t *err);
int plugin_close(int fd);
int plugin_readiness(int fd, int events);
// socket data-path (ingress) operations
ssize_t plugin_recv(int sockfd, void *buf, size_t len, int flags, uint64_t *err);
ssize_t plugin_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen, uint64_t *err);
ssize_t plugin_recvmsg(int sockfd, struct msghdr *msg, int flags, uint64_t *err);
ssize_t plugin_read(int fd, void *buf, size_t count, uint64_t *err);
ssize_t plugin_readv(int fd, const struct iovec *iov, int iovcnt, uint64_t *err);
// socket data-path (egress) operations
ssize_t plugin_send(int sockfd, const void *buf, size_t len, int flags, uint64_t *err);
ssize_t plugin_sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen, uint64_t *err);
ssize_t plugin_sendmsg(int sockfd, const struct msghdr *msg, int flags, uint64_t *err);
ssize_t plugin_write(int fd, const void *buf, size_t count, uint64_t *err);
ssize_t plugin_writev(int fd, const struct iovec *iov, int iovcnt, uint64_t *err);
*/
import "C"
import (
"syscall"
"unsafe"
"gvisor.dev/gvisor/pkg/abi/linux"
linuxerrno "gvisor.dev/gvisor/pkg/abi/linux/errno"
)
// EpollCreate works as a CGO wrapper for plugin_epoll_create.
func EpollCreate() int {
return int(C.plugin_epoll_create())
}
// EpollCtl works as a CGO wrapper for plugin_epoll_ctl.
func EpollCtl(epfd int32, op int, handle, events uint32) {
epollEvent := syscall.EpollEvent{
Events: events,
Fd: int32(handle),
}
C.plugin_epoll_ctl(
C.int(epfd),
C.int(op),
C.int(handle),
(*C.struct_epoll_event)(unsafe.Pointer(&epollEvent)))
}
// EpollWait works as a CGO wrapper for plugin_epoll_wait.
func EpollWait(epfd int32, events []syscall.EpollEvent, n int, us int) int {
if len(events) == 0 {
return 0
}
return int(C.plugin_epoll_wait(
C.int(epfd),
(*C.struct_epoll_event)(unsafe.Pointer(&events[0])),
C.int(n),
C.int(us)))
}
// Socket works as a CGO wrapper for plugin_socket.
// Note: This function will set socket as non-blocking.
func Socket(domain, skType, protocol int) int64 {
var errno uint64
if fd := int64(C.plugin_socket(
C.int(domain),
C.int(skType),
C.int(protocol),
(*C.uint64_t)(unsafe.Pointer(&errno)))); fd < 0 {
return -int64(errno)
} else {
nonblock := 1
C.plugin_ioctl(
C.int(fd),
(*C.uint64_t)(unsafe.Pointer(&errno)),
C.uint64_t(linux.FIONBIO),
unsafe.Pointer(&nonblock))
return fd
}
}
// Bind works as a CGO wrapper for plugin_bind.
func Bind(handle uint32, sa []byte) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_bind(
C.int(handle),
(*C.struct_sockaddr)(GetPtr(sa)),
C.uint(len(sa)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Listen works as a CGO wrapper for plugin_listen.
func Listen(handle uint32, backlog int) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_listen(
C.int(handle),
C.int(backlog),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Accept works as a CGO wrapper for plugin_accept.
// Note: This function will set socket as non-blocking.
func Accept(handle uint32, addrPtr *byte, lenPtr *uint32) int64 {
var errno uint64
if fd := int64(C.plugin_accept(
C.int(handle),
(*C.struct_sockaddr)(unsafe.Pointer(addrPtr)),
(*C.socklen_t)(unsafe.Pointer(lenPtr)),
(*C.uint64_t)(unsafe.Pointer(&errno)))); fd < 0 {
return -int64(errno)
} else {
nonblock := 1
C.plugin_ioctl(
C.int(fd),
(*C.uint64_t)(unsafe.Pointer(&errno)),
C.uint64_t(linux.FIONBIO),
unsafe.Pointer(&nonblock))
return fd
}
}
// Ioctl works as a CGO wrapper for plugin_ioctl.
func Ioctl(handle uint32, cmd uint32, buf []byte) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_ioctl(
C.int(handle),
(*C.uint64_t)(unsafe.Pointer(&errno)),
C.uint64_t(cmd),
GetPtr(buf))),
errno)
}
// Connect works as a CGO wrapper for plugin_connect.
func Connect(handle uint32, addr []byte) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_connect(
C.int(handle),
(*C.struct_sockaddr)(GetPtr(addr)),
C.socklen_t(len(addr)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Getsockopt works as a CGO wrapper for plugin_getsockopt.
func Getsockopt(handle uint32, l int, n int, val []byte, s int) (int64, int) {
var errno uint64
if ret := int64(C.plugin_getsockopt(
C.int(handle),
C.int(l),
C.int(n),
GetPtr(val),
(*C.uint)(unsafe.Pointer(&s)),
(*C.uint64_t)(unsafe.Pointer(&errno)))); ret < 0 {
return -int64(errno), s
} else {
return ret, s
}
}
// Setsockopt works as a CGO wrapper for plugin_setsockopt.
func Setsockopt(handle uint32, l int, n int, val []byte) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_setsockopt(
C.int(handle),
C.int(l),
C.int(n),
GetPtr(val),
C.uint(len(val)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Shutdown works as a CGO wrapper for plugin_shutdown.
func Shutdown(handle uint32, how int) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_shutdown(
C.int(handle),
C.int(how),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Close works as a CGO wrapper for plugin_close.
func Close(handle uint32) {
C.plugin_close(C.int(handle))
}
// Getsockname works as a CGO wrapper for plugin_getsockname.
func Getsockname(handle uint32, addr []byte, addrlen *uint32) int64 {
var errno uint64
if len(addr) == 0 {
return -linuxerrno.EINVAL
}
return convertRetVal(
int64(C.plugin_getsockname(
C.int(handle),
(*C.struct_sockaddr)(unsafe.Pointer(&addr[0])),
(*C.socklen_t)(unsafe.Pointer(addrlen)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// GetPeername works as a CGO wrapper for plugin_getpeername.
func GetPeername(handle uint32, addr []byte, addrlen *uint32) int64 {
var errno uint64
if len(addr) == 0 {
return -linuxerrno.EINVAL
}
return convertRetVal(
int64(C.plugin_getpeername(
C.int(handle),
(*C.struct_sockaddr)(unsafe.Pointer(&addr[0])),
(*C.socklen_t)(unsafe.Pointer(addrlen)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Readiness works as a CGO wrapper for plugin_readiness.
func Readiness(handle uint32, mask uint64) int64 {
return int64(C.plugin_readiness(C.int(handle), C.int(mask)))
}
// Read works as a CGO wrapper for plugin_read.
func Read(handle uint32, buf uintptr, count int) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_read(
C.int(handle),
unsafe.Pointer(buf),
C.size_t(count),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Readv works as a CGO wrapper for plugin_readv.
func Readv(handle uint32, iovs []syscall.Iovec) int64 {
var errno uint64
if len(iovs) == 0 {
return 0
}
return convertRetVal(
int64(C.plugin_readv(
C.int(handle),
(*C.struct_iovec)(unsafe.Pointer(&iovs[0])),
C.int(len(iovs)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Recvfrom works as a CGO wrapper for plugin_recvfrom.
func Recvfrom(handle uint32, buf, addr []byte, flags int) (int64, int) {
var errno uint64
addrlen := len(addr)
if ret := int64(C.plugin_recvfrom(
C.int(handle),
GetPtr(buf),
C.size_t(len(buf)),
C.int(flags),
(*C.struct_sockaddr)(GetPtr(addr)),
(*C.socklen_t)(unsafe.Pointer(&addrlen)),
(*C.uint64_t)(unsafe.Pointer(&errno)))); ret < 0 {
return -int64(errno), addrlen
} else {
return ret, addrlen
}
}
// Recvmsg works as a CGO wrapper for plugin_recvmsg.
func Recvmsg(handle uint32, iovs []syscall.Iovec, addr, control []byte, flags int) (int64, int, int, int) {
lenAddr := len(addr)
lenCtl := len(control)
sysflags := flags | syscall.MSG_DONTWAIT
if len(iovs) == 0 {
return 0, lenAddr, lenCtl, 0
}
var ptrAddr, ptrCtl *byte
if lenAddr > 0 {
ptrAddr = &addr[0]
}
if lenCtl > 0 {
ptrCtl = &control[0]
}
msg := syscall.Msghdr{
Iov: &iovs[0],
Iovlen: uint64(len(iovs)),
Name: ptrAddr,
Namelen: uint32(lenAddr),
Control: ptrCtl,
Controllen: uint64(lenCtl),
}
var errno uint64
if ret := int64(C.plugin_recvmsg(
C.int(handle),
(*C.struct_msghdr)(unsafe.Pointer(&msg)),
C.int(sysflags),
(*C.uint64_t)(unsafe.Pointer(&errno)))); ret < 0 {
return -int64(errno), lenAddr, lenCtl, 0
} else {
return ret, int(msg.Namelen), int(msg.Controllen), int(msg.Flags)
}
}
// Write works as a CGO wrapper for plugin_write.
func Write(handle uint32, buf uintptr, count int) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_write(
C.int(handle),
unsafe.Pointer(buf),
C.size_t(count),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Writev works as a CGO wrapper for plugin_writev.
func Writev(handle uint32, iovs []syscall.Iovec) int64 {
var errno uint64
if len(iovs) == 0 {
return 0
}
return convertRetVal(
int64(C.plugin_writev(
C.int(handle),
(*C.struct_iovec)(unsafe.Pointer(&iovs[0])),
C.int(len(iovs)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Sendto works as a CGO wrapper for plugin_sendto.
func Sendto(handle uint32, buf uintptr, count int, flags int, addr []byte) int64 {
var errno uint64
return convertRetVal(
int64(C.plugin_sendto(
C.int(handle),
unsafe.Pointer(buf),
C.size_t(count),
C.int(flags),
(*C.struct_sockaddr)(GetPtr(addr)),
C.socklen_t(len(addr)),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
// Sendmsg works as a CGO wrapper for plugin_sendmsg.
func Sendmsg(handle uint32, iovs []syscall.Iovec, addr []byte, flags int) int64 {
var errno uint64
if len(iovs) == 0 {
return 0
}
if len(addr) == 0 {
return -linuxerrno.EINVAL
}
msg := syscall.Msghdr{
Iov: &iovs[0],
Iovlen: uint64(len(iovs)),
Name: &addr[0],
Namelen: uint32(len(addr)),
}
return convertRetVal(
int64(C.plugin_sendmsg(
C.int(handle),
(*C.struct_msghdr)(unsafe.Pointer(&msg)),
C.int(flags),
(*C.uint64_t)(unsafe.Pointer(&errno)))),
errno)
}
@@ -0,0 +1,79 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build network_plugins
// +build network_plugins
package cgo
/*
#include <stdlib.h>
// stack initialization operations
int plugin_initstack(char *init_str, int *fds, int num);
int plugin_preinitstack(int pid, char **init_str_ptr, int **fds, int *num);
*/
import "C"
import (
"fmt"
"unsafe"
)
// InitStack implements CGO wrapper for plugin_initstack.
func InitStack(initStr string, fds []int) error {
cs := C.CString(initStr)
defer C.free(unsafe.Pointer(cs))
fdNum := len(fds)
cfds := make([]C.int, fdNum)
for i := 0; i < fdNum; i++ {
cfds[i] = (C.int)(fds[i])
}
if ret := C.plugin_initstack(cs, (*C.int)(&cfds[0]), (C.int)(fdNum)); ret != 0 {
return fmt.Errorf("failed to init stack, ret = %v", ret)
}
return nil
}
// PreInitStack implements CGO wrapper for plugin_preinitstack.
func PreInitStack(pid int) (string, []int, error) {
var (
cInitStr *C.char
cFdArray *C.int
num C.int
)
if ret := C.plugin_preinitstack(
C.int(pid),
(**C.char)(unsafe.Pointer(&cInitStr)),
(**C.int)(unsafe.Pointer(&cFdArray)),
(*C.int)(unsafe.Pointer(&num))); ret != 0 {
return "", nil, fmt.Errorf("failed to prepare init args for the stack, ret = %v", ret)
}
defer func() {
C.free(unsafe.Pointer(cInitStr))
C.free(unsafe.Pointer(cFdArray))
}()
initStr := C.GoString(cInitStr)
fds := make([]int, int(num))
cFds := unsafe.Slice(cFdArray, num)
for i := 0; i < int(num); i++ {
fds[i] = int(cFds[i])
}
return initStr, fds, nil
}
@@ -0,0 +1,39 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build network_plugins
// +build network_plugins
package cgo
import (
"unsafe"
)
// GetPtr gets []byte's start address and converts the address into
// unsafe.Pointer that will be used as C pointer.
func GetPtr(bs []byte) unsafe.Pointer {
if len(bs) == 0 {
return nil
}
return unsafe.Pointer(&bs[0])
}
func convertRetVal(ret int64, errno uint64) int64 {
if ret < 0 {
return -int64(errno)
} else {
return ret
}
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package plugin
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/seccomp"
)
// SeccompFilters defines seccomp allowed rules that are needed by cgo.
func SeccompFilters() seccomp.SyscallRules {
return seccomp.MakeSyscallRules(map[uintptr]seccomp.SyscallRule{
unix.SYS_MMAP: seccomp.PerArg{
// allow alloc_seg in DPDK
seccomp.AnyValue{},
seccomp.AnyValue{},
seccomp.AnyValue{},
seccomp.EqualTo(
unix.MAP_SHARED |
unix.MAP_ANONYMOUS |
unix.MAP_FIXED),
},
})
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package plugin provides a set of interfaces to interact with
// third-party netstack. It will be used during sandbox network setup when
// NetworkType is set as NetworkPlugin.
package plugin
import (
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/waiter"
)
// PluginStack defines a set of stack operations to work with a third-party
// plugin stack.
type PluginStack interface {
inet.Stack
// Init initializes plugin stack.
Init(args *InitStackArgs) error
// PreInit handles prepare steps before initializing plugin stack.
// It may include joining namespace, mounting NIC, etc.
PreInit(args *PreInitStackArgs) (string, []int, error)
}
// InitStackArgs is a struct that holds arguments needed by PluginStack.Init.
type InitStackArgs struct {
// InitStr represents arguments needed to initialize plugin stack.
InitStr string
// FDs represents files opened during stack pre-init stage, which will
// be used in stack initialization.
FDs []int
}
// PreInitStackArgs is a struct that holds arguments needed by
// PluginStack.PreInit.
type PreInitStackArgs struct {
// Pid represents current process that invokes plugin stack
// pre-init.
Pid int
}
var pluginStack PluginStack
// RegisterPluginStack registers given stack as plugin stack.
func RegisterPluginStack(stack PluginStack) {
if pluginStack != nil {
panic("called RegisterPluginStack more than once")
}
pluginStack = stack
}
// GetPluginStack fetches the current registered plugin stack.
func GetPluginStack() PluginStack {
return pluginStack
}
// EventInfo is a struct that holds information necessary to a socket
// notification mechanisms.
type EventInfo struct {
// Queue is the socket corresponding event queue.
Wq *waiter.Queue
// Mask represents events this socket registered.
Mask waiter.EventMask
// Ready represents events has been currently reported.
Ready waiter.EventMask
// Waiting represents whether there is any waiting event.
Waiting bool
}
// PluginNotifier represents a set of operations to handle
// plugin network stack's event notification mechanisms.
type PluginNotifier interface {
// AddFD registers a new socket fd and its corresponding
// event notification info into the global fdMap.
AddFD(fd uint32, eventinfo *EventInfo) error
// RemoveFD unregisters a socket fd and its corresponding
// event notification info from the global fdMap.
RemoveFD(fd uint32)
// UpdateFD updates the set of events the socket fd needs
// to be notified on.
UpdateFD(fd uint32) error
}
+48
View File
@@ -0,0 +1,48 @@
load("//tools:defs.bzl", "go_library")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_library(
name = "stack",
srcs = [
"notifier.go",
"provider.go",
"readwriter.go",
"socket.go",
"stack.go",
"util.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/abi/linux",
"//pkg/abi/linux/errno",
"//pkg/binary",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
"//pkg/marshal",
"//pkg/marshal/primitive",
"//pkg/safemem",
"//pkg/sentry/arch",
"//pkg/sentry/fsimpl/sockfs",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/time",
"//pkg/sentry/socket",
"//pkg/sentry/socket/plugin",
"//pkg/sentry/socket/plugin/cgo",
"//pkg/sentry/unimpl",
"//pkg/sentry/vfs",
"//pkg/syserr",
"//pkg/tcpip",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/usermem",
"//pkg/waiter",
"@org_golang_x_sys//unix:go_default_library",
],
)
+165
View File
@@ -0,0 +1,165 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stack
import (
"fmt"
"runtime"
"sync"
"syscall"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin/cgo"
"gvisor.dev/gvisor/pkg/waiter"
)
// Notifier holds all the state necessary to issue notifications when
// IO events occur on the observed FDs in plugin stack.
type Notifier struct {
// the epoll FD used to register for io notifications.
epFD int32
// mu protects eventMap.
mu sync.Mutex
// eventMap maps file descriptors to their notification queues
// and waiting status.
eventMap map[uint32]*plugin.EventInfo
}
const (
MaxEpollEvents = 128
SleepInMsecond = 100
)
// NewNotifier initialize the event notifier for plugin stack.
// It will allocate a eventMap with fd as key and corresponding eventInfo
// as value and start a goroutine waiting the arrival of events.
func NewNotifier() *Notifier {
ioInit := make(chan int32)
n := &Notifier{
eventMap: make(map[uint32]*plugin.EventInfo),
}
go n.waitAndNotify(ioInit)
epFD := <-ioInit
if epFD < 0 {
return nil
}
return n
}
// AddFD implements plugin.PluginNotifier.AddFD.
func (n *Notifier) AddFD(fd uint32, eventInfo *plugin.EventInfo) {
n.mu.Lock()
defer n.mu.Unlock()
// Panic if we're already notifying on this FD.
if _, ok := n.eventMap[fd]; ok {
panic(fmt.Sprintf("File descriptor %d added twice", fd))
}
// We have nothing to wait for at the moment. Just add it to the map.
n.eventMap[fd] = eventInfo
}
// RemoveFD implements plugin.PluginNotifier.RemoveFD.
func (n *Notifier) RemoveFD(fd uint32) {
n.mu.Lock()
defer n.mu.Unlock()
delete(n.eventMap, fd)
}
// UpdateFD implements plugin.PluginNotifier.UpdateFD.
func (n *Notifier) UpdateFD(fd uint32) {
n.mu.Lock()
defer n.mu.Unlock()
if eventInfo, ok := n.eventMap[fd]; ok {
n.waitFD(fd, eventInfo)
}
}
// waitAndNotify loops waiting for io event notifications from the epoll
// object. Once notifications arrive, they are dispatched to the
// registered queue.
func (n *Notifier) waitAndNotify(ioInit chan int32) error {
// plugin stack leverages TLS varaibles, so bind this goroutine with
// one specific OS thread
runtime.LockOSThread()
// If current thread is not the main thread, change the thread name.
if syscall.Getpid() != syscall.Gettid() {
threadName := []byte("io-thread\x00")
if err := unix.Prctl(unix.PR_SET_NAME, uintptr(cgo.GetPtr(threadName)), 0, 0, 0); err != nil {
return err
}
}
n.epFD = int32(cgo.EpollCreate())
ioInit <- n.epFD
var events [MaxEpollEvents]syscall.EpollEvent
for {
num := cgo.EpollWait(n.epFD, events[:], MaxEpollEvents, SleepInMsecond)
if num <= 0 {
continue
}
n.mu.Lock()
for i := 0; i < num; i++ {
h := uint32(events[i].Fd)
eventInfo, ok := n.eventMap[h]
if !ok {
continue
}
ev := waiter.EventMask(events[i].Events)
eventInfo.Ready |= ev & (eventInfo.Mask | waiter.EventErr | waiter.EventHUp)
// When an error occurred, invoke all events
if ev&(waiter.EventErr|waiter.EventHUp) != 0 {
ev |= waiter.EventIn | waiter.EventOut
}
eventInfo.Wq.Notify(ev)
}
n.mu.Unlock()
}
}
func (n *Notifier) waitFD(fd uint32, eventInfo *plugin.EventInfo) {
mask := eventInfo.Wq.Events()
eventInfo.Mask = mask
if !eventInfo.Waiting && mask == 0 {
return
}
switch {
case !eventInfo.Waiting && mask != 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_ADD, fd, uint32(mask))
eventInfo.Waiting = true
case eventInfo.Waiting && mask == 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_DEL, fd, uint32(mask))
eventInfo.Ready = 0
eventInfo.Waiting = false
case eventInfo.Waiting && mask != 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_MOD, fd, uint32(mask))
eventInfo.Ready &= mask
}
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stack
import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/socket"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin/cgo"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
)
type provider struct {
family int
netProto tcpip.NetworkProtocolNumber
}
// Socket creates a new socket object for the AF_INET or AF_INET6 family.
func (p *provider) Socket(t *kernel.Task, skType linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) {
// Fail right away if there is no plugin stack registered.
ctx := t.NetworkContext()
if ctx == nil {
return nil, nil
}
_, ok := ctx.(*Stack)
if !ok {
return nil, nil
}
// Only accept TCP and UDP.
stype := skType & linux.SOCK_TYPE_MASK
switch stype {
case syscall.SOCK_STREAM:
switch protocol {
case 0, syscall.IPPROTO_TCP:
default:
return nil, syserr.ErrProtocolNotSupported
}
case syscall.SOCK_DGRAM:
switch protocol {
case 0, syscall.IPPROTO_UDP:
default:
return nil, syserr.ErrProtocolNotSupported
}
case syscall.SOCK_RAW:
// Raw sockets require CAP_NET_RAW.
creds := auth.CredentialsFromContext(t)
if !creds.HasCapability(linux.CAP_NET_RAW) {
return nil, syserr.ErrPermissionDenied
}
default:
return nil, syserr.ErrSocketNotSupported
}
handle := cgo.Socket(p.family, int(skType), protocol)
if handle < 0 {
return nil, int2err(handle)
}
fd, err := newSocket(t, p.family, skType, protocol, stack.notifier, int(handle), uint32(skType&syscall.SOCK_NONBLOCK))
return fd, err
}
// Pair just returns nil sockets (not supported).
func (*provider) Pair(*kernel.Task, linux.SockType, int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) {
return nil, nil, nil
}
func init() {
// Providers backed by plugin stack.
p := []provider{
{
family: linux.AF_INET,
netProto: ipv4.ProtocolNumber,
},
{
family: linux.AF_INET6,
netProto: ipv6.ProtocolNumber,
},
}
for i := range p {
socket.RegisterProvider(p[i].family, &p[i])
}
}
@@ -0,0 +1,110 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stack
import (
"sync"
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin/cgo"
)
type pluginStackRW struct {
handle uint32
// Represent both input and output flags.
flags uint32
// Reused as msg_control for read.
to []byte
iovs [3]syscall.Iovec
}
var pluginStackRWPool = sync.Pool{
New: func() any {
return &pluginStackRW{}
},
}
func getReadWriter(handle uint32) *pluginStackRW {
rw := pluginStackRWPool.Get().(*pluginStackRW)
rw.handle = handle
return rw
}
func putReadWriter(rw *pluginStackRW) {
*rw = pluginStackRW{}
pluginStackRWPool.Put(rw)
}
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
func (rw *pluginStackRW) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
// Set MSG_DONTWAIT flag to avoid blocking in plugin stack.
flags := int(rw.flags) & ^linux.MSG_DONTWAIT
if len(rw.to) != 0 || flags != 0 {
iovs := iovecsFromBlockSeq(dsts, rw)
rc, _, lc, mflags := cgo.Recvmsg(rw.handle, iovs, nil, rw.to, int(rw.flags))
if rc >= 0 {
rw.to = rw.to[:lc]
rw.flags = uint32(mflags)
}
return translateReturn(rc)
}
var rc int64
if dsts.IsEmpty() {
rc = 0
} else if dsts.NumBlocks() == 1 {
rc = cgo.Read(rw.handle, dsts.Head().Addr(), dsts.Head().Len())
} else {
rc = cgo.Readv(rw.handle, iovecsFromBlockSeq(dsts, rw))
}
return translateReturn(rc)
}
// WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
//
// Preconditions: rw.d.metadataMu must be locked.
func (rw *pluginStackRW) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
var rc int64
if rw.to != nil {
if srcs.IsEmpty() {
// Invoke plugin stack checking whether there is any error to report
// on target socket which sends 0-length data.
rc = cgo.Sendto(rw.handle, 0, 0, 0, rw.to)
} else if srcs.NumBlocks() == 1 {
rc = cgo.Sendto(rw.handle, srcs.Head().Addr(), srcs.Head().Len(), 0, rw.to)
} else {
iovs := iovecsFromBlockSeq(srcs, rw)
rc = cgo.Sendmsg(rw.handle, iovs, rw.to, 0)
}
} else {
if srcs.IsEmpty() {
// Invoke plugin stack checking whether there is any error to report
// on target socket which sends 0-length data.
rc = cgo.Write(rw.handle, 0, 0)
} else if srcs.NumBlocks() == 1 {
rc = cgo.Write(rw.handle, srcs.Head().Addr(), srcs.Head().Len())
} else {
rc = cgo.Writev(rw.handle, iovecsFromBlockSeq(srcs, rw))
}
}
return translateReturn(rc)
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More