doc: Rewrite demo script

The original demo script was written when pasta wasn't a thing yet,
so it needed to run as root, set up a veth pair, and configure
addresses and routes by itself.

Now pasta can do all that for us, and become part of the demo as
well.

Further, extend it to start qemu, optionally preparing a basic demo
image with mbuto (https://mbuto.sh), and execute one logical step at
a time, for clarity.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
Stefano Brivio 2022-08-09 23:19:13 +02:00
parent b516d151b1
commit bda79ba401
2 changed files with 232 additions and 113 deletions

View file

@ -541,25 +541,11 @@ See also the [test logs](/builds/latest/test/).
man ./passt.1 man ./passt.1
* run the demo script, that creates a network namespace called `passt`, sets up * run the demo script, that detaches user and network namespaces, configures the
sets up a _veth_ pair and and addresses, together with NAT for IPv4 and NDP new network namespace using `pasta`, starts `passt` and, optionally, `qemu`:
proxying for IPv6, then starts _passt_ in the network namespace:
doc/demo.sh doc/demo.sh
* from the same network namespace, start qemu. At the moment, qemu doesn't
support UNIX domain sockets for the `socket` back-end. Two alternatives:
* use the _qrap_ wrapper, which maps a tap socket descriptor to _passt_'s
UNIX domain socket, for example:
ip netns exec passt ./qrap 5 qemu-system-x86_64 ... -net socket,fd=5 -net nic,model=virtio ...
* or patch qemu with [this patch](/passt/tree/qemu/0001-net-Allow-also-UNIX-domain-sockets-to-be-used-as-net.patch)
and start it like this:
qemu-system-x86_64 ... -net socket,connect=/tmp/passt.socket -net nic,model=virtio
* alternatively, you can use libvirt, with [this patch](/passt/tree/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch), * alternatively, you can use libvirt, with [this patch](/passt/tree/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch),
to start qemu (with the patch mentioned above), with this kind of network to start qemu (with the patch mentioned above), with this kind of network
interface configuration: interface configuration:
@ -613,6 +599,17 @@ See also the [test logs](/builds/latest/test/).
dhclient -6 dhclient -6
* alternatively, start pasta as:
./pasta --config-net
to let pasta configure networking in the namespace by itself, using
`netlink`
* ...or run the demo script:
doc/demo.sh
* and that's it, you should now have TCP connections, UDP, and ICMP/ICMPv6 * and that's it, you should now have TCP connections, UDP, and ICMP/ICMPv6
echo working from/to the namespace for IPv4 and IPv6 echo working from/to the namespace for IPv4 and IPv6

View file

@ -4,122 +4,244 @@
# #
# PASST - Plug A Simple Socket Transport # PASST - Plug A Simple Socket Transport
# #
# demo.sh - Set up namespaces, addresses and routes to show PASST functionality # demo.sh - Set up namespace with pasta, start qemu and passt, step by step
# #
# Copyright (c) 2020-2021 Red Hat GmbH # Copyright (c) 2020-2022 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com> # Author: Stefano Brivio <sbrivio@redhat.com>
get_token() { # mbuto_profile() - Profile for https://mbuto.sh/, sourced, return after setting
IFS=' ' mbuto_profile() {
__next=0 PROGS="${PROGS:-ash,dash,bash ip mount ls ln chmod insmod mkdir sleep
for __token in ${@}; do lsmod modprobe find grep mknod mv rm umount iperf3 dhclient cat
[ ${__next} -eq 2 ] && echo "${__token}" && return hostname chown socat dd strace ping killall sysctl wget,curl}"
[ "${__token}" = "${1}" ] && __next=$((__next + 1))
done KMODS="${KMODS:- virtio_net virtio_pci}"
unset IFS
LINKS="${LINKS:-
ash,dash,bash /init
ash,dash,bash /bin/sh}"
DIRS="${DIRS} /tmp /sbin /var/log /var/run /var/lib"
# shellcheck disable=SC2016
FIXUP="${FIXUP}"'
cat > /sbin/dhclient-script << EOF
#!/bin/sh
[ -n "\${new_interface_mtu}" ] && ip link set dev \${interface} mtu \${new_interface_mtu}
[ -n "\${new_ip_address}" ] && ip addr add \${new_ip_address}/\${new_subnet_mask} dev \${interface}
[ -n "\${new_routers}" ] && for r in \${new_routers}; do ip route add default via \${r} dev \${interface}; done
[ -n "\${new_domain_name_servers}" ] && for d in \${new_domain_name_servers}; do echo "nameserver \${d}" >> /etc/resolv.conf; done
[ -n "\${new_domain_name}" ] && echo "search \${new_domain_name}" >> /etc/resolf.conf
[ -n "\${new_domain_search}" ] && (printf "search"; for d in \${new_domain_search}; do printf " %s" "\${d}"; done; printf "\n") >> /etc/resolv.conf
[ -n "\${new_ip6_address}" ] && ip addr add \${new_ip6_address}/\${new_ip6_prefixlen} dev \${interface}
[ -n "\${new_dhcp6_name_servers}" ] && for d in \${new_dhcp6_name_servers}; do echo "nameserver \${d}%\${interface}" >> /etc/resolv.conf; done
[ -n "\${new_dhcp6_domain_search}" ] && (printf "search"; for d in \${new_dhcp6_domain_search}; do printf " %s" "\${d}"; done; printf "\n") >> /etc/resolv.conf
[ -n "\${new_host_name}" ] && hostname "\${new_host_name}"
exit 0
EOF
chmod 755 /sbin/dhclient-script
mkdir -p /etc/dhcp
echo "timeout 3;" > /etc/dhcp/dhclient.conf
ln -s /sbin /usr/sbin
:> /etc/fstab
echo
echo "The guest is up and running. Networking is not configured yet:"
echo
echo "$ ip address show"
echo
ip address show
echo
echo "...the next step will take care of that."
read x
echo "$ ip link set dev eth0 up"
ip link set dev eth0 up
sleep 3
echo "$ /sbin/dhclient -4 -1"
/sbin/dhclient -4 -1
sleep 2
echo "$ /sbin/dhclient -6 -1"
/sbin/dhclient -6 -1
sleep 2
echo
echo "$ ip address show"
ip address show
echo
echo "$ ip route show"
ip route show
echo
echo "...done."
read x
echo "Checking connectivity..."
echo
echo "$ wget --no-check-certificate https://passt.top/ || curl -k https://passt.top/"
wget --no-check-certificate https://passt.top/ || curl -k https://passt.top/
echo "...done."
read x
echo "An interactive shell will start now. When you are done,"
echo "use ^C to terminate the guest and exit the demo."
echo
sh +m
'
} }
ipv6_dev() { get_token "dev" $(ip -o -6 route show default | grep via); } [ "${0##*/}" = "mbuto" ] && mbuto_profile && return 0
ipv6_devaddr() { get_token "inet6" $(ip -o -6 addr show dev "${1}" scope global); }
ipv6_ll_addr() { get_token "inet6" $(ip -o -6 addr show dev "${1}" scope link); } # cmd() - Show command being executed, then run it
ipv6_mask() { echo ${1#*/}; } # $@: Command and arguments
ipv6_mangle() { cmd() {
IFS=':' echo "$" "$@"
__c=0 "$@"
for __16b in ${1%%/*}; do
if [ ${__c} -lt 7 ]; then
printf "${__16b}:"
else
printf "%04x\n" $((0xabc0 + ${2})) && break
fi
__c=$((__c + 1))
done
unset IFS
} }
ndp_setup() { # next() - Go to next step once a key is pressed, sets $KEY
sysctl -w net.ipv6.conf.all.proxy_ndp=1 next() {
ip -6 neigh add proxy "${1}" dev "$(ipv6_dev)" KEY="$(dd ibs=1 count=1 2>/dev/null)"
echo
for i in `seq 1 63`; do
__neigh="$(ipv6_mangle ${1} ${i})"
if [ "${__neigh}" != "${1}" ]; then
ip -6 neigh add proxy "${__neigh}" dev "${2}"
fi
done
} }
ns_idx=0 # cleanup() - Terminate pasta and passt, clean up, restore TTY settings
for i in `seq 1 63`; do cleanup() {
ns="passt_${i}" [ -f "${DEMO_DIR}/pasta.pid" ] && kill "$(cat "${DEMO_DIR}/pasta.pid")"
ns_idx=${i} [ -f "${DEMO_DIR}/passt.pid" ] && kill "$(cat "${DEMO_DIR}/passt.pid")"
rm -rf "${DEMO_DIR}" 2>/dev/null
[ -n "${STTY_BACKUP}" ] && stty "${STTY_BACKUP}"
}
busy=0 # start_pasta_delayed() - Start pasta once $DEMO_DIR/pasta.wait is gone
for p in $(pidof passt); do start_pasta_delayed() {
[ "$(ip netns identify ${p})" = "${ns}" ] && busy=1 && break trap '' EXIT
done while [ -d "${DEMO_DIR}/pasta.wait" ]; do sleep 1; done
[ ${busy} -eq 0 ] && break cmd pasta --config-net -P "${DEMO_DIR}/pasta.pid" \
done "$(cat "${DEMO_DIR}/shell.pid")"
echo
echo "...pasta is running."
exit 0
}
[ ${busy} -ne 0 ] && echo "Couldn't create namespace" && exit 1 # into_ns() - Entry point and demo script to run inside new namespace
into_ns() {
echo "We're in the new namespace now."
next
ip netns del "${ns}" 2>/dev/null || : echo "Networking is not configured yet:"
ip netns add "${ns}" echo
ip link del "veth_${ns}" 2>/dev/null || : cmd ip link show
ip link add "veth_${ns}" up netns "${ns}" type veth peer name "veth_${ns}" echo
ip link set dev "veth_${ns}" up cmd ip address show
ip link set dev "veth_${ns}" mtu 65535 next
ip -n "${ns}" link set dev "veth_${ns}" mtu 65535
ip -n "${ns}" link set dev lo up
ipv4_main="192.0.2.$(((ns_idx - 1) * 4 + 1))" echo "Let's run pasta(1) to configure networking and connect this"
ipv4_ns="192.0.2.$(((ns_idx - 1) * 4 + 2))" echo "namespace. Note that we'll run pasta(1) from outside this"
echo "namespace, because it needs to implement the connection between"
echo "this namespace and the initial (\"outer\") one."
next
ip -n "${ns}" addr add "${ipv4_ns}/30" dev "veth_${ns}" echo "$$" > "${DEMO_DIR}/shell.pid"
ip addr add "${ipv4_main}/30" dev "veth_${ns}" rmdir "${DEMO_DIR}/pasta.wait"
ip -n "${ns}" route add default via "${ipv4_main}" next
sysctl -w net.ipv4.ip_forward=1 echo "Back to the new namespace, networking is configured:"
nft delete table "${ns}_nat" 2>/dev/null || : echo
nft add table "${ns}_nat" cmd ip link show
nft add chain "${ns}_nat" postrouting '{ type nat hook postrouting priority -100 ; }' echo
nft add rule "${ns}_nat" postrouting ip saddr "${ipv4_ns}" masquerade cmd ip address show
next
ipv6_addr="$(ipv6_devaddr "$(ipv6_dev)")" echo "and we can now start passt(1), to connect this namespace to a"
if [ -n "${ipv6_addr}" ]; then echo "virtual machine. If you want to start a shell in this namespace,"
ipv6_passt="$(ipv6_mangle "${ipv6_addr}" ${ns_idx})" echo "press 's' now. Exiting the shell will resume the script."
ndp_setup "${ipv6_passt}" "veth_${ns}" next
ip -n "${ns}" addr add "${ipv6_passt}/$(ipv6_mask "${ipv6_addr}")" dev "veth_${ns}" [ "${KEY}" = "s" ] && ${SHELL}
ip addr add "${ipv6_addr}" dev "veth_${ns}"
ip route add "${ipv6_passt}" dev "veth_${ns}"
passt_ll="$(ipv6_ll_addr "veth_${ns}")"
main_ll="$(get_token "link/ether" $(ip -o link show "veth_${ns}"))"
ip neigh add "${passt_ll%%/*}" dev "veth_${ns}" lladdr "${main_ll}"
ip -n "${ns}" route add default via "${passt_ll%%/*}" dev "veth_${ns}"
sysctl -w net.ipv6.conf.all.forwarding=1 cmd passt -P "${DEMO_DIR}/passt.pid"
else echo
ipv6_passt= echo "...passt is running."
fi next
ethtool -K "veth_${ns}" tx off __arch="$(uname -m)"
ip netns exec "${ns}" ethtool -K "veth_${ns}" tx off case ${__arch} in
ip netns exec "${ns}" sysctl -w net.ipv4.ping_group_range="0 2147483647" x86_64)
__arch_supported=1
__qemu_arch="qemu-system-x86_64 -M pc,accel=kvm:tcg"
;;
*)
__arch_supported=0
;;
esac
if [ "${__arch_supported}" -eq 1 ]; then
echo "We're ready to start a virtual machine now. This script"
echo "can download and use mbuto (https://mbuto.sh/) to build a"
echo "basic initramfs image. Otherwise, press 's' to skip this"
echo "step, and start an existing virtual machine yourself."
echo "You'll need to use the qrap(1) wrapper, with qemu options"
echo "as reported above."
sysctl -w net.core.rmem_max=16777216 next
sysctl -w net.core.wmem_max=16777216 else
sysctl -w net.core.rmem_default=16777216 echo "This script doesn't know, yet, how to run a virtual"
sysctl -w net.core.wmem_default=16777216 echo "machine on your architecture (${__arch}). Please start an"
sysctl -w net.ipv4.tcp_rmem="16777216 131072 16777216" echo "existing virtual machine yourself, using the qrap(1)"
sysctl -w net.ipv4.tcp_wmem="16777216 131072 16777216" echo "wrapper, with qemu options as reported above."
echo
fi
echo if [ "${__arch_supported}" -eq 0 ] || [ "${KEY}" = "s" ]; then
echo "Namespace ${ns} set up, addresses:" echo "Start a virtual machine now. Pressing any key here will"
echo " ${ipv4_ns}" echo "terminate passt and pasta, and clean up."
echo " ${ipv6_passt}" next
echo
echo "Starting passt..."
echo
ip netns exec "${ns}" ./passt -f -e -t all -u all exit 0
fi
cmd git -C "${DEMO_DIR}" clone git://mbuto.sh/mbuto
echo
cmd "${DEMO_DIR}/mbuto/mbuto" \
-p "$(realpath "${0}")" -f "${DEMO_DIR}/demo.img"
echo
echo "The guest image is ready. The next step will start the guest."
echo "Use ^C to terminate it."
next
cmd qrap 5 qemu-system-x86_64 -M pc,accel=kvm:tcg \
-smp "$(nproc)" -m 1024 \
-nographic -serial stdio -nodefaults -no-reboot -vga none \
-initrd "${DEMO_DIR}/demo.img" \
-kernel "/boot/vmlinuz-$(uname -r)" -append "console=ttyS0" \
-net socket,fd=5 -net nic,model=virtio || :
}
STTY_BACKUP="$(stty -g)"
stty -icanon
trap cleanup EXIT INT
[ "${1}" = "into_ns" ] && into_ns && exit 0
DEMO_DIR="$(mktemp -d)"
mkdir "${DEMO_DIR}/pasta.wait"
echo "This script sets up a network and user namespace using pasta(1), then"
echo "starts a virtual machine in it, connected via passt(1), pausing at every"
echo "step. Press any key to go to the next step."
next
echo "Let's create the network and user namespace, first. This could be done"
echo "with pasta(1) itself (just issue \`pasta\`), but for the sake of this"
echo "script we'll create it first with unshare(1), and run the next steps"
echo "of this script from there."
next
start_pasta_delayed &
DEMO_DIR="${DEMO_DIR}" cmd unshare -rUn "${0}" into_ns
exit 0