Katkat

Implement IP Address Arithmetic

OpenAISoftware Engineer · Onsite

Last updated

Implement parsing, binary representation, arithmetic offsets, and canonical formatting for both IPv4 and IPv6 addresses and CIDR subnets.

Problem Statement

Design an IP arithmetic utility that parses, manipulates, and formats both IPv4 and IPv6 addresses and CIDR blocks without using built-in networking libraries (e.g., ipaddress, socket.inet_ntop).

Your utility must support the following methods:

  1. add_offset(ip: str, offset: int) -> str: Parses the given IP string (IPv4 or IPv6), adds an integer offset (which can be positive or negative), and returns the resulting IP address formatted canonically. If the resulting IP falls outside the valid range for its address family ([0, 2^32 - 1] for IPv4, [0, 2^128 - 1] for IPv6), raise a ValueError.
  2. cidr_info(cidr: str) -> tuple[str, str, int]: Parses a CIDR block (e.g., "192.168.1.0/24" or "2001:db8::/120") and returns a tuple (network_address, broadcast_address, total_hosts) where all addresses are in their canonical string representations and total_hosts is the total count of addresses in the block (2^(bits - prefix)).
  3. contains(cidr: str, ip: str) -> bool: Returns True if the specified IP address belongs to the CIDR block, and False otherwise. If the IP version of ip does not match the CIDR block's version, raise a ValueError.

Formatting Requirements

  • IPv4: Standard dotted-quad notation with four decimal integers in [0, 255] separated by dots (e.g., "192.168.1.1"). Leading zeros in octets are not allowed.
  • IPv6: Standard RFC 5952 canonical format:
    • Lowercase hexadecimal characters.
    • Leading zeros within a 16-bit hextet must be suppressed (e.g., :0042: becomes :42:).
    • The longest run of two or more consecutive all-zero hextets must be replaced with ::. If there is a tie for the longest run, shorten the first one.
    • Single zero hextets must not be compressed as :: (e.g., "2001:db8:0:1:1:1:1:1").

Function Interface

class IPArithmetic:
    def add_offset(self, ip: str, offset: int) -> str:
        pass

    def cidr_info(self, cidr: str) -> tuple[str, str, int]:
        pass

    def contains(self, cidr: str, ip: str) -> bool:
        pass

Examples

Example 1

ipa = IPArithmetic()
ipa.add_offset("192.168.1.254", 2)
# Returns: "192.168.2.0"

ipa.cidr_info("192.168.1.0/30")
# Returns: ("192.168.1.0", "192.168.1.3", 4)

ipa.contains("192.168.1.0/24", "192.168.1.42")
# Returns: True

Example 2

ipa = IPArithmetic()
ipa.add_offset("2001:db8::", 1)
# Returns: "2001:db8::1"

ipa.cidr_info("2001:db8::/126")
# Returns: ("2001:db8::", "2001:db8::3", 4)

ipa.add_offset("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 1)
# Raises: ValueError (overflow)

Constraints

  • IPv4 prefix lengths: 0 <= prefix <= 32
  • IPv6 prefix lengths: 0 <= prefix <= 128
  • -2^128 <= offset <= 2^128
  • Built-in networking packages (ipaddress, socket, urllib) are strictly prohibited.

Hints

Hint 1

Convert both IPv4 and IPv6 addresses into raw unsigned integers (32-bit or 128-bit) before performing arithmetic or bitwise masking.

Hint 2

When expanding an IPv6 string containing `::`, determine how many 16-bit hextets are present on each side of the double-colon to fill in the missing zero hextets.

Hint 3

For RFC 5952 IPv6 compression, find the contiguous sub-array of zeros with the maximum length (must be >= 2) and replace only the first occurrence of that maximal run with `::`.

Follow-up questions

  • How would you handle mixed IPv4-mapped IPv6 addresses (e.g., `::ffff:192.0.2.128`)?
  • How can you optimize subnet-membership queries if you need to match millions of incoming IP addresses against thousands of CIDR blocks in real time?
  • How would you implement CIDR aggregation (route summarization) given a list of arbitrary, overlapping IPv4 or IPv6 subnets?