From mboxrd@z Thu Jan 1 00:00:00 1970 From: Stefano Brivio To: passt-dev@passt.top Subject: [PATCH 06/18] slirp4netns.sh: Implement API socket option for port forwarding Date: Tue, 22 Feb 2022 02:34:22 +0100 Message-ID: <20220222013434.4116044-7-sbrivio@redhat.com> In-Reply-To: <20220222013434.4116044-1-sbrivio@redhat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============3488277760401503147==" --===============3488277760401503147== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 --- slirp4netns.sh | 189 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file 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 =20 PASTA_PID=3D"$(mktemp)" PASTA_OPTS=3D"-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=3D"$(command -v ./pasta || command -v pasta || :)" =20 +API_SOCKET=3D +API_DIR=3D"$(mktemp -d)" +PORTS_DIR=3D"${API_DIR}/ports" +FIFO_REQ=3D"${API_DIR}/req.fifo" +FIFO_RESP=3D"${API_DIR}/resp.fifo" +PORT_ARGS=3D + USAGE_RET=3D1 NOTFOUND_RET=3D127 =20 @@ -112,6 +119,172 @@ opt() { esac } =20 +# 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=3D + __next_id=3D1 # slirp4netns starts from ID 1 + PORT_ARGS=3D + + for __entry in $(ls ${PORTS_DIR}); do + PORT_ARGS=3D"${PORT_ARGS} $(cat "${PORTS_DIR}/${__entry}")" + + if [ -z "${__id}" ] && [ ${__entry} -ne ${__next_id} ]; then + __id=3D${__next_id} + fi + + __next_id=3D$((__next_id + 1)) + done + [ -z "${__id}" ] && __id=3D${__next_id} + + # Invalid ports are accepted by slirp4netns, store them as empty files. + # Unknown protocols aren't. + + case ${1} in + "tcp") opt=3D"-t" ;; + "udp") opt=3D"-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=3D"${PORT_ARGS} ${opt} ${2}:${3}" + else + :> "${PORTS_DIR}/${__id}" + fi + + echo "{ \"return\": {\"id\": ${__id}}}" + + NEED_RESTART=3D1 +} + +# 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}" =3D "-t" ] && __proto=3D"tcp" || __proto=3D"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=3D1 + for __entry in $(ls "${PORTS_DIR}"); do + [ ${__first} -eq 0 ] && printf ", " || __first=3D0 + IFS=3D' :' + api_list_one ${__entry} $(cat ${PORTS_DIR}/${__entry}) + unset IFS + done + + printf ']}}' +} + +# api_delete() - Handle remove_hostfwd request: delete entry, update PORT_AR= GS +# $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=3D + for __entry in $(ls ${PORTS_DIR}); do + PORT_ARGS=3D"${PORT_ARGS} $(cat "${PORTS_DIR}/${__entry}")" + done + + printf '{"return":{}}' + + NEED_RESTART=3D1 +} + +# 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=3D${!} + + __req=3D"$(dd count=3D1 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 + "=3D" + (.value | @sh)' || + printf 'execute=3DERR')) + + if [ "${execute}" !=3D "list_hostfwd" ]; then + eval $(echo "${__req}" | + (jq -r '.arguments | to_entries | .[] | + .key + "=3D" + (.value | @sh)' || + printf 'execute=3DERR')) + fi + + NEED_RESTART=3D0 + case ${execute} in + "add_hostfwd") + api_insert "${proto}" "${host_port}" "${guest_port}" + __restart=3D1 + ;; + "list_hostfwd") + api_list + ;; + "remove_hostfwd") + case ${id} in + ''|*[!0-9]*) api_error ;; + *) api_delete "${id}"; __restart=3D1 ;; + 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=3D1 ;; - 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=3D1 ;; netns-type) : Autodetected ;; @@ -203,14 +376,15 @@ if [ ${v6} -eq 1 ]; then add "-a $(gen_addr6) -g fd00::2 -D fd00::3" fi =20 -${PASTA} ${PASTA_OPTS} ${ns_spec} && \ - [ ${RFD} -ne 0 ] && echo "1" >&${RFD} +start +[ -n "${API_SOCKET}" ] && api_handler &2 echo "sent tapfd=3D5 for ${ifname}" +>&2 echo "received tapfd=3D5" =20 cat << EOF -sent tapfd=3D5 for ${ifname} -received tapfd=3D5 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}" =20 if [ ${no_map_gw} -eq 0 ]; then echo "WARNING: 127.0.0.1:* on the host is accessible as 10.0.2.2 (set --dis= able-host-loopback to prohibit connecting to 127.0.0.1:*)" --=20 2.34.1 --===============3488277760401503147==--