feat: Implement usermode networking (#762)

This commit is contained in:
Kroese 2024-06-11 02:13:53 +02:00 committed by GitHub
parent f8879029ec
commit be8bee90f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 32 deletions

View File

@ -5,7 +5,7 @@ set -Eeuo pipefail
[ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down.." && exit 1 [ -f "/run/shm/qemu.end" ] && echo "QEMU is shutting down.." && exit 1
[ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet.." && exit 0 [ ! -s "/run/shm/qemu.pid" ] && echo "QEMU is not running yet.." && exit 0
[[ "$NETWORK" != [Yy1]* ]] && echo "Networking is disabled.." && exit 0 [[ "$NETWORK" == [Nn]* ]] && echo "Networking is disabled.." && exit 0
file="/run/shm/dsm.url" file="/run/shm/dsm.url"
address="/run/shm/qemu.ip" address="/run/shm/qemu.ip"

View File

@ -147,6 +147,7 @@ if [ ! -s "$RDC" ]; then
fKill "progress.sh" fKill "progress.sh"
ERR="Failed to download $LOC" ERR="Failed to download $LOC"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 60
(( rc == 4 )) && error "$ERR , network failure!" && exit 60 (( rc == 4 )) && error "$ERR , network failure!" && exit 60
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60 (( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60
@ -174,6 +175,7 @@ if [ ! -s "$RDC" ]; then
fKill "progress.sh" fKill "progress.sh"
ERR="Failed to download $LOC" ERR="Failed to download $LOC"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 60
(( rc == 4 )) && error "$ERR , network failure!" && exit 60 (( rc == 4 )) && error "$ERR , network failure!" && exit 60
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60 (( rc == 8 )) && error "$ERR , server issued an error response!" && exit 60
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 60 (( rc != 0 )) && error "$ERR , reason: $rc" && exit 60
@ -252,6 +254,7 @@ else
fKill "progress.sh" fKill "progress.sh"
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
(( rc == 4 )) && error "$ERR , network failure!" && exit 69 (( rc == 4 )) && error "$ERR , network failure!" && exit 69
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69 (( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69 (( rc != 0 )) && error "$ERR , reason: $rc" && exit 69

View File

@ -7,6 +7,7 @@ set -Eeuo pipefail
: "${DHCP:="N"}" : "${DHCP:="N"}"
: "${NETWORK:="Y"}" : "${NETWORK:="Y"}"
: "${HOST_PORTS:=""}" : "${HOST_PORTS:=""}"
: "${USER_PORTS:=""}"
: "${VM_NET_DEV:=""}" : "${VM_NET_DEV:=""}"
: "${VM_NET_TAP:="dsm"}" : "${VM_NET_TAP:="dsm"}"
@ -38,7 +39,7 @@ configureDHCP() {
if (( rc != 0 )); then if (( rc != 0 )); then
error "Cannot create macvtap interface. Please make sure that the network type is 'macvlan' and not 'ipvlan'," error "Cannot create macvtap interface. Please make sure that the network type is 'macvlan' and not 'ipvlan',"
error "that your kernel is recent (>4) and supports it, and that the container has the NET_ADMIN capability set." && exit 16 error "that your kernel is recent (>4) and supports it, and that the container has the NET_ADMIN capability set." && return 1
fi fi
while ! ip link set "$VM_NET_TAP" up; do while ! ip link set "$VM_NET_TAP" up; do
@ -52,28 +53,28 @@ configureDHCP() {
# Create dev file (there is no udev in container: need to be done manually) # Create dev file (there is no udev in container: need to be done manually)
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev) IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
(( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && exit 18 (( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
[[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH" [[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
if [[ ! -e "$TAP_PATH" ]]; then if [[ ! -e "$TAP_PATH" ]]; then
{ mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || : { mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
(( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && exit 20 (( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
fi fi
{ exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || : { exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && exit 21 error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
fi fi
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || : { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && exit 22 error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
fi fi
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30" NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30,script=no,downscript=no"
return 0 return 0
} }
@ -96,13 +97,34 @@ configureDNS() {
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//') DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
error "Failed to start dnsmasq, reason: $?" && exit 29 error "Failed to start dnsmasq, reason: $?" && return 1
fi fi
return 0 return 0
} }
getPorts() { getUserPorts() {
local args=""
local list=$1
local ssh="22"
local dsm="5000"
[ -z "$list" ] && list="$ssh,$dsm" || list+=",$ssh,$dsm"
list="${list/,/ }"
list="${list## }"
list="${list%% }"
for port in $list; do
args+="hostfwd=tcp::$port-$VM_NET_IP:$port,"
done
echo "${args%?}"
return 0
}
getHostPorts() {
local list=$1 local list=$1
@ -117,6 +139,17 @@ getPorts() {
return 0 return 0
} }
configureUser() {
NET_OPTS="-netdev user,id=hostnet0,host=${VM_NET_IP%.*}.1,net=${VM_NET_IP%.*}.0/24,dhcpstart=$VM_NET_IP,hostname=$VM_NET_HOST"
local forward
forward=$(getUserPorts "$USER_PORTS")
[ -n "$forward" ] && NET_OPTS+=",$forward"
return 0
}
configureNAT() { configureNAT() {
# Create the necessary file structure for /dev/net/tun # Create the necessary file structure for /dev/net/tun
@ -128,14 +161,14 @@ configureNAT() {
fi fi
if [ ! -c /dev/net/tun ]; then if [ ! -c /dev/net/tun ]; then
error "TUN device missing. $ADD_ERR --device /dev/net/tun --cap-add NET_ADMIN" && exit 25 error "TUN device missing. $ADD_ERR --device /dev/net/tun --cap-add NET_ADMIN" && return 1
fi fi
# Check port forwarding flag # Check port forwarding flag
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
{ sysctl -w net.ipv4.ip_forward=1 > /dev/null; rc=$?; } || : { sysctl -w net.ipv4.ip_forward=1 > /dev/null; rc=$?; } || :
if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && exit 24 error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && return 1
fi fi
fi fi
@ -147,10 +180,12 @@ configureNAT() {
{ ip link add dev dockerbridge type bridge ; rc=$?; } || : { ip link add dev dockerbridge type bridge ; rc=$?; } || :
if (( rc != 0 )); then if (( rc != 0 )); then
error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && exit 23 error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
fi fi
ip address add "${VM_NET_IP%.*}.1/24" broadcast "${VM_NET_IP%.*}.255" dev dockerbridge if ! ip address add "${VM_NET_IP%.*}.1/24" broadcast "${VM_NET_IP%.*}.255" dev dockerbridge; then
error "Failed to add IP address!" && return 1
fi
while ! ip link set dockerbridge up; do while ! ip link set dockerbridge up; do
info "Waiting for IP address to become available..." info "Waiting for IP address to become available..."
@ -159,7 +194,7 @@ configureNAT() {
# QEMU Works with taps, set tap to the bridge created # QEMU Works with taps, set tap to the bridge created
if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
error "$tuntap" && exit 31 error "$tuntap" && return 1
fi fi
while ! ip link set "$VM_NET_TAP" up promisc on; do while ! ip link set "$VM_NET_TAP" up promisc on; do
@ -167,35 +202,44 @@ configureNAT() {
sleep 2 sleep 2
done done
ip link set dev "$VM_NET_TAP" master dockerbridge if ! ip link set dev "$VM_NET_TAP" master dockerbridge; then
error "Failed to set IP link!" && return 1
fi
# Add internet connection to the VM # Add internet connection to the VM
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
exclude=$(getPorts "$HOST_PORTS") exclude=$(getHostPorts "$HOST_PORTS")
if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then
error "$tables" && exit 30 error "$tables" && return 1
fi fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP" if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP"; then
iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP" error "Failed to configure IP tables!" && return 1
fi
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"; then
error "Failed to configure IP tables!" && return 1
fi
if (( KERNEL > 4 )); then if (( KERNEL > 4 )); then
# Hack for guest VMs complaining about "bad udp checksums in 5 packets" # Hack for guest VMs complaining about "bad udp checksums in 5 packets"
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true
fi fi
NET_OPTS="-netdev tap,ifname=$VM_NET_TAP,script=no,downscript=no,id=hostnet0" NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
if [ -c /dev/vhost-net ]; then if [ -c /dev/vhost-net ]; then
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || : { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
(( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40" (( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
fi fi
configureDNS NET_OPTS+=",script=no,downscript=no"
! configureDNS && return 1
return 0 return 0
} }
@ -210,7 +254,7 @@ closeNetwork() {
fi fi
[[ "$NETWORK" != [Yy1]* ]] && return 0 [[ "$NETWORK" == [Nn]* ]] && return 0
exec 30<&- || true exec 30<&- || true
exec 40<&- || true exec 40<&- || true
@ -225,6 +269,8 @@ closeNetwork() {
local pid="/var/run/dnsmasq.pid" local pid="/var/run/dnsmasq.pid"
[ -s "$pid" ] && pKill "$(<"$pid")" [ -s "$pid" ] && pKill "$(<"$pid")"
[[ "${NETWORK,,}" == "user"* ]] && return 0
ip link set "$VM_NET_TAP" down promisc off || true ip link set "$VM_NET_TAP" down promisc off || true
ip link delete "$VM_NET_TAP" || true ip link delete "$VM_NET_TAP" || true
@ -246,8 +292,7 @@ checkOS() {
[[ "${name,,}" == *"microsoft"* ]] && os="Windows" [[ "${name,,}" == *"microsoft"* ]] && os="Windows"
if [ -n "$os" ]; then if [ -n "$os" ]; then
error "You are using Docker Desktop for $os which does not support macvlan, please revert to bridge networking!" warn "you are using Docker Desktop for $os which does not support macvlan, please revert to bridge networking!"
return 1
fi fi
return 0 return 0
@ -304,7 +349,7 @@ getInfo() {
# Configure Network # Configure Network
# ###################################### # ######################################
if [[ "$NETWORK" != [Yy1]* ]]; then if [[ "$NETWORK" == [Nn]* ]]; then
NET_OPTS="" NET_OPTS=""
return 0 return 0
fi fi
@ -324,14 +369,14 @@ fi
if [[ "$DHCP" == [Yy1]* ]]; then if [[ "$DHCP" == [Yy1]* ]]; then
! checkOS && [[ "$DEBUG" != [Yy1]* ]] && exit 19 checkOS
if [[ "$IP" == "172."* ]]; then if [[ "$IP" == "172."* ]]; then
warn "container IP starts with 172.* which is often a sign that you are not on a macvlan network (required for DHCP)!" warn "container IP starts with 172.* which is often a sign that you are not on a macvlan network (required for DHCP)!"
fi fi
# Configuration for DHCP IP # Configure for macvtap interface
configureDHCP ! configureDHCP && exit 20
MSG="Booting DSM instance..." MSG="Booting DSM instance..."
html "$MSG" html "$MSG"
@ -339,15 +384,37 @@ if [[ "$DHCP" == [Yy1]* ]]; then
else else
if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then
! checkOS && [[ "$DEBUG" != [Yy1]* ]] && exit 19 checkOS
fi fi
# Shutdown nginx # Shutdown nginx
nginx -s stop 2> /dev/null nginx -s stop 2> /dev/null
fWait "nginx" fWait "nginx"
# Configuration for static IP if [[ "${NETWORK,,}" != "user"* ]]; then
configureNAT
# Configure for tap interface
if ! configureNAT; then
NETWORK="user"
warn "falling back to usermode networking (slow)!"
ip link set "$VM_NET_TAP" down promisc off &> null || true
ip link delete "$VM_NET_TAP" &> null || true
ip link set dockerbridge down &> null || true
ip link delete dockerbridge &> null || true
fi
fi
if [[ "${NETWORK,,}" == "user"* ]]; then
# Configure for usermode networking (slirp)
! configureUser && exit 24
fi
fi fi

View File

@ -4,7 +4,7 @@ set -Eeuo pipefail
: "${DHCP:="N"}" : "${DHCP:="N"}"
: "${NETWORK:="Y"}" : "${NETWORK:="Y"}"
[[ "$NETWORK" != [Yy1]* ]] && exit 0 [[ "$NETWORK" == [Nn]* ]] && exit 0
info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n" >&2; } info () { printf "%b%s%b" "\E[1;34m \E[1;36m" "$1" "\E[0m\n" >&2; }
error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; } error () { printf "%b%s%b" "\E[1;31m " "ERROR: $1" "\E[0m\n" >&2; }