From 12f27bb122e1df0d04582a3ecae0e49cfd45c5e5 Mon Sep 17 00:00:00 2001 From: Nils Freydank Date: Mon, 17 Feb 2025 13:43:03 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + README.md | 11 ++++++ compressIPv4.py | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 compressIPv4.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..45c71c8 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# compressIPv4 + +IPv4 addresses are typically written down as octets, e.g. `127.0.0.1`. +However the addresses seem to be actually interpreted as one large number (after conversion into binary). +This allows for funny things, like to call `http://0x7f000001:9000` to open a HTTP service on your local host, listenting on port 9000. + +This repo holds a shiny tool to do the converstion for you. +It's inspired by [a blog post from 2021](https://daniel.haxx.se/blog/2021/04/19/curl-those-funny-ipv4-addresses) by the [curl CEO](https://mastodon.social/@bagder/113979166039412014). + diff --git a/compressIPv4.py b/compressIPv4.py new file mode 100755 index 0000000..ec9b62b --- /dev/null +++ b/compressIPv4.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Nils Freydank , 2025 + +import traceback +from typing import Union + +__version__: str = "1.0.0" +__author__: str = "Nils Freydank " +__copyright__: str = "MIT" + + +def get_compressed_v4(addr_v4: str, hex_out: bool = False) -> Union[int, str]: + """ + Read the canonical IPv4 address and compress it into a single number. + + :param addr_v4: String containing the IPv4 address in dotted notation. + :param hex_out: Opt. control if the output should be hex instead of int. + :return: Integer with the compressed, decimal "IPv4 addr" - or hex string. + Return -1 or "-0x1" for invalid addresses. + + Note: Function may explode in funny ways due to missed input checks. + """ + LEN_BIN_BLOCK_MAX: int = 8 # i.e. len(str(bin(255)).replace("0b", "")) + MAX_ADDR_POSSIBLE: int = 2**32 - 1 # full 32bit, i.e. 255.255.255.255 + compressed_addr: str = "" + try: + blocks: list[str] = addr_v4.split(".") + bin_addr: str = "" + for block in blocks: + bin_block: str = str(bin(int(block))).replace("0b", "") + # Make sure to align each block properly to 8 bit. + len_delta: int = LEN_BIN_BLOCK_MAX - len(bin_block) + bin_addr = bin_addr + len_delta * "0" + bin_block + # Nice try. Put a valid IPv4 address inside, next time. + if (compressed_addr := int(bin_addr, 2)) > MAX_ADDR_POSSIBLE: + compressed_addr = -1 + + except ValueError: + print("Something broke, maybe an invalid address?") + traceback.print_exc() + + return hex(compressed_addr) if hex_out else compressed_addr + + +if __name__ == "__main__": + # First, run some checks. + assert get_compressed_v4("0.0.0.1") == 1 + assert get_compressed_v4("0.0.0.1", False) == 1 + assert get_compressed_v4("0.0.0.1", True) == "0x1" + + assert get_compressed_v4("0") == 0 + assert get_compressed_v4("0", False) == 0 + assert get_compressed_v4("0", True) == "0x0" + + assert get_compressed_v4("256.0.0.0") == -1 + assert get_compressed_v4("256.0.0.0", False) == -1 + assert get_compressed_v4("256.0.0.0", True) == "-0x1" + + assert get_compressed_v4("255.255.255.255") == 2**32 - 1 + assert get_compressed_v4("255.255.255.255", False) == 2**32 - 1 + assert get_compressed_v4("255.255.255.255", True) == hex(2**32 - 1) + + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("ip_v4_address", help="IPv4 address that shall be compressed") + parser.add_argument( + "--hex", dest="hex", help="convert into hexadecimal", action="store_true" + ) + parser.add_argument( + "--quiet", + dest="quiet", + help="be quiet do not print the greeter", + action="store_true", + ) + args = parser.parse_args() + out_hex: bool = True if args.hex else False + + if not args.quiet: + # Greet the user. + print("~" * 65) + print(f" v4 address fun, v{__version__}.") + print(f" by {__author__}") + print(f" use according to {__copyright__} license instructions") + print("~" * 65) + + if args.ip_v4_address: + print(get_compressed_v4(args.ip_v4_address, out_hex)) + +# vim:syntax=python:fileencoding=utf-8:ts=4:expandtab:linebreak:wrap