aboutgitcodebugslistschat
path: root/slirp4netns.sh
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2022-02-17 23:25:39 +0100
committerStefano Brivio <sbrivio@redhat.com>2022-02-21 13:41:13 +0100
commit53489b8e6ef61b5f9930575eee047f9ab2342ef7 (patch)
tree1e9a40495106e17d42b782334a93f50a3733d249 /slirp4netns.sh
parentce4e7b4d5ddb213f45164015ef5479943fec2f8e (diff)
downloadpasst-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar.gz
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar.bz2
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar.lz
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar.xz
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.tar.zst
passt-53489b8e6ef61b5f9930575eee047f9ab2342ef7.zip
slirp4netns.sh: Implement API socket option for port forwarding
Introduce the equivalent of the --api-socket option from slirp4netns: spawn a subshell to handle requests, netcat binds to a UNIX domain socket and jq parses messages. Three minor differences compared to slirp4netns: - IPv6 ports are forwarded too - error messages are not as specific, for example we don't tell apart malformed JSON requests from invalid parameters - host addresses are always 0.0.0.0 and ::1, pasta doesn't bind on specific addresses for different ports Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Diffstat (limited to 'slirp4netns.sh')
-rwxr-xr-xslirp4netns.sh189
1 files changed, 182 insertions, 7 deletions
diff --git a/slirp4netns.sh b/slirp4netns.sh
index 7c2188d..1784926 100755
--- a/slirp4netns.sh
+++ b/slirp4netns.sh
@@ -12,13 +12,20 @@
#
# WARNING: Draft quality, not really tested
#
-# Copyright (c) 2021 Red Hat GmbH
+# Copyright (c) 2021-2022 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
PASTA_PID="$(mktemp)"
PASTA_OPTS="-q --ipv4-only -a 10.0.2.0 -n 24 -g 10.0.2.2 -m 1500 --no-ndp --no-dhcpv6 --no-dhcp -P ${PASTA_PID}"
PASTA="$(command -v ./pasta || command -v pasta || :)"
+API_SOCKET=
+API_DIR="$(mktemp -d)"
+PORTS_DIR="${API_DIR}/ports"
+FIFO_REQ="${API_DIR}/req.fifo"
+FIFO_RESP="${API_DIR}/resp.fifo"
+PORT_ARGS=
+
USAGE_RET=1
NOTFOUND_RET=127
@@ -112,6 +119,172 @@ opt() {
esac
}
+# start() - Start pasta
+start() {
+ ${PASTA} ${PASTA_OPTS} ${PORT_ARGS} ${ns_spec}
+ [ ${RFD} -ne 0 ] && echo "1" >&${RFD} || :
+}
+
+# start() - Terminate pasta process
+stop() {
+ kill $(cat ${PASTA_PID})
+}
+
+# api_insert() - Handle add_hostfwd request, update PORT_ARGS
+# $1: Protocol, "tcp" or "udp"
+# $2: Host port
+# $3: Guest port
+api_insert() {
+ __id=
+ __next_id=1 # slirp4netns starts from ID 1
+ PORT_ARGS=
+
+ for __entry in $(ls ${PORTS_DIR}); do
+ PORT_ARGS="${PORT_ARGS} $(cat "${PORTS_DIR}/${__entry}")"
+
+ if [ -z "${__id}" ] && [ ${__entry} -ne ${__next_id} ]; then
+ __id=${__next_id}
+ fi
+
+ __next_id=$((__next_id + 1))
+ done
+ [ -z "${__id}" ] && __id=${__next_id}
+
+ # Invalid ports are accepted by slirp4netns, store them as empty files.
+ # Unknown protocols aren't.
+
+ case ${1} in
+ "tcp") opt="-t" ;;
+ "udp") opt="-u" ;;
+ *)
+ echo '{"error":{"desc":"bad request: add_hostfwd: bad arguments.proto"}}'
+ return
+ ;;
+ esac
+
+ if [ ${2} -ge 0 ] && [ ${2} -le 65535 ] && \
+ [ ${3} -ge 0 ] && [ ${3} -le 65535 ]; then
+ echo "${opt} ${2}:${3}" > "${PORTS_DIR}/${__id}"
+ PORT_ARGS="${PORT_ARGS} ${opt} ${2}:${3}"
+ else
+ :> "${PORTS_DIR}/${__id}"
+ fi
+
+ echo "{ \"return\": {\"id\": ${__id}}}"
+
+ NEED_RESTART=1
+}
+
+# api_list_one() - Print a single port forwarding entry in JSON
+# $1: ID
+# $2: protocol option, -t or -u
+# $3: host port
+# $4: guest port
+api_list_one() {
+ [ "${2}" = "-t" ] && __proto="tcp" || __proto="udp"
+
+ printf '{"id": %i, "proto": "%s", "host_addr": "0.0.0.0", "host_port": %i, "guest_addr": "%s", "guest_port": %i}' \
+ "${1}" "${__proto}" "${3}" "${A4}" "${4}"
+}
+
+# api_list() - Handle list_hostfwd request: list port forwarding entries in JSON
+api_list() {
+ printf '{ "return": {"entries": ['
+
+ __first=1
+ for __entry in $(ls "${PORTS_DIR}"); do
+ [ ${__first} -eq 0 ] && printf ", " || __first=0
+ IFS=' :'
+ api_list_one ${__entry} $(cat ${PORTS_DIR}/${__entry})
+ unset IFS
+ done
+
+ printf ']}}'
+}
+
+# api_delete() - Handle remove_hostfwd request: delete entry, update PORT_ARGS
+# $1: Entry ID -- caller *must* ensure it's a number
+api_delete() {
+ if [ ! -f "${PORTS_DIR}/${1}" ]; then
+ printf '{"error":{"desc":"bad request: remove_hostfwd: bad arguments.id"}}'
+ return
+ fi
+
+ rm "${PORTS_DIR}/${1}"
+
+ PORT_ARGS=
+ for __entry in $(ls ${PORTS_DIR}); do
+ PORT_ARGS="${PORT_ARGS} $(cat "${PORTS_DIR}/${__entry}")"
+ done
+
+ printf '{"return":{}}'
+
+ NEED_RESTART=1
+}
+
+# api_error() - Print generic error in JSON
+api_error() {
+ printf '{"error":{"desc":"bad request"}}'
+}
+
+# api_handler() - Entry point for slirp4netns-like API socket handler
+api_handler() {
+ trap 'exit 0' INT QUIT TERM
+ mkdir "${PORTS_DIR}"
+
+ while true; do
+ mkfifo "${FIFO_REQ}" "${FIFO_RESP}"
+
+ cat "${FIFO_RESP}" | nc -l -U "${API_SOCKET}" | \
+ tee /dev/null >"${FIFO_REQ}" & READER_PID=${!}
+
+ __req="$(dd count=1 2>/dev/null <${FIFO_REQ})"
+
+ >&2 echo "apifd event"
+ >&2 echo "api_handler: got request: ${__req}"
+
+ eval $(echo "${__req}" |
+ (jq -r 'to_entries | .[0] |
+ .key + "=" + (.value | @sh)' ||
+ printf 'execute=ERR'))
+
+ if [ "${execute}" != "list_hostfwd" ]; then
+ eval $(echo "${__req}" |
+ (jq -r '.arguments | to_entries | .[] |
+ .key + "=" + (.value | @sh)' ||
+ printf 'execute=ERR'))
+ fi
+
+ NEED_RESTART=0
+ case ${execute} in
+ "add_hostfwd")
+ api_insert "${proto}" "${host_port}" "${guest_port}"
+ __restart=1
+ ;;
+ "list_hostfwd")
+ api_list
+ ;;
+ "remove_hostfwd")
+ case ${id} in
+ ''|*[!0-9]*) api_error ;;
+ *) api_delete "${id}"; __restart=1 ;;
+ esac
+ ;;
+ *)
+ api_error
+ ;;
+ esac >"${FIFO_RESP}"
+
+ kill ${READER_PID}
+
+ rm "${FIFO_REQ}" "${FIFO_RESP}"
+
+ [ ${NEED_RESTART} -eq 1 ] && { stop; start; }
+ done
+
+ exit 0
+}
+
# usage() - Print slirpnetns(1) usage and exit indicating failure
# $1: Invalid option name, if any
usage() {
@@ -177,7 +350,7 @@ while getopts ce:r:m:6a:hv-: OPT 2>/dev/null; do
r | ready-fd) opt u32 RFD ;;
m | mtu) opt mtu MTU && sub -m ${MTU} ;;
6 | enable-ipv6) V6=1 ;;
- a | api-socket) opt str API ;;
+ a | api-socket) opt str API_SOCKET ;;
cidr) opt net4 A4 M4 && sub -a ${A4} -n ${M4} ;;
disable-host-loopback) add "--no-map-gw" && no_map_gw=1 ;;
netns-type) : Autodetected ;;
@@ -203,14 +376,15 @@ if [ ${v6} -eq 1 ]; then
add "-a $(gen_addr6) -g fd00::2 -D fd00::3"
fi
-${PASTA} ${PASTA_OPTS} ${ns_spec} && \
- [ ${RFD} -ne 0 ] && echo "1" >&${RFD}
+start
+[ -n "${API_SOCKET}" ] && api_handler </dev/null &
+trap "stop; rm -rf ${API_DIR}; rm -f ${API_SOCKET}; rm ${PASTA_PID}" EXIT
+trap 'exit 0' INT QUIT TERM
-trap "kill $(cat ${PASTA_PID}); rm ${PASTA_PID}" INT TERM EXIT
+>&2 echo "sent tapfd=5 for ${ifname}"
+>&2 echo "received tapfd=5"
cat << EOF
-sent tapfd=5 for ${ifname}
-received tapfd=5
Starting slirp
* MTU: ${MTU}
* Network: ${A4}
@@ -219,6 +393,7 @@ Starting slirp
* DNS: 10.0.2.3
* Recommended IP: 10.0.2.100
EOF
+[ -n "${API_SOCKET}" ] && echo "* API socket: ${API_SOCKET}"
if [ ${no_map_gw} -eq 0 ]; then
echo "WARNING: 127.0.0.1:* on the host is accessible as 10.0.2.2 (set --disable-host-loopback to prohibit connecting to 127.0.0.1:*)"