From 3908f3e29699be9b28b3dc54c66eef429adddd61 Mon Sep 17 00:00:00 2001 From: Maxim Likhachev Date: Sat, 12 Jun 2021 23:22:15 +0300 Subject: [PATCH] ipcalc: DIY version --- README.md | 1 + scripts/ipcalc | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100755 scripts/ipcalc diff --git a/README.md b/README.md index 03c4dbb..7dfcce1 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ will create the symbolic link for each script in the \ directory. ## Network - [check-cert](scripts/check-cert) retrieves information about web site's TLS certificate. +- [ipcalc](scripts/ipcalc) is DIY tcl version of [jodies.de/ipcalc](https://jodies.de/ipcalc), it takes an IP address and netmask and calculates the resulting broadcast, network, Cisco wildcard mask, and host range. ## WWW diff --git a/scripts/ipcalc b/scripts/ipcalc new file mode 100755 index 0000000..c2f468c --- /dev/null +++ b/scripts/ipcalc @@ -0,0 +1,278 @@ +#!/usr/bin/env tclsh + +# Copyright (C) 2021, Maxim Lihachev, +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published +# by the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ------------------------------------------------------------------------------ +# +# DESCRIPTION +# +# DIY project that implement basic `ipcalc' functions for IP Address +# calculating. +# +# +# USAGE +# +# ipcalc ip/mask +# e.g. ipcalc 192.168.0.1/24 +# ipcalc 192.168.0.1/255.255.255.0 +# +# ------------------------------------------------------------------------------ + + +# Convert integer to binary +proc int2bin {int} { + binary scan [binary format c $int] B* bin + return $bin +} + +# Convert binary to integer +proc bin2int {bin} { + expr {"0b$bin"} +} + +# Convert binary form of IP address to decimal one +proc binary2decimal {binary_ip} { + join [lmap x [split_binary_ip $binary_ip] {bin2int $x}] {.} +} + +# Convert decimal form of IP addrecc to binary one +proc decimal2binary {decimal_ip} { + return [join [lmap x [split $decimal_ip {.}] {int2bin $x}] {.}] +} + +# Normalize binary form of IP address +# E.g.: +# 11111111.11111111.11111111.00000000 => 11111111111111111111111100000000 +# 11111111 11111111 11111111 00000000 => 11111111111111111111111100000000 +proc normalize_binary_ip {ip} { + string map {{.} {} { } {}} $ip +} + +# Split normal form of binary IP address to chunks +# E.g. +# 11111111111111111111111100000000 => 11111111 11111111 11111111 00000000 +proc split_binary_ip {ip} { + regexp -all -inline .{8} [normalize_binary_ip $ip] +} + +# Make a dotted form of binary IP address +# E.g. +# 11111111111111111111111100000000 => 11111111.11111111.11111111.00000000 +proc format_binary_ip {binary_ip} { + join [split_binary_ip $binary_ip] {.} +} + +# Zip two lists with function +proc zipwith {list1 list2 fx} { + set l1 [regexp -all -inline {.} $list1] + set l2 [regexp -all -inline {.} $list2] + + lmap x $l1 y $l2 {apply $fx $x $y} +} + +# Bitwise AND digit by digit +# E.g.: +# 11011011 +# 11011000 +# |||||||| +# 11011000 +proc bitwise_and {ip1 ip2} { + join [zipwith $ip1 $ip2 {{x y} {expr {$x & $y}}}] {} +} + +# Bitwise OR digit by digit +# E.g.: +# 11011011 +# 11011000 +# |||||||| +# 11011011 +proc bitwise_or {ip1 ip2} { + join [zipwith $ip1 $ip2 {{x y} {expr {$x | $y}}}] {} +} + +# Print data in default ipcalc format +proc show {label {decimal {}} {binary {}}} { + puts [format "%-11s%-21s%s" $label $decimal $binary] +} + +# ------------------------------------------------------------------------------ + +# Get network class +proc network_class {ip} { + set first_octet [lindex [split $ip "."] 0] + + if {$first_octet <= 127} { + set network_class "Class A" + } elseif {$first_octet <= 191} { + set network_class "Class B" + } elseif {$first_octet <= 223} { + set network_class "Class C" + } elseif {$first_octet <= 239} { + set network_class "Class D" + } elseif {$first_octet <= 247} { + set network_class "Class E" + } +} + +# Calculate wildcard +proc wildcard {ip netmask} { + set netmask [integer_netmask $netmask] + set binary_wildcard [format_binary_ip [string repeat 0 $netmask][string repeat 1 [expr {32-$netmask}]]] + set decimal_wildcard [binary2decimal $binary_wildcard] + + return [list $decimal_wildcard $binary_wildcard] +} + +# Get count of available hosts +proc hosts_count {netmask} { + set netmask [integer_netmask $netmask] + expr {2 ** (32 - $netmask) - ($netmask >= 30 ? 0 : 2)} +} + +# 190.0.0.1 10111110.00000000.00000000.00000001 +# 255.255.240.0 11111111.11111111.11110000.00000000 +# &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +# 190.0.0.0 10111110.00000000.00000000.00000000 +proc network {ip netmask} { + set binary_address [normalize_binary_ip [decimal2binary $ip]] + set binary_netmask [normalize_binary_ip [binary_netmask $netmask]] + set binary_network [format_binary_ip [bitwise_and $binary_address $binary_netmask]] + set network [binary2decimal $binary_network] + + return [list "${network}/[integer_netmask $netmask]" $binary_network] +} + +# 255.255.240.0 11111111.11111111.11110000.00000000 +# bit mask 00000000.00000000.00001111.11111111 +# 190.0.0.1 10111110.00000000.00000000.00000001 +# ||||||||||||||||||||||||||||||||||| +# 190.0.15.255 10111110.00000000.00001111.11111111 +proc host_broadcast {ip netmask} { + set binary_address [normalize_binary_ip [decimal2binary $ip]] + set binary_netmask [normalize_binary_ip [binary_netmask $netmask]] + + # 1 => 0, 0 => 1 + set binary_bitmask [string map {1 0 0 1} $binary_netmask] + + set binary_broadcast [format_binary_ip [bitwise_or $binary_address $binary_bitmask]] + set host_broadcast [binary2decimal $binary_broadcast] + + return [list $host_broadcast $binary_broadcast] +} + +# broadcast-1 +# +# 190.0.0.1 10111110.00000000.00000000.00000001 +# 255.255.240.0 11111111.11111111.11110000.00000000 +# 190.0.15.255 10111110.00000000.00001111.11111111 +# +# 190.0.15.255-1 10111110.00000000.00001111.11111111-1 +# 190.0.15.254 10111110.00000000.00001111.11111110 +proc host_max {ip netmask} { + set binary_broadcast [lindex [host_broadcast $ip $netmask] 1] + set binary_max_host [format_binary_ip [expr {[normalize_binary_ip [lindex [host_broadcast $ip $netmask] 1]]-1}]] + set max_host [binary2decimal $binary_max_host] + + return [list $max_host $binary_max_host] +} + +# network+1 +# +# 190.0.0.1 10111110.00000000.00000000.00000001 +# 255.255.240.0 11111111.11111111.11110000.00000000 +# 190.0.0.0 10111110.00000000.00000000.00000000 +# +# 190.0.0.0+1 10111110.00000000.00000000.00000000 +# 190.0.0.1 10111110.00000000.00000000.00000001 +proc host_min {ip netmask} { + set network [lindex [network $ip $netmask] 1] + + set min_host [expr {[normalize_binary_ip $network]+1}] + set binary_min_host [format_binary_ip $min_host] + set decimal_min_host [binary2decimal $min_host] + + return [list $decimal_min_host $binary_min_host] +} + +proc decimal_netmask {netmask} { + lindex $netmask 0 +} + +proc integer_netmask {netmask} { + lindex $netmask 1 +} + +proc binary_netmask {netmask} { + lindex $netmask 2 +} + +# Calculate netmask in decimal, binary and integer formats +proc netmask {mask} { + proc make_binary_netmask {netmask} { + format_binary_ip [string repeat 1 $netmask][string repeat 0 [expr {32-$netmask}]] + } + + proc make_integer_netmask {netmask} { + expr {[llength [split [decimal2binary $netmask] {1}]]-1} + } + + if {[string is integer -strict $mask]} { + set integer_netmask $mask + set binary_netmask [make_binary_netmask $mask] + set decimal_netmask [binary2decimal $binary_netmask] + } else { + set integer_netmask [make_integer_netmask $mask] + set binary_netmask [make_binary_netmask $integer_netmask] + set decimal_netmask $mask + } + + return [list $decimal_netmask $integer_netmask $binary_netmask] +} + +# ------------------------------------------------------------------------------ + +set script_name [file tail $argv0] + +if {$argc < 1} { + puts "USAGE: $script_name ip/mask" + puts " e.g. $script_name 192.168.0.1/24" + puts " $script_name 192.168.0.1/255.255.255.0" + exit 1 +} + +# ------------------------------------------------------------------------------ + +lassign [split [string map {{ } {/}} $argv] {/}] address mask + +if {$mask != {}} { + set netmask [netmask $mask] +} else { + set netmask [netmask 24] +} + +# ------------------------------------------------------------------------------ + +# Default `ipcalc' output format +show Address: $address [decimal2binary $address] +show Netmask: "[decimal_netmask $netmask] = [integer_netmask $netmask]" [binary_netmask $netmask] +show Wildcard: {*}[wildcard $address $netmask] +show => +show Network: {*}[network $address $netmask] +show HostMin: {*}[host_min $address $netmask] +show HostMax: {*}[host_max $address $netmask] +show Broadcast: {*}[host_broadcast $address $netmask] +show Host/Net: [hosts_count $netmask] [network_class $address] +show {} +