]> git.ipfire.org Git - people/stevee/network.git/commitdiff
Replace ipcalc by inetcalc
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 18 Sep 2015 20:37:05 +0000 (22:37 +0200)
committerStefan Schantl <stefan.schantl@ipfire.org>
Fri, 18 Sep 2015 20:37:05 +0000 (22:37 +0200)
This is our own implementation of a binary tool that is used
to validate and calculate with IP addresses.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Reviewed-by: Stefan Schantl <stefan.schantl@ipfire.org>
14 files changed:
.gitignore
Makefile.am
src/dhclient-script
src/functions/functions.device
src/functions/functions.dhcpd
src/functions/functions.ip
src/functions/functions.ip-tunnel
src/functions/functions.ipv4
src/functions/functions.ipv6
src/functions/functions.radvd
src/functions/functions.routing
src/hooks/configs/ipv6-static
src/hooks/zones/6rd
src/inetcalc.c [new file with mode: 0644]

index d522de3925f1772eb9ac646f608caab2e330228d..ad5e18f0fe34162a1eacd6b7d9bce7d19d6585a1 100644 (file)
@@ -2,6 +2,7 @@
 /build-aux
 /missing
 /src/functions/functions
+/src/inetcalc
 /src/network.pc
 /src/ppp/ip-updown
 /src/systemd/*.service
 /*.tar.xz
 *.log
 *.cache
+*.o
 *.stamp
 *.trs
 *~
+.deps
+.dirstamp
 Makefile.in
 aclocal.m4
 config.status
index 7c780ac1f865f9cbcbb69353c0a40fe08ecc6fdb..bd81906a484d7a57d9cf164ba22ab3a4cf159e3c 100644 (file)
@@ -177,6 +177,12 @@ dist_helpers_SCRIPTS = \
        src/helpers/wpa_supplicant \
        src/helpers/wpa_supplicant-config-helper
 
+bin_PROGRAMS = \
+       src/inetcalc
+
+src_inetcalc_SOURCES = \
+       src/inetcalc.c
+
 bridge-stp-install-hook: bridge-stp-uninstall-hook
        ln -svf --relative $(DESTDIR)$(helpersdir)/bridge-stp $(DESTDIR)$(sbindir)/
 
index 9d18dba27010fa920ad5a3154a9846ee6d8d5df4..73dfbfaa9ccf1dea5925af2ccae4b3f98ed90057 100644 (file)
@@ -218,7 +218,7 @@ case "${reason}" in
 
 
                                        # Calc a prefix out of address and subnet mask.
-                                       new_prefix="$(ipv4_get_prefix ${new_ip_address} ${new_subnet_mask})"
+                                       new_prefix="$(ipv4_calculate_prefix ${new_ip_address} ${new_subnet_mask})"
 
                                        # Set the new ip address.
                                        ip_address_add ${interface} ${new_ip_address}/${new_prefix}
index d5ff381d4ce6dcf514f6807ae1e0093ccdc098eb..a851b669d50e864f358549e3620ce8bd108ce59c 100644 (file)
@@ -705,7 +705,7 @@ device_has_ip() {
        local protocol=$(ip_detect_protocol ${addr})
        case "${protocol}" in
                ipv6)
-                       addr=$(ipv6_implode ${addr})
+                       addr=$(ipv6_format "${addr}")
                        ;;
        esac
 
index b1f876742e45533791bacbf5a8df5f98292e8d3f..823fca4747bc20c56b570a1ca47198d24da392e5 100644 (file)
@@ -1029,7 +1029,7 @@ _dhcpd_write_subnet() {
                        print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
                        ;;
                ipv4)
-                       local netmask=$(ipv4_get_netmask ${ADDRESS}/${PREFIX})
+                       local netmask="$(ipv4_prefix2netmask "${PREFIX}")"
                        print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file}
                        ;;
        esac
index 208488ff6331690497bcf7f7d0c7e5735005c947..9572141bafbf7b950289daa083661b7017fc6f3d 100644 (file)
@@ -40,13 +40,13 @@ ip_get_prefix() {
 }
 
 ip_detect_protocol() {
-       local address=${1}
+       local address="${1}"
 
        assert isset address
 
        local protocol
        for protocol in ${IP_SUPPORTED_PROTOCOLS}; do
-               if ${protocol}_is_valid ${address}; then
+               if ${protocol}_is_valid "${address}"; then
                        echo "${protocol}"
                        return ${EXIT_OK}
                fi
@@ -113,6 +113,10 @@ ip_prefix_is_valid() {
        assert ip_protocol_is_supported ${proto}
 }
 
+ip_get_network() {
+       inetcalc -n $@ && return ${EXIT_OK} || return ${EXIT_ERROR}
+}
+
 ip_address_add() {
        local device=${1}
        local address=${2}
@@ -124,10 +128,22 @@ ip_address_add() {
        address=$(ip_split_prefix ${address})
 
        assert isset prefix
+       assert isset address
+
+       echo "ADDRESS = $address"
 
        # Detect the protocol version
-       local protocol=$(ip_detect_protocol ${address}/${prefix})
-       assert ip_protocol_is_supported ${protocol}
+       local protocol=$(ip_detect_protocol "${address}")
+       assert ip_protocol_is_supported "${protocol}"
+
+       case "${protocol}" in
+               ipv6)
+                       assert ipv6_prefix_is_valid "${prefix}"
+                       ;;
+               ipv4)
+                       assert ipv4_prefix_is_valid "${prefix}"
+                       ;;
+       esac
 
        case "${protocol}" in
                ipv4)
@@ -170,8 +186,8 @@ ip_address_del() {
        assert isset prefix
 
        # Detect the protocol version
-       local protocol=$(ip_detect_protocol ${address}/${prefix})
-       assert ip_protocol_is_supported ${protocol}
+       local protocol=$(ip_detect_protocol "${address}")
+       assert ip_protocol_is_supported "${protocol}"
 
        if device_has_ip ${device} ${address}/${prefix}; then
                assert ip addr del ${address}/${prefix} dev ${device}
index 36881f2824b5b713592f75b92d7214d8a6787b76..8a1e2ee467d6f0a211e481b751a128531b2bd6dc 100644 (file)
@@ -99,7 +99,7 @@ ip_tunnel_6rd_set_prefix() {
        assert isset prefix
 
        # Validate the prefix.
-       assert ipv6_is_valid "${prefix}"
+       assert ipv6_net_is_valid "${prefix}"
 
        log INFO "Setting 6rd-prefix ${prefix} on ${device}"
 
index d22b25f6c00ddddd38abec9dfe3c8b73282126ac..799fe0099347f80ccf6ffcf9f8bc5dd0bb391bea 100644 (file)
 IP_SUPPORTED_PROTOCOLS="${IP_SUPPORTED_PROTOCOLS} ipv4"
 
 ipv4_is_valid() {
-       ipcalc --ipv4 -c $@ >/dev/null 2>&1
-
-       case "$?" in
-               0)
-                       return ${EXIT_OK}
-                       ;;
-               *)
-                       return ${EXIT_ERROR}
-                       ;;
-       esac
+       inetcalc -4 -c $@ && return ${EXIT_OK} || return ${EXIT_ERROR}
 }
 
 ipv4_prefix_is_valid() {
@@ -84,56 +75,13 @@ ipv4_update_neighbours() {
        ( sleep 2; arping -q -U -c 1 -I ${device} ${address} ) >/dev/null 2>&1 </dev/null &
 }
 
-ipv4_get_netaddress() {
-       local address=${1}
-       assert isset address
-
-       local prefix=$(ip_get_prefix ${address})
-       isset prefix || prefix="32"
-
-       # Assume host-only address if no prefix has been given.
-       if [ "${prefix}" = "32" ]; then
-               echo "${address}/${prefix}"
-               return ${EXIT_OK}
-       fi
-
-       local NETWORK
-       eval $(ipcalc --network ${address})
-       assert isset NETWORK
-
-       echo "${NETWORK}/${prefix}"
-       return ${EXIT_OK}
-}
-
-ipv4_get_prefix() {
-       local address=${1}
-       local broadcast=${2}
+ipv4_calculate_prefix() {
+       assert [ $# -eq 2 ]
 
-       assert isset address
-       assert isset broadcast
-
-       local PREFIX
-       eval $(ipcalc --prefix ${address} ${broadcast})
-       assert isset PREFIX
+       local address="${1}"
+       local broadcast="${2}"
 
-       echo "${PREFIX}"
-       return ${EXIT_OK}
-}
-
-ipv4_get_netmask() {
-       local address=${1}
-       assert isset address
-
-       # Add prefix if none given.
-       local prefix=$(ip_get_prefix ${address})
-       isset prefix || address="${address}/32"
-
-       local NETMASK
-       eval $(ipcalc --netmask ${address})
-       assert isset NETMASK
-
-       print "${NETMASK}"
-       return ${EXIT_OK}
+       inetcalc -4 -p "${address}" "${broadcast}"
 }
 
 ipv4_flush_device() {
@@ -153,21 +101,114 @@ ipv4_flush_device() {
 }
 
 ipv4_prefix2netmask() {
-       local prefix=${1}
-       shift
-
-       assert isinteger prefix
-
-       # XXX this function is a stub
+       local prefix="${1}"
 
        case "${prefix}" in
+               32)
+                       echo "255.255.255.255"
+                       ;;
+               31)
+                       echo "255.255.255.254"
+                       ;;
+               30)
+                       echo "255.255.255.252"
+                       ;;
+               29)
+                       echo "255.255.255.248"
+                       ;;
+               28)
+                       echo "255.255.255.240"
+                       ;;
+               27)
+                       echo "255.255.255.224"
+                       ;;
+               26)
+                       echo "255.255.255.192"
+                       ;;
+               25)
+                       echo "255.255.255.128"
+                       ;;
                24)
                        echo "255.255.255.0"
                        ;;
+               23)
+                       echo "255.255.254.0"
+                       ;;
+               22)
+                       echo "255.255.252.0"
+                       ;;
+               21)
+                       echo "255.255.248.0"
+                       ;;
+               20)
+                       echo "255.255.240.0"
+                       ;;
+               19)
+                       echo "255.255.224.0"
+                       ;;
+               18)
+                       echo "255.255.192.0"
+                       ;;
+               17)
+                       echo "255.255.128.0"
+                       ;;
+               16)
+                       echo "255.255.0.0"
+                       ;;
+               15)
+                       echo "255.254.0.0"
+                       ;;
+               14)
+                       echo "255.252.0.0"
+                       ;;
+               13)
+                       echo "255.248.0.0"
+                       ;;
+               12)
+                       echo "255.240.0.0"
+                       ;;
+               11)
+                       echo "255.224.0.0"
+                       ;;
+               10)
+                       echo "255.192.0.0"
+                       ;;
+               9)
+                       echo "255.128.0.0"
+                       ;;
+               8)
+                       echo "255.0.0.0"
+                       ;;
+               7)
+                       echo "254.0.0.0"
+                       ;;
+               6)
+                       echo "252.0.0.0"
+                       ;;
+               5)
+                       echo "248.0.0.0"
+                       ;;
+               4)
+                       echo "240.0.0.0"
+                       ;;
+               3)
+                       echo "224.0.0.0"
+                       ;;
+               2)
+                       echo "192.0.0.0"
+                       ;;
+               1)
+                       echo "128.0.0.0"
+                       ;;
+               0)
+                       echo "0.0.0.0"
+                       ;;
                *)
-                       assert false NOT IMPLEMENTED
+                       return ${EXIT_ERROR}
                        ;;
        esac
+
+       return ${EXIT_OK}
 }
 
 ipv4_netmask2prefix() {
@@ -195,55 +236,11 @@ ipv4_netmask2prefix() {
 }
 
 ipv4_get_network() {
-       local network=$(ipv4_get_network_encoded $@)
-
-       ipv4_decode ${network}
-}
-
-ipv4_get_network_encoded() {
-       local net=${1}
-
-       local prefix=$(ip_get_prefix ${net})
-       isset prefix || prefix=32
-
-       local mask=0
-       if [ ${prefix} -ne 0 ]; then
-               mask=$(( -1 << $(( 32 - ${prefix} )) ))
-       fi
-
-       local addr=$(ip_split_prefix ${net})
-       addr=$(ipv4_encode ${addr})
-
-       print "%d" $(( ${addr} & ${mask} ))
+       ip_get_network $@
 }
 
 ipv4_get_broadcast() {
-       local broadcast=$(ipv4_get_broadcast_encoded $@)
-
-       ipv4_decode ${broadcast}
-}
-
-ipv4_get_broadcast_encoded() {
-       local net=${1}
-
-       local prefix=$(ip_get_prefix ${net})
-       assert isset prefix
-
-       prefix=$(( 32 - ${prefix} ))
-
-       local netmask=0
-       local broadcast=-1
-       if [ ${prefix} -eq 32 ]; then
-               :
-       else
-               netmask=$(( -1 << ${prefix} ))
-               broadcast=$(( $(( 1 << ${prefix} )) - 1))
-       fi
-
-       local addr=$(ip_split_prefix ${net})
-       addr=$(ipv4_encode ${addr})
-
-       print "%d" $(( $(( ${addr} & ${netmask} )) | ${broadcast} ))
+       inetcalc -4 -b $@ && return ${EXIT_OK} || return ${EXIT_ERROR}
 }
 
 ipv4_encode() {
@@ -353,7 +350,9 @@ ipv4_range_explicit() {
                        ;;
                *.*.*.*/*)
                        first=$(ipv4_get_network ${range})
