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

  1. Pass exactly "6" or "4" (lowercase, no whitespace) as the v46 argument.
  2. Normalize the value before calling: strings.TrimSpace(strings.ToLower(v)) and map aliases like "ipv6" to "6".
  3. Validate the configured value at startup and fail with a clear config error instead of reaching this sentinel at dial time.
  4. 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

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


AI-assisted analysis of txthinking/brook@5cd13ef3b1 (2026-09-06). Data as JSON: /api/errors/bcfb85a5d61edb55. Report an issue: GitHub.