snail007/goproxy · error

dead loop detected , %s

Error message

dead loop detected , %s

What it means

OutToTCP forwards an incoming HTTP connection to a TCP parent (optionally through a proxy). Before connecting it calls IsDeadLoop(inLocalAddr, req.Host): if the local address of the outbound path equals the host being requested, the service would connect back to itself and loop forever, so it closes the incoming connection and returns this error. It is a self-protection mechanism against recursive proxying.

Source

Thrown at services/http.go:115

	log.Printf("use proxy : %v, %s", useProxy, address)
	//os.Exit(0)
	err = s.OutToTCP(useProxy, address, &inConn, &req)
	if err != nil {
		if *s.cfg.Parent == "" {
			log.Printf("connect to %s fail, ERR:%s", address, err)
		} else {
			log.Printf("connect to %s parent %s fail", *s.cfg.ParentType, *s.cfg.Parent)
		}
		utils.CloseConn(&inConn)
	}
}
func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *utils.HTTPRequest) (err error) {
	inAddr := (*inConn).RemoteAddr().String()
	inLocalAddr := (*inConn).LocalAddr().String()
	//防止死循环
	if s.IsDeadLoop(inLocalAddr, req.Host) {
		utils.CloseConn(inConn)
		err = fmt.Errorf("dead loop detected , %s", req.Host)
		return
	}
	var outConn net.Conn
	var _outConn interface{}
	if useProxy {
		_outConn, err = s.outPool.Pool.Get()
		if err == nil {
			outConn = _outConn.(net.Conn)
		}
	} else {
		outConn, err = utils.ConnectHost(address, *s.cfg.Timeout)
	}
	if err != nil {
		log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
		utils.CloseConn(inConn)
		return
	}

View on GitHub (pinned to e6d6a821db)

Solutions

  1. Fix the parent/proxy config so it points to a genuinely different upstream host:port, not this service's own address
  2. Trace the proxy chain and break any cycle (A->B->A) by pointing one hop at the real destination
  3. Check DNS/hosts entries that cause the parent hostname to resolve to the local machine
  4. If a loop is intentional for testing, bind the service and the parent to clearly distinct addresses/ports

Example fix

// before (nps.conf points parent at itself -> loop)
// http_proxy_ip=127.0.0.1
// http_proxy_port=8024  (same port this service listens on)
// after: point parent at the real upstream
// http_proxy_ip=upstream.example.com
// http_proxy_port=8080
Defensive patterns

Strategy: validation

Validate before calling

func isSelfReference(parentHost string, parentPort, listenPort int) bool {
    addrs, _ := net.LookupHost(parentHost)
    locals, _ := net.InterfaceAddrs() // or simply compare to 127.0.0.1/hostname
    for _, a := range addrs {
        for _, l := range locals {
            if ipnet, ok := l.(*net.IPNet); ok && ipnet.IP.String() == a && parentPort == listenPort {
                return true
            }
        }
    }
    return false
}
// refuse to start if parent resolves to the service's own listen address

Try / catch

if err := svc.Run(); err != nil {
    if strings.Contains(err.Error(), "dead loop detected") {
        log.Fatalf("parent %q points back at this service; fix parent/proxy config", cfg.Parent)
    }
}

Prevention

When it happens

Trigger: The configured parent/proxy address resolves to the same host:port the service itself is listening on, so a request to req.Host would be forwarded back into this very service; IsDeadLoop detects the match and OutToTCP aborts the forwarding.

Common situations: Parent configured as localhost/127.0.0.1 with the same port as this proxy; DNS or /etc/hosts entry making the parent resolve back to the service itself; chained proxies configured in a cycle (A->B->A); port-forwarding rule looping traffic back.

Related errors


AI-assisted analysis of snail007/goproxy@e6d6a821db (2026-09-03). Data as JSON: /api/errors/985f4fb59d6638e0. Report an issue: GitHub.