+                       first="$(ip_split_prefix "${range}")"
                        last=$(ipv4_get_broadcast ${range})
+                       last="$(ip_split_prefix "${range}")"
                        ;;
        esac
 
index 9026cd88c866ec6ceb07de3aed782f8d71edae32..9aedf84717bae313971c1bd883c47d3ad9d2ced5 100644 (file)
@@ -105,16 +105,16 @@ ipv6_device_privacy_extensions_disable() {
 }
 
 ipv6_is_valid() {
-       ipcalc --ipv6 -c $@ >/dev/null 2>&1
-
-       case "$?" in
-               0)
-                       return ${EXIT_OK}
-                       ;;
-               *)
-                       return ${EXIT_ERROR}
-                       ;;
-       esac
+       inetcalc -6 -c $@ && return ${EXIT_OK} || return ${EXIT_ERROR}
+}
+
+ipv6_net_is_valid() {
+       local net="${1}"
+
+       local prefix="$(ip_get_prefix "${net}")"
+       local addr="$(ip_split_prefix "${net}")"
+
+       ipv6_prefix_is_valid "${prefix}" && ipv6_is_valid "${addr}"
 }
 
 ipv6_prefix_is_valid() {
@@ -394,105 +394,45 @@ ipv6_device_get_addresses() {
        list_sort ${addresses}
 }
 
-ipv6_implode() {
-       local address=${1}
-       assert isset address
-
-       local ADDRESS6_IMPL
-       eval $(ipcalc -6 -i ${address} 2>/dev/null)
-       assert isset ADDRESS6_IMPL
-
-       print "${ADDRESS6_IMPL}"
-}
-
-ipv6_explode() {
-       local address=${1}
-       assert isset address
-
-       # Nothing to do if the length of the address is 39.
-       if [ ${#address} -eq 39 ]; then
-               print "${address}"
-               return ${EXIT_OK}
-       fi
-
-       local ADDRESS6_EXPL
-       eval $(ipcalc -6 -e ${address} 2>/dev/null)
-       assert isset ADDRESS6_EXPL
-
-       print "${ADDRESS6_EXPL}"
+ipv6_format() {
+       inetcalc -6 -f $@
 }
 
 ipv6_addr_eq() {
-       local addr1=${1}
-       assert isset addr1
+       assert [[ $# -eq 2 ]]
 
-       local addr2=${2}
-       assert isset addr2
-
-       local addr
-       for addr in addr1 addr2; do
-               printf -v ${addr} "%s" $(ipv6_explode ${!addr})
-       done
+       local addr1="${1}"
+       local addr2="${2}"
 
-       [[ "${addr1}" = "${addr2}" ]] \
+       inetcalc -6 -e "${addr1}" "${addr2}" \
                && return ${EXIT_TRUE} || return ${EXIT_FALSE}
 }
 
 ipv6_addr_gt() {
-       local addr1=${1}
-       assert isset addr1
+       assert [[ $# -eq 2 ]]
 
-       local addr2=${2}
-       assert isset addr2
+       local addr1="${1}"
+       local addr2="${2}"
 
-       local addr
-       for addr in addr1 addr2; do
-               printf -v ${addr} "%s" $(ipv6_explode ${!addr})
-
-               # Remove all colons
-               printf -v ${addr} "${!addr//:/}"
-       done
-
-       local i addr1_oct addr2_oct
-       for i in 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30; do
-               addr1_oct="0x${addr1:${i}:2}"
-               addr2_oct="0x${addr2:${i}:2}"
-
-               [[ ${addr1_oct} -gt ${addr2_oct} ]] && return ${EXIT_TRUE}
-       done
-
-       return ${EXIT_FALSE}
+       inetcalc -6 -g "${addr1}" "${addr2}" \
+               && return ${EXIT_TRUE} || return ${EXIT_FALSE}
 }
 
 ipv6_hash() {
-       local address=${1}
-
+       local address="${1}"
        assert isset address
 
-       # Explode address
-       address=$(ipv6_explode ${address})
-
+       address="$(ipv6_format "${address}")"
        echo "${address//:/}"
 }
 
 ipv6_get_network() {
-       local addr=${1}
-       assert isset addr
-
-       # Check if a prefix (e.g. /64) is provided.
-       local prefix=$(ip_get_prefix ${addr})
-       assert ipv6_prefix_is_valid ${prefix}
-
-       local PREFIX6
-       eval $(ipcalc --ipv6 -p ${addr})
-       assert isset PREFIX6
-
-       print "${PREFIX6}/${prefix}"
+       ip_get_network $@
 }
 
 ipv6_6rd_format_address() {
        local isp_prefix="${1}"
-       assert ipv6_is_valid "${isp_prefix}"
+       assert ipv6_net_is_valid "${isp_prefix}"
 
        local client_address="${2}"
        assert ipv4_is_valid "${client_address}"
@@ -505,7 +445,15 @@ ipv6_6rd_format_address() {
        assert [ "${prefix}" -gt  0 ]
 
        # Explode the address and throw away the second 32 bit.
-       local address="$(ipv6_explode "${isp_prefix}")"
+       local address
+       local segment
+       for segment in ${isp_prefix//:/ }; do
+               while [[ ${#segment} -lt 4 ]]; do
+                       segment="0${segment}"
+               done
+               list_append address "${segment}"
+       done
+       address="$(list_join ":" ${address})"
 
        client_address="$(ipv6_6rd_format_client_address ${client_address})"
        assert isset client_address
@@ -551,7 +499,7 @@ ipv6_6rd_format_address() {
        assert ipv6_is_valid "${formatted_address}"
 
        # Implode the output IP address.
-       formatted_address="$(ipv6_implode "${formatted_address}")"
+       formatted_address="$(ipv6_format "${formatted_address}")"
 
        print "${formatted_address}/${prefix}"
 }
index 2079554ae62b5837d0fd97f436c29b33f22907a0..a2daec35737ccb60c0c756a6f016043a5ec86b71 100644 (file)
@@ -69,11 +69,11 @@ __radvd_config_interface() {
        if [ -z "${addr}" ] || [ "${addr:0:5}" = "fe80:" ]; then
                return ${EXIT_OK}
        fi
-       local prefix=$(ipv6_get_network ${addr})
 
        # Check if the subnet is configured by the DHCP server.
        local dhcpd="false"
-       if dhcpd_subnet_match ipv6 "${prefix}"; then
+       local prefix="$(ipv6_get_network "${addr}")"
+       if isset prefix && dhcpd_subnet_match ipv6 "${prefix}"; then
                dhcpd="true"
        fi
 
index b7b0cc97973335a8a0005756faef231f849841fa..c7aac094f11cdca2c4d88abc8a65df4816666425 100644 (file)
@@ -164,10 +164,10 @@ routing_update() {
 
        case "${proto}" in
                ipv4)
-                       local net_address=$(ipv4_get_netaddress ${local_ip_address})
+                       local network=$(ipv4_get_network "${local_ip_address}")
 
                        log DEBUG "Adding route for subnet ${local_ip_address} to table ${table}"
-                       cmd ${ip_cmd} route add table ${table} ${net_address} dev ${zone}
+                       cmd ${ip_cmd} route add table "${table}" "${network}" dev "${zone}"
                        ;;
        esac
 
index f0d07e4a7639aa20cc6947b80293ab2e517828c2..2a5e8e2c022fe8afa82f3bf953766343203219ad 100644 (file)
@@ -53,10 +53,10 @@ hook_new() {
        done
 
        # Store IPv6 address in small format.
-       ADDRESS=$(ipv6_implode ${ADDRESS})
+       ADDRESS=$(ipv6_format "${ADDRESS}")
 
        if [ -n "${GATEWAY}" ]; then
-               GATEWAY=$(ipv6_implode ${GATEWAY})
+               GATEWAY=$(ipv6_format "${GATEWAY}")
        fi
 
        zone_config_settings_write "${zone}" "${HOOK}.$(ipv6_hash ${ADDRESS}).${PREFIX}"
@@ -123,7 +123,7 @@ hook_status() {
        zone_config_settings_read "${zone}" "${config}"
 
        # Make sure ADDRESS is as short as possible.
-       ADDRESS=$(ipv6_implode ${ADDRESS})
+       ADDRESS=$(ipv6_format "${ADDRESS}")
 
        local status
        if zone_has_ip ${zone} ${ADDRESS}/${PREFIX}; then
index c8509f3a869eb5002ee57d62c5d9d12260506ef8..a277674c36ca4a8e1f74a571c13494780edfb5ca 100644 (file)
@@ -49,7 +49,7 @@ hook_check_settings() {
        assert isset LOCAL_ADDRESS
 
        # Check input.
-       if ! ipv6_is_valid "${SIX_RD_PREFIX}"; then
+       if ! ipv6_net_is_valid "${SIX_RD_PREFIX}"; then
                log ERROR "Invalid 6rd prefix. Please use a valid IPv6 prefix."
                return ${EXIT_ERROR}
        fi
diff --git a/src/inetcalc.c b/src/inetcalc.c
new file mode 100644 (file)
index 0000000..f821d6e
--- /dev/null
@@ -0,0 +1,548 @@
+/*#############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2015 IPFire Network Development Team                          #
+#                                                                             #
+# 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, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# 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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <assert.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+typedef struct ip_address {
+       int family;
+       __uint128_t addr;
+       int prefix;
+} ip_address_t;
+
+static __uint128_t prefix_to_bitmask(int prefix) {
+       __uint128_t bitmask = ~0;
+
+       for (int i = 0; i < 128 - prefix; i++)
+               bitmask >>= 1;
+
+       return bitmask; 
+}
+
+static int bitmask_to_prefix(uint32_t bits) {
+       int prefix = 0;
+
+       // Count all ones until we find the first zero
+       while (bits & (1 << 31)) {
+               bits <<= 1;
+               prefix++;
+       }
+
+       // The remaining bits must all be zero
+       if (bits)
+               return -1;
+
+       return prefix;
+}
+
+static int ip_address_parse_subnet_mask(ip_address_t* ip, const char* prefix) {
+       struct in_addr mask;
+
+       int r = inet_pton(AF_INET, prefix, &mask.s_addr);
+       if (r != 1)
+               return 1;
+
+       uint32_t bits = ntohl(mask.s_addr);
+       ip->prefix = bitmask_to_prefix(bits);
+
+       return (ip->prefix < 0 || ip->prefix > 32);
+}
+
+static int ip_address_parse_prefix_cidr(ip_address_t* ip, const int family, const char* prefix) {
+       ip->prefix = 0;
+       while (*prefix) {
+               char p = *prefix++;
+
+               if (p >= '0' && p <= '9') {
+                       ip->prefix *= 10;
+                       ip->prefix += p - '0';
+               } else {
+                       return 1;
+               }
+       }
+
+       switch (family) {
+               case AF_INET6:
+                       return (ip->prefix < 0 || ip->prefix > 128);
+
+               case AF_INET:
+                       return (ip->prefix < 0 || ip->prefix > 32);
+
+               default:
+                       return 1;
+       }
+}
+
+static int ip_address_parse_prefix(ip_address_t* ip, const int family, const char* prefix) {
+       int r = ip_address_parse_prefix_cidr(ip, family, prefix);
+
+       if (r && family == AF_INET) {
+               r = ip_address_parse_subnet_mask(ip, prefix);
+       }
+
+       return r;
+}
+
+static int ip_address_parse_simple(ip_address_t* ip, const int family, const char* address) {
+       assert(family == AF_INET || family == AF_INET6);
+
+       size_t address_length = strlen(address);
+       char buffer[address_length + 1];
+       strncpy(buffer, address, sizeof(buffer));
+
+       // Search for a prefix or subnet mask
+       char* prefix = strchr(buffer, '/');
+       if (prefix) {
+               buffer[prefix - buffer] = '\0';
+               prefix++;
+       }
+
+       memset(&ip->addr, 0, sizeof(ip->addr));
+       int r = inet_pton(family, buffer, &ip->addr);
+
+       switch (r) {
+               // If parsing the IP address failed, we will return false
+               case 0:
+                       return 1;
+
+               // If the IP address could be successfully parsed, we will
+               // save the address family and return true
+               case 1:
+                       ip->family = family;
+                       r = 0;
+                       break;
+
+               default:
+                       return r;
+       }
+
+       if (prefix)
+               r = ip_address_parse_prefix(ip, family, prefix);
+       else
+               ip->prefix = -1;
+
+       return r;
+}
+
+static int ip_address_parse(ip_address_t* ip, const int family, const char* address) {
+       static int families[] = { AF_INET, AF_INET6, 0 };
+
+       int r = 1;
+       int* f = families;
+       while (*f) {
+               if (family == AF_UNSPEC || family == *f) {
+                       r = ip_address_parse_simple(ip, *f, address);
+
+                       if (r == 0)
+                               break;
+               }
+
+               f++;
+       }
+
+       return r;
+}
+
+static int ip_address_eq(const ip_address_t* a1, const ip_address_t* a2) {
+       if (a1->family != a2->family)
+               return 1;
+
+       if (a1->addr != a2->addr)
+               return 1;
+
+       if (a1->prefix != a2->prefix)
+               return 1;
+
+       return 0;
+}
+
+static int ip_address_gt(const ip_address_t* a1, const ip_address_t* a2) {
+       if (a1->family != a2->family || a1->prefix != a2->prefix)
+               return -1;
+
+       if (a1->addr > a2->addr)
+               return 0;
+
+       return 1;
+}
+
+static int ip_address_format_string(char* buffer, size_t size, const ip_address_t* ip) {
+       assert(ip->family == AF_INET || ip->family == AF_INET6);
+
+       const char* p = inet_ntop(ip->family, &ip->addr, buffer, size);
+       if (!p)
+               return errno;
+
+       return 0;
+}
+
+static void ip_address_print(const ip_address_t* ip) {
+       char buffer[INET6_ADDRSTRLEN+4];
+
+       int r = ip_address_format_string(buffer, sizeof(buffer), ip);
+       if (r)
+               return;
+
+       if (ip->prefix >= 0) {
+               size_t len = strlen(buffer);
+               snprintf(buffer + len, sizeof(buffer) - len, "/%d", ip->prefix);
+       }
+
+       printf("%s\n", buffer);
+}
+
+static void ip_address_make_network(ip_address_t* net, const ip_address_t* ip) {
+       assert(ip->prefix >= 0);
+
+       __uint128_t mask = prefix_to_bitmask(ip->prefix);
+
+       net->family = ip->family;
+       net->prefix = ip->prefix;
+       net->addr = ip->addr & mask;
+}
+
+static void ip_address_make_broadcast(ip_address_t* broadcast, const ip_address_t* ip) {
+       assert(ip->family == AF_INET && ip->prefix >= 0);
+
+       __uint128_t mask = prefix_to_bitmask(ip->prefix);
+
+       broadcast->family = ip->family;
+       broadcast->prefix = ip->prefix;
+       broadcast->addr = ip->addr | ~mask;
+}
+
+static int action_check(const int family, const char* address) {
+       ip_address_t ip;
+
+       int r = ip_address_parse(&ip, family, address);
+       if (r)
+               return r;
+
+       // No prefix allowed
+       return (ip.prefix >= 0);
+}
+
+static int action_equal(const int family, const char* addr1, const char* addr2) {
+       ip_address_t a1;
+       ip_address_t a2;
+       int r;
+
+       r = ip_address_parse(&a1, family, addr1);
+       if (r)
+               return 2;
+
+       r = ip_address_parse(&a2, family, addr2);
+       if (r)
+               return 2;
+
+       return ip_address_eq(&a1, &a2);
+}
+
+static int action_greater(const int family, const char* addr1, const char* addr2) {
+       ip_address_t a1;
+       ip_address_t a2;
+       int r;
+
+       r = ip_address_parse(&a1, family, addr1);
+       if (r)
+               return 2;
+
+       r = ip_address_parse(&a2, family, addr2);
+       if (r)
+               return 2;
+
+       return ip_address_gt(&a1, &a2);
+}
+
+static int action_format(const int family, const char* address) {
+       ip_address_t ip;
+
+       int r = ip_address_parse(&ip, family, address);
+       if (r)
+               return r;
+
+       ip_address_print(&ip);
+       return 0;
+}
+
+static int action_broadcast(const int family, const char* address) {
+       ip_address_t ip;
+       int r = ip_address_parse(&ip, family, address);
+       if (r) {
+               fprintf(stderr, "Invalid IP address: %s\n", address);
+               return r;
+       }
+
+       if (ip.family != AF_INET) {
+               fprintf(stderr, "This is only possible for IPv4\n");
+               return 1;
+       }
+
+       ip_address_t broadcast;
+       ip_address_make_broadcast(&broadcast, &ip);
+
+       ip_address_print(&broadcast);
+       return 0;
+}
+
+static int action_network(const int family, const char* address) {
+       ip_address_t ip;
+
+       int r = ip_address_parse(&ip, family, address);
+       if (r) {
+               fprintf(stderr, "Invalid IP address: %s\n", address);
+               return r;
+       }
+
+       ip_address_t network;
+       ip_address_make_network(&network, &ip);
+
+       ip_address_print(&network);
+       return 0;
+}
+
+static int action_prefix(const int family, const char* addr1, const char* addr2) {
+       int r;
+
+       ip_address_t network;
+       r = ip_address_parse(&network, family, addr1);
+       if (r)
+               return r;
+
+       ip_address_t broadcast;
+       r = ip_address_parse(&broadcast, family, addr2);
+       if (r)
+               return r;
+
+       r = ip_address_gt(&broadcast, &network);
+       if (r)
+               return r;
+
+       uint32_t mask = ntohl(network.addr ^ broadcast.addr);
+       int prefix = bitmask_to_prefix(~mask);
+       if (prefix < 0)
+               return 1;
+
+       printf("%d\n", prefix);
+       return 0;
+}
+
+enum actions {
+       AC_UNSPEC = 0,
+       AC_BROADCAST,
+       AC_CHECK,
+       AC_EQUAL,
+       AC_FORMAT,
+       AC_GREATER,
+       AC_NETWORK,
+       AC_PREFIX,
+};
+
+static void set_action(int* action, int what) {
+       if (*action != AC_UNSPEC) {
+               printf("Another action has already been selected\n");
+               exit(1);
+       }
+
+       *action = what;         
+}
+
+static struct option long_options[] = {
+       {"broadcast",         no_argument,       0, 'b'},
+       {"check",             no_argument,       0, 'c'},
+       {"equal",             no_argument,       0, 'e'},
+       {"format",            no_argument,       0, 'f'},
+       {"greater",           no_argument,       0, 'g'},
+       {"ipv4-only",         no_argument,       0, '4'},
+       {"ipv6-only",         no_argument,       0, '6'},
+       {"network",           no_argument,       0, 'n'},
+       {"prefix",            no_argument,       0, 'p'},
+       {"verbose",           no_argument,       0, 'v'},
+       {0, 0, 0, 0}
+};
+
+int main(int argc, char** argv) {
+       int option_index = 0;
+       int required_arguments = 0;
+
+       int verbose = 0;
+       int action = AC_UNSPEC;
+       int family = AF_UNSPEC;
+
+       while (1) {
+               int c = getopt_long(argc, argv, "46bcefgnpv", long_options, &option_index);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+                       case 0:
+                               if (long_options[option_index].flag != 0)
+                                       break;
+
+                               printf("option: %s", long_options[option_index].name);
+                               if (optarg)
+                                       printf(" with arg %s", optarg);
+                               printf("\n");
+                               break;
+
+                       case '4':
+                               family = AF_INET;
+                               break;
+
+                       case '6':
+                               family = AF_INET6;
+                               break;
+
+                       case 'b':
+                               set_action(&action, AC_BROADCAST);
+                               required_arguments = 1;
+                               break;
+
+                       case 'c':
+                               set_action(&action, AC_CHECK);
+                               required_arguments = 1;
+                               break;
+
+                       case 'e':
+                               set_action(&action, AC_EQUAL);
+                               required_arguments = 2;
+                               break;
+
+                       case 'f':
+                               set_action(&action, AC_FORMAT);
+                               required_arguments = 1;
+                               break;
+
+                       case 'g':
+                               set_action(&action, AC_GREATER);
+                               required_arguments = 2;
+                               break;
+
+                       case 'n':
+                               set_action(&action, AC_NETWORK);
+                               required_arguments = 1;
+                               break;
+
+                       case 'p':
+                               set_action(&action, AC_PREFIX);
+                               required_arguments = 2;
+                               break;
+
+                       case 'v':
+                               verbose = 1;
+                               break;
+
+                       case '?':
+                               break;
+
+                       default:
+                               abort();
+               }
+       }
+
+       while (optind--) {
+               argc--;
+               argv++;
+       }
+
+       if (argc != required_arguments) {
+               fprintf(stderr, "Invalid number of arguments. Got %d, required %d.\n",
+                       argc, required_arguments);
+               return 1;
+       }
+
+       if (verbose && family != AF_UNSPEC)
+               printf("Address family = %d\n", family);
+
+       int r = 0;
+
+       switch (action) {
+               case AC_UNSPEC:
+                       printf("No action specified\n");
+                       r = 1;
+                       break;
+
+               case AC_BROADCAST:
+                       r = action_broadcast(family, argv[0]);
+                       break;
+
+               case AC_CHECK:
+                       r = action_check(family, argv[0]);
+
+                       if (verbose) {
+                               if (r == 0)
+                                       printf("%s is a valid IP address\n", argv[0]);
+                               else
+                                       printf("%s is not a valid IP address\n", argv[0]);
+                       }
+                       break;
+
+               case AC_EQUAL:
+                       r = action_equal(family, argv[0], argv[1]);
+
+                       if (verbose) {
+                               if (r == 0)
+                                       printf("%s equals %s\n", argv[0], argv[1]);
+                               else if (r == 2)
+                                       printf("Invalid IP address provided\n");
+                               else
+                                       printf("%s does not equal %s\n", argv[0], argv[1]); 
+                       }
+                       break;
+
+               case AC_FORMAT:
+                       r = action_format(family, argv[0]);
+
+                       if (verbose && r)
+                               printf("Invalid IP address given\n");
+
+                       break;
+
+               case AC_GREATER:
+                       r = action_greater(family, argv[0], argv[1]);
+
+                       if (verbose) {
+                               if (r == 0)
+                                       printf("%s is greater than %s\n", argv[0], argv[1]);
+                               else if (r == 2)
+                                       printf("Invalid IP address provided\n");
+                               else
+                                       printf("%s is not greater than %s\n", argv[0], argv[1]);
+                       }
+                       break;
+
+               case AC_NETWORK:
+                       r = action_network(family, argv[0]);
+                       break;
+
+               case AC_PREFIX:
+                       r = action_prefix(family, argv[0], argv[1]);
+                       break;
+       }
+
+       return r;
+}