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.

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.

Appendix: 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)