Compare commits

...

12 Commits

Author SHA1 Message Date
David Gibson 954589b64b test: Verify that podman tests are using the pasta binary we expect
Paul Holzinger pointed out that when we invoke the podman tests inside the
passt testsuite, the way we point podman at the newly built pasta binary
is kind of indirect.  It's therefore prudent to check that podman is
actually using the binary we expect it to - in particular that it is using
the binary built in this tree, not some system installed pasta binary.

Suggested-by: Paul Holzinger <pholzing@redhat.com>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:24 +02:00
David Gibson 489b28e216 test: catatonit may not be in $PATH
The pasta_podman/bats test script looks for 'catatonit' amongst other tools
to be avaiiliable on the host.  However, while the podman tests do require
catatonit, it doesn't necessarily need to be in the regular path.  For
example Fedora and RHEL place catatonit in /usr/libexec and podman finds it
there fine.

Therefore, remove it as an htools dependency.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:21 +02:00
David Gibson f9fe3ae5dd test: Build and download podman as a test asset
The pasta_podman/bats test scrpt downloads and builds podman, then runs its
pasta specific tests.  Downloading from within a test case has some
drawbacks:
 * It can be very tedious if you have poor connectivity to the server
 * It makes a test that's ostensibly for pasta itself dependent on the
   state of the github server
 * It precludes runnning the tests in an isolated network environment

The same concerns largely apply to building podman too, because it's pretty
common for Go builds to download dependencies themselves.  Therefore move
the download and build of podman from the test itself, to the Makefile
where we prepare other test assets.

To avoid cryptic failures if something went wrong with the build, make
running the test dependent on having the built podman binary.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:16 +02:00
David Gibson e8b78217bb test: Make sure to update mbuto repository
We download and use mbuto to build trivial boot images for our VM tests.
However, if mbuto is already cloned, we won't update it to the current
version.  Add some make logic to ensure that we do this.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:13 +02:00
David Gibson ef2cb13b49 cppcheck: Explicitly give files to check
Currently "make cppcheck" invokes cppcheck on ".", so it will check all the
.c and .h files it can find in the source tree.  This isn't ideal, because
it can find files that aren't actually part of the real build, or even
stale files which aren't in git.

More practically, some upcoming changes are looking at downloading other
source trees for some tests.  Static errors in there is Not Our Problem,
so checking them is both slow and pointless.

So, change the Makefile to invoke cppcheck only on the specific source
files that are part of the build.  For some reason in this format the
badBitmaskCheck warnings in seccomp.h which were suppressed by 5beb3472e
("cppcheck: Avoid errors due to zeroes in bitwise ORs") no longer trigger.
That means we get unmatchedSuppression warnings instead.  We add an
unmatchedSuppression suppression instead of simply removing the original
suppressions, just in case this odd behaviour isn't the same for all
cppcheck versions.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:11 +02:00
David Gibson 97e8b33f87 netlink: Ignore routes to link-local addresses for selecting interface
Since f919dc7a4b ("conf, netlink: Don't require a default route to
start"), and since 639fdf06ed ("netlink: Fix selection of template
interface") less buggily, we haven't required a default route on the host
in order to operate.  Instead, if we lack a default route we'll pick an
interface with any route, as long as there's only one such interface.  If
there's more than one, we don't have a good criterion to pick, so we give
up with an informational message.

Paul Holzinger pointed out that this code considers it ambiguous even if
all but one of the interfaces has only routes to link-local addresses
(fe80::/10).  A route to link-local addresses isn't really useful from
pasta's point of view, so ignore them instead.  This removes a misleading
message in many cases, and a spurious failure in some cases.

Suggested-by: Paul Holzinger <pholzing@redhat.com>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:08 +02:00
David Gibson 67a6258918 util: Add helper to return name of address family
We have a few places where we want to include the name of the internet
protocol version (IPv4 or IPv6) in a message, which we handle with an
open-coded ?: expression.

This seems like something that might be more widely useful, so make a
trivial helper to return the correct string based on the address family.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 16:59:05 +02:00
Stefano Brivio f4e38b5cd2 netlink: Adjust interface index inside copied nexthop objects too
As pasta duplicates host routes into the target namespaces, interface
indices might not match, so we go through RTA_OIF attributes and fix
them up to match the identifier in the namespace.

But RTA_OIF is not the ony attribute specifying interfaces for routes:
multipath routes use RTA_MULTIPATH attributes with nexthop objects,
which contain in turn interface indices. Fix them up as well.

If we don't, and we have at least two host interfaces, and the host
interface we use as template isn't the first one (hence the
mismatching indices), we'll fail to insert multipath routes with
nexthop objects, and ultimately refuse to start as the kernel
unexpectedly gives us ENODEV.

