From 661b942e9e2a08a5565758ca52da669f4bb3a41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C4=8Cajka?= Date: Sat, 19 Jan 2019 10:54:32 +0100 Subject: [PATCH] Initial commit --- Dockerfile | 31 +++++++++ egress-router.sh | 170 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 Dockerfile create mode 100755 egress-router.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4924c7d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# +# This is the egress router for OpenShift Origin +# +# The standard name for this image is openshift/origin-egress-router +# +FROM registry.fedoraproject.org/f29/origin-base:latest + +ENV NAME=origin-egress-router \ + VERSION=3.11 \ + ARCH=x86_64 + +RUN INSTALL_PKGS="iproute iputils" && \ + dnf install -y $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + dnf clean all + +ADD egress-router.sh /bin/egress-router.sh + +LABEL io.k8s.display-name="OpenShift Origin Egress Router" \ + io.k8s.description="This is a component of OpenShift Origin and contains an egress router." \ + io.openshift.tags="openshift,router,egress" \ + summary="This is a component of OpenShift Origin and contains an egress router." \ + maintainer="Jakub Cajka " \ + License="GPLv2+" \ + name="$FGC/$NAME" \ + com.redhat.component="$NAME" \ + version="$VERSION" \ + architecture="$ARCH" \ + usage="This is a component of OpenShift Origin and contains an egress router." + +ENTRYPOINT /bin/egress-router.sh diff --git a/egress-router.sh b/egress-router.sh new file mode 100755 index 0000000..5b09445 --- /dev/null +++ b/egress-router.sh @@ -0,0 +1,170 @@ +#!/bin/bash + +# OpenShift egress router setup script + +set -o errexit +set -o nounset +set -o pipefail + +function die() { + echo "$*" 1>&2 + exit 1 +} + +BLANK_LINE_OR_COMMENT_REGEX="([[:space:]]*$|#.*)" +PORT_REGEX="[[:digit:]]+" +PROTO_REGEX="(tcp|udp)" +IP_REGEX="[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+" +CIDR_MASK_REGEX="/[[:digit:]]+" + +if [[ "${EGRESS_SOURCE:-}" =~ ^${IP_REGEX}${CIDR_MASK_REGEX}$ ]]; then + EGRESS_SOURCE_IFADDR="${EGRESS_SOURCE}" + EGRESS_SOURCE="${EGRESS_SOURCE%/*}" +elif [[ "${EGRESS_SOURCE:-}" =~ ^${IP_REGEX}$ ]]; then + EGRESS_SOURCE_IFADDR="${EGRESS_SOURCE}/32" +else + die "EGRESS_SOURCE unspecified or invalid" +fi + +if [[ ! "${EGRESS_GATEWAY:-}" =~ ^${IP_REGEX}$ ]]; then + die "EGRESS_GATEWAY unspecified or invalid" +fi + +if [[ -n "${EGRESS_ROUTER_DEBUG:-}" ]]; then + set -x +fi + +function setup_network() { + # The pod may die and get restarted; only try to add the + # address/route/rules if they are not already there. + if ! ip route get "${EGRESS_GATEWAY}" | grep -q macvlan0; then + ip addr add "${EGRESS_SOURCE_IFADDR}" dev macvlan0 + ip link set up dev macvlan0 + + ip route add "${EGRESS_GATEWAY}"/32 dev macvlan0 + ip route del default + ip route add default via "${EGRESS_GATEWAY}" dev macvlan0 + fi + + # Update neighbor ARP caches in case another node previously had the IP. (This is + # the same code ifup uses.) + arping -q -A -c 1 -I macvlan0 "${EGRESS_SOURCE}" + ( sleep 2; + arping -q -U -c 1 -I macvlan0 "${EGRESS_SOURCE}" || true ) > /dev/null 2>&1 < /dev/null & +} + +function validate_port() { + local port=$1 + if [[ "${port}" -lt "1" || "${port}" -gt "65535" ]]; then + die "Invalid port: ${port}, must be in the range 1 to 65535" + fi +} + +function gen_iptables_rules() { + if [[ -z "${EGRESS_DESTINATION:-}" ]]; then + die "EGRESS_DESTINATION unspecified" + fi + + did_fallback= + declare -A used_ports=() + while read dest; do + if [[ "${dest}" =~ ^${BLANK_LINE_OR_COMMENT_REGEX}$ ]]; then + # comment or blank line + continue + fi + if [[ -n "${did_fallback}" ]]; then + die "EGRESS_DESTINATION fallback IP must be the last line" 1>&2 + fi + + localport="" + if [[ "${dest}" =~ ^${IP_REGEX}$ ]]; then + # single IP address: do fallback "all ports to same IP" + echo -A PREROUTING -i eth0 -j DNAT --to-destination "${dest}" + did_fallback=1 + + elif [[ "${dest}" =~ ^${PORT_REGEX}\ +${PROTO_REGEX}\ +${IP_REGEX}$ ]]; then + read localport proto destip <<< "${dest}" + echo -A PREROUTING -i eth0 -p "${proto}" --dport "${localport}" -j DNAT --to-destination "${destip}" + + elif [[ "${dest}" =~ ^${PORT_REGEX}\ +${PROTO_REGEX}\ +${IP_REGEX}\ +${PORT_REGEX}$ ]]; then + read localport proto destip destport <<< "${dest}" + validate_port ${destport} + echo -A PREROUTING -i eth0 -p "${proto}" --dport "${localport}" -j DNAT --to-destination "${destip}:${destport}" + + else + die "EGRESS_DESTINATION value '${dest}' is invalid" 1>&2 + + fi + + if [[ -n "${localport}" ]]; then + validate_port ${localport} + + if [[ "${used_ports[${localport}]:-}" == "" ]]; then + used_ports[${localport}]=1 + else + die "EGRESS_DESTINATION localport $localport is already used, must be unique for each destination" + fi + fi + done <<< "${EGRESS_DESTINATION}" + echo -A POSTROUTING -o macvlan0 -j SNAT --to-source "${EGRESS_SOURCE}" +} + +function setup_iptables() { + iptables -t nat -F + ( echo "*nat"; + echo ":PREROUTING ACCEPT [0:0]"; + echo ":POSTROUTING ACCEPT [0:0]"; + gen_iptables_rules; + echo "COMMIT" ) | iptables-restore --noflush --table nat +} + +function wait_until_killed() { + # Kubernetes will use SIGTERM to kill us, but bash ignores SIGTERM by + # default in interactive shells, and it thinks this shell is + # interactive due to the way in which docker invokes it. We can get + # bash to react to SIGTERM if we explicitly set a trap for it, except + # that bash doesn't process signal traps while it is waiting for a + # process to finish, and we have to be waiting for a process to finish + # because there's no way to sleep forever within bash. + # + # Fortunately, signal traps do interrupt the "wait" builtin. So... + # set up a SIGTERM trap, run a command that sleeps forever *in the + # background*, and then wait for either the command to finish or the + # signal to arrive. + + trap "exit" TERM + tail -f /dev/null & + wait +} + +case "${EGRESS_ROUTER_MODE:=legacy}" in + init) + setup_network + setup_iptables + ;; + + legacy) + setup_network + setup_iptables + wait_until_killed + ;; + + http-proxy) + setup_network + ;; + + dns-proxy) + setup_network + ;; + + unit-test) + gen_iptables_rules + ;; + + *) + die "Unrecognized EGRESS_ROUTER_MODE '${EGRESS_ROUTER_MODE}'" + ;; +esac + +# We don't have to do any cleanup because deleting the network +# namespace will clean everything up for us.