lionsoul2014/ip2region · error · ValueError

invalid ip address `{}` ({} expected)

Error message

invalid ip address `{}` ({} expected)

What it means

search() also validates the IP version: if the parsed bytes' length (4 for IPv4, 16 for IPv6) differs from the xdb structure's byte_num, the address cannot be looked up in this file and ValueError is raised, naming the IP and the expected version.

Source

Thrown at binding/python/ip2region/searcher.py:49

    def get_ip_version(self):
        return self.version

    def get_io_count(self):
        return self.__io_count

    def search(self, ip: Union[bytes, str]):
        # check and parse the string ip
        ip_bytes = None
        if isinstance(ip, str):
            ip_bytes = util.parse_ip(ip)
        elif isinstance(ip, bytes):
            ip_bytes = ip
        else:
            raise ValueError("invalid ip address `{}`".format(ip))

        # ip version check
        if len(ip_bytes) != self.version.byte_num:
            raise ValueError("invalid ip address `{}` ({} expected)".format(
                util.ip_to_string(ip_bytes), self.version.name))

        # reset the global io_count
        self.__io_count = 0

        # located the segment index block based on the vector index
        s_ptr, e_ptr, i0, i1 = 0, 0, ip_bytes[0], ip_bytes[1]
        idx = i0 * util.VectorIndexCols * util.VectorIndexSize + i1 * util.VectorIndexSize
        if self.vector_index != None:
            s_ptr = util.le_get_uint32(self.vector_index, idx)
            e_ptr = util.le_get_uint32(self.vector_index, idx + 4)
        elif self.c_buffer != None:
            offset = util.HeaderInfoLength + idx
            s_ptr = util.le_get_uint32(self.c_buffer, offset)
            e_ptr = util.le_get_uint32(self.c_buffer, offset + 4)
        else:
            buff = self.read(util.HeaderInfoLength + idx, util.VectorIndexSize)
            s_ptr = util.le_get_uint32(buff, 0)

View on GitHub (pinned to c1a1fc7d59)

Solutions

  1. Load an xdb file whose IP version matches your addresses (v3/IPv6-capable file for IPv6 lookups)
  2. Check the address family with util.ip_to_string/ipaddress before searching and route v4/v6 to the right searcher
  3. When passing raw bytes, ensure exactly 4 bytes (v4) or 16 bytes (v6)

Example fix

# before
searcher = Searcher.new_with_file_only(dbfile='ip2region.xdb')  # v4 file
searcher.search('::ffff:1.2.3.4')
# after
searcher6 = Searcher.new_with_file_only(dbfile='ip2region_v6.xdb')
searcher6.search('::ffff:1.2.3.4')
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress
def pick_searcher(ip: str):
    version = ipaddress.ip_address(ip).version
    return searcher_v6 if version == 6 else searcher_v4

Type guard

def matches_searcher_version(ip_bytes: bytes, searcher) -> bool:
    return len(ip_bytes) == searcher.version.byte_num

Try / catch

try:
    region = searcher.search(ip)
except ValueError as e:
    if '{} expected'.format(searcher.version.name) in str(e):
        region = None  # wrong family for this xdb
    else:
        raise

Prevention

When it happens

Trigger: Searching an IPv6 address (16 bytes) against an IPv4 (v2 structure) xdb, or an IPv4 address against an IPv6/v3 xdb; passing bytes of the wrong length directly.

Common situations: App migrated to IPv6 traffic but still ships the old IPv4-only xdb; loading the wrong file for the searcher version; manually constructing bytes with the wrong length.

Related errors


AI-assisted analysis of lionsoul2014/ip2region@c1a1fc7d59 (2026-09-02). Data as JSON: /api/errors/5dfbc0869039d945. Report an issue: GitHub.