Skip to content

[BUG] Duplicating HTTP/2 SETTINGS ID vs Golang server (x/net/http2, Caddy) leads to conn timeout and behave wild on server side #428

@cowsay

Description

@cowsay

Client:

func main() {
	session := azuretls.NewSession()
	defer session.Close()
    session.SetTimeout(3 * time.Second)

	// dupe settings: 6:262144,6:262144
	if err := session.ApplyHTTP2("1:65536,2:0,3:1000,4:6291456,6:262144,6:262144|15663105|0|m,s,a,p"); err != nil {
		panic(err)
	}

	resp, err := session.Do(&azuretls.Request{
		Method: "GET",
		Url: "https://localhost:8080",
	})
	if err != nil {
		fmt.Printf("Request error: %v\n", err)
		return
	}
	fmt.Println("Proto:", resp.HttpResponse.Proto)
}

Server:

func main() {
	// Create a simple HTTP handler
	handler := func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!\n"))
	}

	// Set up the server
	server := &http.Server{
		Addr:    ":8080",
		Handler: http.HandlerFunc(handler),
	}

	// Enable HTTP/2
	http2.ConfigureServer(server, nil)

	log.Println("Starting server on https://localhost:8080")
	err := server.ListenAndServeTLS("cert.pem", "key.pem")
	if err != nil {
		log.Fatal(err)
	}
}

Result on Client side:

Request error: timeout

Result on Server side:
Multiple errors flood like that (~150 lines/sec on localhost):

2025/11/13 10:01:58 http2: server connection error from 127.0.0.1:45838: connection error: PROTOCOL_ERROR
2025/11/13 10:01:58 http2: server connection error from 127.0.0.1:45850: connection error: PROTOCOL_ERROR
2025/11/13 10:01:58 http2: server connection error from 127.0.0.1:45858: connection error: PROTOCOL_ERROR
2025/11/13 10:01:58 http2: server connection error from 127.0.0.1:45874: connection error: PROTOCOL_ERROR
2025/11/13 10:01:58 http2: server connection error from 127.0.0.1:45876: connection error: PROTOCOL_ERROR

Same can be tested vs https://caddyserver.com/, it will hang until timeout.

Quickly observing, this PROTOCOL_ERROR coming from f.HasDuplicates() checking server.go#L1766, and then leads to server.go#L1560, which should send GoAway:

	case ConnectionError:
		if res.f != nil {
			if id := res.f.Header().StreamID; id > sc.maxClientStreamID {
				sc.maxClientStreamID = id
			}
		}
		sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev)
		sc.goAway(ErrCode(ev))
		return true // goAway will handle shutdown

So I also capture connection to caddyserver.com and it looks like:
Image
It is send GOAWAY's but looks like the client ignores it and tries to reconnect or something like that.

I guess the easiest fix will be to not allow SETTINGS ID duplication, but the https://tls.peet.ws/api/all is actually works with that and show two identical SETTINGS. Or client should respect GOAWAY's.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions