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>
This commit is contained in:
parent
ce4e7b4d5d
commit
53489b8e6e
1 changed files with 182 additions and 7 deletions
189
slirp4netns.sh
189
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:*)"
|
||||
|
|
Loading…
Reference in a new issue