Link: https://github.com/containers/podman/issues/22192
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
2024-04-05 16:58:52 +02:00
Danish Prakash 88c2f08eba apparmor: Fix access to procfs namespace entries in pasta's abstraction
From an original patch by Danish Prakash.

With commit ff22a78d7b ("pasta: Don't try to watch namespaces in
procfs with inotify, use timer instead"), if a filesystem-bound
target namespace is passed on the command line, we'll grab a handle
on its parent directory. That commit, however, didn't introduce a
matching AppArmor rule. Add it here.

To access a network namespace procfs entry, we also need a 'ptrace'
rule. See commit 594dce66d3 ("isolation: keep CAP_SYS_PTRACE when
required") for details as to when we need this -- essentially, it's
about operation with Buildah.

Reported-by: Jörg Sonnenberger <joerg@bec.de>
Link: https://github.com/containers/buildah/issues/5440
Link: https://bugzilla.suse.com/show_bug.cgi?id=1221840
Fixes: ff22a78d7b ("pasta: Don't try to watch namespaces in procfs with inotify, use timer instead")
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 12:12:26 +02:00
Stefano Brivio 100919ce74 apparmor: Expand scope of @{run}/user access, allow writing PID files too
With Podman's custom networks, pasta will typically need to open the
target network namespace at /run/user/<UID>/containers/networks:
grant access to anything under /run/user/<UID> instead of limiting it
to some subpath.

Note that in this case, Podman will need pasta to write out a PID
file, so we need write access, for similar locations, too.

Reported-by: Jörg Sonnenberger <joerg@bec.de>
Link: https://github.com/containers/buildah/issues/5440
Link: https://bugzilla.suse.com/show_bug.cgi?id=1221840
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 12:12:26 +02:00
Stefano Brivio dc7b7f28b7 apparmor: Add mount rule with explicit, empty source in passt abstraction
For the policy to work as expected across either AppArmor commit
9d3f8c6cc05d ("parser: fix parsing of source as mount point for
propagation type flags") and commit 300889c3a4b7 ("parser: fix option
flag processing for single conditional rules"), we need one mount
rule with matching mount options as "source" (that is, without
source), and one without mount options and an explicit, empty source.

Link: https://github.com/containers/buildah/issues/5440
Link: https://bugzilla.suse.com/show_bug.cgi?id=1221840
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-04-05 12:12:26 +02:00
Stefano Brivio bbea2752f6 README.md: Alpine, Guix and OpenSUSE now have packages for passt
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
2024-04-05 12:12:23 +02:00
11 changed files with 93 additions and 20 deletions

View File

@ -308,4 +308,4 @@ cppcheck: $(SRCS) $(HEADERS)
--inline-suppr \
--suppress=unusedStructMember \
$(filter -D%,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
.
$(SRCS) $(HEADERS)

View File

@ -342,16 +342,18 @@ speeding up local connections, and usually requiring NAT. _pasta_:
### Availability
* official packages for:
* ✅ [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=passt)
* ✅ [Arch Linux](https://archlinux.org/packages/extra/x86_64/passt/) ([aarch64](https://archlinuxarm.org/packages/aarch64/passt), [i486](https://www.archlinux32.org/packages/?q=passt))
* ✅ [CentOS Stream](https://gitlab.com/redhat/centos-stream/rpms/passt)
* ✅ [Debian](https://tracker.debian.org/pkg/passt)
* ✅ [Fedora](https://src.fedoraproject.org/rpms/passt)
* ✅ [Gentoo](https://packages.gentoo.org/packages/net-misc/passt)
* ✅ [GNU Guix](https://packages.guix.gnu.org/packages/passt/)
* ✅ [OpenSUSE](https://build.opensuse.org/package/requests/Virtualization:containers/passt)
* ✅ [Ubuntu](https://launchpad.net/ubuntu/+source/passt)
* ✅ [Void Linux](https://voidlinux.org/packages/?q=passt)
* unofficial packages for:
* ✅ [EPEL, Mageia](https://copr.fedorainfracloud.org/coprs/sbrivio/passt/)
* 🛠 [openSUSE](https://build.opensuse.org/package/show/Virtualization:containers/passt)
* ✅ unofficial [packages](https://passt.top/builds/latest/x86_64/) from x86_64
static builds for other RPM-based distributions
* ✅ unofficial [packages](https://passt.top/builds/latest/x86_64/) from x86_64

View File

@ -27,6 +27,7 @@
/ r, # isolate_prefork(), isolation.c
mount options=(rw, runbindable) /,
mount "" -> "/",
mount "" -> "/tmp/",
pivot_root "/tmp/" -> "/tmp/",
umount "/",

View File

@ -27,8 +27,9 @@
@{PROC}/@{pid}/net/udp r,
@{PROC}/@{pid}/net/udp6 r,
@{run}/user/@{uid}/netns/* r, # pasta_open_ns(), pasta.c
@{run}/user/@{uid}/** rw, # pasta_open_ns(), main()
@{PROC}/[0-9]*/ns/ r, # pasta_netns_quit_init(),
@{PROC}/[0-9]*/ns/net r, # pasta_wait_for_ns(),
@{PROC}/[0-9]*/ns/user r, # conf_pasta_ns()
@ -42,3 +43,5 @@
/{usr/,}bin/** Ux,
/usr/bin/pasta.avx2 ix, # arch_avx2_exec(), arch.c
ptrace r, # pasta_open_ns()

9
ip.h
View File

@ -24,6 +24,11 @@
#define IN4ADDR_ANY_INIT \
{ .s_addr = htonl_constant(INADDR_ANY) }
#define IN4_IS_ADDR_LINKLOCAL(a) \
((ntohl(((struct in_addr *)(a))->s_addr) >> 16) == 0xa9fe)
#define IN4_IS_PREFIX_LINKLOCAL(a, len) \
((len) >= 16 && IN4_IS_ADDR_LINKLOCAL(a))
#define L2_BUF_IP4_INIT(proto) \
{ \
.version = 4, \
@ -40,6 +45,10 @@
#define L2_BUF_IP4_PSUM(proto) ((uint32_t)htons_constant(0x4500) + \
(uint32_t)htons(0xff00 | (proto)))
#define IN6_IS_PREFIX_LINKLOCAL(a, len) \
((len) >= 10 && IN6_IS_ADDR_LINKLOCAL(a))
#define L2_BUF_IP6_INIT(proto) \
{ \
.priority = 0, \

View File

@ -33,6 +33,7 @@
#include "util.h"
#include "passt.h"
#include "log.h"
#include "ip.h"
#include "netlink.h"
/* Netlink expects a buffer of at least 8kiB or the system page size,
@ -270,6 +271,7 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
seq = nl_send(s, &req, RTM_GETROUTE, NLM_F_DUMP, sizeof(req));
nl_foreach_oftype(nh, status, s, buf, seq, RTM_NEWROUTE) {
struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
const void *dst = NULL;
unsigned thisifi = 0;
if (rtm->rtm_family != af)
@ -284,12 +286,23 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
rtnh = (struct rtnexthop *)RTA_DATA(rta);
thisifi = rtnh->rtnh_ifindex;
} else if (rta->rta_type == RTA_DST) {
dst = RTA_DATA(rta);
}
}
if (!thisifi)
continue; /* No interface for this route */
/* Skip routes to link-local addresses */
if (af == AF_INET && dst &&
IN4_IS_PREFIX_LINKLOCAL(dst, rtm->rtm_dst_len))
continue;
if (af == AF_INET6 && dst &&
IN6_IS_PREFIX_LINKLOCAL(dst, rtm->rtm_dst_len))
continue;
if (rtm->rtm_dst_len == 0) {
/* Default route */
ndef++;
@ -309,7 +322,7 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
if (defifi) {
if (ndef > 1)
info("Multiple default %s routes, picked first",
af == AF_INET ? "IPv4" : "IPv6");
af_name(af));
return defifi;
}
@ -318,11 +331,11 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
return anyifi;
info("Multiple interfaces with %s routes, use -i to select one",
af == AF_INET ? "IPv4" : "IPv6");
af_name(af));
}
if (!nany)
info("No interfaces with %s routes", af == AF_INET ? "IPv4" : "IPv6");
info("No interfaces with usable %s routes", af_name(af));
return 0;
}
@ -546,12 +559,19 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); RTA_OK(rta, na);
rta = RTA_NEXT(rta, na)) {
/* RTA_OIF and RTA_MULTIPATH attributes carry the
* identifier of a host interface. Change them to match
* the corresponding identifier in the target namespace.
*/
if (rta->rta_type == RTA_OIF) {
/* The host obviously list's the host interface
* id here, we need to change it to the
* namespace's interface id
*/
*(unsigned int *)RTA_DATA(rta) = ifi_dst;
} else if (rta->rta_type == RTA_MULTIPATH) {
struct rtnexthop *rtnh;
for (rtnh = (struct rtnexthop *)RTA_DATA(rta);
RTNH_OK(rtnh, RTA_PAYLOAD(rta));
rtnh = RTNH_NEXT(rtnh))
rtnh->rtnh_ifindex = ifi_dst;
} else if (rta->rta_type == RTA_PREFSRC) {
/* Host routes might include a preferred source
* address, which must be one of the host's

View File

@ -29,11 +29,11 @@ HEADER="/* This file was automatically generated by $(basename ${0}) */
# Prefix for each profile: check that 'arch' in seccomp_data is matching
PRE='
struct sock_filter filter_@PROFILE@[] = {
/* cppcheck-suppress badBitmaskCheck */
/* 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@),
/* cppcheck-suppress badBitmaskCheck */
/* cppcheck-suppress [badBitmaskCheck, unmatchedSuppression] */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, nr))),

1
test/.gitignore vendored
View File

@ -1,5 +1,6 @@
test_logs/
mbuto/
podman/
*.img
QEMU_EFI.fd
*.qcow2

View File

@ -52,10 +52,10 @@ UBUNTU_NEW_IMGS = xenial-server-cloudimg-powerpc-disk1.img \
jammy-server-cloudimg-s390x.img
UBUNTU_IMGS = $(UBUNTU_OLD_IMGS) $(UBUNTU_NEW_IMGS)
DOWNLOAD_ASSETS = mbuto \
DOWNLOAD_ASSETS = mbuto podman \
$(DEBIAN_IMGS) $(FEDORA_IMGS) $(OPENSUSE_IMGS) $(UBUNTU_IMGS)
TESTDATA_ASSETS = small.bin big.bin medium.bin
LOCAL_ASSETS = mbuto.img mbuto.mem.img QEMU_EFI.fd \
LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \
$(DEBIAN_IMGS:%=prepared-%) $(FEDORA_IMGS:%=prepared-%) \
$(UBUNTU_NEW_IMGS:%=prepared-%) \
nstool guest-key guest-key.pub \
@ -67,13 +67,27 @@ CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
assets: $(ASSETS)
.PHONY: pull-%
pull-%: %
git -C $* pull
mbuto:
git clone git://mbuto.sh/mbuto
mbuto/mbuto: pull-mbuto
podman:
git clone https://github.com/containers/podman.git
# To succesfully build podman, you will need gpgme and systemd
# development packages
podman/bin/podman: pull-podman
$(MAKE) -C podman
guest-key guest-key.pub:
ssh-keygen -f guest-key -N ''
mbuto.img: passt.mbuto mbuto guest-key.pub $(TESTDATA_ASSETS)
mbuto.img: passt.mbuto mbuto/mbuto guest-key.pub $(TESTDATA_ASSETS)
./mbuto/mbuto -p ./$< -c lz4 -f $@
mbuto.mem.img: passt.mem.mbuto mbuto ../passt.avx2

View File

@ -11,11 +11,16 @@
# Copyright (c) 2022 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
htools git make go bats catatonit ip jq socat
htools git make go bats ip jq socat ./test/podman/bin/podman
set PODMAN test/podman/bin/podman
hout WD pwd
test Podman pasta path
hout PASTA_BIN CONTAINERS_HELPER_BINARY_DIR="__WD__" __PODMAN__ info --format "{{.Host.Pasta.Executable}}"
check [ "__PASTA_BIN__" = "__WD__/pasta" ]
test Podman system test with bats
host git -C __STATEDIR__ clone https://github.com/containers/podman.git
host make -C __STATEDIR__/podman
hout WD pwd
host PODMAN="__STATEDIR__/podman/bin/podman" CONTAINERS_HELPER_BINARY_DIR="__WD__" bats __STATEDIR__/podman/test/system/505-networking-pasta.bats
host PODMAN="__PODMAN__" CONTAINERS_HELPER_BINARY_DIR="__WD__" bats test/podman/test/system/505-networking-pasta.bats

18
util.h
View File

@ -156,6 +156,24 @@ int fls(unsigned long x);
int write_file(const char *path, const char *buf);
int write_remainder(int fd, const struct iovec *iov, int iovcnt, size_t skip);
/**
* af_name() - Return name of an address family
* @af: Address/protocol family (AF_INET or AF_INET6)
*
* Returns: Name of the protocol family as a string
*/
static inline const char *af_name(sa_family_t af)
{
switch (af) {
case AF_INET:
return "IPv4";
case AF_INET6:
return "IPv6";
default:
return "<unknown address family>";
}
}
/**
* mod_sub() - Modular arithmetic subtraction
* @a: Minued, unsigned value < @m