├── LICENSE ├── README.md └── graphpath /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Olivier Cochard-Labbé 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | graphpath 2 | ========= 3 | 4 | ## Description 5 | 6 | Graphpath generates an ASCII network diagram from the route table of a Unix/Linux router. It's a [BSDRP](https://bsdrp.net)'s tool. 7 | 8 | ## Dependecy 9 | 10 | None: it's just a shell script using standards tools (route, arp, ifconfig on *BSD and ip on Linux). 11 | 12 | ## Examples 13 | 14 | Here are some graphpath output: 15 | 16 | ``` 17 | [root@me]~# graphpath 10.0.11.11 10.0.12.12 18 | +----------------------------+ +----------------------------+ 19 | | SOURCE HOST | | DESTINATION HOST | 20 | | IP: 10.0.11.11 | | IP: 10.0.12.12 | 21 | +----------------------------+ +----------------------------+ 22 | | | 23 | +----------------------------+ +----------------------------+ 24 | | ROUTER TOWARDS SOURCE | | ROUTER TOWARDS DESTINATION | 25 | | IP: 10.0.1.11 | | IP: 10.0.1.12 | 26 | | ARP: 02:01:32:38:b0:03 | | ARP: 02:01:32:38:b0:04 | 27 | +----------------------------+ +----------------------------+ 28 | | | 29 | --+---+-----------------------------+--- 30 | | 31 | +----------------------------+ 32 | | IF: bridge1 | 33 | | MAC: 02:ab:de:8c:30:01 | 34 | | IP: 10.0.1.1 | 35 | | net: 10.0.11.0 | 36 | | mask: 255.255.255.0 | 37 | | | 38 | | THIS ROUTER | 39 | +----------------------------+ 40 | ``` 41 | 42 | ``` 43 | [root@me]~# graphpath 2001:db8:11::11 2001:db8:1::12 44 | +---------------------------------------------------+ +---------------------------------------------------+ 45 | | SOURCE HOST | | DESTINATION HOST | 46 | | IP: 2001:db8:11::11 | | IP: 2001:db8:1::12 | 47 | | | | NDP: 02:01:c9:01:b0:04 | 48 | +---------------------------------------------------+ +---------------------------------------------------+ 49 | | | 50 | +---------------------------------------------------+ | 51 | | ROUTER TOWARDS SOURCE | | 52 | | IP: 2001:db8:1::11 | | 53 | | NDP: 02:01:c9:01:b0:03 | | 54 | +---------------------------------------------------+ | 55 | | | 56 | --+---+----------------------------------------------+--- 57 | | 58 | +---------------------------------------------------+ 59 | | IF: bridge1 | 60 | | MAC: 02:de:f2:41:54:01 | 61 | | IP: 2001:db8:1::1 | 62 | | net: 2001:db8:11:: | 63 | | mask: ffff:ffff:ffff:ffff:: | 64 | | | 65 | | THIS ROUTER | 66 | +---------------------------------------------------+ 67 | ``` 68 | 69 | ``` 70 | [root@me]~# graphpath 10.0.11.11 10.0.21.21 71 | +----------------------------+ 72 | | SOURCE HOST | 73 | | IP: 10.0.11.11 | 74 | +----------------------------+ 75 | | 76 | +----------------------------+ 77 | | ROUTER TOWARDS SOURCE | 78 | | IP: 10.0.1.11 | 79 | | ARP: 02:01:32:38:b0:03 | 80 | +----------------------------+ 81 | | 82 | +----------------------------+ 83 | | IF: bridge1 | 84 | | MAC: 02:ab:de:8c:30:01 | 85 | | IP: 10.0.1.1 | 86 | | net: 10.0.11.0 | 87 | | mask: 255.255.255.0 | 88 | | | 89 | | THIS ROUTER | 90 | | | 91 | | net: 10.0.21.0 | 92 | | mask: 255.255.255.0 | 93 | | IP: 10.0.2.1 | 94 | | MAC: 02:ab:de:8c:30:02 | 95 | | IF: bridge2 | 96 | +----------------------------+ 97 | | 98 | +----------------------------+ 99 | | ROUTER TOWARDS DESTINATION | 100 | | IP: 10.0.2.21 | 101 | | ARP: 02:02:32:38:b0:05 | 102 | +----------------------------+ 103 | | 104 | +----------------------------+ 105 | | DESTINATION HOST | 106 | | IP: 10.0.21.21 | 107 | +----------------------------+ 108 | ``` 109 | 110 | ``` 111 | [root@me]~# graphpath 10.0.11.11 10.0.1.12 112 | +----------------------------+ +----------------------------+ 113 | | SOURCE HOST | | DESTINATION HOST | 114 | | IP: 10.0.11.11 | | IP: 10.0.1.12 | 115 | | | | ARP: 02:01:32:38:b0:04 | 116 | +----------------------------+ +----------------------------+ 117 | | | 118 | +----------------------------+ | 119 | | ROUTER TOWARDS SOURCE | | 120 | | IP: 10.0.1.11 | | 121 | | ARP: 02:01:32:38:b0:03 | | 122 | +----------------------------+ | 123 | | | 124 | --+---+-----------------------------+--- 125 | | 126 | +----------------------------+ 127 | | IF: bridge1 | 128 | | MAC: 02:ab:de:8c:30:01 | 129 | | IP: 10.0.1.1 | 130 | | net: 10.0.11.0 | 131 | | mask: 255.255.255.0 | 132 | | | 133 | | THIS ROUTER | 134 | +----------------------------+ 135 | ``` 136 | 137 | ``` 138 | [root@me]~# graphpath 10.0.1.12 10.0.11.11 139 | +----------------------------+ +----------------------------+ 140 | | SOURCE HOST | | DESTINATION HOST | 141 | | IP: 10.0.1.12 | | IP: 10.0.11.11 | 142 | | ARP: 02:01:32:38:b0:04 | | | 143 | +----------------------------+ +----------------------------+ 144 | | | 145 | | +----------------------------+ 146 | | | ROUTER TOWARDS DESTINATION | 147 | | | IP: 10.0.1.11 | 148 | | | ARP: 02:01:32:38:b0:03 | 149 | | +----------------------------+ 150 | | | 151 | --+---+-----------------------------+--- 152 | | 153 | +----------------------------+ 154 | | IF: bridge1 | 155 | | MAC: 02:ab:de:8c:30:01 | 156 | | IP: 10.0.1.1 | 157 | | net: 10.0.1.0 | 158 | | mask: 255.255.255.0 | 159 | | | 160 | | THIS ROUTER | 161 | +----------------------------+ 162 | ``` 163 | -------------------------------------------------------------------------------- /graphpath: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Graphpath generates an ASCII network diagram from the route table of a Unix/Linux 4 | # https://bsdrp.net 5 | # 6 | # Copyright (c) 2018-2021, Olivier Cochard-Labbé (olivier@cochard.me) 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions 11 | # are met: 12 | # 1. Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright 15 | # notice, this list of conditions and the following disclaimer in the 16 | # documentation and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | # SUCH DAMAGE. 29 | 30 | 31 | ######################################################## 32 | # ## Concept documentation ## 33 | # 34 | # From this host ('me'), there are mainly 2 main families diagrams: 35 | # 36 | # - The first model family, when source and destination are towards differents 37 | # interfaces: 38 | # 39 | # +-----+ +-----+ +-----+ +-----+ 40 | # | src | | src | | src | | src | 41 | # +-----+ +-----+ +-----+ +-----+ 42 | # | | | | 43 | # +----+ +--------+ +--------+ +----+ 44 | # | me | | router | | router | | me | 45 | # +----+ +--------+ +--------+ +----+ 46 | # | | | | 47 | # +-----+ +----+ +----+ +--------+ 48 | # | dst | | me | | me | | router | 49 | # +-----+ +----+ +----+ +--------+ 50 | # | | | 51 | # +-----+ +--------+ +-----+ 52 | # | dst | | router | | dst | 53 | # +-----+ +--------+ +-----+ 54 | # | 55 | # +-----+ 56 | # | dst | 57 | # +-----+ 58 | # 59 | # - The second model family, when source and destination are towards the same 60 | # interface: 61 | # 62 | # +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ 63 | # | src | | dst | | src | | dst | | src | | dst | | src | | dst | | src | | dst | 64 | # +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ 65 | # | | | | | | | | | | 66 | # | | | | | | | | -+----+---+- 67 | # | | | | | | | | | 68 | # -+--+----+- | | | | +--------+ +--------+ | 69 | # | | | | | | router | | router | | 70 | # +--------+ +--------+ | | +--------+ +--------+ +--------+ | 71 | # | router | | router | | | | router | | | | 72 | # +--------+ +--------+ | | +--------+ -+-----+----+- +----+ 73 | # | | | | | | | me | 74 | # +----+ -+--+-----+- -+--+---+- +----+ +----+ 75 | # | me | | | | me | 76 | # +----+ +----+ +----+ +----+ 77 | # | me | | me | 78 | # +----+ +----+ 79 | # 80 | # Looking at these diagrams, we can extract 3 type of boxes: 81 | # - standard and simple unique box: draw_block() 82 | # 83 | # | 84 | # +----------------+ 85 | # | src/dst/router | 86 | # | IP: address | 87 | # | ARP: MAC | 88 | # +----------------+ 89 | # | 90 | # 91 | # - A dual box on the same level: draw_2blocks() 92 | # 93 | # +---------------+ +---------------+ 94 | # | src or router | | dst or router | 95 | # +---------------+ +---------------+ 96 | # 97 | 98 | # - A 'me' block: draw_me() 99 | # 100 | # | 101 | # +---------+ 102 | # | if_name | 103 | # | if_ip | 104 | # | route | 105 | # (next only if src_int != dst_int) 106 | # | route | 107 | # | if_ip | 108 | # | if_name | 109 | # +---------+ 110 | # | 111 | # 112 | # - About the box witdh 113 | # 114 | # inet4 maximum witdh size: 115 | #| ROUTER TOWARDS DESTINATION | 116 | #| IP: 192.168.100.100 | 117 | #| ARP: 00:0d:b9:3c:a0:cd | 118 | #+----------------------------+ 119 | #123456789012345678901234567890 120 | # 121 | # => 30 (or 28 without line) 122 | # 123 | # inet6 maximum witdh size: 124 | #| IP: 2001:0db8:0000:0000:0000:ff00:0042:8329 | 125 | #| NDP: 00:0d:b9:3c:a0:cd | 126 | #+-----------------------------------------------+ 127 | #1234567890123456789012345679012345679012345679012 128 | # 129 | # => 52 (or 50 without line) 130 | # 131 | # 132 | ######################################################## 133 | 134 | # Forcing clean script 135 | set -eu 136 | 137 | # Functions definitions 138 | 139 | # A usefull function (from: http://code.google.com/p/sh-die/) 140 | die() { echo -n "EXIT: " >&2; echo "$@" >&2; exit 1; } 141 | 142 | usage() { 143 | echo "Usage:" 144 | echo "$0 [-v] source-IP destination-IP" 145 | echo " -v: display version number" 146 | echo "Example:" 147 | echo " $0 198.18.0.10 198.19.0.10" 148 | echo " $0 2001:db8:18::10 2001:db8:19::10" 149 | exit 0 150 | } 151 | 152 | draw_lan_line () { 153 | # Draw a LAN line, like this one: 154 | # --+---+-----------------------------+--- 155 | # This line is only used in top of 'THIS ROUTER' box 156 | 157 | printf '%*s--+---+' $((box_hw - 1)) 158 | printf '%*s' $((box_w - 4)) ' ' | tr ' ' '-' 159 | printf '+---\n' 160 | } 161 | 162 | draw_connector () { 163 | # Draw one connector, like this one: 164 | # | 165 | eval "printf '%s%-$((box_hw))s|%s\n' "\${l_pad}\" "\${r_pad}\"" 166 | } 167 | 168 | draw_dual_connectors () { 169 | # Draw dual connectors, like this one: 170 | # | | 171 | eval "printf '%-$((box_hw + 1))s|%-$((box_w))s|\n'" 172 | } 173 | 174 | draw_block_line () { 175 | # Draw box's upper or lower line, like this one: 176 | # +----------------------------+ 177 | # The 'me' box didn't need left & right padding 178 | # because it's always put on the left side 179 | local ll_pad="$1" 180 | local lr_pad="$2" 181 | 182 | printf "%s+" "${ll_pad}" 183 | printf '%*s' $((box_w + 1)) ' ' | tr ' ' '-' 184 | printf "+%s\n" "${lr_pad}" 185 | } 186 | 187 | draw_2blocks_line () { 188 | # Draw 2 box's upper or lower line, like this one: 189 | # +-----------------------------+ +-----------------------------+ 190 | 191 | printf "+" 192 | printf '%*s' $((box_w + 1)) ' ' | tr ' ' '-' 193 | printf "+ +" 194 | printf '%*s' $((box_w + 1)) ' ' | tr ' ' '-' 195 | printf "+\n" 196 | } 197 | 198 | draw_block () { 199 | # Draw one block for source&destination host and router 200 | # 201 | # $l_pad | $r_pad 202 | # $l_pad +-------+ $r_pad 203 | # $l_pad |$label | $r_pad 204 | # $l_pad |$ip | $r_pad 205 | # $l_pad +-------+ $r_pad 206 | # $l_pad | $r_pad 207 | # 208 | # l_pad will draw a vertical line on the left side of the box 209 | # r_pad will draw a vertical line on the rigth side of the box 210 | 211 | local model="$1" # src or dst, used for drawing link on the upper or lower side 212 | local label="$2" # text to display into the block 213 | local ip="$3" # IP address for src & dst host (=direct for a router) 214 | local gateway="$4" # IP address for a router 215 | local gateway_arp="$5" # ARP cache if directly connected to "me" block 216 | local position="$6" # left for box on left size, right other 217 | l_pad="" # left padding text 218 | r_pad="" # right padding text 219 | 220 | # There is a special case: If ip=direct, this mean this block is useless 221 | [ "$ip" = direct ] && return 0 222 | 223 | # If in second family mode, need to move or add vertical line 224 | if [ "${position}" = "left" ]; then 225 | eval "l_pad=\$(printf '%-$((box_hw + 1))s|%-$((box_hw + 5))s')" 226 | fi 227 | if [ "${position}" = "right" ]; then 228 | r_pad=$(printf '%*s|' $((box_hw - 1))) 229 | fi 230 | 231 | if [ "$model" = "dst" -a -z "${l_pad}" ]; then 232 | # Draw connector on upper side 233 | draw_connector 234 | fi 235 | 236 | # Draw box's upper line 237 | draw_block_line "${l_pad}" "${r_pad}" 238 | eval " 239 | printf '%s| %-$((box_w - 1))s |%s\n' \"\${l_pad}\" \"\${label}\" \"\${r_pad}\" 240 | printf '%s| IP: %-$((box_w - 6))s|%s\n' \"\${l_pad}\" \${ip} \"\${r_pad}\" 241 | " 242 | 243 | if [ "$gateway" = "direct" ]; then 244 | eval "printf '%s| %s: %-$((box_w - 6))s|%s\n' \"\${l_pad}\" \"\${ADD_RES}\" \"${gateway_arp}\" \"${r_pad}\"" 245 | fi 246 | 247 | # Draw box's lower line 248 | draw_block_line "${l_pad}" "${r_pad}" 249 | 250 | # Draw block lower connectors 251 | if [ "$model" = "src" ]; then 252 | # needs a simple connectors if l_pad and r_pad are empty 253 | # needs dual connectors in other case 254 | [ -z "${l_pad}" -a -z "${r_pad}" ] && draw_connector || draw_dual_connectors 255 | else 256 | # If destination block and l_pad used, need a dual connectors 257 | [ -n "${l_pad}" ] && draw_dual_connectors 258 | fi 259 | } 260 | 261 | draw_2blocks () { 262 | # Draw two blocks on the same level 263 | # Second model family 264 | local label1="$1" 265 | local label2="$2" 266 | draw_2blocks_line 267 | eval "printf '| %-$((box_w))s| | %-$((box_w))s|\n' \"${label1}\" \"${label2}\"" 268 | if echo "${label1}" | grep -q 'ROUTER'; then 269 | eval " 270 | printf '| IP: %-$((box_w - 6))s| | IP: %-$((box_w - 6))s|\n' \${src_gateway} \${dst_gateway} 271 | printf '| %s: %-$((box_w - 6))s| | %s: %-$((box_w - 6))s|\n' \${ADD_RES} \${src_gateway_arp} \${ADD_RES} \${dst_gateway_arp} 272 | " 273 | else 274 | eval "printf '| IP: %-$((box_w - 6))s| | IP: %-$((box_w - 6))s|\n' \${src_ip} \${dst_ip}" 275 | fi 276 | if [ "${src_gateway}" = "direct" ]; then 277 | eval "printf '| %s: %-$((box_w - 6))s|' \${ADD_RES} \${src_gateway_arp}" 278 | elif [ "${dst_gateway}" = "direct" ]; then 279 | eval "printf '|%-$((box_w + 1))s|'" 280 | fi 281 | if [ "${dst_gateway}" = "direct" ]; then 282 | eval "printf ' | %s: %-$((box_w - 6))s|\n' \${ADD_RES} \${dst_gateway_arp}" 283 | elif [ "${src_gateway}" = "direct" ]; then 284 | eval "printf ' | %-$((box_w))s|\n'" 285 | fi 286 | draw_2blocks_line 287 | draw_dual_connectors 288 | } 289 | 290 | draw_me () { 291 | # Draw this device block 292 | 293 | # Draw box's lower line 294 | draw_block_line "" "" 295 | eval " 296 | printf '| IF: %-$((box_w - 6))s|\n' \${src_interface} 297 | printf '| MAC: %-$((box_w - 6))s|\n' \${src_interface_mac} 298 | printf '| IP: %-$((box_w - 6))s|\n' \${src_interface_ip} 299 | printf '| net: %-$((box_w - 6))s|\n' \${src_destination} 300 | " 301 | [ -n "${src_mask}" ] && eval "printf '| mask: %-$((box_w - 6))s|\n' \${src_mask}" 302 | eval " 303 | printf '|%-$((box_w + 1))s|\n' 304 | printf '|%-$((box_hw - 5))s THIS %-$((box_hw + 2))s|\n' \"\" \${device} 305 | " 306 | if [ "${src_interface}" != "${dst_interface}" ]; then 307 | # First model type, need to draw lower part 308 | eval " 309 | printf '|%-$((box_w +1))s|\n' 310 | printf '| net: %-$((box_w - 6))s|\n' \${dst_destination} 311 | " 312 | [ -n "${dst_mask}" ] && eval "printf '| mask: %-$((box_w - 6))s|\n' \${dst_mask}" 313 | eval " 314 | printf '| IP: %-$((box_w - 6))s|\n' ${dst_interface_ip} 315 | printf '| MAC: %-$((box_w - 6))s|\n' ${dst_interface_mac} 316 | printf '| IF: %-$((box_w - 6))s|\n' ${dst_interface} 317 | " 318 | fi 319 | # Draw box's lower line 320 | draw_block_line "" "" 321 | } 322 | 323 | get_bsd() { 324 | # Extract routes data 325 | # Check if router enabled 326 | [ $(sysctl -n net.inet.ip.forwarding) -eq 0 ] && forwarding=false || forwarding=true 327 | [ $(sysctl -n net.inet6.ip6.forwarding) -eq 0 ] && forwarding6=false || forwarding6=true 328 | 329 | (${inet6}) && family="-inet6" || family="-inet" 330 | tmp=$(mktemp) 331 | # Populate src_* and dst_* variables 332 | for i in src dst; do 333 | eval " 334 | # if route didn't fill _gateway, this mean its directly connected 335 | ${i}_gateway='direct' 336 | 337 | # some route crossing PPP tun doesn't have _mask 338 | ${i}_mask='255.255.255.255' 339 | 340 | # call route only once 341 | case \${OS} in 342 | FreeBSD|Darwin) 343 | route -n get \${family} \${${i}_ip} > \${tmp} || die \"Route towards \${${i}_ip} not found\" 344 | ;; 345 | *) 346 | route -n get \${${i}_ip} > \${tmp} || die \"Route towards \${${i}_ip} not found\" 347 | ;; 348 | esac 349 | 350 | # Output are like this one: 351 | ### inet4 ### 352 | # route to: 2.2.2.2 353 | #destination: 0.0.0.0 354 | # mask: 0.0.0.0 355 | # gateway: 192.168.1.100 356 | # fib: 0 357 | # interface: bxe3 358 | # flags: 359 | # recvpipe sendpipe ssthresh rtt,msec mtu weight expire 360 | # 0 0 0 0 1500 1 0 361 | ### inet6 ### 362 | # route to: 2001:db8:11::11 363 | #destination: 2001:db8:11:: 364 | # mask: ffff:ffff:ffff:ffff:: 365 | # gateway: 2001:db8:1::11 366 | # fib: 0 367 | # interface: bridge1 368 | # flags: 369 | # recvpipe sendpipe ssthresh rtt,msec mtu weight expire 370 | # 0 0 0 0 1500 1 0 371 | 372 | while read line; do 373 | data=\$(echo \$line | cut -d ':' -f 1) 374 | case \$data in 375 | # Theorically we can remove for all lines, the first 13 characters 376 | # but the while read didn't take care of the first spaces 377 | \"route to\") 378 | ${i}_routeto=\${line#??????????} 379 | ;; 380 | destination) 381 | ${i}_destination=\${line#?????????????} 382 | ;; 383 | mask) 384 | ${i}_mask=\${line#??????} 385 | ;; 386 | gateway) 387 | ${i}_gateway=\${line#?????????} 388 | ;; 389 | fib) 390 | ${i}_fib=\${line#?????} 391 | ;; 392 | interface) 393 | ${i}_interface=\${line#???????????} 394 | ${i}_interface_mac=\$(ifconfig \${${i}_interface} | grep -E 'ether|lladdr|address' | cut -d ' ' -f 2) 395 | if (\${inet6}); then 396 | ${i}_interface_ip=\$(ifconfig \${${i}_interface} | grep -w inet6 | head -1 | cut -d ' ' -f 2) 397 | else 398 | ${i}_interface_ip=\$(ifconfig \${${i}_interface} | grep -w inet | cut -d ' ' -f 2) 399 | fi 400 | ;; 401 | flags) 402 | ${i}_flags=\${line#?????????} 403 | ;; 404 | esac 405 | done < \${tmp} 406 | [ \"\${${i}_gateway}\" = \"direct\" ] && lookup=\${${i}_routeto} || lookup=\${${i}_gateway} 407 | if (\${inet6}); then 408 | ${i}_gateway_arp=\$(ndp -n \${lookup} | tail -1 | tr -s ' ' | cut -d ' ' -f 2) 409 | # When it's empty, it return the IPv6 address between () 410 | echo \${${i}_gateway_arp} | grep -q '(' && ${i}_gateway_arp='empty' 411 | else 412 | case \${OS} in 413 | OpenBSD) 414 | ${i}_gateway_arp=\$(arp -n \${lookup} | grep \${lookup} | tr -s ' ' | cut -d ' ' -f 2) 415 | ;; 416 | *) 417 | ${i}_gateway_arp=\$(arp -n \${lookup} | cut -d ' ' -f 4) 418 | ;; 419 | esac 420 | fi 421 | [ \"\${${i}_gateway_arp}\" = \"no\" ] && ${i}_gateway_arp='empty' || true 422 | " 423 | rm ${tmp} || die "can't delete ${tmp}" 424 | done 425 | } 426 | 427 | get_linux () { 428 | # Extract routes data from a Linux 429 | (${inet6}) && family="-6" || family="-4" 430 | # ip route has a non consistent output: STUPID Linux ! 431 | # Check if router enabled 432 | if [ -f /proc/sys/net/ipv4/ip_forward ]; then 433 | [ $(cat /proc/sys/net/ipv4/ip_forward) -eq 0 ] && forwarding=false || forwarding=true 434 | else 435 | forwarding=false 436 | fi 437 | if [ -f /proc/sys/net/ipv6/conf/all/forwarding ]; then 438 | [ $(cat /proc/sys/net/ipv6/conf/all/forwarding) -eq 0 ] && forwarding6=false || forwarding6=true 439 | else 440 | forwarding6=false 441 | fi 442 | tmp=$(mktemp) 443 | # Populate src_* and dst_* variables 444 | for i in src dst; do 445 | eval " 446 | # if route didn't fill _gateway, this mean its directly connected 447 | ${i}_gateway='direct' 448 | 449 | # No mask on linux (using a / into the destination variable) 450 | ${i}_mask='' 451 | 452 | # call route only once 453 | ip -o route get \${${i}_ip} > \${tmp} 454 | 455 | # Output are like these: 456 | # 2.2.2.3 via 10.253.52.126 dev eth2 src 10.253.52.115 \ cache 457 | # 192.168.237.116 dev eth1.337 src 192.168.237.115 \ cache 458 | # 10.253.52.124 dev eth2 src 10.253.52.115 \ cache 459 | # local 127.0.0.1 dev lo src 127.0.0.1 460 | # 2607:f8b0:4004:80c::200e from :: via 2001:470:66:324::1 dev he-ipv6 src 2001:470:66:324::2 metric 1024 pref medium 461 | 462 | read ip via gateway dev if src if_ip < \${tmp} 463 | if [ \"\${via}\" = \"via\" ]; then 464 | ${i}_routeto=\${${i}_ip} 465 | ${i}_gateway=\${gateway} 466 | ${i}_gateway_arp=\$(ip neigh show \${${i}_gateway} | cut -d ' ' -f 5) 467 | ${i}_interface=\${if} 468 | ${i}_interface_ip=\$(echo \${if_ip} | cut -d ' ' -f1) 469 | elif [ \"\${via}\" = \"dev\" ]; then 470 | ${i}_routeto=\${${i}_ip} 471 | ${i}_interface=\${gateway} 472 | ${i}_interface_ip=\$(echo \${if} | cut -d ' ' -f1) 473 | elif [ \"\${via}\" = \"from\" ]; then 474 | # Inet6 route 475 | ${i}_routeto=\${${i}_ip} 476 | ${i}_gateway=\${if} 477 | # 2002:333:333::1 dev eth1 lladdr 00:12:1e:33:AA:BB router REACHABLE 478 | ${i}_gateway_arp=\$(ip -6 neigh show \${${i}_gateway} | cut -d ' ' -f 5) 479 | ${i}_interface=\$(echo \${if_ip} | cut -d ' ' -f1) 480 | ${i}_interface_ip=\$(echo \${if_ip} | cut -d ' ' -f3) 481 | else 482 | echo "WARNING: Not supported condition!" 483 | ${i}_routeto=\${via} 484 | ${i}_interface=\${dev} 485 | ${i}_interface_ip=\${via} 486 | fi 487 | ${i}_interface_mac=\$(ip link show \${${i}_interface}| grep ether | tr -s ' ' | cut -d ' ' -f 3) 488 | 489 | # We still need other data (destination subnet and mask) 490 | # need to use another ip command to retrieve the subnet matching 491 | # ip route show to match 192.168.229.58 492 | # default via 10.253.52.126 dev eth2 onlink 493 | # 192.168.229.56/29 via 192.168.237.100 dev eth1.337 494 | ${i}_destination=\$(ip -o \${family} route show to match \${${i}_ip} | grep \${if} | cut -d ' ' -f1) 495 | 496 | if [ \"\${${i}_gateway}\" = \"direct\" ]; then 497 | ${i}_gateway_arp=\$(ip neigh show \${${i}_routeto} | cut -d ' ' -f 5) 498 | [ \"\${${i}_gateway_arp}\" = \"no\" ] && ${i}_gateway_arp='empty' || true 499 | fi 500 | " 501 | rm ${tmp} || die "can't delete ${tmp}" 502 | done 503 | } 504 | 505 | ### Main function ### 506 | 507 | version="1.2" 508 | 509 | if [ "$#" -eq 1 ]; then 510 | if [ "$1" = "-v" ]; then 511 | echo "Version ${version}" 512 | exit 0 513 | else 514 | usage 515 | fi 516 | fi 517 | [ "$#" -ne 2 ] && usage 518 | 519 | src_ip=$1 520 | dst_ip=$2 521 | device="ROUTER" 522 | inet6=false 523 | 524 | # Small checks 525 | [ "${src_ip}" = "${dst_ip}" ] && die "Same source and destination IP addresses" 526 | $(echo ${src_ip} | grep -q ':') && inet6=true 527 | 528 | OS=$(uname) 529 | case ${OS} in 530 | FreeBSD|OpenBSD|NetBSD|Darwin) 531 | get_bsd 532 | ;; 533 | Linux) 534 | get_linux 535 | ;; 536 | *) 537 | die "This script was not tested on ${OS}" 538 | ;; 539 | esac 540 | 541 | if [ $forwarding = false -o $forwarding6 = false ]; then 542 | device="HOST " 543 | fi 544 | 545 | 546 | [ "${device}" = "HOST" ] && echo "This tool is mainly designed for drawing router or firewall routing view" 547 | 548 | ### Start ASCII drawing 549 | if (${inet6}); then 550 | ADD_RES="NDP" 551 | # inet6 addresses needs large box able to contains 50 caracters 552 | box_w=50 553 | else 554 | ADD_RES="ARP" 555 | # inet4 addresses only need to contains 28 caracters 556 | box_w=28 557 | fi 558 | box_hw=$((box_w / 2 - 1)) 559 | 560 | if [ "${src_interface}" != "${dst_interface}" ]; then 561 | # First model family 562 | if [ "${src_ip}" != "${src_interface_ip}" ]; then 563 | draw_block src 'SOURCE HOST' "${src_ip}" "${src_gateway}" "${src_gateway_arp}" "" 564 | [ "${src_gateway}" != "direct" ] && draw_block src 'ROUTER TOWARDS SOURCE' "${src_gateway}" direct "${src_gateway_arp}" "" 565 | fi 566 | draw_me 567 | if [ "${dst_ip}" != "${dst_interface_ip}" ]; then 568 | [ "${dst_gateway}" != "direct" ] && draw_block dst 'ROUTER TOWARDS DESTINATION' "${dst_gateway}" direct "${dst_gateway_arp}" "" 569 | draw_block dst 'DESTINATION HOST' "${dst_ip}" "${dst_gateway}" "${dst_gateway_arp}" "" 570 | fi 571 | else 572 | # Second model family 573 | draw_2blocks 'SOURCE HOST' 'DESTINATION HOST' 574 | if [ "${src_gateway}" = "${dst_gateway}" ]; then 575 | draw_lan_line 576 | eval "printf '%-$((box_hw + 1))s|\n'" 577 | draw_block src 'ROUTER' "${src_gateway}" direct "${src_gateway_arp}" "" 578 | else 579 | if [ "${src_gateway}" != "direct" -a "${dst_gateway}" = "direct" ]; then 580 | draw_block src 'ROUTER TOWARDS SOURCE' "${src_gateway}" direct "${src_gateway_arp}" "right" 581 | elif [ "${dst_gateway}" != "direct" -a "${src_gateway}" = "direct" ]; then 582 | draw_block dst 'ROUTER TOWARDS DESTINATION' "${dst_gateway}" direct "${dst_gateway_arp}" "left" 583 | else 584 | draw_2blocks 'ROUTER TOWARDS SOURCE' 'ROUTER TOWARDS DESTINATION' 585 | fi 586 | draw_lan_line 587 | eval "printf '%-$((box_hw + 5))s|\n'" 588 | fi 589 | draw_me 590 | fi 591 | --------------------------------------------------------------------------------