aboutgitcodebugslistschat
diff options
context:
space:
mode:
-rw-r--r--.clang-format126
-rw-r--r--.clang-tidy93
-rw-r--r--.clangd3
-rw-r--r--Makefile185
-rw-r--r--README.md6
-rw-r--r--arch.c8
-rw-r--r--arp.c12
-rw-r--r--checksum.c53
-rw-r--r--checksum.h7
-rw-r--r--conf.c589
-rw-r--r--contrib/apparmor/abstractions/passt2
-rw-r--r--contrib/selinux/passt.te8
-rw-r--r--contrib/selinux/pasta.te11
-rw-r--r--dhcp.c72
-rw-r--r--dhcpv6.c66
-rw-r--r--epoll_type.h4
-rw-r--r--flow.c136
-rw-r--r--flow.h25
-rw-r--r--flow_table.h2
-rw-r--r--fwd.c275
-rw-r--r--fwd.h3
-rw-r--r--icmp.c8
-rw-r--r--inany.c20
-rw-r--r--inany.h1
-rw-r--r--iov.c92
-rw-r--r--iov.h76
-rw-r--r--ip.h12
-rw-r--r--isolation.c17
-rw-r--r--linux_dep.h144
-rw-r--r--log.c37
-rw-r--r--ndp.c226
-rw-r--r--ndp.h7
-rw-r--r--netlink.c152
-rw-r--r--netlink.h6
-rw-r--r--packet.c91
-rw-r--r--packet.h22
-rw-r--r--passt.1196
-rw-r--r--passt.c76
-rw-r--r--passt.h74
-rw-r--r--pasta.c84
-rw-r--r--pcap.c33
-rw-r--r--pcap.h2
-rw-r--r--pif.c42
-rw-r--r--pif.h3
-rwxr-xr-xseccomp.sh19
-rw-r--r--tap.c306
-rw-r--r--tap.h9
-rw-r--r--tcp.c509
-rw-r--r--tcp.h15
-rw-r--r--tcp_buf.c384
-rw-r--r--tcp_buf.h10
-rw-r--r--tcp_internal.h99
-rw-r--r--tcp_splice.c52
-rw-r--r--tcp_vu.c464
-rw-r--r--tcp_vu.h12
-rw-r--r--test/Makefile10
-rw-r--r--test/README.md9
-rw-r--r--test/lib/layout14
-rwxr-xr-xtest/lib/perf_report15
-rwxr-xr-xtest/lib/setup106
-rwxr-xr-xtest/lib/setup_ugly2
-rwxr-xr-xtest/lib/term41
-rwxr-xr-xtest/lib/test2
-rw-r--r--test/nstool.c40
-rwxr-xr-xtest/passt.mbuto10
-rw-r--r--test/passt/dhcp2
-rw-r--r--test/passt/ndp8
-rw-r--r--test/passt_in_ns/dhcp75
-rw-r--r--test/passt_in_ns/tcp46
-rw-r--r--test/passt_in_ns/udp26
l---------test/passt_vu1
l---------test/passt_vu_in_ns1
-rw-r--r--test/pasta/dhcp2
-rw-r--r--test/pasta/ndp7
-rw-r--r--test/pasta/tcp16
-rw-r--r--test/pasta/udp8
-rw-r--r--test/pasta_options/log_to_file4
-rw-r--r--test/perf/passt_tcp49
-rw-r--r--test/perf/passt_udp33
-rw-r--r--test/perf/passt_vu_tcp211
-rw-r--r--test/perf/passt_vu_udp159
-rw-r--r--test/perf/pasta_tcp57
-rw-r--r--test/perf/pasta_udp29
-rwxr-xr-xtest/run32
-rw-r--r--test/two_guests/basic14
l---------test/two_guests_vu1
-rw-r--r--test/valgrind.supp9
-rw-r--r--udp.c373
-rw-r--r--udp.h6
-rw-r--r--udp_flow.c61
-rw-r--r--udp_flow.h4
-rw-r--r--udp_internal.h34
-rw-r--r--udp_vu.c345
-rw-r--r--udp_vu.h13
-rw-r--r--util.c294
-rw-r--r--util.h86
-rw-r--r--vhost_user.c984
-rw-r--r--vhost_user.h206
-rw-r--r--virtio.c665
-rw-r--r--virtio.h184
-rw-r--r--vu_common.c283
-rw-r--r--vu_common.h60
102 files changed, 7563 insertions, 2060 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..78f177a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# clang-format configuration file. Intended for clang-format >= 11.
+#
+# For more information, see:
+#
+# Documentation/dev-tools/clang-format.rst
+# https://clang.llvm.org/docs/ClangFormat.html
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+
+# Taken from:
+# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \
+# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
+# | LC_ALL=C sort -u
+ForEachMacros:
+ - 'for_each_nst'
+
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentGotoLabels: false
+IndentPPDirectives: None
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatementsExceptForEachMacros
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..9d346ec
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,93 @@
+---
+Checks:
+ - "clang-diagnostic-*,clang-analyzer-*,*,-modernize-*"
+
+ # TODO: enable once https://bugs.llvm.org/show_bug.cgi?id=41311 is fixed
+ - "-clang-analyzer-valist.Uninitialized"
+
+ # Dubious value, would kill readability
+ - "-cppcoreguidelines-init-variables"
+
+ # Dubious value over the compiler's built-in warning. Would
+ # increase verbosity.
+ - "-bugprone-assignment-in-if-condition"
+
+ # Debatable whether these improve readability, right now it would look
+ # like a mess
+ - "-google-readability-braces-around-statements"
+ - "-hicpp-braces-around-statements"
+ - "-readability-braces-around-statements"
+
+ # TODO: in most cases they are justified, but probably not everywhere
+ #
+ - "-readability-magic-numbers"
+ - "-cppcoreguidelines-avoid-magic-numbers"
+
+ # TODO: this is Linux-only for the moment, nice to fix eventually
+ - "-llvmlibc-restrict-system-libc-headers"
+
+ # Those are needed for syscalls, epoll_wait flags, etc.
+ - "-hicpp-signed-bitwise"
+
+ # Probably not doable to impement this without plain memcpy(), memset()
+ - "-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling"
+
+ # TODO: not really important, but nice to fix eventually
+ - "-llvm-include-order"
+
+ # Dubious value, would kill readability
+ - "-readability-isolate-declaration"
+
+ # TODO: nice to fix eventually
+ - "-bugprone-narrowing-conversions"
+ - "-cppcoreguidelines-narrowing-conversions"
+
+ # TODO: check, fix, and more in general constify wherever possible
+ - "-cppcoreguidelines-avoid-non-const-global-variables"
+
+ # TODO: check paths where it might make sense to improve performance
+ - "-altera-unroll-loops"
+ - "-altera-id-dependent-backward-branch"
+
+ # Not much can be done about them other than being careful
+ - "-bugprone-easily-swappable-parameters"
+
+ # TODO: split reported functions
+ - "-readability-function-cognitive-complexity"
+
+ # "Poor" alignment needed for structs reflecting message formats/headers
+ - "-altera-struct-pack-align"
+
+ # TODO: check again if multithreading is implemented
+ - "-concurrency-mt-unsafe"
+
+ # Complains about any identifier <3 characters, reasonable for
+ # globals, pointlessly verbose for locals and parameters.
+ - "-readability-identifier-length"
+
+ # Wants to include headers which *directly* provide the things
+ # we use. That sounds nice, but means it will often want a OS
+ # specific header instead of a mostly standard one, such as
+ # <linux/limits.h> instead of <limits.h>.
+ - "-misc-include-cleaner"
+
+ # Want to replace all #defines of integers with enums. Kind of
+ # makes sense when those defines form an enum-like set, but
+ # weird for cases like standalone constants, and causes other
+ # awkwardness for a bunch of cases we use
+ - "-cppcoreguidelines-macro-to-enum"
+
+ # It's been a couple of centuries since multiplication has been granted
+ # precedence over addition in modern mathematical notation. Adding
+ # parentheses to reinforce that certainly won't improve readability.
+ - "-readability-math-missing-parentheses"
+WarningsAsErrors: "*"
+HeaderFileExtensions:
+ - h
+ImplementationFileExtensions:
+ - c
+HeaderFilterRegex: ""
+FormatStyle: none
+CheckOptions:
+ bugprone-suspicious-string-compare.WarnOnImplicitComparison: "false"
+SystemHeaders: false
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..41bec92
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,3 @@
+CompileFlags:
+ # Don't try to interpret our headers as C++'
+ Add: [-xc, -Wall]
diff --git a/Makefile b/Makefile
index b6329e3..464eef1 100644
--- a/Makefile
+++ b/Makefile
@@ -15,39 +15,32 @@ VERSION ?= $(shell git describe --tags HEAD 2>/dev/null || echo "unknown\ versio
# the IPv6 socket API? (Linux does)
DUAL_STACK_SOCKETS := 1
-RLIMIT_STACK_VAL := $(shell /bin/sh -c 'ulimit -s')
-ifeq ($(RLIMIT_STACK_VAL),unlimited)
-RLIMIT_STACK_VAL := 1024
-endif
-
TARGET ?= $(shell $(CC) -dumpmachine)
+$(if $(TARGET),,$(error Failed to get target architecture))
# Get 'uname -m'-like architecture description for target
-TARGET_ARCH := $(shell echo $(TARGET) | cut -f1 -d- | tr [A-Z] [a-z])
-TARGET_ARCH := $(shell echo $(TARGET_ARCH) | sed 's/powerpc/ppc/')
-
-AUDIT_ARCH := $(shell echo $(TARGET_ARCH) | tr [a-z] [A-Z] | sed 's/^ARM.*/ARM/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/I[456]86/I386/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPC64/PPC/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPCLE/PPC64LE/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/MIPS64EL/MIPSEL64/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/HPPA/PARISC/')
-AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/SH4/SH/')
+TARGET_ARCH := $(firstword $(subst -, ,$(TARGET)))
+TARGET_ARCH := $(patsubst [:upper:],[:lower:],$(TARGET_ARCH))
+TARGET_ARCH := $(subst powerpc,ppc,$(TARGET_ARCH))
+
+# On some systems enabling optimization also enables source fortification,
+# automagically. Do not override it.
+FORTIFY_FLAG :=
+ifeq ($(shell $(CC) -O2 -dM -E - < /dev/null 2>&1 | grep ' _FORTIFY_SOURCE ' > /dev/null; echo $$?),1)
+FORTIFY_FLAG := -D_FORTIFY_SOURCE=2
+endif
FLAGS := -Wall -Wextra -Wno-format-zero-length
FLAGS += -pedantic -std=c11 -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
-FLAGS += -D_FORTIFY_SOURCE=2 -O2 -pie -fPIE
+FLAGS += $(FORTIFY_FLAG) -O2 -pie -fPIE
FLAGS += -DPAGE_SIZE=$(shell getconf PAGE_SIZE)
-FLAGS += -DNETNS_RUN_DIR=\"/run/netns\"
-FLAGS += -DPASST_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH)
-FLAGS += -DRLIMIT_STACK_VAL=$(RLIMIT_STACK_VAL)
-FLAGS += -DARCH=\"$(TARGET_ARCH)\"
FLAGS += -DVERSION=\"$(VERSION)\"
FLAGS += -DDUAL_STACK_SOCKETS=$(DUAL_STACK_SOCKETS)
PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c flow.c fwd.c \
icmp.c igmp.c inany.c iov.c ip.c isolation.c lineread.c log.c mld.c \
ndp.c netlink.c packet.c passt.c pasta.c pcap.c pif.c tap.c tcp.c \
- tcp_buf.c tcp_splice.c udp.c udp_flow.c util.c
+ tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \
+ vhost_user.c virtio.c vu_common.c
QRAP_SRCS = qrap.c
SRCS = $(PASST_SRCS) $(QRAP_SRCS)
@@ -57,24 +50,10 @@ PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h flow.h fwd.h \
flow_table.h icmp.h icmp_flow.h inany.h iov.h ip.h isolation.h \
lineread.h log.h ndp.h netlink.h packet.h passt.h pasta.h pcap.h pif.h \
siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h \
- udp.h udp_flow.h util.h
+ tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h vhost_user.h \
+ virtio.h vu_common.h
HEADERS = $(PASST_HEADERS) seccomp.h
-C := \#include <linux/tcp.h>\nstruct tcp_info x = { .tcpi_snd_wnd = 0 };
-ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0)
- FLAGS += -DHAS_SND_WND
-endif
-
-C := \#include <linux/tcp.h>\nstruct tcp_info x = { .tcpi_bytes_acked = 0 };
-ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0)
- FLAGS += -DHAS_BYTES_ACKED
-endif
-
-C := \#include <linux/tcp.h>\nstruct tcp_info x = { .tcpi_min_rtt = 0 };
-ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0)
- FLAGS += -DHAS_MIN_RTT
-endif
-
C := \#include <sys/random.h>\nint main(){int a=getrandom(0, 0, 0);}
ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0)
FLAGS += -DHAS_GETRANDOM
@@ -84,11 +63,6 @@ ifeq ($(shell :|$(CC) -fstack-protector-strong -S -xc - -o - >/dev/null 2>&1; ec
FLAGS += -fstack-protector-strong
endif
-C := \#define _GNU_SOURCE\n\#include <fcntl.h>\nint x = FALLOC_FL_COLLAPSE_RANGE;
-ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0)
- EXTRA_SYSCALLS += fallocate
-endif
-
prefix ?= /usr/local
exec_prefix ?= $(prefix)
bindir ?= $(exec_prefix)/bin
@@ -125,11 +99,12 @@ pasta.avx2 pasta.1 pasta: pasta%: passt%
ln -sf $< $@
qrap: $(QRAP_SRCS) passt.h
- $(CC) $(FLAGS) $(CFLAGS) $(CPPFLAGS) $(QRAP_SRCS) -o qrap $(LDFLAGS)
+ $(CC) $(FLAGS) $(CFLAGS) $(CPPFLAGS) -DARCH=\"$(TARGET_ARCH)\" $(QRAP_SRCS) -o qrap $(LDFLAGS)
valgrind: EXTRA_SYSCALLS += rt_sigprocmask rt_sigtimedwait rt_sigaction \
rt_sigreturn getpid gettid kill clock_gettime mmap \
- munmap open unlink gettimeofday futex
+ mmap2 munmap open unlink gettimeofday futex statx \
+ readlink
valgrind: FLAGS += -g -DVALGRIND
valgrind: all
@@ -189,116 +164,11 @@ docs: README.md
done < README.md; \
) > README.plain.md
-# Checkers currently disabled for clang-tidy:
-# - llvmlibc-restrict-system-libc-headers
-# TODO: this is Linux-only for the moment, nice to fix eventually
-#
-# - google-readability-braces-around-statements
-# - hicpp-braces-around-statements
-# - readability-braces-around-statements
-# Debatable whether that improves readability, right now it would look
-# like a mess
-#
-# - readability-magic-numbers
-# - cppcoreguidelines-avoid-magic-numbers
-# TODO: in most cases they are justified, but probably not everywhere
-#
-# - clang-analyzer-valist.Uninitialized
-# TODO: enable once https://bugs.llvm.org/show_bug.cgi?id=41311 is fixed
-#
-# - clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
-# Probably not doable to impement this without plain memcpy(), memset()
-#
-# - cppcoreguidelines-init-variables
-# Dubious value, would kill readability
-#
-# - hicpp-signed-bitwise
-# Those are needed for syscalls, epoll_wait flags, etc.
-#
-# - llvm-include-order
-# TODO: not really important, but nice to fix eventually
-#
-# - readability-isolate-declaration
-# Dubious value, would kill readability
-#
-# - bugprone-narrowing-conversions
-# - cppcoreguidelines-narrowing-conversions
-# TODO: nice to fix eventually
-#
-# - cppcoreguidelines-avoid-non-const-global-variables
-# TODO: check, fix, and more in general constify wherever possible
-#
-# - altera-unroll-loops
-# - altera-id-dependent-backward-branch
-# TODO: check paths where it might make sense to improve performance
-#
-# - bugprone-easily-swappable-parameters
-# Not much can be done about them other than being careful
-#
-# - readability-function-cognitive-complexity
-# TODO: split reported functions
-#
-# - altera-struct-pack-align
-# "Poor" alignment needed for structs reflecting message formats/headers
-#
-# - concurrency-mt-unsafe
-# TODO: check again if multithreading is implemented
-#
-# - readability-identifier-length
-# Complains about any identifier <3 characters, reasonable for
-# globals, pointlessly verbose for locals and parameters.
-#
-# - bugprone-assignment-in-if-condition
-# Dubious value over the compiler's built-in warning. Would
-# increase verbosity.
-#
-# - misc-include-cleaner
-# Wants to include headers which *directly* provide the things
-# we use. That sounds nice, but means it will often want a OS
-# specific header instead of a mostly standard one, such as
-# <linux/limits.h> instead of <limits.h>.
-#
-# - cppcoreguidelines-macro-to-enum
-# Want to replace all #defines of integers with enums. Kind of
-# makes sense when those defines form an enum-like set, but
-# weird for cases like standalone constants, and causes other
-# awkwardness for a bunch of cases we use
+clang-tidy: $(PASST_SRCS) $(HEADERS)
+ clang-tidy $(PASST_SRCS) -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
+ -DCLANG_TIDY_58992
-clang-tidy: $(SRCS) $(HEADERS)
- clang-tidy -checks=*,-modernize-*,\
- -clang-analyzer-valist.Uninitialized,\
- -cppcoreguidelines-init-variables,\
- -bugprone-assignment-in-if-condition,\
- -google-readability-braces-around-statements,\
- -hicpp-braces-around-statements,\
- -readability-braces-around-statements,\
- -readability-magic-numbers,\
- -llvmlibc-restrict-system-libc-headers,\
- -hicpp-signed-bitwise,\
- -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,\
- -llvm-include-order,\
- -cppcoreguidelines-avoid-magic-numbers,\
- -readability-isolate-declaration,\
- -bugprone-narrowing-conversions,\
- -cppcoreguidelines-narrowing-conversions,\
- -cppcoreguidelines-avoid-non-const-global-variables,\
- -altera-unroll-loops,-altera-id-dependent-backward-branch,\
- -bugprone-easily-swappable-parameters,\
- -readability-function-cognitive-complexity,\
- -altera-struct-pack-align,\
- -concurrency-mt-unsafe,\
- -readability-identifier-length,\
- -misc-include-cleaner,\
- -cppcoreguidelines-macro-to-enum \
- -config='{CheckOptions: [{key: bugprone-suspicious-string-compare.WarnOnImplicitComparison, value: "false"}]}' \
- --warnings-as-errors=* $(SRCS) -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) -DCLANG_TIDY_58992
-
-SYSTEM_INCLUDES := /usr/include $(wildcard /usr/include/$(TARGET))
-ifeq ($(shell $(CC) -v 2>&1 | grep -c "gcc version"),1)
-VER := $(shell $(CC) -dumpversion)
-SYSTEM_INCLUDES += /usr/lib/gcc/$(TARGET)/$(VER)/include
-endif
-cppcheck: $(SRCS) $(HEADERS)
+cppcheck: $(PASST_SRCS) $(HEADERS)
if cppcheck --check-level=exhaustive /dev/null > /dev/null 2>&1; then \
CPPCHECK_EXHAUSTIVE="--check-level=exhaustive"; \
else \
@@ -307,11 +177,8 @@ cppcheck: $(SRCS) $(HEADERS)
cppcheck --std=c11 --error-exitcode=1 --enable=all --force \
--inconclusive --library=posix --quiet \
$${CPPCHECK_EXHAUSTIVE} \
- $(SYSTEM_INCLUDES:%=-I%) \
- $(SYSTEM_INCLUDES:%=--config-exclude=%) \
- $(SYSTEM_INCLUDES:%=--suppress=*:%/*) \
- $(SYSTEM_INCLUDES:%=--suppress=unmatchedSuppression:%/*) \
--inline-suppr \
+ --suppress=missingIncludeSystem \
--suppress=unusedStructMember \
- $(filter -D%,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
- $(SRCS) $(HEADERS)
+ $(filter -D%,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) -D CPPCHECK_6936 \
+ $(PASST_SRCS) $(HEADERS)
diff --git a/README.md b/README.md
index bb114ea..54fed07 100644
--- a/README.md
+++ b/README.md
@@ -321,7 +321,7 @@ speeding up local connections, and usually requiring NAT. _pasta_:
protocol
* ✅ 4 to 50 times IPv4 TCP throughput of existing, conceptually similar
solutions depending on MTU (UDP and IPv6 hard to compare)
-* 🛠 [_vhost-user_ support](https://bugs.passt.top/show_bug.cgi?id=25) for
+* ✅ [_vhost-user_ support](https://bugs.passt.top/show_bug.cgi?id=25) for
maximum one copy on every data path and lower request-response latency
* ⌚ [multithreading](https://bugs.passt.top/show_bug.cgi?id=13)
* ⌚ [raw IP socket support](https://bugs.passt.top/show_bug.cgi?id=14) if
@@ -338,7 +338,9 @@ speeding up local connections, and usually requiring NAT. _pasta_:
[_slirp4netns_ replacement](/passt/tree/slirp4netns.sh)
* ✅ out-of-tree patch for
[Kata Containers](/passt/tree/contrib/kata-containers) available
-* ⌚ drop-in replacement for VPNKit (rootless Docker)
+* ✅ rootless Docker
+ [network back-end](https://docs.docker.com/engine/security/rootless/#networking-errors)
+ via moby/rootlesskit
### Availability
* official packages for:
diff --git a/arch.c b/arch.c
index 04bebfc..e1ee729 100644
--- a/arch.c
+++ b/arch.c
@@ -19,6 +19,7 @@
#include <unistd.h>
#include "log.h"
+#include "util.h"
/**
* arch_avx2_exec() - Switch to AVX2 build if supported
@@ -40,8 +41,11 @@ void arch_avx2_exec(char **argv)
if (__builtin_cpu_supports("avx2")) {
char new_path[PATH_MAX + sizeof(".avx2")];
- snprintf(new_path, PATH_MAX + sizeof(".avx2"), "%s.avx2", exe);
- execve(new_path, argv, environ);
+ if (snprintf_check(new_path, PATH_MAX + sizeof(".avx2"),
+ "%s.avx2", exe))
+ die_perror("Can't build AVX2 executable path");
+
+ execv(new_path, argv);
warn_perror("Can't run AVX2 build, using non-AVX2 version");
}
}
diff --git a/arp.c b/arp.c
index 93b22c5..fc482bb 100644
--- a/arp.c
+++ b/arp.c
@@ -59,20 +59,18 @@ int arp(const struct ctx *c, const struct pool *p)
ah->ar_op != htons(ARPOP_REQUEST))
return 1;
- /* Discard announcements (but not 0.0.0.0 "probes"): we might have the
- * same IP address, hide that.
- */
- if (memcmp(am->sip, (unsigned char[4]){ 0 }, sizeof(am->tip)) &&
+ /* Discard announcements, but not 0.0.0.0 "probes" */
+ if (memcmp(am->sip, &in4addr_any, sizeof(am->sip)) &&
!memcmp(am->sip, am->tip, sizeof(am->sip)))
return 1;
- /* Don't resolve our own address, either. */
+ /* Don't resolve the guest's assigned address, either. */
if (!memcmp(am->tip, &c->ip4.addr, sizeof(am->tip)))
return 1;
ah->ar_op = htons(ARPOP_REPLY);
memcpy(am->tha, am->sha, sizeof(am->tha));
- memcpy(am->sha, c->mac, sizeof(am->sha));
+ memcpy(am->sha, c->our_tap_mac, sizeof(am->sha));
memcpy(swap, am->tip, sizeof(am->tip));
memcpy(am->tip, am->sip, sizeof(am->tip));
@@ -80,7 +78,7 @@ int arp(const struct ctx *c, const struct pool *p)
l2len = sizeof(*eh) + sizeof(*ah) + sizeof(*am);
memcpy(eh->h_dest, eh->h_source, sizeof(eh->h_dest));
- memcpy(eh->h_source, c->mac, sizeof(eh->h_source));
+ memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
tap_send_single(c, eh, l2len);
diff --git a/checksum.c b/checksum.c
index 006614f..b01e0fe 100644
--- a/checksum.c
+++ b/checksum.c
@@ -59,6 +59,7 @@
#include "util.h"
#include "ip.h"
#include "checksum.h"
+#include "iov.h"
/* Checksums are optional for UDP over IPv4, so we usually just set
* them to 0. Change this to 1 to calculate real UDP over IPv4
@@ -165,22 +166,22 @@ uint32_t proto_ipv4_header_psum(uint16_t l4len, uint8_t protocol,
* @udp4hr: UDP header, initialised apart from checksum
* @saddr: IPv4 source address
* @daddr: IPv4 destination address
- * @payload: UDP packet payload
- * @dlen: Length of @payload (not including UDP header)
+ * @data: UDP payload (as IO vector tail)
*/
void csum_udp4(struct udphdr *udp4hr,
struct in_addr saddr, struct in_addr daddr,
- const void *payload, size_t dlen)
+ struct iov_tail *data)
{
/* UDP checksums are optional, so don't bother */
udp4hr->check = 0;
if (UDP4_REAL_CHECKSUMS) {
- uint16_t l4len = dlen + sizeof(struct udphdr);
+ uint16_t l4len = iov_tail_size(data) + sizeof(struct udphdr);
uint32_t psum = proto_ipv4_header_psum(l4len, IPPROTO_UDP,
saddr, daddr);
+
psum = csum_unfolded(udp4hr, sizeof(struct udphdr), psum);
- udp4hr->check = csum(payload, dlen, psum);
+ udp4hr->check = csum_iov_tail(data, psum);
}
}
@@ -226,19 +227,22 @@ uint32_t proto_ipv6_header_psum(uint16_t payload_len, uint8_t protocol,
/**
* csum_udp6() - Calculate and set checksum for a UDP over IPv6 packet
* @udp6hr: UDP header, initialised apart from checksum
- * @payload: UDP packet payload
- * @dlen: Length of @payload (not including UDP header)
+ * @saddr: Source address
+ * @daddr: Destination address
+ * @data: UDP payload (as IO vector tail)
*/
void csum_udp6(struct udphdr *udp6hr,
const struct in6_addr *saddr, const struct in6_addr *daddr,
- const void *payload, size_t dlen)
+ struct iov_tail *data)
{
- uint32_t psum = proto_ipv6_header_psum(dlen + sizeof(struct udphdr),
- IPPROTO_UDP, saddr, daddr);
+ uint16_t l4len = iov_tail_size(data) + sizeof(struct udphdr);
+ uint32_t psum = proto_ipv6_header_psum(l4len, IPPROTO_UDP,
+ saddr, daddr);
+
udp6hr->check = 0;
psum = csum_unfolded(udp6hr, sizeof(struct udphdr), psum);
- udp6hr->check = csum(payload, dlen, psum);
+ udp6hr->check = csum_iov_tail(data, psum);
}
/**
@@ -448,7 +452,8 @@ uint32_t csum_unfolded(const void *buf, size_t len, uint32_t init)
intptr_t align = ROUND_UP((intptr_t)buf, sizeof(__m256i));
unsigned int pad = align - (intptr_t)buf;
- if (len < pad)
+ /* Don't mix sum_16b() and csum_avx2() with odd padding lengths */
+ if (pad & 1 || len < pad)
pad = len;
if (pad)
@@ -493,21 +498,23 @@ uint16_t csum(const void *buf, size_t len, uint32_t init)
}
/**
- * csum_iov() - Calculates the unfolded checksum over an array of IO vectors
- *
- * @iov Pointer to the array of IO vectors
- * @n Length of the array
+ * csum_iov_tail() - Calculate unfolded checksum for the tail of an IO vector
+ * @tail: IO vector tail to checksum
* @init Initial 32-bit checksum, 0 for no pre-computed checksum
*
* Return: 16-bit folded, complemented checksum
*/
-/* cppcheck-suppress unusedFunction */
-uint16_t csum_iov(const struct iovec *iov, size_t n, uint32_t init)
+uint16_t csum_iov_tail(struct iov_tail *tail, uint32_t init)
{
- unsigned int i;
-
- for (i = 0; i < n; i++)
- init = csum_unfolded(iov[i].iov_base, iov[i].iov_len, init);
-
+ if (iov_tail_prune(tail)) {
+ size_t i;
+
+ init = csum_unfolded((char *)tail->iov[0].iov_base + tail->off,
+ tail->iov[0].iov_len - tail->off, init);
+ for (i = 1; i < tail->cnt; i++) {
+ const struct iovec *iov = &tail->iov[i];
+ init = csum_unfolded(iov->iov_base, iov->iov_len, init);
+ }
+ }
return (uint16_t)~csum_fold(init);
}
diff --git a/checksum.h b/checksum.h
index c5964ac..e243c97 100644
--- a/checksum.h
+++ b/checksum.h
@@ -9,6 +9,7 @@
struct udphdr;
struct icmphdr;
struct icmp6hdr;
+struct iov_tail;
uint32_t sum_16b(const void *buf, size_t len);
uint16_t csum_fold(uint32_t sum);
@@ -19,19 +20,19 @@ uint32_t proto_ipv4_header_psum(uint16_t l4len, uint8_t protocol,
struct in_addr saddr, struct in_addr daddr);
void csum_udp4(struct udphdr *udp4hr,
struct in_addr saddr, struct in_addr daddr,
- const void *payload, size_t dlen);
+ struct iov_tail *data);
void csum_icmp4(struct icmphdr *icmp4hr, const void *payload, size_t dlen);
uint32_t proto_ipv6_header_psum(uint16_t payload_len, uint8_t protocol,
const struct in6_addr *saddr,
const struct in6_addr *daddr);
void csum_udp6(struct udphdr *udp6hr,
const struct in6_addr *saddr, const struct in6_addr *daddr,
- const void *payload, size_t dlen);
+ struct iov_tail *data);
void csum_icmp6(struct icmp6hdr *icmp6hr,
const struct in6_addr *saddr, const struct in6_addr *daddr,
const void *payload, size_t dlen);
uint32_t csum_unfolded(const void *buf, size_t len, uint32_t init);
uint16_t csum(const void *buf, size_t len, uint32_t init);
-uint16_t csum_iov(const struct iovec *iov, size_t n, uint32_t init);
+uint16_t csum_iov_tail(struct iov_tail *tail, uint32_t init);
#endif /* CHECKSUM_H */
diff --git a/conf.c b/conf.c
index ed097bd..df2b016 100644
--- a/conf.c
+++ b/conf.c
@@ -45,6 +45,23 @@
#include "lineread.h"
#include "isolation.h"
#include "log.h"
+#include "vhost_user.h"
+
+#define NETNS_RUN_DIR "/run/netns"
+
+#define IP4_LL_GUEST_ADDR (struct in_addr){ htonl_constant(0xa9fe0201) }
+ /* 169.254.2.1, libslirp default: 10.0.2.1 */
+
+#define IP4_LL_GUEST_GW (struct in_addr){ htonl_constant(0xa9fe0202) }
+ /* 169.254.2.2, libslirp default: 10.0.2.2 */
+
+#define IP4_LL_PREFIX_LEN 16
+
+#define IP6_LL_GUEST_GW (struct in6_addr) \
+ {{{ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, \
+ 0, 0, 0, 0, 0, 0, 0, 0x01 }}}
+
+const char *pasta_default_ifn = "tap0";
/**
* next_chunk - Return the next piece of a string delimited by a character
@@ -116,11 +133,10 @@ static int parse_port_range(const char *s, char **endptr,
static void conf_ports(const struct ctx *c, char optname, const char *optarg,
struct fwd_ports *fwd)
{
- char addr_buf[sizeof(struct in6_addr)] = { 0 }, *addr = addr_buf;
+ union inany_addr addr_buf = inany_any6, *addr = &addr_buf;
char buf[BUFSIZ], *spec, *ifname = NULL, *p;
bool exclude_only = true, bound_one = false;
uint8_t exclude[PORT_BITMAP_SIZE] = { 0 };
- sa_family_t af = AF_UNSPEC;
unsigned i;
int ret;
@@ -156,19 +172,23 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
die("'all' port forwarding is only allowed for passt");
fwd->mode = FWD_ALL;
- memset(fwd->map, 0xff, PORT_EPHEMERAL_MIN / 8);
- for (i = 0; i < PORT_EPHEMERAL_MIN; i++) {
+ /* Skip port 0. It has special meaning for many socket APIs, so
+ * trying to bind it is not really safe.
+ */
+ for (i = 1; i < NUM_PORTS; i++) {
+ if (fwd_port_is_ephemeral(i))
+ continue;
+
+ bitmap_set(fwd->map, i);
if (optname == 't') {
- ret = tcp_sock_init(c, AF_UNSPEC, NULL, NULL,
- i);
+ ret = tcp_sock_init(c, NULL, NULL, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
} else if (optname == 'u') {
- ret = udp_sock_init(c, 0, AF_UNSPEC, NULL, NULL,
- i);
+ ret = udp_sock_init(c, 0, NULL, NULL, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
@@ -220,11 +240,7 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
p++;
}
- if (inet_pton(AF_INET, p, addr))
- af = AF_INET;
- else if (inet_pton(AF_INET6, p, addr))
- af = AF_INET6;
- else
+ if (!inany_pton(p, addr))
goto bad;
}
} else {
@@ -259,20 +275,24 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
} while ((p = next_chunk(p, ',')));
if (exclude_only) {
- for (i = 0; i < PORT_EPHEMERAL_MIN; i++) {
- if (bitmap_isset(exclude, i))
+ /* Skip port 0. It has special meaning for many socket APIs, so
+ * trying to bind it is not really safe.
+ */
+ for (i = 1; i < NUM_PORTS; i++) {
+ if (fwd_port_is_ephemeral(i) ||
+ bitmap_isset(exclude, i))
continue;
bitmap_set(fwd->map, i);
if (optname == 't') {
- ret = tcp_sock_init(c, af, addr, ifname, i);
+ ret = tcp_sock_init(c, addr, ifname, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
} else if (optname == 'u') {
- ret = udp_sock_init(c, 0, af, addr, ifname, i);
+ ret = udp_sock_init(c, 0, addr, ifname, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
@@ -328,9 +348,9 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
ret = 0;
if (optname == 't')
- ret = tcp_sock_init(c, af, addr, ifname, i);
+ ret = tcp_sock_init(c, addr, ifname, i);
else if (optname == 'u')
- ret = udp_sock_init(c, 0, af, addr, ifname, i);
+ ret = udp_sock_init(c, 0, addr, ifname, i);
if (ret)
goto bind_fail;
}
@@ -345,7 +365,7 @@ mode_conflict:
die("Port forwarding mode '%s' conflicts with previous mode", optarg);
bind_fail:
die("Failed to bind port %u (%s) for option '-%c %s', exiting",
- i, strerror(-ret), optname, optarg);
+ i, strerror_(-ret), optname, optarg);
bind_all_fail:
die("Failed to bind any port for '-%c %s', exiting", optname, optarg);
}
@@ -353,55 +373,93 @@ bind_all_fail:
/**
* add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration
* @c: Execution context
- * @addr: Address found in /etc/resolv.conf
- * @conf: Pointer to reference of current entry in array of IPv4 resolvers
+ * @addr: Guest nameserver IPv4 address
+ * @idx: Index of free entry in array of IPv4 resolvers
+ *
+ * Return: Number of entries added (0 or 1)
*/
-static void add_dns4(struct ctx *c, const struct in_addr *addr,
- struct in_addr **conf)
+static unsigned add_dns4(struct ctx *c, const struct in_addr *addr,
+ unsigned idx)
{
- /* Guest or container can only access local addresses via redirect */
- if (IN4_IS_ADDR_LOOPBACK(addr)) {
- if (!c->no_map_gw) {
- **conf = c->ip4.gw;
- (*conf)++;
-
- if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match))
- c->ip4.dns_match = c->ip4.gw;
- }
- } else {
- **conf = *addr;
- (*conf)++;
- }
+ if (idx >= ARRAY_SIZE(c->ip4.dns))
+ return 0;
- if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host))
- c->ip4.dns_host = *addr;
+ c->ip4.dns[idx] = *addr;
+ return 1;
}
/**
* add_dns6() - Possibly add the IPv6 address of a DNS resolver to configuration
* @c: Execution context
- * @addr: Address found in /etc/resolv.conf
- * @conf: Pointer to reference of current entry in array of IPv6 resolvers
+ * @addr: Guest nameserver IPv6 address
+ * @idx: Index of free entry in array of IPv6 resolvers
+ *
+ * Return: Number of entries added (0 or 1)
*/
-static void add_dns6(struct ctx *c,
- struct in6_addr *addr, struct in6_addr **conf)
+static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr,
+ unsigned idx)
{
- /* Guest or container can only access local addresses via redirect */
- if (IN6_IS_ADDR_LOOPBACK(addr)) {
- if (!c->no_map_gw) {
- memcpy(*conf, &c->ip6.gw, sizeof(**conf));
- (*conf)++;
+ if (idx >= ARRAY_SIZE(c->ip6.dns))
+ return 0;
- if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match))
- memcpy(&c->ip6.dns_match, addr, sizeof(*addr));
+ c->ip6.dns[idx] = *addr;
+ return 1;
+}
+
+/**
+ * add_dns_resolv() - Possibly add ns from host resolv.conf to configuration
+ * @c: Execution context
+ * @nameserver: Nameserver address string from /etc/resolv.conf
+ * @idx4: Pointer to index of current entry in array of IPv4 resolvers
+ * @idx6: Pointer to index of current entry in array of IPv6 resolvers
+ *
+ * @idx4 or @idx6 may be NULL, in which case resolvers of the corresponding type
+ * are ignored.
+ */
+static void add_dns_resolv(struct ctx *c, const char *nameserver,
+ unsigned *idx4, unsigned *idx6)
+{
+ struct in6_addr ns6;
+ struct in_addr ns4;
+
+ if (idx4 && inet_pton(AF_INET, nameserver, &ns4)) {
+ if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host))
+ c->ip4.dns_host = ns4;
+
+ /* Guest or container can only access local addresses via
+ * redirect
+ */
+ if (IN4_IS_ADDR_LOOPBACK(&ns4)) {
+ if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
+ return;
+
+ ns4 = c->ip4.map_host_loopback;
+ if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match))
+ c->ip4.dns_match = c->ip4.map_host_loopback;
}
- } else {
- memcpy(*conf, addr, sizeof(**conf));
- (*conf)++;
+
+ *idx4 += add_dns4(c, &ns4, *idx4);
}
- if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host))
- c->ip6.dns_host = *addr;
+ if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host))
+ c->ip6.dns_host = ns6;
+
+ /* Guest or container can only access local addresses via
+ * redirect
+ */
+ if (IN6_IS_ADDR_LOOPBACK(&ns6)) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
+ return;
+
+ ns6 = c->ip6.map_host_loopback;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match))
+ c->ip6.dns_match = c->ip6.map_host_loopback;
+ }
+
+ *idx6 += add_dns6(c, &ns6, *idx6);
+ }
}
/**
@@ -410,18 +468,16 @@ static void add_dns6(struct ctx *c,
*/
static void get_dns(struct ctx *c)
{
- struct in6_addr *dns6 = &c->ip6.dns[0], dns6_tmp;
- struct in_addr *dns4 = &c->ip4.dns[0], dns4_tmp;
int dns4_set, dns6_set, dnss_set, dns_set, fd;
+ unsigned dns4_idx = 0, dns6_idx = 0;
struct fqdn *s = c->dns_search;
struct lineread resolvconf;
- unsigned int added = 0;
ssize_t line_len;
char *line, *end;
const char *p;
- dns4_set = !c->ifi4 || !IN4_IS_ADDR_UNSPECIFIED(dns4);
- dns6_set = !c->ifi6 || !IN6_IS_ADDR_UNSPECIFIED(dns6);
+ dns4_set = !c->ifi4 || !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[0]);
+ dns6_set = !c->ifi6 || !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[0]);
dnss_set = !!*s->n || c->no_dns_search;
dns_set = (dns4_set && dns6_set) || c->no_dns;
@@ -442,19 +498,9 @@ static void get_dns(struct ctx *c)
if (end)
*end = 0;
- if (!dns4_set &&
- dns4 - &c->ip4.dns[0] < ARRAY_SIZE(c->ip4.dns) - 1
- && inet_pton(AF_INET, p + 1, &dns4_tmp)) {
- add_dns4(c, &dns4_tmp, &dns4);
- added++;
- }
-
- if (!dns6_set &&
- dns6 - &c->ip6.dns[0] < ARRAY_SIZE(c->ip6.dns) - 1
- && inet_pton(AF_INET6, p + 1, &dns6_tmp)) {
- add_dns6(c, &dns6_tmp, &dns6);
- added++;
- }
+ add_dns_resolv(c, p + 1,
+ dns4_set ? NULL : &dns4_idx,
+ dns6_set ? NULL : &dns6_idx);
} else if (!dnss_set && strstr(line, "search ") == line &&
s == c->dns_search) {
end = strpbrk(line, "\n");
@@ -481,7 +527,7 @@ static void get_dns(struct ctx *c)
out:
if (!dns_set) {
- if (!added)
+ if (!(dns4_idx + dns6_idx))
warn("Couldn't get any nameserver address");
if (c->no_dhcp_dns)
@@ -545,10 +591,15 @@ static void conf_pasta_ns(int *netns_only, char *userns, char *netns,
if (pidval < 0 || pidval > INT_MAX)
die("Invalid PID %s", argv[optind]);
- snprintf(netns, PATH_MAX, "/proc/%ld/ns/net", pidval);
- if (!*userns)
- snprintf(userns, PATH_MAX, "/proc/%ld/ns/user",
- pidval);
+ if (snprintf_check(netns, PATH_MAX,
+ "/proc/%ld/ns/net", pidval))
+ die_perror("Can't build netns path");
+
+ if (!*userns) {
+ if (snprintf_check(userns, PATH_MAX,
+ "/proc/%ld/ns/user", pidval))
+ die_perror("Can't build userns path");
+ }
}
}
@@ -586,26 +637,25 @@ static int conf_ip4_prefix(const char *arg)
* conf_ip4() - Verify or detect IPv4 support, get relevant addresses
* @ifi: Host interface to attempt (0 to determine one)
* @ip4: IPv4 context (will be written)
- * @mac: MAC address to use (written if unset)
*
* Return: Interface index for IPv4, or 0 on failure.
*/
-static unsigned int conf_ip4(unsigned int ifi,
- struct ip4_ctx *ip4, unsigned char *mac)
+static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4)
{
if (!ifi)
ifi = nl_get_ext_if(nl_sock, AF_INET);
if (!ifi) {
- info("Couldn't pick external interface: disabling IPv4");
+ debug("Failed to detect external interface for IPv4");
return 0;
}
- if (IN4_IS_ADDR_UNSPECIFIED(&ip4->gw)) {
- int rc = nl_route_get_def(nl_sock, ifi, AF_INET, &ip4->gw);
+ if (IN4_IS_ADDR_UNSPECIFIED(&ip4->guest_gw)) {
+ int rc = nl_route_get_def(nl_sock, ifi, AF_INET,
+ &ip4->guest_gw);
if (rc < 0) {
- err("Couldn't discover IPv4 gateway address: %s",
- strerror(-rc));
+ debug("Couldn't discover IPv4 gateway address: %s",
+ strerror_(-rc));
return 0;
}
}
@@ -614,8 +664,8 @@ static unsigned int conf_ip4(unsigned int ifi,
int rc = nl_addr_get(nl_sock, ifi, AF_INET,
&ip4->addr, &ip4->prefix_len, NULL);
if (rc < 0) {
- err("Couldn't discover IPv4 address: %s",
- strerror(-rc));
+ debug("Couldn't discover IPv4 address: %s",
+ strerror_(-rc));
return 0;
}
}
@@ -632,21 +682,9 @@ static unsigned int conf_ip4(unsigned int ifi,
ip4->prefix_len = 32;
}
- memcpy(&ip4->addr_seen, &ip4->addr, sizeof(ip4->addr_seen));
-
- if (MAC_IS_ZERO(mac)) {
- int rc = nl_link_get_mac(nl_sock, ifi, mac);
- if (rc < 0) {
- char ifname[IFNAMSIZ];
-
- err("Couldn't discover MAC address for %s: %s",
- if_indextoname(ifi, ifname), strerror(-rc));
- return 0;
- }
+ ip4->addr_seen = ip4->addr;
- if (MAC_IS_ZERO(mac))
- memcpy(mac, MAC_LAA, ETH_ALEN);
- }
+ ip4->our_tap_addr = ip4->guest_gw;
if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr))
return 0;
@@ -655,15 +693,26 @@ static unsigned int conf_ip4(unsigned int ifi,
}
/**
+ * conf_ip4_local() - Configure IPv4 addresses and attributes for local mode
+ * @ip4: IPv4 context (will be written)
+ */
+static void conf_ip4_local(struct ip4_ctx *ip4)
+{
+ ip4->addr_seen = ip4->addr = IP4_LL_GUEST_ADDR;
+ ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW;
+ ip4->prefix_len = IP4_LL_PREFIX_LEN;
+
+ ip4->no_copy_addrs = ip4->no_copy_routes = true;
+}
+
+/**
* conf_ip6() - Verify or detect IPv6 support, get relevant addresses
* @ifi: Host interface to attempt (0 to determine one)
* @ip6: IPv6 context (will be written)
- * @mac: MAC address to use (written if unset)
*
* Return: Interface index for IPv6, or 0 on failure.
*/
-static unsigned int conf_ip6(unsigned int ifi,
- struct ip6_ctx *ip6, unsigned char *mac)
+static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6)
{
int prefix_len = 0;
int rc;
@@ -672,51 +721,51 @@ static unsigned int conf_ip6(unsigned int ifi,
ifi = nl_get_ext_if(nl_sock, AF_INET6);
if (!ifi) {
- info("Couldn't pick external interface: disabling IPv6");
+ debug("Failed to detect external interface for IPv6");
return 0;
}
- if (IN6_IS_ADDR_UNSPECIFIED(&ip6->gw)) {
- rc = nl_route_get_def(nl_sock, ifi, AF_INET6, &ip6->gw);
+ if (IN6_IS_ADDR_UNSPECIFIED(&ip6->guest_gw)) {
+ rc = nl_route_get_def(nl_sock, ifi, AF_INET6, &ip6->guest_gw);
if (rc < 0) {
- err("Couldn't discover IPv6 gateway address: %s",
- strerror(-rc));
+ debug("Couldn't discover IPv6 gateway address: %s",
+ strerror_(-rc));
return 0;
}
}
rc = nl_addr_get(nl_sock, ifi, AF_INET6,
IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL,
- &prefix_len, &ip6->addr_ll);
+ &prefix_len, &ip6->our_tap_ll);
if (rc < 0) {
- err("Couldn't discover IPv6 address: %s", strerror(-rc));
+ debug("Couldn't discover IPv6 address: %s", strerror_(-rc));
return 0;
}
- memcpy(&ip6->addr_seen, &ip6->addr, sizeof(ip6->addr));
- memcpy(&ip6->addr_ll_seen, &ip6->addr_ll, sizeof(ip6->addr_ll));
+ ip6->addr_seen = ip6->addr;
- if (MAC_IS_ZERO(mac)) {
- rc = nl_link_get_mac(nl_sock, ifi, mac);
- if (rc < 0) {
- char ifname[IFNAMSIZ];
- err("Couldn't discover MAC address for %s: %s",
- if_indextoname(ifi, ifname), strerror(-rc));
- return 0;
- }
-
- if (MAC_IS_ZERO(mac))
- memcpy(mac, MAC_LAA, ETH_ALEN);
- }
+ if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw))
+ ip6->our_tap_ll = ip6->guest_gw;
if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ||
- IN6_IS_ADDR_UNSPECIFIED(&ip6->addr_ll))
+ IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll))
return 0;
return ifi;
}
/**
+ * conf_ip6_local() - Configure IPv6 addresses and attributes for local mode
+ * @ip6: IPv6 context (will be written)
+ */
+static void conf_ip6_local(struct ip6_ctx *ip6)
+{
+ ip6->our_tap_ll = ip6->guest_gw = IP6_LL_GUEST_GW;
+
+ ip6->no_copy_addrs = ip6->no_copy_routes = true;
+}
+
+/**
* usage() - Print usage, exit with given status code
* @name: Executable name
* @f: Stream to print usage info to
@@ -725,19 +774,19 @@ static unsigned int conf_ip6(unsigned int ifi,
static void usage(const char *name, FILE *f, int status)
{
if (strstr(name, "pasta")) {
- fprintf(f, "Usage: %s [OPTION]... [COMMAND] [ARGS]...\n", name);
- fprintf(f, " %s [OPTION]... PID\n", name);
- fprintf(f, " %s [OPTION]... --netns [PATH|NAME]\n", name);
- fprintf(f,
+ FPRINTF(f, "Usage: %s [OPTION]... [COMMAND] [ARGS]...\n", name);
+ FPRINTF(f, " %s [OPTION]... PID\n", name);
+ FPRINTF(f, " %s [OPTION]... --netns [PATH|NAME]\n", name);
+ FPRINTF(f,
"\n"
"Without PID or --netns, run the given command or a\n"
"default shell in a new network and user namespace, and\n"
"connect it via pasta.\n");
} else {
- fprintf(f, "Usage: %s [OPTION]...\n", name);
+ FPRINTF(f, "Usage: %s [OPTION]...\n", name);
}
- fprintf(f,
+ FPRINTF(f,
"\n"
" -d, --debug Be verbose\n"
" --trace Be extra verbose, implies --debug\n"
@@ -754,17 +803,22 @@ static void usage(const char *name, FILE *f, int status)
" --version Show version and exit\n");
if (strstr(name, "pasta")) {
- fprintf(f,
+ FPRINTF(f,
" -I, --ns-ifname NAME namespace interface name\n"
" default: same interface name as external one\n");
} else {
- fprintf(f,
- " -s, --socket PATH UNIX domain socket path\n"
+ FPRINTF(f,
+ " -s, --socket, --socket-path PATH UNIX domain socket path\n"
" default: probe free path starting from "
UNIX_SOCK_PATH "\n", 1);
+ FPRINTF(f,
+ " --vhost-user Enable vhost-user mode\n"
+ " UNIX domain socket is provided by -s option\n"
+ " --print-capabilities print back-end capabilities in JSON format,\n"
+ " only meaningful for vhost-user mode\n");
}
- fprintf(f,
+ FPRINTF(f,
" -F, --fd FD Use FD as pre-opened connected socket\n"
" -p, --pcap FILE Log tap-facing traffic to pcap file\n"
" -P, --pid FILE Write own PID to the given file\n"
@@ -778,7 +832,7 @@ static void usage(const char *name, FILE *f, int status)
" -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits\n"
" default: netmask from matching address on the host\n"
" -M, --mac-addr ADDR Use source MAC address ADDR\n"
- " default: MAC address from interface with default route\n"
+ " default: 9a:55:9a:55:9a:55 (locally administered)\n"
" -g, --gateway ADDR Pass IPv4 or IPv6 address as gateway\n"
" default: gateway from interface with default route\n"
" -i, --interface NAME Interface for addresses and routes\n"
@@ -795,31 +849,40 @@ static void usage(const char *name, FILE *f, int status)
" can be specified multiple times\n"
" a single, empty option disables DNS information\n");
if (strstr(name, "pasta"))
- fprintf(f, " default: don't use any addresses\n");
+ FPRINTF(f, " default: don't use any addresses\n");
else
- fprintf(f, " default: use addresses from /etc/resolv.conf\n");
- fprintf(f,
+ FPRINTF(f, " default: use addresses from /etc/resolv.conf\n");
+ FPRINTF(f,
" -S, --search LIST Space-separated list, search domains\n"
" a single, empty option disables the DNS search list\n");
if (strstr(name, "pasta"))
- fprintf(f, " default: don't use any search list\n");
+ FPRINTF(f, " default: don't use any search list\n");
else
- fprintf(f, " default: use search list from /etc/resolv.conf\n");
+ FPRINTF(f, " default: use search list from /etc/resolv.conf\n");
if (strstr(name, "pasta"))
- fprintf(f, " --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP\n");
+ FPRINTF(f, " --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP\n");
else
- fprintf(f, " --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP\n");
+ FPRINTF(f, " --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP\n");
if (strstr(name, "pasta"))
- fprintf(f, " --dhcp-search Pass list via DHCP/DHCPv6/NDP\n");
+ FPRINTF(f, " --dhcp-search Pass list via DHCP/DHCPv6/NDP\n");
else
- fprintf(f, " --no-dhcp-search No list in DHCP/DHCPv6/NDP\n");
-
- fprintf(f,
+ FPRINTF(f, " --no-dhcp-search No list in DHCP/DHCPv6/NDP\n");
+
+ FPRINTF(f,
+ " --map-host-loopback ADDR Translate ADDR to refer to host\n"
+ " can be specified zero to two times (for IPv4 and IPv6)\n"
+ " default: gateway address\n"
+ " --map-guest-addr ADDR Translate ADDR to guest's address\n"
+ " can be specified zero to two times (for IPv4 and IPv6)\n"
+ " default: none\n"
" --dns-forward ADDR Forward DNS queries sent to ADDR\n"
" can be specified zero to two times (for IPv4 and IPv6)\n"
" default: don't forward DNS queries\n"
+ " --dns-host ADDR Host nameserver to direct queries to\n"
+ " can be specified zero to two times (for IPv4 and IPv6)\n"
+ " default: first nameserver from host's /etc/resolv.conf\n"
" --no-tcp Disable TCP protocol handler\n"
" --no-udp Disable UDP protocol handler\n"
" --no-icmp Disable ICMP/ICMPv6 protocol handler\n"
@@ -827,6 +890,7 @@ static void usage(const char *name, FILE *f, int status)
" --no-ndp Disable NDP responses\n"
" --no-dhcpv6 Disable DHCPv6 server\n"
" --no-ra Disable router advertisements\n"
+ " --freebind Bind to any address for forwarding\n"
" --no-map-gw Don't map gateway address to host\n"
" -4, --ipv4-only Enable IPv4 operation only\n"
" -6, --ipv6-only Enable IPv6 operation only\n");
@@ -834,7 +898,7 @@ static void usage(const char *name, FILE *f, int status)
if (strstr(name, "pasta"))
goto pasta_opts;
- fprintf(f,
+ FPRINTF(f,
" -1, --one-off Quit after handling one single client\n"
" -t, --tcp-ports SPEC TCP port forwarding to guest\n"
" can be specified multiple times\n"
@@ -865,7 +929,7 @@ static void usage(const char *name, FILE *f, int status)
pasta_opts:
- fprintf(f,
+ FPRINTF(f,
" -t, --tcp-ports SPEC TCP port forwarding to namespace\n"
" can be specified multiple times\n"
" SPEC can be:\n"
@@ -899,6 +963,9 @@ pasta_opts:
" -U, --udp-ns SPEC UDP port forwarding to init namespace\n"
" SPEC is as described above\n"
" default: auto\n"
+ " --host-lo-to-ns-lo DEPRECATED:\n"
+ " Translate host-loopback forwards to\n"
+ " namespace loopback\n"
" --userns NSPATH Target user namespace to join\n"
" --netns PATH|NAME Target network namespace to join\n"
" --netns-only Don't join existing user namespace\n"
@@ -910,7 +977,8 @@ pasta_opts:
" Don't copy all routes to namespace\n"
" --no-copy-addrs DEPRECATED:\n"
" Don't copy all addresses to namespace\n"
- " --ns-mac-addr ADDR Set MAC address on tap interface\n");
+ " --ns-mac-addr ADDR Set MAC address on tap interface\n"
+ " --no-splice Disable inbound socket splicing\n");
exit(status);
}
@@ -921,15 +989,18 @@ pasta_opts:
*/
static void conf_print(const struct ctx *c)
{
- char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN], ifn[IFNAMSIZ];
+ char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN];
+ char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ];
int i;
- info("Template interface: %s%s%s%s%s",
- c->ifi4 ? if_indextoname(c->ifi4, ifn) : "",
- c->ifi4 ? " (IPv4)" : "",
- (c->ifi4 && c->ifi6) ? ", " : "",
- c->ifi6 ? if_indextoname(c->ifi6, ifn) : "",
- c->ifi6 ? " (IPv6)" : "");
+ if (c->ifi4 > 0 || c->ifi6 > 0) {
+ info("Template interface: %s%s%s%s%s",
+ c->ifi4 > 0 ? if_indextoname(c->ifi4, ifn) : "",
+ c->ifi4 > 0 ? " (IPv4)" : "",
+ (c->ifi4 && c->ifi6) ? ", " : "",
+ c->ifi6 > 0 ? if_indextoname(c->ifi6, ifn) : "",
+ c->ifi6 > 0 ? " (IPv6)" : "");
+ }
if (*c->ip4.ifname_out || *c->ip6.ifname_out) {
info("Outbound interface: %s%s%s%s%s",
@@ -955,11 +1026,14 @@ static void conf_print(const struct ctx *c)
info("Namespace interface: %s", c->pasta_ifn);
info("MAC:");
- info(" host: %02x:%02x:%02x:%02x:%02x:%02x",
- c->mac[0], c->mac[1], c->mac[2],
- c->mac[3], c->mac[4], c->mac[5]);
+ info(" host: %s", eth_ntop(c->our_tap_mac, bufmac, sizeof(bufmac)));
if (c->ifi4) {
+ if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
+ info(" NAT to host 127.0.0.1: %s",
+ inet_ntop(AF_INET, &c->ip4.map_host_loopback,
+ buf4, sizeof(buf4)));
+
if (!c->no_dhcp) {
uint32_t mask;
@@ -971,7 +1045,8 @@ static void conf_print(const struct ctx *c)
info(" mask: %s",
inet_ntop(AF_INET, &mask, buf4, sizeof(buf4)));
info(" router: %s",
- inet_ntop(AF_INET, &c->ip4.gw, buf4, sizeof(buf4)));
+ inet_ntop(AF_INET, &c->ip4.guest_gw,
+ buf4, sizeof(buf4)));
}
for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) {
@@ -989,11 +1064,16 @@ static void conf_print(const struct ctx *c)
}
if (c->ifi6) {
+ if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
+ info(" NAT to host ::1: %s",
+ inet_ntop(AF_INET6, &c->ip6.map_host_loopback,
+ buf6, sizeof(buf6)));
+
if (!c->no_ndp && !c->no_dhcpv6)
info("NDP/DHCPv6:");
- else if (!c->no_ndp)
- info("DHCPv6:");
else if (!c->no_dhcpv6)
+ info("DHCPv6:");
+ else if (!c->no_ndp)
info("NDP:");
else
goto dns6;
@@ -1001,9 +1081,10 @@ static void conf_print(const struct ctx *c)
info(" assign: %s",
inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6)));
info(" router: %s",
- inet_ntop(AF_INET6, &c->ip6.gw, buf6, sizeof(buf6)));
+ inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6)));
info(" our link-local: %s",
- inet_ntop(AF_INET6, &c->ip6.addr_ll, buf6, sizeof(buf6)));
+ inet_ntop(AF_INET6, &c->ip6.our_tap_ll,
+ buf6, sizeof(buf6)));
dns6:
for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i]); i++) {
@@ -1122,6 +1203,38 @@ static void conf_ugid(char *runas, uid_t *uid, gid_t *gid)
}
/**
+ * conf_nat() - Parse --map-host-loopback or --map-guest-addr option
+ * @arg: String argument to option
+ * @addr4: IPv4 to update with parsed address
+ * @addr6: IPv6 to update with parsed address
+ * @no_map_gw: --no-map-gw flag, or NULL, updated for "none" argument
+ */
+static void conf_nat(const char *arg, struct in_addr *addr4,
+ struct in6_addr *addr6, int *no_map_gw)
+{
+ if (strcmp(arg, "none") == 0) {
+ *addr4 = in4addr_any;
+ *addr6 = in6addr_any;
+ if (no_map_gw)
+ *no_map_gw = 1;
+ }
+
+ if (inet_pton(AF_INET6, arg, addr6) &&
+ !IN6_IS_ADDR_UNSPECIFIED(addr6) &&
+ !IN6_IS_ADDR_LOOPBACK(addr6) &&
+ !IN6_IS_ADDR_MULTICAST(addr6))
+ return;
+
+ if (inet_pton(AF_INET, arg, addr4) &&
+ !IN4_IS_ADDR_UNSPECIFIED(addr4) &&
+ !IN4_IS_ADDR_LOOPBACK(addr4) &&
+ !IN4_IS_ADDR_MULTICAST(addr4))
+ return;
+
+ die("Invalid address to remap to host: %s", optarg);
+}
+
+/**
* conf_open_files() - Open files as requested by configuration
* @c: Execution context
*/
@@ -1130,7 +1243,11 @@ static void conf_open_files(struct ctx *c)
if (c->mode != MODE_PASTA && c->fd_tap == -1)
c->fd_tap_listen = tap_sock_unix_open(c->sock_path);
- c->pidfile_fd = pidfile_open(c->pidfile);
+ if (*c->pidfile) {
+ c->pidfile_fd = output_file_open(c->pidfile, O_WRONLY);
+ if (c->pidfile_fd < 0)
+ die_perror("Couldn't open PID file %s", c->pidfile);
+ }
}
/**
@@ -1174,7 +1291,7 @@ fail:
*/
void conf(struct ctx *c, int argc, char **argv)
{
- int netns_only = 0;
+ int netns_only = 0, no_map_gw = 0;
const struct option options[] = {
{"debug", no_argument, NULL, 'd' },
{"quiet", no_argument, NULL, 'q' },
@@ -1203,7 +1320,9 @@ void conf(struct ctx *c, int argc, char **argv)
{"no-dhcpv6", no_argument, &c->no_dhcpv6, 1 },
{"no-ndp", no_argument, &c->no_ndp, 1 },
{"no-ra", no_argument, &c->no_ra, 1 },
- {"no-map-gw", no_argument, &c->no_map_gw, 1 },
+ {"no-splice", no_argument, &c->no_splice, 1 },
+ {"freebind", no_argument, &c->freebind, 1 },
+ {"no-map-gw", no_argument, &no_map_gw, 1 },
{"ipv4-only", no_argument, NULL, '4' },
{"ipv6-only", no_argument, NULL, '6' },
{"one-off", no_argument, NULL, '1' },
@@ -1230,6 +1349,14 @@ void conf(struct ctx *c, int argc, char **argv)
{"no-copy-routes", no_argument, NULL, 18 },
{"no-copy-addrs", no_argument, NULL, 19 },
{"netns-only", no_argument, NULL, 20 },
+ {"map-host-loopback", required_argument, NULL, 21 },
+ {"map-guest-addr", required_argument, NULL, 22 },
+ {"host-lo-to-ns-lo", no_argument, NULL, 23 },
+ {"dns-host", required_argument, NULL, 24 },
+ {"vhost-user", no_argument, NULL, 25 },
+ /* vhost-user backend program convention */
+ {"print-capabilities", no_argument, NULL, 26 },
+ {"socket-path", required_argument, NULL, 's' },
{ 0 },
};
const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt";
@@ -1237,8 +1364,7 @@ void conf(struct ctx *c, int argc, char **argv)
bool copy_addrs_opt = false, copy_routes_opt = false;
enum fwd_ports_mode fwd_default = FWD_NONE;
bool v4_only = false, v6_only = false;
- struct in6_addr *dns6 = c->ip6.dns;
- struct in_addr *dns4 = c->ip4.dns;
+ unsigned dns4_idx = 0, dns6_idx = 0;
struct fqdn *dnss = c->dns_search;
unsigned int ifi4 = 0, ifi6 = 0;
const char *logfile = NULL;
@@ -1260,8 +1386,9 @@ void conf(struct ctx *c, int argc, char **argv)
c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET;
c->udp.fwd_in.mode = c->udp.fwd_out.mode = FWD_UNSET;
+ memcpy(c->our_tap_mac, MAC_OUR_LAA, ETH_ALEN);
- optind = 1;
+ optind = 0;
do {
name = getopt_long(argc, argv, optstring, options, NULL);
@@ -1290,7 +1417,7 @@ void conf(struct ctx *c, int argc, char **argv)
if (c->mode != MODE_PASTA)
die("--ns-mac-addr is for pasta mode only");
- parse_mac(c->mac_guest, optarg);
+ parse_mac(c->guest_mac, optarg);
break;
case 5:
if (c->mode != MODE_PASTA)
@@ -1352,9 +1479,9 @@ void conf(struct ctx *c, int argc, char **argv)
break;
case 14:
- fprintf(stdout,
+ FPRINTF(stdout,
c->mode == MODE_PASTA ? "pasta " : "passt ");
- fprintf(stdout, VERSION_BLOB);
+ FPRINTF(stdout, VERSION_BLOB);
exit(EXIT_SUCCESS);
case 15:
ret = snprintf(c->ip4.ifname_out,
@@ -1399,6 +1526,38 @@ void conf(struct ctx *c, int argc, char **argv)
netns_only = 1;
*userns = 0;
break;
+ case 21:
+ conf_nat(optarg, &c->ip4.map_host_loopback,
+ &c->ip6.map_host_loopback, &no_map_gw);
+ break;
+ case 22:
+ conf_nat(optarg, &c->ip4.map_guest_addr,
+ &c->ip6.map_guest_addr, NULL);
+ break;
+ case 23:
+ if (c->mode != MODE_PASTA)
+ die("--host-lo-to-ns-lo is for pasta mode only");
+ c->host_lo_to_ns_lo = 1;
+ break;
+ case 24:
+ if (inet_pton(AF_INET6, optarg, &c->ip6.dns_host) &&
+ !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host))
+ break;
+
+ if (inet_pton(AF_INET, optarg, &c->ip4.dns_host) &&
+ !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host) &&
+ !IN4_IS_ADDR_BROADCAST(&c->ip4.dns_host))
+ break;
+
+ die("Invalid host nameserver address: %s", optarg);
+ case 25:
+ if (c->mode == MODE_PASTA)
+ die("--vhost-user is for passt mode only");
+ c->mode = MODE_VU;
+ break;
+ case 26:
+ vu_print_capabilities();
+ break;
case 'd':
c->debug = 1;
c->quiet = 0;
@@ -1501,21 +1660,21 @@ void conf(struct ctx *c, int argc, char **argv)
break;
case 'M':
- parse_mac(c->mac, optarg);
+ parse_mac(c->our_tap_mac, optarg);
break;
case 'g':
- if (inet_pton(AF_INET6, optarg, &c->ip6.gw) &&
- !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw) &&
- !IN6_IS_ADDR_LOOPBACK(&c->ip6.gw)) {
+ if (inet_pton(AF_INET6, optarg, &c->ip6.guest_gw) &&
+ !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.guest_gw) &&
+ !IN6_IS_ADDR_LOOPBACK(&c->ip6.guest_gw)) {
if (c->mode == MODE_PASTA)
c->ip6.no_copy_routes = true;
break;
}
- if (inet_pton(AF_INET, optarg, &c->ip4.gw) &&
- !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.gw) &&
- !IN4_IS_ADDR_BROADCAST(&c->ip4.gw) &&
- !IN4_IS_ADDR_LOOPBACK(&c->ip4.gw)) {
+ if (inet_pton(AF_INET, optarg, &c->ip4.guest_gw) &&
+ !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw) &&
+ !IN4_IS_ADDR_BROADCAST(&c->ip4.guest_gw) &&
+ !IN4_IS_ADDR_LOOPBACK(&c->ip4.guest_gw)) {
if (c->mode == MODE_PASTA)
c->ip4.no_copy_routes = true;
break;
@@ -1599,6 +1758,9 @@ void conf(struct ctx *c, int argc, char **argv)
}
} while (name != -1);
+ if (c->mode != MODE_PASTA)
+ c->no_splice = 1;
+
if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
if (copy_routes_opt)
die("--no-copy-routes needs --config-net");
@@ -1630,25 +1792,44 @@ void conf(struct ctx *c, int argc, char **argv)
nl_sock_init(c, false);
if (!v6_only)
- c->ifi4 = conf_ip4(ifi4, &c->ip4, c->mac);
+ c->ifi4 = conf_ip4(ifi4, &c->ip4);
if (!v4_only)
- c->ifi6 = conf_ip6(ifi6, &c->ip6, c->mac);
- if ((!c->ifi4 && !c->ifi6) ||
- (*c->ip4.ifname_out && !c->ifi4) ||
+ c->ifi6 = conf_ip6(ifi6, &c->ip6);
+ if ((*c->ip4.ifname_out && !c->ifi4) ||
(*c->ip6.ifname_out && !c->ifi6))
die("External interface not usable");
+ if (!c->ifi4 && !c->ifi6) {
+ info("No external interface as template, switch to local mode");
+
+ conf_ip4_local(&c->ip4);
+ c->ifi4 = -1;
+
+ conf_ip6_local(&c->ip6);
+ c->ifi6 = -1;
- if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.gw))
- c->no_map_gw = c->no_dhcp = 1;
+ if (!*c->pasta_ifn) {
+ strncpy(c->pasta_ifn, pasta_default_ifn,
+ sizeof(c->pasta_ifn) - 1);
+ }
+ }
- if (c->ifi6 && IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw))
- c->no_map_gw = 1;
+ if (c->ifi4 && !no_map_gw &&
+ IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
+ c->ip4.map_host_loopback = c->ip4.guest_gw;
+
+ if (c->ifi6 && !no_map_gw &&
+ IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
+ c->ip6.map_host_loopback = c->ip6.guest_gw;
+
+ if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw))
+ c->no_dhcp = 1;
/* Inbound port options & DNS can be parsed now (after IPv4/IPv6
* settings)
*/
+ fwd_probe_ephemeral();
udp_portmap_clear();
- optind = 1;
+ optind = 0;
do {
name = getopt_long(argc, argv, optstring, options, NULL);
@@ -1663,13 +1844,13 @@ void conf(struct ctx *c, int argc, char **argv)
if (!strcmp(optarg, "none")) {
c->no_dns = 1;
- dns4 = &c->ip4.dns[0];
+ dns4_idx = 0;
memset(c->ip4.dns, 0, sizeof(c->ip4.dns));
c->ip4.dns[0] = (struct in_addr){ 0 };
c->ip4.dns_match = (struct in_addr){ 0 };
c->ip4.dns_host = (struct in_addr){ 0 };
- dns6 = &c->ip6.dns[0];
+ dns6_idx = 0;
memset(c->ip6.dns, 0, sizeof(c->ip6.dns));
c->ip6.dns_match = (struct in6_addr){ 0 };
c->ip6.dns_host = (struct in6_addr){ 0 };
@@ -1679,15 +1860,13 @@ void conf(struct ctx *c, int argc, char **argv)
c->no_dns = 0;
- if (dns4 - &c->ip4.dns[0] < ARRAY_SIZE(c->ip4.dns) &&
- inet_pton(AF_INET, optarg, &dns4_tmp)) {
- add_dns4(c, &dns4_tmp, &dns4);
+ if (inet_pton(AF_INET, optarg, &dns4_tmp)) {
+ dns4_idx += add_dns4(c, &dns4_tmp, dns4_idx);
continue;
}
- if (dns6 - &c->ip6.dns[0] < ARRAY_SIZE(c->ip6.dns) &&
- inet_pton(AF_INET6, optarg, &dns6_tmp)) {
- add_dns6(c, &dns6_tmp, &dns6);
+ if (inet_pton(AF_INET6, optarg, &dns6_tmp)) {
+ dns6_idx += add_dns6(c, &dns6_tmp, dns6_idx);
continue;
}
@@ -1720,7 +1899,7 @@ void conf(struct ctx *c, int argc, char **argv)
nl_sock_init(c, true);
/* ...and outbound port options now that namespaces are set up. */
- optind = 1;
+ optind = 0;
do {
name = getopt_long(argc, argv, optstring, options, NULL);
@@ -1736,6 +1915,8 @@ void conf(struct ctx *c, int argc, char **argv)
if (!c->ifi6) {
c->no_ndp = 1;
c->no_dhcpv6 = 1;
+ } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
+ c->no_dhcpv6 = 1;
}
if (!c->mtu)
@@ -1744,9 +1925,9 @@ void conf(struct ctx *c, int argc, char **argv)
get_dns(c);
if (!*c->pasta_ifn) {
- if (c->ifi4)
+ if (c->ifi4 > 0)
if_indextoname(c->ifi4, c->pasta_ifn);
- else
+ else if (c->ifi6 > 0)
if_indextoname(c->ifi6, c->pasta_ifn);
}
diff --git a/contrib/apparmor/abstractions/passt b/contrib/apparmor/abstractions/passt
index d245115..43fd63f 100644
--- a/contrib/apparmor/abstractions/passt
+++ b/contrib/apparmor/abstractions/passt
@@ -34,6 +34,8 @@
owner @{PROC}/@{pid}/uid_map r, # conf_ugid()
+ @{PROC}/sys/net/ipv4/ip_local_port_range r, # fwd_probe_ephemeral()
+
network netlink raw, # nl_sock_init_do(), netlink.c
network inet stream, # tcp.c
diff --git a/contrib/selinux/passt.te b/contrib/selinux/passt.te
index bbb0917..c6cea34 100644
--- a/contrib/selinux/passt.te
+++ b/contrib/selinux/passt.te
@@ -47,9 +47,8 @@ require {
type port_t;
type http_port_t;
- type passwd_file_t;
-
class netlink_route_socket { bind create nlmsg_read };
+ type sysctl_net_t;
class capability { sys_tty_config setuid setgid };
class cap_userns { setpcap sys_admin sys_ptrace };
@@ -95,8 +94,7 @@ allow passt_t self:capability { sys_tty_config setpcap net_bind_service setuid s
allow passt_t self:cap_userns { setpcap sys_admin sys_ptrace };
allow passt_t self:user_namespace create;
-allow passt_t passwd_file_t:file read_file_perms;
-sssd_search_lib(passt_t)
+auth_read_passwd(passt_t)
allow passt_t proc_net_t:file read;
allow passt_t net_conf_t:file { open read };
@@ -104,6 +102,8 @@ allow passt_t net_conf_t:lnk_file read;
allow passt_t tmp_t:sock_file { create unlink write };
allow passt_t self:netlink_route_socket { bind create nlmsg_read read write setopt };
kernel_search_network_sysctl(passt_t)
+allow passt_t sysctl_net_t:dir search;
+allow passt_t sysctl_net_t:file { open read };
corenet_tcp_bind_all_nodes(passt_t)
corenet_udp_bind_all_nodes(passt_t)
diff --git a/contrib/selinux/pasta.te b/contrib/selinux/pasta.te
index 4e36c3f..69be081 100644
--- a/contrib/selinux/pasta.te
+++ b/contrib/selinux/pasta.te
@@ -68,9 +68,6 @@ require {
type system_dbusd_t;
type systemd_hostnamed_t;
type systemd_systemctl_exec_t;
- type passwd_file_t;
- type sssd_public_t;
- type sssd_var_lib_t;
class dbus send_msg;
class system module_request;
class system status;
@@ -115,8 +112,7 @@ allow pasta_t self:capability { setpcap net_bind_service sys_tty_config dac_read
allow pasta_t self:cap_userns { setpcap sys_admin sys_ptrace net_admin net_bind_service };
allow pasta_t self:user_namespace create;
-allow pasta_t passwd_file_t:file read_file_perms;
-sssd_search_lib(pasta_t)
+auth_read_passwd(pasta_t)
domain_auto_trans(pasta_t, bin_t, unconfined_t);
domain_auto_trans(pasta_t, shell_exec_t, unconfined_t);
@@ -178,12 +174,9 @@ allow pasta_t init_t:system status;
allow pasta_t unconfined_t:dir search;
allow pasta_t unconfined_t:file read;
allow pasta_t unconfined_t:lnk_file read;
-allow pasta_t passwd_file_t:file { getattr open read };
allow pasta_t self:process { setpgid setcap };
allow pasta_t shell_exec_t:file { execute execute_no_trans map };
-allow pasta_t sssd_var_lib_t:dir search;
-allow pasta_t sssd_public_t:dir search;
allow pasta_t hostname_exec_t:file { execute execute_no_trans getattr open read map };
allow pasta_t system_dbusd_t:unix_stream_socket connectto;
allow pasta_t system_dbusd_t:dbus send_msg;
@@ -196,7 +189,7 @@ allow pasta_t ifconfig_var_run_t:dir { read search watch };
allow pasta_t self:tun_socket create;
allow pasta_t tun_tap_device_t:chr_file { ioctl open read write };
allow pasta_t sysctl_net_t:dir search;
-allow pasta_t sysctl_net_t:file { open write };
+allow pasta_t sysctl_net_t:file { open read write };
allow pasta_t kernel_t:system module_request;
allow pasta_t nsfs_t:file read;
diff --git a/dhcp.c b/dhcp.c
index aa9f59d..d8515aa 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -36,9 +36,9 @@
/**
* struct opt - DHCP option
* @sent: Convenience flag, set while filling replies
- * @slen: Length of option defined for server
+ * @slen: Length of option defined for server, -1 if not going to be sent
* @s: Option payload from server
- * @clen: Length of option received from client
+ * @clen: Length of option received from client, -1 if not received
* @c: Option payload from client
*/
struct opt {
@@ -68,6 +68,11 @@ static struct opt opts[255];
*/
void dhcp_init(void)
{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(opts); i++)
+ opts[i].slen = -1;
+
opts[1] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, }; /* Mask */
opts[3] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, }; /* Router */
opts[51] = (struct opt) { 0, 4, { 0xff,
@@ -107,6 +112,8 @@ struct msg {
uint32_t xid;
uint16_t secs;
uint16_t flags;
+#define FLAG_BROADCAST htons_constant(0x8000)
+
uint32_t ciaddr;
struct in_addr yiaddr;
uint32_t siaddr;
@@ -154,17 +161,17 @@ static int fill(struct msg *m)
* option 53 at the beginning of the list.
* Put it there explicitly, unless requested via option 55.
*/
- if (!memchr(opts[55].c, 53, opts[55].clen))
+ if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
fill_one(m, 53, &offset);
for (i = 0; i < opts[55].clen; i++) {
o = opts[55].c[i];
- if (opts[o].slen)
+ if (opts[o].slen != -1)
fill_one(m, o, &offset);
}
for (o = 0; o < 255; o++) {
- if (opts[o].slen && !opts[o].sent)
+ if (opts[o].slen != -1 && !opts[o].sent)
fill_one(m, o, &offset);
}
@@ -264,6 +271,9 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len)
".\xc0");
}
}
+
+ if (!opts[119].slen)
+ opts[119].slen = -1;
}
/**
@@ -276,10 +286,11 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len)
int dhcp(const struct ctx *c, const struct pool *p)
{
size_t mlen, dlen, offset = 0, opt_len, opt_off = 0;
+ char macstr[ETH_ADDRSTRLEN];
+ struct in_addr mask, dst;
const struct ethhdr *eh;
const struct iphdr *iph;
const struct udphdr *uh;
- struct in_addr mask;
unsigned int i;
struct msg *m;
@@ -312,6 +323,9 @@ int dhcp(const struct ctx *c, const struct pool *p)
offset += offsetof(struct msg, o);
+ for (i = 0; i < ARRAY_SIZE(opts); i++)
+ opts[i].clen = -1;
+
while (opt_off + 2 < opt_len) {
const uint8_t *olen, *val;
uint8_t *type;
@@ -330,36 +344,44 @@ int dhcp(const struct ctx *c, const struct pool *p)
opt_off += *olen + 2;
}
- if (opts[53].c[0] == DHCPDISCOVER) {
- info("DHCP: offer to discover");
- opts[53].s[0] = DHCPOFFER;
- } else if (opts[53].c[0] == DHCPREQUEST || !opts[53].clen) {
- info("%s: ack to request", opts[53].clen ? "DHCP" : "BOOTP");
+ opts[80].slen = -1;
+ if (opts[53].clen > 0 && opts[53].c[0] == DHCPDISCOVER) {
+ if (opts[80].clen == -1) {
+ info("DHCP: offer to discover");
+ opts[53].s[0] = DHCPOFFER;
+ } else {
+ info("DHCP: ack to discover (Rapid Commit)");
+ opts[53].s[0] = DHCPACK;
+ opts[80].slen = 0;
+ }
+ } else if (opts[53].clen <= 0 || opts[53].c[0] == DHCPREQUEST) {
+ info("%s: ack to request", /* DHCP needs a valid message type */
+ (opts[53].clen <= 0) ? "BOOTP" : "DHCP");
opts[53].s[0] = DHCPACK;
} else {
return -1;
}
- info(" from %02x:%02x:%02x:%02x:%02x:%02x",
- m->chaddr[0], m->chaddr[1], m->chaddr[2],
- m->chaddr[3], m->chaddr[4], m->chaddr[5]);
+ info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr)));
m->yiaddr = c->ip4.addr;
mask.s_addr = htonl(0xffffffff << (32 - c->ip4.prefix_len));
- memcpy(opts[1].s, &mask, sizeof(mask));
- memcpy(opts[3].s, &c->ip4.gw, sizeof(c->ip4.gw));
- memcpy(opts[54].s, &c->ip4.gw, sizeof(c->ip4.gw));
+ memcpy(opts[1].s, &mask, sizeof(mask));
+ memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw));
+ memcpy(opts[54].s, &c->ip4.our_tap_addr, sizeof(c->ip4.our_tap_addr));
/* If the gateway is not on the assigned subnet, send an option 121
* (Classless Static Routing) adding a dummy route to it.
*/
if ((c->ip4.addr.s_addr & mask.s_addr)
- != (c->ip4.gw.s_addr & mask.s_addr)) {
+ != (c->ip4.guest_gw.s_addr & mask.s_addr)) {
/* a.b.c.d/32:0.0.0.0, 0:a.b.c.d */
opts[121].slen = 14;
opts[121].s[0] = 32;
- memcpy(opts[121].s + 1, &c->ip4.gw, sizeof(c->ip4.gw));
- memcpy(opts[121].s + 10, &c->ip4.gw, sizeof(c->ip4.gw));
+ memcpy(opts[121].s + 1,
+ &c->ip4.guest_gw, sizeof(c->ip4.guest_gw));
+ memcpy(opts[121].s + 10,
+ &c->ip4.guest_gw, sizeof(c->ip4.guest_gw));
}
if (c->mtu != -1) {
@@ -373,12 +395,20 @@ int dhcp(const struct ctx *c, const struct pool *p)
((struct in_addr *)opts[6].s)[i] = c->ip4.dns[i];
opts[6].slen += sizeof(uint32_t);
}
+ if (!opts[6].slen)
+ opts[6].slen = -1;
if (!c->no_dhcp_dns_search)
opt_set_dns_search(c, sizeof(m->o));
dlen = offsetof(struct msg, o) + fill(m);
- tap_udp4_send(c, c->ip4.gw, 67, c->ip4.addr, 68, m, dlen);
+
+ if (m->flags & FLAG_BROADCAST)
+ dst = in4addr_broadcast;
+ else
+ dst = c->ip4.addr;
+
+ tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, m, dlen);
return 1;
}
diff --git a/dhcpv6.c b/dhcpv6.c
index 7dcca2a..0523bba 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -296,45 +296,42 @@ static struct opt_hdr *dhcpv6_opt(const struct pool *p, size_t *offset,
static struct opt_hdr *dhcpv6_ia_notonlink(const struct pool *p,
struct in6_addr *la)
{
+ int ia_types[2] = { OPT_IA_NA, OPT_IA_TA }, *ia_type;
+ const struct opt_ia_addr *opt_addr;
char buf[INET6_ADDRSTRLEN];
struct in6_addr req_addr;
- struct opt_hdr *ia, *h;
+ const struct opt_hdr *h;
+ struct opt_hdr *ia;
size_t offset;
- int ia_type;
- ia_type = OPT_IA_NA;
-ia_ta:
- offset = 0;
- while ((ia = dhcpv6_opt(p, &offset, ia_type))) {
- if (ntohs(ia->l) < OPT_VSIZE(ia_na))
- return NULL;
+ foreach(ia_type, ia_types) {
+ offset = 0;
+ while ((ia = dhcpv6_opt(p, &offset, *ia_type))) {
+ if (ntohs(ia->l) < OPT_VSIZE(ia_na))
+ return NULL;
- offset += sizeof(struct opt_ia_na);
+ offset += sizeof(struct opt_ia_na);
- while ((h = dhcpv6_opt(p, &offset, OPT_IAAADR))) {
- struct opt_ia_addr *opt_addr = (struct opt_ia_addr *)h;
+ while ((h = dhcpv6_opt(p, &offset, OPT_IAAADR))) {
+ if (ntohs(h->l) != OPT_VSIZE(ia_addr))
+ return NULL;
- if (ntohs(h->l) != OPT_VSIZE(ia_addr))
- return NULL;
+ opt_addr = (const struct opt_ia_addr *)h;
+ req_addr = opt_addr->addr;
+ if (!IN6_ARE_ADDR_EQUAL(la, &req_addr))
+ goto err;
- memcpy(&req_addr, &opt_addr->addr, sizeof(req_addr));
- if (!IN6_ARE_ADDR_EQUAL(la, &req_addr)) {
- info("DHCPv6: requested address %s not on link",
- inet_ntop(AF_INET6, &req_addr,
- buf, sizeof(buf)));
- return ia;
+ offset += sizeof(struct opt_ia_addr);
}
-
- offset += sizeof(struct opt_ia_addr);
}
}
- if (ia_type == OPT_IA_NA) {
- ia_type = OPT_IA_TA;
- goto ia_ta;
- }
-
return NULL;
+
+err:
+ info("DHCPv6: requested address %s not on link",
+ inet_ntop(AF_INET6, &req_addr, buf, sizeof(buf)));
+ return ia;
}
/**
@@ -363,7 +360,7 @@ static size_t dhcpv6_dns_fill(const struct ctx *c, char *buf, int offset)
srv->hdr.l = 0;
}
- memcpy(&srv->addr[i], &c->ip6.dns[i], sizeof(srv->addr[i]));
+ srv->addr[i] = c->ip6.dns[i];
srv->hdr.l += sizeof(srv->addr[i]);
offset += sizeof(srv->addr[i]);
}
@@ -426,11 +423,11 @@ search:
int dhcpv6(struct ctx *c, const struct pool *p,
const struct in6_addr *saddr, const struct in6_addr *daddr)
{
- struct opt_hdr *ia, *bad_ia, *client_id;
- const struct opt_hdr *server_id;
+ const struct opt_hdr *client_id, *server_id, *ia;
const struct in6_addr *src;
const struct msg_hdr *mh;
const struct udphdr *uh;
+ struct opt_hdr *bad_ia;
size_t mlen, n;
uh = packet_get(p, 0, 0, sizeof(*uh), &mlen);
@@ -451,10 +448,7 @@ int dhcpv6(struct ctx *c, const struct pool *p,
c->ip6.addr_ll_seen = *saddr;
- if (IN6_IS_ADDR_LINKLOCAL(&c->ip6.gw))
- src = &c->ip6.gw;
- else
- src = &c->ip6.addr_ll;
+ src = &c->ip6.our_tap_ll;
mh = packet_get(p, 0, sizeof(*uh), sizeof(*mh), NULL);
if (!mh)
@@ -574,8 +568,10 @@ void dhcpv6_init(const struct ctx *c)
resp.server_id.duid_time = duid_time;
resp_not_on_link.server_id.duid_time = duid_time;
- memcpy(resp.server_id.duid_lladdr, c->mac, sizeof(c->mac));
- memcpy(resp_not_on_link.server_id.duid_lladdr, c->mac, sizeof(c->mac));
+ memcpy(resp.server_id.duid_lladdr,
+ c->our_tap_mac, sizeof(c->our_tap_mac));
+ memcpy(resp_not_on_link.server_id.duid_lladdr,
+ c->our_tap_mac, sizeof(c->our_tap_mac));
resp.ia_addr.addr = c->ip6.addr;
}
diff --git a/epoll_type.h b/epoll_type.h
index 0ad1efa..f3ef415 100644
--- a/epoll_type.h
+++ b/epoll_type.h
@@ -36,6 +36,10 @@ enum epoll_type {
EPOLL_TYPE_TAP_PASST,
/* socket listening for qemu socket connections */
EPOLL_TYPE_TAP_LISTEN,
+ /* vhost-user command socket */
+ EPOLL_TYPE_VHOST_CMD,
+ /* vhost-user kick event socket */
+ EPOLL_TYPE_VHOST_KICK,
EPOLL_NUM_TYPES,
};
diff --git a/flow.c b/flow.c
index 687e9fd..ee1221b 100644
--- a/flow.c
+++ b/flow.c
@@ -127,18 +127,18 @@ static struct timespec flow_timer_run;
* @af: Address family (AF_INET or AF_INET6)
* @eaddr: Endpoint address (pointer to in_addr or in6_addr)
* @eport: Endpoint port
- * @faddr: Forwarding address (pointer to in_addr or in6_addr)
- * @fport: Forwarding port
+ * @oaddr: Our address (pointer to in_addr or in6_addr)
+ * @oport: Our port
*/
static void flowside_from_af(struct flowside *side, sa_family_t af,
const void *eaddr, in_port_t eport,
- const void *faddr, in_port_t fport)
+ const void *oaddr, in_port_t oport)
{
- if (faddr)
- inany_from_af(&side->faddr, af, faddr);
+ if (oaddr)
+ inany_from_af(&side->oaddr, af, oaddr);
else
- side->faddr = inany_any6;
- side->fport = fport;
+ side->oaddr = inany_any6;
+ side->oport = oport;
if (eaddr)
inany_from_af(&side->eaddr, af, eaddr);
@@ -193,8 +193,8 @@ static int flowside_sock_splice(void *arg)
* @tgt: Target flowside
* @data: epoll reference portion for protocol handlers
*
- * Return: socket fd of protocol @proto bound to the forwarding address and port
- * from @tgt (if specified).
+ * Return: socket fd of protocol @proto bound to our address and port from @tgt
+ * (if specified).
*/
int flowside_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
const struct flowside *tgt, uint32_t data)
@@ -205,11 +205,11 @@ int flowside_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
ASSERT(pif_is_socket(pif));
- pif_sockaddr(c, &sa, &sl, pif, &tgt->faddr, tgt->fport);
+ pif_sockaddr(c, &sa, &sl, pif, &tgt->oaddr, tgt->oport);
switch (pif) {
case PIF_HOST:
- if (inany_is_loopback(&tgt->faddr))
+ if (inany_is_loopback(&tgt->oaddr))
ifname = NULL;
else if (sa.sa_family == AF_INET)
ifname = c->ip4.ifname_out;
@@ -283,46 +283,60 @@ void flow_log_(const struct flow_common *f, int pri, const char *fmt, ...)
"Flow %u (%s): %s", flow_idx(f), type_or_state, msg);
}
-/**
- * flow_set_state() - Change flow's state
- * @f: Flow changing state
- * @state: New state
+/** flow_log_details_() - Log the details of a flow
+ * @f: flow to log
+ * @pri: Log priority
+ * @state: State to log details according to
+ *
+ * Logs the details of the flow: endpoints, interfaces, type etc.
*/
-static void flow_set_state(struct flow_common *f, enum flow_state state)
+void flow_log_details_(const struct flow_common *f, int pri,
+ enum flow_state state)
{
char estr0[INANY_ADDRSTRLEN], fstr0[INANY_ADDRSTRLEN];
char estr1[INANY_ADDRSTRLEN], fstr1[INANY_ADDRSTRLEN];
const struct flowside *ini = &f->side[INISIDE];
const struct flowside *tgt = &f->side[TGTSIDE];
- uint8_t oldstate = f->state;
-
- ASSERT(state < FLOW_NUM_STATES);
- ASSERT(oldstate < FLOW_NUM_STATES);
-
- f->state = state;
- flow_log_(f, LOG_DEBUG, "%s -> %s", flow_state_str[oldstate],
- FLOW_STATE(f));
- if (MAX(state, oldstate) >= FLOW_STATE_TGT)
- flow_log_(f, LOG_DEBUG,
+ if (state >= FLOW_STATE_TGT)
+ flow_log_(f, pri,
"%s [%s]:%hu -> [%s]:%hu => %s [%s]:%hu -> [%s]:%hu",
pif_name(f->pif[INISIDE]),
inany_ntop(&ini->eaddr, estr0, sizeof(estr0)),
ini->eport,
- inany_ntop(&ini->faddr, fstr0, sizeof(fstr0)),
- ini->fport,
+ inany_ntop(&ini->oaddr, fstr0, sizeof(fstr0)),
+ ini->oport,
pif_name(f->pif[TGTSIDE]),
- inany_ntop(&tgt->faddr, fstr1, sizeof(fstr1)),
- tgt->fport,
+ inany_ntop(&tgt->oaddr, fstr1, sizeof(fstr1)),
+ tgt->oport,
inany_ntop(&tgt->eaddr, estr1, sizeof(estr1)),
tgt->eport);
- else if (MAX(state, oldstate) >= FLOW_STATE_INI)
- flow_log_(f, LOG_DEBUG, "%s [%s]:%hu -> [%s]:%hu => ?",
+ else if (state >= FLOW_STATE_INI)
+ flow_log_(f, pri, "%s [%s]:%hu -> [%s]:%hu => ?",
pif_name(f->pif[INISIDE]),
inany_ntop(&ini->eaddr, estr0, sizeof(estr0)),
ini->eport,
- inany_ntop(&ini->faddr, fstr0, sizeof(fstr0)),
- ini->fport);
+ inany_ntop(&ini->oaddr, fstr0, sizeof(fstr0)),
+ ini->oport);
+}
+
+/**
+ * flow_set_state() - Change flow's state
+ * @f: Flow changing state
+ * @state: New state
+ */
+static void flow_set_state(struct flow_common *f, enum flow_state state)
+{
+ uint8_t oldstate = f->state;
+
+ ASSERT(state < FLOW_NUM_STATES);
+ ASSERT(oldstate < FLOW_NUM_STATES);
+
+ f->state = state;
+ flow_log_(f, LOG_DEBUG, "%s -> %s", flow_state_str[oldstate],
+ FLOW_STATE(f));
+
+ flow_log_details_(f, LOG_DEBUG, MAX(state, oldstate));
}
/**
@@ -347,7 +361,7 @@ static void flow_initiate_(union flow *flow, uint8_t pif)
* flow_initiate_af() - Move flow to INI, setting INISIDE details
* @flow: Flow to change state
* @pif: pif of the initiating side
- * @af: Address family of @eaddr and @faddr
+ * @af: Address family of @saddr and @daddr
* @saddr: Source address (pointer to in_addr or in6_addr)
* @sport: Endpoint port
* @daddr: Destination address (pointer to in_addr or in6_addr)
@@ -384,10 +398,10 @@ const struct flowside *flow_initiate_sa(union flow *flow, uint8_t pif,
inany_from_sockaddr(&ini->eaddr, &ini->eport, ssa);
if (inany_v4(&ini->eaddr))
- ini->faddr = inany_any4;
+ ini->oaddr = inany_any4;
else
- ini->faddr = inany_any6;
- ini->fport = dport;
+ ini->oaddr = inany_any6;
+ ini->oport = dport;
flow_initiate_(flow, pif);
return ini;
}
@@ -432,8 +446,8 @@ const struct flowside *flow_target(const struct ctx *c, union flow *flow,
pif_name(f->pif[INISIDE]),
inany_ntop(&ini->eaddr, estr, sizeof(estr)),
ini->eport,
- inany_ntop(&ini->faddr, fstr, sizeof(fstr)),
- ini->fport);
+ inany_ntop(&ini->oaddr, fstr, sizeof(fstr)),
+ ini->oport);
}
if (tgtpif == PIF_NONE)
@@ -561,18 +575,12 @@ static uint64_t flow_hash(const struct ctx *c, uint8_t proto, uint8_t pif,
{
struct siphash_state state = SIPHASH_INIT(c->hash_secret);
- /* For the hash table to work, we need complete endpoint information,
- * and at least a forwarding port.
- */
- ASSERT(pif != PIF_NONE && !inany_is_unspecified(&side->eaddr) &&
- side->eport != 0 && side->fport != 0);
-
- inany_siphash_feed(&state, &side->faddr);
+ inany_siphash_feed(&state, &side->oaddr);
inany_siphash_feed(&state, &side->eaddr);
return siphash_final(&state, 38, (uint64_t)proto << 40 |
(uint64_t)pif << 32 |
- (uint64_t)side->fport << 16 |
+ (uint64_t)side->oport << 16 |
(uint64_t)side->eport);
}
@@ -586,8 +594,11 @@ static uint64_t flow_hash(const struct ctx *c, uint8_t proto, uint8_t pif,
static uint64_t flow_sidx_hash(const struct ctx *c, flow_sidx_t sidx)
{
const struct flow_common *f = &flow_at_sidx(sidx)->f;
- return flow_hash(c, FLOW_PROTO(f),
- f->pif[sidx.sidei], &f->side[sidx.sidei]);
+ const struct flowside *side = &f->side[sidx.sidei];
+ uint8_t pif = f->pif[sidx.sidei];
+
+ ASSERT(pif != PIF_NONE);
+ return flow_hash(c, FLOW_PROTO(f), pif, side);
}
/**
@@ -695,7 +706,7 @@ static flow_sidx_t flowside_lookup(const struct ctx *c, uint8_t proto,
!(FLOW_PROTO(&flow->f) == proto &&
flow->f.pif[sidx.sidei] == pif &&
flowside_eq(&flow->f.side[sidx.sidei], side)))
- b = (b + 1) % FLOW_HASH_SIZE;
+ b = mod_sub(b, 1, FLOW_HASH_SIZE);
return flow_hashtab[b];
}
@@ -707,20 +718,20 @@ static flow_sidx_t flowside_lookup(const struct ctx *c, uint8_t proto,
* @pif: Interface of the flow
* @af: Address family, AF_INET or AF_INET6
* @eaddr: Guest side endpoint address (guest local address)
- * @faddr: Guest side forwarding address (guest remote address)
+ * @oaddr: Our guest side address (guest remote address)
* @eport: Guest side endpoint port (guest local port)
- * @fport: Guest side forwarding port (guest remote port)
+ * @oport: Our guest side port (guest remote port)
*
* Return: sidx of the matching flow & side, FLOW_SIDX_NONE if not found
*/
flow_sidx_t flow_lookup_af(const struct ctx *c,
uint8_t proto, uint8_t pif, sa_family_t af,
- const void *eaddr, const void *faddr,
- in_port_t eport, in_port_t fport)
+ const void *eaddr, const void *oaddr,
+ in_port_t eport, in_port_t oport)
{
struct flowside side;
- flowside_from_af(&side, af, eaddr, eport, faddr, fport);
+ flowside_from_af(&side, af, eaddr, eport, oaddr, oport);
return flowside_lookup(c, proto, pif, &side);
}
@@ -730,22 +741,22 @@ flow_sidx_t flow_lookup_af(const struct ctx *c,
* @proto: Protocol of the flow (IP L4 protocol number)
* @pif: Interface of the flow
* @esa: Socket address of the endpoint
- * @fport: Forwarding port number
+ * @oport: Our port number
*
* Return: sidx of the matching flow & side, FLOW_SIDX_NONE if not found
*/
flow_sidx_t flow_lookup_sa(const struct ctx *c, uint8_t proto, uint8_t pif,
- const void *esa, in_port_t fport)
+ const void *esa, in_port_t oport)
{
struct flowside side = {
- .fport = fport,
+ .oport = oport,
};
inany_from_sockaddr(&side.eaddr, &side.eport, esa);
if (inany_v4(&side.eaddr))
- side.faddr = inany_any4;
+ side.oaddr = inany_any4;
else
- side.faddr = inany_any6;
+ side.oaddr = inany_any6;
return flowside_lookup(c, proto, pif, &side);
}
@@ -830,7 +841,8 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
closed = icmp_ping_timer(c, &flow->ping, now);
break;
case FLOW_UDP:
- if (timer)
+ closed = udp_flow_defer(&flow->udp);
+ if (!closed && timer)
closed = udp_flow_timer(c, &flow->udp, now);
break;
default:
diff --git a/flow.h b/flow.h
index 078fd60..24ba3ef 100644
--- a/flow.h
+++ b/flow.h
@@ -140,14 +140,14 @@ extern const uint8_t flow_proto[];
/**
* struct flowside - Address information for one side of a flow
* @eaddr: Endpoint address (remote address from passt's PoV)
- * @faddr: Forwarding address (local address from passt's PoV)
+ * @oaddr: Our address (local address from passt's PoV)
* @eport: Endpoint port
- * @fport: Forwarding port
+ * @oport: Our port
*/
struct flowside {
- union inany_addr faddr;
+ union inany_addr oaddr;
union inany_addr eaddr;
- in_port_t fport;
+ in_port_t oport;
in_port_t eport;
};
@@ -162,8 +162,8 @@ static inline bool flowside_eq(const struct flowside *left,
{
return inany_equals(&left->eaddr, &right->eaddr) &&
left->eport == right->eport &&
- inany_equals(&left->faddr, &right->faddr) &&
- left->fport == right->fport;
+ inany_equals(&left->oaddr, &right->oaddr) &&
+ left->oport == right->oport;
}
int flowside_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
@@ -240,10 +240,10 @@ uint64_t flow_hash_insert(const struct ctx *c, flow_sidx_t sidx);
void flow_hash_remove(const struct ctx *c, flow_sidx_t sidx);
flow_sidx_t flow_lookup_af(const struct ctx *c,
uint8_t proto, uint8_t pif, sa_family_t af,
- const void *eaddr, const void *faddr,
- in_port_t eport, in_port_t fport);
+ const void *eaddr, const void *oaddr,
+ in_port_t eport, in_port_t oport);
flow_sidx_t flow_lookup_sa(const struct ctx *c, uint8_t proto, uint8_t pif,
- const void *esa, in_port_t fport);
+ const void *esa, in_port_t oport);
union flow;
@@ -264,4 +264,11 @@ void flow_log_(const struct flow_common *f, int pri, const char *fmt, ...)
flow_dbg((f), __VA_ARGS__); \
} while (0)
+void flow_log_details_(const struct flow_common *f, int pri,
+ enum flow_state state);
+#define flow_log_details(f_, pri) \
+ flow_log_details_(&((f_)->f), (pri), (f_)->f.state)
+#define flow_dbg_details(f_) flow_log_details((f_), LOG_DEBUG)
+#define flow_err_details(f_) flow_log_details((f_), LOG_ERR)
+
#endif /* FLOW_H */
diff --git a/flow_table.h b/flow_table.h
index a499e7b..f15db53 100644
--- a/flow_table.h
+++ b/flow_table.h
@@ -110,7 +110,7 @@ static inline const struct flowside *flowside_at_sidx(flow_sidx_t sidx)
const union flow *flow = flow_at_sidx(sidx);
if (!flow)
- return PIF_NONE;
+ return NULL;
return &flow->f.side[sidx.sidei];
}
diff --git a/fwd.c b/fwd.c
index dea36f6..2829cd2 100644
--- a/fwd.c
+++ b/fwd.c
@@ -27,6 +27,80 @@
#include "lineread.h"
#include "flow_table.h"
+/* Empheral port range: values from RFC 6335 */
+static in_port_t fwd_ephemeral_min = (1 << 15) + (1 << 14);
+static in_port_t fwd_ephemeral_max = NUM_PORTS - 1;
+
+#define PORT_RANGE_SYSCTL "/proc/sys/net/ipv4/ip_local_port_range"
+
+/** fwd_probe_ephemeral() - Determine what ports this host considers ephemeral
+ *
+ * Work out what ports the host thinks are emphemeral and record it for later
+ * use by fwd_port_is_ephemeral(). If we're unable to probe, assume the range
+ * recommended by RFC 6335.
+ */
+void fwd_probe_ephemeral(void)
+{
+ char *line, *tab, *end;
+ struct lineread lr;
+ long min, max;
+ ssize_t len;
+ int fd;
+
+ fd = open(PORT_RANGE_SYSCTL, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ warn_perror("Unable to open %s", PORT_RANGE_SYSCTL);
+ return;
+ }
+
+ lineread_init(&lr, fd);
+ len = lineread_get(&lr, &line);
+ close(fd);
+
+ if (len < 0)
+ goto parse_err;
+
+ tab = strchr(line, '\t');
+ if (!tab)
+ goto parse_err;
+ *tab = '\0';
+
+ errno = 0;
+ min = strtol(line, &end, 10);
+ if (*end || errno)
+ goto parse_err;
+
+ errno = 0;
+ max = strtol(tab + 1, &end, 10);
+ if (*end || errno)
+ goto parse_err;
+
+ if (min < 0 || min >= (long)NUM_PORTS ||
+ max < 0 || max >= (long)NUM_PORTS)
+ goto parse_err;
+
+ fwd_ephemeral_min = min;
+ fwd_ephemeral_max = max;
+
+ return;
+
+parse_err:
+ warn("Unable to parse %s", PORT_RANGE_SYSCTL);
+}
+
+/**
+ * fwd_port_is_ephemeral() - Is port number ephemeral?
+ * @port: Port number
+ *
+ * Return: true if @port is ephemeral, that is may be allocated by the kernel as
+ * a local port for outgoing connections or datagrams, but should not be
+ * used for binding services to.
+ */
+bool fwd_port_is_ephemeral(in_port_t port)
+{
+ return (port >= fwd_ephemeral_min) && (port <= fwd_ephemeral_max);
+}
+
/* See enum in kernel's include/net/tcp_states.h */
#define UDP_LISTEN 0x07
#define TCP_LISTEN 0x0a
@@ -167,7 +241,86 @@ void fwd_scan_ports_init(struct ctx *c)
static bool is_dns_flow(uint8_t proto, const struct flowside *ini)
{
return ((proto == IPPROTO_UDP) || (proto == IPPROTO_TCP)) &&
- ((ini->fport == 53) || (ini->fport == 853));
+ ((ini->oport == 53) || (ini->oport == 853));
+}
+
+/**
+ * fwd_guest_accessible4() - Is IPv4 address guest-accessible
+ * @c: Execution context
+ * @addr: Host visible IPv4 address
+ *
+ * Return: true if @addr on the host is accessible to the guest without
+ * translation, false otherwise
+ */
+static bool fwd_guest_accessible4(const struct ctx *c,
+ const struct in_addr *addr)
+{
+ if (IN4_IS_ADDR_LOOPBACK(addr))
+ return false;
+
+ /* In socket interfaces 0.0.0.0 generally means "any" or unspecified,
+ * however on the wire it can mean "this host on this network". Since
+ * that has a different meaning for host and guest, we can't let it
+ * through untranslated.
+ */
+ if (IN4_IS_ADDR_UNSPECIFIED(addr))
+ return false;
+
+ /* For IPv4, addr_seen is initialised to addr, so is always a valid
+ * address
+ */
+ if (IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr) ||
+ IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen))
+ return false;
+
+ return true;
+}
+
+/**
+ * fwd_guest_accessible6() - Is IPv6 address guest-accessible
+ * @c: Execution context
+ * @addr: Host visible IPv6 address
+ *
+ * Return: true if @addr on the host is accessible to the guest without
+ * translation, false otherwise
+ */
+static bool fwd_guest_accessible6(const struct ctx *c,
+ const struct in6_addr *addr)
+{
+ if (IN6_IS_ADDR_LOOPBACK(addr))
+ return false;
+
+ if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr))
+ return false;
+
+ /* For IPv6, addr_seen starts unspecified, because we don't know what LL
+ * address the guest will take until we see it. Only check against it
+ * if it has been set to a real address.
+ */
+ if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_seen) &&
+ IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr_seen))
+ return false;
+
+ return true;
+}
+
+/**
+ * fwd_guest_accessible() - Is IPv[46] address guest-accessible
+ * @c: Execution context
+ * @addr: Host visible IPv[46] address
+ *
+ * Return: true if @addr on the host is accessible to the guest without
+ * translation, false otherwise
+ */
+static bool fwd_guest_accessible(const struct ctx *c,
+ const union inany_addr *addr)
+{
+ const struct in_addr *a4 = inany_v4(addr);
+
+ if (a4)
+ return fwd_guest_accessible4(c, a4);
+
+ return fwd_guest_accessible6(c, &addr->a6);
}
/**
@@ -184,33 +337,37 @@ uint8_t fwd_nat_from_tap(const struct ctx *c, uint8_t proto,
const struct flowside *ini, struct flowside *tgt)
{
if (is_dns_flow(proto, ini) &&
- inany_equals4(&ini->faddr, &c->ip4.dns_match))
+ inany_equals4(&ini->oaddr, &c->ip4.dns_match))
tgt->eaddr = inany_from_v4(c->ip4.dns_host);
else if (is_dns_flow(proto, ini) &&
- inany_equals6(&ini->faddr, &c->ip6.dns_match))
+ inany_equals6(&ini->oaddr, &c->ip6.dns_match))
tgt->eaddr.a6 = c->ip6.dns_host;
- else if (!c->no_map_gw && inany_equals4(&ini->faddr, &c->ip4.gw))
+ else if (inany_equals4(&ini->oaddr, &c->ip4.map_host_loopback))
tgt->eaddr = inany_loopback4;
- else if (!c->no_map_gw && inany_equals6(&ini->faddr, &c->ip6.gw))
+ else if (inany_equals6(&ini->oaddr, &c->ip6.map_host_loopback))
tgt->eaddr = inany_loopback6;
+ else if (inany_equals4(&ini->oaddr, &c->ip4.map_guest_addr))
+ tgt->eaddr = inany_from_v4(c->ip4.addr);
+ else if (inany_equals6(&ini->oaddr, &c->ip6.map_guest_addr))
+ tgt->eaddr.a6 = c->ip6.addr;
else
- tgt->eaddr = ini->faddr;
+ tgt->eaddr = ini->oaddr;
- tgt->eport = ini->fport;
+ tgt->eport = ini->oport;
/* The relevant addr_out controls the host side source address. This
* may be unspecified, which allows the kernel to pick an address.
*/
if (inany_v4(&tgt->eaddr))
- tgt->faddr = inany_from_v4(c->ip4.addr_out);
+ tgt->oaddr = inany_from_v4(c->ip4.addr_out);
else
- tgt->faddr.a6 = c->ip6.addr_out;
+ tgt->oaddr.a6 = c->ip6.addr_out;
/* Let the kernel pick a host side source port */
- tgt->fport = 0;
+ tgt->oport = 0;
if (proto == IPPROTO_UDP) {
/* But for UDP we preserve the source port */
- tgt->fport = ini->eport;
+ tgt->oport = ini->eport;
}
return PIF_HOST;
@@ -230,13 +387,13 @@ uint8_t fwd_nat_from_splice(const struct ctx *c, uint8_t proto,
const struct flowside *ini, struct flowside *tgt)
{
if (!inany_is_loopback(&ini->eaddr) ||
- (!inany_is_loopback(&ini->faddr) && !inany_is_unspecified(&ini->faddr))) {
+ (!inany_is_loopback(&ini->oaddr) && !inany_is_unspecified(&ini->oaddr))) {
char estr[INANY_ADDRSTRLEN], fstr[INANY_ADDRSTRLEN];
debug("Non loopback address on %s: [%s]:%hu -> [%s]:%hu",
pif_name(PIF_SPLICE),
inany_ntop(&ini->eaddr, estr, sizeof(estr)), ini->eport,
- inany_ntop(&ini->faddr, fstr, sizeof(fstr)), ini->fport);
+ inany_ntop(&ini->oaddr, fstr, sizeof(fstr)), ini->oport);
return PIF_NONE;
}
@@ -248,20 +405,20 @@ uint8_t fwd_nat_from_splice(const struct ctx *c, uint8_t proto,
/* Preserve the specific loopback adddress used, but let the kernel pick
* a source port on the target side
*/
- tgt->faddr = ini->eaddr;
- tgt->fport = 0;
+ tgt->oaddr = ini->eaddr;
+ tgt->oport = 0;
- tgt->eport = ini->fport;
+ tgt->eport = ini->oport;
if (proto == IPPROTO_TCP)
tgt->eport += c->tcp.fwd_out.delta[tgt->eport];
else if (proto == IPPROTO_UDP)
tgt->eport += c->udp.fwd_out.delta[tgt->eport];
/* Let the kernel pick a host side source port */
- tgt->fport = 0;
+ tgt->oport = 0;
if (proto == IPPROTO_UDP)
/* But for UDP preserve the source port */
- tgt->fport = ini->eport;
+ tgt->oport = ini->eport;
return PIF_HOST;
}
@@ -280,53 +437,79 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
const struct flowside *ini, struct flowside *tgt)
{
/* Common for spliced and non-spliced cases */
- tgt->eport = ini->fport;
+ tgt->eport = ini->oport;
if (proto == IPPROTO_TCP)
tgt->eport += c->tcp.fwd_in.delta[tgt->eport];
else if (proto == IPPROTO_UDP)
tgt->eport += c->udp.fwd_in.delta[tgt->eport];
- if (c->mode == MODE_PASTA && inany_is_loopback(&ini->eaddr) &&
+ if (!c->no_splice && inany_is_loopback(&ini->eaddr) &&
(proto == IPPROTO_TCP || proto == IPPROTO_UDP)) {
/* spliceable */
- /* Preserve the specific loopback adddress used, but let the
- * kernel pick a source port on the target side
+ /* The traffic will go over the guest's 'lo' interface, but by
+ * default use its external address, so we don't inadvertently
+ * expose services that listen only on the guest's loopback
+ * address. That can be overridden by --host-lo-to-ns-lo which
+ * will instead forward to the loopback address in the guest.
+ *
+ * In either case, let the kernel pick the source address to
+ * match.
*/
- tgt->faddr = ini->eaddr;
- tgt->fport = 0;
+ if (inany_v4(&ini->eaddr)) {
+ if (c->host_lo_to_ns_lo)
+ tgt->eaddr = inany_loopback4;
+ else
+ tgt->eaddr = inany_from_v4(c->ip4.addr_seen);
+ tgt->oaddr = inany_any4;
+ } else {
+ if (c->host_lo_to_ns_lo)
+ tgt->eaddr = inany_loopback6;
+ else
+ tgt->eaddr.a6 = c->ip6.addr_seen;
+ tgt->oaddr = inany_any6;
+ }
+
+ /* Let the kernel pick source port */
+ tgt->oport = 0;
if (proto == IPPROTO_UDP)
/* But for UDP preserve the source port */
- tgt->fport = ini->eport;
-
- if (inany_v4(&ini->eaddr))
- tgt->eaddr = inany_loopback4;
- else
- tgt->eaddr = inany_loopback6;
+ tgt->oport = ini->eport;
return PIF_SPLICE;
}
- tgt->faddr = ini->eaddr;
- tgt->fport = ini->eport;
-
- if (inany_is_loopback4(&tgt->faddr) ||
- inany_is_unspecified4(&tgt->faddr) ||
- inany_equals4(&tgt->faddr, &c->ip4.addr_seen)) {
- tgt->faddr = inany_from_v4(c->ip4.gw);
- } else if (inany_is_loopback6(&tgt->faddr) ||
- inany_equals6(&tgt->faddr, &c->ip6.addr_seen) ||
- inany_equals6(&tgt->faddr, &c->ip6.addr)) {
- if (IN6_IS_ADDR_LINKLOCAL(&c->ip6.gw))
- tgt->faddr.a6 = c->ip6.gw;
- else
- tgt->faddr.a6 = c->ip6.addr_ll;
+ if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback) &&
+ inany_equals4(&ini->eaddr, &in4addr_loopback)) {
+ /* Specifically 127.0.0.1, not 127.0.0.0/8 */
+ tgt->oaddr = inany_from_v4(c->ip4.map_host_loopback);
+ } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback) &&
+ inany_equals6(&ini->eaddr, &in6addr_loopback)) {
+ tgt->oaddr.a6 = c->ip6.map_host_loopback;
+ } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) &&
+ inany_equals4(&ini->eaddr, &c->ip4.addr)) {
+ tgt->oaddr = inany_from_v4(c->ip4.map_guest_addr);
+ } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) &&
+ inany_equals6(&ini->eaddr, &c->ip6.addr)) {
+ tgt->oaddr.a6 = c->ip6.map_guest_addr;
+ } else if (!fwd_guest_accessible(c, &ini->eaddr)) {
+ if (inany_v4(&ini->eaddr)) {
+ if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
+ /* No source address we can use */
+ return PIF_NONE;
+ tgt->oaddr = inany_from_v4(c->ip4.our_tap_addr);
+ } else {
+ tgt->oaddr.a6 = c->ip6.our_tap_ll;
+ }
+ } else {
+ tgt->oaddr = ini->eaddr;
}
+ tgt->oport = ini->eport;
- if (inany_v4(&tgt->faddr)) {
+ if (inany_v4(&tgt->oaddr)) {
tgt->eaddr = inany_from_v4(c->ip4.addr_seen);
} else {
- if (inany_is_linklocal6(&tgt->faddr))
+ if (inany_is_linklocal6(&tgt->oaddr))
tgt->eaddr.a6 = c->ip6.addr_ll_seen;
else
tgt->eaddr.a6 = c->ip6.addr_seen;
diff --git a/fwd.h b/fwd.h
index b4aa8d5..3562f3c 100644
--- a/fwd.h
+++ b/fwd.h
@@ -12,6 +12,9 @@ struct flowside;
/* Number of ports for both TCP and UDP */
#define NUM_PORTS (1U << 16)
+void fwd_probe_ephemeral(void);
+bool fwd_port_is_ephemeral(in_port_t port);
+
enum fwd_ports_mode {
FWD_UNSET = 0,
FWD_SPEC = 1,
diff --git a/icmp.c b/icmp.c
index cb81c76..143e93b 100644
--- a/icmp.c
+++ b/icmp.c
@@ -85,7 +85,7 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref)
n = recvfrom(ref.fd, buf, sizeof(buf), 0, &sr.sa, &sl);
if (n < 0) {
- flow_err(pingf, "recvfrom() error: %s", strerror(errno));
+ flow_err(pingf, "recvfrom() error: %s", strerror_(errno));
return;
}
@@ -125,13 +125,13 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref)
ini->eport, seq);
if (pingf->f.type == FLOW_PING4) {
- const struct in_addr *saddr = inany_v4(&ini->faddr);
+ const struct in_addr *saddr = inany_v4(&ini->oaddr);
const struct in_addr *daddr = inany_v4(&ini->eaddr);
ASSERT(saddr && daddr); /* Must have IPv4 addresses */
tap_icmp4_send(c, *saddr, *daddr, buf, n);
} else if (pingf->f.type == FLOW_PING6) {
- const struct in6_addr *saddr = &ini->faddr.a6;
+ const struct in6_addr *saddr = &ini->oaddr.a6;
const struct in6_addr *daddr = &ini->eaddr.a6;
tap_icmp6_send(c, saddr, daddr, buf, n);
@@ -301,7 +301,7 @@ int icmp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
pif_sockaddr(c, &sa, &sl, PIF_HOST, &tgt->eaddr, 0);
if (sendto(pingf->sock, pkt, l4len, MSG_NOSIGNAL, &sa.sa, sl) < 0) {
flow_dbg(pingf, "failed to relay request to socket: %s",
- strerror(errno));
+ strerror_(errno));
} else {
flow_dbg(pingf,
"echo request to socket, ID: %"PRIu16", seq: %"PRIu16,
diff --git a/inany.c b/inany.c
index 5e391dc..f5483bf 100644
--- a/inany.c
+++ b/inany.c
@@ -36,3 +36,23 @@ const char *inany_ntop(const union inany_addr *src, char *dst, socklen_t size)
return inet_ntop(AF_INET6, &src->a6, dst, size);
}
+
+/** inany_pton - Parse an IPv[46] address from text format
+ * @src: IPv[46] address
+ * @dst: output buffer, filled with parsed address
+ *
+ * Return: On success, 1, if no parseable address is found, 0
+ */
+int inany_pton(const char *src, union inany_addr *dst)
+{
+ if (inet_pton(AF_INET, src, &dst->v4mapped.a4)) {
+ memset(&dst->v4mapped.zero, 0, sizeof(dst->v4mapped.zero));
+ memset(&dst->v4mapped.one, 0xff, sizeof(dst->v4mapped.one));
+ return 1;
+ }
+
+ if (inet_pton(AF_INET6, src, &dst->a6))
+ return 1;
+
+ return 0;
+}
diff --git a/inany.h b/inany.h
index d2893ce..6a12c29 100644
--- a/inany.h
+++ b/inany.h
@@ -270,5 +270,6 @@ static inline void inany_siphash_feed(struct siphash_state *state,
#define INANY_ADDRSTRLEN MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)
const char *inany_ntop(const union inany_addr *src, char *dst, socklen_t size);
+int inany_pton(const char *src, union inany_addr *dst);
#endif /* INANY_H */
diff --git a/iov.c b/iov.c
index 3f9e229..3b12272 100644
--- a/iov.c
+++ b/iov.c
@@ -68,7 +68,6 @@ size_t iov_skip_bytes(const struct iovec *iov, size_t n,
*
* Returns: The number of bytes successfully copied.
*/
-/* cppcheck-suppress unusedFunction */
size_t iov_from_buf(const struct iovec *iov, size_t iov_cnt,
size_t offset, const void *buf, size_t bytes)
{
@@ -156,3 +155,94 @@ size_t iov_size(const struct iovec *iov, size_t iov_cnt)
return len;
}
+
+/**
+ * iov_tail_prune() - Remove any unneeded buffers from an IOV tail
+ * @tail: IO vector tail (modified)
+ *
+ * If an IOV tail's offset is large enough, it may not include any bytes from
+ * the first (or first several) buffers in the underlying IO vector. Modify the
+ * tail's representation so it contains the same logical bytes, but only
+ * includes buffers that are actually needed. This will avoid stepping through
+ * unnecessary elements of the underlying IO vector on future operations.
+ *
+ * Return: true if the tail still contains any bytes, otherwise false
+ */
+bool iov_tail_prune(struct iov_tail *tail)
+{
+ size_t i;
+
+ i = iov_skip_bytes(tail->iov, tail->cnt, tail->off, &tail->off);
+ tail->iov += i;
+ tail->cnt -= i;
+
+ return !!tail->cnt;
+}
+
+/**
+ * iov_tail_size - Calculate the total size of an IO vector tail
+ * @tail: IO vector tail
+ *
+ * Returns: The total size in bytes.
+ */
+size_t iov_tail_size(struct iov_tail *tail)
+{
+ iov_tail_prune(tail);
+ return iov_size(tail->iov, tail->cnt) - tail->off;
+}
+
+/**
+ * iov_peek_header_() - Get pointer to a header from an IOV tail
+ * @tail: IOV tail to get header from
+ * @len: Length of header to get, in bytes
+ * @align: Required alignment of header, in bytes
+ *
+ * @tail may be pruned, but will represent the same bytes as before.
+ *
+ * Returns: Pointer to the first @len logical bytes of the tail, NULL if that
+ * overruns the IO vector, is not contiguous or doesn't have the
+ * requested alignment.
+ */
+void *iov_peek_header_(struct iov_tail *tail, size_t len, size_t align)
+{
+ char *p;
+
+ if (!iov_tail_prune(tail))
+ return NULL; /* Nothing left */
+
+ if (tail->off + len < tail->off)
+ return NULL; /* Overflow */
+
+ if (tail->off + len > tail->iov[0].iov_len)
+ return NULL; /* Not contiguous */
+
+ p = (char *)tail->iov[0].iov_base + tail->off;
+ if ((uintptr_t)p % align)
+ return NULL; /* not aligned */
+
+ return p;
+}
+
+/**
+ * iov_remove_header_() - Remove a header from an IOV tail
+ * @tail: IOV tail to remove header from (modified)
+ * @len: Length of header to remove, in bytes
+ * @align: Required alignment of header, in bytes
+ *
+ * On success, @tail is updated so that it longer includes the bytes of the
+ * returned header.
+ *
+ * Returns: Pointer to the first @len logical bytes of the tail, NULL if that
+ * overruns the IO vector, is not contiguous or doesn't have the
+ * requested alignment.
+ */
+void *iov_remove_header_(struct iov_tail *tail, size_t len, size_t align)
+{
+ char *p = iov_peek_header_(tail, len, align);
+
+ if (!p)
+ return NULL;
+
+ tail->off = tail->off + len;
+ return p;
+}
diff --git a/iov.h b/iov.h
index a9e1722..9855bf0 100644
--- a/iov.h
+++ b/iov.h
@@ -28,4 +28,80 @@ size_t iov_from_buf(const struct iovec *iov, size_t iov_cnt,
size_t iov_to_buf(const struct iovec *iov, size_t iov_cnt,
size_t offset, void *buf, size_t bytes);
size_t iov_size(const struct iovec *iov, size_t iov_cnt);
+
+/*
+ * DOC: Theory of Operation, struct iov_tail
+ *
+ * Sometimes a single logical network frame is split across multiple buffers,
+ * represented by an IO vector (struct iovec[]). We often want to process this
+ * one header / network layer at a time. So, it's useful to maintain a "tail"
+ * of the vector representing the parts we haven't yet extracted.
+ *
+ * The headers we extract need not line up with buffer boundaries (though we do
+ * assume they're contiguous within a single buffer for now). So, we could
+ * represent that tail as another struct iovec[], but that would mean copying
+ * the whole array of struct iovecs, just so we can adjust the offset and length
+ * on the first one.
+ *
+ * So, instead represent the tail as pointer into an existing struct iovec[],
+ * with an explicit offset for where the "tail" starts within it. If we extract
+ * enough headers that some buffers of the original vector no longer contain
+ * part of the tail, we (lazily) advance our struct iovec * to the first buffer
+ * we still need, and adjust the vector length and offset to match.
+ */
+
+/**
+ * struct iov_tail - An IO vector which may have some headers logically removed
+ * @iov: IO vector
+ * @cnt: Number of entries in @iov
+ * @off: Current offset in @iov
+ */
+struct iov_tail {
+ const struct iovec *iov;
+ size_t cnt, off;
+};
+
+/**
+ * IOV_TAIL() - Create a new IOV tail
+ * @iov_: IO vector to create tail from
+ * @cnt_: Length of the IO vector at @iov_
+ * @off_: Byte offset in the IO vector where the tail begins
+ */
+#define IOV_TAIL(iov_, cnt_, off_) \
+ (struct iov_tail){ .iov = (iov_), .cnt = (cnt_), .off = (off_) }
+
+bool iov_tail_prune(struct iov_tail *tail);
+size_t iov_tail_size(struct iov_tail *tail);
+void *iov_peek_header_(struct iov_tail *tail, size_t len, size_t align);
+void *iov_remove_header_(struct iov_tail *tail, size_t len, size_t align);
+
+/**
+ * IOV_PEEK_HEADER() - Get typed pointer to a header from an IOV tail
+ * @tail_: IOV tail to get header from
+ * @type_: Data type of the header
+ *
+ * @tail_ may be pruned, but will represent the same bytes as before.
+ *
+ * Returns: Pointer of type (@type_ *) located at the start of @tail_, NULL if
+ * we can't get a contiguous and aligned pointer.
+ */
+#define IOV_PEEK_HEADER(tail_, type_) \
+ ((type_ *)(iov_peek_header_((tail_), \
+ sizeof(type_), __alignof__(type_))))
+
+/**
+ * IOV_REMOVE_HEADER() - Remove and return typed header from an IOV tail
+ * @tail_: IOV tail to remove header from (modified)
+ * @type_: Data type of the header to remove
+ *
+ * On success, @tail_ is updated so that it longer includes the bytes of the
+ * returned header.
+ *
+ * Returns: Pointer of type (@type_ *) located at the old start of @tail_, NULL
+ * if we can't get a contiguous and aligned pointer.
+ */
+#define IOV_REMOVE_HEADER(tail_, type_) \
+ ((type_ *)(iov_remove_header_((tail_), \
+ sizeof(type_), __alignof__(type_))))
+
#endif /* IOVEC_H */
diff --git a/ip.h b/ip.h
index b8d4a5b..1544dbf 100644
--- a/ip.h
+++ b/ip.h
@@ -92,4 +92,16 @@ struct ipv6_opt_hdr {
char *ipv6_l4hdr(const struct pool *p, int idx, size_t offset, uint8_t *proto,
size_t *dlen);
+
+/* IPv6 link-local all-nodes multicast adddress, ff02::1 */
+static const struct in6_addr in6addr_ll_all_nodes = {
+ .s6_addr = {
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ },
+};
+
+/* IPv4 Limited Broadcast (RFC 919, Section 7), 255.255.255.255 */
+static const struct in_addr in4addr_broadcast = { 0xffffffff };
+
#endif /* IP_H */
diff --git a/isolation.c b/isolation.c
index 45fba1e..c944fb3 100644
--- a/isolation.c
+++ b/isolation.c
@@ -379,12 +379,21 @@ void isolate_postfork(const struct ctx *c)
prctl(PR_SET_DUMPABLE, 0);
- if (c->mode == MODE_PASTA) {
- prog.len = (unsigned short)ARRAY_SIZE(filter_pasta);
- prog.filter = filter_pasta;
- } else {
+ switch (c->mode) {
+ case MODE_PASST:
prog.len = (unsigned short)ARRAY_SIZE(filter_passt);
prog.filter = filter_passt;
+ break;
+ case MODE_PASTA:
+ prog.len = (unsigned short)ARRAY_SIZE(filter_pasta);
+ prog.filter = filter_pasta;
+ break;
+ case MODE_VU:
+ prog.len = (unsigned short)ARRAY_SIZE(filter_vu);
+ prog.filter = filter_vu;
+ break;
+ default:
+ ASSERT(0);
}
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
diff --git a/linux_dep.h b/linux_dep.h
new file mode 100644
index 0000000..240f50a
--- /dev/null
+++ b/linux_dep.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ *
+ * Declarations for Linux specific dependencies
+ */
+
+#ifndef LINUX_DEP_H
+#define LINUX_DEP_H
+
+/* struct tcp_info_linux - Information from Linux TCP_INFO getsockopt()
+ *
+ * Largely derived from include/linux/tcp.h in the Linux kernel
+ *
+ * Some fields returned by TCP_INFO have been there for ages and are shared with
+ * BSD. struct tcp_info from netinet/tcp.h has only those fields. There are
+ * also a many Linux specific extensions to the structure, which are only found
+ * in the linux/tcp.h version of struct tcp_info.
+ *
+ * We want to use some of those extension fields, when available. We can test
+ * for availability in the runtime kernel using the length returned from
+ * getsockopt(). However, we won't necessarily be compiled against the same
+ * kernel headers as we'll run with, so compiling directly against linux/tcp.h
+ * means wrapping every field access in an #ifdef whose #else does the same
+ * thing as when the field is missing at runtime. This rapidly gets messy.
+ *
+ * Instead we define here struct tcp_info_linux which includes all the Linux
+ * extensions that we want to use. This is taken from v6.11 of the kernel.
+ */
+struct tcp_info_linux {
+ uint8_t tcpi_state;
+ uint8_t tcpi_ca_state;
+ uint8_t tcpi_retransmits;
+ uint8_t tcpi_probes;
+ uint8_t tcpi_backoff;
+ uint8_t tcpi_options;
+ uint8_t tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+ uint8_t tcpi_delivery_rate_app_limited:1, tcpi_fastopen_client_fail:2;
+
+ uint32_t tcpi_rto;
+ uint32_t tcpi_ato;
+ uint32_t tcpi_snd_mss;
+ uint32_t tcpi_rcv_mss;
+
+ uint32_t tcpi_unacked;
+ uint32_t tcpi_sacked;
+ uint32_t tcpi_lost;
+ uint32_t tcpi_retrans;
+ uint32_t tcpi_fackets;
+
+ /* Times. */
+ uint32_t tcpi_last_data_sent;
+ uint32_t tcpi_last_ack_sent;
+ uint32_t tcpi_last_data_recv;
+ uint32_t tcpi_last_ack_recv;
+
+ /* Metrics. */
+ uint32_t tcpi_pmtu;
+ uint32_t tcpi_rcv_ssthresh;
+ uint32_t tcpi_rtt;
+ uint32_t tcpi_rttvar;
+ uint32_t tcpi_snd_ssthresh;
+ uint32_t tcpi_snd_cwnd;
+ uint32_t tcpi_advmss;
+ uint32_t tcpi_reordering;
+
+ uint32_t tcpi_rcv_rtt;
+ uint32_t tcpi_rcv_space;
+
+ uint32_t tcpi_total_retrans;
+
+ /* Linux extensions */
+ uint64_t tcpi_pacing_rate;
+ uint64_t tcpi_max_pacing_rate;
+ uint64_t tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
+ uint64_t tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
+ uint32_t tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */
+ uint32_t tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */
+
+ uint32_t tcpi_notsent_bytes;
+ uint32_t tcpi_min_rtt;
+ uint32_t tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */
+ uint32_t tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */
+
+ uint64_t tcpi_delivery_rate;
+
+ uint64_t tcpi_busy_time; /* Time (usec) busy sending data */
+ uint64_t tcpi_rwnd_limited; /* Time (usec) limited by receive window */
+ uint64_t tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */
+
+ uint32_t tcpi_delivered;
+ uint32_t tcpi_delivered_ce;
+
+ uint64_t tcpi_bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut */
+ uint64_t tcpi_bytes_retrans; /* RFC4898 tcpEStatsPerfOctetsRetrans */
+ uint32_t tcpi_dsack_dups; /* RFC4898 tcpEStatsStackDSACKDups */
+ uint32_t tcpi_reord_seen; /* reordering events seen */
+
+ uint32_t tcpi_rcv_ooopack; /* Out-of-order packets received */
+
+ uint32_t tcpi_snd_wnd; /* peer's advertised receive window after
+ * scaling (bytes)
+ */
+ uint32_t tcpi_rcv_wnd; /* local advertised receive window after
+ * scaling (bytes)
+ */
+
+ uint32_t tcpi_rehash; /* PLB or timeout triggered rehash attempts */
+
+ uint16_t tcpi_total_rto; /* Total number of RTO timeouts, including
+ * SYN/SYN-ACK and recurring timeouts.
+ */
+ uint16_t tcpi_total_rto_recoveries; /* Total number of RTO
+ * recoveries, including any
+ * unfinished recovery.
+ */
+ uint32_t tcpi_total_rto_time; /* Total time spent in RTO recoveries
+ * in milliseconds, including any
+ * unfinished recovery.
+ */
+};
+
+#include <linux/falloc.h>
+
+#ifndef FALLOC_FL_COLLAPSE_RANGE
+#define FALLOC_FL_COLLAPSE_RANGE 0x08
+#endif
+
+#include <linux/close_range.h>
+
+/* glibc < 2.34 and musl as of 1.2.5 need these */
+#ifndef SYS_close_range
+#define SYS_close_range 436
+#endif
+#ifndef CLOSE_RANGE_UNSHARE /* Linux kernel < 5.9 */
+#define CLOSE_RANGE_UNSHARE (1U << 1)
+#endif
+
+__attribute__ ((weak))
+/* cppcheck-suppress funcArgNamesDifferent */
+int close_range(unsigned int first, unsigned int last, int flags) {
+ return syscall(SYS_close_range, first, last, flags);
+}
+
+#endif /* LINUX_DEP_H */
diff --git a/log.c b/log.c
index e7202d0..95e4576 100644
--- a/log.c
+++ b/log.c
@@ -26,6 +26,7 @@
#include <stdarg.h>
#include <sys/socket.h>
+#include "linux_dep.h"
#include "log.h"
#include "util.h"
#include "passt.h"
@@ -92,13 +93,12 @@ const char *logfile_prefix[] = {
" ", /* LOG_DEBUG */
};
-#ifdef FALLOC_FL_COLLAPSE_RANGE
/**
* logfile_rotate_fallocate() - Write header, set log_written after fallocate()
* @fd: Log file descriptor
* @now: Current timestamp
*
- * #syscalls lseek ppc64le:_llseek ppc64:_llseek arm:_llseek
+ * #syscalls lseek ppc64le:_llseek ppc64:_llseek arm:_llseek i686:_llseek
*/
static void logfile_rotate_fallocate(int fd, const struct timespec *now)
{
@@ -126,7 +126,6 @@ static void logfile_rotate_fallocate(int fd, const struct timespec *now)
log_written -= log_cut_size;
}
-#endif /* FALLOC_FL_COLLAPSE_RANGE */
/**
* logfile_rotate_move() - Fallback: move recent entries toward start, then cut
@@ -198,21 +197,17 @@ out:
*
* Return: 0 on success, negative error code on failure
*
- * #syscalls fcntl
- *
- * fallocate() passed as EXTRA_SYSCALL only if FALLOC_FL_COLLAPSE_RANGE is there
+ * #syscalls fcntl fallocate
*/
static int logfile_rotate(int fd, const struct timespec *now)
{
if (fcntl(fd, F_SETFL, O_RDWR /* Drop O_APPEND: explicit lseek() */))
return -errno;
-#ifdef FALLOC_FL_COLLAPSE_RANGE
/* Only for Linux >= 3.15, extent-based ext4 or XFS, glibc >= 2.18 */
if (!fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, 0, log_cut_size))
logfile_rotate_fallocate(fd, now);
else
-#endif
logfile_rotate_move(fd, now);
if (fcntl(fd, F_SETFL, O_RDWR | O_APPEND))
@@ -224,19 +219,23 @@ static int logfile_rotate(int fd, const struct timespec *now)
/**
* logfile_write() - Write entry to log file, trigger rotation if full
* @newline: Append newline at the end of the message, if missing
+ * @cont: Continuation of a previous message, on the same line
* @pri: Facility and level map, same as priority for vsyslog()
* @now: Timestamp
* @format: Same as vsyslog() format
* @ap: Same as vsyslog() ap
*/
-static void logfile_write(bool newline, int pri, const struct timespec *now,
+static void logfile_write(bool newline, bool cont, int pri,
+ const struct timespec *now,
const char *format, va_list ap)
{
char buf[BUFSIZ];
- int n;
+ int n = 0;
- n = logtime_fmt(buf, BUFSIZ, now);
- n += snprintf(buf + n, BUFSIZ - n, ": %s", logfile_prefix[pri]);
+ if (!cont) {
+ n += logtime_fmt(buf, BUFSIZ, now);
+ n += snprintf(buf + n, BUFSIZ - n, ": %s", logfile_prefix[pri]);
+ }
n += vsnprintf(buf + n, BUFSIZ - n, format, ap);
@@ -270,7 +269,7 @@ void vlogmsg(bool newline, bool cont, int pri, const char *format, va_list ap)
char timestr[LOGTIME_STRLEN];
logtime_fmt(timestr, sizeof(timestr), now);
- fprintf(stderr, "%s: ", timestr);
+ FPRINTF(stderr, "%s: ", timestr);
}
if ((log_mask & LOG_MASK(LOG_PRI(pri))) || !log_conf_parsed) {
@@ -278,7 +277,7 @@ void vlogmsg(bool newline, bool cont, int pri, const char *format, va_list ap)
va_copy(ap2, ap); /* Don't clobber ap, we need it again */
if (log_file != -1)
- logfile_write(newline, pri, now, format, ap2);
+ logfile_write(newline, cont, pri, now, format, ap2);
else if (!(log_mask & LOG_MASK(LOG_DEBUG)))
passt_vsyslog(newline, pri, format, ap2);
@@ -289,7 +288,7 @@ void vlogmsg(bool newline, bool cont, int pri, const char *format, va_list ap)
(log_stderr && (log_mask & LOG_MASK(LOG_PRI(pri))))) {
(void)vfprintf(stderr, format, ap);
if (newline && format[strlen(format)] != '\n')
- fprintf(stderr, "\n");
+ FPRINTF(stderr, "\n");
}
}
@@ -323,7 +322,7 @@ void logmsg_perror(int pri, const char *format, ...)
vlogmsg(false, false, pri, format, ap);
va_end(ap);
- logmsg(true, true, pri, ": %s", strerror(errno_copy));
+ logmsg(true, true, pri, ": %s", strerror_(errno_copy));
}
/**
@@ -395,7 +394,7 @@ void passt_vsyslog(bool newline, int pri, const char *format, va_list ap)
n += snprintf(buf + n, BUFSIZ - n, "\n");
if (log_sock >= 0 && send(log_sock, buf, n, 0) != n && log_stderr)
- fprintf(stderr, "Failed to send %i bytes to syslog\n", n);
+ FPRINTF(stderr, "Failed to send %i bytes to syslog\n", n);
}
/**
@@ -412,8 +411,7 @@ void logfile_init(const char *name, const char *path, size_t size)
if (readlink("/proc/self/exe", exe, PATH_MAX - 1) < 0)
die_perror("Failed to read own /proc/self/exe link");
- log_file = open(path, O_CREAT | O_TRUNC | O_APPEND | O_RDWR | O_CLOEXEC,
- S_IRUSR | S_IWUSR);
+ log_file = output_file_open(path, O_APPEND | O_RDWR);
if (log_file == -1)
die_perror("Couldn't open log file %s", path);
@@ -429,4 +427,3 @@ void logfile_init(const char *name, const char *path, size_t size)
/* For FALLOC_FL_COLLAPSE_RANGE: VFS block size can be up to one page */
log_cut_size = ROUND_UP(log_size * LOGFILE_CUT_RATIO / 100, PAGE_SIZE);
}
-
diff --git a/ndp.c b/ndp.c
index 6dcb487..37bf7a3 100644
--- a/ndp.c
+++ b/ndp.c
@@ -33,6 +33,8 @@
#include "tap.h"
#include "log.h"
+#define RT_LIFETIME 65535
+
#define RS 133
#define RA 134
#define NS 135
@@ -158,7 +160,7 @@ struct ndp_ra {
unsigned char var[sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) +
sizeof(struct opt_dnssl)];
-} __attribute__((packed));
+} __attribute__((packed, aligned(__alignof__(struct in6_addr))));
/**
* struct ndp_ns - NDP Neighbor Solicitation (NS) message
@@ -168,19 +170,31 @@ struct ndp_ra {
struct ndp_ns {
struct icmp6hdr ih;
struct in6_addr target_addr;
-} __attribute__((packed));
+} __attribute__((packed, aligned(__alignof__(struct in6_addr))));
/**
- * ndp() - Check for NDP solicitations, reply as needed
+ * ndp_send() - Send an NDP message
* @c: Execution context
- * @ih: ICMPv6 header
- * @saddr: Source IPv6 address
- * @p: Packet pool
- *
- * Return: 0 if not handled here, 1 if handled, -1 on failure
+ * @dst: IPv6 address to send the message to
+ * @buf: ICMPv6 header + message payload
+ * @l4len: Length of message, including ICMPv6 header
*/
-int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
- const struct pool *p)
+static void ndp_send(const struct ctx *c, const struct in6_addr *dst,
+ const void *buf, size_t l4len)
+{
+ const struct in6_addr *src = &c->ip6.our_tap_ll;
+
+ tap_icmp6_send(c, src, dst, buf, l4len);
+}
+
+/**
+ * ndp_na() - Send an NDP Neighbour Advertisement (NA) message
+ * @c: Execution context
+ * @dst: IPv6 address to send the NA to
+ * @addr: IPv6 address to advertise
+ */
+static void ndp_na(const struct ctx *c, const struct in6_addr *dst,
+ const struct in6_addr *addr)
{
struct ndp_na na = {
.ih = {
@@ -190,6 +204,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.icmp6_solicited = 1,
.icmp6_override = 1,
},
+ .target_addr = *addr,
.target_l2_addr = {
.header = {
.type = OPT_TARGET_L2_ADDR,
@@ -197,13 +212,26 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
},
}
};
+
+ memcpy(na.target_l2_addr.mac, c->our_tap_mac, ETH_ALEN);
+
+ ndp_send(c, dst, &na, sizeof(na));
+}
+
+/**
+ * ndp_ra() - Send an NDP Router Advertisement (RA) message
+ * @c: Execution context
+ * @dst: IPv6 address to send the RA to
+ */
+static void ndp_ra(const struct ctx *c, const struct in6_addr *dst)
+{
struct ndp_ra ra = {
.ih = {
.icmp6_type = RA,
.icmp6_code = 0,
.icmp6_hop_limit = 255,
/* RFC 8319 */
- .icmp6_rt_lifetime = htons_constant(65535),
+ .icmp6_rt_lifetime = htons_constant(RT_LIFETIME),
.icmp6_addrconf_managed = 1,
},
.prefix_info = {
@@ -216,6 +244,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.valid_lifetime = ~0U,
.pref_lifetime = ~0U,
},
+ .prefix = c->ip6.addr,
.source_ll = {
.header = {
.type = OPT_SRC_L2_ADDR,
@@ -223,59 +252,26 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
},
},
};
- const struct in6_addr *rsaddr; /* src addr for reply */
unsigned char *ptr = NULL;
- size_t dlen;
- if (ih->icmp6_type < RS || ih->icmp6_type > NA)
- return 0;
-
- if (c->no_ndp)
- return 1;
-
- if (ih->icmp6_type == NS) {
- struct ndp_ns *ns = packet_get(p, 0, 0, sizeof(struct ndp_ns),
- NULL);
-
- if (!ns)
- return -1;
-
- if (IN6_IS_ADDR_UNSPECIFIED(saddr))
- return 1;
+ ptr = &ra.var[0];
- info("NDP: received NS, sending NA");
-
- memcpy(&na.target_addr, &ns->target_addr,
- sizeof(na.target_addr));
- memcpy(na.target_l2_addr.mac, c->mac, ETH_ALEN);
+ if (c->mtu != -1) {
+ struct opt_mtu *mtu = (struct opt_mtu *)ptr;
+ *mtu = (struct opt_mtu) {
+ .header = {
+ .type = OPT_MTU,
+ .len = 1,
+ },
+ .value = htonl(c->mtu),
+ };
+ ptr += sizeof(struct opt_mtu);
+ }
- } else if (ih->icmp6_type == RS) {
+ if (!c->no_dhcp_dns) {
size_t dns_s_len = 0;
int i, n;
- if (c->no_ra)
- return 1;
-
- info("NDP: received RS, sending RA");
- memcpy(&ra.prefix, &c->ip6.addr, sizeof(ra.prefix));
-
- ptr = &ra.var[0];
-
- if (c->mtu != -1) {
- struct opt_mtu *mtu = (struct opt_mtu *)ptr;
- *mtu = (struct opt_mtu) {
- .header = {
- .type = OPT_MTU,
- .len = 1,
- },
- .value = htonl(c->mtu),
- };
- ptr += sizeof(struct opt_mtu);
- }
-
- if (c->no_dhcp_dns)
- goto dns_done;
-
for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n]); n++);
if (n) {
struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr;
@@ -287,8 +283,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.lifetime = ~0U,
};
for (i = 0; i < n; i++) {
- memcpy(&rdnss->dns[i], &c->ip6.dns[i],
- sizeof(rdnss->dns[i]));
+ rdnss->dns[i] = c->ip6.dns[i];
}
ptr += offsetof(struct opt_rdnss, dns) +
i * sizeof(rdnss->dns[0]);
@@ -329,30 +324,109 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
memset(ptr, 0, 8 - dns_s_len % 8); /* padding */
ptr += 8 - dns_s_len % 8;
}
-
-dns_done:
- memcpy(&ra.source_ll.mac, c->mac, ETH_ALEN);
- } else {
- return 1;
}
- if (IN6_IS_ADDR_LINKLOCAL(saddr))
- c->ip6.addr_ll_seen = *saddr;
- else
- c->ip6.addr_seen = *saddr;
+ memcpy(&ra.source_ll.mac, c->our_tap_mac, ETH_ALEN);
+
+ ndp_send(c, dst, &ra, ptr - (unsigned char *)&ra);
+}
+
+/**
+ * ndp() - Check for NDP solicitations, reply as needed
+ * @c: Execution context
+ * @ih: ICMPv6 header
+ * @saddr: Source IPv6 address
+ * @p: Packet pool
+ *
+ * Return: 0 if not handled here, 1 if handled, -1 on failure
+ */
+int ndp(const struct ctx *c, const struct icmp6hdr *ih,
+ const struct in6_addr *saddr, const struct pool *p)
+{
+ if (ih->icmp6_type < RS || ih->icmp6_type > NA)
+ return 0;
- if (IN6_IS_ADDR_LINKLOCAL(&c->ip6.gw))
- rsaddr = &c->ip6.gw;
- else
- rsaddr = &c->ip6.addr_ll;
+ if (c->no_ndp)
+ return 1;
if (ih->icmp6_type == NS) {
- dlen = sizeof(struct ndp_na);
- tap_icmp6_send(c, rsaddr, saddr, &na, dlen);
+ const struct ndp_ns *ns;
+
+ ns = packet_get(p, 0, 0, sizeof(struct ndp_ns), NULL);
+ if (!ns)
+ return -1;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(saddr))
+ return 1;
+
+ info("NDP: received NS, sending NA");
+
+ ndp_na(c, saddr, &ns->target_addr);
} else if (ih->icmp6_type == RS) {
- dlen = ptr - (unsigned char *)&ra;
- tap_icmp6_send(c, rsaddr, saddr, &ra, dlen);
+ if (c->no_ra)
+ return 1;
+
+ info("NDP: received RS, sending RA");
+ ndp_ra(c, saddr);
}
return 1;
}
+
+/* Default interval between unsolicited RAs (seconds) */
+#define DEFAULT_MAX_RTR_ADV_INTERVAL 600 /* RFC 4861, 6.2.1 */
+
+/* Minimum required interval between RAs (seconds) */
+#define MIN_DELAY_BETWEEN_RAS 3 /* RFC 4861, 10 */
+
+static time_t next_ra;
+
+/**
+ * ndp_timer() - Send unsolicited NDP messages if necessary
+ * @c: Execution context
+ * @now: Current (monotonic) time
+ */
+void ndp_timer(const struct ctx *c, const struct timespec *now)
+{
+ time_t max_rtr_adv_interval = DEFAULT_MAX_RTR_ADV_INTERVAL;
+ time_t min_rtr_adv_interval, interval;
+
+ if (c->fd_tap < 0 || c->no_ra || now->tv_sec < next_ra)
+ return;
+
+ /* We must advertise before the route's lifetime expires */
+ max_rtr_adv_interval = MIN(max_rtr_adv_interval, RT_LIFETIME - 1);
+
+ /* But we must not go smaller than the minimum delay */
+ max_rtr_adv_interval = MAX(max_rtr_adv_interval, MIN_DELAY_BETWEEN_RAS);
+
+ /* RFC 4861, 6.2.1 */
+ min_rtr_adv_interval = MAX(max_rtr_adv_interval / 3,
+ MIN_DELAY_BETWEEN_RAS);
+
+ /* As required by RFC 4861, we randomise the interval between
+ * unsolicited RAs. This is to prevent multiple routers on a link
+ * getting synchronised (e.g. after booting a bunch of routers at once)
+ * and causing flurries of RAs at the same time.
+ *
+ * This random doesn't need to be cryptographically strong, so random(3)
+ * is fine. Other routers on the link also want to avoid
+ * synchronisation, and anything malicious has much easier ways to cause
+ * trouble.
+ *
+ * The modulus also makes this not strictly a uniform distribution, but,
+ * again, it's close enough for our purposes.
+ */
+ interval = min_rtr_adv_interval +
+ random() % (max_rtr_adv_interval - min_rtr_adv_interval);
+
+ if (!next_ra)
+ goto first;
+
+ info("NDP: sending unsolicited RA, next in %llds", (long long)interval);
+
+ ndp_ra(c, &in6addr_ll_all_nodes);
+
+first:
+ next_ra = now->tv_sec + interval;
+}
diff --git a/ndp.h b/ndp.h
index a786441..41c2000 100644
--- a/ndp.h
+++ b/ndp.h
@@ -6,7 +6,10 @@
#ifndef NDP_H
#define NDP_H
-int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
- const struct pool *p);
+struct icmp6hdr;
+
+int ndp(const struct ctx *c, const struct icmp6hdr *ih,
+ const struct in6_addr *saddr, const struct pool *p);
+void ndp_timer(const struct ctx *c, const struct timespec *now);
#endif /* NDP_H */
diff --git a/netlink.c b/netlink.c
index 093de26..0407692 100644
--- a/netlink.c
+++ b/netlink.c
@@ -320,7 +320,7 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
}
if (status < 0)
- warn("netlink: RTM_GETROUTE failed: %s", strerror(-status));
+ warn("netlink: RTM_GETROUTE failed: %s", strerror_(-status));
if (defifi) {
if (ndef > 1) {
@@ -353,7 +353,7 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
*/
bool nl_route_get_def_multipath(struct rtattr *rta, void *gw)
{
- size_t nh_len = RTA_PAYLOAD(rta);
+ int nh_len = RTA_PAYLOAD(rta);
struct rtnexthop *rtnh;
bool found = false;
int hops = -1;
@@ -582,7 +582,7 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
*(unsigned int *)RTA_DATA(rta) = ifi_dst;
} else if (rta->rta_type == RTA_MULTIPATH) {
- size_t nh_len = RTA_PAYLOAD(rta);
+ int nh_len = RTA_PAYLOAD(rta);
struct rtnexthop *rtnh;
for (rtnh = (struct rtnexthop *)RTA_DATA(rta);
@@ -674,6 +674,63 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
}
/**
+ * nl_addr_set_ll_nodad() - Set IFA_F_NODAD on IPv6 link-local addresses
+ * @s: Netlink socket
+ * @ifi: Interface index in target namespace
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int nl_addr_set_ll_nodad(int s, unsigned int ifi)
+{
+ struct req_t {
+ struct nlmsghdr nlh;
+ struct ifaddrmsg ifa;
+ } req = {
+ .ifa.ifa_family = AF_INET6,
+ .ifa.ifa_index = ifi,
+ };
+ uint32_t seq, last_seq = 0;
+ ssize_t status, ret = 0;
+ struct nlmsghdr *nh;
+ char buf[NLBUFSIZ];
+
+ seq = nl_send(s, &req, RTM_GETADDR, NLM_F_DUMP, sizeof(req));
+ nl_foreach_oftype(nh, status, s, buf, seq, RTM_NEWADDR) {
+ struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nh);
+ struct rtattr *rta;
+ size_t na;
+
+ if (ifa->ifa_index != ifi || ifa->ifa_scope != RT_SCOPE_LINK)
+ continue;
+
+ ifa->ifa_flags |= IFA_F_NODAD;
+
+ for (rta = IFA_RTA(ifa), na = IFA_PAYLOAD(nh); RTA_OK(rta, na);
+ rta = RTA_NEXT(rta, na)) {
+ /* If 32-bit flags are used, add IFA_F_NODAD there */
+ if (rta->rta_type == IFA_FLAGS)
+ *(uint32_t *)RTA_DATA(rta) |= IFA_F_NODAD;
+ }
+
+ last_seq = nl_send(s, nh, RTM_NEWADDR, NLM_F_REPLACE,
+ nh->nlmsg_len);
+ }
+
+ if (status < 0)
+ ret = status;
+
+ for (seq = seq + 1; seq <= last_seq; seq++) {
+ nl_foreach(nh, status, s, buf, seq)
+ warn("netlink: Unexpected response message");
+
+ if (!ret && status < 0)
+ ret = status;
+ }
+
+ return ret;
+}
+
+/**
* nl_addr_get() - Get most specific global address, given interface and family
* @s: Netlink socket
* @ifi: Interface index in outer network namespace
@@ -682,7 +739,7 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
* @prefix_len: Mask or prefix length, to fill (for IPv4)
* @addr_l: Link-scoped address to fill (for IPv6)
*
- * Return: 9 on success, negative error code on failure
+ * Return: 0 on success, negative error code on failure
*/
int nl_addr_get(int s, unsigned int ifi, sa_family_t af,
void *addr, int *prefix_len, void *addr_l)
@@ -740,7 +797,54 @@ int nl_addr_get(int s, unsigned int ifi, sa_family_t af,
}
/**
- * nl_add_set() - Set IP addresses for given interface and address family
+ * nl_addr_get_ll() - Get first IPv6 link-local address for a given interface
+ * @s: Netlink socket
+ * @ifi: Interface index in outer network namespace
+ * @addr: Link-local address to fill
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int nl_addr_get_ll(int s, unsigned int ifi, struct in6_addr *addr)
+{
+ struct req_t {
+ struct nlmsghdr nlh;
+ struct ifaddrmsg ifa;
+ } req = {
+ .ifa.ifa_family = AF_INET6,
+ .ifa.ifa_index = ifi,
+ };
+ struct nlmsghdr *nh;
+ bool found = false;
+ char buf[NLBUFSIZ];
+ ssize_t status;
+ uint32_t seq;
+
+ seq = nl_send(s, &req, RTM_GETADDR, NLM_F_DUMP, sizeof(req));
+ nl_foreach_oftype(nh, status, s, buf, seq, RTM_NEWADDR) {
+ struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nh);
+ struct rtattr *rta;
+ size_t na;
+
+ if (ifa->ifa_index != ifi || ifa->ifa_scope != RT_SCOPE_LINK ||
+ found)
+ continue;
+
+ for (rta = IFA_RTA(ifa), na = IFA_PAYLOAD(nh); RTA_OK(rta, na);
+ rta = RTA_NEXT(rta, na)) {
+ if (rta->rta_type != IFA_ADDRESS)
+ continue;
+
+ if (!found) {
+ memcpy(addr, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ found = true;
+ }
+ }
+ }
+ return status;
+}
+
+/**
+ * nl_addr_set() - Set IP addresses for given interface and address family
* @s: Netlink socket
* @ifi: Interface index
* @af: Address family
@@ -942,14 +1046,14 @@ int nl_link_set_mac(int s, unsigned int ifi, const void *mac)
}
/**
- * nl_link_up() - Bring link up
+ * nl_link_set_mtu() - Set link MTU
* @s: Netlink socket
* @ifi: Interface index
- * @mtu: If non-zero, set interface MTU
+ * @mtu: Interface MTU
*
* Return: 0 on success, negative error code on failure
*/
-int nl_link_up(int s, unsigned int ifi, int mtu)
+int nl_link_set_mtu(int s, unsigned int ifi, int mtu)
{
struct req_t {
struct nlmsghdr nlh;
@@ -959,17 +1063,35 @@ int nl_link_up(int s, unsigned int ifi, int mtu)
} req = {
.ifm.ifi_family = AF_UNSPEC,
.ifm.ifi_index = ifi,
- .ifm.ifi_flags = IFF_UP,
- .ifm.ifi_change = IFF_UP,
.rta.rta_type = IFLA_MTU,
.rta.rta_len = RTA_LENGTH(sizeof(unsigned int)),
.mtu = mtu,
};
- ssize_t len = sizeof(req);
- if (!mtu)
- /* Shorten request to drop MTU attribute */
- len = offsetof(struct req_t, rta);
+ return nl_do(s, &req, RTM_NEWLINK, 0, sizeof(req));
+}
+
+/**
+ * nl_link_set_flags() - Set link flags
+ * @s: Netlink socket
+ * @ifi: Interface index
+ * @set: Device flags to set
+ * @change: Mask of device flag changes
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int nl_link_set_flags(int s, unsigned int ifi,
+ unsigned int set, unsigned int change)
+{
+ struct req_t {
+ struct nlmsghdr nlh;
+ struct ifinfomsg ifm;
+ } req = {
+ .ifm.ifi_family = AF_UNSPEC,
+ .ifm.ifi_index = ifi,
+ .ifm.ifi_flags = set,
+ .ifm.ifi_change = change,
+ };
- return nl_do(s, &req, RTM_NEWLINK, 0, len);
+ return nl_do(s, &req, RTM_NEWLINK, 0, sizeof(req));
}
diff --git a/netlink.h b/netlink.h
index 3a1f0de..b51e99c 100644
--- a/netlink.h
+++ b/netlink.h
@@ -19,10 +19,14 @@ int nl_addr_get(int s, unsigned int ifi, sa_family_t af,
void *addr, int *prefix_len, void *addr_l);
int nl_addr_set(int s, unsigned int ifi, sa_family_t af,
const void *addr, int prefix_len);
+int nl_addr_get_ll(int s, unsigned int ifi, struct in6_addr *addr);
+int nl_addr_set_ll_nodad(int s, unsigned int ifi);
int nl_addr_dup(int s_src, unsigned int ifi_src,
int s_dst, unsigned int ifi_dst, sa_family_t af);
int nl_link_get_mac(int s, unsigned int ifi, void *mac);
int nl_link_set_mac(int s, unsigned int ifi, const void *mac);
-int nl_link_up(int s, unsigned int ifi, int mtu);
+int nl_link_set_mtu(int s, unsigned int ifi, int mtu);
+int nl_link_set_flags(int s, unsigned int ifi,
+ unsigned int set, unsigned int change);
#endif /* NETLINK_H */
diff --git a/packet.c b/packet.c
index ccfc846..03a11e6 100644
--- a/packet.c
+++ b/packet.c
@@ -23,6 +23,46 @@
#include "log.h"
/**
+ * packet_check_range() - Check if a packet memory range is valid
+ * @p: Packet pool
+ * @offset: Offset of data range in packet descriptor
+ * @len: Length of desired data range
+ * @start: Start of the packet descriptor
+ * @func: For tracing: name of calling function
+ * @line: For tracing: caller line of function call
+ *
+ * Return: 0 if the range is valid, -1 otherwise
+ */
+static int packet_check_range(const struct pool *p, size_t offset, size_t len,
+ const char *start, const char *func, int line)
+{
+ if (p->buf_size == 0) {
+ int ret;
+
+ ret = vu_packet_check_range((void *)p->buf, offset, len, start);
+
+ if (ret == -1)
+ trace("cannot find region, %s:%i", func, line);
+
+ return ret;
+ }
+
+ if (start < p->buf) {
+ trace("packet start %p before buffer start %p, "
+ "%s:%i", (void *)start, (void *)p->buf, func, line);
+ return -1;
+ }
+
+ if (start + len + offset > p->buf + p->buf_size) {
+ trace("packet offset plus length %zu from size %zu, "
+ "%s:%i", start - p->buf + len + offset,
+ p->buf_size, func, line);
+ return -1;
+ }
+
+ return 0;
+}
+/**
* packet_add_do() - Add data as packet descriptor to given pool
* @p: Existing pool
* @len: Length of new descriptor
@@ -41,34 +81,16 @@ void packet_add_do(struct pool *p, size_t len, const char *start,
return;
}
- if (start < p->buf) {
- trace("add packet start %p before buffer start %p, %s:%i",
- (void *)start, (void *)p->buf, func, line);
- return;
- }
-
- if (start + len > p->buf + p->buf_size) {
- trace("add packet start %p, length: %zu, buffer end %p, %s:%i",
- (void *)start, len, (void *)(p->buf + p->buf_size),
- func, line);
+ if (packet_check_range(p, 0, len, start, func, line))
return;
- }
if (len > UINT16_MAX) {
trace("add packet length %zu, %s:%i", len, func, line);
return;
}
-#if UINTPTR_MAX == UINT64_MAX
- if ((uintptr_t)start - (uintptr_t)p->buf > UINT32_MAX) {
- trace("add packet start %p, buffer start %p, %s:%i",
- (void *)start, (void *)p->buf, func, line);
- return;
- }
-#endif
-
- p->pkt[idx].offset = start - p->buf;
- p->pkt[idx].len = len;
+ p->pkt[idx].iov_base = (void *)start;
+ p->pkt[idx].iov_len = len;
p->count++;
}
@@ -96,36 +118,31 @@ void *packet_get_do(const struct pool *p, size_t idx, size_t offset,
return NULL;
}
- if (len > UINT16_MAX || len + offset > UINT32_MAX) {
+ if (len > UINT16_MAX) {
if (func) {
- trace("packet data length %zu, offset %zu, %s:%i",
- len, offset, func, line);
+ trace("packet data length %zu, %s:%i",
+ len, func, line);
}
return NULL;
}
- if (p->pkt[idx].offset + len + offset > p->buf_size) {
+ if (len + offset > p->pkt[idx].iov_len) {
if (func) {
- trace("packet offset plus length %zu from size %zu, "
- "%s:%i", p->pkt[idx].offset + len + offset,
- p->buf_size, func, line);
+ trace("data length %zu, offset %zu from length %zu, "
+ "%s:%i", len, offset, p->pkt[idx].iov_len,
+ func, line);
}
return NULL;
}
- if (len + offset > p->pkt[idx].len) {
- if (func) {
- trace("data length %zu, offset %zu from length %u, "
- "%s:%i", len, offset, p->pkt[idx].len,
- func, line);
- }
+ if (packet_check_range(p, offset, len, p->pkt[idx].iov_base,
+ func, line))
return NULL;
- }
if (left)
- *left = p->pkt[idx].len - offset - len;
+ *left = p->pkt[idx].iov_len - offset - len;
- return p->buf + p->pkt[idx].offset + offset;
+ return (char *)p->pkt[idx].iov_base + offset;
}
/**
diff --git a/packet.h b/packet.h
index a784b07..3f70e94 100644
--- a/packet.h
+++ b/packet.h
@@ -7,19 +7,11 @@
#define PACKET_H
/**
- * struct desc - Generic offset-based descriptor within buffer
- * @offset: Offset of descriptor relative to buffer start, 32-bit limit
- * @len: Length of descriptor, host order, 16-bit limit
- */
-struct desc {
- uint32_t offset;
- uint16_t len;
-};
-
-/**
* struct pool - Generic pool of packets stored in a buffer
- * @buf: Buffer storing packet descriptors
- * @buf_size: Total size of buffer
+ * @buf: Buffer storing packet descriptors,
+ * a struct vu_dev_region array for passt vhost-user mode
+ * @buf_size: Total size of buffer,
+ * 0 for passt vhost-user mode
* @size: Number of usable descriptors for the pool
* @count: Number of used descriptors for the pool
* @pkt: Descriptors: see macros below
@@ -29,9 +21,11 @@ struct pool {
size_t buf_size;
size_t size;
size_t count;
- struct desc pkt[1];
+ struct iovec pkt[1];
};
+int vu_packet_check_range(void *buf, size_t offset, size_t len,
+ const char *start);
void packet_add_do(struct pool *p, size_t len, const char *start,
const char *func, int line);
void *packet_get_do(const struct pool *p, const size_t idx,
@@ -54,7 +48,7 @@ struct _name ## _t { \
size_t buf_size; \
size_t size; \
size_t count; \
- struct desc pkt[_size]; \
+ struct iovec pkt[_size]; \
}
#define PACKET_POOL_INIT_NOCAST(_size, _buf, _buf_size) \
diff --git a/passt.1 b/passt.1
index 3062b71..d9cd33e 100644
--- a/passt.1
+++ b/passt.1
@@ -95,7 +95,7 @@ detached PID namespace after starting, because the PID itself cannot change.
Default is to fork into background.
.TP
-.BR \-e ", " \-\-stderr
+.BR \-e ", " \-\-stderr " " (DEPRECATED)
This option has no effect, and is maintained for compatibility purposes only.
Note that this configuration option is \fBdeprecated\fR and will be removed in a
@@ -160,7 +160,9 @@ once for IPv6).
By default, assigned IPv4 and IPv6 addresses are taken from the host interfaces
with the first default route, if any, for the corresponding IP version. If no
default routes are available and there is any interface with any route for a
-given IP version, the first of these interfaces will be chosen instead.
+given IP version, the first of these interfaces will be chosen instead. If no
+such interface exists, the link-local address 169.254.2.1 is assigned for IPv4,
+and no additional address will be assigned for IPv6.
.TP
.BR \-n ", " \-\-netmask " " \fImask
@@ -174,8 +176,7 @@ according to the CIDR block of the assigned address (RFC 4632).
.BR \-M ", " \-\-mac-addr " " \fIaddr
Use source MAC address \fIaddr\fR when communicating to the guest or to the
target namespace.
-Default is to use the MAC address of the interface with the first IPv4 default
-route on the host.
+Default is the locally administered MAC addresses 9a:55:9a:55:9a:55.
.TP
.BR \-g ", " \-\-gateway " " \fIaddr
@@ -188,7 +189,9 @@ first default route, if any, for the corresponding IP version. If the default
route is a multipath one, the gateway is the first nexthop router returned by
the kernel which has the highest weight in the set of paths. If no default
routes are available and there is just one interface with any route, that
-interface will be chosen instead.
+interface will be chosen instead. If no such interface exists, the link-local
+address 169.254.2.2 is used for IPv4, and the link-local address fe80::1 is used
+for IPv6.
Note: these addresses are also used as source address for packets directed to
the guest or to the target namespace having a loopback or local source address,
@@ -203,7 +206,9 @@ Default is to use the interfaces specified by \fB--outbound-if4\fR and
If no interfaces are given, the interface with the first default routes for each
IP version is selected. If no default routes are available and there is just one
-interface with any route, that interface will be chosen instead.
+interface with any route, that interface will be chosen instead. If no such
+interface exists, host interfaces will be ignored for the purposes of assigning
+addresses and routes, and link-local addresses will be used instead.
.TP
.BR \-o ", " \-\-outbound " " \fIaddr
@@ -222,7 +227,8 @@ derive IPv4 addresses and routes.
By default, the interface given by the default route is selected. If no default
routes are available and there is just one interface with any route, that
-interface will be chosen instead.
+interface will be chosen instead. If no such interface exists, outbound sockets
+will not be bound to any specific interface.
.TP
.BR \-\-outbound-if6 " " \fIname
@@ -232,23 +238,37 @@ derive IPv6 addresses and routes.
By default, the interface given by the default route is selected. If no default
routes are available and there is just one interface with any route, that
-interface will be chosen instead.
+interface will be chosen instead. If no such interface exists, outbound sockets
+will not be bound to any specific interface.
.TP
.BR \-D ", " \-\-dns " " \fIaddr
-Use \fIaddr\fR (IPv4 or IPv6) for DHCP, DHCPv6, NDP or DNS forwarding, as
-configured (see options \fB--no-dhcp-dns\fR, \fB--dhcp-dns\fR,
-\fB--dns-forward\fR) instead of reading addresses from \fI/etc/resolv.conf\fR.
-This option can be specified multiple times. Specifying \fB-D none\fR disables
-usage of DNS addresses altogether.
+Instruct the guest (via DHCP, DHVPv6 or NDP) to use \fIaddr\fR (IPv4
+or IPv6) as a nameserver, as configured (see options
+\fB--no-dhcp-dns\fR, \fB--dhcp-dns\fR) instead of reading addresses
+from \fI/etc/resolv.conf\fR. This option can be specified multiple
+times. Specifying \fB-D none\fR disables usage of DNS addresses
+altogether. Unlike addresses from \fI/etc/resolv.conf\fR, \fIaddr\fR
+is given to the guest without remapping. For example \fB--dns
+127.0.0.1\fR will instruct the guest to use itself as nameserver, not
+the host.
.TP
.BR \-\-dns-forward " " \fIaddr
Map \fIaddr\fR (IPv4 or IPv6) as seen from guest or namespace to the
-first configured DNS resolver (with corresponding IP version). Maps
-only UDP and TCP traffic to port 53 or port 853. Replies are
-translated back with a reverse mapping. This option can be specified
-zero to two times (once for IPv4, once for IPv6).
+nameserver (with corresponding IP version) specified by the
+\fB\-\-dns-host\fR option. Maps only UDP and TCP traffic to port 53 or
+port 853. Replies are translated back with a reverse mapping. This
+option can be specified zero to two times (once for IPv4, once for
+IPv6).
+
+.TP
+.BR \-\-dns-host " " \fIaddr
+Configure the host nameserver which guest or namespace queries to the
+\fB\-\-dns-forward\fR address will be redirected to. This option can
+be specified zero to two times (once for IPv4, once for IPv6).
+By default, the first nameserver from the host's
+\fI/etc/resolv.conf\fR.
.TP
.BR \-S ", " \-\-search " " \fIlist
@@ -324,6 +344,30 @@ Disable Router Advertisements. Router Solicitations coming from guest or target
namespace will be ignored.
.TP
+.BR \-\-freebind
+Allow any binding address to be specified for \fB-t\fR and \fB-u\fR
+options. Usually binding addresses must be addresses currently
+configured on the host. With \fB\-\-freebind\fR, the
+\fBIP_FREEBIND\fR or \fBIPV6_FREEBIND\fR socket option is enabled
+allowing any address to be used. This is typically used to bind
+addresses which might be configured on the host in future, at which
+point the forwarding will immediately start operating.
+
+.TP
+.BR \-\-map-host-loopback " " \fIaddr
+Translate \fIaddr\fR to refer to the host. Packets from the guest to
+\fIaddr\fR will be redirected to the host. On the host such packets
+will appear to have both source and destination of 127.0.0.1 or ::1.
+
+If \fIaddr\fR is 'none', no address is mapped (this implies
+\fB--no-map-gw\fR). Only one IPv4 and one IPv6 address can be
+translated, if the option is specified multiple times, the last one
+takes effect.
+
+Default is to translate the guest's default gateway address, unless
+\fB--no-map-gw\fR is given, in which case no address is mapped.
+
+.TP
.BR \-\-no-map-gw
Don't remap TCP connections and untracked UDP traffic, with the gateway address
as destination, to the host. Implied if there is no gateway on the selected
@@ -331,6 +375,21 @@ default route, or if there is no default route, for any of the enabled address
families.
.TP
+.BR \-\-map-guest-addr " " \fIaddr
+Translate \fIaddr\fR in the guest to be equal to the guest's assigned
+address on the host. That is, packets from the guest to \fIaddr\fR
+will be redirected to the address assigned to the guest with \fB-a\fR,
+or by default the host's global address. This allows the guest to
+access services available on the host's global address, even though its
+own address shadows that of the host.
+
+If \fIaddr\fR is 'none', no address is mapped. Only one IPv4 and one
+IPv6 address can be translated, and if the option is specified
+multiple times, the last one for each address type takes effect.
+
+By default, mapping happens as described for the \-\-map-host-loopback option.
+
+.TP
.BR \-4 ", " \-\-ipv4-only
Enable IPv4-only operation. IPv6 traffic will be ignored.
By default, IPv6 operation is enabled as long as at least an IPv6 route and an
@@ -345,13 +404,21 @@ interface address are configured on a given host interface.
.SS \fBpasst\fR-only options
.TP
-.BR \-s ", " \-\-socket " " \fIpath
+.BR \-s ", " \-\-socket-path ", " \-\-socket " " \fIpath
Path for UNIX domain socket used by \fBqemu\fR(1) or \fBqrap\fR(1) to connect to
\fBpasst\fR.
Default is to probe a free socket, not accepting connections, starting from
\fI/tmp/passt_1.socket\fR to \fI/tmp/passt_64.socket\fR.
.TP
+.BR \-\-vhost-user
+Enable vhost-user. The vhost-user command socket is provided by \fB--socket\fR.
+
+.TP
+.BR \-\-print-capabilities
+Print back-end capabilities in JSON format, only meaningful for vhost-user mode.
+
+.TP
.BR \-F ", " \-\-fd " " \fIFD
Pass a pre-opened, connected socket to \fBpasst\fR. Usually the socket is opened
in the parent process and \fBpasst\fR inherits it when run as a child. This
@@ -452,6 +519,7 @@ Default is \fBnone\fR.
.BR \-I ", " \-\-ns-ifname " " \fIname
Name of tap interface to be created in target namespace.
By default, the same interface name as the external, routable interface is used.
+If no such interface exists, the name \fItap0\fR will be used instead.
.TP
.BR \-t ", " \-\-tcp-ports " " \fIspec
@@ -554,6 +622,13 @@ Configure UDP port forwarding from target namespace to init namespace.
Default is \fBauto\fR.
.TP
+.BR \-\-host-lo-to-ns-lo " " (DEPRECATED)
+If specified, connections forwarded with \fB\-t\fR and \fB\-u\fR from
+the host's loopback address will appear on the loopback address in the
+guest as well. Without this option such forwarded packets will appear
+to come from the guest's public address.
+
+.TP
.BR \-\-userns " " \fIspec
Target user namespace to join, as a path. If PID is given, without this option,
the user namespace will be the one of the corresponding process.
@@ -620,6 +695,11 @@ Configure MAC address \fIaddr\fR on the tap interface in the namespace.
Default is to let the tap driver build a pseudorandom hardware address.
+.TP
+.BR \-\-no-splice
+Disable the bypass path for inbound, local traffic. See the section \fBHandling
+of local traffic in pasta\fR in the \fBNOTES\fR for more details.
+
.SH EXAMPLES
.SS \fBpasta
@@ -830,38 +910,41 @@ root@localhost's password:
.SH NOTES
-.SS Handling of traffic with local destination and source addresses
-
-Both \fBpasst\fR and \fBpasta\fR can bind on ports with a local address,
-depending on the configuration. Local destination or source addresses need to be
-changed before packets are delivered to the guest or target namespace: most
-operating systems would drop packets received from non-loopback interfaces with
-local addresses, and it would also be impossible for guest or target namespace
-to route answers back.
-
-For convenience, and somewhat arbitrarily, the source address on these packets
-is translated to the address of the default IPv4 or IPv6 gateway (if any) --
-this is known to be an existing, valid address on the same subnet.
-
-Loopback destination addresses are instead translated to the observed external
-address of the guest or target namespace. For IPv6 packets, if usage of a
-link-local address by guest or namespace has ever been observed, and the
-original destination address is also a link-local address, the observed
-link-local address is used. Otherwise, the observed global address is used. For
-both IPv4 and IPv6, if no addresses have been seen yet, the configured addresses
-will be used instead.
+.SS Handling of traffic with loopback destination and source addresses
+
+Both \fBpasst\fR and \fBpasta\fR can bind on ports with a loopback
+address (127.0.0.0/8 or ::1), depending on the configuration. Loopback
+destination or source addresses need to be changed before packets are
+delivered to the guest or target namespace: most operating systems
+would drop packets received with loopback addresses on non-loopback
+interfaces, and it would also be impossible for guest or target
+namespace to route answers back.
+
+For convenience, the source address on these packets is translated to
+the address specified by the \fB\-\-map-host-loopback\fR option (with
+some exceptions in pasta mode, see next section below). If not
+specified this defaults, somewhat arbitrarily, to the address of
+default IPv4 or IPv6 gateway (if any) -- this is known to be an
+existing, valid address on the same subnet. If \fB\-\-no-map-gw\fR or
+\fB\-\-map-host-loopback none\fR are specified this translation is
+disabled and packets with loopback addresses are simply dropped.
+
+Loopback destination addresses are translated to the observed external
+address of the guest or target namespace. For IPv6, the observed
+link-local address is used if the translated source address is
+link-local, otherwise the observed global address is used. For both
+IPv4 and IPv6, if no addresses have been seen yet, the configured
+addresses will be used instead.
For example, if \fBpasst\fR or \fBpasta\fR receive a connection from 127.0.0.1,
with destination 127.0.0.10, and the default IPv4 gateway is 192.0.2.1, while
the last observed source address from guest or namespace is 192.0.2.2, this will
be translated to a connection from 192.0.2.1 to 192.0.2.2.
-Similarly, for traffic coming from guest or namespace, packets with destination
-address corresponding to the default gateway will have their destination address
-translated to a loopback address, if and only if a packet, in the opposite
-direction, with a loopback destination or source address, port-wise matching for
-UDP, or connection-wise for TCP, has been recently forwarded to guest or
-namespace. This behaviour can be disabled with \-\-no\-map\-gw.
+Similarly, for traffic coming from guest or namespace, packets with
+destination address corresponding to the \fB\-\-map-host-loopback\fR
+address will have their destination address translated to a loopback
+address.
.SS Handling of local traffic in pasta
@@ -877,8 +960,15 @@ and the new socket using the \fBsplice\fR(2) system call, and for UDP, a pair
of \fBrecvmmsg\fR(2) and \fBsendmmsg\fR(2) system calls deals with packet
transfers.
-This bypass only applies to local connections and traffic, because it's not
-possible to bind sockets to foreign addresses.
+Because it's not possible to bind sockets to foreign addresses, this
+bypass only applies to local connections and traffic. It also means
+that the address translation differs slightly from passt mode.
+Connections from loopback to loopback on the host will appear to come
+from the target namespace's public address within the guest, unless
+\fB\-\-host-lo-to-ns-lo\fR is specified, in which case they will
+appear to come from loopback in the namespace as well. The latter
+behaviour used to be the default, but is usually undesirable, since it
+can unintentionally expose namespace local services to the host.
.SS Binding to low numbered ports (well-known or system ports, up to 1023)
@@ -963,6 +1053,20 @@ If the sending window cannot be queried, it will always be announced as the
current sending buffer size to guest or target namespace. This might affect
throughput of TCP connections.
+.SS Local mode for disconnected setups
+
+If \fBpasst\fR and \fBpasta\fR fail to find a host interface with a configured
+address, other than loopback addresses, they will, obviously, not attempt to
+source addresses or routes from the host.
+
+In this case, unless configured otherwise, they will assign the IPv4 link-local
+address 169.254.2.1 to the guest or target namespace, and no IPv6 address. The
+notion of the guest or target namespace IPv6 address is derived from the first
+link-local address observed.
+
+Default gateways will be assigned as the link-local address 169.254.2.2 for
+IPv4, and as the link-local address fe80::1 for IPv6.
+
.SH LIMITATIONS
Currently, IGMP/MLD proxying (RFC 4605) and support for SCTP (RFC 4960) are not
diff --git a/passt.c b/passt.c
index 4b3c306..1a0c404 100644
--- a/passt.c
+++ b/passt.c
@@ -36,9 +36,6 @@
#include <sys/prctl.h>
#include <netinet/if_ether.h>
#include <libgen.h>
-#ifdef HAS_GETRANDOM
-#include <sys/random.h>
-#endif
#include "util.h"
#include "passt.h"
@@ -52,6 +49,8 @@
#include "arch.h"
#include "log.h"
#include "tcp_splice.h"
+#include "ndp.h"
+#include "vu_common.h"
#define EPOLL_EVENTS 8
@@ -74,6 +73,8 @@ char *epoll_type_str[] = {
[EPOLL_TYPE_TAP_PASTA] = "/dev/net/tun device",
[EPOLL_TYPE_TAP_PASST] = "connected qemu socket",
[EPOLL_TYPE_TAP_LISTEN] = "listening qemu socket",
+ [EPOLL_TYPE_VHOST_CMD] = "vhost-user command socket",
+ [EPOLL_TYPE_VHOST_KICK] = "vhost-user kick socket",
};
static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES,
"epoll_type_str[] doesn't match enum epoll_type");
@@ -110,40 +111,25 @@ static void post_handler(struct ctx *c, const struct timespec *now)
flow_defer_handler(c, now);
#undef CALL_PROTO_HANDLER
+
+ if (!c->no_ndp)
+ ndp_timer(c, now);
}
/**
- * secret_init() - Create secret value for SipHash calculations
+ * random_init() - Initialise things based on random data
* @c: Execution context
*/
-static void secret_init(struct ctx *c)
+static void random_init(struct ctx *c)
{
-#ifndef HAS_GETRANDOM
- int dev_random = open("/dev/random", O_RDONLY);
- unsigned int random_read = 0;
-
- while (dev_random && random_read < sizeof(c->hash_secret)) {
- int ret = read(dev_random,
- (uint8_t *)&c->hash_secret + random_read,
- sizeof(c->hash_secret) - random_read);
+ unsigned int seed;
- if (ret == -1 && errno == EINTR)
- continue;
-
- if (ret <= 0)
- break;
+ /* Create secret value for SipHash calculations */
+ raw_random(&c->hash_secret, sizeof(c->hash_secret));
- random_read += ret;
- }
- if (dev_random >= 0)
- close(dev_random);
-
- if (random_read < sizeof(c->hash_secret))
-#else
- if (getrandom(&c->hash_secret, sizeof(c->hash_secret),
- GRND_RANDOM) < 0)
-#endif /* !HAS_GETRANDOM */
- die_perror("Failed to get random bytes for hash table and TCP");
+ /* Seed pseudo-RNG for things that need non-cryptographic random */
+ raw_random(&seed, sizeof(seed));
+ srandom(seed);
}
/**
@@ -191,11 +177,11 @@ void exit_handler(int signal)
* Return: non-zero on failure
*
* #syscalls read write writev
- * #syscalls socket bind connect getsockopt setsockopt s390x:socketcall close
- * #syscalls recvfrom sendto shutdown
+ * #syscalls socket getsockopt setsockopt s390x:socketcall i686:socketcall close
+ * #syscalls bind connect recvfrom sendto shutdown
* #syscalls arm:recv ppc64le:recv arm:send ppc64le:send
- * #syscalls accept4|accept listen epoll_ctl epoll_wait|epoll_pwait epoll_pwait
- * #syscalls clock_gettime arm:clock_gettime64
+ * #syscalls accept4 accept listen epoll_ctl epoll_wait|epoll_pwait epoll_pwait
+ * #syscalls clock_gettime arm:clock_gettime64 i686:clock_gettime64
*/
int main(int argc, char **argv)
{
@@ -207,7 +193,8 @@ int main(int argc, char **argv)
struct timespec now;
struct sigaction sa;
- clock_gettime(CLOCK_MONOTONIC, &log_start);
+ if (clock_gettime(CLOCK_MONOTONIC, &log_start))
+ die_perror("Failed to get CLOCK_MONOTONIC time");
arch_avx2_exec(argv);
@@ -261,18 +248,19 @@ int main(int argc, char **argv)
pasta_netns_quit_init(&c);
- tap_sock_init(&c);
+ tap_backend_init(&c);
- secret_init(&c);
+ random_init(&c);
- clock_gettime(CLOCK_MONOTONIC, &now);
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ die_perror("Failed to get CLOCK_MONOTONIC time");
flow_init();
if ((!c.no_udp && udp_init(&c)) || (!c.no_tcp && tcp_init(&c)))
exit(EXIT_FAILURE);
- proto_update_l2_buf(c.mac_guest, c.mac);
+ proto_update_l2_buf(c.guest_mac, c.our_tap_mac);
if (c.ifi4 && !c.no_dhcp)
dhcp_init();
@@ -307,13 +295,15 @@ int main(int argc, char **argv)
timer_init(&c, &now);
loop:
- /* NOLINTNEXTLINE(bugprone-branch-clone): intervals can be the same */
+ /* NOLINTBEGIN(bugprone-branch-clone): intervals can be the same */
/* cppcheck-suppress [duplicateValueTernary, unmatchedSuppression] */
nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, TIMER_INTERVAL);
+ /* NOLINTEND(bugprone-branch-clone) */
if (nfds == -1 && errno != EINTR)
die_perror("epoll_wait() failed in main loop");
- clock_gettime(CLOCK_MONOTONIC, &now);
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ err_perror("Failed to get CLOCK_MONOTONIC time");
for (i = 0; i < nfds; i++) {
union epoll_ref ref = *((union epoll_ref *)&events[i].data.u64);
@@ -360,6 +350,12 @@ loop:
case EPOLL_TYPE_PING:
icmp_sock_handler(&c, ref);
break;
+ case EPOLL_TYPE_VHOST_CMD:
+ vu_control_handler(c.vdev, c.fd_tap, eventmask);
+ break;
+ case EPOLL_TYPE_VHOST_KICK:
+ vu_kick_cb(c.vdev, ref, &now);
+ break;
default:
/* Can't happen */
ASSERT(0);
diff --git a/passt.h b/passt.h
index ef68403..0dd4efa 100644
--- a/passt.h
+++ b/passt.h
@@ -25,6 +25,14 @@ union epoll_ref;
#include "fwd.h"
#include "tcp.h"
#include "udp.h"
+#include "vhost_user.h"
+
+/* Default address for our end on the tap interface. Bit 0 of byte 0 must be 0
+ * (unicast) and bit 1 of byte 1 must be 1 (locally administered). Otherwise
+ * it's arbitrary.
+ */
+#define MAC_OUR_LAA \
+ ((uint8_t [ETH_ALEN]){0x9a, 0x55, 0x9a, 0x55, 0x9a, 0x55})
/**
* union epoll_ref - Breakdown of reference for epoll fd bookkeeping
@@ -36,6 +44,7 @@ union epoll_ref;
* @icmp: ICMP-specific reference part
* @data: Data handled by protocol handlers
* @nsdir_fd: netns dirfd for fallback timer checking if namespace is gone
+ * @queue: vhost-user queue index for this fd
* @u64: Opaque reference for epoll_ctl() and epoll_wait()
*/
union epoll_ref {
@@ -51,6 +60,7 @@ union epoll_ref {
union udp_listen_epoll_ref udp;
uint32_t data;
int nsdir_fd;
+ int queue;
};
};
uint64_t u64;
@@ -87,6 +97,7 @@ struct fqdn {
enum passt_modes {
MODE_PASST,
MODE_PASTA,
+ MODE_VU,
};
/**
@@ -94,9 +105,14 @@ enum passt_modes {
* @addr: IPv4 address assigned to guest
* @addr_seen: Latest IPv4 address seen as source from tap
* @prefixlen: IPv4 prefix length (netmask)
- * @gw: Default IPv4 gateway
+ * @guest_gw: IPv4 gateway as seen by the guest
+ * @map_host_loopback: Outbound connections to this address are NATted to the
+ * host's 127.0.0.1
+ * @map_guest_addr: Outbound connections to this address are NATted to the
+ * guest's assigned address
* @dns: DNS addresses for DHCP, zero-terminated
* @dns_match: Forward DNS query if sent to this address
+ * @our_tap_addr: IPv4 address for passt's use on tap
* @dns_host: Use this DNS on the host for forwarding
* @addr_out: Optional source address for outbound traffic
* @ifname_out: Optional interface name to bind outbound sockets to
@@ -104,15 +120,21 @@ enum passt_modes {
* @no_copy_addrs: Don't copy all addresses when configuring namespace
*/
struct ip4_ctx {
+ /* PIF_TAP addresses */
struct in_addr addr;
struct in_addr addr_seen;
int prefix_len;
- struct in_addr gw;
+ struct in_addr guest_gw;
+ struct in_addr map_host_loopback;
+ struct in_addr map_guest_addr;
struct in_addr dns[MAXNS + 1];
struct in_addr dns_match;
- struct in_addr dns_host;
+ struct in_addr our_tap_addr;
+ /* PIF_HOST addresses */
+ struct in_addr dns_host;
struct in_addr addr_out;
+
char ifname_out[IFNAMSIZ];
bool no_copy_routes;
@@ -122,12 +144,16 @@ struct ip4_ctx {
/**
* struct ip6_ctx - IPv6 execution context
* @addr: IPv6 address assigned to guest
- * @addr_ll: Link-local IPv6 address on external, routable interface
* @addr_seen: Latest IPv6 global/site address seen as source from tap
* @addr_ll_seen: Latest IPv6 link-local address seen as source from tap
- * @gw: Default IPv6 gateway
+ * @guest_gw: IPv6 gateway as seen by the guest
+ * @map_host_loopback: Outbound connections to this address are NATted to the
+ * host's [::1]
+ * @map_guest_addr: Outbound connections to this address are NATted to the
+ * guest's assigned address
* @dns: DNS addresses for DHCPv6 and NDP, zero-terminated
* @dns_match: Forward DNS query if sent to this address
+ * @our_tap_ll: Link-local IPv6 address for passt's use on tap
* @dns_host: Use this DNS on the host for forwarding
* @addr_out: Optional source address for outbound traffic
* @ifname_out: Optional interface name to bind outbound sockets to
@@ -135,16 +161,21 @@ struct ip4_ctx {
* @no_copy_addrs: Don't copy all addresses when configuring namespace
*/
struct ip6_ctx {
+ /* PIF_TAP addresses */
struct in6_addr addr;
- struct in6_addr addr_ll;
struct in6_addr addr_seen;
struct in6_addr addr_ll_seen;
- struct in6_addr gw;
+ struct in6_addr guest_gw;
+ struct in6_addr map_host_loopback;
+ struct in6_addr map_guest_addr;
struct in6_addr dns[MAXNS + 1];
struct in6_addr dns_match;
- struct in6_addr dns_host;
+ struct in6_addr our_tap_ll;
+ /* PIF_HOST addresses */
+ struct in6_addr dns_host;
struct in6_addr addr_out;
+
char ifname_out[IFNAMSIZ];
bool no_copy_routes;
@@ -172,13 +203,13 @@ struct ip6_ctx {
* @epollfd: File descriptor for epoll instance
* @fd_tap_listen: File descriptor for listening AF_UNIX socket, if any
* @fd_tap: AF_UNIX socket, tuntap device, or pre-opened socket
- * @mac: Host MAC address
- * @mac_guest: MAC address of guest or namespace, seen or configured
+ * @our_tap_mac: Pasta/passt's MAC on the tap link
+ * @guest_mac: MAC address of guest or namespace, seen or configured
* @hash_secret: 128-bit secret for siphash functions
- * @ifi4: Index of template interface for IPv4, 0 if IPv4 disabled
+ * @ifi4: Template interface for IPv4, -1: none, 0: IPv4 disabled
* @ip: IPv4 configuration
* @dns_search: DNS search list
- * @ifi6: Index of template interface for IPv6, 0 if IPv6 disabled
+ * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
* @ip6: IPv6 configuration
* @pasta_ifn: Name of namespace interface for pasta
* @pasta_ifi: Index of namespace interface for pasta
@@ -198,9 +229,12 @@ struct ip6_ctx {
* @no_dhcpv6: Disable DHCPv6 server
* @no_ndp: Disable NDP handler altogether
* @no_ra: Disable router advertisements
- * @no_map_gw: Don't map connections, untracked UDP to gateway to host
+ * @no_splice: Disable socket splicing for inbound traffic
+ * @host_lo_to_ns_lo: Map host loopback addresses to ns loopback addresses
+ * @freebind: Allow binding of non-local addresses for forwarding
* @low_wmem: Low probed net.core.wmem_max
* @low_rmem: Low probed net.core.rmem_max
+ * @vdev: vhost-user device
*/
struct ctx {
enum passt_modes mode;
@@ -226,16 +260,16 @@ struct ctx {
int epollfd;
int fd_tap_listen;
int fd_tap;
- unsigned char mac[ETH_ALEN];
- unsigned char mac_guest[ETH_ALEN];
+ unsigned char our_tap_mac[ETH_ALEN];
+ unsigned char guest_mac[ETH_ALEN];
uint64_t hash_secret[2];
- unsigned int ifi4;
+ int ifi4;
struct ip4_ctx ip4;
struct fqdn dns_search[MAXDNSRCH];
- unsigned int ifi6;
+ int ifi6;
struct ip6_ctx ip6;
char pasta_ifn[IF_NAMESIZE];
@@ -258,10 +292,14 @@ struct ctx {
int no_dhcpv6;
int no_ndp;
int no_ra;
- int no_map_gw;
+ int no_splice;
+ int host_lo_to_ns_lo;
+ int freebind;
int low_wmem;
int low_rmem;
+
+ struct vu_dev *vdev;
};
void proto_update_l2_buf(const unsigned char *eth_d,
diff --git a/pasta.c b/pasta.c
index 615ff7b..ff41c95 100644
--- a/pasta.c
+++ b/pasta.c
@@ -13,7 +13,7 @@
*
* #syscalls:pasta clone waitid exit exit_group rt_sigprocmask
* #syscalls:pasta rt_sigreturn|sigreturn
- * #syscalls:pasta arm:sigreturn ppc64:sigreturn s390x:sigreturn
+ * #syscalls:pasta arm:sigreturn ppc64:sigreturn s390x:sigreturn i686:sigreturn
*/
#include <sched.h>
@@ -102,7 +102,9 @@ static int pasta_wait_for_ns(void *arg)
int flags = O_RDONLY | O_CLOEXEC;
char ns[PATH_MAX];
- snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid);
+ if (snprintf_check(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid))
+ die_perror("Can't build netns path");
+
do {
while ((c->pasta_netns_fd = open(ns, flags)) < 0) {
if (errno != ENOENT)
@@ -239,8 +241,11 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid,
c->quiet = 1;
/* Configure user and group mappings */
- snprintf(uidmap, BUFSIZ, "0 %u 1", uid);
- snprintf(gidmap, BUFSIZ, "0 %u 1", gid);
+ if (snprintf_check(uidmap, BUFSIZ, "0 %u 1", uid))
+ die_perror("Can't build uidmap");
+
+ if (snprintf_check(gidmap, BUFSIZ, "0 %u 1", gid))
+ die_perror("Can't build gidmap");
if (write_file("/proc/self/uid_map", uidmap) ||
write_file("/proc/self/setgroups", "deny") ||
@@ -288,22 +293,30 @@ void pasta_ns_conf(struct ctx *c)
{
int rc = 0;
- rc = nl_link_up(nl_sock_ns, 1 /* lo */, 0);
+ rc = nl_link_set_flags(nl_sock_ns, 1 /* lo */, IFF_UP, IFF_UP);
if (rc < 0)
die("Couldn't bring up loopback interface in namespace: %s",
- strerror(-rc));
+ strerror_(-rc));
/* Get or set MAC in target namespace */
- if (MAC_IS_ZERO(c->mac_guest))
- nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->mac_guest);
+ if (MAC_IS_ZERO(c->guest_mac))
+ nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
else
- rc = nl_link_set_mac(nl_sock_ns, c->pasta_ifi, c->mac_guest);
+ rc = nl_link_set_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
if (rc < 0)
die("Couldn't set MAC address in namespace: %s",
- strerror(-rc));
+ strerror_(-rc));
if (c->pasta_conf_ns) {
- nl_link_up(nl_sock_ns, c->pasta_ifi, c->mtu);
+ unsigned int flags = IFF_UP;
+
+ if (c->mtu != -1)
+ nl_link_set_mtu(nl_sock_ns, c->pasta_ifi, c->mtu);
+
+ if (c->ifi6) /* Avoid duplicate address detection on link up */
+ flags |= IFF_NOARP;
+
+ nl_link_set_flags(nl_sock_ns, c->pasta_ifi, flags, flags);
if (c->ifi4) {
if (c->ip4.no_copy_addrs) {
@@ -319,12 +332,13 @@ void pasta_ns_conf(struct ctx *c)
if (rc < 0) {
die("Couldn't set IPv4 address(es) in namespace: %s",
- strerror(-rc));
+ strerror_(-rc));
}
if (c->ip4.no_copy_routes) {
rc = nl_route_set_def(nl_sock_ns, c->pasta_ifi,
- AF_INET, &c->ip4.gw);
+ AF_INET,
+ &c->ip4.guest_gw);
} else {
rc = nl_route_dup(nl_sock, c->ifi4, nl_sock_ns,
c->pasta_ifi, AF_INET);
@@ -332,14 +346,34 @@ void pasta_ns_conf(struct ctx *c)
if (rc < 0) {
die("Couldn't set IPv4 route(s) in guest: %s",
- strerror(-rc));
+ strerror_(-rc));
}
}
if (c->ifi6) {
+ rc = nl_addr_get_ll(nl_sock_ns, c->pasta_ifi,
+ &c->ip6.addr_ll_seen);
+ if (rc < 0) {
+ warn("Can't get LL address from namespace: %s",
+ strerror_(-rc));
+ }
+
+ rc = nl_addr_set_ll_nodad(nl_sock_ns, c->pasta_ifi);
+ if (rc < 0) {
+ warn("Can't set nodad for LL in namespace: %s",
+ strerror_(-rc));
+ }
+
+ /* We dodged DAD: re-enable neighbour solicitations */
+ nl_link_set_flags(nl_sock_ns, c->pasta_ifi,
+ 0, IFF_NOARP);
+
if (c->ip6.no_copy_addrs) {
- rc = nl_addr_set(nl_sock_ns, c->pasta_ifi,
- AF_INET6, &c->ip6.addr, 64);
+ if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
+ rc = nl_addr_set(nl_sock_ns,
+ c->pasta_ifi, AF_INET6,
+ &c->ip6.addr, 64);
+ }
} else {
rc = nl_addr_dup(nl_sock, c->ifi6,
nl_sock_ns, c->pasta_ifi,
@@ -348,12 +382,13 @@ void pasta_ns_conf(struct ctx *c)
if (rc < 0) {
die("Couldn't set IPv6 address(es) in namespace: %s",
- strerror(-rc));
+ strerror_(-rc));
}
if (c->ip6.no_copy_routes) {
rc = nl_route_set_def(nl_sock_ns, c->pasta_ifi,
- AF_INET6, &c->ip6.gw);
+ AF_INET6,
+ &c->ip6.guest_gw);
} else {
rc = nl_route_dup(nl_sock, c->ifi6,
nl_sock_ns, c->pasta_ifi,
@@ -362,12 +397,12 @@ void pasta_ns_conf(struct ctx *c)
if (rc < 0) {
die("Couldn't set IPv6 route(s) in guest: %s",
- strerror(-rc));
+ strerror_(-rc));
}
}
}
- proto_update_l2_buf(c->mac_guest, NULL);
+ proto_update_l2_buf(c->guest_mac, NULL);
}
/**
@@ -400,29 +435,29 @@ static int pasta_netns_quit_timer(void)
*/
void pasta_netns_quit_init(const struct ctx *c)
{
- union epoll_ref ref = { .type = EPOLL_TYPE_NSQUIT_INOTIFY };
struct epoll_event ev = { .events = EPOLLIN };
int flags = O_NONBLOCK | O_CLOEXEC;
struct statfs s = { 0 };
bool try_inotify = true;
int fd = -1, dir_fd;
+ union epoll_ref ref;
if (c->mode != MODE_PASTA || c->no_netns_quit || !*c->netns_base)
return;
if ((dir_fd = open(c->netns_dir, O_CLOEXEC | O_RDONLY)) < 0)
- die("netns dir open: %s, exiting", strerror(errno));
+ die("netns dir open: %s, exiting", strerror_(errno));
if (fstatfs(dir_fd, &s) || s.f_type == DEVPTS_SUPER_MAGIC ||
s.f_type == PROC_SUPER_MAGIC || s.f_type == SYSFS_MAGIC)
try_inotify = false;
if (try_inotify && (fd = inotify_init1(flags)) < 0)
- warn("inotify_init1(): %s, use a timer", strerror(errno));
+ warn("inotify_init1(): %s, use a timer", strerror_(errno));
if (fd >= 0 && inotify_add_watch(fd, c->netns_dir, IN_DELETE) < 0) {
warn("inotify_add_watch(): %s, use a timer",
- strerror(errno));
+ strerror_(errno));
close(fd);
fd = -1;
}
@@ -436,6 +471,7 @@ void pasta_netns_quit_init(const struct ctx *c)
ref.type = EPOLL_TYPE_NSQUIT_TIMER;
} else {
close(dir_fd);
+ ref.type = EPOLL_TYPE_NSQUIT_INOTIFY;
}
if (fd > FD_REF_MAX)
diff --git a/pcap.c b/pcap.c
index 46cc4b0..3d623cf 100644
--- a/pcap.c
+++ b/pcap.c
@@ -86,9 +86,8 @@ static void pcap_frame(const struct iovec *iov, size_t iovcnt,
.caplen = l2len,
.len = l2len
};
- struct iovec hiov = { &h, sizeof(h) };
- if (write_remainder(pcap_fd, &hiov, 1, 0) < 0 ||
+ if (write_all_buf(pcap_fd, &h, sizeof(h)) < 0 ||
write_remainder(pcap_fd, iov, iovcnt, offset) < 0)
debug_perror("Cannot log packet, length %zu", l2len);
}
@@ -101,12 +100,14 @@ static void pcap_frame(const struct iovec *iov, size_t iovcnt,
void pcap(const char *pkt, size_t l2len)
{
struct iovec iov = { (char *)pkt, l2len };
- struct timespec now;
+ struct timespec now = { 0 };
if (pcap_fd == -1)
return;
- clock_gettime(CLOCK_REALTIME, &now);
+ if (clock_gettime(CLOCK_REALTIME, &now))
+ err_perror("Failed to get CLOCK_REALTIME time");
+
pcap_frame(&iov, 1, 0, &now);
}
@@ -120,13 +121,14 @@ void pcap(const char *pkt, size_t l2len)
void pcap_multiple(const struct iovec *iov, size_t frame_parts, unsigned int n,
size_t offset)
{
- struct timespec now;
+ struct timespec now = { 0 };
unsigned int i;
if (pcap_fd == -1)
return;
- clock_gettime(CLOCK_REALTIME, &now);
+ if (clock_gettime(CLOCK_REALTIME, &now))
+ err_perror("Failed to get CLOCK_REALTIME time");
for (i = 0; i < n; i++)
pcap_frame(iov + i * frame_parts, frame_parts, offset, &now);
@@ -139,17 +141,19 @@ void pcap_multiple(const struct iovec *iov, size_t frame_parts, unsigned int n,
* @iov: Pointer to the array of struct iovec describing the I/O vector
* containing packet data to write, including L2 header
* @iovcnt: Number of buffers (@iov entries)
+ * @offset: Offset of the L2 frame within the full data length
*/
-/* cppcheck-suppress unusedFunction */
-void pcap_iov(const struct iovec *iov, size_t iovcnt)
+void pcap_iov(const struct iovec *iov, size_t iovcnt, size_t offset)
{
- struct timespec now;
+ struct timespec now = { 0 };
if (pcap_fd == -1)
return;
- clock_gettime(CLOCK_REALTIME, &now);
- pcap_frame(iov, iovcnt, 0, &now);
+ if (clock_gettime(CLOCK_REALTIME, &now))
+ err_perror("Failed to get CLOCK_REALTIME time");
+
+ pcap_frame(iov, iovcnt, offset, &now);
}
/**
@@ -158,18 +162,15 @@ void pcap_iov(const struct iovec *iov, size_t iovcnt)
*/
void pcap_init(struct ctx *c)
{
- int flags = O_WRONLY | O_CREAT | O_TRUNC;
-
if (pcap_fd != -1)
return;
if (!*c->pcap)
return;
- flags |= c->foreground ? O_CLOEXEC : 0;
- pcap_fd = open(c->pcap, flags, S_IRUSR | S_IWUSR);
+ pcap_fd = output_file_open(c->pcap, O_WRONLY);
if (pcap_fd == -1) {
- perror("open");
+ err_perror("Couldn't open pcap file %s", c->pcap);
return;
}
diff --git a/pcap.h b/pcap.h
index 5339237..9795f2e 100644
--- a/pcap.h
+++ b/pcap.h
@@ -9,7 +9,7 @@
void pcap(const char *pkt, size_t l2len);
void pcap_multiple(const struct iovec *iov, size_t frame_parts, unsigned int n,
size_t offset);
-void pcap_iov(const struct iovec *iov, size_t iovcnt);
+void pcap_iov(const struct iovec *iov, size_t iovcnt, size_t offset);
void pcap_init(struct ctx *c);
#endif /* PCAP_H */
diff --git a/pif.c b/pif.c
index a099e31..592fafa 100644
--- a/pif.c
+++ b/pif.c
@@ -59,3 +59,45 @@ void pif_sockaddr(const struct ctx *c, union sockaddr_inany *sa, socklen_t *sl,
*sl = sizeof(sa->sa6);
}
}
+
+/** pif_sock_l4() - Open a socket bound to an address on a specified interface
+ * @c: Execution context
+ * @type: Socket epoll type
+ * @pif: Interface for this socket
+ * @addr: Address to bind to, or NULL for dual-stack any
+ * @ifname: Interface for binding, NULL for any
+ * @port: Port number to bind to (host byte order)
+ * @data: epoll reference portion for protocol handlers
+ *
+ * NOTE: For namespace pifs, this must be called having already entered the
+ * relevant namespace.
+ *
+ * Return: newly created socket, negative error code on failure
+ */
+int pif_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
+ const union inany_addr *addr, const char *ifname,
+ in_port_t port, uint32_t data)
+{
+ union sockaddr_inany sa = {
+ .sa6.sin6_family = AF_INET6,
+ .sa6.sin6_addr = in6addr_any,
+ .sa6.sin6_port = htons(port),
+ };
+ socklen_t sl;
+
+ ASSERT(pif_is_socket(pif));
+
+ if (pif == PIF_SPLICE) {
+ /* Sanity checks */
+ ASSERT(!ifname);
+ ASSERT(addr && inany_is_loopback(addr));
+ }
+
+ if (!addr)
+ return sock_l4_sa(c, type, &sa, sizeof(sa.sa6),
+ ifname, false, data);
+
+ pif_sockaddr(c, &sa, &sl, pif, addr, port);
+ return sock_l4_sa(c, type, &sa, sl,
+ ifname, sa.sa_family == AF_INET6, data);
+}
diff --git a/pif.h b/pif.h
index 8777bb5..f029282 100644
--- a/pif.h
+++ b/pif.h
@@ -59,5 +59,8 @@ static inline bool pif_is_socket(uint8_t pif)
void pif_sockaddr(const struct ctx *c, union sockaddr_inany *sa, socklen_t *sl,
uint8_t pif, const union inany_addr *addr, in_port_t port);
+int pif_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
+ const union inany_addr *addr, const char *ifname,
+ in_port_t port, uint32_t data);
#endif /* PIF_H */
diff --git a/seccomp.sh b/seccomp.sh
index 052e1c8..6499c58 100755
--- a/seccomp.sh
+++ b/seccomp.sh
@@ -20,6 +20,15 @@ OUT="$(mktemp)"
[ -z "${ARCH}" ] && ARCH="$(uname -m)"
[ -z "${CC}" ] && CC="cc"
+AUDIT_ARCH="AUDIT_ARCH_$(echo ${ARCH} | tr [a-z] [A-Z] \
+ | sed 's/^ARM.*/ARM/' \
+ | sed 's/I[456]86/I386/' \
+ | sed 's/PPC64/PPC/' \
+ | sed 's/PPCLE/PPC64LE/' \
+ | sed 's/MIPS64EL/MIPSEL64/' \
+ | sed 's/HPPA/PARISC/' \
+ | sed 's/SH4/SH/')"
+
HEADER="/* This file was automatically generated by $(basename ${0}) */
#ifndef AUDIT_ARCH_PPC64LE
@@ -32,7 +41,7 @@ struct sock_filter filter_@PROFILE@[] = {
/* cppcheck-suppress [badBitmaskCheck, unmatchedSuppression] */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, arch))),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PASST_AUDIT_ARCH, 0, @KILL@),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, @AUDIT_ARCH@, 0, @KILL@),
/* cppcheck-suppress [badBitmaskCheck, unmatchedSuppression] */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, nr))),
@@ -233,7 +242,8 @@ gen_profile() {
sub ${__i} CALL "NR:${__nr}" "NAME:${__name}" "ALLOW:${__allow}"
done
- finish PRE "PROFILE:${__profile}" "KILL:$(( __statements + 1))"
+ finish PRE "PROFILE:${__profile}" "KILL:$(( __statements + 1))" \
+ "AUDIT_ARCH:${AUDIT_ARCH}"
}
printf '%s\n' "${HEADER}" > "${OUT}"
@@ -242,7 +252,10 @@ for __p in ${__profiles}; do
__calls="$(sed -n 's/[\t ]*\*[\t ]*#syscalls\(:'"${__p}"'\|\)[\t ]\{1,\}\(.*\)/\2/p' ${IN})"
__calls="${__calls} ${EXTRA_SYSCALLS:-}"
__calls="$(filter ${__calls})"
- echo "seccomp profile ${__p} allows: ${__calls}" | tr '\n' ' ' | fmt -t
+
+ cols="$(stty -a | sed -n 's/.*columns \([0-9]*\).*/\1/p' || :)" 2>/dev/null
+ case $cols in [0-9]*) col_args="-w ${cols}";; *) col_args="";; esac
+ echo "seccomp profile ${__p} allows: ${__calls}" | tr '\n' ' ' | fmt -t ${col_args}
# Pad here to keep gen_profile() "simple"
__count=0
diff --git a/tap.c b/tap.c
index 87be3a6..cd32a90 100644
--- a/tap.c
+++ b/tap.c
@@ -58,6 +58,8 @@
#include "packet.h"
#include "tap.h"
#include "log.h"
+#include "vhost_user.h"
+#include "vu_common.h"
/* IPv4 (plus ARP) and IPv6 message batches from tap/guest to IP handlers */
static PACKET_POOL_NOINIT(pool_tap4, TAP_MSGS, pkt_buf);
@@ -78,16 +80,22 @@ void tap_send_single(const struct ctx *c, const void *data, size_t l2len)
struct iovec iov[2];
size_t iovcnt = 0;
- if (c->mode == MODE_PASST) {
+ switch (c->mode) {
+ case MODE_PASST:
iov[iovcnt] = IOV_OF_LVALUE(vnet_len);
iovcnt++;
- }
-
- iov[iovcnt].iov_base = (void *)data;
- iov[iovcnt].iov_len = l2len;
- iovcnt++;
+ /* fall through */
+ case MODE_PASTA:
+ iov[iovcnt].iov_base = (void *)data;
+ iov[iovcnt].iov_len = l2len;
+ iovcnt++;
- tap_send_frames(c, iov, iovcnt, 1);
+ tap_send_frames(c, iov, iovcnt, 1);
+ break;
+ case MODE_VU:
+ vu_send_single(c, data, l2len);
+ break;
+ }
}
/**
@@ -118,8 +126,8 @@ static void *tap_push_l2h(const struct ctx *c, void *buf, uint16_t proto)
struct ethhdr *eh = (struct ethhdr *)buf;
/* TODO: ARP table lookup */
- memcpy(eh->h_dest, c->mac_guest, ETH_ALEN);
- memcpy(eh->h_source, c->mac, ETH_ALEN);
+ memcpy(eh->h_dest, c->guest_mac, ETH_ALEN);
+ memcpy(eh->h_source, c->our_tap_mac, ETH_ALEN);
eh->h_proto = ntohs(proto);
return eh + 1;
}
@@ -172,11 +180,16 @@ void tap_udp4_send(const struct ctx *c, struct in_addr src, in_port_t sport,
struct iphdr *ip4h = tap_push_l2h(c, buf, ETH_P_IP);
struct udphdr *uh = tap_push_ip4h(ip4h, src, dst, l4len, IPPROTO_UDP);
char *data = (char *)(uh + 1);
+ const struct iovec iov = {
+ .iov_base = (void *)in,
+ .iov_len = dlen
+ };
+ struct iov_tail payload = IOV_TAIL(&iov, 1, 0);
uh->source = htons(sport);
uh->dest = htons(dport);
uh->len = htons(l4len);
- csum_udp4(uh, src, dst, in, dlen);
+ csum_udp4(uh, src, dst, &payload);
memcpy(data, in, dlen);
tap_send_single(c, buf, dlen + (data - buf));
@@ -247,7 +260,7 @@ static void *tap_push_ip6h(struct ipv6hdr *ip6h,
void tap_udp6_send(const struct ctx *c,
const struct in6_addr *src, in_port_t sport,
const struct in6_addr *dst, in_port_t dport,
- uint32_t flow, const void *in, size_t dlen)
+ uint32_t flow, void *in, size_t dlen)
{
size_t l4len = dlen + sizeof(struct udphdr);
char buf[USHRT_MAX];
@@ -255,11 +268,16 @@ void tap_udp6_send(const struct ctx *c,
struct udphdr *uh = tap_push_ip6h(ip6h, src, dst,
l4len, IPPROTO_UDP, flow);
char *data = (char *)(uh + 1);
+ const struct iovec iov = {
+ .iov_base = in,
+ .iov_len = dlen
+ };
+ struct iov_tail payload = IOV_TAIL(&iov, 1, 0);
uh->source = htons(sport);
uh->dest = htons(dport);
uh->len = htons(l4len);
- csum_udp6(uh, src, dst, in, dlen);
+ csum_udp6(uh, src, dst, &payload);
memcpy(data, in, dlen);
tap_send_single(c, buf, dlen + (data - buf));
@@ -406,10 +424,18 @@ size_t tap_send_frames(const struct ctx *c, const struct iovec *iov,
if (!nframes)
return 0;
- if (c->mode == MODE_PASTA)
+ switch (c->mode) {
+ case MODE_PASTA:
m = tap_send_frames_pasta(c, iov, bufs_per_frame, nframes);
- else
+ break;
+ case MODE_PASST:
m = tap_send_frames_passt(c, iov, bufs_per_frame, nframes);
+ break;
+ case MODE_VU:
+ /* fall through */
+ default:
+ ASSERT(0);
+ }
if (m < nframes)
debug("tap: failed to send %zu frames of %zu",
@@ -795,6 +821,9 @@ resume:
if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_seen)) {
c->ip6.addr_seen = *saddr;
}
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr))
+ c->ip6.addr = *saddr;
} else if (!IN6_IS_ADDR_UNSPECIFIED(saddr)){
c->ip6.addr_seen = *saddr;
}
@@ -946,9 +975,9 @@ void tap_add_packet(struct ctx *c, ssize_t l2len, char *p)
eh = (struct ethhdr *)p;
- if (memcmp(c->mac_guest, eh->h_source, ETH_ALEN)) {
- memcpy(c->mac_guest, eh->h_source, ETH_ALEN);
- proto_update_l2_buf(c->mac_guest, NULL);
+ if (memcmp(c->guest_mac, eh->h_source, ETH_ALEN)) {
+ memcpy(c->guest_mac, eh->h_source, ETH_ALEN);
+ proto_update_l2_buf(c->guest_mac, NULL);
}
switch (ntohs(eh->h_proto)) {
@@ -968,7 +997,7 @@ void tap_add_packet(struct ctx *c, ssize_t l2len, char *p)
* tap_sock_reset() - Handle closing or failure of connect AF_UNIX socket
* @c: Execution context
*/
-static void tap_sock_reset(struct ctx *c)
+void tap_sock_reset(struct ctx *c)
{
info("Client connection closed%s", c->one_off ? ", exiting" : "");
@@ -979,27 +1008,22 @@ static void tap_sock_reset(struct ctx *c)
epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_tap, NULL);
close(c->fd_tap);
c->fd_tap = -1;
+ if (c->mode == MODE_VU)
+ vu_cleanup(c->vdev);
}
/**
- * tap_handler_passt() - Packet handler for AF_UNIX file descriptor
+ * tap_passt_input() - Handler for new data on the socket to qemu
* @c: Execution context
- * @events: epoll events
* @now: Current timestamp
*/
-void tap_handler_passt(struct ctx *c, uint32_t events,
- const struct timespec *now)
+static void tap_passt_input(struct ctx *c, const struct timespec *now)
{
static const char *partial_frame;
static ssize_t partial_len = 0;
ssize_t n;
char *p;
- if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
- tap_sock_reset(c);
- return;
- }
-
tap_flush_pools();
if (partial_len) {
@@ -1010,10 +1034,13 @@ void tap_handler_passt(struct ctx *c, uint32_t events,
memmove(pkt_buf, partial_frame, partial_len);
}
- n = recv(c->fd_tap, pkt_buf + partial_len, TAP_BUF_BYTES - partial_len,
- MSG_DONTWAIT);
+ do {
+ n = recv(c->fd_tap, pkt_buf + partial_len,
+ TAP_BUF_BYTES - partial_len, MSG_DONTWAIT);
+ } while ((n < 0) && errno == EINTR);
+
if (n < 0) {
- if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
err_perror("Receive error on guest connection, reset");
tap_sock_reset(c);
}
@@ -1052,54 +1079,76 @@ void tap_handler_passt(struct ctx *c, uint32_t events,
}
/**
- * tap_handler_pasta() - Packet handler for /dev/net/tun file descriptor
+ * tap_handler_passt() - Event handler for AF_UNIX file descriptor
* @c: Execution context
* @events: epoll events
* @now: Current timestamp
*/
-void tap_handler_pasta(struct ctx *c, uint32_t events,
+void tap_handler_passt(struct ctx *c, uint32_t events,
const struct timespec *now)
{
- ssize_t n, len;
- int ret;
+ if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
+ tap_sock_reset(c);
+ return;
+ }
- if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
- die("Disconnect event on /dev/net/tun device, exiting");
+ if (events & EPOLLIN)
+ tap_passt_input(c, now);
+}
-redo:
- n = 0;
+/**
+ * tap_pasta_input() - Handler for new data on the socket to hypervisor
+ * @c: Execution context
+ * @now: Current timestamp
+ */
+static void tap_pasta_input(struct ctx *c, const struct timespec *now)
+{
+ ssize_t n, len;
tap_flush_pools();
-restart:
- while ((len = read(c->fd_tap, pkt_buf + n, TAP_BUF_BYTES - n)) > 0) {
- if (len < (ssize_t)sizeof(struct ethhdr) ||
- len > (ssize_t)ETH_MAX_MTU) {
- n += len;
- continue;
- }
+ for (n = 0; n <= (ssize_t)(TAP_BUF_BYTES - ETH_MAX_MTU); n += len) {
+ len = read(c->fd_tap, pkt_buf + n, ETH_MAX_MTU);
+ if (len == 0) {
+ die("EOF on tap device, exiting");
+ } else if (len < 0) {
+ if (errno == EINTR) {
+ len = 0;
+ continue;
+ }
- tap_add_packet(c, len, pkt_buf + n);
+ if (errno == EAGAIN && errno == EWOULDBLOCK)
+ break; /* all done for now */
- if ((n += len) == TAP_BUF_BYTES)
- break;
- }
+ die("Error on tap device, exiting");
+ }
- if (len < 0 && errno == EINTR)
- goto restart;
+ /* Ignore frames of bad length */
+ if (len < (ssize_t)sizeof(struct ethhdr) ||
+ len > (ssize_t)ETH_MAX_MTU)
+ continue;
- ret = errno;
+ tap_add_packet(c, len, pkt_buf + n);
+ }
tap_handler(c, now);
+}
- if (len > 0 || ret == EAGAIN)
- return;
-
- if (n == TAP_BUF_BYTES)
- goto redo;
+/**
+ * tap_handler_pasta() - Packet handler for /dev/net/tun file descriptor
+ * @c: Execution context
+ * @events: epoll events
+ * @now: Current timestamp
+ */
+void tap_handler_pasta(struct ctx *c, uint32_t events,
+ const struct timespec *now)
+{
+ if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
+ die("Disconnect event on /dev/net/tun device, exiting");
- die("Error on tap device, exiting");
+ if (events & EPOLLIN)
+ tap_pasta_input(c, now);
}
/**
@@ -1110,7 +1159,7 @@ restart:
*/
int tap_sock_unix_open(char *sock_path)
{
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
@@ -1125,10 +1174,12 @@ int tap_sock_unix_open(char *sock_path)
if (*sock_path)
memcpy(path, sock_path, UNIX_PATH_MAX);
- else
- snprintf(path, UNIX_PATH_MAX - 1, UNIX_SOCK_PATH, i);
+ else if (snprintf_check(path, UNIX_PATH_MAX - 1,
+ UNIX_SOCK_PATH, i))
+ die_perror("Can't build UNIX domain socket path");
- ex = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ ex = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+ 0);
if (ex < 0)
die_perror("Failed to check for UNIX domain conflicts");
@@ -1163,10 +1214,35 @@ int tap_sock_unix_open(char *sock_path)
}
/**
+ * tap_backend_show_hints() - Give help information to start QEMU
+ * @c: Execution context
+ */
+static void tap_backend_show_hints(struct ctx *c)
+{
+ switch (c->mode) {
+ case MODE_PASTA:
+ /* No hints */
+ break;
+ case MODE_PASST:
+ info("\nYou can now start qemu (>= 7.2, with commit 13c6be96618c):");
+ info(" kvm ... -device virtio-net-pci,netdev=s -netdev stream,id=s,server=off,addr.type=unix,addr.path=%s",
+ c->sock_path);
+ info("or qrap, for earlier qemu versions:");
+ info(" ./qrap 5 kvm ... -net socket,fd=5 -net nic,model=virtio");
+ break;
+ case MODE_VU:
+ info("You can start qemu with:");
+ info(" kvm ... -chardev socket,id=chr0,path=%s -netdev vhost-user,id=netdev0,chardev=chr0 -device virtio-net,netdev=netdev0 -object memory-backend-memfd,id=memfd0,share=on,size=$RAMSIZE -numa node,memdev=memfd0\n",
+ c->sock_path);
+ break;
+ }
+}
+
+/**
* tap_sock_unix_init() - Start listening for connections on AF_UNIX socket
* @c: Execution context
*/
-static void tap_sock_unix_init(struct ctx *c)
+static void tap_sock_unix_init(const struct ctx *c)
{
union epoll_ref ref = { .type = EPOLL_TYPE_TAP_LISTEN };
struct epoll_event ev = { 0 };
@@ -1177,12 +1253,33 @@ static void tap_sock_unix_init(struct ctx *c)
ev.events = EPOLLIN | EPOLLET;
ev.data.u64 = ref.u64;
epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap_listen, &ev);
+}
+
+/**
+ * tap_start_connection() - start a new connection
+ * @c: Execution context
+ */
+static void tap_start_connection(const struct ctx *c)
+{
+ struct epoll_event ev = { 0 };
+ union epoll_ref ref = { 0 };
+
+ ref.fd = c->fd_tap;
+ switch (c->mode) {
+ case MODE_PASST:
+ ref.type = EPOLL_TYPE_TAP_PASST;
+ break;
+ case MODE_PASTA:
+ ref.type = EPOLL_TYPE_TAP_PASTA;
+ break;
+ case MODE_VU:
+ ref.type = EPOLL_TYPE_VHOST_CMD;
+ break;
+ }
- info("\nYou can now start qemu (>= 7.2, with commit 13c6be96618c):");
- info(" kvm ... -device virtio-net-pci,netdev=s -netdev stream,id=s,server=off,addr.type=unix,addr.path=%s",
- c->sock_path);
- info("or qrap, for earlier qemu versions:");
- info(" ./qrap 5 kvm ... -net socket,fd=5 -net nic,model=virtio");
+ ev.events = EPOLLIN | EPOLLRDHUP;
+ ev.data.u64 = ref.u64;
+ epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap, &ev);
}
/**
@@ -1192,8 +1289,6 @@ static void tap_sock_unix_init(struct ctx *c)
*/
void tap_listen_handler(struct ctx *c, uint32_t events)
{
- union epoll_ref ref = { .type = EPOLL_TYPE_TAP_PASST };
- struct epoll_event ev = { 0 };
int v = INT_MAX / 2;
struct ucred ucred;
socklen_t len;
@@ -1232,10 +1327,7 @@ void tap_listen_handler(struct ctx *c, uint32_t events)
setsockopt(c->fd_tap, SOL_SOCKET, SO_SNDBUF, &v, sizeof(v)))
trace("tap: failed to set SO_SNDBUF to %i", v);
- ref.fd = c->fd_tap;
- ev.events = EPOLLIN | EPOLLRDHUP;
- ev.data.u64 = ref.u64;
- epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap, &ev);
+ tap_start_connection(c);
}
/**
@@ -1261,7 +1353,7 @@ static int tap_ns_tun(void *arg)
if (fd < 0)
die_perror("Failed to open() /dev/net/tun");
- rc = ioctl(fd, TUNSETIFF, &ifr);
+ rc = ioctl(fd, (int)TUNSETIFF, &ifr);
if (rc < 0)
die_perror("TUNSETIFF ioctl on /dev/net/tun failed");
@@ -1279,64 +1371,68 @@ static int tap_ns_tun(void *arg)
*/
static void tap_sock_tun_init(struct ctx *c)
{
- union epoll_ref ref = { .type = EPOLL_TYPE_TAP_PASTA };
- struct epoll_event ev = { 0 };
-
NS_CALL(tap_ns_tun, c);
if (c->fd_tap == -1)
die("Failed to set up tap device in namespace");
pasta_ns_conf(c);
- ref.fd = c->fd_tap;
- ev.events = EPOLLIN | EPOLLRDHUP;
- ev.data.u64 = ref.u64;
- epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap, &ev);
+ tap_start_connection(c);
}
/**
- * tap_sock_init() - Create and set up AF_UNIX socket or tuntap file descriptor
- * @c: Execution context
+ * tap_sock_update_pool() - Set the buffer base and size for the pool of packets
+ * @base: Buffer base
+ * @size Buffer size
*/
-void tap_sock_init(struct ctx *c)
+void tap_sock_update_pool(void *base, size_t size)
{
- size_t sz = sizeof(pkt_buf);
int i;
- pool_tap4_storage = PACKET_INIT(pool_tap4, TAP_MSGS, pkt_buf, sz);
- pool_tap6_storage = PACKET_INIT(pool_tap6, TAP_MSGS, pkt_buf, sz);
+ pool_tap4_storage = PACKET_INIT(pool_tap4, TAP_MSGS, base, size);
+ pool_tap6_storage = PACKET_INIT(pool_tap6, TAP_MSGS, base, size);
for (i = 0; i < TAP_SEQS; i++) {
- tap4_l4[i].p = PACKET_INIT(pool_l4, UIO_MAXIOV, pkt_buf, sz);
- tap6_l4[i].p = PACKET_INIT(pool_l4, UIO_MAXIOV, pkt_buf, sz);
+ tap4_l4[i].p = PACKET_INIT(pool_l4, UIO_MAXIOV, base, size);
+ tap6_l4[i].p = PACKET_INIT(pool_l4, UIO_MAXIOV, base, size);
}
+}
- if (c->fd_tap != -1) { /* Passed as --fd */
- struct epoll_event ev = { 0 };
- union epoll_ref ref;
+/**
+ * tap_backend_init() - Create and set up AF_UNIX socket or
+ * tuntap file descriptor
+ * @c: Execution context
+ */
+void tap_backend_init(struct ctx *c)
+{
+ if (c->mode == MODE_VU) {
+ tap_sock_update_pool(NULL, 0);
+ vu_init(c);
+ } else {
+ tap_sock_update_pool(pkt_buf, sizeof(pkt_buf));
+ }
+ if (c->fd_tap != -1) { /* Passed as --fd */
ASSERT(c->one_off);
- ref.fd = c->fd_tap;
- if (c->mode == MODE_PASST)
- ref.type = EPOLL_TYPE_TAP_PASST;
- else
- ref.type = EPOLL_TYPE_TAP_PASTA;
-
- ev.events = EPOLLIN | EPOLLRDHUP;
- ev.data.u64 = ref.u64;
- epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_tap, &ev);
+ tap_start_connection(c);
return;
}
- if (c->mode == MODE_PASTA) {
+ switch (c->mode) {
+ case MODE_PASTA:
tap_sock_tun_init(c);
- } else {
+ break;
+ case MODE_VU:
+ case MODE_PASST:
tap_sock_unix_init(c);
/* In passt mode, we don't know the guest's MAC address until it
* sends us packets. Use the broadcast address so that our
* first packets will reach it.
*/
- memset(&c->mac_guest, 0xff, sizeof(c->mac_guest));
+ memset(&c->guest_mac, 0xff, sizeof(c->guest_mac));
+ break;
}
+
+ tap_backend_show_hints(c);
}
diff --git a/tap.h b/tap.h
index ec9e2ac..dfbd8b9 100644
--- a/tap.h
+++ b/tap.h
@@ -40,7 +40,8 @@ static inline struct iovec tap_hdr_iov(const struct ctx *c,
*/
static inline void tap_hdr_update(struct tap_hdr *thdr, size_t l2len)
{
- thdr->vnet_len = htonl(l2len);
+ if (thdr)
+ thdr->vnet_len = htonl(l2len);
}
void tap_udp4_send(const struct ctx *c, struct in_addr src, in_port_t sport,
@@ -53,7 +54,7 @@ const struct in6_addr *tap_ip6_daddr(const struct ctx *c,
void tap_udp6_send(const struct ctx *c,
const struct in6_addr *src, in_port_t sport,
const struct in6_addr *dst, in_port_t dport,
- uint32_t flow, const void *in, size_t dlen);
+ uint32_t flow, void *in, size_t dlen);
void tap_icmp6_send(const struct ctx *c,
const struct in6_addr *src, const struct in6_addr *dst,
const void *in, size_t l4len);
@@ -68,7 +69,9 @@ void tap_handler_pasta(struct ctx *c, uint32_t events,
void tap_handler_passt(struct ctx *c, uint32_t events,
const struct timespec *now);
int tap_sock_unix_open(char *sock_path);
-void tap_sock_init(struct ctx *c);
+void tap_sock_reset(struct ctx *c);
+void tap_sock_update_pool(void *base, size_t size);
+void tap_backend_init(struct ctx *c);
void tap_flush_pools(void);
void tap_handler(struct ctx *c, const struct timespec *now);
void tap_add_packet(struct ctx *c, ssize_t l2len, char *p);
diff --git a/tcp.c b/tcp.c
index c0820ce..ec433f7 100644
--- a/tcp.c
+++ b/tcp.c
@@ -274,6 +274,7 @@
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
+#include <netinet/tcp.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
@@ -286,8 +287,6 @@
#include <time.h>
#include <arpa/inet.h>
-#include <linux/tcp.h> /* For struct tcp_info */
-
#include "checksum.h"
#include "util.h"
#include "iov.h"
@@ -300,19 +299,16 @@
#include "log.h"
#include "inany.h"
#include "flow.h"
+#include "linux_dep.h"
#include "flow_table.h"
#include "tcp_internal.h"
#include "tcp_buf.h"
+#include "tcp_vu.h"
/* MSS rounding: see SET_MSS() */
#define MSS_DEFAULT 536
#define WINDOW_DEFAULT 14600 /* RFC 6928 */
-#ifdef HAS_SND_WND
-# define KERNEL_REPORTS_SND_WND(c) ((c)->tcp.kernel_snd_wnd)
-#else
-# define KERNEL_REPORTS_SND_WND(c) (0 && (c))
-#endif
#define ACK_INTERVAL 10 /* ms */
#define SYN_TIMEOUT 10 /* s */
@@ -323,11 +319,6 @@
#define LOW_RTT_TABLE_SIZE 8
#define LOW_RTT_THRESHOLD 10 /* us */
-/* We need to include <linux/tcp.h> for tcpi_bytes_acked, instead of
- * <netinet/tcp.h>, but that doesn't include a definition for SOL_TCP
- */
-#define SOL_TCP IPPROTO_TCP
-
#define ACK_IF_NEEDED 0 /* See tcp_send_flag() */
#define CONN_IS_CLOSING(conn) \
@@ -361,8 +352,8 @@ static const char *tcp_flag_str[] __attribute((__unused__)) = {
static int tcp_sock_init_ext [NUM_PORTS][IP_VERSIONS];
static int tcp_sock_ns [NUM_PORTS][IP_VERSIONS];
-/* Table of guest side forwarding addresses with very low RTT (assumed
- * to be local to the host), LRU
+/* Table of our guest side addresses with very low RTT (assumed to be local to
+ * the host), LRU
*/
static union inany_addr low_rtt_dst[LOW_RTT_TABLE_SIZE];
@@ -371,6 +362,20 @@ char tcp_buf_discard [MAX_WINDOW];
/* Does the kernel support TCP_PEEK_OFF? */
bool peek_offset_cap;
+/* Size of data returned by TCP_INFO getsockopt() */
+socklen_t tcp_info_size;
+
+#define tcp_info_cap(f_) \
+ ((offsetof(struct tcp_info_linux, tcpi_##f_) + \
+ sizeof(((struct tcp_info_linux *)NULL)->tcpi_##f_)) <= tcp_info_size)
+
+/* Kernel reports sending window in TCP_INFO (kernel commit 8f7baad7f035) */
+#define snd_wnd_cap tcp_info_cap(snd_wnd)
+/* Kernel reports bytes acked in TCP_INFO (kernel commit 0df48c26d84) */
+#define bytes_acked_cap tcp_info_cap(bytes_acked)
+/* Kernel reports minimum RTT in TCP_INFO (kernel commit cd9b266095f4) */
+#define min_rtt_cap tcp_info_cap(min_rtt)
+
/* sendmsg() to socket */
static struct iovec tcp_iov [UIO_MAXIOV];
@@ -440,7 +445,7 @@ static uint32_t tcp_conn_epoll_events(uint8_t events, uint8_t conn_flags)
if (events == TAP_SYN_RCVD)
return EPOLLOUT | EPOLLET | EPOLLRDHUP;
- return EPOLLRDHUP;
+ return EPOLLET | EPOLLRDHUP;
}
/**
@@ -511,7 +516,7 @@ static void tcp_timer_ctl(const struct ctx *c, struct tcp_tap_conn *conn)
fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (fd == -1 || fd > FD_REF_MAX) {
flow_dbg(conn, "failed to get timer: %s",
- strerror(errno));
+ strerror_(errno));
if (fd > -1)
close(fd);
conn->timer = -1;
@@ -521,7 +526,7 @@ static void tcp_timer_ctl(const struct ctx *c, struct tcp_tap_conn *conn)
if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, conn->timer, &ev)) {
flow_dbg(conn, "failed to add timer: %s",
- strerror(errno));
+ strerror_(errno));
close(conn->timer);
conn->timer = -1;
return;
@@ -545,7 +550,8 @@ static void tcp_timer_ctl(const struct ctx *c, struct tcp_tap_conn *conn)
(unsigned long long)it.it_value.tv_sec,
(unsigned long long)it.it_value.tv_nsec / 1000 / 1000);
- timerfd_settime(conn->timer, 0, &it, NULL);
+ if (timerfd_settime(conn->timer, 0, &it, NULL))
+ flow_err(conn, "failed to set timer: %s", strerror_(errno));
}
/**
@@ -663,7 +669,7 @@ static int tcp_rtt_dst_low(const struct tcp_tap_conn *conn)
int i;
for (i = 0; i < LOW_RTT_TABLE_SIZE; i++)
- if (inany_equals(&tapside->faddr, low_rtt_dst + i))
+ if (inany_equals(&tapside->oaddr, low_rtt_dst + i))
return 1;
return 0;
@@ -675,18 +681,17 @@ static int tcp_rtt_dst_low(const struct tcp_tap_conn *conn)
* @tinfo: Pointer to struct tcp_info for socket
*/
static void tcp_rtt_dst_check(const struct tcp_tap_conn *conn,
- const struct tcp_info *tinfo)
+ const struct tcp_info_linux *tinfo)
{
-#ifdef HAS_MIN_RTT
const struct flowside *tapside = TAPFLOW(conn);
int i, hole = -1;
- if (!tinfo->tcpi_min_rtt ||
+ if (!min_rtt_cap ||
(int)tinfo->tcpi_min_rtt > LOW_RTT_THRESHOLD)
return;
for (i = 0; i < LOW_RTT_TABLE_SIZE; i++) {
- if (inany_equals(&tapside->faddr, low_rtt_dst + i))
+ if (inany_equals(&tapside->oaddr, low_rtt_dst + i))
return;
if (hole == -1 && IN6_IS_ADDR_UNSPECIFIED(low_rtt_dst + i))
hole = i;
@@ -698,14 +703,10 @@ static void tcp_rtt_dst_check(const struct tcp_tap_conn *conn,
if (hole == -1)
return;
- low_rtt_dst[hole++] = tapside->faddr;
+ low_rtt_dst[hole++] = tapside->oaddr;
if (hole == LOW_RTT_TABLE_SIZE)
hole = 0;
inany_from_af(low_rtt_dst + hole, AF_INET6, &in6addr_any);
-#else
- (void)conn;
- (void)tinfo;
-#endif /* HAS_MIN_RTT */
}
/**
@@ -752,34 +753,16 @@ static void tcp_sock_set_bufsize(const struct ctx *c, int s)
}
/**
- * tcp_update_check_tcp4() - Update TCP checksum from stored one
- * @iph: IPv4 header
- * @th: TCP header followed by TCP payload
- */
-static void tcp_update_check_tcp4(const struct iphdr *iph, struct tcphdr *th)
-{
- uint16_t l4len = ntohs(iph->tot_len) - sizeof(struct iphdr);
- struct in_addr saddr = { .s_addr = iph->saddr };
- struct in_addr daddr = { .s_addr = iph->daddr };
- uint32_t sum = proto_ipv4_header_psum(l4len, IPPROTO_TCP, saddr, daddr);
-
- th->check = 0;
- th->check = csum(th, l4len, sum);
-}
-
-/**
- * tcp_update_check_tcp6() - Calculate TCP checksum for IPv6
- * @ip6h: IPv6 header
- * @th: TCP header followed by TCP payload
+ * tcp_update_csum() - Calculate TCP checksum
+ * @psum: Unfolded partial checksum of the IPv4 or IPv6 pseudo-header
+ * @th: TCP header (updated)
+ * @payload: TCP payload
*/
-static void tcp_update_check_tcp6(struct ipv6hdr *ip6h, struct tcphdr *th)
+void tcp_update_csum(uint32_t psum, struct tcphdr *th, struct iov_tail *payload)
{
- uint16_t l4len = ntohs(ip6h->payload_len);
- uint32_t sum = proto_ipv6_header_psum(l4len, IPPROTO_TCP,
- &ip6h->saddr, &ip6h->daddr);
-
th->check = 0;
- th->check = csum(th, l4len, sum);
+ psum = csum_unfolded(th, sizeof(*th), psum);
+ th->check = csum_iov_tail(payload, psum);
}
/**
@@ -865,7 +848,6 @@ bool tcp_flow_defer(const struct tcp_tap_conn *conn)
/* cppcheck-suppress [constParameterPointer, unmatchedSuppression] */
void tcp_defer_handler(struct ctx *c)
{
- tcp_flags_flush(c);
tcp_payload_flush(c);
}
@@ -881,7 +863,7 @@ static void tcp_fill_header(struct tcphdr *th,
{
const struct flowside *tapside = TAPFLOW(conn);
- th->source = htons(tapside->fport);
+ th->source = htons(tapside->oport);
th->dest = htons(tapside->eport);
th->seq = htonl(seq);
th->ack_seq = htonl(conn->seq_ack_to_tap);
@@ -895,116 +877,82 @@ static void tcp_fill_header(struct tcphdr *th,
}
/**
- * tcp_fill_headers4() - Fill 802.3, IPv4, TCP headers in pre-cooked buffers
- * @conn: Connection pointer
- * @taph: tap backend specific header
- * @iph: Pointer to IPv4 header
- * @th: Pointer to TCP header
- * @dlen: TCP payload length
- * @check: Checksum, if already known
- * @seq: Sequence number for this segment
- *
- * Return: The IPv4 payload length, host order
+ * tcp_fill_headers() - Fill 802.3, IP, TCP headers
+ * @conn: Connection pointer
+ * @taph: tap backend specific header
+ * @ip4h: Pointer to IPv4 header, or NULL
+ * @ip6h: Pointer to IPv6 header, or NULL
+ * @th: Pointer to TCP header
+ * @payload: TCP payload
+ * @ip4_check: IPv4 checksum, if already known
+ * @seq: Sequence number for this segment
+ * @no_tcp_csum: Do not set TCP checksum
*/
-static size_t tcp_fill_headers4(const struct tcp_tap_conn *conn,
- struct tap_hdr *taph,
- struct iphdr *iph, struct tcphdr *th,
- size_t dlen, const uint16_t *check,
- uint32_t seq)
+void tcp_fill_headers(const struct tcp_tap_conn *conn,
+ struct tap_hdr *taph,
+ struct iphdr *ip4h, struct ipv6hdr *ip6h,
+ struct tcphdr *th, struct iov_tail *payload,
+ const uint16_t *ip4_check, uint32_t seq, bool no_tcp_csum)
{
const struct flowside *tapside = TAPFLOW(conn);
- const struct in_addr *src4 = inany_v4(&tapside->faddr);
- const struct in_addr *dst4 = inany_v4(&tapside->eaddr);
- size_t l4len = dlen + sizeof(*th);
- size_t l3len = l4len + sizeof(*iph);
+ size_t l4len = iov_tail_size(payload) + sizeof(*th);
+ size_t l3len = l4len;
+ uint32_t psum = 0;
- ASSERT(src4 && dst4);
+ if (ip4h) {
+ const struct in_addr *src4 = inany_v4(&tapside->oaddr);
+ const struct in_addr *dst4 = inany_v4(&tapside->eaddr);
- iph->tot_len = htons(l3len);
- iph->saddr = src4->s_addr;
- iph->daddr = dst4->s_addr;
+ ASSERT(src4 && dst4);
- iph->check = check ? *check :
- csum_ip4_header(l3len, IPPROTO_TCP, *src4, *dst4);
+ l3len += + sizeof(*ip4h);
- tcp_fill_header(th, conn, seq);
+ ip4h->tot_len = htons(l3len);
+ ip4h->saddr = src4->s_addr;
+ ip4h->daddr = dst4->s_addr;
- tcp_update_check_tcp4(iph, th);
+ if (ip4_check)
+ ip4h->check = *ip4_check;
+ else
+ ip4h->check = csum_ip4_header(l3len, IPPROTO_TCP,
+ *src4, *dst4);
- tap_hdr_update(taph, l3len + sizeof(struct ethhdr));
+ if (!no_tcp_csum) {
+ psum = proto_ipv4_header_psum(l4len, IPPROTO_TCP,
+ *src4, *dst4);
+ }
+ }
- return l4len;
-}
+ if (ip6h) {
+ l3len += sizeof(*ip6h);
-/**
- * tcp_fill_headers6() - Fill 802.3, IPv6, TCP headers in pre-cooked buffers
- * @conn: Connection pointer
- * @taph: tap backend specific header
- * @ip6h: Pointer to IPv6 header
- * @th: Pointer to TCP header
- * @dlen: TCP payload length
- * @check: Checksum, if already known
- * @seq: Sequence number for this segment
- *
- * Return: The IPv6 payload length, host order
- */
-static size_t tcp_fill_headers6(const struct tcp_tap_conn *conn,
- struct tap_hdr *taph,
- struct ipv6hdr *ip6h, struct tcphdr *th,
- size_t dlen, uint32_t seq)
-{
- const struct flowside *tapside = TAPFLOW(conn);
- size_t l4len = dlen + sizeof(*th);
+ ip6h->payload_len = htons(l4len);
+ ip6h->saddr = tapside->oaddr.a6;
+ ip6h->daddr = tapside->eaddr.a6;
- ip6h->payload_len = htons(l4len);
- ip6h->saddr = tapside->faddr.a6;
- ip6h->daddr = tapside->eaddr.a6;
+ ip6h->hop_limit = 255;
+ ip6h->version = 6;
+ ip6h->nexthdr = IPPROTO_TCP;
- ip6h->hop_limit = 255;
- ip6h->version = 6;
- ip6h->nexthdr = IPPROTO_TCP;
+ ip6h->flow_lbl[0] = (conn->sock >> 16) & 0xf;
+ ip6h->flow_lbl[1] = (conn->sock >> 8) & 0xff;
+ ip6h->flow_lbl[2] = (conn->sock >> 0) & 0xff;
- ip6h->flow_lbl[0] = (conn->sock >> 16) & 0xf;
- ip6h->flow_lbl[1] = (conn->sock >> 8) & 0xff;
- ip6h->flow_lbl[2] = (conn->sock >> 0) & 0xff;
+ if (!no_tcp_csum) {
+ psum = proto_ipv6_header_psum(l4len, IPPROTO_TCP,
+ &ip6h->saddr,
+ &ip6h->daddr);
+ }
+ }
tcp_fill_header(th, conn, seq);
- tcp_update_check_tcp6(ip6h, th);
-
- tap_hdr_update(taph, l4len + sizeof(*ip6h) + sizeof(struct ethhdr));
-
- return l4len;
-}
-
-/**
- * tcp_l2_buf_fill_headers() - Fill 802.3, IP, TCP headers in pre-cooked buffers
- * @conn: Connection pointer
- * @iov: Pointer to an array of iovec of TCP pre-cooked buffers
- * @dlen: TCP payload length
- * @check: Checksum, if already known
- * @seq: Sequence number for this segment
- *
- * Return: IP payload length, host order
- */
-size_t tcp_l2_buf_fill_headers(const struct tcp_tap_conn *conn,
- struct iovec *iov, size_t dlen,
- const uint16_t *check, uint32_t seq)
-{
- const struct flowside *tapside = TAPFLOW(conn);
- const struct in_addr *a4 = inany_v4(&tapside->faddr);
-
- if (a4) {
- return tcp_fill_headers4(conn, iov[TCP_IOV_TAP].iov_base,
- iov[TCP_IOV_IP].iov_base,
- iov[TCP_IOV_PAYLOAD].iov_base, dlen,
- check, seq);
- }
+ if (no_tcp_csum)
+ th->check = 0;
+ else
+ tcp_update_csum(psum, th, payload);
- return tcp_fill_headers6(conn, iov[TCP_IOV_TAP].iov_base,
- iov[TCP_IOV_IP].iov_base,
- iov[TCP_IOV_PAYLOAD].iov_base, dlen,
- seq);
+ tap_hdr_update(taph, l3len + sizeof(struct ethhdr));
}
/**
@@ -1017,42 +965,41 @@ size_t tcp_l2_buf_fill_headers(const struct tcp_tap_conn *conn,
* Return: 1 if sequence or window were updated, 0 otherwise
*/
int tcp_update_seqack_wnd(const struct ctx *c, struct tcp_tap_conn *conn,
- int force_seq, struct tcp_info *tinfo)
+ bool force_seq, struct tcp_info_linux *tinfo)
{
uint32_t prev_wnd_to_tap = conn->wnd_to_tap << conn->ws_to_tap;
uint32_t prev_ack_to_tap = conn->seq_ack_to_tap;
/* cppcheck-suppress [ctunullpointer, unmatchedSuppression] */
socklen_t sl = sizeof(*tinfo);
- struct tcp_info tinfo_new;
+ struct tcp_info_linux tinfo_new;
uint32_t new_wnd_to_tap = prev_wnd_to_tap;
int s = conn->sock;
-#ifndef HAS_BYTES_ACKED
- (void)force_seq;
-
- conn->seq_ack_to_tap = conn->seq_from_tap;
- if (SEQ_LT(conn->seq_ack_to_tap, prev_ack_to_tap))
- conn->seq_ack_to_tap = prev_ack_to_tap;
-#else
- if ((unsigned)SNDBUF_GET(conn) < SNDBUF_SMALL || tcp_rtt_dst_low(conn)
- || CONN_IS_CLOSING(conn) || (conn->flags & LOCAL) || force_seq) {
+ if (!bytes_acked_cap) {
conn->seq_ack_to_tap = conn->seq_from_tap;
- } else if (conn->seq_ack_to_tap != conn->seq_from_tap) {
- if (!tinfo) {
- tinfo = &tinfo_new;
- if (getsockopt(s, SOL_TCP, TCP_INFO, tinfo, &sl))
- return 0;
- }
-
- conn->seq_ack_to_tap = tinfo->tcpi_bytes_acked +
- conn->seq_init_from_tap;
-
if (SEQ_LT(conn->seq_ack_to_tap, prev_ack_to_tap))
conn->seq_ack_to_tap = prev_ack_to_tap;
+ } else {
+ if ((unsigned)SNDBUF_GET(conn) < SNDBUF_SMALL ||
+ tcp_rtt_dst_low(conn) || CONN_IS_CLOSING(conn) ||
+ (conn->flags & LOCAL) || force_seq) {
+ conn->seq_ack_to_tap = conn->seq_from_tap;
+ } else if (conn->seq_ack_to_tap != conn->seq_from_tap) {
+ if (!tinfo) {
+ tinfo = &tinfo_new;
+ if (getsockopt(s, SOL_TCP, TCP_INFO, tinfo, &sl))
+ return 0;
+ }
+
+ conn->seq_ack_to_tap = tinfo->tcpi_bytes_acked +
+ conn->seq_init_from_tap;
+
+ if (SEQ_LT(conn->seq_ack_to_tap, prev_ack_to_tap))
+ conn->seq_ack_to_tap = prev_ack_to_tap;
+ }
}
-#endif /* !HAS_BYTES_ACKED */
- if (!KERNEL_REPORTS_SND_WND(c)) {
+ if (!snd_wnd_cap) {
tcp_get_sndbuf(conn);
new_wnd_to_tap = MIN(SNDBUF_GET(conn), MAX_WINDOW);
conn->wnd_to_tap = MIN(new_wnd_to_tap >> conn->ws_to_tap,
@@ -1063,14 +1010,13 @@ int tcp_update_seqack_wnd(const struct ctx *c, struct tcp_tap_conn *conn,
if (!tinfo) {
if (prev_wnd_to_tap > WINDOW_DEFAULT) {
goto out;
-}
+ }
tinfo = &tinfo_new;
if (getsockopt(s, SOL_TCP, TCP_INFO, tinfo, &sl)) {
goto out;
-}
+ }
}
-#ifdef HAS_SND_WND
if ((conn->flags & LOCAL) || tcp_rtt_dst_low(conn)) {
new_wnd_to_tap = tinfo->tcpi_snd_wnd;
} else {
@@ -1078,7 +1024,6 @@ int tcp_update_seqack_wnd(const struct ctx *c, struct tcp_tap_conn *conn,
new_wnd_to_tap = MIN((int)tinfo->tcpi_snd_wnd,
SNDBUF_GET(conn));
}
-#endif
new_wnd_to_tap = MIN(new_wnd_to_tap, MAX_WINDOW);
if (!(conn->events & ESTABLISHED))
@@ -1136,44 +1081,35 @@ static void tcp_update_seqack_from_tap(const struct ctx *c,
* 0 if there is no flag to send
* 1 otherwise
*/
-int tcp_prepare_flags(struct ctx *c, struct tcp_tap_conn *conn,
- int flags, struct tcphdr *th, char *data,
+int tcp_prepare_flags(const struct ctx *c, struct tcp_tap_conn *conn,
+ int flags, struct tcphdr *th, struct tcp_syn_opts *opts,
size_t *optlen)
{
- struct tcp_info tinfo = { 0 };
+ struct tcp_info_linux tinfo = { 0 };
socklen_t sl = sizeof(tinfo);
int s = conn->sock;
if (SEQ_GE(conn->seq_ack_to_tap, conn->seq_from_tap) &&
- !flags && conn->wnd_to_tap)
+ !flags && conn->wnd_to_tap) {
+ conn_flag(c, conn, ~ACK_TO_TAP_DUE);
return 0;
+ }
if (getsockopt(s, SOL_TCP, TCP_INFO, &tinfo, &sl)) {
conn_event(c, conn, CLOSED);
return -ECONNRESET;
}
-#ifdef HAS_SND_WND
- if (!c->tcp.kernel_snd_wnd && tinfo.tcpi_snd_wnd)
- c->tcp.kernel_snd_wnd = 1;
-#endif
-
if (!(conn->flags & LOCAL))
tcp_rtt_dst_check(conn, &tinfo);
- if (!tcp_update_seqack_wnd(c, conn, flags, &tinfo) && !flags)
+ if (!tcp_update_seqack_wnd(c, conn, !!flags, &tinfo) && !flags)
return 0;
*optlen = 0;
if (flags & SYN) {
int mss;
- /* Options: MSS, NOP and window scale (8 bytes) */
- *optlen = OPT_MSS_LEN + 1 + OPT_WS_LEN;
-
- *data++ = OPT_MSS;
- *data++ = OPT_MSS_LEN;
-
if (c->mtu == -1) {
mss = tinfo.tcpi_snd_mss;
} else {
@@ -1189,16 +1125,11 @@ int tcp_prepare_flags(struct ctx *c, struct tcp_tap_conn *conn,
else if (mss > PAGE_SIZE)
mss = ROUND_DOWN(mss, PAGE_SIZE);
}
- *(uint16_t *)data = htons(MIN(USHRT_MAX, mss));
-
- data += OPT_MSS_LEN - 2;
conn->ws_to_tap = MIN(MAX_WS, tinfo.tcpi_snd_wscale);
- *data++ = OPT_NOP;
- *data++ = OPT_WS;
- *data++ = OPT_WS_LEN;
- *data++ = conn->ws_to_tap;
+ *opts = TCP_SYN_OPTS(mss, conn->ws_to_tap);
+ *optlen = sizeof(*opts);
} else if (!(flags & RST)) {
flags |= ACK;
}
@@ -1235,8 +1166,12 @@ int tcp_prepare_flags(struct ctx *c, struct tcp_tap_conn *conn,
*
* Return: negative error code on connection reset, 0 otherwise
*/
-int tcp_send_flag(struct ctx *c, struct tcp_tap_conn *conn, int flags)
+static int tcp_send_flag(const struct ctx *c, struct tcp_tap_conn *conn,
+ int flags)
{
+ if (c->mode == MODE_VU)
+ return tcp_vu_send_flag(c, conn, flags);
+
return tcp_buf_send_flag(c, conn, flags);
}
@@ -1245,7 +1180,7 @@ int tcp_send_flag(struct ctx *c, struct tcp_tap_conn *conn, int flags)
* @c: Execution context
* @conn: Connection pointer
*/
-void tcp_rst_do(struct ctx *c, struct tcp_tap_conn *conn)
+void tcp_rst_do(const struct ctx *c, struct tcp_tap_conn *conn)
{
if (conn->events == CLOSED)
return;
@@ -1335,7 +1270,7 @@ static int tcp_conn_new_sock(const struct ctx *c, sa_family_t af)
{
int s;
- s = socket(af, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
+ s = socket(af, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (s > FD_REF_MAX) {
close(s);
@@ -1372,7 +1307,7 @@ int tcp_conn_sock(const struct ctx *c, sa_family_t af)
return s;
err("TCP: Unable to open socket for new connection: %s",
- strerror(-s));
+ strerror_(-s));
return -1;
}
@@ -1417,15 +1352,15 @@ static void tcp_bind_outbound(const struct ctx *c,
socklen_t sl;
- pif_sockaddr(c, &bind_sa, &sl, PIF_HOST, &tgt->faddr, tgt->fport);
- if (!inany_is_unspecified(&tgt->faddr) || tgt->fport) {
+ pif_sockaddr(c, &bind_sa, &sl, PIF_HOST, &tgt->oaddr, tgt->oport);
+ if (!inany_is_unspecified(&tgt->oaddr) || tgt->oport) {
if (bind(s, &bind_sa.sa, sl)) {
char sstr[INANY_ADDRSTRLEN];
flow_dbg(conn,
"Can't bind TCP outbound socket to %s:%hu: %s",
- inany_ntop(&tgt->faddr, sstr, sizeof(sstr)),
- tgt->fport, strerror(errno));
+ inany_ntop(&tgt->oaddr, sstr, sizeof(sstr)),
+ tgt->oport, strerror_(errno));
}
}
@@ -1436,7 +1371,7 @@ static void tcp_bind_outbound(const struct ctx *c,
strlen(c->ip4.ifname_out))) {
flow_dbg(conn, "Can't bind IPv4 TCP socket to"
" interface %s: %s", c->ip4.ifname_out,
- strerror(errno));
+ strerror_(errno));
}
}
} else if (bind_sa.sa_family == AF_INET6) {
@@ -1446,7 +1381,7 @@ static void tcp_bind_outbound(const struct ctx *c,
strlen(c->ip6.ifname_out))) {
flow_dbg(conn, "Can't bind IPv6 TCP socket to"
" interface %s: %s", c->ip6.ifname_out,
- strerror(errno));
+ strerror_(errno));
}
}
}
@@ -1463,7 +1398,7 @@ static void tcp_bind_outbound(const struct ctx *c,
* @optlen: Bytes in options: caller MUST ensure available length
* @now: Current timestamp
*/
-static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
+static void tcp_conn_from_tap(const struct ctx *c, sa_family_t af,
const void *saddr, const void *daddr,
const struct tcphdr *th, const char *opts,
size_t optlen, const struct timespec *now)
@@ -1497,12 +1432,12 @@ static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
conn = FLOW_SET_TYPE(flow, FLOW_TCP, tcp);
if (!inany_is_unicast(&ini->eaddr) || ini->eport == 0 ||
- !inany_is_unicast(&ini->faddr) || ini->fport == 0) {
+ !inany_is_unicast(&ini->oaddr) || ini->oport == 0) {
char sstr[INANY_ADDRSTRLEN], dstr[INANY_ADDRSTRLEN];
debug("Invalid endpoint in TCP SYN: %s:%hu -> %s:%hu",
inany_ntop(&ini->eaddr, sstr, sizeof(sstr)), ini->eport,
- inany_ntop(&ini->faddr, dstr, sizeof(dstr)), ini->fport);
+ inany_ntop(&ini->oaddr, dstr, sizeof(dstr)), ini->oport);
goto cancel;
}
@@ -1628,8 +1563,11 @@ static int tcp_sock_consume(const struct tcp_tap_conn *conn, uint32_t ack_seq)
*
* #syscalls recvmsg
*/
-static int tcp_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
+static int tcp_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
{
+ if (c->mode == MODE_VU)
+ return tcp_vu_data_from_sock(c, conn);
+
return tcp_buf_data_from_sock(c, conn);
}
@@ -1644,8 +1582,8 @@ static int tcp_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
*
* Return: count of consumed packets
*/
-static int tcp_data_from_tap(struct ctx *c, struct tcp_tap_conn *conn,
- const struct pool *p, int idx)
+static int tcp_data_from_tap(const struct ctx *c, struct tcp_tap_conn *conn,
+ const struct pool *p, int idx)
{
int i, iov_i, ack = 0, fin = 0, retr = 0, keep = -1, partial_send = 0;
uint16_t max_ack_seq_wnd = conn->wnd_from_tap;
@@ -1686,6 +1624,20 @@ static int tcp_data_from_tap(struct ctx *c, struct tcp_tap_conn *conn,
continue;
seq = ntohl(th->seq);
+ if (SEQ_LT(seq, conn->seq_from_tap) && len <= 1) {
+ flow_trace(conn,
+ "keep-alive sequence: %u, previous: %u",
+ seq, conn->seq_from_tap);
+
+ tcp_send_flag(c, conn, ACK);
+ tcp_timer_ctl(c, conn);
+
+ if (p->count == 1)
+ return 1;
+
+ continue;
+ }
+
ack_seq = ntohl(th->ack_seq);
if (th->ack) {
@@ -1842,7 +1794,8 @@ out:
* @opts: Pointer to start of options
* @optlen: Bytes in options: caller MUST ensure available length
*/
-static void tcp_conn_from_sock_finish(struct ctx *c, struct tcp_tap_conn *conn,
+static void tcp_conn_from_sock_finish(const struct ctx *c,
+ struct tcp_tap_conn *conn,
const struct tcphdr *th,
const char *opts, size_t optlen)
{
@@ -1865,11 +1818,12 @@ static void tcp_conn_from_sock_finish(struct ctx *c, struct tcp_tap_conn *conn,
return;
}
+ tcp_send_flag(c, conn, ACK);
+
/* The client might have sent data already, which we didn't
* dequeue waiting for SYN,ACK from tap -- check now.
*/
tcp_data_from_sock(c, conn);
- tcp_send_flag(c, conn, ACK);
}
/**
@@ -1885,7 +1839,7 @@ static void tcp_conn_from_sock_finish(struct ctx *c, struct tcp_tap_conn *conn,
*
* Return: count of consumed packets
*/
-int tcp_tap_handler(struct ctx *c, uint8_t pif, sa_family_t af,
+int tcp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
const void *saddr, const void *daddr,
const struct pool *p, int idx, const struct timespec *now)
{
@@ -2023,7 +1977,7 @@ reset:
* @c: Execution context
* @conn: Connection pointer
*/
-static void tcp_connect_finish(struct ctx *c, struct tcp_tap_conn *conn)
+static void tcp_connect_finish(const struct ctx *c, struct tcp_tap_conn *conn)
{
socklen_t sl;
int so;
@@ -2049,8 +2003,8 @@ static void tcp_connect_finish(struct ctx *c, struct tcp_tap_conn *conn)
* @sa: Peer socket address (from accept())
* @now: Current timestamp
*/
-static void tcp_tap_conn_from_sock(struct ctx *c, union flow *flow, int s,
- const struct timespec *now)
+static void tcp_tap_conn_from_sock(const struct ctx *c, union flow *flow,
+ int s, const struct timespec *now)
{
struct tcp_tap_conn *conn = FLOW_SET_TYPE(flow, FLOW_TCP, tcp);
uint64_t hash;
@@ -2081,7 +2035,7 @@ static void tcp_tap_conn_from_sock(struct ctx *c, union flow *flow, int s,
* @ref: epoll reference of listening socket
* @now: Current timestamp
*/
-void tcp_listen_handler(struct ctx *c, union epoll_ref ref,
+void tcp_listen_handler(const struct ctx *c, union epoll_ref ref,
const struct timespec *now)
{
const struct flowside *ini;
@@ -2100,7 +2054,8 @@ void tcp_listen_handler(struct ctx *c, union epoll_ref ref,
goto cancel;
/* FIXME: When listening port has a specific bound address, record that
- * as the forwarding address */
+ * as our address
+ */
ini = flow_initiate_sa(flow, ref.tcp_listen.pif, &sa,
ref.tcp_listen.port);
@@ -2143,9 +2098,9 @@ cancel:
* @c: Execution context
* @ref: epoll reference of timer (not connection)
*
- * #syscalls timerfd_gettime
+ * #syscalls timerfd_gettime arm:timerfd_gettime64 i686:timerfd_gettime64
*/
-void tcp_timer_handler(struct ctx *c, union epoll_ref ref)
+void tcp_timer_handler(const struct ctx *c, union epoll_ref ref)
{
struct itimerspec check_armed = { { 0 }, { 0 } };
struct tcp_tap_conn *conn = &FLOW(ref.flow)->tcp;
@@ -2157,7 +2112,9 @@ void tcp_timer_handler(struct ctx *c, union epoll_ref ref)
* timer is currently armed, this event came from a previous setting,
* and we just set the timer to a new point in the future: discard it.
*/
- timerfd_gettime(conn->timer, &check_armed);
+ if (timerfd_gettime(conn->timer, &check_armed))
+ flow_err(conn, "failed to read timer: %s", strerror_(errno));
+
if (check_armed.it_value.tv_sec || check_armed.it_value.tv_nsec)
return;
@@ -2195,7 +2152,10 @@ void tcp_timer_handler(struct ctx *c, union epoll_ref ref)
* case. This avoids having to preemptively reset the timer on
* ~ACK_TO_TAP_DUE or ~ACK_FROM_TAP_DUE.
*/
- timerfd_settime(conn->timer, 0, &new, &old);
+ if (timerfd_settime(conn->timer, 0, &new, &old))
+ flow_err(conn, "failed to set timer: %s",
+ strerror_(errno));
+
if (old.it_value.tv_sec == ACT_TIMEOUT) {
flow_dbg(conn, "activity timeout");
tcp_rst(c, conn);
@@ -2209,7 +2169,8 @@ void tcp_timer_handler(struct ctx *c, union epoll_ref ref)
* @ref: epoll reference
* @events: epoll events bitmap
*/
-void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events)
+void tcp_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events)
{
struct tcp_tap_conn *conn = conn_at_sidx(ref.flowside);
@@ -2240,7 +2201,7 @@ void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events)
tcp_data_from_sock(c, conn);
if (events & EPOLLOUT)
- tcp_update_seqack_wnd(c, conn, 0, NULL);
+ tcp_update_seqack_wnd(c, conn, false, NULL);
return;
}
@@ -2263,17 +2224,16 @@ void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events)
}
/**
- * tcp_sock_init_af() - Initialise listening socket for a given af and port
+ * tcp_sock_init_one() - Initialise listening socket for address and port
* @c: Execution context
- * @af: Address family to listen on
- * @port: Port, host order
- * @addr: Pointer to address for binding, NULL if not configured
+ * @addr: Pointer to address for binding, NULL for dual stack any
* @ifname: Name of interface to bind to, NULL if not configured
+ * @port: Port, host order
*
* Return: fd for the new listening socket, negative error code on failure
*/
-static int tcp_sock_init_af(const struct ctx *c, sa_family_t af, in_port_t port,
- const void *addr, const char *ifname)
+static int tcp_sock_init_one(const struct ctx *c, const union inany_addr *addr,
+ const char *ifname, in_port_t port)
{
union tcp_listen_epoll_ref tref = {
.port = port,
@@ -2281,12 +2241,13 @@ static int tcp_sock_init_af(const struct ctx *c, sa_family_t af, in_port_t port,
};
int s;
- s = sock_l4(c, af, EPOLL_TYPE_TCP_LISTEN, addr, ifname, port, tref.u32);
+ s = pif_sock_l4(c, EPOLL_TYPE_TCP_LISTEN, PIF_HOST, addr,
+ ifname, port, tref.u32);
if (c->tcp.fwd_in.mode == FWD_AUTO) {
- if (af == AF_INET || af == AF_UNSPEC)
+ if (!addr || inany_v4(addr))
tcp_sock_init_ext[port][V4] = s < 0 ? -1 : s;
- if (af == AF_INET6 || af == AF_UNSPEC)
+ if (!addr || !inany_v4(addr))
tcp_sock_init_ext[port][V6] = s < 0 ? -1 : s;
}
@@ -2300,31 +2261,32 @@ static int tcp_sock_init_af(const struct ctx *c, sa_family_t af, in_port_t port,
/**
* tcp_sock_init() - Create listening sockets for a given host ("inbound") port
* @c: Execution context
- * @af: Address family to select a specific IP version, or AF_UNSPEC
* @addr: Pointer to address for binding, NULL if not configured
* @ifname: Name of interface to bind to, NULL if not configured
* @port: Port, host order
*
* Return: 0 on (partial) success, negative error code on (complete) failure
*/
-int tcp_sock_init(const struct ctx *c, sa_family_t af, const void *addr,
+int tcp_sock_init(const struct ctx *c, const union inany_addr *addr,
const char *ifname, in_port_t port)
{
int r4 = FD_REF_MAX + 1, r6 = FD_REF_MAX + 1;
ASSERT(!c->no_tcp);
- if (af == AF_UNSPEC && c->ifi4 && c->ifi6)
+ if (!addr && c->ifi4 && c->ifi6)
/* Attempt to get a dual stack socket */
- if (tcp_sock_init_af(c, AF_UNSPEC, port, addr, ifname) >= 0)
+ if (tcp_sock_init_one(c, NULL, ifname, port) >= 0)
return 0;
/* Otherwise create a socket per IP version */
- if ((af == AF_INET || af == AF_UNSPEC) && c->ifi4)
- r4 = tcp_sock_init_af(c, AF_INET, port, addr, ifname);
+ if ((!addr || inany_v4(addr)) && c->ifi4)
+ r4 = tcp_sock_init_one(c, addr ? addr : &inany_any4,
+ ifname, port);
- if ((af == AF_INET6 || af == AF_UNSPEC) && c->ifi6)
- r6 = tcp_sock_init_af(c, AF_INET6, port, addr, ifname);
+ if ((!addr || !inany_v4(addr)) && c->ifi6)
+ r6 = tcp_sock_init_one(c, addr ? addr : &inany_any6,
+ ifname, port);
if (IN_INTERVAL(0, FD_REF_MAX, r4) || IN_INTERVAL(0, FD_REF_MAX, r6))
return 0;
@@ -2347,8 +2309,8 @@ static void tcp_ns_sock_init4(const struct ctx *c, in_port_t port)
ASSERT(c->mode == MODE_PASTA);
- s = sock_l4(c, AF_INET, EPOLL_TYPE_TCP_LISTEN, &in4addr_loopback,
- NULL, port, tref.u32);
+ s = pif_sock_l4(c, EPOLL_TYPE_TCP_LISTEN, PIF_SPLICE, &inany_loopback4,
+ NULL, port, tref.u32);
if (s >= 0)
tcp_sock_set_bufsize(c, s);
else
@@ -2373,8 +2335,8 @@ static void tcp_ns_sock_init6(const struct ctx *c, in_port_t port)
ASSERT(c->mode == MODE_PASTA);
- s = sock_l4(c, AF_INET6, EPOLL_TYPE_TCP_LISTEN, &in6addr_loopback,
- NULL, port, tref.u32);
+ s = pif_sock_l4(c, EPOLL_TYPE_TCP_LISTEN, PIF_SPLICE, &inany_loopback6,
+ NULL, port, tref.u32);
if (s >= 0)
tcp_sock_set_bufsize(c, s);
else
@@ -2460,13 +2422,13 @@ static void tcp_sock_refill_init(const struct ctx *c)
int rc = tcp_sock_refill_pool(c, init_sock_pool4, AF_INET);
if (rc < 0)
warn("TCP: Error refilling IPv4 host socket pool: %s",
- strerror(-rc));
+ strerror_(-rc));
}
if (c->ifi6) {
int rc = tcp_sock_refill_pool(c, init_sock_pool6, AF_INET6);
if (rc < 0)
warn("TCP: Error refilling IPv6 host socket pool: %s",
- strerror(-rc));
+ strerror_(-rc));
}
}
@@ -2476,7 +2438,7 @@ static void tcp_sock_refill_init(const struct ctx *c)
*
* Return: true if supported, false otherwise
*/
-bool tcp_probe_peek_offset_cap(sa_family_t af)
+static bool tcp_probe_peek_offset_cap(sa_family_t af)
{
bool ret = false;
int s, optv = 0;
@@ -2494,6 +2456,34 @@ bool tcp_probe_peek_offset_cap(sa_family_t af)
}
/**
+ * tcp_probe_tcp_info() - Check what data TCP_INFO reports
+ *
+ * Return: Number of bytes returned by TCP_INFO getsockopt()
+ */
+static socklen_t tcp_probe_tcp_info(void)
+{
+ struct tcp_info_linux tinfo;
+ socklen_t sl = sizeof(tinfo);
+ int s;
+
+ s = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
+ if (s < 0) {
+ warn_perror("Temporary TCP socket creation failed");
+ return false;
+ }
+
+ if (getsockopt(s, SOL_TCP, TCP_INFO, &tinfo, &sl)) {
+ warn_perror("Failed to get TCP_INFO on temporary socket");
+ close(s);
+ return false;
+ }
+
+ close(s);
+
+ return sl;
+}
+
+/**
* tcp_init() - Get initial sequence, hash secret, initialise per-socket data
* @c: Execution context
*
@@ -2503,11 +2493,7 @@ int tcp_init(struct ctx *c)
{
ASSERT(!c->no_tcp);
- if (c->ifi4)
- tcp_sock4_iov_init(c);
-
- if (c->ifi6)
- tcp_sock6_iov_init(c);
+ tcp_sock_iov_init(c);
memset(init_sock_pool4, 0xff, sizeof(init_sock_pool4));
memset(init_sock_pool6, 0xff, sizeof(init_sock_pool6));
@@ -2526,6 +2512,15 @@ int tcp_init(struct ctx *c)
(!c->ifi6 || tcp_probe_peek_offset_cap(AF_INET6));
debug("SO_PEEK_OFF%ssupported", peek_offset_cap ? " " : " not ");
+ tcp_info_size = tcp_probe_tcp_info();
+
+#define dbg_tcpi(f_) debug("TCP_INFO tcpi_%s field%s supported", \
+ STRINGIFY(f_), tcp_info_cap(f_) ? " " : " not ")
+ dbg_tcpi(snd_wnd);
+ dbg_tcpi(bytes_acked);
+ dbg_tcpi(min_rtt);
+#undef dbg_tcpi
+
return 0;
}
@@ -2567,7 +2562,7 @@ static void tcp_port_rebind(struct ctx *c, bool outbound)
if (outbound)
tcp_ns_sock_init(c, port);
else
- tcp_sock_init(c, AF_UNSPEC, NULL, NULL, port);
+ tcp_sock_init(c, NULL, NULL, port);
}
}
}
diff --git a/tcp.h b/tcp.h
index e9ff019..cf30744 100644
--- a/tcp.h
+++ b/tcp.h
@@ -10,14 +10,15 @@
struct ctx;
-void tcp_timer_handler(struct ctx *c, union epoll_ref ref);
-void tcp_listen_handler(struct ctx *c, union epoll_ref ref,
+void tcp_timer_handler(const struct ctx *c, union epoll_ref ref);
+void tcp_listen_handler(const struct ctx *c, union epoll_ref ref,
const struct timespec *now);
-void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events);
-int tcp_tap_handler(struct ctx *c, uint8_t pif, sa_family_t af,
+void tcp_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events);
+int tcp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
const void *saddr, const void *daddr,
const struct pool *p, int idx, const struct timespec *now);
-int tcp_sock_init(const struct ctx *c, sa_family_t af, const void *addr,
+int tcp_sock_init(const struct ctx *c, const union inany_addr *addr,
const char *ifname, in_port_t port);
int tcp_init(struct ctx *c);
void tcp_timer(struct ctx *c, const struct timespec *now);
@@ -58,16 +59,12 @@ union tcp_listen_epoll_ref {
* @fwd_in: Port forwarding configuration for inbound packets
* @fwd_out: Port forwarding configuration for outbound packets
* @timer_run: Timestamp of most recent timer run
- * @kernel_snd_wnd: Kernel reports sending window (with commit 8f7baad7f035)
* @pipe_size: Size of pipes for spliced connections
*/
struct tcp_ctx {
struct fwd_ports fwd_in;
struct fwd_ports fwd_out;
struct timespec timer_run;
-#ifdef HAS_SND_WND
- int kernel_snd_wnd;
-#endif
size_t pipe_size;
};
diff --git a/tcp_buf.c b/tcp_buf.c
index c31e9f3..a975a55 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -20,7 +20,7 @@
#include <netinet/ip.h>
-#include <linux/tcp.h>
+#include <netinet/tcp.h>
#include "util.h"
#include "ip.h"
@@ -38,88 +38,32 @@
(c->mode == MODE_PASTA ? 1 : TCP_FRAMES_MEM)
/* Static buffers */
-/**
- * struct tcp_payload_t - TCP header and data to send segments with payload
- * @th: TCP header
- * @data: TCP data
- */
-struct tcp_payload_t {
- struct tcphdr th;
- uint8_t data[IP_MAX_MTU - sizeof(struct tcphdr)];
-#ifdef __AVX2__
-} __attribute__ ((packed, aligned(32))); /* For AVX2 checksum routines */
-#else
-} __attribute__ ((packed, aligned(__alignof__(unsigned int))));
-#endif
-/**
- * struct tcp_flags_t - TCP header and data to send zero-length
- * segments (flags)
- * @th: TCP header
- * @opts TCP options
- */
-struct tcp_flags_t {
- struct tcphdr th;
- char opts[OPT_MSS_LEN + OPT_WS_LEN + 1];
-#ifdef __AVX2__
-} __attribute__ ((packed, aligned(32)));
-#else
-} __attribute__ ((packed, aligned(__alignof__(unsigned int))));
-#endif
-
-/* Ethernet header for IPv4 frames */
+/* Ethernet header for IPv4 and IPv6 frames */
static struct ethhdr tcp4_eth_src;
-
-static struct tap_hdr tcp4_payload_tap_hdr[TCP_FRAMES_MEM];
-/* IPv4 headers */
-static struct iphdr tcp4_payload_ip[TCP_FRAMES_MEM];
-/* TCP segments with payload for IPv4 frames */
-static struct tcp_payload_t tcp4_payload[TCP_FRAMES_MEM];
-
-static_assert(MSS4 <= sizeof(tcp4_payload[0].data), "MSS4 is greater than 65516");
-
-/* References tracking the owner connection of frames in the tap outqueue */
-static struct tcp_tap_conn *tcp4_frame_conns[TCP_FRAMES_MEM];
-static unsigned int tcp4_payload_used;
-
-static struct tap_hdr tcp4_flags_tap_hdr[TCP_FRAMES_MEM];
-/* IPv4 headers for TCP segment without payload */
-static struct iphdr tcp4_flags_ip[TCP_FRAMES_MEM];
-/* TCP segments without payload for IPv4 frames */
-static struct tcp_flags_t tcp4_flags[TCP_FRAMES_MEM];
-
-static unsigned int tcp4_flags_used;
-
-/* Ethernet header for IPv6 frames */
static struct ethhdr tcp6_eth_src;
-static struct tap_hdr tcp6_payload_tap_hdr[TCP_FRAMES_MEM];
-/* IPv6 headers */
-static struct ipv6hdr tcp6_payload_ip[TCP_FRAMES_MEM];
-/* TCP headers and data for IPv6 frames */
-static struct tcp_payload_t tcp6_payload[TCP_FRAMES_MEM];
+static struct tap_hdr tcp_payload_tap_hdr[TCP_FRAMES_MEM];
-static_assert(MSS6 <= sizeof(tcp6_payload[0].data), "MSS6 is greater than 65516");
+/* IP headers for IPv4 and IPv6 */
+struct iphdr tcp4_payload_ip[TCP_FRAMES_MEM];
+struct ipv6hdr tcp6_payload_ip[TCP_FRAMES_MEM];
-/* References tracking the owner connection of frames in the tap outqueue */
-static struct tcp_tap_conn *tcp6_frame_conns[TCP_FRAMES_MEM];
-static unsigned int tcp6_payload_used;
+/* TCP segments with payload for IPv4 and IPv6 frames */
+static struct tcp_payload_t tcp_payload[TCP_FRAMES_MEM];
-static struct tap_hdr tcp6_flags_tap_hdr[TCP_FRAMES_MEM];
-/* IPv6 headers for TCP segment without payload */
-static struct ipv6hdr tcp6_flags_ip[TCP_FRAMES_MEM];
-/* TCP segment without payload for IPv6 frames */
-static struct tcp_flags_t tcp6_flags[TCP_FRAMES_MEM];
+static_assert(MSS4 <= sizeof(tcp_payload[0].data), "MSS4 is greater than 65516");
+static_assert(MSS6 <= sizeof(tcp_payload[0].data), "MSS6 is greater than 65516");
-static unsigned int tcp6_flags_used;
+/* References tracking the owner connection of frames in the tap outqueue */
+static struct tcp_tap_conn *tcp_frame_conns[TCP_FRAMES_MEM];
+static unsigned int tcp_payload_used;
/* recvmsg()/sendmsg() data for tap */
static struct iovec iov_sock [TCP_FRAMES_MEM + 1];
-static struct iovec tcp4_l2_iov [TCP_FRAMES_MEM][TCP_NUM_IOVS];
-static struct iovec tcp6_l2_iov [TCP_FRAMES_MEM][TCP_NUM_IOVS];
-static struct iovec tcp4_l2_flags_iov [TCP_FRAMES_MEM][TCP_NUM_IOVS];
-static struct iovec tcp6_l2_flags_iov [TCP_FRAMES_MEM][TCP_NUM_IOVS];
+static struct iovec tcp_l2_iov[TCP_FRAMES_MEM][TCP_NUM_IOVS];
+
/**
* tcp_update_l2_buf() - Update Ethernet header buffers with addresses
* @eth_d: Ethernet destination address, NULL if unchanged
@@ -132,115 +76,40 @@ void tcp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s)
}
/**
- * tcp_sock4_iov_init() - Initialise scatter-gather L2 buffers for IPv4 sockets
+ * tcp_sock_iov_init() - Initialise scatter-gather L2 buffers for IPv4 sockets
* @c: Execution context
*/
-void tcp_sock4_iov_init(const struct ctx *c)
-{
- struct iphdr iph = L2_BUF_IP4_INIT(IPPROTO_TCP);
- struct iovec *iov;
- int i;
-
- tcp4_eth_src.h_proto = htons_constant(ETH_P_IP);
-
- for (i = 0; i < ARRAY_SIZE(tcp4_payload); i++) {
- tcp4_payload_ip[i] = iph;
- tcp4_payload[i].th.doff = sizeof(struct tcphdr) / 4;
- tcp4_payload[i].th.ack = 1;
- }
-
- for (i = 0; i < ARRAY_SIZE(tcp4_flags); i++) {
- tcp4_flags_ip[i] = iph;
- tcp4_flags[i].th.doff = sizeof(struct tcphdr) / 4;
- tcp4_flags[i].th.ack = 1;
- }
-
- for (i = 0; i < TCP_FRAMES_MEM; i++) {
- iov = tcp4_l2_iov[i];
-
- iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp4_payload_tap_hdr[i]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp4_eth_src);
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[i]);
- iov[TCP_IOV_PAYLOAD].iov_base = &tcp4_payload[i];
- }
-
- for (i = 0; i < TCP_FRAMES_MEM; i++) {
- iov = tcp4_l2_flags_iov[i];
-
- iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp4_flags_tap_hdr[i]);
- iov[TCP_IOV_ETH].iov_base = &tcp4_eth_src;
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp4_eth_src);
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_flags_ip[i]);
- iov[TCP_IOV_PAYLOAD].iov_base = &tcp4_flags[i];
- }
-}
-
-/**
- * tcp_sock6_iov_init() - Initialise scatter-gather L2 buffers for IPv6 sockets
- * @c: Execution context
- */
-void tcp_sock6_iov_init(const struct ctx *c)
+void tcp_sock_iov_init(const struct ctx *c)
{
struct ipv6hdr ip6 = L2_BUF_IP6_INIT(IPPROTO_TCP);
- struct iovec *iov;
+ struct iphdr iph = L2_BUF_IP4_INIT(IPPROTO_TCP);
int i;
tcp6_eth_src.h_proto = htons_constant(ETH_P_IPV6);
+ tcp4_eth_src.h_proto = htons_constant(ETH_P_IP);
- for (i = 0; i < ARRAY_SIZE(tcp6_payload); i++) {
+ for (i = 0; i < ARRAY_SIZE(tcp_payload); i++) {
tcp6_payload_ip[i] = ip6;
- tcp6_payload[i].th.doff = sizeof(struct tcphdr) / 4;
- tcp6_payload[i].th.ack = 1;
- }
-
- for (i = 0; i < ARRAY_SIZE(tcp6_flags); i++) {
- tcp6_flags_ip[i] = ip6;
- tcp6_flags[i].th.doff = sizeof(struct tcphdr) / 4;
- tcp6_flags[i].th .ack = 1;
- }
-
- for (i = 0; i < TCP_FRAMES_MEM; i++) {
- iov = tcp6_l2_iov[i];
-
- iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp6_payload_tap_hdr[i]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp6_eth_src);
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[i]);
- iov[TCP_IOV_PAYLOAD].iov_base = &tcp6_payload[i];
+ tcp4_payload_ip[i] = iph;
}
for (i = 0; i < TCP_FRAMES_MEM; i++) {
- iov = tcp6_l2_flags_iov[i];
+ struct iovec *iov = tcp_l2_iov[i];
- iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp6_flags_tap_hdr[i]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp6_eth_src);
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_flags_ip[i]);
- iov[TCP_IOV_PAYLOAD].iov_base = &tcp6_flags[i];
+ iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp_payload_tap_hdr[i]);
+ iov[TCP_IOV_ETH].iov_len = sizeof(struct ethhdr);
+ iov[TCP_IOV_PAYLOAD].iov_base = &tcp_payload[i];
}
}
/**
- * tcp_flags_flush() - Send out buffers for segments with no data (flags)
- * @c: Execution context
- */
-void tcp_flags_flush(const struct ctx *c)
-{
- tap_send_frames(c, &tcp6_l2_flags_iov[0][0], TCP_NUM_IOVS,
- tcp6_flags_used);
- tcp6_flags_used = 0;
-
- tap_send_frames(c, &tcp4_l2_flags_iov[0][0], TCP_NUM_IOVS,
- tcp4_flags_used);
- tcp4_flags_used = 0;
-}
-
-/**
* tcp_revert_seq() - Revert affected conn->seq_to_tap after failed transmission
* @ctx: Execution context
* @conns: Array of connection pointers corresponding to queued frames
* @frames: Two-dimensional array containing queued frames with sub-iovs
* @num_frames: Number of entries in the two arrays to be compared
*/
-static void tcp_revert_seq(struct ctx *c, struct tcp_tap_conn **conns,
+static void tcp_revert_seq(const struct ctx *c, struct tcp_tap_conn **conns,
struct iovec (*frames)[TCP_NUM_IOVS], int num_frames)
{
int i;
@@ -262,28 +131,49 @@ static void tcp_revert_seq(struct ctx *c, struct tcp_tap_conn **conns,
}
/**
- * tcp_payload_flush() - Send out buffers for segments with data
+ * tcp_payload_flush() - Send out buffers for segments with data or flags
* @c: Execution context
*/
-void tcp_payload_flush(struct ctx *c)
+void tcp_payload_flush(const struct ctx *c)
{
size_t m;
- m = tap_send_frames(c, &tcp6_l2_iov[0][0], TCP_NUM_IOVS,
- tcp6_payload_used);
- if (m != tcp6_payload_used) {
- tcp_revert_seq(c, &tcp6_frame_conns[m], &tcp6_l2_iov[m],
- tcp6_payload_used - m);
+ m = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
+ tcp_payload_used);
+ if (m != tcp_payload_used) {
+ tcp_revert_seq(c, &tcp_frame_conns[m], &tcp_l2_iov[m],
+ tcp_payload_used - m);
}
- tcp6_payload_used = 0;
+ tcp_payload_used = 0;
+}
- m = tap_send_frames(c, &tcp4_l2_iov[0][0], TCP_NUM_IOVS,
- tcp4_payload_used);
- if (m != tcp4_payload_used) {
- tcp_revert_seq(c, &tcp4_frame_conns[m], &tcp4_l2_iov[m],
- tcp4_payload_used - m);
- }
- tcp4_payload_used = 0;
+/**
+ * tcp_buf_fill_headers() - Fill 802.3, IP, TCP headers in pre-cooked buffers
+ * @conn: Connection pointer
+ * @iov: Pointer to an array of iovec of TCP pre-cooked buffers
+ * @check: Checksum, if already known
+ * @seq: Sequence number for this segment
+ * @no_tcp_csum: Do not set TCP checksum
+ */
+static void tcp_l2_buf_fill_headers(const struct tcp_tap_conn *conn,
+ struct iovec *iov, const uint16_t *check,
+ uint32_t seq, bool no_tcp_csum)
+{
+ struct iov_tail tail = IOV_TAIL(&iov[TCP_IOV_PAYLOAD], 1, 0);
+ struct tcphdr *th = IOV_REMOVE_HEADER(&tail, struct tcphdr);
+ struct tap_hdr *taph = iov[TCP_IOV_TAP].iov_base;
+ const struct flowside *tapside = TAPFLOW(conn);
+ const struct in_addr *a4 = inany_v4(&tapside->oaddr);
+ struct ipv6hdr *ip6h = NULL;
+ struct iphdr *ip4h = NULL;
+
+ if (a4)
+ ip4h = iov[TCP_IOV_IP].iov_base;
+ else
+ ip6h = iov[TCP_IOV_IP].iov_base;
+
+ tcp_fill_headers(conn, taph, ip4h, ip6h, th, &tail,
+ check, seq, no_tcp_csum);
}
/**
@@ -294,58 +184,50 @@ void tcp_payload_flush(struct ctx *c)
*
* Return: negative error code on connection reset, 0 otherwise
*/
-int tcp_buf_send_flag(struct ctx *c, struct tcp_tap_conn *conn, int flags)
+int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
{
- struct tcp_flags_t *payload;
+ struct tcp_payload_t *payload;
struct iovec *iov;
size_t optlen;
size_t l4len;
uint32_t seq;
int ret;
- if (CONN_V4(conn))
- iov = tcp4_l2_flags_iov[tcp4_flags_used++];
- else
- iov = tcp6_l2_flags_iov[tcp6_flags_used++];
+ iov = tcp_l2_iov[tcp_payload_used];
+ if (CONN_V4(conn)) {
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_ETH].iov_base = &tcp4_eth_src;
+ } else {
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_ETH].iov_base = &tcp6_eth_src;
+ }
payload = iov[TCP_IOV_PAYLOAD].iov_base;
-
seq = conn->seq_to_tap;
ret = tcp_prepare_flags(c, conn, flags, &payload->th,
- payload->opts, &optlen);
- if (ret <= 0) {
- if (CONN_V4(conn))
- tcp4_flags_used--;
- else
- tcp6_flags_used--;
+ (struct tcp_syn_opts *)&payload->data, &optlen);
+ if (ret <= 0)
return ret;
- }
- l4len = tcp_l2_buf_fill_headers(conn, iov, optlen, NULL, seq);
+ tcp_payload_used++;
+ l4len = optlen + sizeof(struct tcphdr);
iov[TCP_IOV_PAYLOAD].iov_len = l4len;
+ tcp_l2_buf_fill_headers(conn, iov, NULL, seq, false);
if (flags & DUP_ACK) {
- struct iovec *dup_iov;
- int i;
-
- if (CONN_V4(conn))
- dup_iov = tcp4_l2_flags_iov[tcp4_flags_used++];
- else
- dup_iov = tcp6_l2_flags_iov[tcp6_flags_used++];
-
- for (i = 0; i < TCP_NUM_IOVS; i++)
- memcpy(dup_iov[i].iov_base, iov[i].iov_base,
- iov[i].iov_len);
- dup_iov[TCP_IOV_PAYLOAD].iov_len = iov[TCP_IOV_PAYLOAD].iov_len;
+ struct iovec *dup_iov = tcp_l2_iov[tcp_payload_used++];
+
+ memcpy(dup_iov[TCP_IOV_TAP].iov_base, iov[TCP_IOV_TAP].iov_base,
+ iov[TCP_IOV_TAP].iov_len);
+ dup_iov[TCP_IOV_ETH].iov_base = iov[TCP_IOV_ETH].iov_base;
+ dup_iov[TCP_IOV_IP] = iov[TCP_IOV_IP];
+ memcpy(dup_iov[TCP_IOV_PAYLOAD].iov_base,
+ iov[TCP_IOV_PAYLOAD].iov_base, l4len);
+ dup_iov[TCP_IOV_PAYLOAD].iov_len = l4len;
}
- if (CONN_V4(conn)) {
- if (tcp4_flags_used > TCP_FRAMES_MEM - 2)
- tcp_flags_flush(c);
- } else {
- if (tcp6_flags_used > TCP_FRAMES_MEM - 2)
- tcp_flags_flush(c);
- }
+ if (tcp_payload_used > TCP_FRAMES_MEM - 2)
+ tcp_payload_flush(c);
return 0;
}
@@ -358,39 +240,38 @@ int tcp_buf_send_flag(struct ctx *c, struct tcp_tap_conn *conn, int flags)
* @no_csum: Don't compute IPv4 checksum, use the one from previous buffer
* @seq: Sequence number to be sent
*/
-static void tcp_data_to_tap(struct ctx *c, struct tcp_tap_conn *conn,
+static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
ssize_t dlen, int no_csum, uint32_t seq)
{
+ struct tcp_payload_t *payload;
+ const uint16_t *check = NULL;
struct iovec *iov;
- size_t l4len;
conn->seq_to_tap = seq + dlen;
-
+ tcp_frame_conns[tcp_payload_used] = conn;
+ iov = tcp_l2_iov[tcp_payload_used];
if (CONN_V4(conn)) {
- struct iovec *iov_prev = tcp4_l2_iov[tcp4_payload_used - 1];
- const uint16_t *check = NULL;
-
if (no_csum) {
+ struct iovec *iov_prev = tcp_l2_iov[tcp_payload_used - 1];
struct iphdr *iph = iov_prev[TCP_IOV_IP].iov_base;
+
check = &iph->check;
}
-
- tcp4_frame_conns[tcp4_payload_used] = conn;
-
- iov = tcp4_l2_iov[tcp4_payload_used++];
- l4len = tcp_l2_buf_fill_headers(conn, iov, dlen, check, seq);
- iov[TCP_IOV_PAYLOAD].iov_len = l4len;
- if (tcp4_payload_used > TCP_FRAMES_MEM - 1)
- tcp_payload_flush(c);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_ETH].iov_base = &tcp4_eth_src;
} else if (CONN_V6(conn)) {
- tcp6_frame_conns[tcp6_payload_used] = conn;
-
- iov = tcp6_l2_iov[tcp6_payload_used++];
- l4len = tcp_l2_buf_fill_headers(conn, iov, dlen, NULL, seq);
- iov[TCP_IOV_PAYLOAD].iov_len = l4len;
- if (tcp6_payload_used > TCP_FRAMES_MEM - 1)
- tcp_payload_flush(c);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_ETH].iov_base = &tcp6_eth_src;
}
+ payload = iov[TCP_IOV_PAYLOAD].iov_base;
+ payload->th.th_off = sizeof(struct tcphdr) / 4;
+ payload->th.th_x2 = 0;
+ payload->th.th_flags = 0;
+ payload->th.ack = 1;
+ iov[TCP_IOV_PAYLOAD].iov_len = dlen + sizeof(struct tcphdr);
+ tcp_l2_buf_fill_headers(conn, iov, check, seq, false);
+ if (++tcp_payload_used > TCP_FRAMES_MEM - 1)
+ tcp_payload_flush(c);
}
/**
@@ -402,12 +283,11 @@ static void tcp_data_to_tap(struct ctx *c, struct tcp_tap_conn *conn,
*
* #syscalls recvmsg
*/
-int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
+int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
{
uint32_t wnd_scaled = conn->wnd_from_tap << conn->ws_from_tap;
int fill_bufs, send_bufs = 0, last_len, iov_rem = 0;
- int sendlen, len, dlen, v4 = CONN_V4(conn);
- int s = conn->sock, i, ret = 0;
+ int len, dlen, i, s = conn->sock;
struct msghdr mh_sock = { 0 };
uint16_t mss = MSS_GET(conn);
uint32_t already_sent, seq;
@@ -454,19 +334,15 @@ int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
mh_sock.msg_iovlen = fill_bufs;
}
- if (( v4 && tcp4_payload_used + fill_bufs > TCP_FRAMES_MEM) ||
- (!v4 && tcp6_payload_used + fill_bufs > TCP_FRAMES_MEM)) {
+ if (tcp_payload_used + fill_bufs > TCP_FRAMES_MEM) {
tcp_payload_flush(c);
/* Silence Coverity CWE-125 false positive */
- tcp4_payload_used = tcp6_payload_used = 0;
+ tcp_payload_used = 0;
}
for (i = 0, iov = iov_sock + 1; i < fill_bufs; i++, iov++) {
- if (v4)
- iov->iov_base = &tcp4_payload[tcp4_payload_used + i].data;
- else
- iov->iov_base = &tcp6_payload[tcp6_payload_used + i].data;
+ iov->iov_base = &tcp_payload[tcp_payload_used + i].data;
iov->iov_len = mss;
}
if (iov_rem)
@@ -477,12 +353,19 @@ int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
len = recvmsg(s, &mh_sock, MSG_PEEK);
while (len < 0 && errno == EINTR);
- if (len < 0)
- goto err;
+ if (len < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ tcp_rst(c, conn);
+ return -errno;
+ }
+
+ return 0;
+ }
if (!len) {
if ((conn->events & (SOCK_FIN_RCVD | TAP_FIN_SENT)) == SOCK_FIN_RCVD) {
- if ((ret = tcp_buf_send_flag(c, conn, FIN | ACK))) {
+ int ret = tcp_buf_send_flag(c, conn, FIN | ACK);
+ if (ret) {
tcp_rst(c, conn);
return ret;
}
@@ -493,28 +376,27 @@ int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
return 0;
}
- sendlen = len;
if (!peek_offset_cap)
- sendlen -= already_sent;
+ len -= already_sent;
- if (sendlen <= 0) {
+ if (len <= 0) {
conn_flag(c, conn, STALLED);
return 0;
}
conn_flag(c, conn, ~STALLED);
- send_bufs = DIV_ROUND_UP(sendlen, mss);
- last_len = sendlen - (send_bufs - 1) * mss;
+ send_bufs = DIV_ROUND_UP(len, mss);
+ last_len = len - (send_bufs - 1) * mss;
/* Likely, some new data was acked too. */
- tcp_update_seqack_wnd(c, conn, 0, NULL);
+ tcp_update_seqack_wnd(c, conn, false, NULL);
/* Finally, queue to tap */
dlen = mss;
seq = conn->seq_to_tap;
for (i = 0; i < send_bufs; i++) {
- int no_csum = i && i != send_bufs - 1 && tcp4_payload_used;
+ int no_csum = i && i != send_bufs - 1 && tcp_payload_used;
if (i == send_bufs - 1)
dlen = last_len;
@@ -526,12 +408,4 @@ int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn)
conn_flag(c, conn, ACK_FROM_TAP_DUE);
return 0;
-
-err:
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- ret = -errno;
- tcp_rst(c, conn);
- }
-
- return ret;
}
diff --git a/tcp_buf.h b/tcp_buf.h
index 3db4c56..54f5e53 100644
--- a/tcp_buf.h
+++ b/tcp_buf.h
@@ -6,11 +6,9 @@
#ifndef TCP_BUF_H
#define TCP_BUF_H
-void tcp_sock4_iov_init(const struct ctx *c);
-void tcp_sock6_iov_init(const struct ctx *c);
-void tcp_flags_flush(const struct ctx *c);
-void tcp_payload_flush(struct ctx *c);
-int tcp_buf_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn);
-int tcp_buf_send_flag(struct ctx *c, struct tcp_tap_conn *conn, int flags);
+void tcp_sock_iov_init(const struct ctx *c);
+void tcp_payload_flush(const struct ctx *c);
+int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn);
+int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags);
#endif /*TCP_BUF_H */
diff --git a/tcp_internal.h b/tcp_internal.h
index 8b60aab..94e5780 100644
--- a/tcp_internal.h
+++ b/tcp_internal.h
@@ -33,9 +33,7 @@
#define OPT_EOL 0
#define OPT_NOP 1
#define OPT_MSS 2
-#define OPT_MSS_LEN 4
#define OPT_WS 3
-#define OPT_WS_LEN 3
#define OPT_SACKP 4
#define OPT_SACK 5
#define OPT_TS 8
@@ -44,7 +42,7 @@
#define TAPFLOW(conn_) (&((conn_)->f.side[TAPSIDE(conn_)]))
#define TAP_SIDX(conn_) (FLOW_SIDX((conn_), TAPSIDE(conn_)))
-#define CONN_V4(conn) (!!inany_v4(&TAPFLOW(conn)->faddr))
+#define CONN_V4(conn) (!!inany_v4(&TAPFLOW(conn)->oaddr))
#define CONN_V6(conn) (!CONN_V4(conn))
/*
@@ -63,6 +61,79 @@ enum tcp_iov_parts {
TCP_NUM_IOVS
};
+/**
+ * struct tcp_payload_t - TCP header and data to send segments with payload
+ * @th: TCP header
+ * @data: TCP data
+ */
+struct tcp_payload_t {
+ struct tcphdr th;
+ uint8_t data[IP_MAX_MTU - sizeof(struct tcphdr)];
+#ifdef __AVX2__
+} __attribute__ ((packed, aligned(32))); /* For AVX2 checksum routines */
+#else
+} __attribute__ ((packed, aligned(__alignof__(unsigned int))));
+#endif
+
+/** struct tcp_opt_nop - TCP NOP option
+ * @kind: Option kind (OPT_NOP = 1)
+ */
+struct tcp_opt_nop {
+ uint8_t kind;
+} __attribute__ ((packed));
+#define TCP_OPT_NOP ((struct tcp_opt_nop){ .kind = OPT_NOP, })
+
+/** struct tcp_opt_mss - TCP MSS option
+ * @kind: Option kind (OPT_MSS == 2)
+ * @len: Option length (4)
+ * @mss: Maximum Segment Size
+ */
+struct tcp_opt_mss {
+ uint8_t kind;
+ uint8_t len;
+ uint16_t mss;
+} __attribute__ ((packed));
+#define TCP_OPT_MSS(mss_) \
+ ((struct tcp_opt_mss) { \
+ .kind = OPT_MSS, \
+ .len = sizeof(struct tcp_opt_mss), \
+ .mss = htons(mss_), \
+ })
+
+/** struct tcp_opt_ws - TCP Window Scaling option
+ * @kind: Option kind (OPT_WS == 3)
+ * @len: Option length (3)
+ * @shift: Window scaling shift
+ */
+struct tcp_opt_ws {
+ uint8_t kind;
+ uint8_t len;
+ uint8_t shift;
+} __attribute__ ((packed));
+#define TCP_OPT_WS(shift_) \
+ ((struct tcp_opt_ws) { \
+ .kind = OPT_WS, \
+ .len = sizeof(struct tcp_opt_ws), \
+ .shift = (shift_), \
+ })
+
+/** struct tcp_syn_opts - TCP options we apply to SYN packets
+ * @mss: Maximum Segment Size (MSS) option
+ * @nop: NOP opt (for alignment)
+ * @ws: Window Scaling (WS) option
+ */
+struct tcp_syn_opts {
+ struct tcp_opt_mss mss;
+ struct tcp_opt_nop nop;
+ struct tcp_opt_ws ws;
+} __attribute__ ((packed));
+#define TCP_SYN_OPTS(mss_, ws_) \
+ ((struct tcp_syn_opts){ \
+ .mss = TCP_OPT_MSS(mss_), \
+ .nop = TCP_OPT_NOP, \
+ .ws = TCP_OPT_WS(ws_), \
+ })
+
extern char tcp_buf_discard [MAX_WINDOW];
void conn_flag_do(const struct ctx *c, struct tcp_tap_conn *conn,
@@ -82,19 +153,27 @@ void conn_event_do(const struct ctx *c, struct tcp_tap_conn *conn,
conn_event_do(c, conn, event); \
} while (0)
-void tcp_rst_do(struct ctx *c, struct tcp_tap_conn *conn);
+void tcp_rst_do(const struct ctx *c, struct tcp_tap_conn *conn);
#define tcp_rst(c, conn) \
do { \
flow_dbg((conn), "TCP reset at %s:%i", __func__, __LINE__); \
tcp_rst_do(c, conn); \
} while (0)
-size_t tcp_l2_buf_fill_headers(const struct tcp_tap_conn *conn,
- struct iovec *iov, size_t dlen,
- const uint16_t *check, uint32_t seq);
+struct tcp_info_linux;
+
+void tcp_update_csum(uint32_t psum, struct tcphdr *th,
+ struct iov_tail *payload);
+void tcp_fill_headers(const struct tcp_tap_conn *conn,
+ struct tap_hdr *taph,
+ struct iphdr *ip4h, struct ipv6hdr *ip6h,
+ struct tcphdr *th, struct iov_tail *payload,
+ const uint16_t *ip4_check, uint32_t seq, bool no_tcp_csum);
+
int tcp_update_seqack_wnd(const struct ctx *c, struct tcp_tap_conn *conn,
- int force_seq, struct tcp_info *tinfo);
-int tcp_prepare_flags(struct ctx *c, struct tcp_tap_conn *conn, int flags,
- struct tcphdr *th, char *data, size_t *optlen);
+ bool force_seq, struct tcp_info_linux *tinfo);
+int tcp_prepare_flags(const struct ctx *c, struct tcp_tap_conn *conn,
+ int flags, struct tcphdr *th, struct tcp_syn_opts *opts,
+ size_t *optlen);
#endif /* TCP_INTERNAL_H */
diff --git a/tcp_splice.c b/tcp_splice.c
index 483e45d..3a000ff 100644
--- a/tcp_splice.c
+++ b/tcp_splice.c
@@ -28,7 +28,7 @@
* - FIN_SENT_0: FIN (write shutdown) sent to accepted socket
* - FIN_SENT_1: FIN (write shutdown) sent to target socket
*
- * #syscalls:pasta pipe2|pipe fcntl arm:fcntl64 ppc64:fcntl64
+ * #syscalls:pasta pipe2|pipe fcntl arm:fcntl64 ppc64:fcntl64 i686:fcntl64
*/
#include <sched.h>
@@ -160,7 +160,7 @@ static int tcp_splice_epoll_ctl(const struct ctx *c,
if (epoll_ctl(c->epollfd, m, conn->s[0], &ev[0]) ||
epoll_ctl(c->epollfd, m, conn->s[1], &ev[1])) {
int ret = -errno;
- flow_err(conn, "ERROR on epoll_ctl(): %s", strerror(errno));
+ flow_err(conn, "ERROR on epoll_ctl(): %s", strerror_(errno));
return ret;
}
@@ -314,13 +314,13 @@ static int tcp_splice_connect_finish(const struct ctx *c,
if (conn->pipe[sidei][0] < 0) {
if (pipe2(conn->pipe[sidei], O_NONBLOCK | O_CLOEXEC)) {
flow_err(conn, "cannot create %d->%d pipe: %s",
- sidei, !sidei, strerror(errno));
+ sidei, !sidei, strerror_(errno));
conn_flag(c, conn, CLOSING);
return -EIO;
}
if (fcntl(conn->pipe[sidei][0], F_SETPIPE_SZ,
- c->tcp.pipe_size)) {
+ c->tcp.pipe_size) != (int)c->tcp.pipe_size) {
flow_trace(conn,
"cannot set %d->%d pipe size to %zu",
sidei, !sidei, c->tcp.pipe_size);
@@ -348,6 +348,7 @@ static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn)
uint8_t tgtpif = conn->f.pif[TGTSIDE];
union sockaddr_inany sa;
socklen_t sl;
+ int one = 1;
if (tgtpif == PIF_HOST)
conn->s[1] = tcp_conn_sock(c, af);
@@ -359,18 +360,27 @@ static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn)
if (conn->s[1] < 0)
return -1;
- if (setsockopt(conn->s[1], SOL_TCP, TCP_QUICKACK,
- &((int){ 1 }), sizeof(int))) {
+ if (setsockopt(conn->s[1], SOL_TCP, TCP_QUICKACK, &one, sizeof(one))) {
flow_trace(conn, "failed to set TCP_QUICKACK on socket %i",
conn->s[1]);
}
+ if (setsockopt(conn->s[0], SOL_TCP, TCP_NODELAY, &one, sizeof(one))) {
+ flow_trace(conn, "failed to set TCP_NODELAY on socket %i",
+ conn->s[0]);
+ }
+
+ if (setsockopt(conn->s[1], SOL_TCP, TCP_NODELAY, &one, sizeof(one))) {
+ flow_trace(conn, "failed to set TCP_NODELAY on socket %i",
+ conn->s[1]);
+ }
+
pif_sockaddr(c, &sa, &sl, tgtpif, &tgt->eaddr, tgt->eport);
if (connect(conn->s[1], &sa.sa, sl)) {
if (errno != EINPROGRESS) {
flow_trace(conn, "Couldn't connect socket for splice: %s",
- strerror(errno));
+ strerror_(errno));
return -errno;
}
@@ -469,10 +479,10 @@ void tcp_splice_sock_handler(struct ctx *c, union epoll_ref ref,
rc = getsockopt(ref.fd, SOL_SOCKET, SO_ERROR, &err, &sl);
if (rc)
flow_err(conn, "Error retrieving SO_ERROR: %s",
- strerror(errno));
+ strerror_(errno));
else
flow_trace(conn, "Error event on socket: %s",
- strerror(err));
+ strerror_(err));
goto close;
}
@@ -503,7 +513,7 @@ swap:
lowat_act_flag = RCVLOWAT_ACT(fromsidei);
while (1) {
- ssize_t readlen, to_write = 0, written;
+ ssize_t readlen, written, pending;
int more = 0;
retry:
@@ -518,14 +528,11 @@ retry:
if (errno != EAGAIN)
goto close;
-
- to_write = c->tcp.pipe_size;
} else if (!readlen) {
eof = 1;
- to_write = c->tcp.pipe_size;
} else {
never_read = 0;
- to_write += readlen;
+
if (readlen >= (long)c->tcp.pipe_size * 90 / 100)
more = SPLICE_F_MORE;
@@ -535,10 +542,10 @@ retry:
eintr:
written = splice(conn->pipe[fromsidei][0], NULL,
- conn->s[!fromsidei], NULL, to_write,
+ conn->s[!fromsidei], NULL, c->tcp.pipe_size,
SPLICE_F_MOVE | more | SPLICE_F_NONBLOCK);
flow_trace(conn, "%zi from write-side call (passed %zi)",
- written, to_write);
+ written, c->tcp.pipe_size);
/* Most common case: skip updating counters. */
if (readlen > 0 && readlen == written) {
@@ -554,7 +561,7 @@ eintr:
&lowat, sizeof(lowat))) {
flow_trace(conn,
"Setting SO_RCVLOWAT %i: %s",
- lowat, strerror(errno));
+ lowat, strerror_(errno));
} else {
conn_flag(c, conn, lowat_set_flag);
conn_flag(c, conn, lowat_act_flag);
@@ -584,10 +591,9 @@ eintr:
if (never_read && written == (long)(c->tcp.pipe_size))
goto retry;
- if (!never_read && written < to_write) {
- to_write -= written;
+ pending = conn->read[fromsidei] - conn->written[fromsidei];
+ if (!never_read && written > 0 && written < pending)
goto retry;
- }
if (eof)
break;
@@ -676,7 +682,7 @@ static void tcp_splice_pipe_refill(const struct ctx *c)
continue;
if (fcntl(splice_pipe_pool[i][0], F_SETPIPE_SZ,
- c->tcp.pipe_size)) {
+ c->tcp.pipe_size) != (int)c->tcp.pipe_size) {
trace("TCP (spliced): cannot set pool pipe size to %zu",
c->tcp.pipe_size);
}
@@ -700,13 +706,13 @@ static int tcp_sock_refill_ns(void *arg)
int rc = tcp_sock_refill_pool(c, ns_sock_pool4, AF_INET);
if (rc < 0)
warn("TCP: Error refilling IPv4 ns socket pool: %s",
- strerror(-rc));
+ strerror_(-rc));
}
if (c->ifi6) {
int rc = tcp_sock_refill_pool(c, ns_sock_pool6, AF_INET6);
if (rc < 0)
warn("TCP: Error refilling IPv6 ns socket pool: %s",
- strerror(-rc));
+ strerror_(-rc));
}
return 0;
diff --git a/tcp_vu.c b/tcp_vu.c
new file mode 100644
index 0000000..10e17d3
--- /dev/null
+++ b/tcp_vu.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* tcp_vu.c - TCP L2 vhost-user management functions
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#include <sys/socket.h>
+
+#include <netinet/if_ether.h>
+#include <linux/virtio_net.h>
+
+#include "util.h"
+#include "ip.h"
+#include "passt.h"
+#include "siphash.h"
+#include "inany.h"
+#include "vhost_user.h"
+#include "tcp.h"
+#include "pcap.h"
+#include "flow.h"
+#include "tcp_conn.h"
+#include "flow_table.h"
+#include "tcp_vu.h"
+#include "tap.h"
+#include "tcp_internal.h"
+#include "checksum.h"
+#include "vu_common.h"
+#include <time.h>
+
+static struct iovec iov_vu[VIRTQUEUE_MAX_SIZE + 1];
+static struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE];
+static int head[VIRTQUEUE_MAX_SIZE + 1];
+static int head_cnt;
+
+/**
+ * tcp_vu_hdrlen() - return the size of the header in level 2 frame (TCP)
+ * @v6: Set for IPv6 packet
+ *
+ * Return: Return the size of the header
+ */
+static size_t tcp_vu_hdrlen(bool v6)
+{
+ size_t hdrlen;
+
+ hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) +
+ sizeof(struct ethhdr) + sizeof(struct tcphdr);
+
+ if (v6)
+ hdrlen += sizeof(struct ipv6hdr);
+ else
+ hdrlen += sizeof(struct iphdr);
+
+ return hdrlen;
+}
+
+/**
+ * tcp_vu_send_flag() - Send segment with flags to vhost-user (no payload)
+ * @c: Execution context
+ * @conn: Connection pointer
+ * @flags: TCP flags: if not set, send segment only if ACK is due
+ *
+ * Return: negative error code on connection reset, 0 otherwise
+ */
+int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
+{
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ size_t optlen, hdrlen;
+ struct vu_virtq_element flags_elem[2];
+ struct ipv6hdr *ip6h = NULL;
+ struct iphdr *ip4h = NULL;
+ struct iovec flags_iov[2];
+ struct tcp_syn_opts *opts;
+ struct iov_tail payload;
+ struct tcphdr *th;
+ struct ethhdr *eh;
+ uint32_t seq;
+ int elem_cnt;
+ int nb_ack;
+ int ret;
+
+ hdrlen = tcp_vu_hdrlen(CONN_V6(conn));
+
+ vu_set_element(&flags_elem[0], NULL, &flags_iov[0]);
+
+ elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1,
+ hdrlen + sizeof(struct tcp_syn_opts), NULL);
+ if (elem_cnt != 1)
+ return -1;
+
+ ASSERT(flags_elem[0].in_sg[0].iov_len >=
+ hdrlen + sizeof(struct tcp_syn_opts));
+
+ vu_set_vnethdr(vdev, flags_elem[0].in_sg[0].iov_base, 1);
+
+ eh = vu_eth(flags_elem[0].in_sg[0].iov_base);
+
+ memcpy(eh->h_dest, c->guest_mac, sizeof(eh->h_dest));
+ memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
+
+ if (CONN_V4(conn)) {
+ eh->h_proto = htons(ETH_P_IP);
+
+ ip4h = vu_ip(flags_elem[0].in_sg[0].iov_base);
+ *ip4h = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
+
+ th = vu_payloadv4(flags_elem[0].in_sg[0].iov_base);
+ } else {
+ eh->h_proto = htons(ETH_P_IPV6);
+
+ ip6h = vu_ip(flags_elem[0].in_sg[0].iov_base);
+ *ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
+ th = vu_payloadv6(flags_elem[0].in_sg[0].iov_base);
+ }
+
+ memset(th, 0, sizeof(*th));
+ th->doff = sizeof(*th) / 4;
+ th->ack = 1;
+
+ seq = conn->seq_to_tap;
+ opts = (struct tcp_syn_opts *)(th + 1);
+ ret = tcp_prepare_flags(c, conn, flags, th, opts, &optlen);
+ if (ret <= 0) {
+ vu_queue_rewind(vq, 1);
+ return ret;
+ }
+
+ flags_elem[0].in_sg[0].iov_len = hdrlen + optlen;
+ payload = IOV_TAIL(flags_elem[0].in_sg, 1, hdrlen);
+
+ tcp_fill_headers(conn, NULL, ip4h, ip6h, th, &payload,
+ NULL, seq, !*c->pcap);
+
+ if (*c->pcap) {
+ pcap_iov(&flags_elem[0].in_sg[0], 1,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+ nb_ack = 1;
+
+ if (flags & DUP_ACK) {
+ vu_set_element(&flags_elem[1], NULL, &flags_iov[1]);
+
+ elem_cnt = vu_collect(vdev, vq, &flags_elem[1], 1,
+ flags_elem[0].in_sg[0].iov_len, NULL);
+ if (elem_cnt == 1 &&
+ flags_elem[1].in_sg[0].iov_len >=
+ flags_elem[0].in_sg[0].iov_len) {
+ memcpy(flags_elem[1].in_sg[0].iov_base,
+ flags_elem[0].in_sg[0].iov_base,
+ flags_elem[0].in_sg[0].iov_len);
+ nb_ack++;
+
+ if (*c->pcap) {
+ pcap_iov(&flags_elem[1].in_sg[0], 1,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+ }
+ }
+
+ vu_flush(vdev, vq, flags_elem, nb_ack);
+
+ return 0;
+}
+
+/** tcp_vu_sock_recv() - Receive datastream from socket into vhost-user buffers
+ * @c: Execution context
+ * @conn: Connection pointer
+ * @v6: Set for IPv6 connections
+ * @already_sent: Number of bytes already sent
+ * @fillsize: Maximum bytes to fill in guest-side receiving window
+ * @iov_cnt: number of iov (output)
+ *
+ * Return: Number of iov entries used to store the data or negative error code
+ */
+static ssize_t tcp_vu_sock_recv(const struct ctx *c,
+ const struct tcp_tap_conn *conn, bool v6,
+ uint32_t already_sent, size_t fillsize,
+ int *iov_cnt)
+{
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ struct msghdr mh_sock = { 0 };
+ uint16_t mss = MSS_GET(conn);
+ int s = conn->sock;
+ ssize_t ret, len;
+ size_t hdrlen;
+ int elem_cnt;
+ int i;
+
+ *iov_cnt = 0;
+
+ hdrlen = tcp_vu_hdrlen(v6);
+
+ vu_init_elem(elem, &iov_vu[1], VIRTQUEUE_MAX_SIZE);
+
+ elem_cnt = 0;
+ head_cnt = 0;
+ while (fillsize > 0 && elem_cnt < VIRTQUEUE_MAX_SIZE) {
+ struct iovec *iov;
+ size_t frame_size, dlen;
+ int cnt;
+
+ cnt = vu_collect(vdev, vq, &elem[elem_cnt],
+ VIRTQUEUE_MAX_SIZE - elem_cnt,
+ MIN(mss, fillsize) + hdrlen, &frame_size);
+ if (cnt == 0)
+ break;
+
+ dlen = frame_size - hdrlen;
+
+ /* reserve space for headers in iov */
+ iov = &elem[elem_cnt].in_sg[0];
+ ASSERT(iov->iov_len >= hdrlen);
+ iov->iov_base = (char *)iov->iov_base + hdrlen;
+ iov->iov_len -= hdrlen;
+ head[head_cnt++] = elem_cnt;
+
+ fillsize -= dlen;
+ elem_cnt += cnt;
+ }
+
+ if (peek_offset_cap) {
+ mh_sock.msg_iov = iov_vu + 1;
+ mh_sock.msg_iovlen = elem_cnt;
+ } else {
+ iov_vu[0].iov_base = tcp_buf_discard;
+ iov_vu[0].iov_len = already_sent;
+
+ mh_sock.msg_iov = iov_vu;
+ mh_sock.msg_iovlen = elem_cnt + 1;
+ }
+
+ do
+ ret = recvmsg(s, &mh_sock, MSG_PEEK);
+ while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ vu_queue_rewind(vq, elem_cnt);
+ return -errno;
+ }
+
+ if (!peek_offset_cap)
+ ret -= already_sent;
+
+ /* adjust iov number and length of the last iov */
+ len = ret;
+ for (i = 0; len && i < elem_cnt; i++) {
+ struct iovec *iov = &elem[i].in_sg[0];
+
+ if (iov->iov_len > (size_t)len)
+ iov->iov_len = len;
+
+ len -= iov->iov_len;
+ }
+ /* adjust head count */
+ while (head_cnt > 0 && head[head_cnt - 1] > i)
+ head_cnt--;
+ /* mark end of array */
+ head[head_cnt] = i;
+ *iov_cnt = i;
+
+ /* release unused buffers */
+ vu_queue_rewind(vq, elem_cnt - i);
+
+ /* restore space for headers in iov */
+ for (i = 0; i < head_cnt; i++) {
+ struct iovec *iov = &elem[head[i]].in_sg[0];
+
+ iov->iov_base = (char *)iov->iov_base - hdrlen;
+ iov->iov_len += hdrlen;
+ }
+
+ return ret;
+}
+
+/**
+ * tcp_vu_prepare() - Prepare the frame header
+ * @c: Execution context
+ * @conn: Connection pointer
+ * @iov: Pointer to the array of IO vectors
+ * @iov_cnt: Number of entries in @iov
+ * @check: Checksum, if already known
+ * @no_tcp_csum: Do not set TCP checksum
+ */
+static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn,
+ struct iovec *iov, size_t iov_cnt,
+ const uint16_t **check, bool no_tcp_csum)
+{
+ const struct flowside *toside = TAPFLOW(conn);
+ bool v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr));
+ size_t hdrlen = tcp_vu_hdrlen(v6);
+ struct iov_tail payload = IOV_TAIL(iov, iov_cnt, hdrlen);
+ char *base = iov[0].iov_base;
+ struct ipv6hdr *ip6h = NULL;
+ struct iphdr *ip4h = NULL;
+ struct tcphdr *th;
+ struct ethhdr *eh;
+
+ /* we guess the first iovec provided by the guest can embed
+ * all the headers needed by L2 frame
+ */
+ ASSERT(iov[0].iov_len >= hdrlen);
+
+ eh = vu_eth(base);
+
+ memcpy(eh->h_dest, c->guest_mac, sizeof(eh->h_dest));
+ memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
+
+ /* initialize header */
+
+ if (!v6) {
+ eh->h_proto = htons(ETH_P_IP);
+
+ ip4h = vu_ip(base);
+ *ip4h = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
+ th = vu_payloadv4(base);
+ } else {
+ eh->h_proto = htons(ETH_P_IPV6);
+
+ ip6h = vu_ip(base);
+ *ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
+
+ th = vu_payloadv6(base);
+ }
+
+ memset(th, 0, sizeof(*th));
+ th->doff = sizeof(*th) / 4;
+ th->ack = 1;
+
+ tcp_fill_headers(conn, NULL, ip4h, ip6h, th, &payload,
+ *check, conn->seq_to_tap, no_tcp_csum);
+ if (ip4h)
+ *check = &ip4h->check;
+}
+
+/**
+ * tcp_vu_data_from_sock() - Handle new data from socket, queue to vhost-user,
+ * in window
+ * @c: Execution context
+ * @conn: Connection pointer
+ *
+ * Return: Negative on connection reset, 0 otherwise
+ */
+int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
+{
+ uint32_t wnd_scaled = conn->wnd_from_tap << conn->ws_from_tap;
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ ssize_t len, previous_dlen;
+ size_t hdrlen, fillsize;
+ int v6 = CONN_V6(conn);
+ uint32_t already_sent;
+ const uint16_t *check;
+ int i, iov_cnt;
+
+ if (!vu_queue_enabled(vq) || !vu_queue_started(vq)) {
+ debug("Got packet, but RX virtqueue not usable yet");
+ return 0;
+ }
+
+ already_sent = conn->seq_to_tap - conn->seq_ack_from_tap;
+
+ if (SEQ_LT(already_sent, 0)) {
+ /* RFC 761, section 2.1. */
+ flow_trace(conn, "ACK sequence gap: ACK for %u, sent: %u",
+ conn->seq_ack_from_tap, conn->seq_to_tap);
+ conn->seq_to_tap = conn->seq_ack_from_tap;
+ already_sent = 0;
+ if (tcp_set_peek_offset(conn->sock, 0)) {
+ tcp_rst(c, conn);
+ return -1;
+ }
+ }
+
+ if (!wnd_scaled || already_sent >= wnd_scaled) {
+ conn_flag(c, conn, STALLED);
+ conn_flag(c, conn, ACK_FROM_TAP_DUE);
+ return 0;
+ }
+
+ /* Set up buffer descriptors we'll fill completely and partially. */
+
+ fillsize = wnd_scaled - already_sent;
+
+ /* collect the buffers from vhost-user and fill them with the
+ * data from the socket
+ */
+ len = tcp_vu_sock_recv(c, conn, v6, already_sent, fillsize, &iov_cnt);
+ if (len < 0) {
+ if (len != -EAGAIN && len != -EWOULDBLOCK) {
+ tcp_rst(c, conn);
+ return len;
+ }
+ return 0;
+ }
+
+ if (!len) {
+ if (already_sent) {
+ conn_flag(c, conn, STALLED);
+ } else if ((conn->events & (SOCK_FIN_RCVD | TAP_FIN_SENT)) ==
+ SOCK_FIN_RCVD) {
+ int ret = tcp_vu_send_flag(c, conn, FIN | ACK);
+ if (ret) {
+ tcp_rst(c, conn);
+ return ret;
+ }
+
+ conn_event(c, conn, TAP_FIN_SENT);
+ }
+
+ return 0;
+ }
+
+ conn_flag(c, conn, ~STALLED);
+
+ /* Likely, some new data was acked too. */
+ tcp_update_seqack_wnd(c, conn, false, NULL);
+
+ /* initialize headers */
+ /* iov_vu is an array of buffers and the buffer size can be
+ * smaller than the frame size we want to use but with
+ * num_buffer we can merge several virtio iov buffers in one packet
+ * we need only to set the packet headers in the first iov and
+ * num_buffer to the number of iov entries
+ */
+
+ hdrlen = tcp_vu_hdrlen(v6);
+ for (i = 0, previous_dlen = -1, check = NULL; i < head_cnt; i++) {
+ struct iovec *iov = &elem[head[i]].in_sg[0];
+ int buf_cnt = head[i + 1] - head[i];
+ ssize_t dlen = iov_size(iov, buf_cnt) - hdrlen;
+
+ vu_set_vnethdr(vdev, iov->iov_base, buf_cnt);
+
+ /* The IPv4 header checksum varies only with dlen */
+ if (previous_dlen != dlen)
+ check = NULL;
+ previous_dlen = dlen;
+
+ tcp_vu_prepare(c, conn, iov, buf_cnt, &check, !*c->pcap);
+
+ if (*c->pcap) {
+ pcap_iov(iov, buf_cnt,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+
+ conn->seq_to_tap += dlen;
+ }
+
+ /* send packets */
+ vu_flush(vdev, vq, elem, iov_cnt);
+
+ conn_flag(c, conn, ACK_FROM_TAP_DUE);
+
+ return 0;
+}
diff --git a/tcp_vu.h b/tcp_vu.h
new file mode 100644
index 0000000..6ab6057
--- /dev/null
+++ b/tcp_vu.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#ifndef TCP_VU_H
+#define TCP_VU_H
+
+int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags);
+int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn);
+
+#endif /*TCP_VU_H */
diff --git a/test/Makefile b/test/Makefile
index 35a3b55..5e49047 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -8,7 +8,6 @@
WGET = wget -c
DEBIAN_IMGS = debian-8.11.0-openstack-amd64.qcow2 \
- debian-9-nocloud-amd64-daily-20200210-166.qcow2 \
debian-10-nocloud-amd64.qcow2 \
debian-10-generic-arm64.qcow2 \
debian-10-generic-ppc64el-20220911-1135.qcow2 \
@@ -42,8 +41,7 @@ OPENSUSE_IMGS = openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2 \
openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2 \
openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2 \
openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz \
- openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz \
- openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
+ openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz
UBUNTU_OLD_IMGS = trusty-server-cloudimg-amd64-disk1.img \
trusty-server-cloudimg-i386-disk1.img \
@@ -135,9 +133,6 @@ realclean: clean
debian-8.11.0-openstack-%.qcow2:
$(WGET) -O $@ https://cloud.debian.org/images/cloud/OpenStack/archive/8.11.0/debian-8.11.0-openstack-$*.qcow2
-debian-9-nocloud-%-daily-20200210-166.qcow2:
- $(WGET) -O $@ https://cloud.debian.org/images/cloud/stretch/daily/20200210-166/debian-9-nocloud-$*-daily-20200210-166.qcow2
-
debian-10-nocloud-%.qcow2:
$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-nocloud-$*.qcow2
@@ -203,9 +198,6 @@ openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz:
openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz:
$(WGET) -O $@ http://download.opensuse.org/ports/armv7hl/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz
-openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2:
- $(WGET) -O $@ https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
-
# Ubuntu downloads
trusty-server-cloudimg-%-disk1.img:
$(WGET) -O $@ https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-$*-disk1.img
diff --git a/test/README.md b/test/README.md
index 0936b04..91ca603 100644
--- a/test/README.md
+++ b/test/README.md
@@ -28,10 +28,11 @@ on a system, i.e. common utilities such as a shell are not included here.
Example for Debian, and possibly most Debian-based distributions:
- build-essential git jq strace iperf3 qemu-system-x86 tmux sipcalc bats bc
- catatonit clang-tidy cppcheck go isc-dhcp-common psmisc linux-cpupower socat
- netcat-openbsd fakeroot lz4 lm-sensors qemu-system-arm qemu-system-ppc
- qemu-system-misc qemu-system-x86 valgrind
+ bats bc build-essential catatonit clang-tidy conmon cppcheck crun fakeroot
+ git go iperf3 isc-dhcp-common jq libgpgme-dev libseccomp-dev linux-cpupower
+ lm-sensors lz4 netavark netcat-openbsd psmisc qemu-efi-aarch64
+ qemu-system-arm qemu-system-misc qemu-system-ppc qemu-system-x86
+ qemu-system-x86 sipcalc socat strace tmux uidmap valgrind
NOTE: the tests need a qemu version >= 7.2, or one that contains commit
13c6be96618c ("net: stream: add unix socket"): this change introduces support
diff --git a/test/lib/layout b/test/lib/layout
index f9a1cf1..4d03572 100644
--- a/test/lib/layout
+++ b/test/lib/layout
@@ -15,7 +15,7 @@
# layout_pasta() - Panes for host, pasta, and separate one for namespace
layout_pasta() {
- sleep 3
+ sleep 1
tmux kill-pane -a -t 0
cmd_write 0 clear
@@ -46,7 +46,7 @@ layout_pasta() {
# layout_passt() - Panes for host, passt, and guest
layout_passt() {
- sleep 3
+ sleep 1
tmux kill-pane -a -t 0
cmd_write 0 clear
@@ -77,7 +77,7 @@ layout_passt() {
# layout_passt_in_pasta() - Host, passt within pasta, namespace and guest
layout_passt_in_pasta() {
- sleep 3
+ sleep 1
tmux kill-pane -a -t 0
cmd_write 0 clear
@@ -113,7 +113,7 @@ layout_passt_in_pasta() {
# layout_two_guests() - Two guest panes, two passt panes, plus host and log
layout_two_guests() {
- sleep 3
+ sleep 1
tmux kill-pane -a -t 0
cmd_write 0 clear
@@ -152,7 +152,7 @@ layout_two_guests() {
# layout_demo_pasta() - Four panes for pasta demo
layout_demo_pasta() {
- sleep 3
+ sleep 1
cmd_write 0 cd ${BASEPATH}
cmd_write 0 clear
@@ -188,7 +188,7 @@ layout_demo_pasta() {
# layout_demo_passt() - Four panes for passt demo
layout_demo_passt() {
- sleep 3
+ sleep 1
cmd_write 0 cd ${BASEPATH}
cmd_write 0 clear
@@ -224,7 +224,7 @@ layout_demo_passt() {
# layout_demo_podman() - Four panes for pasta demo with Podman
layout_demo_podman() {
- sleep 3
+ sleep 1
cmd_write 0 cd ${BASEPATH}
cmd_write 0 clear
diff --git a/test/lib/perf_report b/test/lib/perf_report
index d1ef50b..c4ec817 100755
--- a/test/lib/perf_report
+++ b/test/lib/perf_report
@@ -49,6 +49,21 @@ td:empty { visibility: hidden; }
__passt_tcp_LINE__ __passt_udp_LINE__
</table>
+</li><li><p>passt with vhost-user support</p>
+<table class="passt" width="70%">
+ <tr>
+ <th/>
+ <th id="perf_passt_vu_tcp" colspan="__passt_vu_tcp_cols__">TCP, __passt_vu_tcp_threads__ at __passt_vu_tcp_freq__ GHz</th>
+ <th id="perf_passt_vu_udp" colspan="__passt_vu_udp_cols__">UDP, __passt_vu_udp_threads__ at __passt_vu_udp_freq__ GHz</th>
+ </tr>
+ <tr>
+ <td align="right">MTU:</td>
+ __passt_vu_tcp_header__
+ __passt_vu_udp_header__
+ </tr>
+ __passt_vu_tcp_LINE__ __passt_vu_udp_LINE__
+</table>
+
<style type="text/CSS">
table.pasta_local td { border: 0px solid; padding: 6px; line-height: 1; }
table.pasta_local td { text-align: right; }
diff --git a/test/lib/setup b/test/lib/setup
index 9b39b9f..580825f 100755
--- a/test/lib/setup
+++ b/test/lib/setup
@@ -15,8 +15,9 @@
INITRAMFS="${BASEPATH}/mbuto.img"
VCPUS="$( [ $(nproc) -ge 8 ] && echo 6 || echo $(( $(nproc) / 2 + 1 )) )"
-__mem_kib="$(sed -n 's/MemTotal:[ ]*\([0-9]*\) kB/\1/p' /proc/meminfo)"
-VMEM="$((${__mem_kib} / 1024 / 4))"
+MEM_KIB="$(sed -n 's/MemTotal:[ ]*\([0-9]*\) kB/\1/p' /proc/meminfo)"
+QEMU_ARCH="$(uname -m)"
+[ "${QEMU_ARCH}" = "i686" ] && QEMU_ARCH=i386
# setup_build() - Set up pane layout for build tests
setup_build() {
@@ -44,6 +45,7 @@ setup_passt() {
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+ [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
context_run passt "make clean"
context_run passt "make valgrind"
@@ -52,16 +54,29 @@ setup_passt() {
# pidfile isn't created until passt is listening
wait_for [ -f "${STATESETUP}/passt.pid" ]
+ __vmem="$((${MEM_KIB} / 1024 / 4))"
+ if [ ${VHOST_USER} -eq 1 ]; then
+ __vmem="$(((${__vmem} + 500) / 1000))G"
+ __qemu_netdev=" \
+ -chardev socket,id=c,path=${STATESETUP}/passt.socket \
+ -netdev vhost-user,id=v,chardev=c \
+ -device virtio-net,netdev=v \
+ -object memory-backend-memfd,id=m,share=on,size=${__vmem} \
+ -numa node,memdev=m"
+ else
+ __qemu_netdev="-device virtio-net-pci,netdev=s \
+ -netdev stream,id=s,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket"
+ fi
+
GUEST_CID=94557
- context_run_bg qemu 'qemu-system-$(uname -m)' \
+ context_run_bg qemu 'qemu-system-'"${QEMU_ARCH}" \
' -machine accel=kvm' \
- ' -m '${VMEM}' -cpu host -smp '${VCPUS} \
- ' -kernel ' "/boot/vmlinuz-$(uname -r)" \
+ ' -m '${__vmem}' -cpu host -smp '${VCPUS} \
+ ' -kernel '"${KERNEL}" \
' -initrd '${INITRAMFS}' -nographic -serial stdio' \
' -nodefaults' \
' -append "console=ttyS0 mitigations=off apparmor=0" ' \
- ' -device virtio-net-pci,netdev=s0 ' \
- " -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket " \
+ " ${__qemu_netdev}" \
" -pidfile ${STATESETUP}/qemu.pid" \
" -device vhost-vsock-pci,guest-cid=$GUEST_CID"
@@ -124,7 +139,12 @@ setup_passt_in_ns() {
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
- context_run_bg pasta "./pasta ${__opts} -t 10001,10002,10011,10012 -T 10003,10013 -u 10001,10002,10011,10012 -U 10003,10013 -P ${STATESETUP}/pasta.pid --config-net ${NSTOOL} hold ${STATESETUP}/ns.hold"
+ __map_host4=192.0.2.1
+ __map_host6=2001:db8:9a55::1
+ __map_ns4=192.0.2.2
+ __map_ns6=2001:db8:9a55::2
+
+ context_run_bg pasta "./pasta ${__opts} -t 10001,10002,10011,10012 -T 10003,10013 -u 10001,10002,10011,10012 -U 10003,10013 -P ${STATESETUP}/pasta.pid --map-host-loopback ${__map_host4} --map-host-loopback ${__map_host6} --config-net ${NSTOOL} hold ${STATESETUP}/ns.hold"
wait_for [ -f "${STATESETUP}/pasta.pid" ]
context_setup_nstool qemu ${STATESETUP}/ns.hold
@@ -135,29 +155,43 @@ setup_passt_in_ns() {
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_in_pasta.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+ [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
if [ ${VALGRIND} -eq 1 ]; then
context_run passt "make clean"
context_run passt "make valgrind"
- context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid"
+ context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
else
context_run passt "make clean"
context_run passt "make"
- context_run_bg passt "./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid"
+ context_run_bg passt "./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
fi
wait_for [ -f "${STATESETUP}/passt.pid" ]
+ __vmem="$((${MEM_KIB} / 1024 / 4))"
+ if [ ${VHOST_USER} -eq 1 ]; then
+ __vmem="$(((${__vmem} + 500) / 1000))G"
+ __qemu_netdev=" \
+ -chardev socket,id=c,path=${STATESETUP}/passt.socket \
+ -netdev vhost-user,id=v,chardev=c \
+ -device virtio-net,netdev=v \
+ -object memory-backend-memfd,id=m,share=on,size=${__vmem} \
+ -numa node,memdev=m"
+ else
+ __qemu_netdev="-device virtio-net-pci,netdev=s \
+ -netdev stream,id=s,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket"
+ fi
+
GUEST_CID=94557
- context_run_bg qemu 'qemu-system-$(uname -m)' \
+ context_run_bg qemu 'qemu-system-'"${QEMU_ARCH}" \
' -machine accel=kvm' \
' -M accel=kvm:tcg' \
- ' -m '${VMEM}' -cpu host -smp '${VCPUS} \
- ' -kernel ' "/boot/vmlinuz-$(uname -r)" \
+ ' -m '${__vmem}' -cpu host -smp '${VCPUS} \
+ ' -kernel '"${KERNEL}" \
' -initrd '${INITRAMFS}' -nographic -serial stdio' \
' -nodefaults' \
' -append "console=ttyS0 mitigations=off apparmor=0" ' \
- ' -device virtio-net-pci,netdev=s0 ' \
- " -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket " \
+ " ${__qemu_netdev}" \
" -pidfile ${STATESETUP}/qemu.pid" \
" -device vhost-vsock-pci,guest-cid=$GUEST_CID"
@@ -207,6 +241,7 @@ setup_two_guests() {
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_1.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+ [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
context_run_bg passt_1 "./passt -s ${STATESETUP}/passt_1.socket -P ${STATESETUP}/passt_1.pid -f ${__opts} -t 10001 -u 10001"
wait_for [ -f "${STATESETUP}/passt_1.pid" ]
@@ -215,33 +250,54 @@ setup_two_guests() {
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_2.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+ [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
context_run_bg passt_2 "./passt -s ${STATESETUP}/passt_2.socket -P ${STATESETUP}/passt_2.pid -f ${__opts} -t 10004 -u 10004"
wait_for [ -f "${STATESETUP}/passt_2.pid" ]
+ __vmem="$((${MEM_KIB} / 1024 / 4))"
+ if [ ${VHOST_USER} -eq 1 ]; then
+ __vmem="$(((${__vmem} + 500) / 1000))G"
+ __qemu_netdev1=" \
+ -chardev socket,id=c,path=${STATESETUP}/passt_1.socket \
+ -netdev vhost-user,id=v,chardev=c \
+ -device virtio-net,netdev=v \
+ -object memory-backend-memfd,id=m,share=on,size=${__vmem} \
+ -numa node,memdev=m"
+ __qemu_netdev2=" \
+ -chardev socket,id=c,path=${STATESETUP}/passt_2.socket \
+ -netdev vhost-user,id=v,chardev=c \
+ -device virtio-net,netdev=v \
+ -object memory-backend-memfd,id=m,share=on,size=${__vmem} \
+ -numa node,memdev=m"
+ else
+ __qemu_netdev1="-device virtio-net-pci,netdev=s \
+ -netdev stream,id=s,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_1.socket"
+ __qemu_netdev2="-device virtio-net-pci,netdev=s \
+ -netdev stream,id=s,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_2.socket"
+ fi
+
GUEST_1_CID=94557
- context_run_bg qemu_1 'qemu-system-$(uname -m)' \
+ context_run_bg qemu_1 'qemu-system-'"${QEMU_ARCH}" \
' -M accel=kvm:tcg' \
- ' -m '${VMEM}' -cpu host -smp '${VCPUS} \
- ' -kernel ' "/boot/vmlinuz-$(uname -r)" \
+ ' -m '${__vmem}' -cpu host -smp '${VCPUS} \
+ ' -kernel '"${KERNEL}" \
' -initrd '${INITRAMFS}' -nographic -serial stdio' \
' -nodefaults' \
' -append "console=ttyS0 mitigations=off apparmor=0" ' \
- ' -device virtio-net-pci,netdev=s0 ' \
- " -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_1.socket " \
+ " ${__qemu_netdev1}" \
" -pidfile ${STATESETUP}/qemu_1.pid" \
" -device vhost-vsock-pci,guest-cid=$GUEST_1_CID"
GUEST_2_CID=94558
- context_run_bg qemu_2 'qemu-system-$(uname -m)' \
+ context_run_bg qemu_2 'qemu-system-'"${QEMU_ARCH}" \
' -M accel=kvm:tcg' \
- ' -m '${VMEM}' -cpu host -smp '${VCPUS} \
- ' -kernel ' "/boot/vmlinuz-$(uname -r)" \
+ ' -m '${__vmem}' -cpu host -smp '${VCPUS} \
+ ' -kernel '"${KERNEL}" \
' -initrd '${INITRAMFS}' -nographic -serial stdio' \
' -nodefaults' \
' -append "console=ttyS0 mitigations=off apparmor=0" ' \
- ' -device virtio-net-pci,netdev=s0 ' \
- " -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_2.socket " \
+ " ${__qemu_netdev2}" \
" -pidfile ${STATESETUP}/qemu_2.pid" \
" -device vhost-vsock-pci,guest-cid=$GUEST_2_CID"
diff --git a/test/lib/setup_ugly b/test/lib/setup_ugly
index 4b2a077..2802cc3 100755
--- a/test/lib/setup_ugly
+++ b/test/lib/setup_ugly
@@ -33,7 +33,7 @@ setup_memory() {
pane_or_context_run guest 'qemu-system-$(uname -m)' \
' -machine accel=kvm' \
- ' -m '${VMEM}' -cpu host -smp '${VCPUS} \
+ ' -m '$((${MEM_KIB} / 1024 / 4))' -cpu host -smp '${VCPUS} \
' -kernel ' "/boot/vmlinuz-$(uname -r)" \
' -initrd '${INITRAMFS_MEM}' -nographic -serial stdio' \
' -nodefaults' \
diff --git a/test/lib/term b/test/lib/term
index 262937e..ed690de 100755
--- a/test/lib/term
+++ b/test/lib/term
@@ -31,8 +31,8 @@ PR_DELAY_INIT=100 # ms
# $@: Message to print
info() {
tmux select-pane -t ${PANE_INFO}
- echo "${@}" >> $STATEBASE/log_pipe
- echo "${@}" >> "${LOGFILE}"
+ printf "${@}\n" >> $STATEBASE/log_pipe
+ printf "${@}\n" >> "${LOGFILE}"
}
# info_n() - Highlight, print message to pane and to log file without newline
@@ -47,13 +47,13 @@ info_n() {
# $@: Message to print
info_nolog() {
tmux select-pane -t ${PANE_INFO}
- echo "${@}" >> $STATEBASE/log_pipe
+ printf "${@}\n" >> $STATEBASE/log_pipe
}
# info_nolog() - Print message to log file
# $@: Message to print
log() {
- echo "${@}" >> "${LOGFILE}"
+ printf "${@}\n" >> "${LOGFILE}"
}
# info_nolog_n() - Send message to pane without highlighting it, without newline
@@ -97,7 +97,6 @@ display_delay() {
switch_pane() {
tmux select-pane -t ${1}
PR_DELAY=${PR_DELAY_INIT}
- display_delay "0.2"
}
# cmd_write() - Write a command to a pane, letter by letter, and execute it
@@ -199,7 +198,7 @@ pane_run() {
# $1: Pane name
pane_wait() {
__lc="$(echo "${1}" | tr [A-Z] [a-z])"
- sleep 0.1 || sleep 1
+ sleep 0.01 || sleep 1
__done=0
while
@@ -207,7 +206,7 @@ pane_wait() {
case ${__l} in
*"$ " | *"# ") return ;;
esac
- do sleep 0.1 || sleep 1; done
+ do sleep 0.01 || sleep 1; done
}
# pane_parse() - Print last line, @EMPTY@ if command had no output
@@ -231,7 +230,7 @@ pane_status() {
__status="$(pane_parse "${1}")"
while ! [ "${__status}" -eq "${__status}" ] 2>/dev/null; do
- sleep 1
+ sleep 0.01 || sleep 1
pane_run "${1}" 'echo $?'
pane_wait "${1}"
__status="$(pane_parse "${1}")"
@@ -383,6 +382,16 @@ info_check_failed() {
printf " < failed.\n" >> "${LOGFILE}"
}
+# status_bar_blink() - Make status bar blink
+status_bar_blink() {
+ for i in `seq 1 3`; do
+ tmux set status-right-style 'bg=colour1 fg=colour196 bold'
+ sleep 0.1 || sleep 1
+ tmux set status-right-style 'bg=colour1 fg=colour233 bold'
+ sleep 0.1 || sleep 1
+ done
+}
+
# info_passed() - Display, log, and make status bar blink when a test passes
info_passed() {
switch_pane ${PANE_INFO}
@@ -391,12 +400,7 @@ info_passed() {
log "...passed."
log
- for i in `seq 1 3`; do
- tmux set status-right-style 'bg=colour1 fg=colour2 bold'
- sleep "0.1"
- tmux set status-right-style 'bg=colour1 fg=colour233 bold'
- sleep "0.1"
- done
+ [ ${FAST} -eq 1 ] || status_bar_blink
}
# info_failed() - Display, log, and make status bar blink when a test passes
@@ -407,12 +411,7 @@ info_failed() {
log "...failed."
log
- for i in `seq 1 3`; do
- tmux set status-right-style 'bg=colour1 fg=colour196 bold'
- sleep "0.1"
- tmux set status-right-style 'bg=colour1 fg=colour233 bold'
- sleep "0.1"
- done
+ [ ${FAST} -eq 1 ] || status_bar_blink
pause_continue \
"Press any key to pause test session" \
@@ -665,7 +664,7 @@ pause_continue() {
# run_term() - Start tmux session, running entry point, with recording if needed
run_term() {
- TMUX="tmux new-session -s passt_test -eSTATEBASE=$STATEBASE -ePCAP=$PCAP -eDEBUG=$DEBUG"
+ TMUX="tmux new-session -s passt_test -eSTATEBASE=$STATEBASE -ePCAP=$PCAP -eDEBUG=$DEBUG -eTRACE=$TRACE -eKERNEL=$KERNEL"
if [ ${CI} -eq 1 ]; then
printf '\e[8;50;240t'
diff --git a/test/lib/test b/test/lib/test
index c525f8e..e6726be 100755
--- a/test/lib/test
+++ b/test/lib/test
@@ -33,7 +33,7 @@ test_iperf3k() {
pane_or_context_run "${__sctx}" 'kill -INT $(cat s.pid); rm s.pid'
- sleep 3 # Wait for kernel to free up ports
+ sleep 1 # Wait for kernel to free up ports
}
# test_iperf3() - Ugly helper for iperf3 directive
diff --git a/test/nstool.c b/test/nstool.c
index fc357d8..7ab5d2a 100644
--- a/test/nstool.c
+++ b/test/nstool.c
@@ -31,10 +31,15 @@
#define ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0])))
-#define die(...) \
- do { \
- fprintf(stderr, __VA_ARGS__); \
- exit(1); \
+#define die(...) \
+ do { \
+ fprintf(stderr, "nstool: " __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define err(...) \
+ do { \
+ fprintf(stderr, "nstool: " __VA_ARGS__); \
} while (0)
struct ns_type {
@@ -156,6 +161,9 @@ static int connect_ctl(const char *sockpath, bool wait,
static void cmd_hold(int argc, char *argv[])
{
+ struct sigaction sa = {
+ .sa_handler = SIG_IGN,
+ };
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
struct sockaddr_un addr;
const char *sockpath = argv[1];
@@ -185,6 +193,10 @@ static void cmd_hold(int argc, char *argv[])
if (!getcwd(info.cwd, sizeof(info.cwd)))
die("getcwd(): %s\n", strerror(errno));
+ rc = sigaction(SIGPIPE, &sa, NULL);
+ if (rc)
+ die("sigaction(SIGPIPE): %s\n", strerror(errno));
+
do {
int afd = accept(fd, NULL, NULL);
char buf;
@@ -193,17 +205,21 @@ static void cmd_hold(int argc, char *argv[])
die("accept(): %s\n", strerror(errno));
rc = write(afd, &info, sizeof(info));
- if (rc < 0)
- die("write(): %s\n", strerror(errno));
+ if (rc < 0) {
+ err("holder write() to control socket: %s\n",
+ strerror(errno));
+ }
if ((size_t)rc < sizeof(info))
- die("short write() on control socket\n");
+ err("holder short write() on control socket\n");
rc = read(afd, &buf, sizeof(buf));
- if (rc < 0)
- die("read(): %s\n", strerror(errno));
+ if (rc < 0) {
+ err("holder read() on control socket: %s\n",
+ strerror(errno));
+ }
close(afd);
- } while (rc == 0);
+ } while (rc <= 0);
unlink(sockpath);
}
@@ -346,7 +362,7 @@ static int openns(const char *fmt, ...)
}
static pid_t sig_pid;
-static void sig_handler(int signum)
+static void sig_propagate(int signum)
{
int err;
@@ -358,7 +374,7 @@ static void sig_handler(int signum)
static void wait_for_child(pid_t pid)
{
struct sigaction sa = {
- .sa_handler = sig_handler,
+ .sa_handler = sig_propagate,
.sa_flags = SA_RESETHAND,
};
int status, err;
diff --git a/test/passt.mbuto b/test/passt.mbuto
index 436eecc..138d365 100755
--- a/test/passt.mbuto
+++ b/test/passt.mbuto
@@ -15,6 +15,14 @@ PROGS="${PROGS:-ash,dash,bash ip mount ls insmod mkdir ln cat chmod lsmod
sed tr chown sipcalc cut socat dd strace ping tail killall sleep sysctl
nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp}"
+# OpenSSH 9.8 introduced split binaries, with sshd being the daemon, and
+# sshd-session the per-session program. We need the latter as well, and the path
+# depends on the distribution. It doesn't exist on older versions.
+for bin in /usr/lib/openssh/sshd-session /usr/lib/ssh/sshd-session \
+ /usr/libexec/openssh/sshd-session; do
+ command -v "${bin}" >/dev/null && PROGS="${PROGS} ${bin}"
+done
+
KMODS="${KMODS:- virtio_net virtio_pci vmw_vsock_virtio_transport}"
LINKS="${LINKS:-
@@ -78,7 +86,7 @@ EOF
EOF
chmod 600 /root/.ssh/authorized_keys
chmod 700 /root
- socat VSOCK-LISTEN:22,fork EXEC:"sshd -i -e" 2> /var/log/vsock-ssh.log &
+ socat VSOCK-LISTEN:22,fork EXEC:"/sbin/sshd -i -e" 2> /var/log/vsock-ssh.log &
sh +m
'
diff --git a/test/passt/dhcp b/test/passt/dhcp
index e05a4bb..9925ab9 100644
--- a/test/passt/dhcp
+++ b/test/passt/dhcp
@@ -49,6 +49,8 @@ check [ "__SEARCH__" = "__HOST_SEARCH__" ]
test DHCPv6: address
guest /sbin/dhclient -6 __IFNAME__
+# Wait for DAD to complete
+guest while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
gout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
check [ "__ADDR6__" = "__HOST_ADDR6__" ]
diff --git a/test/passt/ndp b/test/passt/ndp
index 6bf8af3..516cd6b 100644
--- a/test/passt/ndp
+++ b/test/passt/ndp
@@ -16,13 +16,15 @@ htools ip jq sipcalc grep cut
test Interface name
gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-guest ip link set dev __IFNAME__ up && sleep 2
+guest ip link set dev __IFNAME__ up
+# Wait for SLAAC & DAD to complete
+guest while ! ip -j -6 addr show dev __IFNAME__ | jq -e '.[].addr_info.[] | select(.protocol == "kernel_ra")'; do sleep 0.1; done
hout HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
check [ -n "__IFNAME__" ]
test SLAAC: prefix
-gout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local] | .[0]'
-gout PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
+gout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.protocol == "kernel_ra") | .local + "/" + (.prefixlen | tostring)] | .[0]'
+gout PREFIX6 sipcalc __ADDR6__ | grep prefix | cut -d' ' -f4
hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
hout HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
check [ "__PREFIX6__" = "__HOST_PREFIX6__" ]
diff --git a/test/passt_in_ns/dhcp b/test/passt_in_ns/dhcp
new file mode 100644
index 0000000..a38a690
--- /dev/null
+++ b/test/passt_in_ns/dhcp
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+# for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+# for network namespace/tap device mode
+#
+# test/passt/dhcp - Check DHCP and DHCPv6 functionality in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools ip jq dhclient sed tr
+htools ip jq sed tr head
+
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
+test Interface name
+gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check [ -n "__IFNAME__" ]
+
+test DHCP: address
+guest /sbin/dhclient -4 __IFNAME__
+gout ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+hout HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
+check [ "__ADDR__" = "__HOST_ADDR__" ]
+
+test DHCP: route
+gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout HOST_GW ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check [ "__GW__" = "__HOST_GW__" ]
+
+test DHCP: MTU
+gout MTU ip -j link show | jq -rM '.[] | select(.ifname == "__IFNAME__").mtu'
+check [ __MTU__ = 65520 ]
+
+test DHCP: DNS
+gout DNS sed -n 's/^nameserver \([0-9]*\.\)\(.*\)/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+hout HOST_DNS sed -n 's/^nameserver \([0-9]*\.\)\(.*\)/\1\2/p' /etc/resolv.conf | head -n3 | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+check [ "__DNS__" = "__HOST_DNS__" ] || ( [ "__DNS__" = "__MAP_NS4__" ] && expr "__HOST_DNS__" : "127[.]" )
+
+# FQDNs should be terminated by dots, but the guest DHCP client might omit them:
+# strip them first
+test DHCP: search list
+gout SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+hout HOST_SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+check [ "__SEARCH__" = "__HOST_SEARCH__" ]
+
+test DHCPv6: address
+guest /sbin/dhclient -6 __IFNAME__
+# Wait for DAD to complete
+guest while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+gout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
+hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
+check [ "__ADDR6__" = "__HOST_ADDR6__" ]
+
+test DHCPv6: route
+gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check [ "__GW6__" = "__HOST_GW6__" ]
+
+# Strip interface specifier: interface names might differ between host and guest
+test DHCPv6: DNS
+gout DNS6 sed -n 's/^nameserver \([^:]*:\)\([^%]*\).*/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+hout HOST_DNS6 sed -n 's/^nameserver \([^:]*:\)\([^%]*\).*/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+check [ "__DNS6__" = "__HOST_DNS6__" ] || [ "__DNS6__" = "__MAP_NS6__" -a "__HOST_DNS6__" = "::1" ]
+
+test DHCPv6: search list
+gout SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+hout HOST_SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+check [ "__SEARCH6__" = "__HOST_SEARCH6__" ]
diff --git a/test/passt_in_ns/tcp b/test/passt_in_ns/tcp
index cdb7060..319880b 100644
--- a/test/passt_in_ns/tcp
+++ b/test/passt_in_ns/tcp
@@ -15,6 +15,11 @@ gtools socat ip jq
htools socat ip jq
nstools socat ip jq
+set MAP_HOST4 192.0.2.1
+set MAP_HOST6 2001:db8:9a55::1
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
set TEMP_BIG __STATEDIR__/test_big.bin
set TEMP_SMALL __STATEDIR__/test_small.bin
set TEMP_NS_BIG __STATEDIR__/test_ns_big.bin
@@ -27,7 +32,7 @@ host socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10001
guestw
guest cmp test_big.bin /root/big.bin
-test TCP/IPv4: host to ns: big transfer
+test TCP/IPv4: host to ns (spliced): big transfer
nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10002
@@ -36,16 +41,15 @@ check cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
test TCP/IPv4: guest to host: big transfer
hostb socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
-gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
sleep 1
-guest socat -u OPEN:/root/big.bin TCP4:__GW__:10003
+guest socat -u OPEN:/root/big.bin TCP4:__MAP_HOST4__:10003
hostw
check cmp __TEMP_BIG__ __BASEPATH__/big.bin
test TCP/IPv4: guest to ns: big transfer
nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
sleep 1
-guest socat -u OPEN:/root/big.bin TCP4:__GW__:10002
+guest socat -u OPEN:/root/big.bin TCP4:__MAP_NS4__:10002
nsw
check cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
@@ -59,7 +63,7 @@ check cmp __TEMP_BIG__ __BASEPATH__/big.bin
test TCP/IPv4: ns to host (via tap): big transfer
hostb socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
sleep 1
-ns socat -u OPEN:__BASEPATH__/big.bin TCP4:__GW__:10003
+ns socat -u OPEN:__BASEPATH__/big.bin TCP4:__MAP_HOST4__:10003
hostw
check cmp __TEMP_BIG__ __BASEPATH__/big.bin
@@ -86,7 +90,7 @@ host socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10001
guestw
guest cmp test_small.bin /root/small.bin
-test TCP/IPv4: host to ns: small transfer
+test TCP/IPv4: host to ns (spliced): small transfer
nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10002
@@ -95,16 +99,15 @@ check cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
test TCP/IPv4: guest to host: small transfer
hostb socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
-gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
sleep 1
-guest socat -u OPEN:/root/small.bin TCP4:__GW__:10003
+guest socat -u OPEN:/root/small.bin TCP4:__MAP_HOST4__:10003
hostw
check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
test TCP/IPv4: guest to ns: small transfer
nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
sleep 1
-guest socat -u OPEN:/root/small.bin TCP4:__GW__:10002
+guest socat -u OPEN:/root/small.bin TCP4:__MAP_NS4__:10002
nsw
check cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
@@ -118,7 +121,7 @@ check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
test TCP/IPv4: ns to host (via tap): small transfer
hostb socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
sleep 1
-ns socat -u OPEN:__BASEPATH__/small.bin TCP4:__GW__:10003
+ns socat -u OPEN:__BASEPATH__/small.bin TCP4:__MAP_HOST4__:10003
hostw
check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
@@ -143,7 +146,7 @@ host socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10001
guestw
guest cmp test_big.bin /root/big.bin
-test TCP/IPv6: host to ns: big transfer
+test TCP/IPv6: host to ns (spliced): big transfer
nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10002
@@ -152,17 +155,15 @@ check cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
test TCP/IPv6: guest to host: big transfer
hostb socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
-gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-guest socat -u OPEN:/root/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+guest socat -u OPEN:/root/big.bin TCP6:[__MAP_HOST6__]:10003
hostw
check cmp __TEMP_BIG__ __BASEPATH__/big.bin
test TCP/IPv6: guest to ns: big transfer
nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
sleep 1
-guest socat -u OPEN:/root/big.bin TCP6:[__GW6__%__IFNAME__]:10002
+guest socat -u OPEN:/root/big.bin TCP6:[__MAP_NS6__]:10002
nsw
check cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
@@ -175,9 +176,8 @@ check cmp __TEMP_BIG__ __BASEPATH__/big.bin
test TCP/IPv6: ns to host (via tap): big transfer
hostb socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
-nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-ns socat -u OPEN:__BASEPATH__/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+ns socat -u OPEN:__BASEPATH__/big.bin TCP6:[__MAP_HOST6__]:10003
hostw
check cmp __TEMP_BIG__ __BASEPATH__/big.bin
@@ -190,6 +190,7 @@ guest cmp test_big.bin /root/big.bin
test TCP/IPv6: ns to guest (using namespace address): big transfer
guestb socat -u TCP6-LISTEN:10001 OPEN:test_big.bin,create,trunc
+nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
nsout ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
sleep 1
ns socat -u OPEN:__BASEPATH__/big.bin TCP6:[__ADDR6__]:10001
@@ -203,7 +204,7 @@ host socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10001
guestw
guest cmp test_small.bin /root/small.bin
-test TCP/IPv6: host to ns: small transfer
+test TCP/IPv6: host to ns (spliced): small transfer
nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10002
@@ -212,17 +213,15 @@ check cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
test TCP/IPv6: guest to host: small transfer
hostb socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
-gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-guest socat -u OPEN:/root/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+guest socat -u OPEN:/root/small.bin TCP6:[__MAP_HOST6__]:10003
hostw
check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
test TCP/IPv6: guest to ns: small transfer
nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_SMALL__
sleep 1
-guest socat -u OPEN:/root/small.bin TCP6:[__GW6__%__IFNAME__]:10002
+guest socat -u OPEN:/root/small.bin TCP6:[__MAP_NS6__]:10002
nsw
check cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
@@ -235,9 +234,8 @@ check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
test TCP/IPv6: ns to host (via tap): small transfer
hostb socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
-nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-ns socat -u OPEN:__BASEPATH__/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+ns socat -u OPEN:__BASEPATH__/small.bin TCP6:[__MAP_HOST6__]:10003
hostw
check cmp __TEMP_SMALL__ __BASEPATH__/small.bin
diff --git a/test/passt_in_ns/udp b/test/passt_in_ns/udp
index 8a02513..791511c 100644
--- a/test/passt_in_ns/udp
+++ b/test/passt_in_ns/udp
@@ -15,6 +15,11 @@ gtools socat ip jq
nstools socat ip jq
htools socat ip jq
+set MAP_HOST4 192.0.2.1
+set MAP_HOST6 2001:db8:9a55::1
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
set TEMP __STATEDIR__/test.bin
set TEMP_NS __STATEDIR__/test_ns.bin
@@ -25,7 +30,7 @@ host socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10001,shut-null
guestw
guest cmp test.bin /root/medium.bin
-test UDP/IPv4: host to ns
+test UDP/IPv4: host to ns (recvmmsg/sendmmsg)
nsb socat -u UDP4-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10002,shut-null
@@ -34,16 +39,15 @@ check cmp __TEMP_NS__ __BASEPATH__/medium.bin
test UDP/IPv4: guest to host
hostb socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
-gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
sleep 1
-guest socat -u OPEN:/root/medium.bin UDP4:__GW__:10003,shut-null
+guest socat -u OPEN:/root/medium.bin UDP4:__MAP_HOST4__:10003,shut-null
hostw
check cmp __TEMP__ __BASEPATH__/medium.bin
test UDP/IPv4: guest to ns
nsb socat -u UDP4-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
sleep 1
-guest socat -u OPEN:/root/medium.bin UDP4:__GW__:10002,shut-null
+guest socat -u OPEN:/root/medium.bin UDP4:__MAP_NS4__:10002,shut-null
nsw
check cmp __TEMP_NS__ __BASEPATH__/medium.bin
@@ -57,7 +61,7 @@ check cmp __TEMP__ __BASEPATH__/medium.bin
test UDP/IPv4: ns to host (via tap)
hostb socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
sleep 1
-ns socat -u OPEN:__BASEPATH__/medium.bin UDP4:__GW__:10003,shut-null
+ns socat -u OPEN:__BASEPATH__/medium.bin UDP4:__MAP_HOST4__:10003,shut-null
hostw
check cmp __TEMP__ __BASEPATH__/medium.bin
@@ -84,7 +88,7 @@ host socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10001,shut-null
guestw
guest cmp test.bin /root/medium.bin
-test UDP/IPv6: host to ns
+test UDP/IPv6: host to ns (recvmmsg/sendmmsg)
nsb socat -u UDP6-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
sleep 1
host socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10002,shut-null
@@ -93,17 +97,15 @@ check cmp __TEMP_NS__ __BASEPATH__/medium.bin
test UDP/IPv6: guest to host
hostb socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
-gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-guest socat -u OPEN:/root/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+guest socat -u OPEN:/root/medium.bin UDP6:[__MAP_HOST6__]:10003,shut-null
hostw
check cmp __TEMP__ __BASEPATH__/medium.bin
test UDP/IPv6: guest to ns
nsb socat -u UDP6-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
sleep 1
-guest socat -u OPEN:/root/medium.bin UDP6:[__GW6__%__IFNAME__]:10002,shut-null
+guest socat -u OPEN:/root/medium.bin UDP6:[__MAP_NS6__]:10002,shut-null
nsw
check cmp __TEMP_NS__ __BASEPATH__/medium.bin
@@ -116,9 +118,8 @@ check cmp __TEMP__ __BASEPATH__/medium.bin
test UDP/IPv6: ns to host (via tap)
hostb socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
-nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
sleep 1
-ns socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+ns socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__MAP_HOST6__]:10003,shut-null
hostw
check cmp __TEMP__ __BASEPATH__/medium.bin
@@ -131,6 +132,7 @@ guest cmp test.bin /root/medium.bin
test UDP/IPv6: ns to guest (using namespace address)
guestb socat -u UDP6-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
nsout ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
sleep 1
ns socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__ADDR6__]:10001,shut-null
diff --git a/test/passt_vu b/test/passt_vu
new file mode 120000
index 0000000..22f1840
--- /dev/null
+++ b/test/passt_vu
@@ -0,0 +1 @@
+passt \ No newline at end of file
diff --git a/test/passt_vu_in_ns b/test/passt_vu_in_ns
new file mode 120000
index 0000000..3ff479e
--- /dev/null
+++ b/test/passt_vu_in_ns
@@ -0,0 +1 @@
+passt_in_ns \ No newline at end of file
diff --git a/test/pasta/dhcp b/test/pasta/dhcp
index 41556b8..d4f3ad5 100644
--- a/test/pasta/dhcp
+++ b/test/pasta/dhcp
@@ -35,6 +35,8 @@ check [ __MTU__ = 65520 ]
test DHCPv6: address
ns /sbin/dhclient -6 --no-pid __IFNAME__
+# Wait for DAD to complete
+ns while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
hout HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
nsout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
diff --git a/test/pasta/ndp b/test/pasta/ndp
index d45ff7b..952c1ea 100644
--- a/test/pasta/ndp
+++ b/test/pasta/ndp
@@ -18,11 +18,12 @@ test Interface name
nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
check [ -n "__IFNAME__" ]
ns ip link set dev __IFNAME__ up
-sleep 2
+# Wait for SLAAC & DAD to complete
+ns while ! ip -j -6 addr show dev __IFNAME__ | jq -e '.[].addr_info.[] | select(.protocol == "kernel_ra")'; do sleep 0.1; done
test SLAAC: prefix
-nsout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local] | .[0]'
-nsout PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
+nsout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.protocol == "kernel_ra") | .local + "/" + (.prefixlen | tostring)] | .[0]'
+nsout PREFIX6 sipcalc __ADDR6__ | grep prefix | cut -d' ' -f4
hout HOST_ADDR6 ip -j -6 addr show|jq -rM ['.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
hout HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
check [ "__PREFIX6__" = "__HOST_PREFIX6__" ]
diff --git a/test/pasta/tcp b/test/pasta/tcp
index 6ab18c5..53b6f25 100644
--- a/test/pasta/tcp
+++ b/test/pasta/tcp
@@ -19,8 +19,8 @@ set TEMP_NS_BIG __STATEDIR__/test_ns_big.bin
set TEMP_SMALL __STATEDIR__/test_small.bin
set TEMP_NS_SMALL __STATEDIR__/test_ns_small.bin
-test TCP/IPv4: host to ns: big transfer
-nsb socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_BIG__,create,trunc
+test TCP/IPv4: host to ns (spliced): big transfer
+nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
host socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10002
nsw
check cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
@@ -38,8 +38,8 @@ ns socat -u OPEN:__BASEPATH__/big.bin TCP4:__GW__:10003
hostw
check cmp __BASEPATH__/big.bin __TEMP_BIG__
-test TCP/IPv4: host to ns: small transfer
-nsb socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_SMALL__,create,trunc
+test TCP/IPv4: host to ns (spliced): small transfer
+nsb socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
host socat OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10002
nsw
check cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
@@ -57,8 +57,8 @@ ns socat -u OPEN:__BASEPATH__/small.bin TCP4:__GW__:10003
hostw
check cmp __BASEPATH__/small.bin __TEMP_SMALL__
-test TCP/IPv6: host to ns: big transfer
-nsb socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_BIG__,create,trunc
+test TCP/IPv6: host to ns (spliced): big transfer
+nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
host socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10002
nsw
check cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
@@ -77,8 +77,8 @@ ns socat -u OPEN:__BASEPATH__/big.bin TCP6:[__GW6__%__IFNAME__]:10003
hostw
check cmp __BASEPATH__/big.bin __TEMP_BIG__
-test TCP/IPv6: host to ns: small transfer
-nsb socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_SMALL__,create,trunc
+test TCP/IPv6: host to ns (spliced): small transfer
+nsb socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
host socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10002
nsw
check cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
diff --git a/test/pasta/udp b/test/pasta/udp
index 30e3a85..7734d02 100644
--- a/test/pasta/udp
+++ b/test/pasta/udp
@@ -17,8 +17,8 @@ htools dd socat ip jq
set TEMP __STATEDIR__/test.bin
set TEMP_NS __STATEDIR__/test_ns.bin
-test UDP/IPv4: host to ns
-nsb socat -u UDP4-LISTEN:10002,bind=127.0.0.1,null-eof OPEN:__TEMP_NS__,create,trunc
+test UDP/IPv4: host to ns (recvmmsg/sendmmsg)
+nsb socat -u UDP4-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
host socat OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10002,shut-null
nsw
check cmp __BASEPATH__/medium.bin __TEMP_NS__
@@ -37,8 +37,8 @@ ns socat -u OPEN:__BASEPATH__/medium.bin UDP4:__GW__:10003,shut-null
hostw
check cmp __BASEPATH__/medium.bin __TEMP__
-test UDP/IPv6: host to ns
-nsb socat -u UDP6-LISTEN:10002,bind=[::1],null-eof OPEN:__TEMP_NS__,create,trunc
+test UDP/IPv6: host to ns (recvmmsg/sendmmsg)
+nsb socat -u UDP6-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
host socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10002,shut-null
nsw
check cmp __BASEPATH__/medium.bin __TEMP_NS__
diff --git a/test/pasta_options/log_to_file b/test/pasta_options/log_to_file
index fe50e50..3ead06c 100644
--- a/test/pasta_options/log_to_file
+++ b/test/pasta_options/log_to_file
@@ -19,7 +19,7 @@ sleep 1
endef
def flood_log_client
-host tcp_crr --nolog -P 10001 -C 10002 -6 -c -H ::1
+host tcp_crr --nolog -l1 -P 10001 -C 10002 -6 -c -H ::1
endef
def check_log_size_mountns
@@ -42,7 +42,7 @@ pout PID2 echo $!
check head -1 __LOG_FILE__ | grep '^pasta .* [(]__PID2__[)]$'
test Maximum log size
-passtb ./pasta --config-net -d -f -l __LOG_FILE__ --log-size $((100 * 1024)) -- sh -c 'while true; do tcp_crr --nolog -P 10001 -C 10002 -6; done'
+passtb ./pasta --config-net -d -f -l __LOG_FILE__ --log-size $((100 * 1024)) -- sh -c 'while true; do tcp_crr --nolog -l1 -P 10001 -C 10002 -6; done'
sleep 1
flood_log_client
diff --git a/test/perf/passt_tcp b/test/perf/passt_tcp
index 14343cb..5978c49 100644
--- a/test/perf/passt_tcp
+++ b/test/perf/passt_tcp
@@ -15,6 +15,9 @@ gtools /sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr # From neper
nstools /sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr
htools bc head sed seq
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
test passt: throughput and latency
guest /sbin/sysctl -w net.core.rmem_max=536870912
@@ -29,8 +32,6 @@ ns /sbin/sysctl -w net.ipv4.tcp_rmem="4096 524288 134217728"
ns /sbin/sysctl -w net.ipv4.tcp_wmem="4096 524288 134217728"
ns /sbin/sysctl -w net.ipv4.tcp_timestamps=0
-gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
hout FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
@@ -38,7 +39,7 @@ hout FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sy
hout FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
set THREADS 4
-set TIME 10
+set TIME 1
set OMIT 0.1
set OPTS -Z -P __THREADS__ -l 1M -O__OMIT__
@@ -54,16 +55,16 @@ iperf3s ns 10002
bw -
bw -
guest ip link set dev __IFNAME__ mtu 1280
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -w 4M
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 4M
bw __BW__ 1.2 1.5
guest ip link set dev __IFNAME__ mtu 1500
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -w 4M
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 4M
bw __BW__ 1.6 1.8
guest ip link set dev __IFNAME__ mtu 9000
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -w 8M
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 8M
bw __BW__ 4.0 5.0
guest ip link set dev __IFNAME__ mtu 65520
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -w 16M
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 16M
bw __BW__ 7.0 8.0
iperf3k ns
@@ -75,7 +76,7 @@ lat -
lat -
lat -
nsb tcp_rr --nolog -6
-gout LAT tcp_rr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT tcp_rr --nolog -l1 -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
tl TCP CRR latency over IPv6: guest to host
@@ -85,33 +86,39 @@ lat -
lat -
lat -
nsb tcp_crr --nolog -6
-gout LAT tcp_crr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT tcp_crr --nolog -l1 -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 500 400
tr TCP throughput over IPv4: guest to host
iperf3s ns 10002
guest ip link set dev __IFNAME__ mtu 256
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 1M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 1M
bw __BW__ 0.2 0.3
guest ip link set dev __IFNAME__ mtu 576
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 1M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 1M
bw __BW__ 0.5 0.8
guest ip link set dev __IFNAME__ mtu 1280
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 4M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 4M
bw __BW__ 1.2 1.5
guest ip link set dev __IFNAME__ mtu 1500
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 4M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 4M
bw __BW__ 1.6 1.8
guest ip link set dev __IFNAME__ mtu 9000
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 8M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 8M
bw __BW__ 4.0 5.0
guest ip link set dev __IFNAME__ mtu 65520
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -w 16M
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 16M
bw __BW__ 7.0 8.0
iperf3k ns
+# Reducing MTU below 1280 deconfigures IPv6, get our address back
+guest dhclient -6 -x
+guest dhclient -6 __IFNAME__
+# Wait for DAD to complete
+guest while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+
tl TCP RR latency over IPv4: guest to host
lat -
lat -
@@ -119,7 +126,7 @@ lat -
lat -
lat -
nsb tcp_rr --nolog -4
-gout LAT tcp_rr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT tcp_rr --nolog -l1 -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
tl TCP CRR latency over IPv4: guest to host
@@ -129,7 +136,7 @@ lat -
lat -
lat -
nsb tcp_crr --nolog -4
-gout LAT tcp_crr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT tcp_crr --nolog -l1 -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 500 400
tr TCP throughput over IPv6: host to guest
@@ -153,7 +160,7 @@ lat -
lat -
guestb tcp_rr --nolog -P 10001 -C 10011 -6
sleep 1
-nsout LAT tcp_rr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
tl TCP CRR latency over IPv6: host to guest
@@ -164,7 +171,7 @@ lat -
lat -
guestb tcp_crr --nolog -P 10001 -C 10011 -6
sleep 1
-nsout LAT tcp_crr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 500 350
@@ -189,7 +196,7 @@ lat -
lat -
guestb tcp_rr --nolog -P 10001 -C 10011 -4
sleep 1
-nsout LAT tcp_rr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
tl TCP CRR latency over IPv6: host to guest
@@ -200,7 +207,7 @@ lat -
lat -
guestb tcp_crr --nolog -P 10001 -C 10011 -4
sleep 1
-nsout LAT tcp_crr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 500 300
te
diff --git a/test/perf/passt_udp b/test/perf/passt_udp
index 8919280..4c66c41 100644
--- a/test/perf/passt_udp
+++ b/test/perf/passt_udp
@@ -15,6 +15,9 @@ gtools /sbin/sysctl ip jq nproc sleep iperf3 udp_rr # From neper
nstools ip jq sleep iperf3 udp_rr
htools bc head sed
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
test passt: throughput and latency
guest /sbin/sysctl -w net.core.rmem_max=16777216
@@ -22,16 +25,12 @@ guest /sbin/sysctl -w net.core.wmem_max=16777216
guest /sbin/sysctl -w net.core.rmem_default=16777216
guest /sbin/sysctl -w net.core.wmem_default=16777216
-gout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-
hout FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
hout FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
hout FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
set THREADS 2
-set TIME 10
+set TIME 1
set OPTS -u -P __THREADS__ --pacing-timer 1000
info Throughput in Gbps, latency in µs, __THREADS__ threads at __FREQ__ GHz
@@ -46,13 +45,13 @@ iperf3s ns 10002
bw -
bw -
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -b 3G -l 1232
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 3G -l 1232
bw __BW__ 0.8 1.2
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -b 4G -l 1452
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 4G -l 1452
bw __BW__ 1.0 1.5
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -b 8G -l 8952
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 8G -l 8952
bw __BW__ 4.0 5.0
-iperf3 BW guest __GW6__%__IFNAME__ 10002 __TIME__ __OPTS__ -b 15G -l 64372
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 15G -l 64372
bw __BW__ 4.0 5.0
iperf3k ns
@@ -64,7 +63,7 @@ lat -
lat -
lat -
nsb udp_rr --nolog -6
-gout LAT udp_rr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT udp_rr --nolog -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
@@ -72,17 +71,17 @@ tr UDP throughput over IPv4: guest to host
iperf3s ns 10002
# (datagram size) = (packet size) - 28: 20 bytes of IPv4 header, 8 of UDP header
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 1G -l 228
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 1G -l 228
bw __BW__ 0.0 0.0
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 2G -l 548
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 2G -l 548
bw __BW__ 0.4 0.6
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 3G -l 1252
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 3G -l 1252
bw __BW__ 0.8 1.2
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 4G -l 1472
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 4G -l 1472
bw __BW__ 1.0 1.5
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 8G -l 8972
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 8G -l 8972
bw __BW__ 4.0 5.0
-iperf3 BW guest __GW__ 10002 __TIME__ __OPTS__ -b 15G -l 65492
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 15G -l 65492
bw __BW__ 4.0 5.0
iperf3k ns
@@ -94,7 +93,7 @@ lat -
lat -
lat -
nsb udp_rr --nolog -4
-gout LAT udp_rr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+gout LAT udp_rr --nolog -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
lat __LAT__ 200 150
diff --git a/test/perf/passt_vu_tcp b/test/perf/passt_vu_tcp
new file mode 100644
index 0000000..c4409b9
--- /dev/null
+++ b/test/perf/passt_vu_tcp
@@ -0,0 +1,211 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+# for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+# for network namespace/tap device mode
+#
+# test/perf/passt_vu_tcp - Check TCP performance in passt vhost-user mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools /sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr # From neper
+nstools /sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr
+htools bc head sed seq
+
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
+test passt: throughput and latency
+
+guest /sbin/sysctl -w net.core.rmem_max=536870912
+guest /sbin/sysctl -w net.core.wmem_max=536870912
+guest /sbin/sysctl -w net.core.rmem_default=33554432
+guest /sbin/sysctl -w net.core.wmem_default=33554432
+guest /sbin/sysctl -w net.ipv4.tcp_rmem="4096 131072 268435456"
+guest /sbin/sysctl -w net.ipv4.tcp_wmem="4096 131072 268435456"
+guest /sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+ns /sbin/sysctl -w net.ipv4.tcp_rmem="4096 524288 134217728"
+ns /sbin/sysctl -w net.ipv4.tcp_wmem="4096 524288 134217728"
+ns /sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+gout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+
+hout FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+set THREADS 6
+set TIME 2
+set OMIT 0.1
+set OPTS -Z -P __THREADS__ -O__OMIT__ -N
+
+info Throughput in Gbps, latency in µs, __THREADS__ threads at __FREQ__ GHz
+report passt_vu tcp __THREADS__ __FREQ__
+
+th MTU 256B 576B 1280B 1500B 9000B 65520B
+
+
+tr TCP throughput over IPv6: guest to host
+iperf3s ns 10002
+
+bw -
+bw -
+guest ip link set dev __IFNAME__ mtu 1280
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 16M -l 1M
+bw __BW__ 1.2 1.5
+guest ip link set dev __IFNAME__ mtu 1500
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 32M -l 1M
+bw __BW__ 1.6 1.8
+guest ip link set dev __IFNAME__ mtu 9000
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 64M -l 1M
+bw __BW__ 4.0 5.0
+guest ip link set dev __IFNAME__ mtu 65520
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -w 64M -l 1M
+bw __BW__ 7.0 8.0
+
+iperf3k ns
+
+tl TCP RR latency over IPv6: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb tcp_rr --nolog -6
+gout LAT tcp_rr --nolog -l1 -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+tl TCP CRR latency over IPv6: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb tcp_crr --nolog -6
+gout LAT tcp_crr --nolog -l1 -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 500 400
+
+tr TCP throughput over IPv4: guest to host
+iperf3s ns 10002
+
+guest ip link set dev __IFNAME__ mtu 256
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 2M -l 1M
+bw __BW__ 0.2 0.3
+guest ip link set dev __IFNAME__ mtu 576
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 4M -l 1M
+bw __BW__ 0.5 0.8
+guest ip link set dev __IFNAME__ mtu 1280
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 8M -l 1M
+bw __BW__ 1.2 1.5
+guest ip link set dev __IFNAME__ mtu 1500
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 16M -l 1M
+bw __BW__ 1.6 1.8
+guest ip link set dev __IFNAME__ mtu 9000
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 64M -l 1M
+bw __BW__ 4.0 5.0
+guest ip link set dev __IFNAME__ mtu 65520
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -w 64M -l 1M
+bw __BW__ 7.0 8.0
+
+iperf3k ns
+
+# Reducing MTU below 1280 deconfigures IPv6, get our address back
+guest dhclient -6 -x
+guest dhclient -6 __IFNAME__
+
+tl TCP RR latency over IPv4: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb tcp_rr --nolog -4
+gout LAT tcp_rr --nolog -l1 -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+tl TCP CRR latency over IPv4: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb tcp_crr --nolog -4
+gout LAT tcp_crr --nolog -l1 -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 500 400
+
+tr TCP throughput over IPv6: host to guest
+iperf3s guest 10001
+
+bw -
+bw -
+bw -
+bw -
+bw -
+iperf3 BW ns ::1 10001 __TIME__ __OPTS__ -w 256M -l 16k
+bw __BW__ 6.0 6.8
+
+iperf3k guest
+
+tl TCP RR latency over IPv6: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb tcp_rr --nolog -P 10001 -C 10011 -6
+sleep 1
+nsout LAT tcp_rr --nolog -l1 -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+tl TCP CRR latency over IPv6: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb tcp_crr --nolog -P 10001 -C 10011 -6
+sleep 1
+nsout LAT tcp_crr --nolog -l1 -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 500 350
+
+
+tr TCP throughput over IPv4: host to guest
+iperf3s guest 10001
+
+bw -
+bw -
+bw -
+bw -
+bw -
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -w 256M -l 16k
+bw __BW__ 6.0 6.8
+
+iperf3k guest
+
+tl TCP RR latency over IPv4: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb tcp_rr --nolog -P 10001 -C 10011 -4
+sleep 1
+nsout LAT tcp_rr --nolog -l1 -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+tl TCP CRR latency over IPv6: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb tcp_crr --nolog -P 10001 -C 10011 -4
+sleep 1
+nsout LAT tcp_crr --nolog -l1 -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 500 300
+
+te
diff --git a/test/perf/passt_vu_udp b/test/perf/passt_vu_udp
new file mode 100644
index 0000000..943ac11
--- /dev/null
+++ b/test/perf/passt_vu_udp
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+# for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+# for network namespace/tap device mode
+#
+# test/perf/passt_vu_udp - Check UDP performance in passt vhost-user mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools /sbin/sysctl ip jq nproc sleep iperf3 udp_rr # From neper
+nstools ip jq sleep iperf3 udp_rr
+htools bc head sed
+
+set MAP_NS4 192.0.2.2
+set MAP_NS6 2001:db8:9a55::2
+
+test passt: throughput and latency
+
+guest /sbin/sysctl -w net.core.rmem_max=16777216
+guest /sbin/sysctl -w net.core.wmem_max=16777216
+guest /sbin/sysctl -w net.core.rmem_default=16777216
+guest /sbin/sysctl -w net.core.wmem_default=16777216
+
+hout FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+set THREADS 2
+set TIME 1
+set OPTS -u -P __THREADS__ --pacing-timer 1000
+
+info Throughput in Gbps, latency in µs, __THREADS__ threads at __FREQ__ GHz
+
+report passt_vu udp __THREADS__ __FREQ__
+
+th pktlen 256B 576B 1280B 1500B 9000B 65520B
+
+tr UDP throughput over IPv6: guest to host
+iperf3s ns 10002
+# (datagram size) = (packet size) - 48: 40 bytes of IPv6 header, 8 of UDP header
+
+bw -
+bw -
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 3G -l 1232
+bw __BW__ 0.8 1.2
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 4G -l 1452
+bw __BW__ 1.0 1.5
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 10G -l 8952
+bw __BW__ 4.0 5.0
+iperf3 BW guest __MAP_NS6__ 10002 __TIME__ __OPTS__ -b 20G -l 64372
+bw __BW__ 4.0 5.0
+
+iperf3k ns
+
+tl UDP RR latency over IPv6: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb udp_rr --nolog -6
+gout LAT udp_rr --nolog -6 -c -H __MAP_NS6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+
+tr UDP throughput over IPv4: guest to host
+iperf3s ns 10002
+# (datagram size) = (packet size) - 28: 20 bytes of IPv4 header, 8 of UDP header
+
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 1G -l 228
+bw __BW__ 0.0 0.0
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 2G -l 548
+bw __BW__ 0.4 0.6
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 3G -l 1252
+bw __BW__ 0.8 1.2
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 4G -l 1472
+bw __BW__ 1.0 1.5
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 10G -l 8972
+bw __BW__ 4.0 5.0
+iperf3 BW guest __MAP_NS4__ 10002 __TIME__ __OPTS__ -b 20G -l 65492
+bw __BW__ 4.0 5.0
+
+iperf3k ns
+
+tl UDP RR latency over IPv4: guest to host
+lat -
+lat -
+lat -
+lat -
+lat -
+nsb udp_rr --nolog -4
+gout LAT udp_rr --nolog -4 -c -H __MAP_NS4__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+
+tr UDP throughput over IPv6: host to guest
+iperf3s guest 10001
+# (datagram size) = (packet size) - 48: 40 bytes of IPv6 header, 8 of UDP header
+
+bw -
+bw -
+iperf3 BW ns ::1 10001 __TIME__ __OPTS__ -b 3G -l 1232
+bw __BW__ 0.8 1.2
+iperf3 BW ns ::1 10001 __TIME__ __OPTS__ -b 4G -l 1452
+bw __BW__ 1.0 1.5
+iperf3 BW ns ::1 10001 __TIME__ __OPTS__ -b 10G -l 8952
+bw __BW__ 3.0 4.0
+iperf3 BW ns ::1 10001 __TIME__ __OPTS__ -b 20G -l 64372
+bw __BW__ 3.0 4.0
+
+iperf3k guest
+
+tl UDP RR latency over IPv6: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb udp_rr --nolog -P 10001 -C 10011 -6
+sleep 1
+nsout LAT udp_rr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+
+tr UDP throughput over IPv4: host to guest
+iperf3s guest 10001
+# (datagram size) = (packet size) - 28: 20 bytes of IPv4 header, 8 of UDP header
+
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 1G -l 228
+bw __BW__ 0.0 0.0
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 2G -l 548
+bw __BW__ 0.4 0.6
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 3G -l 1252
+bw __BW__ 0.8 1.2
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 4G -l 1472
+bw __BW__ 1.0 1.5
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 10G -l 8972
+bw __BW__ 3.0 4.0
+iperf3 BW ns 127.0.0.1 10001 __TIME__ __OPTS__ -b 20G -l 65492
+bw __BW__ 3.0 4.0
+
+iperf3k guest
+
+tl UDP RR latency over IPv4: host to guest
+lat -
+lat -
+lat -
+lat -
+lat -
+guestb udp_rr --nolog -P 10001 -C 10011 -4
+sleep 1
+nsout LAT udp_rr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat __LAT__ 200 150
+
+te
diff --git a/test/perf/pasta_tcp b/test/perf/pasta_tcp
index 8d2f911..bc0de3c 100644
--- a/test/perf/pasta_tcp
+++ b/test/perf/pasta_tcp
@@ -14,6 +14,9 @@
htools head ip seq bc sleep iperf3 tcp_rr tcp_crr jq sed
nstools /sbin/sysctl nproc ip seq sleep iperf3 tcp_rr tcp_crr jq sed
+set MAP_HOST4 192.0.2.1
+set MAP_HOST6 2001:db8:9a55::1
+
test pasta: throughput and latency (local connections)
ns /sbin/sysctl -w net.ipv4.tcp_rmem="131072 524288 134217728"
@@ -22,7 +25,7 @@ ns /sbin/sysctl -w net.ipv4.tcp_timestamps=0
set THREADS 4
-set TIME 10
+set TIME 1
set OMIT 0.1
set OPTS -Z -w 4M -l 1M -P __THREADS__ -O__OMIT__
@@ -46,13 +49,13 @@ iperf3k host
tl TCP RR latency over IPv6: ns to host
hostb tcp_rr --nolog -P 10003 -C 10013 -6
-nsout LAT tcp_rr --nolog -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 150 100
tl TCP CRR latency over IPv6: ns to host
hostb tcp_crr --nolog -P 10003 -C 10013 -6
-nsout LAT tcp_crr --nolog -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 500 350
@@ -67,13 +70,13 @@ iperf3k host
tl TCP RR latency over IPv4: ns to host
hostb tcp_rr --nolog -P 10003 -C 10013 -4
-nsout LAT tcp_rr --nolog -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 150 100
tl TCP CRR latency over IPv4: ns to host
hostb tcp_crr --nolog -P 10003 -C 10013 -4
-nsout LAT tcp_crr --nolog -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 500 350
@@ -87,13 +90,13 @@ iperf3k ns
tl TCP RR latency over IPv6: host to ns
nsb tcp_rr --nolog -P 10002 -C 10012 -6
-hout LAT tcp_rr --nolog -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_rr --nolog -l1 -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 150 100
tl TCP CRR latency over IPv6: host to ns
nsb tcp_crr --nolog -P 10002 -C 10012 -6
-hout LAT tcp_crr --nolog -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_crr --nolog -l1 -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 1000 700
@@ -108,13 +111,13 @@ iperf3k ns
tl TCP RR latency over IPv4: host to ns
nsb tcp_rr --nolog -P 10002 -C 10012 -4
-hout LAT tcp_rr --nolog -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_rr --nolog -l1 -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 150 100
tl TCP CRR latency over IPv4: host to ns
nsb tcp_crr --nolog -P 10002 -C 10012 -4
-hout LAT tcp_crr --nolog -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_crr --nolog -l1 -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 1000 700
@@ -122,8 +125,6 @@ te
test pasta: throughput and latency (connections via tap)
-nsout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-nsout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
set THREADS 2
set OPTS -Z -P __THREADS__ -i1 -O__OMIT__
@@ -137,16 +138,16 @@ tr TCP throughput over IPv6: ns to host
iperf3s host 10003
ns ip link set dev __IFNAME__ mtu 1500
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -w 512k
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -w 512k
bw __BW__ 0.2 0.4
ns ip link set dev __IFNAME__ mtu 4000
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -w 1M
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -w 1M
bw __BW__ 0.3 0.5
ns ip link set dev __IFNAME__ mtu 16384
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -w 8M
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -w 8M
bw __BW__ 1.5 2.0
ns ip link set dev __IFNAME__ mtu 65520
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -w 8M
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -w 8M
bw __BW__ 2.0 2.5
iperf3k host
@@ -156,7 +157,7 @@ lat -
lat -
lat -
hostb tcp_rr --nolog -P 10003 -C 10013 -6
-nsout LAT tcp_rr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10003 -C 10013 -6 -c -H __MAP_HOST6__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 150 100
@@ -165,7 +166,7 @@ lat -
lat -
lat -
hostb tcp_crr --nolog -P 10003 -C 10013 -6
-nsout LAT tcp_crr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10003 -C 10013 -6 -c -H __MAP_HOST6__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 1500 500
@@ -174,16 +175,16 @@ tr TCP throughput over IPv4: ns to host
iperf3s host 10003
ns ip link set dev __IFNAME__ mtu 1500
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -w 512k
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -w 512k
bw __BW__ 0.2 0.4
ns ip link set dev __IFNAME__ mtu 4000
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -w 1M
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -w 1M
bw __BW__ 0.3 0.5
ns ip link set dev __IFNAME__ mtu 16384
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -w 8M
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -w 8M
bw __BW__ 1.5 2.0
ns ip link set dev __IFNAME__ mtu 65520
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -w 8M
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -w 8M
bw __BW__ 2.0 2.5
iperf3k host
@@ -193,7 +194,7 @@ lat -
lat -
lat -
hostb tcp_rr --nolog -P 10003 -C 10013 -4
-nsout LAT tcp_rr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_rr --nolog -l1 -P 10003 -C 10013 -4 -c -H __MAP_HOST4__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 150 100
@@ -202,7 +203,7 @@ lat -
lat -
lat -
hostb tcp_crr --nolog -P 10003 -C 10013 -4
-nsout LAT tcp_crr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT tcp_crr --nolog -l1 -P 10003 -C 10013 -4 -c -H __MAP_HOST4__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 1500 500
@@ -210,7 +211,7 @@ tr TCP throughput over IPv6: host to ns
iperf3s ns 10002
nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-nsout ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+nsout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local] | .[0]'
bw -
bw -
bw -
@@ -224,7 +225,7 @@ lat -
lat -
lat -
nsb tcp_rr --nolog -P 10002 -C 10012 -6
-hout LAT tcp_rr --nolog -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_rr --nolog -l1 -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 150 100
@@ -234,7 +235,7 @@ lat -
lat -
sleep 1
nsb tcp_crr --nolog -P 10002 -C 10012 -6
-hout LAT tcp_crr --nolog -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_crr --nolog -l1 -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 5000 10000
@@ -256,7 +257,7 @@ lat -
lat -
lat -
nsb tcp_rr --nolog -P 10002 -C 10012 -4
-hout LAT tcp_rr --nolog -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_rr --nolog -l1 -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 150 100
@@ -266,7 +267,7 @@ lat -
lat -
sleep 1
nsb tcp_crr --nolog -P 10002 -C 10012 -4
-hout LAT tcp_crr --nolog -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hout LAT tcp_crr --nolog -l1 -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
nsw
lat __LAT__ 5000 10000
diff --git a/test/perf/pasta_udp b/test/perf/pasta_udp
index 6acbfd3..ab2f3e8 100644
--- a/test/perf/pasta_udp
+++ b/test/perf/pasta_udp
@@ -14,6 +14,9 @@
htools bc head ip sleep iperf3 udp_rr jq sed
nstools ip sleep iperf3 udp_rr jq sed
+set MAP_HOST4 192.0.2.1
+set MAP_HOST6 2001:db8:9a55::1
+
test pasta: throughput and latency (local traffic)
hout FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
@@ -21,7 +24,7 @@ hout FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sy
hout FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
set THREADS 1
-set TIME 10
+set TIME 1
set OPTS -u -P __THREADS__
info Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz
@@ -133,8 +136,6 @@ te
test pasta: throughput and latency (traffic via tap)
-nsout GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-nsout GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
info Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz
@@ -146,13 +147,13 @@ tr UDP throughput over IPv6: ns to host
iperf3s host 10003
# (datagram size) = (packet size) - 48: 40 bytes of IPv6 header, 8 of UDP header
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -b 8G -l 1472
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -b 8G -l 1472
bw __BW__ 0.3 0.5
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -b 12G -l 3972
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -b 12G -l 3972
bw __BW__ 0.5 0.8
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -b 20G -l 16356
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -b 20G -l 16356
bw __BW__ 3.0 4.0
-iperf3 BW ns __GW6__%__IFNAME__ 10003 __TIME__ __OPTS__ -b 30G -l 65472
+iperf3 BW ns __MAP_HOST6__ 10003 __TIME__ __OPTS__ -b 30G -l 65472
bw __BW__ 6.0 7.0
iperf3k host
@@ -162,7 +163,7 @@ lat -
lat -
lat -
hostb udp_rr --nolog -P 10003 -C 10013 -6
-nsout LAT udp_rr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT udp_rr --nolog -P 10003 -C 10013 -6 -c -H __MAP_HOST6__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 200 150
@@ -171,13 +172,13 @@ tr UDP throughput over IPv4: ns to host
iperf3s host 10003
# (datagram size) = (packet size) - 28: 20 bytes of IPv4 header, 8 of UDP header
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -b 8G -l 1472
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -b 8G -l 1472
bw __BW__ 0.3 0.5
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -b 12G -l 3972
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -b 12G -l 3972
bw __BW__ 0.5 0.8
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -b 20G -l 16356
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -b 20G -l 16356
bw __BW__ 3.0 4.0
-iperf3 BW ns __GW__ 10003 __TIME__ __OPTS__ -b 30G -l 65492
+iperf3 BW ns __MAP_HOST4__ 10003 __TIME__ __OPTS__ -b 30G -l 65492
bw __BW__ 6.0 7.0
iperf3k host
@@ -187,7 +188,7 @@ lat -
lat -
lat -
hostb udp_rr --nolog -P 10003 -C 10013 -4
-nsout LAT udp_rr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsout LAT udp_rr --nolog -P 10003 -C 10013 -4 -c -H __MAP_HOST4__ | sed -n 's/^throughput=\(.*\)/\1/p'
hostw
lat __LAT__ 200 150
@@ -195,7 +196,7 @@ tr UDP throughput over IPv6: host to ns
iperf3s ns 10002
nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-nsout ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+nsout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local] | .[0]'
iperf3 BW host __ADDR6__ 10002 __TIME__ __OPTS__ -b 8G -l 1472
bw __BW__ 0.3 0.5
iperf3 BW host __ADDR6__ 10002 __TIME__ __OPTS__ -b 12G -l 3972
diff --git a/test/run b/test/run
index 3b37663..f188d8e 100755
--- a/test/run
+++ b/test/run
@@ -38,6 +38,9 @@ TRACE=${TRACE:-0}
# If set, tell passt and pasta to take packet captures
PCAP=${PCAP:-0}
+# Custom kernel to boot guests with, if given
+KERNEL=${KERNEL:-"/boot/vmlinuz-$(uname -r)"}
+
COMMIT="$(git log --oneline --no-decorate -1)"
. lib/util
@@ -90,6 +93,7 @@ run() {
test memory/passt
teardown memory
+ VHOST_USER=0
setup passt
test passt/ndp
test passt/dhcp
@@ -101,7 +105,7 @@ run() {
VALGRIND=1
setup passt_in_ns
test passt/ndp
- test passt/dhcp
+ test passt_in_ns/dhcp
test passt_in_ns/icmp
test passt_in_ns/tcp
test passt_in_ns/udp
@@ -112,10 +116,25 @@ run() {
test two_guests/basic
teardown two_guests
+ VHOST_USER=1
+ setup passt_in_ns
+ test passt_vu/ndp
+ test passt_vu_in_ns/dhcp
+ test passt_vu_in_ns/icmp
+ test passt_vu_in_ns/tcp
+ test passt_vu_in_ns/udp
+ test passt_vu_in_ns/shutdown
+ teardown passt_in_ns
+
+ setup two_guests
+ test two_guests_vu/basic
+ teardown two_guests
+
VALGRIND=0
+ VHOST_USER=0
setup passt_in_ns
test passt/ndp
- test passt/dhcp
+ test passt_in_ns/dhcp
test perf/passt_tcp
test perf/passt_udp
test perf/pasta_tcp
@@ -123,6 +142,15 @@ run() {
test passt_in_ns/shutdown
teardown passt_in_ns
+ VHOST_USER=1
+ setup passt_in_ns
+ test passt_vu/ndp
+ test passt_vu_in_ns/dhcp
+ test perf/passt_vu_tcp
+ test perf/passt_vu_udp
+ test passt_vu_in_ns/shutdown
+ teardown passt_in_ns
+
# TODO: Make those faster by at least pre-installing gcc and make on
# non-x86 images, then re-enable.
skip_distro() {
diff --git a/test/two_guests/basic b/test/two_guests/basic
index 4d49e85..e2338ff 100644
--- a/test/two_guests/basic
+++ b/test/two_guests/basic
@@ -36,9 +36,13 @@ check [ "__ADDR2__" = "__HOST_ADDR__" ]
test DHCPv6: addresses
# Link is up now, wait for DAD to complete
-sleep 2
+guest1 while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+guest2 while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
guest1 /sbin/dhclient -6 __IFNAME1__
guest2 /sbin/dhclient -6 __IFNAME2__
+# Wait for DAD to complete on the DHCP address
+guest1 while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+guest2 while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
g1out ADDR1_6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
g2out ADDR2_6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME2__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
@@ -48,33 +52,33 @@ check [ "__ADDR2_6__" = "__HOST_ADDR6__" ]
test TCP/IPv4: guest 1 > guest 2
g1out GW1 ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
guest2b socat -u TCP4-LISTEN:10004 OPEN:msg,create,trunc
+sleep 1
guest1 echo "Hello_from_guest_1" | socat -u STDIN TCP4:__GW1__:10004
guest2w
-sleep 1
g2out MSG2 cat msg
check [ "__MSG2__" = "Hello_from_guest_1" ]
test TCP/IPv6: guest 2 > guest 1
g2out GW2_6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
guest1b socat -u TCP6-LISTEN:10001 OPEN:msg,create,trunc
+sleep 1
guest2 echo "Hello_from_guest_2" | socat -u STDIN TCP6:[__GW2_6__%__IFNAME2__]:10001
guest1w
-sleep 1
g1out MSG1 cat msg
check [ "__MSG1__" = "Hello_from_guest_2" ]
test UDP/IPv4: guest 1 > guest 2
guest2b socat -u TCP4-LISTEN:10004 OPEN:msg,create,trunc
+sleep 1
guest1 echo "Hello_from_guest_1" | socat -u STDIN TCP4:__GW1__:10004
guest2w
-sleep 1
g2out MSG2 cat msg
check [ "__MSG2__" = "Hello_from_guest_1" ]
test UDP/IPv6: guest 2 > guest 1
guest1b socat -u TCP6-LISTEN:10001 OPEN:msg,create,trunc
+sleep 1
guest2 echo "Hello_from_guest_2" | socat -u STDIN TCP6:[__GW2_6__%__IFNAME2__]:10001
guest1w
-sleep 1
g1out MSG1 cat msg
check [ "__MSG1__" = "Hello_from_guest_2" ]
diff --git a/test/two_guests_vu b/test/two_guests_vu
new file mode 120000
index 0000000..a8648fc
--- /dev/null
+++ b/test/two_guests_vu
@@ -0,0 +1 @@
+two_guests \ No newline at end of file
diff --git a/test/valgrind.supp b/test/valgrind.supp
index a158394..735b5f6 100644
--- a/test/valgrind.supp
+++ b/test/valgrind.supp
@@ -6,3 +6,12 @@
...
fun:tcp_sock_consume
}
+
+# same as above, for architectures with the recv() system call (at least i686):
+{
+ passt_recv_MSG_TRUNC_into_NULL_buffer
+ Memcheck:Param
+ socketcall.recv(buf)
+ ...
+ fun:tcp_sock_consume
+}
diff --git a/udp.c b/udp.c
index 7731257..923cc38 100644
--- a/udp.c
+++ b/udp.c
@@ -109,8 +109,8 @@
#include "pcap.h"
#include "log.h"
#include "flow_table.h"
-
-#define UDP_MAX_FRAMES 32 /* max # of frames to receive at once */
+#include "udp_internal.h"
+#include "udp_vu.h"
/* "Spliced" sockets indexed by bound port (host order) */
static int udp_splice_ns [IP_VERSIONS][NUM_PORTS];
@@ -118,20 +118,8 @@ static int udp_splice_init[IP_VERSIONS][NUM_PORTS];
/* Static buffers */
-/**
- * struct udp_payload_t - UDP header and data for inbound messages
- * @uh: UDP header
- * @data: UDP data
- */
-static struct udp_payload_t {
- struct udphdr uh;
- char data[USHRT_MAX - sizeof(struct udphdr)];
-#ifdef __AVX2__
-} __attribute__ ((packed, aligned(32)))
-#else
-} __attribute__ ((packed, aligned(__alignof__(unsigned int))))
-#endif
-udp_payload[UDP_MAX_FRAMES];
+/* UDP header and data for inbound messages */
+static struct udp_payload_t udp_payload[UDP_MAX_FRAMES];
/* Ethernet header for IPv4 frames */
static struct ethhdr udp4_eth_hdr;
@@ -169,17 +157,16 @@ udp_meta[UDP_MAX_FRAMES];
* @UDP_NUM_IOVS the number of entries in the iovec array
*/
enum udp_iov_idx {
- UDP_IOV_TAP = 0,
- UDP_IOV_ETH = 1,
- UDP_IOV_IP = 2,
- UDP_IOV_PAYLOAD = 3,
- UDP_NUM_IOVS
+ UDP_IOV_TAP,
+ UDP_IOV_ETH,
+ UDP_IOV_IP,
+ UDP_IOV_PAYLOAD,
+ UDP_NUM_IOVS,
};
/* IOVs and msghdr arrays for receiving datagrams from sockets */
static struct iovec udp_iov_recv [UDP_MAX_FRAMES];
-static struct mmsghdr udp4_mh_recv [UDP_MAX_FRAMES];
-static struct mmsghdr udp6_mh_recv [UDP_MAX_FRAMES];
+static struct mmsghdr udp_mh_recv [UDP_MAX_FRAMES];
/* IOVs and msghdr arrays for sending "spliced" datagrams to sockets */
static union sockaddr_inany udp_splice_to;
@@ -222,6 +209,7 @@ void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s)
static void udp_iov_init_one(const struct ctx *c, size_t i)
{
struct udp_payload_t *payload = &udp_payload[i];
+ struct msghdr *mh = &udp_mh_recv[i].msg_hdr;
struct udp_meta_t *meta = &udp_meta[i];
struct iovec *siov = &udp_iov_recv[i];
struct iovec *tiov = udp_l2_iov[i];
@@ -236,27 +224,10 @@ static void udp_iov_init_one(const struct ctx *c, size_t i)
tiov[UDP_IOV_TAP] = tap_hdr_iov(c, &meta->taph);
tiov[UDP_IOV_PAYLOAD].iov_base = payload;
- /* It's useful to have separate msghdr arrays for receiving. Otherwise,
- * an IPv4 recv() will alter msg_namelen, so we'd have to reset it every
- * time or risk truncating the address on future IPv6 recv()s.
- */
- if (c->ifi4) {
- struct msghdr *mh = &udp4_mh_recv[i].msg_hdr;
-
- mh->msg_name = &meta->s_in;
- mh->msg_namelen = sizeof(struct sockaddr_in);
- mh->msg_iov = siov;
- mh->msg_iovlen = 1;
- }
-
- if (c->ifi6) {
- struct msghdr *mh = &udp6_mh_recv[i].msg_hdr;
-
- mh->msg_name = &meta->s_in;
- mh->msg_namelen = sizeof(struct sockaddr_in6);
- mh->msg_iov = siov;
- mh->msg_iovlen = 1;
- }
+ mh->msg_name = &meta->s_in;
+ mh->msg_namelen = sizeof(meta->s_in);
+ mh->msg_iov = siov;
+ mh->msg_iovlen = 1;
}
/**
@@ -311,17 +282,19 @@ static void udp_splice_send(const struct ctx *c, size_t start, size_t n,
/**
* udp_update_hdr4() - Update headers for one IPv4 datagram
- * @ip4h: Pre-filled IPv4 header (except for tot_len and saddr)
- * @bp: Pointer to udp_payload_t to update
- * @toside: Flowside for destination side
- * @dlen: Length of UDP payload
+ * @ip4h: Pre-filled IPv4 header (except for tot_len and saddr)
+ * @bp: Pointer to udp_payload_t to update
+ * @toside: Flowside for destination side
+ * @dlen: Length of UDP payload
+ * @no_udp_csum: Do not set UDP checksum
*
* Return: size of IPv4 payload (UDP header + data)
*/
-static size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp,
- const struct flowside *toside, size_t dlen)
+size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp,
+ const struct flowside *toside, size_t dlen,
+ bool no_udp_csum)
{
- const struct in_addr *src = inany_v4(&toside->faddr);
+ const struct in_addr *src = inany_v4(&toside->oaddr);
const struct in_addr *dst = inany_v4(&toside->eaddr);
size_t l4len = dlen + sizeof(bp->uh);
size_t l3len = l4len + sizeof(*ip4h);
@@ -333,39 +306,64 @@ static size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp,
ip4h->saddr = src->s_addr;
ip4h->check = csum_ip4_header(l3len, IPPROTO_UDP, *src, *dst);
- bp->uh.source = htons(toside->fport);
+ bp->uh.source = htons(toside->oport);
bp->uh.dest = htons(toside->eport);
bp->uh.len = htons(l4len);
- csum_udp4(&bp->uh, *src, *dst, bp->data, dlen);
+ if (no_udp_csum) {
+ bp->uh.check = 0;
+ } else {
+ const struct iovec iov = {
+ .iov_base = bp->data,
+ .iov_len = dlen
+ };
+ struct iov_tail data = IOV_TAIL(&iov, 1, 0);
+ csum_udp4(&bp->uh, *src, *dst, &data);
+ }
return l4len;
}
/**
* udp_update_hdr6() - Update headers for one IPv6 datagram
- * @ip6h: Pre-filled IPv6 header (except for payload_len and addresses)
- * @bp: Pointer to udp_payload_t to update
- * @toside: Flowside for destination side
- * @dlen: Length of UDP payload
+ * @ip6h: Pre-filled IPv6 header (except for payload_len and
+ * addresses)
+ * @bp: Pointer to udp_payload_t to update
+ * @toside: Flowside for destination side
+ * @dlen: Length of UDP payload
+ * @no_udp_csum: Do not set UDP checksum
*
* Return: size of IPv6 payload (UDP header + data)
*/
-static size_t udp_update_hdr6(struct ipv6hdr *ip6h, struct udp_payload_t *bp,
- const struct flowside *toside, size_t dlen)
+size_t udp_update_hdr6(struct ipv6hdr *ip6h, struct udp_payload_t *bp,
+ const struct flowside *toside, size_t dlen,
+ bool no_udp_csum)
{
uint16_t l4len = dlen + sizeof(bp->uh);
ip6h->payload_len = htons(l4len);
ip6h->daddr = toside->eaddr.a6;
- ip6h->saddr = toside->faddr.a6;
+ ip6h->saddr = toside->oaddr.a6;
ip6h->version = 6;
ip6h->nexthdr = IPPROTO_UDP;
ip6h->hop_limit = 255;
- bp->uh.source = htons(toside->fport);
+ bp->uh.source = htons(toside->oport);
bp->uh.dest = htons(toside->eport);
bp->uh.len = ip6h->payload_len;
- csum_udp6(&bp->uh, &toside->faddr.a6, &toside->eaddr.a6, bp->data, dlen);
+ if (no_udp_csum) {
+ /* 0 is an invalid checksum for UDP IPv6 and dropped by
+ * the kernel stack, even if the checksum is disabled by virtio
+ * flags. We need to put any non-zero value here.
+ */
+ bp->uh.check = 0xffff;
+ } else {
+ const struct iovec iov = {
+ .iov_base = bp->data,
+ .iov_len = dlen
+ };
+ struct iov_tail data = IOV_TAIL(&iov, 1, 0);
+ csum_udp6(&bp->uh, &toside->oaddr.a6, &toside->eaddr.a6, &data);
+ }
return l4len;
}
@@ -375,23 +373,27 @@ static size_t udp_update_hdr6(struct ipv6hdr *ip6h, struct udp_payload_t *bp,
* @mmh: Receiving mmsghdr array
* @idx: Index of the datagram to prepare
* @toside: Flowside for destination side
+ * @no_udp_csum: Do not set UDP checksum
*/
-static void udp_tap_prepare(const struct mmsghdr *mmh, unsigned idx,
- const struct flowside *toside)
+static void udp_tap_prepare(const struct mmsghdr *mmh,
+ unsigned idx, const struct flowside *toside,
+ bool no_udp_csum)
{
struct iovec (*tap_iov)[UDP_NUM_IOVS] = &udp_l2_iov[idx];
struct udp_payload_t *bp = &udp_payload[idx];
struct udp_meta_t *bm = &udp_meta[idx];
size_t l4len;
- if (!inany_v4(&toside->eaddr) || !inany_v4(&toside->faddr)) {
- l4len = udp_update_hdr6(&bm->ip6h, bp, toside, mmh[idx].msg_len);
+ if (!inany_v4(&toside->eaddr) || !inany_v4(&toside->oaddr)) {
+ l4len = udp_update_hdr6(&bm->ip6h, bp, toside,
+ mmh[idx].msg_len, no_udp_csum);
tap_hdr_update(&bm->taph, l4len + sizeof(bm->ip6h) +
sizeof(udp6_eth_hdr));
(*tap_iov)[UDP_IOV_ETH] = IOV_OF_LVALUE(udp6_eth_hdr);
(*tap_iov)[UDP_IOV_IP] = IOV_OF_LVALUE(bm->ip6h);
} else {
- l4len = udp_update_hdr4(&bm->ip4h, bp, toside, mmh[idx].msg_len);
+ l4len = udp_update_hdr4(&bm->ip4h, bp, toside,
+ mmh[idx].msg_len, no_udp_csum);
tap_hdr_update(&bm->taph, l4len + sizeof(bm->ip4h) +
sizeof(udp4_eth_hdr));
(*tap_iov)[UDP_IOV_ETH] = IOV_OF_LVALUE(udp4_eth_hdr);
@@ -404,11 +406,12 @@ static void udp_tap_prepare(const struct mmsghdr *mmh, unsigned idx,
* udp_sock_recverr() - Receive and clear an error from a socket
* @s: Socket to receive from
*
- * Return: true if errors received and processed, false if no more errors
+ * Return: 1 if error received and processed, 0 if no more errors in queue, < 0
+ * if there was an error reading the queue
*
* #syscalls recvmsg
*/
-static bool udp_sock_recverr(int s)
+static int udp_sock_recverr(int s)
{
const struct sock_extended_err *ee;
const struct cmsghdr *hdr;
@@ -425,14 +428,16 @@ static bool udp_sock_recverr(int s)
rc = recvmsg(s, &mh, MSG_ERRQUEUE);
if (rc < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK)
- err_perror("Failed to read error queue");
- return false;
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return 0;
+
+ err_perror("UDP: Failed to read error queue");
+ return -1;
}
if (!(mh.msg_flags & MSG_ERRQUEUE)) {
err("Missing MSG_ERRQUEUE flag reading error queue");
- return false;
+ return -1;
}
hdr = CMSG_FIRSTHDR(&mh);
@@ -441,16 +446,63 @@ static bool udp_sock_recverr(int s)
(hdr->cmsg_level == IPPROTO_IPV6 &&
hdr->cmsg_type == IPV6_RECVERR))) {
err("Unexpected cmsg reading error queue");
- return false;
+ return -1;
}
ee = (const struct sock_extended_err *)CMSG_DATA(hdr);
/* TODO: When possible propagate and otherwise handle errors */
debug("%s error on UDP socket %i: %s",
- str_ee_origin(ee), s, strerror(ee->ee_errno));
+ str_ee_origin(ee), s, strerror_(ee->ee_errno));
+
+ return 1;
+}
- return true;
+/**
+ * udp_sock_errs() - Process errors on a socket
+ * @c: Execution context
+ * @s: Socket to receive from
+ * @events: epoll events bitmap
+ *
+ * Return: Number of errors handled, or < 0 if we have an unrecoverable error
+ */
+int udp_sock_errs(const struct ctx *c, int s, uint32_t events)
+{
+ unsigned n_err = 0;
+ socklen_t errlen;
+ int rc, err;
+
+ ASSERT(!c->no_udp);
+
+ if (!(events & EPOLLERR))
+ return 0; /* Nothing to do */
+
+ /* Empty the error queue */
+ while ((rc = udp_sock_recverr(s)) > 0)
+ n_err += rc;
+
+ if (rc < 0)
+ return -1; /* error reading error, unrecoverable */
+
+ errlen = sizeof(err);
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0 ||
+ errlen != sizeof(err)) {
+ err_perror("Error reading SO_ERROR");
+ return -1; /* error reading error, unrecoverable */
+ }
+
+ if (err) {
+ debug("Unqueued error on UDP socket %i: %s", s, strerror_(err));
+ n_err++;
+ }
+
+ if (!n_err) {
+ /* EPOLLERR, but no errors to clear !? */
+ err("EPOLLERR event without reported errors on socket %i", s);
+ return -1; /* no way to clear, unrecoverable */
+ }
+
+ return n_err;
}
/**
@@ -460,7 +512,9 @@ static bool udp_sock_recverr(int s)
* @events: epoll events bitmap
* @mmh mmsghdr array to receive into
*
- * #syscalls recvmmsg
+ * Return: Number of datagrams received
+ *
+ * #syscalls recvmmsg arm:recvmmsg_time64 i686:recvmmsg_time64
*/
static int udp_sock_recv(const struct ctx *c, int s, uint32_t events,
struct mmsghdr *mmh)
@@ -476,12 +530,6 @@ static int udp_sock_recv(const struct ctx *c, int s, uint32_t events,
ASSERT(!c->no_udp);
- /* Clear any errors first */
- if (events & EPOLLERR) {
- while (udp_sock_recverr(s))
- ;
- }
-
if (!(events & EPOLLIN))
return 0;
@@ -495,7 +543,7 @@ static int udp_sock_recv(const struct ctx *c, int s, uint32_t events,
}
/**
- * udp_listen_sock_handler() - Handle new data from socket
+ * udp_buf_listen_sock_handler() - Handle new data from socket
* @c: Execution context
* @ref: epoll reference
* @events: epoll events bitmap
@@ -503,13 +551,21 @@ static int udp_sock_recv(const struct ctx *c, int s, uint32_t events,
*
* #syscalls recvmmsg
*/
-void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
- uint32_t events, const struct timespec *now)
+static void udp_buf_listen_sock_handler(const struct ctx *c,
+ union epoll_ref ref, uint32_t events,
+ const struct timespec *now)
{
- struct mmsghdr *mmh_recv = ref.udp.v6 ? udp6_mh_recv : udp4_mh_recv;
+ const socklen_t sasize = sizeof(udp_meta[0].s_in);
int n, i;
- if ((n = udp_sock_recv(c, ref.fd, events, mmh_recv)) <= 0)
+ if (udp_sock_errs(c, ref.fd, events) < 0) {
+ err("UDP: Unrecoverable error on listening socket:"
+ " (%s port %hu)", pif_name(ref.udp.pif), ref.udp.port);
+ /* FIXME: what now? close/re-open socket? */
+ return;
+ }
+
+ if ((n = udp_sock_recv(c, ref.fd, events, udp_mh_recv)) <= 0)
return;
/* We divide datagrams into batches based on how we need to send them,
@@ -518,6 +574,7 @@ void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
* populate it one entry *ahead* of the loop counter.
*/
udp_meta[0].tosidx = udp_flow_from_sock(c, ref, &udp_meta[0].s_in, now);
+ udp_mh_recv[0].msg_hdr.msg_namelen = sasize;
for (i = 0; i < n; ) {
flow_sidx_t batchsidx = udp_meta[i].tosidx;
uint8_t batchpif = pif_at_sidx(batchsidx);
@@ -525,10 +582,11 @@ void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
do {
if (pif_is_socket(batchpif)) {
- udp_splice_prepare(mmh_recv, i);
+ udp_splice_prepare(udp_mh_recv, i);
} else if (batchpif == PIF_TAP) {
- udp_tap_prepare(mmh_recv, i,
- flowside_at_sidx(batchsidx));
+ udp_tap_prepare(udp_mh_recv, i,
+ flowside_at_sidx(batchsidx),
+ false);
}
if (++i >= n)
@@ -537,6 +595,7 @@ void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
udp_meta[i].tosidx = udp_flow_from_sock(c, ref,
&udp_meta[i].s_in,
now);
+ udp_mh_recv[i].msg_hdr.msg_namelen = sasize;
} while (flow_sidx_eq(udp_meta[i].tosidx, batchsidx));
if (pif_is_socket(batchpif)) {
@@ -561,7 +620,26 @@ void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
}
/**
- * udp_reply_sock_handler() - Handle new data from flow specific socket
+ * udp_listen_sock_handler() - Handle new data from socket
+ * @c: Execution context
+ * @ref: epoll reference
+ * @events: epoll events bitmap
+ * @now: Current timestamp
+ */
+void udp_listen_sock_handler(const struct ctx *c,
+ union epoll_ref ref, uint32_t events,
+ const struct timespec *now)
+{
+ if (c->mode == MODE_VU) {
+ udp_vu_listen_sock_handler(c, ref, events, now);
+ return;
+ }
+
+ udp_buf_listen_sock_handler(c, ref, events, now);
+}
+
+/**
+ * udp_buf_reply_sock_handler() - Handle new data from flow specific socket
* @c: Execution context
* @ref: epoll reference
* @events: epoll events bitmap
@@ -569,22 +647,28 @@ void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
*
* #syscalls recvmmsg
*/
-void udp_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
- uint32_t events, const struct timespec *now)
+static void udp_buf_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events,
+ const struct timespec *now)
{
- const struct flowside *fromside = flowside_at_sidx(ref.flowside);
flow_sidx_t tosidx = flow_sidx_opposite(ref.flowside);
const struct flowside *toside = flowside_at_sidx(tosidx);
struct udp_flow *uflow = udp_at_sidx(ref.flowside);
- int from_s = uflow->s[ref.flowside.sidei];
- bool v6 = !inany_v4(&fromside->eaddr);
- struct mmsghdr *mmh_recv = v6 ? udp6_mh_recv : udp4_mh_recv;
uint8_t topif = pif_at_sidx(tosidx);
- int n, i;
+ int n, i, from_s;
ASSERT(!c->no_udp && uflow);
- if ((n = udp_sock_recv(c, from_s, events, mmh_recv)) <= 0)
+ from_s = uflow->s[ref.flowside.sidei];
+
+ if (udp_sock_errs(c, from_s, events) < 0) {
+ flow_err(uflow, "Unrecoverable error on reply socket");
+ flow_err_details(uflow);
+ udp_flow_close(c, uflow);
+ return;
+ }
+
+ if ((n = udp_sock_recv(c, from_s, events, udp_mh_recv)) <= 0)
return;
flow_trace(uflow, "Received %d datagrams on reply socket", n);
@@ -592,9 +676,11 @@ void udp_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
for (i = 0; i < n; i++) {
if (pif_is_socket(topif))
- udp_splice_prepare(mmh_recv, i);
+ udp_splice_prepare(udp_mh_recv, i);
else if (topif == PIF_TAP)
- udp_tap_prepare(mmh_recv, i, toside);
+ udp_tap_prepare(udp_mh_recv, i, toside, false);
+ /* Restore sockaddr length clobbered by recvmsg() */
+ udp_mh_recv[i].msg_hdr.msg_namelen = sizeof(udp_meta[i].s_in);
}
if (pif_is_socket(topif)) {
@@ -610,6 +696,24 @@ void udp_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
}
/**
+ * udp_reply_sock_handler() - Handle new data from flow specific socket
+ * @c: Execution context
+ * @ref: epoll reference
+ * @events: epoll events bitmap
+ * @now: Current timestamp
+ */
+void udp_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events, const struct timespec *now)
+{
+ if (c->mode == MODE_VU) {
+ udp_vu_reply_sock_handler(c, ref, events, now);
+ return;
+ }
+
+ udp_buf_reply_sock_handler(c, ref, events, now);
+}
+
+/**
* udp_tap_handler() - Handle packets from tap
* @c: Execution context
* @pif: pif on which the packet is arriving
@@ -719,55 +823,62 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
* udp_sock_init() - Initialise listening sockets for a given port
* @c: Execution context
* @ns: In pasta mode, if set, bind with loopback address in namespace
- * @af: Address family to select a specific IP version, or AF_UNSPEC
* @addr: Pointer to address for binding, NULL if not configured
* @ifname: Name of interface to bind to, NULL if not configured
* @port: Port, host order
*
* Return: 0 on (partial) success, negative error code on (complete) failure
*/
-int udp_sock_init(const struct ctx *c, int ns, sa_family_t af,
- const void *addr, const char *ifname, in_port_t port)
+int udp_sock_init(const struct ctx *c, int ns, const union inany_addr *addr,
+ const char *ifname, in_port_t port)
{
- union udp_listen_epoll_ref uref = { .port = port };
- int s, r4 = FD_REF_MAX + 1, r6 = FD_REF_MAX + 1;
+ union udp_listen_epoll_ref uref = {
+ .pif = ns ? PIF_SPLICE : PIF_HOST,
+ .port = port,
+ };
+ int r4 = FD_REF_MAX + 1, r6 = FD_REF_MAX + 1;
ASSERT(!c->no_udp);
- if (ns)
- uref.pif = PIF_SPLICE;
- else
- uref.pif = PIF_HOST;
+ if (!addr && c->ifi4 && c->ifi6 && !ns) {
+ int s;
- if ((af == AF_INET || af == AF_UNSPEC) && c->ifi4) {
- uref.v6 = 0;
+ /* Attempt to get a dual stack socket */
+ s = pif_sock_l4(c, EPOLL_TYPE_UDP_LISTEN, PIF_HOST,
+ NULL, ifname, port, uref.u32);
+ udp_splice_init[V4][port] = s < 0 ? -1 : s;
+ udp_splice_init[V6][port] = s < 0 ? -1 : s;
+ if (IN_INTERVAL(0, FD_REF_MAX, s))
+ return 0;
+ }
+ if ((!addr || inany_v4(addr)) && c->ifi4) {
if (!ns) {
- r4 = s = sock_l4(c, AF_INET, EPOLL_TYPE_UDP_LISTEN,
- addr, ifname, port, uref.u32);
+ r4 = pif_sock_l4(c, EPOLL_TYPE_UDP_LISTEN, PIF_HOST,
+ addr ? addr : &inany_any4, ifname,
+ port, uref.u32);
- udp_splice_init[V4][port] = s < 0 ? -1 : s;
+ udp_splice_init[V4][port] = r4 < 0 ? -1 : r4;
} else {
- r4 = s = sock_l4(c, AF_INET, EPOLL_TYPE_UDP_LISTEN,
- &in4addr_loopback,
- ifname, port, uref.u32);
- udp_splice_ns[V4][port] = s < 0 ? -1 : s;
+ r4 = pif_sock_l4(c, EPOLL_TYPE_UDP_LISTEN, PIF_SPLICE,
+ &inany_loopback4, ifname,
+ port, uref.u32);
+ udp_splice_ns[V4][port] = r4 < 0 ? -1 : r4;
}
}
- if ((af == AF_INET6 || af == AF_UNSPEC) && c->ifi6) {
- uref.v6 = 1;
-
+ if ((!addr || !inany_v4(addr)) && c->ifi6) {
if (!ns) {
- r6 = s = sock_l4(c, AF_INET6, EPOLL_TYPE_UDP_LISTEN,
- addr, ifname, port, uref.u32);
+ r6 = pif_sock_l4(c, EPOLL_TYPE_UDP_LISTEN, PIF_HOST,
+ addr ? addr : &inany_any6, ifname,
+ port, uref.u32);
- udp_splice_init[V6][port] = s < 0 ? -1 : s;
+ udp_splice_init[V6][port] = r6 < 0 ? -1 : r6;
} else {
- r6 = s = sock_l4(c, AF_INET6, EPOLL_TYPE_UDP_LISTEN,
- &in6addr_loopback,
- ifname, port, uref.u32);
- udp_splice_ns[V6][port] = s < 0 ? -1 : s;
+ r6 = pif_sock_l4(c, EPOLL_TYPE_UDP_LISTEN, PIF_SPLICE,
+ &inany_loopback6, ifname,
+ port, uref.u32);
+ udp_splice_ns[V6][port] = r6 < 0 ? -1 : r6;
}
}
@@ -834,7 +945,7 @@ static void udp_port_rebind(struct ctx *c, bool outbound)
if ((c->ifi4 && socks[V4][port] == -1) ||
(c->ifi6 && socks[V6][port] == -1))
- udp_sock_init(c, outbound, AF_UNSPEC, NULL, NULL, port);
+ udp_sock_init(c, outbound, NULL, NULL, port);
}
}
diff --git a/udp.h b/udp.h
index fb42e1c..de2df6d 100644
--- a/udp.h
+++ b/udp.h
@@ -16,8 +16,8 @@ void udp_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
int udp_tap_handler(const struct ctx *c, uint8_t pif,
sa_family_t af, const void *saddr, const void *daddr,
const struct pool *p, int idx, const struct timespec *now);
-int udp_sock_init(const struct ctx *c, int ns, sa_family_t af,
- const void *addr, const char *ifname, in_port_t port);
+int udp_sock_init(const struct ctx *c, int ns, const union inany_addr *addr,
+ const char *ifname, in_port_t port);
int udp_init(struct ctx *c);
void udp_timer(struct ctx *c, const struct timespec *now);
void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s);
@@ -26,14 +26,12 @@ void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s);
* union udp_listen_epoll_ref - epoll reference for "listening" UDP sockets
* @port: Source port for connected sockets, bound port otherwise
* @pif: pif for this socket
- * @v6: Set for IPv6 sockets or connections
* @u32: Opaque u32 value of reference
*/
union udp_listen_epoll_ref {
struct {
in_port_t port;
uint8_t pif;
- bool v6:1;
};
uint32_t u32;
};
diff --git a/udp_flow.c b/udp_flow.c
index 8b25ad1..9fd7d06 100644
--- a/udp_flow.c
+++ b/udp_flow.c
@@ -8,6 +8,7 @@
#include <errno.h>
#include <fcntl.h>
#include <sys/uio.h>
+#include <unistd.h>
#include "util.h"
#include "passt.h"
@@ -38,8 +39,11 @@ struct udp_flow *udp_at_sidx(flow_sidx_t sidx)
* @c: Execution context
* @uflow: UDP flow
*/
-static void udp_flow_close(const struct ctx *c, struct udp_flow *uflow)
+void udp_flow_close(const struct ctx *c, struct udp_flow *uflow)
{
+ if (uflow->closed)
+ return; /* Nothing to do */
+
if (uflow->s[INISIDE] >= 0) {
/* The listening socket needs to stay in epoll */
close(uflow->s[INISIDE]);
@@ -55,6 +59,8 @@ static void udp_flow_close(const struct ctx *c, struct udp_flow *uflow)
flow_hash_remove(c, FLOW_SIDX(uflow, INISIDE));
if (!pif_is_socket(uflow->f.pif[TGTSIDE]))
flow_hash_remove(c, FLOW_SIDX(uflow, TGTSIDE));
+
+ uflow->closed = true;
}
/**
@@ -69,16 +75,10 @@ static void udp_flow_close(const struct ctx *c, struct udp_flow *uflow)
static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow,
int s_ini, const struct timespec *now)
{
- const struct flowside *ini = &flow->f.side[INISIDE];
struct udp_flow *uflow = NULL;
const struct flowside *tgt;
uint8_t tgtpif;
- if (!inany_is_unicast(&ini->eaddr) || ini->eport == 0) {
- flow_trace(flow, "Invalid endpoint to initiate UDP flow");
- goto cancel;
- }
-
if (!(tgt = flow_target(c, flow, IPPROTO_UDP)))
goto cancel;
tgtpif = flow->f.pif[TGTSIDE];
@@ -95,7 +95,7 @@ static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow,
if (uflow->s[INISIDE] < 0) {
flow_err(uflow,
"Couldn't duplicate listening socket: %s",
- strerror(errno));
+ strerror_(errno));
goto cancel;
}
}
@@ -115,14 +115,14 @@ static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow,
if (uflow->s[TGTSIDE] < 0) {
flow_dbg(uflow,
"Couldn't open socket for spliced flow: %s",
- strerror(errno));
+ strerror_(errno));
goto cancel;
}
if (flowside_connect(c, uflow->s[TGTSIDE], tgtpif, tgt) < 0) {
flow_dbg(uflow,
"Couldn't connect flow socket: %s",
- strerror(errno));
+ strerror_(errno));
goto cancel;
}
@@ -144,7 +144,7 @@ static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow,
} else if (errno != EAGAIN) {
flow_err(uflow,
"Unexpected error discarding datagrams: %s",
- strerror(errno));
+ strerror_(errno));
}
}
@@ -174,7 +174,7 @@ cancel:
* @s_in: Source socket address, filled in by recvmmsg()
* @now: Timestamp
*
- * #syscalls fcntl
+ * #syscalls fcntl arm:fcntl64 ppc64:fcntl64 i686:fcntl64
*
* Return: sidx for the destination side of the flow for this packet, or
* FLOW_SIDX_NONE if we couldn't find or create a flow.
@@ -183,6 +183,7 @@ flow_sidx_t udp_flow_from_sock(const struct ctx *c, union epoll_ref ref,
const union sockaddr_inany *s_in,
const struct timespec *now)
{
+ const struct flowside *ini;
struct udp_flow *uflow;
union flow *flow;
flow_sidx_t sidx;
@@ -204,7 +205,19 @@ flow_sidx_t udp_flow_from_sock(const struct ctx *c, union epoll_ref ref,
return FLOW_SIDX_NONE;
}
- flow_initiate_sa(flow, ref.udp.pif, s_in, ref.udp.port);
+ ini = flow_initiate_sa(flow, ref.udp.pif, s_in, ref.udp.port);
+
+ if (!inany_is_unicast(&ini->eaddr) ||
+ ini->eport == 0 || ini->oport == 0) {
+ /* In principle ini->oddr also must be specified, but when we've
+ * been initiated from a socket bound to 0.0.0.0 or ::, we don't
+ * know our address, so we have to leave it unpopulated.
+ */
+ flow_err(flow, "Invalid endpoint on UDP recvfrom()");
+ flow_alloc_cancel(flow);
+ return FLOW_SIDX_NONE;
+ }
+
return udp_flow_new(c, flow, ref.fd, now);
}
@@ -227,6 +240,7 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
in_port_t srcport, in_port_t dstport,
const struct timespec *now)
{
+ const struct flowside *ini;
struct udp_flow *uflow;
union flow *flow;
flow_sidx_t sidx;
@@ -250,12 +264,31 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
return FLOW_SIDX_NONE;
}
- flow_initiate_af(flow, PIF_TAP, af, saddr, srcport, daddr, dstport);
+ ini = flow_initiate_af(flow, PIF_TAP, af, saddr, srcport,
+ daddr, dstport);
+
+ if (inany_is_unspecified(&ini->eaddr) || ini->eport == 0 ||
+ inany_is_unspecified(&ini->oaddr) || ini->oport == 0) {
+ flow_dbg(flow, "Invalid endpoint on UDP packet");
+ flow_alloc_cancel(flow);
+ return FLOW_SIDX_NONE;
+ }
return udp_flow_new(c, flow, -1, now);
}
/**
+ * udp_flow_defer() - Deferred per-flow handling (clean up aborted flows)
+ * @uflow: Flow to handle
+ *
+ * Return: true if the connection is ready to free, false otherwise
+ */
+bool udp_flow_defer(const struct udp_flow *uflow)
+{
+ return uflow->closed;
+}
+
+/**
* udp_flow_timer() - Handler for timed events related to a given flow
* @c: Execution context
* @uflow: UDP flow
diff --git a/udp_flow.h b/udp_flow.h
index 12ddf03..9a1b059 100644
--- a/udp_flow.h
+++ b/udp_flow.h
@@ -10,6 +10,7 @@
/**
* struct udp - Descriptor for a flow of UDP packets
* @f: Generic flow information
+ * @closed: Flow is already closed
* @ts: Activity timestamp
* @s: Socket fd (or -1) for each side of the flow
*/
@@ -17,6 +18,7 @@ struct udp_flow {
/* Must be first element */
struct flow_common f;
+ bool closed :1;
time_t ts;
int s[SIDES];
};
@@ -30,6 +32,8 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
const void *saddr, const void *daddr,
in_port_t srcport, in_port_t dstport,
const struct timespec *now);
+void udp_flow_close(const struct ctx *c, struct udp_flow *uflow);
+bool udp_flow_defer(const struct udp_flow *uflow);
bool udp_flow_timer(const struct ctx *c, struct udp_flow *uflow,
const struct timespec *now);
diff --git a/udp_internal.h b/udp_internal.h
new file mode 100644
index 0000000..cc80e30
--- /dev/null
+++ b/udp_internal.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (c) 2021 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef UDP_INTERNAL_H
+#define UDP_INTERNAL_H
+
+#include "tap.h" /* needed by udp_meta_t */
+
+#define UDP_MAX_FRAMES 32 /* max # of frames to receive at once */
+
+/**
+ * struct udp_payload_t - UDP header and data for inbound messages
+ * @uh: UDP header
+ * @data: UDP data
+ */
+struct udp_payload_t {
+ struct udphdr uh;
+ char data[USHRT_MAX - sizeof(struct udphdr)];
+#ifdef __AVX2__
+} __attribute__ ((packed, aligned(32)));
+#else
+} __attribute__ ((packed, aligned(__alignof__(unsigned int))));
+#endif
+
+size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp,
+ const struct flowside *toside, size_t dlen,
+ bool no_udp_csum);
+size_t udp_update_hdr6(struct ipv6hdr *ip6h, struct udp_payload_t *bp,
+ const struct flowside *toside, size_t dlen,
+ bool no_udp_csum);
+int udp_sock_errs(const struct ctx *c, int s, uint32_t events);
+#endif /* UDP_INTERNAL_H */
diff --git a/udp_vu.c b/udp_vu.c
new file mode 100644
index 0000000..4123510
--- /dev/null
+++ b/udp_vu.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* udp_vu.c - UDP L2 vhost-user management functions
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#include <unistd.h>
+#include <assert.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/uio.h>
+#include <linux/virtio_net.h>
+
+#include "checksum.h"
+#include "util.h"
+#include "ip.h"
+#include "siphash.h"
+#include "inany.h"
+#include "passt.h"
+#include "pcap.h"
+#include "log.h"
+#include "vhost_user.h"
+#include "udp_internal.h"
+#include "flow.h"
+#include "flow_table.h"
+#include "udp_flow.h"
+#include "udp_vu.h"
+#include "vu_common.h"
+
+static struct iovec iov_vu [VIRTQUEUE_MAX_SIZE];
+static struct vu_virtq_element elem [VIRTQUEUE_MAX_SIZE];
+
+/**
+ * udp_vu_hdrlen() - return the size of the header in level 2 frame (UDP)
+ * @v6: Set for IPv6 packet
+ *
+ * Return: Return the size of the header
+ */
+static size_t udp_vu_hdrlen(bool v6)
+{
+ size_t hdrlen;
+
+ hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) +
+ sizeof(struct ethhdr) + sizeof(struct udphdr);
+
+ if (v6)
+ hdrlen += sizeof(struct ipv6hdr);
+ else
+ hdrlen += sizeof(struct iphdr);
+
+ return hdrlen;
+}
+
+/**
+ * udp_vu_sock_info() - get socket information
+ * @s: Socket to get information from
+ * @s_in: Socket address (output)
+ *
+ * Return: 0 if socket address can be read, -1 otherwise
+ */
+static int udp_vu_sock_info(int s, union sockaddr_inany *s_in)
+{
+ struct msghdr msg = {
+ .msg_name = s_in,
+ .msg_namelen = sizeof(union sockaddr_inany),
+ };
+
+ return recvmsg(s, &msg, MSG_PEEK | MSG_DONTWAIT);
+}
+
+/**
+ * udp_vu_sock_recv() - Receive datagrams from socket into vhost-user buffers
+ * @c: Execution context
+ * @s: Socket to receive from
+ * @events: epoll events bitmap
+ * @v6: Set for IPv6 connections
+ * @dlen: Size of received data (output)
+ *
+ * Return: Number of iov entries used to store the datagram
+ */
+static int udp_vu_sock_recv(const struct ctx *c, int s, uint32_t events,
+ bool v6, ssize_t *dlen)
+{
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ int iov_cnt, idx, iov_used;
+ struct msghdr msg = { 0 };
+ size_t off, hdrlen;
+
+ ASSERT(!c->no_udp);
+
+ if (!(events & EPOLLIN))
+ return 0;
+
+ /* compute L2 header length */
+ hdrlen = udp_vu_hdrlen(v6);
+
+ vu_init_elem(elem, iov_vu, VIRTQUEUE_MAX_SIZE);
+
+ iov_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE,
+ IP_MAX_MTU + ETH_HLEN +
+ sizeof(struct virtio_net_hdr_mrg_rxbuf),
+ NULL);
+ if (iov_cnt == 0)
+ return 0;
+
+ /* reserve space for the headers */
+ ASSERT(iov_vu[0].iov_len >= hdrlen);
+ iov_vu[0].iov_base = (char *)iov_vu[0].iov_base + hdrlen;
+ iov_vu[0].iov_len -= hdrlen;
+
+ /* read data from the socket */
+ msg.msg_iov = iov_vu;
+ msg.msg_iovlen = iov_cnt;
+
+ *dlen = recvmsg(s, &msg, 0);
+ if (*dlen < 0) {
+ vu_queue_rewind(vq, iov_cnt);
+ return 0;
+ }
+
+ /* restore the pointer to the headers address */
+ iov_vu[0].iov_base = (char *)iov_vu[0].iov_base - hdrlen;
+ iov_vu[0].iov_len += hdrlen;
+
+ /* count the numbers of buffer filled by recvmsg() */
+ idx = iov_skip_bytes(iov_vu, iov_cnt, *dlen + hdrlen, &off);
+
+ /* adjust last iov length */
+ if (idx < iov_cnt)
+ iov_vu[idx].iov_len = off;
+ iov_used = idx + !!off;
+
+ vu_set_vnethdr(vdev, iov_vu[0].iov_base, iov_used);
+
+ /* release unused buffers */
+ vu_queue_rewind(vq, iov_cnt - iov_used);
+
+ return iov_used;
+}
+
+/**
+ * udp_vu_prepare() - Prepare the packet header
+ * @c: Execution context
+ * @toside: Address information for one side of the flow
+ * @dlen: Packet data length
+ *
+ * Return: Layer-4 length
+ */
+static size_t udp_vu_prepare(const struct ctx *c,
+ const struct flowside *toside, ssize_t dlen)
+{
+ struct ethhdr *eh;
+ size_t l4len;
+
+ /* ethernet header */
+ eh = vu_eth(iov_vu[0].iov_base);
+
+ memcpy(eh->h_dest, c->guest_mac, sizeof(eh->h_dest));
+ memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
+
+ /* initialize header */
+ if (inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr)) {
+ struct iphdr *iph = vu_ip(iov_vu[0].iov_base);
+ struct udp_payload_t *bp = vu_payloadv4(iov_vu[0].iov_base);
+
+ eh->h_proto = htons(ETH_P_IP);
+
+ *iph = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_UDP);
+
+ l4len = udp_update_hdr4(iph, bp, toside, dlen, true);
+ } else {
+ struct ipv6hdr *ip6h = vu_ip(iov_vu[0].iov_base);
+ struct udp_payload_t *bp = vu_payloadv6(iov_vu[0].iov_base);
+
+ eh->h_proto = htons(ETH_P_IPV6);
+
+ *ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_UDP);
+
+ l4len = udp_update_hdr6(ip6h, bp, toside, dlen, true);
+ }
+
+ return l4len;
+}
+
+/**
+ * udp_vu_csum() - Calculate and set checksum for a UDP packet
+ * @toside: Address information for one side of the flow
+ * @iov_used: Number of used iov_vu items
+ */
+static void udp_vu_csum(const struct flowside *toside, int iov_used)
+{
+ const struct in_addr *src4 = inany_v4(&toside->oaddr);
+ const struct in_addr *dst4 = inany_v4(&toside->eaddr);
+ char *base = iov_vu[0].iov_base;
+ struct udp_payload_t *bp;
+ struct iov_tail data;
+
+ if (src4 && dst4) {
+ bp = vu_payloadv4(base);
+ data = IOV_TAIL(iov_vu, iov_used, (char *)&bp->data - base);
+ csum_udp4(&bp->uh, *src4, *dst4, &data);
+ } else {
+ bp = vu_payloadv6(base);
+ data = IOV_TAIL(iov_vu, iov_used, (char *)&bp->data - base);
+ csum_udp6(&bp->uh, &toside->oaddr.a6, &toside->eaddr.a6, &data);
+ }
+}
+
+/**
+ * udp_vu_listen_sock_handler() - Handle new data from socket
+ * @c: Execution context
+ * @ref: epoll reference
+ * @events: epoll events bitmap
+ * @now: Current timestamp
+ */
+void udp_vu_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events, const struct timespec *now)
+{
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ int i;
+
+ if (udp_sock_errs(c, ref.fd, events) < 0) {
+ err("UDP: Unrecoverable error on listening socket:"
+ " (%s port %hu)", pif_name(ref.udp.pif), ref.udp.port);
+ return;
+ }
+
+ for (i = 0; i < UDP_MAX_FRAMES; i++) {
+ const struct flowside *toside;
+ union sockaddr_inany s_in;
+ flow_sidx_t sidx;
+ uint8_t pif;
+ ssize_t dlen;
+ int iov_used;
+ bool v6;
+
+ if (udp_vu_sock_info(ref.fd, &s_in) < 0)
+ break;
+
+ sidx = udp_flow_from_sock(c, ref, &s_in, now);
+ pif = pif_at_sidx(sidx);
+
+ if (pif != PIF_TAP) {
+ if (flow_sidx_valid(sidx)) {
+ flow_sidx_t fromsidx = flow_sidx_opposite(sidx);
+ struct udp_flow *uflow = udp_at_sidx(sidx);
+
+ flow_err(uflow,
+ "No support for forwarding UDP from %s to %s",
+ pif_name(pif_at_sidx(fromsidx)),
+ pif_name(pif));
+ } else {
+ debug("Discarding 1 datagram without flow");
+ }
+
+ continue;
+ }
+
+ toside = flowside_at_sidx(sidx);
+
+ v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr));
+
+ iov_used = udp_vu_sock_recv(c, ref.fd, events, v6, &dlen);
+ if (iov_used <= 0)
+ break;
+
+ udp_vu_prepare(c, toside, dlen);
+ if (*c->pcap) {
+ udp_vu_csum(toside, iov_used);
+ pcap_iov(iov_vu, iov_used,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+ vu_flush(vdev, vq, elem, iov_used);
+ }
+}
+
+/**
+ * udp_vu_reply_sock_handler() - Handle new data from flow specific socket
+ * @c: Execution context
+ * @ref: epoll reference
+ * @events: epoll events bitmap
+ * @now: Current timestamp
+ */
+void udp_vu_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events, const struct timespec *now)
+{
+ flow_sidx_t tosidx = flow_sidx_opposite(ref.flowside);
+ const struct flowside *toside = flowside_at_sidx(tosidx);
+ struct udp_flow *uflow = udp_at_sidx(ref.flowside);
+ int from_s = uflow->s[ref.flowside.sidei];
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ int i;
+
+ ASSERT(!c->no_udp);
+
+ if (udp_sock_errs(c, from_s, events) < 0) {
+ flow_err(uflow, "Unrecoverable error on reply socket");
+ flow_err_details(uflow);
+ udp_flow_close(c, uflow);
+ return;
+ }
+
+ for (i = 0; i < UDP_MAX_FRAMES; i++) {
+ uint8_t topif = pif_at_sidx(tosidx);
+ ssize_t dlen;
+ int iov_used;
+ bool v6;
+
+ ASSERT(uflow);
+
+ if (topif != PIF_TAP) {
+ uint8_t frompif = pif_at_sidx(ref.flowside);
+
+ flow_err(uflow,
+ "No support for forwarding UDP from %s to %s",
+ pif_name(frompif), pif_name(topif));
+ continue;
+ }
+
+ v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr));
+
+ iov_used = udp_vu_sock_recv(c, from_s, events, v6, &dlen);
+ if (iov_used <= 0)
+ break;
+ flow_trace(uflow, "Received 1 datagram on reply socket");
+ uflow->ts = now->tv_sec;
+
+ udp_vu_prepare(c, toside, dlen);
+ if (*c->pcap) {
+ udp_vu_csum(toside, iov_used);
+ pcap_iov(iov_vu, iov_used,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+ vu_flush(vdev, vq, elem, iov_used);
+ }
+}
diff --git a/udp_vu.h b/udp_vu.h
new file mode 100644
index 0000000..ba7018d
--- /dev/null
+++ b/udp_vu.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#ifndef UDP_VU_H
+#define UDP_VU_H
+
+void udp_vu_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events, const struct timespec *now);
+void udp_vu_reply_sock_handler(const struct ctx *c, union epoll_ref ref,
+ uint32_t events, const struct timespec *now);
+#endif /* UDP_VU_H */
diff --git a/util.c b/util.c
index 0b41404..11973c4 100644
--- a/util.c
+++ b/util.c
@@ -28,11 +28,15 @@
#include <linux/errqueue.h>
#include <getopt.h>
+#include "linux_dep.h"
#include "util.h"
#include "iov.h"
#include "passt.h"
#include "packet.h"
#include "log.h"
+#ifdef HAS_GETRANDOM
+#include <sys/random.h>
+#endif
/**
* sock_l4_sa() - Create and bind socket to socket address, add to epoll list
@@ -52,6 +56,7 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
{
sa_family_t af = ((const struct sockaddr *)sa)->sa_family;
union epoll_ref ref = { .type = type, .data = data };
+ bool freebind = false;
struct epoll_event ev;
int fd, y = 1, ret;
uint8_t proto;
@@ -61,8 +66,11 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
case EPOLL_TYPE_TCP_LISTEN:
proto = IPPROTO_TCP;
socktype = SOCK_STREAM | SOCK_NONBLOCK;
+ freebind = c->freebind;
break;
case EPOLL_TYPE_UDP_LISTEN:
+ freebind = c->freebind;
+ /* fallthrough */
case EPOLL_TYPE_UDP_REPLY:
proto = IPPROTO_UDP;
socktype = SOCK_DGRAM | SOCK_NONBLOCK;
@@ -82,7 +90,7 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
ret = -errno;
if (fd < 0) {
- warn("L4 socket: %s", strerror(-ret));
+ warn("L4 socket: %s", strerror_(-ret));
return ret;
}
@@ -127,6 +135,18 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
}
}
+ if (freebind) {
+ int level = af == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
+ int opt = af == AF_INET ? IP_FREEBIND : IPV6_FREEBIND;
+
+ if (setsockopt(fd, level, opt, &y, sizeof(y))) {
+ err_perror("Failed to set %s on socket %i",
+ af == AF_INET ? "IP_FREEBIND"
+ : "IPV6_FREEBIND",
+ fd);
+ }
+ }
+
if (bind(fd, sa, sl) < 0) {
/* We'll fail to bind to low ports if we don't have enough
* capabilities, and we'll fail to bind on already bound ports,
@@ -142,7 +162,7 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
if (type == EPOLL_TYPE_TCP_LISTEN && listen(fd, 128) < 0) {
ret = -errno;
- warn("TCP socket listen: %s", strerror(-ret));
+ warn("TCP socket listen: %s", strerror_(-ret));
close(fd);
return ret;
}
@@ -151,65 +171,12 @@ int sock_l4_sa(const struct ctx *c, enum epoll_type type,
ev.data.u64 = ref.u64;
if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
ret = -errno;
- warn("L4 epoll_ctl: %s", strerror(-ret));
+ warn("L4 epoll_ctl: %s", strerror_(-ret));
return ret;
}
return fd;
}
-/**
- * sock_l4() - Create and bind socket for given L4, add to epoll list
- * @c: Execution context
- * @af: Address family, AF_INET or AF_INET6
- * @type: epoll type
- * @bind_addr: Address for binding, NULL for any
- * @ifname: Interface for binding, NULL for any
- * @port: Port, host order
- * @data: epoll reference portion for protocol handlers
- *
- * Return: newly created socket, negative error code on failure
- */
-int sock_l4(const struct ctx *c, sa_family_t af, enum epoll_type type,
- const void *bind_addr, const char *ifname, uint16_t port,
- uint32_t data)
-{
- switch (af) {
- case AF_INET: {
- struct sockaddr_in addr4 = {
- .sin_family = AF_INET,
- .sin_port = htons(port),
- { 0 }, { 0 },
- };
- if (bind_addr)
- addr4.sin_addr = *(struct in_addr *)bind_addr;
- return sock_l4_sa(c, type, &addr4, sizeof(addr4), ifname,
- false, data);
- }
-
- case AF_UNSPEC:
- if (!DUAL_STACK_SOCKETS || bind_addr)
- return -EINVAL;
- /* fallthrough */
- case AF_INET6: {
- struct sockaddr_in6 addr6 = {
- .sin6_family = AF_INET6,
- .sin6_port = htons(port),
- 0, IN6ADDR_ANY_INIT, 0,
- };
- if (bind_addr) {
- addr6.sin6_addr = *(struct in6_addr *)bind_addr;
-
- if (!memcmp(bind_addr, &c->ip6.addr_ll,
- sizeof(c->ip6.addr_ll)))
- addr6.sin6_scope_id = c->ifi6;
- }
- return sock_l4_sa(c, type, &addr6, sizeof(addr6), ifname,
- af == AF_INET6, data);
- }
- default:
- return -EINVAL;
- }
-}
/**
* sock_probe_mem() - Check if setting high SO_SNDBUF and SO_RCVBUF is allowed
@@ -220,7 +187,8 @@ void sock_probe_mem(struct ctx *c)
int v = INT_MAX / 2, s;
socklen_t sl;
- if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ s = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
+ if (s < 0) {
c->low_wmem = c->low_rmem = 1;
return;
}
@@ -250,7 +218,7 @@ void sock_probe_mem(struct ctx *c)
int64_t timespec_diff_us(const struct timespec *a, const struct timespec *b)
{
if (a->tv_nsec < b->tv_nsec) {
- return (b->tv_nsec - a->tv_nsec) / 1000 +
+ return (a->tv_nsec + 1000000000 - b->tv_nsec) / 1000 +
(a->tv_sec - b->tv_sec - 1) * 1000000;
}
@@ -444,25 +412,20 @@ void pidfile_write(int fd, pid_t pid)
}
/**
- * pidfile_open() - Open PID file if needed
- * @path: Path for PID file, empty string if no PID file is requested
+ * output_file_open() - Open file for output, if needed
+ * @path: Path for output file
+ * @flags: Flags for open() other than O_CREAT, O_TRUNC, O_CLOEXEC
*
- * Return: descriptor for PID file, -1 if path is NULL, won't return on failure
+ * Return: file descriptor on success, -1 on failure with errno set by open()
*/
-int pidfile_open(const char *path)
+int output_file_open(const char *path, int flags)
{
- int fd;
-
- if (!*path)
- return -1;
-
- if ((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
- S_IRUSR | S_IWUSR)) < 0) {
- perror("PID file open");
- exit(EXIT_FAILURE);
- }
-
- return fd;
+ /* We use O_CLOEXEC here, but clang-tidy as of LLVM 16 to 19 looks for
+ * it in the 'mode' argument if we have one
+ */
+ return open(path, O_CREAT | O_TRUNC | O_CLOEXEC | flags,
+ /* NOLINTNEXTLINE(android-cloexec-open) */
+ S_IRUSR | S_IWUSR);
}
/**
@@ -486,16 +449,11 @@ int __daemon(int pidfile_fd, int devnull_fd)
exit(EXIT_SUCCESS);
}
- errno = 0;
-
- setsid();
-
- dup2(devnull_fd, STDIN_FILENO);
- dup2(devnull_fd, STDOUT_FILENO);
- dup2(devnull_fd, STDERR_FILENO);
- close(devnull_fd);
-
- if (errno)
+ if (setsid() < 0 ||
+ dup2(devnull_fd, STDIN_FILENO) < 0 ||
+ dup2(devnull_fd, STDOUT_FILENO) < 0 ||
+ dup2(devnull_fd, STDERR_FILENO) < 0 ||
+ close(devnull_fd))
exit(EXIT_FAILURE);
return 0;
@@ -583,6 +541,36 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
#endif
}
+/* write_all_buf() - write all of a buffer to an fd
+ * @fd: File descriptor
+ * @buf: Pointer to base of buffer
+ * @len: Length of buffer
+ *
+ * Return: 0 on success, -1 on error (with errno set)
+ *
+ * #syscalls write
+ */
+int write_all_buf(int fd, const void *buf, size_t len)
+{
+ const char *p = buf;
+ size_t left = len;
+
+ while (left) {
+ ssize_t rc;
+
+ do
+ rc = write(fd, p, left);
+ while ((rc < 0) && errno == EINTR);
+
+ if (rc < 0)
+ return -1;
+
+ p += rc;
+ left -= rc;
+ }
+ return 0;
+}
+
/* write_remainder() - write the tail of an IO vector to an fd
* @fd: File descriptor
* @iov: IO vector
@@ -591,28 +579,30 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
*
* Return: 0 on success, -1 on error (with errno set)
*
- * #syscalls write writev
+ * #syscalls writev
*/
int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip)
{
- size_t offset, i;
+ size_t i = 0, offset;
- while ((i = iov_skip_bytes(iov, iovcnt, skip, &offset)) < iovcnt) {
+ while ((i += iov_skip_bytes(iov + i, iovcnt - i, skip, &offset)) < iovcnt) {
ssize_t rc;
if (offset) {
- rc = write(fd, (char *)iov[i].iov_base + offset,
- iov[i].iov_len - offset);
- } else {
- rc = writev(fd, &iov[i], iovcnt - i);
+ /* Write the remainder of the partially written buffer */
+ if (write_all_buf(fd, (char *)iov[i].iov_base + offset,
+ iov[i].iov_len - offset) < 0)
+ return -1;
+ i++;
}
+ /* Write as much of the remaining whole buffers as we can */
+ rc = writev(fd, &iov[i], iovcnt - i);
if (rc < 0)
return -1;
- skip += rc;
+ skip = rc;
}
-
return 0;
}
@@ -676,6 +666,25 @@ const char *sockaddr_ntop(const void *sa, char *dst, socklen_t size)
return dst;
}
+/** eth_ntop() - Convert an Ethernet MAC address to text format
+ * @mac: MAC address
+ * @dst: Output buffer, minimum ETH_ADDRSTRLEN bytes
+ * @size: Size of buffer at @dst
+ *
+ * Return: On success, a non-null pointer to @dst, NULL on failure
+ */
+const char *eth_ntop(const unsigned char *mac, char *dst, size_t size)
+{
+ int len;
+
+ len = snprintf(dst, size, "%02x:%02x:%02x:%02x:%02x:%02x",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ if (len < 0 || (size_t)len >= size)
+ return NULL;
+
+ return dst;
+}
+
/** str_ee_origin() - Convert socket extended error origin to a string
* @ee: Socket extended error structure
*
@@ -710,7 +719,7 @@ void close_open_files(int argc, char **argv)
int name, rc;
do {
- name = getopt_long(argc, argv, "+:F", optfd, NULL);
+ name = getopt_long(argc, argv, "-:F:", optfd, NULL);
if (name == 'F') {
errno = 0;
@@ -732,6 +741,99 @@ void close_open_files(int argc, char **argv)
rc = close_range(fd + 1, ~0U, CLOSE_RANGE_UNSHARE);
}
- if (rc)
- die_perror("Failed to close files leaked by parent");
+ if (rc) {
+ if (errno == ENOSYS || errno == EINVAL) {
+ /* This probably means close_range() or the
+ * CLOSE_RANGE_UNSHARE flag is not supported by the
+ * kernel. Not much we can do here except carry on and
+ * hope for the best.
+ */
+ warn(
+"Can't use close_range() to ensure no files leaked by parent");
+ } else {
+ die_perror("Failed to close files leaked by parent");
+ }
+ }
+
+}
+
+/**
+ * snprintf_check() - snprintf() wrapper, checking for truncation and errors
+ * @str: Output buffer
+ * @size: Maximum size to write to @str
+ * @format: Message
+ *
+ * Return: false on success, true on truncation or error, sets errno on failure
+ */
+bool snprintf_check(char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, format);
+ rc = vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ if (rc < 0) {
+ errno = EIO;
+ return true;
+ }
+
+ if ((size_t)rc >= size) {
+ errno = ENOBUFS;
+ return true;
+ }
+
+ return false;
+}
+
+#define DEV_RANDOM "/dev/random"
+
+/**
+ * raw_random() - Get high quality random bytes
+ * @buf: Buffer to fill with random bytes
+ * @buflen: Number of bytes of random data to put in @buf
+ *
+ * Assumes that the random data is essential, and will die() if unable to obtain
+ * it.
+ */
+void raw_random(void *buf, size_t buflen)
+{
+ size_t random_read = 0;
+#ifndef HAS_GETRANDOM
+ int fd = open(DEV_RANDOM, O_RDONLY);
+
+ if (fd < 0)
+ die_perror("Couldn't open %s", DEV_RANDOM);
+#endif
+
+ while (random_read < buflen) {
+ ssize_t ret;
+
+#ifdef HAS_GETRANDOM
+ ret = getrandom((char *)buf + random_read,
+ buflen - random_read, GRND_RANDOM);
+#else
+ ret = read(dev_random, (char *)buf + random_read,
+ buflen - random_read);
+#endif
+
+ if (ret == -1 && errno == EINTR)
+ continue;
+
+ if (ret < 0)
+ die_perror("Error on random data source");
+
+ if (ret == 0)
+ break;
+
+ random_read += ret;
+ }
+
+#ifndef HAS_GETRANDOM
+ close(dev_random);
+#endif
+
+ if (random_read < buflen)
+ die("Unexpected EOF on random data source");
}
diff --git a/util.h b/util.h
index cb4d181..3fa1d12 100644
--- a/util.h
+++ b/util.h
@@ -11,9 +11,12 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <arpa/inet.h>
+#include <unistd.h>
+#include <sys/syscall.h>
#include "log.h"
@@ -64,6 +67,15 @@
#define STRINGIFY(x) #x
#define STR(x) STRINGIFY(x)
+#ifdef CPPCHECK_6936
+/* Some cppcheck versions get confused by aborts inside a loop, causing
+ * it to give false positive uninitialised variable warnings later in
+ * the function, because it doesn't realise the non-initialising path
+ * already exited. See https://trac.cppcheck.net/ticket/13227
+ */
+#define ASSERT(expr) \
+ ((expr) ? (void)0 : abort())
+#else
#define ASSERT(expr) \
do { \
if (!(expr)) { \
@@ -75,6 +87,7 @@
abort(); \
} \
} while (0)
+#endif
#ifdef P_tmpdir
#define TMPDIR P_tmpdir
@@ -88,15 +101,14 @@
#define ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0])))
+#define foreach(item, array) \
+ for ((item) = (array); (item) - (array) < ARRAY_SIZE(array); (item)++)
+
#define IN_INTERVAL(a, b, x) ((x) >= (a) && (x) <= (b))
#define FD_PROTO(x, proto) \
(IN_INTERVAL(c->proto.fd_min, c->proto.fd_max, (x)))
-#define PORT_EPHEMERAL_MIN ((1 << 15) + (1 << 14)) /* RFC 6335 */
-#define PORT_IS_EPHEMERAL(port) ((port) >= PORT_EPHEMERAL_MIN)
-
#define MAC_ZERO ((uint8_t [ETH_ALEN]){ 0 })
-#define MAC_LAA ((uint8_t [ETH_ALEN]){ BIT(1), 0, 0, 0, 0, 0 })
#define MAC_IS_ZERO(addr) (!memcmp((addr), MAC_ZERO, ETH_ALEN))
#ifndef __bswap_constant_16
@@ -132,7 +144,16 @@ static inline uint32_t ntohl_unaligned(const void *p)
return ntohl(val);
}
-#define NS_FN_STACK_SIZE (RLIMIT_STACK_VAL * 1024 / 8)
+static inline void barrier(void) { __asm__ __volatile__("" ::: "memory"); }
+#define smp_mb() do { barrier(); __atomic_thread_fence(__ATOMIC_SEQ_CST); } while (0)
+#define smp_mb_release() do { barrier(); __atomic_thread_fence(__ATOMIC_RELEASE); } while (0)
+#define smp_mb_acquire() do { barrier(); __atomic_thread_fence(__ATOMIC_ACQUIRE); } while (0)
+
+#define smp_wmb() smp_mb_release()
+#define smp_rmb() smp_mb_acquire()
+
+#define NS_FN_STACK_SIZE (1024 * 1024) /* 1MiB */
+
int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
void *arg);
#define NS_CALL(fn, arg) \
@@ -145,9 +166,9 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
(void *)(arg)); \
} while (0)
-#define RCVBUF_BIG (2UL * 1024 * 1024)
-#define SNDBUF_BIG (4UL * 1024 * 1024)
-#define SNDBUF_SMALL (128UL * 1024)
+#define RCVBUF_BIG (2ULL * 1024 * 1024)
+#define SNDBUF_BIG (4ULL * 1024 * 1024)
+#define SNDBUF_SMALL (128ULL * 1024)
#include <net/if.h>
#include <limits.h>
@@ -158,14 +179,9 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
struct ctx;
-/* cppcheck-suppress funcArgNamesDifferent */
-__attribute__ ((weak)) int ffsl(long int i) { return __builtin_ffsl(i); }
int sock_l4_sa(const struct ctx *c, enum epoll_type type,
const void *sa, socklen_t sl,
const char *ifname, bool v6only, uint32_t data);
-int sock_l4(const struct ctx *c, sa_family_t af, enum epoll_type type,
- const void *bind_addr, const char *ifname, uint16_t port,
- uint32_t data);
void sock_probe_mem(struct ctx *c);
long timespec_diff_ms(const struct timespec *a, const struct timespec *b);
int64_t timespec_diff_us(const struct timespec *a, const struct timespec *b);
@@ -177,13 +193,15 @@ char *line_read(char *buf, size_t len, int fd);
void ns_enter(const struct ctx *c);
bool ns_is_init(void);
int open_in_ns(const struct ctx *c, const char *path, int flags);
-int pidfile_open(const char *path);
+int output_file_open(const char *path, int flags);
void pidfile_write(int fd, pid_t pid);
int __daemon(int pidfile_fd, int devnull_fd);
int fls(unsigned long x);
int write_file(const char *path, const char *buf);
+int write_all_buf(int fd, const void *buf, size_t len);
int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip);
void close_open_files(int argc, char **argv);
+bool snprintf_check(char *str, size_t size, const char *format, ...);
/**
* af_name() - Return name of an address family
@@ -215,9 +233,12 @@ static inline const char *af_name(sa_family_t af)
#define SOCKADDR_STRLEN MAX(SOCKADDR_INET_STRLEN, SOCKADDR_INET6_STRLEN)
+#define ETH_ADDRSTRLEN (sizeof("00:11:22:33:44:55"))
+
struct sock_extended_err;
const char *sockaddr_ntop(const void *sa, char *dst, socklen_t size);
+const char *eth_ntop(const unsigned char *mac, char *dst, size_t size);
const char *str_ee_origin(const struct sock_extended_err *ee);
/**
@@ -248,6 +269,43 @@ static inline bool mod_between(unsigned x, unsigned i, unsigned j, unsigned m)
return mod_sub(x, i, m) < mod_sub(j, i, m);
}
+/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */
+#define FPRINTF(f, ...) (void)fprintf(f, __VA_ARGS__)
+
+void raw_random(void *buf, size_t buflen);
+
+/*
+ * Starting from glibc 2.40.9000 and commit 25a5eb4010df ("string: strerror,
+ * strsignal cannot use buffer after dlmopen (bug 32026)"), strerror() needs
+ * getrandom(2) and brk(2) as it allocates memory for the locale-translated
+ * error description, but our seccomp profiles forbid both.
+ *
+ * Use the strerror_() wrapper instead, calling into strerrordesc_np() to get
+ * a static untranslated string. It's a GNU implementation, but also defined by
+ * bionic.
+ *
+ * If strerrordesc_np() is not defined (e.g. musl), call strerror(). C libraries
+ * not defining strerrordesc_np() are expected to provide strerror()
+ * implementations that are simple enough for us to call.
+ */
+__attribute__ ((weak)) const char *strerrordesc_np(int errnum);
+
+/**
+ * strerror_() - strerror() wrapper calling strerrordesc_np() if available
+ * @errnum: Error code
+ *
+ * Return: error description string
+ */
+static inline const char *strerror_(int errnum)
+{
+ if (strerrordesc_np)
+ return strerrordesc_np(errnum);
+
+ return strerror(errnum);
+}
+
+#define strerror(x) @ "Don't call strerror() directly, use strerror_() instead"
+
/*
* Workarounds for https://github.com/llvm/llvm-project/issues/58992
*
diff --git a/vhost_user.c b/vhost_user.c
new file mode 100644
index 0000000..4b8558f
--- /dev/null
+++ b/vhost_user.c
@@ -0,0 +1,984 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vhost-user API, command management and virtio interface
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ *
+ * Some parts from QEMU subprojects/libvhost-user/libvhost-user.c
+ * licensed under the following terms:
+ *
+ * Copyright IBM, Corp. 2007
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Marc-André Lureau <mlureau@redhat.com>
+ * Victor Kaplansky <victork@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <time.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/mman.h>
+#include <linux/vhost_types.h>
+#include <linux/virtio_net.h>
+
+#include "util.h"
+#include "passt.h"
+#include "tap.h"
+#include "vhost_user.h"
+#include "pcap.h"
+
+/* vhost-user version we are compatible with */
+#define VHOST_USER_VERSION 1
+
+static struct vu_dev vdev_storage;
+
+/**
+ * vu_print_capabilities() - print vhost-user capabilities
+ * this is part of the vhost-user backend
+ * convention.
+ */
+void vu_print_capabilities(void)
+{
+ info("{");
+ info(" \"type\": \"net\"");
+ info("}");
+ exit(EXIT_SUCCESS);
+}
+
+/**
+ * vu_request_to_string() - convert a vhost-user request number to its name
+ * @req: request number
+ *
+ * Return: the name of request number
+ */
+static const char *vu_request_to_string(unsigned int req)
+{
+ if (req < VHOST_USER_MAX) {
+#define REQ(req) [req] = #req
+ static const char * const vu_request_str[VHOST_USER_MAX] = {
+ REQ(VHOST_USER_NONE),
+ REQ(VHOST_USER_GET_FEATURES),
+ REQ(VHOST_USER_SET_FEATURES),
+ REQ(VHOST_USER_SET_OWNER),
+ REQ(VHOST_USER_RESET_OWNER),
+ REQ(VHOST_USER_SET_MEM_TABLE),
+ REQ(VHOST_USER_SET_LOG_BASE),
+ REQ(VHOST_USER_SET_LOG_FD),
+ REQ(VHOST_USER_SET_VRING_NUM),
+ REQ(VHOST_USER_SET_VRING_ADDR),
+ REQ(VHOST_USER_SET_VRING_BASE),
+ REQ(VHOST_USER_GET_VRING_BASE),
+ REQ(VHOST_USER_SET_VRING_KICK),
+ REQ(VHOST_USER_SET_VRING_CALL),
+ REQ(VHOST_USER_SET_VRING_ERR),
+ REQ(VHOST_USER_GET_PROTOCOL_FEATURES),
+ REQ(VHOST_USER_SET_PROTOCOL_FEATURES),
+ REQ(VHOST_USER_GET_QUEUE_NUM),
+ REQ(VHOST_USER_SET_VRING_ENABLE),
+ REQ(VHOST_USER_SEND_RARP),
+ REQ(VHOST_USER_NET_SET_MTU),
+ REQ(VHOST_USER_SET_BACKEND_REQ_FD),
+ REQ(VHOST_USER_IOTLB_MSG),
+ REQ(VHOST_USER_SET_VRING_ENDIAN),
+ REQ(VHOST_USER_GET_CONFIG),
+ REQ(VHOST_USER_SET_CONFIG),
+ REQ(VHOST_USER_POSTCOPY_ADVISE),
+ REQ(VHOST_USER_POSTCOPY_LISTEN),
+ REQ(VHOST_USER_POSTCOPY_END),
+ REQ(VHOST_USER_GET_INFLIGHT_FD),
+ REQ(VHOST_USER_SET_INFLIGHT_FD),
+ REQ(VHOST_USER_GPU_SET_SOCKET),
+ REQ(VHOST_USER_VRING_KICK),
+ REQ(VHOST_USER_GET_MAX_MEM_SLOTS),
+ REQ(VHOST_USER_ADD_MEM_REG),
+ REQ(VHOST_USER_REM_MEM_REG),
+ };
+#undef REQ
+ return vu_request_str[req];
+ }
+
+ return "unknown";
+}
+
+/**
+ * qva_to_va() - Translate front-end (QEMU) virtual address to our virtual
+ * address
+ * @dev: vhost-user device
+ * @qemu_addr: front-end userspace address
+ *
+ * Return: the memory address in our process virtual address space.
+ */
+static void *qva_to_va(struct vu_dev *dev, uint64_t qemu_addr)
+{
+ unsigned int i;
+
+ /* Find matching memory region. */
+ for (i = 0; i < dev->nregions; i++) {
+ const struct vu_dev_region *r = &dev->regions[i];
+
+ if ((qemu_addr >= r->qva) && (qemu_addr < (r->qva + r->size))) {
+ /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
+ return (void *)(uintptr_t)(qemu_addr - r->qva +
+ r->mmap_addr +
+ r->mmap_offset);
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * vmsg_close_fds() - Close all file descriptors of a given message
+ * @vmsg: vhost-user message with the list of the file descriptors
+ */
+static void vmsg_close_fds(const struct vhost_user_msg *vmsg)
+{
+ int i;
+
+ for (i = 0; i < vmsg->fd_num; i++)
+ close(vmsg->fds[i]);
+}
+
+/**
+ * vu_remove_watch() - Remove a file descriptor from our passt epoll
+ * file descriptor
+ * @vdev: vhost-user device
+ * @fd: file descriptor to remove
+ */
+static void vu_remove_watch(const struct vu_dev *vdev, int fd)
+{
+ epoll_ctl(vdev->context->epollfd, EPOLL_CTL_DEL, fd, NULL);
+}
+
+/**
+ * vmsg_set_reply_u64() - Set reply payload.u64 and clear request flags
+ * and fd_num
+ * @vmsg: vhost-user message
+ * @val: 64-bit value to reply
+ */
+static void vmsg_set_reply_u64(struct vhost_user_msg *vmsg, uint64_t val)
+{
+ vmsg->hdr.flags = 0; /* defaults will be set by vu_send_reply() */
+ vmsg->hdr.size = sizeof(vmsg->payload.u64);
+ vmsg->payload.u64 = val;
+ vmsg->fd_num = 0;
+}
+
+/**
+ * vu_message_read_default() - Read incoming vhost-user message from the
+ * front-end
+ * @conn_fd: vhost-user command socket
+ * @vmsg: vhost-user message
+ *
+ * Return: 0 if recvmsg() has been interrupted or if there's no data to read,
+ * 1 if a message has been received
+ */
+static int vu_message_read_default(int conn_fd, struct vhost_user_msg *vmsg)
+{
+ char control[CMSG_SPACE(VHOST_MEMORY_BASELINE_NREGIONS *
+ sizeof(int))] = { 0 };
+ struct iovec iov = {
+ .iov_base = (char *)vmsg,
+ .iov_len = VHOST_USER_HDR_SIZE,
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = control,
+ .msg_controllen = sizeof(control),
+ };
+ ssize_t ret, sz_payload;
+ struct cmsghdr *cmsg;
+
+ ret = recvmsg(conn_fd, &msg, MSG_DONTWAIT);
+ if (ret < 0) {
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+ return 0;
+ die_perror("vhost-user message receive (recvmsg)");
+ }
+
+ vmsg->fd_num = 0;
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ size_t fd_size;
+
+ ASSERT(cmsg->cmsg_len >= CMSG_LEN(0));
+ fd_size = cmsg->cmsg_len - CMSG_LEN(0);
+ ASSERT(fd_size <= sizeof(vmsg->fds));
+ vmsg->fd_num = fd_size / sizeof(int);
+ memcpy(vmsg->fds, CMSG_DATA(cmsg), fd_size);
+ break;
+ }
+ }
+
+ sz_payload = vmsg->hdr.size;
+ if ((size_t)sz_payload > sizeof(vmsg->payload)) {
+ die("vhost-user message request too big: %d,"
+ " size: vmsg->size: %zd, "
+ "while sizeof(vmsg->payload) = %zu",
+ vmsg->hdr.request, sz_payload, sizeof(vmsg->payload));
+ }
+
+ if (sz_payload) {
+ do
+ ret = recv(conn_fd, &vmsg->payload, sz_payload, 0);
+ while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ die_perror("vhost-user message receive");
+
+ if (ret == 0)
+ die("EOF on vhost-user message receive");
+
+ if (ret < sz_payload)
+ die("Short-read on vhost-user message receive");
+ }
+
+ return 1;
+}
+
+/**
+ * vu_message_write() - Send a message to the front-end
+ * @conn_fd: vhost-user command socket
+ * @vmsg: vhost-user message
+ *
+ * #syscalls:vu sendmsg
+ */
+static void vu_message_write(int conn_fd, struct vhost_user_msg *vmsg)
+{
+ char control[CMSG_SPACE(VHOST_MEMORY_BASELINE_NREGIONS * sizeof(int))] = { 0 };
+ struct iovec iov = {
+ .iov_base = (char *)vmsg,
+ .iov_len = VHOST_USER_HDR_SIZE + vmsg->hdr.size,
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = control,
+ };
+ int rc;
+
+ ASSERT(vmsg->fd_num <= VHOST_MEMORY_BASELINE_NREGIONS);
+ if (vmsg->fd_num > 0) {
+ size_t fdsize = vmsg->fd_num * sizeof(int);
+ struct cmsghdr *cmsg;
+
+ msg.msg_controllen = CMSG_SPACE(fdsize);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(fdsize);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), vmsg->fds, fdsize);
+ }
+
+ do
+ rc = sendmsg(conn_fd, &msg, 0);
+ while (rc < 0 && errno == EINTR);
+
+ if (rc < 0)
+ die_perror("vhost-user message send");
+
+ if ((uint32_t)rc < VHOST_USER_HDR_SIZE + vmsg->hdr.size)
+ die("EOF on vhost-user message send");
+}
+
+/**
+ * vu_send_reply() - Update message flags and send it to front-end
+ * @conn_fd: vhost-user command socket
+ * @vmsg: vhost-user message
+ */
+static void vu_send_reply(int conn_fd, struct vhost_user_msg *msg)
+{
+ msg->hdr.flags &= ~VHOST_USER_VERSION_MASK;
+ msg->hdr.flags |= VHOST_USER_VERSION;
+ msg->hdr.flags |= VHOST_USER_REPLY_MASK;
+
+ vu_message_write(conn_fd, msg);
+}
+
+/**
+ * vu_get_features_exec() - Provide back-end features bitmask to front-end
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: True as a reply is requested
+ */
+static bool vu_get_features_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ uint64_t features =
+ 1ULL << VIRTIO_F_VERSION_1 |
+ 1ULL << VIRTIO_NET_F_MRG_RXBUF |
+ 1ULL << VHOST_USER_F_PROTOCOL_FEATURES;
+
+ (void)vdev;
+
+ vmsg_set_reply_u64(msg, features);
+
+ debug("Sending back to guest u64: 0x%016"PRIx64, msg->payload.u64);
+
+ return true;
+}
+
+/**
+ * vu_set_enable_all_rings() - Enable/disable all the virtqueues
+ * @vdev: vhost-user device
+ * @enable: New virtqueues state
+ */
+static void vu_set_enable_all_rings(struct vu_dev *vdev, bool enable)
+{
+ uint16_t i;
+
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++)
+ vdev->vq[i].enable = enable;
+}
+
+/**
+ * vu_set_features_exec() - Enable features of the back-end
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_features_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ debug("u64: 0x%016"PRIx64, msg->payload.u64);
+
+ vdev->features = msg->payload.u64;
+ /* We only support devices conforming to VIRTIO 1.0 or
+ * later
+ */
+ if (!vu_has_feature(vdev, VIRTIO_F_VERSION_1))
+ die("virtio legacy devices aren't supported by passt");
+
+ if (!vu_has_feature(vdev, VHOST_USER_F_PROTOCOL_FEATURES))
+ vu_set_enable_all_rings(vdev, true);
+
+ return false;
+}
+
+/**
+ * vu_set_owner_exec() - Session start flag, do nothing in our case
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_owner_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ (void)vdev;
+ (void)msg;
+
+ return false;
+}
+
+/**
+ * map_ring() - Convert ring front-end (QEMU) addresses to our process
+ * virtual address space.
+ * @vdev: vhost-user device
+ * @vq: Virtqueue
+ *
+ * Return: True if ring cannot be mapped to our address space
+ */
+static bool map_ring(struct vu_dev *vdev, struct vu_virtq *vq)
+{
+ vq->vring.desc = qva_to_va(vdev, vq->vra.desc_user_addr);
+ vq->vring.used = qva_to_va(vdev, vq->vra.used_user_addr);
+ vq->vring.avail = qva_to_va(vdev, vq->vra.avail_user_addr);
+
+ debug("Setting virtq addresses:");
+ debug(" vring_desc at %p", (void *)vq->vring.desc);
+ debug(" vring_used at %p", (void *)vq->vring.used);
+ debug(" vring_avail at %p", (void *)vq->vring.avail);
+
+ return !(vq->vring.desc && vq->vring.used && vq->vring.avail);
+}
+
+/**
+ * vu_set_mem_table_exec() - Sets the memory map regions to be able to
+ * translate the vring addresses.
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ *
+ * #syscalls:vu mmap munmap
+ */
+static bool vu_set_mem_table_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ struct vhost_user_memory m = msg->payload.memory, *memory = &m;
+ unsigned int i;
+
+ for (i = 0; i < vdev->nregions; i++) {
+ const struct vu_dev_region *r = &vdev->regions[i];
+
+ if (r->mmap_addr) {
+ /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
+ munmap((void *)(uintptr_t)r->mmap_addr,
+ r->size + r->mmap_offset);
+ }
+ }
+ vdev->nregions = memory->nregions;
+
+ debug("vhost-user nregions: %u", memory->nregions);
+ for (i = 0; i < vdev->nregions; i++) {
+ struct vhost_user_memory_region *msg_region = &memory->regions[i];
+ struct vu_dev_region *dev_region = &vdev->regions[i];
+ void *mmap_addr;
+
+ debug("vhost-user region %d", i);
+ debug(" guest_phys_addr: 0x%016"PRIx64,
+ msg_region->guest_phys_addr);
+ debug(" memory_size: 0x%016"PRIx64,
+ msg_region->memory_size);
+ debug(" userspace_addr 0x%016"PRIx64,
+ msg_region->userspace_addr);
+ debug(" mmap_offset 0x%016"PRIx64,
+ msg_region->mmap_offset);
+
+ dev_region->gpa = msg_region->guest_phys_addr;
+ dev_region->size = msg_region->memory_size;
+ dev_region->qva = msg_region->userspace_addr;
+ dev_region->mmap_offset = msg_region->mmap_offset;
+
+ /* We don't use offset argument of mmap() since the
+ * mapped address has to be page aligned.
+ */
+ mmap_addr = mmap(0, dev_region->size + dev_region->mmap_offset,
+ PROT_READ | PROT_WRITE, MAP_SHARED |
+ MAP_NORESERVE, msg->fds[i], 0);
+
+ if (mmap_addr == MAP_FAILED)
+ die_perror("vhost-user region mmap error");
+
+ dev_region->mmap_addr = (uint64_t)(uintptr_t)mmap_addr;
+ debug(" mmap_addr: 0x%016"PRIx64,
+ dev_region->mmap_addr);
+
+ close(msg->fds[i]);
+ }
+
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++) {
+ if (vdev->vq[i].vring.desc) {
+ if (map_ring(vdev, &vdev->vq[i]))
+ die("remapping queue %d during setmemtable", i);
+ }
+ }
+
+ /* As vu_packet_check_range() has no access to the number of
+ * memory regions, mark the end of the array with mmap_addr = 0
+ */
+ ASSERT(vdev->nregions < VHOST_USER_MAX_RAM_SLOTS - 1);
+ vdev->regions[vdev->nregions].mmap_addr = 0;
+
+ tap_sock_update_pool(vdev->regions, 0);
+
+ return false;
+}
+
+/**
+ * vu_set_vring_num_exec() - Set the size of the queue (vring size)
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_num_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ unsigned int idx = msg->payload.state.index;
+ unsigned int num = msg->payload.state.num;
+
+ debug("State.index: %u", idx);
+ debug("State.num: %u", num);
+ vdev->vq[idx].vring.num = num;
+
+ return false;
+}
+
+/**
+ * vu_set_vring_addr_exec() - Set the addresses of the vring
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_addr_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ /* We need to copy the payload to vhost_vring_addr structure
+ * to access index because address of msg->payload.addr
+ * can be unaligned as it is packed.
+ */
+ struct vhost_vring_addr addr = msg->payload.addr;
+ struct vu_virtq *vq = &vdev->vq[addr.index];
+
+ debug("vhost_vring_addr:");
+ debug(" index: %d", addr.index);
+ debug(" flags: %d", addr.flags);
+ debug(" desc_user_addr: 0x%016" PRIx64,
+ (uint64_t)addr.desc_user_addr);
+ debug(" used_user_addr: 0x%016" PRIx64,
+ (uint64_t)addr.used_user_addr);
+ debug(" avail_user_addr: 0x%016" PRIx64,
+ (uint64_t)addr.avail_user_addr);
+ debug(" log_guest_addr: 0x%016" PRIx64,
+ (uint64_t)addr.log_guest_addr);
+
+ vq->vra = msg->payload.addr;
+ vq->vring.flags = addr.flags;
+ vq->vring.log_guest_addr = addr.log_guest_addr;
+
+ if (map_ring(vdev, vq))
+ die("Invalid vring_addr message");
+
+ vq->used_idx = le16toh(vq->vring.used->idx);
+
+ if (vq->last_avail_idx != vq->used_idx) {
+ debug("Last avail index != used index: %u != %u",
+ vq->last_avail_idx, vq->used_idx);
+ }
+
+ return false;
+}
+/**
+ * vu_set_vring_base_exec() - Sets the next index to use for descriptors
+ * in this vring
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_base_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ unsigned int idx = msg->payload.state.index;
+ unsigned int num = msg->payload.state.num;
+
+ debug("State.index: %u", idx);
+ debug("State.num: %u", num);
+ vdev->vq[idx].shadow_avail_idx = vdev->vq[idx].last_avail_idx = num;
+
+ return false;
+}
+
+/**
+ * vu_get_vring_base_exec() - Stops the vring and returns the current
+ * descriptor index or indices
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: True as a reply is requested
+ */
+static bool vu_get_vring_base_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ unsigned int idx = msg->payload.state.index;
+
+ debug("State.index: %u", idx);
+ msg->payload.state.num = vdev->vq[idx].last_avail_idx;
+ msg->hdr.size = sizeof(msg->payload.state);
+
+ vdev->vq[idx].started = false;
+
+ if (vdev->vq[idx].call_fd != -1) {
+ close(vdev->vq[idx].call_fd);
+ vdev->vq[idx].call_fd = -1;
+ }
+ if (vdev->vq[idx].kick_fd != -1) {
+ vu_remove_watch(vdev, vdev->vq[idx].kick_fd);
+ close(vdev->vq[idx].kick_fd);
+ vdev->vq[idx].kick_fd = -1;
+ }
+
+ return true;
+}
+
+/**
+ * vu_set_watch() - Add a file descriptor to the passt epoll file descriptor
+ * @vdev: vhost-user device
+ * @idx: queue index of the file descriptor to add
+ */
+static void vu_set_watch(const struct vu_dev *vdev, int idx)
+{
+ union epoll_ref ref = {
+ .type = EPOLL_TYPE_VHOST_KICK,
+ .fd = vdev->vq[idx].kick_fd,
+ .queue = idx
+ };
+ struct epoll_event ev = { 0 };
+
+ ev.data.u64 = ref.u64;
+ ev.events = EPOLLIN;
+ epoll_ctl(vdev->context->epollfd, EPOLL_CTL_ADD, ref.fd, &ev);
+}
+
+/**
+ * vu_check_queue_msg_file() - Check if a message is valid,
+ * close fds if NOFD bit is set
+ * @vmsg: vhost-user message
+ */
+static void vu_check_queue_msg_file(struct vhost_user_msg *msg)
+{
+ bool nofd = msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK;
+ int idx = msg->payload.u64 & VHOST_USER_VRING_IDX_MASK;
+
+ if (idx >= VHOST_USER_MAX_QUEUES)
+ die("Invalid vhost-user queue index: %u", idx);
+
+ if (nofd) {
+ vmsg_close_fds(msg);
+ return;
+ }
+
+ if (msg->fd_num != 1)
+ die("Invalid fds in vhost-user request: %d", msg->hdr.request);
+}
+
+/**
+ * vu_set_vring_kick_exec() - Set the event file descriptor for adding buffers
+ * to the vring
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_kick_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ bool nofd = msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK;
+ int idx = msg->payload.u64 & VHOST_USER_VRING_IDX_MASK;
+
+ debug("u64: 0x%016"PRIx64, msg->payload.u64);
+
+ vu_check_queue_msg_file(msg);
+
+ if (vdev->vq[idx].kick_fd != -1) {
+ vu_remove_watch(vdev, vdev->vq[idx].kick_fd);
+ close(vdev->vq[idx].kick_fd);
+ vdev->vq[idx].kick_fd = -1;
+ }
+
+ if (!nofd)
+ vdev->vq[idx].kick_fd = msg->fds[0];
+
+ debug("Got kick_fd: %d for vq: %d", vdev->vq[idx].kick_fd, idx);
+
+ vdev->vq[idx].started = true;
+
+ if (vdev->vq[idx].kick_fd != -1 && VHOST_USER_IS_QUEUE_TX(idx)) {
+ vu_set_watch(vdev, idx);
+ debug("Waiting for kicks on fd: %d for vq: %d",
+ vdev->vq[idx].kick_fd, idx);
+ }
+
+ return false;
+}
+
+/**
+ * vu_set_vring_call_exec() - Set the event file descriptor to signal when
+ * buffers are used
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_call_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ bool nofd = msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK;
+ int idx = msg->payload.u64 & VHOST_USER_VRING_IDX_MASK;
+
+ debug("u64: 0x%016"PRIx64, msg->payload.u64);
+
+ vu_check_queue_msg_file(msg);
+
+ if (vdev->vq[idx].call_fd != -1) {
+ close(vdev->vq[idx].call_fd);
+ vdev->vq[idx].call_fd = -1;
+ }
+
+ if (!nofd)
+ vdev->vq[idx].call_fd = msg->fds[0];
+
+ /* in case of I/O hang after reconnecting */
+ if (vdev->vq[idx].call_fd != -1)
+ eventfd_write(msg->fds[0], 1);
+
+ debug("Got call_fd: %d for vq: %d", vdev->vq[idx].call_fd, idx);
+
+ return false;
+}
+
+/**
+ * vu_set_vring_err_exec() - Set the event file descriptor to signal when
+ * error occurs
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_err_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ bool nofd = msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK;
+ int idx = msg->payload.u64 & VHOST_USER_VRING_IDX_MASK;
+
+ debug("u64: 0x%016"PRIx64, msg->payload.u64);
+
+ vu_check_queue_msg_file(msg);
+
+ if (vdev->vq[idx].err_fd != -1) {
+ close(vdev->vq[idx].err_fd);
+ vdev->vq[idx].err_fd = -1;
+ }
+
+ if (!nofd)
+ vdev->vq[idx].err_fd = msg->fds[0];
+
+ return false;
+}
+
+/**
+ * vu_get_protocol_features_exec() - Provide the protocol (vhost-user) features
+ * to the front-end
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: True as a reply is requested
+ */
+static bool vu_get_protocol_features_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ uint64_t features = 1ULL << VHOST_USER_PROTOCOL_F_REPLY_ACK;
+
+ (void)vdev;
+ vmsg_set_reply_u64(msg, features);
+
+ return true;
+}
+
+/**
+ * vu_set_protocol_features_exec() - Enable protocol (vhost-user) features
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_protocol_features_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ uint64_t features = msg->payload.u64;
+
+ debug("u64: 0x%016"PRIx64, features);
+
+ vdev->protocol_features = msg->payload.u64;
+
+ return false;
+}
+
+/**
+ * vu_get_queue_num_exec() - Tell how many queues we support
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: True as a reply is requested
+ */
+static bool vu_get_queue_num_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ (void)vdev;
+
+ vmsg_set_reply_u64(msg, VHOST_USER_MAX_QUEUES);
+
+ return true;
+}
+
+/**
+ * vu_set_vring_enable_exec() - Enable or disable corresponding vring
+ * @vdev: vhost-user device
+ * @vmsg: vhost-user message
+ *
+ * Return: False as no reply is requested
+ */
+static bool vu_set_vring_enable_exec(struct vu_dev *vdev,
+ struct vhost_user_msg *msg)
+{
+ unsigned int enable = msg->payload.state.num;
+ unsigned int idx = msg->payload.state.index;
+
+ debug("State.index: %u", idx);
+ debug("State.enable: %u", enable);
+
+ if (idx >= VHOST_USER_MAX_QUEUES)
+ die("Invalid vring_enable index: %u", idx);
+
+ vdev->vq[idx].enable = enable;
+ return false;
+}
+
+/**
+ * vu_init() - Initialize vhost-user device structure
+ * @c: execution context
+ * @vdev: vhost-user device
+ */
+void vu_init(struct ctx *c)
+{
+ int i;
+
+ c->vdev = &vdev_storage;
+ c->vdev->context = c;
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++) {
+ c->vdev->vq[i] = (struct vu_virtq){
+ .call_fd = -1,
+ .kick_fd = -1,
+ .err_fd = -1,
+ .notification = true,
+ };
+ }
+}
+
+/**
+ * vu_cleanup() - Reset vhost-user device
+ * @vdev: vhost-user device
+ */
+void vu_cleanup(struct vu_dev *vdev)
+{
+ unsigned int i;
+
+ for (i = 0; i < VHOST_USER_MAX_QUEUES; i++) {
+ struct vu_virtq *vq = &vdev->vq[i];
+
+ vq->started = false;
+ vq->notification = true;
+
+ if (vq->call_fd != -1) {
+ close(vq->call_fd);
+ vq->call_fd = -1;
+ }
+ if (vq->err_fd != -1) {
+ close(vq->err_fd);
+ vq->err_fd = -1;
+ }
+ if (vq->kick_fd != -1) {
+ vu_remove_watch(vdev, vq->kick_fd);
+ close(vq->kick_fd);
+ vq->kick_fd = -1;
+ }
+
+ vq->vring.desc = 0;
+ vq->vring.used = 0;
+ vq->vring.avail = 0;
+ }
+
+ for (i = 0; i < vdev->nregions; i++) {
+ const struct vu_dev_region *r = &vdev->regions[i];
+
+ if (r->mmap_addr) {
+ /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
+ munmap((void *)(uintptr_t)r->mmap_addr,
+ r->size + r->mmap_offset);
+ }
+ }
+ vdev->nregions = 0;
+}
+
+/**
+ * vu_sock_reset() - Reset connection socket
+ * @vdev: vhost-user device
+ */
+static void vu_sock_reset(struct vu_dev *vdev)
+{
+ tap_sock_reset(vdev->context);
+}
+
+static bool (*vu_handle[VHOST_USER_MAX])(struct vu_dev *vdev,
+ struct vhost_user_msg *msg) = {
+ [VHOST_USER_GET_FEATURES] = vu_get_features_exec,
+ [VHOST_USER_SET_FEATURES] = vu_set_features_exec,
+ [VHOST_USER_GET_PROTOCOL_FEATURES] = vu_get_protocol_features_exec,
+ [VHOST_USER_SET_PROTOCOL_FEATURES] = vu_set_protocol_features_exec,
+ [VHOST_USER_GET_QUEUE_NUM] = vu_get_queue_num_exec,
+ [VHOST_USER_SET_OWNER] = vu_set_owner_exec,
+ [VHOST_USER_SET_MEM_TABLE] = vu_set_mem_table_exec,
+ [VHOST_USER_SET_VRING_NUM] = vu_set_vring_num_exec,
+ [VHOST_USER_SET_VRING_ADDR] = vu_set_vring_addr_exec,
+ [VHOST_USER_SET_VRING_BASE] = vu_set_vring_base_exec,
+ [VHOST_USER_GET_VRING_BASE] = vu_get_vring_base_exec,
+ [VHOST_USER_SET_VRING_KICK] = vu_set_vring_kick_exec,
+ [VHOST_USER_SET_VRING_CALL] = vu_set_vring_call_exec,
+ [VHOST_USER_SET_VRING_ERR] = vu_set_vring_err_exec,
+ [VHOST_USER_SET_VRING_ENABLE] = vu_set_vring_enable_exec,
+};
+
+/**
+ * vu_control_handler() - Handle control commands for vhost-user
+ * @vdev: vhost-user device
+ * @fd: vhost-user message socket
+ * @events: epoll events
+ */
+void vu_control_handler(struct vu_dev *vdev, int fd, uint32_t events)
+{
+ struct vhost_user_msg msg = { 0 };
+ bool need_reply, reply_requested;
+ int ret;
+
+ if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
+ vu_sock_reset(vdev);
+ return;
+ }
+
+ ret = vu_message_read_default(fd, &msg);
+ if (ret == 0) {
+ vu_sock_reset(vdev);
+ return;
+ }
+ debug("================ Vhost user message ================");
+ debug("Request: %s (%d)", vu_request_to_string(msg.hdr.request),
+ msg.hdr.request);
+ debug("Flags: 0x%x", msg.hdr.flags);
+ debug("Size: %u", msg.hdr.size);
+
+ need_reply = msg.hdr.flags & VHOST_USER_NEED_REPLY_MASK;
+
+ if (msg.hdr.request >= 0 && msg.hdr.request < VHOST_USER_MAX &&
+ vu_handle[msg.hdr.request])
+ reply_requested = vu_handle[msg.hdr.request](vdev, &msg);
+ else
+ die("Unhandled request: %d", msg.hdr.request);
+
+ /* cppcheck-suppress legacyUninitvar */
+ if (!reply_requested && need_reply) {
+ msg.payload.u64 = 0;
+ msg.hdr.flags = 0;
+ msg.hdr.size = sizeof(msg.payload.u64);
+ msg.fd_num = 0;
+ reply_requested = true;
+ }
+
+ if (reply_requested)
+ vu_send_reply(fd, &msg);
+}
diff --git a/vhost_user.h b/vhost_user.h
new file mode 100644
index 0000000..464ba21
--- /dev/null
+++ b/vhost_user.h
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vhost-user API, command management and virtio interface
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+/* some parts from subprojects/libvhost-user/libvhost-user.h */
+
+#ifndef VHOST_USER_H
+#define VHOST_USER_H
+
+#include "virtio.h"
+#include "iov.h"
+
+#define VHOST_USER_F_PROTOCOL_FEATURES 30
+
+#define VHOST_MEMORY_BASELINE_NREGIONS 8
+
+/**
+ * enum vhost_user_protocol_feature - List of available vhost-user features
+ */
+enum vhost_user_protocol_feature {
+ VHOST_USER_PROTOCOL_F_MQ = 0,
+ VHOST_USER_PROTOCOL_F_LOG_SHMFD = 1,
+ VHOST_USER_PROTOCOL_F_RARP = 2,
+ VHOST_USER_PROTOCOL_F_REPLY_ACK = 3,
+ VHOST_USER_PROTOCOL_F_NET_MTU = 4,
+ VHOST_USER_PROTOCOL_F_BACKEND_REQ = 5,
+ VHOST_USER_PROTOCOL_F_CROSS_ENDIAN = 6,
+ VHOST_USER_PROTOCOL_F_CRYPTO_SESSION = 7,
+ VHOST_USER_PROTOCOL_F_PAGEFAULT = 8,
+ VHOST_USER_PROTOCOL_F_CONFIG = 9,
+ VHOST_USER_PROTOCOL_F_SLAVE_SEND_FD = 10,
+ VHOST_USER_PROTOCOL_F_HOST_NOTIFIER = 11,
+ VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD = 12,
+ VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS = 14,
+ VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS = 15,
+
+ VHOST_USER_PROTOCOL_F_MAX
+};
+
+/**
+ * enum vhost_user_request - List of available vhost-user requests
+ */
+enum vhost_user_request {
+ VHOST_USER_NONE = 0,
+ VHOST_USER_GET_FEATURES = 1,
+ VHOST_USER_SET_FEATURES = 2,
+ VHOST_USER_SET_OWNER = 3,
+ VHOST_USER_RESET_OWNER = 4,
+ VHOST_USER_SET_MEM_TABLE = 5,
+ VHOST_USER_SET_LOG_BASE = 6,
+ VHOST_USER_SET_LOG_FD = 7,
+ VHOST_USER_SET_VRING_NUM = 8,
+ VHOST_USER_SET_VRING_ADDR = 9,
+ VHOST_USER_SET_VRING_BASE = 10,
+ VHOST_USER_GET_VRING_BASE = 11,
+ VHOST_USER_SET_VRING_KICK = 12,
+ VHOST_USER_SET_VRING_CALL = 13,
+ VHOST_USER_SET_VRING_ERR = 14,
+ VHOST_USER_GET_PROTOCOL_FEATURES = 15,
+ VHOST_USER_SET_PROTOCOL_FEATURES = 16,
+ VHOST_USER_GET_QUEUE_NUM = 17,
+ VHOST_USER_SET_VRING_ENABLE = 18,
+ VHOST_USER_SEND_RARP = 19,
+ VHOST_USER_NET_SET_MTU = 20,
+ VHOST_USER_SET_BACKEND_REQ_FD = 21,
+ VHOST_USER_IOTLB_MSG = 22,
+ VHOST_USER_SET_VRING_ENDIAN = 23,
+ VHOST_USER_GET_CONFIG = 24,
+ VHOST_USER_SET_CONFIG = 25,
+ VHOST_USER_CREATE_CRYPTO_SESSION = 26,
+ VHOST_USER_CLOSE_CRYPTO_SESSION = 27,
+ VHOST_USER_POSTCOPY_ADVISE = 28,
+ VHOST_USER_POSTCOPY_LISTEN = 29,
+ VHOST_USER_POSTCOPY_END = 30,
+ VHOST_USER_GET_INFLIGHT_FD = 31,
+ VHOST_USER_SET_INFLIGHT_FD = 32,
+ VHOST_USER_GPU_SET_SOCKET = 33,
+ VHOST_USER_VRING_KICK = 35,
+ VHOST_USER_GET_MAX_MEM_SLOTS = 36,
+ VHOST_USER_ADD_MEM_REG = 37,
+ VHOST_USER_REM_MEM_REG = 38,
+ VHOST_USER_MAX
+};
+
+/**
+ * struct vhost_user_header - vhost-user message header
+ * @request: Request type of the message
+ * @flags: Request flags
+ * @size: The following payload size
+ */
+struct vhost_user_header {
+ enum vhost_user_request request;
+
+#define VHOST_USER_VERSION_MASK 0x3
+#define VHOST_USER_REPLY_MASK (0x1 << 2)
+#define VHOST_USER_NEED_REPLY_MASK (0x1 << 3)
+ uint32_t flags;
+ uint32_t size;
+} __attribute__ ((__packed__));
+
+/**
+ * struct vhost_user_memory_region - Front-end shared memory region information
+ * @guest_phys_addr: Guest physical address of the region
+ * @memory_size: Memory size
+ * @userspace_addr: front-end (QEMU) userspace address
+ * @mmap_offset: region offset in the shared memory area
+ */
+struct vhost_user_memory_region {
+ uint64_t guest_phys_addr;
+ uint64_t memory_size;
+ uint64_t userspace_addr;
+ uint64_t mmap_offset;
+};
+
+/**
+ * struct vhost_user_memory - List of all the shared memory regions
+ * @nregions: Number of memory regions
+ * @padding: Padding
+ * @regions: Memory regions list
+ */
+struct vhost_user_memory {
+ uint32_t nregions;
+ uint32_t padding;
+ struct vhost_user_memory_region regions[VHOST_MEMORY_BASELINE_NREGIONS];
+};
+
+/**
+ * union vhost_user_payload - vhost-user message payload
+ * @u64: 64-bit payload
+ * @state: vring state payload
+ * @addr: vring addresses payload
+ * vhost_user_memory: Memory regions information payload
+ */
+union vhost_user_payload {
+#define VHOST_USER_VRING_IDX_MASK 0xff
+#define VHOST_USER_VRING_NOFD_MASK (0x1 << 8)
+ uint64_t u64;
+ struct vhost_vring_state state;
+ struct vhost_vring_addr addr;
+ struct vhost_user_memory memory;
+};
+
+/**
+ * struct vhost_user_msg - vhost-use message
+ * @hdr: Message header
+ * @payload: Message payload
+ * @fds: File descriptors associated with the message
+ * in the ancillary data.
+ * (shared memory or event file descriptors)
+ * @fd_num: Number of file descriptors
+ */
+struct vhost_user_msg {
+ struct vhost_user_header hdr;
+ union vhost_user_payload payload;
+
+ int fds[VHOST_MEMORY_BASELINE_NREGIONS];
+ int fd_num;
+} __attribute__ ((__packed__));
+#define VHOST_USER_HDR_SIZE sizeof(struct vhost_user_header)
+
+/* index of the RX virtqueue */
+#define VHOST_USER_RX_QUEUE 0
+/* index of the TX virtqueue */
+#define VHOST_USER_TX_QUEUE 1
+
+/* in case of multiqueue, the RX and TX queues are interleaved */
+#define VHOST_USER_IS_QUEUE_TX(n) (n % 2)
+#define VHOST_USER_IS_QUEUE_RX(n) (!(n % 2))
+
+/* Default virtio-net header for passt */
+#define VU_HEADER ((struct virtio_net_hdr){ \
+ .flags = VIRTIO_NET_HDR_F_DATA_VALID, \
+ .gso_type = VIRTIO_NET_HDR_GSO_NONE, \
+})
+
+/**
+ * vu_queue_enabled - Return state of a virtqueue
+ * @vq: virtqueue to check
+ *
+ * Return: true if the virqueue is enabled, false otherwise
+ */
+static inline bool vu_queue_enabled(const struct vu_virtq *vq)
+{
+ return vq->enable;
+}
+
+/**
+ * vu_queue_started - Return state of a virtqueue
+ * @vq: virtqueue to check
+ *
+ * Return: true if the virqueue is started, false otherwise
+ */
+static inline bool vu_queue_started(const struct vu_virtq *vq)
+{
+ return vq->started;
+}
+
+void vu_print_capabilities(void);
+void vu_init(struct ctx *c);
+void vu_cleanup(struct vu_dev *vdev);
+void vu_control_handler(struct vu_dev *vdev, int fd, uint32_t events);
+#endif /* VHOST_USER_H */
diff --git a/virtio.c b/virtio.c
new file mode 100644
index 0000000..625bac3
--- /dev/null
+++ b/virtio.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0-or-later AND BSD-3-Clause
+/*
+ * virtio API, vring and virtqueue functions definition
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+/* Some parts copied from QEMU subprojects/libvhost-user/libvhost-user.c
+ * originally licensed under the following terms:
+ *
+ * --
+ *
+ * Copyright IBM, Corp. 2007
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Marc-André Lureau <mlureau@redhat.com>
+ * Victor Kaplansky <victork@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ *
+ * Some parts copied from QEMU hw/virtio/virtio.c
+ * licensed under the following terms:
+ *
+ * Copyright IBM, Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * --
+ *
+ * virtq_used_event() and virtq_avail_event() from
+ * https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-712000A
+ * licensed under the following terms:
+ *
+ * --
+ *
+ * This header is BSD licensed so anyone can use the definitions
+ * to implement compatible drivers/servers.
+ *
+ * Copyright 2007, 2009, IBM Corporation
+ * Copyright 2011, Red Hat, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of IBM nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ‘‘AS IS’’ AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <endian.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+
+#include "util.h"
+#include "virtio.h"
+
+#define VIRTQUEUE_MAX_SIZE 1024
+
+/**
+ * vu_gpa_to_va() - Translate guest physical address to our virtual address.
+ * @dev: Vhost-user device
+ * @plen: Physical length to map (input), capped to region (output)
+ * @guest_addr: Guest physical address
+ *
+ * Return: virtual address in our address space of the guest physical address
+ */
+static void *vu_gpa_to_va(const struct vu_dev *dev, uint64_t *plen,
+ uint64_t guest_addr)
+{
+ unsigned int i;
+
+ if (*plen == 0)
+ return NULL;
+
+ /* Find matching memory region. */
+ for (i = 0; i < dev->nregions; i++) {
+ const struct vu_dev_region *r = &dev->regions[i];
+
+ if ((guest_addr >= r->gpa) &&
+ (guest_addr < (r->gpa + r->size))) {
+ if ((guest_addr + *plen) > (r->gpa + r->size))
+ *plen = r->gpa + r->size - guest_addr;
+ /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
+ return (void *)(uintptr_t)(guest_addr - r->gpa +
+ r->mmap_addr +
+ r->mmap_offset);
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * vring_avail_flags() - Read the available ring flags
+ * @vq: Virtqueue
+ *
+ * Return: the available ring descriptor flags of the given virtqueue
+ */
+static inline uint16_t vring_avail_flags(const struct vu_virtq *vq)
+{
+ return le16toh(vq->vring.avail->flags);
+}
+
+/**
+ * vring_avail_idx() - Read the available ring index
+ * @vq: Virtqueue
+ *
+ * Return: the available ring index of the given virtqueue
+ */
+static inline uint16_t vring_avail_idx(struct vu_virtq *vq)
+{
+ vq->shadow_avail_idx = le16toh(vq->vring.avail->idx);
+
+ return vq->shadow_avail_idx;
+}
+
+/**
+ * vring_avail_ring() - Read an available ring entry
+ * @vq: Virtqueue
+ * @i: Index of the entry to read
+ *
+ * Return: the ring entry content (head of the descriptor chain)
+ */
+static inline uint16_t vring_avail_ring(const struct vu_virtq *vq, int i)
+{
+ return le16toh(vq->vring.avail->ring[i]);
+}
+
+/**
+ * virtq_used_event - Get location of used event indices
+ * (only with VIRTIO_F_EVENT_IDX)
+ * @vq Virtqueue
+ *
+ * Return: return the location of the used event index
+ */
+static inline uint16_t *virtq_used_event(const struct vu_virtq *vq)
+{
+ /* For backwards compat, used event index is at *end* of avail ring. */
+ return &vq->vring.avail->ring[vq->vring.num];
+}
+
+/**
+ * vring_get_used_event() - Get the used event from the available ring
+ * @vq Virtqueue
+ *
+ * Return: the used event (available only if VIRTIO_RING_F_EVENT_IDX is set)
+ * used_event is a performant alternative where the driver
+ * specifies how far the device can progress before a notification
+ * is required.
+ */
+static inline uint16_t vring_get_used_event(const struct vu_virtq *vq)
+{
+ return le16toh(*virtq_used_event(vq));
+}
+
+/**
+ * virtqueue_get_head() - Get the head of the descriptor chain for a given
+ * index
+ * @vq: Virtqueue
+ * @idx: Available ring entry index
+ * @head: Head of the descriptor chain
+ */
+static void virtqueue_get_head(const struct vu_virtq *vq,
+ unsigned int idx, unsigned int *head)
+{
+ /* Grab the next descriptor number they're advertising, and increment
+ * the index we've seen.
+ */
+ *head = vring_avail_ring(vq, idx % vq->vring.num);
+
+ /* If their number is silly, that's a fatal mistake. */
+ if (*head >= vq->vring.num)
+ die("vhost-user: Guest says index %u is available", *head);
+}
+
+/**
+ * virtqueue_read_indirect_desc() - Copy virtio ring descriptors from guest
+ * memory
+ * @dev: Vhost-user device
+ * @desc: Destination address to copy the descriptors to
+ * @addr: Guest memory address to copy from
+ * @len: Length of memory to copy
+ *
+ * Return: -1 if there is an error, 0 otherwise
+ */
+static int virtqueue_read_indirect_desc(const struct vu_dev *dev,
+ struct vring_desc *desc,
+ uint64_t addr, size_t len)
+{
+ uint64_t read_len;
+
+ if (len > (VIRTQUEUE_MAX_SIZE * sizeof(struct vring_desc)))
+ return -1;
+
+ if (len == 0)
+ return -1;
+
+ while (len) {
+ const struct vring_desc *orig_desc;
+
+ read_len = len;
+ orig_desc = vu_gpa_to_va(dev, &read_len, addr);
+ if (!orig_desc)
+ return -1;
+
+ memcpy(desc, orig_desc, read_len);
+ len -= read_len;
+ addr += read_len;
+ desc += read_len / sizeof(struct vring_desc);
+ }
+
+ return 0;
+}
+
+/**
+ * enum virtqueue_read_desc_state - State in the descriptor chain
+ * @VIRTQUEUE_READ_DESC_ERROR Found an invalid descriptor
+ * @VIRTQUEUE_READ_DESC_DONE No more descriptors in the chain
+ * @VIRTQUEUE_READ_DESC_MORE there are more descriptors in the chain
+ */
+enum virtqueue_read_desc_state {
+ VIRTQUEUE_READ_DESC_ERROR = -1,
+ VIRTQUEUE_READ_DESC_DONE = 0, /* end of chain */
+ VIRTQUEUE_READ_DESC_MORE = 1, /* more buffers in chain */
+};
+
+/**
+ * virtqueue_read_next_desc() - Read the the next descriptor in the chain
+ * @desc: Virtio ring descriptors
+ * @i: Index of the current descriptor
+ * @max: Maximum value of the descriptor index
+ * @next: Index of the next descriptor in the chain (output value)
+ *
+ * Return: current chain descriptor state (error, next, done)
+ */
+static int virtqueue_read_next_desc(const struct vring_desc *desc,
+ int i, unsigned int max, unsigned int *next)
+{
+ /* If this descriptor says it doesn't chain, we're done. */
+ if (!(le16toh(desc[i].flags) & VRING_DESC_F_NEXT))
+ return VIRTQUEUE_READ_DESC_DONE;
+
+ /* Check they're not leading us off end of descriptors. */
+ *next = le16toh(desc[i].next);
+ /* Make sure compiler knows to grab that: we don't want it changing! */
+ smp_wmb();
+
+ if (*next >= max)
+ return VIRTQUEUE_READ_DESC_ERROR;
+
+ return VIRTQUEUE_READ_DESC_MORE;
+}
+
+/**
+ * vu_queue_empty() - Check if virtqueue is empty
+ * @vq: Virtqueue
+ *
+ * Return: true if the virtqueue is empty, false otherwise
+ */
+bool vu_queue_empty(struct vu_virtq *vq)
+{
+ if (!vq->vring.avail)
+ return true;
+
+ if (vq->shadow_avail_idx != vq->last_avail_idx)
+ return false;
+
+ return vring_avail_idx(vq) == vq->last_avail_idx;
+}
+
+/**
+ * vring_can_notify() - Check if a notification can be sent
+ * @dev: Vhost-user device
+ * @vq: Virtqueue
+ *
+ * Return: true if notification can be sent
+ */
+static bool vring_can_notify(const struct vu_dev *dev, struct vu_virtq *vq)
+{
+ uint16_t old, new;
+ bool v;
+
+ /* We need to expose used array entries before checking used event. */
+ smp_mb();
+
+ /* Always notify when queue is empty (when feature acknowledge) */
+ if (vu_has_feature(dev, VIRTIO_F_NOTIFY_ON_EMPTY) &&
+ !vq->inuse && vu_queue_empty(vq))
+ return true;
+
+ if (!vu_has_feature(dev, VIRTIO_RING_F_EVENT_IDX))
+ return !(vring_avail_flags(vq) & VRING_AVAIL_F_NO_INTERRUPT);
+
+ v = vq->signalled_used_valid;
+ vq->signalled_used_valid = true;
+ old = vq->signalled_used;
+ new = vq->signalled_used = vq->used_idx;
+ return !v || vring_need_event(vring_get_used_event(vq), new, old);
+}
+
+/**
+ * vu_queue_notify() - Send a notification to the given virtqueue
+ * @dev: Vhost-user device
+ * @vq: Virtqueue
+ */
+void vu_queue_notify(const struct vu_dev *dev, struct vu_virtq *vq)
+{
+ if (!vq->vring.avail)
+ return;
+
+ if (!vring_can_notify(dev, vq)) {
+ debug("vhost-user: virtqueue can skip notify...");
+ return;
+ }
+
+ if (eventfd_write(vq->call_fd, 1) < 0)
+ die_perror("Error writing vhost-user queue eventfd");
+}
+
+/* virtq_avail_event() - Get location of available event indices
+ * (only with VIRTIO_F_EVENT_IDX)
+ * @vq: Virtqueue
+ *
+ * Return: return the location of the available event index
+ */
+static inline uint16_t *virtq_avail_event(const struct vu_virtq *vq)
+{
+ /* For backwards compat, avail event index is at *end* of used ring. */
+ return (uint16_t *)&vq->vring.used->ring[vq->vring.num];
+}
+
+/**
+ * vring_set_avail_event() - Set avail_event
+ * @vq: Virtqueue
+ * @val: Value to set to avail_event
+ * avail_event is used in the same way the used_event is in the
+ * avail_ring.
+ * avail_event is used to advise the driver that notifications
+ * are unnecessary until the driver writes entry with an index
+ * specified by avail_event into the available ring.
+ */
+static inline void vring_set_avail_event(const struct vu_virtq *vq,
+ uint16_t val)
+{
+ uint16_t val_le = htole16(val);
+
+ if (!vq->notification)
+ return;
+
+ memcpy(virtq_avail_event(vq), &val_le, sizeof(val_le));
+}
+
+/**
+ * virtqueue_map_desc() - Translate descriptor ring physical address into our
+ * virtual address space
+ * @dev: Vhost-user device
+ * @p_num_sg: First iov entry to use (input),
+ * first iov entry not used (output)
+ * @iov: Iov array to use to store buffer virtual addresses
+ * @max_num_sg: Maximum number of iov entries
+ * @pa: Guest physical address of the buffer to map into our virtual
+ * address
+ * @sz: Size of the buffer
+ *
+ * Return: false on error, true otherwise
+ */
+static bool virtqueue_map_desc(const struct vu_dev *dev,
+ unsigned int *p_num_sg, struct iovec *iov,
+ unsigned int max_num_sg,
+ uint64_t pa, size_t sz)
+{
+ unsigned int num_sg = *p_num_sg;
+
+ ASSERT(num_sg < max_num_sg);
+ ASSERT(sz);
+
+ while (sz) {
+ uint64_t len = sz;
+
+ iov[num_sg].iov_base = vu_gpa_to_va(dev, &len, pa);
+ if (iov[num_sg].iov_base == NULL)
+ die("vhost-user: invalid address for buffers");
+ iov[num_sg].iov_len = len;
+ num_sg++;
+ sz -= len;
+ pa += len;
+ }
+
+ *p_num_sg = num_sg;
+ return true;
+}
+
+/**
+ * vu_queue_map_desc - Map the virtqueue descriptor ring into our virtual
+ * address space
+ * @dev: Vhost-user device
+ * @vq: Virtqueue
+ * @idx: First descriptor ring entry to map
+ * @elem: Virtqueue element to store descriptor ring iov
+ *
+ * Return: -1 if there is an error, 0 otherwise
+ */
+static int vu_queue_map_desc(const struct vu_dev *dev,
+ struct vu_virtq *vq, unsigned int idx,
+ struct vu_virtq_element *elem)
+{
+ const struct vring_desc *desc = vq->vring.desc;
+ struct vring_desc desc_buf[VIRTQUEUE_MAX_SIZE];
+ unsigned int out_num = 0, in_num = 0;
+ unsigned int max = vq->vring.num;
+ unsigned int i = idx;
+ uint64_t read_len;
+ int rc;
+
+ if (le16toh(desc[i].flags) & VRING_DESC_F_INDIRECT) {
+ unsigned int desc_len;
+ uint64_t desc_addr;
+
+ if (le32toh(desc[i].len) % sizeof(struct vring_desc))
+ die("vhost-user: Invalid size for indirect buffer table");
+
+ /* loop over the indirect descriptor table */
+ desc_addr = le64toh(desc[i].addr);
+ desc_len = le32toh(desc[i].len);
+ max = desc_len / sizeof(struct vring_desc);
+ read_len = desc_len;
+ desc = vu_gpa_to_va(dev, &read_len, desc_addr);
+ if (desc && read_len != desc_len) {
+ /* Failed to use zero copy */
+ desc = NULL;
+ if (!virtqueue_read_indirect_desc(dev, desc_buf, desc_addr, desc_len))
+ desc = desc_buf;
+ }
+ if (!desc)
+ die("vhost-user: Invalid indirect buffer table");
+ i = 0;
+ }
+
+ /* Collect all the descriptors */
+ do {
+ if (le16toh(desc[i].flags) & VRING_DESC_F_WRITE) {
+ if (!virtqueue_map_desc(dev, &in_num, elem->in_sg,
+ elem->in_num,
+ le64toh(desc[i].addr),
+ le32toh(desc[i].len)))
+ return -1;
+ } else {
+ if (in_num)
+ die("Incorrect order for descriptors");
+ if (!virtqueue_map_desc(dev, &out_num, elem->out_sg,
+ elem->out_num,
+ le64toh(desc[i].addr),
+ le32toh(desc[i].len))) {
+ return -1;
+ }
+ }
+
+ /* If we've got too many, that implies a descriptor loop. */
+ if ((in_num + out_num) > max)
+ die("vhost-user: Loop in queue descriptor list");
+ rc = virtqueue_read_next_desc(desc, i, max, &i);
+ } while (rc == VIRTQUEUE_READ_DESC_MORE);
+
+ if (rc == VIRTQUEUE_READ_DESC_ERROR)
+ die("vhost-user: Failed to read descriptor list");
+
+ elem->index = idx;
+ elem->in_num = in_num;
+ elem->out_num = out_num;
+
+ return 0;
+}
+
+/**
+ * vu_queue_pop() - Pop an entry from the virtqueue
+ * @dev: Vhost-user device
+ * @vq: Virtqueue
+ * @elem: Virtqueue element to file with the entry information
+ *
+ * Return: -1 if there is an error, 0 otherwise
+ */
+int vu_queue_pop(const struct vu_dev *dev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem)
+{
+ unsigned int head;
+ int ret;
+
+ if (!vq->vring.avail)
+ return -1;
+
+ if (vu_queue_empty(vq))
+ return -1;
+
+ /* Needed after vu_queue_empty(), see comment in
+ * virtqueue_num_heads().
+ */
+ smp_rmb();
+
+ if (vq->inuse >= vq->vring.num)
+ die("vhost-user queue size exceeded");
+
+ virtqueue_get_head(vq, vq->last_avail_idx++, &head);
+
+ if (vu_has_feature(dev, VIRTIO_RING_F_EVENT_IDX))
+ vring_set_avail_event(vq, vq->last_avail_idx);
+
+ ret = vu_queue_map_desc(dev, vq, head, elem);
+
+ if (ret < 0)
+ return ret;
+
+ vq->inuse++;
+
+ return 0;
+}
+
+/**
+ * vu_queue_detach_element() - Detach an element from the virqueue
+ * @vq: Virtqueue
+ */
+void vu_queue_detach_element(struct vu_virtq *vq)
+{
+ vq->inuse--;
+ /* unmap, when DMA support is added */
+}
+
+/**
+ * vu_queue_unpop() - Push back the previously popped element from the virqueue
+ * @vq: Virtqueue
+ */
+/* cppcheck-suppress unusedFunction */
+void vu_queue_unpop(struct vu_virtq *vq)
+{
+ vq->last_avail_idx--;
+ vu_queue_detach_element(vq);
+}
+
+/**
+ * vu_queue_rewind() - Push back a given number of popped elements
+ * @vq: Virtqueue
+ * @num: Number of element to unpop
+ */
+bool vu_queue_rewind(struct vu_virtq *vq, unsigned int num)
+{
+ if (num > vq->inuse)
+ return false;
+
+ vq->last_avail_idx -= num;
+ vq->inuse -= num;
+ return true;
+}
+
+/**
+ * vring_used_write() - Write an entry in the used ring
+ * @vq: Virtqueue
+ * @uelem: Entry to write
+ * @i: Index of the entry in the used ring
+ */
+static inline void vring_used_write(struct vu_virtq *vq,
+ const struct vring_used_elem *uelem, int i)
+{
+ struct vring_used *used = vq->vring.used;
+
+ used->ring[i] = *uelem;
+}
+
+/**
+ * vu_queue_fill_by_index() - Update information of a descriptor ring entry
+ * in the used ring
+ * @vq: Virtqueue
+ * @index: Descriptor ring index
+ * @len: Size of the element
+ * @idx: Used ring entry index
+ */
+void vu_queue_fill_by_index(struct vu_virtq *vq, unsigned int index,
+ unsigned int len, unsigned int idx)
+{
+ struct vring_used_elem uelem;
+
+ if (!vq->vring.avail)
+ return;
+
+ idx = (idx + vq->used_idx) % vq->vring.num;
+
+ uelem.id = htole32(index);
+ uelem.len = htole32(len);
+ vring_used_write(vq, &uelem, idx);
+}
+
+/**
+ * vu_queue_fill() - Update information of a given element in the used ring
+ * @dev: Vhost-user device
+ * @vq: Virtqueue
+ * @elem: Element information to fill
+ * @len: Size of the element
+ * @idx: Used ring entry index
+ */
+void vu_queue_fill(struct vu_virtq *vq, const struct vu_virtq_element *elem,
+ unsigned int len, unsigned int idx)
+{
+ vu_queue_fill_by_index(vq, elem->index, len, idx);
+}
+
+/**
+ * vring_used_idx_set() - Set the descriptor ring current index
+ * @vq: Virtqueue
+ * @val: Value to set in the index
+ */
+static inline void vring_used_idx_set(struct vu_virtq *vq, uint16_t val)
+{
+ vq->vring.used->idx = htole16(val);
+
+ vq->used_idx = val;
+}
+
+/**
+ * vu_queue_flush() - Flush the virtqueue
+ * @vq: Virtqueue
+ * @count: Number of entry to flush
+ */
+void vu_queue_flush(struct vu_virtq *vq, unsigned int count)
+{
+ uint16_t old, new;
+
+ if (!vq->vring.avail)
+ return;
+
+ /* Make sure buffer is written before we update index. */
+ smp_wmb();
+
+ old = vq->used_idx;
+ new = old + count;
+ vring_used_idx_set(vq, new);
+ vq->inuse -= count;
+ if ((uint16_t)(new - vq->signalled_used) < (uint16_t)(new - old))
+ vq->signalled_used_valid = false;
+}
diff --git a/virtio.h b/virtio.h
new file mode 100644
index 0000000..0af259d
--- /dev/null
+++ b/virtio.h
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio API, vring and virtqueue functions definition
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#ifndef VIRTIO_H
+#define VIRTIO_H
+
+#include <stdbool.h>
+#include <linux/vhost_types.h>
+
+/* Maximum size of a virtqueue */
+#define VIRTQUEUE_MAX_SIZE 1024
+
+/**
+ * struct vu_ring - Virtqueue rings
+ * @num: Size of the queue
+ * @desc: Descriptor ring
+ * @avail: Available ring
+ * @used: Used ring
+ * @log_guest_addr: Guest address for logging
+ * @flags: Vring flags
+ * VHOST_VRING_F_LOG is set if log address is valid
+ */
+struct vu_ring {
+ unsigned int num;
+ struct vring_desc *desc;
+ struct vring_avail *avail;
+ struct vring_used *used;
+ uint64_t log_guest_addr;
+ uint32_t flags;
+};
+
+/**
+ * struct vu_virtq - Virtqueue definition
+ * @vring: Virtqueue rings
+ * @last_avail_idx: Next head to pop
+ * @shadow_avail_idx: Last avail_idx read from VQ.
+ * @used_idx: Descriptor ring current index
+ * @signalled_used: Last used index value we have signalled on
+ * @signalled_used_valid: True if signalled_used if valid
+ * @notification: True if the queues notify (via event
+ * index or interrupt)
+ * @inuse: Number of entries in use
+ * @call_fd: The event file descriptor to signal when
+ * buffers are used.
+ * @kick_fd: The event file descriptor for adding
+ * buffers to the vring
+ * @err_fd: The event file descriptor to signal when
+ * error occurs
+ * @enable: True if the virtqueue is enabled
+ * @started: True if the virtqueue is started
+ * @vra: QEMU address of our rings
+ */
+struct vu_virtq {
+ struct vu_ring vring;
+ uint16_t last_avail_idx;
+ uint16_t shadow_avail_idx;
+ uint16_t used_idx;
+ uint16_t signalled_used;
+ bool signalled_used_valid;
+ bool notification;
+ unsigned int inuse;
+ int call_fd;
+ int kick_fd;
+ int err_fd;
+ unsigned int enable;
+ bool started;
+ struct vhost_vring_addr vra;
+};
+
+/**
+ * struct vu_dev_region - guest shared memory region
+ * @gpa: Guest physical address of the region
+ * @size: Memory size in bytes
+ * @qva: QEMU virtual address
+ * @mmap_offset: Offset where the region starts in the mapped memory
+ * @mmap_addr: Address of the mapped memory
+ */
+struct vu_dev_region {
+ uint64_t gpa;
+ uint64_t size;
+ uint64_t qva;
+ uint64_t mmap_offset;
+ uint64_t mmap_addr;
+};
+
+#define VHOST_USER_MAX_QUEUES 2
+
+/*
+ * Set a reasonable maximum number of ram slots, which will be supported by
+ * any architecture.
+ */
+#define VHOST_USER_MAX_RAM_SLOTS 32
+
+/**
+ * struct vu_dev - vhost-user device information
+ * @context: Execution context
+ * @nregions: Number of shared memory regions
+ * @regions: Guest shared memory regions
+ * @features: Vhost-user features
+ * @protocol_features: Vhost-user protocol features
+ */
+struct vu_dev {
+ struct ctx *context;
+ uint32_t nregions;
+ struct vu_dev_region regions[VHOST_USER_MAX_RAM_SLOTS];
+ struct vu_virtq vq[VHOST_USER_MAX_QUEUES];
+ uint64_t features;
+ uint64_t protocol_features;
+};
+
+/**
+ * struct vu_virtq_element - virtqueue element
+ * @index: Descriptor ring index
+ * @out_num: Number of outgoing iovec buffers
+ * @in_num: Number of incoming iovec buffers
+ * @in_sg: Incoming iovec buffers
+ * @out_sg: Outgoing iovec buffers
+ */
+struct vu_virtq_element {
+ unsigned int index;
+ unsigned int out_num;
+ unsigned int in_num;
+ struct iovec *in_sg;
+ struct iovec *out_sg;
+};
+
+/**
+ * has_feature() - Check a feature bit in a features set
+ * @features: Features set
+ * @fb: Feature bit to check
+ *
+ * Return: True if the feature bit is set
+ */
+static inline bool has_feature(uint64_t features, unsigned int fbit)
+{
+ return !!(features & (1ULL << fbit));
+}
+
+/**
+ * vu_has_feature() - Check if a virtio-net feature is available
+ * @vdev: Vhost-user device
+ * @bit: Feature to check
+ *
+ * Return: True if the feature is available
+ */
+static inline bool vu_has_feature(const struct vu_dev *vdev,
+ unsigned int fbit)
+{
+ return has_feature(vdev->features, fbit);
+}
+
+/**
+ * vu_has_protocol_feature() - Check if a vhost-user feature is available
+ * @vdev: Vhost-user device
+ * @bit: Feature to check
+ *
+ * Return: True if the feature is available
+ */
+/* cppcheck-suppress unusedFunction */
+static inline bool vu_has_protocol_feature(const struct vu_dev *vdev,
+ unsigned int fbit)
+{
+ return has_feature(vdev->protocol_features, fbit);
+}
+
+bool vu_queue_empty(struct vu_virtq *vq);
+void vu_queue_notify(const struct vu_dev *dev, struct vu_virtq *vq);
+int vu_queue_pop(const struct vu_dev *dev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem);
+void vu_queue_detach_element(struct vu_virtq *vq);
+void vu_queue_unpop(struct vu_virtq *vq);
+bool vu_queue_rewind(struct vu_virtq *vq, unsigned int num);
+void vu_queue_fill_by_index(struct vu_virtq *vq, unsigned int index,
+ unsigned int len, unsigned int idx);
+void vu_queue_fill(struct vu_virtq *vq,
+ const struct vu_virtq_element *elem, unsigned int len,
+ unsigned int idx);
+void vu_queue_flush(struct vu_virtq *vq, unsigned int count);
+#endif /* VIRTIO_H */
diff --git a/vu_common.c b/vu_common.c
new file mode 100644
index 0000000..6d365be
--- /dev/null
+++ b/vu_common.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ *
+ * common_vu.c - vhost-user common UDP and TCP functions
+ */
+
+#include <unistd.h>
+#include <sys/uio.h>
+#include <sys/eventfd.h>
+#include <netinet/if_ether.h>
+#include <linux/virtio_net.h>
+
+#include "util.h"
+#include "passt.h"
+#include "tap.h"
+#include "vhost_user.h"
+#include "pcap.h"
+#include "vu_common.h"
+
+/**
+ * vu_packet_check_range() - Check if a given memory zone is contained in
+ * a mapped guest memory region
+ * @buf: Array of the available memory regions
+ * @offset: Offset of data range in packet descriptor
+ * @size: Length of desired data range
+ * @start: Start of the packet descriptor
+ *
+ * Return: 0 if the zone is in a mapped memory region, -1 otherwise
+ */
+int vu_packet_check_range(void *buf, size_t offset, size_t len,
+ const char *start)
+{
+ struct vu_dev_region *dev_region;
+
+ for (dev_region = buf; dev_region->mmap_addr; dev_region++) {
+ /* NOLINTNEXTLINE(performance-no-int-to-ptr) */
+ char *m = (char *)(uintptr_t)dev_region->mmap_addr;
+
+ if (m <= start &&
+ start + offset + len <= m + dev_region->mmap_offset +
+ dev_region->size)
+ return 0;
+ }
+
+ return -1;
+}
+
+/**
+ * vu_init_elem() - initialize an array of virtqueue elements with 1 iov in each
+ * @elem: Array of virtqueue elements to initialize
+ * @iov: Array of iovec to assign to virtqueue element
+ * @elem_cnt: Number of virtqueue element
+ */
+void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov, int elem_cnt)
+{
+ int i;
+
+ for (i = 0; i < elem_cnt; i++)
+ vu_set_element(&elem[i], NULL, &iov[i]);
+}
+
+/**
+ * vu_collect() - collect virtio buffers from a given virtqueue
+ * @vdev: vhost-user device
+ * @vq: virtqueue to collect from
+ * @elem: Array of virtqueue element
+ * each element must be initialized with one iovec entry
+ * in the in_sg array.
+ * @max_elem: Number of virtqueue elements in the array
+ * @size: Maximum size of the data in the frame
+ * @frame_size: The total size of the buffers (output)
+ *
+ * Return: number of elements used to contain the frame
+ */
+int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem, int max_elem,
+ size_t size, size_t *frame_size)
+{
+ size_t current_size = 0;
+ int elem_cnt = 0;
+
+ while (current_size < size && elem_cnt < max_elem) {
+ struct iovec *iov;
+ int ret;
+
+ ret = vu_queue_pop(vdev, vq, &elem[elem_cnt]);
+ if (ret < 0)
+ break;
+
+ if (elem[elem_cnt].in_num < 1) {
+ warn("virtio-net receive queue contains no in buffers");
+ vu_queue_detach_element(vq);
+ break;
+ }
+
+ iov = &elem[elem_cnt].in_sg[0];
+
+ if (iov->iov_len > size - current_size)
+ iov->iov_len = size - current_size;
+
+ current_size += iov->iov_len;
+ elem_cnt++;
+
+ if (!vu_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF))
+ break;
+ }
+
+ if (frame_size)
+ *frame_size = current_size;
+
+ return elem_cnt;
+}
+
+/**
+ * vu_set_vnethdr() - set virtio-net headers
+ * @vdev: vhost-user device
+ * @vnethdr: Address of the header to set
+ * @num_buffers: Number of guest buffers of the frame
+ */
+void vu_set_vnethdr(const struct vu_dev *vdev,
+ struct virtio_net_hdr_mrg_rxbuf *vnethdr,
+ int num_buffers)
+{
+ vnethdr->hdr = VU_HEADER;
+ if (vu_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF))
+ vnethdr->num_buffers = htole16(num_buffers);
+}
+
+/**
+ * vu_flush() - flush all the collected buffers to the vhost-user interface
+ * @vdev: vhost-user device
+ * @vq: vhost-user virtqueue
+ * @elem: virtqueue elements array to send back to the virtqueue
+ * @elem_cnt: Length of the array
+ */
+void vu_flush(const struct vu_dev *vdev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem, int elem_cnt)
+{
+ int i;
+
+ for (i = 0; i < elem_cnt; i++)
+ vu_queue_fill(vq, &elem[i], elem[i].in_sg[0].iov_len, i);
+
+ vu_queue_flush(vq, elem_cnt);
+ vu_queue_notify(vdev, vq);
+}
+
+/**
+ * vu_handle_tx() - Receive data from the TX virtqueue
+ * @vdev: vhost-user device
+ * @index: index of the virtqueue
+ * @now: Current timestamp
+ */
+static void vu_handle_tx(struct vu_dev *vdev, int index,
+ const struct timespec *now)
+{
+ struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE];
+ struct iovec out_sg[VIRTQUEUE_MAX_SIZE];
+ struct vu_virtq *vq = &vdev->vq[index];
+ int hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf);
+ int out_sg_count;
+ int count;
+
+ ASSERT(VHOST_USER_IS_QUEUE_TX(index));
+
+ tap_flush_pools();
+
+ count = 0;
+ out_sg_count = 0;
+ while (count < VIRTQUEUE_MAX_SIZE) {
+ int ret;
+
+ vu_set_element(&elem[count], &out_sg[out_sg_count], NULL);
+ ret = vu_queue_pop(vdev, vq, &elem[count]);
+ if (ret < 0)
+ break;
+ out_sg_count += elem[count].out_num;
+
+ if (elem[count].out_num < 1) {
+ warn("virtio-net transmit queue contains no out buffers");
+ break;
+ }
+ ASSERT(elem[count].out_num == 1);
+
+ tap_add_packet(vdev->context,
+ elem[count].out_sg[0].iov_len - hdrlen,
+ (char *)elem[count].out_sg[0].iov_base + hdrlen);
+ count++;
+ }
+ tap_handler(vdev->context, now);
+
+ if (count) {
+ int i;
+
+ for (i = 0; i < count; i++)
+ vu_queue_fill(vq, &elem[i], 0, i);
+ vu_queue_flush(vq, count);
+ vu_queue_notify(vdev, vq);
+ }
+}
+
+/**
+ * vu_kick_cb() - Called on a kick event to start to receive data
+ * @vdev: vhost-user device
+ * @ref: epoll reference information
+ * @now: Current timestamp
+ */
+void vu_kick_cb(struct vu_dev *vdev, union epoll_ref ref,
+ const struct timespec *now)
+{
+ eventfd_t kick_data;
+ ssize_t rc;
+
+ rc = eventfd_read(ref.fd, &kick_data);
+ if (rc == -1)
+ die_perror("vhost-user kick eventfd_read()");
+
+ debug("vhost-user: got kick_data: %016"PRIx64" idx: %d",
+ kick_data, ref.queue);
+ if (VHOST_USER_IS_QUEUE_TX(ref.queue))
+ vu_handle_tx(vdev, ref.queue, now);
+}
+
+/**
+ * vu_send_single() - Send a buffer to the front-end using the RX virtqueue
+ * @c: execution context
+ * @buf: address of the buffer
+ * @size: size of the buffer
+ *
+ * Return: number of bytes sent, -1 if there is an error
+ */
+int vu_send_single(const struct ctx *c, const void *buf, size_t size)
+{
+ struct vu_dev *vdev = c->vdev;
+ struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
+ struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE];
+ struct iovec in_sg[VIRTQUEUE_MAX_SIZE];
+ size_t total;
+ int elem_cnt;
+ int i;
+
+ debug("vu_send_single size %zu", size);
+
+ if (!vu_queue_enabled(vq) || !vu_queue_started(vq)) {
+ debug("Got packet, but RX virtqueue not usable yet");
+ return -1;
+ }
+
+ vu_init_elem(elem, in_sg, VIRTQUEUE_MAX_SIZE);
+
+ size += sizeof(struct virtio_net_hdr_mrg_rxbuf);
+ elem_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE, size, &total);
+ if (total < size) {
+ debug("vu_send_single: no space to send the data "
+ "elem_cnt %d size %zd", elem_cnt, total);
+ goto err;
+ }
+
+ vu_set_vnethdr(vdev, in_sg[0].iov_base, elem_cnt);
+
+ total -= sizeof(struct virtio_net_hdr_mrg_rxbuf);
+
+ /* copy data from the buffer to the iovec */
+ iov_from_buf(in_sg, elem_cnt, sizeof(struct virtio_net_hdr_mrg_rxbuf),
+ buf, total);
+
+ if (*c->pcap) {
+ pcap_iov(in_sg, elem_cnt,
+ sizeof(struct virtio_net_hdr_mrg_rxbuf));
+ }
+
+ vu_flush(vdev, vq, elem, elem_cnt);
+
+ debug("vhost-user sent %zu", total);
+
+ return total;
+err:
+ for (i = 0; i < elem_cnt; i++)
+ vu_queue_detach_element(vq);
+
+ return -1;
+}
diff --git a/vu_common.h b/vu_common.h
new file mode 100644
index 0000000..bd70faf
--- /dev/null
+++ b/vu_common.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ *
+ * vhost-user common UDP and TCP functions
+ */
+
+#ifndef VU_COMMON_H
+#define VU_COMMON_H
+#include <linux/virtio_net.h>
+
+static inline void *vu_eth(void *base)
+{
+ return ((char *)base + sizeof(struct virtio_net_hdr_mrg_rxbuf));
+}
+
+static inline void *vu_ip(void *base)
+{
+ return (struct ethhdr *)vu_eth(base) + 1;
+}
+
+static inline void *vu_payloadv4(void *base)
+{
+ return (struct iphdr *)vu_ip(base) + 1;
+}
+
+static inline void *vu_payloadv6(void *base)
+{
+ return (struct ipv6hdr *)vu_ip(base) + 1;
+}
+
+/**
+ * vu_set_element() - Initialize a vu_virtq_element
+ * @elem: Element to initialize
+ * @out_sg: One out iovec entry to set in elem
+ * @in_sg: One in iovec entry to set in elem
+ */
+static inline void vu_set_element(struct vu_virtq_element *elem,
+ struct iovec *out_sg, struct iovec *in_sg)
+{
+ elem->out_num = !!out_sg;
+ elem->out_sg = out_sg;
+ elem->in_num = !!in_sg;
+ elem->in_sg = in_sg;
+}
+
+void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov,
+ int elem_cnt);
+int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem, int max_elem, size_t size,
+ size_t *frame_size);
+void vu_set_vnethdr(const struct vu_dev *vdev,
+ struct virtio_net_hdr_mrg_rxbuf *vnethdr,
+ int num_buffers);
+void vu_flush(const struct vu_dev *vdev, struct vu_virtq *vq,
+ struct vu_virtq_element *elem, int elem_cnt);
+void vu_kick_cb(struct vu_dev *vdev, union epoll_ref ref,
+ const struct timespec *now);
+int vu_send_single(const struct ctx *c, const void *buf, size_t size);
+#endif /* VU_COMMON_H */