fatedier/frp · error

resolve local udp addr error: %v

Error message

resolve local udp addr error: %v

What it means

Returned by nathole.Prepare (pkg/nathole/nathole.go) when net.ResolveUDPAddr("udp4", localAddr.String()) fails. Prepare re-resolves the local address discovered during STUN binding as an explicit udp4 address before listening on it; failure means the discovered local address string cannot be parsed as an IPv4 UDP endpoint.

Source

Thrown at pkg/nathole/nathole.go:136

func Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult, error) {
	// discover for Nat type
	addrs, localAddr, err := Discover(stunServers, "")
	if err != nil {
		return nil, fmt.Errorf("discover error: %v", err)
	}
	if len(addrs) < 2 {
		return nil, fmt.Errorf("discover error: not enough addresses")
	}

	localIPs, _ := ListLocalIPsForNatHole(10)
	natFeature, err := ClassifyNATFeature(addrs, localIPs)
	if err != nil {
		return nil, fmt.Errorf("classify nat feature error: %v", err)
	}

	laddr, err := net.ResolveUDPAddr("udp4", localAddr.String())
	if err != nil {
		return nil, fmt.Errorf("resolve local udp addr error: %v", err)
	}
	listenConn, err := net.ListenUDP("udp4", laddr)
	if err != nil {
		return nil, fmt.Errorf("listen local udp addr error: %v", err)
	}

	// Apply NAT traversal options
	var assistedAddrs []string
	if !opts.DisableAssistedAddrs {
		assistedAddrs = make([]string, 0, len(localIPs))
		for _, ip := range localIPs {
			assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
		}
	}
	return &PrepareResult{
		Addrs:         addrs,
		AssistedAddrs: assistedAddrs,
		ListenConn:    listenConn,

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Force IPv4 for STUN discovery — use IPv4 literals or hostnames that resolve A records for the STUN servers
  2. Disable IPv6 on the socket/host if the environment prefers v6 unexpectedly
  3. Check the wrapped error text; anything other than address-family mismatch is a genuine malformed address and worth reporting

Example fix

# before
natHoleSTUNServer = ["stun.example.com:3478"]  # may resolve to IPv6

# after
natHoleSTUNServer = ["203.0.113.20:3478"]  # IPv4 literal forces udp4 path
Defensive patterns

Strategy: validation

Validate before calling

if localAddr != nil && localAddr.IP.To4() == nil {
    // discovery happened over IPv6; force IPv4 STUN endpoints before Prepare
}

Try / catch

if err != nil && strings.Contains(err.Error(), "resolve local udp addr error") {
    // switch STUN config to IPv4 literals/endpoints and retry Prepare
}

Prevention

When it happens

Trigger: nathole.Prepare where localAddr (the local socket address from discovery) stringifies into something unresolvable as udp4 — e.g. an IPv6 local address on a dual-stack socket, since the network is hard-coded to "udp4" here.

Common situations: Host whose STUN exchange ran over IPv6 (STUN server resolved to an AAAA record) so the local endpoint is IPv6 while Prepare demands udp4; unusual local address formats from the OS.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/7a54cc66fd679db7. Report an issue: GitHub.