Skip to content

Instantly share code, notes, and snippets.

@deric
Last active March 15, 2023 12:58
Show Gist options
  • Save deric/b52628663e7758deec0a4ab5ab4e5908 to your computer and use it in GitHub Desktop.
Save deric/b52628663e7758deec0a4ab5ab4e5908 to your computer and use it in GitHub Desktop.
Generate iptables port forwarding for running containers (assumes default Docker chains already exists)
#!/bin/bash
# Copyright 2020-2022 Tomas Barton
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -o nounset -o pipefail
#
function -h {
cat <<USAGE
Generate iptables rules for running docker containers. Use
$(basename $0) -v -n
to inspect iptables rules without applying changes.
USAGE:
-b / --binary iptables binary
-i / --interface Docker virtual interface, default: docker0
-d / --debug Debugging output
-n / --noop Dry run / no iptables rule is applied
-v / --verbose Detailed output
USAGE
}; function --help { -h ;}
function msg { out "$*" >&1 ;}
function out { printf '%s\n' "$*" ;}
function iptables_apply {
local binary="$1"
local table="$2"
local action="$3"
local rule="$4"
local noop=$5
local verbose=$6
# check if the rule is already defined
eval "${binary} -t ${table} --check ${rule} 2>/dev/null"
if [[ $? -ne 0 ]]; then
if [[ $noop == true ]]; then
msg $rule;
else
if [[ $verbose == true ]]; then
msg "${rule}"
fi
eval "${binary} -t ${table} ${action} ${rule}";
fi
fi
}
function main {
local verbose=false
local debug=false
local noop=false
local interface="docker0"
local binary="iptables"
while [[ $# -gt 0 ]]
do
case "$1" in # Munging globals, beware
-i|--interface) interface="$2"; shift 2 ;;
-b|--binary) binary="$2"; shift 2 ;;
-n|--noop) noop=true; shift 1 ;;
-v|--verbose) verbose=true; shift 1 ;;
-d|--debug) debug=true; shift 1 ;;
*) err 'Argument error. Please see help: -h' ;;
esac
done
if [[ $debug == true ]]; then
set -x
fi
if [[ $noop == true ]]; then
msg "NOOP: Only printing iptables rules to be eventually applied"
fi
# list currently running container IDs
local containers=$(docker ps --format '{{.ID}}')
if [[ ! -z "$containers" ]]; then
while read -r cont; do
# old docker API response
local ip=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' ${cont})
if [[ -z "${ip}" ]]; then
# newer docker API, probably > 23.01
ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${cont})
fi
if [[ $verbose == true ]]; then
msg "Container ${cont}"
fi
# extract port forwarding
local ports=$(docker inspect -f '{{json .NetworkSettings.Ports}}' ${cont})
if [[ "${ports}" != "{}" ]]; then
local fwd=$(echo "${ports}" | jq -r '. as $a| keys[] | select($a[.]!=null) as $f | "\($f)/\($a[$f][].HostPort)"')
if [[ ! -z "$fwd" ]]; then
# pass tripples likes `3000/tcp/29956`
while read -r pfwd; do
local dport protocol hport
local IFS="/"
read dport protocol hport <<< "${pfwd}"
if [[ -z "${ip}" ]]; then
err "ERROR: Empty IP for container: ${cont}"
fi
local rule="DOCKER -d ${ip}\/32 ! -i ${interface} -o ${interface} -p ${protocol} -m ${protocol} --dport ${dport} -j ACCEPT"
iptables_apply "${binary}" "filter" "-A" "${rule}" ${noop} ${verbose}
rule="POSTROUTING -s ${ip}\/32 -d ${ip}\/32 -p ${protocol} -m ${protocol} --dport ${dport} -j MASQUERADE"
iptables_apply "${binary}" "nat" "-A" "${rule}" ${noop} ${verbose}
rule="DOCKER ! -i ${interface} -p ${protocol} -m ${protocol} --dport ${hport} -j DNAT --to-destination ${ip}:${dport}"
iptables_apply "${binary}" "nat" "-A" "${rule}" ${noop} ${verbose}
done <<< "$fwd"
fi
fi
done <<< "$containers"
fi
}
if [[ ${1:-} ]] && declare -F | cut -d' ' -f3 | fgrep -qx -- "${1:-}"
then
case "$1" in
-h|--help) : ;;
*) ;;
esac
"$@"
else
main "$@"
fi
@deric
Copy link
Author

deric commented Jan 4, 2021

@panomitrius Right, that's fairly recent release. I've tested the script only on Docker versions up to 19.03.x releases. Probably Docker changed the format of the response in the latest release.

@panomitrius
Copy link

Alright, thanks for your time @deric. I found this script that works for my purposes :)

@fdurand
Copy link

fdurand commented May 2, 2022

@deric can i ask you what licence do you have for this code and can i use it in a opensource project ?

@deric
Copy link
Author

deric commented May 2, 2022

@fdurand Sure, you can use it. I usually don't include license header on a single script. Apache 2 license would be ok?

@fdurand
Copy link

fdurand commented May 3, 2022

@deric Yes Apache 2 License is perfect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment