scripts/graphpath

406 lines
15 KiB
Bash
Executable File

#!/bin/sh
#
# SOURCE: https://github.com/ocochard/graphpath
#
# Graphpath generates an ASCII network diagram from the route table of a Unix/Linux
# https://bsdrp.net
#
# Copyright (c) 2018, Olivier Cochard-Labbé (olivier@cochard.me)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
########################################################
# ## Concept documentation ##
#
# From this host ('me'), there are mainly 2 main families diagrams:
#
# - The first model family, when source and destination are towards differents
# interfaces:
#
# +-----+ +-----+ +-----+ +-----+
# | src | | src | | src | | src |
# +-----+ +-----+ +-----+ +-----+
# | | | |
# +----+ +--------+ +--------+ +----+
# | me | | router | | router | | me |
# +----+ +--------+ +--------+ +----+
# | | | |
# +-----+ +----+ +----+ +--------+
# | dst | | me | | me | | router |
# +-----+ +----+ +----+ +--------+
# | | |
# +-----+ +--------+ +-----+
# | dst | | router | | dst |
# +-----+ +--------+ +-----+
# |
# +-----+
# | dst |
# +-----+
#
# - The second model family, when source and destination are towards the same
# interface:
#
# +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
# | src | | dst | | src | | dst | | src | | dst | | src | | dst | | src | | dst |
# +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
# | | | | | | | | | |
# | | | | | | | | -+----+---+-
# | | | | | | | | |
# -+--+----+- | | | | +--------+ +--------+ |
# | | | | | | router | | router | |
# +--------+ +--------+ | | +--------+ +--------+ +--------+ |
# | router | | router | | | | router | | | |
# +--------+ +--------+ | | +--------+ -+-----+----+- +----+
# | | | | | | | me |
# +----+ -+--+-----+- -+--+---+- +----+ +----+
# | me | | | | me |
# +----+ +----+ +----+ +----+
# | me | | me |
# +----+ +----+
#
# Looking at these diagrams, we can extract 3 type of boxes:
# - standard and simple unique box: draw_block()
#
# |
# +----------------+
# | src/dst/router |
# | IP: address |
# | ARP: MAC |
# +----------------+
# |
#
# - A dual box on the same level: draw_2blocks()
#
# +---------------+ +---------------+
# | src or router | | dst or router |
# +---------------+ +---------------+
#
# - A 'me' block: draw_me()
#
# |
# +---------+
# | if_name |
# | if_ip |
# | route |
# (next only if src_int != dst_int)
# | route |
# | if_ip |
# | if_name |
# +---------+
# |
########################################################
# Forcing clean script
set -eu
# Functions definitions
# A usefull function (from: http://code.google.com/p/sh-die/)
die() { echo -n "EXIT: " >&2; echo "$@" >&2; exit 1; }
usage() {
echo "Usage:"
echo "$0 [-v] source-IP destination-IP"
echo " -v: display version number"
echo "Example:"
echo " $0 198.18.0.10 198.19.0.10"
exit 0
}
draw_block () {
# Draw one block for source&destination host and router
local model=$1 # src or dst, used for drawing link on the upper or lower side
local label=$2 # text to display into the block
local ip=$3 # IP address for src & dst host (=direct for a router)
local gateway=$4 # IP address for a router
local gateway_arp=$5 # ARP cache if directly connected to "me" block
local position=$6
left=""
right=""
# There is a special case: If ip=direct, this mean this block is useless
[ "$ip" = direct ] && return 0
# If in second family mode, need to move or add vertical line
[ "${position}" = "left" ] && left=" | "
[ "${position}" = "right" ] && right=" |"
# If label is "ROUTER TOWARDS DESTINATION", need to move this block to the left
#echo ${label} | grep -q "ROUTER TOWARDS DESTINATION" && left=" | "
# If label is "ROUTER TOWARDS SOURCE", need to add an vertical line to the left"
#echo ${label} | grep -q "ROUTER TOWARDS SOURCE" && right=" |"
[ "$model" = "dst" -a -z "${left}" ] && echo "${left} |${right}"
echo "${left}+----------------------------+${right}"
printf '%s| %-26s |%s\n' "${left}" "${label}" "${right}"
printf '%s| IP: %-21s|%s\n' "${left}" ${ip} "${right}"
[ "$gateway" = "direct" ] && printf '%s| ARP: %-21s|%s\n' "${left}" "${gateway_arp}" "${right}"
echo "${left}+----------------------------+${right}"
[ "$model" = "src" -o -n "${left}" ] && echo "${left} | ${right}" || true
}
draw_2blocks () {
# Draw two blocks on the same level
# Second model family
local label1=$1
local label2=$2
echo "+----------------------------+ +----------------------------+"
printf '| %-26s | | %-26s |\n' "${label1}" "${label2}"
if echo "${label1}" | grep -q 'ROUTER'; then
printf '| IP: %-18s | | IP: %-18s |\n' ${src_gateway} ${dst_gateway}
printf '| ARP: %-18s | | ARP: %-18s |\n' ${src_gateway_arp} ${dst_gateway_arp}
else
printf '| IP: %-18s | | IP: %-18s |\n' ${src_ip} ${dst_ip}
fi
if [ "${src_gateway}" = "direct" ]; then
printf '| ARP: %-18s |' ${src_gateway_arp}
elif [ "${dst_gateway}" = "direct" ]; then
printf '| |'
fi
if [ "${dst_gateway}" = "direct" ]; then
printf ' | ARP: %-18s |\n' ${dst_gateway_arp}
elif [ "${src_gateway}" = "direct" ]; then
printf ' | |\n'
fi
echo "+----------------------------+ +----------------------------+"
echo " | |"
}
draw_me () {
# Draw this device block
echo "+----------------------------+"
printf '| IF: %-18s |\n' ${src_interface}
printf '| MAC: %-18s |\n' ${src_interface_mac}
printf '| IP: %-18s |\n' ${src_interface_ip}
printf '| net: %-18s |\n' ${src_destination}
[ -n "${src_mask}" ] && printf '| mask: %-18s |\n' ${src_mask}
echo "| |"
echo "| THIS ${device} |"
if [ "${src_interface}" != "${dst_interface}" ]; then
# First model type, need to draw lower part
echo "| |"
printf '| net: %-18s |\n' ${dst_destination}
[ -n "${dst_mask}" ] && printf '| mask: %-18s |\n' ${dst_mask}
printf '| IP: %-18s |\n' ${dst_interface_ip}
printf '| MAC: %-18s |\n' ${dst_interface_mac}
printf '| IF: %-18s |\n' ${dst_interface}
fi
echo "+----------------------------+"
}
get_bsd() {
# Extract routes data
# Check if router enabled
[ $(sysctl -n net.inet.ip.forwarding) -eq 0 ] && forwarding=false || forwarding=true
[ $(sysctl -n net.inet6.ip6.forwarding) -eq 0 ] && forwarding6=false || forwarding6=true
# Populate src_* and dst_* variables
for i in src dst; do
eval "
# if route didn't fill _gateway, this mean its directly connected
${i}_gateway='direct'
# some route crossing PPP tun doesn't have _mask
${i}_mask='255.255.255.255'
# call route only once
tmp=\$(mktemp)
route -n get \${${i}_ip} > \${tmp} || die \"Route towards \${${i}_ip} not found\"
# Output are like this one:
# route to: 2.2.2.2
#destination: 0.0.0.0
# mask: 0.0.0.0
# gateway: 192.168.1.100
# fib: 0
# interface: bxe3
# flags: <UP,GATEWAY,DONE,STATIC>
# recvpipe sendpipe ssthresh rtt,msec mtu weight expire
# 0 0 0 0 1500 1 0
while read line; do
data=\$(echo \$line | cut -d ':' -f 1)
case \$data in
\"route to\")
${i}_routeto=\$(echo \$line | cut -d ':' -f 2 | tr -d ' ')
;;
destination)
${i}_destination=\$(echo \$line | cut -d ':' -f 2 | tr -d ' ')
;;
mask)
${i}_mask=\$(echo \$line | cut -d ':' -f 2 | tr -d ' ')
;;
gateway)
${i}_gateway=\$(echo \$line | cut -d ':' -f 2 | tr -d ' ')
;;
fib)
${i}_fib=\$(echo \$line | cut -d ':' -f 2)
;;
interface)
${i}_interface=\$(echo \$line | cut -d ':' -f 2 | tr -d ' ')
${i}_interface_mac=\$(ifconfig \${${i}_interface} | grep -E 'ether|lladdr|address' | cut -d ' ' -f 2)
${i}_interface_ip=\$(ifconfig \${${i}_interface} | grep -w inet | cut -d ' ' -f 2)
;;
flags)
${i}_flags=\$(echo \$line | cut -d ':' -f 2)
;;
esac
done < \${tmp}
[ \"\${${i}_gateway}\" = \"direct\" ] && lookup=\${${i}_routeto} || lookup=\${${i}_gateway}
case \${OS} in
OpenBSD)
${i}_gateway_arp=\$(arp -n \${lookup} | grep \${lookup} | tr -s ' ' | cut -d ' ' -f 2)
;;
*)
${i}_gateway_arp=\$(arp -n \${lookup} | cut -d ' ' -f 4)
;;
esac
[ \"\${${i}_gateway_arp}\" = \"no\" ] && ${i}_gateway_arp='empty' || true
"
rm ${tmp} || die "can't delete ${tmp}"
done
}
get_linux () {
# Extract routes data from a Linux
# ip route has a non consistent output: STUPID Linux !
# Check if router enabled
[ $(cat /proc/sys/net/ipv4/ip_forward) -eq 0 ] && forwarding=false || forwarding=true
[ $(cat /proc/sys/net/ipv6/conf/all/forwarding) -eq 0 ] && forwarding6=false || forwarding6=true
# Populate src_* and dst_* variables
for i in src dst; do
eval "
# if route didn't fill _gateway, this mean its directly connected
${i}_gateway='direct'
# No mask on linux (using a / into the destination variable)
${i}_mask=''
# call route only once
tmp=\$(mktemp)
ip -o route get \${${i}_ip} > \${tmp}
# Output are like these:
# 2.2.2.3 via 10.253.52.126 dev eth2 src 10.253.52.115 \ cache
# 192.168.237.116 dev eth1.337 src 192.168.237.115 \ cache
# 10.253.52.124 dev eth2 src 10.253.52.115 \ cache
# local 127.0.0.1 dev lo src 127.0.0.1
read ip via gateway dev if src if_ip < \${tmp}
if [ \"\${via}\" = \"via\" ]; then
${i}_routeto=\${${i}_ip}
${i}_gateway=\${gateway}
${i}_gateway_arp=\$(ip neigh show \${${i}_gateway} | cut -d ' ' -f 5)
${i}_interface=\${if}
${i}_interface_ip=\$(echo \${if_ip} | cut -d ' ' -f1)
elif [ \"\${via}\" = \"dev\" ]; then
${i}_routeto=\${${i}_ip}
${i}_interface=\${gateway}
${i}_interface_ip=\$(echo \${if} | cut -d ' ' -f1)
else
echo "WARNING: Not supported condition!"
${i}_routeto=\${via}
${i}_interface=\${dev}
${i}_interface_ip=\${via}
fi
${i}_interface_mac=\$(ip link show \${${i}_interface}| grep ether | tr -s ' ' | cut -d ' ' -f 3)
# We still need other data (destination subnet and mask)
# need to use another ip commande for getting the subnet matching
# ip route show to match 192.168.229.58
# default via 10.253.52.126 dev eth2 onlink
# 192.168.229.56/29 via 192.168.237.100 dev eth1.337
${i}_destination=\$(ip -o route show to match \${${i}_ip} | grep \${if} | cut -d ' ' -f1)
if [ \"\${${i}_gateway}\" = \"direct\" ]; then
${i}_gateway_arp=\$(ip neigh show \${${i}_routeto} | cut -d ' ' -f 5)
[ \"\${${i}_gateway_arp}\" = \"no\" ] && ${i}_gateway_arp='empty' || true
fi
"
rm ${tmp} || die "can't delete ${tmp}"
done
}
### Main function ###
version="1.0"
if [ "$#" -eq 1 ]; then
if [ "$1" = "-v" ]; then
echo "Version ${version}"
exit 0
else
usage
fi
fi
[ "$#" -ne 2 ] && usage
src_ip=$1
dst_ip=$2
device="ROUTER"
# Small checks
[ "${src_ip}" = "${dst_ip}" ] && die "Same source and destination IP addresses"
OS=$(uname)
case ${OS} in
FreeBSD|OpenBSD|NetBSD)
get_bsd
;;
Linux)
get_linux
;;
*)
die "This script was not tested on ${OS}"
;;
esac
if [ $forwarding = false -o $forwarding6 = false ]; then
device="HOST "
fi
[ "${device}" = "HOST" ] && echo "This tool is mainly designed for drawing router or firewall routing view"
### Start ASCII drawing
if [ "${src_interface}" != "${dst_interface}" ]; then
# First model family
if [ "${src_ip}" != "${src_interface_ip}" ]; then
draw_block src 'SOURCE HOST' "${src_ip}" "${src_gateway}" "${src_gateway_arp}" ""
[ "${src_gateway}" != "direct" ] && draw_block src 'ROUTER TOWARDS SOURCE' "${src_gateway}" direct "${src_gateway_arp}" ""
fi
draw_me
if [ "${dst_ip}" != "${dst_interface_ip}" ]; then
[ "${dst_gateway}" != "direct" ] && draw_block dst 'ROUTER TOWARDS DESTINATION' "${dst_gateway}" direct "${dst_gateway_arp}" ""
draw_block dst 'DESTINATION HOST' "${dst_ip}" "${dst_gateway}" "${dst_gateway_arp}" ""
fi
else
# Second model family
draw_2blocks 'SOURCE HOST' 'DESTINATION HOST'
if [ "${src_gateway}" = "${dst_gateway}" ]; then
echo " --+---+-----------------------------+---"
echo " | "
draw_block src 'ROUTER' "${src_gateway}" direct "${src_gateway_arp}" ""
else
if [ "${src_gateway}" != "direct" -a "${dst_gateway}" = "direct" ]; then
draw_block src 'ROUTER TOWARDS SOURCE' "${src_gateway}" direct "${src_gateway_arp}" "right"
elif [ "${dst_gateway}" != "direct" -a "${src_gateway}" = "direct" ]; then
draw_block dst 'ROUTER TOWARDS DESTINATION' "${dst_gateway}" direct "${dst_gateway_arp}" "left"
else
draw_2blocks 'ROUTER TOWARDS SOURCE' 'ROUTER TOWARDS DESTINATION'
fi
echo " --+---+-----------------------------+---"
echo " |"
fi
draw_me
fi