IP Address Parsing and Validation Edge Cases in the geoip Library
The geoip library handles malformed inputs, IPv4-in-IPv6 mappings, comment lines, and invalid CIDR ranges by centralizing all parsing in lib/entry.go's processPrefix method while using lookup.go's isValidIPOrCIDR for CLI pre-validation.
The loyalsoldier/geoip library provides robust IP address parsing and validation for GeoIP data processing. Its architecture anticipates ambiguous inputs by normalizing all addresses into canonical netip.Prefix structures while detecting edge cases like IPv4-mapped IPv6 addresses, malformed CIDR notation, and empty or commented lines.
Core Parsing Logic in lib/entry.go
The processPrefix method in lib/entry.go serves as the central entry point for all IP and CIDR normalization. This function processes raw input strings and converts them into standardized netip.Prefix objects with explicit IPv4 or IPv6 family detection, handling multiple edge cases through dedicated branches.
Empty Lines and Comment Handling
Before attempting numeric parsing, the library strips trailing comments and detects empty inputs. When a line contains only whitespace or comment markers, processPrefix returns ErrCommentLine (defined in lib/common.go), signaling the container builder to skip the entry rather than fail.
// From lib/entry.go lines 95-98
return nil, "", ErrCommentLine
This handles inputs like # comment or // comment by stripping delimiters first, then checking if the remaining string is empty.
Plain IPv4 and IPv6 Addresses
For inputs without CIDR notation, the library uses netip.ParseAddr to validate the string. IPv4 addresses automatically receive a /32 prefix, while IPv6 addresses receive a /128 prefix (lines 28-42). Both undergo unmapping to ensure canonical representation and prevent ambiguity in later processing stages.
CIDR Notation and IPv4-in-IPv6 Mapping
The most complex edge case involves IPv4-in-IPv6 prefixes such as ::ffff:192.0.2.0/120. When processPrefix detects ip.Is4In6() (lines 33-49), it unmaps the address and adjusts the prefix length by subtracting 96 bits, converting it to a pure IPv4 prefix (e.g., 192.0.2.0/24).
Standard CIDR strings use net.ParseCIDR with conversion to netip.Addr via go4.org/netipx. The library explicitly checks for illegal IPv4-mapped-IPv6 strings in this branch (lines 99-115) to prevent mixing address families in the output database.
Invalid Input Detection
The library returns specific errors for various failure modes:
- ErrInvalidIPLength: Triggered when an address length is neither 4 nor 6 bytes after parsing (lines 85-87)
- ErrInvalidPrefix: Returned when prefix bits exceed the maximum for the address family (e.g.,
/33for IPv4) duringip.Prefix(bits)creation (lines 33-55) - ErrInvalidPrefixType: Falls through when the input type is unsupported, such as malformed
net.IPNetstructures or invalid pointer types (lines 144-147)
These error constants defined in lib/common.go allow upstream callers to distinguish between recoverable warnings and critical parsing failures.
CLI Input Validation
The lookup command uses isValidIPOrCIDR in lookup.go (lines 44-60) to pre-validate user queries before invoking heavy Entry processing. This lightweight validator distinguishes between CIDR and plain IP strings using strings.Contains(search, "/"), then applies netip.ParsePrefix or netip.ParseAddr accordingly.
func isValidIPOrCIDR(search string) bool {
if search == "" {
return false
}
var err error
switch strings.Contains(search, "/") {
case true:
_, err = netip.ParsePrefix(search)
case false:
_, err = netip.ParseAddr(search)
}
return err == nil
}
This prevents malformed inputs like empty strings or invalid octets from reaching the database lookup stage, improving responsiveness in both single-shot and REPL modes.
Practical Usage Examples
Normalizing Mixed Address Formats
The AddPrefix method accepts various input types and normalizes them through processPrefix:
package main
import (
"fmt"
"github.com/Loyalsoldier/geoip/lib"
)
func main() {
e := lib.NewEntry("MIXED")
// Plain IPv4 becomes 1.2.3.4/32
_ = e.AddPrefix("1.2.3.4")
// IPv4-in-IPv6 becomes 192.0.2.0/24
_ = e.AddPrefix("::ffff:192.0.2.0/120")
// IPv6 CIDR preserved as fd00::/64
_ = e.AddPrefix("fd00::/64")
list, _ := e.MarshalText()
fmt.Println(list)
}
The output contains canonical prefixes: ["1.2.3.4/32" "192.0.2.0/24" "fd00::/64"]. Note how the IPv4-in-IPv6 input is automatically transformed into standard IPv4 CIDR notation.
CLI Validation Behavior
$ geoip lookup -f text -u ./mylist.txt 1.2.3.4
true
$ geoip lookup -f text -u ./mylist.txt invalid_address
false
The second command returns false immediately because isValidIPOrCIDR rejects the malformed input before any database operations occur.
Summary
- The
processPrefixmethod inlib/entry.gocentralizes all IP address parsing and validation edge cases into a single canonical representation - IPv4-in-IPv6 prefixes are automatically unmapped and converted to pure IPv4 CIDR notation by subtracting 96 bits from the prefix length
- Empty lines and comments return
ErrCommentLinefor silent skipping without interrupting batch processing - Invalid IP lengths trigger
ErrInvalidIPLength, while malformed CIDRs generateErrInvalidPrefix, both defined inlib/common.go - The CLI's
isValidIPOrCIDRfunction inlookup.goprovides early validation to prevent processing invalid queries against the IP database
Frequently Asked Questions
How does the geoip library handle IPv4 addresses embedded in IPv6 notation?
When processPrefix encounters an address where ip.Is4In6() returns true, it unmaps the IPv4-mapped IPv6 address and subtracts 96 from the prefix length. This converts inputs like ::ffff:192.0.2.0/120 into standard IPv4 CIDR 192.0.2.0/24 as implemented in lib/entry.go lines 33-49.
What error does the library return for empty or comment-only lines?
The library returns ErrCommentLine (defined in lib/common.go) when processing lines that contain only whitespace or comment markers after stripping trailing comments. This allows bulk parsers in lib/entry.go to skip these lines without terminating the entire operation.
How does the CLI validate user input before processing?
The lookup command in lookup.go uses the isValidIPOrCIDR helper to validate strings before invoking Entry methods. It checks for empty input and uses netip.ParseAddr or netip.ParsePrefix to ensure only valid IP addresses or CIDR ranges proceed to the lookup stage, preventing malformed queries from reaching the core engine.
What happens when an IP address has an invalid byte length?
If an address does not satisfy Is4() or Is6() checks after parsing, processPrefix returns ErrInvalidIPLength. This catches malformed addresses that survive initial string parsing but fail byte-length validation at lines 85-87 of lib/entry.go.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →