txthinking/brook · error
black hole
Error message
black hole
What it means
This is the fallthrough at the end of the IP method: if v46 is neither "6" nor "4", none of the address-scanning branches run and the method returns this error. It indicates an invalid v46 argument value — the caller asked for neither IPv4 nor IPv6, so the library has no address-selection strategy and returns the sentinel 'black hole'.
Source
Thrown at plugins/dialwithnic/dialwithnic.go:64
return v.(*net.IPNet).IP, nil
}
}
return nil, errors.New("no ipv6 from nic")
}
if v46 == "4" {
for _, v := range addrs {
if v.(*net.IPNet).IP.IsGlobalUnicast() && !v.(*net.IPNet).IP.IsPrivate() && v.(*net.IPNet).IP.To4() != nil {
return v.(*net.IPNet).IP, nil
}
}
for _, v := range addrs {
if v.(*net.IPNet).IP.IsGlobalUnicast() && v.(*net.IPNet).IP.IsPrivate() && v.(*net.IPNet).IP.To4() != nil {
return v.(*net.IPNet).IP, nil
}
}
return nil, errors.New("no ipv4 from nic")
}
return nil, errors.New("black hole")
}
func (p *DialWithNIC) TouchBrook() {
brook.DialTCP = func(network string, laddr, raddr string) (net.Conn, error) {
var la, ra *net.TCPAddr
if laddr != "" {
var err error
la, err = net.ResolveTCPAddr(network, laddr)
if err != nil {
return nil, err
}
}
a, err := brook.Resolve(network, raddr)
if err != nil {
return nil, err
}
ra = a.(*net.TCPAddr)
if la == nil {View on GitHub (pinned to 5cd13ef3b1)
Solutions
- Pass exactly "6" or "4" (lowercase, no whitespace) as the v46 argument.
- Normalize the value before calling: strings.TrimSpace(strings.ToLower(v)) and map aliases like "ipv6" to "6".
- Validate the configured value at startup and fail with a clear config error instead of reaching this sentinel at dial time.
- If the value is optional in your config, default it explicitly to "4" or "6" before invoking the plugin.
Example fix
// before
ip, err := plugin.IP(cfg.Family) // cfg.Family == "IPv6"
// after
v46 := map[string]string{"ipv6": "6", "ipv4": "4", "6": "6", "4": "4"}[strings.ToLower(strings.TrimSpace(cfg.Family))]
ip, err := plugin.IP(v46) Defensive patterns
Strategy: validation
Validate before calling
func normalizeFamily(v string) (string, error) {
switch strings.ToLower(strings.TrimSpace(v)) {
case "4", "ipv4":
return "4", nil
case "6", "ipv6":
return "6", nil
default:
return "", fmt.Errorf("invalid family %q: must be 4 or 6", v)
}
} Type guard
func isKnownFamily(v string) bool {
return v == "4" || v == "6"
} Try / catch
ip, err := plugin.IP(v46)
if err != nil && err.Error() == "black hole" {
return fmt.Errorf("invalid family %q: must be exactly \"4\" or \"6\"", v46)
} Prevention
- Pass only the exact lowercase strings "4" or "6"; the check is case- and whitespace-sensitive.
- Normalize config/env values (trim, lowercase, map aliases like ipv4/ipv6) before calling.
- Validate family values at startup so bad config fails loudly before dial time.
- Always set an explicit default for optional family settings instead of passing empty strings.
When it happens
Trigger: Calling IP with any v46 value other than the exact strings "6" or "4" — e.g. "", "v6", "ipv6", "IPv4", or a value read from config that was not normalized. Note it is case-sensitive: "V6"/"V4" also land here.
Common situations: v46 read from a config file or environment variable with unexpected casing or whitespace; an unset/empty config value passed straight through; typos like "ip6"; refactoring that changed the expected enum values.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid dial with IP
- no ipv6 from nic
- no ipv4 from nic
- socks5 server requires a clear IP for UDP, only port is not
- Looks like you want create socks5 from a socks5, you may wan
AI-assisted analysis of txthinking/brook@5cd13ef3b1 (2026-09-06).
Data as JSON: /api/errors/bcfb85a5d61edb55.
Report an issue: GitHub.